mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-20 00:08:38 +00:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1e5874868 | ||
|
|
38e2088d09 | ||
|
|
8447e07aa0 | ||
|
|
fbe4e4bc6d | ||
|
|
f1213607ce | ||
|
|
de832c530b | ||
|
|
0c872b17e2 | ||
|
|
5fcbb85b4c | ||
|
|
3cdc93d373 | ||
|
|
36e99d9139 | ||
|
|
7bee2d468b | ||
|
|
8630cc40c8 | ||
|
|
3683195a0e | ||
|
|
7f5b946645 | ||
|
|
fdb194fe67 | ||
|
|
9063b9206b | ||
|
|
8d300894e5 | ||
|
|
3a0d3c444e | ||
|
|
8118fe0690 | ||
|
|
344c2ad23d | ||
|
|
d1a6613541 | ||
|
|
3dc88c65aa | ||
|
|
46384829c9 | ||
|
|
415ee8cc57 | ||
|
|
351d45e049 | ||
|
|
f81bbc60c5 | ||
|
|
eb6e93a172 | ||
|
|
e17c3ecf2a | ||
|
|
048b0fb538 | ||
|
|
ac7ecb6bb4 | ||
|
|
fcaf038dac | ||
|
|
f3da5d6afc | ||
|
|
84f33fcef4 | ||
|
|
22f0d423a3 | ||
|
|
f1d7609796 | ||
|
|
77386ecae5 | ||
|
|
4e86d21ef8 | ||
|
|
044fc87691 | ||
|
|
79fad62a4d | ||
|
|
6622d3b147 | ||
|
|
6b6d5429ad | ||
|
|
d59d0a8415 | ||
|
|
e3100d3938 | ||
|
|
42c020e552 | ||
|
|
554273833b | ||
|
|
093a29df62 | ||
|
|
31aa6b2e4a | ||
|
|
9f7ecdf054 | ||
|
|
7c4c74bf84 | ||
|
|
b8f03abd8b | ||
|
|
47f846540b | ||
|
|
bc14713079 | ||
|
|
770a8fee66 | ||
|
|
1df9af08e6 | ||
|
|
27464f6120 | ||
|
|
dfd45c6e25 | ||
|
|
adc1759cee | ||
|
|
88369ec71a | ||
|
|
a5d8ae53dd | ||
|
|
cd7b65868a | ||
|
|
7575704a01 | ||
|
|
b6ce8f435c | ||
|
|
565acca9fb | ||
|
|
610954ba73 | ||
|
|
74a748b03f | ||
|
|
585a075ec9 | ||
|
|
891d40afe1 | ||
|
|
387f0dd1c7 |
@@ -1,4 +1,3 @@
|
||||
sudo: false
|
||||
language: csharp
|
||||
solution: ArchiSteamFarm.sln
|
||||
|
||||
@@ -8,3 +7,6 @@ git:
|
||||
mono:
|
||||
- weekly
|
||||
- latest
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
@@ -52,69 +52,58 @@ namespace ArchiSteamFarm {
|
||||
*/
|
||||
|
||||
internal sealed class NotificationsCallback : CallbackMsg {
|
||||
internal sealed class Notification {
|
||||
internal enum ENotificationType : uint {
|
||||
Unknown = 0,
|
||||
Trading = 1,
|
||||
// Only custom below, different than ones available as user_notification_type
|
||||
Items = 514
|
||||
}
|
||||
|
||||
internal readonly ENotificationType NotificationType;
|
||||
|
||||
internal Notification(ENotificationType notificationType) {
|
||||
NotificationType = notificationType;
|
||||
}
|
||||
internal enum ENotification : byte {
|
||||
Unknown = 0,
|
||||
Trading = 1,
|
||||
// Only custom below, different than ones available as user_notification_type
|
||||
Items = 255
|
||||
}
|
||||
|
||||
internal readonly List<Notification> Notifications;
|
||||
internal readonly HashSet<ENotification> Notifications;
|
||||
|
||||
internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
|
||||
JobID = jobID;
|
||||
|
||||
if (msg == null || msg.notifications == null) {
|
||||
return;
|
||||
if (jobID == null || msg == null) {
|
||||
throw new ArgumentNullException("jobID || msg");
|
||||
}
|
||||
|
||||
Notifications = new List<Notification>(msg.notifications.Count);
|
||||
JobID = jobID;
|
||||
|
||||
Notifications = new HashSet<ENotification>();
|
||||
foreach (var notification in msg.notifications) {
|
||||
Notifications.Add(new Notification((Notification.ENotificationType) notification.user_notification_type));
|
||||
Notifications.Add((ENotification) notification.user_notification_type);
|
||||
}
|
||||
}
|
||||
|
||||
internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {
|
||||
JobID = jobID;
|
||||
|
||||
if (msg == null) {
|
||||
return;
|
||||
if (jobID == null || msg == null) {
|
||||
throw new ArgumentNullException("jobID || msg");
|
||||
}
|
||||
|
||||
JobID = jobID;
|
||||
|
||||
if (msg.count_new_items > 0) {
|
||||
Notifications = new List<Notification>(1) {
|
||||
new Notification(Notification.ENotificationType.Items)
|
||||
Notifications = new HashSet<ENotification> {
|
||||
ENotification.Items
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class OfflineMessageCallback : CallbackMsg {
|
||||
internal readonly uint OfflineMessages;
|
||||
internal readonly List<uint> Users;
|
||||
internal readonly uint OfflineMessagesCount;
|
||||
|
||||
internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
|
||||
JobID = jobID;
|
||||
|
||||
if (msg == null) {
|
||||
return;
|
||||
if (jobID == null || msg == null) {
|
||||
throw new ArgumentNullException("jobID || msg");
|
||||
}
|
||||
|
||||
OfflineMessages = msg.offline_messages;
|
||||
Users = msg.friends_with_offline_messages;
|
||||
JobID = jobID;
|
||||
OfflineMessagesCount = msg.offline_messages;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PurchaseResponseCallback : CallbackMsg {
|
||||
internal enum EPurchaseResult : int {
|
||||
internal enum EPurchaseResult : sbyte {
|
||||
Unknown = -1,
|
||||
OK = 0,
|
||||
AlreadyOwned = 9,
|
||||
@@ -131,12 +120,11 @@ namespace ArchiSteamFarm {
|
||||
internal readonly Dictionary<uint, string> Items;
|
||||
|
||||
internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
|
||||
JobID = jobID;
|
||||
|
||||
if (msg == null) {
|
||||
return;
|
||||
if (jobID == null || msg == null) {
|
||||
throw new ArgumentNullException("jobID || msg");
|
||||
}
|
||||
|
||||
JobID = jobID;
|
||||
Result = (EResult) msg.eresult;
|
||||
PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
|
||||
|
||||
@@ -150,14 +138,14 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
List<KeyValue> lineItems = ReceiptInfo["lineitems"].Children;
|
||||
var lineItems = ReceiptInfo["lineitems"].Children;
|
||||
Items = new Dictionary<uint, string>(lineItems.Count);
|
||||
|
||||
foreach (KeyValue lineItem in lineItems) {
|
||||
uint appID = (uint) lineItem["PackageID"].AsUnsignedLong();
|
||||
string gameName = lineItem["ItemDescription"].AsString();
|
||||
gameName = WebUtility.UrlDecode(gameName); // Apparently steam expects client to decode sent HTML
|
||||
Items.Add(appID, gameName);
|
||||
string gameName = lineItem["ItemDescription"].Value;
|
||||
gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML
|
||||
Items[appID] = gameName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,71 +160,34 @@ namespace ArchiSteamFarm {
|
||||
|
||||
*/
|
||||
|
||||
internal void AcceptClanInvite(ulong clanID) {
|
||||
if (clanID == 0 || !Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
|
||||
request.Body.GroupID = clanID;
|
||||
request.Body.AcceptInvite = true;
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void DeclineClanInvite(ulong clanID) {
|
||||
if (clanID == 0 || !Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
|
||||
request.Body.GroupID = clanID;
|
||||
request.Body.AcceptInvite = false;
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void PlayGame(string gameName) {
|
||||
if (!Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
|
||||
|
||||
var gamePlayed = new CMsgClientGamesPlayed.GamePlayed();
|
||||
if (!string.IsNullOrEmpty(gameName)) {
|
||||
gamePlayed.game_id = new GameID() {
|
||||
AppType = GameID.GameType.Shortcut,
|
||||
ModID = uint.MaxValue
|
||||
};
|
||||
gamePlayed.game_extra_info = gameName;
|
||||
}
|
||||
|
||||
request.Body.games_played.Add(gamePlayed);
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void PlayGames(params uint[] gameIDs) {
|
||||
if (!Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
|
||||
foreach (uint gameID in gameIDs) {
|
||||
if (gameID == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
|
||||
game_id = new GameID(gameID),
|
||||
game_extra_info = gameName,
|
||||
game_id = new GameID() {
|
||||
AppType = GameID.GameType.Shortcut,
|
||||
ModID = uint.MaxValue
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void PlayGames(ICollection<uint> gameIDs) {
|
||||
internal void PlayGames(uint gameID) {
|
||||
if (!Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayGames(new HashSet<uint> { gameID });
|
||||
}
|
||||
|
||||
internal void PlayGames(HashSet<uint> gameIDs) {
|
||||
if (gameIDs == null || !Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
<Compile Include="GlobalDatabase.cs" />
|
||||
<Compile Include="BotDatabase.cs" />
|
||||
<Compile Include="CardsFarmer.cs" />
|
||||
<Compile Include="CMsgs\CMsgClientClanInviteAction.cs" />
|
||||
<Compile Include="Debugging.cs" />
|
||||
<Compile Include="GlobalConfig.cs" />
|
||||
<Compile Include="JSON\GitHub.cs" />
|
||||
|
||||
@@ -28,7 +28,6 @@ using SteamKit2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
@@ -36,11 +35,10 @@ using System.Threading;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class ArchiWebHandler {
|
||||
private const string SteamCommunity = "steamcommunity.com";
|
||||
private const string SteamCommunityHost = "steamcommunity.com";
|
||||
private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds
|
||||
|
||||
private static string SteamCommunityURL = "https://" + SteamCommunity;
|
||||
|
||||
private static string SteamCommunityURL = "https://" + SteamCommunityHost;
|
||||
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000;
|
||||
|
||||
private readonly Bot Bot;
|
||||
@@ -51,7 +49,54 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal static void Init() {
|
||||
Timeout = Program.GlobalConfig.HttpTimeout * 1000;
|
||||
SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunity;
|
||||
SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunityHost;
|
||||
}
|
||||
|
||||
private static uint GetAppIDFromMarketHashName(string hashName) {
|
||||
if (string.IsNullOrEmpty(hashName)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int index = hashName.IndexOf('-');
|
||||
if (index < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint appID;
|
||||
if (!uint.TryParse(hashName.Substring(0, index), out appID)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return appID;
|
||||
}
|
||||
|
||||
private static Steam.Item.EType GetItemType(string name) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
return Steam.Item.EType.Unknown;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "Booster Pack":
|
||||
return Steam.Item.EType.BoosterPack;
|
||||
case "Coupon":
|
||||
return Steam.Item.EType.Coupon;
|
||||
case "Gift":
|
||||
return Steam.Item.EType.Gift;
|
||||
case "Steam Gems":
|
||||
return Steam.Item.EType.SteamGems;
|
||||
default:
|
||||
if (name.EndsWith("Emoticon", StringComparison.Ordinal)) {
|
||||
return Steam.Item.EType.Emoticon;
|
||||
} else if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
|
||||
return Steam.Item.EType.FoilTradingCard;
|
||||
} else if (name.EndsWith("Profile Background", StringComparison.Ordinal)) {
|
||||
return Steam.Item.EType.ProfileBackground;
|
||||
} else if (name.EndsWith("Trading Card", StringComparison.Ordinal)) {
|
||||
return Steam.Item.EType.TradingCard;
|
||||
} else {
|
||||
return Steam.Item.EType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ArchiWebHandler(Bot bot) {
|
||||
@@ -116,13 +161,13 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Logging.LogGenericInfo("Success!", Bot.BotName);
|
||||
|
||||
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunity));
|
||||
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
|
||||
|
||||
string steamLogin = authResult["token"].Value;
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunity));
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
|
||||
|
||||
string steamLoginSecure = authResult["tokensecure"].Value;
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunity));
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
|
||||
|
||||
if (!UnlockParentalAccount(parentalPin).Result) {
|
||||
return false;
|
||||
@@ -132,49 +177,71 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<bool?> IsLoggedIn() {
|
||||
HtmlDocument htmlDocument = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile").ConfigureAwait(false);
|
||||
internal async Task<bool> AcceptGift(ulong gid) {
|
||||
if (gid == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
||||
if (string.IsNullOrEmpty(sessionID)) {
|
||||
Logging.LogNullError("sessionID");
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/gifts/" + gid + "/acceptunpack";
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
||||
{ "sessionid", sessionID }
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
|
||||
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='account_pulldown']");
|
||||
return htmlNode != null;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<bool> RefreshSessionIfNeeded() {
|
||||
DateTime now = DateTime.Now;
|
||||
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
||||
return true;
|
||||
internal async Task<bool> JoinGroup(ulong groupID) {
|
||||
if (groupID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
now = DateTime.Now;
|
||||
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
||||
SessionSemaphore.Release();
|
||||
return true;
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result;
|
||||
|
||||
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
|
||||
if (isLoggedIn.GetValueOrDefault(true)) {
|
||||
result = true;
|
||||
now = DateTime.Now;
|
||||
LastSessionRefreshCheck = now;
|
||||
} else {
|
||||
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
|
||||
result = await Bot.RefreshSession().ConfigureAwait(false);
|
||||
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
||||
if (string.IsNullOrEmpty(sessionID)) {
|
||||
Logging.LogNullError("sessionID");
|
||||
return false;
|
||||
}
|
||||
|
||||
SessionSemaphore.Release();
|
||||
return result;
|
||||
string request = SteamCommunityURL + "/gid/" + groupID;
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
||||
{ "sessionID", sessionID },
|
||||
{ "action", "join" }
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
|
||||
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<Dictionary<uint, string>> GetOwnedGames() {
|
||||
@@ -222,7 +289,47 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal List<Steam.TradeOffer> GetTradeOffers() {
|
||||
internal Dictionary<uint, string> GetOwnedGames(ulong steamID) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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++) {
|
||||
try {
|
||||
response = iPlayerService.GetOwnedGames(
|
||||
steamid: steamID,
|
||||
include_appinfo: 1,
|
||||
secure: !Program.GlobalConfig.ForceHttp
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<uint, string> result = new Dictionary<uint, string>(response["games"].Children.Count);
|
||||
foreach (KeyValue game in response["games"].Children) {
|
||||
uint appID = (uint) game["appid"].AsUnsignedLong();
|
||||
if (appID == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result[appID] = game["name"].Value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal HashSet<Steam.TradeOffer> GetTradeOffers() {
|
||||
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
return null;
|
||||
}
|
||||
@@ -236,6 +343,7 @@ namespace ArchiSteamFarm {
|
||||
response = iEconService.GetTradeOffers(
|
||||
get_received_offers: 1,
|
||||
active_only: 1,
|
||||
get_descriptions: 1,
|
||||
secure: !Program.GlobalConfig.ForceHttp
|
||||
);
|
||||
} catch (Exception e) {
|
||||
@@ -249,74 +357,93 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Steam.TradeOffer> result = new List<Steam.TradeOffer>();
|
||||
Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>> descriptions = new Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>>();
|
||||
foreach (KeyValue description in response["descriptions"].Children) {
|
||||
ulong classID = description["classid"].AsUnsignedLong();
|
||||
if (classID == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong instanceID = description["instanceid"].AsUnsignedLong();
|
||||
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(classID, instanceID);
|
||||
if (descriptions.ContainsKey(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint appID = 0;
|
||||
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
||||
|
||||
string hashName = description["market_hash_name"].Value;
|
||||
if (!string.IsNullOrEmpty(hashName)) {
|
||||
appID = GetAppIDFromMarketHashName(hashName);
|
||||
}
|
||||
|
||||
string descriptionType = description["type"].Value;
|
||||
if (!string.IsNullOrEmpty(descriptionType)) {
|
||||
type = GetItemType(descriptionType);
|
||||
}
|
||||
|
||||
descriptions[key] = new Tuple<uint, Steam.Item.EType>(appID, type);
|
||||
}
|
||||
|
||||
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
|
||||
foreach (KeyValue trade in response["trade_offers_received"].Children) {
|
||||
// TODO: Correct some of these when SK2 with https://github.com/SteamRE/SteamKit/pull/255 gets released
|
||||
Steam.TradeOffer tradeOffer = new Steam.TradeOffer {
|
||||
tradeofferid = trade["tradeofferid"].AsString(),
|
||||
accountid_other = (uint) trade["accountid_other"].AsUnsignedLong(), // TODO: Correct this when SK2 with https://github.com/SteamRE/SteamKit/pull/255 gets released
|
||||
trade_offer_state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
|
||||
TradeOfferID = trade["tradeofferid"].AsUnsignedLong(),
|
||||
OtherSteamID3 = (uint) trade["accountid_other"].AsUnsignedLong(),
|
||||
State = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
|
||||
};
|
||||
|
||||
foreach (KeyValue item in trade["items_to_give"].Children) {
|
||||
tradeOffer.items_to_give.Add(new Steam.Item {
|
||||
appid = item["appid"].AsString(),
|
||||
contextid = item["contextid"].AsString(),
|
||||
assetid = item["assetid"].AsString(),
|
||||
classid = item["classid"].AsString(),
|
||||
instanceid = item["instanceid"].AsString(),
|
||||
amount = item["amount"].AsString(),
|
||||
});
|
||||
Steam.Item steamItem = new Steam.Item {
|
||||
AppID = (uint) item["appid"].AsUnsignedLong(),
|
||||
ContextID = item["contextid"].AsUnsignedLong(),
|
||||
AssetID = item["assetid"].AsUnsignedLong(),
|
||||
ClassID = item["classid"].AsUnsignedLong(),
|
||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||
};
|
||||
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(steamItem.ClassID, steamItem.InstanceID);
|
||||
|
||||
Tuple<uint, Steam.Item.EType> description;
|
||||
if (descriptions.TryGetValue(key, out description)) {
|
||||
steamItem.RealAppID = description.Item1;
|
||||
steamItem.Type = description.Item2;
|
||||
}
|
||||
|
||||
tradeOffer.ItemsToGive.Add(steamItem);
|
||||
}
|
||||
|
||||
foreach (KeyValue item in trade["items_to_receive"].Children) {
|
||||
tradeOffer.items_to_receive.Add(new Steam.Item {
|
||||
appid = item["appid"].AsString(),
|
||||
contextid = item["contextid"].AsString(),
|
||||
assetid = item["assetid"].AsString(),
|
||||
classid = item["classid"].AsString(),
|
||||
instanceid = item["instanceid"].AsString(),
|
||||
amount = item["amount"].AsString(),
|
||||
});
|
||||
Steam.Item steamItem = new Steam.Item {
|
||||
AppID = (uint) item["appid"].AsUnsignedLong(),
|
||||
ContextID = item["contextid"].AsUnsignedLong(),
|
||||
AssetID = item["assetid"].AsUnsignedLong(),
|
||||
ClassID = item["classid"].AsUnsignedLong(),
|
||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||
};
|
||||
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(steamItem.ClassID, steamItem.InstanceID);
|
||||
|
||||
Tuple<uint, Steam.Item.EType> description;
|
||||
if (descriptions.TryGetValue(key, out description)) {
|
||||
steamItem.RealAppID = description.Item1;
|
||||
steamItem.Type = description.Item2;
|
||||
}
|
||||
|
||||
tradeOffer.ItemsToReceive.Add(steamItem);
|
||||
}
|
||||
|
||||
result.Add(tradeOffer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<bool> JoinClan(ulong clanID) {
|
||||
if (clanID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
||||
if (string.IsNullOrEmpty(sessionID)) {
|
||||
Logging.LogNullError("sessionID");
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/gid/" + clanID;
|
||||
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
||||
{"sessionID", sessionID},
|
||||
{"action", "join"}
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
|
||||
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
||||
if (tradeID == 0) {
|
||||
return false;
|
||||
@@ -334,11 +461,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
|
||||
string request = referer + "/accept";
|
||||
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(3) {
|
||||
{"sessionid", sessionID},
|
||||
{"serverid", "1"},
|
||||
{"tradeofferid", tradeID.ToString()}
|
||||
{ "sessionid", sessionID },
|
||||
{ "serverid", "1" },
|
||||
{ "tradeofferid", tradeID.ToString() }
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
@@ -354,40 +480,120 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<List<Steam.Item>> GetMyTradableInventory() {
|
||||
internal async Task<HashSet<Steam.Item>> GetMyTradableInventory() {
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JObject jObject = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
|
||||
jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/753/6?trading=1").ConfigureAwait(false);
|
||||
}
|
||||
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
||||
|
||||
if (jObject == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
ushort nextPage = 0;
|
||||
while (true) {
|
||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1&start=" + nextPage;
|
||||
|
||||
IEnumerable<JToken> jTokens = jObject.SelectTokens("$.rgInventory.*");
|
||||
if (jTokens == null) {
|
||||
Logging.LogNullError("jTokens", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
JObject jObject = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
|
||||
jObject = await WebBrowser.UrlGetToJObject(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
List<Steam.Item> result = new List<Steam.Item>();
|
||||
foreach (JToken jToken in jTokens) {
|
||||
try {
|
||||
result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString()));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
if (jObject == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
IEnumerable<JToken> descriptions = jObject.SelectTokens("$.rgDescriptions.*");
|
||||
if (descriptions == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>> descriptionMap = new Dictionary<Tuple<ulong, ulong>, Tuple<uint, Steam.Item.EType>>();
|
||||
foreach (JToken description in descriptions) {
|
||||
string classIDString = description["classid"].ToString();
|
||||
if (string.IsNullOrEmpty(classIDString)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong classID;
|
||||
if (!ulong.TryParse(classIDString, out classID) || classID == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string instanceIDString = description["instanceid"].ToString();
|
||||
if (string.IsNullOrEmpty(instanceIDString)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong instanceID;
|
||||
if (!ulong.TryParse(instanceIDString, out instanceID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(classID, instanceID);
|
||||
if (descriptionMap.ContainsKey(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint appID = 0;
|
||||
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
||||
|
||||
string hashName = description["market_hash_name"].ToString();
|
||||
if (!string.IsNullOrEmpty(hashName)) {
|
||||
appID = GetAppIDFromMarketHashName(hashName);
|
||||
}
|
||||
|
||||
string descriptionType = description["type"].ToString();
|
||||
if (!string.IsNullOrEmpty(descriptionType)) {
|
||||
type = GetItemType(descriptionType);
|
||||
}
|
||||
|
||||
descriptionMap[key] = new Tuple<uint, Steam.Item.EType>(appID, type);
|
||||
}
|
||||
|
||||
IEnumerable<JToken> items = jObject.SelectTokens("$.rgInventory.*");
|
||||
if (descriptions == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (JToken item in items) {
|
||||
|
||||
Steam.Item steamItem;
|
||||
|
||||
try {
|
||||
steamItem = JsonConvert.DeserializeObject<Steam.Item>(item.ToString());
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (steamItem == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(steamItem.ClassID, steamItem.InstanceID);
|
||||
|
||||
Tuple<uint, Steam.Item.EType> description;
|
||||
if (descriptionMap.TryGetValue(key, out description)) {
|
||||
steamItem.RealAppID = description.Item1;
|
||||
steamItem.Type = description.Item2;
|
||||
}
|
||||
|
||||
result.Add(steamItem);
|
||||
}
|
||||
|
||||
bool more;
|
||||
if (!bool.TryParse(jObject["more"].ToString(), out more) || !more) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ushort.TryParse(jObject["more_start"].ToString(), out nextPage)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<bool> SendTradeOffer(List<Steam.Item> inventory, ulong partnerID, string token = null) {
|
||||
internal async Task<bool> SendTradeOffer(HashSet<Steam.Item> inventory, ulong partnerID, string token = null) {
|
||||
if (inventory == null || inventory.Count == 0 || partnerID == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -402,39 +608,42 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Steam.TradeOfferRequest> trades = new List<Steam.TradeOfferRequest>(1 + inventory.Count / Trading.MaxItemsPerTrade);
|
||||
HashSet<Steam.TradeOfferRequest> trades = new HashSet<Steam.TradeOfferRequest>();
|
||||
|
||||
Steam.TradeOfferRequest singleTrade = null;
|
||||
for (ushort i = 0; i < inventory.Count; i++) {
|
||||
if (i % Trading.MaxItemsPerTrade == 0) {
|
||||
|
||||
byte itemID = 0;
|
||||
foreach (Steam.Item item in inventory) {
|
||||
if (itemID % Trading.MaxItemsPerTrade == 0) {
|
||||
if (trades.Count >= Trading.MaxTradesPerAccount) {
|
||||
break;
|
||||
}
|
||||
|
||||
singleTrade = new Steam.TradeOfferRequest();
|
||||
trades.Add(singleTrade);
|
||||
itemID = 0;
|
||||
}
|
||||
|
||||
Steam.Item item = inventory[i];
|
||||
singleTrade.me.assets.Add(new Steam.Item() {
|
||||
appid = "753",
|
||||
contextid = "6",
|
||||
amount = item.amount,
|
||||
assetid = item.id
|
||||
singleTrade.ItemsToGive.Assets.Add(new Steam.Item() {
|
||||
AppID = Steam.Item.SteamAppID,
|
||||
ContextID = Steam.Item.SteamContextID,
|
||||
Amount = item.Amount,
|
||||
AssetID = item.AssetID
|
||||
});
|
||||
|
||||
itemID++;
|
||||
}
|
||||
|
||||
string referer = SteamCommunityURL + "/tradeoffer/new";
|
||||
string request = referer + "/send";
|
||||
|
||||
foreach (Steam.TradeOfferRequest trade in trades) {
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(6) {
|
||||
{"sessionid", sessionID},
|
||||
{"serverid", "1"},
|
||||
{"partner", partnerID.ToString()},
|
||||
{"tradeoffermessage", "Sent by ASF"},
|
||||
{"json_tradeoffer", JsonConvert.SerializeObject(trade)},
|
||||
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"}
|
||||
{ "sessionid", sessionID },
|
||||
{ "serverid", "1" },
|
||||
{ "partner", partnerID.ToString() },
|
||||
{ "tradeoffermessage", "Sent by ASF" },
|
||||
{ "json_tradeoffer", JsonConvert.SerializeObject(trade) },
|
||||
{ "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}" }
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
@@ -460,9 +669,11 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/my/badges?p=" + page;
|
||||
|
||||
HtmlDocument htmlDocument = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/badges?p=" + page).ConfigureAwait(false);
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
@@ -482,9 +693,11 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/my/gamecards/" + appID;
|
||||
|
||||
HtmlDocument htmlDocument = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/gamecards/" + appID).ConfigureAwait(false);
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
@@ -500,9 +713,11 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/my/inventory";
|
||||
|
||||
bool result = false;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
|
||||
result = await WebBrowser.UrlGet(SteamCommunityURL + "/my/inventory").ConfigureAwait(false);
|
||||
result = await WebBrowser.UrlHead(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
@@ -513,37 +728,47 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptGift(ulong gid) {
|
||||
if (gid == 0) {
|
||||
return false;
|
||||
private async Task<bool?> IsLoggedIn() {
|
||||
string request = SteamCommunityURL + "/my/profile";
|
||||
|
||||
Uri uri = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && uri == null; i++) {
|
||||
uri = await WebBrowser.UrlHeadToUri(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
||||
if (string.IsNullOrEmpty(sessionID)) {
|
||||
Logging.LogNullError("sessionID");
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/gifts/" + gid + "/acceptunpack";
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
||||
{ "sessionid", sessionID }
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
|
||||
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
if (uri == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !uri.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshSessionIfNeeded() {
|
||||
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
||||
SessionSemaphore.Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool result;
|
||||
|
||||
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
|
||||
if (isLoggedIn.GetValueOrDefault(true)) {
|
||||
result = true;
|
||||
LastSessionRefreshCheck = DateTime.Now;
|
||||
} else {
|
||||
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
|
||||
result = await Bot.RefreshSession().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
SessionSemaphore.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> UnlockParentalAccount(string parentalPin) {
|
||||
@@ -552,16 +777,15 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
|
||||
|
||||
string request = SteamCommunityURL + "/parental/ajaxunlock";
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
||||
{ "pin", parentalPin }
|
||||
};
|
||||
|
||||
string referer = SteamCommunityURL;
|
||||
string request = referer + "/parental/ajaxunlock";
|
||||
|
||||
bool result = false;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
|
||||
result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false);
|
||||
result = await WebBrowser.UrlPost(request, data, SteamCommunityURL).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
|
||||
@@ -33,6 +33,7 @@ using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Bot {
|
||||
@@ -43,6 +44,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 LoginSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
internal readonly string BotName;
|
||||
internal readonly ArchiHandler ArchiHandler;
|
||||
@@ -63,12 +65,12 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal bool KeepRunning { get; private set; }
|
||||
|
||||
private bool InvalidPassword, LoggedInElsewhere;
|
||||
private bool InvalidPassword, LoggedInElsewhere, FirstTradeSent;
|
||||
private string AuthCode, TwoFactorCode;
|
||||
|
||||
internal static async Task RefreshCMs(uint cellID) {
|
||||
bool initialized = false;
|
||||
for (byte i = 0; i < 3 && !initialized; i++) {
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && !initialized; i++) {
|
||||
try {
|
||||
Logging.LogGenericInfo("Refreshing list of CMs...");
|
||||
await SteamDirectory.Initialize(cellID).ConfigureAwait(false);
|
||||
@@ -82,7 +84,7 @@ namespace ArchiSteamFarm {
|
||||
if (initialized) {
|
||||
Logging.LogGenericInfo("Success!");
|
||||
} else {
|
||||
Logging.LogGenericWarning("Failed to initialize list of CMs after 3 tries, ASF will use built-in SK2 list, it may take a while to connect");
|
||||
Logging.LogGenericWarning("Failed to initialize list of CMs after " + WebBrowser.MaxRetries + " tries, ASF will use built-in SK2 list, it may take a while to connect");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +102,16 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Steam keys are offered in many formats: https://support.steampowered.com/kb_article.php?ref=7480-WUSF-3601
|
||||
// It's pointless to implement them all, so we'll just do a simple check if key is supposed to be valid
|
||||
// Every valid key, apart from Prey one has at least two dashes
|
||||
return Utilities.GetCharCountInString(key, '-') >= 2;
|
||||
// This regex should catch all of them, we can always further extend it in future
|
||||
return Regex.IsMatch(key, @"[0-9A-Z]{4,5}-[0-9A-Z]{4,5}-[0-9A-Z]{4,5}-?(?:(?:[0-9A-Z]{4,5}-?)?(?:[0-9A-Z]{4,5}))?");
|
||||
}
|
||||
|
||||
private static async Task LimitLoginRequestsAsync() {
|
||||
await LoginSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
Task.Run(async () => {
|
||||
await Utilities.SleepAsync(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
|
||||
LoginSemaphore.Release();
|
||||
}).Forget();
|
||||
}
|
||||
|
||||
internal Bot(string botName) {
|
||||
@@ -124,26 +133,22 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
lock (Bots) {
|
||||
if (Bots.ContainsKey(botName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bots[botName] = this;
|
||||
}
|
||||
|
||||
SentryFile = botPath + ".bin";
|
||||
|
||||
BotDatabase = BotDatabase.Load(botPath + ".db");
|
||||
if (BotDatabase == null) {
|
||||
Logging.LogGenericError("Bot database could not be loaded, refusing to start this bot instance!", botName);
|
||||
return;
|
||||
}
|
||||
|
||||
bool alreadyExists;
|
||||
lock (Bots) {
|
||||
alreadyExists = Bots.ContainsKey(botName);
|
||||
if (!alreadyExists) {
|
||||
Bots[botName] = this;
|
||||
}
|
||||
}
|
||||
|
||||
if (alreadyExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
SentryFile = botPath + ".bin";
|
||||
|
||||
if (BotDatabase.SteamGuardAccount == null) {
|
||||
// Support and convert SDA files
|
||||
string maFilePath = botPath + ".maFile";
|
||||
@@ -157,8 +162,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized && Directory.Exists(Program.DebugDirectory)) {
|
||||
try {
|
||||
SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
|
||||
Debugging.NetHookAlreadyInitialized = true;
|
||||
SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, botName);
|
||||
}
|
||||
@@ -224,39 +229,58 @@ namespace ArchiSteamFarm {
|
||||
Start().Forget();
|
||||
}
|
||||
|
||||
internal async Task AcceptConfirmations(bool confirm, Confirmation.ConfirmationType allowedConfirmationType = Confirmation.ConfirmationType.Unknown) {
|
||||
internal async Task<bool> AcceptConfirmations(bool confirm, Confirmation.ConfirmationType allowedConfirmationType = Confirmation.ConfirmationType.Unknown) {
|
||||
if (BotDatabase.SteamGuardAccount == null) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
bool result = false;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
|
||||
result = true;
|
||||
|
||||
Confirmation[] confirmations = await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false);
|
||||
if (confirmations == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Confirmation confirmation in confirmations) {
|
||||
if (allowedConfirmationType != Confirmation.ConfirmationType.Unknown && confirmation.ConfType != allowedConfirmationType) {
|
||||
try {
|
||||
if (!await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false)) {
|
||||
result = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (confirm) {
|
||||
BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation);
|
||||
} else {
|
||||
BotDatabase.SteamGuardAccount.DenyConfirmation(confirmation);
|
||||
Confirmation[] confirmations = await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false);
|
||||
if (confirmations == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (Confirmation confirmation in confirmations) {
|
||||
if (allowedConfirmationType != Confirmation.ConfirmationType.Unknown && confirmation.ConfType != allowedConfirmationType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (confirm) {
|
||||
if (!BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!BotDatabase.SteamGuardAccount.DenyConfirmation(confirmation)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SteamGuardAccount.WGTokenInvalidException) {
|
||||
result = false;
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, BotName);
|
||||
return false;
|
||||
}
|
||||
} catch (SteamGuardAccount.WGTokenInvalidException) {
|
||||
Logging.LogGenericWarning("Handling confirmation: Failed!", BotName);
|
||||
Logging.LogGenericWarning("Confirmation could not be accepted because of invalid token exception", BotName);
|
||||
Logging.LogGenericWarning("If issue persists, consider removing and readding ASF 2FA", BotName);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, BotName);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
Logging.LogGenericWTF("Could not accept confirmations even after " + WebBrowser.MaxRetries + " tries", BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void ResetGamesPlayed() {
|
||||
@@ -296,7 +320,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal async Task OnFarmingFinished(bool farmedSomething) {
|
||||
if (farmedSomething && BotConfig.SendOnFarmingFinished) {
|
||||
if ((farmedSomething || !FirstTradeSent) && BotConfig.SendOnFarmingFinished) {
|
||||
FirstTradeSent = true;
|
||||
await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -310,11 +335,11 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!message.StartsWith("!")) {
|
||||
if (message[0] != '!') {
|
||||
return await ResponseRedeem(steamID, message.Replace(",", Environment.NewLine), true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!message.Contains(" ")) {
|
||||
if (message.IndexOf(' ') < 0) {
|
||||
switch (message) {
|
||||
case "!2fa":
|
||||
return Response2FA(steamID);
|
||||
@@ -350,7 +375,7 @@ namespace ArchiSteamFarm {
|
||||
return ResponseUnknown(steamID);
|
||||
}
|
||||
} else {
|
||||
string[] args = message.Split(' ');
|
||||
string[] args = message.Split((char[]) null, StringSplitOptions.RemoveEmptyEntries);
|
||||
switch (args[0]) {
|
||||
case "!2fa":
|
||||
return Response2FA(steamID, args[1]);
|
||||
@@ -368,8 +393,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
case "!farm":
|
||||
return ResponseFarm(steamID, args[1]);
|
||||
case "!help":
|
||||
return ResponseHelp(steamID, args[1]);
|
||||
case "!loot":
|
||||
return await ResponseSendTrade(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!owns":
|
||||
@@ -410,9 +433,9 @@ namespace ArchiSteamFarm {
|
||||
Task.Run(() => HandleCallbacks()).Forget();
|
||||
}
|
||||
|
||||
// 2FA tokens are expiring soon, use limiter only when we don't have any pending
|
||||
if (TwoFactorCode == null) {
|
||||
await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
|
||||
// 2FA tokens are expiring soon, don't use limiter when user is providing one
|
||||
if (TwoFactorCode == null || BotDatabase.SteamGuardAccount != null) {
|
||||
await LimitLoginRequestsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Starting...", BotName);
|
||||
@@ -463,6 +486,7 @@ namespace ArchiSteamFarm {
|
||||
Logging.LogGenericInfo("ASF requires a few more steps to complete authenticator import...", BotName);
|
||||
|
||||
if (!InitializeLoginAndPassword()) {
|
||||
BotDatabase.SteamGuardAccount = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -473,10 +497,12 @@ namespace ArchiSteamFarm {
|
||||
case LoginResult.Need2FA:
|
||||
userLogin.TwoFactorCode = Program.GetUserInput(Program.EUserInputType.TwoFactorAuthentication, BotName);
|
||||
if (string.IsNullOrEmpty(userLogin.TwoFactorCode)) {
|
||||
BotDatabase.SteamGuardAccount = null;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BotDatabase.SteamGuardAccount = null;
|
||||
Logging.LogGenericError("Unhandled situation: " + loginResult, BotName);
|
||||
return;
|
||||
}
|
||||
@@ -520,7 +546,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return await bot.ResponsePause(steamID).ConfigureAwait(false);
|
||||
@@ -533,6 +563,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (CardsFarmer.CurrentGamesFarming.Count > 0) {
|
||||
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm.";
|
||||
} else if (CardsFarmer.ManualMode) {
|
||||
return "Bot " + BotName + " is running in manual mode.";
|
||||
} else if (SteamClient.IsConnected) {
|
||||
return "Bot " + BotName + " is not farming anything.";
|
||||
} else if (KeepRunning) {
|
||||
@@ -549,7 +581,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return bot.ResponseStatus(steamID);
|
||||
@@ -588,12 +624,20 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
|
||||
List<Steam.Item> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
|
||||
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
|
||||
|
||||
if (inventory == null || inventory.Count == 0) {
|
||||
return "Nothing to send, inventory seems empty!";
|
||||
}
|
||||
|
||||
// Remove from our pending inventory all items that are not steam cards and boosters
|
||||
inventory.RemoveWhere(item => item.Type != Steam.Item.EType.TradingCard && item.Type != Steam.Item.EType.FoilTradingCard && item.Type != Steam.Item.EType.BoosterPack);
|
||||
inventory.TrimExcess();
|
||||
|
||||
if (inventory.Count == 0) {
|
||||
return "Nothing to send, inventory seems empty!";
|
||||
}
|
||||
|
||||
if (await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) {
|
||||
await AcceptConfirmations(true, Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
|
||||
return "Trade offer sent successfully!";
|
||||
@@ -609,7 +653,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return await bot.ResponseSendTrade(steamID).ConfigureAwait(false);
|
||||
@@ -635,7 +683,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return bot.Response2FA(steamID);
|
||||
@@ -664,7 +716,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return bot.Response2FAOff(steamID);
|
||||
@@ -690,7 +746,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return await bot.Response2FAConfirm(steamID, confirm).ConfigureAwait(false);
|
||||
@@ -734,7 +794,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return bot.ResponseFarm(steamID);
|
||||
@@ -748,19 +812,6 @@ namespace ArchiSteamFarm {
|
||||
return "https://github.com/" + Program.GithubRepo + "/wiki/Commands";
|
||||
}
|
||||
|
||||
private static string ResponseHelp(ulong steamID, string botName) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(botName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
}
|
||||
|
||||
return bot.ResponseHelp(steamID);
|
||||
}
|
||||
|
||||
private async Task<string> ResponseRedeem(ulong steamID, string message, bool validate) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(message) || !IsMaster(steamID)) {
|
||||
return null;
|
||||
@@ -774,12 +825,16 @@ namespace ArchiSteamFarm {
|
||||
while (!string.IsNullOrEmpty(key) && currentBot != null) {
|
||||
if (validate && !IsValidCdKey(key)) {
|
||||
key = reader.ReadLine(); // Next key
|
||||
continue; // Without changing the bot
|
||||
continue; // Keep current bot
|
||||
}
|
||||
|
||||
if (currentBot.SteamClient.IsConnected) {
|
||||
if (!currentBot.SteamClient.IsConnected) {
|
||||
currentBot = null; // Either bot will be changed, or loop aborted
|
||||
} else {
|
||||
ArchiHandler.PurchaseResponseCallback result = await currentBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
|
||||
if (result != null) {
|
||||
if (result == null) {
|
||||
currentBot = null; // Either bot will be changed, or loop aborted
|
||||
} else {
|
||||
switch (result.PurchaseResult) {
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
|
||||
@@ -787,7 +842,12 @@ namespace ArchiSteamFarm {
|
||||
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + result.PurchaseResult + " | Items: " + string.Join("", result.Items));
|
||||
|
||||
key = reader.ReadLine(); // Next key
|
||||
break; // Next bot (if needed)
|
||||
|
||||
if (result.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
|
||||
break; // Next bot (if needed)
|
||||
} else {
|
||||
continue; // Keep current bot
|
||||
}
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.AlreadyOwned:
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.BaseGameRequired:
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OnCooldown:
|
||||
@@ -861,7 +921,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return await bot.ResponseRedeem(steamID, message, validate).ConfigureAwait(false);
|
||||
@@ -902,7 +966,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private async Task<string> ResponseAddLicense(ulong steamID, HashSet<uint> gameIDs) {
|
||||
if (steamID == 0 || gameIDs == null || gameIDs.Count == 0 || !IsMaster(steamID)) {
|
||||
if (steamID == 0 || gameIDs == null || gameIDs.Count == 0 || !SteamClient.IsConnected || !IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -926,7 +990,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string[] gameIDs = games.Split(',');
|
||||
@@ -952,7 +1020,13 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<uint, string> ownedGames = await ArchiWebHandler.GetOwnedGames().ConfigureAwait(false);
|
||||
Dictionary<uint, string> ownedGames;
|
||||
if (!string.IsNullOrEmpty(BotConfig.SteamApiKey)) {
|
||||
ownedGames = ArchiWebHandler.GetOwnedGames(SteamClient.SteamID);
|
||||
} else {
|
||||
ownedGames = await ArchiWebHandler.GetOwnedGames().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (ownedGames == null || ownedGames.Count == 0) {
|
||||
return "List of owned games is empty!";
|
||||
}
|
||||
@@ -998,7 +1072,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return await bot.ResponseOwns(steamID, query).ConfigureAwait(false);
|
||||
@@ -1031,7 +1109,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string[] gameIDs = games.Split(',');
|
||||
@@ -1072,7 +1154,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return await bot.ResponseStart(steamID).ConfigureAwait(false);
|
||||
@@ -1098,7 +1184,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
if (IsOwner(steamID)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return bot.ResponseStop(steamID);
|
||||
@@ -1157,7 +1247,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private void SendMessageToChannel(ulong steamID, string message) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(message)) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(message) || !SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1168,7 +1258,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private void SendMessageToUser(ulong steamID, string message) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(message)) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(message) || !SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1271,7 +1361,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private void JoinMasterChat() {
|
||||
if (BotConfig.SteamMasterClanID == 0) {
|
||||
if (!SteamClient.IsConnected || BotConfig.SteamMasterClanID == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1370,6 +1460,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Disconnected from Steam!", BotName);
|
||||
|
||||
FirstTradeSent = false;
|
||||
CardsFarmer.StopFarming().Forget();
|
||||
|
||||
// If we initiated disconnect, do not attempt to reconnect
|
||||
@@ -1377,10 +1469,6 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!KeepRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (InvalidPassword) {
|
||||
InvalidPassword = false;
|
||||
if (!string.IsNullOrEmpty(BotDatabase.LoginKey)) { // InvalidPassword means usually that login key has expired, if we used it
|
||||
@@ -1402,11 +1490,15 @@ namespace ArchiSteamFarm {
|
||||
await Utilities.SleepAsync(Program.GlobalConfig.AccountPlayingDelay * 60 * 1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!KeepRunning || SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Reconnecting...", BotName);
|
||||
|
||||
// 2FA tokens are expiring soon, use limiter only when we don't have any pending
|
||||
if (TwoFactorCode == null) {
|
||||
await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
|
||||
// 2FA tokens are expiring soon, don't use limiter when user is providing one
|
||||
if (TwoFactorCode == null || BotDatabase.SteamGuardAccount != null) {
|
||||
await LimitLoginRequestsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
SteamClient.Connect();
|
||||
@@ -1622,14 +1714,14 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (BotConfig.SteamMasterClanID != 0) {
|
||||
Task.Run(async () => {
|
||||
await ArchiWebHandler.JoinClan(BotConfig.SteamMasterClanID).ConfigureAwait(false);
|
||||
await ArchiWebHandler.JoinGroup(BotConfig.SteamMasterClanID).ConfigureAwait(false);
|
||||
JoinMasterChat();
|
||||
}).Forget();
|
||||
}
|
||||
|
||||
if (Program.GlobalConfig.Statistics) {
|
||||
Task.Run(async () => {
|
||||
await ArchiWebHandler.JoinClan(ArchiSCFarmGroup).ConfigureAwait(false);
|
||||
await ArchiWebHandler.JoinGroup(ArchiSCFarmGroup).ConfigureAwait(false);
|
||||
SteamFriends.JoinChat(ArchiSCFarmGroup);
|
||||
}).Forget();
|
||||
}
|
||||
@@ -1707,11 +1799,11 @@ namespace ArchiSteamFarm {
|
||||
bool checkTrades = false;
|
||||
bool markInventory = false;
|
||||
foreach (var notification in callback.Notifications) {
|
||||
switch (notification.NotificationType) {
|
||||
case ArchiHandler.NotificationsCallback.Notification.ENotificationType.Items:
|
||||
switch (notification) {
|
||||
case ArchiHandler.NotificationsCallback.ENotification.Items:
|
||||
markInventory = true;
|
||||
break;
|
||||
case ArchiHandler.NotificationsCallback.Notification.ENotificationType.Trading:
|
||||
case ArchiHandler.NotificationsCallback.ENotification.Trading:
|
||||
checkTrades = true;
|
||||
break;
|
||||
}
|
||||
@@ -1727,7 +1819,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) {
|
||||
if (callback == null) {
|
||||
if (callback == null || callback.OfflineMessagesCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool AcceptGifts { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool SteamTradeMatcher { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool ForwardKeysToOtherBots { get; private set; } = false;
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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 SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage {
|
||||
internal ulong GroupID { get; set; } = 0;
|
||||
internal bool AcceptInvite { get; set; } = true;
|
||||
|
||||
EMsg ISteamSerializableMessage.GetEMsg() {
|
||||
return EMsg.ClientAcknowledgeClanInvite;
|
||||
}
|
||||
|
||||
void ISteamSerializable.Serialize(Stream stream) {
|
||||
if (stream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
BinaryWriter binaryWriter = new BinaryWriter(stream);
|
||||
binaryWriter.Write(GroupID);
|
||||
binaryWriter.Write(AcceptInvite);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ISteamSerializable.Deserialize(Stream stream) {
|
||||
if (stream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
BinaryReader binaryReader = new BinaryReader(stream);
|
||||
GroupID = binaryReader.ReadUInt64();
|
||||
AcceptInvite = binaryReader.ReadBoolean();
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ namespace ArchiSteamFarm {
|
||||
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
|
||||
internal readonly HashSet<uint> CurrentGamesFarming = new HashSet<uint>();
|
||||
|
||||
private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false);
|
||||
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
|
||||
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
|
||||
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer Timer;
|
||||
|
||||
@@ -84,15 +84,15 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
await Semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (NowFarming || ManualMode) {
|
||||
Semaphore.Release(); // We have nothing to do, don't forget to release semaphore
|
||||
FarmingSemaphore.Release(); // We have nothing to do, don't forget to release semaphore
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await IsAnythingToFarm().ConfigureAwait(false)) {
|
||||
Semaphore.Release(); // We have nothing to do, don't forget to release semaphore
|
||||
FarmingSemaphore.Release(); // We have nothing to do, don't forget to release semaphore
|
||||
Logging.LogGenericInfo("We don't have anything to farm on this account!", Bot.BotName);
|
||||
await Bot.OnFarmingFinished(false).ConfigureAwait(false);
|
||||
return;
|
||||
@@ -100,9 +100,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games to farm on this account...", Bot.BotName);
|
||||
NowFarming = true;
|
||||
Semaphore.Release(); // From this point we allow other calls to shut us down
|
||||
|
||||
bool farmedSomething = false;
|
||||
FarmingSemaphore.Release(); // From this point we allow other calls to shut us down
|
||||
|
||||
do {
|
||||
// Now the algorithm used for farming depends on whether account is restricted or not
|
||||
@@ -114,7 +112,6 @@ namespace ArchiSteamFarm {
|
||||
while (gamesToFarmSolo.Count > 0) {
|
||||
uint appID = gamesToFarmSolo.First();
|
||||
if (await FarmSolo(appID).ConfigureAwait(false)) {
|
||||
farmedSomething = true;
|
||||
gamesToFarmSolo.Remove(appID);
|
||||
gamesToFarmSolo.TrimExcess();
|
||||
} else {
|
||||
@@ -135,9 +132,7 @@ namespace ArchiSteamFarm {
|
||||
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
|
||||
while (GamesToFarm.Count > 0) {
|
||||
uint appID = GamesToFarm.Keys.FirstOrDefault();
|
||||
if (await FarmSolo(appID).ConfigureAwait(false)) {
|
||||
farmedSomething = true;
|
||||
} else {
|
||||
if (!await FarmSolo(appID).ConfigureAwait(false)) {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
@@ -150,7 +145,7 @@ namespace ArchiSteamFarm {
|
||||
NowFarming = false;
|
||||
|
||||
Logging.LogGenericInfo("Farming finished!", Bot.BotName);
|
||||
await Bot.OnFarmingFinished(farmedSomething).ConfigureAwait(false);
|
||||
await Bot.OnFarmingFinished(true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task StopFarming() {
|
||||
@@ -158,10 +153,10 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
await Semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (!NowFarming) {
|
||||
Semaphore.Release();
|
||||
FarmingSemaphore.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -179,7 +174,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
FarmResetEvent.Reset();
|
||||
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
|
||||
Semaphore.Release();
|
||||
FarmingSemaphore.Release();
|
||||
}
|
||||
|
||||
internal async Task RestartFarming() {
|
||||
@@ -421,7 +416,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
for (ushort farmingTime = 0; farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) {
|
||||
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
@@ -452,7 +447,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
bool success = true;
|
||||
while (maxHour < 2) {
|
||||
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool AutoUpdates { get; private set; } = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool AutoRestart { get; private set; } = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable;
|
||||
|
||||
|
||||
@@ -27,15 +27,15 @@ using System.Collections.Generic;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class GitHub {
|
||||
internal sealed class Asset {
|
||||
[JsonProperty(PropertyName = "name", Required = Required.Always)]
|
||||
internal string Name { get; private set; }
|
||||
|
||||
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
|
||||
internal string DownloadURL { get; private set; }
|
||||
}
|
||||
|
||||
internal sealed class ReleaseResponse {
|
||||
internal sealed class Asset {
|
||||
[JsonProperty(PropertyName = "name", Required = Required.Always)]
|
||||
internal string Name { get; private set; }
|
||||
|
||||
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
|
||||
internal string DownloadURL { get; private set; }
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)]
|
||||
internal string Tag { get; private set; }
|
||||
|
||||
|
||||
@@ -28,40 +28,161 @@ using System.Collections.Generic;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Steam {
|
||||
internal sealed class Item {
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal string appid { get; set; }
|
||||
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
|
||||
internal const ushort SteamAppID = 753;
|
||||
internal const byte SteamContextID = 6;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal string contextid { get; set; }
|
||||
internal enum EType : byte {
|
||||
Unknown,
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal string assetid { get; set; }
|
||||
BoosterPack,
|
||||
Coupon,
|
||||
Gift,
|
||||
SteamGems,
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal string id {
|
||||
get { return assetid; }
|
||||
set { assetid = value; }
|
||||
Emoticon,
|
||||
FoilTradingCard,
|
||||
ProfileBackground,
|
||||
TradingCard
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.AllowNull)]
|
||||
internal string classid { get; set; }
|
||||
internal uint AppID { get; set; }
|
||||
|
||||
[JsonProperty(Required = Required.AllowNull)]
|
||||
internal string instanceid { get; set; }
|
||||
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)]
|
||||
internal string AppIDString {
|
||||
get {
|
||||
return AppID.ToString();
|
||||
}
|
||||
set {
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal string amount { get; set; }
|
||||
uint result;
|
||||
if (!uint.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppID = result;
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong ContextID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
|
||||
internal string ContextIDString {
|
||||
get {
|
||||
return ContextID.ToString();
|
||||
}
|
||||
set {
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContextID = result;
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong AssetID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
|
||||
internal string AssetIDString {
|
||||
get {
|
||||
return AssetID.ToString();
|
||||
}
|
||||
set {
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AssetID = result;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
|
||||
internal string ID {
|
||||
get { return AssetIDString; }
|
||||
set { AssetIDString = value; }
|
||||
}
|
||||
|
||||
internal ulong ClassID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
|
||||
internal string ClassIDString {
|
||||
get {
|
||||
return ClassID.ToString();
|
||||
}
|
||||
set {
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClassID = result;
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong InstanceID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull)]
|
||||
internal string InstanceIDString {
|
||||
get {
|
||||
return InstanceID.ToString();
|
||||
}
|
||||
set {
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
InstanceID = result;
|
||||
}
|
||||
}
|
||||
|
||||
internal uint Amount { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
|
||||
internal string AmountString {
|
||||
get {
|
||||
return Amount.ToString();
|
||||
}
|
||||
set {
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint result;
|
||||
if (!uint.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Amount = result;
|
||||
}
|
||||
}
|
||||
|
||||
internal uint RealAppID { get; set; }
|
||||
internal EType Type { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class ItemList {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal List<Steam.Item> assets { get; } = new List<Steam.Item>();
|
||||
}
|
||||
|
||||
internal sealed class TradeOffer {
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
|
||||
internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
|
||||
internal enum ETradeOfferState : byte {
|
||||
Unknown,
|
||||
Invalid,
|
||||
@@ -77,46 +198,142 @@ namespace ArchiSteamFarm {
|
||||
OnHold
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal string tradeofferid { get; set; }
|
||||
internal ulong TradeOfferID { get; set; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal uint accountid_other { get; set; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal ETradeOfferState trade_offer_state { get; set; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal List<Steam.Item> items_to_give { get; } = new List<Steam.Item>();
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal List<Steam.Item> items_to_receive { get; } = new List<Steam.Item>();
|
||||
|
||||
// Extra
|
||||
private ulong _OtherSteamID64 = 0;
|
||||
internal ulong OtherSteamID64 {
|
||||
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always)]
|
||||
internal string TradeOfferIDString {
|
||||
get {
|
||||
if (_OtherSteamID64 == 0 && accountid_other != 0) {
|
||||
_OtherSteamID64 = new SteamID(accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
|
||||
return TradeOfferID.ToString();
|
||||
}
|
||||
set {
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return _OtherSteamID64;
|
||||
ulong result;
|
||||
if (!ulong.TryParse(value, out result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TradeOfferID = result;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "accountid_other", Required = Required.Always)]
|
||||
internal uint OtherSteamID3 { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "trade_offer_state", Required = Required.Always)]
|
||||
internal ETradeOfferState State { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "items_to_give", Required = Required.Always)]
|
||||
internal HashSet<Item> ItemsToGive { get; } = new HashSet<Item>();
|
||||
|
||||
[JsonProperty(PropertyName = "items_to_receive", Required = Required.Always)]
|
||||
internal HashSet<Item> ItemsToReceive { get; } = new HashSet<Item>();
|
||||
|
||||
// Extra
|
||||
internal ulong OtherSteamID64 {
|
||||
get {
|
||||
if (OtherSteamID3 == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
OtherSteamID3 = new SteamID(value).AccountID;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsSteamCardsOnlyTradeForUs() {
|
||||
foreach (Item item in ItemsToGive) {
|
||||
if (item.AppID != Item.SteamAppID || item.ContextID != Item.SteamContextID || (item.Type != Item.EType.FoilTradingCard && item.Type != Item.EType.TradingCard)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool IsPotentiallyDupesTradeForUs() {
|
||||
Dictionary<uint, Dictionary<Item.EType, uint>> ItemsToGivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
|
||||
foreach (Item item in ItemsToGive) {
|
||||
Dictionary<Item.EType, uint> ItemsPerType;
|
||||
if (!ItemsToGivePerGame.TryGetValue(item.RealAppID, out ItemsPerType)) {
|
||||
ItemsPerType = new Dictionary<Item.EType, uint>();
|
||||
ItemsPerType[item.Type] = item.Amount;
|
||||
ItemsToGivePerGame[item.RealAppID] = ItemsPerType;
|
||||
} else {
|
||||
uint amount;
|
||||
if (ItemsPerType.TryGetValue(item.Type, out amount)) {
|
||||
ItemsPerType[item.Type] = amount + item.Amount;
|
||||
} else {
|
||||
ItemsPerType[item.Type] = item.Amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<uint, Dictionary<Item.EType, uint>> ItemsToReceivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
|
||||
foreach (Item item in ItemsToReceive) {
|
||||
Dictionary<Item.EType, uint> ItemsPerType;
|
||||
if (!ItemsToReceivePerGame.TryGetValue(item.RealAppID, out ItemsPerType)) {
|
||||
ItemsPerType = new Dictionary<Item.EType, uint>();
|
||||
ItemsPerType[item.Type] = item.Amount;
|
||||
ItemsToReceivePerGame[item.RealAppID] = ItemsPerType;
|
||||
} else {
|
||||
uint amount;
|
||||
if (ItemsPerType.TryGetValue(item.Type, out amount)) {
|
||||
ItemsPerType[item.Type] = amount + item.Amount;
|
||||
} else {
|
||||
ItemsPerType[item.Type] = item.Amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that amount of items to give is at least amount of items to receive (per game and per type)
|
||||
foreach (KeyValuePair<uint, Dictionary<Item.EType, uint>> ItemsPerGame in ItemsToGivePerGame) {
|
||||
Dictionary<Item.EType, uint> otherItemsPerType;
|
||||
if (!ItemsToReceivePerGame.TryGetValue(ItemsPerGame.Key, out otherItemsPerType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Item.EType, uint> ItemsPerType in ItemsPerGame.Value) {
|
||||
uint otherAmount;
|
||||
if (!otherItemsPerType.TryGetValue(ItemsPerType.Key, out otherAmount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ItemsPerType.Value > otherAmount) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TradeOfferRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal bool newversion { get; } = true;
|
||||
internal sealed class ItemList {
|
||||
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
|
||||
internal HashSet<Item> Assets { get; } = new HashSet<Item>();
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal int version { get; } = 2;
|
||||
[JsonProperty(PropertyName = "newversion", Required = Required.Always)]
|
||||
internal bool NewVersion { get; } = true;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal Steam.ItemList me { get; } = new Steam.ItemList();
|
||||
[JsonProperty(PropertyName = "version", Required = Required.Always)]
|
||||
internal byte Version { get; } = 2;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal Steam.ItemList them { get; } = new Steam.ItemList();
|
||||
[JsonProperty(PropertyName = "me", Required = Required.Always)]
|
||||
internal ItemList ItemsToGive { get; } = new ItemList();
|
||||
|
||||
[JsonProperty(PropertyName = "them", Required = Required.Always)]
|
||||
internal ItemList ItemsToReceive { get; } = new ItemList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,16 +38,21 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (LogToFile) {
|
||||
lock (FileLock) {
|
||||
if (!LogToFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
File.Delete(Program.LogFile);
|
||||
} catch (Exception e) {
|
||||
LogToFile = false;
|
||||
LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
@@ -55,7 +60,7 @@ namespace ArchiSteamFarm {
|
||||
Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?");
|
||||
}
|
||||
|
||||
internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
@@ -63,7 +68,7 @@ namespace ArchiSteamFarm {
|
||||
Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
|
||||
if (exception == null) {
|
||||
return;
|
||||
}
|
||||
@@ -76,7 +81,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +89,7 @@ namespace ArchiSteamFarm {
|
||||
Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
@@ -92,7 +97,7 @@ namespace ArchiSteamFarm {
|
||||
Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(nullObjectName)) {
|
||||
return;
|
||||
}
|
||||
@@ -101,7 +106,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
@@ -125,6 +130,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (LogToFile) {
|
||||
lock (FileLock) {
|
||||
if (!LogToFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
File.AppendAllText(Program.LogFile, loggedMessage);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace ArchiSteamFarm {
|
||||
WCFHostname
|
||||
}
|
||||
|
||||
internal enum EMode : byte {
|
||||
private enum EMode : byte {
|
||||
Unknown,
|
||||
Normal, // Standard most common usage
|
||||
Client, // WCF client only
|
||||
@@ -65,13 +65,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
|
||||
|
||||
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
|
||||
internal static readonly Version Version = Assembly.GetName().Version;
|
||||
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
|
||||
|
||||
private static readonly object ConsoleLock = new object();
|
||||
private static readonly SemaphoreSlim SteamSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false);
|
||||
private static readonly string ExecutableFile = Assembly.Location;
|
||||
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
|
||||
private static readonly string ExecutableFile = Assembly.GetEntryAssembly().Location;
|
||||
private static readonly string ExecutableName = Path.GetFileName(ExecutableFile);
|
||||
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
|
||||
private static readonly WCF WCF = new WCF();
|
||||
@@ -185,7 +183,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
GitHub.Asset binaryAsset = null;
|
||||
GitHub.ReleaseResponse.Asset binaryAsset = null;
|
||||
foreach (var asset in releaseResponse.Assets) {
|
||||
if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase)) {
|
||||
continue;
|
||||
@@ -251,10 +249,17 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Update process is finished! ASF will now restart itself...");
|
||||
await Utilities.SleepAsync(5000);
|
||||
Logging.LogGenericInfo("Update process finished!");
|
||||
|
||||
Restart();
|
||||
if (GlobalConfig.AutoRestart) {
|
||||
Logging.LogGenericInfo("Restarting...");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
Restart();
|
||||
} else {
|
||||
Logging.LogGenericInfo("Exiting...");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Exit(int exitCode = 0) {
|
||||
@@ -272,14 +277,6 @@ namespace ArchiSteamFarm {
|
||||
Exit();
|
||||
}
|
||||
|
||||
internal static async Task LimitSteamRequestsAsync() {
|
||||
await SteamSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
Task.Run(async () => {
|
||||
await Utilities.SleepAsync(GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
|
||||
SteamSemaphore.Release();
|
||||
}).Forget();
|
||||
}
|
||||
|
||||
internal static string GetUserInput(EUserInputType userInputType, string botName = null, string extraInformation = null) {
|
||||
if (userInputType == EUserInputType.Unknown) {
|
||||
return null;
|
||||
@@ -500,7 +497,9 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot bot = new Bot(botName);
|
||||
if (bot.BotConfig != null && bot.BotConfig.Enabled) {
|
||||
isRunning = true;
|
||||
if (bot.BotConfig.StartOnLaunch) {
|
||||
isRunning = true;
|
||||
}
|
||||
} else {
|
||||
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
|
||||
}
|
||||
@@ -516,7 +515,7 @@ namespace ArchiSteamFarm {
|
||||
Init(args);
|
||||
|
||||
// Wait for signal to shutdown
|
||||
ShutdownResetEvent.WaitOne();
|
||||
ShutdownResetEvent.Wait();
|
||||
|
||||
// We got a signal to shutdown
|
||||
Exit();
|
||||
|
||||
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("2.0.3.2")]
|
||||
[assembly: AssemblyFileVersion("2.0.3.2")]
|
||||
[assembly: AssemblyVersion("2.0.4.3")]
|
||||
[assembly: AssemblyFileVersion("2.0.4.3")]
|
||||
|
||||
@@ -57,16 +57,12 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal async Task CheckTrades() {
|
||||
bool shouldRun = false;
|
||||
lock (TradesSemaphore) {
|
||||
if (ParsingTasks < 2) {
|
||||
ParsingTasks++;
|
||||
shouldRun = true;
|
||||
if (ParsingTasks >= 2) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldRun) {
|
||||
return;
|
||||
ParsingTasks++;
|
||||
}
|
||||
|
||||
await TradesSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
@@ -80,8 +76,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private async Task ParseActiveTrades() {
|
||||
List<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
|
||||
if (tradeOffers == null) {
|
||||
HashSet<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
|
||||
if (tradeOffers == null || tradeOffers.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -90,32 +86,27 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
|
||||
if (tradeOffer == null || tradeOffer.trade_offer_state != Steam.TradeOffer.ETradeOfferState.Active) {
|
||||
if (tradeOffer == null || tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong tradeID;
|
||||
if (!ulong.TryParse(tradeOffer.tradeofferid, out tradeID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldAcceptTrade(tradeOffer)) {
|
||||
Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName);
|
||||
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
|
||||
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
|
||||
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
} else {
|
||||
Logging.LogGenericInfo("Ignoring trade: " + tradeID, Bot.BotName);
|
||||
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
|
||||
private async Task<bool> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
|
||||
if (tradeOffer == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always accept trades when we're not losing anything
|
||||
if (tradeOffer.items_to_give.Count == 0) {
|
||||
if (tradeOffer.ItemsToGive.Count == 0) {
|
||||
// Unless it's steam fuckup and we're dealing with broken trade
|
||||
return tradeOffer.items_to_receive.Count > 0;
|
||||
return tradeOffer.ItemsToReceive.Count > 0;
|
||||
}
|
||||
|
||||
// Always accept trades from SteamMasterID
|
||||
@@ -123,10 +114,85 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Add optional SteamTradeMatcher integration here
|
||||
// If we don't have SteamTradeMatcher enabled, this is the end for us
|
||||
if (!Bot.BotConfig.SteamTradeMatcher) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no rule above matched this trade, reject it
|
||||
return false;
|
||||
// Decline trade if we're giving more count-wise
|
||||
if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
|
||||
if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// At this point we're sure that STM trade is valid
|
||||
// Now check if it's worth for us to do the trade
|
||||
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
|
||||
if (inventory == null || inventory.Count == 0) {
|
||||
return true; // OK, assume that this trade is valid, we can't check our EQ
|
||||
}
|
||||
|
||||
// Get appIDs we're interested in
|
||||
HashSet<uint> appIDs = new HashSet<uint>();
|
||||
foreach (Steam.Item item in tradeOffer.ItemsToGive) {
|
||||
appIDs.Add(item.RealAppID);
|
||||
}
|
||||
|
||||
// Now remove from our inventory all items we're NOT interested in
|
||||
inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID));
|
||||
inventory.TrimExcess();
|
||||
|
||||
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
|
||||
if (inventory.Count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Now let's create a map which maps items to their amount in our EQ
|
||||
Dictionary<Tuple<ulong, ulong>, uint> amountMap = new Dictionary<Tuple<ulong, ulong>, uint>();
|
||||
foreach (Steam.Item item in inventory) {
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(item.ClassID, item.InstanceID);
|
||||
|
||||
uint amount;
|
||||
if (amountMap.TryGetValue(key, out amount)) {
|
||||
amountMap[key] = amount + item.Amount;
|
||||
} else {
|
||||
amountMap[key] = item.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate our value of items to give
|
||||
uint itemsToGiveDupesValue = 0;
|
||||
foreach (Steam.Item item in tradeOffer.ItemsToGive) {
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(item.ClassID, item.InstanceID);
|
||||
|
||||
uint amount;
|
||||
if (!amountMap.TryGetValue(key, out amount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
itemsToGiveDupesValue += amount;
|
||||
}
|
||||
|
||||
// Calculate our value of items to receive
|
||||
uint itemsToReceiveDupesValue = 0;
|
||||
foreach (Steam.Item item in tradeOffer.ItemsToReceive) {
|
||||
Tuple<ulong, ulong> key = new Tuple<ulong, ulong>(item.ClassID, item.InstanceID);
|
||||
|
||||
uint amount;
|
||||
if (!amountMap.TryGetValue(key, out amount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
itemsToReceiveDupesValue += amount;
|
||||
}
|
||||
|
||||
// Trade is worth for us if we're in total trading more of our dupes for less of our dupes (or at least same amount)
|
||||
// Which means that itemsToGiveDupesValue should be greater than itemsToReceiveDupesValue
|
||||
return itemsToGiveDupesValue > itemsToReceiveDupesValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ namespace ArchiSteamFarm {
|
||||
internal static void Forget(this Task task) { }
|
||||
|
||||
internal static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
|
||||
if (action == null) {
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return Task.WhenAll(sequence.Select(action));
|
||||
}
|
||||
|
||||
@@ -64,20 +68,5 @@ namespace ArchiSteamFarm {
|
||||
|
||||
return Task.Delay(miliseconds);
|
||||
}
|
||||
|
||||
internal static uint GetCharCountInString(string s, char c) {
|
||||
if (string.IsNullOrEmpty(s)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint count = 0;
|
||||
foreach (char singleChar in s) {
|
||||
if (singleChar == c) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +122,13 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Logging.LogGenericInfo("Received command: " + input);
|
||||
|
||||
string command = '!' + input;
|
||||
string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
|
||||
string output;
|
||||
if (Program.GlobalConfig.SteamOwnerID == 0) {
|
||||
output = "Refusing to handle request because SteamOwnerID is not set!";
|
||||
} else {
|
||||
string command = '!' + input;
|
||||
output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Answered to command: " + input + " with: " + output);
|
||||
return output;
|
||||
|
||||
@@ -83,23 +83,27 @@ namespace ArchiSteamFarm {
|
||||
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
|
||||
}
|
||||
|
||||
internal async Task<bool> UrlGet(string request, string referer = null) {
|
||||
internal async Task<bool> UrlHead(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using (HttpResponseMessage response = await UrlGetToResponse(request, referer).ConfigureAwait(false)) {
|
||||
using (HttpResponseMessage response = await UrlHeadToResponse(request, referer).ConfigureAwait(false)) {
|
||||
return response != null;
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) {
|
||||
internal async Task<Uri> UrlHeadToUri(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
using (HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false)) {
|
||||
return response != null;
|
||||
using (HttpResponseMessage response = await UrlHeadToResponse(request, referer).ConfigureAwait(false)) {
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.RequestMessage.RequestUri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,10 +145,8 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
content = WebUtility.HtmlDecode(content);
|
||||
HtmlDocument htmlDocument = new HtmlDocument();
|
||||
htmlDocument.LoadHtml(content);
|
||||
|
||||
htmlDocument.LoadHtml(WebUtility.HtmlDecode(content));
|
||||
return htmlDocument;
|
||||
}
|
||||
|
||||
@@ -192,6 +194,16 @@ namespace ArchiSteamFarm {
|
||||
return xmlDocument;
|
||||
}
|
||||
|
||||
internal async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using (HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false)) {
|
||||
return response != null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
@@ -200,6 +212,14 @@ namespace ArchiSteamFarm {
|
||||
return await UrlRequest(request, HttpMethod.Get, null, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> UrlHeadToResponse(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Head, null, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> UrlPostToResponse(string request, Dictionary<string, string> data = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
@@ -213,7 +233,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (request.StartsWith("https://") && Program.GlobalConfig.ForceHttp) {
|
||||
if (request.StartsWith("https://", StringComparison.Ordinal) && Program.GlobalConfig.ForceHttp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -239,15 +259,17 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (responseMessage == null || !responseMessage.IsSuccessStatusCode) {
|
||||
if (responseMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!responseMessage.IsSuccessStatusCode) {
|
||||
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
|
||||
Logging.LogGenericError("Request: " + request + " failed!", Identifier);
|
||||
if (responseMessage != null) {
|
||||
Logging.LogGenericError("Status code: " + responseMessage.StatusCode, Identifier);
|
||||
Logging.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false), Identifier);
|
||||
responseMessage.Dispose();
|
||||
}
|
||||
Logging.LogGenericError("Status code: " + responseMessage.StatusCode, Identifier);
|
||||
Logging.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false), Identifier);
|
||||
}
|
||||
responseMessage.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"Debug": false,
|
||||
"Headless": false,
|
||||
"AutoUpdates": true,
|
||||
"AutoRestart": true,
|
||||
"UpdateChannel": 1,
|
||||
"SteamProtocol": 6,
|
||||
"SteamOwnerID": 0,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"FarmOffline": false,
|
||||
"HandleOfflineMessages": false,
|
||||
"AcceptGifts": false,
|
||||
"SteamTradeMatcher": false,
|
||||
"ForwardKeysToOtherBots": false,
|
||||
"DistributeKeys": false,
|
||||
"UseAsfAsMobileAuthenticator": false,
|
||||
|
||||
@@ -28,7 +28,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
internal class ASFConfig {
|
||||
internal abstract class ASFConfig {
|
||||
internal static readonly HashSet<ASFConfig> ASFConfigs = new HashSet<ASFConfig>();
|
||||
|
||||
internal string FilePath { get; set; }
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
@@ -39,6 +40,7 @@ namespace ConfigGenerator {
|
||||
public string SteamLogin { get; set; } = null;
|
||||
|
||||
[JsonProperty]
|
||||
[PasswordPropertyText(true)]
|
||||
public string SteamPassword { get; set; } = null;
|
||||
|
||||
[JsonProperty]
|
||||
@@ -68,6 +70,9 @@ namespace ConfigGenerator {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool AcceptGifts { get; set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SteamTradeMatcher { get; set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool ForwardKeysToOtherBots { get; set; } = false;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
internal class ConfigPage : TabPage {
|
||||
internal sealed class ConfigPage : TabPage {
|
||||
internal readonly ASFConfig ASFConfig;
|
||||
|
||||
internal ConfigPage(ASFConfig config) {
|
||||
|
||||
@@ -27,8 +27,8 @@ using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
class DialogBox {
|
||||
public static DialogResult InputBox(string title, string promptText, out string value) {
|
||||
internal sealed class DialogBox {
|
||||
internal static DialogResult InputBox(string title, string promptText, out string value) {
|
||||
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(promptText)) {
|
||||
value = null;
|
||||
return DialogResult.Abort;
|
||||
@@ -75,7 +75,7 @@ namespace ConfigGenerator {
|
||||
return dialogResult;
|
||||
}
|
||||
|
||||
public static DialogResult YesNoBox(string title, string promptText) {
|
||||
internal static DialogResult YesNoBox(string title, string promptText) {
|
||||
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(promptText)) {
|
||||
return DialogResult.Abort;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ namespace ConfigGenerator {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool AutoUpdates { get; set; } = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool AutoRestart { get; set; } = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
public partial class MainForm : Form {
|
||||
internal sealed partial class MainForm : Form {
|
||||
private const byte ReservedTabs = 3;
|
||||
|
||||
private readonly TabPage NewTab = new TabPage { Text = "+" };
|
||||
@@ -40,7 +40,7 @@ namespace ConfigGenerator {
|
||||
private ConfigPage ASFTab;
|
||||
private TabPage OldTab;
|
||||
|
||||
public MainForm() {
|
||||
internal MainForm() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace ConfigGenerator {
|
||||
|
||||
private const string ASFDirectory = "ArchiSteamFarm";
|
||||
|
||||
private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
|
||||
9
appveyor.yml
Normal file
9
appveyor.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 1.0.{build}-{branch}
|
||||
image: Visual Studio 2015
|
||||
configuration: Release
|
||||
platform: Any CPU
|
||||
clone_depth: 10
|
||||
build:
|
||||
project: ArchiSteamFarm.sln
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
Reference in New Issue
Block a user