2015-10-28 19:21:27 +01:00
|
|
|
|
/*
|
|
|
|
|
|
_ _ _ ____ _ _____
|
|
|
|
|
|
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
|
|
|
|
|
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
|
|
|
|
|
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
|
|
|
|
|
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
|
|
|
|
|
|
2016-01-16 04:21:36 +01:00
|
|
|
|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
2015-10-28 19:21:27 +01:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2016-01-13 11:17:58 +02:00
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
2015-10-28 19:21:27 +01:00
|
|
|
|
using HtmlAgilityPack;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
using SteamKit2;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2016-05-30 01:57:06 +02:00
|
|
|
|
using System.Linq;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2016-03-15 04:20:28 +01:00
|
|
|
|
using System.Xml;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
using System.Threading;
|
2016-05-13 06:32:42 +02:00
|
|
|
|
using ArchiSteamFarm.JSON;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
|
|
|
|
|
namespace ArchiSteamFarm {
|
2015-11-27 16:25:03 +01:00
|
|
|
|
internal sealed class ArchiWebHandler {
|
2016-04-24 23:32:23 +02:00
|
|
|
|
private const string SteamCommunityHost = "steamcommunity.com";
|
2016-03-29 14:33:05 +02:00
|
|
|
|
private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds
|
2016-03-13 20:19:52 +01:00
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
private static string SteamCommunityURL = "https://" + SteamCommunityHost;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000;
|
2015-11-20 14:47:16 +01:00
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
private readonly Bot Bot;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
|
2016-04-12 16:58:45 +02:00
|
|
|
|
private readonly WebBrowser WebBrowser;
|
2016-03-27 23:07:00 +02:00
|
|
|
|
|
2016-06-20 13:45:12 +02:00
|
|
|
|
internal bool Ready { get; private set; }
|
|
|
|
|
|
|
2016-06-19 05:40:46 +02:00
|
|
|
|
private ulong SteamID;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
private DateTime LastSessionRefreshCheck = DateTime.MinValue;
|
2015-12-18 15:50:10 +01:00
|
|
|
|
|
2016-03-06 23:28:56 +01:00
|
|
|
|
internal static void Init() {
|
|
|
|
|
|
Timeout = Program.GlobalConfig.HttpTimeout * 1000;
|
2016-04-24 23:32:23 +02:00
|
|
|
|
SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunityHost;
|
2016-03-06 23:28:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-21 00:51:22 +02:00
|
|
|
|
private static uint GetAppIDFromMarketHashName(string hashName) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(hashName)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(hashName));
|
2016-04-21 00:51:22 +02:00
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int index = hashName.IndexOf('-');
|
2016-06-30 18:13:22 +02:00
|
|
|
|
if (index <= 0) {
|
2016-04-21 00:51:22 +02:00
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint appID;
|
2016-06-20 15:02:26 +02:00
|
|
|
|
return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Steam.Item.EType GetItemType(string name) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(name)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(name));
|
2016-04-21 00:51:22 +02:00
|
|
|
|
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;
|
2016-05-13 06:32:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
|
2016-04-21 00:51:22 +02:00
|
|
|
|
return Steam.Item.EType.FoilTradingCard;
|
2016-05-13 06:32:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (name.EndsWith("Profile Background", StringComparison.Ordinal)) {
|
2016-04-21 00:51:22 +02:00
|
|
|
|
return Steam.Item.EType.ProfileBackground;
|
|
|
|
|
|
}
|
2016-05-13 06:32:42 +02:00
|
|
|
|
|
|
|
|
|
|
return name.EndsWith("Trading Card", StringComparison.Ordinal) ? Steam.Item.EType.TradingCard : Steam.Item.EType.Unknown;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-01 16:33:27 +02:00
|
|
|
|
private static bool ParseItems(Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptions, List<KeyValue> input, HashSet<Steam.Item> output) {
|
2016-07-01 16:34:32 +02:00
|
|
|
|
if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
|
2016-07-01 16:33:27 +02:00
|
|
|
|
Logging.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (KeyValue item in input) {
|
2016-07-08 07:11:37 +02:00
|
|
|
|
uint appID = item["appid"].AsUnsignedInteger();
|
2016-07-01 16:33:27 +02:00
|
|
|
|
if (appID == 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(appID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ulong contextID = item["contextid"].AsUnsignedLong();
|
|
|
|
|
|
if (contextID == 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(contextID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ulong classID = item["classid"].AsUnsignedLong();
|
|
|
|
|
|
if (classID == 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(classID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-08 07:11:37 +02:00
|
|
|
|
uint amount = item["amount"].AsUnsignedInteger();
|
2016-07-01 16:33:27 +02:00
|
|
|
|
if (amount == 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(amount));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint realAppID = 0;
|
|
|
|
|
|
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
|
|
|
|
|
|
|
|
|
|
|
Tuple<uint, Steam.Item.EType> description;
|
|
|
|
|
|
if (descriptions.TryGetValue(classID, out description)) {
|
|
|
|
|
|
realAppID = description.Item1;
|
|
|
|
|
|
type = description.Item2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Steam.Item steamItem = new Steam.Item(appID, contextID, classID, amount, realAppID, type);
|
|
|
|
|
|
output.Add(steamItem);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-06 02:20:41 +01:00
|
|
|
|
internal ArchiWebHandler(Bot bot) {
|
2016-03-11 02:07:20 +01:00
|
|
|
|
if (bot == null) {
|
2016-05-13 06:32:42 +02:00
|
|
|
|
throw new ArgumentNullException(nameof(bot));
|
2016-03-11 02:07:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
Bot = bot;
|
2016-04-12 16:58:45 +02:00
|
|
|
|
|
|
|
|
|
|
WebBrowser = new WebBrowser(bot.BotName);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-20 13:45:12 +02:00
|
|
|
|
internal void OnDisconnected() => Ready = false;
|
|
|
|
|
|
|
2016-06-25 04:05:30 +02:00
|
|
|
|
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
|
|
|
|
|
|
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce)) {
|
|
|
|
|
|
Logging.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce), Bot.BotName);
|
2015-12-25 17:49:00 +01:00
|
|
|
|
return false;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-25 04:05:30 +02:00
|
|
|
|
SteamID = steamID;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-06-30 18:13:22 +02:00
|
|
|
|
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
|
|
|
|
|
// Generate an AES session key
|
2016-06-28 04:32:48 +02:00
|
|
|
|
byte[] sessionKey = SteamKit2.CryptoHelper.GenerateRandomBlock(32);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
|
|
|
|
|
// RSA encrypt it with the public key for the universe we're on
|
2016-03-10 01:20:17 +01:00
|
|
|
|
byte[] cryptedSessionKey;
|
2016-06-25 04:05:30 +02:00
|
|
|
|
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) {
|
2015-10-25 06:16:50 +01:00
|
|
|
|
cryptedSessionKey = rsa.Encrypt(sessionKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Copy our login key
|
2016-02-20 23:00:30 +01:00
|
|
|
|
byte[] loginKey = new byte[webAPIUserNonce.Length];
|
2015-10-25 06:16:50 +01:00
|
|
|
|
Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
|
|
|
|
|
|
|
|
|
|
|
|
// AES encrypt the loginkey with our session key
|
2016-06-28 04:32:48 +02:00
|
|
|
|
byte[] cryptedLoginKey = SteamKit2.CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-02-20 23:00:30 +01:00
|
|
|
|
// Do the magic
|
2016-01-24 18:08:27 +01:00
|
|
|
|
Logging.LogGenericInfo("Logging in to ISteamUserAuth...", Bot.BotName);
|
2016-02-20 23:00:30 +01:00
|
|
|
|
|
|
|
|
|
|
KeyValue authResult;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) {
|
|
|
|
|
|
iSteamUserAuth.Timeout = Timeout;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
authResult = iSteamUserAuth.AuthenticateUser(
|
2016-06-30 18:13:22 +02:00
|
|
|
|
steamid: steamID,
|
2015-10-25 06:16:50 +01:00
|
|
|
|
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
|
|
|
|
|
|
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
|
|
|
|
|
|
method: WebRequestMethods.Http.Post,
|
2016-03-13 20:19:52 +01:00
|
|
|
|
secure: !Program.GlobalConfig.ForceHttp
|
2015-10-25 06:16:50 +01:00
|
|
|
|
);
|
|
|
|
|
|
} catch (Exception e) {
|
2016-01-24 18:08:27 +01:00
|
|
|
|
Logging.LogGenericException(e, Bot.BotName);
|
2015-12-25 17:49:00 +01:00
|
|
|
|
return false;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (authResult == null) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(authResult), Bot.BotName);
|
2015-12-25 17:49:00 +01:00
|
|
|
|
return false;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-12 07:44:59 +02:00
|
|
|
|
Logging.LogGenericInfo("Success!", Bot.BotName);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-04-12 07:40:02 +02:00
|
|
|
|
string steamLogin = authResult["token"].Value;
|
2016-04-24 23:32:23 +02:00
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-04-12 07:40:02 +02:00
|
|
|
|
string steamLoginSecure = authResult["tokensecure"].Value;
|
2016-04-24 23:32:23 +02:00
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
|
2016-02-28 21:24:50 +01:00
|
|
|
|
|
2016-06-20 13:45:12 +02:00
|
|
|
|
// Unlock Steam Parental if needed
|
|
|
|
|
|
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
|
2016-03-29 14:33:05 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-03-27 23:07:00 +02:00
|
|
|
|
|
2016-06-20 13:45:12 +02:00
|
|
|
|
Ready = true;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
LastSessionRefreshCheck = DateTime.Now;
|
2015-12-25 17:49:00 +01:00
|
|
|
|
return true;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 10:40:36 +02:00
|
|
|
|
internal async Task<bool> AcceptGift(ulong gid) {
|
|
|
|
|
|
if (gid == 0) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(gid), Bot.BotName);
|
2016-04-25 10:40:36 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(sessionID), Bot.BotName);
|
2016-04-25 10:40:36 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string request = SteamCommunityURL + "/gifts/" + gid + "/acceptunpack";
|
|
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
|
|
|
|
|
{ "sessionid", sessionID }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
|
2016-04-25 10:40:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
internal async Task<bool> JoinGroup(ulong groupID) {
|
|
|
|
|
|
if (groupID == 0) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(groupID), Bot.BotName);
|
2016-04-24 23:32:23 +02:00
|
|
|
|
return false;
|
2016-01-14 02:48:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
2015-12-16 22:05:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(sessionID), Bot.BotName);
|
2016-04-24 23:32:23 +02:00
|
|
|
|
return false;
|
2015-12-16 22:05:42 +01:00
|
|
|
|
}
|
2015-12-16 22:14:53 +01:00
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
string request = SteamCommunityURL + "/gid/" + groupID;
|
|
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
|
|
|
|
|
{ "sessionID", sessionID },
|
|
|
|
|
|
{ "action", "join" }
|
|
|
|
|
|
};
|
2016-03-29 14:33:05 +02:00
|
|
|
|
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
|
2015-12-16 22:05:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-19 05:40:46 +02:00
|
|
|
|
internal async Task<HtmlDocument> GetConfirmations(string deviceID, string confirmationHash, uint time) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0)) {
|
2016-06-19 09:34:09 +02:00
|
|
|
|
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time), Bot.BotName);
|
2016-06-19 05:40:46 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-20 13:09:27 +02:00
|
|
|
|
string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
|
2016-06-19 05:40:46 +02:00
|
|
|
|
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(string deviceID, string confirmationHash, uint time, uint confirmationID) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0)) {
|
|
|
|
|
|
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID), Bot.BotName);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-19 09:34:09 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-19 05:40:46 +02:00
|
|
|
|
string request = SteamCommunityURL + "/mobileconf/details/" + confirmationID + "?p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
|
|
|
|
|
|
|
|
|
|
|
|
string json = await WebBrowser.UrlGetToContentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
if (string.IsNullOrEmpty(json)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Steam.ConfirmationDetails response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = JsonConvert.DeserializeObject<Steam.ConfirmationDetails>(json);
|
|
|
|
|
|
} catch (JsonException e) {
|
|
|
|
|
|
Logging.LogGenericException(e, Bot.BotName);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-20 06:51:42 +02:00
|
|
|
|
if (response == null) {
|
|
|
|
|
|
Logging.LogNullError(nameof(response), Bot.BotName);
|
|
|
|
|
|
return null;
|
2016-06-19 05:40:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-20 06:51:42 +02:00
|
|
|
|
response.ConfirmationID = confirmationID;
|
|
|
|
|
|
return response;
|
2016-06-19 05:40:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal async Task<bool> HandleConfirmation(string deviceID, string confirmationHash, uint time, uint confirmationID, ulong confirmationKey, bool accept) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0) || (confirmationKey == 0)) {
|
|
|
|
|
|
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey), Bot.BotName);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-19 09:34:09 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-19 05:40:46 +02:00
|
|
|
|
string request = SteamCommunityURL + "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey;
|
|
|
|
|
|
|
|
|
|
|
|
string json = await WebBrowser.UrlGetToContentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
if (string.IsNullOrEmpty(json)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Steam.ConfirmationResponse response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = JsonConvert.DeserializeObject<Steam.ConfirmationResponse>(json);
|
|
|
|
|
|
} catch (JsonException e) {
|
|
|
|
|
|
Logging.LogGenericException(e, Bot.BotName);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response != null) {
|
|
|
|
|
|
return response.Success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Logging.LogNullError(nameof(response), Bot.BotName);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-15 04:20:28 +01:00
|
|
|
|
internal async Task<Dictionary<uint, string>> GetOwnedGames() {
|
2016-03-29 14:33:05 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-29 14:48:31 +02:00
|
|
|
|
string request = SteamCommunityURL + "/my/games/?xml=1";
|
2016-03-15 04:20:28 +01:00
|
|
|
|
|
2016-05-30 01:57:06 +02:00
|
|
|
|
XmlDocument response = await WebBrowser.UrlGetToXMLRetry(request).ConfigureAwait(false);
|
2016-03-15 04:20:28 +01:00
|
|
|
|
if (response == null) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
XmlNodeList xmlNodeList = response.SelectNodes("gamesList/games/game");
|
2016-05-13 06:32:42 +02:00
|
|
|
|
if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) {
|
2016-03-15 04:20:28 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Dictionary<uint, string> result = new Dictionary<uint, string>(xmlNodeList.Count);
|
|
|
|
|
|
foreach (XmlNode xmlNode in xmlNodeList) {
|
|
|
|
|
|
XmlNode appNode = xmlNode.SelectSingleNode("appID");
|
|
|
|
|
|
if (appNode == null) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(appNode), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-03-15 04:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint appID;
|
|
|
|
|
|
if (!uint.TryParse(appNode.InnerText, out appID)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(appID), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-03-15 04:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
XmlNode nameNode = xmlNode.SelectSingleNode("name");
|
|
|
|
|
|
if (nameNode == null) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(nameNode), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-03-15 04:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result[appID] = nameNode.InnerText;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-29 15:44:11 +02:00
|
|
|
|
internal Dictionary<uint, string> GetOwnedGames(ulong steamID) {
|
2016-05-13 06:32:42 +02:00
|
|
|
|
if ((steamID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
2016-06-19 05:57:29 +02:00
|
|
|
|
Logging.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
2016-04-29 15:44:11 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KeyValue response = null;
|
2016-06-22 02:02:43 +02:00
|
|
|
|
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
|
|
|
|
|
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
|
|
|
|
|
|
iPlayerService.Timeout = Timeout;
|
2016-04-29 15:44:11 +02:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = iPlayerService.GetOwnedGames(
|
|
|
|
|
|
steamid: steamID,
|
|
|
|
|
|
include_appinfo: 1,
|
|
|
|
|
|
secure: !Program.GlobalConfig.ForceHttp
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (Exception e) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogGenericException(e, Bot.BotName);
|
2016-04-29 15:44:11 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
2016-07-04 20:57:33 +02:00
|
|
|
|
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
2016-04-29 15:44:11 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Dictionary<uint, string> result = new Dictionary<uint, string>(response["games"].Children.Count);
|
|
|
|
|
|
foreach (KeyValue game in response["games"].Children) {
|
2016-07-08 07:11:37 +02:00
|
|
|
|
uint appID = game["appid"].AsUnsignedInteger();
|
2016-04-29 15:44:11 +02:00
|
|
|
|
if (appID == 0) {
|
2016-06-19 12:20:12 +02:00
|
|
|
|
Logging.LogNullError(nameof(appID), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-04-29 15:44:11 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-29 15:47:07 +02:00
|
|
|
|
result[appID] = game["name"].Value;
|
2016-04-29 15:44:11 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-19 05:40:46 +02:00
|
|
|
|
internal uint GetServerTime() {
|
|
|
|
|
|
KeyValue response = null;
|
2016-06-22 02:02:43 +02:00
|
|
|
|
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
|
|
|
|
|
using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) {
|
|
|
|
|
|
iTwoFactorService.Timeout = Timeout;
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = iTwoFactorService.QueryTime(
|
|
|
|
|
|
method: WebRequestMethods.Http.Post,
|
|
|
|
|
|
secure: !Program.GlobalConfig.ForceHttp
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
Logging.LogGenericException(e, Bot.BotName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response != null) {
|
2016-07-08 07:11:37 +02:00
|
|
|
|
return response["server_time"].AsUnsignedInteger();
|
2016-06-19 05:40:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-04 20:57:33 +02:00
|
|
|
|
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
2016-06-19 05:40:46 +02:00
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-12 02:18:18 +02:00
|
|
|
|
internal async Task<byte?> GetTradeHoldDuration(ulong tradeID) {
|
|
|
|
|
|
if (tradeID == 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(tradeID), Bot.BotName);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-29 09:11:59 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-20 13:09:27 +02:00
|
|
|
|
string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
|
2016-06-12 02:18:18 +02:00
|
|
|
|
|
|
|
|
|
|
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
if (htmlDocument == null) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script");
|
2016-06-26 20:29:56 +02:00
|
|
|
|
if (htmlNode == null) { // Trade can be no longer valid
|
2016-06-12 02:18:18 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string text = htmlNode.InnerText;
|
|
|
|
|
|
if (string.IsNullOrEmpty(text)) {
|
|
|
|
|
|
Logging.LogNullError(nameof(text), Bot.BotName);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int index = text.IndexOf("g_daysTheirEscrow = ", StringComparison.Ordinal);
|
|
|
|
|
|
if (index < 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(index), Bot.BotName);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
index += 20;
|
|
|
|
|
|
text = text.Substring(index);
|
|
|
|
|
|
|
|
|
|
|
|
index = text.IndexOf(';');
|
|
|
|
|
|
if (index < 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(index), Bot.BotName);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
text = text.Substring(0, index);
|
|
|
|
|
|
|
|
|
|
|
|
byte holdDuration;
|
|
|
|
|
|
if (byte.TryParse(text, out holdDuration)) {
|
|
|
|
|
|
return holdDuration;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Logging.LogNullError(nameof(holdDuration), Bot.BotName);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-29 20:18:47 +02:00
|
|
|
|
internal HashSet<Steam.TradeOffer> GetActiveTradeOffers() {
|
2016-03-06 02:20:41 +01:00
|
|
|
|
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
2016-06-19 05:57:29 +02:00
|
|
|
|
Logging.LogNullError(nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
2015-10-28 20:01:43 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-14 02:48:56 +01:00
|
|
|
|
KeyValue response = null;
|
2016-06-22 02:02:43 +02:00
|
|
|
|
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
|
|
|
|
|
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
|
|
|
|
|
iEconService.Timeout = Timeout;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-01-14 02:48:56 +01:00
|
|
|
|
try {
|
|
|
|
|
|
response = iEconService.GetTradeOffers(
|
|
|
|
|
|
get_received_offers: 1,
|
|
|
|
|
|
active_only: 1,
|
2016-04-20 21:27:57 +02:00
|
|
|
|
get_descriptions: 1,
|
2016-03-13 20:19:52 +01:00
|
|
|
|
secure: !Program.GlobalConfig.ForceHttp
|
2016-01-14 02:48:56 +01:00
|
|
|
|
);
|
|
|
|
|
|
} catch (Exception e) {
|
2016-01-24 18:08:27 +01:00
|
|
|
|
Logging.LogGenericException(e, Bot.BotName);
|
2016-01-14 02:48:56 +01:00
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
2016-07-04 20:57:33 +02:00
|
|
|
|
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
|
|
|
|
Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptions = new Dictionary<ulong, Tuple<uint, Steam.Item.EType>>();
|
2016-04-20 21:27:57 +02:00
|
|
|
|
foreach (KeyValue description in response["descriptions"].Children) {
|
|
|
|
|
|
ulong classID = description["classid"].AsUnsignedLong();
|
|
|
|
|
|
if (classID == 0) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(classID), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-04-20 21:27:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
|
|
|
|
if (descriptions.ContainsKey(classID)) {
|
2016-04-21 00:51:22 +02:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2016-04-20 21:27:57 +02:00
|
|
|
|
|
2016-04-21 00:51:22 +02:00
|
|
|
|
uint appID = 0;
|
2016-04-20 21:27:57 +02:00
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
string hashName = description["market_hash_name"].Value;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
if (!string.IsNullOrEmpty(hashName)) {
|
|
|
|
|
|
appID = GetAppIDFromMarketHashName(hashName);
|
2016-04-20 21:27:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-10 01:16:05 +02:00
|
|
|
|
if (appID == 0) {
|
2016-07-08 07:11:37 +02:00
|
|
|
|
appID = description["appid"].AsUnsignedInteger();
|
2016-06-10 01:16:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
|
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
string descriptionType = description["type"].Value;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
if (!string.IsNullOrEmpty(descriptionType)) {
|
|
|
|
|
|
type = GetItemType(descriptionType);
|
2016-04-20 21:27:57 +02:00
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
|
|
|
|
descriptions[classID] = new Tuple<uint, Steam.Item.EType>(appID, type);
|
2016-04-20 21:27:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
|
2015-10-25 06:16:50 +01:00
|
|
|
|
foreach (KeyValue trade in response["trade_offers_received"].Children) {
|
2016-06-29 20:18:47 +02:00
|
|
|
|
Steam.TradeOffer.ETradeOfferState state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>();
|
|
|
|
|
|
if (state == Steam.TradeOffer.ETradeOfferState.Unknown) {
|
|
|
|
|
|
Logging.LogNullError(nameof(state));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (state != Steam.TradeOffer.ETradeOfferState.Active) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ulong tradeOfferID = trade["tradeofferid"].AsUnsignedLong();
|
|
|
|
|
|
if (tradeOfferID == 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(tradeOfferID));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-08 07:11:37 +02:00
|
|
|
|
uint otherSteamID3 = trade["accountid_other"].AsUnsignedInteger();
|
2016-06-29 20:18:47 +02:00
|
|
|
|
if (otherSteamID3 == 0) {
|
|
|
|
|
|
Logging.LogNullError(nameof(otherSteamID3));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Steam.TradeOffer tradeOffer = new Steam.TradeOffer(tradeOfferID, otherSteamID3, state);
|
|
|
|
|
|
|
2016-07-01 16:33:27 +02:00
|
|
|
|
List<KeyValue> itemsToGive = trade["items_to_give"].Children;
|
|
|
|
|
|
if (itemsToGive.Count > 0) {
|
|
|
|
|
|
if (!ParseItems(descriptions, itemsToGive, tradeOffer.ItemsToGive)) {
|
|
|
|
|
|
Logging.LogGenericError("Parsing " + nameof(itemsToGive) + " failed!", Bot.BotName);
|
2016-06-29 20:18:47 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
2016-04-20 21:27:57 +02:00
|
|
|
|
|
2016-07-01 16:33:27 +02:00
|
|
|
|
List<KeyValue> itemsToReceive = trade["items_to_receive"].Children;
|
|
|
|
|
|
if (itemsToReceive.Count > 0) {
|
|
|
|
|
|
if (!ParseItems(descriptions, itemsToReceive, tradeOffer.ItemsToReceive)) {
|
|
|
|
|
|
Logging.LogGenericError("Parsing " + nameof(itemsToReceive) + " failed!", Bot.BotName);
|
2016-06-29 20:18:47 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
2016-04-20 21:27:57 +02:00
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
result.Add(tradeOffer);
|
|
|
|
|
|
}
|
2015-10-28 22:34:53 +01:00
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
|
|
|
|
|
if (tradeID == 0) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(tradeID), Bot.BotName);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-29 14:33:05 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-12 16:58:45 +02:00
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
2016-04-12 07:40:02 +02:00
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(sessionID), Bot.BotName);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 10:40:36 +02:00
|
|
|
|
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
|
|
|
|
|
|
string request = referer + "/accept";
|
2016-02-22 18:34:45 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(3) {
|
2016-04-20 21:27:57 +02:00
|
|
|
|
{ "sessionid", sessionID },
|
|
|
|
|
|
{ "serverid", "1" },
|
|
|
|
|
|
{ "tradeofferid", tradeID.ToString() }
|
2015-10-25 06:16:50 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-04 22:02:38 +02:00
|
|
|
|
internal bool DeclineTradeOffer(ulong tradeID) {
|
|
|
|
|
|
if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
2016-06-19 05:57:29 +02:00
|
|
|
|
Logging.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
2016-06-04 22:02:38 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KeyValue response = null;
|
2016-06-22 02:02:43 +02:00
|
|
|
|
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
|
|
|
|
|
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
|
|
|
|
|
iEconService.Timeout = Timeout;
|
2016-06-04 22:02:38 +02:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = iEconService.DeclineTradeOffer(
|
|
|
|
|
|
tradeofferid: tradeID.ToString(),
|
|
|
|
|
|
method: WebRequestMethods.Http.Post,
|
|
|
|
|
|
secure: !Program.GlobalConfig.ForceHttp
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
Logging.LogGenericException(e, Bot.BotName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-08 13:01:41 +02:00
|
|
|
|
if (response != null) {
|
|
|
|
|
|
return true;
|
2016-06-04 22:02:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-04 20:57:33 +02:00
|
|
|
|
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
2016-06-08 13:01:41 +02:00
|
|
|
|
return false;
|
2016-06-04 22:02:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-08 13:01:41 +02:00
|
|
|
|
internal async Task<HashSet<Steam.Item>> GetMyInventory(bool tradable) {
|
2016-03-29 14:33:05 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-06-08 13:01:41 +02:00
|
|
|
|
uint currentPage = 0;
|
2016-04-21 02:32:36 +02:00
|
|
|
|
while (true) {
|
2016-06-08 13:01:41 +02:00
|
|
|
|
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=" + (tradable ? "1" : "0") + "&start=" + currentPage;
|
2016-04-22 17:51:13 +02:00
|
|
|
|
|
2016-05-30 01:57:06 +02:00
|
|
|
|
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request).ConfigureAwait(false);
|
2016-04-21 02:32:36 +02:00
|
|
|
|
if (jObject == null) {
|
|
|
|
|
|
return null;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
IEnumerable<JToken> descriptions = jObject.SelectTokens("$.rgDescriptions.*");
|
|
|
|
|
|
if (descriptions == null) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return null; // OK, empty inventory
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
|
|
|
|
Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptionMap = new Dictionary<ulong, Tuple<uint, Steam.Item.EType>>();
|
2016-04-21 02:32:36 +02:00
|
|
|
|
foreach (JToken description in descriptions) {
|
|
|
|
|
|
string classIDString = description["classid"].ToString();
|
|
|
|
|
|
if (string.IsNullOrEmpty(classIDString)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(classIDString), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-04-21 02:32:36 +02:00
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
ulong classID;
|
2016-05-13 06:32:42 +02:00
|
|
|
|
if (!ulong.TryParse(classIDString, out classID) || (classID == 0)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(classID), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-04-21 02:32:36 +02:00
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
|
|
|
|
if (descriptionMap.ContainsKey(classID)) {
|
2016-04-21 02:32:36 +02:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint appID = 0;
|
|
|
|
|
|
|
|
|
|
|
|
string hashName = description["market_hash_name"].ToString();
|
|
|
|
|
|
if (!string.IsNullOrEmpty(hashName)) {
|
|
|
|
|
|
appID = GetAppIDFromMarketHashName(hashName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-10 01:16:05 +02:00
|
|
|
|
if (appID == 0) {
|
|
|
|
|
|
string appIDString = description["appid"].ToString();
|
|
|
|
|
|
if (string.IsNullOrEmpty(appIDString)) {
|
|
|
|
|
|
Logging.LogNullError(nameof(appIDString), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-06-10 01:16:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!uint.TryParse(appIDString, out appID)) {
|
|
|
|
|
|
Logging.LogNullError(nameof(appID), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-06-10 01:16:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-08 13:01:41 +02:00
|
|
|
|
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
|
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
string descriptionType = description["type"].ToString();
|
|
|
|
|
|
if (!string.IsNullOrEmpty(descriptionType)) {
|
|
|
|
|
|
type = GetItemType(descriptionType);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
|
|
|
|
descriptionMap[classID] = new Tuple<uint, Steam.Item.EType>(appID, type);
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
IEnumerable<JToken> items = jObject.SelectTokens("$.rgInventory.*");
|
2016-05-13 06:32:42 +02:00
|
|
|
|
if (items == null) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(items), Bot.BotName);
|
2016-04-21 02:32:36 +02:00
|
|
|
|
return null;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
foreach (JToken item in items) {
|
|
|
|
|
|
Steam.Item steamItem;
|
2016-02-22 18:34:45 +01:00
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
try {
|
2016-06-08 13:01:41 +02:00
|
|
|
|
steamItem = item.ToObject<Steam.Item>();
|
2016-04-21 02:32:36 +02:00
|
|
|
|
} catch (JsonException e) {
|
|
|
|
|
|
Logging.LogGenericException(e, Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-04-21 02:32:36 +02:00
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
if (steamItem == null) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(steamItem), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-04-21 02:32:36 +02:00
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
Tuple<uint, Steam.Item.EType> description;
|
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
|
|
|
|
if (descriptionMap.TryGetValue(steamItem.ClassID, out description)) {
|
2016-04-21 02:32:36 +02:00
|
|
|
|
steamItem.RealAppID = description.Item1;
|
|
|
|
|
|
steamItem.Type = description.Item2;
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
result.Add(steamItem);
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-04-21 02:32:36 +02:00
|
|
|
|
bool more;
|
|
|
|
|
|
if (!bool.TryParse(jObject["more"].ToString(), out more) || !more) {
|
2016-06-03 00:06:13 +02:00
|
|
|
|
break; // OK, last page
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-08 13:01:41 +02:00
|
|
|
|
uint nextPage;
|
2016-06-29 20:33:52 +02:00
|
|
|
|
if (!uint.TryParse(jObject["more_start"].ToString(), out nextPage) || (nextPage <= currentPage)) {
|
2016-06-08 13:01:41 +02:00
|
|
|
|
Logging.LogNullError(nameof(nextPage), Bot.BotName);
|
2016-06-29 20:33:52 +02:00
|
|
|
|
return null;
|
2016-04-21 02:32:36 +02:00
|
|
|
|
}
|
2016-05-30 01:57:06 +02:00
|
|
|
|
|
2016-06-08 13:01:41 +02:00
|
|
|
|
currentPage = nextPage;
|
2016-01-13 11:17:58 +02:00
|
|
|
|
}
|
2016-01-14 01:30:12 +01:00
|
|
|
|
|
2016-01-13 11:17:58 +02:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-20 21:27:57 +02:00
|
|
|
|
internal async Task<bool> SendTradeOffer(HashSet<Steam.Item> inventory, ulong partnerID, string token = null) {
|
2016-05-13 06:32:42 +02:00
|
|
|
|
if ((inventory == null) || (inventory.Count == 0) || (partnerID == 0)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(inventory) + " || " + nameof(inventory.Count) + " || " + nameof(partnerID), Bot.BotName);
|
2016-01-13 11:17:58 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-29 14:33:05 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-12 16:58:45 +02:00
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
2016-04-12 07:40:02 +02:00
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(sessionID), Bot.BotName);
|
2016-01-14 01:30:12 +01:00
|
|
|
|
return false;
|
2016-01-13 22:59:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-13 06:32:42 +02:00
|
|
|
|
Steam.TradeOfferRequest singleTrade = new Steam.TradeOfferRequest();
|
|
|
|
|
|
HashSet<Steam.TradeOfferRequest> trades = new HashSet<Steam.TradeOfferRequest> { singleTrade };
|
2016-04-20 21:27:57 +02:00
|
|
|
|
|
|
|
|
|
|
byte itemID = 0;
|
|
|
|
|
|
foreach (Steam.Item item in inventory) {
|
2016-05-13 06:32:42 +02:00
|
|
|
|
if (itemID >= Trading.MaxItemsPerTrade) {
|
2016-01-24 00:00:27 +01:00
|
|
|
|
if (trades.Count >= Trading.MaxTradesPerAccount) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-10 02:21:28 +01:00
|
|
|
|
singleTrade = new Steam.TradeOfferRequest();
|
2016-01-22 10:13:02 +01:00
|
|
|
|
trades.Add(singleTrade);
|
2016-04-20 21:27:57 +02:00
|
|
|
|
itemID = 0;
|
2016-01-22 10:13:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-29 20:18:47 +02:00
|
|
|
|
singleTrade.ItemsToGive.Assets.Add(new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamContextID, item.AssetID, item.Amount));
|
2016-04-20 21:27:57 +02:00
|
|
|
|
itemID++;
|
2016-01-13 11:17:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 10:46:29 +02:00
|
|
|
|
string referer = SteamCommunityURL + "/tradeoffer/new";
|
|
|
|
|
|
string request = referer + "/send";
|
2016-05-30 01:57:06 +02:00
|
|
|
|
foreach (Dictionary<string, string> data in trades.Select(trade => 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}\"}}" }
|
|
|
|
|
|
})) {
|
|
|
|
|
|
if (!await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
2016-01-22 10:13:02 +01:00
|
|
|
|
}
|
2016-01-13 11:17:58 +02:00
|
|
|
|
}
|
2016-01-14 01:30:12 +01:00
|
|
|
|
|
|
|
|
|
|
return true;
|
2016-01-13 11:17:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-14 02:48:56 +01:00
|
|
|
|
internal async Task<HtmlDocument> GetBadgePage(byte page) {
|
2016-03-29 14:48:31 +02:00
|
|
|
|
if (page == 0) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(page), Bot.BotName);
|
2016-01-14 02:48:56 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-29 14:33:05 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-20 13:09:27 +02:00
|
|
|
|
string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
|
2016-03-29 14:48:31 +02:00
|
|
|
|
if (appID == 0) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogNullError(nameof(appID), Bot.BotName);
|
2016-01-14 02:48:56 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-29 14:33:05 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-20 13:09:27 +02:00
|
|
|
|
string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
2016-02-22 18:34:45 +01:00
|
|
|
|
|
2016-02-28 03:40:59 +01:00
|
|
|
|
internal async Task<bool> MarkInventory() {
|
2016-03-29 14:33:05 +02:00
|
|
|
|
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-22 17:50:01 +02:00
|
|
|
|
string request = SteamCommunityURL + "/my/inventory";
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false);
|
2016-02-28 03:40:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
private async Task<bool?> IsLoggedIn() {
|
2016-05-30 23:25:50 +02:00
|
|
|
|
// It would make sense to use /my/profile here, but it dismisses notifications related to profile comments
|
|
|
|
|
|
// So instead, we'll use some less intrusive link, such as /my/videos
|
|
|
|
|
|
string request = SteamCommunityURL + "/my/videos";
|
2016-04-24 23:32:23 +02:00
|
|
|
|
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Uri uri = await WebBrowser.UrlHeadToUriRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
if (uri == null) {
|
|
|
|
|
|
return null;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return !uri.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
|
2016-04-24 23:32:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<bool> RefreshSessionIfNeeded() {
|
|
|
|
|
|
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
|
|
|
|
|
return true;
|
2016-03-27 23:07:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
2016-03-27 23:07:00 +02:00
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
|
|
|
|
|
SessionSemaphore.Release();
|
|
|
|
|
|
return true;
|
2016-03-27 23:07:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
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);
|
2016-03-27 23:07:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-24 23:32:23 +02:00
|
|
|
|
SessionSemaphore.Release();
|
|
|
|
|
|
return result;
|
2016-03-27 23:07:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-29 14:33:05 +02:00
|
|
|
|
private async Task<bool> UnlockParentalAccount(string parentalPin) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
if (string.IsNullOrEmpty(parentalPin)) {
|
|
|
|
|
|
Logging.LogNullError(nameof(parentalPin), Bot.BotName);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (parentalPin.Equals("0")) {
|
2016-03-29 14:33:05 +02:00
|
|
|
|
return true;
|
2016-02-22 18:34:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
|
2016-04-24 23:32:23 +02:00
|
|
|
|
|
|
|
|
|
|
string request = SteamCommunityURL + "/parental/ajaxunlock";
|
2016-02-22 18:34:45 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
|
|
|
|
|
{ "pin", parentalPin }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-05-30 01:57:06 +02:00
|
|
|
|
bool result = await WebBrowser.UrlPostRetry(request, data, SteamCommunityURL).ConfigureAwait(false);
|
2016-04-14 20:58:33 +02:00
|
|
|
|
if (!result) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
Logging.LogGenericInfo("Failed!", Bot.BotName);
|
2016-03-29 14:33:05 +02:00
|
|
|
|
return false;
|
2016-02-22 18:34:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-14 20:58:33 +02:00
|
|
|
|
Logging.LogGenericInfo("Success!", Bot.BotName);
|
|
|
|
|
|
return true;
|
2016-02-22 18:34:45 +01:00
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|