mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-26 11:16:47 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3da5d6afc | ||
|
|
84f33fcef4 | ||
|
|
22f0d423a3 | ||
|
|
f1d7609796 | ||
|
|
77386ecae5 | ||
|
|
4e86d21ef8 | ||
|
|
044fc87691 | ||
|
|
79fad62a4d | ||
|
|
6622d3b147 | ||
|
|
6b6d5429ad | ||
|
|
d59d0a8415 | ||
|
|
e3100d3938 | ||
|
|
42c020e552 | ||
|
|
554273833b | ||
|
|
093a29df62 | ||
|
|
31aa6b2e4a | ||
|
|
9f7ecdf054 | ||
|
|
7c4c74bf84 | ||
|
|
b8f03abd8b | ||
|
|
47f846540b | ||
|
|
bc14713079 | ||
|
|
770a8fee66 | ||
|
|
1df9af08e6 | ||
|
|
27464f6120 | ||
|
|
dfd45c6e25 |
@@ -1,4 +1,3 @@
|
||||
sudo: false
|
||||
language: csharp
|
||||
solution: ArchiSteamFarm.sln
|
||||
|
||||
@@ -8,3 +7,6 @@ git:
|
||||
mono:
|
||||
- weekly
|
||||
- latest
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly List<Notification> Notifications;
|
||||
internal readonly HashSet<Notification> Notifications;
|
||||
|
||||
internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
|
||||
JobID = jobID;
|
||||
@@ -76,7 +76,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Notifications = new List<Notification>(msg.notifications.Count);
|
||||
Notifications = new HashSet<Notification>();
|
||||
foreach (var notification in msg.notifications) {
|
||||
Notifications.Add(new Notification((Notification.ENotificationType) notification.user_notification_type));
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (msg.count_new_items > 0) {
|
||||
Notifications = new List<Notification>(1) {
|
||||
Notifications = new HashSet<Notification>() {
|
||||
new Notification(Notification.ENotificationType.Items)
|
||||
};
|
||||
}
|
||||
@@ -99,7 +99,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal sealed class OfflineMessageCallback : CallbackMsg {
|
||||
internal readonly uint OfflineMessages;
|
||||
internal readonly List<uint> Users;
|
||||
|
||||
internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
|
||||
JobID = jobID;
|
||||
@@ -109,7 +108,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
OfflineMessages = msg.offline_messages;
|
||||
Users = msg.friends_with_offline_messages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,8 +153,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
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
|
||||
string gameName = lineItem["ItemDescription"].Value;
|
||||
gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML
|
||||
Items.Add(appID, gameName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,53 @@ namespace ArchiSteamFarm {
|
||||
SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunity;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (bot == null) {
|
||||
throw new ArgumentNullException("bot");
|
||||
@@ -132,9 +179,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal async Task<bool?> IsLoggedIn() {
|
||||
string request = SteamCommunityURL + "/my/profile";
|
||||
|
||||
HtmlDocument htmlDocument = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile").ConfigureAwait(false);
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
@@ -147,15 +196,13 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal async Task<bool> RefreshSessionIfNeeded() {
|
||||
DateTime now = DateTime.Now;
|
||||
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
||||
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
now = DateTime.Now;
|
||||
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
||||
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
||||
SessionSemaphore.Release();
|
||||
return true;
|
||||
}
|
||||
@@ -165,8 +212,7 @@ namespace ArchiSteamFarm {
|
||||
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
|
||||
if (isLoggedIn.GetValueOrDefault(true)) {
|
||||
result = true;
|
||||
now = DateTime.Now;
|
||||
LastSessionRefreshCheck = now;
|
||||
LastSessionRefreshCheck = DateTime.Now;
|
||||
} else {
|
||||
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
|
||||
result = await Bot.RefreshSession().ConfigureAwait(false);
|
||||
@@ -249,8 +295,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<Tuple<ulong, ulong>, uint> appIDMap = new Dictionary<Tuple<ulong, ulong>, uint>();
|
||||
Dictionary<Tuple<ulong, ulong>, Steam.Item.EType> typeMap = new Dictionary<Tuple<ulong, ulong>, Steam.Item.EType>();
|
||||
Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>> descriptionMap = 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) {
|
||||
@@ -260,36 +305,24 @@ namespace ArchiSteamFarm {
|
||||
ulong instanceID = description["instanceid"].AsUnsignedLong();
|
||||
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(classID, instanceID);
|
||||
|
||||
if (!appIDMap.ContainsKey(key)) {
|
||||
string hashName = description["market_hash_name"].Value;
|
||||
if (!string.IsNullOrEmpty(hashName)) {
|
||||
int index = hashName.IndexOf('-');
|
||||
if (index < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint appID;
|
||||
if (!uint.TryParse(hashName.Substring(0, index), out appID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
appIDMap[key] = appID;
|
||||
}
|
||||
if (descriptionMap.ContainsKey(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!typeMap.ContainsKey(key)) {
|
||||
string type = description["type"].Value;
|
||||
if (!string.IsNullOrEmpty(type)) {
|
||||
if (type.EndsWith("Trading Card", StringComparison.Ordinal)) {
|
||||
typeMap[key] = Steam.Item.EType.TradingCard;
|
||||
} else if (type.EndsWith("Profile Background", StringComparison.Ordinal)) {
|
||||
typeMap[key] = Steam.Item.EType.ProfileBackground;
|
||||
} else {
|
||||
typeMap[key] = Steam.Item.EType.Unknown;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
descriptionMap[key] = new Tuple<uint, Steam.Item.EType>(appID, type);
|
||||
}
|
||||
|
||||
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
|
||||
@@ -308,19 +341,15 @@ namespace ArchiSteamFarm {
|
||||
AssetID = item["assetid"].AsUnsignedLong(),
|
||||
ClassID = item["classid"].AsUnsignedLong(),
|
||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||
Amount = (byte) item["amount"].AsUnsignedLong()
|
||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||
};
|
||||
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(steamItem.ClassID, steamItem.InstanceID);
|
||||
|
||||
uint realAppID;
|
||||
if (appIDMap.TryGetValue(key, out realAppID)) {
|
||||
steamItem.RealAppID = realAppID;
|
||||
}
|
||||
|
||||
Steam.Item.EType type;
|
||||
if (typeMap.TryGetValue(key, out type)) {
|
||||
steamItem.Type = type;
|
||||
Tuple<uint, Steam.Item.EType> description;
|
||||
if (descriptionMap.TryGetValue(key, out description)) {
|
||||
steamItem.RealAppID = description.Item1;
|
||||
steamItem.Type = description.Item2;
|
||||
}
|
||||
|
||||
tradeOffer.ItemsToGive.Add(steamItem);
|
||||
@@ -333,19 +362,15 @@ namespace ArchiSteamFarm {
|
||||
AssetID = item["assetid"].AsUnsignedLong(),
|
||||
ClassID = item["classid"].AsUnsignedLong(),
|
||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||
Amount = (byte) item["amount"].AsUnsignedLong()
|
||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||
};
|
||||
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(steamItem.ClassID, steamItem.InstanceID);
|
||||
|
||||
uint realAppID;
|
||||
if (appIDMap.TryGetValue(key, out realAppID)) {
|
||||
steamItem.RealAppID = realAppID;
|
||||
}
|
||||
|
||||
Steam.Item.EType type;
|
||||
if (typeMap.TryGetValue(key, out type)) {
|
||||
steamItem.Type = type;
|
||||
Tuple<uint, Steam.Item.EType> description;
|
||||
if (descriptionMap.TryGetValue(key, out description)) {
|
||||
steamItem.RealAppID = description.Item1;
|
||||
steamItem.Type = description.Item2;
|
||||
}
|
||||
|
||||
tradeOffer.ItemsToReceive.Add(steamItem);
|
||||
@@ -434,28 +459,108 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
JObject jObject = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
|
||||
jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (jObject == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
IEnumerable<JToken> jTokens = jObject.SelectTokens("$.rgInventory.*");
|
||||
if (jTokens == null) {
|
||||
Logging.LogNullError("jTokens", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
||||
foreach (JToken jToken in jTokens) {
|
||||
try {
|
||||
result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString()));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
|
||||
ushort nextPage = 0;
|
||||
while (true) {
|
||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1&start=" + nextPage;
|
||||
|
||||
JObject jObject = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
|
||||
jObject = await WebBrowser.UrlGetToJObject(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,9 +644,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) {
|
||||
@@ -561,9 +668,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) {
|
||||
@@ -579,9 +688,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.UrlGet(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
|
||||
@@ -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 {
|
||||
@@ -100,9 +101,8 @@ 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}))?");
|
||||
}
|
||||
|
||||
internal Bot(string botName) {
|
||||
@@ -295,8 +295,8 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task OnFarmingFinished(bool farmedSomething) {
|
||||
if (farmedSomething && BotConfig.SendOnFarmingFinished) {
|
||||
internal async Task OnFarmingFinished() {
|
||||
if (BotConfig.SendOnFarmingFinished) {
|
||||
await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -310,11 +310,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 +350,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]);
|
||||
@@ -410,8 +410,8 @@ namespace ArchiSteamFarm {
|
||||
Task.Run(() => HandleCallbacks()).Forget();
|
||||
}
|
||||
|
||||
// 2FA tokens are expiring soon, use limiter only when we don't have any pending
|
||||
if (TwoFactorCode == null) {
|
||||
// 2FA tokens are expiring soon, use limiter only when user is providing one
|
||||
if (TwoFactorCode == null || BotDatabase.SteamGuardAccount != null) {
|
||||
await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -463,6 +463,7 @@ namespace ArchiSteamFarm {
|
||||
Logging.LogGenericInfo("ASF requires a few more steps to complete authenticator import...", BotName);
|
||||
|
||||
if (!InitializeLoginAndPassword()) {
|
||||
BotDatabase.SteamGuardAccount = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -473,10 +474,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;
|
||||
}
|
||||
@@ -594,6 +597,12 @@ namespace ArchiSteamFarm {
|
||||
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);
|
||||
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!";
|
||||
@@ -1413,8 +1422,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Logging.LogGenericInfo("Reconnecting...", BotName);
|
||||
|
||||
// 2FA tokens are expiring soon, use limiter only when we don't have any pending
|
||||
if (TwoFactorCode == null) {
|
||||
// 2FA tokens are expiring soon, use limiter only when user is providing one
|
||||
if (TwoFactorCode == null || BotDatabase.SteamGuardAccount != null) {
|
||||
await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -1736,7 +1745,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) {
|
||||
if (callback == null) {
|
||||
if (callback == null || callback.OfflineMessages == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace ArchiSteamFarm {
|
||||
if (!await IsAnythingToFarm().ConfigureAwait(false)) {
|
||||
Semaphore.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);
|
||||
await Bot.OnFarmingFinished().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,8 +102,6 @@ namespace ArchiSteamFarm {
|
||||
NowFarming = true;
|
||||
Semaphore.Release(); // From this point we allow other calls to shut us down
|
||||
|
||||
bool farmedSomething = false;
|
||||
|
||||
do {
|
||||
// Now the algorithm used for farming depends on whether account is restricted or not
|
||||
if (Bot.BotConfig.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
|
||||
@@ -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().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task StopFarming() {
|
||||
|
||||
@@ -34,6 +34,14 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal enum EType : byte {
|
||||
Unknown,
|
||||
|
||||
BoosterPack,
|
||||
Coupon,
|
||||
Gift,
|
||||
SteamGems,
|
||||
|
||||
Emoticon,
|
||||
FoilTradingCard,
|
||||
ProfileBackground,
|
||||
TradingCard
|
||||
}
|
||||
@@ -71,8 +79,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.TryParse(value, out result)) {
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,8 +100,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.TryParse(value, out result)) {
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,7 +110,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
|
||||
internal string id {
|
||||
internal string ID {
|
||||
get { return AssetIDString; }
|
||||
set { AssetIDString = value; }
|
||||
}
|
||||
@@ -119,8 +127,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.TryParse(value, out result)) {
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,8 +148,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.TryParse(value, out result)) {
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,7 +157,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal byte Amount;
|
||||
internal uint Amount;
|
||||
|
||||
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
|
||||
internal string AmountString {
|
||||
@@ -161,8 +169,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
byte result;
|
||||
if (!byte.TryParse(value, out result)) {
|
||||
uint result;
|
||||
if (!uint.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,8 +182,7 @@ namespace ArchiSteamFarm {
|
||||
internal EType Type { get; set; }
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -242,60 +249,77 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsSteamCardsOnlyTrade {
|
||||
get {
|
||||
foreach (Item item in ItemsToGive) {
|
||||
if (item.AppID != Item.SteamAppID || item.ContextID != Item.SteamContextID || item.Type != Item.EType.TradingCard) {
|
||||
return false;
|
||||
}
|
||||
internal bool IsSteamCardsOnlyTrade() {
|
||||
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;
|
||||
}
|
||||
|
||||
foreach (Item item in ItemsToReceive) {
|
||||
if (item.AppID != Item.SteamAppID || item.ContextID != Item.SteamContextID || item.Type != Item.EType.TradingCard) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (Item item in ItemsToReceive) {
|
||||
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 IsPotentiallyDupesTrade {
|
||||
get {
|
||||
Dictionary<uint, byte> ItemsToGivePerGameAmount = new Dictionary<uint, byte>();
|
||||
foreach (Item item in ItemsToGive) {
|
||||
byte amount;
|
||||
if (ItemsToGivePerGameAmount.TryGetValue(item.RealAppID, out amount)) {
|
||||
ItemsToGivePerGameAmount[item.RealAppID] = (byte) (amount + item.Amount);
|
||||
internal bool IsPotentiallyDupesTrade() {
|
||||
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 {
|
||||
ItemsToGivePerGameAmount[item.RealAppID] = item.Amount;
|
||||
ItemsPerType[item.Type] = item.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<uint, byte> ItemsToReceivePerGameAmount = new Dictionary<uint, byte>();
|
||||
foreach (Item item in ItemsToReceive) {
|
||||
byte amount;
|
||||
if (ItemsToReceivePerGameAmount.TryGetValue(item.RealAppID, out amount)) {
|
||||
ItemsToReceivePerGameAmount[item.RealAppID] = (byte) (amount + item.Amount);
|
||||
} else {
|
||||
ItemsToReceivePerGameAmount[item.RealAppID] = item.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that amounts are exactly the same
|
||||
foreach (KeyValuePair<uint, byte> item in ItemsToGivePerGameAmount) {
|
||||
byte otherValue;
|
||||
if (!ItemsToReceivePerGameAmount.TryGetValue(item.Key, out otherValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.Value != otherValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 per type and per game matches
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +333,7 @@ namespace ArchiSteamFarm {
|
||||
internal bool NewVersion { get; } = true;
|
||||
|
||||
[JsonProperty(PropertyName = "version", Required = Required.Always)]
|
||||
internal int Version { get; } = 2;
|
||||
internal byte Version { get; } = 2;
|
||||
|
||||
[JsonProperty(PropertyName = "me", Required = Required.Always)]
|
||||
internal ItemList ItemsToGive { get; } = new ItemList();
|
||||
|
||||
@@ -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.5")]
|
||||
[assembly: AssemblyFileVersion("2.0.3.5")]
|
||||
[assembly: AssemblyVersion("2.0.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.0.4.0")]
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
|
||||
private readonly HashSet<ulong> RecentlyParsedTrades = new HashSet<ulong>();
|
||||
|
||||
private byte ParsingTasks;
|
||||
|
||||
@@ -79,12 +80,36 @@ namespace ArchiSteamFarm {
|
||||
TradesSemaphore.Release();
|
||||
}
|
||||
|
||||
private async Task ForgetRecentTrade(ulong tradeID) {
|
||||
await Utilities.SleepAsync(24 * 60 * 60 * 1000).ConfigureAwait(false);
|
||||
lock (RecentlyParsedTrades) {
|
||||
RecentlyParsedTrades.Remove(tradeID);
|
||||
RecentlyParsedTrades.TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ParseActiveTrades() {
|
||||
HashSet<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
|
||||
if (tradeOffers == null || tradeOffers.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
lock (RecentlyParsedTrades) {
|
||||
tradeOffers.RemoveWhere(trade => RecentlyParsedTrades.Contains(trade.TradeOfferID));
|
||||
}
|
||||
|
||||
if (tradeOffers.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Steam.TradeOffer tradeOffer in tradeOffers) {
|
||||
lock (RecentlyParsedTrades) {
|
||||
RecentlyParsedTrades.Add(tradeOffer.TradeOfferID);
|
||||
}
|
||||
|
||||
ForgetRecentTrade(tradeOffer.TradeOfferID).Forget();
|
||||
}
|
||||
|
||||
await tradeOffers.ForEachAsync(ParseTrade).ConfigureAwait(false);
|
||||
await Bot.AcceptConfirmations(true, Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
|
||||
}
|
||||
@@ -94,7 +119,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldAcceptTrade(tradeOffer)) {
|
||||
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
|
||||
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
} else {
|
||||
@@ -102,7 +127,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
|
||||
private async Task<bool> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
|
||||
if (tradeOffer == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -129,14 +154,73 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Rule 2 - We always trade steam cards and only for the same set
|
||||
if (!tradeOffer.IsSteamCardsOnlyTrade || !tradeOffer.IsPotentiallyDupesTrade) {
|
||||
if (!tradeOffer.IsSteamCardsOnlyTrade() || !tradeOffer.IsPotentiallyDupesTrade()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This STM trade SHOULD be fine
|
||||
// Potential TODO: Ensure that our inventory in fact has proper amount of both received and given cards
|
||||
// This way we could calculate amounts before and after trade, ensuring that we're in fact trading dupes and not 1 + 2 -> 0 + 3
|
||||
return true;
|
||||
// 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));
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,10 +141,8 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
content = WebUtility.HtmlDecode(content);
|
||||
HtmlDocument htmlDocument = new HtmlDocument();
|
||||
htmlDocument.LoadHtml(content);
|
||||
|
||||
htmlDocument.LoadHtml(WebUtility.HtmlDecode(content));
|
||||
return htmlDocument;
|
||||
}
|
||||
|
||||
@@ -213,7 +211,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 +237,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;
|
||||
}
|
||||
|
||||
|
||||
9
appveyor.yml
Normal file
9
appveyor.yml
Normal 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
|
||||
Reference in New Issue
Block a user