mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-22 17:28:37 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d16228b2a5 | ||
|
|
99dbca1b36 | ||
|
|
943bc7e3d9 | ||
|
|
9535479602 | ||
|
|
90ade53ae7 | ||
|
|
9a51386b7e | ||
|
|
03ee96057f | ||
|
|
a23bca7960 | ||
|
|
6b4ae6a4d7 | ||
|
|
5c80fd158d | ||
|
|
885800c539 | ||
|
|
920d4b9ed6 | ||
|
|
2d02bd609e | ||
|
|
e658ae33b1 | ||
|
|
379018866b | ||
|
|
1ad1e8b792 | ||
|
|
c3dde4c822 | ||
|
|
b40dc2e572 | ||
|
|
70bdd34d66 | ||
|
|
56a6e10189 | ||
|
|
41f8a0a474 | ||
|
|
92f347e28b | ||
|
|
1a1914540c | ||
|
|
449e4f9511 | ||
|
|
959bf98039 | ||
|
|
017c5eb7ef | ||
|
|
9e575584a8 | ||
|
|
19da8c6d11 | ||
|
|
3d19a69c60 | ||
|
|
f6a8d16c85 | ||
|
|
f13991c2da | ||
|
|
40a3d6558d | ||
|
|
fea76a3dda | ||
|
|
1087c01a2c | ||
|
|
fd49ff5483 | ||
|
|
0f5d9a665c | ||
|
|
ad63432645 | ||
|
|
f36681ea18 | ||
|
|
ce94035d98 |
@@ -47,6 +47,8 @@ namespace ArchiSteamFarm {
|
||||
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
|
||||
private readonly WebBrowser WebBrowser;
|
||||
|
||||
internal bool Ready { get; private set; }
|
||||
|
||||
private ulong SteamID;
|
||||
private DateTime LastSessionRefreshCheck = DateTime.MinValue;
|
||||
|
||||
@@ -67,12 +69,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
uint appID;
|
||||
if (uint.TryParse(hashName.Substring(0, index), out appID)) {
|
||||
return appID;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(appID));
|
||||
return 0;
|
||||
return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0;
|
||||
}
|
||||
|
||||
private static Steam.Item.EType GetItemType(string name) {
|
||||
@@ -117,13 +114,15 @@ namespace ArchiSteamFarm {
|
||||
WebBrowser = new WebBrowser(bot.BotName);
|
||||
}
|
||||
|
||||
internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
|
||||
if ((steamClient == null) || string.IsNullOrEmpty(webAPIUserNonce)) {
|
||||
Logging.LogNullError(nameof(steamClient) + " || " + nameof(webAPIUserNonce), Bot.BotName);
|
||||
internal void OnDisconnected() => Ready = false;
|
||||
|
||||
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
|
||||
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce)) {
|
||||
Logging.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
SteamID = steamClient.SteamID;
|
||||
SteamID = steamID;
|
||||
|
||||
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString()));
|
||||
|
||||
@@ -132,7 +131,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// RSA encrypt it with the public key for the universe we're on
|
||||
byte[] cryptedSessionKey;
|
||||
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
|
||||
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) {
|
||||
cryptedSessionKey = rsa.Encrypt(sessionKey);
|
||||
}
|
||||
|
||||
@@ -179,10 +178,12 @@ namespace ArchiSteamFarm {
|
||||
string steamLoginSecure = authResult["tokensecure"].Value;
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
|
||||
|
||||
if (!UnlockParentalAccount(parentalPin).Result) {
|
||||
// Unlock Steam Parental if needed
|
||||
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ready = true;
|
||||
LastSessionRefreshCheck = DateTime.Now;
|
||||
return true;
|
||||
}
|
||||
@@ -246,7 +247,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/mobileconf/conf?p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
|
||||
string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
|
||||
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -369,10 +370,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
KeyValue response = null;
|
||||
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
|
||||
iPlayerService.Timeout = Timeout;
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
|
||||
iPlayerService.Timeout = Timeout;
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
try {
|
||||
response = iPlayerService.GetOwnedGames(
|
||||
steamid: steamID,
|
||||
@@ -406,10 +407,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal uint GetServerTime() {
|
||||
KeyValue response = null;
|
||||
using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) {
|
||||
iTwoFactorService.Timeout = Timeout;
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) {
|
||||
iTwoFactorService.Timeout = Timeout;
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
try {
|
||||
response = iTwoFactorService.QueryTime(
|
||||
method: WebRequestMethods.Http.Post,
|
||||
@@ -435,7 +436,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/tradeoffer/" + tradeID;
|
||||
string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
|
||||
|
||||
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
@@ -487,10 +488,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
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++) {
|
||||
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.GetTradeOffers(
|
||||
get_received_offers: 1,
|
||||
@@ -624,10 +625,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
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++) {
|
||||
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(),
|
||||
@@ -837,7 +838,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/my/badges?p=" + page;
|
||||
string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
|
||||
|
||||
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
||||
}
|
||||
@@ -852,7 +853,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/my/gamecards/" + appID;
|
||||
string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
|
||||
|
||||
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace ArchiSteamFarm {
|
||||
internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>();
|
||||
|
||||
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
|
||||
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
internal readonly string BotName;
|
||||
@@ -56,14 +57,20 @@ namespace ArchiSteamFarm {
|
||||
private readonly string SentryFile;
|
||||
private readonly BotDatabase BotDatabase;
|
||||
private readonly CallbackManager CallbackManager;
|
||||
|
||||
[JsonProperty]
|
||||
private readonly CardsFarmer CardsFarmer;
|
||||
|
||||
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
|
||||
private readonly SteamApps SteamApps;
|
||||
private readonly SteamFriends SteamFriends;
|
||||
private readonly SteamUser SteamUser;
|
||||
private readonly Timer AcceptConfirmationsTimer, SendItemsTimer;
|
||||
private readonly Trading Trading;
|
||||
|
||||
[JsonProperty]
|
||||
internal bool KeepRunning { get; private set; }
|
||||
|
||||
internal bool PlayingBlocked { get; private set; }
|
||||
|
||||
private bool FirstTradeSent, InvalidPassword, SkipFirstShutdown;
|
||||
@@ -78,7 +85,7 @@ namespace ArchiSteamFarm {
|
||||
initialized = true;
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,10 +114,18 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async Task LimitGiftsRequestsAsync() {
|
||||
await GiftsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
Task.Run(async () => {
|
||||
await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false);
|
||||
GiftsSemaphore.Release();
|
||||
}).Forget();
|
||||
}
|
||||
|
||||
private static async Task LimitLoginRequestsAsync() {
|
||||
await LoginSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
Task.Run(async () => {
|
||||
await Utilities.SleepAsync(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
|
||||
await Task.Delay(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
|
||||
LoginSemaphore.Release();
|
||||
}).Forget();
|
||||
}
|
||||
@@ -226,7 +241,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if ((SendItemsTimer == null) && (BotConfig.SendTradePeriod > 0)) {
|
||||
SendItemsTimer = new Timer(
|
||||
async e => await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false),
|
||||
async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromHours(BotConfig.SendTradePeriod), // Delay
|
||||
TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period
|
||||
@@ -252,7 +267,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (acceptedType != Steam.ConfirmationDetails.EType.Unknown) {
|
||||
if (confirmations.RemoveWhere(confirmation => confirmation.Type != acceptedType) > 0) {
|
||||
if (confirmations.RemoveWhere(confirmation => (confirmation.Type != acceptedType) && (confirmation.Type != Steam.ConfirmationDetails.EType.Other)) > 0) {
|
||||
confirmations.TrimExcess();
|
||||
if (confirmations.Count == 0) {
|
||||
return;
|
||||
@@ -261,8 +276,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if ((acceptedSteamID != 0) || ((acceptedTradeIDs != null) && (acceptedTradeIDs.Count > 0))) {
|
||||
List<Task<Steam.ConfirmationDetails>> detailsTasks = confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails).ToList();
|
||||
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(detailsTasks).ConfigureAwait(false);
|
||||
List<Task<Steam.ConfirmationDetails>> tasks = confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails).ToList();
|
||||
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
HashSet<uint> ignoredConfirmationIDs = new HashSet<uint>();
|
||||
foreach (Steam.ConfirmationDetails details in detailsResults.Where(details => (details != null) && (
|
||||
@@ -282,8 +297,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
List<Task<bool>> tasks = confirmations.Select(confirmation => BotDatabase.MobileAuthenticator.HandleConfirmation(confirmation, accept)).ToList();
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
// This could be done in parallel, but for some reason Steam allows only one confirmation being accepted at the time
|
||||
// Therefore, even though no physical barrier stops us from doing so, we execute this synchronously
|
||||
foreach (MobileAuthenticator.Confirmation confirmation in confirmations) {
|
||||
await BotDatabase.MobileAuthenticator.HandleConfirmation(confirmation, accept).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<bool> RefreshSession() {
|
||||
@@ -306,7 +324,7 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ArchiWebHandler.Init(SteamClient, callback.Nonce, BotConfig.SteamParentalPIN)) {
|
||||
if (await ArchiWebHandler.Init(SteamClient.SteamID, SteamClient.ConnectedUniverse, callback.Nonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -321,7 +339,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if ((farmedSomething || !FirstTradeSent) && BotConfig.SendOnFarmingFinished) {
|
||||
FirstTradeSent = true;
|
||||
await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false);
|
||||
await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (BotConfig.ShutdownOnFarmingFinished) {
|
||||
@@ -351,6 +369,8 @@ namespace ArchiSteamFarm {
|
||||
return await Response2FAConfirm(steamID, false).ConfigureAwait(false);
|
||||
case "!2faok":
|
||||
return await Response2FAConfirm(steamID, true).ConfigureAwait(false);
|
||||
case "!api":
|
||||
return ResponseAPI(steamID);
|
||||
case "!exit":
|
||||
return ResponseExit(steamID);
|
||||
case "!farm":
|
||||
@@ -358,11 +378,15 @@ namespace ArchiSteamFarm {
|
||||
case "!help":
|
||||
return ResponseHelp(steamID);
|
||||
case "!loot":
|
||||
return await ResponseSendTrade(steamID).ConfigureAwait(false);
|
||||
return await ResponseLoot(steamID).ConfigureAwait(false);
|
||||
case "!lootall":
|
||||
return await ResponseLootAll(steamID).ConfigureAwait(false);
|
||||
case "!pause":
|
||||
return await ResponsePause(steamID).ConfigureAwait(false);
|
||||
return await ResponsePause(steamID, true).ConfigureAwait(false);
|
||||
case "!rejoinchat":
|
||||
return ResponseRejoinChat(steamID);
|
||||
case "!resume":
|
||||
return await ResponsePause(steamID, false).ConfigureAwait(false);
|
||||
case "!restart":
|
||||
return ResponseRestart(steamID);
|
||||
case "!status":
|
||||
@@ -397,7 +421,7 @@ namespace ArchiSteamFarm {
|
||||
case "!farm":
|
||||
return await ResponseFarm(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!loot":
|
||||
return await ResponseSendTrade(steamID, args[1]).ConfigureAwait(false);
|
||||
return await ResponseLoot(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!owns":
|
||||
if (args.Length > 2) {
|
||||
return await ResponseOwns(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
@@ -405,7 +429,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
return await ResponseOwns(steamID, BotName, args[1]).ConfigureAwait(false);
|
||||
case "!pause":
|
||||
return await ResponsePause(steamID, args[1]).ConfigureAwait(false);
|
||||
return await ResponsePause(steamID, args[1], true).ConfigureAwait(false);
|
||||
case "!play":
|
||||
if (args.Length > 2) {
|
||||
return await ResponsePlay(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
@@ -418,6 +442,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
return await ResponseRedeem(steamID, BotName, args[1], false).ConfigureAwait(false);
|
||||
case "!resume":
|
||||
return await ResponsePause(steamID, args[1], false).ConfigureAwait(false);
|
||||
case "!start":
|
||||
return await ResponseStart(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!status":
|
||||
@@ -500,7 +526,7 @@ namespace ArchiSteamFarm {
|
||||
Logging.LogGenericInfo("Successfully finished importing mobile authenticator!", BotName);
|
||||
}
|
||||
|
||||
private async Task<string> ResponsePause(ulong steamID) {
|
||||
private async Task<string> ResponsePause(ulong steamID, bool pause) {
|
||||
if (steamID == 0) {
|
||||
Logging.LogNullError(nameof(steamID), BotName);
|
||||
return null;
|
||||
@@ -510,16 +536,24 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (CardsFarmer.ManualMode) {
|
||||
await CardsFarmer.SwitchToManualMode(false).ConfigureAwait(false);
|
||||
return "Automatic farming is enabled again!";
|
||||
if (pause) {
|
||||
if (CardsFarmer.ManualMode) {
|
||||
return "Automatic farming is stopped already!";
|
||||
}
|
||||
|
||||
await CardsFarmer.SwitchToManualMode(true).ConfigureAwait(false);
|
||||
return "Automatic farming is now stopped!";
|
||||
}
|
||||
|
||||
await CardsFarmer.SwitchToManualMode(true).ConfigureAwait(false);
|
||||
return "Automatic farming is now stopped!";
|
||||
if (!CardsFarmer.ManualMode) {
|
||||
return "Automatic farming is enabled already!";
|
||||
}
|
||||
|
||||
await CardsFarmer.SwitchToManualMode(false).ConfigureAwait(false);
|
||||
return "Automatic farming is now enabled!";
|
||||
}
|
||||
|
||||
private static async Task<string> ResponsePause(ulong steamID, string botName) {
|
||||
private static async Task<string> ResponsePause(ulong steamID, string botName, bool pause) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
|
||||
Logging.LogNullError(nameof(steamID) + " || " + nameof(botName));
|
||||
return null;
|
||||
@@ -527,7 +561,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (Bots.TryGetValue(botName, out bot)) {
|
||||
return await bot.ResponsePause(steamID).ConfigureAwait(false);
|
||||
return await bot.ResponsePause(steamID, pause).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (IsOwner(steamID)) {
|
||||
@@ -608,7 +642,7 @@ namespace ArchiSteamFarm {
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private async Task<string> ResponseSendTrade(ulong steamID) {
|
||||
private async Task<string> ResponseLoot(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
Logging.LogNullError(nameof(steamID), BotName);
|
||||
return null;
|
||||
@@ -649,7 +683,7 @@ namespace ArchiSteamFarm {
|
||||
return "Trade offer sent successfully!";
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseSendTrade(ulong steamID, string botName) {
|
||||
private static async Task<string> ResponseLoot(ulong steamID, string botName) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
|
||||
Logging.LogNullError(nameof(steamID) + " || " + nameof(botName));
|
||||
return null;
|
||||
@@ -657,7 +691,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (Bots.TryGetValue(botName, out bot)) {
|
||||
return await bot.ResponseSendTrade(steamID).ConfigureAwait(false);
|
||||
return await bot.ResponseLoot(steamID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (IsOwner(steamID)) {
|
||||
@@ -667,6 +701,20 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseLootAll(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
Logging.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsOwner(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await Task.WhenAll(Bots.Values.Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false);
|
||||
return "Done!";
|
||||
}
|
||||
|
||||
private async Task<string> Response2FA(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
Logging.LogNullError(nameof(steamID), BotName);
|
||||
@@ -739,6 +787,28 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ResponseAPI(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
Logging.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsOwner(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var response = new {
|
||||
Bots
|
||||
};
|
||||
|
||||
try {
|
||||
return JsonConvert.SerializeObject(response);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResponseExit(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
Logging.LogNullError(nameof(steamID));
|
||||
@@ -751,7 +821,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Schedule the task after some time so user can receive response
|
||||
Task.Run(async () => {
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
Program.Exit();
|
||||
}).Forget();
|
||||
|
||||
@@ -953,7 +1023,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Schedule the task after some time so user can receive response
|
||||
Task.Run(async () => {
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
Program.Restart();
|
||||
}).Forget();
|
||||
|
||||
@@ -1454,8 +1524,12 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Logging.LogGenericInfo("Disconnected from Steam!", BotName);
|
||||
|
||||
ArchiWebHandler.OnDisconnected();
|
||||
CardsFarmer.OnDisconnected();
|
||||
Trading.OnDisconnected();
|
||||
|
||||
FirstTradeSent = false;
|
||||
CardsFarmer.StopFarming().Forget();
|
||||
HandledGifts.ClearAndTrim();
|
||||
|
||||
// If we initiated disconnect, do not attempt to reconnect
|
||||
if (callback.UserInitiated) {
|
||||
@@ -1469,7 +1543,7 @@ namespace ArchiSteamFarm {
|
||||
Logging.LogGenericInfo("Removed expired login key", BotName);
|
||||
} else { // If we didn't use login key, InvalidPassword usually means we got captcha or other network-based throttling
|
||||
Logging.LogGenericInfo("Will retry after 25 minutes...", BotName);
|
||||
await Utilities.SleepAsync(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
|
||||
await Task.Delay(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1503,9 +1577,21 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!ArchiWebHandler.Ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool acceptedSomething = false;
|
||||
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => gid != 0)) {
|
||||
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
|
||||
HandledGifts.Add(gid);
|
||||
|
||||
Logging.LogGenericInfo("Accepting gift: " + gid + "...", BotName);
|
||||
await LimitGiftsRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
if (await ArchiWebHandler.AcceptGift(gid).ConfigureAwait(false)) {
|
||||
acceptedSomething = true;
|
||||
Logging.LogGenericInfo("Success!", BotName);
|
||||
@@ -1698,7 +1784,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (!ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN)) {
|
||||
if (!await ArchiWebHandler.Init(callback.ClientSteamID, SteamClient.ConnectedUniverse, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
|
||||
if (!await RefreshSession().ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
@@ -1724,7 +1810,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Trading.CheckTrades().Forget();
|
||||
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
|
||||
CardsFarmer.StartFarming().Forget();
|
||||
break;
|
||||
case EResult.NoConnection:
|
||||
|
||||
@@ -31,10 +31,14 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CardsFarmer {
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
|
||||
|
||||
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
|
||||
@@ -42,6 +46,7 @@ namespace ArchiSteamFarm {
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer Timer;
|
||||
|
||||
[JsonProperty]
|
||||
internal bool ManualMode { get; private set; }
|
||||
|
||||
private bool KeepFarming, NowFarming;
|
||||
@@ -175,7 +180,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
|
||||
for (byte i = 0; (i < 5) && NowFarming; i++) {
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (NowFarming) {
|
||||
@@ -187,6 +192,8 @@ namespace ArchiSteamFarm {
|
||||
FarmingSemaphore.Release();
|
||||
}
|
||||
|
||||
internal void OnDisconnected() => StopFarming().Forget();
|
||||
|
||||
internal void OnNewItemsNotification() {
|
||||
if (!NowFarming) {
|
||||
return;
|
||||
@@ -377,12 +384,29 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
|
||||
if (htmlNode != null) {
|
||||
return !htmlNode.InnerText.Contains("No card drops");
|
||||
if (htmlNode == null) {
|
||||
Logging.LogNullError(nameof(htmlNode), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(htmlNode), Bot.BotName);
|
||||
return null;
|
||||
string progress = htmlNode.InnerText;
|
||||
if (string.IsNullOrEmpty(progress)) {
|
||||
Logging.LogNullError(nameof(progress), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte cardsRemaining = 0;
|
||||
|
||||
Match match = Regex.Match(progress, @"\d+");
|
||||
if (match.Success) {
|
||||
if (!byte.TryParse(match.Value, out cardsRemaining)) {
|
||||
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Status for " + appID + ": " + cardsRemaining + " cards remaining", Bot.BotName);
|
||||
return cardsRemaining > 0;
|
||||
}
|
||||
|
||||
private bool FarmMultiple() {
|
||||
|
||||
@@ -49,11 +49,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
public bool MoveNext() => Enumerator.MoveNext();
|
||||
public void Reset() => Enumerator.Reset();
|
||||
|
||||
public void Dispose() {
|
||||
if (Lock != null) {
|
||||
Lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
public void Dispose() => Lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ using System.Threading;
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class ConcurrentHashSet<T> : ICollection<T>, IDisposable {
|
||||
private readonly HashSet<T> HashSet = new HashSet<T>();
|
||||
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);
|
||||
|
||||
@@ -81,11 +81,14 @@ namespace ArchiSteamFarm {
|
||||
internal byte FarmingDelay { get; private set; } = DefaultFarmingDelay;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte LoginLimiterDelay { get; private set; } = 7;
|
||||
internal byte LoginLimiterDelay { get; private set; } = 10;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte InventoryLimiterDelay { get; private set; } = 3;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte GiftsLimiterDelay { get; private set; } = 1;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool ForceHttp { get; private set; } = false;
|
||||
|
||||
|
||||
@@ -442,8 +442,10 @@ namespace ArchiSteamFarm.JSON {
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(PropertyName = "html")]
|
||||
private string HTML;
|
||||
#pragma warning restore 649
|
||||
|
||||
private uint _OtherSteamID3;
|
||||
private uint OtherSteamID3 {
|
||||
|
||||
@@ -1,4 +1,28 @@
|
||||
using System;
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace ArchiSteamFarm {
|
||||
// We booted successfully so we can now remove old exe file
|
||||
if (File.Exists(oldExeFile)) {
|
||||
// It's entirely possible that old process is still running, allow at least a second before trying to remove the file
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
File.Delete(oldExeFile);
|
||||
@@ -172,7 +172,7 @@ namespace ArchiSteamFarm {
|
||||
if (!updateOverride && !GlobalConfig.AutoUpdates) {
|
||||
Logging.LogGenericInfo("New version is available!");
|
||||
Logging.LogGenericInfo("Consider updating yourself!");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Downloading new version...");
|
||||
byte[] result = await WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
|
||||
if (result == null) {
|
||||
return;
|
||||
@@ -247,11 +248,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (GlobalConfig.AutoRestart) {
|
||||
Logging.LogGenericInfo("Restarting...");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
Restart();
|
||||
} else {
|
||||
Logging.LogGenericInfo("Exiting...");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,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.1.0.1")]
|
||||
[assembly: AssemblyFileVersion("2.1.0.1")]
|
||||
[assembly: AssemblyVersion("2.1.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.1.1.0")]
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -31,12 +32,23 @@ using ArchiSteamFarm.JSON;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Trading {
|
||||
private enum ParseTradeResult : byte {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
Unknown,
|
||||
Error,
|
||||
AcceptedWithItemLose,
|
||||
AcceptedWithoutItemLose,
|
||||
RejectedTemporarily,
|
||||
RejectedPermanently
|
||||
}
|
||||
|
||||
internal const byte MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser
|
||||
internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve
|
||||
|
||||
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>();
|
||||
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
private byte ParsingTasks;
|
||||
@@ -44,7 +56,7 @@ namespace ArchiSteamFarm {
|
||||
internal static async Task LimitInventoryRequestsAsync() {
|
||||
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
Task.Run(async () => {
|
||||
await Utilities.SleepAsync(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
|
||||
await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
|
||||
InventorySemaphore.Release();
|
||||
}).Forget();
|
||||
}
|
||||
@@ -57,6 +69,8 @@ namespace ArchiSteamFarm {
|
||||
Bot = bot;
|
||||
}
|
||||
|
||||
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim();
|
||||
|
||||
internal async Task CheckTrades() {
|
||||
lock (TradesSemaphore) {
|
||||
if (ParsingTasks >= 2) {
|
||||
@@ -86,76 +100,86 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tradeOffers.RemoveWhere(tradeoffer => tradeoffer.State != Steam.TradeOffer.ETradeOfferState.Active) > 0) {
|
||||
if (tradeOffers.RemoveWhere(tradeoffer => (tradeoffer.State != Steam.TradeOffer.ETradeOfferState.Active) || IgnoredTrades.Contains(tradeoffer.TradeOfferID)) > 0) {
|
||||
tradeOffers.TrimExcess();
|
||||
if (tradeOffers.Count == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<Task<bool>> tasks = tradeOffers.Select(ParseTrade).ToList();
|
||||
bool[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
List<Task<ParseTradeResult>> tasks = tradeOffers.Select(ParseTrade).ToList();
|
||||
ParseTradeResult[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
if (results.Any(result => result)) {
|
||||
if (results.Any(result => result == ParseTradeResult.AcceptedWithItemLose)) {
|
||||
HashSet<ulong> tradeIDs = new HashSet<ulong>(tradeOffers.Select(tradeOffer => tradeOffer.TradeOfferID));
|
||||
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, tradeIDs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ParseTrade(Steam.TradeOffer tradeOffer) {
|
||||
private async Task<ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) {
|
||||
if (tradeOffer == null) {
|
||||
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
|
||||
return false;
|
||||
return ParseTradeResult.Error;
|
||||
}
|
||||
|
||||
if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
|
||||
return false;
|
||||
return ParseTradeResult.Error;
|
||||
}
|
||||
|
||||
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
|
||||
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
}
|
||||
ParseTradeResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
|
||||
switch (result) {
|
||||
case ParseTradeResult.AcceptedWithItemLose:
|
||||
case ParseTradeResult.AcceptedWithoutItemLose:
|
||||
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false) ? result : ParseTradeResult.Error;
|
||||
case ParseTradeResult.RejectedPermanently:
|
||||
case ParseTradeResult.RejectedTemporarily:
|
||||
if (Bot.BotConfig.IsBotAccount) {
|
||||
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID) ? result : ParseTradeResult.Error;
|
||||
}
|
||||
|
||||
if (Bot.BotConfig.IsBotAccount) {
|
||||
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID);
|
||||
}
|
||||
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
if (result == ParseTradeResult.RejectedPermanently) {
|
||||
IgnoredTrades.Add(tradeOffer.TradeOfferID);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return false;
|
||||
return result;
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
|
||||
private async Task<ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
|
||||
if (tradeOffer == null) {
|
||||
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
|
||||
return false;
|
||||
return ParseTradeResult.Error;
|
||||
}
|
||||
|
||||
// Always accept trades when we're not losing anything
|
||||
if (tradeOffer.ItemsToGive.Count == 0) {
|
||||
// Unless it's steam fuckup and we're dealing with broken trade
|
||||
return tradeOffer.ItemsToReceive.Count > 0;
|
||||
return tradeOffer.ItemsToReceive.Count > 0 ? ParseTradeResult.AcceptedWithoutItemLose : ParseTradeResult.RejectedTemporarily;
|
||||
}
|
||||
|
||||
// Always accept trades from SteamMasterID
|
||||
if ((tradeOffer.OtherSteamID64 != 0) && (tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID)) {
|
||||
return true;
|
||||
return ParseTradeResult.AcceptedWithItemLose;
|
||||
}
|
||||
|
||||
// If we don't have SteamTradeMatcher enabled, this is the end for us
|
||||
if (!Bot.BotConfig.SteamTradeMatcher) {
|
||||
return false;
|
||||
return ParseTradeResult.RejectedPermanently;
|
||||
}
|
||||
|
||||
// Decline trade if we're giving more count-wise
|
||||
if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) {
|
||||
return false;
|
||||
return ParseTradeResult.RejectedPermanently;
|
||||
}
|
||||
|
||||
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
|
||||
if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) {
|
||||
return false;
|
||||
return ParseTradeResult.RejectedPermanently;
|
||||
}
|
||||
|
||||
// At this point we're sure that STM trade is valid
|
||||
@@ -164,7 +188,7 @@ namespace ArchiSteamFarm {
|
||||
if (tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) {
|
||||
byte? holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDuration(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
if (holdDuration.GetValueOrDefault() > 0) {
|
||||
return false;
|
||||
return ParseTradeResult.RejectedPermanently;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +197,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyInventory(false).ConfigureAwait(false);
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
return true; // OK, assume that this trade is valid, we can't check our EQ
|
||||
return ParseTradeResult.AcceptedWithItemLose; // OK, assume that this trade is valid, we can't check our EQ
|
||||
}
|
||||
|
||||
// Get appIDs we're interested in
|
||||
@@ -185,7 +209,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
|
||||
if (inventory.Count == 0) {
|
||||
return true;
|
||||
return ParseTradeResult.AcceptedWithItemLose;
|
||||
}
|
||||
|
||||
// Now let's create a map which maps items to their amount in our EQ
|
||||
@@ -201,14 +225,16 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Calculate our value of items to give
|
||||
List<uint> amountsToGive = new List<uint>(tradeOffer.ItemsToGive.Count);
|
||||
Dictionary<ulong, uint> amountMapToGive = new Dictionary<ulong, uint>(amountMap);
|
||||
foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) {
|
||||
uint amount;
|
||||
if (!amountMap.TryGetValue(key, out amount)) {
|
||||
if (!amountMapToGive.TryGetValue(key, out amount)) {
|
||||
amountsToGive.Add(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
amountsToGive.Add(amount);
|
||||
amountMapToGive[key] = amount - 1; // We're giving one, so we have one less
|
||||
}
|
||||
|
||||
// Sort it ascending
|
||||
@@ -216,24 +242,27 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Calculate our value of items to receive
|
||||
List<uint> amountsToReceive = new List<uint>(tradeOffer.ItemsToReceive.Count);
|
||||
Dictionary<ulong, uint> amountMapToReceive = new Dictionary<ulong, uint>(amountMap);
|
||||
foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) {
|
||||
uint amount;
|
||||
if (!amountMap.TryGetValue(key, out amount)) {
|
||||
if (!amountMapToReceive.TryGetValue(key, out amount)) {
|
||||
amountsToReceive.Add(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
amountsToReceive.Add(amount);
|
||||
amountMapToReceive[key] = amount + 1; // We're getting one, so we have one more
|
||||
}
|
||||
|
||||
// Sort it ascending
|
||||
amountsToReceive.Sort();
|
||||
|
||||
// Check actual difference
|
||||
// We sum only values at proper indexes of giving, because user might be overpaying
|
||||
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
|
||||
|
||||
// Trade is worth for us if the difference is greater than 0
|
||||
return difference > 0;
|
||||
return difference > 0 ? ParseTradeResult.AcceptedWithItemLose : ParseTradeResult.RejectedTemporarily;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,14 +53,5 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal static uint GetUnixTime() => (uint) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
|
||||
internal static Task SleepAsync(int miliseconds) {
|
||||
if (miliseconds >= 0) {
|
||||
return Task.Delay(miliseconds);
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(miliseconds));
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Most web services expect that UserAgent is set, so we declare it globally
|
||||
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
|
||||
|
||||
// We should always operate in English language, declare it globally
|
||||
HttpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.8,en-GB;q=0.6");
|
||||
}
|
||||
|
||||
internal async Task<bool> UrlHeadRetry(string request, string referer = null) {
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
"MaxFarmingTime": 10,
|
||||
"IdleFarmingPeriod": 3,
|
||||
"FarmingDelay": 15,
|
||||
"LoginLimiterDelay": 7,
|
||||
"LoginLimiterDelay": 10,
|
||||
"InventoryLimiterDelay": 3,
|
||||
"GiftsLimiterDelay": 1,
|
||||
"ForceHttp": false,
|
||||
"HttpTimeout": 60,
|
||||
"WCFHostname": "localhost",
|
||||
|
||||
30
CONTRIBUTING.md
Normal file
30
CONTRIBUTING.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Contributing
|
||||
|
||||
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first.
|
||||
|
||||
## Issues
|
||||
|
||||
GitHub **[issues](https://github.com/JustArchi/ArchiSteamFarm/issues)** page is being used for ASF TODO list, regarding both features and bugs. It has rather strict policy - GitHub is not technical support and all cases that are not suggestions or bug reports should NOT be posted there. You have ASF chat and Steam group for general discussion, questions or technical issues. Please avoid using GitHub issues, unless you indeed want to report a bug or suggest an enhancement. Even prior to doing that, please make sure that you're indeed dealing with a bug, or your suggestion makes sense, preferably by asking on chat/steam group first. Invalid issues will be closed immediately.
|
||||
|
||||
---
|
||||
|
||||
### Bugs
|
||||
|
||||
Posting a log is **mandatory**, regardless if it contains information that is relevant or not. You're allowed to make small modifications such as changing bot names to something more generic, but you should not be doing anything else. You want us to fix the bug you've encountered, then help us instead of making it harder - we're not being paid for that, and we're not forced to fix the bug you've encountered. Include as much relevant info as possible - if bug is reproducable, when it happens, if it's a result of a command - which one, does it happen always or only sometimes, with one account or all of them - everything you consider appropriate, that would help us reproduce the bug and fix it. The more information you include, the higher the chance of bug getting fixed. And this is probably what you want, right?
|
||||
|
||||
---
|
||||
|
||||
### Suggestions
|
||||
|
||||
ASF has rather strict scope - farming Steam cards from Steam games, which means that anything going greatly out of the scope will not be accepted, even if it's considered useful. A good example of that is Steam discovery queue, that provides extra cards during Steam sales - this is out of the scope of ASF as a program, ASF focuses on one task and is doing it efficiently, if you want to create your own bot that does exactly what you want - pay somebody for creating it.
|
||||
|
||||
If your suggestion doesn't go out of the scope of ASF, then explain to us in the issue why you consider it useful, why do you think that adding it to ASF is beneficial for **all users**, not yourself. Why we should spend our time coding it, convince us. If suggestion indeed makes sense, or can be considered practical, most likely we won't have anything against that, but **you** should be the one pointing out advantages, not us.
|
||||
|
||||
---
|
||||
|
||||
## Pull requests
|
||||
|
||||
In general any pull request is welcome and should be accepted, unless there is a strong reason against it. A strong reason includes e.g. a feature going potentially out of the scope of ASF. If you're improving existing codebase, rewriting code to be more efficient, clean, better commented - there is absolutely no reason to reject it. If you want to add missing feature, and you're not sure if it should be included in ASF, it won't hurt to ask before spending your own time.
|
||||
|
||||
Every pull request is carefully examined by our continuous integration system - it won't be accepted if it doesn't compile properly or causes any test to fail. We also expect that you at least barely tested the modification you're trying to add, and not blindly editing the file without even checking if it compiles. Consider the fact that you're not coding it only for yourself, but for thousands of users.
|
||||
|
||||
@@ -81,11 +81,14 @@ namespace ConfigGenerator {
|
||||
public byte FarmingDelay { get; set; } = DefaultFarmingDelay;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte LoginLimiterDelay { get; set; } = 7;
|
||||
public byte LoginLimiterDelay { get; set; } = 10;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte InventoryLimiterDelay { get; set; } = 3;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte GiftsLimiterDelay { get; set; } = 1;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool ForceHttp { get; set; } = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user