Files
ArchiSteamFarm/ArchiSteamFarm/ArchiWebHandler.cs

489 lines
16 KiB
C#
Raw Normal View History

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;
using System.Net;
using System.Net.Http;
2015-10-25 06:16:50 +01:00
using System.Text;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
2015-11-27 16:25:03 +01:00
internal sealed class ArchiWebHandler {
2016-03-06 23:32:17 +01:00
private static int Timeout = 30 * 1000;
2015-11-20 14:47:16 +01:00
2015-10-25 06:16:50 +01:00
private readonly Bot Bot;
2016-02-28 21:24:50 +01:00
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>(4);
2015-10-25 06:16:50 +01:00
private ulong SteamID;
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-03-06 02:20:41 +01:00
internal ArchiWebHandler(Bot bot) {
2015-10-25 06:16:50 +01:00
Bot = bot;
}
2016-02-22 18:34:45 +01:00
internal async Task<bool> Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
2015-10-28 21:47:31 +01:00
if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce)) {
return false;
2015-10-25 06:16:50 +01:00
}
SteamID = steamClient.SteamID;
2016-02-20 23:00:30 +01:00
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString()));
2015-10-25 06:16:50 +01:00
// Generate an AES session key
byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
// RSA encrypt it with the public key for the universe we're on
2016-03-10 01:20:17 +01:00
byte[] cryptedSessionKey;
2015-10-25 06:16:50 +01:00
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
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
byte[] cryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
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(
steamid: SteamID,
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,
secure: true
);
} catch (Exception e) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericException(e, Bot.BotName);
return false;
2015-10-25 06:16:50 +01:00
}
}
if (authResult == null) {
return false;
2015-10-25 06:16:50 +01:00
}
2016-01-24 18:08:27 +01:00
Logging.LogGenericInfo("Success!", Bot.BotName);
2015-10-25 06:16:50 +01:00
string steamLogin = authResult["token"].AsString();
string steamLoginSecure = authResult["tokensecure"].AsString();
2016-01-14 02:48:56 +01:00
Cookie["sessionid"] = sessionID;
Cookie["steamLogin"] = steamLogin;
Cookie["steamLoginSecure"] = steamLoginSecure;
2015-10-25 06:16:50 +01:00
2016-02-28 21:24:50 +01:00
// The below is used for display purposes only
Cookie["webTradeEligibility"] = "{\"allowed\":0,\"reason\":0,\"allowed_at_time\":0,\"steamguard_required_days\":0,\"sales_this_year\":0,\"max_sales_per_year\":0,\"forms_requested\":0}";
2015-12-18 15:50:10 +01:00
await UnlockParentalAccount(parentalPin).ConfigureAwait(false);
return true;
2015-10-25 06:16:50 +01:00
}
internal async Task<bool?> IsLoggedIn() {
if (SteamID == 0) {
return false;
}
2016-01-14 02:48:56 +01:00
HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument("https://steamcommunity.com/my/profile", Cookie).ConfigureAwait(false);
}
if (htmlDocument == null) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='account_pulldown']");
return htmlNode != null;
}
2015-12-16 22:14:53 +01:00
internal async Task<bool> ReconnectIfNeeded() {
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.HasValue && !isLoggedIn.Value) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName);
2016-03-06 22:14:02 +01:00
Task.Run(async () => await Bot.Restart().ConfigureAwait(false)).Forget();
2015-12-16 22:14:53 +01:00
return true;
}
2015-12-16 22:14:53 +01:00
return false;
}
2016-03-10 02:21:28 +01:00
internal List<Steam.TradeOffer> GetTradeOffers() {
2016-03-06 02:20:41 +01:00
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
2015-10-28 20:01:43 +01:00
return null;
}
2016-01-14 02:48:56 +01:00
KeyValue response = null;
2016-03-06 02:20:41 +01:00
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
2015-10-25 06:16:50 +01:00
iEconService.Timeout = Timeout;
2016-01-14 02:48:56 +01:00
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
try {
response = iEconService.GetTradeOffers(
get_received_offers: 1,
active_only: 1,
secure: true
);
} 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-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
2015-10-25 06:16:50 +01:00
return null;
}
2016-03-10 02:21:28 +01:00
List<Steam.TradeOffer> result = new List<Steam.TradeOffer>();
2015-10-25 06:16:50 +01:00
foreach (KeyValue trade in response["trade_offers_received"].Children) {
2016-03-10 02:21:28 +01:00
Steam.TradeOffer tradeOffer = new Steam.TradeOffer {
2015-10-25 06:16:50 +01:00
tradeofferid = trade["tradeofferid"].AsString(),
accountid_other = trade["accountid_other"].AsInteger(),
2016-03-10 02:21:28 +01:00
trade_offer_state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
2015-10-25 06:16:50 +01:00
};
foreach (KeyValue item in trade["items_to_give"].Children) {
2016-03-10 02:21:28 +01:00
tradeOffer.items_to_give.Add(new Steam.Item {
2015-10-25 06:16:50 +01:00
appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(),
classid = item["classid"].AsString(),
instanceid = item["instanceid"].AsString(),
amount = item["amount"].AsString(),
});
}
foreach (KeyValue item in trade["items_to_receive"].Children) {
2016-03-10 02:21:28 +01:00
tradeOffer.items_to_receive.Add(new Steam.Item {
2015-10-25 06:16:50 +01:00
appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(),
classid = item["classid"].AsString(),
instanceid = item["instanceid"].AsString(),
amount = item["amount"].AsString(),
});
}
result.Add(tradeOffer);
}
2015-10-25 06:16:50 +01:00
return result;
}
2016-01-14 02:48:56 +01:00
internal async Task<bool> JoinClan(ulong clanID) {
2015-10-29 16:38:16 +01:00
if (clanID == 0) {
2016-01-14 02:48:56 +01:00
return false;
2015-10-29 16:38:16 +01:00
}
string sessionID;
2016-01-02 17:31:55 +01:00
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
2016-01-14 02:48:56 +01:00
return false;
2015-10-29 16:38:16 +01:00
}
2016-01-02 17:31:55 +01:00
string request = "https://steamcommunity.com/gid/" + clanID;
2015-10-29 16:38:16 +01:00
2016-02-22 18:34:45 +01:00
Dictionary<string, string> data = new Dictionary<string, string>(2) {
2015-10-29 16:38:16 +01:00
{"sessionID", sessionID},
{"action", "join"}
};
2016-01-14 02:48:56 +01:00
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie).ConfigureAwait(false);
}
if (response == null) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
2016-01-14 02:48:56 +01:00
return false;
}
return true;
2015-10-29 16:38:16 +01:00
}
2015-10-25 06:16:50 +01:00
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
if (tradeID == 0) {
return false;
}
string sessionID;
2016-01-02 17:31:55 +01:00
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
2015-10-25 06:16:50 +01:00
return false;
}
2016-01-14 02:48:56 +01:00
string referer = "https://steamcommunity.com/tradeoffer/" + tradeID;
string request = referer + "/accept";
2015-10-25 06:16:50 +01:00
2016-02-22 18:34:45 +01:00
Dictionary<string, string> data = new Dictionary<string, string>(3) {
2015-10-25 06:16:50 +01:00
{"sessionid", sessionID},
{"serverid", "1"},
{"tradeofferid", tradeID.ToString()}
};
2016-01-14 02:48:56 +01:00
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false);
2015-11-19 18:34:18 +01:00
}
2016-01-14 02:48:56 +01:00
if (response == null) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
2016-01-14 02:48:56 +01:00
return false;
2015-10-29 17:06:02 +01:00
}
2016-01-14 02:48:56 +01:00
return true;
2015-10-25 06:16:50 +01:00
}
internal bool DeclineTradeOffer(ulong tradeID) {
2016-03-06 02:20:41 +01:00
if (tradeID == 0 || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
2015-10-28 20:01:43 +01:00
return false;
}
2016-01-14 02:48:56 +01:00
KeyValue response = null;
2016-03-06 02:20:41 +01:00
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
2015-10-25 06:16:50 +01:00
iEconService.Timeout = Timeout;
2016-01-14 02:48:56 +01:00
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
try {
response = iEconService.DeclineTradeOffer(
tradeofferid: tradeID.ToString(),
method: WebRequestMethods.Http.Post,
secure: true
);
} 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
}
}
2016-01-14 02:48:56 +01:00
if (response == null) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
2016-01-14 02:48:56 +01:00
return false;
}
return true;
2015-10-25 06:16:50 +01:00
}
2016-03-10 02:21:28 +01:00
internal async Task<List<Steam.Item>> GetMyTradableInventory() {
2016-01-14 02:48:56 +01:00
JObject jObject = null;
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
jObject = await WebBrowser.UrlGetToJObject("https://steamcommunity.com/my/inventory/json/753/6?trading=1", Cookie).ConfigureAwait(false);
2016-01-14 02:48:56 +01:00
}
if (jObject == null) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
2016-01-14 02:48:56 +01:00
return null;
}
IEnumerable<JToken> jTokens = jObject.SelectTokens("$.rgInventory.*");
2016-02-22 18:34:45 +01:00
if (jTokens == null) {
Logging.LogNullError("jTokens", Bot.BotName);
return null;
}
2016-03-10 02:21:28 +01:00
List<Steam.Item> result = new List<Steam.Item>();
2016-01-14 02:48:56 +01:00
foreach (JToken jToken in jTokens) {
2016-02-22 18:34:45 +01:00
try {
2016-03-10 02:21:28 +01:00
result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString()));
2016-02-22 18:34:45 +01:00
} catch (Exception e) {
Logging.LogGenericException(e, Bot.BotName);
}
2016-01-13 11:17:58 +02:00
}
2016-01-13 11:17:58 +02:00
return result;
}
2016-03-10 02:21:28 +01:00
internal async Task<bool> SendTradeOffer(List<Steam.Item> inventory, ulong partnerID, string token = null) {
2016-01-14 02:48:56 +01:00
if (inventory == null || inventory.Count == 0 || partnerID == 0) {
2016-01-13 11:17:58 +02:00
return false;
}
string sessionID;
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
return false;
2016-01-13 22:59:57 +02:00
}
2016-03-10 02:21:28 +01:00
List<Steam.TradeOfferRequest> trades = new List<Steam.TradeOfferRequest>(1 + inventory.Count / Trading.MaxItemsPerTrade);
2016-01-22 10:13:02 +01:00
2016-03-10 02:21:28 +01:00
Steam.TradeOfferRequest singleTrade = null;
2016-01-24 00:00:27 +01:00
for (ushort i = 0; i < inventory.Count; i++) {
2016-01-22 10:13:02 +01:00
if (i % Trading.MaxItemsPerTrade == 0) {
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-03-10 02:21:28 +01:00
Steam.Item item = inventory[i];
singleTrade.me.assets.Add(new Steam.Item() {
appid = "753",
contextid = "6",
amount = item.amount,
assetid = item.id
});
2016-01-13 11:17:58 +02:00
}
string referer = "https://steamcommunity.com/tradeoffer/new";
string request = referer + "/send";
2016-03-10 02:21:28 +01:00
foreach (Steam.TradeOfferRequest trade in trades) {
2016-02-22 18:34:45 +01:00
Dictionary<string, string> data = new Dictionary<string, string>(6) {
2016-01-22 10:13:02 +01:00
{"sessionid", sessionID},
{"serverid", "1"},
{"partner", partnerID.ToString()},
{"tradeoffermessage", "Sent by ASF"},
{"json_tradeoffer", JsonConvert.SerializeObject(trade)},
2016-03-10 01:20:17 +01:00
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"}
2016-01-22 10:13:02 +01:00
};
2016-01-22 10:13:02 +01:00
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false);
}
2016-01-14 02:48:56 +01:00
2016-01-22 10:13:02 +01:00
if (response == null) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
2016-01-22 10:13:02 +01:00
return false;
}
2016-01-13 11:17:58 +02: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) {
if (page == 0 || SteamID == 0) {
return null;
}
HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument("https://steamcommunity.com/profiles/" + SteamID + "/badges?l=english&p=" + page, Cookie).ConfigureAwait(false);
}
if (htmlDocument == null) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
2015-10-25 06:16:50 +01:00
}
2016-01-14 02:48:56 +01:00
return htmlDocument;
2015-10-25 06:16:50 +01:00
}
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
2016-01-14 02:48:56 +01:00
if (appID == 0 || SteamID == 0) {
return null;
}
HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument("https://steamcommunity.com/profiles/" + SteamID + "/gamecards/" + appID + "?l=english", Cookie).ConfigureAwait(false);
}
if (htmlDocument == null) {
2016-01-24 18:08:27 +01:00
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
2015-10-25 06:16:50 +01:00
}
2016-01-14 02:48:56 +01:00
return htmlDocument;
2015-10-25 06:16:50 +01:00
}
2016-02-22 18:34:45 +01:00
internal async Task<bool> MarkInventory() {
if (SteamID == 0) {
return false;
}
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
2016-02-28 21:24:50 +01:00
response = await WebBrowser.UrlGet("https://steamcommunity.com/profiles/" + SteamID + "/inventory", Cookie).ConfigureAwait(false);
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
return true;
}
2016-02-22 18:34:45 +01:00
private async Task UnlockParentalAccount(string parentalPin) {
if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) {
return;
}
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "pin", parentalPin }
};
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost("https://steamcommunity.com/parental/ajaxunlock", data, Cookie, "https://steamcommunity.com/").ConfigureAwait(false);
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return;
}
IEnumerable<string> setCookieValues;
if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) {
Logging.LogNullError("setCookieValues", Bot.BotName);
return;
}
foreach (string setCookieValue in setCookieValues) {
2016-03-10 01:20:17 +01:00
if (!setCookieValue.Contains("steamparental=")) {
continue;
2016-02-22 18:34:45 +01:00
}
2016-03-10 01:20:17 +01:00
string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=", StringComparison.Ordinal) + 14);
int index = setCookie.IndexOf(';');
if (index > 0) {
setCookie = setCookie.Substring(0, index);
}
Cookie["steamparental"] = setCookie;
Logging.LogGenericInfo("Success!", Bot.BotName);
return;
2016-02-22 18:34:45 +01:00
}
Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName);
}
2015-10-25 06:16:50 +01:00
}
}