mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-20 08:18:37 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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,7 +114,9 @@ namespace ArchiSteamFarm {
|
||||
WebBrowser = new WebBrowser(bot.BotName);
|
||||
}
|
||||
|
||||
internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
|
||||
internal void OnDisconnected() => Ready = false;
|
||||
|
||||
internal async Task<bool> Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
|
||||
if ((steamClient == null) || string.IsNullOrEmpty(webAPIUserNonce)) {
|
||||
Logging.LogNullError(nameof(steamClient) + " || " + nameof(webAPIUserNonce), Bot.BotName);
|
||||
return false;
|
||||
@@ -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, 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();
|
||||
|
||||
@@ -1452,10 +1522,12 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiWebHandler.OnDisconnected();
|
||||
Logging.LogGenericInfo("Disconnected from Steam!", BotName);
|
||||
|
||||
FirstTradeSent = false;
|
||||
CardsFarmer.StopFarming().Forget();
|
||||
HandledGifts.ClearAndTrim();
|
||||
|
||||
// If we initiated disconnect, do not attempt to reconnect
|
||||
if (callback.UserInitiated) {
|
||||
@@ -1469,7 +1541,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 +1575,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 +1782,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (!ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN)) {
|
||||
if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
|
||||
if (!await RefreshSession().ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
@@ -1724,7 +1808,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) {
|
||||
@@ -377,12 +382,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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.0.9")]
|
||||
[assembly: AssemblyFileVersion("2.1.0.9")]
|
||||
|
||||
@@ -44,7 +44,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();
|
||||
}
|
||||
@@ -201,14 +201,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,20 +218,23 @@ 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
|
||||
|
||||
@@ -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