mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-28 04:00:46 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
770a8fee66 | ||
|
|
1df9af08e6 | ||
|
|
27464f6120 | ||
|
|
dfd45c6e25 | ||
|
|
adc1759cee | ||
|
|
88369ec71a | ||
|
|
a5d8ae53dd |
@@ -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;
|
||||
@@ -222,7 +221,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 +235,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 +249,128 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Steam.TradeOffer> result = new List<Steam.TradeOffer>();
|
||||
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>();
|
||||
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 (!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 (!typeMap.ContainsKey(key)) {
|
||||
string type = description["type"].Value;
|
||||
if (!string.IsNullOrEmpty(type)) {
|
||||
switch (type) {
|
||||
case "Booster Pack":
|
||||
typeMap[key] = Steam.Item.EType.BoosterPack;
|
||||
break;
|
||||
case "Coupon":
|
||||
typeMap[key] = Steam.Item.EType.Coupon;
|
||||
break;
|
||||
case "Gift":
|
||||
typeMap[key] = Steam.Item.EType.Gift;
|
||||
break;
|
||||
case "Steam Gems":
|
||||
typeMap[key] = Steam.Item.EType.SteamGems;
|
||||
break;
|
||||
default:
|
||||
if (type.EndsWith("Emoticon", StringComparison.Ordinal)) {
|
||||
typeMap[key] = Steam.Item.EType.Emoticon;
|
||||
} else if (type.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
|
||||
typeMap[key] = Steam.Item.EType.FoilTradingCard;
|
||||
} else if (type.EndsWith("Profile Background", StringComparison.Ordinal)) {
|
||||
typeMap[key] = Steam.Item.EType.ProfileBackground;
|
||||
} else if (type.EndsWith("Trading Card", StringComparison.Ordinal)) {
|
||||
typeMap[key] = Steam.Item.EType.TradingCard;
|
||||
} else {
|
||||
typeMap[key] = Steam.Item.EType.Unknown;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
uint realAppID;
|
||||
if (appIDMap.TryGetValue(key, out realAppID)) {
|
||||
steamItem.RealAppID = realAppID;
|
||||
}
|
||||
|
||||
Steam.Item.EType type;
|
||||
if (typeMap.TryGetValue(key, out type)) {
|
||||
steamItem.Type = type;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
uint realAppID;
|
||||
if (appIDMap.TryGetValue(key, out realAppID)) {
|
||||
steamItem.RealAppID = realAppID;
|
||||
}
|
||||
|
||||
Steam.Item.EType type;
|
||||
if (typeMap.TryGetValue(key, out type)) {
|
||||
steamItem.Type = type;
|
||||
}
|
||||
|
||||
tradeOffer.ItemsToReceive.Add(steamItem);
|
||||
}
|
||||
|
||||
result.Add(tradeOffer);
|
||||
}
|
||||
|
||||
@@ -300,8 +395,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 +431,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,14 +449,14 @@ 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);
|
||||
jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (jObject == null) {
|
||||
@@ -375,7 +470,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Steam.Item> result = new List<Steam.Item>();
|
||||
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
||||
foreach (JToken jToken in jTokens) {
|
||||
try {
|
||||
result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString()));
|
||||
@@ -387,7 +482,7 @@ namespace ArchiSteamFarm {
|
||||
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 +497,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 +528,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;
|
||||
|
||||
@@ -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,7 +591,7 @@ 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!";
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.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;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.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;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.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;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.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,152 @@ 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 {
|
||||
get {
|
||||
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 {
|
||||
get {
|
||||
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 int 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.4")]
|
||||
[assembly: AssemblyFileVersion("2.0.3.4")]
|
||||
[assembly: AssemblyVersion("2.0.3.6")]
|
||||
[assembly: AssemblyFileVersion("2.0.3.6")]
|
||||
|
||||
@@ -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,31 +80,50 @@ 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong tradeID;
|
||||
if (!ulong.TryParse(tradeOffer.tradeofferid, out tradeID)) {
|
||||
if (tradeOffer == null || tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldAcceptTrade(tradeOffer)) {
|
||||
Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName);
|
||||
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +133,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// 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,25 @@ 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user