Compare commits

..

68 Commits

Author SHA1 Message Date
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
28 changed files with 1135 additions and 607 deletions

View File

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

View File

@@ -52,69 +52,58 @@ namespace ArchiSteamFarm {
*/
internal sealed class NotificationsCallback : CallbackMsg {
internal sealed class Notification {
internal enum ENotificationType : uint {
Unknown = 0,
Trading = 1,
// Only custom below, different than ones available as user_notification_type
Items = 514
}
internal readonly ENotificationType NotificationType;
internal Notification(ENotificationType notificationType) {
NotificationType = notificationType;
}
internal enum ENotification : byte {
Unknown = 0,
Trading = 1,
// Only custom below, different than ones available as user_notification_type
Items = 255
}
internal readonly List<Notification> Notifications;
internal readonly HashSet<ENotification> Notifications;
internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
JobID = jobID;
if (msg == null || msg.notifications == null) {
return;
if (jobID == null || msg == null) {
throw new ArgumentNullException("jobID || msg");
}
Notifications = new List<Notification>(msg.notifications.Count);
JobID = jobID;
Notifications = new HashSet<ENotification>();
foreach (var notification in msg.notifications) {
Notifications.Add(new Notification((Notification.ENotificationType) notification.user_notification_type));
Notifications.Add((ENotification) notification.user_notification_type);
}
}
internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {
JobID = jobID;
if (msg == null) {
return;
if (jobID == null || msg == null) {
throw new ArgumentNullException("jobID || msg");
}
JobID = jobID;
if (msg.count_new_items > 0) {
Notifications = new List<Notification>(1) {
new Notification(Notification.ENotificationType.Items)
Notifications = new HashSet<ENotification> {
ENotification.Items
};
}
}
}
internal sealed class OfflineMessageCallback : CallbackMsg {
internal readonly uint OfflineMessages;
internal readonly List<uint> Users;
internal readonly uint OfflineMessagesCount;
internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
JobID = jobID;
if (msg == null) {
return;
if (jobID == null || msg == null) {
throw new ArgumentNullException("jobID || msg");
}
OfflineMessages = msg.offline_messages;
Users = msg.friends_with_offline_messages;
JobID = jobID;
OfflineMessagesCount = msg.offline_messages;
}
}
internal sealed class PurchaseResponseCallback : CallbackMsg {
internal enum EPurchaseResult : int {
internal enum EPurchaseResult : sbyte {
Unknown = -1,
OK = 0,
AlreadyOwned = 9,
@@ -131,12 +120,11 @@ namespace ArchiSteamFarm {
internal readonly Dictionary<uint, string> Items;
internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
JobID = jobID;
if (msg == null) {
return;
if (jobID == null || msg == null) {
throw new ArgumentNullException("jobID || msg");
}
JobID = jobID;
Result = (EResult) msg.eresult;
PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
@@ -150,14 +138,14 @@ namespace ArchiSteamFarm {
return;
}
List<KeyValue> lineItems = ReceiptInfo["lineitems"].Children;
var lineItems = ReceiptInfo["lineitems"].Children;
Items = new Dictionary<uint, string>(lineItems.Count);
foreach (KeyValue lineItem in lineItems) {
uint appID = (uint) lineItem["PackageID"].AsUnsignedLong();
string gameName = lineItem["ItemDescription"].AsString();
gameName = WebUtility.UrlDecode(gameName); // Apparently steam expects client to decode sent HTML
Items.Add(appID, gameName);
string gameName = lineItem["ItemDescription"].Value;
gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML
Items[appID] = gameName;
}
}
}
@@ -172,71 +160,34 @@ 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) {
if (!Client.IsConnected) {
return;
}
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
var gamePlayed = new CMsgClientGamesPlayed.GamePlayed();
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 {
game_id = new GameID(gameID),
game_extra_info = gameName,
game_id = new GameID() {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
}
});
}
Client.Send(request);
}
internal void PlayGames(ICollection<uint> gameIDs) {
internal void PlayGames(uint gameID) {
if (!Client.IsConnected) {
return;
}
PlayGames(new HashSet<uint> { gameID });
}
internal void PlayGames(HashSet<uint> gameIDs) {
if (gameIDs == null || !Client.IsConnected) {
return;
}

View File

@@ -102,7 +102,6 @@
<Compile Include="GlobalDatabase.cs" />
<Compile Include="BotDatabase.cs" />
<Compile Include="CardsFarmer.cs" />
<Compile Include="CMsgs\CMsgClientClanInviteAction.cs" />
<Compile Include="Debugging.cs" />
<Compile Include="GlobalConfig.cs" />
<Compile Include="JSON\GitHub.cs" />

View File

@@ -28,7 +28,6 @@ using SteamKit2;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
@@ -36,11 +35,10 @@ using System.Threading;
namespace ArchiSteamFarm {
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 static string SteamCommunityURL = "https://" + SteamCommunity;
private static string SteamCommunityURL = "https://" + SteamCommunityHost;
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000;
private readonly Bot Bot;
@@ -51,7 +49,54 @@ namespace ArchiSteamFarm {
internal static void Init() {
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;
if (!uint.TryParse(hashName.Substring(0, index), out appID)) {
return 0;
}
return 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;
} else if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
return Steam.Item.EType.FoilTradingCard;
} else if (name.EndsWith("Profile Background", StringComparison.Ordinal)) {
return Steam.Item.EType.ProfileBackground;
} else if (name.EndsWith("Trading Card", StringComparison.Ordinal)) {
return Steam.Item.EType.TradingCard;
} else {
return Steam.Item.EType.Unknown;
}
}
}
internal ArchiWebHandler(Bot bot) {
@@ -116,13 +161,13 @@ namespace ArchiSteamFarm {
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;
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunity));
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
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) {
return false;
@@ -132,49 +177,71 @@ namespace ArchiSteamFarm {
return true;
}
internal async Task<bool?> IsLoggedIn() {
HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile").ConfigureAwait(false);
internal async Task<bool> AcceptGift(ulong gid) {
if (gid == 0) {
return false;
}
if (htmlDocument == null) {
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 + "/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) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
return false;
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='account_pulldown']");
return htmlNode != null;
return true;
}
internal async Task<bool> RefreshSessionIfNeeded() {
DateTime now = DateTime.Now;
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
return true;
internal async Task<bool> JoinGroup(ulong groupID) {
if (groupID == 0) {
return false;
}
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
now = DateTime.Now;
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
SessionSemaphore.Release();
return true;
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
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);
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false;
}
SessionSemaphore.Release();
return result;
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) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
return true;
}
internal async Task<Dictionary<uint, string>> GetOwnedGames() {
@@ -222,7 +289,47 @@ namespace ArchiSteamFarm {
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)) {
return null;
}
@@ -236,6 +343,7 @@ namespace ArchiSteamFarm {
response = iEconService.GetTradeOffers(
get_received_offers: 1,
active_only: 1,
get_descriptions: 1,
secure: !Program.GlobalConfig.ForceHttp
);
} catch (Exception e) {
@@ -249,74 +357,93 @@ namespace ArchiSteamFarm {
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) {
// TODO: Correct some of these when SK2 with https://github.com/SteamRE/SteamKit/pull/255 gets released
Steam.TradeOffer tradeOffer = new Steam.TradeOffer {
tradeofferid = trade["tradeofferid"].AsString(),
accountid_other = (uint) trade["accountid_other"].AsUnsignedLong(), // TODO: Correct this when SK2 with https://github.com/SteamRE/SteamKit/pull/255 gets released
trade_offer_state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
TradeOfferID = trade["tradeofferid"].AsUnsignedLong(),
OtherSteamID3 = (uint) trade["accountid_other"].AsUnsignedLong(),
State = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
};
foreach (KeyValue item in trade["items_to_give"].Children) {
tradeOffer.items_to_give.Add(new Steam.Item {
appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(),
classid = item["classid"].AsString(),
instanceid = item["instanceid"].AsString(),
amount = item["amount"].AsString(),
});
Steam.Item steamItem = new Steam.Item {
AppID = (uint) item["appid"].AsUnsignedLong(),
ContextID = item["contextid"].AsUnsignedLong(),
AssetID = item["assetid"].AsUnsignedLong(),
ClassID = item["classid"].AsUnsignedLong(),
InstanceID = item["instanceid"].AsUnsignedLong(),
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) {
tradeOffer.items_to_receive.Add(new Steam.Item {
appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(),
classid = item["classid"].AsString(),
instanceid = item["instanceid"].AsString(),
amount = item["amount"].AsString(),
});
Steam.Item steamItem = new Steam.Item {
AppID = (uint) item["appid"].AsUnsignedLong(),
ContextID = item["contextid"].AsUnsignedLong(),
AssetID = item["assetid"].AsUnsignedLong(),
ClassID = item["classid"].AsUnsignedLong(),
InstanceID = item["instanceid"].AsUnsignedLong(),
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);
}
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) {
if (tradeID == 0) {
return false;
@@ -334,11 +461,10 @@ namespace ArchiSteamFarm {
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
string request = referer + "/accept";
Dictionary<string, string> data = new Dictionary<string, string>(3) {
{"sessionid", sessionID},
{"serverid", "1"},
{"tradeofferid", tradeID.ToString()}
{ "sessionid", sessionID },
{ "serverid", "1" },
{ "tradeofferid", tradeID.ToString() }
};
bool result = false;
@@ -354,40 +480,120 @@ namespace ArchiSteamFarm {
return true;
}
internal async Task<List<Steam.Item>> GetMyTradableInventory() {
internal async Task<HashSet<Steam.Item>> GetMyTradableInventory() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
JObject jObject = null;
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/753/6?trading=1").ConfigureAwait(false);
}
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
if (jObject == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
}
ushort nextPage = 0;
while (true) {
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1&start=" + nextPage;
IEnumerable<JToken> jTokens = jObject.SelectTokens("$.rgInventory.*");
if (jTokens == null) {
Logging.LogNullError("jTokens", Bot.BotName);
return null;
}
JObject jObject = null;
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
jObject = await WebBrowser.UrlGetToJObject(request).ConfigureAwait(false);
}
List<Steam.Item> result = new List<Steam.Item>();
foreach (JToken jToken in jTokens) {
try {
result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString()));
} catch (Exception e) {
Logging.LogGenericException(e, Bot.BotName);
if (jObject == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
}
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 (descriptions == 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;
}
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) {
return false;
}
@@ -402,39 +608,42 @@ namespace ArchiSteamFarm {
return false;
}
List<Steam.TradeOfferRequest> trades = new List<Steam.TradeOfferRequest>(1 + inventory.Count / Trading.MaxItemsPerTrade);
HashSet<Steam.TradeOfferRequest> trades = new HashSet<Steam.TradeOfferRequest>();
Steam.TradeOfferRequest singleTrade = null;
for (ushort i = 0; i < inventory.Count; i++) {
if (i % Trading.MaxItemsPerTrade == 0) {
byte itemID = 0;
foreach (Steam.Item item in inventory) {
if (itemID % Trading.MaxItemsPerTrade == 0) {
if (trades.Count >= Trading.MaxTradesPerAccount) {
break;
}
singleTrade = new Steam.TradeOfferRequest();
trades.Add(singleTrade);
itemID = 0;
}
Steam.Item item = inventory[i];
singleTrade.me.assets.Add(new Steam.Item() {
appid = "753",
contextid = "6",
amount = item.amount,
assetid = item.id
singleTrade.ItemsToGive.Assets.Add(new Steam.Item() {
AppID = Steam.Item.SteamAppID,
ContextID = Steam.Item.SteamContextID,
Amount = item.Amount,
AssetID = item.AssetID
});
itemID++;
}
string referer = SteamCommunityURL + "/tradeoffer/new";
string request = referer + "/send";
foreach (Steam.TradeOfferRequest trade in trades) {
Dictionary<string, string> data = new Dictionary<string, string>(6) {
{"sessionid", sessionID},
{"serverid", "1"},
{"partner", partnerID.ToString()},
{"tradeoffermessage", "Sent by ASF"},
{"json_tradeoffer", JsonConvert.SerializeObject(trade)},
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"}
{ "sessionid", sessionID },
{ "serverid", "1" },
{ "partner", partnerID.ToString() },
{ "tradeoffermessage", "Sent by ASF" },
{ "json_tradeoffer", JsonConvert.SerializeObject(trade) },
{ "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}" }
};
bool result = false;
@@ -460,9 +669,11 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/my/badges?p=" + page;
HtmlDocument htmlDocument = null;
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) {
@@ -482,9 +693,11 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/my/gamecards/" + appID;
HtmlDocument htmlDocument = null;
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) {
@@ -500,9 +713,11 @@ namespace ArchiSteamFarm {
return false;
}
string request = SteamCommunityURL + "/my/inventory";
bool result = false;
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) {
@@ -513,37 +728,47 @@ namespace ArchiSteamFarm {
return true;
}
internal async Task<bool> AcceptGift(ulong gid) {
if (gid == 0) {
return false;
private async Task<bool?> IsLoggedIn() {
string request = SteamCommunityURL + "/my/profile";
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)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
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) {
if (uri == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
return null;
}
return true;
return !uri.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
}
private async Task<bool> RefreshSessionIfNeeded() {
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
return true;
}
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
SessionSemaphore.Release();
return true;
}
bool result;
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);
}
SessionSemaphore.Release();
return result;
}
private async Task<bool> UnlockParentalAccount(string parentalPin) {
@@ -552,16 +777,15 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
string request = SteamCommunityURL + "/parental/ajaxunlock";
Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "pin", parentalPin }
};
string referer = SteamCommunityURL;
string request = referer + "/parental/ajaxunlock";
bool result = false;
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) {

View File

@@ -33,6 +33,7 @@ using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Text.RegularExpressions;
namespace ArchiSteamFarm {
internal sealed class Bot {
@@ -43,6 +44,7 @@ namespace ArchiSteamFarm {
internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>();
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
internal readonly string BotName;
internal readonly ArchiHandler ArchiHandler;
@@ -63,12 +65,12 @@ namespace ArchiSteamFarm {
internal bool KeepRunning { get; private set; }
private bool InvalidPassword, LoggedInElsewhere;
private bool InvalidPassword, LoggedInElsewhere, FirstTradeSent;
private string AuthCode, TwoFactorCode;
internal static async Task RefreshCMs(uint cellID) {
bool initialized = false;
for (byte i = 0; i < 3 && !initialized; i++) {
for (byte i = 0; i < WebBrowser.MaxRetries && !initialized; i++) {
try {
Logging.LogGenericInfo("Refreshing list of CMs...");
await SteamDirectory.Initialize(cellID).ConfigureAwait(false);
@@ -82,7 +84,7 @@ namespace ArchiSteamFarm {
if (initialized) {
Logging.LogGenericInfo("Success!");
} else {
Logging.LogGenericWarning("Failed to initialize list of CMs after 3 tries, ASF will use built-in SK2 list, it may take a while to connect");
Logging.LogGenericWarning("Failed to initialize list of CMs after " + WebBrowser.MaxRetries + " tries, ASF will use built-in SK2 list, it may take a while to connect");
}
}
@@ -100,9 +102,16 @@ namespace ArchiSteamFarm {
}
// Steam keys are offered in many formats: https://support.steampowered.com/kb_article.php?ref=7480-WUSF-3601
// It's pointless to implement them all, so we'll just do a simple check if key is supposed to be valid
// Every valid key, apart from Prey one has at least two dashes
return Utilities.GetCharCountInString(key, '-') >= 2;
// This regex should catch all of them, we can always further extend it in future
return Regex.IsMatch(key, @"[0-9A-Z]{4,5}-[0-9A-Z]{4,5}-[0-9A-Z]{4,5}-?(?:(?:[0-9A-Z]{4,5}-?)?(?:[0-9A-Z]{4,5}))?");
}
private static async Task LimitLoginRequestsAsync() {
await LoginSemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Utilities.SleepAsync(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
LoginSemaphore.Release();
}).Forget();
}
internal Bot(string botName) {
@@ -124,26 +133,22 @@ namespace ArchiSteamFarm {
return;
}
lock (Bots) {
if (Bots.ContainsKey(botName)) {
return;
}
Bots[botName] = this;
}
SentryFile = botPath + ".bin";
BotDatabase = BotDatabase.Load(botPath + ".db");
if (BotDatabase == null) {
Logging.LogGenericError("Bot database could not be loaded, refusing to start this bot instance!", botName);
return;
}
bool alreadyExists;
lock (Bots) {
alreadyExists = Bots.ContainsKey(botName);
if (!alreadyExists) {
Bots[botName] = this;
}
}
if (alreadyExists) {
return;
}
SentryFile = botPath + ".bin";
if (BotDatabase.SteamGuardAccount == null) {
// Support and convert SDA files
string maFilePath = botPath + ".maFile";
@@ -157,8 +162,8 @@ namespace ArchiSteamFarm {
if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized && Directory.Exists(Program.DebugDirectory)) {
try {
SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
Debugging.NetHookAlreadyInitialized = true;
SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
} catch (Exception e) {
Logging.LogGenericException(e, botName);
}
@@ -224,39 +229,58 @@ namespace ArchiSteamFarm {
Start().Forget();
}
internal async Task AcceptConfirmations(bool confirm, Confirmation.ConfirmationType allowedConfirmationType = Confirmation.ConfirmationType.Unknown) {
internal async Task<bool> AcceptConfirmations(bool confirm, Confirmation.ConfirmationType allowedConfirmationType = Confirmation.ConfirmationType.Unknown) {
if (BotDatabase.SteamGuardAccount == null) {
return;
return true;
}
try {
if (!await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false)) {
return;
}
bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
result = true;
Confirmation[] confirmations = await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false);
if (confirmations == null) {
return;
}
foreach (Confirmation confirmation in confirmations) {
if (allowedConfirmationType != Confirmation.ConfirmationType.Unknown && confirmation.ConfType != allowedConfirmationType) {
try {
if (!await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false)) {
result = false;
continue;
}
if (confirm) {
BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation);
} else {
BotDatabase.SteamGuardAccount.DenyConfirmation(confirmation);
Confirmation[] confirmations = await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false);
if (confirmations == null) {
return true;
}
foreach (Confirmation confirmation in confirmations) {
if (allowedConfirmationType != Confirmation.ConfirmationType.Unknown && confirmation.ConfType != allowedConfirmationType) {
continue;
}
if (confirm) {
if (!BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation)) {
result = false;
break;
}
} else {
if (!BotDatabase.SteamGuardAccount.DenyConfirmation(confirmation)) {
result = false;
break;
}
}
}
} catch (SteamGuardAccount.WGTokenInvalidException) {
result = false;
continue;
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
return false;
}
} catch (SteamGuardAccount.WGTokenInvalidException) {
Logging.LogGenericWarning("Handling confirmation: Failed!", BotName);
Logging.LogGenericWarning("Confirmation could not be accepted because of invalid token exception", BotName);
Logging.LogGenericWarning("If issue persists, consider removing and readding ASF 2FA", BotName);
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
}
if (!result) {
Logging.LogGenericWTF("Could not accept confirmations even after " + WebBrowser.MaxRetries + " tries", BotName);
return false;
}
return true;
}
internal void ResetGamesPlayed() {
@@ -296,7 +320,8 @@ namespace ArchiSteamFarm {
}
internal async Task OnFarmingFinished(bool farmedSomething) {
if (farmedSomething && BotConfig.SendOnFarmingFinished) {
if ((farmedSomething || !FirstTradeSent) && BotConfig.SendOnFarmingFinished) {
FirstTradeSent = true;
await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false);
}
@@ -310,11 +335,11 @@ namespace ArchiSteamFarm {
return null;
}
if (!message.StartsWith("!")) {
if (message[0] != '!') {
return await ResponseRedeem(steamID, message.Replace(",", Environment.NewLine), true).ConfigureAwait(false);
}
if (!message.Contains(" ")) {
if (message.IndexOf(' ') < 0) {
switch (message) {
case "!2fa":
return Response2FA(steamID);
@@ -350,7 +375,7 @@ namespace ArchiSteamFarm {
return ResponseUnknown(steamID);
}
} else {
string[] args = message.Split(' ');
string[] args = message.Split((char[]) null, StringSplitOptions.RemoveEmptyEntries);
switch (args[0]) {
case "!2fa":
return Response2FA(steamID, args[1]);
@@ -368,8 +393,6 @@ namespace ArchiSteamFarm {
}
case "!farm":
return ResponseFarm(steamID, args[1]);
case "!help":
return ResponseHelp(steamID, args[1]);
case "!loot":
return await ResponseSendTrade(steamID, args[1]).ConfigureAwait(false);
case "!owns":
@@ -410,9 +433,9 @@ namespace ArchiSteamFarm {
Task.Run(() => HandleCallbacks()).Forget();
}
// 2FA tokens are expiring soon, use limiter only when we don't have any pending
if (TwoFactorCode == null) {
await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
// 2FA tokens are expiring soon, don't use limiter when user is providing one
if (TwoFactorCode == null || BotDatabase.SteamGuardAccount != null) {
await LimitLoginRequestsAsync().ConfigureAwait(false);
}
Logging.LogGenericInfo("Starting...", BotName);
@@ -463,6 +486,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("ASF requires a few more steps to complete authenticator import...", BotName);
if (!InitializeLoginAndPassword()) {
BotDatabase.SteamGuardAccount = null;
return;
}
@@ -473,10 +497,12 @@ namespace ArchiSteamFarm {
case LoginResult.Need2FA:
userLogin.TwoFactorCode = Program.GetUserInput(Program.EUserInputType.TwoFactorAuthentication, BotName);
if (string.IsNullOrEmpty(userLogin.TwoFactorCode)) {
BotDatabase.SteamGuardAccount = null;
return;
}
break;
default:
BotDatabase.SteamGuardAccount = null;
Logging.LogGenericError("Unhandled situation: " + loginResult, BotName);
return;
}
@@ -520,7 +546,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return await bot.ResponsePause(steamID).ConfigureAwait(false);
@@ -533,6 +563,8 @@ namespace ArchiSteamFarm {
if (CardsFarmer.CurrentGamesFarming.Count > 0) {
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm.";
} else if (CardsFarmer.ManualMode) {
return "Bot " + BotName + " is running in manual mode.";
} else if (SteamClient.IsConnected) {
return "Bot " + BotName + " is not farming anything.";
} else if (KeepRunning) {
@@ -549,7 +581,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return bot.ResponseStatus(steamID);
@@ -588,12 +624,20 @@ namespace ArchiSteamFarm {
}
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
List<Steam.Item> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
if (inventory == null || inventory.Count == 0) {
return "Nothing to send, inventory seems empty!";
}
// Remove from our pending inventory all items that are not steam cards and boosters
inventory.RemoveWhere(item => item.Type != Steam.Item.EType.TradingCard && item.Type != Steam.Item.EType.FoilTradingCard && item.Type != Steam.Item.EType.BoosterPack);
inventory.TrimExcess();
if (inventory.Count == 0) {
return "Nothing to send, inventory seems empty!";
}
if (await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) {
await AcceptConfirmations(true, Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
return "Trade offer sent successfully!";
@@ -609,7 +653,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return await bot.ResponseSendTrade(steamID).ConfigureAwait(false);
@@ -635,7 +683,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return bot.Response2FA(steamID);
@@ -664,7 +716,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return bot.Response2FAOff(steamID);
@@ -690,7 +746,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return await bot.Response2FAConfirm(steamID, confirm).ConfigureAwait(false);
@@ -734,7 +794,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return bot.ResponseFarm(steamID);
@@ -748,19 +812,6 @@ namespace ArchiSteamFarm {
return "https://github.com/" + Program.GithubRepo + "/wiki/Commands";
}
private static string ResponseHelp(ulong steamID, string botName) {
if (steamID == 0 || string.IsNullOrEmpty(botName)) {
return null;
}
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
}
return bot.ResponseHelp(steamID);
}
private async Task<string> ResponseRedeem(ulong steamID, string message, bool validate) {
if (steamID == 0 || string.IsNullOrEmpty(message) || !IsMaster(steamID)) {
return null;
@@ -774,12 +825,16 @@ namespace ArchiSteamFarm {
while (!string.IsNullOrEmpty(key) && currentBot != null) {
if (validate && !IsValidCdKey(key)) {
key = reader.ReadLine(); // Next key
continue; // Without changing the bot
continue; // Keep current bot
}
if (currentBot.SteamClient.IsConnected) {
if (!currentBot.SteamClient.IsConnected) {
currentBot = null; // Either bot will be changed, or loop aborted
} else {
ArchiHandler.PurchaseResponseCallback result = await currentBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
if (result != null) {
if (result == null) {
currentBot = null; // Either bot will be changed, or loop aborted
} else {
switch (result.PurchaseResult) {
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
@@ -787,7 +842,12 @@ namespace ArchiSteamFarm {
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + result.PurchaseResult + " | Items: " + string.Join("", result.Items));
key = reader.ReadLine(); // Next key
break; // Next bot (if needed)
if (result.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
break; // Next bot (if needed)
} else {
continue; // Keep current bot
}
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.AlreadyOwned:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.BaseGameRequired:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OnCooldown:
@@ -861,7 +921,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return await bot.ResponseRedeem(steamID, message, validate).ConfigureAwait(false);
@@ -902,7 +966,7 @@ namespace ArchiSteamFarm {
}
private async Task<string> ResponseAddLicense(ulong steamID, HashSet<uint> gameIDs) {
if (steamID == 0 || gameIDs == null || gameIDs.Count == 0 || !IsMaster(steamID)) {
if (steamID == 0 || gameIDs == null || gameIDs.Count == 0 || !SteamClient.IsConnected || !IsMaster(steamID)) {
return null;
}
@@ -926,7 +990,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
string[] gameIDs = games.Split(',');
@@ -952,7 +1020,13 @@ namespace ArchiSteamFarm {
return null;
}
Dictionary<uint, string> ownedGames = await ArchiWebHandler.GetOwnedGames().ConfigureAwait(false);
Dictionary<uint, string> ownedGames;
if (!string.IsNullOrEmpty(BotConfig.SteamApiKey)) {
ownedGames = ArchiWebHandler.GetOwnedGames(SteamClient.SteamID);
} else {
ownedGames = await ArchiWebHandler.GetOwnedGames().ConfigureAwait(false);
}
if (ownedGames == null || ownedGames.Count == 0) {
return "List of owned games is empty!";
}
@@ -998,7 +1072,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return await bot.ResponseOwns(steamID, query).ConfigureAwait(false);
@@ -1031,7 +1109,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
string[] gameIDs = games.Split(',');
@@ -1072,7 +1154,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return await bot.ResponseStart(steamID).ConfigureAwait(false);
@@ -1098,7 +1184,11 @@ namespace ArchiSteamFarm {
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
} else {
return null;
}
}
return bot.ResponseStop(steamID);
@@ -1157,7 +1247,7 @@ namespace ArchiSteamFarm {
}
private void SendMessageToChannel(ulong steamID, string message) {
if (steamID == 0 || string.IsNullOrEmpty(message)) {
if (steamID == 0 || string.IsNullOrEmpty(message) || !SteamClient.IsConnected) {
return;
}
@@ -1168,7 +1258,7 @@ namespace ArchiSteamFarm {
}
private void SendMessageToUser(ulong steamID, string message) {
if (steamID == 0 || string.IsNullOrEmpty(message)) {
if (steamID == 0 || string.IsNullOrEmpty(message) || !SteamClient.IsConnected) {
return;
}
@@ -1271,7 +1361,7 @@ namespace ArchiSteamFarm {
}
private void JoinMasterChat() {
if (BotConfig.SteamMasterClanID == 0) {
if (!SteamClient.IsConnected || BotConfig.SteamMasterClanID == 0) {
return;
}
@@ -1370,6 +1460,8 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericInfo("Disconnected from Steam!", BotName);
FirstTradeSent = false;
CardsFarmer.StopFarming().Forget();
// If we initiated disconnect, do not attempt to reconnect
@@ -1377,10 +1469,6 @@ namespace ArchiSteamFarm {
return;
}
if (!KeepRunning) {
return;
}
if (InvalidPassword) {
InvalidPassword = false;
if (!string.IsNullOrEmpty(BotDatabase.LoginKey)) { // InvalidPassword means usually that login key has expired, if we used it
@@ -1402,11 +1490,15 @@ namespace ArchiSteamFarm {
await Utilities.SleepAsync(Program.GlobalConfig.AccountPlayingDelay * 60 * 1000).ConfigureAwait(false);
}
if (!KeepRunning || SteamClient.IsConnected) {
return;
}
Logging.LogGenericInfo("Reconnecting...", BotName);
// 2FA tokens are expiring soon, use limiter only when we don't have any pending
if (TwoFactorCode == null) {
await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
// 2FA tokens are expiring soon, don't use limiter when user is providing one
if (TwoFactorCode == null || BotDatabase.SteamGuardAccount != null) {
await LimitLoginRequestsAsync().ConfigureAwait(false);
}
SteamClient.Connect();
@@ -1622,14 +1714,14 @@ namespace ArchiSteamFarm {
if (BotConfig.SteamMasterClanID != 0) {
Task.Run(async () => {
await ArchiWebHandler.JoinClan(BotConfig.SteamMasterClanID).ConfigureAwait(false);
await ArchiWebHandler.JoinGroup(BotConfig.SteamMasterClanID).ConfigureAwait(false);
JoinMasterChat();
}).Forget();
}
if (Program.GlobalConfig.Statistics) {
Task.Run(async () => {
await ArchiWebHandler.JoinClan(ArchiSCFarmGroup).ConfigureAwait(false);
await ArchiWebHandler.JoinGroup(ArchiSCFarmGroup).ConfigureAwait(false);
SteamFriends.JoinChat(ArchiSCFarmGroup);
}).Forget();
}
@@ -1707,11 +1799,11 @@ namespace ArchiSteamFarm {
bool checkTrades = false;
bool markInventory = false;
foreach (var notification in callback.Notifications) {
switch (notification.NotificationType) {
case ArchiHandler.NotificationsCallback.Notification.ENotificationType.Items:
switch (notification) {
case ArchiHandler.NotificationsCallback.ENotification.Items:
markInventory = true;
break;
case ArchiHandler.NotificationsCallback.Notification.ENotificationType.Trading:
case ArchiHandler.NotificationsCallback.ENotification.Trading:
checkTrades = true;
break;
}
@@ -1727,7 +1819,7 @@ namespace ArchiSteamFarm {
}
private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) {
if (callback == null) {
if (callback == null || callback.OfflineMessagesCount == 0) {
return;
}

View File

@@ -68,6 +68,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal bool AcceptGifts { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool SteamTradeMatcher { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForwardKeysToOtherBots { get; private set; } = false;

View File

@@ -1,67 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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 SteamKit2;
using SteamKit2.Internal;
using System;
using System.IO;
namespace ArchiSteamFarm {
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage {
internal ulong GroupID { get; set; } = 0;
internal bool AcceptInvite { get; set; } = true;
EMsg ISteamSerializableMessage.GetEMsg() {
return EMsg.ClientAcknowledgeClanInvite;
}
void ISteamSerializable.Serialize(Stream stream) {
if (stream == null) {
return;
}
try {
BinaryWriter binaryWriter = new BinaryWriter(stream);
binaryWriter.Write(GroupID);
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

@@ -37,8 +37,8 @@ namespace ArchiSteamFarm {
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
internal readonly HashSet<uint> CurrentGamesFarming = new HashSet<uint>();
private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false);
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
private readonly Bot Bot;
private readonly Timer Timer;
@@ -84,15 +84,15 @@ namespace ArchiSteamFarm {
return;
}
await Semaphore.WaitAsync().ConfigureAwait(false);
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
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;
}
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);
await Bot.OnFarmingFinished(false).ConfigureAwait(false);
return;
@@ -100,9 +100,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games to farm on this account...", Bot.BotName);
NowFarming = true;
Semaphore.Release(); // From this point we allow other calls to shut us down
bool farmedSomething = false;
FarmingSemaphore.Release(); // From this point we allow other calls to shut us down
do {
// Now the algorithm used for farming depends on whether account is restricted or not
@@ -114,7 +112,6 @@ namespace ArchiSteamFarm {
while (gamesToFarmSolo.Count > 0) {
uint appID = gamesToFarmSolo.First();
if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true;
gamesToFarmSolo.Remove(appID);
gamesToFarmSolo.TrimExcess();
} else {
@@ -135,9 +132,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
while (GamesToFarm.Count > 0) {
uint appID = GamesToFarm.Keys.FirstOrDefault();
if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true;
} else {
if (!await FarmSolo(appID).ConfigureAwait(false)) {
NowFarming = false;
return;
}
@@ -150,7 +145,7 @@ namespace ArchiSteamFarm {
NowFarming = false;
Logging.LogGenericInfo("Farming finished!", Bot.BotName);
await Bot.OnFarmingFinished(farmedSomething).ConfigureAwait(false);
await Bot.OnFarmingFinished(true).ConfigureAwait(false);
}
internal async Task StopFarming() {
@@ -158,10 +153,10 @@ namespace ArchiSteamFarm {
return;
}
await Semaphore.WaitAsync().ConfigureAwait(false);
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
if (!NowFarming) {
Semaphore.Release();
FarmingSemaphore.Release();
return;
}
@@ -179,7 +174,7 @@ namespace ArchiSteamFarm {
FarmResetEvent.Reset();
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
Semaphore.Release();
FarmingSemaphore.Release();
}
internal async Task RestartFarming() {
@@ -421,7 +416,7 @@ namespace ArchiSteamFarm {
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
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;
break;
}
@@ -452,7 +447,7 @@ namespace ArchiSteamFarm {
bool success = true;
while (maxHour < 2) {
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false;
break;
}

View File

@@ -55,6 +55,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoRestart { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable;

View File

@@ -27,15 +27,15 @@ using System.Collections.Generic;
namespace ArchiSteamFarm {
internal static class GitHub {
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; }
}
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)]
internal string Tag { get; private set; }

View File

@@ -28,40 +28,161 @@ using System.Collections.Generic;
namespace ArchiSteamFarm {
internal static class Steam {
internal sealed class Item {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
[JsonProperty(Required = Required.DisallowNull)]
internal string appid { get; set; }
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
internal const ushort SteamAppID = 753;
internal const byte SteamContextID = 6;
[JsonProperty(Required = Required.DisallowNull)]
internal string contextid { get; set; }
internal enum EType : byte {
Unknown,
[JsonProperty(Required = Required.DisallowNull)]
internal string assetid { get; set; }
BoosterPack,
Coupon,
Gift,
SteamGems,
[JsonProperty(Required = Required.DisallowNull)]
internal string id {
get { return assetid; }
set { assetid = value; }
Emoticon,
FoilTradingCard,
ProfileBackground,
TradingCard
}
[JsonProperty(Required = Required.AllowNull)]
internal string classid { get; set; }
internal uint AppID { get; set; }
[JsonProperty(Required = Required.AllowNull)]
internal string instanceid { get; set; }
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)]
internal string AppIDString {
get {
return AppID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
}
[JsonProperty(Required = Required.Always)]
internal string amount { get; set; }
uint result;
if (!uint.TryParse(value, out result)) {
return;
}
AppID = result;
}
}
internal ulong ContextID { get; set; }
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
internal 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)]
internal 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)]
internal string ID {
get { return AssetIDString; }
set { AssetIDString = value; }
}
internal ulong ClassID { get; set; }
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
internal 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)]
internal 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)]
internal 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 {
[JsonProperty(Required = Required.Always)]
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 sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
internal enum ETradeOfferState : byte {
Unknown,
Invalid,
@@ -77,46 +198,142 @@ namespace ArchiSteamFarm {
OnHold
}
[JsonProperty(Required = Required.Always)]
internal string tradeofferid { get; set; }
internal ulong TradeOfferID { get; set; }
[JsonProperty(Required = Required.Always)]
internal uint accountid_other { get; set; }
[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 {
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always)]
internal string TradeOfferIDString {
get {
if (_OtherSteamID64 == 0 && accountid_other != 0) {
_OtherSteamID64 = new SteamID(accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
return TradeOfferID.ToString();
}
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 { 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 {
get {
if (OtherSteamID3 == 0) {
return 0;
}
return new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
}
set {
if (value == 0) {
return;
}
OtherSteamID3 = new SteamID(value).AccountID;
}
}
internal bool IsSteamCardsOnlyTradeForUs() {
foreach (Item item in ItemsToGive) {
if (item.AppID != Item.SteamAppID || item.ContextID != Item.SteamContextID || (item.Type != Item.EType.FoilTradingCard && item.Type != Item.EType.TradingCard)) {
return false;
}
}
return true;
}
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>();
ItemsPerType[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>();
ItemsPerType[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;
}
}
internal sealed class TradeOfferRequest {
[JsonProperty(Required = Required.Always)]
internal bool newversion { get; } = true;
internal sealed class ItemList {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal HashSet<Item> Assets { get; } = new HashSet<Item>();
}
[JsonProperty(Required = Required.Always)]
internal int version { get; } = 2;
[JsonProperty(PropertyName = "newversion", Required = Required.Always)]
internal bool NewVersion { get; } = true;
[JsonProperty(Required = Required.Always)]
internal Steam.ItemList me { get; } = new Steam.ItemList();
[JsonProperty(PropertyName = "version", Required = Required.Always)]
internal byte Version { get; } = 2;
[JsonProperty(Required = Required.Always)]
internal Steam.ItemList them { get; } = new Steam.ItemList();
[JsonProperty(PropertyName = "me", Required = Required.Always)]
internal ItemList ItemsToGive { get; } = new ItemList();
[JsonProperty(PropertyName = "them", Required = Required.Always)]
internal ItemList ItemsToReceive { get; } = new ItemList();
}
}
}

View File

@@ -38,16 +38,21 @@ namespace ArchiSteamFarm {
if (LogToFile) {
lock (FileLock) {
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)) {
return;
}
@@ -55,7 +60,7 @@ namespace ArchiSteamFarm {
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)) {
return;
}
@@ -63,7 +68,7 @@ namespace ArchiSteamFarm {
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) {
return;
}
@@ -76,7 +81,7 @@ namespace ArchiSteamFarm {
}
}
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)) {
return;
}
@@ -84,7 +89,7 @@ namespace ArchiSteamFarm {
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)) {
return;
}
@@ -92,7 +97,7 @@ namespace ArchiSteamFarm {
Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message);
}
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
}
@@ -101,7 +106,7 @@ namespace ArchiSteamFarm {
}
[Conditional("DEBUG")]
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)) {
return;
}
@@ -125,6 +130,10 @@ namespace ArchiSteamFarm {
if (LogToFile) {
lock (FileLock) {
if (!LogToFile) {
return;
}
try {
File.AppendAllText(Program.LogFile, loggedMessage);
} catch (Exception e) {

View File

@@ -48,7 +48,7 @@ namespace ArchiSteamFarm {
WCFHostname
}
internal enum EMode : byte {
private enum EMode : byte {
Unknown,
Normal, // Standard most common usage
Client, // WCF client only
@@ -65,13 +65,11 @@ namespace ArchiSteamFarm {
private const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
internal static readonly Version Version = Assembly.GetName().Version;
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
private static readonly object ConsoleLock = new object();
private static readonly SemaphoreSlim SteamSemaphore = new SemaphoreSlim(1);
private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false);
private static readonly string ExecutableFile = Assembly.Location;
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
private static readonly string ExecutableFile = Assembly.GetEntryAssembly().Location;
private static readonly string ExecutableName = Path.GetFileName(ExecutableFile);
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
private static readonly WCF WCF = new WCF();
@@ -185,7 +183,7 @@ namespace ArchiSteamFarm {
return;
}
GitHub.Asset binaryAsset = null;
GitHub.ReleaseResponse.Asset binaryAsset = null;
foreach (var asset in releaseResponse.Assets) {
if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase)) {
continue;
@@ -251,10 +249,17 @@ namespace ArchiSteamFarm {
return;
}
Logging.LogGenericInfo("Update process is finished! ASF will now restart itself...");
await Utilities.SleepAsync(5000);
Logging.LogGenericInfo("Update process finished!");
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) {
@@ -272,14 +277,6 @@ namespace ArchiSteamFarm {
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) {
if (userInputType == EUserInputType.Unknown) {
return null;
@@ -500,7 +497,9 @@ namespace ArchiSteamFarm {
Bot bot = new Bot(botName);
if (bot.BotConfig != null && bot.BotConfig.Enabled) {
isRunning = true;
if (bot.BotConfig.StartOnLaunch) {
isRunning = true;
}
} else {
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
}
@@ -516,7 +515,7 @@ namespace ArchiSteamFarm {
Init(args);
// Wait for signal to shutdown
ShutdownResetEvent.WaitOne();
ShutdownResetEvent.Wait();
// We got a signal to shutdown
Exit();

View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.0.3.2")]
[assembly: AssemblyFileVersion("2.0.3.2")]
[assembly: AssemblyVersion("2.0.4.3")]
[assembly: AssemblyFileVersion("2.0.4.3")]

View File

@@ -57,16 +57,12 @@ namespace ArchiSteamFarm {
}
internal async Task CheckTrades() {
bool shouldRun = false;
lock (TradesSemaphore) {
if (ParsingTasks < 2) {
ParsingTasks++;
shouldRun = true;
if (ParsingTasks >= 2) {
return;
}
}
if (!shouldRun) {
return;
ParsingTasks++;
}
await TradesSemaphore.WaitAsync().ConfigureAwait(false);
@@ -80,8 +76,8 @@ namespace ArchiSteamFarm {
}
private async Task ParseActiveTrades() {
List<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
if (tradeOffers == null) {
HashSet<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
if (tradeOffers == null || tradeOffers.Count == 0) {
return;
}
@@ -90,32 +86,27 @@ namespace ArchiSteamFarm {
}
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;
}
ulong tradeID;
if (!ulong.TryParse(tradeOffer.tradeofferid, out tradeID)) {
return;
}
if (ShouldAcceptTrade(tradeOffer)) {
Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName);
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
} 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) {
return false;
}
// 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
return tradeOffer.items_to_receive.Count > 0;
return tradeOffer.ItemsToReceive.Count > 0;
}
// Always accept trades from SteamMasterID
@@ -123,10 +114,85 @@ namespace ArchiSteamFarm {
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
return false;
// Decline trade if we're giving more count-wise
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
uint itemsToGiveDupesValue = 0;
foreach (Steam.Item item in tradeOffer.ItemsToGive) {
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(item.ClassID, item.InstanceID);
uint amount;
if (!amountMap.TryGetValue(key, out amount)) {
continue;
}
itemsToGiveDupesValue += amount;
}
// Calculate our value of items to receive
uint itemsToReceiveDupesValue = 0;
foreach (Steam.Item item in tradeOffer.ItemsToReceive) {
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(item.ClassID, item.InstanceID);
uint amount;
if (!amountMap.TryGetValue(key, out amount)) {
continue;
}
itemsToReceiveDupesValue += amount;
}
// Trade is worth for us if we're in total trading more of our dupes for less of our dupes (or at least same amount)
// Which means that itemsToGiveDupesValue should be greater than itemsToReceiveDupesValue
return itemsToGiveDupesValue > itemsToReceiveDupesValue;
}
}
}

View File

@@ -33,6 +33,10 @@ namespace ArchiSteamFarm {
internal static void Forget(this Task task) { }
internal static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
if (action == null) {
return Task.FromResult(true);
}
return Task.WhenAll(sequence.Select(action));
}
@@ -64,20 +68,5 @@ namespace ArchiSteamFarm {
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

@@ -122,8 +122,13 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Received command: " + input);
string command = '!' + input;
string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
string output;
if (Program.GlobalConfig.SteamOwnerID == 0) {
output = "Refusing to handle request because SteamOwnerID is not set!";
} else {
string command = '!' + input;
output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
}
Logging.LogGenericInfo("Answered to command: " + input + " with: " + output);
return output;

View File

@@ -83,23 +83,27 @@ namespace ArchiSteamFarm {
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)) {
return false;
}
using (HttpResponseMessage response = await UrlGetToResponse(request, referer).ConfigureAwait(false)) {
using (HttpResponseMessage response = await UrlHeadToResponse(request, referer).ConfigureAwait(false)) {
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)) {
return false;
return null;
}
using (HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false)) {
return response != null;
using (HttpResponseMessage response = await UrlHeadToResponse(request, referer).ConfigureAwait(false)) {
if (response == null) {
return null;
}
return response.RequestMessage.RequestUri;
}
}
@@ -141,10 +145,8 @@ namespace ArchiSteamFarm {
return null;
}
content = WebUtility.HtmlDecode(content);
HtmlDocument htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(content);
htmlDocument.LoadHtml(WebUtility.HtmlDecode(content));
return htmlDocument;
}
@@ -192,6 +194,16 @@ namespace ArchiSteamFarm {
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) {
if (string.IsNullOrEmpty(request)) {
return null;
@@ -200,6 +212,14 @@ namespace ArchiSteamFarm {
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) {
if (string.IsNullOrEmpty(request)) {
return null;
@@ -213,7 +233,7 @@ namespace ArchiSteamFarm {
return null;
}
if (request.StartsWith("https://") && Program.GlobalConfig.ForceHttp) {
if (request.StartsWith("https://", StringComparison.Ordinal) && Program.GlobalConfig.ForceHttp) {
return null;
}
@@ -239,15 +259,17 @@ namespace ArchiSteamFarm {
}
}
if (responseMessage == null || !responseMessage.IsSuccessStatusCode) {
if (responseMessage == null) {
return null;
}
if (!responseMessage.IsSuccessStatusCode) {
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();
}
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,
"Headless": false,
"AutoUpdates": true,
"AutoRestart": true,
"UpdateChannel": 1,
"SteamProtocol": 6,
"SteamOwnerID": 0,

View File

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

View File

@@ -28,7 +28,7 @@ using System.Collections.Generic;
using System.IO;
namespace ConfigGenerator {
internal class ASFConfig {
internal abstract class ASFConfig {
internal static readonly HashSet<ASFConfig> ASFConfigs = new HashSet<ASFConfig>();
internal string FilePath { get; set; }

View File

@@ -25,6 +25,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
namespace ConfigGenerator {
@@ -39,6 +40,7 @@ namespace ConfigGenerator {
public string SteamLogin { get; set; } = null;
[JsonProperty]
[PasswordPropertyText(true)]
public string SteamPassword { get; set; } = null;
[JsonProperty]
@@ -68,6 +70,9 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool AcceptGifts { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool SteamTradeMatcher { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool ForwardKeysToOtherBots { get; set; } = false;

View File

@@ -26,7 +26,7 @@ using System.IO;
using System.Windows.Forms;
namespace ConfigGenerator {
internal class ConfigPage : TabPage {
internal sealed class ConfigPage : TabPage {
internal readonly ASFConfig ASFConfig;
internal ConfigPage(ASFConfig config) {

View File

@@ -27,8 +27,8 @@ using System.Drawing;
using System.Windows.Forms;
namespace ConfigGenerator {
class DialogBox {
public static DialogResult InputBox(string title, string promptText, out string value) {
internal sealed class DialogBox {
internal static DialogResult InputBox(string title, string promptText, out string value) {
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(promptText)) {
value = null;
return DialogResult.Abort;
@@ -75,7 +75,7 @@ namespace ConfigGenerator {
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)) {
return DialogResult.Abort;
}

View File

@@ -54,6 +54,9 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool AutoUpdates { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
public bool AutoRestart { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable;

View File

@@ -30,7 +30,7 @@ using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace ConfigGenerator {
public partial class MainForm : Form {
internal sealed partial class MainForm : Form {
private const byte ReservedTabs = 3;
private readonly TabPage NewTab = new TabPage { Text = "+" };
@@ -40,7 +40,7 @@ namespace ConfigGenerator {
private ConfigPage ASFTab;
private TabPage OldTab;
public MainForm() {
internal MainForm() {
InitializeComponent();
}

View File

@@ -36,7 +36,7 @@ namespace ConfigGenerator {
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>
/// The main entry point for the application.

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