2015-10-28 19:21:27 +01:00
|
|
|
|
/*
|
|
|
|
|
|
_ _ _ ____ _ _____
|
|
|
|
|
|
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
|
|
|
|
|
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
|
|
|
|
|
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
|
|
|
|
|
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
|
|
|
|
|
|
2017-01-02 20:05:21 +01:00
|
|
|
|
Copyright 2015-2017 Ł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-11-06 12:06:02 +01:00
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
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;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
using System.Threading;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
using System.Threading.Tasks;
|
2016-03-15 04:20:28 +01:00
|
|
|
|
using System.Xml;
|
2016-05-13 06:32:42 +02:00
|
|
|
|
using ArchiSteamFarm.JSON;
|
2017-01-06 13:20:36 +01:00
|
|
|
|
using ArchiSteamFarm.Localization;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
using HtmlAgilityPack;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
|
using SteamKit2;
|
2016-12-04 05:50:11 +01:00
|
|
|
|
using Formatting = Newtonsoft.Json.Formatting;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
|
|
|
|
|
namespace ArchiSteamFarm {
|
2016-07-08 07:41:36 +02:00
|
|
|
|
internal sealed class ArchiWebHandler : IDisposable {
|
2017-01-06 13:20:36 +01:00
|
|
|
|
private const string IEconService = "IEconService";
|
|
|
|
|
|
private const string IPlayerService = "IPlayerService";
|
|
|
|
|
|
private const string ISteamUserAuth = "ISteamUserAuth";
|
|
|
|
|
|
private const string ITwoFactorService = "ITwoFactorService";
|
|
|
|
|
|
|
2017-01-22 20:10:28 +01:00
|
|
|
|
private const byte MinSessionTTL = GlobalConfig.DefaultConnectionTimeout / 4; // Assume session is valid for at least that amount of seconds
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
|
|
|
|
|
|
// We must use HTTPS for SteamCommunity, as http would make certain POST requests failing (trades)
|
2016-12-25 05:53:47 +01:00
|
|
|
|
private const string SteamCommunityHost = "steamcommunity.com";
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
private const string SteamCommunityURL = "https://" + SteamCommunityHost;
|
2016-12-23 18:51:45 +01:00
|
|
|
|
|
|
|
|
|
|
// We could (and should) use HTTPS for SteamStore, but that would make certain POST requests failing
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
private const string SteamStoreHost = "store.steampowered.com";
|
2016-12-23 03:01:37 +01:00
|
|
|
|
private const string SteamStoreURL = "http://" + SteamStoreHost;
|
2016-03-13 20:19:52 +01:00
|
|
|
|
|
2017-01-22 20:10:28 +01:00
|
|
|
|
private static int Timeout = GlobalConfig.DefaultConnectionTimeout * 1000; // This must be int type
|
2015-11-20 14:47:16 +01:00
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
private readonly Bot Bot;
|
2017-02-08 14:35:01 +01:00
|
|
|
|
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1);
|
2016-03-29 14:33:05 +02:00
|
|
|
|
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
|
2017-01-11 15:41:02 +01:00
|
|
|
|
private readonly SemaphoreSlim SteamApiKeySemaphore = 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; }
|
|
|
|
|
|
|
2017-02-08 14:35:01 +01:00
|
|
|
|
private bool? CachedPublicInventory;
|
|
|
|
|
|
private string CachedSteamApiKey;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
private DateTime LastSessionRefreshCheck = DateTime.MinValue;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
private ulong SteamID;
|
2015-12-18 15:50:10 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal ArchiWebHandler(Bot bot) {
|
|
|
|
|
|
if (bot == null) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(bot));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Bot = bot;
|
|
|
|
|
|
|
|
|
|
|
|
WebBrowser = new WebBrowser(bot.ArchiLogger);
|
2016-03-06 23:28:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:41:02 +01:00
|
|
|
|
public void Dispose() {
|
2017-02-08 14:35:01 +01:00
|
|
|
|
PublicInventorySemaphore.Dispose();
|
2017-01-11 15:41:02 +01:00
|
|
|
|
SessionSemaphore.Dispose();
|
|
|
|
|
|
SteamApiKeySemaphore.Dispose();
|
|
|
|
|
|
}
|
2016-11-24 07:32:16 +01:00
|
|
|
|
|
2016-12-07 19:08:36 +01:00
|
|
|
|
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (tradeID == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(tradeID));
|
2016-12-07 19:08:36 +01:00
|
|
|
|
return false;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-12-07 19:08:36 +01:00
|
|
|
|
return false;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(sessionID));
|
2016-12-07 19:08:36 +01:00
|
|
|
|
return false;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
|
|
|
|
|
|
string request = referer + "/accept";
|
|
|
|
|
|
|
2016-11-24 07:46:37 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(3) {
|
|
|
|
|
|
{ "sessionid", sessionID },
|
|
|
|
|
|
{ "serverid", "1" },
|
|
|
|
|
|
{ "tradeofferid", tradeID.ToString() }
|
|
|
|
|
|
};
|
2016-11-24 07:32:16 +01:00
|
|
|
|
|
2016-12-07 19:08:36 +01:00
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal async Task<bool> AddFreeLicense(uint subID) {
|
|
|
|
|
|
if (subID == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(subID));
|
|
|
|
|
|
return false;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-05-13 06:32:42 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(sessionID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-05-13 06:32:42 +02:00
|
|
|
|
|
2016-12-23 18:51:45 +01:00
|
|
|
|
const string request = SteamStoreURL + "/checkout/addfreelicense";
|
2016-11-24 07:46:37 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(3) {
|
|
|
|
|
|
{ "sessionid", sessionID },
|
|
|
|
|
|
{ "subid", subID.ToString() },
|
|
|
|
|
|
{ "action", "add_to_cart" }
|
|
|
|
|
|
};
|
2016-11-24 07:32:16 +01:00
|
|
|
|
|
|
|
|
|
|
HtmlDocument htmlDocument = await WebBrowser.UrlPostToHtmlDocumentRetry(request, data).ConfigureAwait(false);
|
|
|
|
|
|
return htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='add_free_content_success_area']") != null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-02 19:59:33 +01:00
|
|
|
|
/*
|
2016-12-23 03:01:37 +01:00
|
|
|
|
internal async Task<bool> ClearFromDiscoveryQueue(uint appID) {
|
|
|
|
|
|
if (appID == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(appID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-12-23 03:01:37 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamStoreURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(sessionID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string request = SteamStoreURL + "/app/" + appID;
|
|
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
|
|
|
|
|
{ "sessionid", sessionID },
|
|
|
|
|
|
{ "appid_to_clear_from_queue", appID.ToString() }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
|
|
|
|
|
|
}
|
2017-01-02 19:59:33 +01:00
|
|
|
|
*/
|
2016-12-23 03:01:37 +01:00
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
internal async Task DeclineTradeOffer(ulong tradeID) {
|
|
|
|
|
|
if (tradeID == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(tradeID));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string steamApiKey = await GetApiKey().ConfigureAwait(false);
|
2017-02-08 14:35:01 +01:00
|
|
|
|
if (string.IsNullOrEmpty(steamApiKey)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KeyValue response = null;
|
|
|
|
|
|
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
2017-01-11 15:22:00 +01:00
|
|
|
|
using (dynamic iEconService = WebAPI.GetInterface(IEconService, steamApiKey)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
iEconService.Timeout = Timeout;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
response = iEconService.DeclineTradeOffer(
|
|
|
|
|
|
tradeofferid: tradeID.ToString(),
|
|
|
|
|
|
method: WebRequestMethods.Http.Post,
|
|
|
|
|
|
secure: true
|
|
|
|
|
|
);
|
2016-11-24 07:32:16 +01:00
|
|
|
|
} catch (Exception e) {
|
2017-01-25 23:23:19 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarningException(e);
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-05-13 06:32:42 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (response == null) {
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxRetries));
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-02 19:59:33 +01:00
|
|
|
|
/*
|
2016-12-23 03:01:37 +01:00
|
|
|
|
internal async Task<HashSet<uint>> GenerateNewDiscoveryQueue() {
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-12-23 03:01:37 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamStoreURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(sessionID));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-23 18:51:45 +01:00
|
|
|
|
const string request = SteamStoreURL + "/explore/generatenewdiscoveryqueue";
|
2016-12-23 03:01:37 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
|
|
|
|
|
{ "sessionid", sessionID },
|
|
|
|
|
|
{ "queuetype", "0" }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Steam.NewDiscoveryQueueResponse output = await WebBrowser.UrlPostToJsonResultRetry<Steam.NewDiscoveryQueueResponse>(request, data).ConfigureAwait(false);
|
|
|
|
|
|
return output?.Queue;
|
|
|
|
|
|
}
|
2017-01-02 19:59:33 +01:00
|
|
|
|
*/
|
2016-12-23 03:01:37 +01:00
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
internal async Task<HashSet<Steam.TradeOffer>> GetActiveTradeOffers() {
|
|
|
|
|
|
string steamApiKey = await GetApiKey().ConfigureAwait(false);
|
2017-02-08 14:35:01 +01:00
|
|
|
|
if (string.IsNullOrEmpty(steamApiKey)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
2016-07-01 16:33:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
KeyValue response = null;
|
|
|
|
|
|
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
2017-01-11 15:22:00 +01:00
|
|
|
|
using (dynamic iEconService = WebAPI.GetInterface(IEconService, steamApiKey)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
iEconService.Timeout = Timeout;
|
2016-07-01 16:33:27 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
try {
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
response = iEconService.GetTradeOffers(
|
|
|
|
|
|
get_received_offers: 1,
|
|
|
|
|
|
active_only: 1,
|
|
|
|
|
|
get_descriptions: 1,
|
|
|
|
|
|
secure: true
|
|
|
|
|
|
);
|
2016-11-24 07:32:16 +01:00
|
|
|
|
} catch (Exception e) {
|
2017-01-25 23:23:19 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarningException(e);
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
2016-07-01 16:33:27 +02:00
|
|
|
|
}
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
2016-07-01 16:33:27 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (response == null) {
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxRetries));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptions = new Dictionary<ulong, Tuple<uint, Steam.Item.EType>>();
|
|
|
|
|
|
foreach (KeyValue description in response["descriptions"].Children) {
|
|
|
|
|
|
ulong classID = description["classid"].AsUnsignedLong();
|
2016-07-01 16:33:27 +02:00
|
|
|
|
if (classID == 0) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(classID));
|
|
|
|
|
|
return null;
|
2016-07-01 16:33:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (descriptions.ContainsKey(classID)) {
|
|
|
|
|
|
continue;
|
2016-07-01 16:33:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
uint appID = 0;
|
2016-07-01 16:33:27 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string hashName = description["market_hash_name"].Value;
|
|
|
|
|
|
if (!string.IsNullOrEmpty(hashName)) {
|
|
|
|
|
|
appID = GetAppIDFromMarketHashName(hashName);
|
2016-07-01 16:33:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (appID == 0) {
|
|
|
|
|
|
appID = description["appid"].AsUnsignedInteger();
|
|
|
|
|
|
}
|
2016-07-01 16:33:27 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
2016-07-01 16:33:27 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string descriptionType = description["type"].Value;
|
|
|
|
|
|
if (!string.IsNullOrEmpty(descriptionType)) {
|
|
|
|
|
|
type = GetItemType(descriptionType);
|
|
|
|
|
|
}
|
2016-03-11 02:07:20 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
descriptions[classID] = new Tuple<uint, Steam.Item.EType>(appID, type);
|
|
|
|
|
|
}
|
2016-04-12 16:58:45 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
|
|
|
|
|
|
foreach (KeyValue trade in response["trade_offers_received"].Children) {
|
|
|
|
|
|
Steam.TradeOffer.ETradeOfferState state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>();
|
|
|
|
|
|
if (state == Steam.TradeOffer.ETradeOfferState.Unknown) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(state));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (state != Steam.TradeOffer.ETradeOfferState.Active) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2016-07-08 07:41:36 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
ulong tradeOfferID = trade["tradeofferid"].AsUnsignedLong();
|
|
|
|
|
|
if (tradeOfferID == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(tradeOfferID));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2016-06-20 13:45:12 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
uint otherSteamID3 = trade["accountid_other"].AsUnsignedInteger();
|
|
|
|
|
|
if (otherSteamID3 == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(otherSteamID3));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Steam.TradeOffer tradeOffer = new Steam.TradeOffer(tradeOfferID, otherSteamID3, state);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
List<KeyValue> itemsToGive = trade["items_to_give"].Children;
|
|
|
|
|
|
if (itemsToGive.Count > 0) {
|
|
|
|
|
|
if (!ParseItems(descriptions, itemsToGive, tradeOffer.ItemsToGive)) {
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorParsingObject, nameof(itemsToGive)));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
List<KeyValue> itemsToReceive = trade["items_to_receive"].Children;
|
|
|
|
|
|
if (itemsToReceive.Count > 0) {
|
|
|
|
|
|
if (!ParseItems(descriptions, itemsToReceive, tradeOffer.ItemsToReceive)) {
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorParsingObject, nameof(itemsToReceive)));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
result.Add(tradeOffer);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal async Task<HtmlDocument> GetBadgePage(byte page) {
|
|
|
|
|
|
if (page == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(page));
|
|
|
|
|
|
return null;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
|
|
|
|
|
|
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(string deviceID, string confirmationHash, uint time, MobileAuthenticator.Confirmation confirmation) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmation == null)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmation));
|
|
|
|
|
|
return null;
|
2016-08-06 16:29:05 +02:00
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
2016-08-06 16:29:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string request = SteamCommunityURL + "/mobileconf/details/" + confirmation.ID + "?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
|
2016-09-30 01:44:14 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Steam.ConfirmationDetails response = await WebBrowser.UrlGetToJsonResultRetry<Steam.ConfirmationDetails>(request).ConfigureAwait(false);
|
|
|
|
|
|
if ((response == null) || !response.Success) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2016-09-30 01:44:14 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
response.Confirmation = confirmation;
|
|
|
|
|
|
return response;
|
|
|
|
|
|
}
|
2016-02-28 21:24:50 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal async Task<HtmlDocument> GetConfirmations(string deviceID, string confirmationHash, uint time) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2016-08-06 16:29:05 +02:00
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
}
|
2016-03-27 23:07:00 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
|
|
|
|
|
|
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-02 19:59:33 +01:00
|
|
|
|
/*
|
2016-12-23 03:01:37 +01:00
|
|
|
|
internal async Task<HtmlDocument> GetDiscoveryQueuePage() {
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-12-23 03:01:37 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-23 18:51:45 +01:00
|
|
|
|
const string request = SteamStoreURL + "/explore?l=english";
|
2016-12-23 03:01:37 +01:00
|
|
|
|
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
}
|
2017-01-02 19:59:33 +01:00
|
|
|
|
*/
|
2016-12-23 03:01:37 +01:00
|
|
|
|
|
2016-09-30 01:44:14 +02:00
|
|
|
|
internal async Task<HashSet<ulong>> GetFamilySharingSteamIDs() {
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-09-30 01:44:14 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-23 18:51:45 +01:00
|
|
|
|
const string request = SteamStoreURL + "/account/managedevices";
|
2016-09-30 01:44:14 +02:00
|
|
|
|
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
HtmlNodeCollection htmlNodes = htmlDocument?.DocumentNode.SelectNodes("(//table[@class='accountTable'])[last()]//a/@data-miniprofile");
|
|
|
|
|
|
if (htmlNodes == null) {
|
|
|
|
|
|
return null; // OK, no authorized steamIDs
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HashSet<ulong> result = new HashSet<ulong>();
|
|
|
|
|
|
|
2016-10-01 00:25:15 +02:00
|
|
|
|
foreach (string miniProfile in htmlNodes.Select(htmlNode => htmlNode.GetAttributeValue("data-miniprofile", null))) {
|
2016-09-30 01:44:14 +02:00
|
|
|
|
if (string.IsNullOrEmpty(miniProfile)) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(miniProfile));
|
2016-09-30 01:44:14 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint steamID3;
|
2016-10-01 00:25:15 +02:00
|
|
|
|
if (!uint.TryParse(miniProfile, out steamID3) || (steamID3 == 0)) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(steamID3));
|
2016-09-30 01:44:14 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ulong steamID = new SteamID(steamID3, EUniverse.Public, EAccountType.Individual);
|
|
|
|
|
|
result.Add(steamID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
|
|
|
|
|
|
if (appID == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(appID));
|
|
|
|
|
|
return null;
|
2016-11-17 21:30:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
2016-11-17 21:30:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
|
|
|
|
|
|
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
internal async Task<Dictionary<uint, string>> GetMyOwnedGames() {
|
|
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const string request = SteamCommunityURL + "/my/games/?xml=1";
|
|
|
|
|
|
|
|
|
|
|
|
XmlDocument response = await WebBrowser.UrlGetToXMLRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
XmlNodeList xmlNodeList = response?.SelectNodes("gamesList/games/game");
|
|
|
|
|
|
if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) {
|
|
|
|
|
|
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) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(appNode));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint appID;
|
|
|
|
|
|
if (!uint.TryParse(appNode.InnerText, out appID)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(appID));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
XmlNode nameNode = xmlNode.SelectSingleNode("name");
|
|
|
|
|
|
if (nameNode == null) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(nameNode));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result[appID] = nameNode.InnerText;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-25 05:52:17 +01:00
|
|
|
|
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable, HashSet<Steam.Item.EType> wantedTypes) {
|
|
|
|
|
|
if ((wantedTypes == null) || (wantedTypes.Count == 0)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(wantedTypes));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
2016-11-17 21:30:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
2016-11-17 21:30:17 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamCommunityContextID + "?l=english&trading=" + (tradable ? "1" : "0") + "&start=";
|
|
|
|
|
|
uint currentPage = 0;
|
2016-11-17 21:30:17 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
while (true) {
|
|
|
|
|
|
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request + currentPage).ConfigureAwait(false);
|
2016-01-14 02:48:56 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
IEnumerable<JToken> descriptions = jObject?.SelectTokens("$.rgDescriptions.*");
|
|
|
|
|
|
if (descriptions == null) {
|
|
|
|
|
|
return null; // OK, empty inventory
|
|
|
|
|
|
}
|
2015-12-16 22:05:42 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptionMap = new Dictionary<ulong, Tuple<uint, Steam.Item.EType>>();
|
|
|
|
|
|
foreach (JToken description in descriptions.Where(description => description != null)) {
|
|
|
|
|
|
string classIDString = description["classid"]?.ToString();
|
|
|
|
|
|
if (string.IsNullOrEmpty(classIDString)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(classIDString));
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2015-12-16 22:14:53 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
ulong classID;
|
|
|
|
|
|
if (!ulong.TryParse(classIDString, out classID) || (classID == 0)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(classID));
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2016-03-29 14:33:05 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (descriptionMap.ContainsKey(classID)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2015-12-16 22:05:42 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
uint appID = 0;
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string hashName = description["market_hash_name"]?.ToString();
|
|
|
|
|
|
if (!string.IsNullOrEmpty(hashName)) {
|
|
|
|
|
|
appID = GetAppIDFromMarketHashName(hashName);
|
|
|
|
|
|
}
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (appID == 0) {
|
|
|
|
|
|
string appIDString = description["appid"]?.ToString();
|
|
|
|
|
|
if (string.IsNullOrEmpty(appIDString)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(appIDString));
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (!uint.TryParse(appIDString, out appID) || (appID == 0)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(appID));
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
2016-06-19 09:34:09 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string descriptionType = description["type"]?.ToString();
|
|
|
|
|
|
if (!string.IsNullOrEmpty(descriptionType)) {
|
|
|
|
|
|
type = GetItemType(descriptionType);
|
|
|
|
|
|
}
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
descriptionMap[classID] = new Tuple<uint, Steam.Item.EType>(appID, type);
|
|
|
|
|
|
}
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
IEnumerable<JToken> items = jObject.SelectTokens("$.rgInventory.*");
|
|
|
|
|
|
if (items == null) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(items));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
foreach (JToken item in items.Where(item => item != null)) {
|
|
|
|
|
|
Steam.Item steamItem;
|
2016-08-21 22:35:31 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
try {
|
|
|
|
|
|
steamItem = item.ToObject<Steam.Item>();
|
|
|
|
|
|
} catch (JsonException e) {
|
|
|
|
|
|
Bot.ArchiLogger.LogGenericException(e);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2016-08-21 22:35:31 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (steamItem == null) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(steamItem));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2016-08-21 22:35:31 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
steamItem.AppID = Steam.Item.SteamAppID;
|
|
|
|
|
|
steamItem.ContextID = Steam.Item.SteamCommunityContextID;
|
2016-08-21 22:35:31 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Tuple<uint, Steam.Item.EType> description;
|
|
|
|
|
|
if (descriptionMap.TryGetValue(steamItem.ClassID, out description)) {
|
|
|
|
|
|
steamItem.RealAppID = description.Item1;
|
|
|
|
|
|
steamItem.Type = description.Item2;
|
|
|
|
|
|
}
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-12-25 05:52:17 +01:00
|
|
|
|
if (!wantedTypes.Contains(steamItem.Type)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
result.Add(steamItem);
|
|
|
|
|
|
}
|
2016-06-19 09:34:09 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
bool more;
|
|
|
|
|
|
if (!bool.TryParse(jObject["more"]?.ToString(), out more) || !more) {
|
|
|
|
|
|
break; // OK, last page
|
|
|
|
|
|
}
|
2016-07-16 21:03:39 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
uint nextPage;
|
|
|
|
|
|
if (!uint.TryParse(jObject["more_start"]?.ToString(), out nextPage) || (nextPage <= currentPage)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(nextPage));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2016-07-16 21:03:39 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
currentPage = nextPage;
|
2016-07-15 16:13:36 +02:00
|
|
|
|
}
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return result;
|
2016-06-19 05:40:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
internal async Task<Dictionary<uint, string>> GetOwnedGames(ulong steamID) {
|
|
|
|
|
|
if (steamID == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(steamID));
|
2016-03-15 04:20:28 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
string steamApiKey = await GetApiKey().ConfigureAwait(false);
|
2017-02-08 14:35:01 +01:00
|
|
|
|
if (string.IsNullOrEmpty(steamApiKey)) {
|
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++) {
|
2017-01-11 15:22:00 +01:00
|
|
|
|
using (dynamic iPlayerService = WebAPI.GetInterface(IPlayerService, steamApiKey)) {
|
2016-06-22 02:02:43 +02:00
|
|
|
|
iPlayerService.Timeout = Timeout;
|
2016-04-29 15:44:11 +02:00
|
|
|
|
|
|
|
|
|
|
try {
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
response = iPlayerService.GetOwnedGames(
|
|
|
|
|
|
steamid: steamID,
|
|
|
|
|
|
include_appinfo: 1,
|
|
|
|
|
|
secure: true
|
|
|
|
|
|
);
|
2016-04-29 15:44:11 +02:00
|
|
|
|
} catch (Exception e) {
|
2017-01-25 23:23:19 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarningException(e);
|
2016-04-29 15:44:11 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxRetries));
|
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-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(appID));
|
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++) {
|
2017-01-06 13:20:36 +01:00
|
|
|
|
using (dynamic iTwoFactorService = WebAPI.GetInterface(ITwoFactorService)) {
|
2016-06-22 02:02:43 +02:00
|
|
|
|
iTwoFactorService.Timeout = Timeout;
|
2016-06-19 05:40:46 +02:00
|
|
|
|
|
|
|
|
|
|
try {
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
response = iTwoFactorService.QueryTime(
|
|
|
|
|
|
method: WebRequestMethods.Http.Post,
|
|
|
|
|
|
secure: true
|
|
|
|
|
|
);
|
2016-06-19 05:40:46 +02:00
|
|
|
|
} catch (Exception e) {
|
2017-01-25 23:23:19 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarningException(e);
|
2016-06-19 05:40:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response != null) {
|
2016-07-08 07:11:37 +02:00
|
|
|
|
return response["server_time"].AsUnsignedInteger();
|
2016-06-19 05:40:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxRetries));
|
2016-06-19 05:40:46 +02:00
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-02 19:59:33 +01:00
|
|
|
|
/*
|
2016-12-23 03:01:37 +01:00
|
|
|
|
internal async Task<HtmlDocument> GetSteamAwardsPage() {
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-12-23 03:01:37 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-23 18:51:45 +01:00
|
|
|
|
const string request = SteamStoreURL + "/SteamAwards?l=english";
|
2016-12-23 03:01:37 +01:00
|
|
|
|
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
}
|
2017-01-02 19:59:33 +01:00
|
|
|
|
*/
|
2016-12-23 03:01:37 +01:00
|
|
|
|
|
2016-06-12 02:18:18 +02:00
|
|
|
|
internal async Task<byte?> GetTradeHoldDuration(ulong tradeID) {
|
|
|
|
|
|
if (tradeID == 0) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(tradeID));
|
2016-06-12 02:18:18 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-06-29 09:11:59 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
2016-07-10 20:17:35 +02:00
|
|
|
|
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)) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(text));
|
2016-06-12 02:18:18 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int index = text.IndexOf("g_daysTheirEscrow = ", StringComparison.Ordinal);
|
|
|
|
|
|
if (index < 0) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(index));
|
2016-06-12 02:18:18 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
index += 20;
|
|
|
|
|
|
text = text.Substring(index);
|
|
|
|
|
|
|
|
|
|
|
|
index = text.IndexOf(';');
|
|
|
|
|
|
if (index < 0) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(index));
|
2016-06-12 02:18:18 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
text = text.Substring(0, index);
|
|
|
|
|
|
|
|
|
|
|
|
byte holdDuration;
|
|
|
|
|
|
if (byte.TryParse(text, out holdDuration)) {
|
|
|
|
|
|
return holdDuration;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(holdDuration));
|
2016-06-12 02:18:18 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01: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)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey));
|
|
|
|
|
|
return null;
|
2016-10-12 22:56:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return null;
|
2016-10-12 22:56:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01: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;
|
2016-10-12 22:56:19 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Steam.ConfirmationResponse response = await WebBrowser.UrlGetToJsonResultRetry<Steam.ConfirmationResponse>(request).ConfigureAwait(false);
|
|
|
|
|
|
return response?.Success;
|
2016-10-12 22:56:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal async Task<bool?> HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet<MobileAuthenticator.Confirmation> confirmations, bool accept) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations));
|
2015-10-28 20:01:43 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2015-10-25 06:16:50 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
const string request = SteamCommunityURL + "/mobileconf/multiajaxop";
|
2016-11-24 07:49:44 +01:00
|
|
|
|
List<KeyValuePair<string, string>> data = new List<KeyValuePair<string, string>>(7 + confirmations.Count * 2) {
|
2016-11-24 07:46:37 +01:00
|
|
|
|
new KeyValuePair<string, string>("op", accept ? "allow" : "cancel"),
|
|
|
|
|
|
new KeyValuePair<string, string>("p", deviceID),
|
|
|
|
|
|
new KeyValuePair<string, string>("a", SteamID.ToString()),
|
|
|
|
|
|
new KeyValuePair<string, string>("k", confirmationHash),
|
|
|
|
|
|
new KeyValuePair<string, string>("t", time.ToString()),
|
|
|
|
|
|
new KeyValuePair<string, string>("m", "android"),
|
|
|
|
|
|
new KeyValuePair<string, string>("tag", "conf")
|
|
|
|
|
|
};
|
2016-04-20 21:27:57 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
foreach (MobileAuthenticator.Confirmation confirmation in confirmations) {
|
|
|
|
|
|
data.Add(new KeyValuePair<string, string>("cid[]", confirmation.ID.ToString()));
|
|
|
|
|
|
data.Add(new KeyValuePair<string, string>("ck[]", confirmation.Key.ToString()));
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
2015-10-28 22:34:53 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Steam.ConfirmationResponse response = await WebBrowser.UrlPostToJsonResultRetry<Steam.ConfirmationResponse>(request, data).ConfigureAwait(false);
|
|
|
|
|
|
return response?.Success;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-08 14:35:01 +01:00
|
|
|
|
internal async Task<bool> HasPublicInventory() {
|
|
|
|
|
|
if (CachedPublicInventory.HasValue) {
|
|
|
|
|
|
return CachedPublicInventory.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-08 14:36:10 +01:00
|
|
|
|
// We didn't fetch state yet
|
2017-02-08 14:35:01 +01:00
|
|
|
|
await PublicInventorySemaphore.WaitAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (CachedPublicInventory.HasValue) {
|
|
|
|
|
|
return CachedPublicInventory.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool? isInventoryPublic = await IsInventoryPublic().ConfigureAwait(false);
|
|
|
|
|
|
if (!isInventoryPublic.HasValue) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CachedPublicInventory = isInventoryPublic.Value;
|
|
|
|
|
|
return isInventoryPublic.Value;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
PublicInventorySemaphore.Release();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
internal async Task<bool> HasValidApiKey() => !string.IsNullOrEmpty(await GetApiKey().ConfigureAwait(false));
|
|
|
|
|
|
|
2017-01-22 20:10:28 +01:00
|
|
|
|
internal static void Init() => Timeout = Program.GlobalConfig.ConnectionTimeout * 1000;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
|
|
|
|
|
|
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin));
|
|
|
|
|
|
return false;
|
2016-06-04 22:02:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
SteamID = steamID;
|
2016-06-04 22:02:38 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
|
2016-06-04 22:02:38 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
// Generate an AES session key
|
|
|
|
|
|
byte[] sessionKey = SteamKit2.CryptoHelper.GenerateRandomBlock(32);
|
2016-06-04 22:02:38 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
// RSA encrypt it with the public key for the universe we're on
|
|
|
|
|
|
byte[] cryptedSessionKey;
|
|
|
|
|
|
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) {
|
|
|
|
|
|
cryptedSessionKey = rsa.Encrypt(sessionKey);
|
2016-03-29 14:33:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
// Copy our login key
|
|
|
|
|
|
byte[] loginKey = new byte[webAPIUserNonce.Length];
|
|
|
|
|
|
Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
|
2016-04-22 17:51:13 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
// AES encrypt the loginkey with our session key
|
|
|
|
|
|
byte[] cryptedLoginKey = SteamKit2.CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
// Do the magic
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.LoggingIn, ISteamUserAuth));
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
KeyValue authResult;
|
2017-01-06 13:20:36 +01:00
|
|
|
|
using (dynamic iSteamUserAuth = WebAPI.GetInterface(ISteamUserAuth)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
iSteamUserAuth.Timeout = Timeout;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
try {
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
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
|
|
|
|
|
|
);
|
2016-11-24 07:32:16 +01:00
|
|
|
|
} catch (Exception e) {
|
2017-01-25 23:23:19 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericWarningException(e);
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (authResult == null) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(authResult));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-04-21 02:32:36 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string steamLogin = authResult["token"].Value;
|
|
|
|
|
|
if (string.IsNullOrEmpty(steamLogin)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(steamLogin));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-04-21 02:32:36 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string steamLoginSecure = authResult["tokensecure"].Value;
|
|
|
|
|
|
if (string.IsNullOrEmpty(steamLoginSecure)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(steamLoginSecure));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-04-21 02:32:36 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
|
|
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamStoreHost));
|
2016-06-10 01:16:05 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
|
|
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamStoreHost));
|
2016-06-10 01:16:05 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
|
|
|
|
|
|
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamStoreHost));
|
2016-06-08 13:01:41 +02:00
|
|
|
|
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericInfo(Strings.Success);
|
2016-04-21 02:32:36 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
// Unlock Steam Parental if needed
|
|
|
|
|
|
if (!parentalPin.Equals("0")) {
|
|
|
|
|
|
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
2016-04-21 00:51:22 +02:00
|
|
|
|
}
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
Ready = true;
|
2017-01-23 00:46:44 +01:00
|
|
|
|
LastSessionRefreshCheck = DateTime.UtcNow;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal async Task<bool> JoinGroup(ulong groupID) {
|
|
|
|
|
|
if (groupID == 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(groupID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-02-22 18:34:45 +01:00
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(sessionID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
string request = SteamCommunityURL + "/gid/" + groupID;
|
2016-11-24 07:46:37 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
|
|
|
|
|
{ "sessionID", sessionID },
|
|
|
|
|
|
{ "action", "join" }
|
|
|
|
|
|
};
|
2016-10-01 00:25:15 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal async Task<bool> MarkInventory() {
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
const string request = SteamCommunityURL + "/my/inventory";
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
}
|
2016-04-21 00:51:22 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
internal void OnDisconnected() => Ready = false;
|
2016-05-30 01:57:06 +02:00
|
|
|
|
|
2017-02-11 22:24:49 +01:00
|
|
|
|
internal async Task<EPurchaseResultDetail> RedeemWalletKey(string key) {
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (string.IsNullOrEmpty(key)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(key));
|
2017-02-11 22:24:49 +01:00
|
|
|
|
return EPurchaseResultDetail.Timeout;
|
2016-01-13 11:17:58 +02:00
|
|
|
|
}
|
2016-01-14 01:30:12 +01:00
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2017-02-11 22:24:49 +01:00
|
|
|
|
return EPurchaseResultDetail.Timeout;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-23 18:51:45 +01:00
|
|
|
|
const string request = SteamStoreURL + "/account/validatewalletcode";
|
2016-11-24 07:46:37 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
|
|
|
|
|
{ "wallet_code", key }
|
|
|
|
|
|
};
|
2016-11-24 07:32:16 +01:00
|
|
|
|
|
|
|
|
|
|
Steam.RedeemWalletResponse response = await WebBrowser.UrlPostToJsonResultRetry<Steam.RedeemWalletResponse>(request, data).ConfigureAwait(false);
|
2017-02-11 22:24:49 +01:00
|
|
|
|
return response?.PurchaseResultDetail ?? EPurchaseResultDetail.Timeout;
|
2016-01-13 11:17:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
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-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(inventory.Count) + " || " + nameof(partnerID));
|
2016-01-13 11:17:58 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-03-29 14:33:05 +02:00
|
|
|
|
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-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(sessionID));
|
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-10-01 00:36:48 +02:00
|
|
|
|
singleTrade.ItemsToGive.Assets.Add(item);
|
2016-04-20 21:27:57 +02:00
|
|
|
|
itemID++;
|
2016-01-13 11:17:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
const string referer = SteamCommunityURL + "/tradeoffer/new";
|
|
|
|
|
|
const string request = referer + "/send";
|
2016-12-01 08:29:42 +01: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) },
|
2016-12-04 05:50:11 +01:00
|
|
|
|
{ "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : new JObject { { "trade_offer_access_token", token } }.ToString(Formatting.None) }
|
2016-12-01 08:29:42 +01:00
|
|
|
|
})) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
private async Task<string> GetApiKey(bool allowRegister = true) {
|
2017-02-08 14:35:01 +01:00
|
|
|
|
if (CachedSteamApiKey != null) {
|
2017-01-11 15:41:02 +01:00
|
|
|
|
// We fetched API key already, and either got valid one, or permanent AccessDenied
|
|
|
|
|
|
// In any case, this is our final result
|
2017-02-08 14:35:01 +01:00
|
|
|
|
return CachedSteamApiKey;
|
2017-01-11 15:41:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// We didn't fetch API key yet
|
|
|
|
|
|
await SteamApiKeySemaphore.WaitAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2017-02-08 14:35:01 +01:00
|
|
|
|
if (CachedSteamApiKey != null) {
|
|
|
|
|
|
return CachedSteamApiKey;
|
2017-01-11 15:41:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tuple<ESteamApiKeyState, string> result = await GetApiKeyState().ConfigureAwait(false);
|
|
|
|
|
|
if (result == null) {
|
|
|
|
|
|
// Request timed out, bad luck, we'll try again later
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (result.Item1) {
|
|
|
|
|
|
case ESteamApiKeyState.Registered:
|
|
|
|
|
|
// We succeeded in fetching API key, and it resulted in registered key
|
|
|
|
|
|
// Cache the result and return it
|
2017-02-08 14:35:01 +01:00
|
|
|
|
CachedSteamApiKey = result.Item2;
|
|
|
|
|
|
return CachedSteamApiKey;
|
2017-01-11 15:41:02 +01:00
|
|
|
|
case ESteamApiKeyState.NotRegisteredYet:
|
|
|
|
|
|
// We succeeded in fetching API key, and it resulted in no key registered yet
|
|
|
|
|
|
if (!allowRegister) {
|
|
|
|
|
|
// But this call doesn't allow us to register it, so return null
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2017-01-11 15:22:00 +01:00
|
|
|
|
|
2017-01-11 15:41:02 +01:00
|
|
|
|
// If we're allowed to register the key, let's do so
|
|
|
|
|
|
if (!await RegisterApiKey().ConfigureAwait(false)) {
|
|
|
|
|
|
// Request timed out, bad luck, we'll try again later
|
2017-01-11 15:22:00 +01:00
|
|
|
|
return null;
|
2017-01-11 15:41:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// We should have the key ready, so let's fetch it again
|
|
|
|
|
|
result = await GetApiKeyState().ConfigureAwait(false);
|
|
|
|
|
|
if (result?.Item1 != ESteamApiKeyState.Registered) {
|
|
|
|
|
|
// Something went wrong, bad luck, we'll try again later
|
2017-01-11 15:22:00 +01:00
|
|
|
|
return null;
|
2017-01-11 15:41:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
goto case ESteamApiKeyState.Registered;
|
|
|
|
|
|
case ESteamApiKeyState.AccessDenied:
|
|
|
|
|
|
// We succeeded in fetching API key, but it resulted in access denied
|
|
|
|
|
|
// Cache the result as empty, and return null
|
2017-02-08 14:35:01 +01:00
|
|
|
|
CachedSteamApiKey = "";
|
2017-01-11 15:41:02 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
default:
|
|
|
|
|
|
// We got some kind of error, maybe it's temporary, maybe it's permanent
|
|
|
|
|
|
// Don't cache anything, we'll try again later
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
SteamApiKeySemaphore.Release();
|
2017-01-11 15:22:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<Tuple<ESteamApiKeyState, string>> GetApiKeyState() {
|
|
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const string request = SteamCommunityURL + "/dev/apikey?l=english";
|
|
|
|
|
|
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
HtmlNode titleNode = htmlDocument?.DocumentNode.SelectSingleNode("//div[@id='mainContents']/h2");
|
|
|
|
|
|
if (titleNode == null) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string title = titleNode.InnerText;
|
|
|
|
|
|
if (string.IsNullOrEmpty(title)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(title));
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.Error, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (title.Contains("Access Denied")) {
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.AccessDenied, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@id='bodyContents_ex']/p");
|
|
|
|
|
|
if (htmlNode == null) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(htmlNode));
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.Error, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string text = htmlNode.InnerText;
|
|
|
|
|
|
if (string.IsNullOrEmpty(text)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(text));
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.Error, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (text.Contains("Registering for a Steam Web API Key")) {
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.NotRegisteredYet, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int keyIndex = text.IndexOf("Key: ", StringComparison.Ordinal);
|
|
|
|
|
|
if (keyIndex < 0) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(keyIndex));
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.Error, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
keyIndex += 5;
|
|
|
|
|
|
|
|
|
|
|
|
if (text.Length <= keyIndex) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(text));
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.Error, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
text = text.Substring(keyIndex);
|
|
|
|
|
|
if (text.Length != 32) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(text));
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.Error, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Utilities.IsValidHexadecimalString(text)) {
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.Registered, text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(text));
|
|
|
|
|
|
return new Tuple<ESteamApiKeyState, string>(ESteamApiKeyState.Error, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-02 19:59:33 +01:00
|
|
|
|
/*
|
2016-12-23 03:01:37 +01:00
|
|
|
|
internal async Task<bool> SteamAwardsVote(byte voteID, uint appID) {
|
|
|
|
|
|
if ((voteID == 0) || (appID == 0)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(voteID) + " || " + nameof(appID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
2016-12-23 03:01:37 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamStoreURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(sessionID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-23 18:51:45 +01:00
|
|
|
|
const string request = SteamStoreURL + "/salevote";
|
2016-12-23 03:01:37 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(3) {
|
|
|
|
|
|
{ "sessionid", sessionID },
|
|
|
|
|
|
{ "voteid", voteID.ToString() },
|
|
|
|
|
|
{ "appid", appID.ToString() }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
|
|
|
|
|
|
}
|
2017-01-02 19:59:33 +01:00
|
|
|
|
*/
|
2016-12-23 03:01:37 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
private static uint GetAppIDFromMarketHashName(string hashName) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(hashName)) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(hashName));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return 0;
|
2016-01-14 02:48:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
int index = hashName.IndexOf('-');
|
|
|
|
|
|
if (index <= 0) {
|
|
|
|
|
|
return 0;
|
2016-03-29 14:33:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
uint appID;
|
|
|
|
|
|
return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
private static Steam.Item.EType GetItemType(string name) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(name)) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(name));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return Steam.Item.EType.Unknown;
|
2016-01-14 02:48:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
switch (name) {
|
|
|
|
|
|
case "Booster Pack":
|
|
|
|
|
|
return Steam.Item.EType.BoosterPack;
|
|
|
|
|
|
case "Steam Gems":
|
|
|
|
|
|
return Steam.Item.EType.SteamGems;
|
|
|
|
|
|
default:
|
|
|
|
|
|
if (name.EndsWith("Emoticon", StringComparison.Ordinal)) {
|
|
|
|
|
|
return Steam.Item.EType.Emoticon;
|
|
|
|
|
|
}
|
2016-03-29 14:33:05 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
|
|
|
|
|
|
return Steam.Item.EType.FoilTradingCard;
|
|
|
|
|
|
}
|
2016-02-22 18:34:45 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
if (name.EndsWith("Profile Background", StringComparison.Ordinal)) {
|
|
|
|
|
|
return Steam.Item.EType.ProfileBackground;
|
|
|
|
|
|
}
|
2016-03-29 14:33:05 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return name.EndsWith("Trading Card", StringComparison.Ordinal) ? Steam.Item.EType.TradingCard : Steam.Item.EType.Unknown;
|
|
|
|
|
|
}
|
2016-02-28 03:40:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-08 14:35:01 +01:00
|
|
|
|
private async Task<bool?> IsInventoryPublic() {
|
|
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const string request = SteamCommunityURL + "/my/edit/settings?l=english";
|
|
|
|
|
|
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
HtmlNode htmlNode = htmlDocument?.DocumentNode.SelectSingleNode("//input[@id='inventoryPrivacySetting_public']");
|
|
|
|
|
|
if (htmlNode == null) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Notice: checked doesn't have a value - null is lack of attribute, "" is attribute existing
|
|
|
|
|
|
string state = htmlNode.GetAttributeValue("checked", null);
|
|
|
|
|
|
|
|
|
|
|
|
return state != null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
const 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);
|
2016-07-10 20:17:35 +02:00
|
|
|
|
return !uri?.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
|
2016-04-24 23:32:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
private static bool ParseItems(Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptions, List<KeyValue> input, HashSet<Steam.Item> output) {
|
|
|
|
|
|
if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (KeyValue item in input) {
|
|
|
|
|
|
uint appID = item["appid"].AsUnsignedInteger();
|
|
|
|
|
|
if (appID == 0) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(appID));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ulong contextID = item["contextid"].AsUnsignedLong();
|
|
|
|
|
|
if (contextID == 0) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(contextID));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ulong classID = item["classid"].AsUnsignedLong();
|
|
|
|
|
|
if (classID == 0) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(classID));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint amount = item["amount"].AsUnsignedInteger();
|
|
|
|
|
|
if (amount == 0) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(amount));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint realAppID = appID;
|
|
|
|
|
|
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-04-24 23:32:23 +02:00
|
|
|
|
private async Task<bool> RefreshSessionIfNeeded() {
|
2017-01-23 00:46:44 +01:00
|
|
|
|
if (DateTime.UtcNow.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
2016-04-24 23:32:23 +02:00
|
|
|
|
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-09-21 18:22:27 +02:00
|
|
|
|
try {
|
2017-01-23 00:46:44 +01:00
|
|
|
|
if (DateTime.UtcNow.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
|
2016-09-21 18:22:27 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2016-04-24 23:32:23 +02:00
|
|
|
|
|
2016-09-21 18:22:27 +02:00
|
|
|
|
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
|
|
|
|
|
|
if (isLoggedIn.GetValueOrDefault(true)) {
|
2017-01-23 00:46:44 +01:00
|
|
|
|
LastSessionRefreshCheck = DateTime.UtcNow;
|
2016-09-21 18:22:27 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
} else {
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericInfo(Strings.RefreshingOurSession);
|
2016-09-21 18:22:27 +02:00
|
|
|
|
return await Bot.RefreshSession().ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
SessionSemaphore.Release();
|
2016-03-27 23:07:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-11 15:22:00 +01:00
|
|
|
|
private async Task<bool> RegisterApiKey() {
|
|
|
|
|
|
if (!Ready || !await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
|
|
|
|
|
if (string.IsNullOrEmpty(sessionID)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(sessionID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const string request = SteamCommunityURL + "/dev/registerkey";
|
|
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(4) {
|
|
|
|
|
|
{ "domain", "localhost" },
|
|
|
|
|
|
{ "agreeToTerms", "agreed" },
|
|
|
|
|
|
{ "sessionid", sessionID },
|
|
|
|
|
|
{ "Submit", "Register" }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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)) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(parentalPin));
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-06 13:20:36 +01:00
|
|
|
|
Bot.ArchiLogger.LogGenericInfo(Strings.UnlockingParentalAccount);
|
2016-04-24 23:32:23 +02:00
|
|
|
|
|
2017-01-17 18:25:38 +01:00
|
|
|
|
if (!await UnlockParentalCommunityAccount(parentalPin).ConfigureAwait(false)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!await UnlockParentalStoreAccount(parentalPin).ConfigureAwait(false)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Bot.ArchiLogger.LogGenericInfo(Strings.Success);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<bool> UnlockParentalCommunityAccount(string parentalPin) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(parentalPin)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(parentalPin));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
|
|
|
|
const string request = SteamCommunityURL + "/parental/ajaxunlock";
|
2016-11-24 07:46:37 +01:00
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
|
|
|
|
|
{ "pin", parentalPin }
|
|
|
|
|
|
};
|
2016-02-22 18:34:45 +01:00
|
|
|
|
|
2017-01-17 18:25:38 +01:00
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data, SteamCommunityURL).ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<bool> UnlockParentalStoreAccount(string parentalPin) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(parentalPin)) {
|
|
|
|
|
|
Bot.ArchiLogger.LogNullError(nameof(parentalPin));
|
2016-03-29 14:33:05 +02:00
|
|
|
|
return false;
|
2016-02-22 18:34:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-17 18:25:38 +01:00
|
|
|
|
const string request = SteamStoreURL + "/parental/ajaxunlock";
|
|
|
|
|
|
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
|
|
|
|
|
{ "pin", parentalPin }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return await WebBrowser.UrlPostRetry(request, data, SteamStoreURL).ConfigureAwait(false);
|
2016-02-22 18:34:45 +01:00
|
|
|
|
}
|
2017-01-11 15:22:00 +01:00
|
|
|
|
|
|
|
|
|
|
private enum ESteamApiKeyState : byte {
|
|
|
|
|
|
Error,
|
|
|
|
|
|
Registered,
|
|
|
|
|
|
NotRegisteredYet,
|
|
|
|
|
|
AccessDenied
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|