mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-19 15:58:39 +00:00
Compare commits
45 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 | ||
|
|
adc1759cee | ||
|
|
88369ec71a | ||
|
|
a5d8ae53dd | ||
|
|
cd7b65868a | ||
|
|
7575704a01 | ||
|
|
b6ce8f435c | ||
|
|
565acca9fb | ||
|
|
610954ba73 | ||
|
|
74a748b03f | ||
|
|
585a075ec9 | ||
|
|
891d40afe1 | ||
|
|
387f0dd1c7 | ||
|
|
8b4d3c219c | ||
|
|
365877ec89 | ||
|
|
f03a43d573 | ||
|
|
d15a9cbfca | ||
|
|
acfad624fb | ||
|
|
03a7d5f4ac | ||
|
|
0f96d84d36 | ||
|
|
0edc9f4ff6 |
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -54,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");
|
||||
@@ -133,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) {
|
||||
@@ -148,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;
|
||||
}
|
||||
@@ -166,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);
|
||||
@@ -222,7 +267,7 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal List<Steam.TradeOffer> GetTradeOffers() {
|
||||
internal HashSet<Steam.TradeOffer> GetTradeOffers() {
|
||||
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
return null;
|
||||
}
|
||||
@@ -236,6 +281,7 @@ namespace ArchiSteamFarm {
|
||||
response = iEconService.GetTradeOffers(
|
||||
get_received_offers: 1,
|
||||
active_only: 1,
|
||||
get_descriptions: 1,
|
||||
secure: !Program.GlobalConfig.ForceHttp
|
||||
);
|
||||
} catch (Exception e) {
|
||||
@@ -249,33 +295,87 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Steam.TradeOffer> result = new List<Steam.TradeOffer>();
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong instanceID = description["instanceid"].AsUnsignedLong();
|
||||
|
||||
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"].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>();
|
||||
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 (descriptionMap.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 (descriptionMap.TryGetValue(key, out description)) {
|
||||
steamItem.RealAppID = description.Item1;
|
||||
steamItem.Type = description.Item2;
|
||||
}
|
||||
|
||||
tradeOffer.ItemsToReceive.Add(steamItem);
|
||||
}
|
||||
|
||||
result.Add(tradeOffer);
|
||||
}
|
||||
|
||||
@@ -300,8 +400,8 @@ namespace ArchiSteamFarm {
|
||||
string request = SteamCommunityURL + "/gid/" + clanID;
|
||||
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
||||
{"sessionID", sessionID},
|
||||
{"action", "join"}
|
||||
{ "sessionID", sessionID },
|
||||
{ "action", "join" }
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
@@ -336,9 +436,9 @@ namespace ArchiSteamFarm {
|
||||
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 +454,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,26 +582,30 @@ 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";
|
||||
@@ -429,12 +613,12 @@ namespace ArchiSteamFarm {
|
||||
|
||||
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 +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) {
|
||||
@@ -482,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) {
|
||||
@@ -500,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;
|
||||
}
|
||||
@@ -588,12 +591,18 @@ 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);
|
||||
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!";
|
||||
@@ -774,12 +783,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 +800,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:
|
||||
@@ -1226,14 +1244,27 @@ namespace ArchiSteamFarm {
|
||||
|
||||
string sms = Program.GetUserInput(Program.EUserInputType.SMS, BotName);
|
||||
if (string.IsNullOrEmpty(sms)) {
|
||||
Logging.LogGenericWarning("Aborted!", BotName);
|
||||
DelinkMobileAuthenticator();
|
||||
return;
|
||||
}
|
||||
|
||||
AuthenticatorLinker.FinalizeResult finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(sms);
|
||||
if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) {
|
||||
Logging.LogGenericError("Unhandled situation: " + finalizeResult, BotName);
|
||||
DelinkMobileAuthenticator();
|
||||
return;
|
||||
AuthenticatorLinker.FinalizeResult finalizeResult;
|
||||
while ((finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(sms)) != AuthenticatorLinker.FinalizeResult.Success) {
|
||||
switch (finalizeResult) {
|
||||
case AuthenticatorLinker.FinalizeResult.BadSMSCode:
|
||||
sms = Program.GetUserInput(Program.EUserInputType.SMS, BotName);
|
||||
if (string.IsNullOrEmpty(sms)) {
|
||||
Logging.LogGenericWarning("Aborted!", BotName);
|
||||
DelinkMobileAuthenticator();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logging.LogGenericError("Unhandled situation: " + finalizeResult, BotName);
|
||||
DelinkMobileAuthenticator();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that we also save changes made by finalization step (if any)
|
||||
@@ -1248,13 +1279,13 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = BotDatabase.SteamGuardAccount.DeactivateAuthenticator();
|
||||
|
||||
if (result) {
|
||||
// Try to deactivate authenticator, and assume we're safe to remove if it wasn't fully enrolled yet (even if request fails)
|
||||
if (BotDatabase.SteamGuardAccount.DeactivateAuthenticator() || !BotDatabase.SteamGuardAccount.FullyEnrolled) {
|
||||
BotDatabase.SteamGuardAccount = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void JoinMasterChat() {
|
||||
@@ -1364,10 +1395,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
|
||||
@@ -1389,10 +1416,14 @@ 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) {
|
||||
// 2FA tokens are expiring soon, use limiter only when user is providing one
|
||||
if (TwoFactorCode == null || BotDatabase.SteamGuardAccount != null) {
|
||||
await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -1714,7 +1745,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) {
|
||||
if (callback == null) {
|
||||
if (callback == null || callback.OfflineMessages == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ 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 ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
|
||||
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer Timer;
|
||||
@@ -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() {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
[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;
|
||||
|
||||
[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;
|
||||
|
||||
[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;
|
||||
|
||||
[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;
|
||||
|
||||
[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;
|
||||
|
||||
[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,148 @@ namespace ArchiSteamFarm {
|
||||
OnHold
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal string tradeofferid { get; set; }
|
||||
internal ulong TradeOfferID;
|
||||
|
||||
[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 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.FoilTradingCard && item.Type != Item.EType.TradingCard)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,12 @@ 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();
|
||||
@@ -187,7 +186,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
GitHub.Asset binaryAsset = null;
|
||||
foreach (var asset in releaseResponse.Assets) {
|
||||
if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName)) {
|
||||
if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -205,21 +204,22 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Downloading new version...");
|
||||
Stream newExe = await WebBrowser.UrlGetToStream(binaryAsset.DownloadURL).ConfigureAwait(false);
|
||||
if (newExe == null) {
|
||||
Logging.LogGenericWarning("Could not download new version!");
|
||||
byte[] result = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && result == null; i++) {
|
||||
Logging.LogGenericInfo("Downloading new version...");
|
||||
result = await WebBrowser.UrlGetToBytes(binaryAsset.DownloadURL).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
return;
|
||||
}
|
||||
|
||||
// We start deep update logic here
|
||||
string newExeFile = ExecutableFile + ".new";
|
||||
|
||||
// Firstly we create new exec
|
||||
try {
|
||||
using (FileStream fileStream = File.Open(newExeFile, FileMode.Create)) {
|
||||
await newExe.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
}
|
||||
File.WriteAllBytes(newExeFile, result);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
@@ -250,10 +250,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) {
|
||||
@@ -515,7 +522,7 @@ namespace ArchiSteamFarm {
|
||||
Init(args);
|
||||
|
||||
// Wait for signal to shutdown
|
||||
ShutdownResetEvent.WaitOne();
|
||||
ShutdownResetEvent.Wait();
|
||||
|
||||
// We got a signal to shutdown
|
||||
Exit();
|
||||
|
||||
@@ -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.0")]
|
||||
[assembly: AssemblyFileVersion("2.0.3.0")]
|
||||
[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,43 +80,62 @@ 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() {
|
||||
List<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
|
||||
if (tradeOffers == null) {
|
||||
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);
|
||||
}
|
||||
|
||||
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 +143,84 @@ 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;
|
||||
// Rule 1 - We always trade the same amount of items
|
||||
if (tradeOffer.ItemsToGive.Count != tradeOffer.ItemsToReceive.Count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rule 2 - We always trade steam cards and only for the same set
|
||||
if (!tradeOffer.IsSteamCardsOnlyTrade() || !tradeOffer.IsPotentiallyDupesTrade()) {
|
||||
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));
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,13 +88,9 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpResponseMessage response = await UrlGetToResponse(request, referer).ConfigureAwait(false);
|
||||
if (response == null) {
|
||||
return false;
|
||||
using (HttpResponseMessage response = await UrlGetToResponse(request, referer).ConfigureAwait(false)) {
|
||||
return response != null;
|
||||
}
|
||||
|
||||
response.Dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) {
|
||||
@@ -102,29 +98,9 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false);
|
||||
if (response == null) {
|
||||
return false;
|
||||
using (HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false)) {
|
||||
return response != null;
|
||||
}
|
||||
|
||||
response.Dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Get, null, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task<HttpResponseMessage> UrlPostToResponse(string request, Dictionary<string, string> data = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Post, data, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task<string> UrlGetToContent(string request, string referer = null) {
|
||||
@@ -132,31 +108,27 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResponseMessage httpResponse = await UrlGetToResponse(request, referer).ConfigureAwait(false);
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
using (HttpResponseMessage httpResponse = await UrlGetToResponse(request, referer).ConfigureAwait(false)) {
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string result = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
httpResponse.Dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<Stream> UrlGetToStream(string request, string referer = null) {
|
||||
internal async Task<byte[]> UrlGetToBytes(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResponseMessage httpResponse = await UrlGetToResponse(request, referer).ConfigureAwait(false);
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
using (HttpResponseMessage httpResponse = await UrlGetToResponse(request, referer).ConfigureAwait(false)) {
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await httpResponse.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Stream result = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
httpResponse.Dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<HtmlDocument> UrlGetToHtmlDocument(string request, string referer = null) {
|
||||
@@ -169,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;
|
||||
}
|
||||
|
||||
@@ -220,12 +190,28 @@ namespace ArchiSteamFarm {
|
||||
return xmlDocument;
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Get, 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;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Post, data, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request) || httpMethod == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (request.StartsWith("https://") && Program.GlobalConfig.ForceHttp) {
|
||||
if (request.StartsWith("https://", StringComparison.Ordinal) && Program.GlobalConfig.ForceHttp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -251,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"Debug": false,
|
||||
"Headless": false,
|
||||
"AutoUpdates": true,
|
||||
"AutoRestart": true,
|
||||
"UpdateChannel": 1,
|
||||
"SteamProtocol": 6,
|
||||
"SteamOwnerID": 0,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"FarmOffline": false,
|
||||
"HandleOfflineMessages": false,
|
||||
"AcceptGifts": false,
|
||||
"SteamTradeMatcher": false,
|
||||
"ForwardKeysToOtherBots": false,
|
||||
"DistributeKeys": false,
|
||||
"UseAsfAsMobileAuthenticator": false,
|
||||
|
||||
@@ -68,6 +68,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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
|
||||
"$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
|
||||
del "$(SolutionDir)out\ASF-GUI.exe.config"
|
||||
@@ -143,6 +144,7 @@
|
||||
mono -O=all "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
|
||||
rm "$(SolutionDir)out/ASF-GUI.exe.config"
|
||||
</PostBuildEvent>
|
||||
-->
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
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