Files
Gardient 9583c1afb2 First actual commit
added sources to repository
2015-08-28 21:49:50 +03:00

256 lines
9.3 KiB
C#

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