Correct anti-captcha delay
[ArchiSteamFarm.git] / ArchiSteamFarm / ArchiWebHandler.cs
blob3f47c78c7165c9c6ea4664978eaacaa5a0d97f17
1 /*
2 _ _ _ ____ _ _____
3 / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
4 / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
5 / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
6 /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
8 Copyright 2015 Łukasz "JustArchi" Domeradzki
9 Contact: JustArchi@JustArchi.net
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
15 http://www.apache.org/licenses/LICENSE-2.0
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
25 using HtmlAgilityPack;
26 using SteamKit2;
27 using System;
28 using System.Collections.Generic;
29 using System.Globalization;
30 using System.Net;
31 using System.Net.Http;
32 using System.Text;
33 using System.Threading.Tasks;
35 namespace ArchiSteamFarm {
36 internal sealed class ArchiWebHandler {
37 private const int Timeout = 1000 * WebBrowser.HttpTimeout; // In miliseconds
39 private readonly Bot Bot;
40 private readonly string ApiKey;
41 private readonly Dictionary<string, string> SteamCookieDictionary = new Dictionary<string, string>();
43 private ulong SteamID;
44 private string VanityURL;
46 // This is required because home_process request must be done on final URL
47 private string GetHomeProcess() {
48 if (!string.IsNullOrEmpty(VanityURL)) {
49 return "http://steamcommunity.com/id/" + VanityURL + "/home_process";
50 } else if (SteamID != 0) {
51 return "http://steamcommunity.com/profiles/" + SteamID + "/home_process";
52 } else {
53 return null;
57 internal ArchiWebHandler(Bot bot, string apiKey) {
58 Bot = bot;
60 if (!string.IsNullOrEmpty(apiKey) && !apiKey.Equals("null")) {
61 ApiKey = apiKey;
65 internal async Task Init(SteamClient steamClient, string webAPIUserNonce, string vanityURL, string parentalPin) {
66 if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce)) {
67 return;
70 SteamID = steamClient.SteamID;
71 VanityURL = vanityURL;
73 string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString(CultureInfo.InvariantCulture)));
75 // Generate an AES session key
76 byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
78 // RSA encrypt it with the public key for the universe we're on
79 byte[] cryptedSessionKey = null;
80 using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
81 cryptedSessionKey = rsa.Encrypt(sessionKey);
84 // Copy our login key
85 byte[] loginKey = new byte[20];
86 Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
88 // AES encrypt the loginkey with our session key
89 byte[] cryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
91 // Send the magic
92 KeyValue authResult;
93 Logging.LogGenericInfo(Bot.BotName, "Logging in to ISteamUserAuth...");
94 using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) {
95 iSteamUserAuth.Timeout = Timeout;
97 try {
98 authResult = iSteamUserAuth.AuthenticateUser(
99 steamid: SteamID,
100 sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
101 encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
102 method: WebRequestMethods.Http.Post,
103 secure: true
105 } catch (Exception e) {
106 Logging.LogGenericException(Bot.BotName, e);
107 steamClient.Disconnect(); // We may get 403 if we use the same webAPIUserNonce twice
108 return;
112 if (authResult == null) {
113 steamClient.Disconnect(); // Try again
114 return;
117 Logging.LogGenericInfo(Bot.BotName, "Success!");
119 string steamLogin = authResult["token"].AsString();
120 string steamLoginSecure = authResult["tokensecure"].AsString();
122 SteamCookieDictionary.Clear();
123 SteamCookieDictionary.Add("sessionid", sessionID);
124 SteamCookieDictionary.Add("steamLogin", steamLogin);
125 SteamCookieDictionary.Add("steamLoginSecure", steamLoginSecure);
126 SteamCookieDictionary.Add("birthtime", "-473356799"); // ( ͡° ͜ʖ ͡°)
128 if (!string.IsNullOrEmpty(parentalPin) && !parentalPin.Equals("0")) {
129 Logging.LogGenericInfo(Bot.BotName, "Unlocking parental account...");
130 Dictionary<string, string> postData = new Dictionary<string, string>() {
131 {"pin", parentalPin}
134 HttpResponseMessage response = await WebBrowser.UrlPost("https://steamcommunity.com/parental/ajaxunlock", postData, SteamCookieDictionary, "https://steamcommunity.com/").ConfigureAwait(false);
135 if (response != null && response.IsSuccessStatusCode) {
136 Logging.LogGenericInfo(Bot.BotName, "Success!");
138 var setCookieValues = response.Headers.GetValues("Set-Cookie");
139 foreach (string setCookieValue in setCookieValues) {
140 if (setCookieValue.Contains("steamparental=")) {
141 string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=") + 14);
142 setCookie = setCookie.Substring(0, setCookie.IndexOf(';'));
143 SteamCookieDictionary.Add("steamparental", setCookie);
144 break;
147 } else {
148 Logging.LogGenericInfo(Bot.BotName, "Failed!");
152 Bot.Trading.CheckTrades();
155 internal List<SteamTradeOffer> GetTradeOffers() {
156 if (ApiKey == null) {
157 return null;
160 KeyValue response;
161 using (dynamic iEconService = WebAPI.GetInterface("IEconService")) {
162 // Timeout
163 iEconService.Timeout = Timeout;
165 try {
166 response = iEconService.GetTradeOffers(
167 key: ApiKey,
168 get_received_offers: 1,
169 active_only: 1,
170 secure: true
172 } catch (Exception e) {
173 Logging.LogGenericException(Bot.BotName, e);
174 return null;
178 if (response == null) {
179 return null;
182 List<SteamTradeOffer> result = new List<SteamTradeOffer>();
183 foreach (KeyValue trade in response["trade_offers_received"].Children) {
184 SteamTradeOffer tradeOffer = new SteamTradeOffer {
185 tradeofferid = trade["tradeofferid"].AsString(),
186 accountid_other = trade["accountid_other"].AsInteger(),
187 message = trade["message"].AsString(),
188 expiration_time = trade["expiration_time"].AsInteger(),
189 trade_offer_state = (SteamTradeOffer.ETradeOfferState) trade["trade_offer_state"].AsInteger(),
190 items_to_give = new List<SteamItem>(),
191 items_to_receive = new List<SteamItem>(),
192 is_our_offer = trade["is_our_offer"].AsBoolean(),
193 time_created = trade["time_created"].AsInteger(),
194 time_updated = trade["time_updated"].AsInteger(),
195 from_real_time_trade = trade["from_real_time_trade"].AsBoolean()
197 foreach (KeyValue item in trade["items_to_give"].Children) {
198 tradeOffer.items_to_give.Add(new SteamItem {
199 appid = item["appid"].AsString(),
200 contextid = item["contextid"].AsString(),
201 assetid = item["assetid"].AsString(),
202 currencyid = item["currencyid"].AsString(),
203 classid = item["classid"].AsString(),
204 instanceid = item["instanceid"].AsString(),
205 amount = item["amount"].AsString(),
206 missing = item["missing"].AsBoolean()
209 foreach (KeyValue item in trade["items_to_receive"].Children) {
210 tradeOffer.items_to_receive.Add(new SteamItem {
211 appid = item["appid"].AsString(),
212 contextid = item["contextid"].AsString(),
213 assetid = item["assetid"].AsString(),
214 currencyid = item["currencyid"].AsString(),
215 classid = item["classid"].AsString(),
216 instanceid = item["instanceid"].AsString(),
217 amount = item["amount"].AsString(),
218 missing = item["missing"].AsBoolean()
221 result.Add(tradeOffer);
224 return result;
227 internal async Task JoinClan(ulong clanID) {
228 if (clanID == 0) {
229 return;
232 string sessionID;
233 if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
234 return;
237 string request = "http://steamcommunity.com/gid/" + clanID;
239 Dictionary<string, string> postData = new Dictionary<string, string>() {
240 {"sessionID", sessionID},
241 {"action", "join"}
244 await WebBrowser.UrlPost(request, postData, SteamCookieDictionary).ConfigureAwait(false);
247 internal async Task LeaveClan(ulong clanID) {
248 if (clanID == 0) {
249 return;
252 string sessionID;
253 if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
254 return;
257 string request = GetHomeProcess();
258 Dictionary<string, string> postData = new Dictionary<string, string>() {
259 {"sessionID", sessionID},
260 {"action", "leaveGroup"},
261 {"groupId", clanID.ToString()}
264 await WebBrowser.UrlPost(request, postData, SteamCookieDictionary).ConfigureAwait(false);
267 internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
268 if (tradeID == 0) {
269 return false;
272 string sessionID;
273 if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
274 return false;
277 string referer = "https://steamcommunity.com/tradeoffer/" + tradeID + "/";
278 string request = referer + "accept";
280 Dictionary<string, string> postData = new Dictionary<string, string>() {
281 {"sessionid", sessionID},
282 {"serverid", "1"},
283 {"tradeofferid", tradeID.ToString()}
286 HttpResponseMessage result = await WebBrowser.UrlPost(request, postData, SteamCookieDictionary, referer).ConfigureAwait(false);
287 if (result == null) {
288 return false;
291 bool success = result.IsSuccessStatusCode;
293 if (!success) {
294 Logging.LogGenericWarning(Bot.BotName, "Request failed, reason: " + result.ReasonPhrase);
295 switch (result.StatusCode) {
296 case HttpStatusCode.InternalServerError:
297 Logging.LogGenericWarning(Bot.BotName, "That might be caused by 7-days trade lock from new device");
298 Logging.LogGenericWarning(Bot.BotName, "Try again in 7 days, declining that offer for now");
299 DeclineTradeOffer(tradeID);
300 break;
304 return success;
307 internal bool DeclineTradeOffer(ulong tradeID) {
308 if (ApiKey == null) {
309 return false;
312 if (tradeID == 0) {
313 return false;
316 KeyValue response;
317 using (dynamic iEconService = WebAPI.GetInterface("IEconService")) {
318 // Timeout
319 iEconService.Timeout = Timeout;
321 try {
322 response = iEconService.DeclineTradeOffer(
323 key: ApiKey,
324 tradeofferid: tradeID.ToString(),
325 method: WebRequestMethods.Http.Post,
326 secure: true
328 } catch (Exception e) {
329 Logging.LogGenericException(Bot.BotName, e);
330 return false;
334 return response != null; // Steam API doesn't respond with any error code, assume any response is a success
337 internal async Task<HtmlDocument> GetBadgePage(int page) {
338 if (SteamID == 0 || page == 0) {
339 return null;
342 return await WebBrowser.UrlGetToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID + "/badges?p=" + page, SteamCookieDictionary).ConfigureAwait(false);
345 internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
346 if (SteamID == 0 || appID == 0) {
347 return null;
350 return await WebBrowser.UrlGetToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID + "/gamecards/" + appID, SteamCookieDictionary).ConfigureAwait(false);