First actual commit

added sources to repository
This commit is contained in:
Gardient
2015-08-28 21:49:50 +03:00
parent be56d43707
commit 9583c1afb2
58 changed files with 5466 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class AuthenticatedUser : RedditUser
{
private const string ModeratorUrl = "/reddits/mine/moderator.json";
private const string UnreadMessagesUrl = "/message/unread.json?mark=true&limit=25";
private const string ModQueueUrl = "/r/mod/about/modqueue.json";
private const string UnmoderatedUrl = "/r/mod/about/unmoderated.json";
private const string ModMailUrl = "/message/moderator.json";
private const string MessagesUrl = "/message/messages.json";
private const string InboxUrl = "/message/inbox.json";
private const string SentUrl = "/message/sent.json";
public new AuthenticatedUser Init(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
JsonConvert.PopulateObject(json["name"] == null ? json["data"].ToString() : json.ToString(), this,
reddit.JsonSerializerSettings);
return this;
}
public async new Task<AuthenticatedUser> InitAsync(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(json["name"] == null ? json["data"].ToString() : json.ToString(), this,
reddit.JsonSerializerSettings));
return this;
}
private void CommonInit(Reddit reddit, JToken json, IWebAgent webAgent)
{
base.Init(reddit, json, webAgent);
}
public Listing<Subreddit> ModeratorSubreddits
{
get
{
return new Listing<Subreddit>(Reddit, ModeratorUrl, WebAgent);
}
}
public Listing<Thing> UnreadMessages
{
get
{
return new Listing<Thing>(Reddit, UnreadMessagesUrl, WebAgent);
}
}
public Listing<VotableThing> ModerationQueue
{
get
{
return new Listing<VotableThing>(Reddit, ModQueueUrl, WebAgent);
}
}
public Listing<Post> UnmoderatedLinks
{
get
{
return new Listing<Post>(Reddit, UnmoderatedUrl, WebAgent);
}
}
public Listing<PrivateMessage> ModMail
{
get
{
return new Listing<PrivateMessage>(Reddit, ModMailUrl, WebAgent);
}
}
public Listing<PrivateMessage> PrivateMessages
{
get
{
return new Listing<PrivateMessage>(Reddit, MessagesUrl, WebAgent);
}
}
public Listing<PrivateMessage> Inbox
{
get
{
return new Listing<PrivateMessage>(Reddit, InboxUrl, WebAgent);
}
}
public Listing<PrivateMessage> Sent
{
get
{
return new Listing<PrivateMessage>(Reddit, SentUrl, WebAgent);
}
}
#region Obsolete Getter Methods
[Obsolete("Use ModeratorSubreddits property instead")]
public Listing<Subreddit> GetModeratorReddits()
{
return ModeratorSubreddits;
}
[Obsolete("Use UnreadMessages property instead")]
public Listing<Thing> GetUnreadMessages()
{
return UnreadMessages;
}
[Obsolete("Use ModerationQueue property instead")]
public Listing<VotableThing> GetModerationQueue()
{
return new Listing<VotableThing>(Reddit, ModQueueUrl, WebAgent);
}
public Listing<Post> GetUnmoderatedLinks()
{
return new Listing<Post>(Reddit, UnmoderatedUrl, WebAgent);
}
[Obsolete("Use ModMail property instead")]
public Listing<PrivateMessage> GetModMail()
{
return new Listing<PrivateMessage>(Reddit, ModMailUrl, WebAgent);
}
[Obsolete("Use PrivateMessages property instead")]
public Listing<PrivateMessage> GetPrivateMessages()
{
return new Listing<PrivateMessage>(Reddit, MessagesUrl, WebAgent);
}
[Obsolete("Use Inbox property instead")]
public Listing<PrivateMessage> GetInbox()
{
return new Listing<PrivateMessage>(Reddit, InboxUrl, WebAgent);
}
#endregion Obsolete Getter Methods
[JsonProperty("modhash")]
public string Modhash { get; set; }
[JsonProperty("has_mail")]
public bool HasMail { get; set; }
[JsonProperty("has_mod_mail")]
public bool HasModMail { get; set; }
}
}

View File

@@ -0,0 +1,313 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class Comment : VotableThing
{
private const string CommentUrl = "/api/comment";
private const string DistinguishUrl = "/api/distinguish";
private const string EditUserTextUrl = "/api/editusertext";
private const string RemoveUrl = "/api/remove";
private const string SetAsReadUrl = "/api/read_message";
[JsonIgnore]
private Reddit Reddit { get; set; }
[JsonIgnore]
private IWebAgent WebAgent { get; set; }
public Comment Init(Reddit reddit, JToken json, IWebAgent webAgent, Thing sender)
{
var data = CommonInit(reddit, json, webAgent, sender);
ParseComments(reddit, json, webAgent, sender);
JsonConvert.PopulateObject(data.ToString(), this, reddit.JsonSerializerSettings);
return this;
}
public async Task<Comment> InitAsync(Reddit reddit, JToken json, IWebAgent webAgent, Thing sender)
{
var data = CommonInit(reddit, json, webAgent, sender);
await ParseCommentsAsync(reddit, json, webAgent, sender);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(data.ToString(), this, reddit.JsonSerializerSettings));
return this;
}
private JToken CommonInit(Reddit reddit, JToken json, IWebAgent webAgent, Thing sender)
{
base.Init(reddit, webAgent, json);
var data = json["data"];
Reddit = reddit;
WebAgent = webAgent;
this.Parent = sender;
// Handle Reddit's API being horrible
if (data["context"] != null)
{
var context = data["context"].Value<string>();
LinkId = context.Split('/')[4];
}
return data;
}
private void ParseComments(Reddit reddit, JToken data, IWebAgent webAgent, Thing sender)
{
// Parse sub comments
var replies = data["data"]["replies"];
var subComments = new List<Comment>();
if (replies != null && replies.Count() > 0)
{
foreach (var comment in replies["data"]["children"])
subComments.Add(new Comment().Init(reddit, comment, webAgent, sender));
}
Comments = subComments.ToArray();
}
private async Task ParseCommentsAsync(Reddit reddit, JToken data, IWebAgent webAgent, Thing sender)
{
// Parse sub comments
var replies = data["data"]["replies"];
var subComments = new List<Comment>();
if (replies != null && replies.Count() > 0)
{
foreach (var comment in replies["data"]["children"])
subComments.Add(await new Comment().InitAsync(reddit, comment, webAgent, sender));
}
Comments = subComments.ToArray();
}
[JsonProperty("author")]
public string Author { get; set; }
[JsonProperty("banned_by")]
public string BannedBy { get; set; }
[JsonProperty("body")]
public string Body { get; set; }
[JsonProperty("body_html")]
public string BodyHtml { get; set; }
[JsonProperty("parent_id")]
public string ParentId { get; set; }
[JsonProperty("subreddit")]
public string Subreddit { get; set; }
[JsonProperty("approved_by")]
public string ApprovedBy { get; set; }
[JsonProperty("author_flair_css_class")]
public string AuthorFlairCssClass { get; set; }
[JsonProperty("author_flair_text")]
public string AuthorFlairText { get; set; }
[JsonProperty("gilded")]
public int Gilded { get; set; }
[JsonProperty("link_id")]
public string LinkId { get; set; }
[JsonProperty("link_title")]
public string LinkTitle { get; set; }
[JsonProperty("num_reports")]
public int? NumReports { get; set; }
[JsonProperty("distinguished")]
[JsonConverter(typeof(DistinguishConverter))]
public DistinguishType Distinguished { get; set; }
[JsonIgnore]
public IList<Comment> Comments { get; private set; }
[JsonIgnore]
public Thing Parent { get; internal set; }
public override string Shortlink
{
get
{
// Not really a "short" link, but you can't actually use short links for comments
string linkId = "";
int index = this.LinkId.IndexOf('_');
if (index > -1)
{
linkId = this.LinkId.Substring(index + 1);
}
return String.Format("{0}://{1}/r/{2}/comments/{3}/_/{4}",
RedditSharp.WebAgent.Protocol, RedditSharp.WebAgent.RootDomain,
this.Subreddit, this.Parent != null ? this.Parent.Id : linkId, this.Id);
}
}
public Comment Reply(string message)
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
var request = WebAgent.CreatePost(CommentUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
text = message,
thing_id = FullName,
uh = Reddit.User.Modhash,
api_type = "json"
//r = Subreddit
});
stream.Close();
try
{
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(data);
if (json["json"]["ratelimit"] != null)
throw new RateLimitException(TimeSpan.FromSeconds(json["json"]["ratelimit"].ValueOrDefault<double>()));
return new Comment().Init(Reddit, json["json"]["data"]["things"][0], WebAgent, this);
}
catch (WebException ex)
{
var error = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
return null;
}
}
public void Distinguish(DistinguishType distinguishType)
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
var request = WebAgent.CreatePost(DistinguishUrl);
var stream = request.GetRequestStream();
string how;
switch (distinguishType)
{
case DistinguishType.Admin:
how = "admin";
break;
case DistinguishType.Moderator:
how = "yes";
break;
case DistinguishType.None:
how = "no";
break;
default:
how = "special";
break;
}
WebAgent.WritePostBody(stream, new
{
how,
id = Id,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(data);
if (json["jquery"].Count(i => i[0].Value<int>() == 11 && i[1].Value<int>() == 12) == 0)
throw new AuthenticationException("You are not permitted to distinguish this comment.");
}
/// <summary>
/// Replaces the text in this comment with the input text.
/// </summary>
/// <param name="newText">The text to replace the comment's contents</param>
public void EditText(string newText)
{
if (Reddit.User == null)
throw new Exception("No user logged in.");
var request = WebAgent.CreatePost(EditUserTextUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
text = newText,
thing_id = FullName,
uh = Reddit.User.Modhash
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
JToken json = JToken.Parse(result);
if (json["json"].ToString().Contains("\"errors\": []"))
Body = newText;
else
throw new Exception("Error editing text.");
}
public void Remove()
{
RemoveImpl(false);
}
public void RemoveSpam()
{
RemoveImpl(true);
}
private void RemoveImpl(bool spam)
{
var request = WebAgent.CreatePost(RemoveUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
id = FullName,
spam = spam,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
public void SetAsRead()
{
var request = WebAgent.CreatePost(SetAsReadUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
id = FullName,
uh = Reddit.User.Modhash,
api_type = "json"
});
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
}
public enum DistinguishType
{
Moderator,
Admin,
Special,
None
}
internal class DistinguishConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DistinguishType) || objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
var value = token.Value<string>();
if (value == null)
return DistinguishType.None;
switch (value)
{
case "moderator": return DistinguishType.Moderator;
case "admin": return DistinguishType.Admin;
case "special": return DistinguishType.Special;
default: return DistinguishType.None;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var d = (DistinguishType)value;
if (d == DistinguishType.None)
{
writer.WriteNull();
return;
}
writer.WriteValue(d.ToString().ToLower());
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class CreatedThing : Thing
{
private Reddit Reddit { get; set; }
protected CreatedThing Init(Reddit reddit, JToken json)
{
CommonInit(reddit, json);
JsonConvert.PopulateObject(json["data"].ToString(), this, reddit.JsonSerializerSettings);
return this;
}
protected async Task<CreatedThing> InitAsync(Reddit reddit, JToken json)
{
CommonInit(reddit, json);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(json["data"].ToString(), this, reddit.JsonSerializerSettings));
return this;
}
private void CommonInit(Reddit reddit, JToken json)
{
base.Init(json);
Reddit = reddit;
}
[JsonProperty("created")]
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime Created { get; set; }
[JsonProperty("created_utc")]
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime CreatedUTC { get; set; }
}
}

344
RedditSharp/Things/Post.cs Normal file
View File

@@ -0,0 +1,344 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class Post : VotableThing
{
private const string CommentUrl = "/api/comment";
private const string RemoveUrl = "/api/remove";
private const string DelUrl = "/api/del";
private const string GetCommentsUrl = "/comments/{0}.json";
private const string ApproveUrl = "/api/approve";
private const string EditUserTextUrl = "/api/editusertext";
private const string HideUrl = "/api/hide";
private const string UnhideUrl = "/api/unhide";
private const string SetFlairUrl = "/api/flair";
private const string MarkNSFWUrl = "/api/marknsfw";
private const string UnmarkNSFWUrl = "/api/unmarknsfw";
private const string ContestModeUrl = "/api/set_contest_mode";
[JsonIgnore]
private Reddit Reddit { get; set; }
[JsonIgnore]
private IWebAgent WebAgent { get; set; }
public Post Init(Reddit reddit, JToken post, IWebAgent webAgent)
{
CommonInit(reddit, post, webAgent);
JsonConvert.PopulateObject(post["data"].ToString(), this, reddit.JsonSerializerSettings);
return this;
}
public async Task<Post> InitAsync(Reddit reddit, JToken post, IWebAgent webAgent)
{
CommonInit(reddit, post, webAgent);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(post["data"].ToString(), this, reddit.JsonSerializerSettings));
return this;
}
private void CommonInit(Reddit reddit, JToken post, IWebAgent webAgent)
{
base.Init(reddit, webAgent, post);
Reddit = reddit;
WebAgent = webAgent;
}
[JsonProperty("author")]
public string AuthorName { get; set; }
[JsonIgnore]
public RedditUser Author
{
get
{
return Reddit.GetUser(AuthorName);
}
}
public Comment[] Comments
{
get
{
return ListComments().ToArray();
}
}
[JsonProperty("approved_by")]
public string ApprovedBy { get; set; }
[JsonProperty("author_flair_css_class")]
public string AuthorFlairCssClass { get; set; }
[JsonProperty("author_flair_text")]
public string AuthorFlairText { get; set; }
[JsonProperty("banned_by")]
public string BannedBy { get; set; }
[JsonProperty("domain")]
public string Domain { get; set; }
[JsonProperty("edited")]
public bool Edited { get; set; }
[JsonProperty("is_self")]
public bool IsSelfPost { get; set; }
[JsonProperty("link_flair_css_class")]
public string LinkFlairCssClass { get; set; }
[JsonProperty("link_flair_text")]
public string LinkFlairText { get; set; }
[JsonProperty("num_comments")]
public int CommentCount { get; set; }
[JsonProperty("over_18")]
public bool NSFW { get; set; }
[JsonProperty("permalink")]
[JsonConverter(typeof(UrlParser))]
public Uri Permalink { get; set; }
[JsonProperty("score")]
public int Score { get; set; }
[JsonProperty("selftext")]
public string SelfText { get; set; }
[JsonProperty("selftext_html")]
public string SelfTextHtml { get; set; }
[JsonProperty("subreddit")]
public string Subreddit { get; set; }
[JsonProperty("thumbnail")]
[JsonConverter(typeof(UrlParser))]
public Uri Thumbnail { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("url")]
[JsonConverter(typeof(UrlParser))]
public Uri Url { get; set; }
[JsonProperty("num_reports")]
public int? Reports { get; set; }
public Comment Comment(string message)
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
var request = WebAgent.CreatePost(CommentUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
text = message,
thing_id = FullName,
uh = Reddit.User.Modhash,
api_type = "json"
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(data);
if (json["json"]["ratelimit"] != null)
throw new RateLimitException(TimeSpan.FromSeconds(json["json"]["ratelimit"].ValueOrDefault<double>()));
return new Comment().Init(Reddit, json["json"]["data"]["things"][0], WebAgent, this);
}
private string SimpleAction(string endpoint)
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
var request = WebAgent.CreatePost(endpoint);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
id = FullName,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
return data;
}
private string SimpleActionToggle(string endpoint, bool value)
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
var request = WebAgent.CreatePost(endpoint);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
id = FullName,
state = value,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
return data;
}
public void Approve()
{
var data = SimpleAction(ApproveUrl);
}
public void Remove()
{
RemoveImpl(false);
}
public void RemoveSpam()
{
RemoveImpl(true);
}
private void RemoveImpl(bool spam)
{
var request = WebAgent.CreatePost(RemoveUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
id = FullName,
spam = spam,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
public void Del()
{
var data = SimpleAction(ApproveUrl);
}
public void Hide()
{
var data = SimpleAction(HideUrl);
}
public void Unhide()
{
var data = SimpleAction(UnhideUrl);
}
public void MarkNSFW()
{
var data = SimpleAction(MarkNSFWUrl);
}
public void UnmarkNSFW()
{
var data = SimpleAction(UnmarkNSFWUrl);
}
public void ContestMode(bool state)
{
var data = SimpleActionToggle(ContestModeUrl, state);
}
#region Obsolete Getter Methods
[Obsolete("Use Comments property instead")]
public Comment[] GetComments()
{
return Comments;
}
#endregion Obsolete Getter Methods
/// <summary>
/// Replaces the text in this post with the input text.
/// </summary>
/// <param name="newText">The text to replace the post's contents</param>
public void EditText(string newText)
{
if (Reddit.User == null)
throw new Exception("No user logged in.");
if (!IsSelfPost)
throw new Exception("Submission to edit is not a self-post.");
var request = WebAgent.CreatePost(EditUserTextUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
text = newText,
thing_id = FullName,
uh = Reddit.User.Modhash
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
JToken json = JToken.Parse(result);
if (json["json"].ToString().Contains("\"errors\": []"))
SelfText = newText;
else
throw new Exception("Error editing text.");
}
public void Update()
{
JToken post = Reddit.GetToken(this.Url);
JsonConvert.PopulateObject(post["data"].ToString(), this, Reddit.JsonSerializerSettings);
}
public void SetFlair(string flairText, string flairClass)
{
if (Reddit.User == null)
throw new Exception("No user logged in.");
var request = WebAgent.CreatePost(SetFlairUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
r = Subreddit,
css_class = flairClass,
link = FullName,
//name = Name,
text = flairText,
uh = Reddit.User.Modhash
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(result);
LinkFlairText = flairText;
}
public List<Comment> ListComments(int? limit = null)
{
var url = string.Format(GetCommentsUrl, Id);
if (limit.HasValue)
{
var query = HttpUtility.ParseQueryString(string.Empty);
query.Add("limit", limit.Value.ToString());
url = string.Format("{0}?{1}", url, query);
}
var request = WebAgent.CreateGet(url);
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JArray.Parse(data);
var postJson = json.Last()["data"]["children"];
var comments = new List<Comment>();
foreach (var comment in postJson)
{
comments.Add(new Comment().Init(Reddit, comment, WebAgent, this));
}
return comments;
}
}
}

View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class PrivateMessage : Thing
{
private const string SetAsReadUrl = "/api/read_message";
private const string CommentUrl = "/api/comment";
private Reddit Reddit { get; set; }
private IWebAgent WebAgent { get; set; }
[JsonProperty("body")]
public string Body { get; set; }
[JsonProperty("body_html")]
public string BodyHtml { get; set; }
[JsonProperty("was_comment")]
public bool IsComment { get; set; }
[JsonProperty("created")]
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime Sent { get; set; }
[JsonProperty("created_utc")]
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime SentUTC { get; set; }
[JsonProperty("dest")]
public string Destination { get; set; }
[JsonProperty("author")]
public string Author { get; set; }
[JsonProperty("subreddit")]
public string Subreddit { get; set; }
[JsonProperty("new")]
public bool Unread { get; set; }
[JsonProperty("subject")]
public string Subject { get; set; }
[JsonProperty("parent_id")]
public string ParentID { get; set; }
[JsonProperty("first_message_name")]
public string FirstMessageName { get; set; }
[JsonIgnore]
public PrivateMessage[] Replies { get; set; }
[JsonIgnore]
public PrivateMessage Parent
{
get
{
if (string.IsNullOrEmpty(ParentID))
return null;
var id = ParentID.Remove(0, 3);
var listing = new Listing<PrivateMessage>(Reddit, "/message/messages/" + id + ".json", WebAgent);
var firstMessage = listing.First();
if (firstMessage.FullName == ParentID)
return listing.First();
else
return firstMessage.Replies.First(x => x.FullName == ParentID);
}
}
public Listing<PrivateMessage> Thread
{
get
{
if (string.IsNullOrEmpty(ParentID))
return null;
var id = ParentID.Remove(0, 3);
return new Listing<PrivateMessage>(Reddit, "/message/messages/" + id + ".json", WebAgent);
}
}
public PrivateMessage Init(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
JsonConvert.PopulateObject(json["data"].ToString(), this, reddit.JsonSerializerSettings);
return this;
}
public async Task<PrivateMessage> InitAsync(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(json["data"].ToString(), this, reddit.JsonSerializerSettings));
return this;
}
private void CommonInit(Reddit reddit, JToken json, IWebAgent webAgent)
{
base.Init(json);
Reddit = reddit;
WebAgent = webAgent;
var data = json["data"];
if (data["replies"] != null && data["replies"].Any())
{
if (data["replies"]["data"] != null)
{
if (data["replies"]["data"]["children"] != null)
{
var replies = new List<PrivateMessage>();
foreach (var reply in data["replies"]["data"]["children"])
replies.Add(new PrivateMessage().Init(reddit, reply, webAgent));
Replies = replies.ToArray();
}
}
}
}
#region Obsolete Getter Methods
[Obsolete("Use Thread property instead")]
public Listing<PrivateMessage> GetThread()
{
return Thread;
}
#endregion Obsolete Gettter Methods
public void SetAsRead()
{
var request = WebAgent.CreatePost(SetAsReadUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
id = FullName,
uh = Reddit.User.Modhash,
api_type = "json"
});
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
public void Reply(string message)
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
var request = WebAgent.CreatePost(CommentUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
text = message,
thing_id = FullName,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(data);
}
}
}

View File

@@ -0,0 +1,201 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class RedditUser : Thing
{
private const string OverviewUrl = "/user/{0}.json";
private const string CommentsUrl = "/user/{0}/comments.json";
private const string LinksUrl = "/user/{0}/submitted.json";
private const string SubscribedSubredditsUrl = "/subreddits/mine.json";
private const string LikedUrl = "/user/{0}/liked.json";
private const string DislikedUrl = "/user/{0}/disliked.json";
private const int MAX_LIMIT = 100;
public RedditUser Init(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
JsonConvert.PopulateObject(json["name"] == null ? json["data"].ToString() : json.ToString(), this,
reddit.JsonSerializerSettings);
return this;
}
public async Task<RedditUser> InitAsync(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(json["name"] == null ? json["data"].ToString() : json.ToString(), this,
reddit.JsonSerializerSettings));
return this;
}
private void CommonInit(Reddit reddit, JToken json, IWebAgent webAgent)
{
base.Init(json);
Reddit = reddit;
WebAgent = webAgent;
}
[JsonIgnore]
protected Reddit Reddit { get; set; }
[JsonIgnore]
protected IWebAgent WebAgent { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("is_gold")]
public bool HasGold { get; set; }
[JsonProperty("is_mod")]
public bool IsModerator { get; set; }
[JsonProperty("link_karma")]
public int LinkKarma { get; set; }
[JsonProperty("comment_karma")]
public int CommentKarma { get; set; }
[JsonProperty("created")]
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime Created { get; set; }
public Listing<VotableThing> Overview
{
get
{
return new Listing<VotableThing>(Reddit, string.Format(OverviewUrl, Name), WebAgent);
}
}
public Listing<Post> LikedPosts
{
get
{
return new Listing<Post>(Reddit, string.Format(LikedUrl, Name), WebAgent);
}
}
public Listing<Post> DislikedPosts
{
get
{
return new Listing<Post>(Reddit, string.Format(DislikedUrl, Name), WebAgent);
}
}
public Listing<Comment> Comments
{
get
{
return new Listing<Comment>(Reddit, string.Format(CommentsUrl, Name), WebAgent);
}
}
public Listing<Post> Posts
{
get
{
return new Listing<Post>(Reddit, string.Format(LinksUrl, Name), WebAgent);
}
}
public Listing<Subreddit> SubscribedSubreddits
{
get
{
return new Listing<Subreddit>(Reddit, SubscribedSubredditsUrl, WebAgent);
}
}
/// <summary>
/// Get a listing of comments from the user sorted by <paramref name="sorting"/>, from time <paramref name="fromTime"/>
/// and limited to <paramref name="limit"/>.
/// </summary>
/// <param name="sorting">How to sort the comments (hot, new, top, controversial).</param>
/// <param name="limit">How many comments to fetch per request. Max is 100.</param>
/// <param name="fromTime">What time frame of comments to show (hour, day, week, month, year, all).</param>
/// <returns>The listing of comments requested.</returns>
public Listing<Comment> GetComments(Sort sorting = Sort.New, int limit = 25, FromTime fromTime = FromTime.All)
{
if ((limit < 1) || (limit > MAX_LIMIT))
throw new ArgumentOutOfRangeException("limit", "Valid range: [1," + MAX_LIMIT + "]");
string commentsUrl = string.Format(CommentsUrl, Name);
commentsUrl += string.Format("?sort={0}&limit={1}&t={2}", Enum.GetName(typeof(Sort), sorting), limit, Enum.GetName(typeof(FromTime), fromTime));
return new Listing<Comment>(Reddit, commentsUrl, WebAgent);
}
/// <summary>
/// Get a listing of posts from the user sorted by <paramref name="sorting"/>, from time <paramref name="fromTime"/>
/// and limited to <paramref name="limit"/>.
/// </summary>
/// <param name="sorting">How to sort the posts (hot, new, top, controversial).</param>
/// <param name="limit">How many posts to fetch per request. Max is 100.</param>
/// <param name="fromTime">What time frame of posts to show (hour, day, week, month, year, all).</param>
/// <returns>The listing of posts requested.</returns>
public Listing<Post> GetPosts(Sort sorting = Sort.New, int limit = 25, FromTime fromTime = FromTime.All)
{
if ((limit < 1) || (limit > 100))
throw new ArgumentOutOfRangeException("limit", "Valid range: [1,100]");
string linksUrl = string.Format(LinksUrl, Name);
linksUrl += string.Format("?sort={0}&limit={1}&t={2}", Enum.GetName(typeof(Sort), sorting), limit, Enum.GetName(typeof(FromTime), fromTime));
return new Listing<Post>(Reddit, linksUrl, WebAgent);
}
public override string ToString()
{
return Name;
}
#region Obsolete Getter Methods
[Obsolete("Use Overview property instead")]
public Listing<VotableThing> GetOverview()
{
return Overview;
}
[Obsolete("Use Comments property instead")]
public Listing<Comment> GetComments()
{
return Comments;
}
[Obsolete("Use Posts property instead")]
public Listing<Post> GetPosts()
{
return Posts;
}
[Obsolete("Use SubscribedSubreddits property instead")]
public Listing<Subreddit> GetSubscribedSubreddits()
{
return SubscribedSubreddits;
}
#endregion Obsolete Getter Methods
}
public enum Sort
{
New,
Hot,
Top,
Controversial
}
public enum FromTime
{
All,
Year,
Month,
Week,
Day,
Hour
}
}

View File

@@ -0,0 +1,677 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Threading.Tasks;
using HtmlAgilityPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class Subreddit : Thing
{
private const string SubredditPostUrl = "/r/{0}.json";
private const string SubredditNewUrl = "/r/{0}/new.json?sort=new";
private const string SubredditHotUrl = "/r/{0}/hot.json";
private const string SubredditTopUrl = "/r/{0}/top.json?t={1}";
private const string SubscribeUrl = "/api/subscribe";
private const string GetSettingsUrl = "/r/{0}/about/edit.json";
private const string GetReducedSettingsUrl = "/r/{0}/about.json";
private const string ModqueueUrl = "/r/{0}/about/modqueue.json";
private const string UnmoderatedUrl = "/r/{0}/about/unmoderated.json";
private const string FlairTemplateUrl = "/api/flairtemplate";
private const string ClearFlairTemplatesUrl = "/api/clearflairtemplates";
private const string SetUserFlairUrl = "/api/flair";
private const string StylesheetUrl = "/r/{0}/about/stylesheet.json";
private const string UploadImageUrl = "/api/upload_sr_img";
private const string FlairSelectorUrl = "/api/flairselector";
private const string AcceptModeratorInviteUrl = "/api/accept_moderator_invite";
private const string LeaveModerationUrl = "/api/unfriend";
private const string BanUserUrl = "/api/friend";
private const string AddModeratorUrl = "/api/friend";
private const string AddContributorUrl = "/api/friend";
private const string ModeratorsUrl = "/r/{0}/about/moderators.json";
private const string FrontPageUrl = "/.json";
private const string SubmitLinkUrl = "/api/submit";
private const string FlairListUrl = "/r/{0}/api/flairlist.json";
private const string CommentsUrl = "/r/{0}/comments.json";
private const string SearchUrl = "/r/{0}/search.json?q={1}&restrict_sr=on&sort={2}&t={3}";
[JsonIgnore]
private Reddit Reddit { get; set; }
[JsonIgnore]
private IWebAgent WebAgent { get; set; }
[JsonIgnore]
public Wiki Wiki { get; private set; }
[JsonProperty("created")]
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime? Created { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("description_html")]
public string DescriptionHTML { get; set; }
[JsonProperty("display_name")]
public string DisplayName { get; set; }
[JsonProperty("header_img")]
public string HeaderImage { get; set; }
[JsonProperty("header_title")]
public string HeaderTitle { get; set; }
[JsonProperty("over18")]
public bool? NSFW { get; set; }
[JsonProperty("public_description")]
public string PublicDescription { get; set; }
[JsonProperty("subscribers")]
public int? Subscribers { get; set; }
[JsonProperty("accounts_active")]
public int? ActiveUsers { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("url")]
[JsonConverter(typeof(UrlParser))]
public Uri Url { get; set; }
[JsonIgnore]
public string Name { get; set; }
public Listing<Post> GetTop(FromTime timePeriod)
{
if (Name == "/")
{
return new Listing<Post>(Reddit, "/top.json?t=" + Enum.GetName(typeof(FromTime), timePeriod).ToLower(), WebAgent);
}
return new Listing<Post>(Reddit, string.Format(SubredditTopUrl, Name, Enum.GetName(typeof(FromTime), timePeriod)).ToLower(), WebAgent);
}
public Listing<Post> Posts
{
get
{
if (Name == "/")
return new Listing<Post>(Reddit, "/.json", WebAgent);
return new Listing<Post>(Reddit, string.Format(SubredditPostUrl, Name), WebAgent);
}
}
public Listing<Comment> Comments
{
get
{
if (Name == "/")
return new Listing<Comment>(Reddit, "/comments.json", WebAgent);
return new Listing<Comment>(Reddit, string.Format(CommentsUrl, Name), WebAgent);
}
}
public Listing<Post> New
{
get
{
if (Name == "/")
return new Listing<Post>(Reddit, "/new.json", WebAgent);
return new Listing<Post>(Reddit, string.Format(SubredditNewUrl, Name), WebAgent);
}
}
public Listing<Post> Hot
{
get
{
if (Name == "/")
return new Listing<Post>(Reddit, "/.json", WebAgent);
return new Listing<Post>(Reddit, string.Format(SubredditHotUrl, Name), WebAgent);
}
}
public Listing<VotableThing> ModQueue
{
get
{
return new Listing<VotableThing>(Reddit, string.Format(ModqueueUrl, Name), WebAgent);
}
}
public Listing<Post> UnmoderatedLinks
{
get
{
return new Listing<Post>(Reddit, string.Format(UnmoderatedUrl, Name), WebAgent);
}
}
public Listing<Post> Search(string terms)
{
return new Listing<Post>(Reddit, string.Format(SearchUrl, Name, Uri.EscapeUriString(terms), "relevance", "all"), WebAgent);
}
public SubredditSettings Settings
{
get
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
try
{
var request = WebAgent.CreateGet(string.Format(GetSettingsUrl, Name));
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(data);
return new SubredditSettings(this, Reddit, json, WebAgent);
}
catch // TODO: More specific catch
{
// Do it unauthed
var request = WebAgent.CreateGet(string.Format(GetReducedSettingsUrl, Name));
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(data);
return new SubredditSettings(this, Reddit, json, WebAgent);
}
}
}
public UserFlairTemplate[] UserFlairTemplates // Hacky, there isn't a proper endpoint for this
{
get
{
var request = WebAgent.CreatePost(FlairSelectorUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
name = Reddit.User.Name,
r = Name,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var document = new HtmlDocument();
document.LoadHtml(data);
if (document.DocumentNode.Descendants("div").First().Attributes["error"] != null)
throw new InvalidOperationException("This subreddit does not allow users to select flair.");
var templateNodes = document.DocumentNode.Descendants("li");
var list = new List<UserFlairTemplate>();
foreach (var node in templateNodes)
{
list.Add(new UserFlairTemplate
{
CssClass = node.Descendants("span").First().Attributes["class"].Value.Split(' ')[1],
Text = node.Descendants("span").First().InnerText
});
}
return list.ToArray();
}
}
public SubredditStyle Stylesheet
{
get
{
var request = WebAgent.CreateGet(string.Format(StylesheetUrl, Name));
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
return new SubredditStyle(Reddit, this, json, WebAgent);
}
}
public IEnumerable<ModeratorUser> Moderators
{
get
{
var request = WebAgent.CreateGet(string.Format(ModeratorsUrl, Name));
var response = request.GetResponse();
var responseString = WebAgent.GetResponseString(response.GetResponseStream());
var json = JObject.Parse(responseString);
var type = json["kind"].ToString();
if (type != "UserList")
throw new FormatException("Reddit responded with an object that is not a user listing.");
var data = json["data"];
var mods = data["children"].ToArray();
var result = new ModeratorUser[mods.Length];
for (var i = 0; i < mods.Length; i++)
{
var mod = new ModeratorUser(Reddit, mods[i]);
result[i] = mod;
}
return result;
}
}
public Subreddit Init(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
JsonConvert.PopulateObject(json["data"].ToString(), this, reddit.JsonSerializerSettings);
SetName();
return this;
}
public async Task<Subreddit> InitAsync(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(json["data"].ToString(), this, reddit.JsonSerializerSettings));
SetName();
return this;
}
private void SetName()
{
Name = Url.ToString();
if (Name.StartsWith("/r/"))
Name = Name.Substring(3);
if (Name.StartsWith("r/"))
Name = Name.Substring(2);
Name = Name.TrimEnd('/');
}
private void CommonInit(Reddit reddit, JToken json, IWebAgent webAgent)
{
base.Init(json);
Reddit = reddit;
WebAgent = webAgent;
Wiki = new Wiki(reddit, this, webAgent);
}
public static Subreddit GetRSlashAll(Reddit reddit)
{
var rSlashAll = new Subreddit
{
DisplayName = "/r/all",
Title = "/r/all",
Url = new Uri("/r/all", UriKind.Relative),
Name = "all",
Reddit = reddit,
WebAgent = reddit._webAgent
};
return rSlashAll;
}
public static Subreddit GetFrontPage(Reddit reddit)
{
var frontPage = new Subreddit
{
DisplayName = "Front Page",
Title = "reddit: the front page of the internet",
Url = new Uri("/", UriKind.Relative),
Name = "/",
Reddit = reddit,
WebAgent = reddit._webAgent
};
return frontPage;
}
public void Subscribe()
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
var request = WebAgent.CreatePost(SubscribeUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
action = "sub",
sr = FullName,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
// Discard results
}
public void Unsubscribe()
{
if (Reddit.User == null)
throw new AuthenticationException("No user logged in.");
var request = WebAgent.CreatePost(SubscribeUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
action = "unsub",
sr = FullName,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
// Discard results
}
public void ClearFlairTemplates(FlairType flairType)
{
var request = WebAgent.CreatePost(ClearFlairTemplatesUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
flair_type = flairType == FlairType.Link ? "LINK_FLAIR" : "USER_FLAIR",
uh = Reddit.User.Modhash,
r = Name
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
public void AddFlairTemplate(string cssClass, FlairType flairType, string text, bool userEditable)
{
var request = WebAgent.CreatePost(FlairTemplateUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
css_class = cssClass,
flair_type = flairType == FlairType.Link ? "LINK_FLAIR" : "USER_FLAIR",
text = text,
text_editable = userEditable,
uh = Reddit.User.Modhash,
r = Name,
api_type = "json"
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
}
public string GetFlairText(string user)
{
var request = WebAgent.CreateGet(String.Format(FlairListUrl + "?name=" + user, Name));
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
return (string)json["users"][0]["flair_text"];
}
public string GetFlairCssClass(string user)
{
var request = WebAgent.CreateGet(String.Format(FlairListUrl + "?name=" + user, Name));
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
return (string)json["users"][0]["flair_css_class"];
}
public void SetUserFlair(string user, string cssClass, string text)
{
var request = WebAgent.CreatePost(SetUserFlairUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
css_class = cssClass,
text = text,
uh = Reddit.User.Modhash,
r = Name,
name = user
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
public void UploadHeaderImage(string name, ImageType imageType, byte[] file)
{
var request = WebAgent.CreatePost(UploadImageUrl);
var formData = new MultipartFormBuilder(request);
formData.AddDynamic(new
{
name,
uh = Reddit.User.Modhash,
r = Name,
formid = "image-upload",
img_type = imageType == ImageType.PNG ? "png" : "jpg",
upload = "",
header = 1
});
formData.AddFile("file", "foo.png", file, imageType == ImageType.PNG ? "image/png" : "image/jpeg");
formData.Finish();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
// TODO: Detect errors
}
public void AddModerator(string user)
{
var request = WebAgent.CreatePost(AddModeratorUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
uh = Reddit.User.Modhash,
r = Name,
type = "moderator",
name = user
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
}
public void AcceptModeratorInvite()
{
var request = WebAgent.CreatePost(AcceptModeratorInviteUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
uh = Reddit.User.Modhash,
r = Name
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
}
public void RemoveModerator(string id)
{
var request = WebAgent.CreatePost(LeaveModerationUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
uh = Reddit.User.Modhash,
r = Name,
type = "moderator",
id
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
}
public override string ToString()
{
return "/r/" + DisplayName;
}
public void AddContributor(string user)
{
var request = WebAgent.CreatePost(AddContributorUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
uh = Reddit.User.Modhash,
r = Name,
type = "contributor",
name = user
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
}
public void RemoveContributor(string id)
{
var request = WebAgent.CreatePost(LeaveModerationUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
uh = Reddit.User.Modhash,
r = Name,
type = "contributor",
id
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
}
public void BanUser(string user, string reason)
{
var request = WebAgent.CreatePost(BanUserUrl);
WebAgent.WritePostBody(request.GetRequestStream(), new
{
api_type = "json",
uh = Reddit.User.Modhash,
r = Name,
type = "banned",
id = "#banned",
name = user,
note = reason,
action = "add",
container = FullName
});
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
}
private Post Submit(SubmitData data)
{
if (Reddit.User == null)
throw new RedditException("No user logged in.");
var request = WebAgent.CreatePost(SubmitLinkUrl);
WebAgent.WritePostBody(request.GetRequestStream(), data);
var response = request.GetResponse();
var result = WebAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(result);
ICaptchaSolver solver = Reddit.CaptchaSolver;
if (json["json"]["errors"].Any() && json["json"]["errors"][0][0].ToString() == "BAD_CAPTCHA"
&& solver != null)
{
data.Iden = json["json"]["captcha"].ToString();
CaptchaResponse captchaResponse = solver.HandleCaptcha(new Captcha(data.Iden));
// We throw exception due to this method being expected to return a valid Post object, but we cannot
// if we got a Captcha error.
if (captchaResponse.Cancel)
throw new CaptchaFailedException("Captcha verification failed when submitting " + data.Kind + " post");
data.Captcha = captchaResponse.Answer;
return Submit(data);
}
else if (json["json"]["errors"].Any() && json["json"]["errors"][0][0].ToString() == "ALREADY_SUB")
{
throw new DuplicateLinkException(String.Format("Post failed when submitting. The following link has already been submitted: {0}", SubmitLinkUrl));
}
return new Post().Init(Reddit, json["json"], WebAgent);
}
/// <summary>
/// Submits a link post in the current subreddit using the logged-in user
/// </summary>
/// <param name="title">The title of the submission</param>
/// <param name="url">The url of the submission link</param>
public Post SubmitPost(string title, string url, string captchaId = "", string captchaAnswer = "", bool resubmit = false)
{
return
Submit(
new LinkData
{
Subreddit = Name,
UserHash = Reddit.User.Modhash,
Title = title,
URL = url,
Resubmit = resubmit,
Iden = captchaId,
Captcha = captchaAnswer
});
}
/// <summary>
/// Submits a text post in the current subreddit using the logged-in user
/// </summary>
/// <param name="title">The title of the submission</param>
/// <param name="text">The raw markdown text of the submission</param>
public Post SubmitTextPost(string title, string text, string captchaId = "", string captchaAnswer = "")
{
return
Submit(
new TextData
{
Subreddit = Name,
UserHash = Reddit.User.Modhash,
Title = title,
Text = text,
Iden = captchaId,
Captcha = captchaAnswer
});
}
#region Obsolete Getter Methods
[Obsolete("Use Posts property instead")]
public Listing<Post> GetPosts()
{
return Posts;
}
[Obsolete("Use New property instead")]
public Listing<Post> GetNew()
{
return New;
}
[Obsolete("Use Hot property instead")]
public Listing<Post> GetHot()
{
return Hot;
}
[Obsolete("Use ModQueue property instead")]
public Listing<VotableThing> GetModQueue()
{
return ModQueue;
}
[Obsolete("Use UnmoderatedLinks property instead")]
public Listing<Post> GetUnmoderatedLinks()
{
return UnmoderatedLinks;
}
[Obsolete("Use Settings property instead")]
public SubredditSettings GetSettings()
{
return Settings;
}
[Obsolete("Use UserFlairTemplates property instead")]
public UserFlairTemplate[] GetUserFlairTemplates() // Hacky, there isn't a proper endpoint for this
{
return UserFlairTemplates;
}
[Obsolete("Use Stylesheet property instead")]
public SubredditStyle GetStylesheet()
{
return Stylesheet;
}
[Obsolete("Use Moderators property instead")]
public IEnumerable<ModeratorUser> GetModerators()
{
return Moderators;
}
#endregion Obsolete Getter Methods
}
}

113
RedditSharp/Things/Thing.cs Normal file
View File

@@ -0,0 +1,113 @@
using System;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
namespace RedditSharp.Things
{
public class Thing
{
public static Thing Parse(Reddit reddit, JToken json, IWebAgent webAgent)
{
var kind = json["kind"].ValueOrDefault<string>();
switch (kind)
{
case "t1":
return new Comment().Init(reddit, json, webAgent, null);
case "t2":
return new RedditUser().Init(reddit, json, webAgent);
case "t3":
return new Post().Init(reddit, json, webAgent);
case "t4":
return new PrivateMessage().Init(reddit, json, webAgent);
case "t5":
return new Subreddit().Init(reddit, json, webAgent);
default:
return null;
}
}
// if we can't determine the type of thing by "kind", try by type
public static Thing Parse<T>(Reddit reddit, JToken json, IWebAgent webAgent) where T : Thing
{
Thing result = Parse(reddit, json, webAgent);
if (result == null)
{
if (typeof(T) == typeof(WikiPageRevision))
{
return new WikiPageRevision().Init(reddit, json, webAgent);
}
}
return result;
}
internal void Init(JToken json)
{
if (json == null)
return;
var data = json["name"] == null ? json["data"] : json;
FullName = data["name"].ValueOrDefault<string>();
Id = data["id"].ValueOrDefault<string>();
Kind = json["kind"].ValueOrDefault<string>();
FetchedAt = DateTime.Now;
}
public virtual string Shortlink
{
get { return "http://redd.it/" + Id; }
}
public string Id { get; set; }
public string FullName { get; set; }
public string Kind { get; set; }
/// <summary>
/// The time at which this object was fetched from reddit servers.
/// </summary>
public DateTime FetchedAt { get; private set; }
/// <summary>
/// Gets the time since last fetch from reddit servers.
/// </summary>
public TimeSpan TimeSinceFetch
{
get
{
return DateTime.Now - FetchedAt;
}
}
public static async Task<Thing> ParseAsync(Reddit reddit, JToken json, IWebAgent webAgent)
{
var kind = json["kind"].ValueOrDefault<string>();
switch (kind)
{
case "t1":
return await new Comment().InitAsync(reddit, json, webAgent, null);
case "t2":
return await new RedditUser().InitAsync(reddit, json, webAgent);
case "t3":
return await new Post().InitAsync(reddit, json, webAgent);
case "t4":
return await new PrivateMessage().InitAsync(reddit, json, webAgent);
case "t5":
return await new Subreddit().InitAsync(reddit, json, webAgent);
default:
return null;
}
}
// if we can't determine the type of thing by "kind", try by type
public static async Task<Thing> ParseAsync<T>(Reddit reddit, JToken json, IWebAgent webAgent) where T : Thing
{
Thing result = await ParseAsync(reddit, json, webAgent);
if (result == null)
{
if (typeof(T) == typeof(WikiPageRevision))
{
return await new WikiPageRevision().InitAsync(reddit, json, webAgent);
}
}
return result;
}
}
}

View File

@@ -0,0 +1,162 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class VotableThing : CreatedThing
{
public enum VoteType
{
Upvote = 1,
None = 0,
Downvote = -1
}
private const string VoteUrl = "/api/vote";
private const string SaveUrl = "/api/save";
private const string UnsaveUrl = "/api/unsave";
[JsonIgnore]
private IWebAgent WebAgent { get; set; }
[JsonIgnore]
private Reddit Reddit { get; set; }
protected VotableThing Init(Reddit reddit, IWebAgent webAgent, JToken json)
{
CommonInit(reddit, webAgent, json);
JsonConvert.PopulateObject(json["data"].ToString(), this, Reddit.JsonSerializerSettings);
return this;
}
protected async Task<VotableThing> InitAsync(Reddit reddit, IWebAgent webAgent, JToken json)
{
CommonInit(reddit, webAgent, json);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(json["data"].ToString(), this, Reddit.JsonSerializerSettings));
return this;
}
private void CommonInit(Reddit reddit, IWebAgent webAgent, JToken json)
{
base.Init(reddit, json);
Reddit = reddit;
WebAgent = webAgent;
}
[JsonProperty("downs")]
public int Downvotes { get; set; }
[JsonProperty("ups")]
public int Upvotes { get; set; }
[JsonProperty("saved")]
public bool Saved { get; set; }
/// <summary>
/// True if the logged in user has upvoted this.
/// False if they have not.
/// Null if they have not cast a vote.
/// </summary>
[JsonProperty("likes")]
public bool? Liked { get; set; }
/// <summary>
/// Gets or sets the vote for the current VotableThing.
/// </summary>
[JsonIgnore]
public VoteType Vote
{
get
{
switch (this.Liked)
{
case true: return VoteType.Upvote;
case false: return VoteType.Downvote;
default: return VoteType.None;
}
}
set { this.SetVote(value); }
}
public void Upvote()
{
this.SetVote(VoteType.Upvote);
}
public void Downvote()
{
this.SetVote(VoteType.Downvote);
}
public void SetVote(VoteType type)
{
if (this.Vote == type) return;
var request = WebAgent.CreatePost(VoteUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
dir = (int)type,
id = FullName,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
if (Liked == true) Upvotes--;
if (Liked == false) Downvotes--;
switch(type)
{
case VoteType.Upvote: Liked = true; Upvotes++; return;
case VoteType.None: Liked = null; return;
case VoteType.Downvote: Liked = false; Downvotes++; return;
}
}
public void Save()
{
var request = WebAgent.CreatePost(SaveUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
id = FullName,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
Saved = true;
}
public void Unsave()
{
var request = WebAgent.CreatePost(UnsaveUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
id = FullName,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
Saved = false;
}
public void ClearVote()
{
var request = WebAgent.CreatePost(VoteUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
dir = 0,
id = FullName,
uh = Reddit.User.Modhash
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp.Things
{
public class WikiPageRevision : Thing
{
[JsonProperty("id")]
new public string Id { get; private set; }
[JsonProperty("timestamp")]
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime? TimeStamp { get; set; }
[JsonProperty("reason")]
public string Reason { get; private set; }
[JsonProperty("page")]
public string Page { get; private set; }
[JsonIgnore]
public RedditUser Author { get; set; }
protected internal WikiPageRevision() { }
internal WikiPageRevision Init(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
JsonConvert.PopulateObject(json.ToString(), this, reddit.JsonSerializerSettings);
return this;
}
internal async Task<WikiPageRevision> InitAsync(Reddit reddit, JToken json, IWebAgent webAgent)
{
CommonInit(reddit, json, webAgent);
await Task.Factory.StartNew(() => JsonConvert.PopulateObject(json.ToString(), this, reddit.JsonSerializerSettings));
return this;
}
private void CommonInit(Reddit reddit, JToken json, IWebAgent webAgent)
{
base.Init(json);
Author = new RedditUser().Init(reddit, json["author"], webAgent);
}
}
}