mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-23 09:48:37 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5f7e7988c | ||
|
|
80ed0e66bb | ||
|
|
5529a8e1f0 | ||
|
|
496bea5ac5 | ||
|
|
52f3a86255 | ||
|
|
7d205cfa42 | ||
|
|
c4f47c56da | ||
|
|
546440d9dc | ||
|
|
ffa6548594 | ||
|
|
53d59ce2a9 | ||
|
|
b966db5845 | ||
|
|
aae41d5c1f | ||
|
|
8ace0d7782 |
17
ArchiSteamFarm.sln.DotSettings
Normal file
17
ArchiSteamFarm.sln.DotSettings
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ASF/@EntryIndexedValue">ASF</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FA/@EntryIndexedValue">FA</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FS/@EntryIndexedValue">FS</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OK/@EntryIndexedValue">OK</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PIN/@EntryIndexedValue">PIN</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SC/@EntryIndexedValue">SC</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SMS/@EntryIndexedValue">SMS</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TTL/@EntryIndexedValue">TTL</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=URL/@EntryIndexedValue">URL</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WCF/@EntryIndexedValue">WCF</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WTF/@EntryIndexedValue">WTF</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XML/@EntryIndexedValue">XML</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="AaBb" /><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String></wpf:ResourceDictionary>
|
||||||
@@ -85,9 +85,7 @@ namespace ArchiSteamFarm {
|
|||||||
JobID = jobID;
|
JobID = jobID;
|
||||||
|
|
||||||
if (msg.count_new_items > 0) {
|
if (msg.count_new_items > 0) {
|
||||||
Notifications = new HashSet<ENotification> {
|
Notifications = new HashSet<ENotification> { ENotification.Items };
|
||||||
ENotification.Items
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries");
|
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,16 +393,14 @@ namespace ArchiSteamFarm {
|
|||||||
State = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
|
State = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (KeyValue item in trade["items_to_give"].Children) {
|
foreach (Steam.Item steamItem in trade["items_to_give"].Children.Select(item => new Steam.Item {
|
||||||
Steam.Item steamItem = new Steam.Item {
|
AppID = (uint) item["appid"].AsUnsignedLong(),
|
||||||
AppID = (uint) item["appid"].AsUnsignedLong(),
|
ContextID = item["contextid"].AsUnsignedLong(),
|
||||||
ContextID = item["contextid"].AsUnsignedLong(),
|
AssetID = item["assetid"].AsUnsignedLong(),
|
||||||
AssetID = item["assetid"].AsUnsignedLong(),
|
ClassID = item["classid"].AsUnsignedLong(),
|
||||||
ClassID = item["classid"].AsUnsignedLong(),
|
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
})) {
|
||||||
};
|
|
||||||
|
|
||||||
Tuple<uint, Steam.Item.EType> description;
|
Tuple<uint, Steam.Item.EType> description;
|
||||||
if (descriptions.TryGetValue(steamItem.ClassID, out description)) {
|
if (descriptions.TryGetValue(steamItem.ClassID, out description)) {
|
||||||
steamItem.RealAppID = description.Item1;
|
steamItem.RealAppID = description.Item1;
|
||||||
@@ -412,16 +410,14 @@ namespace ArchiSteamFarm {
|
|||||||
tradeOffer.ItemsToGive.Add(steamItem);
|
tradeOffer.ItemsToGive.Add(steamItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyValue item in trade["items_to_receive"].Children) {
|
foreach (Steam.Item steamItem in trade["items_to_receive"].Children.Select(item => new Steam.Item {
|
||||||
Steam.Item steamItem = new Steam.Item {
|
AppID = (uint) item["appid"].AsUnsignedLong(),
|
||||||
AppID = (uint) item["appid"].AsUnsignedLong(),
|
ContextID = item["contextid"].AsUnsignedLong(),
|
||||||
ContextID = item["contextid"].AsUnsignedLong(),
|
AssetID = item["assetid"].AsUnsignedLong(),
|
||||||
AssetID = item["assetid"].AsUnsignedLong(),
|
ClassID = item["classid"].AsUnsignedLong(),
|
||||||
ClassID = item["classid"].AsUnsignedLong(),
|
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
})) {
|
||||||
};
|
|
||||||
|
|
||||||
Tuple<uint, Steam.Item.EType> description;
|
Tuple<uint, Steam.Item.EType> description;
|
||||||
if (descriptions.TryGetValue(steamItem.ClassID, out description)) {
|
if (descriptions.TryGetValue(steamItem.ClassID, out description)) {
|
||||||
steamItem.RealAppID = description.Item1;
|
steamItem.RealAppID = description.Item1;
|
||||||
@@ -464,16 +460,49 @@ namespace ArchiSteamFarm {
|
|||||||
return await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
|
return await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<HashSet<Steam.Item>> GetMyTradableInventory() {
|
internal bool DeclineTradeOffer(ulong tradeID) {
|
||||||
|
if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||||
|
// TODO: Correct this when Mono 4.4+ will be a latest stable one | https://bugzilla.xamarin.com/show_bug.cgi?id=39455
|
||||||
|
Logging.LogNullError("tradeID || SteamApiKey", Bot.BotName);
|
||||||
|
//Logging.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValue response = null;
|
||||||
|
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
||||||
|
iEconService.Timeout = Timeout;
|
||||||
|
|
||||||
|
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||||
|
try {
|
||||||
|
response = iEconService.DeclineTradeOffer(
|
||||||
|
tradeofferid: tradeID.ToString(),
|
||||||
|
method: WebRequestMethods.Http.Post,
|
||||||
|
secure: !Program.GlobalConfig.ForceHttp
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logging.LogGenericException(e, Bot.BotName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<HashSet<Steam.Item>> GetMyInventory(bool tradable) {
|
||||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
||||||
|
|
||||||
ushort nextPage = 0;
|
uint currentPage = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1&start=" + nextPage;
|
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=" + (tradable ? "1" : "0") + "&start=" + currentPage;
|
||||||
|
|
||||||
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request).ConfigureAwait(false);
|
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request).ConfigureAwait(false);
|
||||||
if (jObject == null) {
|
if (jObject == null) {
|
||||||
@@ -504,13 +533,14 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint appID = 0;
|
uint appID = 0;
|
||||||
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
|
||||||
|
|
||||||
string hashName = description["market_hash_name"].ToString();
|
string hashName = description["market_hash_name"].ToString();
|
||||||
if (!string.IsNullOrEmpty(hashName)) {
|
if (!string.IsNullOrEmpty(hashName)) {
|
||||||
appID = GetAppIDFromMarketHashName(hashName);
|
appID = GetAppIDFromMarketHashName(hashName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
||||||
|
|
||||||
string descriptionType = description["type"].ToString();
|
string descriptionType = description["type"].ToString();
|
||||||
if (!string.IsNullOrEmpty(descriptionType)) {
|
if (!string.IsNullOrEmpty(descriptionType)) {
|
||||||
type = GetItemType(descriptionType);
|
type = GetItemType(descriptionType);
|
||||||
@@ -526,11 +556,10 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (JToken item in items) {
|
foreach (JToken item in items) {
|
||||||
|
|
||||||
Steam.Item steamItem;
|
Steam.Item steamItem;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
steamItem = JsonConvert.DeserializeObject<Steam.Item>(item.ToString());
|
steamItem = item.ToObject<Steam.Item>();
|
||||||
} catch (JsonException e) {
|
} catch (JsonException e) {
|
||||||
Logging.LogGenericException(e, Bot.BotName);
|
Logging.LogGenericException(e, Bot.BotName);
|
||||||
continue;
|
continue;
|
||||||
@@ -555,12 +584,17 @@ namespace ArchiSteamFarm {
|
|||||||
break; // OK, last page
|
break; // OK, last page
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ushort.TryParse(jObject["more_start"].ToString(), out nextPage)) {
|
uint nextPage;
|
||||||
continue;
|
if (!uint.TryParse(jObject["more_start"].ToString(), out nextPage)) {
|
||||||
|
Logging.LogNullError(nameof(nextPage), Bot.BotName);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.LogNullError(nameof(nextPage), Bot.BotName);
|
if (nextPage <= currentPage) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage = nextPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -315,7 +315,11 @@ namespace ArchiSteamFarm {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void OnFarmingStopped() => ResetGamesPlayed();
|
||||||
|
|
||||||
internal async Task OnFarmingFinished(bool farmedSomething) {
|
internal async Task OnFarmingFinished(bool farmedSomething) {
|
||||||
|
OnFarmingStopped();
|
||||||
|
|
||||||
if ((farmedSomething || !FirstTradeSent) && BotConfig.SendOnFarmingFinished) {
|
if ((farmedSomething || !FirstTradeSent) && BotConfig.SendOnFarmingFinished) {
|
||||||
FirstTradeSent = true;
|
FirstTradeSent = true;
|
||||||
await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false);
|
await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false);
|
||||||
@@ -326,11 +330,8 @@ namespace ArchiSteamFarm {
|
|||||||
SkipFirstShutdown = false;
|
SkipFirstShutdown = false;
|
||||||
} else {
|
} else {
|
||||||
Stop();
|
Stop();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResetGamesPlayed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<string> Response(ulong steamID, string message) {
|
internal async Task<string> Response(ulong steamID, string message) {
|
||||||
@@ -375,6 +376,8 @@ namespace ArchiSteamFarm {
|
|||||||
return ResponseStop(steamID);
|
return ResponseStop(steamID);
|
||||||
case "!update":
|
case "!update":
|
||||||
return await ResponseUpdate(steamID).ConfigureAwait(false);
|
return await ResponseUpdate(steamID).ConfigureAwait(false);
|
||||||
|
case "!version":
|
||||||
|
return ResponseVersion(steamID);
|
||||||
default:
|
default:
|
||||||
return ResponseUnknown(steamID);
|
return ResponseUnknown(steamID);
|
||||||
}
|
}
|
||||||
@@ -655,7 +658,7 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
|
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
|
||||||
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
|
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMyInventory(true).ConfigureAwait(false);
|
||||||
|
|
||||||
if ((inventory == null) || (inventory.Count == 0)) {
|
if ((inventory == null) || (inventory.Count == 0)) {
|
||||||
return "Nothing to send, inventory seems empty!";
|
return "Nothing to send, inventory seems empty!";
|
||||||
@@ -835,7 +838,11 @@ namespace ArchiSteamFarm {
|
|||||||
return "This bot instance is not connected!";
|
return "This bot instance is not connected!";
|
||||||
}
|
}
|
||||||
|
|
||||||
CardsFarmer.RestartFarming().Forget();
|
if (CardsFarmer.CurrentGamesFarming.Count > 0) {
|
||||||
|
return "This bot instance is farming already!";
|
||||||
|
}
|
||||||
|
|
||||||
|
CardsFarmer.StartFarming().Forget();
|
||||||
return "Done!";
|
return "Done!";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1305,6 +1312,19 @@ namespace ArchiSteamFarm {
|
|||||||
return "Done!";
|
return "Done!";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ResponseVersion(ulong steamID) {
|
||||||
|
if (steamID == 0) {
|
||||||
|
Logging.LogNullError(nameof(steamID));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsMaster(steamID)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "ASF V" + Program.Version;
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleCallbacks() {
|
private void HandleCallbacks() {
|
||||||
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
|
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
|
||||||
while (KeepRunning || SteamClient.IsConnected) {
|
while (KeepRunning || SteamClient.IsConnected) {
|
||||||
@@ -1646,7 +1666,8 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (acceptedSomething) {
|
if (acceptedSomething) {
|
||||||
CardsFarmer.RestartFarming().Forget();
|
// Start farming, but only if we're not farming already
|
||||||
|
CardsFarmer.StartFarming().Forget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1704,11 +1725,12 @@ namespace ArchiSteamFarm {
|
|||||||
// TODO: Accept clan invites from master?
|
// TODO: Accept clan invites from master?
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!IsMaster(friend.SteamID)) {
|
if (IsMaster(friend.SteamID)) {
|
||||||
break;
|
SteamFriends.AddFriend(friend.SteamID);
|
||||||
|
} else if (BotConfig.IsBotAccount) {
|
||||||
|
SteamFriends.RemoveFriend(friend.SteamID);
|
||||||
}
|
}
|
||||||
|
|
||||||
SteamFriends.AddFriend(friend.SteamID);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1945,26 +1967,19 @@ namespace ArchiSteamFarm {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkTrades = false;
|
|
||||||
bool markInventory = false;
|
|
||||||
foreach (ArchiHandler.NotificationsCallback.ENotification notification in callback.Notifications) {
|
foreach (ArchiHandler.NotificationsCallback.ENotification notification in callback.Notifications) {
|
||||||
switch (notification) {
|
switch (notification) {
|
||||||
case ArchiHandler.NotificationsCallback.ENotification.Items:
|
case ArchiHandler.NotificationsCallback.ENotification.Items:
|
||||||
markInventory = true;
|
CardsFarmer.OnNewItemsNotification();
|
||||||
|
if (BotConfig.DismissInventoryNotifications) {
|
||||||
|
await ArchiWebHandler.MarkInventory().ConfigureAwait(false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ArchiHandler.NotificationsCallback.ENotification.Trading:
|
case ArchiHandler.NotificationsCallback.ENotification.Trading:
|
||||||
checkTrades = true;
|
await Trading.CheckTrades().ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkTrades) {
|
|
||||||
Trading.CheckTrades().Forget();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (markInventory && BotConfig.DismissInventoryNotifications) {
|
|
||||||
await ArchiWebHandler.MarkInventory().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) {
|
private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) {
|
||||||
@@ -2007,8 +2022,8 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (callback.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
|
if (callback.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
|
||||||
// We will restart CF module to recalculate current status and decide about new optimal approach
|
// Start farming, but only if we're not farming already
|
||||||
CardsFarmer.RestartFarming().Forget();
|
CardsFarmer.StartFarming().Forget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,12 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace ArchiSteamFarm {
|
namespace ArchiSteamFarm {
|
||||||
// ReSharper disable once ClassCannotBeInstantiated
|
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||||
// ReSharper disable once ClassNeverInstantiated.Global
|
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||||
internal sealed class BotConfig {
|
internal sealed class BotConfig {
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonProperty(Required = Required.DisallowNull)]
|
||||||
internal bool Enabled { get; private set; } = false;
|
internal bool Enabled { get; private set; } = false;
|
||||||
@@ -70,6 +71,9 @@ namespace ArchiSteamFarm {
|
|||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonProperty(Required = Required.DisallowNull)]
|
||||||
internal bool AcceptGifts { get; private set; } = false;
|
internal bool AcceptGifts { get; private set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty(Required = Required.DisallowNull)]
|
||||||
|
internal bool IsBotAccount { get; private set; } = false;
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonProperty(Required = Required.DisallowNull)]
|
||||||
internal bool SteamTradeMatcher { get; private set; } = false;
|
internal bool SteamTradeMatcher { get; private set; } = false;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace ArchiSteamFarm {
|
|||||||
|
|
||||||
internal bool ManualMode { get; private set; }
|
internal bool ManualMode { get; private set; }
|
||||||
|
|
||||||
private bool NowFarming;
|
private bool KeepFarming, NowFarming;
|
||||||
|
|
||||||
internal CardsFarmer(Bot bot) {
|
internal CardsFarmer(Bot bot) {
|
||||||
if (bot == null) {
|
if (bot == null) {
|
||||||
@@ -107,7 +107,7 @@ namespace ArchiSteamFarm {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NowFarming = true;
|
KeepFarming = NowFarming = true;
|
||||||
FarmingSemaphore.Release(); // From this point we allow other calls to shut us down
|
FarmingSemaphore.Release(); // From this point we allow other calls to shut us down
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@@ -170,10 +170,11 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logging.LogGenericInfo("Sending signal to stop farming", Bot.BotName);
|
Logging.LogGenericInfo("Sending signal to stop farming", Bot.BotName);
|
||||||
|
KeepFarming = false;
|
||||||
FarmResetEvent.Set();
|
FarmResetEvent.Set();
|
||||||
|
|
||||||
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
|
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
|
||||||
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && NowFarming; i++) {
|
for (byte i = 0; (i < 5) && NowFarming; i++) {
|
||||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,14 +182,17 @@ namespace ArchiSteamFarm {
|
|||||||
Logging.LogGenericWarning("Timed out!", Bot.BotName);
|
Logging.LogGenericWarning("Timed out!", Bot.BotName);
|
||||||
}
|
}
|
||||||
|
|
||||||
FarmResetEvent.Reset();
|
|
||||||
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
|
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
|
||||||
|
Bot.OnFarmingStopped();
|
||||||
FarmingSemaphore.Release();
|
FarmingSemaphore.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task RestartFarming() {
|
internal void OnNewItemsNotification() {
|
||||||
await StopFarming().ConfigureAwait(false);
|
if (!NowFarming) {
|
||||||
await StartFarming().ConfigureAwait(false);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FarmResetEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
|
private static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
|
||||||
@@ -217,22 +221,25 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte maxPages = 1;
|
byte maxPages = 1;
|
||||||
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
|
|
||||||
if ((htmlNodeCollection != null) && (htmlNodeCollection.Count > 0)) {
|
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("(//a[@class='pagelink'])[last()]");
|
||||||
HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1];
|
if (htmlNode != null) {
|
||||||
string lastPage = htmlNode.InnerText;
|
string lastPage = htmlNode.InnerText;
|
||||||
if (!string.IsNullOrEmpty(lastPage)) {
|
if (string.IsNullOrEmpty(lastPage)) {
|
||||||
if (!byte.TryParse(lastPage, out maxPages)) {
|
Logging.LogNullError(nameof(lastPage), Bot.BotName);
|
||||||
maxPages = 1; // Should never happen
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) {
|
||||||
|
Logging.LogNullError(nameof(maxPages), Bot.BotName);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GamesToFarm.Clear();
|
GamesToFarm.Clear();
|
||||||
|
|
||||||
CheckPage(htmlDocument);
|
CheckPage(htmlDocument);
|
||||||
|
|
||||||
if (maxPages <= 1) {
|
if (maxPages == 1) {
|
||||||
return GamesToFarm.Count > 0;
|
return GamesToFarm.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,8 +262,7 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_title_stats']");
|
HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_title_stats']");
|
||||||
if (htmlNodes == null) {
|
if (htmlNodes == null) { // For example a page full of non-games badges
|
||||||
Logging.LogNullError(nameof(htmlNodes), Bot.BotName);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,22 +429,28 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bot.ArchiHandler.PlayGames(appID);
|
Bot.ArchiHandler.PlayGames(appID);
|
||||||
|
DateTime endFarmingDate = DateTime.Now.AddHours(Program.GlobalConfig.MaxFarmingTime);
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||||
for (ushort farmingTime = 0; (farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime) && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) {
|
|
||||||
|
while (keepFarming.GetValueOrDefault(true) && (DateTime.Now < endFarmingDate)) {
|
||||||
|
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
|
||||||
|
|
||||||
|
DateTime startFarmingPeriod = DateTime.Now;
|
||||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||||
success = false;
|
FarmResetEvent.Reset();
|
||||||
break;
|
success = KeepFarming;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't forget to update our GamesToFarm hours
|
// Don't forget to update our GamesToFarm hours
|
||||||
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
|
GamesToFarm[appID] += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||||
GamesToFarm[appID] += timePlayed;
|
|
||||||
|
if (!success) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||||
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.LogGenericInfo("Stopped farming: " + appID, Bot.BotName);
|
Logging.LogGenericInfo("Stopped farming: " + appID, Bot.BotName);
|
||||||
@@ -459,19 +471,25 @@ namespace ArchiSteamFarm {
|
|||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
while (maxHour < 2) {
|
while (maxHour < 2) {
|
||||||
|
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||||
|
|
||||||
|
DateTime startFarmingPeriod = DateTime.Now;
|
||||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||||
success = false;
|
FarmResetEvent.Reset();
|
||||||
break;
|
success = KeepFarming;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't forget to update our GamesToFarm hours
|
// Don't forget to update our GamesToFarm hours
|
||||||
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
|
float timePlayed = (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||||
foreach (uint appID in appIDs) {
|
foreach (uint appID in appIDs) {
|
||||||
GamesToFarm[appID] += timePlayed;
|
GamesToFarm[appID] += timePlayed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
maxHour += timePlayed;
|
maxHour += timePlayed;
|
||||||
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", appIDs), Bot.BotName);
|
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ using System.IO;
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
namespace ArchiSteamFarm {
|
namespace ArchiSteamFarm {
|
||||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated"), SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||||
|
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||||
internal sealed class GlobalConfig {
|
internal sealed class GlobalConfig {
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
internal enum EUpdateChannel : byte {
|
internal enum EUpdateChannel : byte {
|
||||||
@@ -42,7 +43,7 @@ namespace ArchiSteamFarm {
|
|||||||
internal const byte DefaultHttpTimeout = 60;
|
internal const byte DefaultHttpTimeout = 60;
|
||||||
|
|
||||||
private const byte DefaultMaxFarmingTime = 10;
|
private const byte DefaultMaxFarmingTime = 10;
|
||||||
private const byte DefaultFarmingDelay = 5;
|
private const byte DefaultFarmingDelay = 15;
|
||||||
private const ushort DefaultWCFPort = 1242;
|
private const ushort DefaultWCFPort = 1242;
|
||||||
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
|
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
|
||||||
|
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ namespace ArchiSteamFarm {
|
|||||||
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
|
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
|
||||||
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
|
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
|
||||||
|
|
||||||
Logging.LogGenericInfo("Archi's Steam Farm, version " + Version);
|
Logging.LogGenericInfo("ASF V" + Version);
|
||||||
Directory.SetCurrentDirectory(ExecutableDirectory);
|
Directory.SetCurrentDirectory(ExecutableDirectory);
|
||||||
InitServices();
|
InitServices();
|
||||||
|
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("2.0.5.1")]
|
[assembly: AssemblyVersion("2.0.5.3")]
|
||||||
[assembly: AssemblyFileVersion("2.0.5.1")]
|
[assembly: AssemblyFileVersion("2.0.5.3")]
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ namespace ArchiSteamFarm {
|
|||||||
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
|
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
|
||||||
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||||
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||||
|
} else if (Bot.BotConfig.IsBotAccount) {
|
||||||
|
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||||
|
Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID);
|
||||||
} else {
|
} else {
|
||||||
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||||
}
|
}
|
||||||
@@ -150,7 +153,7 @@ namespace ArchiSteamFarm {
|
|||||||
|
|
||||||
// At this point we're sure that STM trade is valid
|
// At this point we're sure that STM trade is valid
|
||||||
// Now check if it's worth for us to do the trade
|
// Now check if it's worth for us to do the trade
|
||||||
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
|
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyInventory(false).ConfigureAwait(false);
|
||||||
if ((inventory == null) || (inventory.Count == 0)) {
|
if ((inventory == null) || (inventory.Count == 0)) {
|
||||||
return true; // OK, assume that this trade is valid, we can't check our EQ
|
return true; // OK, assume that this trade is valid, we can't check our EQ
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"SteamOwnerID": 0,
|
"SteamOwnerID": 0,
|
||||||
"MaxFarmingTime": 10,
|
"MaxFarmingTime": 10,
|
||||||
"IdleFarmingPeriod": 3,
|
"IdleFarmingPeriod": 3,
|
||||||
"FarmingDelay": 5,
|
"FarmingDelay": 15,
|
||||||
"LoginLimiterDelay": 7,
|
"LoginLimiterDelay": 7,
|
||||||
"InventoryLimiterDelay": 3,
|
"InventoryLimiterDelay": 3,
|
||||||
"ForceHttp": false,
|
"ForceHttp": false,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"FarmOffline": false,
|
"FarmOffline": false,
|
||||||
"HandleOfflineMessages": false,
|
"HandleOfflineMessages": false,
|
||||||
"AcceptGifts": false,
|
"AcceptGifts": false,
|
||||||
|
"IsBotAccount": false,
|
||||||
"SteamTradeMatcher": false,
|
"SteamTradeMatcher": false,
|
||||||
"ForwardKeysToOtherBots": false,
|
"ForwardKeysToOtherBots": false,
|
||||||
"DistributeKeys": false,
|
"DistributeKeys": false,
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace ConfigGenerator {
|
namespace ConfigGenerator {
|
||||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global"), SuppressMessage("ReSharper", "CollectionNeverQueried.Global"), SuppressMessage("ReSharper", "MemberCanBePrivate.Global"), SuppressMessage("ReSharper", "UnusedMember.Global")]
|
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
|
||||||
|
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")]
|
||||||
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||||
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
internal sealed class BotConfig : ASFConfig {
|
internal sealed class BotConfig : ASFConfig {
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonProperty(Required = Required.DisallowNull)]
|
||||||
public bool Enabled { get; set; } = false;
|
public bool Enabled { get; set; } = false;
|
||||||
@@ -41,7 +44,8 @@ namespace ConfigGenerator {
|
|||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string SteamLogin { get; set; } = null;
|
public string SteamLogin { get; set; } = null;
|
||||||
|
|
||||||
[JsonProperty, PasswordPropertyText(true)]
|
[JsonProperty]
|
||||||
|
[PasswordPropertyText(true)]
|
||||||
public string SteamPassword { get; set; } = null;
|
public string SteamPassword { get; set; } = null;
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
@@ -71,6 +75,9 @@ namespace ConfigGenerator {
|
|||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonProperty(Required = Required.DisallowNull)]
|
||||||
public bool AcceptGifts { get; set; } = false;
|
public bool AcceptGifts { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty(Required = Required.DisallowNull)]
|
||||||
|
public bool IsBotAccount { get; set; } = false;
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonProperty(Required = Required.DisallowNull)]
|
||||||
public bool SteamTradeMatcher { get; set; } = false;
|
public bool SteamTradeMatcher { get; set; } = false;
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ using System.IO;
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
namespace ConfigGenerator {
|
namespace ConfigGenerator {
|
||||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global"), SuppressMessage("ReSharper", "CollectionNeverQueried.Global"), SuppressMessage("ReSharper", "MemberCanBePrivate.Global"), SuppressMessage("ReSharper", "UnusedMember.Global")]
|
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
|
||||||
|
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")]
|
||||||
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||||
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
internal sealed class GlobalConfig : ASFConfig {
|
internal sealed class GlobalConfig : ASFConfig {
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
|
||||||
internal enum EUpdateChannel : byte {
|
internal enum EUpdateChannel : byte {
|
||||||
Unknown,
|
Unknown,
|
||||||
Stable,
|
Stable,
|
||||||
@@ -40,7 +42,7 @@ namespace ConfigGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const byte DefaultMaxFarmingTime = 10;
|
private const byte DefaultMaxFarmingTime = 10;
|
||||||
private const byte DefaultFarmingDelay = 5;
|
private const byte DefaultFarmingDelay = 15;
|
||||||
private const byte DefaultHttpTimeout = 60;
|
private const byte DefaultHttpTimeout = 60;
|
||||||
private const ushort DefaultWCFPort = 1242;
|
private const ushort DefaultWCFPort = 1242;
|
||||||
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
|
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ ArchiSteamFarm
|
|||||||
[](https://travis-ci.org/JustArchi/ArchiSteamFarm)
|
[](https://travis-ci.org/JustArchi/ArchiSteamFarm)
|
||||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
|
[](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
|
||||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases)
|
[](https://github.com/JustArchi/ArchiSteamFarm/releases)
|
||||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4)
|
[](https://www.paypal.me/JustArchi/1usd)
|
||||||
[](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
|
[](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user