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 } }