Compare commits

..

64 Commits

Author SHA1 Message Date
JustArchi
5ebf2bf891 Bump to 2.2.0.0
Some misc breaking changes justify this bump, not to mention that we're close to reaching that anyway
2016-12-25 07:02:56 +01:00
JustArchi
ec2c78ea81 Unify Forwarding and Distributing under RedeemingPreferences
Similar to TradingPreferences
2016-12-25 06:29:13 +01:00
JustArchi
eb88814c72 Adapt structure of JSON files to the one generated by sorted ConfigGenerator 2016-12-25 06:04:29 +01:00
JustArchi
a28ad1fdac Misc 2016-12-25 05:53:47 +01:00
JustArchi
d36eaebbfe Closes #383 2016-12-25 05:52:17 +01:00
JustArchi
55067c669e Closes #382 2016-12-24 23:12:40 +01:00
JustArchi
9d882784ee Implement workaround for #380 2016-12-24 19:27:36 +01:00
JustArchi
829bd8ce2f Adapt @Pandiora idea
See: 6038faaa5f (commitcomment-20287032)
2016-12-23 20:14:52 +01:00
JustArchi
532808c65d Bump 2016-12-23 19:26:30 +01:00
JustArchi
cc317e10a8 Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... 😢
2016-12-23 19:04:36 +01:00
JustArchi
8aaee38a85 SteamStoreURL is const now
Also explain why
2016-12-23 18:51:45 +01:00
JustArchi
30c69cf57c Move main ArchiLogger from ASF to Program
It makes more sense to put it in ASF class due to sharing potential, but I want to unify ArchiBoT logging and this makes it easier for maintenance
2016-12-23 18:49:52 +01:00
JustArchi
4219107d2b Closes #376 2016-12-23 04:03:51 +01:00
JustArchi
ea89ef6696 Replace GetUnixTime() with more elegant .NET 4.6 alternative 2016-12-23 03:54:46 +01:00
JustArchi
4b1fc7ae56 Misc 2016-12-23 03:47:12 +01:00
JustArchi
6038faaa5f I like my Steam account 2016-12-23 03:37:29 +01:00
JustArchi
e3f87e4a19 Adapt delays for lower misses 2016-12-23 03:24:37 +01:00
JustArchi
90d74de169 [EXPERIMENTAL] Queue discovery
Yes, you know my donation address
2016-12-23 03:01:37 +01:00
JustArchi
b781195b77 Respect AutoRestart in ASF.json changes 2016-12-21 23:25:36 +01:00
JustArchi
c74d4c857e Bump 2016-12-19 04:59:29 +01:00
JustArchi
f1e67ee333 Make older Mono being able to access Statistics server 2016-12-19 04:45:30 +01:00
JustArchi
8d89095670 Misc 2016-12-18 16:10:01 +01:00
JustArchi
7fe65144c1 Code cleanup 2016-12-18 16:03:32 +01:00
JustArchi
4404874e52 Misc 2016-12-18 15:51:20 +01:00
JustArchi
a1cf8c91c2 Misc
progressNode is actually null from time to time, no need to mark it as error if we're aware of this Steam fuckup
2016-12-18 15:49:06 +01:00
JustArchi
b7eac82596 Misc 2016-12-17 03:28:39 +01:00
JustArchi
b73bdee5af Bump 2016-12-17 03:23:29 +01:00
JustArchi
51ce849aee Misc 2016-12-17 02:28:28 +01:00
JustArchi
78af201cdb Skip reporting non-STM accounts
Initially I wanted to use this to build reliable statistics as in, number of active/inactive accounts, total accounts and so on
However, I don't really think my server will be able to handle it with more and more users, even if reporting that was active so far was still bare minimum.
Let's just stick to STM-only reporting for now, I don't have enough money for anything more advanced for now.
2016-12-17 02:27:14 +01:00
JustArchi
a580b6ab61 Bump 2016-12-16 15:33:19 +01:00
JustArchi
3cb24e19d9 Stop killing trade bots 2016-12-16 15:30:52 +01:00
JustArchi
399d438f8d Bump 2016-12-16 15:05:51 +01:00
JustArchi
00fb1b7c69 Misc 2016-12-16 15:02:41 +01:00
JustArchi
f477b79fa2 Fix cross-OS WCF incompatibility
Mono really struggles with this and can't respond to windows clients
2016-12-16 14:55:33 +01:00
JustArchi
8ead6be751 Fix CG generation of WCFHost 2016-12-16 14:20:32 +01:00
JustArchi
e7912a05e7 Bump 2016-12-15 04:53:16 +01:00
JustArchi
1bb19415d4 Closes #371 2016-12-15 03:38:16 +01:00
JustArchi
aa063425ce Bump 2016-12-15 02:26:16 +01:00
JustArchi
e5a39cc0de EXPERIMENTAL: Hopefully fixes #374 2016-12-15 01:56:13 +01:00
JustArchi
6460918bdc Allow Mono failures
It SIGSEGVs too often to consider it reliable test during compilation. I want to keep mono status around, especially for ASF badges in README, but I don't want to see commits failing because latest Mono segfaulted.
2016-12-12 21:44:20 +01:00
JustArchi
0d2f3f09b0 Add limited info 2016-12-12 15:35:21 +01:00
JustArchi
711d573e28 Misc 2016-12-11 18:16:55 +01:00
JustArchi
e90780ddac Fix WCF on Mono
It doesn't support metadata in tcp binding
2016-12-11 17:58:53 +01:00
JustArchi
cf4e9172ee Closes #373 2016-12-11 16:56:33 +01:00
JustArchi
9d0cc07d4e Reject clan invites when IsBotAccount
Also make it possible for quite rare situation of ASF accepting invite to SteamMasterClanID if it couldn't join due to group being non-public
2016-12-11 04:23:44 +01:00
JustArchi
8eeab55d0b Adapt debug routines to debug log level 2016-12-08 03:47:07 +01:00
JustArchi
31387fe6a1 Fix GamesPlayedWhileIdle for Paused CardsFarmer 2016-12-08 03:27:25 +01:00
JustArchi
556a56f2d3 Start logging failing requests on debug level now 2016-12-08 01:02:45 +01:00
JustArchi
82a992ee9c Misc 2016-12-08 00:43:09 +01:00
JustArchi
be01239114 More stability improvements 2016-12-08 00:37:42 +01:00
JustArchi
7b74577ce0 Misc 2016-12-07 19:08:36 +01:00
JustArchi
99cc57616c Bump 2016-12-07 14:49:59 +01:00
JustArchi
0af6d48a3c Revert "Misc"
This reverts commit bbe38e34ee.
2016-12-07 14:27:59 +01:00
JustArchi
bbe38e34ee Misc 2016-12-07 14:20:58 +01:00
JustArchi
e937579c46 Add unique ASF ID 2016-12-07 14:05:19 +01:00
JustArchi
ac69473fb3 Fix GamesPlayedWhileIdle with family sharing 2016-12-05 00:24:11 +01:00
JustArchi
fb1f2910dd Bump 2016-12-04 20:55:57 +01:00
JustArchi
1a917a668c Misc 2016-12-04 20:41:01 +01:00
JustArchi
065d29177a Misc 2016-12-04 15:31:04 +01:00
JustArchi
b5cc74ca42 Misc 2016-12-04 15:30:00 +01:00
JustArchi
24ad984568 Bump 2016-12-04 08:22:42 +01:00
JustArchi
d48e0b06b6 Don't emit heartbeats when not required
This way people won't kill my server with DDoS...
2016-12-04 08:20:18 +01:00
JustArchi
cb0d1c9484 Misc 2016-12-04 06:01:18 +01:00
JustArchi
c75d99eb86 Bump 2016-12-04 05:53:09 +01:00
43 changed files with 1327 additions and 532 deletions

View File

@@ -15,6 +15,7 @@ matrix:
- mono: weekly
# - mono: alpha
# - mono: beta
- mono: latest
before_script:
- source mono_envsetup.sh

View File

@@ -34,8 +34,6 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal static class ASF {
internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF);
private static readonly ConcurrentDictionary<Bot, DateTime> LastWriteTimes = new ConcurrentDictionary<Bot, DateTime>();
private static Timer AutoUpdatesTimer;
@@ -44,7 +42,7 @@ namespace ArchiSteamFarm {
internal static async Task CheckForUpdate(bool updateOverride = false) {
string exeFile = Assembly.GetEntryAssembly().Location;
if (string.IsNullOrEmpty(exeFile)) {
ArchiLogger.LogNullError(nameof(exeFile));
Program.ArchiLogger.LogNullError(nameof(exeFile));
return;
}
@@ -58,8 +56,8 @@ namespace ArchiSteamFarm {
try {
File.Delete(oldExeFile);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericError("Could not remove old ASF binary, please remove " + oldExeFile + " manually in order for update function to work!");
Program.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericError("Could not remove old ASF binary, please remove " + oldExeFile + " manually in order for update function to work!");
}
}
@@ -72,7 +70,7 @@ namespace ArchiSteamFarm {
TimeSpan.FromDays(1) // Period
);
ArchiLogger.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
Program.ArchiLogger.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
}
string releaseURL = SharedInfo.GithubReleaseURL;
@@ -80,20 +78,20 @@ namespace ArchiSteamFarm {
releaseURL += "/latest";
}
ArchiLogger.LogGenericInfo("Checking new version...");
Program.ArchiLogger.LogGenericInfo("Checking new version...");
GitHub.ReleaseResponse releaseResponse;
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
releaseResponse = await Program.WebBrowser.UrlGetToJsonResultRetry<GitHub.ReleaseResponse>(releaseURL).ConfigureAwait(false);
if (releaseResponse == null) {
ArchiLogger.LogGenericWarning("Could not check latest version!");
Program.ArchiLogger.LogGenericWarning("Could not check latest version!");
return;
}
} else {
List<GitHub.ReleaseResponse> releases = await Program.WebBrowser.UrlGetToJsonResultRetry<List<GitHub.ReleaseResponse>>(releaseURL).ConfigureAwait(false);
if ((releases == null) || (releases.Count == 0)) {
ArchiLogger.LogGenericWarning("Could not check latest version!");
Program.ArchiLogger.LogGenericWarning("Could not check latest version!");
return;
}
@@ -101,33 +99,33 @@ namespace ArchiSteamFarm {
}
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
ArchiLogger.LogGenericWarning("Could not check latest version!");
Program.ArchiLogger.LogGenericWarning("Could not check latest version!");
return;
}
Version newVersion = new Version(releaseResponse.Tag);
ArchiLogger.LogGenericInfo("Local version: " + SharedInfo.Version + " | Remote version: " + newVersion);
Program.ArchiLogger.LogGenericInfo("Local version: " + SharedInfo.Version + " | Remote version: " + newVersion);
if (SharedInfo.Version.CompareTo(newVersion) >= 0) { // If local version is the same or newer than remote version
return;
}
if (!updateOverride && !Program.GlobalConfig.AutoUpdates) {
ArchiLogger.LogGenericInfo("New version is available!");
ArchiLogger.LogGenericInfo("Consider updating yourself!");
Program.ArchiLogger.LogGenericInfo("New version is available!");
Program.ArchiLogger.LogGenericInfo("Consider updating yourself!");
await Task.Delay(5000).ConfigureAwait(false);
return;
}
if (File.Exists(oldExeFile)) {
ArchiLogger.LogGenericWarning("Refusing to proceed with auto update as old " + oldExeFile + " binary could not be removed, please remove it manually");
Program.ArchiLogger.LogGenericWarning("Refusing to proceed with auto update as old " + oldExeFile + " binary could not be removed, please remove it manually");
return;
}
// Auto update logic starts here
if (releaseResponse.Assets == null) {
ArchiLogger.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
Program.ArchiLogger.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
return;
}
@@ -135,17 +133,17 @@ namespace ArchiSteamFarm {
GitHub.ReleaseResponse.Asset binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(exeFileName, StringComparison.OrdinalIgnoreCase));
if (binaryAsset == null) {
ArchiLogger.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!");
Program.ArchiLogger.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!");
return;
}
if (string.IsNullOrEmpty(binaryAsset.DownloadURL)) {
ArchiLogger.LogGenericWarning("Could not proceed with update because download URL is empty!");
Program.ArchiLogger.LogGenericWarning("Could not proceed with update because download URL is empty!");
return;
}
ArchiLogger.LogGenericInfo("Downloading new version...");
ArchiLogger.LogGenericInfo("While waiting, consider donating if you appreciate the work being done :)");
Program.ArchiLogger.LogGenericInfo("Downloading new version...");
Program.ArchiLogger.LogGenericInfo("While waiting, consider donating if you appreciate the work being done :)");
byte[] result = await Program.WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
if (result == null) {
@@ -158,7 +156,7 @@ namespace ArchiSteamFarm {
try {
File.WriteAllBytes(newExeFile, result);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return;
}
@@ -166,7 +164,7 @@ namespace ArchiSteamFarm {
try {
File.Move(exeFile, oldExeFile);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
try {
// Cleanup
File.Delete(newExeFile);
@@ -180,7 +178,7 @@ namespace ArchiSteamFarm {
try {
File.Move(newExeFile, exeFile);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
try {
// Cleanup
File.Move(oldExeFile, exeFile);
@@ -191,26 +189,17 @@ namespace ArchiSteamFarm {
return;
}
ArchiLogger.LogGenericInfo("Update process finished!");
if (Program.GlobalConfig.AutoRestart) {
ArchiLogger.LogGenericInfo("Restarting...");
await Task.Delay(5000).ConfigureAwait(false);
Program.Restart();
} else {
ArchiLogger.LogGenericInfo("Exiting...");
await Task.Delay(5000).ConfigureAwait(false);
Program.Exit();
}
Program.ArchiLogger.LogGenericInfo("Update process finished!");
await RestartOrExit().ConfigureAwait(false);
}
internal static void InitBots() {
internal static async Task InitBots() {
if (Bot.Bots.Count != 0) {
return;
}
// Before attempting to connect, initialize our list of CMs
Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider);
await Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false);
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
switch (botName) {
@@ -224,7 +213,7 @@ namespace ArchiSteamFarm {
}
if (Bot.Bots.Count == 0) {
ArchiLogger.LogGenericWarning("No bots are defined, did you forget to configure your ASF?");
Program.ArchiLogger.LogGenericWarning("No bots are defined, did you forget to configure your ASF?");
}
}
@@ -247,7 +236,7 @@ namespace ArchiSteamFarm {
private static async Task CreateBot(string botName) {
if (string.IsNullOrEmpty(botName)) {
ArchiLogger.LogNullError(nameof(botName));
Program.ArchiLogger.LogNullError(nameof(botName));
return;
}
@@ -267,7 +256,7 @@ namespace ArchiSteamFarm {
private static async void OnChanged(object sender, FileSystemEventArgs e) {
if ((sender == null) || (e == null)) {
ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
Program.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
}
@@ -277,8 +266,8 @@ namespace ArchiSteamFarm {
}
if (botName.Equals(SharedInfo.ASF)) {
ArchiLogger.LogGenericError("Global config file has been changed, restarting...");
Program.Restart();
Program.ArchiLogger.LogGenericWarning("Global config file has been changed!");
await RestartOrExit().ConfigureAwait(false);
return;
}
@@ -319,7 +308,7 @@ namespace ArchiSteamFarm {
private static void OnCreated(object sender, FileSystemEventArgs e) {
if ((sender == null) || (e == null)) {
ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
Program.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
}
@@ -333,7 +322,7 @@ namespace ArchiSteamFarm {
private static void OnDeleted(object sender, FileSystemEventArgs e) {
if ((sender == null) || (e == null)) {
ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
Program.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
}
@@ -343,7 +332,7 @@ namespace ArchiSteamFarm {
}
if (botName.Equals(SharedInfo.ASF)) {
ArchiLogger.LogGenericError("Global config file has been removed, exiting...");
Program.ArchiLogger.LogGenericError("Global config file has been removed, exiting...");
Program.Exit(1);
return;
}
@@ -356,7 +345,7 @@ namespace ArchiSteamFarm {
private static void OnRenamed(object sender, RenamedEventArgs e) {
if ((sender == null) || (e == null)) {
ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
Program.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
}
@@ -366,7 +355,7 @@ namespace ArchiSteamFarm {
}
if (oldBotName.Equals(SharedInfo.ASF)) {
ArchiLogger.LogGenericError("Global config file has been renamed, exiting...");
Program.ArchiLogger.LogGenericError("Global config file has been renamed, exiting...");
Program.Exit(1);
return;
}
@@ -384,6 +373,18 @@ namespace ArchiSteamFarm {
CreateBot(newBotName).Forget();
}
private static async Task RestartOrExit() {
if (Program.GlobalConfig.AutoRestart) {
Program.ArchiLogger.LogGenericInfo("Restarting...");
await Task.Delay(5000).ConfigureAwait(false);
Program.Restart();
} else {
Program.ArchiLogger.LogGenericInfo("Exiting...");
await Task.Delay(5000).ConfigureAwait(false);
Program.Exit();
}
}
internal sealed class BotConfigEventArgs : EventArgs {
internal readonly BotConfig BotConfig;

View File

@@ -29,6 +29,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using ArchiSteamFarm.CMsgs;
using SteamKit2;
using SteamKit2.Internal;
@@ -75,6 +76,24 @@ namespace ArchiSteamFarm {
}
}
internal void AcceptClanInvite(ulong clanID, bool accept) {
if (clanID == 0) {
ArchiLogger.LogNullError(nameof(clanID));
return;
}
if (!Client.IsConnected) {
return;
}
ClientMsg<CMsgClientClanInviteAction> request = new ClientMsg<CMsgClientClanInviteAction>();
request.Body.ClanID = clanID;
request.Body.AcceptInvite = accept;
Client.Send(request);
}
// TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed
internal void LogOnWithoutMachineID(SteamUser.LogOnDetails details) {
if (details == null) {
@@ -150,11 +169,19 @@ namespace ArchiSteamFarm {
ClientMsgProtobuf<CMsgClientGamesPlayed> request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
if (!string.IsNullOrEmpty(gameName)) {
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_extra_info = gameName, game_id = new GameID { AppType = GameID.GameType.Shortcut, ModID = uint.MaxValue } });
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
game_extra_info = gameName,
game_id = new GameID {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
}
});
}
foreach (uint gameID in gameIDs.Where(gameID => gameID != 0)) {
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_id = new GameID(gameID) });
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
game_id = new GameID(gameID)
});
}
Client.Send(request);
@@ -368,7 +395,7 @@ namespace ArchiSteamFarm {
KeyValue receiptInfo = new KeyValue();
using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
if (!receiptInfo.TryReadAsBinary(ms)) {
ASF.ArchiLogger.LogNullError(nameof(ms));
Program.ArchiLogger.LogNullError(nameof(ms));
return;
}
}
@@ -385,14 +412,14 @@ namespace ArchiSteamFarm {
// Valid, coupons have PackageID of -1 (don't ask me why)
packageID = lineItem["ItemAppID"].AsUnsignedInteger();
if (packageID == 0) {
ASF.ArchiLogger.LogNullError(nameof(packageID));
Program.ArchiLogger.LogNullError(nameof(packageID));
return;
}
}
string gameName = lineItem["ItemDescription"].Value;
if (string.IsNullOrEmpty(gameName)) {
ASF.ArchiLogger.LogNullError(nameof(gameName));
Program.ArchiLogger.LogNullError(nameof(gameName));
return;
}

View File

@@ -77,6 +77,15 @@ namespace ArchiSteamFarm {
Logger.Debug($"{previousMethodName}() {message}");
}
internal void LogGenericDebugException(Exception exception, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception));
return;
}
Logger.Debug(exception, $"{previousMethodName}()");
}
internal void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));

View File

@@ -116,6 +116,7 @@
<Compile Include="ASF.cs" />
<Compile Include="Bot.cs" />
<Compile Include="BotConfig.cs" />
<Compile Include="CMsgs\CMsgClientClanInviteAction.cs" />
<Compile Include="ConcurrentEnumerator.cs" />
<Compile Include="ConcurrentHashSet.cs" />
<Compile Include="CryptoHelper.cs" />
@@ -137,6 +138,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SharedInfo.cs" />
<Compile Include="Statistics.cs" />
<Compile Include="SteamSaleEvent.cs" />
<Compile Include="Trading.cs" />
<Compile Include="Utilities.cs" />
<Compile Include="WCF.cs" />

View File

@@ -40,11 +40,15 @@ using Formatting = Newtonsoft.Json.Formatting;
namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler : IDisposable {
private const byte MinSessionTTL = GlobalConfig.DefaultHttpTimeout / 4; // Assume session is valid for at least that amount of seconds
private const string SteamCommunityHost = "steamcommunity.com";
private const string SteamStoreHost = "store.steampowered.com";
private static string SteamCommunityURL = "https://" + SteamCommunityHost;
private static string SteamStoreURL = "https://" + SteamStoreHost;
// We must use HTTPS for SteamCommunity, as http would make certain POST requests failing (trades)
private const string SteamCommunityHost = "steamcommunity.com";
private const string SteamCommunityURL = "https://" + SteamCommunityHost;
// We could (and should) use HTTPS for SteamStore, but that would make certain POST requests failing
private const string SteamStoreHost = "store.steampowered.com";
private const string SteamStoreURL = "http://" + SteamStoreHost;
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000; // This must be int type
private readonly Bot Bot;
@@ -68,20 +72,20 @@ namespace ArchiSteamFarm {
public void Dispose() => SessionSemaphore.Dispose();
internal async Task AcceptTradeOffer(ulong tradeID) {
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
if (tradeID == 0) {
Bot.ArchiLogger.LogNullError(nameof(tradeID));
return;
return false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return;
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Bot.ArchiLogger.LogNullError(nameof(sessionID));
return;
return false;
}
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
@@ -93,7 +97,7 @@ namespace ArchiSteamFarm {
{ "tradeofferid", tradeID.ToString() }
};
await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
return await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
}
internal async Task<bool> AddFreeLicense(uint subID) {
@@ -112,7 +116,7 @@ namespace ArchiSteamFarm {
return false;
}
string request = SteamStoreURL + "/checkout/addfreelicense";
const string request = SteamStoreURL + "/checkout/addfreelicense";
Dictionary<string, string> data = new Dictionary<string, string>(3) {
{ "sessionid", sessionID },
{ "subid", subID.ToString() },
@@ -123,6 +127,31 @@ namespace ArchiSteamFarm {
return htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='add_free_content_success_area']") != null;
}
internal async Task<bool> ClearFromDiscoveryQueue(uint appID) {
if (appID == 0) {
Bot.ArchiLogger.LogNullError(nameof(appID));
return false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamStoreURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Bot.ArchiLogger.LogNullError(nameof(sessionID));
return false;
}
string request = SteamStoreURL + "/app/" + appID;
Dictionary<string, string> data = new Dictionary<string, string>(2) {
{ "sessionid", sessionID },
{ "appid_to_clear_from_queue", appID.ToString() }
};
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
}
internal void DeclineTradeOffer(ulong tradeID) {
if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
Bot.ArchiLogger.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey));
@@ -135,7 +164,11 @@ namespace ArchiSteamFarm {
iEconService.Timeout = Timeout;
try {
response = iEconService.DeclineTradeOffer(tradeofferid: tradeID.ToString(), method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp);
response = iEconService.DeclineTradeOffer(
tradeofferid: tradeID.ToString(),
method: WebRequestMethods.Http.Post,
secure: true
);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
}
@@ -147,6 +180,27 @@ namespace ArchiSteamFarm {
}
}
internal async Task<HashSet<uint>> GenerateNewDiscoveryQueue() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamStoreURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Bot.ArchiLogger.LogNullError(nameof(sessionID));
return null;
}
const string request = SteamStoreURL + "/explore/generatenewdiscoveryqueue";
Dictionary<string, string> data = new Dictionary<string, string>(2) {
{ "sessionid", sessionID },
{ "queuetype", "0" }
};
Steam.NewDiscoveryQueueResponse output = await WebBrowser.UrlPostToJsonResultRetry<Steam.NewDiscoveryQueueResponse>(request, data).ConfigureAwait(false);
return output?.Queue;
}
internal HashSet<Steam.TradeOffer> GetActiveTradeOffers() {
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
Bot.ArchiLogger.LogNullError(nameof(Bot.BotConfig.SteamApiKey));
@@ -159,7 +213,12 @@ namespace ArchiSteamFarm {
iEconService.Timeout = Timeout;
try {
response = iEconService.GetTradeOffers(get_received_offers: 1, active_only: 1, get_descriptions: 1, secure: !Program.GlobalConfig.ForceHttp);
response = iEconService.GetTradeOffers(
get_received_offers: 1,
active_only: 1,
get_descriptions: 1,
secure: true
);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
}
@@ -301,12 +360,21 @@ namespace ArchiSteamFarm {
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
internal async Task<HtmlDocument> GetDiscoveryQueuePage() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
const string request = SteamStoreURL + "/explore?l=english";
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
internal async Task<HashSet<ulong>> GetFamilySharingSteamIDs() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string request = SteamStoreURL + "/account/managedevices";
const string request = SteamStoreURL + "/account/managedevices";
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
HtmlNodeCollection htmlNodes = htmlDocument?.DocumentNode.SelectNodes("(//table[@class='accountTable'])[last()]//a/@data-miniprofile");
@@ -349,7 +417,12 @@ namespace ArchiSteamFarm {
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable) {
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable, HashSet<Steam.Item.EType> wantedTypes) {
if ((wantedTypes == null) || (wantedTypes.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(wantedTypes));
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
@@ -445,6 +518,10 @@ namespace ArchiSteamFarm {
steamItem.Type = description.Item2;
}
if (!wantedTypes.Contains(steamItem.Type)) {
continue;
}
result.Add(steamItem);
}
@@ -470,7 +547,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/my/games/?xml=1";
const string request = SteamCommunityURL + "/my/games/?xml=1";
XmlDocument response = await WebBrowser.UrlGetToXMLRetry(request).ConfigureAwait(false);
@@ -517,7 +594,11 @@ namespace ArchiSteamFarm {
iPlayerService.Timeout = Timeout;
try {
response = iPlayerService.GetOwnedGames(steamid: steamID, include_appinfo: 1, secure: !Program.GlobalConfig.ForceHttp);
response = iPlayerService.GetOwnedGames(
steamid: steamID,
include_appinfo: 1,
secure: true
);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
}
@@ -550,7 +631,10 @@ namespace ArchiSteamFarm {
iTwoFactorService.Timeout = Timeout;
try {
response = iTwoFactorService.QueryTime(method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp);
response = iTwoFactorService.QueryTime(
method: WebRequestMethods.Http.Post,
secure: true
);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
}
@@ -565,6 +649,15 @@ namespace ArchiSteamFarm {
return 0;
}
internal async Task<HtmlDocument> GetSteamAwardsPage() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
const string request = SteamStoreURL + "/SteamAwards?l=english";
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
internal async Task<byte?> GetTradeHoldDuration(ulong tradeID) {
if (tradeID == 0) {
Bot.ArchiLogger.LogNullError(nameof(tradeID));
@@ -642,8 +735,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/mobileconf/multiajaxop";
const string request = SteamCommunityURL + "/mobileconf/multiajaxop";
List<KeyValuePair<string, string>> data = new List<KeyValuePair<string, string>>(7 + confirmations.Count * 2) {
new KeyValuePair<string, string>("op", accept ? "allow" : "cancel"),
new KeyValuePair<string, string>("p", deviceID),
@@ -663,11 +755,7 @@ namespace ArchiSteamFarm {
return response?.Success;
}
internal static void Init() {
Timeout = Program.GlobalConfig.HttpTimeout * 1000;
SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunityHost;
SteamStoreURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamStoreHost;
}
internal static void Init() => Timeout = Program.GlobalConfig.HttpTimeout * 1000;
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) {
@@ -703,7 +791,13 @@ namespace ArchiSteamFarm {
iSteamUserAuth.Timeout = Timeout;
try {
authResult = iSteamUserAuth.AuthenticateUser(steamid: steamID, sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)), encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)), method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp);
authResult = iSteamUserAuth.AuthenticateUser(
steamid: steamID,
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
method: WebRequestMethods.Http.Post,
secure: true
);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
return false;
@@ -780,7 +874,7 @@ namespace ArchiSteamFarm {
return false;
}
string request = SteamCommunityURL + "/my/inventory";
const string request = SteamCommunityURL + "/my/inventory";
return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false);
}
@@ -796,7 +890,7 @@ namespace ArchiSteamFarm {
return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout;
}
string request = SteamStoreURL + "/account/validatewalletcode";
const string request = SteamStoreURL + "/account/validatewalletcode";
Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "wallet_code", key }
};
@@ -840,8 +934,8 @@ namespace ArchiSteamFarm {
itemID++;
}
string referer = SteamCommunityURL + "/tradeoffer/new";
string request = referer + "/send";
const string referer = SteamCommunityURL + "/tradeoffer/new";
const string request = referer + "/send";
foreach (Dictionary<string, string> data in trades.Select(trade => new Dictionary<string, string>(6) {
{ "sessionid", sessionID },
{ "serverid", "1" },
@@ -858,9 +952,35 @@ namespace ArchiSteamFarm {
return true;
}
internal async Task<bool> SteamAwardsVote(byte voteID, uint appID) {
if ((voteID == 0) || (appID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(voteID) + " || " + nameof(appID));
return false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamStoreURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Bot.ArchiLogger.LogNullError(nameof(sessionID));
return false;
}
const string request = SteamStoreURL + "/salevote";
Dictionary<string, string> data = new Dictionary<string, string>(3) {
{ "sessionid", sessionID },
{ "voteid", voteID.ToString() },
{ "appid", appID.ToString() }
};
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
}
private static uint GetAppIDFromMarketHashName(string hashName) {
if (string.IsNullOrEmpty(hashName)) {
ASF.ArchiLogger.LogNullError(nameof(hashName));
Program.ArchiLogger.LogNullError(nameof(hashName));
return 0;
}
@@ -875,7 +995,7 @@ namespace ArchiSteamFarm {
private static Steam.Item.EType GetItemType(string name) {
if (string.IsNullOrEmpty(name)) {
ASF.ArchiLogger.LogNullError(nameof(name));
Program.ArchiLogger.LogNullError(nameof(name));
return Steam.Item.EType.Unknown;
}
@@ -908,7 +1028,7 @@ namespace ArchiSteamFarm {
private async Task<bool?> IsLoggedIn() {
// It would make sense to use /my/profile here, but it dismisses notifications related to profile comments
// So instead, we'll use some less intrusive link, such as /my/videos
string request = SteamCommunityURL + "/my/videos";
const string request = SteamCommunityURL + "/my/videos";
Uri uri = await WebBrowser.UrlHeadToUriRetry(request).ConfigureAwait(false);
return !uri?.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
@@ -916,32 +1036,32 @@ namespace ArchiSteamFarm {
private static bool ParseItems(Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptions, List<KeyValue> input, HashSet<Steam.Item> output) {
if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
Program.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
return false;
}
foreach (KeyValue item in input) {
uint appID = item["appid"].AsUnsignedInteger();
if (appID == 0) {
ASF.ArchiLogger.LogNullError(nameof(appID));
Program.ArchiLogger.LogNullError(nameof(appID));
return false;
}
ulong contextID = item["contextid"].AsUnsignedLong();
if (contextID == 0) {
ASF.ArchiLogger.LogNullError(nameof(contextID));
Program.ArchiLogger.LogNullError(nameof(contextID));
return false;
}
ulong classID = item["classid"].AsUnsignedLong();
if (classID == 0) {
ASF.ArchiLogger.LogNullError(nameof(classID));
Program.ArchiLogger.LogNullError(nameof(classID));
return false;
}
uint amount = item["amount"].AsUnsignedInteger();
if (amount == 0) {
ASF.ArchiLogger.LogNullError(nameof(amount));
Program.ArchiLogger.LogNullError(nameof(amount));
return false;
}
@@ -994,7 +1114,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo("Unlocking parental account...");
string request = SteamCommunityURL + "/parental/ajaxunlock";
const string request = SteamCommunityURL + "/parental/ajaxunlock";
Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "pin", parentalPin }
};

View File

@@ -82,27 +82,32 @@ namespace ArchiSteamFarm {
private readonly SteamClient SteamClient;
private readonly ConcurrentHashSet<ulong> SteamFamilySharingIDs = new ConcurrentHashSet<ulong>();
private readonly SteamFriends SteamFriends;
private readonly SteamSaleEvent SteamSaleEvent;
private readonly SteamUser SteamUser;
private readonly Trading Trading;
[JsonProperty]
internal BotConfig BotConfig { get; private set; }
[JsonProperty]
internal bool IsLimitedUser { get; private set; }
[JsonProperty]
internal bool KeepRunning { get; private set; }
private Timer AcceptConfirmationsTimer;
private string AuthCode, TwoFactorCode;
private string AuthCode;
private Timer ConnectingTimeoutTimer;
private Timer FamilySharingInactivityTimer;
private bool FirstTradeSent;
private byte HeartBeatFailures;
private EResult LastLogOnResult;
private ulong LibraryLockedBySteamID;
private bool LootingAllowed = true;
private bool PlayingBlocked;
private Timer SendItemsTimer;
private bool SkipFirstShutdown;
private string TwoFactorCode;
internal Bot(string botName) {
if (string.IsNullOrEmpty(botName)) {
@@ -203,13 +208,17 @@ namespace ArchiSteamFarm {
CardsFarmer = new CardsFarmer(this);
CardsFarmer.SetInitialState(BotConfig.Paused);
SteamSaleEvent = new SteamSaleEvent(this);
Trading = new Trading(this);
if (Program.GlobalConfig.Statistics) {
Statistics = new Statistics(this);
}
HeartBeatTimer = new Timer(async e => await HeartBeat().ConfigureAwait(false), null, TimeSpan.FromMinutes(1) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay
HeartBeatTimer = new Timer(
async e => await HeartBeat().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(1) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay
TimeSpan.FromMinutes(1) // Period
);
@@ -226,10 +235,12 @@ namespace ArchiSteamFarm {
InitializationSemaphore.Dispose();
SteamFamilySharingIDs.Dispose();
OwnedPackageIDs.Dispose();
SteamSaleEvent.Dispose();
Trading.Dispose();
// Those are objects that might be null and the check should be in-place
AcceptConfirmationsTimer?.Dispose();
ConnectingTimeoutTimer?.Dispose();
FamilySharingInactivityTimer?.Dispose();
SendItemsTimer?.Dispose();
}
@@ -279,24 +290,32 @@ namespace ArchiSteamFarm {
}
internal static string GetAPIStatus() {
var response = new { Bots };
var response = new {
Bots
};
try {
return JsonConvert.SerializeObject(response);
} catch (JsonException e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
}
internal static void InitializeCMs(uint cellID, IServerListProvider serverListProvider) {
internal static async Task InitializeCMs(uint cellID, IServerListProvider serverListProvider) {
if (serverListProvider == null) {
ASF.ArchiLogger.LogNullError(nameof(serverListProvider));
Program.ArchiLogger.LogNullError(nameof(serverListProvider));
return;
}
CMClient.Servers.CellID = cellID;
CMClient.Servers.ServerListProvider = serverListProvider;
// Normally we wouldn't need to do this, but there is a case where our list might be invalid or outdated
// Ensure that we always ask once for list of up-to-date servers, even if we have list saved
Program.ArchiLogger.LogGenericInfo("Initializing SteamDirectory...");
await SteamDirectory.Initialize(cellID).ConfigureAwait(false);
Program.ArchiLogger.LogGenericInfo("Done!");
}
internal async Task LootIfNeeded() {
@@ -358,15 +377,18 @@ namespace ArchiSteamFarm {
TimeSpan period = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod);
if (AcceptConfirmationsTimer == null) {
AcceptConfirmationsTimer = new Timer(async e => await AcceptConfirmations(true).ConfigureAwait(false), null, delay, // Delay
AcceptConfirmationsTimer = new Timer(
async e => await AcceptConfirmations(true).ConfigureAwait(false),
null,
delay, // Delay
period // Period
);
} else {
AcceptConfirmationsTimer.Change(delay, period);
}
} else {
AcceptConfirmationsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
AcceptConfirmationsTimer?.Dispose();
} else if (AcceptConfirmationsTimer != null) {
AcceptConfirmationsTimer.Dispose();
AcceptConfirmationsTimer = null;
}
if ((BotConfig.SendTradePeriod > 0) && (BotConfig.SteamMasterID != 0)) {
@@ -374,15 +396,18 @@ namespace ArchiSteamFarm {
TimeSpan period = TimeSpan.FromHours(BotConfig.SendTradePeriod);
if (SendItemsTimer == null) {
SendItemsTimer = new Timer(async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false), null, delay, // Delay
SendItemsTimer = new Timer(
async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
null,
delay, // Delay
period // Period
);
} else {
SendItemsTimer.Change(delay, period);
}
} else {
SendItemsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
SendItemsTimer?.Dispose();
} else if (SendItemsTimer != null) {
SendItemsTimer.Dispose();
SendItemsTimer = null;
}
await Initialize().ConfigureAwait(false);
@@ -451,6 +476,8 @@ namespace ArchiSteamFarm {
return ResponseHelp(steamID);
case "!LOOT":
return await ResponseLoot(steamID).ConfigureAwait(false);
case "!LOOT^":
return ResponseLootSwitch(steamID);
case "!LOOTALL":
return await ResponseLootAll(steamID).ConfigureAwait(false);
case "!PASSWORD":
@@ -500,6 +527,8 @@ namespace ArchiSteamFarm {
return await ResponseFarm(steamID, args[1]).ConfigureAwait(false);
case "!LOOT":
return await ResponseLoot(steamID, args[1]).ConfigureAwait(false);
case "!LOOT^":
return ResponseLootSwitch(steamID, args[1]);
case "!OWNS":
if (args.Length > 2) {
return await ResponseOwns(steamID, args[1], args[2]).ConfigureAwait(false);
@@ -577,6 +606,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericInfo("Shared library has not been launched in given time period, farming process resumed!");
StopFamilySharingInactivityTimer();
CardsFarmer.Resume(false);
}
@@ -611,6 +641,16 @@ namespace ArchiSteamFarm {
return;
}
if (ConnectingTimeoutTimer == null) {
ConnectingTimeoutTimer = new Timer(
async e => await Connect().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(1), // Delay
TimeSpan.FromMinutes(1) // Period
);
}
ArchiLogger.LogGenericInfo("Connecting...");
SteamClient.Connect();
}
}
@@ -629,6 +669,11 @@ namespace ArchiSteamFarm {
private void Disconnect() {
lock (SteamClient) {
if (ConnectingTimeoutTimer != null) {
ConnectingTimeoutTimer.Dispose();
ConnectingTimeoutTimer = null;
}
SteamClient.Disconnect();
}
}
@@ -771,12 +816,21 @@ namespace ArchiSteamFarm {
return false;
}
private bool IsMasterClanID(ulong steamID) {
if (steamID != 0) {
return steamID == BotConfig.SteamMasterClanID;
}
ArchiLogger.LogNullError(nameof(steamID));
return false;
}
private static bool IsOwner(ulong steamID) {
if (steamID != 0) {
return (steamID == Program.GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID));
}
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return false;
}
@@ -785,7 +839,7 @@ namespace ArchiSteamFarm {
return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.IgnoreCase);
}
ASF.ArchiLogger.LogNullError(nameof(key));
Program.ArchiLogger.LogNullError(nameof(key));
return false;
}
@@ -874,6 +928,11 @@ namespace ArchiSteamFarm {
return;
}
if (ConnectingTimeoutTimer != null) {
ConnectingTimeoutTimer.Dispose();
ConnectingTimeoutTimer = null;
}
if (callback.Result != EResult.OK) {
ArchiLogger.LogGenericError("Unable to connect to Steam: " + callback.Result);
return;
@@ -943,6 +1002,11 @@ namespace ArchiSteamFarm {
return;
}
if (ConnectingTimeoutTimer != null) {
ConnectingTimeoutTimer.Dispose();
ConnectingTimeoutTimer = null;
}
HeartBeatFailures = 0;
EResult lastLogOnResult = LastLogOnResult;
@@ -1057,10 +1121,18 @@ namespace ArchiSteamFarm {
}
foreach (SteamFriends.FriendsListCallback.Friend friend in callback.FriendList.Where(friend => friend.Relationship == EFriendRelationship.RequestRecipient)) {
if (IsMaster(friend.SteamID)) {
SteamFriends.AddFriend(friend.SteamID);
} else if (BotConfig.IsBotAccount) {
SteamFriends.RemoveFriend(friend.SteamID);
if (friend.SteamID.AccountType == EAccountType.Clan) {
if (IsMasterClanID(friend.SteamID)) {
ArchiHandler.AcceptClanInvite(friend.SteamID, true);
} else if (BotConfig.IsBotAccount) {
ArchiHandler.AcceptClanInvite(friend.SteamID, false);
}
} else {
if (IsMaster(friend.SteamID)) {
SteamFriends.AddFriend(friend.SteamID);
} else if (BotConfig.IsBotAccount) {
SteamFriends.RemoveFriend(friend.SteamID);
}
}
}
}
@@ -1103,7 +1175,7 @@ namespace ArchiSteamFarm {
HashSet<uint> ownedPackageIDs = new HashSet<uint>(callback.LicenseList.Select(license => license.PackageID));
OwnedPackageIDs.ReplaceIfNeededWith(ownedPackageIDs);
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback or SharedLibraryLockStatusCallback
if (!ArchiWebHandler.Ready) {
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
@@ -1115,6 +1187,13 @@ namespace ArchiSteamFarm {
}
}
// Normally we ResetGamesPlayed() in OnFarmingStopped() but there is no farming event if CardsFarmer module is disabled
// Therefore, trigger extra ResetGamesPlayed(), but only in this specific case
if (CardsFarmer.Paused) {
ResetGamesPlayed();
}
// We trigger OnNewGameAdded() anyway, as CardsFarmer has other things to handle regardless of being Paused or not
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
}
@@ -1182,7 +1261,7 @@ namespace ArchiSteamFarm {
}
// Sometimes Steam won't send us our own PersonaStateCallback, so request it explicitly
SteamFriends.RequestFriendInfo(callback.ClientSteamID, EClientPersonaStateFlag.Presence);
SteamFriends.RequestFriendInfo(callback.ClientSteamID, EClientPersonaStateFlag.PlayerName | EClientPersonaStateFlag.Presence);
if (BotDatabase.MobileAuthenticator == null) {
// Support and convert SDA files
@@ -1279,7 +1358,17 @@ namespace ArchiSteamFarm {
}
// Inform the steam servers that we're accepting this sentry file
SteamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails { JobID = callback.JobID, FileName = callback.FileName, BytesWritten = callback.BytesToWrite, FileSize = fileSize, Offset = callback.Offset, Result = EResult.OK, LastError = 0, OneTimePassword = callback.OneTimePassword, SentryFileHash = sentryHash });
SteamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails {
JobID = callback.JobID,
FileName = callback.FileName,
BytesWritten = callback.BytesToWrite,
FileSize = fileSize,
Offset = callback.Offset,
Result = EResult.OK,
LastError = 0,
OneTimePassword = callback.OneTimePassword,
SentryFileHash = sentryHash
});
}
private void OnNotifications(ArchiHandler.NotificationsCallback callback) {
@@ -1390,7 +1479,7 @@ namespace ArchiSteamFarm {
}
private void ResetGamesPlayed() {
if (PlayingBlocked) {
if (!IsPlayingPossible || (FamilySharingInactivityTimer != null)) {
return;
}
@@ -1421,7 +1510,7 @@ namespace ArchiSteamFarm {
private static async Task<string> Response2FA(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -1460,7 +1549,7 @@ namespace ArchiSteamFarm {
private static async Task<string> Response2FAConfirm(ulong steamID, string botName, bool confirm) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -1512,7 +1601,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponseAddLicense(ulong steamID, string botName, string games) {
if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games));
return null;
}
@@ -1549,13 +1638,13 @@ namespace ArchiSteamFarm {
return !IsOwner(steamID) ? null : GetAPIStatus();
}
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
private static string ResponseExit(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
@@ -1593,7 +1682,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponseFarm(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -1632,6 +1721,10 @@ namespace ArchiSteamFarm {
return null;
}
if (!LootingAllowed) {
return "Looting is temporarily disabled!";
}
if (!IsConnectedAndLoggedOn) {
return "This bot instance is not connected!";
}
@@ -1646,30 +1739,23 @@ namespace ArchiSteamFarm {
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMySteamInventory(true).ConfigureAwait(false);
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMySteamInventory(true, BotConfig.LootableTypes).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return "Nothing to send, inventory seems empty!";
}
// Remove from our pending inventory all items that are not steam cards and boosters
if (inventory.RemoveWhere(item => (item.Type != Steam.Item.EType.TradingCard) && ((item.Type != Steam.Item.EType.FoilTradingCard) || !BotConfig.IsBotAccount) && (item.Type != Steam.Item.EType.BoosterPack)) > 0) {
if (inventory.Count == 0) {
return "Nothing to send, inventory seems empty!";
}
}
if (!await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) {
return "Trade offer failed due to error!";
}
await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
await Task.Delay(3000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, BotConfig.SteamMasterID).ConfigureAwait(false);
return "Trade offer sent successfully!";
}
private static async Task<string> ResponseLoot(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -1687,7 +1773,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponseLootAll(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
@@ -1699,6 +1785,38 @@ namespace ArchiSteamFarm {
return "Done!";
}
private string ResponseLootSwitch(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
return null;
}
if (!IsMaster(steamID)) {
return null;
}
LootingAllowed = !LootingAllowed;
return "Looting is now " + (LootingAllowed ? "enabled" : "disabled") + "!";
}
private static string ResponseLootSwitch(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
Bot bot;
if (Bots.TryGetValue(botName, out bot)) {
return bot.ResponseLootSwitch(steamID);
}
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
}
return null;
}
private async Task<string> ResponseOwns(ulong steamID, string query) {
if ((steamID == 0) || string.IsNullOrEmpty(query)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
@@ -1756,7 +1874,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponseOwns(ulong steamID, string botName, string query) {
if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(query)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(query));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(query));
return null;
}
@@ -1774,7 +1892,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponseOwnsAll(ulong steamID, string query) {
if ((steamID == 0) || string.IsNullOrEmpty(query)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
return null;
}
@@ -1811,7 +1929,7 @@ namespace ArchiSteamFarm {
private static string ResponsePassword(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -1857,7 +1975,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponsePause(ulong steamID, string botName, bool sticky) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -1897,7 +2015,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponsePlay(ulong steamID, string botName, string games) {
if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games));
return null;
}
@@ -1944,8 +2062,8 @@ namespace ArchiSteamFarm {
return null;
}
bool forward = !redeemFlags.HasFlag(ERedeemFlags.SkipForwarding) && (redeemFlags.HasFlag(ERedeemFlags.ForceForwarding) || BotConfig.ForwardKeysToOtherBots);
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistribution) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistribution) || BotConfig.DistributeKeys);
bool forward = !redeemFlags.HasFlag(ERedeemFlags.SkipForwarding) && (redeemFlags.HasFlag(ERedeemFlags.ForceForwarding) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Forwarding));
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistribution) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistribution) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing));
message = message.Replace(",", Environment.NewLine);
StringBuilder response = new StringBuilder();
@@ -2060,7 +2178,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponseRedeem(ulong steamID, string botName, string message, ERedeemFlags redeemFlags = ERedeemFlags.None) {
if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(message)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(message));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(message));
return null;
}
@@ -2078,7 +2196,7 @@ namespace ArchiSteamFarm {
private static string ResponseRejoinChat(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
@@ -2095,7 +2213,7 @@ namespace ArchiSteamFarm {
private static string ResponseRestart(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
@@ -2137,7 +2255,7 @@ namespace ArchiSteamFarm {
private static string ResponseResume(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -2174,7 +2292,7 @@ namespace ArchiSteamFarm {
private static string ResponseStart(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -2192,7 +2310,7 @@ namespace ArchiSteamFarm {
private static string ResponseStartAll(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
@@ -2233,6 +2351,10 @@ namespace ArchiSteamFarm {
return "Bot " + BotName + " is paused or running in manual mode.";
}
if (IsLimitedUser) {
return "Bot " + BotName + " is limited.";
}
if (CardsFarmer.CurrentGamesFarming.Count == 0) {
return "Bot " + BotName + " is not farming anything.";
}
@@ -2252,7 +2374,7 @@ namespace ArchiSteamFarm {
private static string ResponseStatus(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -2270,7 +2392,7 @@ namespace ArchiSteamFarm {
private static string ResponseStatusAll(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
@@ -2304,7 +2426,7 @@ namespace ArchiSteamFarm {
private static string ResponseStop(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
Program.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
@@ -2331,7 +2453,7 @@ namespace ArchiSteamFarm {
private static async Task<string> ResponseUpdate(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
Program.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
@@ -2416,7 +2538,10 @@ namespace ArchiSteamFarm {
return;
}
FamilySharingInactivityTimer = new Timer(e => CheckFamilySharingInactivity(), null, TimeSpan.FromMinutes(FamilySharingInactivityMinutes), // Delay
FamilySharingInactivityTimer = new Timer(
e => CheckFamilySharingInactivity(),
null,
TimeSpan.FromMinutes(FamilySharingInactivityMinutes), // Delay
Timeout.InfiniteTimeSpan // Period
);
}
@@ -2426,7 +2551,6 @@ namespace ArchiSteamFarm {
return;
}
FamilySharingInactivityTimer.Change(Timeout.Infinite, Timeout.Infinite);
FamilySharingInactivityTimer.Dispose();
FamilySharingInactivityTimer = null;
}

View File

@@ -27,6 +27,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using ArchiSteamFarm.JSON;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
@@ -53,9 +54,6 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool DismissInventoryNotifications = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool DistributeKeys = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Enabled = false;
@@ -65,9 +63,6 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool FarmOffline = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool ForwardKeysToOtherBots = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>();
@@ -77,12 +72,18 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool IsBotAccount = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<Steam.Item.EType> LootableTypes = new HashSet<Steam.Item.EType> { Steam.Item.EType.BoosterPack, Steam.Item.EType.FoilTradingCard, Steam.Item.EType.TradingCard };
[JsonProperty(Required = Required.DisallowNull)]
internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Paused = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly ERedeemingPreferences RedeemingPreferences = ERedeemingPreferences.None;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool SendOnFarmingFinished = false;
@@ -121,7 +122,7 @@ namespace ArchiSteamFarm {
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
Program.ArchiLogger.LogNullError(nameof(filePath));
return null;
}
@@ -134,12 +135,12 @@ namespace ArchiSteamFarm {
try {
botConfig = JsonConvert.DeserializeObject<BotConfig>(File.ReadAllText(filePath));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
if (botConfig == null) {
ASF.ArchiLogger.LogNullError(nameof(botConfig));
Program.ArchiLogger.LogNullError(nameof(botConfig));
return null;
}
@@ -155,7 +156,7 @@ namespace ArchiSteamFarm {
return botConfig;
}
ASF.ArchiLogger.LogGenericWarning("Playing more than " + CardsFarmer.MaxGamesPlayedConcurrently + " games concurrently is not possible, only first " + CardsFarmer.MaxGamesPlayedConcurrently + " entries from GamesPlayedWhileIdle will be used");
Program.ArchiLogger.LogGenericWarning("Playing more than " + CardsFarmer.MaxGamesPlayedConcurrently + " games concurrently is not possible, only first " + CardsFarmer.MaxGamesPlayedConcurrently + " entries from GamesPlayedWhileIdle will be used");
HashSet<uint> validGames = new HashSet<uint>(botConfig.GamesPlayedWhileIdle.Take(CardsFarmer.MaxGamesPlayedConcurrently));
botConfig.GamesPlayedWhileIdle.IntersectWith(validGames);
@@ -176,6 +177,14 @@ namespace ArchiSteamFarm {
NamesDescending
}
[Flags]
internal enum ERedeemingPreferences : byte {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
None = 0,
Forwarding = 1,
Distributing = 2
}
[Flags]
internal enum ETradingPreferences : byte {
[SuppressMessage("ReSharper", "UnusedMember.Global")]

View File

@@ -82,7 +82,7 @@ namespace ArchiSteamFarm {
internal static BotDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
Program.ArchiLogger.LogNullError(nameof(filePath));
return null;
}
@@ -95,12 +95,12 @@ namespace ArchiSteamFarm {
try {
botDatabase = JsonConvert.DeserializeObject<BotDatabase>(File.ReadAllText(filePath));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
if (botDatabase == null) {
ASF.ArchiLogger.LogNullError(nameof(botDatabase));
Program.ArchiLogger.LogNullError(nameof(botDatabase));
return null;
}
@@ -111,7 +111,7 @@ namespace ArchiSteamFarm {
internal void Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json));
Program.ArchiLogger.LogNullError(nameof(json));
return;
}
@@ -121,7 +121,7 @@ namespace ArchiSteamFarm {
File.WriteAllText(FilePath, json);
break;
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
}
Thread.Sleep(1000);

View File

@@ -0,0 +1,58 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System.IO;
using SteamKit2;
using SteamKit2.Internal;
namespace ArchiSteamFarm.CMsgs {
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage {
internal bool AcceptInvite { private get; set; }
internal ulong ClanID { private get; set; }
void ISteamSerializable.Deserialize(Stream stream) {
if (stream == null) {
Program.ArchiLogger.LogNullError(nameof(stream));
return;
}
BinaryReader binaryReader = new BinaryReader(stream);
ClanID = binaryReader.ReadUInt64();
AcceptInvite = binaryReader.ReadBoolean();
}
EMsg ISteamSerializableMessage.GetEMsg() => EMsg.ClientAcknowledgeClanInvite;
void ISteamSerializable.Serialize(Stream stream) {
if (stream == null) {
Program.ArchiLogger.LogNullError(nameof(stream));
return;
}
BinaryWriter binaryWriter = new BinaryWriter(stream);
binaryWriter.Write(ClanID);
binaryWriter.Write(AcceptInvite);
}
}
}

View File

@@ -36,6 +36,8 @@ namespace ArchiSteamFarm {
internal sealed class CardsFarmer : IDisposable {
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
private static readonly HashSet<uint> UntrustedAppIDs = new HashSet<uint> { 440, 570, 730 };
[JsonProperty]
internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>();
@@ -62,7 +64,10 @@ namespace ArchiSteamFarm {
Bot = bot;
if (Program.GlobalConfig.IdleFarmingPeriod > 0) {
IdleFarmingTimer = new Timer(e => CheckGamesForFarming(), null, TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5 * Bot.Bots.Count), // Delay
IdleFarmingTimer = new Timer(
e => CheckGamesForFarming(),
null,
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5 * Bot.Bots.Count), // Delay
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
);
}
@@ -82,15 +87,16 @@ namespace ArchiSteamFarm {
internal void OnDisconnected() => StopFarming().Forget();
internal async Task OnNewGameAdded() {
// If we're not farming yet, obviously it's worth it to make a check
if (!NowFarming) {
// If we're not farming yet, obviously it's worth it to make a check
StartFarming().Forget();
return;
}
// 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 2 hours in current round
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < 2)) {
// If we have Complex algorithm and some games to boost, it's also worth to make a check
// That's because we would check for new games after our current round anyway
await StopFarming().ConfigureAwait(false);
StartFarming().Forget();
}
@@ -251,6 +257,25 @@ namespace ArchiSteamFarm {
}
}
private async Task CheckGame(uint appID, string name, float hours) {
if ((appID == 0) || string.IsNullOrEmpty(name) || (hours < 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(name) + " || " + nameof(hours));
return;
}
ushort? cardsRemaining = await GetCardsRemaining(appID).ConfigureAwait(false);
if (!cardsRemaining.HasValue) {
Bot.ArchiLogger.LogGenericWarning("Could not check cards status for " + appID + " (" + name + "), will try again later!");
return;
}
if (cardsRemaining.Value == 0) {
return;
}
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining.Value));
}
private void CheckGamesForFarming() {
if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) {
return;
@@ -259,118 +284,167 @@ namespace ArchiSteamFarm {
StartFarming().Forget();
}
private void CheckPage(HtmlDocument htmlDocument) {
private async Task CheckPage(HtmlDocument htmlDocument) {
if (htmlDocument == null) {
Bot.ArchiLogger.LogNullError(nameof(htmlDocument));
return;
}
HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_title_stats']");
if (htmlNodes == null) { // No eligible badges
HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_title_stats_content']");
if (htmlNodes == null) {
// No eligible badges whatsoever
return;
}
HashSet<Task> extraTasks = new HashSet<Task>();
foreach (HtmlNode htmlNode in htmlNodes) {
HtmlNode farmingNode = htmlNode.SelectSingleNode(".//a[@class='btn_green_white_innerfade btn_small_thin']");
if (farmingNode == null) {
continue; // This game is not needed for farming
HtmlNode appIDNode = htmlNode.SelectSingleNode(".//div[@class='card_drop_info_dialog']");
if (appIDNode == null) {
// It's just a badge, nothing more
continue;
}
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']");
if (progressNode == null) {
continue; // e.g. Holiday Sale 2015
string appIDString = appIDNode.GetAttributeValue("id", null);
if (string.IsNullOrEmpty(appIDString)) {
Bot.ArchiLogger.LogNullError(nameof(appIDString));
continue;
}
// AppIDs
string steamLink = farmingNode.GetAttributeValue("href", null);
if (string.IsNullOrEmpty(steamLink)) {
Bot.ArchiLogger.LogNullError(nameof(steamLink));
return;
string[] appIDSplitted = appIDString.Split('_');
if (appIDSplitted.Length < 5) {
Bot.ArchiLogger.LogNullError(nameof(appIDSplitted));
continue;
}
int index = steamLink.LastIndexOf('/');
if (index < 0) {
Bot.ArchiLogger.LogNullError(nameof(index));
return;
}
index++;
if (steamLink.Length <= index) {
Bot.ArchiLogger.LogNullError(nameof(steamLink.Length));
return;
}
steamLink = steamLink.Substring(index);
appIDString = appIDSplitted[4];
uint appID;
if (!uint.TryParse(steamLink, out appID) || (appID == 0)) {
if (!uint.TryParse(appIDString, out appID) || (appID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID));
return;
continue;
}
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
// We have this appID blacklisted, so skip it
continue;
}
// Cards
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']");
if (progressNode == null) {
Bot.ArchiLogger.LogNullError(nameof(progressNode));
continue;
}
string progressText = progressNode.InnerText;
if (string.IsNullOrEmpty(progressText)) {
Bot.ArchiLogger.LogNullError(nameof(progressText));
continue;
}
ushort cardsRemaining = 0;
Match progressMatch = Regex.Match(progressText, @"\d+");
// This might fail if we have no card drops remaining, that's fine
if (progressMatch.Success) {
if (!ushort.TryParse(progressMatch.Value, out cardsRemaining)) {
Bot.ArchiLogger.LogNullError(nameof(cardsRemaining));
continue;
}
}
if (cardsRemaining == 0) {
// Normally we'd trust this information and simply skip the rest
// However, Steam is so fucked up that we can't simply assume that it's correct
// It's entirely possible that actual game page has different info, and badge page lied to us
// We can't check every single game though, as this will literally kill people with cards from games they don't own
if (!UntrustedAppIDs.Contains(appID)) {
continue;
}
// To save us on extra work, check cards earned so far first
HtmlNode cardsEarnedNode = htmlNode.SelectSingleNode(".//div[@class='card_drop_info_header']");
string cardsEarnedText = cardsEarnedNode.InnerText;
if (string.IsNullOrEmpty(cardsEarnedText)) {
Bot.ArchiLogger.LogNullError(nameof(cardsEarnedText));
continue;
}
Match cardsEarnedMatch = Regex.Match(cardsEarnedText, @"\d+");
if (!cardsEarnedMatch.Success) {
Bot.ArchiLogger.LogNullError(nameof(cardsEarnedMatch));
continue;
}
ushort cardsEarned;
if (!ushort.TryParse(cardsEarnedMatch.Value, out cardsEarned)) {
Bot.ArchiLogger.LogNullError(nameof(cardsEarned));
continue;
}
if (cardsEarned > 0) {
// If we already earned some cards for this game, it's very likely that it's done
// Let's hope that trusting cardsRemaining AND cardsEarned is enough
// If I ever hear that it's not, I'll most likely need a doctor
continue;
}
// If we have no cardsRemaining and no cardsEarned, it's either:
// - A game we don't own physically, but we have cards from it in inventory
// - F2P game that we didn't spend any money in, but we have cards from it in inventory
// - Steam fuckup
// As you can guess, we must follow the rest of the logic in case of Steam fuckup
// Please kill me ;_;
}
// Hours
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
if (timeNode == null) {
Bot.ArchiLogger.LogNullError(nameof(timeNode));
return;
continue;
}
string hoursString = timeNode.InnerText;
if (string.IsNullOrEmpty(hoursString)) {
Bot.ArchiLogger.LogNullError(nameof(hoursString));
return;
continue;
}
float hours = 0;
Match hoursMatch = Regex.Match(hoursString, @"[0-9\.,]+");
if (hoursMatch.Success) { // Might fail if we have 0.0 hours played
// This might fail if we have exactly 0.0 hours played, that's fine
if (hoursMatch.Success) {
if (!float.TryParse(hoursMatch.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
Bot.ArchiLogger.LogNullError(nameof(hours));
return;
continue;
}
}
// Cards
string progress = progressNode.InnerText;
if (string.IsNullOrEmpty(progress)) {
Bot.ArchiLogger.LogNullError(nameof(progress));
return;
}
Match progressMatch = Regex.Match(progress, @"\d+");
if (!progressMatch.Success) {
Bot.ArchiLogger.LogNullError(nameof(progressMatch));
return;
}
ushort cardsRemaining;
if (!ushort.TryParse(progressMatch.Value, out cardsRemaining) || (cardsRemaining == 0)) {
Bot.ArchiLogger.LogNullError(nameof(cardsRemaining));
return;
}
// Names
HtmlNode nameNode = htmlNode.SelectSingleNode("(.//div[@class='card_drop_info_body'])[last()]");
if (nameNode == null) {
Bot.ArchiLogger.LogNullError(nameof(nameNode));
return;
continue;
}
string name = nameNode.InnerText;
if (string.IsNullOrEmpty(name)) {
Bot.ArchiLogger.LogNullError(nameof(name));
return;
continue;
}
// We handle two cases here - normal one, and no card drops remaining
int nameStartIndex = name.IndexOf(" by playing ", StringComparison.Ordinal);
if (nameStartIndex <= 0) {
Bot.ArchiLogger.LogNullError(nameof(nameStartIndex));
return;
nameStartIndex = name.IndexOf("You don't have any more drops remaining for ", StringComparison.Ordinal);
if (nameStartIndex <= 0) {
Bot.ArchiLogger.LogNullError(nameof(nameStartIndex));
continue;
}
nameStartIndex += 32; // + 12 below
}
nameStartIndex += 12;
@@ -378,14 +452,24 @@ namespace ArchiSteamFarm {
int nameEndIndex = name.LastIndexOf('.');
if (nameEndIndex <= nameStartIndex) {
Bot.ArchiLogger.LogNullError(nameof(nameEndIndex));
return;
continue;
}
name = name.Substring(nameStartIndex, nameEndIndex - nameStartIndex);
// Final result
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining));
// We have two possible cases here
// Either we have decent info about appID, name, hours and cardsRemaining (cardsRemaining > 0)
// OR we strongly believe that Steam lied to us, in this case we will need to check game invidually (cardsRemaining == 0)
if (cardsRemaining > 0) {
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining));
} else {
extraTasks.Add(CheckGame(appID, name, hours));
}
}
// If we have any pending tasks, wait for them
await Task.WhenAll(extraTasks).ConfigureAwait(false);
}
private async Task CheckPage(byte page) {
@@ -399,7 +483,7 @@ namespace ArchiSteamFarm {
return;
}
CheckPage(htmlDocument);
await CheckPage(htmlDocument).ConfigureAwait(false);
}
private async Task<bool> Farm(Game game) {
@@ -522,6 +606,39 @@ namespace ArchiSteamFarm {
return true;
}
private async Task<ushort?> GetCardsRemaining(uint appID) {
if (appID == 0) {
Bot.ArchiLogger.LogNullError(nameof(appID));
return 0;
}
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
HtmlNode progressNode = htmlDocument?.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
if (progressNode == null) {
return null;
}
string progress = progressNode.InnerText;
if (string.IsNullOrEmpty(progress)) {
Bot.ArchiLogger.LogNullError(nameof(progress));
return null;
}
Match match = Regex.Match(progress, @"\d+");
if (!match.Success) {
return 0;
}
ushort cardsRemaining;
if (ushort.TryParse(match.Value, out cardsRemaining) && (cardsRemaining != 0)) {
return cardsRemaining;
}
Bot.ArchiLogger.LogNullError(nameof(cardsRemaining));
return null;
}
private async Task<bool> IsAnythingToFarm() {
Bot.ArchiLogger.LogGenericInfo("Checking badges...");
@@ -550,19 +667,16 @@ namespace ArchiSteamFarm {
}
GamesToFarm.ClearAndTrim();
CheckPage(htmlDocument);
if (maxPages == 1) {
SortGamesToFarm();
return GamesToFarm.Count > 0;
}
List<Task> tasks = new List<Task>(maxPages - 1) { CheckPage(htmlDocument) };
Bot.ArchiLogger.LogGenericInfo("Checking other pages...");
if (maxPages > 1) {
Bot.ArchiLogger.LogGenericInfo("Checking other pages...");
List<Task> tasks = new List<Task>(maxPages - 1);
for (byte page = 2; page <= maxPages; page++) {
byte currentPage = page; // We need a copy of variable being passed when in for loops, as loop will proceed before task is launched
tasks.Add(CheckPage(currentPage));
for (byte page = 2; page <= maxPages; page++) {
byte currentPage = page; // We need a copy of variable being passed when in for loops, as loop will proceed before task is launched
tasks.Add(CheckPage(currentPage));
}
}
await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -576,37 +690,16 @@ namespace ArchiSteamFarm {
return false;
}
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(game.AppID).ConfigureAwait(false);
if (htmlDocument == null) {
ushort? cardsRemaining = await GetCardsRemaining(game.AppID).ConfigureAwait(false);
if (!cardsRemaining.HasValue) {
Bot.ArchiLogger.LogGenericWarning("Could not check cards status for " + game.AppID + " (" + game.GameName + "), will try again later!");
return null;
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
if (htmlNode == null) {
Bot.ArchiLogger.LogNullError(nameof(htmlNode));
return null;
}
game.CardsRemaining = cardsRemaining.Value;
string progress = htmlNode.InnerText;
if (string.IsNullOrEmpty(progress)) {
Bot.ArchiLogger.LogNullError(nameof(progress));
return null;
}
ushort cardsRemaining = 0;
Match match = Regex.Match(progress, @"\d+");
if (match.Success) {
if (!ushort.TryParse(match.Value, out cardsRemaining)) {
Bot.ArchiLogger.LogNullError(nameof(cardsRemaining));
return null;
}
}
game.CardsRemaining = cardsRemaining;
Bot.ArchiLogger.LogGenericInfo("Status for " + game.AppID + " (" + game.GameName + "): " + cardsRemaining + " cards remaining");
return cardsRemaining > 0;
Bot.ArchiLogger.LogGenericInfo("Status for " + game.AppID + " (" + game.GameName + "): " + game.CardsRemaining + " cards remaining");
return game.CardsRemaining > 0;
}
private void SortGamesToFarm() {
@@ -673,11 +766,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

@@ -32,7 +32,7 @@ namespace ArchiSteamFarm {
internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(encrypted));
Program.ArchiLogger.LogNullError(nameof(encrypted));
return null;
}
@@ -50,7 +50,7 @@ namespace ArchiSteamFarm {
internal static string Encrypt(ECryptoMethod cryptoMethod, string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
ASF.ArchiLogger.LogNullError(nameof(decrypted));
Program.ArchiLogger.LogNullError(nameof(decrypted));
return null;
}
@@ -68,7 +68,7 @@ namespace ArchiSteamFarm {
internal static void SetEncryptionKey(string key) {
if (string.IsNullOrEmpty(key)) {
ASF.ArchiLogger.LogNullError(nameof(key));
Program.ArchiLogger.LogNullError(nameof(key));
return;
}
@@ -77,7 +77,7 @@ namespace ArchiSteamFarm {
private static string DecryptAES(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(encrypted));
Program.ArchiLogger.LogNullError(nameof(encrypted));
return null;
}
@@ -91,14 +91,14 @@ namespace ArchiSteamFarm {
decryptedData = SteamKit2.CryptoHelper.SymmetricDecrypt(decryptedData, key);
return Encoding.UTF8.GetString(decryptedData);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
}
private static string DecryptProtectedDataForCurrentUser(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(encrypted));
Program.ArchiLogger.LogNullError(nameof(encrypted));
return null;
}
@@ -108,14 +108,14 @@ namespace ArchiSteamFarm {
return Encoding.UTF8.GetString(decryptedData);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
}
private static string EncryptAES(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
ASF.ArchiLogger.LogNullError(nameof(decrypted));
Program.ArchiLogger.LogNullError(nameof(decrypted));
return null;
}
@@ -129,14 +129,14 @@ namespace ArchiSteamFarm {
encryptedData = SteamKit2.CryptoHelper.SymmetricEncrypt(encryptedData, key);
return Convert.ToBase64String(encryptedData);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
}
private static string EncryptProtectedDataForCurrentUser(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
ASF.ArchiLogger.LogNullError(nameof(decrypted));
Program.ArchiLogger.LogNullError(nameof(decrypted));
return null;
}
@@ -146,7 +146,7 @@ namespace ArchiSteamFarm {
return Convert.ToBase64String(encryptedData);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
}

View File

@@ -38,11 +38,11 @@ namespace ArchiSteamFarm {
internal sealed class DebugListener : IDebugListener {
public void WriteLine(string category, string msg) {
if (string.IsNullOrEmpty(category) && string.IsNullOrEmpty(msg)) {
ASF.ArchiLogger.LogNullError(nameof(category) + " && " + nameof(msg));
Program.ArchiLogger.LogNullError(nameof(category) + " && " + nameof(msg));
return;
}
ASF.ArchiLogger.LogGenericDebug(category + " | " + msg);
Program.ArchiLogger.LogGenericDebug(category + " | " + msg);
}
}
}

View File

@@ -33,7 +33,7 @@ namespace ArchiSteamFarm {
return;
}
ASF.ArchiLogger.LogGenericInfo("No bots are running, exiting");
Program.ArchiLogger.LogGenericInfo("No bots are running, exiting");
await Task.Delay(5000).ConfigureAwait(false);
Program.Shutdown();
}

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<Costura IncludeDebugSymbols='false' />
</Weavers>

View File

@@ -42,7 +42,7 @@ namespace ArchiSteamFarm {
private const ushort DefaultWCFPort = 1242;
// This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730, 566020 };
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoRestart = true;
@@ -59,9 +59,6 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte FarmingDelay = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool ForceHttp = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte GiftsLimiterDelay = 1;
@@ -102,14 +99,14 @@ namespace ArchiSteamFarm {
internal readonly ushort WCFPort = DefaultWCFPort;
[JsonProperty]
internal string WCFHostname { get; set; } = "localhost";
internal string WCFHost { get; set; } = "127.0.0.1";
// This constructor is used only by deserializer
private GlobalConfig() { }
internal static GlobalConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
Program.ArchiLogger.LogNullError(nameof(filePath));
return null;
}
@@ -122,12 +119,12 @@ namespace ArchiSteamFarm {
try {
globalConfig = JsonConvert.DeserializeObject<GlobalConfig>(File.ReadAllText(filePath));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
if (globalConfig == null) {
ASF.ArchiLogger.LogNullError(nameof(globalConfig));
Program.ArchiLogger.LogNullError(nameof(globalConfig));
return null;
}
@@ -138,24 +135,24 @@ namespace ArchiSteamFarm {
case ProtocolType.Udp:
break;
default:
ASF.ArchiLogger.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol);
Program.ArchiLogger.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol);
return null;
}
// User might not know what he's doing
// Ensure that he can't screw core ASF variables
if (globalConfig.MaxFarmingTime == 0) {
ASF.ArchiLogger.LogGenericWarning("Configured MaxFarmingTime is invalid: " + globalConfig.MaxFarmingTime);
Program.ArchiLogger.LogGenericWarning("Configured MaxFarmingTime is invalid: " + globalConfig.MaxFarmingTime);
return null;
}
if (globalConfig.FarmingDelay == 0) {
ASF.ArchiLogger.LogGenericWarning("Configured FarmingDelay is invalid: " + globalConfig.FarmingDelay);
Program.ArchiLogger.LogGenericWarning("Configured FarmingDelay is invalid: " + globalConfig.FarmingDelay);
return null;
}
if (globalConfig.HttpTimeout == 0) {
ASF.ArchiLogger.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout);
Program.ArchiLogger.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout);
return null;
}
@@ -163,7 +160,7 @@ namespace ArchiSteamFarm {
return globalConfig;
}
ASF.ArchiLogger.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort);
Program.ArchiLogger.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort);
return null;
}

View File

@@ -37,6 +37,9 @@ namespace ArchiSteamFarm {
}
};
[JsonProperty(Required = Required.DisallowNull)]
internal readonly Guid Guid = Guid.NewGuid();
[JsonProperty(Required = Required.DisallowNull)]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
@@ -81,7 +84,7 @@ namespace ArchiSteamFarm {
internal static GlobalDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
Program.ArchiLogger.LogNullError(nameof(filePath));
return null;
}
@@ -94,12 +97,12 @@ namespace ArchiSteamFarm {
try {
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath), CustomSerializerSettings);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
if (globalDatabase == null) {
ASF.ArchiLogger.LogNullError(nameof(globalDatabase));
Program.ArchiLogger.LogNullError(nameof(globalDatabase));
return null;
}
@@ -112,7 +115,7 @@ namespace ArchiSteamFarm {
private void Save() {
string json = JsonConvert.SerializeObject(this, CustomSerializerSettings);
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json));
Program.ArchiLogger.LogNullError(nameof(json));
return;
}
@@ -122,7 +125,7 @@ namespace ArchiSteamFarm {
File.WriteAllText(FilePath, json);
break;
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
}
Thread.Sleep(1000);

View File

@@ -39,7 +39,7 @@ namespace ArchiSteamFarm {
public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endPoints) {
if (endPoints == null) {
ASF.ArchiLogger.LogNullError(nameof(endPoints));
Program.ArchiLogger.LogNullError(nameof(endPoints));
return Task.Delay(0);
}

View File

@@ -67,25 +67,25 @@ namespace ArchiSteamFarm.JSON {
HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='tradeoffer']");
if (htmlNode == null) {
ASF.ArchiLogger.LogNullError(nameof(htmlNode));
Program.ArchiLogger.LogNullError(nameof(htmlNode));
return 0;
}
string id = htmlNode.GetAttributeValue("id", null);
if (string.IsNullOrEmpty(id)) {
ASF.ArchiLogger.LogNullError(nameof(id));
Program.ArchiLogger.LogNullError(nameof(id));
return 0;
}
int index = id.IndexOf('_');
if (index < 0) {
ASF.ArchiLogger.LogNullError(nameof(index));
Program.ArchiLogger.LogNullError(nameof(index));
return 0;
}
index++;
if (id.Length <= index) {
ASF.ArchiLogger.LogNullError(nameof(id.Length));
Program.ArchiLogger.LogNullError(nameof(id.Length));
return 0;
}
@@ -94,7 +94,7 @@ namespace ArchiSteamFarm.JSON {
return _TradeOfferID;
}
ASF.ArchiLogger.LogNullError(nameof(_TradeOfferID));
Program.ArchiLogger.LogNullError(nameof(_TradeOfferID));
return 0;
}
}
@@ -131,13 +131,13 @@ namespace ArchiSteamFarm.JSON {
HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//a/@data-miniprofile");
if (htmlNode == null) {
ASF.ArchiLogger.LogNullError(nameof(htmlNode));
Program.ArchiLogger.LogNullError(nameof(htmlNode));
return 0;
}
string miniProfile = htmlNode.GetAttributeValue("data-miniprofile", null);
if (string.IsNullOrEmpty(miniProfile)) {
ASF.ArchiLogger.LogNullError(nameof(miniProfile));
Program.ArchiLogger.LogNullError(nameof(miniProfile));
return 0;
}
@@ -145,7 +145,7 @@ namespace ArchiSteamFarm.JSON {
return _OtherSteamID3;
}
ASF.ArchiLogger.LogNullError(nameof(_OtherSteamID3));
Program.ArchiLogger.LogNullError(nameof(_OtherSteamID3));
return 0;
}
}
@@ -182,7 +182,7 @@ namespace ArchiSteamFarm.JSON {
set {
if (value == null) {
ASF.ArchiLogger.LogNullError(nameof(value));
Program.ArchiLogger.LogNullError(nameof(value));
return;
}
@@ -237,13 +237,13 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
Program.ArchiLogger.LogNullError(nameof(value));
return;
}
uint amount;
if (!uint.TryParse(value, out amount) || (amount == 0)) {
ASF.ArchiLogger.LogNullError(nameof(amount));
Program.ArchiLogger.LogNullError(nameof(amount));
return;
}
@@ -258,13 +258,13 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
Program.ArchiLogger.LogNullError(nameof(value));
return;
}
uint appID;
if (!uint.TryParse(value, out appID) || (appID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appID));
Program.ArchiLogger.LogNullError(nameof(appID));
return;
}
@@ -278,13 +278,13 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
Program.ArchiLogger.LogNullError(nameof(value));
return;
}
ulong assetID;
if (!ulong.TryParse(value, out assetID) || (assetID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(assetID));
Program.ArchiLogger.LogNullError(nameof(assetID));
return;
}
@@ -299,7 +299,7 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
Program.ArchiLogger.LogNullError(nameof(value));
return;
}
@@ -319,13 +319,13 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
Program.ArchiLogger.LogNullError(nameof(value));
return;
}
ulong contextID;
if (!ulong.TryParse(value, out contextID) || (contextID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(contextID));
Program.ArchiLogger.LogNullError(nameof(contextID));
return;
}
@@ -359,19 +359,28 @@ namespace ArchiSteamFarm.JSON {
internal enum EType : byte {
Unknown,
BoosterPack,
Coupon,
Gift,
SteamGems,
Emoticon,
Gift,
FoilTradingCard,
ProfileBackground,
TradingCard
TradingCard,
SteamGems
}
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class NewDiscoveryQueueResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "queue", Required = Required.Always)]
internal readonly HashSet<uint> Queue;
#pragma warning restore 649
private NewDiscoveryQueueResponse() { }
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class RedeemWalletResponse { // Deserialized from JSON
@@ -396,7 +405,7 @@ namespace ArchiSteamFarm.JSON {
}
if (OtherSteamID3 == 0) {
ASF.ArchiLogger.LogNullError(nameof(OtherSteamID3));
Program.ArchiLogger.LogNullError(nameof(OtherSteamID3));
return 0;
}
@@ -440,7 +449,10 @@ namespace ArchiSteamFarm.JSON {
foreach (Item item in ItemsToReceive) {
Dictionary<Item.EType, uint> itemsPerType;
if (!itemsToReceivePerGame.TryGetValue(item.RealAppID, out itemsPerType)) {
itemsPerType = new Dictionary<Item.EType, uint> { [item.Type] = item.Amount };
itemsPerType = new Dictionary<Item.EType, uint> {
{ item.Type, item.Amount }
};
itemsToReceivePerGame[item.RealAppID] = itemsPerType;
} else {
uint amount;

View File

@@ -116,7 +116,7 @@ namespace ArchiSteamFarm {
private static void OnConfigurationChanged(object sender, LoggingConfigurationChangedEventArgs e) {
if ((sender == null) || (e == null)) {
ASF.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
Program.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
}

View File

@@ -35,6 +35,8 @@ using SteamKit2;
namespace ArchiSteamFarm {
internal static class Program {
internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF);
internal static bool IsWCFRunning => WCF.IsServerRunning;
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
@@ -59,7 +61,7 @@ namespace ArchiSteamFarm {
}
if (GlobalConfig.Headless || !Runtime.IsUserInteractive) {
ASF.ArchiLogger.LogGenericWarning("Received a request for user input, but process is running in headless mode!");
ArchiLogger.LogGenericWarning("Received a request for user input, but process is running in headless mode!");
return null;
}
@@ -96,7 +98,7 @@ namespace ArchiSteamFarm {
Console.Write("<" + botName + "> Please enter your 2 factor auth code from your authenticator app: ");
break;
case ASF.EUserInputType.WCFHostname:
Console.Write("<" + botName + "> Please enter your WCF hostname: ");
Console.Write("<" + botName + "> Please enter your WCF host: ");
break;
default:
Console.Write("<" + botName + "> Please enter not documented yet value of \"" + userInputType + "\": ");
@@ -123,7 +125,7 @@ namespace ArchiSteamFarm {
try {
Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericException(e);
}
ShutdownResetEvent.Set();
@@ -138,7 +140,7 @@ namespace ArchiSteamFarm {
ShutdownResetEvent.Set();
}
private static void Init(string[] args) {
private static async Task Init(string[] args) {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
@@ -169,10 +171,10 @@ namespace ArchiSteamFarm {
}
Logging.InitLoggers();
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
if (!Runtime.IsRuntimeSupported) {
ASF.ArchiLogger.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!");
ArchiLogger.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!");
Thread.Sleep(10000);
}
@@ -193,7 +195,7 @@ namespace ArchiSteamFarm {
// Parse post-init args
if (args != null) {
ParsePostInitArgs(args);
await ParsePostInitArgs(args).ConfigureAwait(false);
}
// If we ran ASF as a client, we're done by now
@@ -201,8 +203,8 @@ namespace ArchiSteamFarm {
Exit();
}
ASF.CheckForUpdate().Wait();
ASF.InitBots();
await ASF.CheckForUpdate().ConfigureAwait(false);
await ASF.InitBots().ConfigureAwait(false);
ASF.InitFileWatcher();
}
@@ -211,7 +213,7 @@ namespace ArchiSteamFarm {
GlobalConfig = GlobalConfig.Load(globalConfigFile);
if (GlobalConfig == null) {
ASF.ArchiLogger.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid! Did you forget to read wiki?");
ArchiLogger.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid! Did you forget to read wiki?");
Thread.Sleep(5000);
Exit(1);
}
@@ -220,7 +222,7 @@ namespace ArchiSteamFarm {
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
if (GlobalDatabase == null) {
ASF.ArchiLogger.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
ArchiLogger.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
Thread.Sleep(5000);
Exit(1);
}
@@ -229,7 +231,7 @@ namespace ArchiSteamFarm {
WebBrowser.Init();
WCF.Init();
WebBrowser = new WebBrowser(ASF.ArchiLogger);
WebBrowser = new WebBrowser(ArchiLogger);
}
private static bool InitShutdownSequence() {
@@ -250,7 +252,7 @@ namespace ArchiSteamFarm {
private static void Main(string[] args) {
if (Runtime.IsUserInteractive) {
// App
Init(args);
Init(args).Wait();
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
@@ -266,9 +268,9 @@ namespace ArchiSteamFarm {
}
}
private static void ParsePostInitArgs(IEnumerable<string> args) {
private static async Task ParsePostInitArgs(IEnumerable<string> args) {
if (args == null) {
ASF.ArchiLogger.LogNullError(nameof(args));
ArchiLogger.LogNullError(nameof(args));
return;
}
@@ -282,7 +284,7 @@ namespace ArchiSteamFarm {
case "--server":
Mode |= EMode.Server;
WCF.StartServer();
ASF.InitBots();
await ASF.InitBots().ConfigureAwait(false);
break;
default:
if (arg.StartsWith("--", StringComparison.Ordinal)) {
@@ -294,12 +296,11 @@ namespace ArchiSteamFarm {
}
if (!Mode.HasFlag(EMode.Client)) {
ASF.ArchiLogger.LogGenericWarning("Ignoring command because --client wasn't specified: " + arg);
ArchiLogger.LogGenericWarning("Ignoring command because --client wasn't specified: " + arg);
break;
}
ASF.ArchiLogger.LogGenericInfo("Command sent: " + arg);
ASF.ArchiLogger.LogGenericInfo("Response received: " + WCF.SendCommand(arg));
ArchiLogger.LogGenericInfo("Response received: " + WCF.SendCommand(arg));
break;
}
}
@@ -307,7 +308,7 @@ namespace ArchiSteamFarm {
private static void ParsePreInitArgs(IEnumerable<string> args) {
if (args == null) {
ASF.ArchiLogger.LogNullError(nameof(args));
ArchiLogger.LogNullError(nameof(args));
return;
}
@@ -335,20 +336,20 @@ namespace ArchiSteamFarm {
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (args?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
return;
}
ASF.ArchiLogger.LogFatalException((Exception) args.ExceptionObject);
ArchiLogger.LogFatalException((Exception) args.ExceptionObject);
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if (args?.Exception == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.Exception));
ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.Exception));
return;
}
ASF.ArchiLogger.LogFatalException(args.Exception);
ArchiLogger.LogFatalException(args.Exception);
}
[Flags]
@@ -363,8 +364,13 @@ namespace ArchiSteamFarm {
ServiceName = SharedInfo.ServiceName;
}
protected override void OnStart(string[] args) => Task.Run(() => {
Init(args);
protected override void OnStart(string[] args) => Task.Run(async () => {
// Normally it'd make sense to use already provided string[] args parameter above
// However, that one doesn't seem to work when ASF is started as a service, it's always null
// Therefore, we will use Environment args in such case
string[] envArgs = Environment.GetCommandLineArgs();
await Init(envArgs).ConfigureAwait(false);
ShutdownResetEvent.Wait();
Stop();
});

View File

@@ -39,38 +39,38 @@ namespace ArchiSteamFarm {
if (IsRunningOnMono) {
Version monoVersion = GetMonoVersion();
if (monoVersion == null) {
ASF.ArchiLogger.LogNullError(nameof(monoVersion));
Program.ArchiLogger.LogNullError(nameof(monoVersion));
return false;
}
Version minMonoVersion = new Version(4, 6);
if (monoVersion >= minMonoVersion) {
ASF.ArchiLogger.LogGenericInfo("Your Mono version is OK. Required: " + minMonoVersion + " | Found: " + monoVersion);
Program.ArchiLogger.LogGenericInfo("Your Mono version is OK. Required: " + minMonoVersion + " | Found: " + monoVersion);
_IsRuntimeSupported = true;
return true;
}
ASF.ArchiLogger.LogGenericWarning("Your Mono version is too old. Required: " + minMonoVersion + " | Found: " + monoVersion);
Program.ArchiLogger.LogGenericWarning("Your Mono version is too old. Required: " + minMonoVersion + " | Found: " + monoVersion);
_IsRuntimeSupported = false;
return false;
}
Version netVersion = GetNetVersion();
if (netVersion == null) {
ASF.ArchiLogger.LogNullError(nameof(netVersion));
Program.ArchiLogger.LogNullError(nameof(netVersion));
return false;
}
Version minNetVersion = new Version(4, 6, 1);
if (netVersion >= minNetVersion) {
ASF.ArchiLogger.LogGenericInfo("Your .NET version is OK. Required: " + minNetVersion + " | Found: " + netVersion);
Program.ArchiLogger.LogGenericInfo("Your .NET version is OK. Required: " + minNetVersion + " | Found: " + netVersion);
_IsRuntimeSupported = true;
return true;
}
ASF.ArchiLogger.LogGenericWarning("Your .NET version is too old. Required: " + minNetVersion + " | Found: " + netVersion);
Program.ArchiLogger.LogGenericWarning("Your .NET version is too old. Required: " + minNetVersion + " | Found: " + netVersion);
_IsRuntimeSupported = false;
return false;
}
@@ -98,33 +98,34 @@ namespace ArchiSteamFarm {
}
}
internal static bool RequiresTls12Testing => IsRunningOnMono;
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
private static bool? _IsRuntimeSupported;
private static bool? _IsUserInteractive;
private static Version GetMonoVersion() {
if (MonoRuntime == null) {
ASF.ArchiLogger.LogNullError(nameof(MonoRuntime));
Program.ArchiLogger.LogNullError(nameof(MonoRuntime));
return null;
}
MethodInfo displayName = MonoRuntime.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayName == null) {
ASF.ArchiLogger.LogNullError(nameof(displayName));
Program.ArchiLogger.LogNullError(nameof(displayName));
return null;
}
string versionString = (string) displayName.Invoke(null, null);
if (string.IsNullOrEmpty(versionString)) {
ASF.ArchiLogger.LogNullError(nameof(versionString));
Program.ArchiLogger.LogNullError(nameof(versionString));
return null;
}
int index = versionString.IndexOf(' ');
if (index <= 0) {
ASF.ArchiLogger.LogNullError(nameof(index));
Program.ArchiLogger.LogNullError(nameof(index));
return null;
}
@@ -135,7 +136,7 @@ namespace ArchiSteamFarm {
return version;
}
ASF.ArchiLogger.LogNullError(nameof(version));
Program.ArchiLogger.LogNullError(nameof(version));
return null;
}
@@ -143,18 +144,18 @@ namespace ArchiSteamFarm {
uint release;
using (RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) {
if (registryKey == null) {
ASF.ArchiLogger.LogNullError(nameof(registryKey));
Program.ArchiLogger.LogNullError(nameof(registryKey));
return null;
}
object releaseObj = registryKey.GetValue("Release");
if (releaseObj == null) {
ASF.ArchiLogger.LogNullError(nameof(releaseObj));
Program.ArchiLogger.LogNullError(nameof(releaseObj));
return null;
}
if (!uint.TryParse(releaseObj.ToString(), out release) || (release == 0)) {
ASF.ArchiLogger.LogNullError(nameof(release));
Program.ArchiLogger.LogNullError(nameof(release));
return null;
}
}

View File

@@ -43,8 +43,8 @@ namespace ArchiSteamFarm {
internal const string LogFile = "log.txt";
internal const string ServiceDescription = "ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.";
internal const string ServiceName = "ArchiSteamFarm";
internal const string StatisticsServer = "https://asf.justarchi.net";
internal const string VersionNumber = "2.1.7.1";
internal const string StatisticsServer = "asf.justarchi.net";
internal const string VersionNumber = "2.2.0.0";
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
}

View File

@@ -24,6 +24,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SteamKit2;
@@ -32,11 +33,21 @@ namespace ArchiSteamFarm {
internal sealed class Statistics : IDisposable {
private const byte MinHeartBeatTTL = 5; // Minimum amount of minutes we must wait before sending next HeartBeat
private static readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1);
private static string _URL;
private readonly Bot Bot;
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
private bool HasAutomatedTrading => Bot.HasMobileAuthenticator && Bot.HasValidApiKey;
private bool SteamTradeMatcher => Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher);
private string LastAvatarHash;
private DateTime LastHeartBeat = DateTime.MinValue;
private bool? LastMatchEverything;
private string LastNickname;
private bool ShouldSendHeartBeats;
internal Statistics(Bot bot) {
if (bot == null) {
@@ -49,20 +60,21 @@ namespace ArchiSteamFarm {
public void Dispose() => Semaphore.Dispose();
internal async Task OnHeartBeat() {
if (DateTime.Now < LastHeartBeat.AddMinutes(MinHeartBeatTTL)) {
if (!ShouldSendHeartBeats || (DateTime.Now < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
return;
}
await Semaphore.WaitAsync().ConfigureAwait(false);
try {
if (DateTime.Now < LastHeartBeat.AddMinutes(MinHeartBeatTTL)) {
if (!ShouldSendHeartBeats || (DateTime.Now < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
return;
}
const string request = SharedInfo.StatisticsServer + "/api/HeartBeat";
Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "SteamID", Bot.SteamID.ToString() }
string request = await GetURL().ConfigureAwait(false) + "/api/HeartBeat";
Dictionary<string, string> data = new Dictionary<string, string>(2) {
{ "SteamID", Bot.SteamID.ToString() },
{ "Guid", Program.GlobalDatabase.Guid.ToString("N") }
};
// We don't need retry logic here
@@ -74,58 +86,110 @@ namespace ArchiSteamFarm {
}
}
internal async Task OnLoggedOn() {
await Bot.ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).ConfigureAwait(false);
await Semaphore.WaitAsync().ConfigureAwait(false);
try {
const string request = SharedInfo.StatisticsServer + "/api/LoggedOn";
Dictionary<string, string> data = new Dictionary<string, string>(4) {
{ "SteamID", Bot.SteamID.ToString() },
{ "HasAutomatedTrading", Bot.HasMobileAuthenticator && Bot.HasValidApiKey ? "1" : "0" },
{ "SteamTradeMatcher", Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher) ? "1" : "0" },
{ "MatchEverything", Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) ? "1" : "0" }
};
// We don't need retry logic here
await Program.WebBrowser.UrlPost(request, data).ConfigureAwait(false);
} finally {
Semaphore.Release();
}
}
internal async Task OnLoggedOn() => await Bot.ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).ConfigureAwait(false);
internal async Task OnPersonaState(SteamFriends.PersonaStateCallback callback) {
if (callback == null) {
ASF.ArchiLogger.LogNullError(nameof(callback));
Program.ArchiLogger.LogNullError(nameof(callback));
return;
}
string avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
if (!string.IsNullOrEmpty(LastAvatarHash) && avatarHash.Equals(LastAvatarHash)) {
// Don't announce if we don't meet conditions
if (!HasAutomatedTrading || !SteamTradeMatcher) {
ShouldSendHeartBeats = false;
return;
}
string nickname = callback.Name ?? "";
string avatarHash = "";
if ((callback.AvatarHash != null) && (callback.AvatarHash.Length > 0) && callback.AvatarHash.Any(singleByte => singleByte != 0)) {
avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
if (avatarHash.Equals("0000000000000000000000000000000000000000")) {
avatarHash = "";
}
}
bool matchEverything = Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything);
// Skip announcing if we already announced this bot with the same data
if (!string.IsNullOrEmpty(LastNickname) && nickname.Equals(LastNickname) && !string.IsNullOrEmpty(LastAvatarHash) && avatarHash.Equals(LastAvatarHash) && LastMatchEverything.HasValue && (matchEverything == LastMatchEverything.Value)) {
return;
}
await Semaphore.WaitAsync().ConfigureAwait(false);
try {
if (!string.IsNullOrEmpty(LastAvatarHash) && avatarHash.Equals(LastAvatarHash)) {
// Skip announcing if we already announced this bot with the same data
if (!string.IsNullOrEmpty(LastNickname) && nickname.Equals(LastNickname) && !string.IsNullOrEmpty(LastAvatarHash) && avatarHash.Equals(LastAvatarHash) && LastMatchEverything.HasValue && (matchEverything == LastMatchEverything.Value)) {
return;
}
const string request = SharedInfo.StatisticsServer + "/api/PersonaState";
Dictionary<string, string> data = new Dictionary<string, string>(2) {
// Even if following request fails, we want to send HeartBeats regardless
ShouldSendHeartBeats = true;
string request = await GetURL().ConfigureAwait(false) + "/api/Announce";
Dictionary<string, string> data = new Dictionary<string, string>(5) {
{ "SteamID", Bot.SteamID.ToString() },
{ "AvatarHash", avatarHash }
{ "Guid", Program.GlobalDatabase.Guid.ToString("N") },
{ "Nickname", nickname },
{ "AvatarHash", avatarHash },
{ "MatchEverything", matchEverything ? "1" : "0" }
};
// We don't need retry logic here
if (await Program.WebBrowser.UrlPost(request, data).ConfigureAwait(false)) {
LastNickname = nickname;
LastAvatarHash = avatarHash;
LastMatchEverything = matchEverything;
}
} finally {
Semaphore.Release();
}
}
private static async Task<string> GetURL() {
if (!string.IsNullOrEmpty(_URL)) {
return _URL;
}
// Our statistics server is using TLS 1.2 encryption method, which is not supported e.g. by older versions of Mono
// That's not a problem, as we support HTTP too, but of course we prefer more secure HTTPS if possible
// Because of that, this function is responsible for finding which URL should be used for accessing the server
// If our runtime doesn't require TLS 1.2 tests, skip the rest entirely, just use HTTPS
const string httpsURL = "https://" + SharedInfo.StatisticsServer;
if (!Runtime.RequiresTls12Testing) {
_URL = httpsURL;
return _URL;
}
await InitializationSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (!string.IsNullOrEmpty(_URL)) {
return _URL;
}
// If we connect using HTTPS, use HTTPS as default version
if (await Program.WebBrowser.UrlPost(httpsURL + "/api/ConnectionTest").ConfigureAwait(false)) {
_URL = httpsURL;
return _URL;
}
// If we connect using HTTP, use HTTP as default version instead
const string httpURL = "http://" + SharedInfo.StatisticsServer;
if (await Program.WebBrowser.UrlPost(httpURL + "/api/ConnectionTest").ConfigureAwait(false)) {
_URL = httpURL;
return _URL;
}
} finally {
InitializationSemaphore.Release();
}
// If we didn't manage to establish connection through any of the above, return HTTPS, but don't record it
// We might need to re-run this function in the future
return httpsURL;
}
}
}

View File

@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using HtmlAgilityPack;
namespace ArchiSteamFarm {
internal sealed class SteamSaleEvent : IDisposable {
private static readonly DateTime SaleEndingDateUtc = new DateTime(2017, 1, 2, 18, 0, 0, DateTimeKind.Utc);
private readonly Bot Bot;
private readonly Timer SteamAwardsTimer;
private readonly Timer SteamDiscoveryQueueTimer;
internal SteamSaleEvent(Bot bot) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
}
Bot = bot;
if (DateTime.UtcNow >= SaleEndingDateUtc) {
return;
}
SteamAwardsTimer = new Timer(
async e => await VoteForSteamAwards().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(1 + 0.2 * Bot.Bots.Count), // Delay
TimeSpan.FromHours(6.1) // Period
);
SteamDiscoveryQueueTimer = new Timer(
async e => await ExploreDiscoveryQueue().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(1 + 0.2 * Bot.Bots.Count), // Delay
TimeSpan.FromHours(6.1) // Period
);
}
public void Dispose() {
SteamAwardsTimer?.Dispose();
SteamDiscoveryQueueTimer?.Dispose();
}
private async Task ExploreDiscoveryQueue() {
if (DateTime.UtcNow >= SaleEndingDateUtc) {
return;
}
if (!Bot.ArchiWebHandler.Ready) {
return;
}
Bot.ArchiLogger.LogGenericDebug("Started!");
for (byte i = 0; (i < 3) && !(await IsDiscoveryQueueEmpty().ConfigureAwait(false)).GetValueOrDefault(); i++) {
Bot.ArchiLogger.LogGenericDebug("Getting new queue...");
HashSet<uint> queue = await Bot.ArchiWebHandler.GenerateNewDiscoveryQueue().ConfigureAwait(false);
if (queue == null) {
Bot.ArchiLogger.LogGenericWarning("Aborting due to error!");
break;
}
Bot.ArchiLogger.LogGenericDebug("We got new queue, clearing...");
foreach (uint queuedAppID in queue) {
Bot.ArchiLogger.LogGenericDebug("Clearing " + queuedAppID + "...");
if (await Bot.ArchiWebHandler.ClearFromDiscoveryQueue(queuedAppID).ConfigureAwait(false)) {
continue;
}
Bot.ArchiLogger.LogGenericWarning("Aborting due to error!");
i = byte.MaxValue;
break;
}
}
Bot.ArchiLogger.LogGenericDebug("Done!");
}
private async Task<bool?> IsDiscoveryQueueEmpty() {
if (!Bot.ArchiWebHandler.Ready) {
return null;
}
Bot.ArchiLogger.LogGenericDebug("Checking if discovery queue is empty...");
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetDiscoveryQueuePage().ConfigureAwait(false);
if (htmlDocument == null) {
Bot.ArchiLogger.LogGenericDebug("Could not get discovery queue page, returning null");
return null;
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='subtext']");
if (htmlNode == null) {
Bot.ArchiLogger.LogNullError(nameof(htmlNode));
return null;
}
string text = htmlNode.InnerText;
if (!string.IsNullOrEmpty(text)) {
// It'd make more sense to check "Come back tomorrow", but it might not cover out-of-the-event queue
Bot.ArchiLogger.LogGenericDebug("Our text is: " + text);
return !text.StartsWith("You can get ", StringComparison.Ordinal);
}
Bot.ArchiLogger.LogNullError(nameof(text));
return null;
}
private async Task VoteForSteamAwards() {
if (DateTime.UtcNow >= SaleEndingDateUtc) {
return;
}
if (!Bot.ArchiWebHandler.Ready) {
return;
}
Bot.ArchiLogger.LogGenericDebug("Getting SteamAwards page...");
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetSteamAwardsPage().ConfigureAwait(false);
HtmlNodeCollection nominationsNodes = htmlDocument?.DocumentNode.SelectNodes("//div[@class='vote_nominations store_horizontal_autoslider']");
if (nominationsNodes == null) {
// Event ended, error or likewise
Bot.ArchiLogger.LogGenericDebug("Could not get SteamAwards page, returning");
return;
}
foreach (HtmlNode nominationsNode in nominationsNodes) {
HtmlNode myVoteNode = nominationsNode.SelectSingleNode("./div[@class='vote_nomination your_vote']");
if (myVoteNode != null) {
// Already voted
Bot.ArchiLogger.LogGenericDebug("We voted already, nothing to do");
continue;
}
string voteIDText = nominationsNode.GetAttributeValue("data-voteid", null);
if (string.IsNullOrEmpty(voteIDText)) {
Bot.ArchiLogger.LogNullError(nameof(voteIDText));
return;
}
byte voteID;
if (!byte.TryParse(voteIDText, out voteID) || (voteID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(voteID));
return;
}
HtmlNodeCollection voteNodes = nominationsNode.SelectNodes("./div[@class='vote_nomination ']");
if (voteNodes == null) {
Bot.ArchiLogger.LogNullError(nameof(voteNodes));
return;
}
// Random a game we'll actually vote for, we don't want to make GabeN angry by rigging votes...
HtmlNode voteNode = voteNodes[Utilities.RandomNext(voteNodes.Count)];
string appIDText = voteNode.GetAttributeValue("data-vote-appid", null);
if (string.IsNullOrEmpty(appIDText)) {
Bot.ArchiLogger.LogNullError(nameof(appIDText));
return;
}
uint appID;
if (!uint.TryParse(appIDText, out appID) || (appID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID));
return;
}
Bot.ArchiLogger.LogGenericDebug("Voting in #" + voteID + " for " + appID + "...");
await Bot.ArchiWebHandler.SteamAwardsVote(voteID, appID).ConfigureAwait(false);
Bot.ArchiLogger.LogGenericDebug("Done!");
}
}
}
}

View File

@@ -110,7 +110,7 @@ namespace ArchiSteamFarm {
if (Bot.HasMobileAuthenticator) {
HashSet<ulong> acceptedWithItemLoseTradeIDs = new HashSet<ulong>(results.Where(result => (result != null) && (result.Result == ParseTradeResult.EResult.AcceptedWithItemLose)).Select(result => result.TradeID));
if (acceptedWithItemLoseTradeIDs.Count > 0) {
await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
await Task.Delay(3000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, acceptedWithItemLoseTradeIDs).ConfigureAwait(false);
}
}
@@ -230,7 +230,7 @@ namespace ArchiSteamFarm {
// Now check if it's worth for us to do the trade
await LimitInventoryRequestsAsync().ConfigureAwait(false);
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false).ConfigureAwait(false);
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, new HashSet<Steam.Item.EType> { Steam.Item.EType.TradingCard }).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose); // OK, assume that this trade is valid, we can't check our EQ
}

View File

@@ -30,13 +30,15 @@ using System.Runtime.CompilerServices;
namespace ArchiSteamFarm {
internal static class Utilities {
private static readonly Random Random = new Random();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
internal static void Forget(this object obj) { }
internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
ASF.ArchiLogger.LogNullError(nameof(url) + " || " + nameof(name));
Program.ArchiLogger.LogNullError(nameof(url) + " || " + nameof(name));
return null;
}
@@ -45,14 +47,29 @@ namespace ArchiSteamFarm {
try {
uri = new Uri(url);
} catch (UriFormatException e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
CookieCollection cookies = cookieContainer.GetCookies(uri);
return cookies.Count == 0 ? null : (from Cookie cookie in cookies where cookie.Name.Equals(name) select cookie.Value).FirstOrDefault();
return cookies.Count != 0 ? (from Cookie cookie in cookies where cookie.Name.Equals(name) select cookie.Value).FirstOrDefault() : null;
}
internal static uint GetUnixTime() => (uint) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
internal static uint GetUnixTime() => (uint) DateTimeOffset.Now.ToUnixTimeSeconds();
internal static int RandomNext(int maxWithout) {
if (maxWithout <= 0) {
Program.ArchiLogger.LogNullError(nameof(maxWithout));
return -1;
}
if (maxWithout == 1) {
return 0;
}
lock (Random) {
return Random.Next(maxWithout);
}
}
}
}

View File

@@ -26,7 +26,6 @@ using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
namespace ArchiSteamFarm {
[ServiceContract]
@@ -39,7 +38,7 @@ namespace ArchiSteamFarm {
}
internal sealed class WCF : IWCF, IDisposable {
private static string URL = "http://localhost:1242/ASF";
private static string URL = "net.tcp://127.0.0.1:1242/ASF";
internal bool IsServerRunning => ServiceHost != null;
@@ -55,7 +54,7 @@ namespace ArchiSteamFarm {
public string HandleCommand(string input) {
if (string.IsNullOrEmpty(input)) {
ASF.ArchiLogger.LogNullError(nameof(input));
Program.ArchiLogger.LogNullError(nameof(input));
return null;
}
@@ -69,31 +68,36 @@ namespace ArchiSteamFarm {
}
string command = "!" + input;
string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
ASF.ArchiLogger.LogGenericInfo("Answered to command: " + input + " with: " + output);
// TODO: This should be asynchronous, but for some reason Mono doesn't return any WCF output if it is
// We must keep it synchronous until either Mono gets fixed, or culprit for freeze located (and corrected)
string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result;
Program.ArchiLogger.LogGenericInfo("Answered to WCF command: " + input + " with: " + output);
return output;
}
internal static void Init() {
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
Program.GlobalConfig.WCFHostname = Program.GetUserInput(ASF.EUserInputType.WCFHostname);
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHost)) {
Program.GlobalConfig.WCFHost = Program.GetUserInput(ASF.EUserInputType.WCFHostname);
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHost)) {
return;
}
}
URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";
URL = "net.tcp://" + Program.GlobalConfig.WCFHost + ":" + Program.GlobalConfig.WCFPort + "/ASF";
}
internal string SendCommand(string input) {
if (string.IsNullOrEmpty(input)) {
ASF.ArchiLogger.LogNullError(nameof(input));
Program.ArchiLogger.LogNullError(nameof(input));
return null;
}
Program.ArchiLogger.LogGenericInfo("Sending command: " + input + " to WCF server on " + URL + "...");
if (Client == null) {
Client = new Client(new BasicHttpBinding(), new EndpointAddress(URL));
Client = new Client(new NetTcpBinding { Security = { Mode = SecurityMode.None } }, new EndpointAddress(URL));
}
return Client.HandleCommand(input);
@@ -104,23 +108,19 @@ namespace ArchiSteamFarm {
return;
}
ASF.ArchiLogger.LogGenericInfo("Starting WCF server...");
Program.ArchiLogger.LogGenericInfo("Starting WCF server on " + URL + "...");
try {
ServiceHost = new ServiceHost(typeof(WCF), new Uri(URL));
ServiceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
ServiceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), string.Empty);
ServiceHost.AddServiceEndpoint(typeof(IWCF), new NetTcpBinding { Security = { Mode = SecurityMode.None } }, string.Empty);
ServiceHost.Open();
ASF.ArchiLogger.LogGenericInfo("WCF server ready!");
Program.ArchiLogger.LogGenericInfo("WCF server ready!");
} catch (AddressAccessDeniedException) {
ASF.ArchiLogger.LogGenericError("WCF service could not be started because of AddressAccessDeniedException!");
ASF.ArchiLogger.LogGenericWarning("If you want to use WCF service provided by ASF, consider starting ASF as administrator, or giving proper permissions!");
Program.ArchiLogger.LogGenericError("WCF service could not be started because of AddressAccessDeniedException!");
Program.ArchiLogger.LogGenericWarning("If you want to use WCF service provided by ASF, consider starting ASF as administrator, or giving proper permissions!");
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
}
}
@@ -133,7 +133,7 @@ namespace ArchiSteamFarm {
try {
ServiceHost.Close();
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
}
}
@@ -158,14 +158,14 @@ namespace ArchiSteamFarm {
internal string HandleCommand(string input) {
if (string.IsNullOrEmpty(input)) {
ASF.ArchiLogger.LogNullError(nameof(input));
Program.ArchiLogger.LogNullError(nameof(input));
return null;
}
try {
return Channel.HandleCommand(input);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
Program.ArchiLogger.LogGenericException(e);
return null;
}
}

View File

@@ -102,6 +102,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return null;
}
@@ -121,6 +122,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return null;
}
@@ -140,6 +142,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return null;
}
@@ -178,6 +181,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return null;
}
@@ -197,6 +201,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return false;
}
@@ -216,6 +221,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return null;
}
@@ -246,6 +252,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return false;
}
@@ -334,6 +341,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return null;
}
@@ -470,6 +478,7 @@ namespace ArchiSteamFarm {
}
ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries");
ArchiLogger.LogGenericDebug("Failing request: " + request);
return null;
}
@@ -488,10 +497,6 @@ namespace ArchiSteamFarm {
return null;
}
if (request.StartsWith("https://", StringComparison.Ordinal) && Program.GlobalConfig.ForceHttp) {
return null;
}
HttpResponseMessage responseMessage;
using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) {
if (data != null) {
@@ -512,7 +517,7 @@ namespace ArchiSteamFarm {
} catch (Exception e) {
// This exception is really common, don't bother with it unless debug mode is enabled
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericDebugException(e);
}
return null;
@@ -528,9 +533,9 @@ namespace ArchiSteamFarm {
}
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
ArchiLogger.LogGenericError("Request: " + request + " failed!");
ArchiLogger.LogGenericError("Status code: " + responseMessage.StatusCode);
ArchiLogger.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false));
ArchiLogger.LogGenericDebug("Request: " + request + " failed!");
ArchiLogger.LogGenericDebug("Status code: " + responseMessage.StatusCode);
ArchiLogger.LogGenericDebug("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false));
}
responseMessage.Dispose();

View File

@@ -1,29 +1,29 @@
{
"Debug": false,
"Headless": false,
"AutoUpdates": true,
"AutoRestart": true,
"UpdateChannel": 1,
"SteamProtocol": 6,
"SteamOwnerID": 0,
"MaxFarmingTime": 10,
"IdleFarmingPeriod": 3,
"FarmingDelay": 15,
"LoginLimiterDelay": 10,
"InventoryLimiterDelay": 3,
"GiftsLimiterDelay": 1,
"MaxTradeHoldDuration": 15,
"ForceHttp": false,
"HttpTimeout": 60,
"WCFHostname": "localhost",
"WCFPort": 1242,
"Statistics": true,
"Blacklist": [
267420,
303700,
335590,
368020,
425280,
480730
]
"AutoRestart": true,
"AutoUpdates": true,
"Blacklist": [
267420,
303700,
335590,
368020,
425280,
480730,
566020
],
"Debug": false,
"FarmingDelay": 15,
"GiftsLimiterDelay": 1,
"Headless": false,
"HttpTimeout": 60,
"IdleFarmingPeriod": 3,
"InventoryLimiterDelay": 3,
"LoginLimiterDelay": 10,
"MaxFarmingTime": 10,
"MaxTradeHoldDuration": 15,
"Statistics": true,
"SteamOwnerID": 0,
"SteamProtocol": 6,
"UpdateChannel": 1,
"WCFHost": "127.0.0.1",
"WCFPort": 1242
}

View File

@@ -1,29 +1,33 @@
{
"Enabled": false,
"Paused": false,
"SteamLogin": null,
"SteamPassword": null,
"PasswordFormat": 0,
"SteamParentalPIN": "0",
"SteamApiKey": null,
"SteamMasterID": 0,
"SteamMasterClanID": 0,
"CardDropsRestricted": true,
"DismissInventoryNotifications": true,
"FarmingOrder": 0,
"FarmOffline": false,
"HandleOfflineMessages": false,
"AcceptGifts": false,
"IsBotAccount": false,
"ForwardKeysToOtherBots": false,
"DistributeKeys": false,
"ShutdownOnFarmingFinished": false,
"SendOnFarmingFinished": false,
"SteamTradeToken": null,
"SendTradePeriod": 0,
"TradingPreferences": 1,
"AcceptConfirmationsPeriod": 0,
"CustomGamePlayedWhileFarming": null,
"CustomGamePlayedWhileIdle": null,
"GamesPlayedWhileIdle": []
"AcceptConfirmationsPeriod": 0,
"AcceptGifts": false,
"CardDropsRestricted": true,
"CustomGamePlayedWhileFarming": null,
"CustomGamePlayedWhileIdle": null,
"DismissInventoryNotifications": true,
"Enabled": false,
"FarmingOrder": 0,
"FarmOffline": false,
"GamesPlayedWhileIdle": [],
"HandleOfflineMessages": false,
"IsBotAccount": false,
"LootableTypes": [
1,
5,
7
],
"PasswordFormat": 0,
"Paused": false,
"RedeemingPreferences": 0,
"SendOnFarmingFinished": false,
"SendTradePeriod": 0,
"ShutdownOnFarmingFinished": false,
"SteamApiKey": null,
"SteamLogin": null,
"SteamMasterClanID": 0,
"SteamMasterID": 0,
"SteamParentalPIN": "0",
"SteamPassword": null,
"SteamTradeToken": null,
"TradingPreferences": 1
}

View File

@@ -1,5 +1,5 @@
{
"Enabled": false,
"SteamLogin": null,
"SteamPassword": null
"Enabled": false,
"SteamLogin": null,
"SteamPassword": null
}

View File

@@ -28,6 +28,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Drawing.Design;
using System.IO;
using ConfigGenerator.JSON;
using Newtonsoft.Json;
namespace ConfigGenerator {
@@ -56,9 +57,6 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool DismissInventoryNotifications { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
public bool DistributeKeys { get; set; } = false;
[Category("\t\tCore")]
[JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; set; } = false;
@@ -69,9 +67,6 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool FarmOffline { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool ForwardKeysToOtherBots { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public List<uint> GamesPlayedWhileIdle { get; set; } = new List<uint>();
@@ -83,6 +78,9 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool IsBotAccount { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public List<Steam.Item.EType> LootableTypes { get; set; } = new List<Steam.Item.EType> { Steam.Item.EType.BoosterPack, Steam.Item.EType.FoilTradingCard, Steam.Item.EType.TradingCard };
[Category("\tAccess")]
[JsonProperty(Required = Required.DisallowNull)]
public ECryptoMethod PasswordFormat { get; set; } = ECryptoMethod.PlainText;
@@ -91,6 +89,11 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool Paused { get; set; } = false;
[Category("\tAdvanced")]
[Editor(typeof(FlagEnumUiEditor), typeof(UITypeEditor))]
[JsonProperty(Required = Required.DisallowNull)]
public ERedeemingPreferences RedeemingPreferences { get; set; } = ERedeemingPreferences.None;
[JsonProperty(Required = Required.DisallowNull)]
public bool SendOnFarmingFinished { get; set; } = false;
@@ -191,6 +194,13 @@ namespace ConfigGenerator {
NamesDescending
}
[Flags]
internal enum ERedeemingPreferences : byte {
None = 0,
Forwarding = 1,
Distributing = 2
}
[Flags]
internal enum ETradingPreferences : byte {
None = 0,

View File

@@ -73,6 +73,7 @@
<Compile Include="ConfigPage.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="JSON\Steam.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>

View File

@@ -43,7 +43,7 @@ namespace ConfigGenerator {
private const ushort DefaultWCFPort = 1242;
// This is hardcoded blacklist which should not be possible to change
private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730, 566020 };
[Category("\tUpdates")]
[JsonProperty(Required = Required.DisallowNull)]
@@ -64,10 +64,6 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public byte FarmingDelay { get; set; } = DefaultFarmingDelay;
[Category("\tDebugging")]
[JsonProperty(Required = Required.DisallowNull)]
public bool ForceHttp { get; set; } = false;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public byte GiftsLimiterDelay { get; set; } = 1;
@@ -116,7 +112,7 @@ namespace ConfigGenerator {
[Category("\tAccess")]
[JsonProperty]
public string WCFHostname { get; set; } = "localhost";
public string WCFHost { get; set; } = "127.0.0.1";
[Category("\tAccess")]
[JsonProperty(Required = Required.DisallowNull)]

View File

@@ -0,0 +1,44 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System.Diagnostics.CodeAnalysis;
namespace ConfigGenerator.JSON {
internal static class Steam {
internal static class Item {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum EType : byte {
Unknown,
BoosterPack,
Coupon,
Emoticon,
Gift,
FoilTradingCard,
ProfileBackground,
TradingCard,
SteamGems
}
}
}
}

View File

@@ -31,12 +31,18 @@ namespace GUI {
return;
}
if (callback.AvatarHash != null) {
string avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
string avatarURL = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + avatarHash.Substring(0, 2) + "/" + avatarHash + "_full.jpg";
AvatarPictureBox.ImageLocation = avatarURL;
AvatarPictureBox.LoadAsync();
if ((callback.AvatarHash == null) || (callback.AvatarHash.Length == 0)) {
return;
}
string avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
if (string.IsNullOrEmpty(avatarHash)) {
return;
}
string avatarURL = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + avatarHash.Substring(0, 2) + "/" + avatarHash + "_full.jpg";
AvatarPictureBox.ImageLocation = avatarURL;
AvatarPictureBox.LoadAsync();
}
private void AvatarPictureBox_LoadCompleted(object sender, AsyncCompletedEventArgs e) {

View File

@@ -33,7 +33,7 @@ namespace ArchiSteamFarm {
internal static void OnPersonaState(Bot bot, SteamFriends.PersonaStateCallback callback) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
Program.ArchiLogger.LogNullError(nameof(bot));
return;
}

View File

@@ -103,6 +103,9 @@
<Compile Include="..\ArchiSteamFarm\CardsFarmer.cs">
<Link>CardsFarmer.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\CMsgs\CMsgClientClanInviteAction.cs">
<Link>CMsgs\CMsgClientClanInviteAction.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\ConcurrentEnumerator.cs">
<Link>ConcurrentEnumerator.cs</Link>
</Compile>
@@ -148,6 +151,9 @@
<Compile Include="..\ArchiSteamFarm\Statistics.cs">
<Link>Statistics.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\SteamSaleEvent.cs">
<Link>SteamSaleEvent.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\Trading.cs">
<Link>Trading.cs</Link>
</Compile>

View File

@@ -23,7 +23,7 @@ namespace GUI {
internal static void UpdateBotAvatar(string botName, Image image) {
if (string.IsNullOrEmpty(botName) || (image == null)) {
ASF.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(image));
Program.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(image));
return;
}
@@ -68,17 +68,17 @@ namespace GUI {
BotListView.LargeImageList = BotListView.SmallImageList = AvatarImageList;
await Task.Run(async () => {
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
Program.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
ASF.ArchiLogger.LogGenericError("Config directory could not be found!");
Program.ArchiLogger.LogGenericError("Config directory could not be found!");
Environment.Exit(1);
}
await ASF.CheckForUpdate().ConfigureAwait(false);
// Before attempting to connect, initialize our list of CMs
Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider);
await Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false);
});
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
@@ -133,7 +133,7 @@ namespace GUI {
private static Bitmap ResizeImage(Image image, int width, int height) {
if ((image == null) || (width <= 0) || (height <= 0)) {
ASF.ArchiLogger.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height));
Program.ArchiLogger.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height));
return null;
}

View File

@@ -13,6 +13,8 @@ using SteamKit2;
namespace ArchiSteamFarm {
internal static class Program {
internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF);
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static WebBrowser WebBrowser { get; private set; }
@@ -38,7 +40,7 @@ namespace ArchiSteamFarm {
try {
Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericException(e);
}
Environment.Exit(0);
@@ -51,7 +53,7 @@ namespace ArchiSteamFarm {
Logging.InitCoreLoggers();
if (!Runtime.IsRuntimeSupported) {
ASF.ArchiLogger.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!");
ArchiLogger.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!");
}
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
@@ -101,7 +103,7 @@ namespace ArchiSteamFarm {
GlobalConfig = GlobalConfig.Load(globalConfigFile);
if (GlobalConfig == null) {
ASF.ArchiLogger.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!");
ArchiLogger.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!");
Exit(1);
}
@@ -109,14 +111,14 @@ namespace ArchiSteamFarm {
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
if (GlobalDatabase == null) {
ASF.ArchiLogger.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
ArchiLogger.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
Exit(1);
}
ArchiWebHandler.Init();
WebBrowser.Init();
WebBrowser = new WebBrowser(ASF.ArchiLogger);
WebBrowser = new WebBrowser(ArchiLogger);
}
/// <summary>
@@ -132,20 +134,20 @@ namespace ArchiSteamFarm {
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (args?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
return;
}
ASF.ArchiLogger.LogFatalException((Exception) args.ExceptionObject);
ArchiLogger.LogFatalException((Exception) args.ExceptionObject);
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if (args?.Exception == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.Exception));
ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.Exception));
return;
}
ASF.ArchiLogger.LogFatalException(args.Exception);
ArchiLogger.LogFatalException(args.Exception);
}
}
}

Binary file not shown.

Binary file not shown.