First actual commit
added sources to repository
This commit is contained in:
28
RedditRandomNumberGiveawayHelper.sln
Normal file
28
RedditRandomNumberGiveawayHelper.sln
Normal 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
|
||||||
6
RedditRandomNumberGiveawayHelper/App.config
Normal file
6
RedditRandomNumberGiveawayHelper/App.config
Normal 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>
|
||||||
147
RedditRandomNumberGiveawayHelper/MainForm.Designer.cs
generated
Normal file
147
RedditRandomNumberGiveawayHelper/MainForm.Designer.cs
generated
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
122
RedditRandomNumberGiveawayHelper/MainForm.cs
Normal file
122
RedditRandomNumberGiveawayHelper/MainForm.cs
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
RedditRandomNumberGiveawayHelper/MainForm.resx
Normal file
120
RedditRandomNumberGiveawayHelper/MainForm.resx
Normal 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>
|
||||||
22
RedditRandomNumberGiveawayHelper/Program.cs
Normal file
22
RedditRandomNumberGiveawayHelper/Program.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
RedditRandomNumberGiveawayHelper/Properties/AssemblyInfo.cs
Normal file
36
RedditRandomNumberGiveawayHelper/Properties/AssemblyInfo.cs
Normal 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")]
|
||||||
71
RedditRandomNumberGiveawayHelper/Properties/Resources.Designer.cs
generated
Normal file
71
RedditRandomNumberGiveawayHelper/Properties/Resources.Designer.cs
generated
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
RedditRandomNumberGiveawayHelper/Properties/Resources.resx
Normal file
117
RedditRandomNumberGiveawayHelper/Properties/Resources.resx
Normal 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>
|
||||||
30
RedditRandomNumberGiveawayHelper/Properties/Settings.Designer.cs
generated
Normal file
30
RedditRandomNumberGiveawayHelper/Properties/Settings.Designer.cs
generated
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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
174
RedditSharp/AuthProvider.cs
Normal 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
18
RedditSharp/Captcha.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
RedditSharp/CaptchaFailedException.cs
Normal file
24
RedditSharp/CaptchaFailedException.cs
Normal 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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
RedditSharp/CaptchaResponse.cs
Normal file
14
RedditSharp/CaptchaResponse.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
RedditSharp/ConsoleCaptchaSolver.cs
Normal file
17
RedditSharp/ConsoleCaptchaSolver.cs
Normal 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
61
RedditSharp/Domain.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
27
RedditSharp/DuplicateLinkException.cs
Normal file
27
RedditSharp/DuplicateLinkException.cs
Normal 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
15
RedditSharp/Extensions.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
RedditSharp/FlairTemplate.cs
Normal file
8
RedditSharp/FlairTemplate.cs
Normal 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
8
RedditSharp/FlairType.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace RedditSharp
|
||||||
|
{
|
||||||
|
public enum FlairType
|
||||||
|
{
|
||||||
|
Link,
|
||||||
|
User
|
||||||
|
}
|
||||||
|
}
|
||||||
7
RedditSharp/ICaptchaSolver.cs
Normal file
7
RedditSharp/ICaptchaSolver.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace RedditSharp
|
||||||
|
{
|
||||||
|
public interface ICaptchaSolver
|
||||||
|
{
|
||||||
|
CaptchaResponse HandleCaptcha(Captcha captcha);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
RedditSharp/IWebAgent.cs
Normal file
20
RedditSharp/IWebAgent.cs
Normal 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
17
RedditSharp/LinkData.cs
Normal 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
248
RedditSharp/Listing.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
49
RedditSharp/ModeratorPermission.cs
Normal file
49
RedditSharp/ModeratorPermission.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
RedditSharp/ModeratorUser.cs
Normal file
28
RedditSharp/ModeratorUser.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
RedditSharp/MultipartFormBuilder.cs
Normal file
76
RedditSharp/MultipartFormBuilder.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
RedditSharp/Properties/AssemblyInfo.cs
Normal file
36
RedditSharp/Properties/AssemblyInfo.cs
Normal 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")]
|
||||||
15
RedditSharp/RateLimitException.cs
Normal file
15
RedditSharp/RateLimitException.cs
Normal 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
371
RedditSharp/Reddit.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
20
RedditSharp/RedditAPINameAttribute.cs
Normal file
20
RedditSharp/RedditAPINameAttribute.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
RedditSharp/RedditException.cs
Normal file
59
RedditSharp/RedditException.cs
Normal 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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
108
RedditSharp/RedditSharp.csproj
Normal file
108
RedditSharp/RedditSharp.csproj
Normal 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>
|
||||||
16
RedditSharp/SpamFilterSettings.cs
Normal file
16
RedditSharp/SpamFilterSettings.cs
Normal 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
34
RedditSharp/SubmitData.cs
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
RedditSharp/SubredditImage.cs
Normal file
54
RedditSharp/SubredditImage.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
264
RedditSharp/SubredditSettings.cs
Normal file
264
RedditSharp/SubredditSettings.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
84
RedditSharp/SubredditStyle.cs
Normal file
84
RedditSharp/SubredditStyle.cs
Normal 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
13
RedditSharp/TextData.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace RedditSharp
|
||||||
|
{
|
||||||
|
internal class TextData : SubmitData
|
||||||
|
{
|
||||||
|
[RedditAPIName("text")]
|
||||||
|
internal string Text { get; set; }
|
||||||
|
|
||||||
|
internal TextData()
|
||||||
|
{
|
||||||
|
Kind = "self";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
157
RedditSharp/Things/AuthenticatedUser.cs
Normal file
157
RedditSharp/Things/AuthenticatedUser.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
313
RedditSharp/Things/Comment.cs
Normal file
313
RedditSharp/Things/Comment.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
40
RedditSharp/Things/CreatedThing.cs
Normal file
40
RedditSharp/Things/CreatedThing.cs
Normal 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
344
RedditSharp/Things/Post.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
163
RedditSharp/Things/PrivateMessage.cs
Normal file
163
RedditSharp/Things/PrivateMessage.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
201
RedditSharp/Things/RedditUser.cs
Normal file
201
RedditSharp/Things/RedditUser.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
677
RedditSharp/Things/Subreddit.cs
Normal file
677
RedditSharp/Things/Subreddit.cs
Normal 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
113
RedditSharp/Things/Thing.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
162
RedditSharp/Things/VotableThing.cs
Normal file
162
RedditSharp/Things/VotableThing.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
RedditSharp/Things/WikiPageRevision.cs
Normal file
48
RedditSharp/Things/WikiPageRevision.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
RedditSharp/UnixTimeStamp.cs
Normal file
15
RedditSharp/UnixTimeStamp.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
RedditSharp/UnixTimestampConverter.cs
Normal file
25
RedditSharp/UnixTimestampConverter.cs
Normal 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
35
RedditSharp/UrlParser.cs
Normal 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
255
RedditSharp/WebAgent.cs
Normal 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
168
RedditSharp/Wiki.cs
Normal 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
32
RedditSharp/WikiPage.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
RedditSharp/WikiPageSettings.cs
Normal file
31
RedditSharp/WikiPageSettings.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user