Compare commits

..

87 Commits

Author SHA1 Message Date
JustArchi
b60b864aca Misc 2016-05-13 19:43:48 +02:00
JustArchi
4c64141462 Misc 2016-05-13 19:40:55 +02:00
JustArchi
4b50596709 Enhance concurrent access with ArchiBoT concurrent hashset 2016-05-13 19:39:54 +02:00
JustArchi
622f060575 Prefer flags over comments 2016-05-13 19:20:24 +02:00
JustArchi
20038e8c86 Derp 2016-05-13 06:33:25 +02:00
JustArchi
b8faca2517 Gigantic code review (with ReSharper) 2016-05-13 06:32:42 +02:00
JustArchi
6f93139a18 Bump 2016-05-13 01:43:21 +02:00
JustArchi
50b5c7b87f Correct STM algorithm when overpaying with cards from the same game
us: X X Y
them: Y Y Z

trade:
Y -> X + Z

New:
toGive: 1
toReceive: 2 + 0 -> 0 + 2

diff: 1 - 0 = 1
1 > 0 ? True

Old:
toGive: 1
toReceive: 2

1 > 2 ? False
2016-05-13 01:35:17 +02:00
JustArchi
b60448ef4c Bump 2016-05-12 19:00:26 +02:00
JustArchi
fad08a1fa9 Fix linking new authenticator with null password 2016-05-12 16:32:04 +02:00
JustArchi
a180c100c6 Make use of CellID, closes #223 2016-05-08 15:52:57 +02:00
JustArchi
9d97ca16e8 Misc WCF code review 2016-05-07 15:24:09 +02:00
JustArchi
e833415718 Closes #221 2016-05-07 15:08:24 +02:00
JustArchi
6bb296a674 Bump 2016-05-06 23:58:50 +02:00
JustArchi
f1e5874868 Misc 2016-05-06 23:54:47 +02:00
JustArchi
38e2088d09 Misc 2016-05-06 23:48:33 +02:00
JustArchi
8447e07aa0 Allow overpaying also in steam cards 2016-05-06 23:38:22 +02:00
JustArchi
fbe4e4bc6d Allow STM overpaying 2016-05-06 23:31:00 +02:00
JustArchi
f1213607ce Don't react in any way to failed commands sent by non-owner 2016-05-06 18:07:03 +02:00
JustArchi
de832c530b Misc 2016-05-04 16:43:16 +02:00
JustArchi
0c872b17e2 Misc 2016-05-04 16:41:00 +02:00
JustArchi
5fcbb85b4c Bump 2016-05-03 15:54:26 +02:00
JustArchi
3cdc93d373 Cut SendOnFarmingFinished spam
Allow only one trade to be sent if we didn't farm anything
2016-05-03 15:37:11 +02:00
JustArchi
36e99d9139 Code review 2016-05-03 07:26:41 +02:00
JustArchi
7bee2d468b Bump 2016-04-29 16:52:57 +02:00
JustArchi
8630cc40c8 Small WebBrowser enhancements, closes #212 2016-04-29 16:37:42 +02:00
JustArchi
3683195a0e Misc 2016-04-29 15:47:07 +02:00
JustArchi
7f5b946645 Use API is possible in GetOwnedGames, closes #213 2016-04-29 15:44:11 +02:00
JustArchi
fdb194fe67 Misc 2016-04-27 20:28:36 +02:00
JustArchi
9063b9206b Derp 2016-04-27 20:06:49 +02:00
JustArchi
8d300894e5 Add add proper notice if it still fails 2016-04-27 20:06:17 +02:00
JustArchi
3a0d3c444e Make ASF 2FA less prone to steam fuckups 2016-04-27 20:03:48 +02:00
JustArchi
8118fe0690 Misc 2016-04-26 22:05:58 +02:00
JustArchi
344c2ad23d Misc 2016-04-26 18:01:19 +02:00
JustArchi
d1a6613541 Use asterisks for password field 2016-04-26 14:23:44 +02:00
JustArchi
3dc88c65aa Revert recent trades
When steam is going crazy we're also forgetting trades that simply failed to accept
Also, trade that is not valid currently, might be valid in future, especially STM
2016-04-25 14:34:21 +02:00
JustArchi
46384829c9 Misc 2016-04-25 11:42:46 +02:00
JustArchi
415ee8cc57 Misc 2016-04-25 11:10:39 +02:00
JustArchi
351d45e049 Fix more regressions 2016-04-25 10:46:29 +02:00
JustArchi
f81bbc60c5 Fix regression 2016-04-25 10:40:36 +02:00
JustArchi
eb6e93a172 Code review 2016-04-24 23:32:23 +02:00
JustArchi
e17c3ecf2a Enhance status, closes #207 2016-04-23 19:14:31 +02:00
JustArchi
048b0fb538 Misc 2016-04-23 16:36:43 +02:00
JustArchi
ac7ecb6bb4 Misc 2016-04-23 16:34:00 +02:00
JustArchi
fcaf038dac Bump 2016-04-23 16:24:39 +02:00
JustArchi
f3da5d6afc Fix small regression caused by d0cc10f3c6 2016-04-23 16:21:12 +02:00
JustArchi
84f33fcef4 Bump 2016-04-23 16:12:57 +02:00
JustArchi
22f0d423a3 Closes #205 2016-04-23 14:47:39 +02:00
JustArchi
f1d7609796 Send trades also when we didn't farm anything
Previously I avoided that because of !loot looting entire steam EQ, but now when we loot only cards and boosters, that shouldn't be as annoying as before
2016-04-23 02:15:01 +02:00
JustArchi
77386ecae5 Misc 2016-04-22 17:51:13 +02:00
JustArchi
4e86d21ef8 Misc 2016-04-22 17:50:01 +02:00
JustArchi
044fc87691 Closes #201 2016-04-21 20:14:15 +02:00
JustArchi
79fad62a4d Bump 2016-04-21 19:26:47 +02:00
JustArchi
6622d3b147 Misc 2016-04-21 14:48:24 +02:00
JustArchi
6b6d5429ad Fix 1+1 -> 0+2, #84 2016-04-21 04:50:57 +02:00
JustArchi
d59d0a8415 Misc 2016-04-21 02:37:40 +02:00
JustArchi
e3100d3938 Implement super-smart STM calculations, #84
It's excellent now, also fixed inventory not returning all of my 6k items (sigh)
2016-04-21 02:32:36 +02:00
JustArchi
42c020e552 Add AppVeyor config 2016-04-21 01:09:52 +02:00
JustArchi
554273833b Make travis less annoying 2016-04-21 01:02:07 +02:00
JustArchi
093a29df62 Loot only steam cards and boosters, closes #111 2016-04-21 00:56:15 +02:00
JustArchi
31aa6b2e4a Remove my debug 2016-04-21 00:51:55 +02:00
JustArchi
9f7ecdf054 Add support for inventory descriptions, #111 2016-04-21 00:51:22 +02:00
JustArchi
7c4c74bf84 Bump 2016-04-20 23:57:06 +02:00
JustArchi
b8f03abd8b Fix fuckups 2016-04-20 23:48:33 +02:00
JustArchi
47f846540b Closes #200, thanks to @GUiHKX 2016-04-20 23:44:20 +02:00
JustArchi
bc14713079 Bump 2016-04-20 23:22:16 +02:00
JustArchi
770a8fee66 But keep trading only of cards + foils 2016-04-20 23:16:15 +02:00
JustArchi
1df9af08e6 Bugfixes + other types for STM 2016-04-20 23:02:02 +02:00
JustArchi
27464f6120 Add recent trades optimization 2016-04-20 22:19:31 +02:00
JustArchi
dfd45c6e25 Bump 2016-04-20 21:40:08 +02:00
JustArchi
adc1759cee Misc 2016-04-20 21:34:40 +02:00
JustArchi
88369ec71a Put massive amount of work into STM integration, #84 2016-04-20 21:27:57 +02:00
JustArchi
a5d8ae53dd Bump 2016-04-19 19:54:39 +02:00
JustArchi
cd7b65868a Misc 2016-04-19 12:24:07 +02:00
JustArchi
7575704a01 Misc 2016-04-19 12:23:24 +02:00
JustArchi
b6ce8f435c Use more optimized Slim manual reset events 2016-04-18 18:43:58 +02:00
JustArchi
565acca9fb Add AutoRestart property 2016-04-18 18:38:48 +02:00
JustArchi
610954ba73 Alter logic of key distribution, closes #199 2016-04-18 18:01:49 +02:00
JustArchi
74a748b03f Bump 2016-04-17 00:47:30 +02:00
JustArchi
585a075ec9 Closes #198
It was possible that we initiated a loop for bot that was connected, and it got disconnected shortly after, which could result in infinite loop if DistributeKeys was disabled (and nothing would change that bot to other one)
2016-04-17 00:36:38 +02:00
JustArchi
891d40afe1 Fix potential bug found by zinnerz
We might !stop account waiting in invalid password or game playing condition, which will then initiate connect without checking if it's still valid to do so
2016-04-16 19:24:04 +02:00
JustArchi
387f0dd1c7 Bump 2016-04-15 21:33:58 +02:00
JustArchi
8b4d3c219c Remove GUI app from final zip until I'm happy with the way how it works 2016-04-15 21:29:27 +02:00
JustArchi
365877ec89 Misc 2016-04-15 15:08:50 +02:00
JustArchi
f03a43d573 Misc 2016-04-15 00:32:55 +02:00
JustArchi
d15a9cbfca Misc 2016-04-15 00:18:24 +02:00
JustArchi
acfad624fb Bump 2016-04-14 22:44:35 +02:00
41 changed files with 2014 additions and 1236 deletions

View File

@@ -1,4 +1,3 @@
sudo: false
language: csharp language: csharp
solution: ArchiSteamFarm.sln solution: ArchiSteamFarm.sln
@@ -8,3 +7,6 @@ git:
mono: mono:
- weekly - weekly
- latest - latest
notifications:
email: false

View File

@@ -26,7 +26,9 @@ using SteamKit2;
using SteamKit2.Internal; using SteamKit2.Internal;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -36,7 +38,7 @@ namespace ArchiSteamFarm {
internal ArchiHandler(Bot bot) { internal ArchiHandler(Bot bot) {
if (bot == null) { if (bot == null) {
throw new ArgumentNullException("bot"); throw new ArgumentNullException(nameof(bot));
} }
Bot = bot; Bot = bot;
@@ -52,69 +54,60 @@ namespace ArchiSteamFarm {
*/ */
internal sealed class NotificationsCallback : CallbackMsg { internal sealed class NotificationsCallback : CallbackMsg {
internal sealed class Notification { internal enum ENotification : byte {
internal enum ENotificationType : uint { [SuppressMessage("ReSharper", "UnusedMember.Global")]
Unknown = 0, Unknown = 0,
Trading = 1, Trading = 1,
// Only custom below, different than ones available as user_notification_type // Only custom below, different than ones available as user_notification_type
Items = 514 Items = 255
}
internal readonly ENotificationType NotificationType;
internal Notification(ENotificationType notificationType) {
NotificationType = notificationType;
}
} }
internal readonly List<Notification> Notifications; internal readonly HashSet<ENotification> Notifications;
internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) { internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
JobID = jobID; if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
if (msg == null || msg.notifications == null) {
return;
} }
Notifications = new List<Notification>(msg.notifications.Count); JobID = jobID;
foreach (var notification in msg.notifications) {
Notifications.Add(new Notification((Notification.ENotificationType) notification.user_notification_type)); Notifications = new HashSet<ENotification>();
foreach (CMsgClientUserNotifications.Notification notification in msg.notifications) {
Notifications.Add((ENotification) notification.user_notification_type);
} }
} }
internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) { internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {
JobID = jobID; if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
if (msg == null) {
return;
} }
JobID = jobID;
if (msg.count_new_items > 0) { if (msg.count_new_items > 0) {
Notifications = new List<Notification>(1) { Notifications = new HashSet<ENotification> {
new Notification(Notification.ENotificationType.Items) ENotification.Items
}; };
} }
} }
} }
internal sealed class OfflineMessageCallback : CallbackMsg { internal sealed class OfflineMessageCallback : CallbackMsg {
internal readonly uint OfflineMessages; internal readonly uint OfflineMessagesCount;
internal readonly List<uint> Users;
internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) { internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
JobID = jobID; if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
if (msg == null) {
return;
} }
OfflineMessages = msg.offline_messages; JobID = jobID;
Users = msg.friends_with_offline_messages; OfflineMessagesCount = msg.offline_messages;
} }
} }
internal sealed class PurchaseResponseCallback : CallbackMsg { internal sealed class PurchaseResponseCallback : CallbackMsg {
internal enum EPurchaseResult : int { internal enum EPurchaseResult : sbyte {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
Unknown = -1, Unknown = -1,
OK = 0, OK = 0,
AlreadyOwned = 9, AlreadyOwned = 9,
@@ -125,39 +118,35 @@ namespace ArchiSteamFarm {
OnCooldown = 53 OnCooldown = 53
} }
internal readonly EResult Result;
internal readonly EPurchaseResult PurchaseResult; internal readonly EPurchaseResult PurchaseResult;
internal readonly KeyValue ReceiptInfo;
internal readonly Dictionary<uint, string> Items; internal readonly Dictionary<uint, string> Items;
internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) { internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
JobID = jobID; if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
if (msg == null) {
return;
} }
Result = (EResult) msg.eresult; JobID = jobID;
PurchaseResult = (EPurchaseResult) msg.purchase_result_details; PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
if (msg.purchase_receipt_info == null) { if (msg.purchase_receipt_info == null) {
return; return;
} }
ReceiptInfo = new KeyValue(); KeyValue receiptInfo = new KeyValue();
using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) { using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
if (!ReceiptInfo.TryReadAsBinary(ms)) { if (!receiptInfo.TryReadAsBinary(ms)) {
return; return;
} }
List<KeyValue> lineItems = ReceiptInfo["lineitems"].Children; List<KeyValue> lineItems = receiptInfo["lineitems"].Children;
Items = new Dictionary<uint, string>(lineItems.Count); Items = new Dictionary<uint, string>(lineItems.Count);
foreach (KeyValue lineItem in lineItems) { foreach (KeyValue lineItem in lineItems) {
uint appID = (uint) lineItem["PackageID"].AsUnsignedLong(); uint appID = (uint) lineItem["PackageID"].AsUnsignedLong();
string gameName = lineItem["ItemDescription"].AsString(); string gameName = lineItem["ItemDescription"].Value;
gameName = WebUtility.UrlDecode(gameName); // Apparently steam expects client to decode sent HTML gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML
Items.Add(appID, gameName); Items[appID] = gameName;
} }
} }
} }
@@ -172,83 +161,42 @@ namespace ArchiSteamFarm {
*/ */
internal void AcceptClanInvite(ulong clanID) {
if (clanID == 0 || !Client.IsConnected) {
return;
}
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
request.Body.GroupID = clanID;
request.Body.AcceptInvite = true;
Client.Send(request);
}
internal void DeclineClanInvite(ulong clanID) {
if (clanID == 0 || !Client.IsConnected) {
return;
}
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
request.Body.GroupID = clanID;
request.Body.AcceptInvite = false;
Client.Send(request);
}
internal void PlayGame(string gameName) { internal void PlayGame(string gameName) {
if (!Client.IsConnected) { if (!Client.IsConnected) {
return; return;
} }
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed); ClientMsgProtobuf<CMsgClientGamesPlayed> request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
var gamePlayed = new CMsgClientGamesPlayed.GamePlayed();
if (!string.IsNullOrEmpty(gameName)) { if (!string.IsNullOrEmpty(gameName)) {
gamePlayed.game_id = new GameID() {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
};
gamePlayed.game_extra_info = gameName;
}
request.Body.games_played.Add(gamePlayed);
Client.Send(request);
}
internal void PlayGames(params uint[] gameIDs) {
if (!Client.IsConnected) {
return;
}
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
foreach (uint gameID in gameIDs) {
if (gameID == 0) {
continue;
}
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
game_id = new GameID(gameID), game_extra_info = gameName,
game_id = new GameID {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
}
}); });
} }
Client.Send(request); Client.Send(request);
} }
internal void PlayGames(ICollection<uint> gameIDs) { internal void PlayGames(uint gameID) {
if (gameIDs == null || !Client.IsConnected) { if (!Client.IsConnected) {
return; return;
} }
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed); PlayGames(new HashSet<uint> { gameID });
foreach (uint gameID in gameIDs) { }
if (gameID == 0) {
continue;
}
internal void PlayGames(ICollection<uint> gameIDs) {
if ((gameIDs == null) || !Client.IsConnected) {
return;
}
ClientMsgProtobuf<CMsgClientGamesPlayed> request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
foreach (uint gameID in gameIDs.Where(gameID => gameID != 0)) {
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
game_id = new GameID(gameID), game_id = new GameID(gameID)
}); });
} }
@@ -260,7 +208,7 @@ namespace ArchiSteamFarm {
return null; return null;
} }
var request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey) { ClientMsgProtobuf<CMsgClientRegisterKey> request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey) {
SourceJobID = Client.GetNextJobID() SourceJobID = Client.GetNextJobID()
}; };
@@ -284,8 +232,11 @@ namespace ArchiSteamFarm {
SteamID steamID = new SteamID(details.AccountID, details.AccountInstance, Client.ConnectedUniverse, EAccountType.Individual); SteamID steamID = new SteamID(details.AccountID, details.AccountInstance, Client.ConnectedUniverse, EAccountType.Individual);
var logon = new ClientMsgProtobuf<CMsgClientLogon>(EMsg.ClientLogon); ClientMsgProtobuf<CMsgClientLogon> logon = new ClientMsgProtobuf<CMsgClientLogon>(EMsg.ClientLogon);
logon.Body.obfustucated_private_ip = details.LoginID.Value; if (details.LoginID != null) {
logon.Body.obfustucated_private_ip = details.LoginID.Value;
}
logon.ProtoHeader.client_sessionid = 0; logon.ProtoHeader.client_sessionid = 0;
logon.ProtoHeader.steamid = steamID.ConvertToUInt64(); logon.ProtoHeader.steamid = steamID.ConvertToUInt64();
logon.Body.account_name = details.Username; logon.Body.account_name = details.Username;
@@ -341,7 +292,7 @@ namespace ArchiSteamFarm {
return; return;
} }
var response = new ClientMsgProtobuf<CMsgClientOfflineMessageNotification>(packetMsg); ClientMsgProtobuf<CMsgClientOfflineMessageNotification> response = new ClientMsgProtobuf<CMsgClientOfflineMessageNotification>(packetMsg);
Client.PostCallback(new OfflineMessageCallback(packetMsg.TargetJobID, response.Body)); Client.PostCallback(new OfflineMessageCallback(packetMsg.TargetJobID, response.Body));
} }
@@ -350,7 +301,7 @@ namespace ArchiSteamFarm {
return; return;
} }
var response = new ClientMsgProtobuf<CMsgClientItemAnnouncements>(packetMsg); ClientMsgProtobuf<CMsgClientItemAnnouncements> response = new ClientMsgProtobuf<CMsgClientItemAnnouncements>(packetMsg);
Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body)); Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body));
} }
@@ -359,7 +310,7 @@ namespace ArchiSteamFarm {
return; return;
} }
var response = new ClientMsgProtobuf<CMsgClientPurchaseResponse>(packetMsg); ClientMsgProtobuf<CMsgClientPurchaseResponse> response = new ClientMsgProtobuf<CMsgClientPurchaseResponse>(packetMsg);
Client.PostCallback(new PurchaseResponseCallback(packetMsg.TargetJobID, response.Body)); Client.PostCallback(new PurchaseResponseCallback(packetMsg.TargetJobID, response.Body));
} }
@@ -368,7 +319,7 @@ namespace ArchiSteamFarm {
return; return;
} }
var response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg); ClientMsgProtobuf<CMsgClientUserNotifications> response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg);
Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body)); Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body));
} }
} }

View File

@@ -99,10 +99,11 @@
<Compile Include="ArchiWebHandler.cs" /> <Compile Include="ArchiWebHandler.cs" />
<Compile Include="Bot.cs" /> <Compile Include="Bot.cs" />
<Compile Include="BotConfig.cs" /> <Compile Include="BotConfig.cs" />
<Compile Include="ConcurrentEnumerator.cs" />
<Compile Include="ConcurrentHashSet.cs" />
<Compile Include="GlobalDatabase.cs" /> <Compile Include="GlobalDatabase.cs" />
<Compile Include="BotDatabase.cs" /> <Compile Include="BotDatabase.cs" />
<Compile Include="CardsFarmer.cs" /> <Compile Include="CardsFarmer.cs" />
<Compile Include="CMsgs\CMsgClientClanInviteAction.cs" />
<Compile Include="Debugging.cs" /> <Compile Include="Debugging.cs" />
<Compile Include="GlobalConfig.cs" /> <Compile Include="GlobalConfig.cs" />
<Compile Include="JSON\GitHub.cs" /> <Compile Include="JSON\GitHub.cs" />

View File

@@ -28,19 +28,18 @@ using SteamKit2;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using System.Threading; using System.Threading;
using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler { internal sealed class ArchiWebHandler {
private const string SteamCommunity = "steamcommunity.com"; private const string SteamCommunityHost = "steamcommunity.com";
private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds
private static string SteamCommunityURL = "https://" + SteamCommunity; private static string SteamCommunityURL = "https://" + SteamCommunityHost;
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000; private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000;
private readonly Bot Bot; private readonly Bot Bot;
@@ -51,12 +50,57 @@ namespace ArchiSteamFarm {
internal static void Init() { internal static void Init() {
Timeout = Program.GlobalConfig.HttpTimeout * 1000; Timeout = Program.GlobalConfig.HttpTimeout * 1000;
SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunity; SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunityHost;
}
private static uint GetAppIDFromMarketHashName(string hashName) {
if (string.IsNullOrEmpty(hashName)) {
return 0;
}
int index = hashName.IndexOf('-');
if (index < 1) {
return 0;
}
uint appID;
return !uint.TryParse(hashName.Substring(0, index), out appID) ? 0 : appID;
}
private static Steam.Item.EType GetItemType(string name) {
if (string.IsNullOrEmpty(name)) {
return Steam.Item.EType.Unknown;
}
switch (name) {
case "Booster Pack":
return Steam.Item.EType.BoosterPack;
case "Coupon":
return Steam.Item.EType.Coupon;
case "Gift":
return Steam.Item.EType.Gift;
case "Steam Gems":
return Steam.Item.EType.SteamGems;
default:
if (name.EndsWith("Emoticon", StringComparison.Ordinal)) {
return Steam.Item.EType.Emoticon;
}
if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
return Steam.Item.EType.FoilTradingCard;
}
if (name.EndsWith("Profile Background", StringComparison.Ordinal)) {
return Steam.Item.EType.ProfileBackground;
}
return name.EndsWith("Trading Card", StringComparison.Ordinal) ? Steam.Item.EType.TradingCard : Steam.Item.EType.Unknown;
}
} }
internal ArchiWebHandler(Bot bot) { internal ArchiWebHandler(Bot bot) {
if (bot == null) { if (bot == null) {
throw new ArgumentNullException("bot"); throw new ArgumentNullException(nameof(bot));
} }
Bot = bot; Bot = bot;
@@ -65,11 +109,14 @@ namespace ArchiSteamFarm {
} }
internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) { internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce)) { if ((steamClient == null) || string.IsNullOrEmpty(webAPIUserNonce)) {
return false; return false;
} }
ulong steamID = steamClient.SteamID; ulong steamID = steamClient.SteamID;
if (steamID == 0) {
return false;
}
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString())); string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
@@ -116,13 +163,13 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Success!", Bot.BotName); Logging.LogGenericInfo("Success!", Bot.BotName);
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunity)); WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
string steamLogin = authResult["token"].Value; string steamLogin = authResult["token"].Value;
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunity)); WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
string steamLoginSecure = authResult["tokensecure"].Value; string steamLoginSecure = authResult["tokensecure"].Value;
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunity)); WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
if (!UnlockParentalAccount(parentalPin).Result) { if (!UnlockParentalAccount(parentalPin).Result) {
return false; return false;
@@ -132,49 +179,71 @@ namespace ArchiSteamFarm {
return true; return true;
} }
internal async Task<bool?> IsLoggedIn() { internal async Task<bool> AcceptGift(ulong gid) {
HtmlDocument htmlDocument = null; if (gid == 0) {
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) { return false;
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile").ConfigureAwait(false);
} }
if (htmlDocument == null) { if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return false;
return null;
} }
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='account_pulldown']"); string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
return htmlNode != null; if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false;
}
string request = SteamCommunityURL + "/gifts/" + gid + "/acceptunpack";
Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "sessionid", sessionID }
};
bool result = false;
for (byte i = 0; (i < WebBrowser.MaxRetries) && !result; i++) {
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
}
if (result) {
return true;
}
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
} }
internal async Task<bool> RefreshSessionIfNeeded() { internal async Task<bool> JoinGroup(ulong groupID) {
DateTime now = DateTime.Now; if (groupID == 0) {
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) { return false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false;
}
string request = SteamCommunityURL + "/gid/" + groupID;
Dictionary<string, string> data = new Dictionary<string, string>(2) {
{ "sessionID", sessionID },
{ "action", "join" }
};
bool result = false;
for (byte i = 0; (i < WebBrowser.MaxRetries) && !result; i++) {
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
}
if (result) {
return true; return true;
} }
await SessionSemaphore.WaitAsync().ConfigureAwait(false); Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
now = DateTime.Now;
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
SessionSemaphore.Release();
return true;
}
bool result;
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.GetValueOrDefault(true)) {
result = true;
now = DateTime.Now;
LastSessionRefreshCheck = now;
} else {
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
result = await Bot.RefreshSession().ConfigureAwait(false);
}
SessionSemaphore.Release();
return result;
} }
internal async Task<Dictionary<uint, string>> GetOwnedGames() { internal async Task<Dictionary<uint, string>> GetOwnedGames() {
@@ -185,7 +254,7 @@ namespace ArchiSteamFarm {
string request = SteamCommunityURL + "/my/games/?xml=1"; string request = SteamCommunityURL + "/my/games/?xml=1";
XmlDocument response = null; XmlDocument response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
response = await WebBrowser.UrlGetToXML(request).ConfigureAwait(false); response = await WebBrowser.UrlGetToXML(request).ConfigureAwait(false);
} }
@@ -195,7 +264,7 @@ namespace ArchiSteamFarm {
} }
XmlNodeList xmlNodeList = response.SelectNodes("gamesList/games/game"); XmlNodeList xmlNodeList = response.SelectNodes("gamesList/games/game");
if (xmlNodeList == null || xmlNodeList.Count == 0) { if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) {
return null; return null;
} }
@@ -222,7 +291,47 @@ namespace ArchiSteamFarm {
return result; return result;
} }
internal List<Steam.TradeOffer> GetTradeOffers() { internal Dictionary<uint, string> GetOwnedGames(ulong steamID) {
if ((steamID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return null;
}
KeyValue response = null;
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
iPlayerService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
try {
response = iPlayerService.GetOwnedGames(
steamid: steamID,
include_appinfo: 1,
secure: !Program.GlobalConfig.ForceHttp
);
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries");
return null;
}
Dictionary<uint, string> result = new Dictionary<uint, string>(response["games"].Children.Count);
foreach (KeyValue game in response["games"].Children) {
uint appID = (uint) game["appid"].AsUnsignedLong();
if (appID == 0) {
continue;
}
result[appID] = game["name"].Value;
}
return result;
}
internal HashSet<Steam.TradeOffer> GetTradeOffers() {
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return null; return null;
} }
@@ -231,11 +340,12 @@ namespace ArchiSteamFarm {
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) { using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout; iEconService.Timeout = Timeout;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
try { try {
response = iEconService.GetTradeOffers( response = iEconService.GetTradeOffers(
get_received_offers: 1, get_received_offers: 1,
active_only: 1, active_only: 1,
get_descriptions: 1,
secure: !Program.GlobalConfig.ForceHttp secure: !Program.GlobalConfig.ForceHttp
); );
} catch (Exception e) { } catch (Exception e) {
@@ -249,74 +359,92 @@ namespace ArchiSteamFarm {
return null; return null;
} }
List<Steam.TradeOffer> result = new List<Steam.TradeOffer>(); Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>> descriptions = new Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>>();
foreach (KeyValue description in response["descriptions"].Children) {
ulong classID = description["classid"].AsUnsignedLong();
if (classID == 0) {
continue;
}
ulong instanceID = description["instanceid"].AsUnsignedLong();
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(classID, instanceID);
if (descriptions.ContainsKey(key)) {
continue;
}
uint appID = 0;
Steam.Item.EType type = Steam.Item.EType.Unknown;
string hashName = description["market_hash_name"].Value;
if (!string.IsNullOrEmpty(hashName)) {
appID = GetAppIDFromMarketHashName(hashName);
}
string descriptionType = description["type"].Value;
if (!string.IsNullOrEmpty(descriptionType)) {
type = GetItemType(descriptionType);
}
descriptions[key] = new Tuple<uint, Steam.Item.EType>(appID, type);
}
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
foreach (KeyValue trade in response["trade_offers_received"].Children) { foreach (KeyValue trade in response["trade_offers_received"].Children) {
Steam.TradeOffer tradeOffer = new Steam.TradeOffer { Steam.TradeOffer tradeOffer = new Steam.TradeOffer {
tradeofferid = trade["tradeofferid"].AsString(), TradeOfferID = trade["tradeofferid"].AsUnsignedLong(),
accountid_other = (uint) trade["accountid_other"].AsUnsignedLong(), // TODO: Correct this when SK2 with https://github.com/SteamRE/SteamKit/pull/255 gets released OtherSteamID3 = (uint) trade["accountid_other"].AsUnsignedLong(),
trade_offer_state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>() State = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
}; };
foreach (KeyValue item in trade["items_to_give"].Children) { foreach (KeyValue item in trade["items_to_give"].Children) {
tradeOffer.items_to_give.Add(new Steam.Item { Steam.Item steamItem = new Steam.Item {
appid = item["appid"].AsString(), AppID = (uint) item["appid"].AsUnsignedLong(),
contextid = item["contextid"].AsString(), ContextID = item["contextid"].AsUnsignedLong(),
assetid = item["assetid"].AsString(), AssetID = item["assetid"].AsUnsignedLong(),
classid = item["classid"].AsString(), ClassID = item["classid"].AsUnsignedLong(),
instanceid = item["instanceid"].AsString(), InstanceID = item["instanceid"].AsUnsignedLong(),
amount = item["amount"].AsString(), Amount = (uint) item["amount"].AsUnsignedLong()
}); };
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(steamItem.ClassID, steamItem.InstanceID);
Tuple<uint, Steam.Item.EType> description;
if (descriptions.TryGetValue(key, out description)) {
steamItem.RealAppID = description.Item1;
steamItem.Type = description.Item2;
}
tradeOffer.ItemsToGive.Add(steamItem);
} }
foreach (KeyValue item in trade["items_to_receive"].Children) { foreach (KeyValue item in trade["items_to_receive"].Children) {
tradeOffer.items_to_receive.Add(new Steam.Item { Steam.Item steamItem = new Steam.Item {
appid = item["appid"].AsString(), AppID = (uint) item["appid"].AsUnsignedLong(),
contextid = item["contextid"].AsString(), ContextID = item["contextid"].AsUnsignedLong(),
assetid = item["assetid"].AsString(), AssetID = item["assetid"].AsUnsignedLong(),
classid = item["classid"].AsString(), ClassID = item["classid"].AsUnsignedLong(),
instanceid = item["instanceid"].AsString(), InstanceID = item["instanceid"].AsUnsignedLong(),
amount = item["amount"].AsString(), Amount = (uint) item["amount"].AsUnsignedLong()
}); };
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(steamItem.ClassID, steamItem.InstanceID);
Tuple<uint, Steam.Item.EType> description;
if (descriptions.TryGetValue(key, out description)) {
steamItem.RealAppID = description.Item1;
steamItem.Type = description.Item2;
}
tradeOffer.ItemsToReceive.Add(steamItem);
} }
result.Add(tradeOffer); result.Add(tradeOffer);
} }
return result; return result;
} }
internal async Task<bool> JoinClan(ulong clanID) {
if (clanID == 0) {
return false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false;
}
string request = SteamCommunityURL + "/gid/" + clanID;
Dictionary<string, string> data = new Dictionary<string, string>(2) {
{"sessionID", sessionID},
{"action", "join"}
};
bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
}
if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
return true;
}
internal async Task<bool> AcceptTradeOffer(ulong tradeID) { internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
if (tradeID == 0) { if (tradeID == 0) {
return false; return false;
@@ -334,61 +462,140 @@ namespace ArchiSteamFarm {
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID; string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
string request = referer + "/accept"; string request = referer + "/accept";
Dictionary<string, string> data = new Dictionary<string, string>(3) { Dictionary<string, string> data = new Dictionary<string, string>(3) {
{"sessionid", sessionID}, { "sessionid", sessionID },
{"serverid", "1"}, { "serverid", "1" },
{"tradeofferid", tradeID.ToString()} { "tradeofferid", tradeID.ToString() }
}; };
bool result = false; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && !result; i++) {
result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false); result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false);
} }
if (!result) { if (result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return true;
return false;
} }
return true; Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
} }
internal async Task<List<Steam.Item>> GetMyTradableInventory() { internal async Task<HashSet<Steam.Item>> GetMyTradableInventory() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null; return null;
} }
JObject jObject = null; HashSet<Steam.Item> result = new HashSet<Steam.Item>();
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/753/6?trading=1").ConfigureAwait(false);
}
if (jObject == null) { ushort nextPage = 0;
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); while (true) {
return null; string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1&start=" + nextPage;
}
IEnumerable<JToken> jTokens = jObject.SelectTokens("$.rgInventory.*"); JObject jObject = null;
if (jTokens == null) { for (byte i = 0; (i < WebBrowser.MaxRetries) && (jObject == null); i++) {
Logging.LogNullError("jTokens", Bot.BotName); jObject = await WebBrowser.UrlGetToJObject(request).ConfigureAwait(false);
return null; }
}
List<Steam.Item> result = new List<Steam.Item>(); if (jObject == null) {
foreach (JToken jToken in jTokens) { Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
try { return null;
result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString())); }
} catch (Exception e) {
Logging.LogGenericException(e, Bot.BotName); IEnumerable<JToken> descriptions = jObject.SelectTokens("$.rgDescriptions.*");
if (descriptions == null) {
return null;
}
Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>> descriptionMap = new Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>>();
foreach (JToken description in descriptions) {
string classIDString = description["classid"].ToString();
if (string.IsNullOrEmpty(classIDString)) {
continue;
}
ulong classID;
if (!ulong.TryParse(classIDString, out classID) || (classID == 0)) {
continue;
}
string instanceIDString = description["instanceid"].ToString();
if (string.IsNullOrEmpty(instanceIDString)) {
continue;
}
ulong instanceID;
if (!ulong.TryParse(instanceIDString, out instanceID)) {
continue;
}
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(classID, instanceID);
if (descriptionMap.ContainsKey(key)) {
continue;
}
uint appID = 0;
Steam.Item.EType type = Steam.Item.EType.Unknown;
string hashName = description["market_hash_name"].ToString();
if (!string.IsNullOrEmpty(hashName)) {
appID = GetAppIDFromMarketHashName(hashName);
}
string descriptionType = description["type"].ToString();
if (!string.IsNullOrEmpty(descriptionType)) {
type = GetItemType(descriptionType);
}
descriptionMap[key] = new Tuple<uint, Steam.Item.EType>(appID, type);
}
IEnumerable<JToken> items = jObject.SelectTokens("$.rgInventory.*");
if (items == null) {
return null;
}
foreach (JToken item in items) {
Steam.Item steamItem;
try {
steamItem = JsonConvert.DeserializeObject<Steam.Item>(item.ToString());
} catch (JsonException e) {
Logging.LogGenericException(e, Bot.BotName);
continue;
}
if (steamItem == null) {
continue;
}
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(steamItem.ClassID, steamItem.InstanceID);
Tuple<uint, Steam.Item.EType> description;
if (descriptionMap.TryGetValue(key, out description)) {
steamItem.RealAppID = description.Item1;
steamItem.Type = description.Item2;
}
result.Add(steamItem);
}
bool more;
if (!bool.TryParse(jObject["more"].ToString(), out more) || !more) {
break;
}
if (!ushort.TryParse(jObject["more_start"].ToString(), out nextPage)) {
break;
} }
} }
return result; return result;
} }
internal async Task<bool> SendTradeOffer(List<Steam.Item> inventory, ulong partnerID, string token = null) { internal async Task<bool> SendTradeOffer(HashSet<Steam.Item> inventory, ulong partnerID, string token = null) {
if (inventory == null || inventory.Count == 0 || partnerID == 0) { if ((inventory == null) || (inventory.Count == 0) || (partnerID == 0)) {
return false; return false;
} }
@@ -402,50 +609,54 @@ namespace ArchiSteamFarm {
return false; return false;
} }
List<Steam.TradeOfferRequest> trades = new List<Steam.TradeOfferRequest>(1 + inventory.Count / Trading.MaxItemsPerTrade); Steam.TradeOfferRequest singleTrade = new Steam.TradeOfferRequest();
HashSet<Steam.TradeOfferRequest> trades = new HashSet<Steam.TradeOfferRequest> { singleTrade };
Steam.TradeOfferRequest singleTrade = null; byte itemID = 0;
for (ushort i = 0; i < inventory.Count; i++) { foreach (Steam.Item item in inventory) {
if (i % Trading.MaxItemsPerTrade == 0) { if (itemID >= Trading.MaxItemsPerTrade) {
if (trades.Count >= Trading.MaxTradesPerAccount) { if (trades.Count >= Trading.MaxTradesPerAccount) {
break; break;
} }
singleTrade = new Steam.TradeOfferRequest(); singleTrade = new Steam.TradeOfferRequest();
trades.Add(singleTrade); trades.Add(singleTrade);
itemID = 0;
} }
Steam.Item item = inventory[i]; singleTrade.ItemsToGive.Assets.Add(new Steam.Item {
singleTrade.me.assets.Add(new Steam.Item() { AppID = Steam.Item.SteamAppID,
appid = "753", ContextID = Steam.Item.SteamContextID,
contextid = "6", Amount = item.Amount,
amount = item.amount, AssetID = item.AssetID
assetid = item.id
}); });
itemID++;
} }
string referer = SteamCommunityURL + "/tradeoffer/new"; string referer = SteamCommunityURL + "/tradeoffer/new";
string request = referer + "/send"; string request = referer + "/send";
foreach (Steam.TradeOfferRequest trade in trades) { foreach (Steam.TradeOfferRequest trade in trades) {
Dictionary<string, string> data = new Dictionary<string, string>(6) { Dictionary<string, string> data = new Dictionary<string, string>(6) {
{"sessionid", sessionID}, { "sessionid", sessionID },
{"serverid", "1"}, { "serverid", "1" },
{"partner", partnerID.ToString()}, { "partner", partnerID.ToString() },
{"tradeoffermessage", "Sent by ASF"}, { "tradeoffermessage", "Sent by ASF" },
{"json_tradeoffer", JsonConvert.SerializeObject(trade)}, { "json_tradeoffer", JsonConvert.SerializeObject(trade) },
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"} { "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}" }
}; };
bool result = false; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && !result; i++) {
result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false); result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false);
} }
if (!result) { if (result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); continue;
return false;
} }
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
} }
return true; return true;
@@ -460,17 +671,19 @@ namespace ArchiSteamFarm {
return null; return null;
} }
string request = SteamCommunityURL + "/my/badges?p=" + page;
HtmlDocument htmlDocument = null; HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && (htmlDocument == null); i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/badges?p=" + page).ConfigureAwait(false); htmlDocument = await WebBrowser.UrlGetToHtmlDocument(request).ConfigureAwait(false);
} }
if (htmlDocument == null) { if (htmlDocument != null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return htmlDocument;
return null;
} }
return htmlDocument; Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
} }
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) { internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
@@ -482,17 +695,19 @@ namespace ArchiSteamFarm {
return null; return null;
} }
string request = SteamCommunityURL + "/my/gamecards/" + appID;
HtmlDocument htmlDocument = null; HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && (htmlDocument == null); i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/gamecards/" + appID).ConfigureAwait(false); htmlDocument = await WebBrowser.UrlGetToHtmlDocument(request).ConfigureAwait(false);
} }
if (htmlDocument == null) { if (htmlDocument != null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return htmlDocument;
return null;
} }
return htmlDocument; Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
} }
internal async Task<bool> MarkInventory() { internal async Task<bool> MarkInventory() {
@@ -500,50 +715,62 @@ namespace ArchiSteamFarm {
return false; return false;
} }
string request = SteamCommunityURL + "/my/inventory";
bool result = false; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && !result; i++) {
result = await WebBrowser.UrlGet(SteamCommunityURL + "/my/inventory").ConfigureAwait(false); result = await WebBrowser.UrlHead(request).ConfigureAwait(false);
} }
if (!result) { if (result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return true;
return false;
} }
return true; Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
} }
internal async Task<bool> AcceptGift(ulong gid) { private async Task<bool?> IsLoggedIn() {
if (gid == 0) { string request = SteamCommunityURL + "/my/profile";
return false;
Uri uri = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (uri == null); i++) {
uri = await WebBrowser.UrlHeadToUri(request).ConfigureAwait(false);
} }
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { if (uri != null) {
return false; return !uri.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
} }
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid"); Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
if (string.IsNullOrEmpty(sessionID)) { return null;
Logging.LogNullError("sessionID"); }
return false;
private async Task<bool> RefreshSessionIfNeeded() {
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
return true;
} }
string request = SteamCommunityURL + "/gifts/" + gid + "/acceptunpack"; await SessionSemaphore.WaitAsync().ConfigureAwait(false);
Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "sessionid", sessionID }
};
bool result = false; if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) { SessionSemaphore.Release();
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false); return true;
} }
if (!result) { bool result;
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false; bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.GetValueOrDefault(true)) {
result = true;
LastSessionRefreshCheck = DateTime.Now;
} else {
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
result = await Bot.RefreshSession().ConfigureAwait(false);
} }
return true; SessionSemaphore.Release();
return result;
} }
private async Task<bool> UnlockParentalAccount(string parentalPin) { private async Task<bool> UnlockParentalAccount(string parentalPin) {
@@ -552,16 +779,15 @@ namespace ArchiSteamFarm {
} }
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName); Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
string request = SteamCommunityURL + "/parental/ajaxunlock";
Dictionary<string, string> data = new Dictionary<string, string>(1) { Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "pin", parentalPin } { "pin", parentalPin }
}; };
string referer = SteamCommunityURL;
string request = referer + "/parental/ajaxunlock";
bool result = false; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && !result; i++) {
result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false); result = await WebBrowser.UrlPost(request, data, SteamCommunityURL).ConfigureAwait(false);
} }
if (!result) { if (!result) {

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
// ReSharper disable once ClassCannotBeInstantiated
// ReSharper disable once ClassNeverInstantiated.Global
internal sealed class BotConfig { internal sealed class BotConfig {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal bool Enabled { get; private set; } = false; internal bool Enabled { get; private set; } = false;
@@ -36,10 +38,10 @@ namespace ArchiSteamFarm {
internal bool StartOnLaunch { get; private set; } = true; internal bool StartOnLaunch { get; private set; } = true;
[JsonProperty] [JsonProperty]
internal string SteamLogin { get; set; } = null; internal string SteamLogin { get; set; }
[JsonProperty] [JsonProperty]
internal string SteamPassword { get; set; } = null; internal string SteamPassword { get; set; }
[JsonProperty] [JsonProperty]
internal string SteamParentalPIN { get; set; } = "0"; internal string SteamParentalPIN { get; set; } = "0";
@@ -68,6 +70,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal bool AcceptGifts { get; private set; } = false; internal bool AcceptGifts { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool SteamTradeMatcher { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal bool ForwardKeysToOtherBots { get; private set; } = false; internal bool ForwardKeysToOtherBots { get; private set; } = false;
@@ -96,7 +101,7 @@ namespace ArchiSteamFarm {
internal string CustomGamePlayedWhileIdle { get; private set; } = null; internal string CustomGamePlayedWhileIdle { get; private set; } = null;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 }; internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint> { 0 };
internal static BotConfig Load(string filePath) { internal static BotConfig Load(string filePath) {

View File

@@ -25,6 +25,7 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using SteamAuth; using SteamAuth;
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
@@ -93,7 +94,7 @@ namespace ArchiSteamFarm {
// This constructor is used when creating new database // This constructor is used when creating new database
private BotDatabase(string filePath) { private BotDatabase(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException("filePath"); throw new ArgumentNullException(nameof(filePath));
} }
FilePath = filePath; FilePath = filePath;
@@ -101,6 +102,7 @@ namespace ArchiSteamFarm {
} }
// This constructor is used only by deserializer // This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private BotDatabase() { } private BotDatabase() { }
internal void Save() { internal void Save() {

View File

@@ -35,10 +35,10 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class CardsFarmer { internal sealed class CardsFarmer {
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>(); internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
internal readonly HashSet<uint> CurrentGamesFarming = new HashSet<uint>(); internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false); private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
private readonly Bot Bot; private readonly Bot Bot;
private readonly Timer Timer; private readonly Timer Timer;
@@ -48,12 +48,12 @@ namespace ArchiSteamFarm {
internal CardsFarmer(Bot bot) { internal CardsFarmer(Bot bot) {
if (bot == null) { if (bot == null) {
throw new ArgumentNullException("bot"); throw new ArgumentNullException(nameof(bot));
} }
Bot = bot; Bot = bot;
if (Timer == null && Program.GlobalConfig.IdleFarmingPeriod > 0) { if ((Timer == null) && (Program.GlobalConfig.IdleFarmingPeriod > 0)) {
Timer = new Timer( Timer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false), async e => await CheckGamesForFarming().ConfigureAwait(false),
null, null,
@@ -84,15 +84,15 @@ namespace ArchiSteamFarm {
return; return;
} }
await Semaphore.WaitAsync().ConfigureAwait(false); await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
if (NowFarming || ManualMode) { if (NowFarming || ManualMode) {
Semaphore.Release(); // We have nothing to do, don't forget to release semaphore FarmingSemaphore.Release(); // We have nothing to do, don't forget to release semaphore
return; return;
} }
if (!await IsAnythingToFarm().ConfigureAwait(false)) { if (!await IsAnythingToFarm().ConfigureAwait(false)) {
Semaphore.Release(); // We have nothing to do, don't forget to release semaphore FarmingSemaphore.Release(); // We have nothing to do, don't forget to release semaphore
Logging.LogGenericInfo("We don't have anything to farm on this account!", Bot.BotName); Logging.LogGenericInfo("We don't have anything to farm on this account!", Bot.BotName);
await Bot.OnFarmingFinished(false).ConfigureAwait(false); await Bot.OnFarmingFinished(false).ConfigureAwait(false);
return; return;
@@ -100,9 +100,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games to farm on this account...", Bot.BotName); Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games to farm on this account...", Bot.BotName);
NowFarming = true; NowFarming = true;
Semaphore.Release(); // From this point we allow other calls to shut us down FarmingSemaphore.Release(); // From this point we allow other calls to shut us down
bool farmedSomething = false;
do { do {
// Now the algorithm used for farming depends on whether account is restricted or not // Now the algorithm used for farming depends on whether account is restricted or not
@@ -114,7 +112,6 @@ namespace ArchiSteamFarm {
while (gamesToFarmSolo.Count > 0) { while (gamesToFarmSolo.Count > 0) {
uint appID = gamesToFarmSolo.First(); uint appID = gamesToFarmSolo.First();
if (await FarmSolo(appID).ConfigureAwait(false)) { if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true;
gamesToFarmSolo.Remove(appID); gamesToFarmSolo.Remove(appID);
gamesToFarmSolo.TrimExcess(); gamesToFarmSolo.TrimExcess();
} else { } else {
@@ -136,21 +133,20 @@ namespace ArchiSteamFarm {
while (GamesToFarm.Count > 0) { while (GamesToFarm.Count > 0) {
uint appID = GamesToFarm.Keys.FirstOrDefault(); uint appID = GamesToFarm.Keys.FirstOrDefault();
if (await FarmSolo(appID).ConfigureAwait(false)) { if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true; continue;
} else {
NowFarming = false;
return;
} }
NowFarming = false;
return;
} }
} }
} while (await IsAnythingToFarm().ConfigureAwait(false)); } while (await IsAnythingToFarm().ConfigureAwait(false));
CurrentGamesFarming.Clear(); CurrentGamesFarming.ClearAndTrim();
CurrentGamesFarming.TrimExcess();
NowFarming = false; NowFarming = false;
Logging.LogGenericInfo("Farming finished!", Bot.BotName); Logging.LogGenericInfo("Farming finished!", Bot.BotName);
await Bot.OnFarmingFinished(farmedSomething).ConfigureAwait(false); await Bot.OnFarmingFinished(true).ConfigureAwait(false);
} }
internal async Task StopFarming() { internal async Task StopFarming() {
@@ -158,10 +154,10 @@ namespace ArchiSteamFarm {
return; return;
} }
await Semaphore.WaitAsync().ConfigureAwait(false); await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
if (!NowFarming) { if (!NowFarming) {
Semaphore.Release(); FarmingSemaphore.Release();
return; return;
} }
@@ -169,7 +165,7 @@ namespace ArchiSteamFarm {
FarmResetEvent.Set(); FarmResetEvent.Set();
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName); Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
for (byte i = 0; i < Program.GlobalConfig.HttpTimeout && NowFarming; i++) { for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && NowFarming; i++) {
await Utilities.SleepAsync(1000).ConfigureAwait(false); await Utilities.SleepAsync(1000).ConfigureAwait(false);
} }
@@ -179,7 +175,7 @@ namespace ArchiSteamFarm {
FarmResetEvent.Reset(); FarmResetEvent.Reset();
Logging.LogGenericInfo("Farming stopped!", Bot.BotName); Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
Semaphore.Release(); FarmingSemaphore.Release();
} }
internal async Task RestartFarming() { internal async Task RestartFarming() {
@@ -193,10 +189,8 @@ namespace ArchiSteamFarm {
} }
HashSet<uint> result = new HashSet<uint>(); HashSet<uint> result = new HashSet<uint>();
foreach (KeyValuePair<uint, float> keyValue in gamesToFarm) { foreach (KeyValuePair<uint, float> keyValue in gamesToFarm.Where(keyValue => keyValue.Value >= 2)) {
if (keyValue.Value >= 2) { result.Add(keyValue.Key);
result.Add(keyValue.Key);
}
} }
return result; return result;
@@ -215,7 +209,7 @@ namespace ArchiSteamFarm {
byte maxPages = 1; byte maxPages = 1;
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']"); HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
if (htmlNodeCollection != null && htmlNodeCollection.Count > 0) { if ((htmlNodeCollection != null) && (htmlNodeCollection.Count > 0)) {
HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1]; HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1];
string lastPage = htmlNode.InnerText; string lastPage = htmlNode.InnerText;
if (!string.IsNullOrEmpty(lastPage)) { if (!string.IsNullOrEmpty(lastPage)) {
@@ -229,16 +223,19 @@ namespace ArchiSteamFarm {
CheckPage(htmlDocument); CheckPage(htmlDocument);
if (maxPages > 1) { if (maxPages <= 1) {
Logging.LogGenericInfo("Checking other pages...", Bot.BotName); return GamesToFarm.Count > 0;
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));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
} }
Logging.LogGenericInfo("Checking other pages...", Bot.BotName);
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));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
return GamesToFarm.Count > 0; return GamesToFarm.Count > 0;
} }
@@ -369,21 +366,15 @@ namespace ArchiSteamFarm {
} }
if (maxHour >= 2) { if (maxHour >= 2) {
CurrentGamesFarming.Clear(); CurrentGamesFarming.ClearAndTrim();
CurrentGamesFarming.TrimExcess();
return true; return true;
} }
Logging.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming), Bot.BotName); Logging.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming), Bot.BotName);
if (FarmHours(maxHour, CurrentGamesFarming)) {
CurrentGamesFarming.Clear(); bool result = FarmHours(maxHour, CurrentGamesFarming);
CurrentGamesFarming.TrimExcess(); CurrentGamesFarming.ClearAndTrim();
return true; return result;
} else {
CurrentGamesFarming.Clear();
CurrentGamesFarming.TrimExcess();
return false;
}
} }
private async Task<bool> FarmSolo(uint appID) { private async Task<bool> FarmSolo(uint appID) {
@@ -394,20 +385,22 @@ namespace ArchiSteamFarm {
CurrentGamesFarming.Add(appID); CurrentGamesFarming.Add(appID);
Logging.LogGenericInfo("Now farming: " + appID, Bot.BotName); Logging.LogGenericInfo("Now farming: " + appID, Bot.BotName);
if (await Farm(appID).ConfigureAwait(false)) {
CurrentGamesFarming.Clear(); bool result = await Farm(appID).ConfigureAwait(false);
CurrentGamesFarming.TrimExcess(); CurrentGamesFarming.ClearAndTrim();
float hours;
if (GamesToFarm.TryRemove(appID, out hours)) { if (!result) {
TimeSpan timeSpan = TimeSpan.FromHours(hours);
Logging.LogGenericInfo("Done farming: " + appID + " after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
}
return true;
} else {
CurrentGamesFarming.Clear();
CurrentGamesFarming.TrimExcess();
return false; return false;
} }
float hours;
if (!GamesToFarm.TryRemove(appID, out hours)) {
return false;
}
TimeSpan timeSpan = TimeSpan.FromHours(hours);
Logging.LogGenericInfo("Done farming: " + appID + " after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
return true;
} }
private async Task<bool> Farm(uint appID) { private async Task<bool> Farm(uint appID) {
@@ -420,8 +413,8 @@ namespace ArchiSteamFarm {
bool success = true; bool success = true;
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false); bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
for (ushort farmingTime = 0; farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) { for (ushort farmingTime = 0; (farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime) && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) {
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false; success = false;
break; break;
} }
@@ -439,8 +432,8 @@ namespace ArchiSteamFarm {
return success; return success;
} }
private bool FarmHours(float maxHour, HashSet<uint> appIDs) { private bool FarmHours(float maxHour, ConcurrentHashSet<uint> appIDs) {
if (maxHour < 0 || appIDs == null || appIDs.Count == 0) { if ((maxHour < 0) || (appIDs == null) || (appIDs.Count == 0)) {
return false; return false;
} }
@@ -452,7 +445,7 @@ namespace ArchiSteamFarm {
bool success = true; bool success = true;
while (maxHour < 2) { while (maxHour < 2) {
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false; success = false;
break; break;
} }

View File

@@ -22,45 +22,37 @@
*/ */
using SteamKit2;
using SteamKit2.Internal;
using System; using System;
using System.IO; using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage { internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> {
internal ulong GroupID { get; set; } = 0; public T Current => Enumerator.Current;
internal bool AcceptInvite { get; set; } = true;
EMsg ISteamSerializableMessage.GetEMsg() { object IEnumerator.Current => Current;
return EMsg.ClientAcknowledgeClanInvite;
private readonly IEnumerator<T> Enumerator;
private readonly ReaderWriterLockSlim Lock;
internal ConcurrentEnumerator(ICollection<T> collection, ReaderWriterLockSlim @lock) {
if ((collection == null) || (@lock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(@lock));
}
@lock.EnterReadLock();
Lock = @lock;
Enumerator = collection.GetEnumerator();
} }
void ISteamSerializable.Serialize(Stream stream) { public bool MoveNext() => Enumerator.MoveNext();
if (stream == null) { public void Reset() => Enumerator.Reset();
return;
}
try { public void Dispose() {
BinaryWriter binaryWriter = new BinaryWriter(stream); if (Lock != null) {
binaryWriter.Write(GroupID); Lock.ExitReadLock();
binaryWriter.Write(AcceptInvite);
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
void ISteamSerializable.Deserialize(Stream stream) {
if (stream == null) {
return;
}
try {
BinaryReader binaryReader = new BinaryReader(stream);
GroupID = binaryReader.ReadUInt64();
AcceptInvite = binaryReader.ReadBoolean();
} catch (Exception e) {
Logging.LogGenericException(e);
} }
} }
} }

View File

@@ -0,0 +1,123 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class ConcurrentHashSet<T> : ICollection<T>, IDisposable {
private readonly HashSet<T> HashSet = new HashSet<T>();
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
public bool IsReadOnly => false;
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);
public int Count {
get {
Lock.EnterReadLock();
try {
return HashSet.Count;
} finally {
Lock.ExitReadLock();
}
}
}
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
public bool Add(T item) {
Lock.EnterWriteLock();
try {
return HashSet.Add(item);
} finally {
Lock.ExitWriteLock();
}
}
public void Clear() {
Lock.EnterWriteLock();
try {
HashSet.Clear();
} finally {
Lock.ExitWriteLock();
}
}
public void ClearAndTrim() {
Lock.EnterWriteLock();
try {
HashSet.Clear();
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
public bool Contains(T item) {
Lock.EnterReadLock();
try {
return HashSet.Contains(item);
} finally {
Lock.ExitReadLock();
}
}
public bool Remove(T item) {
Lock.EnterWriteLock();
try {
return HashSet.Remove(item);
} finally {
Lock.ExitWriteLock();
}
}
public void Dispose() {
if (Lock != null) {
Lock.Dispose();
}
}
public void CopyTo(T[] array, int arrayIndex) {
Lock.EnterReadLock();
try {
HashSet.CopyTo(array, arrayIndex);
} finally {
Lock.ExitReadLock();
}
}
void ICollection<T>.Add(T item) => Add(item);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@@ -24,19 +24,20 @@
using SteamKit2; using SteamKit2;
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Debugging { internal static class Debugging {
#if DEBUG #if DEBUG
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal static readonly bool IsDebugBuild = true; internal static readonly bool IsDebugBuild = true;
#else #else
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal static readonly bool IsDebugBuild = false; internal static readonly bool IsDebugBuild = false;
#endif #endif
internal static bool IsReleaseBuild => !IsDebugBuild; internal static bool NetHookAlreadyInitialized { get; set; }
internal static bool NetHookAlreadyInitialized { get; set; } = false;
internal sealed class DebugListener : IDebugListener { internal sealed class DebugListener : IDebugListener {
private readonly string FilePath; private readonly string FilePath;

View File

@@ -25,11 +25,14 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated"), SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class GlobalConfig { internal sealed class GlobalConfig {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum EUpdateChannel : byte { internal enum EUpdateChannel : byte {
Unknown, Unknown,
Stable, Stable,
@@ -55,6 +58,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true; internal bool AutoUpdates { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoRestart { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable; internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable;
@@ -157,11 +163,13 @@ namespace ArchiSteamFarm {
globalConfig.HttpTimeout = DefaultHttpTimeout; globalConfig.HttpTimeout = DefaultHttpTimeout;
} }
if (globalConfig.WCFPort == 0) { if (globalConfig.WCFPort != 0) {
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort + ". Value of " + DefaultWCFPort + " will be used instead"); return globalConfig;
globalConfig.WCFPort = DefaultWCFPort;
} }
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort + ". Value of " + DefaultWCFPort + " will be used instead");
globalConfig.WCFPort = DefaultWCFPort;
return globalConfig; return globalConfig;
} }

View File

@@ -24,6 +24,7 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
@@ -75,7 +76,7 @@ namespace ArchiSteamFarm {
// This constructor is used when creating new database // This constructor is used when creating new database
private GlobalDatabase(string filePath) { private GlobalDatabase(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException("filePath"); throw new ArgumentNullException(nameof(filePath));
} }
FilePath = filePath; FilePath = filePath;
@@ -83,6 +84,7 @@ namespace ArchiSteamFarm {
} }
// This constructor is used only by deserializer // This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private GlobalDatabase() { } private GlobalDatabase() { }
private void Save() { private void Save() {

View File

@@ -22,20 +22,22 @@
*/ */
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm.JSON {
internal static class GitHub { internal static class GitHub {
internal sealed class Asset { [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global"), SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal string Name { get; private set; }
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal string DownloadURL { get; private set; }
}
internal sealed class ReleaseResponse { internal sealed class ReleaseResponse {
internal sealed class Asset {
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal string Name { get; private set; }
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal string DownloadURL { get; private set; }
}
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)] [JsonProperty(PropertyName = "tag_name", Required = Required.Always)]
internal string Tag { get; private set; } internal string Tag { get; private set; }

View File

@@ -22,46 +22,176 @@
*/ */
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using SteamKit2; using SteamKit2;
using System.Collections.Generic;
namespace ArchiSteamFarm { namespace ArchiSteamFarm.JSON {
internal static class Steam { internal static class Steam {
internal sealed class Item { internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset internal const ushort SteamAppID = 753;
[JsonProperty(Required = Required.DisallowNull)] internal const byte SteamContextID = 6;
internal string appid { get; set; }
[JsonProperty(Required = Required.DisallowNull)] internal enum EType : byte {
internal string contextid { get; set; } Unknown,
[JsonProperty(Required = Required.DisallowNull)] BoosterPack,
internal string assetid { get; set; } Coupon,
Gift,
SteamGems,
[JsonProperty(Required = Required.DisallowNull)] Emoticon,
internal string id { FoilTradingCard,
get { return assetid; } ProfileBackground,
set { assetid = value; } TradingCard
} }
[JsonProperty(Required = Required.AllowNull)] internal uint AppID { get; set; }
internal string classid { get; set; }
[JsonProperty(Required = Required.AllowNull)] [JsonProperty(PropertyName = "appid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
internal string instanceid { get; set; } private string AppIDString {
get {
return AppID.ToString();
}
[JsonProperty(Required = Required.Always)] set {
internal string amount { get; set; } if (string.IsNullOrEmpty(value)) {
return;
}
uint result;
if (!uint.TryParse(value, out result)) {
return;
}
AppID = result;
}
}
internal ulong ContextID { get; set; }
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ContextIDString {
get {
return ContextID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
return;
}
ContextID = result;
}
}
internal ulong AssetID { get; set; }
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
private string AssetIDString {
get {
return AssetID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
return;
}
AssetID = result;
}
}
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ID {
get { return AssetIDString; }
set { AssetIDString = value; }
}
internal ulong ClassID { get; set; }
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ClassIDString {
get {
return ClassID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
return;
}
ClassID = result;
}
}
internal ulong InstanceID { get; set; }
[JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
private string InstanceIDString {
get {
return InstanceID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
return;
}
InstanceID = result;
}
}
internal uint Amount { get; set; }
[JsonProperty(PropertyName = "amount", Required = Required.Always), SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AmountString {
get {
return Amount.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
}
uint result;
if (!uint.TryParse(value, out result)) {
return;
}
Amount = result;
}
}
internal uint RealAppID { get; set; }
internal EType Type { get; set; }
} }
internal sealed class ItemList { internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
[JsonProperty(Required = Required.Always)] [SuppressMessage("ReSharper", "UnusedMember.Global")]
internal List<Steam.Item> assets { get; } = new List<Steam.Item>();
}
internal sealed class TradeOffer {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
internal enum ETradeOfferState : byte { internal enum ETradeOfferState : byte {
Unknown, Unknown,
Invalid, Invalid,
@@ -77,46 +207,119 @@ namespace ArchiSteamFarm {
OnHold OnHold
} }
[JsonProperty(Required = Required.Always)] internal ulong TradeOfferID { get; set; }
internal string tradeofferid { get; set; }
[JsonProperty(Required = Required.Always)] [JsonProperty(PropertyName = "tradeofferid", Required = Required.Always), SuppressMessage("ReSharper", "UnusedMember.Local")]
internal uint accountid_other { get; set; } private string TradeOfferIDString {
[JsonProperty(Required = Required.Always)]
internal ETradeOfferState trade_offer_state { get; set; }
[JsonProperty(Required = Required.Always)]
internal List<Steam.Item> items_to_give { get; } = new List<Steam.Item>();
[JsonProperty(Required = Required.Always)]
internal List<Steam.Item> items_to_receive { get; } = new List<Steam.Item>();
// Extra
private ulong _OtherSteamID64 = 0;
internal ulong OtherSteamID64 {
get { get {
if (_OtherSteamID64 == 0 && accountid_other != 0) { return TradeOfferID.ToString();
_OtherSteamID64 = new SteamID(accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64(); }
set {
if (string.IsNullOrEmpty(value)) {
return;
} }
return _OtherSteamID64; ulong result;
if (!ulong.TryParse(value, out result)) {
return;
}
TradeOfferID = result;
} }
} }
[JsonProperty(PropertyName = "accountid_other", Required = Required.Always)]
internal uint OtherSteamID3 { private get; set; }
[JsonProperty(PropertyName = "trade_offer_state", Required = Required.Always)]
internal ETradeOfferState State { get; set; }
[JsonProperty(PropertyName = "items_to_give", Required = Required.Always)]
internal HashSet<Item> ItemsToGive { get; } = new HashSet<Item>();
[JsonProperty(PropertyName = "items_to_receive", Required = Required.Always)]
internal HashSet<Item> ItemsToReceive { get; } = new HashSet<Item>();
// Extra
internal ulong OtherSteamID64 => OtherSteamID3 == 0 ? 0 : new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
internal bool IsSteamCardsOnlyTradeForUs() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamContextID) && ((item.Type == Item.EType.FoilTradingCard) || (item.Type == Item.EType.TradingCard)));
internal bool IsPotentiallyDupesTradeForUs() {
Dictionary<uint, Dictionary<Item.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
foreach (Item item in ItemsToGive) {
Dictionary<Item.EType, uint> itemsPerType;
if (!itemsToGivePerGame.TryGetValue(item.RealAppID, out itemsPerType)) {
itemsPerType = new Dictionary<Item.EType, uint> { [item.Type] = item.Amount };
itemsToGivePerGame[item.RealAppID] = itemsPerType;
} else {
uint amount;
if (itemsPerType.TryGetValue(item.Type, out amount)) {
itemsPerType[item.Type] = amount + item.Amount;
} else {
itemsPerType[item.Type] = item.Amount;
}
}
}
Dictionary<uint, Dictionary<Item.EType, uint>> itemsToReceivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
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 };
itemsToReceivePerGame[item.RealAppID] = itemsPerType;
} else {
uint amount;
if (itemsPerType.TryGetValue(item.Type, out amount)) {
itemsPerType[item.Type] = amount + item.Amount;
} else {
itemsPerType[item.Type] = item.Amount;
}
}
}
// Ensure that amount of items to give is at least amount of items to receive (per game and per type)
foreach (KeyValuePair<uint, Dictionary<Item.EType, uint>> itemsPerGame in itemsToGivePerGame) {
Dictionary<Item.EType, uint> otherItemsPerType;
if (!itemsToReceivePerGame.TryGetValue(itemsPerGame.Key, out otherItemsPerType)) {
return false;
}
foreach (KeyValuePair<Item.EType, uint> itemsPerType in itemsPerGame.Value) {
uint otherAmount;
if (!otherItemsPerType.TryGetValue(itemsPerType.Key, out otherAmount)) {
return false;
}
if (itemsPerType.Value > otherAmount) {
return false;
}
}
}
return true;
}
} }
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class TradeOfferRequest { internal sealed class TradeOfferRequest {
[JsonProperty(Required = Required.Always)] internal sealed class ItemList {
internal bool newversion { get; } = true; [JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal HashSet<Item> Assets { get; } = new HashSet<Item>();
}
[JsonProperty(Required = Required.Always)] [JsonProperty(PropertyName = "newversion", Required = Required.Always)]
internal int version { get; } = 2; internal bool NewVersion { get; } = true;
[JsonProperty(Required = Required.Always)] [JsonProperty(PropertyName = "version", Required = Required.Always)]
internal Steam.ItemList me { get; } = new Steam.ItemList(); internal byte Version { get; } = 2;
[JsonProperty(Required = Required.Always)] [JsonProperty(PropertyName = "me", Required = Required.Always)]
internal Steam.ItemList them { get; } = new Steam.ItemList(); internal ItemList ItemsToGive { get; } = new ItemList();
[JsonProperty(PropertyName = "them", Required = Required.Always)]
internal ItemList ItemsToReceive { get; } = new ItemList();
} }
} }
} }

View File

@@ -24,6 +24,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -36,18 +37,25 @@ namespace ArchiSteamFarm {
internal static void Init() { internal static void Init() {
LogToFile = Program.GlobalConfig.LogToFile; LogToFile = Program.GlobalConfig.LogToFile;
if (LogToFile) { if (!LogToFile) {
lock (FileLock) { return;
try { }
File.Delete(Program.LogFile);
} catch (Exception e) { lock (FileLock) {
LogGenericException(e); if (!LogToFile) {
} return;
}
try {
File.Delete(Program.LogFile);
} catch (Exception e) {
LogToFile = false;
LogGenericException(e);
} }
} }
} }
internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
return; return;
} }
@@ -55,7 +63,7 @@ namespace ArchiSteamFarm {
Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?"); Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?");
} }
internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
return; return;
} }
@@ -63,20 +71,25 @@ namespace ArchiSteamFarm {
Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message); Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message);
} }
internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = "") { internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (exception == null) { while (true) {
return; if (exception == null) {
} return;
}
Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message); Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message);
Log("[!] StackTrace:" + Environment.NewLine + exception.StackTrace); Log("[!] StackTrace:" + Environment.NewLine + exception.StackTrace);
if (exception.InnerException != null) { if (exception.InnerException != null) {
LogGenericException(exception.InnerException, botName, previousMethodName); exception = exception.InnerException;
continue;
}
break;
} }
} }
internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
return; return;
} }
@@ -84,7 +97,7 @@ namespace ArchiSteamFarm {
Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message); Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message);
} }
internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
return; return;
} }
@@ -92,7 +105,8 @@ namespace ArchiSteamFarm {
Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message); Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message);
} }
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = "") { [SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(nullObjectName)) { if (string.IsNullOrEmpty(nullObjectName)) {
return; return;
} }
@@ -100,8 +114,8 @@ namespace ArchiSteamFarm {
LogGenericError(nullObjectName + " is null!", botName, previousMethodName); LogGenericError(nullObjectName + " is null!", botName, previousMethodName);
} }
[Conditional("DEBUG")] [Conditional("DEBUG"), SuppressMessage("ReSharper", "UnusedMember.Global")]
internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
return; return;
} }
@@ -120,17 +134,26 @@ namespace ArchiSteamFarm {
if (!Program.ConsoleIsBusy) { if (!Program.ConsoleIsBusy) {
try { try {
Console.Write(loggedMessage); Console.Write(loggedMessage);
} catch { } }
catch {
// Ignored
}
} }
if (LogToFile) { if (!LogToFile) {
lock (FileLock) { return;
try { }
File.AppendAllText(Program.LogFile, loggedMessage);
} catch (Exception e) { lock (FileLock) {
LogToFile = false; if (!LogToFile) {
LogGenericException(e); return;
} }
try {
File.AppendAllText(Program.LogFile, loggedMessage);
} catch (Exception e) {
LogToFile = false;
LogGenericException(e);
} }
} }
} }

View File

@@ -26,11 +26,13 @@ using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Program { internal static class Program {
@@ -48,37 +50,36 @@ namespace ArchiSteamFarm {
WCFHostname WCFHostname
} }
internal enum EMode : byte { private enum EMode : byte {
[SuppressMessage("ReSharper", "UnusedMember.Local")]
Unknown, Unknown,
Normal, // Standard most common usage Normal, // Standard most common usage
Client, // WCF client only Client, // WCF client only
Server // Normal + WCF server Server // Normal + WCF server
} }
internal const string ASF = "ASF";
internal const string ConfigDirectory = "config"; internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug"; internal const string DebugDirectory = "debug";
internal const string LogFile = "log.txt"; internal const string LogFile = "log.txt";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm"; internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
internal const string GlobalConfigFile = ASF + ".json";
internal const string GlobalDatabaseFile = ASF + ".db";
private const string ASF = "ASF";
private const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only private const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
private const string GlobalConfigFile = ASF + ".json";
private const string GlobalDatabaseFile = ASF + ".db";
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly(); internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
internal static readonly Version Version = Assembly.GetName().Version;
private static readonly object ConsoleLock = new object(); private static readonly object ConsoleLock = new object();
private static readonly SemaphoreSlim SteamSemaphore = new SemaphoreSlim(1); private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false); private static readonly string ExecutableFile = Assembly.GetEntryAssembly().Location;
private static readonly string ExecutableFile = Assembly.Location;
private static readonly string ExecutableName = Path.GetFileName(ExecutableFile); private static readonly string ExecutableName = Path.GetFileName(ExecutableFile);
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile); private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
private static readonly WCF WCF = new WCF(); private static readonly WCF WCF = new WCF();
internal static GlobalConfig GlobalConfig { get; private set; } internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; } internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static bool ConsoleIsBusy { get; private set; } = false; internal static bool ConsoleIsBusy { get; private set; }
private static Timer AutoUpdatesTimer; private static Timer AutoUpdatesTimer;
private static EMode Mode = EMode.Normal; private static EMode Mode = EMode.Normal;
@@ -111,7 +112,7 @@ namespace ArchiSteamFarm {
string response = null; string response = null;
Logging.LogGenericInfo("Checking new version..."); Logging.LogGenericInfo("Checking new version...");
for (byte i = 0; i < WebBrowser.MaxRetries && string.IsNullOrEmpty(response); i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && string.IsNullOrEmpty(response); i++) {
response = await WebBrowser.UrlGetToContent(releaseURL).ConfigureAwait(false); response = await WebBrowser.UrlGetToContent(releaseURL).ConfigureAwait(false);
} }
@@ -137,7 +138,7 @@ namespace ArchiSteamFarm {
return; return;
} }
if (releases == null || releases.Count == 0) { if ((releases == null) || (releases.Count == 0)) {
Logging.LogGenericWarning("Could not check latest version!"); Logging.LogGenericWarning("Could not check latest version!");
return; return;
} }
@@ -155,15 +156,19 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + newVersion); Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + newVersion);
if (Version.CompareTo(newVersion) >= 0) { // If local version is the same or newer than remote version if (Version.CompareTo(newVersion) >= 0) { // If local version is the same or newer than remote version
if (AutoUpdatesTimer == null && GlobalConfig.AutoUpdates) { if ((AutoUpdatesTimer != null) || !GlobalConfig.AutoUpdates) {
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours"); return;
AutoUpdatesTimer = new Timer(
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
} }
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
AutoUpdatesTimer = new Timer(
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
return; return;
} }
@@ -185,15 +190,7 @@ namespace ArchiSteamFarm {
return; return;
} }
GitHub.Asset binaryAsset = null; GitHub.ReleaseResponse.Asset binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase));
foreach (var asset in releaseResponse.Assets) {
if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName)) {
continue;
}
binaryAsset = asset;
break;
}
if (binaryAsset == null) { if (binaryAsset == null) {
Logging.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!"); Logging.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!");
@@ -206,7 +203,7 @@ namespace ArchiSteamFarm {
} }
byte[] result = null; byte[] result = null;
for (byte i = 0; i < WebBrowser.MaxRetries && result == null; i++) { for (byte i = 0; (i < WebBrowser.MaxRetries) && (result == null); i++) {
Logging.LogGenericInfo("Downloading new version..."); Logging.LogGenericInfo("Downloading new version...");
result = await WebBrowser.UrlGetToBytes(binaryAsset.DownloadURL).ConfigureAwait(false); result = await WebBrowser.UrlGetToBytes(binaryAsset.DownloadURL).ConfigureAwait(false);
} }
@@ -234,7 +231,9 @@ namespace ArchiSteamFarm {
try { try {
// Cleanup // Cleanup
File.Delete(newExeFile); File.Delete(newExeFile);
} catch { } } catch {
// Ignored
}
return; return;
} }
@@ -247,14 +246,23 @@ namespace ArchiSteamFarm {
// Cleanup // Cleanup
File.Move(oldExeFile, ExecutableFile); File.Move(oldExeFile, ExecutableFile);
File.Delete(newExeFile); File.Delete(newExeFile);
} catch { } } catch {
// Ignored
}
return; return;
} }
Logging.LogGenericInfo("Update process is finished! ASF will now restart itself..."); Logging.LogGenericInfo("Update process finished!");
await Utilities.SleepAsync(5000);
Restart(); if (GlobalConfig.AutoRestart) {
Logging.LogGenericInfo("Restarting...");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
Restart();
} else {
Logging.LogGenericInfo("Exiting...");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
Exit();
}
} }
internal static void Exit(int exitCode = 0) { internal static void Exit(int exitCode = 0) {
@@ -272,14 +280,6 @@ namespace ArchiSteamFarm {
Exit(); Exit();
} }
internal static async Task LimitSteamRequestsAsync() {
await SteamSemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Utilities.SleepAsync(GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
SteamSemaphore.Release();
}).Forget();
}
internal static string GetUserInput(EUserInputType userInputType, string botName = null, string extraInformation = null) { internal static string GetUserInput(EUserInputType userInputType, string botName = null, string extraInformation = null) {
if (userInputType == EUserInputType.Unknown) { if (userInputType == EUserInputType.Unknown) {
return null; return null;
@@ -329,6 +329,7 @@ namespace ArchiSteamFarm {
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter not documented yet value of \"" + userInputType + "\": "); Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter not documented yet value of \"" + userInputType + "\": ");
break; break;
} }
result = Console.ReadLine(); result = Console.ReadLine();
if (!Console.IsOutputRedirected) { if (!Console.IsOutputRedirected) {
Console.Clear(); // For security purposes Console.Clear(); // For security purposes
@@ -340,10 +341,8 @@ namespace ArchiSteamFarm {
} }
internal static void OnBotShutdown() { internal static void OnBotShutdown() {
foreach (Bot bot in Bot.Bots.Values) { if (Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
if (bot.KeepRunning) { return;
return;
}
} }
if (WCF.IsServerRunning()) { if (WCF.IsServerRunning()) {
@@ -377,7 +376,11 @@ namespace ArchiSteamFarm {
WebBrowser = new WebBrowser("Main"); WebBrowser = new WebBrowser("Main");
} }
private static void ParseArgs(string[] args) { private static void ParseArgs(IEnumerable<string> args) {
if (args == null) {
return;
}
foreach (string arg in args) { foreach (string arg in args) {
switch (arg) { switch (arg) {
case "--client": case "--client":
@@ -388,7 +391,7 @@ namespace ArchiSteamFarm {
WCF.StartServer(); WCF.StartServer();
break; break;
default: default:
if (arg.StartsWith("--")) { if (arg.StartsWith("--", StringComparison.Ordinal)) {
Logging.LogGenericWarning("Unrecognized parameter: " + arg); Logging.LogGenericWarning("Unrecognized parameter: " + arg);
continue; continue;
} }
@@ -413,7 +416,7 @@ namespace ArchiSteamFarm {
} }
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) { private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (sender == null || args == null) { if ((sender == null) || (args == null)) {
return; return;
} }
@@ -421,14 +424,14 @@ namespace ArchiSteamFarm {
} }
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) { private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if (sender == null || args == null) { if ((sender == null) || (args == null)) {
return; return;
} }
Logging.LogGenericException(args.Exception); Logging.LogGenericException(args.Exception);
} }
private static void Init(string[] args) { private static void Init(IEnumerable<string> args) {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
@@ -440,7 +443,7 @@ namespace ArchiSteamFarm {
if (Debugging.IsDebugBuild) { if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up // Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (var i = 0; i < 4; i++) { for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory(".."); Directory.SetCurrentDirectory("..");
if (Directory.Exists(ConfigDirectory)) { if (Directory.Exists(ConfigDirectory)) {
break; break;
@@ -461,7 +464,7 @@ namespace ArchiSteamFarm {
} }
Directory.CreateDirectory(DebugDirectory); Directory.CreateDirectory(DebugDirectory);
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener(Path.Combine(Program.DebugDirectory, "debug.txt"))); SteamKit2.DebugLog.AddListener(new Debugging.DebugListener(Path.Combine(DebugDirectory, "debug.txt")));
SteamKit2.DebugLog.Enabled = true; SteamKit2.DebugLog.Enabled = true;
} }
@@ -489,8 +492,7 @@ namespace ArchiSteamFarm {
bool isRunning = false; bool isRunning = false;
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.json")) { foreach (string botName in Directory.EnumerateFiles(ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
string botName = Path.GetFileNameWithoutExtension(configFile);
switch (botName) { switch (botName) {
case ASF: case ASF:
case "example": case "example":
@@ -499,8 +501,10 @@ namespace ArchiSteamFarm {
} }
Bot bot = new Bot(botName); Bot bot = new Bot(botName);
if (bot.BotConfig != null && bot.BotConfig.Enabled) { if ((bot.BotConfig != null) && bot.BotConfig.Enabled) {
isRunning = true; if (bot.BotConfig.StartOnLaunch) {
isRunning = true;
}
} else { } else {
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName); Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
} }
@@ -516,7 +520,7 @@ namespace ArchiSteamFarm {
Init(args); Init(args);
// Wait for signal to shutdown // Wait for signal to shutdown
ShutdownResetEvent.WaitOne(); ShutdownResetEvent.Wait();
// We got a signal to shutdown // We got a signal to shutdown
Exit(); Exit();

View File

@@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
@@ -32,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.0.3.1")] [assembly: AssemblyVersion("2.0.4.6")]
[assembly: AssemblyFileVersion("2.0.3.1")] [assembly: AssemblyFileVersion("2.0.4.6")]

View File

@@ -25,8 +25,10 @@
using SteamAuth; using SteamAuth;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class Trading { internal sealed class Trading {
@@ -50,23 +52,19 @@ namespace ArchiSteamFarm {
internal Trading(Bot bot) { internal Trading(Bot bot) {
if (bot == null) { if (bot == null) {
throw new ArgumentNullException("bot"); throw new ArgumentNullException(nameof(bot));
} }
Bot = bot; Bot = bot;
} }
internal async Task CheckTrades() { internal async Task CheckTrades() {
bool shouldRun = false;
lock (TradesSemaphore) { lock (TradesSemaphore) {
if (ParsingTasks < 2) { if (ParsingTasks >= 2) {
ParsingTasks++; return;
shouldRun = true;
} }
}
if (!shouldRun) { ParsingTasks++;
return;
} }
await TradesSemaphore.WaitAsync().ConfigureAwait(false); await TradesSemaphore.WaitAsync().ConfigureAwait(false);
@@ -80,8 +78,8 @@ namespace ArchiSteamFarm {
} }
private async Task ParseActiveTrades() { private async Task ParseActiveTrades() {
List<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers(); HashSet<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
if (tradeOffers == null) { if ((tradeOffers == null) || (tradeOffers.Count == 0)) {
return; return;
} }
@@ -90,43 +88,119 @@ namespace ArchiSteamFarm {
} }
private async Task ParseTrade(Steam.TradeOffer tradeOffer) { private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null || tradeOffer.trade_offer_state != Steam.TradeOffer.ETradeOfferState.Active) { if ((tradeOffer == null) || (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active)) {
return; return;
} }
ulong tradeID; if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
if (!ulong.TryParse(tradeOffer.tradeofferid, out tradeID)) { Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return; await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
}
if (ShouldAcceptTrade(tradeOffer)) {
Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName);
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
} else { } else {
Logging.LogGenericInfo("Ignoring trade: " + tradeID, Bot.BotName); Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
} }
} }
private bool ShouldAcceptTrade(Steam.TradeOffer tradeOffer) { private async Task<bool> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) { if (tradeOffer == null) {
return false; return false;
} }
// Always accept trades when we're not losing anything // Always accept trades when we're not losing anything
if (tradeOffer.items_to_give.Count == 0) { if (tradeOffer.ItemsToGive.Count == 0) {
// Unless it's steam fuckup and we're dealing with broken trade // Unless it's steam fuckup and we're dealing with broken trade
return tradeOffer.items_to_receive.Count > 0; return tradeOffer.ItemsToReceive.Count > 0;
} }
// Always accept trades from SteamMasterID // Always accept trades from SteamMasterID
if (tradeOffer.OtherSteamID64 != 0 && tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) { if ((tradeOffer.OtherSteamID64 != 0) && (tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID)) {
return true; return true;
} }
// TODO: Add optional SteamTradeMatcher integration here // If we don't have SteamTradeMatcher enabled, this is the end for us
if (!Bot.BotConfig.SteamTradeMatcher) {
return false;
}
// If no rule above matched this trade, reject it // Decline trade if we're giving more count-wise
return false; if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) {
return false;
}
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) {
return false;
}
// At this point we're sure that STM trade is valid
// Now check if it's worth for us to do the trade
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return true; // OK, assume that this trade is valid, we can't check our EQ
}
// Get appIDs we're interested in
HashSet<uint> appIDs = new HashSet<uint>();
foreach (Steam.Item item in tradeOffer.ItemsToGive) {
appIDs.Add(item.RealAppID);
}
// Now remove from our inventory all items we're NOT interested in
inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID));
inventory.TrimExcess();
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
if (inventory.Count == 0) {
return true;
}
// Now let's create a map which maps items to their amount in our EQ
Dictionary<Tuple<ulong, ulong>, uint> amountMap = new Dictionary<Tuple<ulong, ulong>, uint>();
foreach (Steam.Item item in inventory) {
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(item.ClassID, item.InstanceID);
uint amount;
if (amountMap.TryGetValue(key, out amount)) {
amountMap[key] = amount + item.Amount;
} else {
amountMap[key] = item.Amount;
}
}
// Calculate our value of items to give
List<uint> amountsToGive = new List<uint>(tradeOffer.ItemsToGive.Count);
foreach (Tuple<ulong, ulong> key in tradeOffer.ItemsToGive.Select(item => new Tuple<ulong, ulong>(item.ClassID, item.InstanceID))) {
uint amount;
if (!amountMap.TryGetValue(key, out amount)) {
amountsToGive.Add(0);
continue;
}
amountsToGive.Add(amount);
}
// Sort it ascending
amountsToGive.Sort();
// Calculate our value of items to receive
List<uint> amountsToReceive = new List<uint>(tradeOffer.ItemsToReceive.Count);
foreach (Tuple<ulong, ulong> key in tradeOffer.ItemsToReceive.Select(item => new Tuple<ulong, ulong>(item.ClassID, item.InstanceID))) {
uint amount;
if (!amountMap.TryGetValue(key, out amount)) {
amountsToReceive.Add(0);
continue;
}
amountsToReceive.Add(amount);
}
// Sort it ascending
amountsToReceive.Sort();
// Check actual difference
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
// Trade is worth for us if the difference is greater than 0
return difference > 0;
} }
} }
} }

View File

@@ -24,60 +24,27 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Utilities { internal static class Utilities {
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
internal static void Forget(this Task task) { } internal static void Forget(this Task task) { }
internal static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) { internal static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) => action == null ? Task.FromResult(true) : Task.WhenAll(sequence.Select(action));
return Task.WhenAll(sequence.Select(action));
}
internal static string GetCookieValue(this CookieContainer cookieContainer, string URL, string name) { internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
if (string.IsNullOrEmpty(URL) || string.IsNullOrEmpty(name)) { if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
return null; return null;
} }
CookieCollection cookies = cookieContainer.GetCookies(new Uri(URL)); CookieCollection cookies = cookieContainer.GetCookies(new Uri(url));
if (cookies == null || cookies.Count == 0) { return cookies.Count == 0 ? null : (from Cookie cookie in cookies where cookie.Name.Equals(name, StringComparison.Ordinal) select cookie.Value).FirstOrDefault();
return null;
}
foreach (Cookie cookie in cookies) {
if (!cookie.Name.Equals(name, StringComparison.Ordinal)) {
continue;
}
return cookie.Value;
}
return null;
} }
internal static Task SleepAsync(int miliseconds) { internal static Task SleepAsync(int miliseconds) => miliseconds < 0 ? Task.FromResult(true) : Task.Delay(miliseconds);
if (miliseconds < 0) {
return Task.FromResult(true);
}
return Task.Delay(miliseconds);
}
internal static uint GetCharCountInString(string s, char c) {
if (string.IsNullOrEmpty(s)) {
return 0;
}
uint count = 0;
foreach (char singleChar in s) {
if (singleChar == c) {
count++;
}
}
return count;
}
} }
} }

View File

@@ -52,9 +52,7 @@ namespace ArchiSteamFarm {
URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF"; URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";
} }
internal bool IsServerRunning() { internal bool IsServerRunning() => ServiceHost != null;
return ServiceHost != null;
}
internal void StartServer() { internal void StartServer() {
if (ServiceHost != null) { if (ServiceHost != null) {
@@ -101,28 +99,16 @@ namespace ArchiSteamFarm {
return null; return null;
} }
string[] args = input.Split(' '); Bot bot = Bot.Bots.Values.FirstOrDefault();
if (bot == null) {
string botName; return "ERROR: No bots are enabled!";
if (args.Length > 1) { // If we have args[1] provided, use given botName
botName = args[1];
} else { // If not, just pick first one
botName = Bot.Bots.Keys.FirstOrDefault();
} }
if (string.IsNullOrEmpty(botName)) { if (Program.GlobalConfig.SteamOwnerID == 0) {
return "ERROR: Invalid botName: " + botName; return "Refusing to handle request because SteamOwnerID is not set!";
} }
Bot bot; string command = "!" + input;
if (!Bot.Bots.TryGetValue(botName, out bot)) {
return "ERROR: Couldn't find any bot named: " + botName;
}
Logging.LogGenericInfo("Received command: " + input);
string command = '!' + input;
string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
Logging.LogGenericInfo("Answered to command: " + input + " with: " + output); Logging.LogGenericInfo("Answered to command: " + input + " with: " + output);

View File

@@ -27,7 +27,6 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -65,7 +64,7 @@ namespace ArchiSteamFarm {
internal WebBrowser(string identifier) { internal WebBrowser(string identifier) {
if (string.IsNullOrEmpty(identifier)) { if (string.IsNullOrEmpty(identifier)) {
throw new ArgumentNullException("identifier"); throw new ArgumentNullException(nameof(identifier));
} }
Identifier = identifier; Identifier = identifier;
@@ -83,23 +82,23 @@ namespace ArchiSteamFarm {
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent); HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
} }
internal async Task<bool> UrlGet(string request, string referer = null) { internal async Task<bool> UrlHead(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return false; return false;
} }
using (HttpResponseMessage response = await UrlGetToResponse(request, referer).ConfigureAwait(false)) { using (HttpResponseMessage response = await UrlHeadToResponse(request, referer).ConfigureAwait(false)) {
return response != null; return response != null;
} }
} }
internal async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) { internal async Task<Uri> UrlHeadToUri(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return false; return null;
} }
using (HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false)) { using (HttpResponseMessage response = await UrlHeadToResponse(request, referer).ConfigureAwait(false)) {
return response != null; return response == null ? null : response.RequestMessage.RequestUri;
} }
} }
@@ -141,10 +140,8 @@ namespace ArchiSteamFarm {
return null; return null;
} }
content = WebUtility.HtmlDecode(content);
HtmlDocument htmlDocument = new HtmlDocument(); HtmlDocument htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(content); htmlDocument.LoadHtml(WebUtility.HtmlDecode(content));
return htmlDocument; return htmlDocument;
} }
@@ -192,6 +189,16 @@ namespace ArchiSteamFarm {
return xmlDocument; return xmlDocument;
} }
internal async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return false;
}
using (HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false)) {
return response != null;
}
}
private async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) { private async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
@@ -200,6 +207,14 @@ namespace ArchiSteamFarm {
return await UrlRequest(request, HttpMethod.Get, null, referer).ConfigureAwait(false); return await UrlRequest(request, HttpMethod.Get, null, referer).ConfigureAwait(false);
} }
private async Task<HttpResponseMessage> UrlHeadToResponse(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
return await UrlRequest(request, HttpMethod.Head, null, referer).ConfigureAwait(false);
}
private async Task<HttpResponseMessage> UrlPostToResponse(string request, Dictionary<string, string> data = null, string referer = null) { private async Task<HttpResponseMessage> UrlPostToResponse(string request, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
@@ -209,17 +224,17 @@ namespace ArchiSteamFarm {
} }
private async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, string referer = null) { private async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request) || httpMethod == null) { if (string.IsNullOrEmpty(request) || (httpMethod == null)) {
return null; return null;
} }
if (request.StartsWith("https://") && Program.GlobalConfig.ForceHttp) { if (request.StartsWith("https://", StringComparison.Ordinal) && Program.GlobalConfig.ForceHttp) {
return null; return null;
} }
HttpResponseMessage responseMessage; HttpResponseMessage responseMessage;
using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) { using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) {
if (data != null && data.Count > 0) { if ((data != null) && (data.Count > 0)) {
try { try {
requestMessage.Content = new FormUrlEncodedContent(data); requestMessage.Content = new FormUrlEncodedContent(data);
} catch (UriFormatException e) { } catch (UriFormatException e) {
@@ -239,19 +254,22 @@ namespace ArchiSteamFarm {
} }
} }
if (responseMessage == null || !responseMessage.IsSuccessStatusCode) { if (responseMessage == null) {
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
Logging.LogGenericError("Request: " + request + " failed!", Identifier);
if (responseMessage != null) {
Logging.LogGenericError("Status code: " + responseMessage.StatusCode, Identifier);
Logging.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false), Identifier);
responseMessage.Dispose();
}
}
return null; return null;
} }
return responseMessage; if (responseMessage.IsSuccessStatusCode) {
return responseMessage;
}
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
Logging.LogGenericError("Request: " + request + " failed!", Identifier);
Logging.LogGenericError("Status code: " + responseMessage.StatusCode, Identifier);
Logging.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false), Identifier);
}
responseMessage.Dispose();
return null;
} }
} }
} }

View File

@@ -2,6 +2,7 @@
"Debug": false, "Debug": false,
"Headless": false, "Headless": false,
"AutoUpdates": true, "AutoUpdates": true,
"AutoRestart": true,
"UpdateChannel": 1, "UpdateChannel": 1,
"SteamProtocol": 6, "SteamProtocol": 6,
"SteamOwnerID": 0, "SteamOwnerID": 0,

View File

@@ -12,6 +12,7 @@
"FarmOffline": false, "FarmOffline": false,
"HandleOfflineMessages": false, "HandleOfflineMessages": false,
"AcceptGifts": false, "AcceptGifts": false,
"SteamTradeMatcher": false,
"ForwardKeysToOtherBots": false, "ForwardKeysToOtherBots": false,
"DistributeKeys": false, "DistributeKeys": false,
"UseAsfAsMobileAuthenticator": false, "UseAsfAsMobileAuthenticator": false,

View File

@@ -28,7 +28,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
namespace ConfigGenerator { namespace ConfigGenerator {
internal class ASFConfig { internal abstract class ASFConfig {
internal static readonly HashSet<ASFConfig> ASFConfigs = new HashSet<ASFConfig>(); internal static readonly HashSet<ASFConfig> ASFConfigs = new HashSet<ASFConfig>();
internal string FilePath { get; set; } internal string FilePath { get; set; }
@@ -39,13 +39,13 @@ namespace ConfigGenerator {
protected ASFConfig(string filePath) : this() { protected ASFConfig(string filePath) : this() {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException("filePath"); throw new ArgumentNullException(nameof(filePath));
} }
FilePath = filePath; FilePath = filePath;
} }
internal virtual void Save() { internal void Save() {
lock (FilePath) { lock (FilePath) {
try { try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented)); File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
@@ -55,7 +55,7 @@ namespace ConfigGenerator {
} }
} }
internal virtual void Remove() { internal void Remove() {
string queryPath = Path.GetFileNameWithoutExtension(FilePath); string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) { lock (FilePath) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) { foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
@@ -66,10 +66,11 @@ namespace ConfigGenerator {
} }
} }
} }
ASFConfigs.Remove(this); ASFConfigs.Remove(this);
} }
internal virtual void Rename(string botName) { internal void Rename(string botName) {
if (string.IsNullOrEmpty(botName)) { if (string.IsNullOrEmpty(botName)) {
return; return;
} }
@@ -83,6 +84,7 @@ namespace ConfigGenerator {
Logging.LogGenericException(e); Logging.LogGenericException(e);
} }
} }
FilePath = Path.Combine(Program.ConfigDirectory, botName + ".json"); FilePath = Path.Combine(Program.ConfigDirectory, botName + ".json");
} }
} }

View File

@@ -25,9 +25,12 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
namespace ConfigGenerator { namespace ConfigGenerator {
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global"), SuppressMessage("ReSharper", "CollectionNeverQueried.Global"), SuppressMessage("ReSharper", "MemberCanBePrivate.Global"), SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class BotConfig : ASFConfig { internal sealed class BotConfig : ASFConfig {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; set; } = false; public bool Enabled { get; set; } = false;
@@ -38,7 +41,7 @@ namespace ConfigGenerator {
[JsonProperty] [JsonProperty]
public string SteamLogin { get; set; } = null; public string SteamLogin { get; set; } = null;
[JsonProperty] [JsonProperty, PasswordPropertyText(true)]
public string SteamPassword { get; set; } = null; public string SteamPassword { get; set; } = null;
[JsonProperty] [JsonProperty]
@@ -68,6 +71,9 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool AcceptGifts { get; set; } = false; public bool AcceptGifts { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool SteamTradeMatcher { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool ForwardKeysToOtherBots { get; set; } = false; public bool ForwardKeysToOtherBots { get; set; } = false;
@@ -124,12 +130,12 @@ namespace ConfigGenerator {
return botConfig; return botConfig;
} }
// This constructor is used only by deserializer [SuppressMessage("ReSharper", "UnusedMember.Local")]
private BotConfig() { } private BotConfig() { }
private BotConfig(string filePath) : base(filePath) { private BotConfig(string filePath) : base(filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException("filePath"); throw new ArgumentNullException(nameof(filePath));
} }
GamesPlayedWhileIdle.Add(0); GamesPlayedWhileIdle.Add(0);

View File

@@ -83,6 +83,7 @@
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile> </Compile>
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">

View File

@@ -26,7 +26,7 @@ using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
namespace ConfigGenerator { namespace ConfigGenerator {
internal class ConfigPage : TabPage { internal sealed class ConfigPage : TabPage {
internal readonly ASFConfig ASFConfig; internal readonly ASFConfig ASFConfig;
internal ConfigPage(ASFConfig config) { internal ConfigPage(ASFConfig config) {
@@ -42,8 +42,6 @@ namespace ConfigGenerator {
Controls.Add(enhancedPropertyGrid); Controls.Add(enhancedPropertyGrid);
} }
internal void RefreshText() { internal void RefreshText() => Text = Path.GetFileNameWithoutExtension(ASFConfig.FilePath);
Text = Path.GetFileNameWithoutExtension(ASFConfig.FilePath);
}
} }
} }

View File

@@ -22,14 +22,16 @@
*/ */
using System.Diagnostics.CodeAnalysis;
namespace ConfigGenerator { namespace ConfigGenerator {
internal static class Debugging { internal static class Debugging {
#if DEBUG #if DEBUG
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal static readonly bool IsDebugBuild = true; internal static readonly bool IsDebugBuild = true;
#else #else
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal static readonly bool IsDebugBuild = false; internal static readonly bool IsDebugBuild = false;
#endif #endif
internal static bool IsReleaseBuild => !IsDebugBuild;
} }
} }

View File

@@ -25,92 +25,95 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using ConfigGenerator.Properties;
namespace ConfigGenerator { namespace ConfigGenerator {
class DialogBox { internal static class DialogBox {
public static DialogResult InputBox(string title, string promptText, out string value) { internal static DialogResult InputBox(string title, string promptText, out string value) {
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(promptText)) { if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(promptText)) {
value = null; value = null;
return DialogResult.Abort; return DialogResult.Abort;
} }
Form form = new Form(); TextBox textBox = new TextBox {
Label label = new Label(); Anchor = AnchorStyles.Right,
TextBox textBox = new TextBox(); Bounds = new Rectangle(12, 36, 372, 20),
Width = 1000
};
textBox.Width = 1000; Button buttonOk = new Button {
Button buttonOk = new Button(); Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Button buttonCancel = new Button(); Bounds = new Rectangle(228, 72, 75, 23),
DialogResult = DialogResult.OK,
Text = Resources.OK
};
form.Text = title; Button buttonCancel = new Button {
label.Text = promptText; Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Bounds = new Rectangle(309, 72, 75, 23),
DialogResult = DialogResult.Cancel,
Text = Resources.Cancel
};
buttonOk.Text = "OK"; Label label = new Label {
buttonCancel.Text = "Cancel"; AutoSize = true,
buttonOk.DialogResult = DialogResult.OK; Bounds = new Rectangle(9, 20, 372, 13),
buttonCancel.DialogResult = DialogResult.Cancel; Text = promptText
};
label.SetBounds(9, 20, 372, 13); Form form = new Form {
textBox.SetBounds(12, 36, 372, 20); AcceptButton = buttonOk,
buttonOk.SetBounds(228, 72, 75, 23); CancelButton = buttonCancel,
buttonCancel.SetBounds(309, 72, 75, 23); ClientSize = new Size(Math.Max(300, label.Right + 10), 107),
Controls = { label, textBox, buttonOk, buttonCancel },
label.AutoSize = true; FormBorderStyle = FormBorderStyle.FixedDialog,
textBox.Anchor = textBox.Anchor | AnchorStyles.Right; MinimizeBox = false,
buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; MaximizeBox = false,
buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; StartPosition = FormStartPosition.CenterScreen,
Text = title
form.ClientSize = new Size(396, 107); };
form.Controls.AddRange(new Control[] { label, textBox, buttonOk, buttonCancel });
form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height);
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.StartPosition = FormStartPosition.CenterScreen;
form.MinimizeBox = false;
form.MaximizeBox = false;
form.AcceptButton = buttonOk;
form.CancelButton = buttonCancel;
DialogResult dialogResult = form.ShowDialog(); DialogResult dialogResult = form.ShowDialog();
value = textBox.Text; value = textBox.Text;
return dialogResult; return dialogResult;
} }
public static DialogResult YesNoBox(string title, string promptText) { internal static DialogResult YesNoBox(string title, string promptText) {
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(promptText)) { if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(promptText)) {
return DialogResult.Abort; return DialogResult.Abort;
} }
Form form = new Form(); Button buttonYes = new Button {
Label label = new Label(); Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Bounds = new Rectangle(228, 72, 75, 23),
DialogResult = DialogResult.Yes,
Text = Resources.Yes
};
Button buttonOk = new Button(); Button buttonNo = new Button {
Button buttonCancel = new Button(); Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Bounds = new Rectangle(309, 72, 75, 23),
DialogResult = DialogResult.No,
Text = Resources.No
};
form.Text = title; Label label = new Label {
label.Text = promptText; AutoSize = true,
Bounds = new Rectangle(9, 20, 372, 13),
Text = promptText
};
buttonOk.Text = "Yes"; Form form = new Form {
buttonCancel.Text = "No"; AcceptButton = buttonYes,
buttonOk.DialogResult = DialogResult.Yes; CancelButton = buttonNo,
buttonCancel.DialogResult = DialogResult.No; ClientSize = new Size(Math.Max(300, label.Right + 10), 107),
Controls = { label, buttonYes, buttonNo },
label.SetBounds(9, 20, 372, 13); FormBorderStyle = FormBorderStyle.FixedDialog,
buttonOk.SetBounds(228, 50, 75, 23); MinimizeBox = false,
buttonCancel.SetBounds(309, 50, 75, 23); MaximizeBox = false,
StartPosition = FormStartPosition.CenterScreen,
label.AutoSize = true; Text = title
buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; };
buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
form.ClientSize = new Size(396, 80);
form.Controls.AddRange(new Control[] { label, buttonOk, buttonCancel });
form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height);
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.StartPosition = FormStartPosition.CenterScreen;
form.MinimizeBox = false;
form.MaximizeBox = false;
form.AcceptButton = buttonOk;
form.CancelButton = buttonCancel;
DialogResult dialogResult = form.ShowDialog(); DialogResult dialogResult = form.ShowDialog();
return dialogResult; return dialogResult;

View File

@@ -31,7 +31,7 @@ namespace ConfigGenerator {
internal EnhancedPropertyGrid(ASFConfig config) { internal EnhancedPropertyGrid(ASFConfig config) {
if (config == null) { if (config == null) {
throw new ArgumentNullException("config"); throw new ArgumentNullException(nameof(config));
} }
ASFConfig = config; ASFConfig = config;
@@ -53,20 +53,24 @@ namespace ConfigGenerator {
BotConfig botConfig = ASFConfig as BotConfig; BotConfig botConfig = ASFConfig as BotConfig;
if (botConfig != null) { if (botConfig != null) {
if (botConfig.Enabled) { if (!botConfig.Enabled) {
Tutorial.OnAction(Tutorial.EPhase.BotEnabled); return;
if (!string.IsNullOrEmpty(botConfig.SteamLogin) && !string.IsNullOrEmpty(botConfig.SteamPassword)) { }
Tutorial.OnAction(Tutorial.EPhase.BotReady);
} Tutorial.OnAction(Tutorial.EPhase.BotEnabled);
if (!string.IsNullOrEmpty(botConfig.SteamLogin) && !string.IsNullOrEmpty(botConfig.SteamPassword)) {
Tutorial.OnAction(Tutorial.EPhase.BotReady);
} }
return; return;
} }
GlobalConfig globalConfig = ASFConfig as GlobalConfig; GlobalConfig globalConfig = ASFConfig as GlobalConfig;
if (globalConfig != null) { if (globalConfig == null) {
if (globalConfig.SteamOwnerID != 0) { return;
Tutorial.OnAction(Tutorial.EPhase.GlobalConfigReady); }
}
if (globalConfig.SteamOwnerID != 0) {
Tutorial.OnAction(Tutorial.EPhase.GlobalConfigReady);
} }
} }

View File

@@ -25,11 +25,14 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
namespace ConfigGenerator { namespace ConfigGenerator {
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global"), SuppressMessage("ReSharper", "CollectionNeverQueried.Global"), SuppressMessage("ReSharper", "MemberCanBePrivate.Global"), SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class GlobalConfig : ASFConfig { internal sealed class GlobalConfig : ASFConfig {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum EUpdateChannel : byte { internal enum EUpdateChannel : byte {
Unknown, Unknown,
Stable, Stable,
@@ -43,7 +46,7 @@ namespace ConfigGenerator {
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp; private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
// This is hardcoded blacklist which should not be possible to change // 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 }; private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280 };
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool Debug { get; set; } = false; public bool Debug { get; set; } = false;
@@ -54,6 +57,9 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool AutoUpdates { get; set; } = true; public bool AutoUpdates { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
public bool AutoRestart { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable; public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable;
@@ -99,7 +105,6 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool Statistics { get; set; } = true; public bool Statistics { get; set; } = true;
// TODO: Please remove me immediately after https://github.com/SteamRE/SteamKit/issues/254 gets fixed
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool HackIgnoreMachineID { get; set; } = false; public bool HackIgnoreMachineID { get; set; } = false;
@@ -158,20 +163,22 @@ namespace ConfigGenerator {
globalConfig.HttpTimeout = DefaultHttpTimeout; globalConfig.HttpTimeout = DefaultHttpTimeout;
} }
if (globalConfig.WCFPort == 0) { if (globalConfig.WCFPort != 0) {
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort + ". Value of " + DefaultWCFPort + " will be used instead"); return globalConfig;
globalConfig.WCFPort = DefaultWCFPort;
} }
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort + ". Value of " + DefaultWCFPort + " will be used instead");
globalConfig.WCFPort = DefaultWCFPort;
return globalConfig; return globalConfig;
} }
// This constructor is used only by deserializer [SuppressMessage("ReSharper", "UnusedMember.Local")]
private GlobalConfig() { } private GlobalConfig() { }
private GlobalConfig(string filePath) : base(filePath) { private GlobalConfig(string filePath) : base(filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException("filePath"); throw new ArgumentNullException(nameof(filePath));
} }
Blacklist.AddRange(GlobalBlacklist); Blacklist.AddRange(GlobalBlacklist);

View File

@@ -23,9 +23,9 @@
*/ */
using System; using System;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows.Forms; using System.Windows.Forms;
using ConfigGenerator.Properties;
namespace ConfigGenerator { namespace ConfigGenerator {
internal static class Logging { internal static class Logging {
@@ -34,60 +34,40 @@ namespace ConfigGenerator {
return; return;
} }
MessageBox.Show(message, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); MessageBox.Show(message, Resources.Information, MessageBoxButtons.OK, MessageBoxIcon.Information);
} }
internal static void LogGenericWTF(string message, [CallerMemberName] string previousMethodName = "") { internal static void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
return; return;
} }
MessageBox.Show(previousMethodName + "() " + message, "WTF", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show(previousMethodName + @"() " + message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
} }
internal static void LogGenericError(string message, [CallerMemberName] string previousMethodName = "") { internal static void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = null) {
while (true) {
if (exception == null) {
return;
}
MessageBox.Show(previousMethodName + @"() " + exception.Message + Environment.NewLine + exception.StackTrace, Resources.Exception, MessageBoxButtons.OK, MessageBoxIcon.Error);
if (exception.InnerException != null) {
exception = exception.InnerException;
continue;
}
break;
}
}
internal static void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
return; return;
} }
MessageBox.Show(previousMethodName + "() " + message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show(previousMethodName + @"() " + message, Resources.Warning, MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
internal static void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = "") {
if (exception == null) {
return;
}
MessageBox.Show(previousMethodName + "() " + exception.Message + Environment.NewLine + exception.StackTrace, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
if (exception.InnerException != null) {
LogGenericException(exception.InnerException, previousMethodName);
}
}
internal static void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = "") {
if (string.IsNullOrEmpty(message)) {
return;
}
MessageBox.Show(previousMethodName + "() " + message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
internal static void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = "") {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
}
LogGenericError(nullObjectName + " is null!", previousMethodName);
}
[Conditional("DEBUG")]
internal static void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = "") {
if (string.IsNullOrEmpty(message)) {
return;
}
MessageBox.Show(previousMethodName + "() " + message, "Debug", MessageBoxButtons.OK, MessageBoxIcon.Information);
} }
} }
} }

View File

@@ -26,26 +26,27 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
namespace ConfigGenerator { namespace ConfigGenerator {
public partial class MainForm : Form { internal sealed partial class MainForm : Form {
private const byte ReservedTabs = 3; private const byte ReservedTabs = 3;
private readonly TabPage NewTab = new TabPage { Text = "+" }; private readonly TabPage NewTab = new TabPage { Text = @"+" };
private readonly TabPage RemoveTab = new TabPage { Text = "-" }; private readonly TabPage RemoveTab = new TabPage { Text = @"-" };
private readonly TabPage RenameTab = new TabPage { Text = "~" }; private readonly TabPage RenameTab = new TabPage { Text = @"~" };
private ConfigPage ASFTab; private ConfigPage ASFTab;
private TabPage OldTab; private TabPage OldTab;
public MainForm() { internal MainForm() {
InitializeComponent(); InitializeComponent();
} }
private void MainForm_Load(object sender, EventArgs e) { private void MainForm_Load(object sender, EventArgs e) {
if (sender == null || e == null) { if ((sender == null) || (e == null)) {
return; return;
} }
@@ -53,7 +54,7 @@ namespace ConfigGenerator {
MainTab.TabPages.Add(ASFTab); MainTab.TabPages.Add(ASFTab);
foreach (var configFile in Directory.EnumerateFiles(Program.ConfigDirectory, "*.json")) { foreach (string configFile in Directory.EnumerateFiles(Program.ConfigDirectory, "*.json")) {
string botName = Path.GetFileNameWithoutExtension(configFile); string botName = Path.GetFileNameWithoutExtension(configFile);
switch (botName) { switch (botName) {
case Program.ASF: case Program.ASF:
@@ -71,7 +72,7 @@ namespace ConfigGenerator {
} }
private void MainTab_Selected(object sender, TabControlEventArgs e) { private void MainTab_Selected(object sender, TabControlEventArgs e) {
if (sender == null || e == null) { if ((sender == null) || (e == null)) {
return; return;
} }
@@ -151,11 +152,9 @@ namespace ConfigGenerator {
// Get rid of any potential whitespaces in bot name // Get rid of any potential whitespaces in bot name
input = Regex.Replace(input, @"\s+", ""); input = Regex.Replace(input, @"\s+", "");
foreach (ASFConfig config in ASFConfig.ASFConfigs) { if (ASFConfig.ASFConfigs.Select(config => Path.GetFileNameWithoutExtension(config.FilePath)).Any(fileNameWithoutExtension => (fileNameWithoutExtension == null) || fileNameWithoutExtension.Equals(input))) {
if (Path.GetFileNameWithoutExtension(config.FilePath).Equals(input)) { Logging.LogGenericError("Bot with such name exists already!");
Logging.LogGenericError("Bot with such name exists already!"); return;
return;
}
} }
input = Path.Combine(Program.ConfigDirectory, input + ".json"); input = Path.Combine(Program.ConfigDirectory, input + ".json");
@@ -170,7 +169,7 @@ namespace ConfigGenerator {
} }
private void MainTab_Deselecting(object sender, TabControlCancelEventArgs e) { private void MainTab_Deselecting(object sender, TabControlCancelEventArgs e) {
if (sender == null || e == null) { if ((sender == null) || (e == null)) {
return; return;
} }
@@ -178,7 +177,7 @@ namespace ConfigGenerator {
} }
private void MainForm_Shown(object sender, EventArgs e) { private void MainForm_Shown(object sender, EventArgs e) {
if (sender == null || e == null) { if ((sender == null) || (e == null)) {
return; return;
} }
@@ -186,7 +185,7 @@ namespace ConfigGenerator {
} }
private void MainForm_HelpButtonClicked(object sender, CancelEventArgs e) { private void MainForm_HelpButtonClicked(object sender, CancelEventArgs e) {
if (sender == null || e == null) { if ((sender == null) || (e == null)) {
return; return;
} }

View File

@@ -36,7 +36,7 @@ namespace ConfigGenerator {
private const string ASFDirectory = "ArchiSteamFarm"; private const string ASFDirectory = "ArchiSteamFarm";
private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
@@ -61,10 +61,12 @@ namespace ConfigGenerator {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up // Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) { for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory(".."); Directory.SetCurrentDirectory("..");
if (Directory.Exists(ASFDirectory)) { if (!Directory.Exists(ASFDirectory)) {
Directory.SetCurrentDirectory(ASFDirectory); continue;
break;
} }
Directory.SetCurrentDirectory(ASFDirectory);
break;
} }
// If config directory doesn't exist after our adjustment, abort all of that // If config directory doesn't exist after our adjustment, abort all of that
@@ -73,14 +75,16 @@ namespace ConfigGenerator {
} }
} }
if (!Directory.Exists(ConfigDirectory)) { if (Directory.Exists(ConfigDirectory)) {
Logging.LogGenericError("Config directory could not be found!"); return;
Environment.Exit(1);
} }
Logging.LogGenericError("Config directory could not be found!");
Environment.Exit(1);
} }
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) { private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (sender == null || args == null) { if ((sender == null) || (args == null)) {
return; return;
} }
@@ -88,7 +92,7 @@ namespace ConfigGenerator {
} }
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) { private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if (sender == null || args == null) { if ((sender == null) || (args == null)) {
return; return;
} }

View File

@@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following

View File

@@ -9,54 +9,127 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace ConfigGenerator.Properties { namespace ConfigGenerator.Properties {
using System;
/// <summary> /// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc. /// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary> /// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder // This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {
private static global::System.Resources.ResourceManager resourceMan; private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture; private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() { internal Resources() {
} }
/// <summary> /// <summary>
/// Returns the cached ResourceManager instance used by this class. /// Returns the cached ResourceManager instance used by this class.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager { internal static global::System.Resources.ResourceManager ResourceManager {
get { get {
if ((resourceMan == null)) { if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ConfigGenerator.Properties.Resources", typeof(Resources).Assembly); global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ConfigGenerator.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp; resourceMan = temp;
} }
return resourceMan; return resourceMan;
} }
} }
/// <summary> /// <summary>
/// Overrides the current thread's CurrentUICulture property for all /// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class. /// resource lookups using this strongly typed resource class.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture { internal static global::System.Globalization.CultureInfo Culture {
get { get {
return resourceCulture; return resourceCulture;
} }
set { set {
resourceCulture = value; resourceCulture = value;
} }
} }
}
/// <summary>
/// Looks up a localized string similar to Cancel.
/// </summary>
internal static string Cancel {
get {
return ResourceManager.GetString("Cancel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error.
/// </summary>
internal static string Error {
get {
return ResourceManager.GetString("Error", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exception.
/// </summary>
internal static string Exception {
get {
return ResourceManager.GetString("Exception", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Information.
/// </summary>
internal static string Information {
get {
return ResourceManager.GetString("Information", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No.
/// </summary>
internal static string No {
get {
return ResourceManager.GetString("No", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OK.
/// </summary>
internal static string OK {
get {
return ResourceManager.GetString("OK", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Warning.
/// </summary>
internal static string Warning {
get {
return ResourceManager.GetString("Warning", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Yes.
/// </summary>
internal static string Yes {
get {
return ResourceManager.GetString("Yes", resourceCulture);
}
}
}
} }

View File

@@ -114,4 +114,28 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Information" xml:space="preserve">
<value>Information</value>
</data>
<data name="Error" xml:space="preserve">
<value>Error</value>
</data>
<data name="Exception" xml:space="preserve">
<value>Exception</value>
</data>
<data name="Warning" xml:space="preserve">
<value>Warning</value>
</data>
<data name="OK" xml:space="preserve">
<value>OK</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Yes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="No" xml:space="preserve">
<value>No</value>
</data>
</root> </root>

View File

@@ -38,12 +38,12 @@ namespace ConfigGenerator {
GlobalConfigReady GlobalConfigReady
} }
internal static bool Enabled { get; set; } = true; internal static bool Enabled { private get; set; } = true;
private static EPhase NextPhase = EPhase.Start; private static EPhase NextPhase = EPhase.Start;
internal static void OnAction(EPhase phase) { internal static void OnAction(EPhase phase) {
if (!Enabled || phase != NextPhase) { if (!Enabled || (phase != NextPhase)) {
return; return;
} }

View File

@@ -135,6 +135,7 @@
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<!--
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' "> <PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
"$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll" "$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
del "$(SolutionDir)out\ASF-GUI.exe.config" del "$(SolutionDir)out\ASF-GUI.exe.config"
@@ -143,6 +144,7 @@
mono -O=all "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll" mono -O=all "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
rm "$(SolutionDir)out/ASF-GUI.exe.config" rm "$(SolutionDir)out/ASF-GUI.exe.config"
</PostBuildEvent> </PostBuildEvent>
-->
</PropertyGroup> </PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

9
appveyor.yml Normal file
View File

@@ -0,0 +1,9 @@
version: 1.0.{build}-{branch}
image: Visual Studio 2015
configuration: Release
platform: Any CPU
clone_depth: 10
build:
project: ArchiSteamFarm.sln
parallel: true
verbosity: minimal