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

174
RedditSharp/AuthProvider.cs Normal file
View File

@@ -0,0 +1,174 @@
using System;
using System.Net;
using System.Security.Authentication;
using System.Text;
using Newtonsoft.Json.Linq;
using RedditSharp.Things;
namespace RedditSharp
{
public class AuthProvider
{
private const string AccessUrl = "https://ssl.reddit.com/api/v1/access_token";
private const string OauthGetMeUrl = "https://oauth.reddit.com/api/v1/me";
public static string OAuthToken { get; set; }
public static string RefreshToken { get; set; }
[Flags]
public enum Scope
{
none = 0x0,
identity = 0x1,
edit = 0x2,
flair = 0x4,
history = 0x8,
modconfig = 0x10,
modflair = 0x20,
modlog = 0x40,
modposts = 0x80,
modwiki = 0x100,
mysubreddits = 0x200,
privatemessages = 0x400,
read = 0x800,
report = 0x1000,
save = 0x2000,
submit = 0x4000,
subscribe = 0x8000,
vote = 0x10000,
wikiedit = 0x20000,
wikiread = 0x40000
}
private readonly IWebAgent _webAgent;
private readonly string _redirectUri;
private readonly string _clientId;
private readonly string _clientSecret;
/// <summary>
/// Allows use of reddit's OAuth interface, using an app set up at https://ssl.reddit.com/prefs/apps/.
/// </summary>
/// <param name="clientId">Granted by reddit as part of app.</param>
/// <param name="clientSecret">Granted by reddit as part of app.</param>
/// <param name="redirectUri">Selected as part of app. Reddit will send users back here.</param>
public AuthProvider(string clientId, string clientSecret, string redirectUri)
{
_clientId = clientId;
_clientSecret = clientSecret;
_redirectUri = redirectUri;
_webAgent = new WebAgent();
}
/// <summary>
/// Creates the reddit OAuth2 Url to redirect the user to for authorization.
/// </summary>
/// <param name="state">Used to verify that the user received is the user that was sent</param>
/// <param name="scope">Determines what actions can be performed against the user.</param>
/// <param name="permanent">Set to true for access lasting longer than one hour.</param>
/// <returns></returns>
public string GetAuthUrl(string state, Scope scope, bool permanent = false)
{
return String.Format("https://ssl.reddit.com/api/v1/authorize?client_id={0}&response_type=code&state={1}&redirect_uri={2}&duration={3}&scope={4}", _clientId, state, _redirectUri, permanent ? "permanent" : "temporary", scope.ToString().Replace(" ",""));
}
/// <summary>
/// Gets the OAuth token for the user associated with the provided code.
/// </summary>
/// <param name="code">Sent by reddit as a parameter in the return uri.</param>
/// <param name="isRefresh">Set to true for refresh requests.</param>
/// <returns></returns>
public string GetOAuthToken(string code, bool isRefresh = false)
{
if (Type.GetType("Mono.Runtime") != null)
ServicePointManager.ServerCertificateValidationCallback = (s, c, ch, ssl) => true;
_webAgent.Cookies = new CookieContainer();
var request = _webAgent.CreatePost(AccessUrl);
request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(_clientId + ":" + _clientSecret));
var stream = request.GetRequestStream();
if (isRefresh)
{
_webAgent.WritePostBody(stream, new
{
grant_type = "refresh_token",
refresh_token = code
});
}
else
{
_webAgent.WritePostBody(stream, new
{
grant_type = "authorization_code",
code,
redirect_uri = _redirectUri
});
}
stream.Close();
var json = _webAgent.ExecuteRequest(request);
if (json["access_token"] != null)
{
if (json["refresh_token"] != null)
RefreshToken = json["refresh_token"].ToString();
OAuthToken = json["access_token"].ToString();
return json["access_token"].ToString();
}
throw new AuthenticationException("Could not log in.");
}
/// <summary>
/// Gets the OAuth token for the user.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The user's password.</param>
/// <returns>The access token</returns>
public string GetOAuthToken(string username, string password)
{
if (Type.GetType("Mono.Runtime") != null)
ServicePointManager.ServerCertificateValidationCallback = (s, c, ch, ssl) => true;
_webAgent.Cookies = new CookieContainer();
var request = _webAgent.CreatePost(AccessUrl);
request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(_clientId + ":" + _clientSecret));
var stream = request.GetRequestStream();
_webAgent.WritePostBody(stream, new
{
grant_type = "password",
username,
password,
redirect_uri = _redirectUri
});
stream.Close();
var json = _webAgent.ExecuteRequest(request);
if (json["access_token"] != null)
{
if (json["refresh_token"] != null)
RefreshToken = json["refresh_token"].ToString();
OAuthToken = json["access_token"].ToString();
return json["access_token"].ToString();
}
throw new AuthenticationException("Could not log in.");
}
/// <summary>
/// Gets a user authenticated by OAuth2.
/// </summary>
/// <param name="accessToken">Obtained using GetOAuthToken</param>
/// <returns></returns>
[Obsolete("Reddit.InitOrUpdateUser is preferred")]
public AuthenticatedUser GetUser(string accessToken)
{
var request = _webAgent.CreateGet(OauthGetMeUrl);
request.Headers["Authorization"] = String.Format("bearer {0}", accessToken);
var response = (HttpWebResponse)request.GetResponse();
var result = _webAgent.GetResponseString(response.GetResponseStream());
var thingjson = "{\"kind\": \"t2\", \"data\": " + result + "}";
var json = JObject.Parse(thingjson);
return new AuthenticatedUser().Init(new Reddit(), json, _webAgent);
}
}
}

18
RedditSharp/Captcha.cs Normal file
View File

@@ -0,0 +1,18 @@
using System;
namespace RedditSharp
{
public struct Captcha
{
private const string UrlFormat = "http://www.reddit.com/captcha/{0}";
public readonly string Id;
public readonly Uri Url;
internal Captcha(string id)
{
Id = id;
Url = new Uri(string.Format(UrlFormat, Id), UriKind.Absolute);
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace RedditSharp
{
public class CaptchaFailedException : RedditException
{
public CaptchaFailedException()
{
}
public CaptchaFailedException(string message)
: base(message)
{
}
public CaptchaFailedException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@@ -0,0 +1,14 @@
namespace RedditSharp
{
public class CaptchaResponse
{
public readonly string Answer;
public bool Cancel { get { return string.IsNullOrEmpty(Answer); } }
public CaptchaResponse(string answer = null)
{
Answer = answer;
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace RedditSharp
{
public class ConsoleCaptchaSolver : ICaptchaSolver
{
public CaptchaResponse HandleCaptcha(Captcha captcha)
{
Console.WriteLine("Captcha required! The captcha ID is {0}", captcha.Id);
Console.WriteLine("You can find the captcha image at this url: {0}", captcha.Url);
Console.WriteLine("Please input your captcha response or empty string to cancel:");
var response = Console.ReadLine();
CaptchaResponse captchaResponse = new CaptchaResponse(string.IsNullOrEmpty(response) ? null : response);
return captchaResponse;
}
}
}

61
RedditSharp/Domain.cs Normal file
View File

@@ -0,0 +1,61 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RedditSharp.Things;
namespace RedditSharp
{
public class Domain
{
private const string DomainPostUrl = "/domain/{0}.json";
private const string DomainNewUrl = "/domain/{0}/new.json?sort=new";
private const string DomainHotUrl = "/domain/{0}/hot.json";
private const string FrontPageUrl = "/.json";
[JsonIgnore]
private Reddit Reddit { get; set; }
[JsonIgnore]
private IWebAgent WebAgent { get; set; }
[JsonIgnore]
public string Name { get; set; }
public Listing<Post> Posts
{
get
{
return new Listing<Post>(Reddit, string.Format(DomainPostUrl, Name), WebAgent);
}
}
public Listing<Post> New
{
get
{
return new Listing<Post>(Reddit, string.Format(DomainNewUrl, Name), WebAgent);
}
}
public Listing<Post> Hot
{
get
{
return new Listing<Post>(Reddit, string.Format(DomainHotUrl, Name), WebAgent);
}
}
protected internal Domain(Reddit reddit, Uri domain, IWebAgent webAgent)
{
Reddit = reddit;
WebAgent = webAgent;
Name = domain.Host;
}
public override string ToString()
{
return "/domain/" + Name;
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RedditSharp
{
/// <summary>
/// Exception that gets thrown if you try and submit a duplicate link to a SubReddit
/// </summary>
public class DuplicateLinkException : RedditException
{
public DuplicateLinkException()
{
}
public DuplicateLinkException(string message)
: base(message)
{
}
public DuplicateLinkException(string message, Exception inner)
: base(message, inner)
{
}
}
}

15
RedditSharp/Extensions.cs Normal file
View File

@@ -0,0 +1,15 @@
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
namespace RedditSharp
{
public static class Extensions
{
public static T ValueOrDefault<T>(this IEnumerable<JToken> enumerable)
{
if (enumerable == null)
return default(T);
return enumerable.Value<T>();
}
}
}

View File

@@ -0,0 +1,8 @@
namespace RedditSharp
{
public class UserFlairTemplate // TODO: Consider using this class to set templates as well
{
public string Text { get; set; }
public string CssClass { get; set; }
}
}

8
RedditSharp/FlairType.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace RedditSharp
{
public enum FlairType
{
Link,
User
}
}

View File

@@ -0,0 +1,7 @@
namespace RedditSharp
{
public interface ICaptchaSolver
{
CaptchaResponse HandleCaptcha(Captcha captcha);
}
}

20
RedditSharp/IWebAgent.cs Normal file
View File

@@ -0,0 +1,20 @@
using System.IO;
using System.Net;
using Newtonsoft.Json.Linq;
namespace RedditSharp
{
public interface IWebAgent
{
CookieContainer Cookies { get; set; }
string AuthCookie { get; set; }
string AccessToken { get; set; }
HttpWebRequest CreateRequest(string url, string method);
HttpWebRequest CreateGet(string url);
HttpWebRequest CreatePost(string url);
string GetResponseString(Stream stream);
void WritePostBody(Stream stream, object data, params string[] additionalFields);
JToken CreateAndExecuteRequest(string url);
JToken ExecuteRequest(HttpWebRequest request);
}
}

17
RedditSharp/LinkData.cs Normal file
View File

@@ -0,0 +1,17 @@
namespace RedditSharp
{
internal class LinkData : SubmitData
{
[RedditAPIName("extension")]
internal string Extension { get; set; }
[RedditAPIName("url")]
internal string URL { get; set; }
internal LinkData()
{
Extension = "json";
Kind = "link";
}
}
}

248
RedditSharp/Listing.cs Normal file
View File

@@ -0,0 +1,248 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using RedditSharp.Things;
namespace RedditSharp
{
public class Listing<T> : IEnumerable<T> where T : Thing
{
/// <summary>
/// Gets the default number of listings returned per request
/// </summary>
internal const int DefaultListingPerRequest = 25;
private IWebAgent WebAgent { get; set; }
private Reddit Reddit { get; set; }
private string Url { get; set; }
/// <summary>
/// Creates a new Listing instance
/// </summary>
/// <param name="reddit"></param>
/// <param name="url"></param>
/// <param name="webAgent"></param>
internal Listing(Reddit reddit, string url, IWebAgent webAgent)
{
WebAgent = webAgent;
Reddit = reddit;
Url = url;
}
/// <summary>
/// Returns an enumerator that iterates through a collection, using the specified number of listings per
/// request and optionally the maximum number of listings
/// </summary>
/// <param name="limitPerRequest">The number of listings to be returned per request</param>
/// <param name="maximumLimit">The maximum number of listings to return</param>
/// <returns></returns>
public IEnumerator<T> GetEnumerator(int limitPerRequest, int maximumLimit = -1)
{
return new ListingEnumerator<T>(this, limitPerRequest, maximumLimit);
}
/// <summary>
/// Returns an enumerator that iterates through a collection, using the default number of listings per request
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
return GetEnumerator(DefaultListingPerRequest);
}
/// <summary>
/// Returns an enumerator that iterates through a collection
/// </summary>
/// <returns></returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Returns an IEnumerable instance which will return the specified maximum number of listings
/// </summary>
/// <param name="maximumLimit"></param>
/// <returns></returns>
public IEnumerable<T> GetListing(int maximumLimit)
{
return GetListing(maximumLimit, DefaultListingPerRequest);
}
/// <summary>
/// Returns an IEnumerable instance which will return the specified maximum number of listings
/// with the limited number per request
/// </summary>
/// <param name="maximumLimit"></param>
/// <param name="limitPerRequest"></param>
/// <returns></returns>
public IEnumerable<T> GetListing(int maximumLimit, int limitPerRequest)
{
// Get the enumerator with the specified maximum and per request limits
var enumerator = GetEnumerator(limitPerRequest, maximumLimit);
return GetEnumerator(enumerator);
}
/// <summary>
/// Converts an IEnumerator instance to an IEnumerable
/// </summary>
/// <param name="enumerator"></param>
/// <returns></returns>
private static IEnumerable<T> GetEnumerator(IEnumerator<T> enumerator)
{
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
#pragma warning disable 0693
private class ListingEnumerator<T> : IEnumerator<T> where T : Thing
{
private Listing<T> Listing { get; set; }
private int CurrentPageIndex { get; set; }
private string After { get; set; }
private string Before { get; set; }
private Thing[] CurrentPage { get; set; }
private int Count { get; set; }
private int LimitPerRequest { get; set; }
private int MaximumLimit { get; set; }
/// <summary>
/// Creates a new ListingEnumerator instance
/// </summary>
/// <param name="listing"></param>
/// <param name="limitPerRequest">The number of listings to be returned per request. -1 will exclude this parameter and use the Reddit default (25)</param>
/// <param name="maximumLimit">The maximum number of listings to return, -1 will not add a limit</param>
public ListingEnumerator(Listing<T> listing, int limitPerRequest, int maximumLimit)
{
Listing = listing;
CurrentPageIndex = -1;
CurrentPage = new Thing[0];
// Set the listings per page (if not specified, use the Reddit default of 25) and the maximum listings
LimitPerRequest = (limitPerRequest <= 0 ? DefaultListingPerRequest : limitPerRequest);
MaximumLimit = maximumLimit;
}
public T Current
{
get
{
return (T)CurrentPage[CurrentPageIndex];
}
}
private void FetchNextPage()
{
var url = Listing.Url;
if (After != null)
{
url += (url.Contains("?") ? "&" : "?") + "after=" + After;
}
if (LimitPerRequest != -1)
{
int limit = LimitPerRequest;
if (limit > MaximumLimit)
{
// If the limit is more than the maximum number of listings, adjust
limit = MaximumLimit;
}
else if (Count + limit > MaximumLimit)
{
// If a smaller subset of listings are needed, adjust the limit
limit = MaximumLimit - Count;
}
if (limit > 0)
{
// Add the limit, the maximum number of items to be returned per page
url += (url.Contains("?") ? "&" : "?") + "limit=" + limit;
}
}
if (Count > 0)
{
// Add the count, the number of items already seen in this listing
// The Reddit API uses this to determine when to give values for before and after fields
url += (url.Contains("?") ? "&" : "?") + "count=" + Count;
}
var request = Listing.WebAgent.CreateGet(url);
var response = request.GetResponse();
var data = Listing.WebAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
if (json["kind"].ValueOrDefault<string>() != "Listing")
throw new FormatException("Reddit responded with an object that is not a listing.");
Parse(json);
}
private void Parse(JToken json)
{
var children = json["data"]["children"] as JArray;
CurrentPage = new Thing[children.Count];
for (int i = 0; i < CurrentPage.Length; i++)
CurrentPage[i] = Thing.Parse<T>(Listing.Reddit, children[i], Listing.WebAgent);
// Increase the total count of items returned
Count += CurrentPage.Length;
After = json["data"]["after"].Value<string>();
Before = json["data"]["before"].Value<string>();
}
public void Dispose()
{
// ...
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
CurrentPageIndex++;
if (CurrentPageIndex == CurrentPage.Length)
{
if (After == null && CurrentPageIndex != 0)
{
// No more pages to return
return false;
}
if (MaximumLimit != -1 && Count >= MaximumLimit)
{
// Maximum listing count returned
return false;
}
// Get the next page
FetchNextPage();
CurrentPageIndex = 0;
if (CurrentPage.Length == 0)
{
// No listings were returned in the page
return false;
}
}
return true;
}
public void Reset()
{
After = Before = null;
CurrentPageIndex = -1;
CurrentPage = new Thing[0];
}
}
#pragma warning restore
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp
{
[Flags]
public enum ModeratorPermission
{
None = 0x00,
Access = 0x01,
Config = 0x02,
Flair = 0x04,
Mail = 0x08,
Posts = 0x10,
Wiki = 0x20,
All = Access | Config | Flair | Mail | Posts | Wiki
}
internal class ModeratorPermissionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var data = String.Join(",", JArray.Load(reader).Select(t => t.ToString()));
ModeratorPermission result;
var valid = Enum.TryParse(data, true, out result);
if (!valid)
result = ModeratorPermission.None;
return result;
}
public override bool CanConvert(Type objectType)
{
// NOTE: Not sure if this is what is supposed to be returned
// This method wasn't called in my (Sharparam) tests so unsure what it does
return objectType == typeof (ModeratorPermission);
}
}
}

View File

@@ -0,0 +1,28 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace RedditSharp
{
public class ModeratorUser
{
public ModeratorUser(Reddit reddit, JToken json)
{
JsonConvert.PopulateObject(json.ToString(), this, reddit.JsonSerializerSettings);
}
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("mod_permissions")]
[JsonConverter(typeof (ModeratorPermissionConverter))]
public ModeratorPermission Permissions { get; set; }
public override string ToString()
{
return Name;
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.IO;
using System.Net;
namespace RedditSharp
{
public class MultipartFormBuilder
{
public HttpWebRequest Request { get; set; }
private string Boundary { get; set; }
private MemoryStream Buffer { get; set; }
private TextWriter TextBuffer { get; set; }
public MultipartFormBuilder(HttpWebRequest request)
{
// TODO: See about regenerating the boundary when needed
Request = request;
var random = new Random();
Boundary = "----------" + CreateRandomBoundary();
request.ContentType = "multipart/form-data; boundary=" + Boundary;
Buffer = new MemoryStream();
TextBuffer = new StreamWriter(Buffer);
}
private string CreateRandomBoundary()
{
// TODO: There's probably a better way to go about this
const string characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
string value = "";
var random = new Random();
for (int i = 0; i < 10; i++)
value += characters[random.Next(characters.Length)];
return value;
}
public void AddDynamic(object data)
{
var type = data.GetType();
var properties = type.GetProperties();
foreach (var property in properties)
{
var entry = Convert.ToString(property.GetValue(data, null));
AddString(property.Name, entry);
}
}
public void AddString(string name, string value)
{
TextBuffer.Write("{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n",
"--" + Boundary, name, value);
TextBuffer.Flush();
}
public void AddFile(string name, string filename, byte[] value, string contentType)
{
TextBuffer.Write("{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n",
"--" + Boundary, name, filename, contentType);
TextBuffer.Flush();
Buffer.Write(value, 0, value.Length);
Buffer.Flush();
TextBuffer.Write("\r\n");
TextBuffer.Flush();
}
public void Finish()
{
TextBuffer.Write("--" + Boundary + "--");
TextBuffer.Flush();
var stream = Request.GetRequestStream();
Buffer.Seek(0, SeekOrigin.Begin);
Buffer.WriteTo(stream);
stream.Close();
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RedditSharp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RedditSharp")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5b1e351d-35b7-443e-9341-52c069a14886")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,15 @@
using System;
namespace RedditSharp
{
public class RateLimitException : Exception
{
public TimeSpan TimeToReset { get; set; }
public RateLimitException(TimeSpan timeToReset)
{
TimeToReset = timeToReset;
}
}
}

371
RedditSharp/Reddit.cs Normal file
View File

@@ -0,0 +1,371 @@
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
{
/// <summary>
/// Class to communicate with Reddit.com
/// </summary>
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;
/// <summary>
/// Captcha solver instance to use when solving captchas.
/// </summary>
public ICaptchaSolver CaptchaSolver;
/// <summary>
/// The authenticated user for this instance.
/// </summary>
public AuthenticatedUser User { get; set; }
/// <summary>
/// Sets the Rate Limiting Mode of the underlying WebAgent
/// </summary>
public WebAgent.RateLimitMode RateLimit
{
get { return WebAgent.RateLimit; }
set { WebAgent.RateLimit = value; }
}
internal JsonSerializerSettings JsonSerializerSettings { get; set; }
/// <summary>
/// Gets the FrontPage using the current Reddit instance.
/// </summary>
public Subreddit FrontPage
{
get { return Subreddit.GetFrontPage(this); }
}
/// <summary>
/// Gets /r/All using the current Reddit instance.
/// </summary>
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();
}
/// <summary>
/// Logs in the current Reddit instance.
/// </summary>
/// <param name="username">The username of the user to log on to.</param>
/// <param name="password">The password of the user to log on to.</param>
/// <param name="useSsl">Whether to use SSL or not. (default: true)</param>
/// <returns></returns>
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);
}
/// <summary>
/// Initializes the User property if it's null,
/// otherwise replaces the existing user object
/// with a new one fetched from reddit servers.
/// </summary>
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<Subreddit>(string.Format(SubredditAboutUrl, name));
}
/// <summary>
/// Returns the subreddit.
/// </summary>
/// <param name="name">The name of the subreddit</param>
/// <returns>The Subreddit by given name</returns>
public async Task<Subreddit> GetSubredditAsync(string name)
{
if (name.StartsWith("r/"))
name = name.Substring(2);
if (name.StartsWith("/r/"))
name = name.Substring(3);
return await GetThingAsync<Subreddit>(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);
}
}
/// <summary>
/// Registers a new Reddit user
/// </summary>
/// <param name="userName">The username for the new account.</param>
/// <param name="passwd">The password for the new account.</param>
/// <param name="email">The optional recovery email for the new account.</param>
/// <returns>The newly created user account</returns>
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<T> SearchByUrl<T>(string url) where T : Thing
{
var urlSearchQuery = string.Format(UrlSearchPattern, url);
return Search<T>(urlSearchQuery);
}
public Listing<T> Search<T>(string query) where T : Thing
{
return new Listing<T>(this, string.Format(SearchUrl, query, "relevance", "all"), _webAgent);
}
#region Helpers
protected async internal Task<T> GetThingAsync<T>(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<T>(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
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace RedditSharp
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
internal class RedditAPINameAttribute : Attribute
{
internal string Name { get; private set; }
internal RedditAPINameAttribute(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Runtime.Serialization;
namespace RedditSharp
{
/// <summary>
/// Represents an error that occurred during accessing or manipulating data on Reddit.
/// </summary>
[Serializable]
public class RedditException : Exception
{
/// <summary>
/// Initializes a new instance of the RedditException class.
/// </summary>
public RedditException()
{
}
/// <summary>
/// Initializes a new instance of the RedditException class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public RedditException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the RedditException class with a specified error message and
/// a referenced inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="inner">The exception that is the cause of the current exception, or a null
/// reference (Nothing in Visual Basic) if no inner exception is specified.</param>
public RedditException(string message, Exception inner)
: base(message, inner)
{
}
/// <summary>
/// Initializes a new instance of the RedditException class with serialized data.
/// </summary>
/// <param name="info">The System.Runtime.Serialization.SerializationInfo that holds the
/// serialized object data about the exception being thrown.</param>
/// <param name="context">The System.Runtime.Serialization.StreamingContext that contains
/// contextual information about the source or destination.</param>
/// <exception cref="System.ArgumentNullException">The info parameter is null.</exception>
/// <exception cref="System.Runtime.Serialization.SerializationException">The class name
/// is null or System.Exception.HResult is zero (0).</exception>
protected RedditException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{A368CB75-75F0-4489-904D-B5CEBB0FE624}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RedditSharp</RootNamespace>
<AssemblyName>RedditSharp</AssemblyName>
<FileAlignment>512</FileAlignment>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>True</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>False</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>True</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup />
<ItemGroup>
<Reference Include="HtmlAgilityPack">
<HintPath>..\HtmlAgilityPack.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="SpamFilterSettings.cs" />
<Compile Include="Things\AuthenticatedUser.cs" />
<Compile Include="AuthProvider.cs" />
<Compile Include="Captcha.cs" />
<Compile Include="CaptchaFailedException.cs" />
<Compile Include="CaptchaResponse.cs" />
<Compile Include="ConsoleCaptchaSolver.cs" />
<Compile Include="DuplicateLinkException.cs" />
<Compile Include="ICaptchaSolver.cs" />
<Compile Include="Things\Comment.cs" />
<Compile Include="Things\CreatedThing.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="FlairTemplate.cs" />
<Compile Include="IWebAgent.cs" />
<Compile Include="LinkData.cs" />
<Compile Include="Listing.cs" />
<Compile Include="ModeratorPermission.cs" />
<Compile Include="ModeratorUser.cs" />
<Compile Include="MultipartFormBuilder.cs" />
<Compile Include="FlairType.cs" />
<Compile Include="Things\Post.cs" />
<Compile Include="Things\PrivateMessage.cs" />
<Compile Include="Reddit.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RedditAPINameAttribute.cs" />
<Compile Include="RedditException.cs" />
<Compile Include="Things\RedditUser.cs" />
<Compile Include="SubmitData.cs" />
<Compile Include="Things\Subreddit.cs" />
<Compile Include="SubredditImage.cs" />
<Compile Include="SubredditSettings.cs" />
<Compile Include="SubredditStyle.cs" />
<Compile Include="TextData.cs" />
<Compile Include="Things\Thing.cs" />
<Compile Include="UnixTimeStamp.cs" />
<Compile Include="UnixTimestampConverter.cs" />
<Compile Include="UrlParser.cs" />
<Compile Include="Things\VotableThing.cs" />
<Compile Include="RateLimitException.cs" />
<Compile Include="WebAgent.cs" />
<Compile Include="Wiki.cs" />
<Compile Include="WikiPage.cs" />
<Compile Include="Things\WikiPageRevision.cs" />
<Compile Include="WikiPageSettings.cs" />
<Compile Include="Domain.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,16 @@
namespace RedditSharp
{
public class SpamFilterSettings
{
public SpamFilterStrength LinkPostStrength { get; set; }
public SpamFilterStrength SelfPostStrength { get; set; }
public SpamFilterStrength CommentStrength { get; set; }
public SpamFilterSettings()
{
LinkPostStrength = SpamFilterStrength.High;
SelfPostStrength = SpamFilterStrength.High;
CommentStrength = SpamFilterStrength.High;
}
}
}

34
RedditSharp/SubmitData.cs Normal file
View File

@@ -0,0 +1,34 @@
namespace RedditSharp
{
internal abstract class SubmitData
{
[RedditAPIName("api_type")]
internal string APIType { get; set; }
[RedditAPIName("kind")]
internal string Kind { get; set; }
[RedditAPIName("sr")]
internal string Subreddit { get; set; }
[RedditAPIName("uh")]
internal string UserHash { get; set; }
[RedditAPIName("title")]
internal string Title { get; set; }
[RedditAPIName("iden")]
internal string Iden { get; set; }
[RedditAPIName("captcha")]
internal string Captcha { get; set; }
[RedditAPIName("resubmit")]
internal bool Resubmit { get; set; }
protected SubmitData()
{
APIType = "json";
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
namespace RedditSharp
{
public class SubredditImage
{
private const string DeleteImageUrl = "/api/delete_sr_img";
private Reddit Reddit { get; set; }
private IWebAgent WebAgent { get; set; }
public SubredditImage(Reddit reddit, SubredditStyle subredditStyle,
string cssLink, string name, IWebAgent webAgent)
{
Reddit = reddit;
WebAgent = webAgent;
SubredditStyle = subredditStyle;
Name = name;
CssLink = cssLink;
}
public SubredditImage(Reddit reddit, SubredditStyle subreddit,
string cssLink, string name, string url, IWebAgent webAgent)
: this(reddit, subreddit, cssLink, name, webAgent)
{
Url = new Uri(url);
// Handle legacy image urls
// http://thumbs.reddit.com/FULLNAME_NUMBER.png
int discarded;
if (int.TryParse(url, out discarded))
Url = new Uri(string.Format("http://thumbs.reddit.com/{0}_{1}.png", subreddit.Subreddit.FullName, url), UriKind.Absolute);
}
public string CssLink { get; set; }
public string Name { get; set; }
public Uri Url { get; set; }
public SubredditStyle SubredditStyle { get; set; }
public void Delete()
{
var request = WebAgent.CreatePost(DeleteImageUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
img_name = Name,
uh = Reddit.User.Modhash,
r = SubredditStyle.Subreddit.Name
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
SubredditStyle.Images.Remove(this);
}
}
}

View File

@@ -0,0 +1,264 @@
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RedditSharp.Things;
namespace RedditSharp
{
public class SubredditSettings
{
private const string SiteAdminUrl = "/api/site_admin";
private const string DeleteHeaderImageUrl = "/api/delete_sr_header";
private Reddit Reddit { get; set; }
private IWebAgent WebAgent { get; set; }
[JsonIgnore]
public Subreddit Subreddit { get; set; }
public SubredditSettings(Reddit reddit, Subreddit subreddit, IWebAgent webAgent)
{
Subreddit = subreddit;
Reddit = reddit;
WebAgent = webAgent;
// Default settings, for use when reduced information is given
AllowAsDefault = true;
Domain = null;
Sidebar = string.Empty;
Language = "en";
Title = Subreddit.DisplayName;
WikiEditKarma = 100;
WikiEditAge = 10;
UseDomainCss = false;
UseDomainSidebar = false;
HeaderHoverText = string.Empty;
NSFW = false;
PublicDescription = string.Empty;
WikiEditMode = WikiEditMode.None;
SubredditType = SubredditType.Public;
ShowThumbnails = true;
ContentOptions = ContentOptions.All;
SpamFilter = new SpamFilterSettings();
}
public SubredditSettings(Subreddit subreddit, Reddit reddit, JObject json, IWebAgent webAgent) : this(reddit, subreddit, webAgent)
{
var data = json["data"];
AllowAsDefault = data["default_set"].ValueOrDefault<bool>();
Domain = data["domain"].ValueOrDefault<string>();
Sidebar = HttpUtility.HtmlDecode(data["description"].ValueOrDefault<string>() ?? string.Empty);
Language = data["language"].ValueOrDefault<string>();
Title = data["title"].ValueOrDefault<string>();
WikiEditKarma = data["wiki_edit_karma"].ValueOrDefault<int>();
UseDomainCss = data["domain_css"].ValueOrDefault<bool>();
UseDomainSidebar = data["domain_sidebar"].ValueOrDefault<bool>();
HeaderHoverText = data["header_hover_text"].ValueOrDefault<string>();
NSFW = data["over_18"].ValueOrDefault<bool>();
PublicDescription = HttpUtility.HtmlDecode(data["public_description"].ValueOrDefault<string>() ?? string.Empty);
SpamFilter = new SpamFilterSettings
{
LinkPostStrength = GetSpamFilterStrength(data["spam_links"].ValueOrDefault<string>()),
SelfPostStrength = GetSpamFilterStrength(data["spam_selfposts"].ValueOrDefault<string>()),
CommentStrength = GetSpamFilterStrength(data["spam_comments"].ValueOrDefault<string>())
};
if (data["wikimode"] != null)
{
var wikiMode = data["wikimode"].ValueOrDefault<string>();
switch (wikiMode)
{
case "disabled":
WikiEditMode = WikiEditMode.None;
break;
case "modonly":
WikiEditMode = WikiEditMode.Moderators;
break;
case "anyone":
WikiEditMode = WikiEditMode.All;
break;
}
}
if (data["subreddit_type"] != null)
{
var type = data["subreddit_type"].ValueOrDefault<string>();
switch (type)
{
case "public":
SubredditType = SubredditType.Public;
break;
case "private":
SubredditType = SubredditType.Private;
break;
case "restricted":
SubredditType = SubredditType.Restricted;
break;
}
}
ShowThumbnails = data["show_media"].ValueOrDefault<bool>();
WikiEditAge = data["wiki_edit_age"].ValueOrDefault<int>();
if (data["content_options"] != null)
{
var contentOptions = data["content_options"].ValueOrDefault<string>();
switch (contentOptions)
{
case "any":
ContentOptions = ContentOptions.All;
break;
case "link":
ContentOptions = ContentOptions.LinkOnly;
break;
case "self":
ContentOptions = ContentOptions.SelfOnly;
break;
}
}
}
public bool AllowAsDefault { get; set; }
public string Domain { get; set; }
public string Sidebar { get; set; }
public string Language { get; set; }
public string Title { get; set; }
public int WikiEditKarma { get; set; }
public bool UseDomainCss { get; set; }
public bool UseDomainSidebar { get; set; }
public string HeaderHoverText { get; set; }
public bool NSFW { get; set; }
public string PublicDescription { get; set; }
public WikiEditMode WikiEditMode { get; set; }
public SubredditType SubredditType { get; set; }
public bool ShowThumbnails { get; set; }
public int WikiEditAge { get; set; }
public ContentOptions ContentOptions { get; set; }
public SpamFilterSettings SpamFilter { get; set; }
public void UpdateSettings()
{
var request = WebAgent.CreatePost(SiteAdminUrl);
var stream = request.GetRequestStream();
string link_type;
string type;
string wikimode;
switch (ContentOptions)
{
case ContentOptions.All:
link_type = "any";
break;
case ContentOptions.LinkOnly:
link_type = "link";
break;
default:
link_type = "self";
break;
}
switch (SubredditType)
{
case SubredditType.Public:
type = "public";
break;
case SubredditType.Private:
type = "private";
break;
default:
type = "restricted";
break;
}
switch (WikiEditMode)
{
case WikiEditMode.All:
wikimode = "anyone";
break;
case WikiEditMode.Moderators:
wikimode = "modonly";
break;
default:
wikimode = "disabled";
break;
}
WebAgent.WritePostBody(stream, new
{
allow_top = AllowAsDefault,
description = Sidebar,
domain = Domain,
lang = Language,
link_type,
over_18 = NSFW,
public_description = PublicDescription,
show_media = ShowThumbnails,
sr = Subreddit.FullName,
title = Title,
type,
uh = Reddit.User.Modhash,
wiki_edit_age = WikiEditAge,
wiki_edit_karma = WikiEditKarma,
wikimode,
spam_links = SpamFilter == null ? null : SpamFilter.LinkPostStrength.ToString().ToLowerInvariant(),
spam_selfposts = SpamFilter == null ? null : SpamFilter.SelfPostStrength.ToString().ToLowerInvariant(),
spam_comments = SpamFilter == null ? null : SpamFilter.CommentStrength.ToString().ToLowerInvariant(),
api_type = "json"
}, "header-title", HeaderHoverText);
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
/// <summary>
/// Resets the subreddit's header image to the Reddit logo
/// </summary>
public void ResetHeaderImage()
{
var request = WebAgent.CreatePost(DeleteHeaderImageUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
uh = Reddit.User.Modhash,
r = Subreddit.Name
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
}
private SpamFilterStrength GetSpamFilterStrength(string rawValue)
{
switch(rawValue)
{
case "low":
return SpamFilterStrength.Low;
case "high":
return SpamFilterStrength.High;
case "all":
return SpamFilterStrength.All;
default:
return SpamFilterStrength.High;
}
}
}
public enum WikiEditMode
{
None,
Moderators,
All
}
public enum SubredditType
{
Public,
Restricted,
Private
}
public enum ContentOptions
{
All,
LinkOnly,
SelfOnly
}
public enum SpamFilterStrength
{
Low,
High,
All
}
}

View File

@@ -0,0 +1,84 @@
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using System.Web;
using RedditSharp.Things;
namespace RedditSharp
{
public class SubredditStyle
{
private const string UploadImageUrl = "/api/upload_sr_img";
private const string UpdateCssUrl = "/api/subreddit_stylesheet";
private Reddit Reddit { get; set; }
private IWebAgent WebAgent { get; set; }
public SubredditStyle(Reddit reddit, Subreddit subreddit, IWebAgent webAgent)
{
Reddit = reddit;
Subreddit = subreddit;
WebAgent = webAgent;
}
public SubredditStyle(Reddit reddit, Subreddit subreddit, JToken json, IWebAgent webAgent) : this(reddit, subreddit, webAgent)
{
Images = new List<SubredditImage>();
var data = json["data"];
CSS = HttpUtility.HtmlDecode(data["stylesheet"].Value<string>());
foreach (var image in data["images"])
{
Images.Add(new SubredditImage(
Reddit, this, image["link"].Value<string>(),
image["name"].Value<string>(), image["url"].Value<string>(), WebAgent));
}
}
public string CSS { get; set; }
public List<SubredditImage> Images { get; set; }
public Subreddit Subreddit { get; set; }
public void UpdateCss()
{
var request = WebAgent.CreatePost(UpdateCssUrl);
var stream = request.GetRequestStream();
WebAgent.WritePostBody(stream, new
{
op = "save",
stylesheet_contents = CSS,
uh = Reddit.User.Modhash,
api_type = "json",
r = Subreddit.Name
});
stream.Close();
var response = request.GetResponse();
var data = WebAgent.GetResponseString(response.GetResponseStream());
var json = JToken.Parse(data);
}
public void UploadImage(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 = Subreddit.Name,
formid = "image-upload",
img_type = imageType == ImageType.PNG ? "png" : "jpg",
upload = ""
});
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 enum ImageType
{
PNG,
JPEG
}
}

13
RedditSharp/TextData.cs Normal file
View File

@@ -0,0 +1,13 @@
namespace RedditSharp
{
internal class TextData : SubmitData
{
[RedditAPIName("text")]
internal string Text { get; set; }
internal TextData()
{
Kind = "self";
}
}
}

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

View File

@@ -0,0 +1,15 @@
using System;
namespace RedditSharp
{
public static class UnixTimeStamp
{
public static DateTime UnixTimeStampToDateTime(this long unixTimeStamp)
{
// Unix timestamp is seconds past epoch
var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dtDateTime = dtDateTime.AddSeconds(unixTimeStamp);
return dtDateTime;
}
}
}

View File

@@ -0,0 +1,25 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace RedditSharp
{
public class UnixTimestampConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(double) || objectType == typeof(DateTime);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return token.Value<long>().UnixTimeStampToDateTime();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value);
}
}
}

35
RedditSharp/UrlParser.cs Normal file
View File

@@ -0,0 +1,35 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace RedditSharp
{
class UrlParser : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(String) || objectType == typeof(Uri);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.String)
{
if (Type.GetType("Mono.Runtime") == null)
return new Uri(token.Value<string>(), UriKind.RelativeOrAbsolute);
if (token.Value<string>().StartsWith("/"))
return new Uri(token.Value<string>(), UriKind.Relative);
return new Uri(token.Value<string>(), UriKind.RelativeOrAbsolute);
}
else
return token.Value<Uri>();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value);
}
}
}

255
RedditSharp/WebAgent.cs Normal file
View File

@@ -0,0 +1,255 @@
using System;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;
using Newtonsoft.Json.Linq;
namespace RedditSharp
{
public sealed class WebAgent : IWebAgent
{
/// <summary>
/// Additional values to append to the default RedditSharp user agent.
/// </summary>
public static string UserAgent { get; set; }
/// <summary>
/// It is strongly advised that you leave this enabled. Reddit bans excessive
/// requests with extreme predjudice.
/// </summary>
public static bool EnableRateLimit { get; set; }
public static string Protocol { get; set; }
/// <summary>
/// It is strongly advised that you leave this set to Burst or Pace. Reddit bans excessive
/// requests with extreme predjudice.
/// </summary>
public static RateLimitMode RateLimit { get; set; }
/// <summary>
/// The method by which the WebAgent will limit request rate
/// </summary>
public enum RateLimitMode
{
/// <summary>
/// Limits requests to one every two seconds
/// </summary>
Pace,
/// <summary>
/// Restricts requests to thirty per minute
/// </summary>
Burst,
/// <summary>
/// Does not restrict request rate. ***NOT RECOMMENDED***
/// </summary>
None
}
/// <summary>
/// The root domain RedditSharp uses to address Reddit.
/// www.reddit.com by default
/// </summary>
public static string RootDomain { get; set; }
/// <summary>
/// Used to make calls against Reddit's API using OAuth23
/// </summary>
public string AccessToken { get; set; }
public CookieContainer Cookies { get; set; }
public string AuthCookie { get; set; }
private static DateTime _lastRequest;
private static DateTime _burstStart;
private static int _requestsThisBurst;
public JToken CreateAndExecuteRequest(string url)
{
Uri uri;
if (!Uri.TryCreate(url, UriKind.Absolute, out uri))
{
if (!Uri.TryCreate(String.Format("{0}://{1}{2}", Protocol, RootDomain, url), UriKind.Absolute, out uri))
throw new Exception("Could not parse Uri");
}
var request = CreateGet(uri);
try { return ExecuteRequest(request); }
catch (Exception)
{
var tempProtocol = Protocol;
var tempRootDomain = RootDomain;
Protocol = "http";
RootDomain = "www.reddit.com";
var retval = CreateAndExecuteRequest(url);
Protocol = tempProtocol;
RootDomain = tempRootDomain;
return retval;
}
}
/// <summary>
/// Executes the web request and handles errors in the response
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public JToken ExecuteRequest(HttpWebRequest request)
{
EnforceRateLimit();
var response = request.GetResponse();
var result = GetResponseString(response.GetResponseStream());
var json = JToken.Parse(result);
try
{
if (json["json"] != null)
{
json = json["json"]; //get json object if there is a root node
}
if (json["error"] != null)
{
switch (json["error"].ToString())
{
case "404":
throw new Exception("File Not Found");
case "403":
throw new Exception("Restricted");
case "invalid_grant":
//Refresh authtoken
//AccessToken = authProvider.GetRefreshToken();
//ExecuteRequest(request);
break;
}
}
}
catch
{
}
return json;
}
private static void EnforceRateLimit()
{
switch (RateLimit)
{
case RateLimitMode.Pace:
while ((DateTime.UtcNow - _lastRequest).TotalSeconds < 2)// Rate limiting
Thread.Sleep(250);
_lastRequest = DateTime.UtcNow;
break;
case RateLimitMode.Burst:
if (_requestsThisBurst == 0)//this is first request
_burstStart = DateTime.UtcNow;
if (_requestsThisBurst >= 30) //limit has been reached
{
while ((DateTime.UtcNow - _burstStart).TotalSeconds < 60)
Thread.Sleep(250);
_burstStart = DateTime.UtcNow;
}
_requestsThisBurst++;
break;
}
}
public HttpWebRequest CreateRequest(string url, string method)
{
EnforceRateLimit();
bool prependDomain;
// IsWellFormedUriString returns true on Mono for some reason when using a string like "/api/me"
if (Type.GetType("Mono.Runtime") != null)
prependDomain = !url.StartsWith("http://") && !url.StartsWith("https://");
else
prependDomain = !Uri.IsWellFormedUriString(url, UriKind.Absolute);
HttpWebRequest request;
if (prependDomain)
request = (HttpWebRequest)WebRequest.Create(String.Format("{0}://{1}{2}", Protocol, RootDomain, url));
else
request = (HttpWebRequest)WebRequest.Create(url);
request.CookieContainer = Cookies;
if (Type.GetType("Mono.Runtime") != null)
{
var cookieHeader = Cookies.GetCookieHeader(new Uri("http://reddit.com"));
request.Headers.Set("Cookie", cookieHeader);
}
if (RootDomain == "oauth.reddit.com")// use OAuth
{
request.Headers.Set("Authorization", "bearer " + AccessToken);//Must be included in OAuth calls
}
request.Method = method;
request.UserAgent = UserAgent + " - with RedditSharp by /u/sircmpwn";
return request;
}
private HttpWebRequest CreateRequest(Uri uri, string method)
{
EnforceRateLimit();
var request = (HttpWebRequest)WebRequest.Create(uri);
request.CookieContainer = Cookies;
if (Type.GetType("Mono.Runtime") != null)
{
var cookieHeader = Cookies.GetCookieHeader(new Uri("http://reddit.com"));
request.Headers.Set("Cookie", cookieHeader);
}
if (RootDomain == "oauth.reddit.com")// use OAuth
{
request.Headers.Set("Authorization", "bearer " + AccessToken);//Must be included in OAuth calls
}
request.Method = method;
request.UserAgent = UserAgent + " - with RedditSharp by /u/sircmpwn";
return request;
}
public HttpWebRequest CreateGet(string url)
{
return CreateRequest(url, "GET");
}
private HttpWebRequest CreateGet(Uri url)
{
return CreateRequest(url, "GET");
}
public HttpWebRequest CreatePost(string url)
{
var request = CreateRequest(url, "POST");
request.ContentType = "application/x-www-form-urlencoded";
return request;
}
public string GetResponseString(Stream stream)
{
var data = new StreamReader(stream).ReadToEnd();
stream.Close();
return data;
}
public void WritePostBody(Stream stream, object data, params string[] additionalFields)
{
var type = data.GetType();
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
string value = "";
foreach (var property in properties)
{
var attr = property.GetCustomAttributes(typeof(RedditAPINameAttribute), false).FirstOrDefault() as RedditAPINameAttribute;
string name = attr == null ? property.Name : attr.Name;
var entry = Convert.ToString(property.GetValue(data, null));
value += name + "=" + HttpUtility.UrlEncode(entry).Replace(";", "%3B").Replace("&", "%26") + "&";
}
for (int i = 0; i < additionalFields.Length; i += 2)
{
var entry = Convert.ToString(additionalFields[i + 1]) ?? string.Empty;
value += additionalFields[i] + "=" + HttpUtility.UrlEncode(entry).Replace(";", "%3B").Replace("&", "%26") + "&";
}
value = value.Remove(value.Length - 1); // Remove trailing &
var raw = Encoding.UTF8.GetBytes(value);
stream.Write(raw, 0, raw.Length);
stream.Close();
}
}
}

168
RedditSharp/Wiki.cs Normal file
View File

@@ -0,0 +1,168 @@
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using RedditSharp.Things;
namespace RedditSharp
{
using System;
public class Wiki
{
private Reddit Reddit { get; set; }
private Subreddit Subreddit { get; set; }
private IWebAgent WebAgent { get; set; }
private const string GetWikiPageUrl = "/r/{0}/wiki/{1}.json?v={2}";
private const string GetWikiPagesUrl = "/r/{0}/wiki/pages.json";
private const string WikiPageEditUrl = "/r/{0}/api/wiki/edit";
private const string HideWikiPageUrl = "/r/{0}/api/wiki/hide";
private const string RevertWikiPageUrl = "/r/{0}/api/wiki/revert";
private const string WikiPageAllowEditorAddUrl = "/r/{0}/api/wiki/alloweditor/add";
private const string WikiPageAllowEditorDelUrl = "/r/{0}/api/wiki/alloweditor/del";
private const string WikiPageSettingsUrl = "/r/{0}/wiki/settings/{1}.json";
private const string WikiRevisionsUrl = "/r/{0}/wiki/revisions.json";
private const string WikiPageRevisionsUrl = "/r/{0}/wiki/revisions/{1}.json";
private const string WikiPageDiscussionsUrl = "/r/{0}/wiki/discussions/{1}.json";
public IEnumerable<string> PageNames
{
get
{
var request = WebAgent.CreateGet(string.Format(GetWikiPagesUrl, Subreddit.Name));
var response = request.GetResponse();
string json = WebAgent.GetResponseString(response.GetResponseStream());
return JObject.Parse(json)["data"].Values<string>();
}
}
public Listing<WikiPageRevision> Revisions
{
get
{
return new Listing<WikiPageRevision>(Reddit, string.Format(WikiRevisionsUrl, Subreddit.Name), WebAgent);
}
}
protected internal Wiki(Reddit reddit, Subreddit subreddit, IWebAgent webAgent)
{
Reddit = reddit;
Subreddit = subreddit;
WebAgent = webAgent;
}
public WikiPage GetPage(string page, string version = null)
{
var request = WebAgent.CreateGet(string.Format(GetWikiPageUrl, Subreddit.Name, page, version));
var response = request.GetResponse();
var json = JObject.Parse(WebAgent.GetResponseString(response.GetResponseStream()));
var result = new WikiPage(Reddit, json["data"], WebAgent);
return result;
}
#region Settings
public WikiPageSettings GetPageSettings(string name)
{
var request = WebAgent.CreateGet(string.Format(WikiPageSettingsUrl, Subreddit.Name, name));
var response = request.GetResponse();
var json = JObject.Parse(WebAgent.GetResponseString(response.GetResponseStream()));
var result = new WikiPageSettings(Reddit, json["data"], WebAgent);
return result;
}
public void SetPageSettings(string name, WikiPageSettings settings)
{
var request = WebAgent.CreatePost(string.Format(WikiPageSettingsUrl, Subreddit.Name, name));
WebAgent.WritePostBody(request.GetRequestStream(), new
{
page = name,
permlevel = settings.PermLevel,
listed = settings.Listed,
uh = Reddit.User.Modhash
});
var response = request.GetResponse();
}
#endregion
#region Revisions
public Listing<WikiPageRevision> GetPageRevisions(string page)
{
return new Listing<WikiPageRevision>(Reddit, string.Format(WikiPageRevisionsUrl, Subreddit.Name, page), WebAgent);
}
#endregion
#region Discussions
public Listing<Post> GetPageDiscussions(string page)
{
return new Listing<Post>(Reddit, string.Format(WikiPageDiscussionsUrl, Subreddit.Name, page), WebAgent);
}
#endregion
public void EditPage(string page, string content, string previous = null, string reason = null)
{
var request = WebAgent.CreatePost(string.Format(WikiPageEditUrl, Subreddit.Name));
WebAgent.WritePostBody(request.GetRequestStream(), new
{
content = content,
previous = previous,
reason = reason,
page = page,
uh = Reddit.User.Modhash
});
var response = request.GetResponse();
}
public void HidePage(string page, string revision)
{
var request = WebAgent.CreatePost(string.Format(HideWikiPageUrl, Subreddit.Name));
WebAgent.WritePostBody(request.GetRequestStream(), new
{
page = page,
revision = revision,
uh = Reddit.User.Modhash
});
var response = request.GetResponse();
}
public void RevertPage(string page, string revision)
{
var request = WebAgent.CreatePost(string.Format(RevertWikiPageUrl, Subreddit.Name));
WebAgent.WritePostBody(request.GetRequestStream(), new
{
page = page,
revision = revision,
uh = Reddit.User.Modhash
});
var response = request.GetResponse();
}
public void SetPageEditor(string page, string username, bool allow)
{
var request = WebAgent.CreatePost(string.Format(allow ? WikiPageAllowEditorAddUrl : WikiPageAllowEditorDelUrl, Subreddit.Name));
WebAgent.WritePostBody(request.GetRequestStream(), new
{
page = page,
username = username,
uh = Reddit.User.Modhash
});
var response = request.GetResponse();
}
#region Obsolete Getter Methods
[Obsolete("Use PageNames property instead")]
public IEnumerable<string> GetPageNames()
{
return PageNames;
}
[Obsolete("Use Revisions property instead")]
public Listing<WikiPageRevision> GetRevisions()
{
return Revisions;
}
#endregion Obsolete Getter Methods
}
}

32
RedditSharp/WikiPage.cs Normal file
View File

@@ -0,0 +1,32 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using RedditSharp.Things;
namespace RedditSharp
{
public class WikiPage
{
[JsonProperty("may_revise")]
public string MayRevise { get; set; }
[JsonProperty("revision_date")]
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime? RevisionDate { get; set; }
[JsonProperty("content_html")]
public string HtmlContent { get; set; }
[JsonProperty("content_md")]
public string MarkdownContent { get; set; }
[JsonIgnore]
public RedditUser RevisionBy { get; set; }
protected internal WikiPage(Reddit reddit, JToken json, IWebAgent webAgent)
{
RevisionBy = new RedditUser().Init(reddit, json["revision_by"], webAgent);
JsonConvert.PopulateObject(json.ToString(), this, reddit.JsonSerializerSettings);
}
}
}

View File

@@ -0,0 +1,31 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using RedditSharp.Things;
namespace RedditSharp
{
public class WikiPageSettings
{
[JsonProperty("listed")]
public bool Listed { get; set; }
[JsonProperty("permlevel")]
public int PermLevel { get; set; }
[JsonIgnore]
public IEnumerable<RedditUser> Editors { get; set; }
public WikiPageSettings()
{
}
protected internal WikiPageSettings(Reddit reddit, JToken json, IWebAgent webAgent)
{
var editors = json["editors"].ToArray();
Editors = editors.Select(x => new RedditUser().Init(reddit, x, webAgent));
JsonConvert.PopulateObject(json.ToString(), this, reddit.JsonSerializerSettings);
}
}
}