using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using RedditSharp.Things;
using System.Threading.Tasks;
namespace RedditSharp
{
///
/// Class to communicate with Reddit.com
///
public class Reddit
{
#region Constant Urls
private const string SslLoginUrl = "https://ssl.reddit.com/api/login";
private const string LoginUrl = "/api/login/username";
private const string UserInfoUrl = "/user/{0}/about.json";
private const string MeUrl = "/api/me.json";
private const string OAuthMeUrl = "/api/v1/me.json";
private const string SubredditAboutUrl = "/r/{0}/about.json";
private const string ComposeMessageUrl = "/api/compose";
private const string RegisterAccountUrl = "/api/register";
private const string GetThingUrl = "/api/info.json?id={0}";
private const string GetCommentUrl = "/r/{0}/comments/{1}/foo/{2}.json";
private const string GetPostUrl = "{0}.json";
private const string DomainUrl = "www.reddit.com";
private const string OAuthDomainUrl = "oauth.reddit.com";
private const string SearchUrl = "/search.json?q={0}&restrict_sr=off&sort={1}&t={2}";
private const string UrlSearchPattern = "url:'{0}'";
#endregion
#region Static Variables
static Reddit()
{
WebAgent.UserAgent = "";
WebAgent.RateLimit = WebAgent.RateLimitMode.Pace;
WebAgent.Protocol = "http";
WebAgent.RootDomain = "www.reddit.com";
}
#endregion
internal readonly IWebAgent _webAgent;
///
/// Captcha solver instance to use when solving captchas.
///
public ICaptchaSolver CaptchaSolver;
///
/// The authenticated user for this instance.
///
public AuthenticatedUser User { get; set; }
///
/// Sets the Rate Limiting Mode of the underlying WebAgent
///
public WebAgent.RateLimitMode RateLimit
{
get { return WebAgent.RateLimit; }
set { WebAgent.RateLimit = value; }
}
internal JsonSerializerSettings JsonSerializerSettings { get; set; }
///
/// Gets the FrontPage using the current Reddit instance.
///
public Subreddit FrontPage
{
get { return Subreddit.GetFrontPage(this); }
}
///
/// Gets /r/All using the current Reddit instance.
///
public Subreddit RSlashAll
{
get { return Subreddit.GetRSlashAll(this); }
}
public Reddit()
{
JsonSerializerSettings = new JsonSerializerSettings
{
CheckAdditionalContent = false,
DefaultValueHandling = DefaultValueHandling.Ignore
};
_webAgent = new WebAgent();
CaptchaSolver = new ConsoleCaptchaSolver();
}
public Reddit(WebAgent.RateLimitMode limitMode) : this()
{
WebAgent.UserAgent = "";
WebAgent.RateLimit = limitMode;
WebAgent.RootDomain = "www.reddit.com";
}
public Reddit(string username, string password, bool useSsl = true) : this()
{
LogIn(username, password, useSsl);
}
public Reddit(string accessToken) : this()
{
WebAgent.Protocol = "https";
WebAgent.RootDomain = OAuthDomainUrl;
_webAgent.AccessToken = accessToken;
InitOrUpdateUser();
}
///
/// Logs in the current Reddit instance.
///
/// The username of the user to log on to.
/// The password of the user to log on to.
/// Whether to use SSL or not. (default: true)
///
public AuthenticatedUser LogIn(string username, string password, bool useSsl = true)
{
if (Type.GetType("Mono.Runtime") != null)
ServicePointManager.ServerCertificateValidationCallback = (s, c, ch, ssl) => true;
_webAgent.Cookies = new CookieContainer();
HttpWebRequest request;
if (useSsl)
request = _webAgent.CreatePost(SslLoginUrl);
else
request = _webAgent.CreatePost(LoginUrl);
var stream = request.GetRequestStream();
if (useSsl)
{
_webAgent.WritePostBody(stream, new
{
user = username,
passwd = password,
api_type = "json"
});
}
else
{
_webAgent.WritePostBody(stream, new
{
user = username,
passwd = password,
api_type = "json",
op = "login"
});
}
stream.Close();
var response = (HttpWebResponse)request.GetResponse();
var result = _webAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(result)["json"];
if (json["errors"].Count() != 0)
throw new AuthenticationException("Incorrect login.");
InitOrUpdateUser();
return User;
}
public RedditUser GetUser(string name)
{
var request = _webAgent.CreateGet(string.Format(UserInfoUrl, name));
var response = request.GetResponse();
var result = _webAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(result);
return new RedditUser().Init(this, json, _webAgent);
}
///
/// Initializes the User property if it's null,
/// otherwise replaces the existing user object
/// with a new one fetched from reddit servers.
///
public void InitOrUpdateUser()
{
var request = _webAgent.CreateGet(string.IsNullOrEmpty(_webAgent.AccessToken) ? MeUrl : OAuthMeUrl);
var response = (HttpWebResponse)request.GetResponse();
var result = _webAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(result);
User = new AuthenticatedUser().Init(this, json, _webAgent);
}
#region Obsolete Getter Methods
[Obsolete("Use User property instead")]
public AuthenticatedUser GetMe()
{
return User;
}
#endregion Obsolete Getter Methods
public Subreddit GetSubreddit(string name)
{
if (name.StartsWith("r/"))
name = name.Substring(2);
if (name.StartsWith("/r/"))
name = name.Substring(3);
return GetThing(string.Format(SubredditAboutUrl, name));
}
///
/// Returns the subreddit.
///
/// The name of the subreddit
/// The Subreddit by given name
public async Task GetSubredditAsync(string name)
{
if (name.StartsWith("r/"))
name = name.Substring(2);
if (name.StartsWith("/r/"))
name = name.Substring(3);
return await GetThingAsync(string.Format(SubredditAboutUrl, name));
}
public Domain GetDomain(string domain)
{
if (!domain.StartsWith("http://") && !domain.StartsWith("https://"))
domain = "http://" + domain;
var uri = new Uri(domain);
return new Domain(this, uri, _webAgent);
}
public JToken GetToken(Uri uri)
{
var url = uri.AbsoluteUri;
if (url.EndsWith("/"))
url = url.Remove(url.Length - 1);
var request = _webAgent.CreateGet(string.Format(GetPostUrl, url));
var response = request.GetResponse();
var data = _webAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
return json[0]["data"]["children"].First;
}
public Post GetPost(Uri uri)
{
return new Post().Init(this, GetToken(uri), _webAgent);
}
public void ComposePrivateMessage(string subject, string body, string to, string captchaId = "", string captchaAnswer = "")
{
if (User == null)
throw new Exception("User can not be null.");
var request = _webAgent.CreatePost(ComposeMessageUrl);
_webAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
subject,
text = body,
to,
uh = User.Modhash,
iden = captchaId,
captcha = captchaAnswer
});
var response = request.GetResponse();
var result = _webAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(result);
ICaptchaSolver solver = CaptchaSolver; // Prevent race condition
if (json["json"]["errors"].Any() && json["json"]["errors"][0][0].ToString() == "BAD_CAPTCHA" && solver != null)
{
captchaId = json["json"]["captcha"].ToString();
CaptchaResponse captchaResponse = solver.HandleCaptcha(new Captcha(captchaId));
if (!captchaResponse.Cancel) // Keep trying until we are told to cancel
ComposePrivateMessage(subject, body, to, captchaId, captchaResponse.Answer);
}
}
///
/// Registers a new Reddit user
///
/// The username for the new account.
/// The password for the new account.
/// The optional recovery email for the new account.
/// The newly created user account
public AuthenticatedUser RegisterAccount(string userName, string passwd, string email = "")
{
var request = _webAgent.CreatePost(RegisterAccountUrl);
_webAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
email = email,
passwd = passwd,
passwd2 = passwd,
user = userName
});
var response = request.GetResponse();
var result = _webAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(result);
return new AuthenticatedUser().Init(this, json, _webAgent);
// TODO: Error
}
public Thing GetThingByFullname(string fullname)
{
var request = _webAgent.CreateGet(string.Format(GetThingUrl, fullname));
var response = request.GetResponse();
var data = _webAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
return Thing.Parse(this, json["data"]["children"][0], _webAgent);
}
public Comment GetComment(string subreddit, string name, string linkName)
{
try
{
if (linkName.StartsWith("t3_"))
linkName = linkName.Substring(3);
if (name.StartsWith("t1_"))
name = name.Substring(3);
var request = _webAgent.CreateGet(string.Format(GetCommentUrl, subreddit, linkName, name));
var response = request.GetResponse();
var data = _webAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
return Thing.Parse(this, json[1]["data"]["children"][0], _webAgent) as Comment;
}
catch (WebException)
{
return null;
}
}
public Listing SearchByUrl(string url) where T : Thing
{
var urlSearchQuery = string.Format(UrlSearchPattern, url);
return Search(urlSearchQuery);
}
public Listing Search(string query) where T : Thing
{
return new Listing(this, string.Format(SearchUrl, query, "relevance", "all"), _webAgent);
}
#region Helpers
protected async internal Task GetThingAsync(string url) where T : Thing
{
var request = _webAgent.CreateGet(url);
var response = request.GetResponse();
var data = _webAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
var ret = await Thing.ParseAsync(this, json, _webAgent);
return (T)ret;
}
protected internal T GetThing(string url) where T : Thing
{
var request = _webAgent.CreateGet(url);
var response = request.GetResponse();
var data = _webAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
return (T)Thing.Parse(this, json, _webAgent);
}
#endregion
}
}