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
;
28 using System
.Collections
.Generic
;
29 using System
.Globalization
;
31 using System
.Net
.Http
;
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";
57 internal ArchiWebHandler(Bot bot
, string apiKey
) {
60 if (!string.IsNullOrEmpty(apiKey
) && !apiKey
.Equals("null")) {
65 internal async Task
Init(SteamClient steamClient
, string webAPIUserNonce
, string vanityURL
, string parentalPin
) {
66 if (steamClient
== null || steamClient
.SteamID
== null || string.IsNullOrEmpty(webAPIUserNonce
)) {
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
);
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
);
93 Logging
.LogGenericInfo(Bot
.BotName
, "Logging in to ISteamUserAuth...");
94 using (dynamic iSteamUserAuth
= WebAPI
.GetInterface("ISteamUserAuth")) {
95 iSteamUserAuth
.Timeout
= Timeout
;
98 authResult
= iSteamUserAuth
.AuthenticateUser(
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
,
105 } catch (Exception e
) {
106 Logging
.LogGenericException(Bot
.BotName
, e
);
107 steamClient
.Disconnect(); // We may get 403 if we use the same webAPIUserNonce twice
112 if (authResult
== null) {
113 steamClient
.Disconnect(); // Try again
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>() {
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
);
148 Logging
.LogGenericInfo(Bot
.BotName
, "Failed!");
152 Bot
.Trading
.CheckTrades();
155 internal List
<SteamTradeOffer
> GetTradeOffers() {
156 if (ApiKey
== null) {
161 using (dynamic iEconService
= WebAPI
.GetInterface("IEconService")) {
163 iEconService
.Timeout
= Timeout
;
166 response
= iEconService
.GetTradeOffers(
168 get_received_offers: 1,
172 } catch (Exception e
) {
173 Logging
.LogGenericException(Bot
.BotName
, e
);
178 if (response
== 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
);
227 internal async Task
JoinClan(ulong clanID
) {
233 if (!SteamCookieDictionary
.TryGetValue("sessionid", out sessionID
)) {
237 string request
= "http://steamcommunity.com/gid/" + clanID
;
239 Dictionary
<string, string> postData
= new Dictionary
<string, string>() {
240 {"sessionID", sessionID}
,
244 await WebBrowser
.UrlPost(request
, postData
, SteamCookieDictionary
).ConfigureAwait(false);
247 internal async Task
LeaveClan(ulong clanID
) {
253 if (!SteamCookieDictionary
.TryGetValue("sessionid", out sessionID
)) {
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
) {
273 if (!SteamCookieDictionary
.TryGetValue("sessionid", out sessionID
)) {
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}
,
283 {"tradeofferid", tradeID.ToString()}
286 HttpResponseMessage result
= await WebBrowser
.UrlPost(request
, postData
, SteamCookieDictionary
, referer
).ConfigureAwait(false);
287 if (result
== null) {
291 bool success
= result
.IsSuccessStatusCode
;
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
);
307 internal bool DeclineTradeOffer(ulong tradeID
) {
308 if (ApiKey
== null) {
317 using (dynamic iEconService
= WebAPI
.GetInterface("IEconService")) {
319 iEconService
.Timeout
= Timeout
;
322 response
= iEconService
.DeclineTradeOffer(
324 tradeofferid: tradeID
.ToString(),
325 method: WebRequestMethods
.Http
.Post
,
328 } catch (Exception e
) {
329 Logging
.LogGenericException(Bot
.BotName
, e
);
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) {
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) {
350 return await WebBrowser
.UrlGetToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID
+ "/gamecards/" + appID
, SteamCookieDictionary
).ConfigureAwait(false);