Compare commits

..

67 Commits

Author SHA1 Message Date
JustArchi
3e5268362b Fix auto-update when using --path 2017-10-21 23:20:23 +02:00
JustArchi
e1ddd99935 Various script improvements 2017-10-21 23:03:45 +02:00
JustArchi
811177f749 Bump 2017-10-21 21:51:29 +02:00
JustArchi
428cd63b43 Translations update 2017-10-21 16:57:39 +02:00
JustArchi
3f066cf882 Make --path <path> argument variant work too 2017-10-21 16:56:48 +02:00
Łukasz Domeradzki
e73a797f5c Create CODE_OF_CONDUCT.md 2017-10-19 13:37:31 +02:00
JustArchi
feafd16269 Add handling of --path to headless detection 2017-10-19 12:46:42 +02:00
JustArchi
d34128910b Bump 2017-10-19 11:51:36 +02:00
JustArchi
bca3624fe5 Rename !id to !ib 2017-10-19 01:37:52 +02:00
JustArchi
673b181f8d Packages update 2017-10-19 01:07:15 +02:00
JustArchi
dc08c6aace Remove workaround for https://github.com/travis-ci/travis-ci/issues/8552 2017-10-19 00:28:56 +02:00
JustArchi
919007834a Add !id, !idadd, !idrm 2017-10-19 00:13:08 +02:00
Łukasz Domeradzki
3fc8df555f Update SK2 to Beta1 (#675)
* Revert "ToRevert: SK2 Alpha10"

This reverts commit 88928466fc.

* Trigger build

* Try https://github.com/appveyor/ci/issues/1861#issuecomment-337719176 workaround
2017-10-18 22:50:01 +02:00
JustArchi
ad4e4ed92f Don't catch EXIT signal
It's not only pointless, but causes issues when scripts are being run in another set -e container.
2017-10-18 15:10:32 +02:00
JustArchi
944421e47e cc.sh/run.sh code review 2017-10-18 15:03:52 +02:00
JustArchi
00d8804607 Bump 2017-10-18 12:29:02 +02:00
JustArchi
263c7dc806 Translations update 2017-10-18 12:26:59 +02:00
JustArchi
254cd79f0a Change default of HoursUntilCardDrops
As seen in https://www.steamgifts.com/go/comment/P7chllw
2017-10-18 12:26:03 +02:00
JustArchi
4cb0ae59dd Translations update 2017-10-17 18:19:39 +02:00
JustArchi
09adad5d4e Packages update 2017-10-17 18:18:42 +02:00
JustArchi
624a61af2e Address weird AppVeyor behaviour 2017-10-15 11:45:05 +02:00
JustArchi
88928466fc ToRevert: SK2 Alpha10
Fixed AppVeyor build until https://github.com/appveyor/ci/issues/1861 is dealt with.
2017-10-15 11:42:18 +02:00
JustArchi
6881272610 Rewrite CardDropsRestricted
https://www.steamgifts.com/go/comment/eoRTMqb
2017-10-15 11:15:36 +02:00
JustArchi
b1fb216083 AppVeyor: Misc 2017-10-15 11:04:25 +02:00
JustArchi
af39c55e61 Fix build 2017-10-14 14:35:41 +02:00
JustArchi
3b64b4b900 Packages update 2017-10-14 14:09:05 +02:00
JustArchi
7b34a84442 Misc
This will accept varying number of whitespace characters
2017-10-14 11:59:07 +02:00
JustArchi
128b6d2e5b Add stdin handling for generic scripts 2017-10-14 11:32:47 +02:00
JustArchi
0c7d9bae48 Handle chmod +x for generic helper scripts 2017-10-14 07:47:51 +02:00
JustArchi
d8160df985 Bump 2017-10-14 07:12:38 +02:00
JustArchi
6b4ab6bf5e Packages update 2017-10-14 07:08:58 +02:00
JustArchi
779acf79c4 Docker misc 2017-10-14 06:59:22 +02:00
JustArchi
acae0d8d0e Improve shutdown looting
As discussed in http://steamcommunity.com/groups/ascfarm/discussions/1/1483232961032339275/ - it's possible that SendOnFarmingFinished + ShutdownOnFarmingFinished bot will shutdown before master receives the trade and accepts it through default trading preferences.

This can be easily solved by caching SteamID and using it instead. While I'm at it, let's rewrite other parts that made use of SteamID as well.
2017-10-13 09:05:08 +02:00
JustArchi
426fb12221 Translations update 2017-10-12 11:44:24 +02:00
JustArchi
459f57ea38 Packages update 2017-10-12 11:43:36 +02:00
JustArchi
97333e7eb5 Code review 2017-10-12 11:41:19 +02:00
JustArchi
0d6f0c788b Closes #670 2017-10-11 21:25:34 +02:00
JustArchi
35ad4f0f36 Remove RuntimeIdentifiers from ArchiSteamFarm.Tests
I'm not sure if this is required, tests should always run in source "dotnet build" variant and not anything being published, in worst case my CIs will poke me to add this back.
2017-10-11 00:50:25 +02:00
JustArchi
49c5a53497 Packages update 2017-10-10 21:52:44 +02:00
JustArchi
9e92d14cbe Misc 2017-10-10 21:33:50 +02:00
JustArchi
f636da6a77 Fix IPC not stopping the listener on Stop() 2017-10-10 21:29:43 +02:00
JustArchi
293d259b10 Misc 2017-10-10 03:59:49 +02:00
JustArchi
8515efd6ee Fix ASF not reacting to Docker SIGTERM
This one is tricky, trap code works properly but bash is not reacting to it when executing a binary.

> If Bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.

https://www.gnu.org/software/bash/manual/html_node/Signals.html
2017-10-10 03:49:24 +02:00
JustArchi
261b5f77a0 Bump 2017-10-08 22:44:30 +02:00
JustArchi
149a0423ec Patch https://github.com/travis-ci/travis-ci/issues/8552
And while I'm at it, bump OSX image to see if it helps.
2017-10-08 22:43:51 +02:00
JustArchi
e13341a585 Debump
I wonder if AppVeyor is fixed yet, I need my 3.0.3.5 release please.
2017-10-08 22:32:15 +02:00
JustArchi
df5efb6870 Merge branch 'master' of https://github.com/JustArchi/ArchiSteamFarm 2017-10-08 22:29:44 +02:00
JustArchi
8b60e73743 Rewrite IPC in preparation for #667 2017-10-08 22:29:43 +02:00
JustArchi
43667f3b8c Merge branch 'master' of https://github.com/JustArchi/ArchiSteamFarm 2017-10-08 15:41:13 +02:00
JustArchi
91657963fb Bump 2017-10-08 15:41:12 +02:00
Łukasz Domeradzki
88d8dfa6b4 Misc 2017-10-08 15:25:44 +02:00
JustArchi
3eab43780b Add arguments parsing to batch script
Kill me.
2017-10-08 15:14:29 +02:00
JustArchi
f210499874 Improve docker args 2017-10-08 15:08:15 +02:00
JustArchi
ad2c45adb6 Fix wrong args order 2017-10-07 20:16:27 +02:00
JustArchi
94b3e9e776 Add chmod +x permission for scripts 2017-10-07 20:01:39 +02:00
JustArchi
685e52d53f Docker: make it possible to pass arguments 2017-10-07 19:54:23 +02:00
JustArchi
5c737a286b Misc 2017-10-07 18:28:57 +02:00
JustArchi
8fa58a4841 Docker enhancements 2017-10-07 17:26:08 +02:00
JustArchi
334488919f Bump 2017-10-07 14:53:44 +02:00
JustArchi
355c8888de Address docker culture 2017-10-07 14:48:44 +02:00
JustArchi
653e528f82 Closes #665 2017-10-07 14:36:25 +02:00
JustArchi
cc4c7c9dcd Fix infinite redeem attempts
This happened when we had more keys than bots to feed, e.g. because all of them were rate limited.
2017-10-07 12:11:38 +02:00
JustArchi
a5abe9a1c3 Improve redeem efficiency with rate limited bots
If we stumble upon rate limited bot, it doesn't make any sense to retry that bot anytime soon, just skip it for the rest of the flow instead.
2017-10-07 11:07:48 +02:00
JustArchi
d184466090 Bump 2017-10-07 09:51:24 +02:00
JustArchi
2bc3a1afe0 ToRevert: Temporarily use SK2 alpha8
Should address https://github.com/JustArchi/ArchiSteamFarm/issues/665
2017-10-07 09:48:15 +02:00
JustArchi
aac4469d46 Misc
Misc improvement I noticed as part of #665
2017-10-07 06:05:01 +02:00
JustArchi
057a79824a Bump 2017-10-07 01:55:39 +02:00
37 changed files with 1077 additions and 397 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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);

View File

@@ -82,6 +82,10 @@ namespace ArchiSteamFarm {
return;
}
if (!Debugging.IsUserDebugging) {
return;
}
Logger.Debug(exception, $"{previousMethodName}()");
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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));

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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));
}
}

View File

@@ -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;
}

View File

@@ -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 },

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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": [

View 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
View 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
View 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
View 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/

View File

@@ -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
View 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
View 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
View 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
View 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"]

View File

@@ -3,7 +3,7 @@
[![Build status (Windows)](https://img.shields.io/appveyor/ci/JustArchi/ArchiSteamFarm/master.svg?label=Windows&logoWidth=0.1&maxAge=60)](https://ci.appveyor.com/project/JustArchi/ArchiSteamFarm)
[![Build status (Unix)](https://img.shields.io/travis/JustArchi/ArchiSteamFarm/master.svg?label=Unix&maxAge=60)](https://travis-ci.org/JustArchi/ArchiSteamFarm)
[![Build status (Docker)](https://img.shields.io/docker/build/justarchi/archisteamfarm.svg?label=Docker&maxAge=60)](https://hub.docker.com/r/justarchi/archisteamfarm)
[![License](https://img.shields.io/github/license/JustArchi/ArchiSteamFarm.svg?label=License&maxAge=86400)](./LICENSE-2.0.txt)
[![License](https://img.shields.io/github/license/JustArchi/ArchiSteamFarm.svg?label=License&maxAge=86400)](https://github.com/JustArchi/ArchiSteamFarm/blob/master/LICENSE-2.0.txt)
[![GitHub release](https://img.shields.io/github/release/JustArchi/ArchiSteamFarm.svg?label=Latest&maxAge=60)](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
[![Github downloads](https://img.shields.io/github/downloads/JustArchi/ArchiSteamFarm/latest/total.svg?label=Downloads&maxAge=60)](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/archisteamfarm/localized.svg)](https://github.com/JustArchi/ArchiSteamFarm/wiki/Localization)

View File

@@ -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
View File

@@ -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
View File

@@ -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