Compare commits

..

24 Commits

Author SHA1 Message Date
JustArchi
b8f03abd8b Fix fuckups 2016-04-20 23:48:33 +02:00
JustArchi
47f846540b Closes #200, thanks to @GUiHKX 2016-04-20 23:44:20 +02:00
JustArchi
bc14713079 Bump 2016-04-20 23:22:16 +02:00
JustArchi
770a8fee66 But keep trading only of cards + foils 2016-04-20 23:16:15 +02:00
JustArchi
1df9af08e6 Bugfixes + other types for STM 2016-04-20 23:02:02 +02:00
JustArchi
27464f6120 Add recent trades optimization 2016-04-20 22:19:31 +02:00
JustArchi
dfd45c6e25 Bump 2016-04-20 21:40:08 +02:00
JustArchi
adc1759cee Misc 2016-04-20 21:34:40 +02:00
JustArchi
88369ec71a Put massive amount of work into STM integration, #84 2016-04-20 21:27:57 +02:00
JustArchi
a5d8ae53dd Bump 2016-04-19 19:54:39 +02:00
JustArchi
cd7b65868a Misc 2016-04-19 12:24:07 +02:00
JustArchi
7575704a01 Misc 2016-04-19 12:23:24 +02:00
JustArchi
b6ce8f435c Use more optimized Slim manual reset events 2016-04-18 18:43:58 +02:00
JustArchi
565acca9fb Add AutoRestart property 2016-04-18 18:38:48 +02:00
JustArchi
610954ba73 Alter logic of key distribution, closes #199 2016-04-18 18:01:49 +02:00
JustArchi
74a748b03f Bump 2016-04-17 00:47:30 +02:00
JustArchi
585a075ec9 Closes #198
It was possible that we initiated a loop for bot that was connected, and it got disconnected shortly after, which could result in infinite loop if DistributeKeys was disabled (and nothing would change that bot to other one)
2016-04-17 00:36:38 +02:00
JustArchi
891d40afe1 Fix potential bug found by zinnerz
We might !stop account waiting in invalid password or game playing condition, which will then initiate connect without checking if it's still valid to do so
2016-04-16 19:24:04 +02:00
JustArchi
387f0dd1c7 Bump 2016-04-15 21:33:58 +02:00
JustArchi
8b4d3c219c Remove GUI app from final zip until I'm happy with the way how it works 2016-04-15 21:29:27 +02:00
JustArchi
365877ec89 Misc 2016-04-15 15:08:50 +02:00
JustArchi
f03a43d573 Misc 2016-04-15 00:32:55 +02:00
JustArchi
d15a9cbfca Misc 2016-04-15 00:18:24 +02:00
JustArchi
acfad624fb Bump 2016-04-14 22:44:35 +02:00
16 changed files with 559 additions and 166 deletions

View File

@@ -28,7 +28,6 @@ using SteamKit2;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
@@ -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;

View File

@@ -33,6 +33,7 @@ using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Text.RegularExpressions;
namespace ArchiSteamFarm {
internal sealed class Bot {
@@ -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) {
@@ -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!";
@@ -774,12 +777,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 +794,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 +1238,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 +1273,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 +1389,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,6 +1410,10 @@ 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

View File

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

View File

@@ -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;
@@ -421,7 +421,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 +452,7 @@ namespace ArchiSteamFarm {
bool success = true;
while (maxHour < 2) {
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false;
break;
}

View File

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

View File

@@ -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();
}
}
}

View File

@@ -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;
}
@@ -251,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) {
@@ -516,7 +522,7 @@ namespace ArchiSteamFarm {
Init(args);
// Wait for signal to shutdown
ShutdownResetEvent.WaitOne();
ShutdownResetEvent.Wait();
// We got a signal to shutdown
Exit();

View File

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

View File

@@ -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;
}
}
}

View File

@@ -64,20 +64,5 @@ namespace ArchiSteamFarm {
return Task.Delay(miliseconds);
}
internal static uint GetCharCountInString(string s, char c) {
if (string.IsNullOrEmpty(s)) {
return 0;
}
uint count = 0;
foreach (char singleChar in s) {
if (singleChar == c) {
count++;
}
}
return count;
}
}
}

View File

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

View File

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

View File

@@ -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;

View File

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

View File

@@ -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.

View File

@@ -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.