First actual commit

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

View File

@@ -0,0 +1,28 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedditRandomNumberGiveawayHelper", "RedditRandomNumberGiveawayHelper\RedditRandomNumberGiveawayHelper.csproj", "{8203F99F-1FF9-40D3-953E-4B8F28A91932}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedditSharp", "RedditSharp\RedditSharp.csproj", "{A368CB75-75F0-4489-904D-B5CEBB0FE624}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8203F99F-1FF9-40D3-953E-4B8F28A91932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8203F99F-1FF9-40D3-953E-4B8F28A91932}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8203F99F-1FF9-40D3-953E-4B8F28A91932}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8203F99F-1FF9-40D3-953E-4B8F28A91932}.Release|Any CPU.Build.0 = Release|Any CPU
{A368CB75-75F0-4489-904D-B5CEBB0FE624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A368CB75-75F0-4489-904D-B5CEBB0FE624}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A368CB75-75F0-4489-904D-B5CEBB0FE624}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A368CB75-75F0-4489-904D-B5CEBB0FE624}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
</startup>
</configuration>

View File

@@ -0,0 +1,147 @@
namespace RedditRandomNumberGiveawayHelper
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();
this.label2 = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button();
this.textBox2 = new System.Windows.Forms.TextBox();
this.label3 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 15);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(100, 13);
this.label1.TabIndex = 0;
this.label1.Text = "Giveaway Post URI";
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(118, 12);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(463, 20);
this.textBox1.TabIndex = 1;
//
// numericUpDown1
//
this.numericUpDown1.Location = new System.Drawing.Point(118, 39);
this.numericUpDown1.Maximum = new decimal(new int[] {
100000,
0,
0,
0});
this.numericUpDown1.Minimum = new decimal(new int[] {
1,
0,
0,
0});
this.numericUpDown1.Name = "numericUpDown1";
this.numericUpDown1.Size = new System.Drawing.Size(115, 20);
this.numericUpDown1.TabIndex = 2;
this.numericUpDown1.Value = new decimal(new int[] {
5000,
0,
0,
0});
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 41);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(57, 13);
this.label2.TabIndex = 3;
this.label2.Text = "Max Value";
//
// button1
//
this.button1.Location = new System.Drawing.Point(239, 38);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(342, 23);
this.button1.TabIndex = 4;
this.button1.Text = "Get Me A Random Winner";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// textBox2
//
this.textBox2.Location = new System.Drawing.Point(13, 95);
this.textBox2.Multiline = true;
this.textBox2.Name = "textBox2";
this.textBox2.ReadOnly = true;
this.textBox2.Size = new System.Drawing.Size(568, 140);
this.textBox2.TabIndex = 5;
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(268, 79);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(37, 13);
this.label3.TabIndex = 6;
this.label3.Text = "Result";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(593, 247);
this.Controls.Add(this.label3);
this.Controls.Add(this.textBox2);
this.Controls.Add(this.button1);
this.Controls.Add(this.label2);
this.Controls.Add(this.numericUpDown1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Form1";
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.NumericUpDown numericUpDown1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.Label label3;
}
}

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using RedditSharp;
using RedditSharp.Things;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
namespace RedditRandomNumberGiveawayHelper
{
public partial class MainForm : Form
{
//make sure to format with max number
private static string RANDOM_ORG_URI = "https://www.random.org/integers/?num=1&min=1&max={0}&col=1&base=10&format=plain&rnd=new";
public MainForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//textBox2.Text += string.Format("{0}", Environment.NewLine);
var reddit = new Reddit();
Post giveawayPost = null;
int? randomNumber = null;
Regex numberPost = new Regex(@"(\d{1,6})");
try
{
textBox2.Text += string.Format("Getting giveaway post...{0}", Environment.NewLine);
giveawayPost = reddit.GetPost(new Uri(textBox1.Text));
}
catch (Exception ex)
{
textBox2.Text += string.Format("{1}{0}{2}{0}",
Environment.NewLine,
"Failed getting giveaway post",
"You sure that's the right URI (alsomake sure to get the full uri from the address bar)");
return;
}
textBox2.Text += string.Format("{1}{2}{0}{3}{4}{0}",
Environment.NewLine,
"Post title: ",
giveawayPost.Title,
"Comment count: ",
giveawayPost.CommentCount);
try
{
textBox2.Text += string.Format("{1}{0}",
Environment.NewLine,
"Getting random number from random.org...");
WebRequest randomDotOrgRequest = WebRequest.Create(string.Format(RANDOM_ORG_URI, decimal.Round(numericUpDown1.Value, 0)));
using (WebResponse resp = randomDotOrgRequest.GetResponse())
{
using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
{
randomNumber = int.Parse(sr.ReadToEnd());
}
}
}
catch (Exception ex)
{
textBox2.Text += string.Format("{1}{0}{2}{0}",
Environment.NewLine,
"Failed getting giveaway post",
ex);
return;
}
textBox2.Text += string.Format("{1}{2}{0}{3}{4}{0}",
Environment.NewLine,
"Random Number: ",
randomNumber,
"Getting winning comment...",
"This might take a while...");
Dictionary<string, int> nums = giveawayPost.Comments.Where(c => c.Body != null && numberPost.IsMatch(c.Body)).ToDictionary(k => k.Shortlink, elementSelector: x => int.Parse(numberPost.Match(x.Body).Captures[0].Value));
string winningNumKey = null;
int? winningNumVal = null;
int? diff = null;
for (int i = 0; (winningNumKey == null && winningNumVal == null) && randomNumber + i < decimal.Round(numericUpDown1.Value, 0); i++)
{
foreach (var x in nums)
{
if (x.Value == randomNumber + i || x.Value == randomNumber - i)
{
winningNumKey = x.Key;
winningNumVal = x.Value;
diff = i;
break;
}
}
}
Comment winningComment = giveawayPost.Comments.FirstOrDefault(w => w.Shortlink == winningNumKey);
textBox2.Text += string.Format("{1}{2}{0}{3}{4}{0}{5}{6}{0}{7}{8}{0}",
Environment.NewLine,
"Winning comment (link): ",
winningNumKey,
"Winning comment (body): ",
winningComment.Body,
"Winning comment (commenter): ",
winningComment.Author,
"Diff: ",
diff
);
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RedditRandomNumberGiveawayHelper
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}

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("RedditRandomNumberGiveawayHelper")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RedditRandomNumberGiveawayHelper")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[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("30ddc09b-6ce1-4518-b330-67e36066116e")]
// 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,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.34209
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace RedditRandomNumberGiveawayHelper.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RedditRandomNumberGiveawayHelper.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.34209
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace RedditRandomNumberGiveawayHelper.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8203F99F-1FF9-40D3-953E-4B8F28A91932}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RedditRandomNumberGiveawayHelper</RootNamespace>
<AssemblyName>RedditRandomNumberGiveawayHelper</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RedditSharp\RedditSharp.csproj">
<Project>{a368cb75-75f0-4489-904d-b5cebb0fe624}</Project>
<Name>RedditSharp</Name>
</ProjectReference>
</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>

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