/*
* 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 .
*/
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
{
///
/// SteamClient for logging and getting/accepting/rejecting trade confirmations
///
public class SteamClient : IDisposable
{
///
/// URLs for all mobile services
///
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";
///
/// Default mobile user agent
///
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";
///
/// Regular expressions for trade confirmations
///
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\".*?
([^<]*)
[^<]*([^<]*)
[^<]*([^<]*)
[^<]*", RegexOptions.Singleline | RegexOptions.IgnoreCase);
///
/// Number of Confirmation retries
///
private const int DEFAULT_CONFIRMATIONPOLLER_RETRIES = 3;
///
/// Delay between trade confirmation events
///
public const int CONFIRMATION_EVENT_DELAY = 1000;
///
/// Action for Confirmation polling
///
public enum PollerAction
{
None = 0,
Notify = 1,
AutoConfirm = 2,
SilentAutoConfirm = 3
}
///
/// Hold the Confirmation polling data
///
public class ConfirmationPoller
{
///
/// Seconds between polls
///
public int Duration;
///
/// Action for new Confirmation
///
public PollerAction Action;
///
/// List of current Confirmations ids
///
public List Ids;
///
/// Create new ConfirmationPoller object
///
public ConfirmationPoller()
{
}
///
/// Create a JSON string of the object
///
///
public override string ToString()
{
if (Duration == 0)
{
return "null";
}
else
{
List props = new List
{
"\"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()) + "}";
}
}
///
/// Create a new ConfirmationPoller from a JSON string
///
/// JSON string
/// new ConfirmationPoller or null
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);
}
///
/// Create a new ConfirmationPoller from a JToken
///
/// existing JKToken
///
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();
}
token = tokens.SelectToken("action");
if (token != null)
{
poller.Action = (PollerAction)token.Value();
}
token = tokens.SelectToken("ids");
if (token != null)
{
poller.Ids = token.ToObject>();
}
return (poller.Duration != 0 ? poller : null);
}
}
///
/// A class for a single confirmation
///
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; }
}
///
/// Session state to remember logins
///
public class SteamSession
{
///
/// User's steam ID
///
public string SteamId;
///
/// Current cookies
///
public CookieContainer Cookies;
///
/// Authorization token
///
public string OAuthToken;
///
/// UMQ id
///
public string UmqId;
///
/// Message id
///
public int MessageId;
///
/// Current polling state
///
public ConfirmationPoller Confirmations;
///
/// Create Session instance
///
public SteamSession()
{
Clear();
}
///
/// Create session instance from existing json data
///
/// json session data
public SteamSession(string json) : this()
{
if (string.IsNullOrEmpty(json) == false)
{
try
{
this.FromJson(json);
}
catch (Exception)
{
// invalid json
}
}
}
///
/// Clear the session
///
public void Clear()
{
this.OAuthToken = null;
this.UmqId = null;
this.Cookies = new CookieContainer();
this.Confirmations = null;
}
///
/// Get session data that can be saved and imported
///
///
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")
+ "}";
}
///
/// Convert json data into session
///
///
private void FromJson(string json)
{
var tokens = JObject.Parse(json);
var token = tokens.SelectToken("steamid");
if (token != null)
{
this.SteamId = token.Value();
}
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(), @"([^=]+)=([^;]*);?", 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();
}
//token = tokens.SelectToken("umqid");
//if (token != null)
//{
// this.UmqId = token.Value();
//}
token = tokens.SelectToken("confs");
if (token != null)
{
this.Confirmations = ConfirmationPoller.FromJSON(token);
}
}
}
///
/// Login state fields
///
public bool InvalidLogin;
public bool RequiresCaptcha;
public string CaptchaId;
public string CaptchaUrl;
public bool Requires2FA;
public bool RequiresEmailAuth;
public string EmailDomain;
public string Error;
///
/// Current session
///
public SteamSession Session;
///
/// Current authenticator
///
public SteamAuthenticator Authenticator;
///
/// Saved Html from GetConfirmations used as template for GetDetails
///
private string ConfirmationsHtml;
///
/// Query string from GetConfirmations used in GetDetails
///
private string ConfirmationsQuery;
///
/// Cancellation token for poller
///
private CancellationTokenSource _pollerCancellation;
///
/// Number of Confirmation retries
///
public int ConfirmationPollerRetries = DEFAULT_CONFIRMATIONPOLLER_RETRIES;
///
/// Create a new SteamClient
///
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);
});
}
}
}
///
/// Finalizer
///
~SteamClient()
{
Dispose(false);
}
///
/// Dispose the object
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Dispose this object
///
///
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// clear resources
}
if (_pollerCancellation != null)
{
_pollerCancellation.Cancel();
_pollerCancellation = null;
}
}
///
/// Clear the client state
///
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();
}
///
/// Check if user is logged in
///
///
public bool IsLoggedIn()
{
return (this.Session != null && string.IsNullOrEmpty(this.Session.OAuthToken) == false);
}
///
/// Login to Steam using credentials and optional captcha
///
///
///
///
///
/// true if successful
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() != 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());
p.Modulus = StringToByteArray(rsaresponse.SelectToken("publickey_mod").Value());
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() },
{ "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 loginresponse = JsonConvert.DeserializeObject>(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();
if (oauthjson.SelectToken("steamid") != null)
{
this.Session.SteamId = oauthjson.SelectToken("steamid").Value();
}
//// perform UMQ login
//data.Clear();
//data.Add("access_token", this.Session.OAuthToken);
//response = GetString(API_LOGON, "POST", data);
//loginresponse = JsonConvert.DeserializeObject>(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;
}
///
/// Logout of the current session
///
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();
}
///
/// Refresh the login session cookies from the OAuth code
///
/// true if successful
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()));
token = json.SelectToken("response.token_secure");
if (token == null)
{
return false;
}
this.Session.Cookies.Add(cookieuri, new Cookie("steamLoginSecure", this.Session.SteamId + "||" + token.Value()));
// perform UMQ login
//response = GetString(API_LOGON, "POST", data);
//var loginresponse = JsonConvert.DeserializeObject>(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;
}
}
///
/// Perform a UMQ login
///
///
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>(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;
}
///
/// Delegate for Confirmation event
///
///
/// new Confirmation
/// action to be taken
public delegate void ConfirmationDelegate(object sender, Confirmation newconfirmation, PollerAction action);
///
/// Delegate for Confirmation error
///
///
/// error message
/// optional exception
public delegate void ConfirmationErrorDelegate(object sender, string message, PollerAction action, Exception ex);
///
/// Event fired for new Confirmation
///
public event ConfirmationDelegate ConfirmationEvent;
///
/// Event fired for error on polling
///
public event ConfirmationErrorDelegate ConfirmationErrorEvent;
///
/// Stop the current poller
///
protected void PollConfirmationsStop()
{
// kill any existing poller
if (_pollerCancellation != null)
{
_pollerCancellation.Cancel();
_pollerCancellation = null;
}
this.Session.Confirmations = null;
}
///
/// Start a new poller
///
///
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);
}
///
/// Confirmation polling task
///
///
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 currentIds;
//lock (this.Session.Confirmations)
//{
// currentIds = this.Session.Confirmations.Ids;
//}
var confs = GetConfirmations();
// check for new ids
//List 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)
{
}
}
///
/// Get the current trade Confirmations
///
/// list of Confirmation objects
public List 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.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 trades = new List();
// 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();
}
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;
}
///
/// Get details for an individual Confirmation
///
/// trade Confirmation
/// html string of details
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() == true)
{
string html = JObject.Parse(response).SelectToken("html").Value();
Regex detailsRegex = new Regex(@"(.*]*>\s*).*(
.*?\s*