diff --git a/src/Reddit.NET/Inputs/Emoji/EmojiAddInput.cs b/src/Reddit.NET/Inputs/Emoji/EmojiAddInput.cs index 920a1a4f..9b4c7ba8 100644 --- a/src/Reddit.NET/Inputs/Emoji/EmojiAddInput.cs +++ b/src/Reddit.NET/Inputs/Emoji/EmojiAddInput.cs @@ -15,15 +15,27 @@ public class EmojiAddInput /// public string s3_key { get; set; } + public bool mod_flair_only { get; set; } + + public bool post_flair_allowed { get; set; } + + public bool user_flair_allowed { get; set; } + /// /// Data for emoji to be uploaded. /// /// Name of the emoji to be created. It can be alphanumeric without any special characters except '-' & '_' and cannot exceed 24 characters /// S3 key of the uploaded image which can be obtained from the S3 url. This is of the form subreddit/hash_value - public EmojiAddInput(string name, string s3Key) + /// If this emoji is exclusive to mods' flairs (or mod-assigned flairs). + /// If this emoji should be usable on post flairs. + /// If this emoji should be usable on user flairs. + public EmojiAddInput(string name, string s3Key, bool modFlairOnly, bool postFlairAllowed, bool userFlairAllowed) { this.name = name; s3_key = s3Key; + mod_flair_only = modFlairOnly; + post_flair_allowed = postFlairAllowed; + user_flair_allowed = userFlairAllowed; } } } diff --git a/src/Reddit.NET/Inputs/ImageUploadInput.cs b/src/Reddit.NET/Inputs/Emoji/ImageUploadInput.cs similarity index 77% rename from src/Reddit.NET/Inputs/ImageUploadInput.cs rename to src/Reddit.NET/Inputs/Emoji/ImageUploadInput.cs index efbbdd63..be68889a 100644 --- a/src/Reddit.NET/Inputs/ImageUploadInput.cs +++ b/src/Reddit.NET/Inputs/Emoji/ImageUploadInput.cs @@ -1,6 +1,6 @@ using System; -namespace Reddit.Inputs +namespace Reddit.Inputs.Emoji { [Serializable] public class ImageUploadInput @@ -18,11 +18,11 @@ public class ImageUploadInput /// /// Data for image to be uploaded. /// - /// name and extension of the image file e.g. image1.png + /// name and extension of the image file e.g. image1.png /// mime type of the image e.g. image/png - public ImageUploadInput(string filePath, string mimeType) + public ImageUploadInput(string fileName, string mimeType) { - filepath = filePath; + filepath = fileName; mimetype = mimeType; } } diff --git a/src/Reddit.NET/Models/Emoji.cs b/src/Reddit.NET/Models/Emoji.cs index 89a995dc..f0770bcb 100644 --- a/src/Reddit.NET/Models/Emoji.cs +++ b/src/Reddit.NET/Models/Emoji.cs @@ -1,15 +1,20 @@ -using Newtonsoft.Json; -using Reddit.Inputs; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using System.Xml.Serialization; +using Newtonsoft.Json; +using RestSharp; using Reddit.Inputs.Emoji; using Reddit.Things; -using RestSharp; -using System; -using System.Linq; +using Reddit.Exceptions; namespace Reddit.Models { public class Emoji : BaseModel { + // Used for deserializing S3PostResponse from S3 emoji post. --MingweiSamuel + private static readonly XmlSerializer S3PostResponseXmlSerializer = new XmlSerializer(typeof(S3PostResponse)); + internal override RestClient RestClient { get; set; } public Emoji(string appId, string appSecret, string refreshToken, string accessToken, ref RestClient restClient, string deviceId = null) @@ -24,11 +29,27 @@ public Emoji(string appId, string appSecret, string refreshToken, string accessT /// The subreddit with the emojis /// A valid EmojiAddInput instance /// (TODO - Untested) + // TODO returns {"json": {"errors": []}} public object Add(string subreddit, EmojiAddInput emojiAddInput) { return SendRequest("api/v1/" + subreddit + "/emoji.json", emojiAddInput, Method.POST); } + // TODO - Needs testing. + /// + /// Add an emoji to the DB by posting a message on emoji_upload_q. + /// A job processor that listens on a queue uses the s3_key provided in the request to locate the image in S3 Temp Bucket and moves it to the PERM bucket. + /// It also adds it to the DB using name as the column and sr_fullname as the key and sends the status on the websocket URL that is provided as part of this response. + /// + /// The subreddit with the emojis + /// A valid EmojiAddInput instance + /// (TODO - Untested) + // TODO returns {"json": {"errors": []}} + public async Task AddAsync(string subreddit, EmojiAddInput emojiAddInput) + { + return await SendRequestAsync("api/v1/" + subreddit + "/emoji.json", emojiAddInput, Method.POST); + } + // TODO - Needs testing. /// /// Delete a Subreddit emoji. Remove the emoji from Cassandra and purge the assets from S3 and the image resizing provider. @@ -40,6 +61,18 @@ public object Delete(string subreddit, string emojiName) { return JsonConvert.DeserializeObject(ExecuteRequest("api/v1/" + subreddit + "/emoji/" + emojiName, Method.DELETE)); } + + // TODO - Needs testing. + /// + /// Delete a Subreddit emoji. Remove the emoji from Cassandra and purge the assets from S3 and the image resizing provider. + /// + /// The subreddit with the emojis + /// The name of the emoji to be deleted + /// (TODO - Untested) + public async Task DeleteAsync(string subreddit, string emojiName) + { + return JsonConvert.DeserializeObject(await ExecuteRequestAsync("api/v1/" + subreddit + "/emoji/" + emojiName, Method.DELETE)); + } /// /// Acquire and return an upload lease to s3 temp bucket. @@ -54,39 +87,113 @@ public S3UploadLeaseContainer AcquireLease(string subreddit, ImageUploadInput im return SendRequest("api/v1/" + subreddit + "/emoji_asset_upload_s3.json", imageUploadInput, Method.POST); } - // TODO - Can't get this to work. Action URL keeps returning 403. --Kris - // See: https://www.reddit.com/r/redditdev/comments/9s1pio/getting_aws_error_when_trying_to_upload_emoji/ - // Update: Switching headers to parameters helped, but now it's complaining about the content-type on the image for some reason. --Kris /// - /// Upload an Emoji. + /// Acquire and return an upload lease to s3 temp bucket. + /// The return value of this function is a json object containing credentials for uploading assets to S3 bucket, S3 url for upload request and the key to use for uploading. + /// Using this lease the client will upload the emoji image to S3 temp bucket (included as part of the S3 URL). This lease is used by S3 to verify that the upload is authorized. + /// + /// The subreddit with the emojis + /// A valid ImageUploadInput instance + /// An S3 lease. + public async Task AcquireLeaseAsync(string subreddit, ImageUploadInput imageUploadInput) + { + return await SendRequestAsync("api/v1/" + subreddit + "/emoji_asset_upload_s3.json", imageUploadInput, Method.POST); + } + + #region UploadLeaseImage + // TODO: write tests for these. + + /// + /// Upload an Emoji to S3. /// + /// The data retrieved by AcquireLease. /// Raw image data. + /// File name (with extension) and mime type. + /// Decoded PostResponse including Key. + public S3PostResponse UploadLeaseImage(S3UploadLeaseContainer s3, byte[] imageData, ImageUploadInput imageUploadInput) + { + return HelperUploadLeaseImage(s3.S3UploadLease, new RestRequest(Method.POST) + .AddFile("file", imageData, imageUploadInput.filepath, imageUploadInput.mimetype)); + } + + /// + /// Upload an Emoji to S3. + /// /// The data retrieved by AcquireLease. - /// (TODO - Untested) - public void UploadLeaseImage(byte[] imageData, S3UploadLeaseContainer s3) + /// + /// File name (with extension) and mime type. + /// Optional length of imageData. Otherwise imageData.Length will be used. + /// Decoded PostResponse including Key. + public S3PostResponse UploadLeaseImage(S3UploadLeaseContainer s3, Stream imageData, ImageUploadInput imageUploadInput, long? contentLength = null) { - RestClient = new RestClient("https:" + s3.S3UploadLease.Action); - RestRequest restRequest = new RestRequest(Method.POST); + return HelperUploadLeaseImage(s3.S3UploadLease, new RestRequest(Method.POST) + .AddFile("file", imageData.CopyTo, imageUploadInput.filepath, contentLength ?? imageData.Length, imageUploadInput.mimetype)); + } - foreach (S3UploadLeaseField s3Field in s3.S3UploadLease.Fields) + // Helper for upload lease image. + private S3PostResponse HelperUploadLeaseImage(S3UploadLease s3Lease, IRestRequest restRequest) + { + RestClient s3RestClient = new RestClient("https:" + s3Lease.Action); + foreach (S3UploadLeaseField s3Field in s3Lease.Fields) { - if (!s3Field.Name.Equals("content-type", StringComparison.OrdinalIgnoreCase)) - { - restRequest.AddParameter(s3Field.Name, s3Field.Value); - } + restRequest.AddParameter(s3Field.Name, s3Field.Value); } - //restRequest.AddBody(JsonConvert.SerializeObject(s3.S3UploadLease.Fields)); + IRestResponse response = s3RestClient.Execute(restRequest); + return HandleUploadLeaseImageResponse(response); + } + + /// + /// Upload an Emoji to S3. + /// + /// The data retrieved by AcquireLease. + /// Raw image data. + /// File name (with extension) and mime type. + /// Task which may contain exceptions. + /// Decoded PostResponse including Key. + public async Task UploadLeaseImageAsync( + S3UploadLeaseContainer s3, byte[] imageData, ImageUploadInput imageUploadInput) + { + return await HelperUploadLeaseImageAsync(s3.S3UploadLease, new RestRequest(Method.POST) + .AddFile("file", imageData, imageUploadInput.filepath, imageUploadInput.mimetype)); + } + + /// + /// Upload an Emoji to S3. + /// + /// The data retrieved by AcquireLease. + /// + /// File name (with extension) and mime type. + /// Optional length of imageData. Otherwise imageData.Length will be used. + /// Decoded PostResponse including Key. + public async Task UploadLeaseImageAsync( + S3UploadLeaseContainer s3, Stream imageData, ImageUploadInput imageUploadInput, long? contentLength = null) + { + return await HelperUploadLeaseImageAsync(s3.S3UploadLease, new RestRequest(Method.POST) + .AddFile("file", imageData.CopyTo, imageUploadInput.filepath, contentLength ?? imageData.Length, imageUploadInput.mimetype)); + } + + // Helper for upload lease image (async). + private async Task HelperUploadLeaseImageAsync(S3UploadLease s3Lease, IRestRequest restRequest) + { + RestClient s3RestClient = new RestClient("https:" + s3Lease.Action); + foreach (S3UploadLeaseField s3Field in s3Lease.Fields) + { + restRequest.AddParameter(s3Field.Name, s3Field.Value); + } - restRequest.AddHeader("content-type", "multipart/form-data"); - //restRequest.AddHeader("key", s3.S3UploadLease.Fields.First(item => item.Name.Equals("key", StringComparison.OrdinalIgnoreCase)).Value); - //restRequest.AddParameter("key", s3.S3UploadLease.Fields.First(item => item.Name.Equals("key", StringComparison.OrdinalIgnoreCase)).Value); + IRestResponse response = await s3RestClient.ExecuteTaskAsync(restRequest); + return HandleUploadLeaseImageResponse(response); + } - restRequest.AddFileBytes("file", imageData, "birdie.jpg", s3.S3UploadLease.Fields.First( - item => item.Name.Equals("content-type", StringComparison.OrdinalIgnoreCase)).Value); + private S3PostResponse HandleUploadLeaseImageResponse(IRestResponse s3Response) + { + if (null != s3Response.ErrorException || HttpStatusCode.Created != s3Response.StatusCode) + throw new RedditException("Failed to upload image to s3.", s3Response.ErrorException); - ExecuteRequest(restRequest); + return (S3PostResponse) S3PostResponseXmlSerializer.Deserialize(new StringReader(s3Response.Content)); } + #endregion // TODO - Needs testing. /// @@ -115,5 +222,15 @@ public SnoomojiContainer All(string subreddit) { return JsonConvert.DeserializeObject(ExecuteRequest("api/v1/" + subreddit + "/emojis/all")); } + + /// + /// Get all emojis for a SR. The response includes reddit emojis as well as emojis for the SR specified in the request. + /// + /// The subreddit with the emojis + /// Emojis. + public async Task AllAsync(string subreddit) + { + return JsonConvert.DeserializeObject(await ExecuteRequestAsync("api/v1/" + subreddit + "/emojis/all")); + } } } diff --git a/src/Reddit.NET/Things/Emoji/S3PostResponse.cs b/src/Reddit.NET/Things/Emoji/S3PostResponse.cs new file mode 100644 index 00000000..bb0f7b7f --- /dev/null +++ b/src/Reddit.NET/Things/Emoji/S3PostResponse.cs @@ -0,0 +1,22 @@ +using System; +using System.Xml.Serialization; + +namespace Reddit.Things +{ + [Serializable] + [XmlRoot("PostResponse", IsNullable = false)] + public class S3PostResponse + { + [XmlElement("Location")] + public string Location { get; set; } + + [XmlElement("Bucket")] + public string Bucket { get; set; } + + [XmlElement("Key")] + public string Key { get; set; } + + [XmlElement("ETag")] + public string ETag { get; set; } + } +}