mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-31 05:30:46 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e5268362b | ||
|
|
e1ddd99935 | ||
|
|
811177f749 | ||
|
|
428cd63b43 | ||
|
|
3f066cf882 | ||
|
|
e73a797f5c | ||
|
|
feafd16269 | ||
|
|
d34128910b | ||
|
|
bca3624fe5 | ||
|
|
673b181f8d | ||
|
|
dc08c6aace | ||
|
|
919007834a | ||
|
|
3fc8df555f | ||
|
|
ad4e4ed92f | ||
|
|
944421e47e | ||
|
|
00d8804607 | ||
|
|
263c7dc806 | ||
|
|
254cd79f0a | ||
|
|
4cb0ae59dd | ||
|
|
09adad5d4e | ||
|
|
624a61af2e | ||
|
|
88928466fc | ||
|
|
6881272610 | ||
|
|
b1fb216083 | ||
|
|
af39c55e61 | ||
|
|
3b64b4b900 | ||
|
|
7b34a84442 | ||
|
|
128b6d2e5b | ||
|
|
0c7d9bae48 | ||
|
|
d8160df985 | ||
|
|
6b4ab6bf5e | ||
|
|
779acf79c4 | ||
|
|
acae0d8d0e | ||
|
|
426fb12221 | ||
|
|
459f57ea38 | ||
|
|
97333e7eb5 | ||
|
|
0d6f0c788b | ||
|
|
35ad4f0f36 | ||
|
|
49c5a53497 | ||
|
|
9e92d14cbe | ||
|
|
f636da6a77 | ||
|
|
293d259b10 | ||
|
|
8515efd6ee | ||
|
|
261b5f77a0 | ||
|
|
149a0423ec | ||
|
|
e13341a585 | ||
|
|
df5efb6870 | ||
|
|
8b60e73743 | ||
|
|
43667f3b8c | ||
|
|
91657963fb | ||
|
|
88d8dfa6b4 | ||
|
|
3eab43780b | ||
|
|
f210499874 | ||
|
|
ad2c45adb6 | ||
|
|
94b3e9e776 | ||
|
|
685e52d53f | ||
|
|
5c737a286b | ||
|
|
8fa58a4841 | ||
|
|
334488919f | ||
|
|
355c8888de | ||
|
|
653e528f82 | ||
|
|
cc4c7c9dcd | ||
|
|
a5abe9a1c3 | ||
|
|
d184466090 | ||
|
|
2bc3a1afe0 | ||
|
|
aac4469d46 | ||
|
|
057a79824a |
@@ -68,4 +68,4 @@ matrix:
|
||||
sudo: false
|
||||
- os: osx
|
||||
# Ref: https://docs.travis-ci.com/user/reference/osx/
|
||||
osx_image: xcode9
|
||||
osx_image: xcode9.1
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<ErrorReport>none</ErrorReport>
|
||||
<ApplicationIcon>ASF.ico</ApplicationIcon>
|
||||
<Copyright>Copyright © ArchiSteamFarm 2015-2017</Copyright>
|
||||
<RuntimeIdentifiers>win-x64;linux-x64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description>
|
||||
<Authors>JustArchi</Authors>
|
||||
<Company>JustArchi</Company>
|
||||
@@ -24,9 +23,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170923-02" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0-beta3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171012-09" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -57,28 +57,31 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!File.Exists(SharedInfo.VersionFile)) {
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsEmpty, SharedInfo.VersionFile));
|
||||
string targetDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
string versionFile = Path.Combine(targetDirectory, SharedInfo.VersionFile);
|
||||
|
||||
if (!File.Exists(versionFile)) {
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsEmpty, versionFile));
|
||||
return null;
|
||||
}
|
||||
|
||||
string version;
|
||||
|
||||
try {
|
||||
version = await File.ReadAllTextAsync(SharedInfo.VersionFile).ConfigureAwait(false);
|
||||
version = await File.ReadAllTextAsync(versionFile).ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(version)) {
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, SharedInfo.VersionFile));
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, versionFile));
|
||||
return null;
|
||||
}
|
||||
|
||||
version = version.TrimEnd();
|
||||
if (string.IsNullOrEmpty(version) || !IsValidVersion(version)) {
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, SharedInfo.VersionFile));
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, versionFile));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -101,8 +104,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateCheckingNewVersion);
|
||||
|
||||
string targetDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
|
||||
// Cleanup from previous update - update directory for old in-use runtime files
|
||||
string backupDirectory = Path.Combine(targetDirectory, SharedInfo.UpdateDirectory);
|
||||
if (Directory.Exists(backupDirectory)) {
|
||||
@@ -534,7 +535,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static async Task RestartOrExit() {
|
||||
if (Program.GlobalConfig.AutoRestart) {
|
||||
if (!Program.ServiceMode && Program.GlobalConfig.AutoRestart) {
|
||||
ArchiLogger.LogGenericInfo(Strings.Restarting);
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
await Program.Restart().ConfigureAwait(false);
|
||||
|
||||
@@ -82,6 +82,10 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Debugging.IsUserDebugging) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Debug(exception, $"{previousMethodName}()");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyVersion>3.0.3.2</AssemblyVersion>
|
||||
<FileVersion>3.0.3.2</FileVersion>
|
||||
<AssemblyVersion>3.0.4.0</AssemblyVersion>
|
||||
<FileVersion>3.0.4.0</FileVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ErrorReport>none</ErrorReport>
|
||||
<ApplicationIcon>ASF.ico</ApplicationIcon>
|
||||
@@ -31,15 +31,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.6.0-beta1" />
|
||||
<PackageReference Include="EmbedIO" Version="1.10.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.6.1" />
|
||||
<PackageReference Include="Humanizer" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.0.0-beta11" />
|
||||
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha9" />
|
||||
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageReference Include="SteamKit2" Version="2.0.0-Beta.1" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Principal" Version="4.3.0" />
|
||||
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1094,6 +1094,11 @@ namespace ArchiSteamFarm {
|
||||
return CachedApiKey;
|
||||
}
|
||||
|
||||
if (Bot.IsAccountLimited) {
|
||||
// API key is permanently unavailable for limited accounts
|
||||
return null;
|
||||
}
|
||||
|
||||
// We didn't fetch API key yet
|
||||
await ApiKeySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SteamConfiguration SteamConfiguration = new SteamConfiguration();
|
||||
|
||||
private static SteamConfiguration SteamConfiguration;
|
||||
|
||||
internal readonly ArchiLogger ArchiLogger;
|
||||
internal readonly ArchiWebHandler ArchiWebHandler;
|
||||
@@ -63,12 +64,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal bool CanReceiveSteamCards => !IsAccountLimited && !IsAccountLocked;
|
||||
internal bool HasMobileAuthenticator => BotDatabase?.MobileAuthenticator != null;
|
||||
internal bool IsAccountLimited => AccountFlags.HasFlag(EAccountFlags.LimitedUser) || AccountFlags.HasFlag(EAccountFlags.LimitedUserForce);
|
||||
internal bool IsConnectedAndLoggedOn => SteamID != 0;
|
||||
internal bool IsPlayingPossible => !PlayingBlocked && (LibraryLockedBySteamID == 0);
|
||||
|
||||
[JsonProperty]
|
||||
internal ulong SteamID => SteamClient?.SteamID ?? 0;
|
||||
|
||||
private readonly ArchiHandler ArchiHandler;
|
||||
private readonly BotDatabase BotDatabase;
|
||||
private readonly string BotName;
|
||||
@@ -92,13 +91,17 @@ namespace ArchiSteamFarm {
|
||||
private readonly Trading Trading;
|
||||
|
||||
private string BotPath => Path.Combine(SharedInfo.ConfigDirectory, BotName);
|
||||
private bool IsAccountLimited => AccountFlags.HasFlag(EAccountFlags.LimitedUser) || AccountFlags.HasFlag(EAccountFlags.LimitedUserForce);
|
||||
private bool IsAccountLocked => AccountFlags.HasFlag(EAccountFlags.Lockdown);
|
||||
private string SentryFile => BotPath + ".bin";
|
||||
|
||||
[JsonProperty]
|
||||
private ulong SteamID => SteamClient?.SteamID ?? 0;
|
||||
|
||||
[JsonProperty]
|
||||
internal BotConfig BotConfig { get; private set; }
|
||||
|
||||
internal ulong CachedSteamID { get; private set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal bool KeepRunning { get; private set; }
|
||||
|
||||
@@ -325,7 +328,7 @@ namespace ArchiSteamFarm {
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
if ((hoursPlayed < CardsFarmer.HoursToBump) && !BotConfig.IdleRefundableGames) {
|
||||
if ((hoursPlayed < CardsFarmer.HoursForRefund) && !BotConfig.IdleRefundableGames) {
|
||||
if (!Program.GlobalDatabase.AppIDsToPackageIDs.TryGetValue(appID, out ConcurrentHashSet<uint> packageIDs)) {
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
@@ -441,9 +444,9 @@ namespace ArchiSteamFarm {
|
||||
break;
|
||||
}
|
||||
|
||||
(uint PlayableAppID, DateTime IgnoredUntil) dlcAppData = await GetAppDataForIdling(dlcAppID, hoursPlayed, false).ConfigureAwait(false);
|
||||
if (dlcAppData.PlayableAppID != 0) {
|
||||
return (dlcAppData.PlayableAppID, DateTime.MinValue);
|
||||
(uint playableAppID, _) = await GetAppDataForIdling(dlcAppID, hoursPlayed, false).ConfigureAwait(false);
|
||||
if (playableAppID != 0) {
|
||||
return (playableAppID, DateTime.MinValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,9 +540,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamConfiguration.ProtocolTypes = protocolTypes;
|
||||
SteamConfiguration.CellID = cellID;
|
||||
SteamConfiguration.ServerListProvider = serverListProvider;
|
||||
SteamConfiguration = SteamConfiguration.Create(builder => builder.WithProtocolTypes(protocolTypes).WithCellID(cellID).WithServerListProvider(serverListProvider));
|
||||
|
||||
// Ensure that we ask for a list of servers if we don't have any saved servers available
|
||||
IEnumerable<ServerRecord> servers = await SteamConfiguration.ServerListProvider.FetchServerListAsync().ConfigureAwait(false);
|
||||
@@ -555,13 +556,24 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsBlacklistedFromTrades(ulong steamID) {
|
||||
if (steamID != 0) {
|
||||
return BotDatabase.IsBlacklistedFromTrades(steamID);
|
||||
internal bool IsBlacklistedFromIdling(uint appID) {
|
||||
if (appID == 0) {
|
||||
ArchiLogger.LogNullError(nameof(appID));
|
||||
return false;
|
||||
}
|
||||
|
||||
ArchiLogger.LogNullError(nameof(steamID));
|
||||
return false;
|
||||
bool result = BotDatabase.IsBlacklistedFromIdling(appID);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal bool IsBlacklistedFromTrades(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
ArchiLogger.LogNullError(nameof(steamID));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = BotDatabase.IsBlacklistedFromTrades(steamID);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal bool IsMaster(ulong steamID) {
|
||||
@@ -676,7 +688,7 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await ArchiWebHandler.Init(SteamID, SteamClient.Universe, callback.Nonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
|
||||
if (await ArchiWebHandler.Init(CachedSteamID, SteamClient.Universe, callback.Nonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -707,7 +719,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamFriends.RequestFriendInfo(SteamID, EClientPersonaStateFlag.PlayerName | EClientPersonaStateFlag.Presence);
|
||||
SteamFriends.RequestFriendInfo(CachedSteamID, EClientPersonaStateFlag.PlayerName | EClientPersonaStateFlag.Presence);
|
||||
}
|
||||
|
||||
internal async Task<string> Response(ulong steamID, string message, ulong chatID = 0) {
|
||||
@@ -744,6 +756,8 @@ namespace ArchiSteamFarm {
|
||||
return await ResponseFarm(steamID).ConfigureAwait(false);
|
||||
case "!HELP":
|
||||
return ResponseHelp(steamID);
|
||||
case "!IB":
|
||||
return ResponseIdleBlacklist(steamID);
|
||||
case "!IQ":
|
||||
return ResponseIdleQueue(steamID);
|
||||
case "!LEAVE":
|
||||
@@ -827,6 +841,20 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
goto default;
|
||||
case "!IB":
|
||||
return await ResponseIdleBlacklist(steamID, Utilities.GetArgsString(args, 1, ",")).ConfigureAwait(false);
|
||||
case "!IBADD":
|
||||
if (args.Length > 2) {
|
||||
return await ResponseIdleBlacklistAdd(steamID, args[1], Utilities.GetArgsString(args, 2, ",")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await ResponseIdleBlacklistAdd(steamID, Utilities.GetArgsString(args, 1, ",")).ConfigureAwait(false);
|
||||
case "!IBRM":
|
||||
if (args.Length > 2) {
|
||||
return await ResponseIdleBlacklistRemove(steamID, args[1], Utilities.GetArgsString(args, 2, ",")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await ResponseIdleBlacklistRemove(steamID, Utilities.GetArgsString(args, 1, ",")).ConfigureAwait(false);
|
||||
case "!IQ":
|
||||
return await ResponseIdleQueue(steamID, Utilities.GetArgsString(args, 1, ",")).ConfigureAwait(false);
|
||||
case "!IQADD":
|
||||
@@ -1111,7 +1139,7 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
private ulong GetFirstSteamMasterID() => BotConfig.SteamUserPermissions.Where(kv => (kv.Key != 0) && (kv.Key != SteamID) && (kv.Value == BotConfig.EPermission.Master)).Select(kv => kv.Key).OrderBy(steamID => steamID).FirstOrDefault();
|
||||
private ulong GetFirstSteamMasterID() => BotConfig.SteamUserPermissions.Where(kv => (kv.Key != 0) && (kv.Key != CachedSteamID) && (kv.Value == BotConfig.EPermission.Master)).Select(kv => kv.Key).OrderBy(steamID => steamID).FirstOrDefault();
|
||||
|
||||
private BotConfig.EPermission GetSteamUserPermission(ulong steamID) {
|
||||
if (steamID != 0) {
|
||||
@@ -1172,9 +1200,7 @@ namespace ArchiSteamFarm {
|
||||
HeartBeatFailures = 0;
|
||||
Statistics?.OnHeartBeat().Forget();
|
||||
} catch (Exception e) {
|
||||
if (Debugging.IsUserDebugging) {
|
||||
ArchiLogger.LogGenericDebugException(e);
|
||||
}
|
||||
ArchiLogger.LogGenericDebugException(e);
|
||||
|
||||
if (!KeepRunning || !IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) {
|
||||
return;
|
||||
@@ -1445,7 +1471,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
await SteamFriends.SetPersonaState(EPersonaState.Online);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
ArchiLogger.LogGenericDebugException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1850,6 +1876,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
AccountFlags = callback.AccountFlags;
|
||||
CachedSteamID = callback.ClientSteamID;
|
||||
|
||||
if (IsAccountLimited) {
|
||||
ArchiLogger.LogGenericWarning(Strings.BotAccountLimited);
|
||||
@@ -2033,7 +2060,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.FriendID == SteamID) {
|
||||
if (callback.FriendID == CachedSteamID) {
|
||||
Statistics?.OnPersonaState(callback).Forget();
|
||||
} else if ((callback.FriendID == LibraryLockedBySteamID) && (callback.GameID == 0)) {
|
||||
LibraryLockedBySteamID = 0;
|
||||
@@ -2075,13 +2102,13 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Ignore no status updates
|
||||
if (LibraryLockedBySteamID == 0) {
|
||||
if ((callback.LibraryLockedBySteamID == 0) || (callback.LibraryLockedBySteamID == SteamID)) {
|
||||
if ((callback.LibraryLockedBySteamID == 0) || (callback.LibraryLockedBySteamID == CachedSteamID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LibraryLockedBySteamID = callback.LibraryLockedBySteamID;
|
||||
} else {
|
||||
if ((callback.LibraryLockedBySteamID != 0) && (callback.LibraryLockedBySteamID != SteamID)) {
|
||||
if ((callback.LibraryLockedBySteamID != 0) && (callback.LibraryLockedBySteamID != CachedSteamID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2430,6 +2457,20 @@ namespace ArchiSteamFarm {
|
||||
return GetAPIStatus(Bots.Where(kv => bots.Contains(kv.Value) && kv.Value.IsMaster(steamID)).ToDictionary(kv => kv.Key, kv => kv.Value));
|
||||
}
|
||||
|
||||
private string ResponseBlacklist(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
ArchiLogger.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IReadOnlyCollection<ulong> blacklist = BotDatabase.GetBlacklistedFromTradesSteamIDs();
|
||||
return FormatBotResponse(blacklist.Count > 0 ? string.Join(", ", blacklist) : string.Format(Strings.ErrorIsEmpty, nameof(blacklist)));
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseBlacklist(ulong steamID, string botNames) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames));
|
||||
@@ -2461,15 +2502,6 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseBlacklist(ulong steamID) {
|
||||
if (steamID != 0) {
|
||||
return IsMaster(steamID) ? FormatBotResponse(string.Join(", ", BotDatabase.GetBlacklistedFromTradesSteamIDs())) : null;
|
||||
}
|
||||
|
||||
ArchiLogger.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseBlacklistAdd(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
@@ -2530,6 +2562,35 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseBlacklistRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] targets = targetsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
HashSet<ulong> targetIDs = new HashSet<ulong>();
|
||||
foreach (string target in targets) {
|
||||
if (!ulong.TryParse(target, out ulong targetID) || (targetID == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(targetID)));
|
||||
}
|
||||
|
||||
targetIDs.Add(targetID);
|
||||
}
|
||||
|
||||
if (targetIDs.Count == 0) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
|
||||
}
|
||||
|
||||
await BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseBlacklistRemove(ulong steamID, string botNames, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetsText)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetsText));
|
||||
@@ -2561,35 +2622,6 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseBlacklistRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] targets = targetsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
HashSet<ulong> targetIDs = new HashSet<ulong>();
|
||||
foreach (string target in targets) {
|
||||
if (!ulong.TryParse(target, out ulong targetID) || (targetID == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(targetID)));
|
||||
}
|
||||
|
||||
targetIDs.Add(targetID);
|
||||
}
|
||||
|
||||
if (targetIDs.Count == 0) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
|
||||
}
|
||||
|
||||
await BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
private static string ResponseExit(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID));
|
||||
@@ -2672,6 +2704,185 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
private string ResponseIdleBlacklist(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
ArchiLogger.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IReadOnlyCollection<uint> idleBlacklist = BotDatabase.GetIdlingBlacklistedAppIDs();
|
||||
return FormatBotResponse(idleBlacklist.Count > 0 ? string.Join(", ", idleBlacklist) : string.Format(Strings.ErrorIsEmpty, nameof(idleBlacklist)));
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseIdleBlacklist(ulong steamID, string botNames) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames));
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Bot> bots = GetBots(botNames);
|
||||
if ((bots == null) || (bots.Count == 0)) {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleBlacklist(steamID)));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
results = new List<string>(bots.Count);
|
||||
foreach (Task<string> task in tasks) {
|
||||
results.Add(await task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseIdleBlacklistAdd(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] targets = targetsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
HashSet<uint> appIDs = new HashSet<uint>();
|
||||
foreach (string target in targets) {
|
||||
if (!uint.TryParse(target, out uint appID) || (appID == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID)));
|
||||
}
|
||||
|
||||
appIDs.Add(appID);
|
||||
}
|
||||
|
||||
if (appIDs.Count == 0) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
|
||||
}
|
||||
|
||||
await BotDatabase.AddIdlingBlacklistedAppIDs(appIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseIdleBlacklistAdd(ulong steamID, string botNames, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetsText)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Bot> bots = GetBots(botNames);
|
||||
if ((bots == null) || (bots.Count == 0)) {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleBlacklistAdd(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
results = new List<string>(bots.Count);
|
||||
foreach (Task<string> task in tasks) {
|
||||
results.Add(await task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseIdleBlacklistRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] targets = targetsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
HashSet<uint> appIDs = new HashSet<uint>();
|
||||
foreach (string target in targets) {
|
||||
if (!uint.TryParse(target, out uint appID) || (appID == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID)));
|
||||
}
|
||||
|
||||
appIDs.Add(appID);
|
||||
}
|
||||
|
||||
if (appIDs.Count == 0) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
|
||||
}
|
||||
|
||||
await BotDatabase.RemoveIdlingBlacklistedAppIDs(appIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseIdleBlacklistRemove(ulong steamID, string botNames, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetsText)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Bot> bots = GetBots(botNames);
|
||||
if ((bots == null) || (bots.Count == 0)) {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleBlacklistRemove(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
results = new List<string>(bots.Count);
|
||||
foreach (Task<string> task in tasks) {
|
||||
results.Add(await task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseIdleQueue(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
ArchiLogger.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IReadOnlyCollection<uint> idleQueue = BotDatabase.GetIdlingPriorityAppIDs();
|
||||
return FormatBotResponse(idleQueue.Count > 0 ? string.Join(", ", idleQueue) : string.Format(Strings.ErrorIsEmpty, nameof(idleQueue)));
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseIdleQueue(ulong steamID, string botNames) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames));
|
||||
@@ -2703,16 +2914,6 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseIdleQueue(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
ArchiLogger.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
}
|
||||
|
||||
string result = IsMaster(steamID) ? FormatBotResponse(string.Join(", ", BotDatabase.GetIdlingPriorityAppIDs())) : null;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseIdleQueueAdd(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
@@ -2773,6 +2974,35 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseIdleQueueRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] targets = targetsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
HashSet<uint> appIDs = new HashSet<uint>();
|
||||
foreach (string target in targets) {
|
||||
if (!uint.TryParse(target, out uint appID) || (appID == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID)));
|
||||
}
|
||||
|
||||
appIDs.Add(appID);
|
||||
}
|
||||
|
||||
if (appIDs.Count == 0) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
|
||||
}
|
||||
|
||||
await BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseIdleQueueRemove(ulong steamID, string botNames, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetsText)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetsText));
|
||||
@@ -2804,35 +3034,6 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseIdleQueueRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] targets = targetsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
HashSet<uint> appIDs = new HashSet<uint>();
|
||||
foreach (string target in targets) {
|
||||
if (!uint.TryParse(target, out uint appID) || (appID == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID)));
|
||||
}
|
||||
|
||||
appIDs.Add(appID);
|
||||
}
|
||||
|
||||
if (appIDs.Count == 0) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
|
||||
}
|
||||
|
||||
await BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
private string ResponseInput(ulong steamID, string propertyName, string inputValue) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(inputValue)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(propertyName) + " || " + nameof(inputValue));
|
||||
@@ -2967,7 +3168,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(Strings.BotLootingMasterNotDefined);
|
||||
}
|
||||
|
||||
if (targetSteamMasterID == SteamID) {
|
||||
if (targetSteamMasterID == CachedSteamID) {
|
||||
return FormatBotResponse(Strings.BotLootingYourself);
|
||||
}
|
||||
|
||||
@@ -3160,7 +3361,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Dictionary<uint, string> ownedGames;
|
||||
if (await ArchiWebHandler.HasValidApiKey().ConfigureAwait(false)) {
|
||||
ownedGames = await ArchiWebHandler.GetOwnedGames(SteamID).ConfigureAwait(false);
|
||||
ownedGames = await ArchiWebHandler.GetOwnedGames(CachedSteamID).ConfigureAwait(false);
|
||||
} else {
|
||||
ownedGames = await ArchiWebHandler.GetMyOwnedGames().ConfigureAwait(false);
|
||||
}
|
||||
@@ -3497,16 +3698,17 @@ namespace ArchiSteamFarm {
|
||||
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistributing) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistributing) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing));
|
||||
bool keepMissingGames = !redeemFlags.HasFlag(ERedeemFlags.SkipKeepMissingGames) && (redeemFlags.HasFlag(ERedeemFlags.ForceKeepMissingGames) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.KeepMissingGames));
|
||||
|
||||
HashSet<string> keysList = new HashSet<string>(keys.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
HashSet<string> unusedKeys = new HashSet<string>(keysList);
|
||||
StringBuilder response = new StringBuilder();
|
||||
|
||||
HashSet<string> keysList = new HashSet<string>(keys.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
HashSet<string> unusedKeys = new HashSet<string>(keysList);
|
||||
|
||||
using (HashSet<string>.Enumerator keysEnumerator = keysList.GetEnumerator()) {
|
||||
HashSet<Bot> rateLimitedBots = new HashSet<Bot>();
|
||||
string key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Initial key
|
||||
|
||||
while (!string.IsNullOrEmpty(key)) {
|
||||
using (IEnumerator<Bot> botsEnumerator = Bots.Where(bot => (bot.Value != this) && bot.Value.IsConnectedAndLoggedOn && bot.Value.IsOperator(steamID)).OrderBy(bot => bot.Key).Select(bot => bot.Value).GetEnumerator()) {
|
||||
using (IEnumerator<Bot> botsEnumerator = Bots.Where(bot => (bot.Value != this) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && bot.Value.IsOperator(steamID)).OrderBy(bot => bot.Key).Select(bot => bot.Value).GetEnumerator()) {
|
||||
Bot currentBot = this;
|
||||
while (!string.IsNullOrEmpty(key) && (currentBot != null)) {
|
||||
if (redeemFlags.HasFlag(ERedeemFlags.Validate) && !IsValidCdKey(key)) {
|
||||
@@ -3515,7 +3717,7 @@ namespace ArchiSteamFarm {
|
||||
continue; // Keep current bot
|
||||
}
|
||||
|
||||
if (redeemFlags.HasFlag(ERedeemFlags.SkipInitial) && (currentBot == this)) {
|
||||
if ((currentBot == this) && (redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || rateLimitedBots.Contains(currentBot))) {
|
||||
currentBot = null; // Either bot will be changed, or loop aborted
|
||||
} else {
|
||||
await LimitGiftsRequestsAsync().ConfigureAwait(false);
|
||||
@@ -3525,25 +3727,25 @@ namespace ArchiSteamFarm {
|
||||
response.Append(FormatBotResponse(string.Format(Strings.BotRedeem, key, EPurchaseResultDetail.Timeout), currentBot.BotName));
|
||||
currentBot = null; // Either bot will be changed, or loop aborted
|
||||
} else {
|
||||
if (result.PurchaseResultDetail == EPurchaseResultDetail.CannotRedeemCodeFromClient) {
|
||||
// If it's a wallet code, we try to redeem it first, then handle the inner result as our primary one
|
||||
(EResult Result, EPurchaseResultDetail? PurchaseResult)? walletResult = await currentBot.ArchiWebHandler.RedeemWalletKey(key).ConfigureAwait(false);
|
||||
|
||||
if (walletResult != null) {
|
||||
result.Result = walletResult.Value.Result;
|
||||
result.PurchaseResultDetail = walletResult.Value.PurchaseResult.GetValueOrDefault(walletResult.Value.Result == EResult.OK ? EPurchaseResultDetail.NoDetail : EPurchaseResultDetail.BadActivationCode); // BadActivationCode is our smart guess in this case
|
||||
} else {
|
||||
result.Result = EResult.Timeout;
|
||||
result.PurchaseResultDetail = EPurchaseResultDetail.Timeout;
|
||||
}
|
||||
}
|
||||
|
||||
switch (result.PurchaseResultDetail) {
|
||||
case EPurchaseResultDetail.BadActivationCode:
|
||||
case EPurchaseResultDetail.CannotRedeemCodeFromClient: // Steam wallet code
|
||||
case EPurchaseResultDetail.CannotRedeemCodeFromClient:
|
||||
case EPurchaseResultDetail.DuplicateActivationCode:
|
||||
case EPurchaseResultDetail.NoDetail: // OK
|
||||
case EPurchaseResultDetail.Timeout:
|
||||
if (result.PurchaseResultDetail == EPurchaseResultDetail.CannotRedeemCodeFromClient) {
|
||||
// If it's a wallet code, try to redeem it, and forward the result
|
||||
// The result is final, there is no place for forwarding
|
||||
(EResult Result, EPurchaseResultDetail? PurchaseResult)? walletResult = await currentBot.ArchiWebHandler.RedeemWalletKey(key).ConfigureAwait(false);
|
||||
if (walletResult != null) {
|
||||
result.Result = walletResult.Value.Result;
|
||||
result.PurchaseResultDetail = walletResult.Value.PurchaseResult.GetValueOrDefault(walletResult.Value.Result == EResult.OK ? EPurchaseResultDetail.NoDetail : EPurchaseResultDetail.CannotRedeemCodeFromClient);
|
||||
} else {
|
||||
result.Result = EResult.Timeout;
|
||||
result.PurchaseResultDetail = EPurchaseResultDetail.Timeout;
|
||||
}
|
||||
}
|
||||
|
||||
if ((result.Items != null) && (result.Items.Count > 0)) {
|
||||
response.Append(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, result.Result + "/" + result.PurchaseResultDetail, string.Join("", result.Items)), currentBot.BotName));
|
||||
} else {
|
||||
@@ -3564,7 +3766,6 @@ namespace ArchiSteamFarm {
|
||||
case EPurchaseResultDetail.AccountLocked:
|
||||
case EPurchaseResultDetail.AlreadyPurchased:
|
||||
case EPurchaseResultDetail.DoesNotOwnRequiredApp:
|
||||
case EPurchaseResultDetail.RateLimited:
|
||||
case EPurchaseResultDetail.RestrictedCountry:
|
||||
if ((result.Items != null) && (result.Items.Count > 0)) {
|
||||
response.Append(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, result.Result + "/" + result.PurchaseResultDetail, string.Join("", result.Items)), currentBot.BotName));
|
||||
@@ -3584,7 +3785,7 @@ namespace ArchiSteamFarm {
|
||||
Dictionary<uint, string> items = result.Items ?? new Dictionary<uint, string>();
|
||||
|
||||
bool alreadyHandled = false;
|
||||
foreach (Bot innerBot in Bots.Where(bot => (bot.Value != currentBot) && (!redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || (bot.Value != this)) && bot.Value.IsConnectedAndLoggedOn && bot.Value.IsOperator(steamID) && ((items.Count == 0) || items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.ContainsKey(packageID)))).OrderBy(bot => bot.Key).Select(bot => bot.Value)) {
|
||||
foreach (Bot innerBot in Bots.Where(bot => (bot.Value != currentBot) && (!redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || (bot.Value != this)) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && bot.Value.IsOperator(steamID) && ((items.Count == 0) || items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.ContainsKey(packageID)))).OrderBy(bot => bot.Key).Select(bot => bot.Value)) {
|
||||
await LimitGiftsRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
ArchiHandler.PurchaseResponseCallback otherResult = await innerBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
|
||||
@@ -3600,6 +3801,9 @@ namespace ArchiSteamFarm {
|
||||
alreadyHandled = true; // This key is already handled, as we either redeemed it or we're sure it's dupe/invalid
|
||||
unusedKeys.Remove(key);
|
||||
break;
|
||||
case EPurchaseResultDetail.RateLimited:
|
||||
rateLimitedBots.Add(innerBot);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((otherResult.Items != null) && (otherResult.Items.Count > 0)) {
|
||||
@@ -3616,13 +3820,16 @@ namespace ArchiSteamFarm {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<uint, string> item in otherResult.Items) {
|
||||
foreach (KeyValuePair<uint, string> item in otherResult.Items.Where(item => !items.ContainsKey(item.Key))) {
|
||||
items[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key
|
||||
break; // Next bot (if needed)
|
||||
case EPurchaseResultDetail.RateLimited:
|
||||
rateLimitedBots.Add(currentBot);
|
||||
goto case EPurchaseResultDetail.AccountLocked;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result.PurchaseResultDetail), result.PurchaseResultDetail));
|
||||
|
||||
@@ -3640,11 +3847,17 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (!distribute && !redeemFlags.HasFlag(ERedeemFlags.SkipInitial)) {
|
||||
continue;
|
||||
// We want to change bot in two cases:
|
||||
// a) When we have distribution enabled, obviously
|
||||
// b) When we're skipping initial bot AND we have forwarding enabled, otherwise we won't get down to other accounts
|
||||
if (distribute || (forward && redeemFlags.HasFlag(ERedeemFlags.SkipInitial))) {
|
||||
currentBot = botsEnumerator.MoveNext() ? botsEnumerator.Current : null;
|
||||
}
|
||||
}
|
||||
|
||||
currentBot = botsEnumerator.MoveNext() ? botsEnumerator.Current : null;
|
||||
if (currentBot == null) {
|
||||
// We ran out of bots to try for this key, so change it
|
||||
key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4026,11 +4239,11 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatBotResponse(string.Format(Strings.BotNotFound, botNameTo)) : null;
|
||||
}
|
||||
|
||||
if (targetBot.SteamID == 0) {
|
||||
if (!targetBot.IsConnectedAndLoggedOn) {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
if (targetBot.SteamID == SteamID) {
|
||||
if (targetBot.CachedSteamID == CachedSteamID) {
|
||||
return FormatBotResponse(Strings.BotLootingYourself);
|
||||
}
|
||||
|
||||
@@ -4091,7 +4304,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
string tradeToken = null;
|
||||
|
||||
if (SteamFriends.GetFriendRelationship(targetBot.SteamID) != EFriendRelationship.Friend) {
|
||||
if (SteamFriends.GetFriendRelationship(targetBot.CachedSteamID) != EFriendRelationship.Friend) {
|
||||
tradeToken = await targetBot.ArchiWebHandler.GetTradeToken().ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
@@ -4102,14 +4315,14 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
if (!await ArchiWebHandler.SendTradeOffer(inventory, targetBot.SteamID, tradeToken).ConfigureAwait(false)) {
|
||||
if (!await ArchiWebHandler.SendTradeOffer(inventory, targetBot.CachedSteamID, tradeToken).ConfigureAwait(false)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
if (HasMobileAuthenticator) {
|
||||
// Give Steam network some time to generate confirmations
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetBot.SteamID).ConfigureAwait(false)) {
|
||||
if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetBot.CachedSteamID).ConfigureAwait(false)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
}
|
||||
@@ -4262,7 +4475,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericTrace(steamID + "/" + SteamID + ": " + message);
|
||||
ArchiLogger.LogGenericTrace(steamID + "/" + CachedSteamID + ": " + message);
|
||||
|
||||
for (int i = 0; i < message.Length; i += MaxSteamMessageLength - 2) {
|
||||
if (i > 0) {
|
||||
@@ -4284,7 +4497,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericTrace(steamID + "/" + SteamID + ": " + message);
|
||||
ArchiLogger.LogGenericTrace(steamID + "/" + CachedSteamID + ": " + message);
|
||||
|
||||
for (int i = 0; i < message.Length; i += MaxSteamMessageLength - 2) {
|
||||
if (i > 0) {
|
||||
|
||||
@@ -44,9 +44,6 @@ namespace ArchiSteamFarm {
|
||||
internal readonly bool AutoDiscoveryQueue;
|
||||
#pragma warning restore 649
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool CardDropsRestricted = true;
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty]
|
||||
internal readonly string CustomGamePlayedWhileFarming;
|
||||
@@ -83,6 +80,11 @@ namespace ArchiSteamFarm {
|
||||
internal readonly bool HandleOfflineMessages;
|
||||
#pragma warning restore 649
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly byte HoursUntilCardDrops = 3;
|
||||
#pragma warning restore 649
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool IdleRefundableGames = true;
|
||||
|
||||
@@ -36,6 +36,9 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentHashSet<uint> IdlingBlacklistedAppIDs = new ConcurrentHashSet<uint>();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
|
||||
|
||||
@@ -79,6 +82,17 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task AddIdlingBlacklistedAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdlingBlacklistedAppIDs.AddRange(appIDs)) {
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
@@ -101,8 +115,19 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<ulong> GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs;
|
||||
internal IEnumerable<uint> GetIdlingPriorityAppIDs() => IdlingPriorityAppIDs;
|
||||
internal IReadOnlyCollection<ulong> GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs;
|
||||
internal IReadOnlyCollection<uint> GetIdlingBlacklistedAppIDs() => IdlingBlacklistedAppIDs;
|
||||
internal IReadOnlyCollection<uint> GetIdlingPriorityAppIDs() => IdlingPriorityAppIDs;
|
||||
|
||||
internal bool IsBlacklistedFromIdling(uint appID) {
|
||||
if (appID == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appID));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = IdlingBlacklistedAppIDs.Contains(appID);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal bool IsBlacklistedFromTrades(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
@@ -163,6 +188,17 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task RemoveIdlingBlacklistedAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdlingBlacklistedAppIDs.RemoveRange(appIDs)) {
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
|
||||
@@ -39,7 +39,7 @@ using SteamKit2;
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CardsFarmer : IDisposable {
|
||||
internal const byte DaysForRefund = 14; // In how many days since payment we're allowed to refund
|
||||
internal const byte HoursToBump = 2; // How many hours are required for restricted accounts
|
||||
internal const byte HoursForRefund = 2; // Up to how many hours we're allowed to play for refund
|
||||
|
||||
private const byte HoursToIgnore = 24; // How many hours we ignore unreleased appIDs and don't bother checking them again
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
[JsonProperty]
|
||||
internal TimeSpan TimeRemaining => new TimeSpan(
|
||||
Bot.BotConfig.CardDropsRestricted ? (ushort) Math.Ceiling(GamesToFarm.Count / (float) ArchiHandler.MaxGamesPlayedConcurrently) * HoursToBump : 0,
|
||||
Bot.BotConfig.HoursUntilCardDrops > 0 ? (ushort) Math.Ceiling(GamesToFarm.Count / (float) ArchiHandler.MaxGamesPlayedConcurrently) * Bot.BotConfig.HoursUntilCardDrops : 0,
|
||||
30 * GamesToFarm.Sum(game => game.CardsRemaining),
|
||||
0
|
||||
);
|
||||
@@ -136,7 +136,7 @@ namespace ArchiSteamFarm {
|
||||
// If we have Complex algorithm and some games to boost, it's also worth to make a re-check, but only in this case
|
||||
// That's because we would check for new games after our current round anyway, and having extra games in the queue right away doesn't change anything
|
||||
// Therefore, there is no need for extra restart of CardsFarmer if we have no games under HoursToBump hours in current round
|
||||
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < HoursToBump)) {
|
||||
if ((Bot.BotConfig.HoursUntilCardDrops > 0) && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < Bot.BotConfig.HoursUntilCardDrops)) {
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
await StartFarming().ConfigureAwait(false);
|
||||
}
|
||||
@@ -366,7 +366,7 @@ namespace ArchiSteamFarm {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GlobalConfig.GamesBlacklist.Contains(appID) || GlobalConfig.SalesBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
|
||||
if (GlobalConfig.GamesBlacklist.Contains(appID) || GlobalConfig.SalesBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID) || Bot.IsBlacklistedFromIdling(appID)) {
|
||||
// We have this appID blacklisted, so skip it
|
||||
continue;
|
||||
}
|
||||
@@ -584,11 +584,11 @@ namespace ArchiSteamFarm {
|
||||
private async Task Farm() {
|
||||
do {
|
||||
// Now the algorithm used for farming depends on whether account is restricted or not
|
||||
if (Bot.BotConfig.CardDropsRestricted) {
|
||||
if (Bot.BotConfig.HoursUntilCardDrops > 0) {
|
||||
// If we have restricted card drops, we use complex algorithm
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex"));
|
||||
while (GamesToFarm.Count > 0) {
|
||||
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm.Count > 1 ? GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump) : GamesToFarm);
|
||||
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm.Count > 1 ? GamesToFarm.Where(game => game.HoursPlayed >= Bot.BotConfig.HoursUntilCardDrops) : GamesToFarm);
|
||||
|
||||
if (gamesToCheck.Count > 0) {
|
||||
foreach (Game game in gamesToCheck) {
|
||||
@@ -715,7 +715,7 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maxHour >= HoursToBump) {
|
||||
if (maxHour >= Bot.BotConfig.HoursUntilCardDrops) {
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(maxHour)));
|
||||
return true;
|
||||
}
|
||||
@@ -723,7 +723,7 @@ namespace ArchiSteamFarm {
|
||||
await Bot.IdleGames(games.Select(game => game.PlayableAppID)).ConfigureAwait(false);
|
||||
|
||||
bool success = true;
|
||||
while (maxHour < HoursToBump) {
|
||||
while (maxHour < Bot.BotConfig.HoursUntilCardDrops) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID))));
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.UtcNow;
|
||||
@@ -893,13 +893,13 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private async Task<bool> IsPlayableGame(Game game) {
|
||||
(uint PlayableAppID, DateTime IgnoredUntil) appData = await Bot.GetAppDataForIdling(game.AppID, game.HoursPlayed).ConfigureAwait(false);
|
||||
if (appData.PlayableAppID != 0) {
|
||||
game.PlayableAppID = appData.PlayableAppID;
|
||||
(uint playableAppID, DateTime ignoredUntil) = await Bot.GetAppDataForIdling(game.AppID, game.HoursPlayed).ConfigureAwait(false);
|
||||
if (playableAppID != 0) {
|
||||
game.PlayableAppID = playableAppID;
|
||||
return true;
|
||||
}
|
||||
|
||||
IgnoredAppIDs[game.AppID] = appData.IgnoredUntil != DateTime.MaxValue ? appData.IgnoredUntil : DateTime.UtcNow.AddHours(HoursToIgnore);
|
||||
IgnoredAppIDs[game.AppID] = ignoredUntil != DateTime.MaxValue ? ignoredUntil : DateTime.UtcNow.AddHours(HoursToIgnore);
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingGameNotPossible, game.AppID, game.GameName));
|
||||
return false;
|
||||
}
|
||||
@@ -1037,11 +1037,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (ReferenceEquals(null, obj)) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj)) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ using ArchiSteamFarm.Localization;
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Events {
|
||||
internal static async void OnBotShutdown() {
|
||||
if (IPC.KeepRunning || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
|
||||
if (Program.ServiceMode || IPC.IsRunning || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,17 +23,58 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using Unosquare.Labs.EmbedIO;
|
||||
using Unosquare.Labs.EmbedIO.Constants;
|
||||
using Unosquare.Labs.EmbedIO.Modules;
|
||||
using Unosquare.Swan;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class IPC {
|
||||
internal static bool KeepRunning { get; private set; }
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal sealed class IPCWebApiController : WebApiController {
|
||||
[WebApiHandler(HttpVerbs.Get, "/ipc")]
|
||||
public async Task<bool> ExecuteCommandObsolete(WebServer server, HttpListenerContext context) {
|
||||
if ((server == null) || (context == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(server) + " || " + nameof(context));
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly HttpListener HttpListener = new HttpListener();
|
||||
string command = context.QueryString("command");
|
||||
if (string.IsNullOrEmpty(command)) {
|
||||
await context.PlainTextResponse(string.Format(Strings.ErrorIsEmpty, nameof(command)), HttpStatusCode.BadRequest).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bot targetBot = Bot.Bots.OrderBy(bot => bot.Key).Select(bot => bot.Value).FirstOrDefault();
|
||||
if (targetBot == null) {
|
||||
await context.PlainTextResponse(Strings.ErrorNoBotsDefined, HttpStatusCode.BadRequest).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (command[0] != '!') {
|
||||
command = "!" + command;
|
||||
}
|
||||
|
||||
string content = await targetBot.Response(Program.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false);
|
||||
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCAnswered, command, content));
|
||||
|
||||
await context.PlainTextResponse(content).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class IPC {
|
||||
internal static bool IsRunning => WebServerCancellationToken?.IsCancellationRequested == false;
|
||||
|
||||
private static WebServer WebServer;
|
||||
private static CancellationTokenSource WebServerCancellationToken;
|
||||
|
||||
internal static void Initialize(string host, ushort port) {
|
||||
if (string.IsNullOrEmpty(host) || (port == 0)) {
|
||||
@@ -41,7 +82,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (HttpListener.Prefixes.Count > 0) {
|
||||
if (WebServer != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,142 +94,136 @@ namespace ArchiSteamFarm {
|
||||
break;
|
||||
}
|
||||
|
||||
string url = "http://" + host + ":" + port + "/" + nameof(IPC) + "/";
|
||||
HttpListener.Prefixes.Add(url);
|
||||
string url = "http://" + host + ":" + port + "/";
|
||||
|
||||
Terminal.Settings.DisplayLoggingMessageType = LogMessageType.None;
|
||||
|
||||
Terminal.OnLogMessageReceived += OnLogMessageReceived;
|
||||
|
||||
WebServer = new WebServer(url);
|
||||
WebServer.RegisterModule(new WebApiModule());
|
||||
WebServer.Module<WebApiModule>().RegisterController<IPCWebApiController>();
|
||||
}
|
||||
|
||||
internal static async Task<bool> PlainTextResponse(this HttpListenerContext context, string content, HttpStatusCode statusCode = HttpStatusCode.OK) {
|
||||
if ((context == null) || string.IsNullOrEmpty(content)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(context) + " || " + nameof(content));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (context.Response.StatusCode != (ushort) statusCode) {
|
||||
context.Response.StatusCode = (ushort) statusCode;
|
||||
}
|
||||
|
||||
context.Response.AppendHeader("Access-Control-Allow-Origin", "null");
|
||||
|
||||
Encoding encoding = Encoding.UTF8;
|
||||
|
||||
context.Response.ContentEncoding = encoding;
|
||||
context.Response.ContentType = "text/plain; charset=" + encoding.WebName;
|
||||
|
||||
byte[] buffer = encoding.GetBytes(content + Environment.NewLine);
|
||||
context.Response.ContentLength64 = buffer.Length;
|
||||
|
||||
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericDebugException(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void Start() {
|
||||
if (KeepRunning || (HttpListener.Prefixes.Count == 0)) {
|
||||
if (IsRunning || (WebServer == null) || (WebServer.UrlPrefixes.Count == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCStarting, HttpListener.Prefixes.First()));
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCStarting, WebServer.UrlPrefixes.First()));
|
||||
|
||||
// Fail early if we're not able to start our listener
|
||||
|
||||
try {
|
||||
HttpListener.Start();
|
||||
WebServer.Listener.Start();
|
||||
} catch (HttpListenerException e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
KeepRunning = true;
|
||||
Utilities.StartBackgroundFunction(Run);
|
||||
WebServerCancellationToken = new CancellationTokenSource();
|
||||
Utilities.StartBackgroundFunction(() => Run(WebServerCancellationToken));
|
||||
|
||||
ASF.ArchiLogger.LogGenericInfo(Strings.IPCReady);
|
||||
}
|
||||
|
||||
internal static void Stop() {
|
||||
if (!KeepRunning) {
|
||||
if (WebServerCancellationToken == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeepRunning = false;
|
||||
HttpListener.Stop();
|
||||
Release(WebServerCancellationToken);
|
||||
}
|
||||
|
||||
private static async Task HandleRequest(HttpListenerContext context) {
|
||||
if (context == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(context));
|
||||
private static void OnLogMessageReceived(object sender, LogMessageReceivedEventArgs e) {
|
||||
// Note: it's valid for sender to be null in this function
|
||||
if (e == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(e));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (Program.GlobalConfig.SteamOwnerID == 0) {
|
||||
ASF.ArchiLogger.LogGenericWarning(Strings.ErrorIPCAccessDenied);
|
||||
await context.Response.WriteAsync(HttpStatusCode.Forbidden, Strings.ErrorIPCAccessDenied).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.Request.RawUrl.StartsWith("/" + nameof(IPC), StringComparison.Ordinal)) {
|
||||
await context.Response.WriteAsync(HttpStatusCode.BadRequest, nameof(HttpStatusCode.BadRequest)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (context.Request.HttpMethod) {
|
||||
case WebRequestMethods.Http.Get:
|
||||
for (int i = 0; i < context.Request.QueryString.Count; i++) {
|
||||
string key = context.Request.QueryString.GetKey(i);
|
||||
|
||||
switch (key) {
|
||||
case "command":
|
||||
string command = context.Request.QueryString.Get(i);
|
||||
if (string.IsNullOrWhiteSpace(command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
Bot targetBot = Bot.Bots.OrderBy(bot => bot.Key).Select(bot => bot.Value).FirstOrDefault();
|
||||
if (targetBot == null) {
|
||||
await context.Response.WriteAsync(HttpStatusCode.NotAcceptable, Strings.ErrorNoBotsDefined).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command[0] != '!') {
|
||||
command = "!" + command;
|
||||
}
|
||||
|
||||
string response = await targetBot.Response(Program.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false);
|
||||
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCAnswered, command, response));
|
||||
|
||||
await context.Response.WriteAsync(HttpStatusCode.OK, response).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
await context.Response.WriteAsync(HttpStatusCode.BadRequest, nameof(HttpStatusCode.BadRequest)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Response.ContentLength64 == 0) {
|
||||
await context.Response.WriteAsync(HttpStatusCode.MethodNotAllowed, nameof(HttpStatusCode.MethodNotAllowed)).ConfigureAwait(false);
|
||||
}
|
||||
} finally {
|
||||
context.Response.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task Run() {
|
||||
while (KeepRunning && HttpListener.IsListening) {
|
||||
HttpListenerContext context;
|
||||
|
||||
try {
|
||||
context = await HttpListener.GetContextAsync().ConfigureAwait(false);
|
||||
} catch (HttpListenerException e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
Utilities.StartBackgroundFunction(() => HandleRequest(context), false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WriteAsync(this HttpListenerResponse response, HttpStatusCode statusCode, string message) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(message));
|
||||
if (string.IsNullOrEmpty(e.Message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (response.StatusCode != (ushort) statusCode) {
|
||||
response.StatusCode = (ushort) statusCode;
|
||||
string message = e.Source + " | " + e.Message;
|
||||
|
||||
switch (e.MessageType) {
|
||||
case LogMessageType.Error:
|
||||
case LogMessageType.Warning:
|
||||
ASF.ArchiLogger.LogGenericWarning(message);
|
||||
break;
|
||||
case LogMessageType.Info:
|
||||
ASF.ArchiLogger.LogGenericDebug(message);
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericTrace(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Release(CancellationTokenSource cts) {
|
||||
if (cts == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(cts));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cts == WebServerCancellationToken) {
|
||||
WebServerCancellationToken = null;
|
||||
}
|
||||
|
||||
if (!cts.IsCancellationRequested) {
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
WebServer.Listener.Stop();
|
||||
}
|
||||
|
||||
private static async Task Run(CancellationTokenSource cts) {
|
||||
if (cts == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(cts));
|
||||
return;
|
||||
}
|
||||
|
||||
using (cts) {
|
||||
while ((cts == WebServerCancellationToken) && !cts.IsCancellationRequested) {
|
||||
try {
|
||||
await WebServer.RunAsync(cts.Token).ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
Release(cts);
|
||||
}
|
||||
}
|
||||
|
||||
response.AppendHeader("Access-Control-Allow-Origin", "null");
|
||||
|
||||
Encoding encoding = Encoding.UTF8;
|
||||
|
||||
response.ContentEncoding = encoding;
|
||||
response.ContentType = "text/plain; charset=" + encoding.WebName;
|
||||
|
||||
byte[] buffer = encoding.GetBytes(message + Environment.NewLine);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
|
||||
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
response.StatusCode = (ushort) HttpStatusCode.InternalServerError;
|
||||
ASF.ArchiLogger.LogGenericDebugException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AcceptingTrade" xml:space="preserve">
|
||||
<value>Acceptere handel: {0}</value>
|
||||
<value>Accepterer handel: {0}</value>
|
||||
<comment>{0} will be replaced by trade number</comment>
|
||||
</data>
|
||||
<data name="AutoUpdateCheckInfo" xml:space="preserve">
|
||||
@@ -179,14 +179,19 @@ StackTrace:
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Kunne ikke undersøge seneste version!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Kunne ikke fortsætte med opdateringen da der ikke er noget aktiv der relaterer til den kørende version! Automatisk opdatering til den version er ikke mulig.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Kunne ikke fortsætte med en opdatering da den version ikke inkludere nogen aktiver!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Modtog en anmodning for bruger input, men processen kører i hovedløs tilstand!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>Nægter at håndtere anmodningen, fordi SteamOwnerID ikke er indstillet!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Forlader...</value>
|
||||
</data>
|
||||
@@ -236,7 +241,10 @@ StackTrace:
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Undersøger for ny version...</value>
|
||||
</data>
|
||||
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Downloader ny version: {0} ({1} MB)... Mens du venter, overvej at donere hvis du sætter pris på vores arbejde! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
<value>Opdateringsprocessen er færdig!</value>
|
||||
</data>
|
||||
@@ -275,7 +283,10 @@ StackTrace:
|
||||
<value>Indtast venligst udokumenteret værdig af {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Indtast venligst din IPC vært: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>Modtog ukendt værdi for {0}, venligst rapportér dette: {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
@@ -284,9 +295,17 @@ StackTrace:
|
||||
<value>Spille mere end {0} spil samtidigt er ikke muligt, kun første {0} poster fra {1} vil blive brugt!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Svarede IPC kommando: {0} med: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
</data>
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
<value>IPC serveren er klar!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Starter IPC server på {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Denne bot er allerede stoppet!</value>
|
||||
</data>
|
||||
@@ -646,5 +665,8 @@ StackTrace:
|
||||
<value>Færdig med rensning af Steam opdagelses kø #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Der er {0}/{1} botter der allerede ejer alle spil der kontrolleres.</value>
|
||||
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -180,7 +180,7 @@ StackTrace:
|
||||
<value>¡No se pudo comprobar la última versión!</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>No se ha podido proceder con la actualización porque no hay ninguna opcion que se relacione con la version actual! La actualización automatica a esa version no es posible.</value>
|
||||
<value>No se ha podido proceder con la actualización porque no hay ninguna opción que se relacione con la versión actual! La actualización automática a esa versión no es posible.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>¡No se puede continuar con una actualización porque esa versión no incluye ningún recurso!</value>
|
||||
@@ -189,7 +189,7 @@ StackTrace:
|
||||
<value>Recibida una solicitud de entrada del usuario, ¡pero el proceso se está ejecutando en modo servidor!</value>
|
||||
</data>
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>¡la solicitud no se puede atender porque SteamOwnerID no esta establecido!</value>
|
||||
<value>¡Solicitud denegada porque SteamOwnerID no esta establecido!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
@@ -329,7 +329,7 @@ StackTrace:
|
||||
<value>Comprobando la primera página de las insignias...</value>
|
||||
</data>
|
||||
<data name="CheckingOtherBadgePages" xml:space="preserve">
|
||||
<value>Comprobando por otras paginas de insignias...</value>
|
||||
<value>Comprobando otras paginas de insignias...</value>
|
||||
</data>
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>Algoritmo de recolección elegido: {0}</value>
|
||||
@@ -490,7 +490,7 @@ StackTrace:
|
||||
<value>El intercambio no ha podido enviarse porque no hay ningún usuario con permiso master definido!</value>
|
||||
</data>
|
||||
<data name="BotLootingNoLootableTypes" xml:space="preserve">
|
||||
<value>¡No tienes ningún tipo de conjunto que puedas traspasar!</value>
|
||||
<value>¡No tienes ningún tipo de set looteable!</value>
|
||||
</data>
|
||||
<data name="BotLootingNowDisabled" xml:space="preserve">
|
||||
<value>¡El traspaso está ahora desactivado!</value>
|
||||
@@ -562,7 +562,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
|
||||
</data>
|
||||
<data name="ErrorIsEmpty" xml:space="preserve">
|
||||
<value>¡{0} está vacía!</value>
|
||||
<value>¡{0} está vacío!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="UnusedKeys" xml:space="preserve">
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
<value>Проверка новой версии...</value>
|
||||
</data>
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Скачивание новой версии: {0} ({1} MB)... А пока, подумайте о пожертвовании, если вам нравится наша работа! ;)</value>
|
||||
<value>Загрузка новой версии: {0} ({1} MB)... Во время ожидания подумайте о пожертвовании в пользу разработчиков, если вы цените проделанную работу! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
@@ -297,7 +297,7 @@
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>На запрос IPC: {0} дан ответ: {1}</value>
|
||||
<value>На команду IPC: {0} дан ответ: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
</data>
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
@@ -667,7 +667,7 @@
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Имеются {0} / {1} ботов, которые уже владеют всеми проверенными играми.</value>
|
||||
<value>Имеются {0} / {1} ботов, которые уже владеют всеми проверяемыми играми.</value>
|
||||
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -253,7 +253,7 @@ Yığın izleme:
|
||||
<value>Yeni ASF sürümü mevcut! Güncellemeyi düşünün!</value>
|
||||
</data>
|
||||
<data name="UpdateVersionInfo" xml:space="preserve">
|
||||
<value>Yerel sürümü: {0} | Uzak sürüm: {1}</value>
|
||||
<value>Yerel sürüm: {0} | Uzak sürüm: {1}</value>
|
||||
<comment>{0} will be replaced by current version, {1} will be replaced by remote version</comment>
|
||||
</data>
|
||||
<data name="UserInputDeviceID" xml:space="preserve">
|
||||
@@ -667,7 +667,7 @@ Yığın izleme:
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>{0}/{1} bot zaten sahip olunan tüm oyunları denetliyor.</value>
|
||||
<value>Zaten sahip olunan tüm oyunları denetleyen {0}/{1} bot mevcut.</value>
|
||||
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -180,14 +180,19 @@
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Не вдалося перевірити наявність нової версії!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Неможливо провести оновлення, тому що немає файлів зв'язаних з запущеною зараз версією! Автоматичне оновлення до цієї версії неможливе.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Неможна оновитися, оскільки ця версія не містить ніяких файлів!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Отримано запит на введення даних користувачем, однак процес діє у безінтерфейсному режимі!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>Відмова від обробки запиту, оскільки SteamOwnerID не заданий!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Вихід...</value>
|
||||
</data>
|
||||
@@ -237,7 +242,10 @@
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Перевірка наявності нової версії...</value>
|
||||
</data>
|
||||
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Завантаження нової версії: {0} ({1} MB)... Під час очікування подумайте про пожертвування на користь розробників, якщо ви цінуєте виконану роботу! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
<value>Процесс оновлення закінчено!</value>
|
||||
</data>
|
||||
@@ -276,7 +284,10 @@
|
||||
<value>Будь ласка, введіть недокументоване значення {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Введіть будь ласка ваш IPC хост: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>Отримано невідоме значення для {0}, будь ласка, повідомте про це: {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
@@ -285,9 +296,17 @@
|
||||
<value>Запуск більше ніж {0} ігор одночасно - неможливий, лише перші {0} з {1} будуть задіяні!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>На IPC команду: {0} дана відповідь: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
</data>
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
<value>Сервер IPC готовий!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Запуск серверу IPC на {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Цей бот вже зупинений!</value>
|
||||
</data>
|
||||
@@ -639,7 +658,16 @@
|
||||
<value>Поточне використання пам'яті: {0} МБ.</value>
|
||||
<comment>{0} will be replaced by number (in megabytes) of memory being used</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="ClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Очищення черги знахідок Steam №{0}...</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Черга знахідок Steam №{0} очищена.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Знайдено {0}/{1} ботів які вже мають усі ігри з перевіряємих.</value>
|
||||
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal static GlobalConfig GlobalConfig { get; private set; }
|
||||
internal static GlobalDatabase GlobalDatabase { get; private set; }
|
||||
internal static bool ServiceMode { get; private set; }
|
||||
internal static WebBrowser WebBrowser { get; private set; }
|
||||
|
||||
private static readonly object ConsoleLock = new object();
|
||||
@@ -152,6 +153,28 @@ namespace ArchiSteamFarm {
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private static void HandleCryptKeyArgument(string cryptKey) {
|
||||
if (string.IsNullOrEmpty(cryptKey)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(cryptKey));
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoHelper.SetEncryptionKey(cryptKey);
|
||||
}
|
||||
|
||||
private static void HandlePathArgument(string path) {
|
||||
if (string.IsNullOrEmpty(path)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(path));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Directory.SetCurrentDirectory(path);
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task Init(string[] args) {
|
||||
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
@@ -255,7 +278,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (CultureInfo.CurrentCulture.TwoLetterISOLanguageName.Equals("en")) {
|
||||
if (string.IsNullOrEmpty(CultureInfo.CurrentCulture.Name) || CultureInfo.CurrentCulture.TwoLetterISOLanguageName.Equals("en")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -389,17 +412,47 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
bool cryptKeyNext = false;
|
||||
|
||||
foreach (string arg in args) {
|
||||
switch (arg) {
|
||||
case "":
|
||||
break;
|
||||
case "--path":
|
||||
if (cryptKeyNext) {
|
||||
goto default;
|
||||
}
|
||||
|
||||
// Not handled in PostInit
|
||||
break;
|
||||
case "--cryptkey":
|
||||
if (cryptKeyNext) {
|
||||
goto default;
|
||||
}
|
||||
|
||||
cryptKeyNext = true;
|
||||
break;
|
||||
case "--server":
|
||||
if (cryptKeyNext) {
|
||||
goto default;
|
||||
}
|
||||
|
||||
IPC.Start();
|
||||
break;
|
||||
case "--service":
|
||||
if (cryptKeyNext) {
|
||||
goto default;
|
||||
}
|
||||
|
||||
ServiceMode = true;
|
||||
break;
|
||||
default:
|
||||
if (arg.StartsWith("--", StringComparison.Ordinal)) {
|
||||
if (cryptKeyNext) {
|
||||
cryptKeyNext = false;
|
||||
HandleCryptKeyArgument(arg);
|
||||
} else if (arg.StartsWith("--", StringComparison.Ordinal)) {
|
||||
if (arg.StartsWith("--cryptkey=", StringComparison.Ordinal) && (arg.Length > 11)) {
|
||||
CryptoHelper.SetEncryptionKey(arg.Substring(11));
|
||||
HandleCryptKeyArgument(arg.Substring(11));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,14 +467,35 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
bool pathNext = false;
|
||||
|
||||
foreach (string arg in args) {
|
||||
switch (arg) {
|
||||
case "":
|
||||
break;
|
||||
case "--cryptkey":
|
||||
case "--server":
|
||||
case "--service":
|
||||
if (pathNext) {
|
||||
goto default;
|
||||
}
|
||||
|
||||
// Not handled in PreInit
|
||||
break;
|
||||
case "--path":
|
||||
if (pathNext) {
|
||||
goto default;
|
||||
}
|
||||
|
||||
pathNext = true;
|
||||
break;
|
||||
default:
|
||||
if (arg.StartsWith("--", StringComparison.Ordinal)) {
|
||||
if (pathNext) {
|
||||
pathNext = false;
|
||||
HandlePathArgument(arg);
|
||||
} else if (arg.StartsWith("--", StringComparison.Ordinal)) {
|
||||
if (arg.StartsWith("--path=", StringComparison.Ordinal) && (arg.Length > 7)) {
|
||||
Directory.SetCurrentDirectory(arg.Substring(7));
|
||||
HandlePathArgument(arg.Substring(7));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,11 +50,11 @@ namespace ArchiSteamFarm {
|
||||
private ServerRecordEndPoint() { }
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (ReferenceEquals(null, obj)) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj)) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
const string request = URL + "/api/HeartBeat";
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
||||
{ "SteamID", Bot.SteamID.ToString() },
|
||||
{ "SteamID", Bot.CachedSteamID.ToString() },
|
||||
{ "Guid", Program.GlobalDatabase.Guid.ToString("N") }
|
||||
};
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
const string request = URL + "/api/Announce";
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(8) {
|
||||
{ "SteamID", Bot.SteamID.ToString() },
|
||||
{ "SteamID", Bot.CachedSteamID.ToString() },
|
||||
{ "Guid", Program.GlobalDatabase.Guid.ToString("N") },
|
||||
{ "Nickname", nickname },
|
||||
{ "AvatarHash", avatarHash },
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (string.IsNullOrEmpty(BotName)) {
|
||||
bot = Bot.Bots.Values.FirstOrDefault(targetBot => targetBot.IsConnectedAndLoggedOn && (targetBot.SteamID != SteamID));
|
||||
bot = Bot.Bots.Values.FirstOrDefault(targetBot => targetBot.IsConnectedAndLoggedOn && (targetBot.CachedSteamID != SteamID));
|
||||
if (bot == null) {
|
||||
return;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bot.IsConnectedAndLoggedOn || (bot.SteamID == SteamID)) {
|
||||
if (!bot.IsConnectedAndLoggedOn || (bot.CachedSteamID == SteamID)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Otherwise we either accept donations but not bot trades, or we accept bot trades but not donations
|
||||
bool isBotTrade = (tradeOffer.OtherSteamID64 != 0) && Bot.Bots.Values.Any(bot => bot.SteamID == tradeOffer.OtherSteamID64);
|
||||
bool isBotTrade = (tradeOffer.OtherSteamID64 != 0) && Bot.Bots.Values.Any(bot => bot.CachedSteamID == tradeOffer.OtherSteamID64);
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, (acceptDonations && !isBotTrade) || (acceptBotTrades && isBotTrade) ? ParseTradeResult.EResult.AcceptedWithoutItemLose : ParseTradeResult.EResult.RejectedPermanently);
|
||||
}
|
||||
|
||||
|
||||
@@ -473,11 +473,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
responseMessage = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
// This exception is really common, don't bother with it unless debug mode is enabled
|
||||
if (Debugging.IsUserDebugging) {
|
||||
ArchiLogger.LogGenericDebugException(e);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericDebugException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"AcceptGifts": false,
|
||||
"AutoDiscoveryQueue": false,
|
||||
"CardDropsRestricted": true,
|
||||
"CustomGamePlayedWhileFarming": null,
|
||||
"CustomGamePlayedWhileIdle": null,
|
||||
"DismissInventoryNotifications": false,
|
||||
@@ -10,6 +9,7 @@
|
||||
"FarmOffline": false,
|
||||
"GamesPlayedWhileIdle": [],
|
||||
"HandleOfflineMessages": false,
|
||||
"HoursUntilCardDrops": 3,
|
||||
"IdleRefundableGames": true,
|
||||
"IsBotAccount": false,
|
||||
"LootableTypes": [
|
||||
|
||||
69
ArchiSteamFarm/scripts/generic/ArchiSteamFarm-Service.sh
Executable file
69
ArchiSteamFarm/scripts/generic/ArchiSteamFarm-Service.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
CONFIG_PATH="config/ASF.json"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
SCRIPT_DIR="$(pwd)"
|
||||
SCRIPT_PATH="${SCRIPT_DIR}/${0}"
|
||||
|
||||
BINARY="${SCRIPT_DIR}/ArchiSteamFarm.dll"
|
||||
BINARY_ARGS=()
|
||||
|
||||
PATH_NEXT=0
|
||||
|
||||
PARSE_ARG() {
|
||||
BINARY_ARGS+=("$1")
|
||||
|
||||
case "$1" in
|
||||
--cryptkey|--server|--service) ;;
|
||||
--path) PATH_NEXT=1 ;;
|
||||
--path=*) cd "$(echo "$1" | cut -d '=' -f 2-)" ;;
|
||||
*)
|
||||
if [[ "$PATH_NEXT" -eq 1 ]]; then
|
||||
PATH_NEXT=0
|
||||
cd "$1"
|
||||
fi
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ -n "${ASF_ARGS-}" ]]; then
|
||||
for ARG in $ASF_ARGS; do
|
||||
if [[ -n "$ARG" ]]; then
|
||||
PARSE_ARG "$ARG"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
for ARG in "$@"; do
|
||||
if [[ -n "$ARG" ]]; then
|
||||
PARSE_ARG "$ARG"
|
||||
fi
|
||||
done
|
||||
|
||||
CONFIG_PATH="$(pwd)/${CONFIG_PATH}"
|
||||
|
||||
# Kill underlying ASF process on shell process exit
|
||||
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM
|
||||
|
||||
if ! hash dotnet 2>/dev/null; then
|
||||
echo "ERROR: dotnet CLI tools are not installed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet --info
|
||||
|
||||
while :; do
|
||||
if [[ -f "$CONFIG_PATH" ]] && grep -Eq '"Headless":\s+?true' "$CONFIG_PATH"; then
|
||||
# We're running ASF in headless mode so we don't need STDIN
|
||||
dotnet "$BINARY" "${BINARY_ARGS[@]}" & # Start ASF in the background, trap will work properly due to non-blocking call
|
||||
wait $! # This will forward dotnet error code, set -e will abort the script if it's non-zero
|
||||
else
|
||||
# We're running ASF in non-headless mode, so we need STDIN to be operative
|
||||
dotnet "$BINARY" "${BINARY_ARGS[@]}" # Start ASF in the foreground, trap sadly won't work until process exit
|
||||
fi
|
||||
|
||||
chmod +x "$SCRIPT_PATH" # If ASF exited by itself, we need to ensure that our script is still set to +x after auto-update
|
||||
sleep 1
|
||||
done
|
||||
8
ArchiSteamFarm/scripts/generic/ArchiSteamFarm.cmd
Normal file → Executable file
8
ArchiSteamFarm/scripts/generic/ArchiSteamFarm.cmd
Normal file → Executable file
@@ -1,3 +1,9 @@
|
||||
@echo off
|
||||
pushd %~dp0
|
||||
dotnet ArchiSteamFarm.dll
|
||||
|
||||
SETLOCAL
|
||||
SET ASF_ARGS=%ASF_ARGS% %*
|
||||
|
||||
dotnet --info
|
||||
|
||||
dotnet ArchiSteamFarm.dll %ASF_ARGS%
|
||||
|
||||
64
ArchiSteamFarm/scripts/generic/ArchiSteamFarm.sh
Normal file → Executable file
64
ArchiSteamFarm/scripts/generic/ArchiSteamFarm.sh
Normal file → Executable file
@@ -1,4 +1,66 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
CONFIG_PATH="config/ASF.json"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
dotnet ArchiSteamFarm.dll
|
||||
|
||||
SCRIPT_DIR="$(pwd)"
|
||||
SCRIPT_PATH="${SCRIPT_DIR}/${0}"
|
||||
|
||||
BINARY="${SCRIPT_DIR}/ArchiSteamFarm.dll"
|
||||
BINARY_ARGS=()
|
||||
|
||||
PATH_NEXT=0
|
||||
|
||||
PARSE_ARG() {
|
||||
BINARY_ARGS+=("$1")
|
||||
|
||||
case "$1" in
|
||||
--cryptkey|--server|--service) ;;
|
||||
--path) PATH_NEXT=1 ;;
|
||||
--path=*) cd "$(echo "$1" | cut -d '=' -f 2-)" ;;
|
||||
*)
|
||||
if [[ "$PATH_NEXT" -eq 1 ]]; then
|
||||
PATH_NEXT=0
|
||||
cd "$1"
|
||||
fi
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ -n "${ASF_ARGS-}" ]]; then
|
||||
for ARG in $ASF_ARGS; do
|
||||
if [[ -n "$ARG" ]]; then
|
||||
PARSE_ARG "$ARG"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
for ARG in "$@"; do
|
||||
if [[ -n "$ARG" ]]; then
|
||||
PARSE_ARG "$ARG"
|
||||
fi
|
||||
done
|
||||
|
||||
CONFIG_PATH="$(pwd)/${CONFIG_PATH}"
|
||||
|
||||
# Kill underlying ASF process on shell process exit
|
||||
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM
|
||||
|
||||
if ! hash dotnet 2>/dev/null; then
|
||||
echo "ERROR: dotnet CLI tools are not installed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet --info
|
||||
|
||||
if [[ -f "$CONFIG_PATH" ]] && grep -Eq '"Headless":\s+?true' "$CONFIG_PATH"; then
|
||||
# We're running ASF in headless mode so we don't need STDIN
|
||||
dotnet "$BINARY" "${BINARY_ARGS[@]}" & # Start ASF in the background, trap will work properly due to non-blocking call
|
||||
wait $! # This will forward dotnet error code, set -e will abort the script if it's non-zero
|
||||
else
|
||||
# We're running ASF in non-headless mode, so we need STDIN to be operative
|
||||
dotnet "$BINARY" "${BINARY_ARGS[@]}" # Start ASF in the foreground, trap won't work until process exit
|
||||
fi
|
||||
|
||||
chmod +x "$SCRIPT_PATH" # If ASF exited by itself, we need to ensure that our script is still set to +x after auto-update
|
||||
|
||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ASF@JustArchi.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -1,9 +0,0 @@
|
||||
FROM microsoft/dotnet:2.0-sdk AS build-env
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN dotnet publish ArchiSteamFarm -c Release -o out /nologo
|
||||
|
||||
FROM microsoft/dotnet:2.0-runtime
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app/ArchiSteamFarm/out ./
|
||||
ENTRYPOINT ["dotnet", "ArchiSteamFarm.dll"]
|
||||
13
Dockerfile.Service.arm
Normal file
13
Dockerfile.Service.arm
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM microsoft/dotnet:2.0-sdk AS build-env
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN dotnet publish ArchiSteamFarm -c Release -o out /nologo && \
|
||||
echo "generic" > "ArchiSteamFarm/out/ArchiSteamFarm.version" && \
|
||||
cp "ArchiSteamFarm/scripts/generic/ArchiSteamFarm-Service.sh" "ArchiSteamFarm/out/ArchiSteamFarm-Service.sh"
|
||||
|
||||
FROM microsoft/dotnet:2.0-runtime-stretch-arm32v7
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app/ArchiSteamFarm/out ./
|
||||
ENTRYPOINT ["./ArchiSteamFarm-Service.sh", "--service"]
|
||||
13
Dockerfile.Service.x64
Normal file
13
Dockerfile.Service.x64
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM microsoft/dotnet:2.0-sdk AS build-env
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN dotnet publish ArchiSteamFarm -c Release -o out /nologo && \
|
||||
echo "generic" > "ArchiSteamFarm/out/ArchiSteamFarm.version" && \
|
||||
cp "ArchiSteamFarm/scripts/generic/ArchiSteamFarm-Service.sh" "ArchiSteamFarm/out/ArchiSteamFarm-Service.sh"
|
||||
|
||||
FROM microsoft/dotnet:2.0-runtime
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app/ArchiSteamFarm/out ./
|
||||
ENTRYPOINT ["./ArchiSteamFarm-Service.sh", "--service"]
|
||||
12
Dockerfile.arm
Normal file
12
Dockerfile.arm
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM microsoft/dotnet:2.0-sdk AS build-env
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN dotnet publish ArchiSteamFarm -c Release -o out /nologo && \
|
||||
cp "ArchiSteamFarm/scripts/generic/ArchiSteamFarm.sh" "ArchiSteamFarm/out/ArchiSteamFarm.sh"
|
||||
|
||||
FROM microsoft/dotnet:2.0-runtime-stretch-arm32v7
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app/ArchiSteamFarm/out ./
|
||||
ENTRYPOINT ["./ArchiSteamFarm.sh", "--service"]
|
||||
12
Dockerfile.x64
Normal file
12
Dockerfile.x64
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM microsoft/dotnet:2.0-sdk AS build-env
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN dotnet publish ArchiSteamFarm -c Release -o out /nologo && \
|
||||
cp "ArchiSteamFarm/scripts/generic/ArchiSteamFarm.sh" "ArchiSteamFarm/out/ArchiSteamFarm.sh"
|
||||
|
||||
FROM microsoft/dotnet:2.0-runtime
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app/ArchiSteamFarm/out ./
|
||||
ENTRYPOINT ["./ArchiSteamFarm.sh", "--service"]
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://ci.appveyor.com/project/JustArchi/ArchiSteamFarm)
|
||||
[](https://travis-ci.org/JustArchi/ArchiSteamFarm)
|
||||
[](https://hub.docker.com/r/justarchi/archisteamfarm)
|
||||
[](./LICENSE-2.0.txt)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/blob/master/LICENSE-2.0.txt)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/wiki/Localization)
|
||||
|
||||
24
appveyor.yml
24
appveyor.yml
@@ -18,8 +18,12 @@ matrix:
|
||||
fast_finish: true
|
||||
install:
|
||||
- ps: >-
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
dotnet --info
|
||||
|
||||
$env:DOTNET_INSTALL_DIR = "$pwd\.dotnetcli"
|
||||
@@ -32,29 +36,49 @@ install:
|
||||
.\scripts\obtain\dotnet-install.ps1 -Channel "$env:DOTNET_CHANNEL" -InstallDir "$env:DOTNET_INSTALL_DIR"
|
||||
before_build:
|
||||
- ps: >-
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
dotnet --info
|
||||
|
||||
nuget sources update -Name nuget.org -Source https://api.nuget.org/v3/index.json
|
||||
|
||||
dotnet restore
|
||||
build_script:
|
||||
- ps: >-
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
dotnet build -c "$env:CONFIGURATION" -o 'out\source' --no-restore /nologo
|
||||
test_script:
|
||||
- ps: >-
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
dotnet test ArchiSteamFarm.Tests -c "$env:CONFIGURATION" -o 'out\source' --no-build --no-restore
|
||||
after_test:
|
||||
- ps: >-
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$PublishBlock = {
|
||||
param($RUNTIME)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
Set-Location -Path "$env:APPVEYOR_BUILD_FOLDER"
|
||||
|
||||
|
||||
40
cc.sh
40
cc.sh
@@ -1,38 +1,41 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
PROJECT="ArchiSteamFarm"
|
||||
SOLUTION="ArchiSteamFarm.sln"
|
||||
CONFIGURATION="Release"
|
||||
OUT="out/source"
|
||||
|
||||
SOLUTION="${PROJECT}.sln"
|
||||
CONFIGURATION="Release"
|
||||
PROJECTS=("ArchiSteamFarm")
|
||||
|
||||
CLEAN=0
|
||||
TEST=1
|
||||
|
||||
PRINT_USAGE() {
|
||||
echo "Usage: $0 [--clean] [debug/release]"
|
||||
exit 1
|
||||
}
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
for ARG in "$@"; do
|
||||
case "$ARG" in
|
||||
release|Release) CONFIGURATION="Release" ;;
|
||||
debug|Debug) CONFIGURATION="Debug" ;;
|
||||
--clean) CLEAN=1 ;;
|
||||
*) PRINT_USAGE
|
||||
--no-test) TEST=0 ;;
|
||||
*) echo "Usage: $0 [--clean] [--no-test] [debug/release]"; exit 1
|
||||
esac
|
||||
done
|
||||
|
||||
if ! hash dotnet &>/dev/null; then
|
||||
if [[ "$TEST" -eq 1 ]]; then
|
||||
PROJECTS+=("ArchiSteamFarm.Tests")
|
||||
fi
|
||||
|
||||
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM
|
||||
|
||||
if ! hash dotnet 2>/dev/null; then
|
||||
echo "ERROR: dotnet CLI tools are not installed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet --info
|
||||
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
if [[ -d ".git" ]] && hash git &>/dev/null; then
|
||||
if [[ -d ".git" ]] && hash git 2>/dev/null; then
|
||||
git pull || true
|
||||
fi
|
||||
|
||||
@@ -42,14 +45,19 @@ if [[ ! -f "$SOLUTION" ]]; then
|
||||
fi
|
||||
|
||||
if [[ "$CLEAN" -eq 1 ]]; then
|
||||
dotnet clean -c "$CONFIGURATION" -o "$OUT"
|
||||
rm -rf "ArchiSteamFarm/${OUT}" "ArchiSteamFarm.Tests/${OUT}"
|
||||
dotnet clean "${PROJECTS[@]}" -c "$CONFIGURATION" -o "$OUT"
|
||||
|
||||
for PROJECT in "${PROJECTS[@]}"; do
|
||||
rm -rf "${PROJECT:?}/${OUT}"
|
||||
done
|
||||
fi
|
||||
|
||||
dotnet restore
|
||||
dotnet build "${PROJECTS[@]}" -c "$CONFIGURATION" -o "$OUT" --no-restore /nologo
|
||||
|
||||
dotnet build -c "$CONFIGURATION" -o "$OUT" --no-restore /nologo
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -o "$OUT" --no-build --no-restore
|
||||
if [[ "$TEST" -eq 1 ]]; then
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -o "$OUT" --no-build --no-restore
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Compilation finished successfully! :)"
|
||||
|
||||
79
run.sh
79
run.sh
@@ -1,52 +1,63 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
PROJECT="ArchiSteamFarm"
|
||||
OUT="out/source"
|
||||
CONFIG_PATH="config/ASF.json"
|
||||
|
||||
BINARY="${PROJECT}/${OUT}/${PROJECT}.dll"
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
ASF_ARGS=("")
|
||||
UNTIL_CLEAN_EXIT=0
|
||||
SCRIPT_DIR="$(pwd)"
|
||||
|
||||
PRINT_USAGE() {
|
||||
echo "Usage: $0 [--until-clean-exit] [--cryptkey=] [--path=] [--server]"
|
||||
exit 1
|
||||
BINARY="${SCRIPT_DIR}/$ArchiSteamFarm/out/source/$ArchiSteamFarm.dll"
|
||||
BINARY_ARGS=()
|
||||
|
||||
PATH_NEXT=0
|
||||
|
||||
PARSE_ARG() {
|
||||
BINARY_ARGS+=("$1")
|
||||
|
||||
case "$1" in
|
||||
--cryptkey|--server|--service) ;;
|
||||
--path) PATH_NEXT=1 ;;
|
||||
--path=*) cd "$(echo "$1" | cut -d '=' -f 2-)" ;;
|
||||
*)
|
||||
if [[ "$PATH_NEXT" -eq 1 ]]; then
|
||||
PATH_NEXT=0
|
||||
cd "$1"
|
||||
fi
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ -n "${ASF_ARGS-}" ]]; then
|
||||
for ARG in $ASF_ARGS; do
|
||||
if [[ -n "$ARG" ]]; then
|
||||
PARSE_ARG "$ARG"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
for ARG in "$@"; do
|
||||
case "$ARG" in
|
||||
--cryptkey=*) ASF_ARGS+=("$ARG") ;;
|
||||
--path=*) ASF_ARGS+=("$ARG") ;;
|
||||
--server) ASF_ARGS+=("$ARG") ;;
|
||||
--until-clean-exit) UNTIL_CLEAN_EXIT=1 ;;
|
||||
*) PRINT_USAGE
|
||||
esac
|
||||
if [[ -n "$ARG" ]]; then
|
||||
PARSE_ARG "$ARG"
|
||||
fi
|
||||
done
|
||||
|
||||
if ! hash dotnet &>/dev/null; then
|
||||
CONFIG_PATH="$(pwd)/${CONFIG_PATH}"
|
||||
|
||||
# Kill underlying ASF process on shell process exit
|
||||
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM
|
||||
|
||||
if ! hash dotnet 2>/dev/null; then
|
||||
echo "ERROR: dotnet CLI tools are not installed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet --info
|
||||
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
if [[ ! -f "$BINARY" ]]; then
|
||||
echo "ERROR: $BINARY could not be found!"
|
||||
exit 1
|
||||
if [[ -f "$CONFIG_PATH" ]] && grep -Eq '"Headless":\s+?true' "$CONFIG_PATH"; then
|
||||
# We're running ASF in headless mode so we don't need STDIN
|
||||
dotnet exec "$BINARY" "${BINARY_ARGS[@]}" & # Start ASF in the background, trap will work properly due to non-blocking call
|
||||
wait $! # This will forward dotnet error code, set -e will abort the script if it's non-zero
|
||||
else
|
||||
# We're running ASF in non-headless mode, so we need STDIN to be operative
|
||||
dotnet exec "$BINARY" "${BINARY_ARGS[@]}" # Start ASF in the foreground, trap won't work until process exit
|
||||
fi
|
||||
|
||||
if [[ "$UNTIL_CLEAN_EXIT" -eq 0 ]]; then
|
||||
dotnet exec "$BINARY" "${ASF_ARGS[@]}"
|
||||
exit $? # In this case $? can only be 0 because otherwise set -e terminates the script
|
||||
fi
|
||||
|
||||
while [[ -f "$BINARY" ]]; do
|
||||
if dotnet exec "$BINARY" "${ASF_ARGS[@]}"; then
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user