mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-29 04:30:47 +00:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a76fe94617 | ||
|
|
a60c659cd4 | ||
|
|
697952eded | ||
|
|
d240f6595d | ||
|
|
fc23a426a2 | ||
|
|
d70e71dd68 | ||
|
|
f2089118cf | ||
|
|
c9a065c118 | ||
|
|
6968913e76 | ||
|
|
b8690eb8e8 | ||
|
|
4aebae6132 | ||
|
|
06d2680438 | ||
|
|
ac53ed5653 | ||
|
|
d828eaa074 | ||
|
|
20fccaba16 | ||
|
|
a1330cf31e | ||
|
|
bf642b61ea | ||
|
|
ad116cbeac | ||
|
|
dbffb536d2 | ||
|
|
3955cd96d1 | ||
|
|
689f8564b8 | ||
|
|
4ec4eaa8ab | ||
|
|
86de831062 | ||
|
|
db1d5256af | ||
|
|
7f64190100 | ||
|
|
8b1cc1030e | ||
|
|
e947bac0e7 | ||
|
|
96061f14c5 | ||
|
|
0d7a9c27e5 | ||
|
|
b30e3f3b5d | ||
|
|
495684dfee | ||
|
|
49287ef857 | ||
|
|
fc9f849439 | ||
|
|
32e0de5a70 | ||
|
|
4749a1785f | ||
|
|
c012d90ff6 | ||
|
|
11ab6fdda1 | ||
|
|
4592a20c89 | ||
|
|
c69bad6fec | ||
|
|
da8e7c7b94 | ||
|
|
30df2811bf | ||
|
|
6bcb458eb8 | ||
|
|
7f1e323ff0 | ||
|
|
b5fab46f5f | ||
|
|
514599390f | ||
|
|
448482e499 | ||
|
|
73a991afa8 | ||
|
|
f76393890c | ||
|
|
8315e8f69d | ||
|
|
22c1b6130a | ||
|
|
a37569dfb2 | ||
|
|
08926aa2f1 | ||
|
|
b2eabe93d9 | ||
|
|
bbd9ab89d3 | ||
|
|
4050e6eb60 | ||
|
|
4824386e88 | ||
|
|
82501413a9 | ||
|
|
64b379f4a2 | ||
|
|
82fb45bd0e | ||
|
|
6082ee8644 | ||
|
|
e94162ae1f | ||
|
|
4faabe2429 | ||
|
|
24732a6f61 | ||
|
|
7d87d0f4a6 | ||
|
|
7f406eb0a7 | ||
|
|
f4a21ce20c | ||
|
|
c1a1fef7d8 | ||
|
|
3a9fa2456b | ||
|
|
e482295b13 | ||
|
|
58691180d0 | ||
|
|
3a836c228c | ||
|
|
8dd75d5313 | ||
|
|
f9b5e40fb3 | ||
|
|
f6fb1a27a5 | ||
|
|
34a5999711 | ||
|
|
e219c1eac1 | ||
|
|
a93262aa9d | ||
|
|
740cc5e97c | ||
|
|
2532d96ca6 | ||
|
|
6b4550b86b | ||
|
|
b7d12053b1 | ||
|
|
fefd764325 | ||
|
|
c7733e1dd9 | ||
|
|
f3ae19f4af | ||
|
|
72862d8ff0 | ||
|
|
3633a67661 | ||
|
|
7a0a6c6b6f | ||
|
|
88e22bfe77 | ||
|
|
73f88a069d | ||
|
|
6eb9b888f1 | ||
|
|
8a7c22b9d5 | ||
|
|
c6168413b4 | ||
|
|
b1f7912a21 | ||
|
|
f985159fe4 | ||
|
|
bab57b95ef | ||
|
|
903fb677c0 | ||
|
|
8a03a53629 | ||
|
|
42e5a99225 | ||
|
|
da16086119 | ||
|
|
cc6df1082e |
2
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -43,7 +43,7 @@ Feel free to remove our notice and fill the template below with your details.
|
||||
```
|
||||
Paste here, in-between triple backtick tags
|
||||
|
||||
Ensure that your log is complete and was NOT recorded in Debug mode, as debug log may contain sensitive information that should not be shared publicly, as per our the wiki. Standard ASF log does not include sensitive information.
|
||||
Ensure that your log is complete and was NOT recorded in Debug mode, as debug log may contain sensitive information that should not be shared publicly, as per our wiki statement. Standard ASF log does not include sensitive information.
|
||||
```
|
||||
|
||||
### Global ASF.json config (if using one)
|
||||
|
||||
Submodule ASF-WebConfigGenerator updated: 85eac6c8d1...23a7511af3
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: f48de0c250...0c3584253d
@@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0">
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Composition.AttributedModel" Version="*" />
|
||||
|
||||
@@ -153,8 +153,8 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
|
||||
// This is the earliest method that will be called, right after loading the plugin, long before any bot initialization takes place
|
||||
// It's a good place to initialize all potential (non-bot-specific) structures that you will need across lifetime of your plugin, such as global timers, concurrent dictionaries and alike
|
||||
// If you do not have any global structures to initialize, you can leave this function empty
|
||||
// At this point you can access core ASF's functionality, such as logging or a web browser
|
||||
// Once all plugins execute their OnLoaded() methods, OnASFInit() will be called next
|
||||
// At this point you can access core ASF's functionality, such as logging, but more advanced structures (like ASF's WebBrowser) will be available in OnASFInit(), which itself takes place after every plugin gets OnLoaded()
|
||||
// Typically you should use this function only for preparing core structures of your plugin, and optionally also sending a message to the user (e.g. support link, welcome message or similar), ASF-specific things should usually happen in OnASFInit()
|
||||
public void OnLoaded() {
|
||||
ASF.ArchiLogger.LogGenericInfo("Hey! Thanks for checking if our example plugin works fine, this is a confirmation that indeed " + nameof(OnLoaded) + "() method was called!");
|
||||
ASF.ArchiLogger.LogGenericInfo("Good luck in whatever you're doing!");
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0">
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -92,14 +92,14 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal static async Task Init() {
|
||||
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
|
||||
|
||||
await UpdateAndRestart().ConfigureAwait(false);
|
||||
|
||||
if (!PluginsCore.InitPlugins()) {
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
|
||||
|
||||
await UpdateAndRestart().ConfigureAwait(false);
|
||||
|
||||
await PluginsCore.OnASFInitModules(GlobalConfig.AdditionalProperties).ConfigureAwait(false);
|
||||
|
||||
StringComparer botsComparer = await PluginsCore.GetBotsComparer().ConfigureAwait(false);
|
||||
@@ -346,12 +346,7 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (botName) {
|
||||
case SharedInfo.ASF:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return !botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static async void OnChanged(object sender, FileSystemEventArgs e) {
|
||||
@@ -426,7 +421,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (botName.Equals(SharedInfo.ASF)) {
|
||||
if (botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase)) {
|
||||
ArchiLogger.LogGenericInfo(Strings.GlobalConfigChanged);
|
||||
await RestartOrExit().ConfigureAwait(false);
|
||||
|
||||
@@ -520,7 +515,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (botName.Equals(SharedInfo.ASF)) {
|
||||
if (botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase)) {
|
||||
if (File.Exists(fullPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,22 +157,42 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (resumeInSeconds > 0) {
|
||||
if (CardsFarmerResumeTimer != null) {
|
||||
CardsFarmerResumeTimer.Dispose();
|
||||
CardsFarmerResumeTimer = null;
|
||||
if (CardsFarmerResumeTimer == null) {
|
||||
CardsFarmerResumeTimer = new Timer(
|
||||
e => Resume(),
|
||||
null,
|
||||
TimeSpan.FromSeconds(resumeInSeconds), // Delay
|
||||
Timeout.InfiniteTimeSpan // Period
|
||||
);
|
||||
} else {
|
||||
CardsFarmerResumeTimer.Change(TimeSpan.FromSeconds(resumeInSeconds), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
CardsFarmerResumeTimer = new Timer(
|
||||
e => Resume(),
|
||||
null,
|
||||
TimeSpan.FromSeconds(resumeInSeconds), // Delay
|
||||
Timeout.InfiniteTimeSpan // Period
|
||||
);
|
||||
}
|
||||
|
||||
return (true, Strings.BotAutomaticIdlingNowPaused);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<(bool Success, string Message)> Play(IEnumerable<uint> gameIDs, string gameName = null) {
|
||||
if (gameIDs == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(gameIDs));
|
||||
|
||||
return (false, string.Format(Strings.ErrorObjectIsNull, nameof(gameIDs)));
|
||||
}
|
||||
|
||||
if (!Bot.IsConnectedAndLoggedOn) {
|
||||
return (false, Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
if (!Bot.CardsFarmer.Paused) {
|
||||
await Bot.CardsFarmer.Pause(true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Bot.ArchiHandler.PlayGames(gameIDs, gameName).ConfigureAwait(false);
|
||||
|
||||
return (true, Strings.Done);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<ArchiHandler.PurchaseResponseCallback> RedeemKey(string key) {
|
||||
await LimitGiftsRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>ASF.ico</ApplicationIcon>
|
||||
<AssemblyVersion>4.0.2.2</AssemblyVersion>
|
||||
<AssemblyVersion>4.0.3.0</AssemblyVersion>
|
||||
<Authors>JustArchi</Authors>
|
||||
<Company>JustArchi</Company>
|
||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||
@@ -11,7 +11,7 @@
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);config/**;debug/**;out/**;overlay/**</DefaultItemExcludes>
|
||||
<Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description>
|
||||
<ErrorReport>none</ErrorReport>
|
||||
<FileVersion>4.0.2.2</FileVersion>
|
||||
<FileVersion>4.0.3.0</FileVersion>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>1591</NoWarn>
|
||||
@@ -56,15 +56,16 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0">
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.7" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.8" />
|
||||
<PackageReference Include="Humanizer" Version="2.6.2" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2019.1.1" />
|
||||
<PackageReference Include="Markdig.Signed" Version="0.17.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
|
||||
<PackageReference Include="Markdig.Signed" Version="0.17.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Json" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
@@ -73,9 +74,9 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0-preview5.19227.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.0.0-preview5.19227.9" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="NLog" Version="4.6.4" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.2" />
|
||||
<PackageReference Include="SteamKit2" Version="2.2.0-Beta.3" />
|
||||
<PackageReference Include="NLog" Version="4.6.5" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.4" />
|
||||
<PackageReference Include="SteamKit2" Version="2.2.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.0.0-rc2" />
|
||||
<PackageReference Include="System.Composition" Version="1.3.0-preview5.19224.8" />
|
||||
|
||||
@@ -989,6 +989,50 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static async Task<T> WebLimitRequest<T>(string service, Func<Task<T>> function) {
|
||||
if (string.IsNullOrEmpty(service) || (function == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(service) + " || " + nameof(function));
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
if (ASF.GlobalConfig.WebLimiterDelay == 0) {
|
||||
return await function().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!WebLimitingSemaphores.TryGetValue(service, out (SemaphoreSlim RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore) limiters)) {
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(service), service));
|
||||
|
||||
if (!WebLimitingSemaphores.TryGetValue(nameof(ArchiWebHandler), out limiters)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(limiters));
|
||||
|
||||
return await function().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Sending a request opens a new connection
|
||||
await limiters.OpenConnectionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
// It also increases number of requests
|
||||
await limiters.RateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
// We release rate-limiter semaphore regardless of our task completion, since we use that one only to guarantee rate-limiting of their creation
|
||||
Utilities.InBackground(
|
||||
async () => {
|
||||
await Task.Delay(ASF.GlobalConfig.WebLimiterDelay).ConfigureAwait(false);
|
||||
limiters.RateLimitingSemaphore.Release();
|
||||
}
|
||||
);
|
||||
|
||||
return await function().ConfigureAwait(false);
|
||||
} finally {
|
||||
// We release open connections semaphore only once we're indeed done sending a particular request
|
||||
limiters.OpenConnectionsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptDigitalGiftCard(ulong giftCardID) {
|
||||
if (giftCardID == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(giftCardID));
|
||||
@@ -1906,7 +1950,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Generate login key from the user nonce that we've received from Steam network
|
||||
byte[] loginKey = Encoding.ASCII.GetBytes(webAPIUserNonce);
|
||||
byte[] loginKey = Encoding.UTF8.GetBytes(webAPIUserNonce);
|
||||
|
||||
// AES encrypt our login key with our session key
|
||||
byte[] encryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
|
||||
@@ -1975,6 +2019,12 @@ namespace ArchiSteamFarm {
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamStoreHost));
|
||||
|
||||
// Report proper time when doing timezone-based calculations, see setTimezoneCookies() from https://steamcommunity-a.akamaihd.net/public/shared/javascript/shared_global.js
|
||||
string timeZoneOffset = DateTimeOffset.Now.Offset.TotalSeconds + WebUtility.UrlEncode(",") + "0";
|
||||
|
||||
WebBrowser.CookieContainer.Add(new Cookie("timezoneOffset", timeZoneOffset, "/", "." + SteamCommunityHost));
|
||||
WebBrowser.CookieContainer.Add(new Cookie("timezoneOffset", timeZoneOffset, "/", "." + SteamStoreHost));
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.Success);
|
||||
|
||||
// Unlock Steam Parental if needed
|
||||
@@ -2637,49 +2687,6 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task<T> WebLimitRequest<T>(string service, Func<Task<T>> function) {
|
||||
if (string.IsNullOrEmpty(service) || (function == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(service) + " || " + nameof(function));
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
if (ASF.GlobalConfig.WebLimiterDelay == 0) {
|
||||
return await function().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!WebLimitingSemaphores.TryGetValue(service, out (SemaphoreSlim RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore) limiters)) {
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(service), service));
|
||||
|
||||
if (!WebLimitingSemaphores.TryGetValue(nameof(ArchiWebHandler), out limiters)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(limiters));
|
||||
|
||||
return await function().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Sending a request opens a new connection
|
||||
await limiters.OpenConnectionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
// It also increases number of requests
|
||||
await limiters.RateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
// We release rate-limiter semaphore regardless of our task completion, since we use that one only to guarantee rate-limiting of their creation
|
||||
Utilities.InBackground(
|
||||
async () => {
|
||||
await Task.Delay(ASF.GlobalConfig.WebLimiterDelay).ConfigureAwait(false);
|
||||
limiters.RateLimitingSemaphore.Release();
|
||||
}
|
||||
);
|
||||
|
||||
return await function().ConfigureAwait(false);
|
||||
} finally {
|
||||
// We release open connections semaphore only once we're indeed done sending a particular request
|
||||
limiters.OpenConnectionsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public enum ESession : byte {
|
||||
None,
|
||||
Lowercase,
|
||||
|
||||
@@ -65,8 +65,6 @@ namespace ArchiSteamFarm {
|
||||
private static readonly SemaphoreSlim LoginRateLimitingSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private static bool LoginRateLimited;
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public readonly Actions Actions;
|
||||
@@ -86,6 +84,10 @@ namespace ArchiSteamFarm {
|
||||
[PublicAPI]
|
||||
public readonly Commands Commands;
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public readonly SteamConfiguration SteamConfiguration;
|
||||
|
||||
[JsonProperty]
|
||||
public uint GamesToRedeemInBackgroundCount => BotDatabase?.GamesToRedeemInBackgroundCount ?? 0;
|
||||
|
||||
@@ -103,7 +105,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal readonly ConcurrentDictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)> OwnedPackageIDs = new ConcurrentDictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)>();
|
||||
internal readonly SteamApps SteamApps;
|
||||
internal readonly SteamConfiguration SteamConfiguration;
|
||||
internal readonly SteamFriends SteamFriends;
|
||||
|
||||
internal bool CanReceiveSteamCards => !IsAccountLimited && !IsAccountLocked;
|
||||
@@ -140,9 +141,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamID))]
|
||||
[NotNull]
|
||||
private string SSteamID => SteamID.ToString();
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
[JsonProperty]
|
||||
public EAccountFlags AccountFlags { get; private set; }
|
||||
@@ -170,8 +173,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private string AuthCode;
|
||||
|
||||
#pragma warning disable IDE0052
|
||||
[JsonProperty]
|
||||
private string AvatarHash;
|
||||
#pragma warning restore IDE0052
|
||||
|
||||
private Timer ConnectionFailureTimer;
|
||||
private string DeviceID;
|
||||
@@ -371,25 +376,27 @@ namespace ArchiSteamFarm {
|
||||
if (botName.StartsWith("r!", StringComparison.OrdinalIgnoreCase)) {
|
||||
string botsPattern = botName.Substring(2);
|
||||
|
||||
RegexOptions botsRegex = RegexOptions.None;
|
||||
|
||||
if ((BotsComparer == StringComparer.InvariantCulture) || (BotsComparer == StringComparer.Ordinal)) {
|
||||
botsRegex |= RegexOptions.CultureInvariant;
|
||||
} else if ((BotsComparer == StringComparer.InvariantCultureIgnoreCase) || (BotsComparer == StringComparer.OrdinalIgnoreCase)) {
|
||||
botsRegex |= RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
|
||||
}
|
||||
|
||||
Regex regex;
|
||||
|
||||
try {
|
||||
RegexOptions botsRegex = RegexOptions.None;
|
||||
|
||||
if ((BotsComparer == StringComparer.InvariantCulture) || (BotsComparer == StringComparer.Ordinal)) {
|
||||
botsRegex |= RegexOptions.CultureInvariant;
|
||||
} else if ((BotsComparer == StringComparer.InvariantCultureIgnoreCase) || (BotsComparer == StringComparer.OrdinalIgnoreCase)) {
|
||||
botsRegex |= RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
|
||||
}
|
||||
|
||||
Regex regex = new Regex(botsPattern, botsRegex);
|
||||
|
||||
IEnumerable<Bot> regexMatches = Bots.Where(kvp => regex.IsMatch(kvp.Key)).Select(kvp => kvp.Value);
|
||||
result.UnionWith(regexMatches);
|
||||
regex = new Regex(botsPattern, botsRegex);
|
||||
} catch (ArgumentException e) {
|
||||
ASF.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
IEnumerable<Bot> regexMatches = Bots.Where(kvp => regex.IsMatch(kvp.Key)).Select(kvp => kvp.Value);
|
||||
result.UnionWith(regexMatches);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -545,32 +552,30 @@ namespace ArchiSteamFarm {
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(appID, OwnedPackageIDs.Keys);
|
||||
|
||||
if ((packageIDs == null) || (packageIDs.Count == 0)) {
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
if ((hoursPlayed < CardsFarmer.HoursForRefund) && !BotConfig.IdleRefundableGames) {
|
||||
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(appID);
|
||||
DateTime mostRecent = DateTime.MinValue;
|
||||
|
||||
if (packageIDs == null) {
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
if (packageIDs.Count > 0) {
|
||||
DateTime mostRecent = DateTime.MinValue;
|
||||
|
||||
foreach (uint packageID in packageIDs) {
|
||||
if (!OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsRefundable(packageData.PaymentMethod) && (packageData.TimeCreated > mostRecent)) {
|
||||
mostRecent = packageData.TimeCreated;
|
||||
}
|
||||
foreach (uint packageID in packageIDs) {
|
||||
if (!OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mostRecent > DateTime.MinValue) {
|
||||
DateTime playableIn = mostRecent.AddDays(CardsFarmer.DaysForRefund);
|
||||
if (IsRefundable(packageData.PaymentMethod) && (packageData.TimeCreated > mostRecent)) {
|
||||
mostRecent = packageData.TimeCreated;
|
||||
}
|
||||
}
|
||||
|
||||
if (playableIn > DateTime.UtcNow) {
|
||||
return (0, playableIn);
|
||||
}
|
||||
if (mostRecent > DateTime.MinValue) {
|
||||
DateTime playableIn = mostRecent.AddDays(CardsFarmer.DaysForRefund);
|
||||
|
||||
if (playableIn > DateTime.UtcNow) {
|
||||
return (0, playableIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1189,15 +1194,16 @@ namespace ArchiSteamFarm {
|
||||
EResult result = await ArchiHandler.SendMessage(steamID, messagePart).ConfigureAwait(false);
|
||||
|
||||
switch (result) {
|
||||
case EResult.OK:
|
||||
sent = true;
|
||||
|
||||
break;
|
||||
case EResult.Fail:
|
||||
case EResult.RateLimitExceeded:
|
||||
case EResult.Timeout:
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
|
||||
continue;
|
||||
case EResult.OK:
|
||||
sent = true;
|
||||
|
||||
break;
|
||||
default:
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result), result));
|
||||
|
||||
@@ -1257,15 +1263,16 @@ namespace ArchiSteamFarm {
|
||||
EResult result = await ArchiHandler.SendMessage(chatGroupID, chatID, messagePart).ConfigureAwait(false);
|
||||
|
||||
switch (result) {
|
||||
case EResult.OK:
|
||||
sent = true;
|
||||
|
||||
break;
|
||||
case EResult.Fail:
|
||||
case EResult.RateLimitExceeded:
|
||||
case EResult.Timeout:
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
|
||||
continue;
|
||||
case EResult.OK:
|
||||
sent = true;
|
||||
|
||||
break;
|
||||
default:
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result), result));
|
||||
|
||||
@@ -2031,6 +2038,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
switch (lastLogOnResult) {
|
||||
case EResult.AccountDisabled:
|
||||
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.LoginKey):
|
||||
// Do not attempt to reconnect, those failures are permanent
|
||||
return;
|
||||
case EResult.Invalid:
|
||||
@@ -2040,11 +2048,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
break;
|
||||
case EResult.InvalidPassword:
|
||||
// If we didn't use login key, it's nearly always rate limiting
|
||||
if (string.IsNullOrEmpty(BotDatabase.LoginKey)) {
|
||||
goto case EResult.RateLimitExceeded;
|
||||
}
|
||||
|
||||
await BotDatabase.SetLoginKey().ConfigureAwait(false);
|
||||
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
|
||||
|
||||
@@ -2059,22 +2062,12 @@ namespace ArchiSteamFarm {
|
||||
case EResult.RateLimitExceeded:
|
||||
ArchiLogger.LogGenericInfo(string.Format(Strings.BotRateLimitExceeded, TimeSpan.FromMinutes(LoginCooldownInMinutes).ToHumanReadable()));
|
||||
|
||||
if (LoginRateLimited) {
|
||||
if (!await LoginRateLimitingSemaphore.WaitAsync(1000 * WebBrowser.MaxTries).ConfigureAwait(false)) {
|
||||
break;
|
||||
}
|
||||
|
||||
await LoginRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (LoginRateLimited) {
|
||||
break;
|
||||
}
|
||||
|
||||
LoginRateLimited = true;
|
||||
|
||||
await Task.Delay(LoginCooldownInMinutes * 60 * 1000).ConfigureAwait(false);
|
||||
|
||||
LoginRateLimited = false;
|
||||
} finally {
|
||||
LoginRateLimitingSemaphore.Release();
|
||||
}
|
||||
@@ -2252,30 +2245,14 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return early if this update doesn't bring anything new
|
||||
if (callback.LicenseList.Count == OwnedPackageIDs.Count) {
|
||||
if (callback.LicenseList.All(license => OwnedPackageIDs.ContainsKey(license.PackageID))) {
|
||||
if (!await CardsFarmer.Resume(false).ConfigureAwait(false)) {
|
||||
await ResetGamesPlayed().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Commands.OnNewLicenseList();
|
||||
OwnedPackageIDs.Clear();
|
||||
|
||||
bool refreshData = !BotConfig.IdleRefundableGames || BotConfig.FarmingOrders.Contains(BotConfig.EFarmingOrder.RedeemDateTimesAscending) || BotConfig.FarmingOrders.Contains(BotConfig.EFarmingOrder.RedeemDateTimesDescending);
|
||||
Dictionary<uint, uint> packagesToRefresh = new Dictionary<uint, uint>();
|
||||
|
||||
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList.Where(license => license.PackageID != 0)) {
|
||||
OwnedPackageIDs[license.PackageID] = (license.PaymentMethod, license.TimeCreated);
|
||||
|
||||
if (!refreshData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ASF.GlobalDatabase.PackagesData.TryGetValue(license.PackageID, out (uint ChangeNumber, HashSet<uint> _) packageData) || (packageData.ChangeNumber < license.LastChangeNumber)) {
|
||||
packagesToRefresh[license.PackageID] = (uint) license.LastChangeNumber;
|
||||
}
|
||||
@@ -2287,10 +2264,6 @@ namespace ArchiSteamFarm {
|
||||
ArchiLogger.LogGenericInfo(Strings.Done);
|
||||
}
|
||||
|
||||
if (CardsFarmer.Paused) {
|
||||
await ResetGamesPlayed().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -2348,6 +2321,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
switch (callback.Result) {
|
||||
case EResult.AccountDisabled:
|
||||
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.LoginKey):
|
||||
// Those failures are permanent, we should Stop() the bot if any of those happen
|
||||
ArchiLogger.LogGenericWarning(string.Format(Strings.BotUnableToLogin, callback.Result, callback.ExtendedResult));
|
||||
Stop();
|
||||
|
||||
@@ -1136,12 +1136,14 @@ namespace ArchiSteamFarm {
|
||||
|
||||
foreach (Game game in GamesToFarm) {
|
||||
DateTime redeemDate = DateTime.MinValue;
|
||||
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(game.AppID);
|
||||
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(game.AppID, Bot.OwnedPackageIDs.Keys);
|
||||
|
||||
if (packageIDs != null) {
|
||||
foreach (uint packageID in packageIDs) {
|
||||
if (!Bot.OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
|
||||
continue;
|
||||
Bot.ArchiLogger.LogNullError(nameof(packageData));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (packageData.TimeCreated > redeemDate) {
|
||||
|
||||
@@ -24,6 +24,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Json;
|
||||
using ArchiSteamFarm.Localization;
|
||||
@@ -122,6 +123,8 @@ namespace ArchiSteamFarm {
|
||||
return await ResponsePause(steamID, true).ConfigureAwait(false);
|
||||
case "PAUSE~":
|
||||
return await ResponsePause(steamID, false).ConfigureAwait(false);
|
||||
case "RESET":
|
||||
return await ResponseReset(steamID).ConfigureAwait(false);
|
||||
case "RESUME":
|
||||
return ResponseResume(steamID);
|
||||
case "RESTART":
|
||||
@@ -251,6 +254,8 @@ namespace ArchiSteamFarm {
|
||||
case "R^" when args.Length > 2:
|
||||
case "REDEEM^" when args.Length > 2:
|
||||
return await ResponseAdvancedRedeem(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
case "RESET":
|
||||
return await ResponseReset(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
|
||||
case "RESUME":
|
||||
return await ResponseResume(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
|
||||
case "START":
|
||||
@@ -404,6 +409,36 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Dictionary<uint, string>> FetchGamesOwned(bool cachedOnly = false) {
|
||||
lock (CachedGamesOwned) {
|
||||
if (CachedGamesOwned.Count > 0) {
|
||||
return new Dictionary<uint, string>(CachedGamesOwned);
|
||||
}
|
||||
}
|
||||
|
||||
if (cachedOnly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
bool? hasValidApiKey = await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false);
|
||||
|
||||
Dictionary<uint, string> gamesOwned = hasValidApiKey.GetValueOrDefault() ? await Bot.ArchiWebHandler.GetOwnedGames(Bot.SteamID).ConfigureAwait(false) : await Bot.ArchiWebHandler.GetMyOwnedGames().ConfigureAwait(false);
|
||||
|
||||
if ((gamesOwned != null) && (gamesOwned.Count > 0)) {
|
||||
lock (CachedGamesOwned) {
|
||||
if (CachedGamesOwned.Count == 0) {
|
||||
foreach ((uint appID, string gameName) in gamesOwned) {
|
||||
CachedGamesOwned[appID] = gameName;
|
||||
}
|
||||
|
||||
CachedGamesOwned.TrimExcess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gamesOwned;
|
||||
}
|
||||
|
||||
private async Task<string> Response2FA(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(steamID));
|
||||
@@ -486,9 +521,9 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseAddLicense(ulong steamID, IReadOnlyCollection<uint> gameIDs) {
|
||||
if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs));
|
||||
private async Task<string> ResponseAddLicense(ulong steamID, string query) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(query)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -503,74 +538,73 @@ namespace ArchiSteamFarm {
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
|
||||
foreach (uint gameID in gameIDs) {
|
||||
if (await Bot.ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicenseWithItems, gameID, EResult.OK, "sub/" + gameID)));
|
||||
string[] entries = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (string entry in entries) {
|
||||
uint gameID;
|
||||
string type;
|
||||
|
||||
int index = entry.IndexOf('/');
|
||||
|
||||
if ((index > 0) && (entry.Length > index + 1)) {
|
||||
if (!uint.TryParse(entry.Substring(index + 1), out gameID) || (gameID == 0)) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(gameID))));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
type = entry.Substring(0, index);
|
||||
} else if (uint.TryParse(entry, out gameID) && (gameID > 0)) {
|
||||
type = "SUB";
|
||||
} else {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(gameID))));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
SteamApps.FreeLicenseCallback callback;
|
||||
switch (type.ToUpperInvariant()) {
|
||||
case "A":
|
||||
case "APP":
|
||||
SteamApps.FreeLicenseCallback callback;
|
||||
|
||||
try {
|
||||
callback = await Bot.SteamApps.RequestFreeLicense(gameID);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout)));
|
||||
try {
|
||||
callback = await Bot.SteamApps.RequestFreeLicense(gameID);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, "app/" + gameID, EResult.Timeout)));
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
if (callback == null) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, "app/" + gameID, EResult.Timeout)));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
response.AppendLine(FormatBotResponse((callback.GrantedApps.Count > 0) || (callback.GrantedPackages.Count > 0) ? string.Format(Strings.BotAddLicenseWithItems, "app/" + gameID, callback.Result, string.Join(", ", callback.GrantedApps.Select(appID => "app/" + appID).Union(callback.GrantedPackages.Select(subID => "sub/" + subID)))) : string.Format(Strings.BotAddLicense, "app/" + gameID, callback.Result)));
|
||||
|
||||
break;
|
||||
default:
|
||||
if (!await Bot.ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, "sub/" + gameID, EResult.Fail)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicenseWithItems, gameID, EResult.OK, "sub/" + gameID)));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (callback == null) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout)));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
response.AppendLine(FormatBotResponse((callback.GrantedApps.Count > 0) || (callback.GrantedPackages.Count > 0) ? string.Format(Strings.BotAddLicenseWithItems, gameID, callback.Result, string.Join(", ", callback.GrantedApps.Select(appID => "app/" + appID).Union(callback.GrantedPackages.Select(subID => "sub/" + subID)))) : string.Format(Strings.BotAddLicense, gameID, callback.Result)));
|
||||
}
|
||||
|
||||
return response.Length > 0 ? response.ToString() : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseAddLicense(ulong steamID, string targetGameIDs) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetGameIDs)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetGameIDs));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Bot.HasPermission(steamID, BotConfig.EPermission.Operator)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Bot.IsConnectedAndLoggedOn) {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
string[] gameIDs = targetGameIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (gameIDs.Length == 0) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gameIDs)));
|
||||
}
|
||||
|
||||
HashSet<uint> gamesToRedeem = new HashSet<uint>();
|
||||
|
||||
foreach (string game in gameIDs) {
|
||||
if (!uint.TryParse(game, out uint gameID) || (gameID == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(gameID)));
|
||||
}
|
||||
|
||||
gamesToRedeem.Add(gameID);
|
||||
}
|
||||
|
||||
return await ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[ItemCanBeNull]
|
||||
private static async Task<string> ResponseAddLicense(ulong steamID, string botNames, string targetGameIDs) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetGameIDs)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetGameIDs));
|
||||
private static async Task<string> ResponseAddLicense(ulong steamID, string botNames, string query) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(query)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(query));
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -581,7 +615,7 @@ namespace ArchiSteamFarm {
|
||||
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAddLicense(steamID, targetGameIDs))).ConfigureAwait(false);
|
||||
IList<string> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAddLicense(steamID, query))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
|
||||
|
||||
@@ -659,34 +693,42 @@ namespace ArchiSteamFarm {
|
||||
foreach (string flag in flags) {
|
||||
switch (flag.ToUpperInvariant()) {
|
||||
case "FD":
|
||||
case "FORCEDISTRIBUTING":
|
||||
redeemFlags |= ERedeemFlags.ForceDistributing;
|
||||
|
||||
break;
|
||||
case "FF":
|
||||
case "FORCEFORWARDING":
|
||||
redeemFlags |= ERedeemFlags.ForceForwarding;
|
||||
|
||||
break;
|
||||
case "FKMG":
|
||||
case "FORCEKEEPMISSINGGAMES":
|
||||
redeemFlags |= ERedeemFlags.ForceKeepMissingGames;
|
||||
|
||||
break;
|
||||
case "SD":
|
||||
case "SKIPDISTRIBUTING":
|
||||
redeemFlags |= ERedeemFlags.SkipDistributing;
|
||||
|
||||
break;
|
||||
case "SF":
|
||||
case "SKIPFORWARDING":
|
||||
redeemFlags |= ERedeemFlags.SkipForwarding;
|
||||
|
||||
break;
|
||||
case "SI":
|
||||
case "SKIPINITIAL":
|
||||
redeemFlags |= ERedeemFlags.SkipInitial;
|
||||
|
||||
break;
|
||||
case "SKMG":
|
||||
case "SKIPKEEPMISSINGGAMES":
|
||||
redeemFlags |= ERedeemFlags.SkipKeepMissingGames;
|
||||
|
||||
break;
|
||||
case "V":
|
||||
case "VALIDATE":
|
||||
redeemFlags |= ERedeemFlags.Validate;
|
||||
|
||||
break;
|
||||
@@ -1571,7 +1613,8 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||
}
|
||||
|
||||
private async Task<(string Response, HashSet<uint> OwnedGameIDs)> ResponseOwns(ulong steamID, string query) {
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
private async Task<(string Response, Dictionary<string, string> OwnedGames)> ResponseOwns(ulong steamID, string query) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(query)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
|
||||
|
||||
@@ -1586,78 +1629,139 @@ namespace ArchiSteamFarm {
|
||||
return (FormatBotResponse(Strings.BotNotConnected), null);
|
||||
}
|
||||
|
||||
Dictionary<uint, string> ownedGames = null;
|
||||
|
||||
lock (CachedGamesOwned) {
|
||||
if (CachedGamesOwned.Count > 0) {
|
||||
ownedGames = new Dictionary<uint, string>(CachedGamesOwned);
|
||||
}
|
||||
}
|
||||
|
||||
if (ownedGames == null) {
|
||||
bool? hasValidApiKey = await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false);
|
||||
|
||||
ownedGames = hasValidApiKey.GetValueOrDefault() ? await Bot.ArchiWebHandler.GetOwnedGames(Bot.SteamID).ConfigureAwait(false) : await Bot.ArchiWebHandler.GetMyOwnedGames().ConfigureAwait(false);
|
||||
|
||||
if ((ownedGames == null) || (ownedGames.Count == 0)) {
|
||||
return (FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(ownedGames))), null);
|
||||
}
|
||||
|
||||
lock (CachedGamesOwned) {
|
||||
if (CachedGamesOwned.Count == 0) {
|
||||
foreach ((uint appID, string gameName) in ownedGames) {
|
||||
CachedGamesOwned[appID] = gameName;
|
||||
}
|
||||
|
||||
CachedGamesOwned.TrimExcess();
|
||||
}
|
||||
}
|
||||
}
|
||||
Dictionary<uint, string> gamesOwned = await FetchGamesOwned(true).ConfigureAwait(false);
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
HashSet<uint> ownedGameIDs = new HashSet<uint>();
|
||||
Dictionary<string, string> result = new Dictionary<string, string>();
|
||||
|
||||
if (query.Equals("*")) {
|
||||
foreach ((uint appID, string gameName) in ownedGames) {
|
||||
ownedGameIDs.Add(appID);
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, appID, gameName)));
|
||||
}
|
||||
} else {
|
||||
string[] games = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
string[] entries = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (games.Length == 0) {
|
||||
return (FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(games))), null);
|
||||
foreach (string entry in entries) {
|
||||
string game;
|
||||
string type;
|
||||
|
||||
int index = entry.IndexOf('/');
|
||||
|
||||
if ((index > 0) && (entry.Length > index + 1)) {
|
||||
game = entry.Substring(index + 1);
|
||||
type = entry.Substring(0, index);
|
||||
} else if (uint.TryParse(entry, out uint appID) && (appID > 0)) {
|
||||
game = entry;
|
||||
type = "APP";
|
||||
} else {
|
||||
game = entry;
|
||||
type = "NAME";
|
||||
}
|
||||
|
||||
foreach (string game in games) {
|
||||
// Check if this is gameID
|
||||
if (uint.TryParse(game, out uint gameID) && (gameID != 0)) {
|
||||
if (Bot.OwnedPackageIDs.ContainsKey(gameID)) {
|
||||
ownedGameIDs.Add(gameID);
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlready, gameID)));
|
||||
switch (type.ToUpperInvariant()) {
|
||||
case "A" when uint.TryParse(game, out uint appID) && (appID > 0):
|
||||
case "APP" when uint.TryParse(game, out appID) && (appID > 0):
|
||||
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(appID, Bot.OwnedPackageIDs.Keys);
|
||||
|
||||
continue;
|
||||
if ((packageIDs != null) && (packageIDs.Count > 0)) {
|
||||
if ((gamesOwned != null) && gamesOwned.TryGetValue(appID, out string cachedGameName)) {
|
||||
result["app/" + appID] = cachedGameName;
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, "app/" + appID, cachedGameName)));
|
||||
} else {
|
||||
result["app/" + appID] = null;
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlready, "app/" + appID)));
|
||||
}
|
||||
} else {
|
||||
if (gamesOwned == null) {
|
||||
gamesOwned = await FetchGamesOwned().ConfigureAwait(false);
|
||||
|
||||
if (gamesOwned == null) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gamesOwned))));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (gamesOwned.TryGetValue(appID, out string gameName)) {
|
||||
result["app/" + appID] = gameName;
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, "app/" + appID, gameName)));
|
||||
} else {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, "app/" + appID)));
|
||||
}
|
||||
}
|
||||
|
||||
if (ownedGames.TryGetValue(gameID, out string ownedName)) {
|
||||
ownedGameIDs.Add(gameID);
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, gameID, ownedName)));
|
||||
} else {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, gameID)));
|
||||
break;
|
||||
case "R":
|
||||
case "REGEX":
|
||||
Regex regex;
|
||||
|
||||
try {
|
||||
regex = new Regex(game);
|
||||
} catch (ArgumentException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(regex))));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (gamesOwned == null) {
|
||||
gamesOwned = await FetchGamesOwned().ConfigureAwait(false);
|
||||
|
||||
if (gamesOwned == null) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gamesOwned))));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool foundWithRegex = false;
|
||||
|
||||
foreach ((uint appID, string gameName) in gamesOwned.Where(gameOwned => regex.IsMatch(gameOwned.Value))) {
|
||||
foundWithRegex = true;
|
||||
|
||||
result["app/" + appID] = gameName;
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, "app/" + appID, gameName)));
|
||||
}
|
||||
|
||||
if (!foundWithRegex) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, entry)));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
case "S" when uint.TryParse(game, out uint packageID) && (packageID > 0):
|
||||
case "SUB" when uint.TryParse(game, out packageID) && (packageID > 0):
|
||||
if (Bot.OwnedPackageIDs.ContainsKey(packageID)) {
|
||||
result["sub/" + packageID] = null;
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlready, "sub/" + packageID)));
|
||||
} else {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, "sub/" + packageID)));
|
||||
}
|
||||
|
||||
// This is a string, so check our entire library
|
||||
foreach ((uint appID, string gameName) in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) {
|
||||
ownedGameIDs.Add(appID);
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, appID, gameName)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (gamesOwned == null) {
|
||||
gamesOwned = await FetchGamesOwned().ConfigureAwait(false);
|
||||
|
||||
if (gamesOwned == null) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gamesOwned))));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool foundWithName = false;
|
||||
|
||||
foreach ((uint appID, string gameName) in gamesOwned.Where(gameOwned => gameOwned.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) {
|
||||
foundWithName = true;
|
||||
|
||||
result["app/" + appID] = gameName;
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, "app/" + appID, gameName)));
|
||||
}
|
||||
|
||||
if (!foundWithName) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, entry)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (response.Length > 0 ? response.ToString() : FormatBotResponse(string.Format(Strings.BotNotOwnedYet, query)), ownedGameIDs);
|
||||
return (response.Length > 0 ? response.ToString() : FormatBotResponse(string.Format(Strings.BotNotOwnedYet, query)), result);
|
||||
}
|
||||
|
||||
[ItemCanBeNull]
|
||||
@@ -1674,21 +1778,31 @@ namespace ArchiSteamFarm {
|
||||
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<(string Response, HashSet<uint> OwnedGameIDs)> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseOwns(steamID, query))).ConfigureAwait(false);
|
||||
IList<(string Response, Dictionary<string, string> OwnedGames)> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseOwns(steamID, query))).ConfigureAwait(false);
|
||||
|
||||
List<(string Response, HashSet<uint> OwnedGameIDs)> validResults = new List<(string Response, HashSet<uint> OwnedGameIDs)>(results.Where(result => !string.IsNullOrEmpty(result.Response)));
|
||||
List<(string Response, Dictionary<string, string> OwnedGames)> validResults = new List<(string Response, Dictionary<string, string> OwnedGames)>(results.Where(result => !string.IsNullOrEmpty(result.Response)));
|
||||
|
||||
if (validResults.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<uint, ushort> ownedGameCounts = new Dictionary<uint, ushort>();
|
||||
Dictionary<string, (ushort Count, string GameName)> ownedGamesStats = new Dictionary<string, (ushort Count, string GameName)>();
|
||||
|
||||
foreach (uint gameID in validResults.Where(validResult => (validResult.OwnedGameIDs != null) && (validResult.OwnedGameIDs.Count > 0)).SelectMany(validResult => validResult.OwnedGameIDs)) {
|
||||
ownedGameCounts[gameID] = ownedGameCounts.TryGetValue(gameID, out ushort count) ? ++count : (ushort) 1;
|
||||
foreach ((string gameID, string gameName) in validResults.Where(validResult => (validResult.OwnedGames != null) && (validResult.OwnedGames.Count > 0)).SelectMany(validResult => validResult.OwnedGames)) {
|
||||
if (ownedGamesStats.TryGetValue(gameID, out (ushort Count, string GameName) ownedGameStats)) {
|
||||
ownedGameStats.Count++;
|
||||
} else {
|
||||
ownedGameStats.Count = 1;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(gameName)) {
|
||||
ownedGameStats.GameName = gameName;
|
||||
}
|
||||
|
||||
ownedGamesStats[gameID] = ownedGameStats;
|
||||
}
|
||||
|
||||
IEnumerable<string> extraResponses = ownedGameCounts.Select(kv => FormatStaticResponse(string.Format(Strings.BotOwnsOverviewPerGame, kv.Value, validResults.Count, kv.Key)));
|
||||
IEnumerable<string> extraResponses = ownedGamesStats.Select(kv => FormatStaticResponse(string.Format(Strings.BotOwnsOverviewPerGame, kv.Value.Count, validResults.Count, kv.Key + (!string.IsNullOrEmpty(kv.Value.GameName) ? " | " + kv.Value.GameName : ""))));
|
||||
|
||||
return string.Join(Environment.NewLine, validResults.Select(result => result.Response).Concat(extraResponses));
|
||||
}
|
||||
@@ -1799,13 +1913,9 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
if (!Bot.CardsFarmer.Paused) {
|
||||
await Bot.CardsFarmer.Pause(false).ConfigureAwait(false);
|
||||
}
|
||||
(bool success, string message) = await Bot.Actions.Play(gameIDs, gameName).ConfigureAwait(false);
|
||||
|
||||
await Bot.ArchiHandler.PlayGames(gameIDs, gameName).ConfigureAwait(false);
|
||||
|
||||
return FormatBotResponse(Strings.Done);
|
||||
return FormatBotResponse(success ? message : string.Format(Strings.WarningFailedWithError, message));
|
||||
}
|
||||
|
||||
private async Task<string> ResponsePlay(ulong steamID, string targetGameIDs) {
|
||||
@@ -1846,7 +1956,7 @@ namespace ArchiSteamFarm {
|
||||
gamesToPlay.Add(gameID);
|
||||
}
|
||||
|
||||
return await ResponsePlay(steamID, gamesToPlay, gameName.ToString()).ConfigureAwait(false);
|
||||
return await ResponsePlay(steamID, gamesToPlay, gameName.Length > 0 ? gameName.ToString() : null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[ItemCanBeNull]
|
||||
@@ -2270,6 +2380,47 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||
}
|
||||
|
||||
[ItemCanBeNull]
|
||||
private static async Task<string> ResponseReset(ulong steamID, string botNames) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Bot> bots = Bot.GetBots(botNames);
|
||||
|
||||
if ((bots == null) || (bots.Count == 0)) {
|
||||
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseReset(steamID))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
|
||||
|
||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseReset(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(steamID));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Bot.HasPermission(steamID, BotConfig.EPermission.Master)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Bot.IsConnectedAndLoggedOn) {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
(bool success, string message) = await Bot.Actions.Play(Enumerable.Empty<uint>(), Bot.BotConfig.CustomGamePlayedWhileIdle).ConfigureAwait(false);
|
||||
|
||||
return FormatBotResponse(success ? message : string.Format(Strings.WarningFailedWithError, message));
|
||||
}
|
||||
|
||||
private static string ResponseRestart(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID));
|
||||
|
||||
@@ -111,14 +111,24 @@ namespace ArchiSteamFarm {
|
||||
return globalDatabase;
|
||||
}
|
||||
|
||||
internal HashSet<uint> GetPackageIDs(uint appID) {
|
||||
if (appID == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appID));
|
||||
internal HashSet<uint> GetPackageIDs(uint appID, ICollection<uint> packageIDs) {
|
||||
if ((appID == 0) || (packageIDs == null) || (packageIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(packageIDs));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return PackagesData.Where(package => package.Value.AppIDs?.Contains(appID) == true).Select(package => package.Key).ToHashSet();
|
||||
HashSet<uint> result = new HashSet<uint>();
|
||||
|
||||
foreach (uint packageID in packageIDs.Where(packageID => packageID != 0)) {
|
||||
if (!PackagesData.TryGetValue(packageID, out (uint _, HashSet<uint> AppIDs) packagesData) || (packagesData.AppIDs?.Contains(appID) != true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(packageID);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task RefreshPackages(Bot bot, IReadOnlyDictionary<uint, uint> packages) {
|
||||
@@ -140,6 +150,8 @@ namespace ArchiSteamFarm {
|
||||
Dictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> packagesData = await bot.GetPackagesData(packageIDs).ConfigureAwait(false);
|
||||
|
||||
if ((packagesData == null) || (packagesData.Count == 0)) {
|
||||
bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace ArchiSteamFarm.Helpers {
|
||||
private T InitializedValue;
|
||||
private Timer MaintenanceTimer;
|
||||
|
||||
internal ArchiCacheable([NotNull] Func<Task<(bool Success, T Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
|
||||
public ArchiCacheable([NotNull] Func<Task<(bool Success, T Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
|
||||
ResolveFunction = resolveFunction ?? throw new ArgumentNullException(nameof(resolveFunction));
|
||||
CacheLifetime = cacheLifetime ?? Timeout.InfiniteTimeSpan;
|
||||
}
|
||||
|
||||
@@ -100,13 +100,14 @@ namespace ArchiSteamFarm.IPC {
|
||||
Logging.InitHistoryLogger();
|
||||
|
||||
// Start the server
|
||||
IWebHost kestrelWebHost = builder.Build();
|
||||
IWebHost kestrelWebHost = null;
|
||||
|
||||
try {
|
||||
kestrelWebHost = builder.Build();
|
||||
await kestrelWebHost.StartAsync().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
kestrelWebHost.Dispose();
|
||||
kestrelWebHost?.Dispose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.IPC.Requests;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -37,14 +38,19 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
/// This API endpoint is supposed to be entirely replaced by ASF actions available under /Api/ASF/{action} and /Api/Bot/{bot}/{action}.
|
||||
/// You should use "given bot" commands when executing this endpoint, omitting targets of the command will cause the command to be executed on first defined bot
|
||||
/// </remarks>
|
||||
[HttpPost("{command:required}")]
|
||||
[Consumes("application/json")]
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
public async Task<ActionResult<GenericResponse>> CommandPost(string command) {
|
||||
if (string.IsNullOrEmpty(command)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(command));
|
||||
public async Task<ActionResult<GenericResponse>> CommandPost([FromBody] CommandRequest request) {
|
||||
if (request == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(request));
|
||||
|
||||
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(command))));
|
||||
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request))));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.Command)) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request.Command))));
|
||||
}
|
||||
|
||||
if (ASF.GlobalConfig.SteamOwnerID == 0) {
|
||||
@@ -57,6 +63,8 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
return BadRequest(new GenericResponse(false, Strings.ErrorNoBotsDefined));
|
||||
}
|
||||
|
||||
string command = request.Command;
|
||||
|
||||
if (!string.IsNullOrEmpty(ASF.GlobalConfig.CommandPrefix) && command.StartsWith(ASF.GlobalConfig.CommandPrefix, StringComparison.Ordinal)) {
|
||||
command = command.Substring(ASF.GlobalConfig.CommandPrefix.Length);
|
||||
|
||||
@@ -69,5 +77,26 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
|
||||
return Ok(new GenericResponse<string>(response));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This API endpoint is supposed to be entirely replaced by ASF actions available under /Api/ASF/{action} and /Api/Bot/{bot}/{action}.
|
||||
/// You should use "given bot" commands when executing this endpoint, omitting targets of the command will cause the command to be executed on first defined bot
|
||||
/// </remarks>
|
||||
[HttpPost("{command:required}")]
|
||||
[Obsolete]
|
||||
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
public async Task<ActionResult<GenericResponse>> ObsoleteCommandPost(string command) {
|
||||
if (string.IsNullOrEmpty(command)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(command));
|
||||
|
||||
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(command))));
|
||||
}
|
||||
|
||||
return await CommandPost(new CommandRequest(command)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,20 +39,26 @@ namespace ArchiSteamFarm.IPC.Integration {
|
||||
private const byte MaxFailedAuthorizationAttempts = 5;
|
||||
|
||||
private static readonly SemaphoreSlim AuthorizationSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private static readonly Timer ClearFailedAuthorizationsTimer = new Timer(
|
||||
e => FailedAuthorizations.Clear(),
|
||||
null,
|
||||
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours), // Delay
|
||||
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours) // Period
|
||||
);
|
||||
|
||||
private static readonly ConcurrentDictionary<IPAddress, byte> FailedAuthorizations = new ConcurrentDictionary<IPAddress, byte>();
|
||||
|
||||
private static Timer ClearFailedAuthorizationsTimer;
|
||||
|
||||
private readonly RequestDelegate Next;
|
||||
|
||||
public ApiAuthenticationMiddleware([NotNull] RequestDelegate next) => Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
public ApiAuthenticationMiddleware([NotNull] RequestDelegate next) {
|
||||
Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
|
||||
lock (FailedAuthorizations) {
|
||||
if (ClearFailedAuthorizationsTimer == null) {
|
||||
ClearFailedAuthorizationsTimer = new Timer(
|
||||
e => FailedAuthorizations.Clear(),
|
||||
null,
|
||||
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours), // Delay
|
||||
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours) // Period
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task InvokeAsync(HttpContext context) {
|
||||
|
||||
49
ArchiSteamFarm/IPC/Requests/CommandRequest.cs
Normal file
49
ArchiSteamFarm/IPC/Requests/CommandRequest.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Requests {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
public sealed class CommandRequest {
|
||||
/// <summary>
|
||||
/// Specifies the command that will be executed by ASF.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
[Required]
|
||||
public readonly string Command;
|
||||
|
||||
internal CommandRequest([NotNull] string command) {
|
||||
if (string.IsNullOrEmpty(command)) {
|
||||
throw new ArgumentNullException(nameof(command));
|
||||
}
|
||||
|
||||
Command = command;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private CommandRequest() { }
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -58,6 +59,9 @@ namespace ArchiSteamFarm.IPC {
|
||||
app.UsePathBase(pathBase);
|
||||
}
|
||||
|
||||
// Add support for proxies
|
||||
app.UseForwardedHeaders();
|
||||
|
||||
// Add support for response compression
|
||||
app.UseResponseCompression();
|
||||
|
||||
@@ -99,6 +103,9 @@ namespace ArchiSteamFarm.IPC {
|
||||
|
||||
// The order of dependency injection matters, pay attention to it
|
||||
|
||||
// Add support for proxies
|
||||
services.Configure<ForwardedHeadersOptions>(options => options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto);
|
||||
|
||||
// Add support for response compression
|
||||
services.AddResponseCompression();
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace ArchiSteamFarm.Json {
|
||||
[PublicAPI]
|
||||
public EType Type { get; internal set; }
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
|
||||
[NotNull]
|
||||
private string AmountText {
|
||||
@@ -91,6 +92,7 @@ namespace ArchiSteamFarm.Json {
|
||||
Amount = amount;
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
|
||||
[NotNull]
|
||||
@@ -114,6 +116,7 @@ namespace ArchiSteamFarm.Json {
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
|
||||
[NotNull]
|
||||
private string ClassIDText {
|
||||
@@ -133,7 +136,9 @@ namespace ArchiSteamFarm.Json {
|
||||
ClassID = classID;
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
|
||||
[NotNull]
|
||||
private string ContextIDText {
|
||||
@@ -155,13 +160,16 @@ namespace ArchiSteamFarm.Json {
|
||||
ContextID = contextID;
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
|
||||
[NotNull]
|
||||
private string IDText {
|
||||
get => AssetIDText;
|
||||
set => AssetIDText = value;
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
// Constructed from trades being received or plugins
|
||||
public Asset(uint appID, ulong contextID, ulong classID, uint amount, bool marketable = true, uint realAppID = 0, EType type = EType.Unknown, ERarity rarity = ERarity.Unknown) {
|
||||
@@ -218,6 +226,7 @@ namespace ArchiSteamFarm.Json {
|
||||
internal ulong TradeOfferID { get; private set; }
|
||||
internal EType Type { get; private set; }
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "html", Required = Required.DisallowNull)]
|
||||
private string HTML {
|
||||
set {
|
||||
@@ -287,6 +296,7 @@ namespace ArchiSteamFarm.Json {
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
[JsonConstructor]
|
||||
private ConfirmationDetails() { }
|
||||
@@ -318,6 +328,7 @@ namespace ArchiSteamFarm.Json {
|
||||
[PublicAPI]
|
||||
public bool Success { get; private set; }
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "success", Required = Required.Always)]
|
||||
private byte SuccessNumber {
|
||||
set {
|
||||
@@ -337,6 +348,7 @@ namespace ArchiSteamFarm.Json {
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
[JsonConstructor]
|
||||
protected NumberResponse() { }
|
||||
@@ -418,6 +430,7 @@ namespace ArchiSteamFarm.Json {
|
||||
internal ulong LastAssetID { get; private set; }
|
||||
internal bool MoreItems { get; private set; }
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "last_assetid", Required = Required.DisallowNull)]
|
||||
private string LastAssetIDText {
|
||||
set {
|
||||
@@ -436,11 +449,14 @@ namespace ArchiSteamFarm.Json {
|
||||
LastAssetID = lastAssetID;
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "more_items", Required = Required.DisallowNull)]
|
||||
private byte MoreItemsNumber {
|
||||
set => MoreItems = value > 0;
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
[JsonConstructor]
|
||||
private InventoryResponse() { }
|
||||
@@ -456,6 +472,7 @@ namespace ArchiSteamFarm.Json {
|
||||
internal bool Tradable { get; private set; }
|
||||
internal Asset.EType Type { get; private set; }
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "classid", Required = Required.Always)]
|
||||
private string ClassIDText {
|
||||
set {
|
||||
@@ -474,12 +491,16 @@ namespace ArchiSteamFarm.Json {
|
||||
ClassID = classID;
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "marketable", Required = Required.Always)]
|
||||
private byte MarketableNumber {
|
||||
set => Marketable = value > 0;
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "tags", Required = Required.DisallowNull)]
|
||||
private ImmutableHashSet<Tag> Tags {
|
||||
set {
|
||||
@@ -492,11 +513,14 @@ namespace ArchiSteamFarm.Json {
|
||||
(Type, Rarity, RealAppID) = InterpretTags(value);
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "tradable", Required = Required.Always)]
|
||||
private byte TradableNumber {
|
||||
set => Tradable = value > 0;
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
[JsonConstructor]
|
||||
private Description() { }
|
||||
@@ -703,6 +727,7 @@ namespace ArchiSteamFarm.Json {
|
||||
|
||||
internal ulong TradeOfferID { get; private set; }
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always)]
|
||||
private string TradeOfferIDText {
|
||||
set {
|
||||
@@ -721,6 +746,7 @@ namespace ArchiSteamFarm.Json {
|
||||
TradeOfferID = tradeOfferID;
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
[JsonConstructor]
|
||||
private TradeOfferSendResponse() { }
|
||||
|
||||
@@ -728,5 +728,7 @@ StackTrace:
|
||||
<value>Venter op til {0} for at sikre at vi er klar til at begynde at idle...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
|
||||
</data>
|
||||
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>Rydder op i de gamle filer efter opdateringen...</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -264,13 +264,16 @@ StackTrace:
|
||||
<value>Εισάγετε το όνομα χρήστη Steam σας: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputSteamParentalCode" xml:space="preserve">
|
||||
<value>Εισάγετε το κωδικό γονικού ελέγχου του Steam σας: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamPassword" xml:space="preserve">
|
||||
<value>Εισάγετε τον κωδικό πρόσβασής σας στο Steam: </value>
|
||||
<value>Εισάγετε τον κωδικό πρόσβαση του Steam: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputUnknown" xml:space="preserve">
|
||||
<value>Εισάγετε τη μη τεκμηριωμένη τιμή {0}: </value>
|
||||
<value>Εισάγετε τη μη τεκμηριωμένη τιμή του {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
@@ -280,7 +283,9 @@ StackTrace:
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
<value>Ο διακομιστής IPC είναι έτοιμος!</value>
|
||||
</data>
|
||||
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Έναρξη διακομιστή IPC...</value>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Το bot έχει ήδη σταματήσει!</value>
|
||||
</data>
|
||||
@@ -297,7 +302,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to idle, {3} will be replaced by total number of games to idle, {4} will be replaced by total number of cards to idle, {5} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdlingList" xml:space="preserve">
|
||||
<value>Το bot συλλέγει κάρτες των παιχνιδιών: {0} από ένα σύνολο {1} παιχνιδιών ({2} κάρτες) που απομένουν (υπόλοιπο χρόνου ~{3}).</value>
|
||||
<value>Το bot συλλέγει κάρτες των παιχνιδιών: {0} από ένα σύνολο {1} παιχνιδιών ({2} κάρτες) που απομένουν (υπόλοιπο χρόνου: ~{3}).</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to idle, {2} will be replaced by total number of cards to idle, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="CheckingFirstBadgePage" xml:space="preserve">
|
||||
@@ -314,7 +319,7 @@ StackTrace:
|
||||
<value>Ολοκληρώθηκε!</value>
|
||||
</data>
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Έχουμε συνολικά {0} παιχνίδια ({1} κάρτες) ακόμα για συλλογή καρτών (απομένουν ~{2})...</value>
|
||||
<value>Έχουμε συνολικά {0} παιχνίδια ({1} κάρτες) ακόμα για συλλογή καρτών (υπόλοιπο χρόνου: ~{2})...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
@@ -335,7 +340,9 @@ StackTrace:
|
||||
<data name="IdlingStopped" xml:space="preserve">
|
||||
<value>Η συλλογή καρτών σταμάτησε!</value>
|
||||
</data>
|
||||
|
||||
<data name="IgnoredPermanentPauseEnabled" xml:space="preserve">
|
||||
<value>Αγνοήθηκε το αίτημα, γιατί η μόνιμη παύση είναι ενεργοποιημένη!</value>
|
||||
</data>
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>Δεν έχουμε τίποτα να συλλέξουμε σε αυτόν τον λογαριασμό!</value>
|
||||
</data>
|
||||
@@ -440,7 +447,10 @@ StackTrace:
|
||||
<value>Έγινε αποσύνδεση από το Steam: {0}</value>
|
||||
<comment>{0} will be replaced by logging off reason (string)</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotLoggedOn" xml:space="preserve">
|
||||
<value>Επιτυχής σύνδεση ως {0}.</value>
|
||||
<comment>{0} will be replaced by steam ID (number)</comment>
|
||||
</data>
|
||||
<data name="BotLoggingIn" xml:space="preserve">
|
||||
<value>Σύνδεση...</value>
|
||||
</data>
|
||||
@@ -473,7 +483,10 @@ StackTrace:
|
||||
<value>Κατέχετε ήδη: {0} | {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotRateLimitExceeded" xml:space="preserve">
|
||||
<value>Ξεπεράστηκε το όριο προσπαθειών, θα ξαναγίνει προσπάθεια μετά από {0}...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotReconnecting" xml:space="preserve">
|
||||
<value>Επανασύνδεση...</value>
|
||||
</data>
|
||||
@@ -577,7 +590,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by game's ID (number)</comment>
|
||||
</data>
|
||||
<data name="BotVersion" xml:space="preserve">
|
||||
<value>{0} V{1}</value>
|
||||
<value>{0} Εκδ.{1}</value>
|
||||
<comment>{0} will be replaced by program's name (e.g. "ASF"), {1} will be replaced by program's version (e.g. "1.0.0.0"). This string typically has nothing to translate and you should leave it as it is, unless you need to change the format, e.g. in RTL languages.</comment>
|
||||
</data>
|
||||
<data name="BotAccountLocked" xml:space="preserve">
|
||||
@@ -618,6 +631,10 @@ StackTrace:
|
||||
<data name="BotRefreshingPackagesData" xml:space="preserve">
|
||||
<value>Ανανέωση δεδομένων πακέτων...</value>
|
||||
</data>
|
||||
<data name="WarningDeprecated" xml:space="preserve">
|
||||
<value>Η χρήση του {0} είναι υπό απόσυρση και θα αφαιρεθεί σε μελλοντικές εκδόσεις του προγράμματος. Παρακαλώ χρησιμοποιήστε το {1}.</value>
|
||||
<comment>{0} will be replaced by the name of deprecated property (such as argument, config property or likewise), {1} will be replaced by the name of valid replacement (such as another argument or config property)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
@@ -626,23 +643,45 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
<data name="ErrorAborted" xml:space="preserve">
|
||||
<value>Ακυρώθηκε!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="PluginLoading" xml:space="preserve">
|
||||
<value>Φόρτωση του {0} Εκδ.{1}...</value>
|
||||
<comment>{0} will be replaced by the name of the custom ASF plugin, {1} will be replaced by its version</comment>
|
||||
</data>
|
||||
<data name="NothingFound" xml:space="preserve">
|
||||
<value>Δεν βρέθηκε τίποτα!</value>
|
||||
</data>
|
||||
|
||||
<data name="PleaseWait" xml:space="preserve">
|
||||
<value>Παρακαλώ περιμένετε...</value>
|
||||
</data>
|
||||
<data name="EnterCommand" xml:space="preserve">
|
||||
<value>Εισαγωγή εντολής: </value>
|
||||
</data>
|
||||
<data name="Executing" xml:space="preserve">
|
||||
<value>Εκτέλεση...</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>Η διαδραστική κονσόλα είναι ενεργή, πατήστε 'c' για να εισάγετε εντολή.</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
|
||||
<value>Η διαδραστική κονσόλα δεν είναι διαθέσιμη διότι λείπει η ιδιότητα {0} από το αρχείο διαμόρφωσης.</value>
|
||||
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
|
||||
<value>Το bot έχει {0} παιχνίδια που απομένουν στην ουρά.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>Εκκαθάριση παλιών αρχείων μετά την ενημέρωση...</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -692,15 +692,42 @@ Trazo de pila:
|
||||
<data name="PluginsWarning" xml:space="preserve">
|
||||
<value>Has cargado uno o más plugins personalizados en el ASF. Ya que no podemos ofrecer soporte a configuraciones alteradas, por favor, consulte a los desarrolladores apropiados de los plugins que has decidido usar en caso de que tengas problemas.</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="PleaseWait" xml:space="preserve">
|
||||
<value>Por favor espera...</value>
|
||||
</data>
|
||||
<data name="EnterCommand" xml:space="preserve">
|
||||
<value>Ingrese el comando: </value>
|
||||
</data>
|
||||
<data name="Executing" xml:space="preserve">
|
||||
<value>Ejecutando...</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>La consola interactiva está activa, escriba 'c' para ingresar al modo de comando.</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
|
||||
<value>La consola interactiva no está disponible debido a que faltan {0} propiedades de configuración.</value>
|
||||
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
|
||||
</data>
|
||||
<data name="Response" xml:space="preserve">
|
||||
<value>Respuesta: {0}</value>
|
||||
<comment>{0} will be replaced by the generated response (string)</comment>
|
||||
</data>
|
||||
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
|
||||
<value>El bot tiene {0} juegos restantes en su cola de fondo.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
</data>
|
||||
<data name="ErrorSingleInstanceRequired" xml:space="preserve">
|
||||
<value>El proceso ASF ya está ejecutándose para este directorio de trabajo, ¡abortando!</value>
|
||||
</data>
|
||||
<data name="BotHandledConfirmations" xml:space="preserve">
|
||||
<value>¡Se han manejado correctamente {0} confirmaciones!</value>
|
||||
<comment>{0} will be replaced by number of confirmations</comment>
|
||||
</data>
|
||||
<data name="BotExtraIdlingCooldown" xml:space="preserve">
|
||||
<value>Esperando hasta {0} para asegurar que estamos libres para empezar a recolectar...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
|
||||
</data>
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>Limpiando archivos antiguos después de actualizar...</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -624,7 +624,7 @@ StackTrace :
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Fini de consulter la liste de découvertes #{0}.</value>
|
||||
<value>Fin de l’exploration de la liste de découvertes #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="BotOwnsOverviewPerGame" xml:space="preserve">
|
||||
@@ -665,7 +665,7 @@ StackTrace :
|
||||
<comment>{0} will be replaced by round number</comment>
|
||||
</data>
|
||||
<data name="DoneActivelyMatchingItems" xml:space="preserve">
|
||||
<value>Fini de match des Items, round #{0}.</value>
|
||||
<value>Fin de l'appariement des Items Steam, round #{0}.</value>
|
||||
<comment>{0} will be replaced by round number</comment>
|
||||
</data>
|
||||
<data name="ErrorAborted" xml:space="preserve">
|
||||
@@ -705,7 +705,10 @@ StackTrace :
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>La console interactive est maintenant active, tapez 'c' pour entrer en mode commande.</value>
|
||||
</data>
|
||||
|
||||
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
|
||||
<value>La console interactive n'est pas disponible en raison de la propriété de configuration {0} manquante.</value>
|
||||
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
|
||||
</data>
|
||||
<data name="Response" xml:space="preserve">
|
||||
<value>Réponse : {0}</value>
|
||||
<comment>{0} will be replaced by the generated response (string)</comment>
|
||||
@@ -714,8 +717,18 @@ StackTrace :
|
||||
<value>Le bot a {0} jeux restants dans sa file d'attente.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="ErrorSingleInstanceRequired" xml:space="preserve">
|
||||
<value>Le processus ASF est déjà en cours d'exécution pour ce répertoire de travail, interruption !</value>
|
||||
</data>
|
||||
<data name="BotHandledConfirmations" xml:space="preserve">
|
||||
<value>{0} confirmations gérées avec succès !</value>
|
||||
<comment>{0} will be replaced by number of confirmations</comment>
|
||||
</data>
|
||||
<data name="BotExtraIdlingCooldown" xml:space="preserve">
|
||||
<value>En attente de {0} pour s'assurer que nous pouvons commencer à récolter les cartes...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
|
||||
</data>
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>Nettoyage des anciens fichiers après mise à jour...</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -697,7 +697,9 @@
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>已開啟互動式主控台,按'C'回到指令模式</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,13 @@ namespace ArchiSteamFarm {
|
||||
private static Mutex SingleInstance;
|
||||
|
||||
internal static void CoreInit() {
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !Console.IsOutputRedirected) {
|
||||
// Normally we should use UTF-8 encoding as it's the most correct one for our case, and we already use it on other OSes such as Linux
|
||||
// However, older Windows versions, mainly 7/8.1 can't into UTF-8 without appropriate console font, and expecting from users to change it manually is unwanted
|
||||
// As irrational as it can sound, those versions actually can work with unicode encoding instead, as they magically map it into proper chars despite of incorrect font
|
||||
// We could in theory conditionally use UTF-8 for Windows 10+ and unicode otherwise, but Windows version detection is simply not worth the hassle in this case
|
||||
// Therefore, until we can drop support for Windows < 10, we'll stick with Unicode for all Windows boxes, unless there will be valid reasoning for conditional switch
|
||||
// See https://github.com/JustArchiNET/ArchiSteamFarm/issues/1289 for more details
|
||||
Console.OutputEncoding = Encoding.Unicode;
|
||||
|
||||
DisableQuickEditMode();
|
||||
@@ -46,6 +52,12 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal static void Init(bool systemRequired, GlobalConfig.EOptimizationMode optimizationMode) {
|
||||
if (!Enum.IsDefined(typeof(GlobalConfig.EOptimizationMode), optimizationMode)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(optimizationMode));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
if (systemRequired) {
|
||||
KeepWindowsSystemActive();
|
||||
@@ -78,6 +90,8 @@ namespace ArchiSteamFarm {
|
||||
Mutex singleInstance = new Mutex(true, uniqueName, out bool result);
|
||||
|
||||
if (!result) {
|
||||
singleInstance.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -111,10 +125,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static void DisableQuickEditMode() {
|
||||
if (Console.IsOutputRedirected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/30418886/how-and-why-does-quickedit-mode-in-command-prompt-freeze-applications
|
||||
IntPtr consoleHandle = NativeMethods.GetStdHandle(NativeMethods.StandardInputHandle);
|
||||
|
||||
@@ -137,8 +147,8 @@ namespace ArchiSteamFarm {
|
||||
// More info: https://msdn.microsoft.com/library/windows/desktop/aa373208(v=vs.85).aspx
|
||||
NativeMethods.EExecutionState result = NativeMethods.SetThreadExecutionState(NativeMethods.AwakeExecutionState);
|
||||
|
||||
// SetThreadExecutionState() returns NULL on failure, which is mapped to 0 (EExecutionState.Error) in our case
|
||||
if (result == NativeMethods.EExecutionState.Error) {
|
||||
// SetThreadExecutionState() returns NULL on failure, which is mapped to 0 (EExecutionState.None) in our case
|
||||
if (result == NativeMethods.EExecutionState.None) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, result));
|
||||
}
|
||||
}
|
||||
@@ -166,7 +176,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
[Flags]
|
||||
internal enum EExecutionState : uint {
|
||||
Error = 0,
|
||||
None = 0,
|
||||
SystemRequired = 0x00000001,
|
||||
AwayModeRequired = 0x00000040,
|
||||
Continuous = 0x80000000
|
||||
|
||||
@@ -37,6 +37,8 @@ using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.Plugins {
|
||||
internal static class PluginsCore {
|
||||
internal static bool HasActivePluginsLoaded => ActivePlugins?.Count > 0;
|
||||
|
||||
[ImportMany]
|
||||
private static ImmutableHashSet<IPlugin> ActivePlugins { get; set; }
|
||||
|
||||
@@ -47,7 +49,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
|
||||
[ItemNotNull]
|
||||
internal static async Task<StringComparer> GetBotsComparer() {
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return StringComparer.Ordinal;
|
||||
}
|
||||
|
||||
@@ -86,7 +88,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
}
|
||||
|
||||
internal static bool InitPlugins() {
|
||||
if ((ActivePlugins != null) && (ActivePlugins.Count > 0)) {
|
||||
if (HasActivePluginsLoaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -147,6 +149,8 @@ namespace ArchiSteamFarm.Plugins {
|
||||
ActivePlugins = activePlugins.ToImmutableHashSet();
|
||||
ASF.ArchiLogger.LogGenericInfo(Strings.PluginsWarning);
|
||||
|
||||
Console.Title = SharedInfo.ProgramIdentifier;
|
||||
|
||||
return invalidPlugins.Count == 0;
|
||||
}
|
||||
|
||||
@@ -181,7 +185,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
}
|
||||
|
||||
internal static async Task OnASFInitModules(IReadOnlyDictionary<string, JToken> additionalConfigProperties = null) {
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -200,7 +204,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -224,7 +228,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -242,7 +246,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -260,7 +264,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -284,7 +288,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -302,7 +306,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -320,7 +324,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -339,7 +343,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -363,7 +367,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -381,7 +385,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -405,7 +409,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -429,7 +433,7 @@ namespace ArchiSteamFarm.Plugins {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
|
||||
if (!HasActivePluginsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Resources;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.IPC;
|
||||
using ArchiSteamFarm.Localization;
|
||||
@@ -131,10 +130,8 @@ namespace ArchiSteamFarm {
|
||||
private static async Task InitASF(IReadOnlyCollection<string> args) {
|
||||
OS.CoreInit();
|
||||
|
||||
string programIdentifier = SharedInfo.PublicIdentifier + " V" + SharedInfo.Version + " (" + SharedInfo.BuildInfo.Variant + "/" + SharedInfo.ModuleVersion + " | " + OS.Variant + ")";
|
||||
|
||||
Console.Title = programIdentifier;
|
||||
ASF.ArchiLogger.LogGenericInfo(programIdentifier);
|
||||
Console.Title = SharedInfo.ProgramIdentifier;
|
||||
ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier);
|
||||
|
||||
await InitGlobalConfigAndLanguage().ConfigureAwait(false);
|
||||
|
||||
@@ -452,6 +449,16 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
string envPath = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariablePath);
|
||||
|
||||
if (!string.IsNullOrEmpty(envPath)) {
|
||||
HandlePathArgument(envPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
}
|
||||
|
||||
bool pathNext = false;
|
||||
|
||||
foreach (string arg in args) {
|
||||
|
||||
@@ -23,6 +23,7 @@ using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using ArchiSteamFarm.Plugins;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
@@ -37,6 +38,7 @@ namespace ArchiSteamFarm {
|
||||
internal const string DatabaseExtension = ".db";
|
||||
internal const string DebugDirectory = "debug";
|
||||
internal const string EnvironmentVariableCryptKey = ASF + "_CRYPTKEY";
|
||||
internal const string EnvironmentVariablePath = ASF + "_PATH";
|
||||
internal const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
|
||||
internal const string GithubRepo = "JustArchiNET/" + AssemblyName;
|
||||
internal const string GlobalConfigFileName = ASF + ConfigExtension;
|
||||
@@ -57,14 +59,18 @@ namespace ArchiSteamFarm {
|
||||
internal const string WebsiteDirectory = "www";
|
||||
|
||||
internal static string HomeDirectory => Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location ?? throw new ArgumentNullException(nameof(HomeDirectory)));
|
||||
internal static Guid ModuleVersion => Assembly.GetEntryAssembly()?.ManifestModule.ModuleVersionId ?? throw new ArgumentNullException(nameof(ModuleVersion));
|
||||
|
||||
[NotNull]
|
||||
internal static string PublicIdentifier => AssemblyName + (BuildInfo.IsCustomBuild ? "-custom" : "");
|
||||
internal static string ProgramIdentifier => PublicIdentifier + " V" + Version + " (" + BuildInfo.Variant + "/" + ModuleVersion + " | " + OS.Variant + ")";
|
||||
|
||||
[NotNull]
|
||||
internal static string PublicIdentifier => AssemblyName + (BuildInfo.IsCustomBuild ? "-custom" : PluginsCore.HasActivePluginsLoaded ? "-modded" : "");
|
||||
|
||||
[NotNull]
|
||||
internal static Version Version => Assembly.GetEntryAssembly()?.GetName().Version ?? throw new ArgumentNullException(nameof(Version));
|
||||
|
||||
private static Guid ModuleVersion => Assembly.GetEntryAssembly()?.ManifestModule.ModuleVersionId ?? throw new ArgumentNullException(nameof(ModuleVersion));
|
||||
|
||||
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
|
||||
internal static class BuildInfo {
|
||||
#if ASF_VARIANT_DOCKER
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Don't announce if we don't meet conditions
|
||||
bool? eligible = await IsEligibleForMatching().ConfigureAwait(false);
|
||||
bool? eligible = await IsEligibleForListing().ConfigureAwait(false);
|
||||
|
||||
if (!eligible.HasValue) {
|
||||
// This is actually network failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
@@ -235,6 +235,25 @@ namespace ArchiSteamFarm {
|
||||
return objectResponse?.Content;
|
||||
}
|
||||
|
||||
private async Task<bool?> IsEligibleForListing() {
|
||||
bool? isEligibleForMatching = await IsEligibleForMatching().ConfigureAwait(false);
|
||||
|
||||
if (isEligibleForMatching != true) {
|
||||
return isEligibleForMatching;
|
||||
}
|
||||
|
||||
// Bot must have public inventory
|
||||
bool? hasPublicInventory = await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false);
|
||||
|
||||
if (hasPublicInventory != true) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.WarningFailedWithError, nameof(Bot.ArchiWebHandler.HasPublicInventory) + ": " + (hasPublicInventory?.ToString() ?? "null")));
|
||||
|
||||
return hasPublicInventory;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool?> IsEligibleForMatching() {
|
||||
// Bot must have ASF 2FA
|
||||
if (!Bot.HasMobileAuthenticator) {
|
||||
@@ -257,19 +276,10 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bot must have public inventory
|
||||
bool? hasPublicInventory = await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false);
|
||||
|
||||
if (!hasPublicInventory.GetValueOrDefault()) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.WarningFailedWithError, nameof(Bot.ArchiWebHandler.HasPublicInventory) + ": " + (hasPublicInventory?.ToString() ?? "null")));
|
||||
|
||||
return hasPublicInventory;
|
||||
}
|
||||
|
||||
// Bot must have valid API key (e.g. not being restricted account)
|
||||
bool? hasValidApiKey = await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false);
|
||||
|
||||
if (!hasValidApiKey.GetValueOrDefault()) {
|
||||
if (hasValidApiKey != true) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.WarningFailedWithError, nameof(Bot.ArchiWebHandler.HasValidApiKey) + ": " + (hasValidApiKey?.ToString() ?? "null")));
|
||||
|
||||
return hasValidApiKey;
|
||||
@@ -285,7 +295,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(type => AcceptedMatchableTypes.Contains(type)).ToHashSet();
|
||||
HashSet<Steam.Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
|
||||
if (acceptedMatchableTypes.Count == 0) {
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted);
|
||||
@@ -634,6 +644,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal bool MatchEverything { get; private set; }
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "matchable_backgrounds", Required = Required.Always)]
|
||||
private byte MatchableBackgroundsNumber {
|
||||
set {
|
||||
@@ -653,7 +664,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "matchable_cards", Required = Required.Always)]
|
||||
private byte MatchableCardsNumber {
|
||||
set {
|
||||
@@ -673,7 +686,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "matchable_emoticons", Required = Required.Always)]
|
||||
private byte MatchableEmoticonsNumber {
|
||||
set {
|
||||
@@ -693,7 +708,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "matchable_foil_cards", Required = Required.Always)]
|
||||
private byte MatchableFoilCardsNumber {
|
||||
set {
|
||||
@@ -713,7 +730,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = "match_everything", Required = Required.Always)]
|
||||
private byte MatchEverythingNumber {
|
||||
set {
|
||||
@@ -733,6 +752,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
[JsonConstructor]
|
||||
private ListedUser() { }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>ASF UI</title>
|
||||
<meta http-equiv="refresh" content="0; url=http://127.0.0.1:1242">
|
||||
<meta http-equiv="refresh" content="0; url=http://localhost:1242">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -21,11 +21,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -34,15 +34,18 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
public sealed class WebBrowser : IDisposable {
|
||||
[PublicAPI]
|
||||
public const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
|
||||
|
||||
internal const byte MaxConnections = 5; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
|
||||
internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
|
||||
|
||||
private const byte ExtendedTimeoutMultiplier = 10; // Defines multiplier of timeout for WebBrowsers dealing with huge data (ASF update)
|
||||
private const byte MaxIdleTime = 15; // Defines in seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
|
||||
|
||||
internal readonly CookieContainer CookieContainer = new CookieContainer();
|
||||
[PublicAPI]
|
||||
public TimeSpan Timeout => HttpClient.Timeout;
|
||||
|
||||
internal TimeSpan Timeout => HttpClient.Timeout;
|
||||
internal readonly CookieContainer CookieContainer = new CookieContainer();
|
||||
|
||||
private readonly ArchiLogger ArchiLogger;
|
||||
private readonly HttpClient HttpClient;
|
||||
@@ -605,31 +608,21 @@ namespace ArchiSteamFarm {
|
||||
redirectUri = new Uri(requestUri, redirectUri);
|
||||
}
|
||||
|
||||
response.Dispose();
|
||||
|
||||
// Per https://tools.ietf.org/html/rfc7231#section-7.1.2, a redirect location without a fragment should inherit the fragment from the original URI
|
||||
if (!string.IsNullOrEmpty(requestUri.Fragment) && string.IsNullOrEmpty(redirectUri.Fragment)) {
|
||||
redirectUri = new UriBuilder(redirectUri) { Fragment = requestUri.Fragment }.Uri;
|
||||
}
|
||||
|
||||
// According to the RFC, POST requests in certain types of redirection must be converted into GET
|
||||
if (httpMethod == HttpMethod.Post) {
|
||||
switch (response.StatusCode) {
|
||||
case HttpStatusCode.Found:
|
||||
case HttpStatusCode.Moved:
|
||||
case HttpStatusCode.MultipleChoices:
|
||||
case HttpStatusCode.SeeOther:
|
||||
httpMethod = HttpMethod.Get;
|
||||
data = null;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
response.Dispose();
|
||||
|
||||
return await InternalRequest(redirectUri, httpMethod, data, referer, httpCompletionOption, --maxRedirections).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (response.StatusCode.IsClientErrorCode()) {
|
||||
if (Debugging.IsUserDebugging) {
|
||||
ArchiLogger.LogGenericDebug(string.Format(Strings.Content, await response.Content.ReadAsStringAsync().ConfigureAwait(false)));
|
||||
}
|
||||
|
||||
// Do not retry on client errors
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
[](https://www.patreon.com/JustArchi)
|
||||
[](https://www.paypal.me/JustArchi/5eur)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4)
|
||||
[](https://www.blockchain.com/btc/payment_request?address=1Archi6M1r5b41Rvn1SY2FfJAzsrEUT7aT)
|
||||
[](https://blockstream.info/address/bc1q8archy9jneaqw6s3cs44azt6duyqdt8c6quml0)
|
||||
[](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
|
||||
|
||||
---
|
||||
|
||||
17
SECURITY.md
Normal file
17
SECURITY.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Security policy
|
||||
|
||||
---
|
||||
|
||||
## Supported versions
|
||||
|
||||
We support **[the latest stable](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** release only. In addition to that, limited support applies to **[the latest pre-release](https://github.com/JustArchiNET/ArchiSteamFarm/releases)** version (if available). Check out our **[release cycle](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Release-cycle)** for more info.
|
||||
|
||||
---
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
We're doing our best to protect our community from all harm, therefore we take security vulnerabilities very seriously.
|
||||
|
||||
If you believe that you've found one, we'd appreciate if you let us know about it. You can do so by contacting us privately at ASF@JustArchi.net e-mail, where we'll do our best to evaluate your issue ASAP and keep you updated with the development status. If your vulnerability isn't crucial and doesn't result in a direct escalation, therefore can be known publicly while the appropriate fix is being implemented, you can also open a standard **[issue](https://github.com/JustArchiNET/ArchiSteamFarm/issues/new/choose)** instead.
|
||||
|
||||
Depending on the severity of the issue, we might take further actions in order to limit potential damage, for example by speeding up the release of the next stable ASF version. This is evaluated on a case-by-case basis.
|
||||
7
SUPPORT.md
Normal file
7
SUPPORT.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Support
|
||||
|
||||
Our **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)** is the official online documentation which covers at least a significant majority (if not all) of ASF subjects you could be interested in. We recommend to start with **[setting up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)**, **[configuration](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration)** and our **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)** which should help you with setting up ASF, configuring it, as well as answering the most common questions that you might have. For more advanced matters, as well as further elaboration, we have other pages available on our **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)** that you can visit.
|
||||
|
||||
We also have two support channels dedicated to our ASF users, in case you couldn't manage to solve the issue yourself. We answer all support and technical matters on our **[Steam group](https://steamcommunity.com/groups/archiasf/discussions/1)**, which should be used for majority of the issues and questions, especially advanced ones. In addition to that, we have a **[Discord server](https://discord.gg/hSQgt8j)**, also known as "ASF chat", which is a good choice for more casual, simple questions. You're free to use the support channel that matches your preferences, although keep in mind that you have a higher chance solving your issue on the Steam group, where we're doing our best to answer all questions that couldn't be answered by our community itself (as opposed to ASF chat where we're not active 24/7 and therefore we're not able to help everybody).
|
||||
|
||||
GitHub **[issues](https://github.com/JustArchiNET/ArchiSteamFarm/issues)** page is being used for ASF development, especially in regards to bugs and enhancements. We have a very strict policy regarding that, as GitHub is **not** a general support channel, it's dedicated exclusively to ASF development and we're not answering common ASF matters there, as we have appropriate support channels (mentioned above) for that. If you're not sure whether your matter relates to ASF development or not, we recommend to use a support channel instead. Invalid GitHub issues will be closed immediately and won't be answered.
|
||||
@@ -257,7 +257,7 @@ deploy:
|
||||
- provider: GitHub
|
||||
tag: $(appveyor_repo_tag_name)
|
||||
release: ArchiSteamFarm V$(appveyor_repo_tag_name)
|
||||
description: '### Notice\n\n**Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don''t consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Release-cycle)** if you''d like to learn more.**\n\n---\n\n### Changelog\n\nThis is automated AppVeyor GitHub deployment, human-readable changelog should be available soon. In the meantime please refer to **[GitHub commits](https://github.com/JustArchiNET/ArchiSteamFarm/commits/$(appveyor_repo_tag_name))**.\n\n---\n\n### Support\n\nASF is available for free, this release was made possible thanks to the people that decided to support the project. If you''re grateful for what we''re doing, please consider donating. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even $1 is highly appreciated and shows that you care. Thank you!\n\n [](https://www.patreon.com/JustArchi) [](https://www.paypal.me/JustArchi/5eur) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [](https://www.blockchain.com/btc/payment_request?address=1Archi6M1r5b41Rvn1SY2FfJAzsrEUT7aT) [](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)'
|
||||
description: '### Notice\n\n**Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don''t consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Release-cycle)** if you''d like to learn more.**\n\n---\n\n### Changelog\n\nThis is automated AppVeyor GitHub deployment, human-readable changelog should be available soon. In the meantime please refer to **[GitHub commits](https://github.com/JustArchiNET/ArchiSteamFarm/commits/$(appveyor_repo_tag_name))**.\n\n---\n\n### Support\n\nASF is available for free, this release was made possible thanks to the people that decided to support the project. If you''re grateful for what we''re doing, please consider donating. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even $1 is highly appreciated and shows that you care. Thank you!\n\n [](https://www.patreon.com/JustArchi) [](https://www.paypal.me/JustArchi/5eur) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [](https://blockstream.info/address/bc1q8archy9jneaqw6s3cs44azt6duyqdt8c6quml0) [](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)'
|
||||
auth_token:
|
||||
secure: uYK1wf3znNdkuQqImXR6rZ94ESgzF1vJHCAcJ75Y+m+/pc/Ro6cikzy6O7DVZ39T
|
||||
artifact: /.*/
|
||||
|
||||
Submodule tools/ArchiCrowdin updated: 6695f74e12...8aeda69935
2
wiki
2
wiki
Submodule wiki updated: b65ec70ac9...8adf2672bf
Reference in New Issue
Block a user