mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-18 22:50:50 +08:00
d5006da378
修复仅在steam内置浏览器生效功能有时候没有用的问题
1417 lines
53 KiB
C#
1417 lines
53 KiB
C#
/*
|
|
* Copyright (C) 2015 Colin Mackie.
|
|
* This software is distributed under the terms of the GNU General Public License.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Specialized;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Web;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
#if NETFX_4
|
|
using System.Runtime.Serialization;
|
|
using System.Threading.Tasks;
|
|
#endif
|
|
|
|
namespace WinAuth
|
|
{
|
|
/// <summary>
|
|
/// SteamClient for logging and getting/accepting/rejecting trade confirmations
|
|
/// </summary>
|
|
public class SteamClient : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// URLs for all mobile services
|
|
/// </summary>
|
|
private const string COMMUNITY_DOMAIN = "steamcommunity.com";
|
|
private const string COMMUNITY_BASE = "https://" + COMMUNITY_DOMAIN;
|
|
private const string WEBAPI_BASE = "https://api.steampowered.com";
|
|
private const string API_GETWGTOKEN = WEBAPI_BASE + "/IMobileAuthService/GetWGToken/v0001";
|
|
private const string API_LOGOFF = WEBAPI_BASE + "/ISteamWebUserPresenceOAuth/Logoff/v0001";
|
|
private const string API_LOGON = WEBAPI_BASE + "/ISteamWebUserPresenceOAuth/Logon/v0001";
|
|
private const string API_POLLSTATUS = WEBAPI_BASE + "/ISteamWebUserPresenceOAuth/PollStatus/v0001";
|
|
|
|
/// <summary>
|
|
/// Default mobile user agent
|
|
/// </summary>
|
|
private const string USERAGENT = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
|
|
|
|
/// <summary>
|
|
/// Regular expressions for trade confirmations
|
|
/// </summary>
|
|
private static readonly Regex _tradesRegex = new Regex("\"mobileconf_list_entry\"(.*?)>(.*?)\"mobileconf_list_entry_sep\"", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
|
private static readonly Regex _tradeConfidRegex = new Regex(@"data-confid\s*=\s*""([^""]+)""", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
|
private static readonly Regex _tradeKeyRegex = new Regex(@"data-key\s*=\s*""([^""]+)""", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
|
private static readonly Regex _tradePlayerRegex = new Regex("\"mobileconf_list_entry_icon\"(.*?)src=\"([^\"]+)\"", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
|
private static readonly Regex _tradeDetailsRegex = new Regex("\"mobileconf_list_entry_description\".*?<div>([^<]*)</div>[^<]*<div>([^<]*)</div>[^<]*<div>([^<]*)</div>[^<]*</div>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Number of Confirmation retries
|
|
/// </summary>
|
|
private const int DEFAULT_CONFIRMATIONPOLLER_RETRIES = 3;
|
|
|
|
/// <summary>
|
|
/// Delay between trade confirmation events
|
|
/// </summary>
|
|
public const int CONFIRMATION_EVENT_DELAY = 1000;
|
|
|
|
/// <summary>
|
|
/// Action for Confirmation polling
|
|
/// </summary>
|
|
public enum PollerAction
|
|
{
|
|
None = 0,
|
|
Notify = 1,
|
|
AutoConfirm = 2,
|
|
SilentAutoConfirm = 3
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hold the Confirmation polling data
|
|
/// </summary>
|
|
public class ConfirmationPoller
|
|
{
|
|
/// <summary>
|
|
/// Seconds between polls
|
|
/// </summary>
|
|
public int Duration;
|
|
|
|
/// <summary>
|
|
/// Action for new Confirmation
|
|
/// </summary>
|
|
public PollerAction Action;
|
|
|
|
/// <summary>
|
|
/// List of current Confirmations ids
|
|
/// </summary>
|
|
public List<string> Ids;
|
|
|
|
/// <summary>
|
|
/// Create new ConfirmationPoller object
|
|
/// </summary>
|
|
public ConfirmationPoller()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a JSON string of the object
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override string ToString()
|
|
{
|
|
if (Duration == 0)
|
|
{
|
|
return "null";
|
|
}
|
|
else
|
|
{
|
|
List<string> props = new List<string>
|
|
{
|
|
"\"duration\":" + this.Duration,
|
|
"\"action\":" + (int)this.Action
|
|
};
|
|
if (this.Ids != null)
|
|
{
|
|
props.Add("\"ids\":[" + (this.Ids.Count != 0 ? "\"" + string.Join("\",\"", this.Ids.ToArray()) + "\"" : string.Empty) + "]");
|
|
}
|
|
|
|
return "{" + string.Join(",", props.ToArray()) + "}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new ConfirmationPoller from a JSON string
|
|
/// </summary>
|
|
/// <param name="json">JSON string</param>
|
|
/// <returns>new ConfirmationPoller or null</returns>
|
|
public static ConfirmationPoller FromJSON(string json)
|
|
{
|
|
if (string.IsNullOrEmpty(json) == true || json == "null")
|
|
{
|
|
return null;
|
|
}
|
|
var poller = FromJSON(JObject.Parse(json));
|
|
return (poller.Duration != 0 ? poller : null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new ConfirmationPoller from a JToken
|
|
/// </summary>
|
|
/// <param name="tokens">existing JKToken</param>
|
|
/// <returns></returns>
|
|
public static ConfirmationPoller FromJSON(JToken tokens)
|
|
{
|
|
if (tokens == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var poller = new ConfirmationPoller();
|
|
|
|
var token = tokens.SelectToken("duration");
|
|
if (token != null)
|
|
{
|
|
poller.Duration = token.Value<int>();
|
|
}
|
|
token = tokens.SelectToken("action");
|
|
if (token != null)
|
|
{
|
|
poller.Action = (PollerAction)token.Value<int>();
|
|
}
|
|
token = tokens.SelectToken("ids");
|
|
if (token != null)
|
|
{
|
|
poller.Ids = token.ToObject<List<string>>();
|
|
}
|
|
|
|
return (poller.Duration != 0 ? poller : null);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A class for a single confirmation
|
|
/// </summary>
|
|
public class Confirmation
|
|
{
|
|
public string Id { get; set; }
|
|
public string Key { get; set; }
|
|
public bool Offline { get; set; }
|
|
public bool IsNew { get; set; }
|
|
public string Image { get; set; }
|
|
public string Details { get; set; }
|
|
public string Traded { get; set; }
|
|
public string When { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Session state to remember logins
|
|
/// </summary>
|
|
public class SteamSession
|
|
{
|
|
/// <summary>
|
|
/// User's steam ID
|
|
/// </summary>
|
|
public string SteamId;
|
|
|
|
/// <summary>
|
|
/// Current cookies
|
|
/// </summary>
|
|
public CookieContainer Cookies;
|
|
|
|
/// <summary>
|
|
/// Authorization token
|
|
/// </summary>
|
|
public string OAuthToken;
|
|
|
|
/// <summary>
|
|
/// UMQ id
|
|
/// </summary>
|
|
public string UmqId;
|
|
|
|
/// <summary>
|
|
/// Message id
|
|
/// </summary>
|
|
public int MessageId;
|
|
|
|
/// <summary>
|
|
/// Current polling state
|
|
/// </summary>
|
|
public ConfirmationPoller Confirmations;
|
|
|
|
/// <summary>
|
|
/// Create Session instance
|
|
/// </summary>
|
|
public SteamSession()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create session instance from existing json data
|
|
/// </summary>
|
|
/// <param name="json">json session data</param>
|
|
public SteamSession(string json) : this()
|
|
{
|
|
if (string.IsNullOrEmpty(json) == false)
|
|
{
|
|
try
|
|
{
|
|
this.FromJson(json);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// invalid json
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the session
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
this.OAuthToken = null;
|
|
this.UmqId = null;
|
|
this.Cookies = new CookieContainer();
|
|
this.Confirmations = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get session data that can be saved and imported
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override string ToString()
|
|
{
|
|
return "{\"steamid\":\"" + (this.SteamId ?? string.Empty) + "\","
|
|
+ "\"cookies\":\"" + this.Cookies.GetCookieHeader(new Uri(COMMUNITY_BASE + "/")) + "\","
|
|
+ "\"oauthtoken\":\"" + (this.OAuthToken ?? string.Empty) + "\","
|
|
// + "\"umqid\":\"" + (this.UmqId ?? string.Empty) + "\","
|
|
+ "\"confs\":" + (this.Confirmations != null ? this.Confirmations.ToString() : "null")
|
|
+ "}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert json data into session
|
|
/// </summary>
|
|
/// <param name="json"></param>
|
|
private void FromJson(string json)
|
|
{
|
|
var tokens = JObject.Parse(json);
|
|
var token = tokens.SelectToken("steamid");
|
|
if (token != null)
|
|
{
|
|
this.SteamId = token.Value<string>();
|
|
}
|
|
token = tokens.SelectToken("cookies");
|
|
if (token != null)
|
|
{
|
|
this.Cookies = new CookieContainer();
|
|
|
|
// Net3.5 has a bug that prepends "." to domain, e.g. ".steamcommunity.com"
|
|
var uri = new Uri(COMMUNITY_BASE + "/");
|
|
var match = Regex.Match(token.Value<string>(), @"([^=]+)=([^;]*);?", RegexOptions.Singleline);
|
|
while (match.Success == true)
|
|
{
|
|
this.Cookies.Add(uri, new Cookie(match.Groups[1].Value.Trim(), match.Groups[2].Value.Trim()));
|
|
match = match.NextMatch();
|
|
}
|
|
}
|
|
token = tokens.SelectToken("oauthtoken");
|
|
if (token != null)
|
|
{
|
|
this.OAuthToken = token.Value<string>();
|
|
}
|
|
//token = tokens.SelectToken("umqid");
|
|
//if (token != null)
|
|
//{
|
|
// this.UmqId = token.Value<string>();
|
|
//}
|
|
token = tokens.SelectToken("confs");
|
|
if (token != null)
|
|
{
|
|
this.Confirmations = ConfirmationPoller.FromJSON(token);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Login state fields
|
|
/// </summary>
|
|
public bool InvalidLogin;
|
|
public bool RequiresCaptcha;
|
|
public string CaptchaId;
|
|
public string CaptchaUrl;
|
|
public bool Requires2FA;
|
|
public bool RequiresEmailAuth;
|
|
public string EmailDomain;
|
|
public string Error;
|
|
|
|
/// <summary>
|
|
/// Current session
|
|
/// </summary>
|
|
public SteamSession Session;
|
|
|
|
/// <summary>
|
|
/// Current authenticator
|
|
/// </summary>
|
|
public SteamAuthenticator Authenticator;
|
|
|
|
/// <summary>
|
|
/// Saved Html from GetConfirmations used as template for GetDetails
|
|
/// </summary>
|
|
private string ConfirmationsHtml;
|
|
|
|
/// <summary>
|
|
/// Query string from GetConfirmations used in GetDetails
|
|
/// </summary>
|
|
private string ConfirmationsQuery;
|
|
|
|
/// <summary>
|
|
/// Cancellation token for poller
|
|
/// </summary>
|
|
private CancellationTokenSource _pollerCancellation;
|
|
|
|
|
|
/// <summary>
|
|
/// Number of Confirmation retries
|
|
/// </summary>
|
|
public int ConfirmationPollerRetries = DEFAULT_CONFIRMATIONPOLLER_RETRIES;
|
|
|
|
/// <summary>
|
|
/// Create a new SteamClient
|
|
/// </summary>
|
|
public SteamClient(SteamAuthenticator auth, string session = null)
|
|
{
|
|
this.Authenticator = auth;
|
|
this.Session = new SteamSession(session);
|
|
|
|
if (this.Session.Confirmations != null)
|
|
{
|
|
if (this.IsLoggedIn() == false)
|
|
{
|
|
this.Session.Confirmations = null;
|
|
}
|
|
else
|
|
{
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Refresh();
|
|
PollConfirmations(this.Session.Confirmations);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finalizer
|
|
/// </summary>
|
|
~SteamClient()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose the object
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose this object
|
|
/// </summary>
|
|
/// <param name="disposing"></param>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
// clear resources
|
|
}
|
|
|
|
if (_pollerCancellation != null)
|
|
{
|
|
_pollerCancellation.Cancel();
|
|
_pollerCancellation = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the client state
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
this.InvalidLogin = false;
|
|
this.RequiresCaptcha = false;
|
|
this.CaptchaId = null;
|
|
this.CaptchaUrl = null;
|
|
this.RequiresEmailAuth = false;
|
|
this.EmailDomain = null;
|
|
this.Requires2FA = false;
|
|
this.Error = null;
|
|
|
|
this.Session.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if user is logged in
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool IsLoggedIn()
|
|
{
|
|
return (this.Session != null && string.IsNullOrEmpty(this.Session.OAuthToken) == false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Login to Steam using credentials and optional captcha
|
|
/// </summary>
|
|
/// <param name="username"></param>
|
|
/// <param name="password"></param>
|
|
/// <param name="captchaId"></param>
|
|
/// <param name="captchaText"></param>
|
|
/// <returns>true if successful</returns>
|
|
public bool Login(string username, string password, string captchaId = null, string captchaText = null, string language = null)
|
|
{
|
|
// clear error
|
|
this.Error = null;
|
|
|
|
var data = new NameValueCollection();
|
|
string response;
|
|
|
|
if (this.IsLoggedIn() == false)
|
|
{
|
|
// get session
|
|
if (this.Session.Cookies.Count == 0)
|
|
{
|
|
// .Net3.5 has a bug in CookieContainer that prepends a "." to the domain, i.e. ".steamcommunity.com"
|
|
var cookieuri = new Uri(COMMUNITY_BASE + "/");
|
|
this.Session.Cookies.Add(cookieuri, new Cookie("mobileClientVersion", "3067969+%282.1.3%29"));
|
|
this.Session.Cookies.Add(cookieuri, new Cookie("mobileClient", "android"));
|
|
this.Session.Cookies.Add(cookieuri, new Cookie("steamid", ""));
|
|
this.Session.Cookies.Add(cookieuri, new Cookie("steamLogin", ""));
|
|
this.Session.Cookies.Add(cookieuri, new Cookie("Steam_Language", string.IsNullOrEmpty(language) ? "english" : language));
|
|
this.Session.Cookies.Add(cookieuri, new Cookie("dob", ""));
|
|
|
|
NameValueCollection headers = new NameValueCollection();
|
|
headers.Add("X-Requested-With", "com.valvesoftware.android.steam.community");
|
|
|
|
response = GetString(COMMUNITY_BASE + "/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client", "GET", null, headers);
|
|
}
|
|
|
|
// Steam strips any non-ascii chars from username and password
|
|
username = Regex.Replace(username, @"[^\u0000-\u007F]", string.Empty);
|
|
password = Regex.Replace(password, @"[^\u0000-\u007F]", string.Empty);
|
|
|
|
// get the user's RSA key
|
|
data.Add("username", username);
|
|
response = GetString(COMMUNITY_BASE + "/mobilelogin/getrsakey", "POST", data);
|
|
var rsaresponse = JObject.Parse(response);
|
|
if (rsaresponse.SelectToken("success").Value<bool>() != true)
|
|
{
|
|
this.InvalidLogin = true;
|
|
this.Error = "Unknown username";
|
|
return false;
|
|
}
|
|
|
|
// encrypt password with RSA key
|
|
RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
|
|
string encryptedPassword64;
|
|
using (var rsa = new RSACryptoServiceProvider())
|
|
{
|
|
var passwordBytes = Encoding.ASCII.GetBytes(password);
|
|
var p = rsa.ExportParameters(false);
|
|
p.Exponent = StringToByteArray(rsaresponse.SelectToken("publickey_exp").Value<string>());
|
|
p.Modulus = StringToByteArray(rsaresponse.SelectToken("publickey_mod").Value<string>());
|
|
rsa.ImportParameters(p);
|
|
byte[] encryptedPassword = rsa.Encrypt(passwordBytes, false);
|
|
encryptedPassword64 = Convert.ToBase64String(encryptedPassword);
|
|
}
|
|
|
|
// login request
|
|
data = new NameValueCollection
|
|
{
|
|
{ "password", encryptedPassword64 },
|
|
{ "username", username },
|
|
{ "twofactorcode", this.Authenticator.CurrentCode },
|
|
//data.Add("emailauth", string.Empty);
|
|
{ "loginfriendlyname", "#login_emailauth_friendlyname_mobile" },
|
|
{ "captchagid", (string.IsNullOrEmpty(captchaId) == false ? captchaId : "-1") },
|
|
{ "captcha_text", (string.IsNullOrEmpty(captchaText) == false ? captchaText : "enter above characters") },
|
|
//data.Add("emailsteamid", (string.IsNullOrEmpty(emailcode) == false ? this.SteamId ?? string.Empty : string.Empty));
|
|
{ "rsatimestamp", rsaresponse.SelectToken("timestamp").Value<string>() },
|
|
{ "remember_login", "false" },
|
|
{ "oauth_client_id", "DE45CD61" },
|
|
{ "oauth_scope", "read_profile write_profile read_client write_client" },
|
|
{ "donotache", new DateTime().ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds.ToString() }
|
|
};
|
|
response = GetString(COMMUNITY_BASE + "/mobilelogin/dologin/", "POST", data);
|
|
Dictionary<string, object> loginresponse = JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
|
|
|
|
if (loginresponse.ContainsKey("emailsteamid") == true)
|
|
{
|
|
this.Session.SteamId = loginresponse["emailsteamid"] as string;
|
|
}
|
|
|
|
this.InvalidLogin = false;
|
|
this.RequiresCaptcha = false;
|
|
this.CaptchaId = null;
|
|
this.CaptchaUrl = null;
|
|
this.RequiresEmailAuth = false;
|
|
this.EmailDomain = null;
|
|
this.Requires2FA = false;
|
|
|
|
if (loginresponse.ContainsKey("login_complete") == false || (bool)loginresponse["login_complete"] == false || loginresponse.ContainsKey("oauth") == false)
|
|
{
|
|
this.InvalidLogin = true;
|
|
|
|
// require captcha
|
|
if (loginresponse.ContainsKey("captcha_needed") == true && (bool)loginresponse["captcha_needed"] == true)
|
|
{
|
|
this.RequiresCaptcha = true;
|
|
this.CaptchaId = (string)loginresponse["captcha_gid"];
|
|
this.CaptchaUrl = COMMUNITY_BASE + "/public/captcha.php?gid=" + this.CaptchaId;
|
|
}
|
|
|
|
// require email auth
|
|
if (loginresponse.ContainsKey("emailauth_needed") == true && (bool)loginresponse["emailauth_needed"] == true)
|
|
{
|
|
if (loginresponse.ContainsKey("emaildomain") == true)
|
|
{
|
|
var emaildomain = (string)loginresponse["emaildomain"];
|
|
if (string.IsNullOrEmpty(emaildomain) == false)
|
|
{
|
|
this.EmailDomain = emaildomain;
|
|
}
|
|
}
|
|
this.RequiresEmailAuth = true;
|
|
}
|
|
|
|
// require email auth
|
|
if (loginresponse.ContainsKey("requires_twofactor") == true && (bool)loginresponse["requires_twofactor"] == true)
|
|
{
|
|
this.Requires2FA = true;
|
|
}
|
|
|
|
if (loginresponse.ContainsKey("message") == true)
|
|
{
|
|
this.Error = (string)loginresponse["message"];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// get the OAuth token
|
|
string oauth = (string)loginresponse["oauth"];
|
|
var oauthjson = JObject.Parse(oauth);
|
|
this.Session.OAuthToken = oauthjson.SelectToken("oauth_token").Value<string>();
|
|
if (oauthjson.SelectToken("steamid") != null)
|
|
{
|
|
this.Session.SteamId = oauthjson.SelectToken("steamid").Value<string>();
|
|
}
|
|
|
|
//// perform UMQ login
|
|
//data.Clear();
|
|
//data.Add("access_token", this.Session.OAuthToken);
|
|
//response = GetString(API_LOGON, "POST", data);
|
|
//loginresponse = JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
|
|
//if (loginresponse.ContainsKey("umqid") == true)
|
|
//{
|
|
// this.Session.UmqId = (string)loginresponse["umqid"];
|
|
// if (loginresponse.ContainsKey("message") == true)
|
|
// {
|
|
// this.Session.MessageId = Convert.ToInt32(loginresponse["message"]);
|
|
// }
|
|
//}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logout of the current session
|
|
/// </summary>
|
|
public void Logout()
|
|
{
|
|
if (string.IsNullOrEmpty(this.Session.OAuthToken) == false)
|
|
{
|
|
PollConfirmationsStop();
|
|
|
|
if (string.IsNullOrEmpty(this.Session.UmqId) == false)
|
|
{
|
|
var data = new NameValueCollection
|
|
{
|
|
{ "access_token", this.Session.OAuthToken },
|
|
{ "umqid", this.Session.UmqId }
|
|
};
|
|
GetString(API_LOGOFF, "POST", data);
|
|
}
|
|
}
|
|
|
|
Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refresh the login session cookies from the OAuth code
|
|
/// </summary>
|
|
/// <returns>true if successful</returns>
|
|
public bool Refresh()
|
|
{
|
|
try
|
|
{
|
|
var data = new NameValueCollection();
|
|
data.Add("access_token", this.Session.OAuthToken);
|
|
string response = GetString(API_GETWGTOKEN, "POST", data);
|
|
if (string.IsNullOrEmpty(response) == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var json = JObject.Parse(response);
|
|
var token = json.SelectToken("response.token");
|
|
if (token == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var cookieuri = new Uri(COMMUNITY_BASE + "/");
|
|
this.Session.Cookies.Add(cookieuri, new Cookie("steamLogin", this.Session.SteamId + "||" + token.Value<string>()));
|
|
|
|
token = json.SelectToken("response.token_secure");
|
|
if (token == null)
|
|
{
|
|
return false;
|
|
}
|
|
this.Session.Cookies.Add(cookieuri, new Cookie("steamLoginSecure", this.Session.SteamId + "||" + token.Value<string>()));
|
|
|
|
// perform UMQ login
|
|
//response = GetString(API_LOGON, "POST", data);
|
|
//var loginresponse = JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
|
|
//if (loginresponse.ContainsKey("umqid") == true)
|
|
//{
|
|
// this.Session.UmqId = (string)loginresponse["umqid"];
|
|
// if (loginresponse.ContainsKey("message") == true)
|
|
// {
|
|
// this.Session.MessageId = Convert.ToInt32(loginresponse["message"]);
|
|
// }
|
|
//}
|
|
|
|
return true;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform a UMQ login
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool UmqLogin()
|
|
{
|
|
if (IsLoggedIn() == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var data = new NameValueCollection();
|
|
data.Add("access_token", this.Session.OAuthToken);
|
|
var response = GetString(API_LOGON, "POST", data);
|
|
var loginresponse = JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
|
|
if (loginresponse.ContainsKey("umqid") == true)
|
|
{
|
|
this.Session.UmqId = (string)loginresponse["umqid"];
|
|
if (loginresponse.ContainsKey("message") == true)
|
|
{
|
|
this.Session.MessageId = Convert.ToInt32(loginresponse["message"]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delegate for Confirmation event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="newconfirmation">new Confirmation</param>
|
|
/// <param name="action">action to be taken</param>
|
|
public delegate void ConfirmationDelegate(object sender, Confirmation newconfirmation, PollerAction action);
|
|
|
|
/// <summary>
|
|
/// Delegate for Confirmation error
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="message">error message</param>
|
|
/// <param name="ex">optional exception</param>
|
|
public delegate void ConfirmationErrorDelegate(object sender, string message, PollerAction action, Exception ex);
|
|
|
|
/// <summary>
|
|
/// Event fired for new Confirmation
|
|
/// </summary>
|
|
public event ConfirmationDelegate ConfirmationEvent;
|
|
|
|
/// <summary>
|
|
/// Event fired for error on polling
|
|
/// </summary>
|
|
public event ConfirmationErrorDelegate ConfirmationErrorEvent;
|
|
|
|
/// <summary>
|
|
/// Stop the current poller
|
|
/// </summary>
|
|
protected void PollConfirmationsStop()
|
|
{
|
|
// kill any existing poller
|
|
if (_pollerCancellation != null)
|
|
{
|
|
_pollerCancellation.Cancel();
|
|
_pollerCancellation = null;
|
|
}
|
|
this.Session.Confirmations = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a new poller
|
|
/// </summary>
|
|
/// <param name="poller"></param>
|
|
public void PollConfirmations(ConfirmationPoller poller)
|
|
{
|
|
PollConfirmationsStop();
|
|
|
|
if (poller == null || poller.Duration <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.Session.Confirmations == null)
|
|
{
|
|
this.Session.Confirmations = new ConfirmationPoller();
|
|
}
|
|
this.Session.Confirmations = poller;
|
|
|
|
_pollerCancellation = new CancellationTokenSource();
|
|
var token = _pollerCancellation.Token;
|
|
Task.Factory.StartNew(() => PollConfirmations(token), token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Confirmation polling task
|
|
/// </summary>
|
|
/// <param name="cancel"></param>
|
|
public async void PollConfirmations(CancellationToken cancel)
|
|
{
|
|
//lock (this.Session.Confirmations)
|
|
//{
|
|
// if (this.Session.Confirmations.Ids == null)
|
|
// {
|
|
// try
|
|
// {
|
|
// // this will update the session
|
|
// GetConfirmations();
|
|
// }
|
|
// catch (InvalidSteamRequestException)
|
|
// {
|
|
// // ignore in case of Steam timeout
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
try
|
|
{
|
|
int retryCount = 0;
|
|
while (!cancel.IsCancellationRequested && this.Session.Confirmations != null)
|
|
{
|
|
try
|
|
{
|
|
//List<string> currentIds;
|
|
//lock (this.Session.Confirmations)
|
|
//{
|
|
// currentIds = this.Session.Confirmations.Ids;
|
|
//}
|
|
|
|
var confs = GetConfirmations();
|
|
|
|
// check for new ids
|
|
//List<string> newIds;
|
|
//if (currentIds == null)
|
|
//{
|
|
// newIds = confs.Select(t => t.Id).ToList();
|
|
//}
|
|
//else
|
|
//{
|
|
// newIds = confs.Select(t => t.Id).Except(currentIds).ToList();
|
|
//}
|
|
|
|
// fire events if subscriber
|
|
if (ConfirmationEvent != null /* && newIds.Count() != 0 */)
|
|
{
|
|
var rand = new Random();
|
|
foreach (var conf in confs)
|
|
{
|
|
if (cancel.IsCancellationRequested)
|
|
{
|
|
break;
|
|
}
|
|
|
|
DateTime start = DateTime.Now;
|
|
|
|
ConfirmationEvent(this, conf, this.Session.Confirmations.Action);
|
|
|
|
// Issue#339: add a delay for any autoconfs or notifications
|
|
var delay = CONFIRMATION_EVENT_DELAY + rand.Next(CONFIRMATION_EVENT_DELAY / 2); // delay is 100%-150% of CONFIRMATION_EVENT_DELAY
|
|
var duration = (int)DateTime.Now.Subtract(start).TotalMilliseconds;
|
|
if (delay > duration)
|
|
{
|
|
Thread.Sleep(delay - duration);
|
|
}
|
|
}
|
|
}
|
|
|
|
retryCount = 0;
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
retryCount++;
|
|
if (retryCount >= ConfirmationPollerRetries)
|
|
{
|
|
ConfirmationErrorEvent(this, "Failed to read confirmations", this.Session.Confirmations.Action, ex);
|
|
}
|
|
else
|
|
{
|
|
// try and reset the session
|
|
try
|
|
{
|
|
this.Refresh();
|
|
}
|
|
catch (Exception) { }
|
|
}
|
|
}
|
|
|
|
if (this.Session.Confirmations != null)
|
|
{
|
|
await Task.Delay(this.Session.Confirmations.Duration * 60 * 1000, cancel);
|
|
}
|
|
}
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current trade Confirmations
|
|
/// </summary>
|
|
/// <returns>list of Confirmation objects</returns>
|
|
public List<Confirmation> GetConfirmations()
|
|
{
|
|
long servertime = (SteamAuthenticator.CurrentTime + this.Authenticator.ServerTimeDiff) / 1000L;
|
|
|
|
var jids = JObject.Parse(this.Authenticator.SteamData).SelectToken("identity_secret");
|
|
string ids = (jids != null ? jids.Value<string>() : string.Empty);
|
|
|
|
var timehash = CreateTimeHash(servertime, "conf", ids);
|
|
|
|
var data = new NameValueCollection();
|
|
data.Add("p", this.Authenticator.DeviceId);
|
|
data.Add("a", this.Session.SteamId);
|
|
data.Add("k", timehash);
|
|
data.Add("t", servertime.ToString());
|
|
data.Add("m", "android");
|
|
data.Add("tag", "conf");
|
|
|
|
string html = GetString(COMMUNITY_BASE + "/mobileconf/conf", "GET", data);
|
|
|
|
// save last html for confirmations details
|
|
ConfirmationsHtml = html;
|
|
ConfirmationsQuery = string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(data[key]))));
|
|
|
|
List<Confirmation> trades = new List<Confirmation>();
|
|
|
|
// extract the trades
|
|
Match match = _tradesRegex.Match(html);
|
|
while (match.Success)
|
|
{
|
|
var tradeIds = match.Groups[1].Value;
|
|
|
|
var trade = new Confirmation();
|
|
|
|
var innerMatch = _tradeConfidRegex.Match(tradeIds);
|
|
if (innerMatch.Success)
|
|
{
|
|
trade.Id = innerMatch.Groups[1].Value;
|
|
}
|
|
innerMatch = _tradeKeyRegex.Match(tradeIds);
|
|
if (innerMatch.Success)
|
|
{
|
|
trade.Key = innerMatch.Groups[1].Value;
|
|
}
|
|
|
|
var traded = match.Groups[2].Value;
|
|
|
|
innerMatch = _tradePlayerRegex.Match(traded);
|
|
if (innerMatch.Success)
|
|
{
|
|
if (innerMatch.Groups[1].Value.IndexOf("offline") != -1)
|
|
{
|
|
trade.Offline = true;
|
|
}
|
|
trade.Image = innerMatch.Groups[2].Value;
|
|
}
|
|
|
|
innerMatch = _tradeDetailsRegex.Match(traded);
|
|
if (innerMatch.Success)
|
|
{
|
|
trade.Details = innerMatch.Groups[1].Value;
|
|
trade.Traded = innerMatch.Groups[2].Value;
|
|
trade.When = innerMatch.Groups[3].Value;
|
|
}
|
|
|
|
trades.Add(trade);
|
|
|
|
match = match.NextMatch();
|
|
}
|
|
|
|
if (this.Session.Confirmations != null)
|
|
{
|
|
lock (this.Session.Confirmations)
|
|
{
|
|
if (this.Session.Confirmations.Ids == null)
|
|
{
|
|
this.Session.Confirmations.Ids = new List<string>();
|
|
}
|
|
foreach (var conf in trades)
|
|
{
|
|
conf.IsNew = (this.Session.Confirmations.Ids.Contains(conf.Id) == false);
|
|
if (conf.IsNew == true)
|
|
{
|
|
this.Session.Confirmations.Ids.Add(conf.Id);
|
|
}
|
|
}
|
|
var newIds = trades.Select(t => t.Id).ToList();
|
|
foreach (var confId in this.Session.Confirmations.Ids.ToList())
|
|
{
|
|
if (newIds.Contains(confId) == false)
|
|
{
|
|
this.Session.Confirmations.Ids.Remove(confId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return trades;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get details for an individual Confirmation
|
|
/// </summary>
|
|
/// <param name="trade">trade Confirmation</param>
|
|
/// <returns>html string of details</returns>
|
|
public string GetConfirmationDetails(Confirmation trade)
|
|
{
|
|
// build details URL
|
|
string url = COMMUNITY_BASE + "/mobileconf/details/" + trade.Id + "?" + ConfirmationsQuery;
|
|
|
|
string response = this.GetString(url);
|
|
if (response.IndexOf("success") == -1)
|
|
{
|
|
throw new InvalidSteamRequestException("Invalid request from steam: " + response);
|
|
}
|
|
if (JObject.Parse(response).SelectToken("success").Value<bool>() == true)
|
|
{
|
|
string html = JObject.Parse(response).SelectToken("html").Value<string>();
|
|
|
|
Regex detailsRegex = new Regex(@"(.*<body[^>]*>\s*<div\s+class=""[^""]+"">).*(</div>.*?</body>\s*</html>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
|
var match = detailsRegex.Match(this.ConfirmationsHtml);
|
|
if (match.Success == true)
|
|
{
|
|
return match.Groups[1].Value + html + match.Groups[2].Value;
|
|
}
|
|
}
|
|
|
|
return "<html><head></head><body><p>Cannot load trade confirmation details</p></body></html>";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Confirm or reject a specific trade confirmation
|
|
/// </summary>
|
|
/// <param name="id">id of trade</param>
|
|
/// <param name="key">key for trade</param>
|
|
/// <param name="accept">true to accept, false to reject</param>
|
|
/// <returns>true if successful</returns>
|
|
public bool ConfirmTrade(string id, string key, bool accept)
|
|
{
|
|
if (string.IsNullOrEmpty(this.Session.OAuthToken) == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
long servertime = (SteamAuthenticator.CurrentTime + this.Authenticator.ServerTimeDiff) / 1000L;
|
|
|
|
var jids = JObject.Parse(this.Authenticator.SteamData).SelectToken("identity_secret");
|
|
string ids = (jids != null ? jids.Value<string>() : string.Empty);
|
|
var timehash = CreateTimeHash(servertime, "conf", ids);
|
|
|
|
var data = new NameValueCollection();
|
|
data.Add("op", accept ? "allow" : "cancel");
|
|
data.Add("p", this.Authenticator.DeviceId);
|
|
data.Add("a", this.Session.SteamId);
|
|
data.Add("k", timehash);
|
|
data.Add("t", servertime.ToString());
|
|
data.Add("m", "android");
|
|
data.Add("tag", "conf");
|
|
//
|
|
data.Add("cid", id);
|
|
data.Add("ck", key);
|
|
|
|
try
|
|
{
|
|
string response = GetString(COMMUNITY_BASE + "/mobileconf/ajaxop", "GET", data);
|
|
if (string.IsNullOrEmpty(response) == true)
|
|
{
|
|
this.Error = "Blank response";
|
|
return false;
|
|
}
|
|
|
|
var success = JObject.Parse(response).SelectToken("success");
|
|
if (success == null || success.Value<bool>() == false)
|
|
{
|
|
this.Error = "Failed";
|
|
return false;
|
|
}
|
|
|
|
if (this.Session.Confirmations != null)
|
|
{
|
|
lock (this.Session.Confirmations)
|
|
{
|
|
if (this.Session.Confirmations.Ids.Contains(id) == true)
|
|
{
|
|
this.Session.Confirmations.Ids.Remove(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (InvalidSteamRequestException ex)
|
|
{
|
|
#if DEBUG
|
|
this.Error = ex.Message + Environment.NewLine + ex.StackTrace;
|
|
#else
|
|
this.Error = ex.Message;
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the hash needed for the confirmations query string
|
|
/// </summary>
|
|
/// <param name="time">current time</param>
|
|
/// <param name="tag">tag</param>
|
|
/// <param name="secret">identity secret</param>
|
|
/// <returns>hash string</returns>
|
|
private static string CreateTimeHash(long time, string tag, string secret)
|
|
{
|
|
byte[] b64secret = Convert.FromBase64String(secret);
|
|
|
|
int bufferSize = 8;
|
|
if (string.IsNullOrEmpty(tag) == false)
|
|
{
|
|
bufferSize += Math.Min(32, tag.Length);
|
|
}
|
|
byte[] buffer = new byte[bufferSize];
|
|
|
|
byte[] timeArray = BitConverter.GetBytes(time);
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Array.Reverse(timeArray);
|
|
}
|
|
Array.Copy(timeArray, buffer, 8);
|
|
if (string.IsNullOrEmpty(tag) == false)
|
|
{
|
|
Array.Copy(Encoding.UTF8.GetBytes(tag), 0, buffer, 8, bufferSize - 8);
|
|
}
|
|
|
|
HMACSHA1 hmac = new HMACSHA1(b64secret, true);
|
|
byte[] hash = hmac.ComputeHash(buffer);
|
|
|
|
return Convert.ToBase64String(hash, Base64FormattingOptions.None);
|
|
}
|
|
|
|
#region Web Request
|
|
|
|
/// <summary>
|
|
/// Get binary data web request
|
|
/// </summary>
|
|
/// <param name="url">url</param>
|
|
/// <param name="method">GET or POST</param>
|
|
/// <param name="formdata">optional form data</param>
|
|
/// <param name="headers">optional headers</param>
|
|
/// <returns>array of returned data</returns>
|
|
public byte[] GetData(string url, string method = null, NameValueCollection formdata = null, NameValueCollection headers = null)
|
|
{
|
|
return Request(url, method ?? "GET", formdata, headers);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get string from web request
|
|
/// </summary>
|
|
/// <param name="url">url</param>
|
|
/// <param name="method">GET or POST</param>
|
|
/// <param name="formdata">optional form data</param>
|
|
/// <param name="headers">optional headers</param>
|
|
/// <returns>string of returned data</returns>
|
|
public string GetString(string url, string method = null, NameValueCollection formdata = null, NameValueCollection headers = null)
|
|
{
|
|
byte[] data = Request(url, method ?? "GET", formdata, headers);
|
|
if (data == null || data.Length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
else
|
|
{
|
|
return Encoding.UTF8.GetString(data);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make a request to Steam URL
|
|
/// </summary>
|
|
/// <param name="url">url</param>
|
|
/// <param name="method">GET or POST</param>
|
|
/// <param name="formdata">optional form data</param>
|
|
/// <param name="headers">optional headers</param>
|
|
/// <returns>returned data</returns>
|
|
protected byte[] Request(string url, string method, NameValueCollection data, NameValueCollection headers)
|
|
{
|
|
// ensure only one request per account at a time
|
|
lock (this)
|
|
{
|
|
// create form-encoded data for query or body
|
|
string query = (data == null ? string.Empty : string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(data[key])))));
|
|
if (string.Compare(method, "GET", true) == 0)
|
|
{
|
|
url += (url.IndexOf("?") == -1 ? "?" : "&") + query;
|
|
}
|
|
|
|
// call the server
|
|
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
|
request.Method = method;
|
|
request.Accept = "text/javascript, text/html, application/xml, text/xml, */*";
|
|
request.ServicePoint.Expect100Continue = false;
|
|
request.UserAgent = USERAGENT;
|
|
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
|
|
request.Referer = COMMUNITY_BASE;
|
|
if (headers != null)
|
|
{
|
|
request.Headers.Add(headers);
|
|
}
|
|
|
|
request.CookieContainer = this.Session.Cookies;
|
|
|
|
if (string.Compare(method, "POST", true) == 0)
|
|
{
|
|
request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
|
|
request.ContentLength = query.Length;
|
|
|
|
StreamWriter requestStream = new StreamWriter(request.GetRequestStream());
|
|
requestStream.Write(query);
|
|
requestStream.Close();
|
|
}
|
|
|
|
try
|
|
{
|
|
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
|
{
|
|
LogRequest(method, url, request.CookieContainer, data, response.StatusCode.ToString() + " " + response.StatusDescription);
|
|
|
|
// OK?
|
|
if (response.StatusCode != HttpStatusCode.OK)
|
|
{
|
|
throw new InvalidSteamRequestException(string.Format("{0}: {1}", (int)response.StatusCode, response.StatusDescription));
|
|
}
|
|
|
|
// load the response
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
byte[] buffer = new byte[4096];
|
|
int read;
|
|
while ((read = response.GetResponseStream().Read(buffer, 0, 4096)) > 0)
|
|
{
|
|
ms.Write(buffer, 0, read);
|
|
}
|
|
|
|
byte[] responsedata = ms.ToArray();
|
|
|
|
LogRequest(method, url, request.CookieContainer, data, responsedata != null && responsedata.Length != 0 ? Encoding.UTF8.GetString(responsedata) : string.Empty);
|
|
|
|
return responsedata;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogException(method, url, request.CookieContainer, data, ex);
|
|
|
|
if (ex is WebException && ((WebException)ex).Response != null && ((HttpWebResponse)((WebException)ex).Response).StatusCode == HttpStatusCode.Forbidden)
|
|
{
|
|
throw new UnauthorisedSteamRequestException(ex);
|
|
}
|
|
throw new InvalidSteamRequestException(ex.Message, ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Log an exception from a Request
|
|
/// </summary>
|
|
/// <param name="method">Get or POST</param>
|
|
/// <param name="url">Request URL</param>
|
|
/// <param name="cookies">cookie container</param>
|
|
/// <param name="request">Request data</param>
|
|
/// <param name="ex">Thrown exception</param>
|
|
private static void LogException(string method, string url, CookieContainer cookies, NameValueCollection request, Exception ex)
|
|
{
|
|
StringBuilder data = new StringBuilder();
|
|
if (cookies != null)
|
|
{
|
|
foreach (Cookie cookie in cookies.GetCookies(new Uri(url)))
|
|
{
|
|
if (data.Length == 0)
|
|
{
|
|
data.Append("Cookies:");
|
|
}
|
|
else
|
|
{
|
|
data.Append("&");
|
|
}
|
|
data.Append(cookie.Name + "=" + cookie.Value);
|
|
}
|
|
data.Append(" ");
|
|
}
|
|
|
|
if (request != null)
|
|
{
|
|
foreach (var key in request.AllKeys)
|
|
{
|
|
if (data.Length == 0)
|
|
{
|
|
data.Append("Req:");
|
|
}
|
|
else
|
|
{
|
|
data.Append("&");
|
|
}
|
|
data.Append(key + "=" + request[key]);
|
|
}
|
|
data.Append(" ");
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Log a normal response
|
|
/// </summary>
|
|
/// <param name="method">Get or POST</param>
|
|
/// <param name="url">Request URL</param>
|
|
/// <param name="cookies">cookie container</param>
|
|
/// <param name="request">Request data</param>
|
|
/// <param name="response">response body</param>
|
|
private static void LogRequest(string method, string url, CookieContainer cookies, NameValueCollection request, string response)
|
|
{
|
|
StringBuilder data = new StringBuilder();
|
|
if (cookies != null)
|
|
{
|
|
foreach (Cookie cookie in cookies.GetCookies(new Uri(url)))
|
|
{
|
|
if (data.Length == 0)
|
|
{
|
|
data.Append("Cookies:");
|
|
}
|
|
else
|
|
{
|
|
data.Append("&");
|
|
}
|
|
data.Append(cookie.Name + "=" + cookie.Value);
|
|
}
|
|
data.Append(" ");
|
|
}
|
|
|
|
if (request != null)
|
|
{
|
|
foreach (var key in request.AllKeys)
|
|
{
|
|
if (data.Length == 0)
|
|
{
|
|
data.Append("Req:");
|
|
}
|
|
else
|
|
{
|
|
data.Append("&");
|
|
}
|
|
data.Append(key + "=" + request[key]);
|
|
}
|
|
data.Append(" ");
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Convert a hex string into a byte array. E.g. "001f406a" -> byte[] {0x00, 0x1f, 0x40, 0x6a}
|
|
/// </summary>
|
|
/// <param name="hex">hex string to convert</param>
|
|
/// <returns>byte[] of hex string</returns>
|
|
private static byte[] StringToByteArray(string hex)
|
|
{
|
|
int len = hex.Length;
|
|
byte[] bytes = new byte[len / 2];
|
|
for (int i = 0; i < len; i += 2)
|
|
{
|
|
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a byte array into a ascii hex string, e.g. byte[]{0x00,0x1f,0x40,ox6a} -> "001f406a"
|
|
/// </summary>
|
|
/// <param name="bytes">byte array to convert</param>
|
|
/// <returns>string version of byte array</returns>
|
|
private static string ByteArrayToString(byte[] bytes)
|
|
{
|
|
// Use BitConverter, but it sticks dashes in the string
|
|
return BitConverter.ToString(bytes).Replace("-", string.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Our custom exception for the internal Http Request
|
|
/// </summary>
|
|
public class InvalidSteamRequestException : ApplicationException
|
|
{
|
|
public InvalidSteamRequestException(string msg = null, Exception ex = null) : base(msg, ex) { }
|
|
}
|
|
|
|
public class UnauthorisedSteamRequestException : InvalidSteamRequestException
|
|
{
|
|
public UnauthorisedSteamRequestException(Exception ex = null) : base("Unauthorised", ex) { }
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|