summaryrefslogtreecommitdiff
path: root/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs')
-rw-r--r--src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs134
1 files changed, 134 insertions, 0 deletions
diff --git a/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs b/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs
new file mode 100644
index 00000000..ef83a91e
--- /dev/null
+++ b/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using Pathoschild.Http.Client;
+
+namespace StardewModdingAPI.Web.Framework.Clients.Pastebin
+{
+ /// <summary>An API client for Pastebin.</summary>
+ internal class PastebinClient : IPastebinClient
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The underlying HTTP client.</summary>
+ private readonly IClient Client;
+
+ /// <summary>The user key used to authenticate with the Pastebin API.</summary>
+ private readonly string UserKey;
+
+ /// <summary>The developer key used to authenticate with the Pastebin API.</summary>
+ private readonly string DevKey;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="baseUrl">The base URL for the Pastebin API.</param>
+ /// <param name="userAgent">The user agent for the API client.</param>
+ /// <param name="userKey">The user key used to authenticate with the Pastebin API.</param>
+ /// <param name="devKey">The developer key used to authenticate with the Pastebin API.</param>
+ public PastebinClient(string baseUrl, string userAgent, string userKey, string devKey)
+ {
+ this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent);
+ this.UserKey = userKey;
+ this.DevKey = devKey;
+ }
+
+ /// <summary>Fetch a saved paste.</summary>
+ /// <param name="id">The paste ID.</param>
+ public async Task<PasteInfo> GetAsync(string id)
+ {
+ try
+ {
+ // get from API
+ string content = await this.Client
+ .GetAsync($"raw/{id}")
+ .AsString();
+
+ // handle Pastebin errors
+ if (string.IsNullOrWhiteSpace(content))
+ return new PasteInfo { Error = "Received an empty response from Pastebin." };
+ if (content.StartsWith("<!DOCTYPE"))
+ return new PasteInfo { Error = $"Received a captcha challenge from Pastebin. Please visit https://pastebin.com/{id} in a new window to solve it." };
+ return new PasteInfo { Success = true, Content = content };
+ }
+ catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
+ {
+ return new PasteInfo { Error = "There's no log with that ID." };
+ }
+ catch (Exception ex)
+ {
+ return new PasteInfo { Error = ex.ToString() };
+ }
+ }
+
+ /// <summary>Save a paste to Pastebin.</summary>
+ /// <param name="content">The paste content.</param>
+ public async Task<SavePasteResult> PostAsync(string content)
+ {
+ try
+ {
+ // validate
+ if (string.IsNullOrWhiteSpace(content))
+ return new SavePasteResult { Error = "The log content can't be empty." };
+
+ // post to API
+ string response = await this.Client
+ .PostAsync("api/api_post.php")
+ .WithBodyContent(this.GetFormUrlEncodedContent(new Dictionary<string, string>
+ {
+ ["api_option"] = "paste",
+ ["api_user_key"] = this.UserKey,
+ ["api_dev_key"] = this.DevKey,
+ ["api_paste_private"] = "1", // unlisted
+ ["api_paste_name"] = $"SMAPI log {DateTime.UtcNow:s}",
+ ["api_paste_expire_date"] = "N", // never expire
+ ["api_paste_code"] = content
+ }))
+ .AsString();
+
+ // handle Pastebin errors
+ if (string.IsNullOrWhiteSpace(response))
+ return new SavePasteResult { Error = "Received an empty response from Pastebin." };
+ if (response.StartsWith("Bad API request"))
+ return new SavePasteResult { Error = response };
+ if (!response.Contains("/"))
+ return new SavePasteResult { Error = $"Received an unknown response: {response}" };
+
+ // return paste ID
+ string pastebinID = response.Split("/").Last();
+ return new SavePasteResult { Success = true, ID = pastebinID };
+ }
+ catch (Exception ex)
+ {
+ return new SavePasteResult { Success = false, Error = ex.ToString() };
+ }
+ }
+
+ /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+ public void Dispose()
+ {
+ this.Client.Dispose();
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Build an HTTP content body with form-url-encoded content.</summary>
+ /// <param name="data">The content to encode.</param>
+ /// <remarks>This bypasses an issue where <see cref="FormUrlEncodedContent"/> restricts the body length to the maximum size of a URL, which isn't applicable here.</remarks>
+ private HttpContent GetFormUrlEncodedContent(IDictionary<string, string> data)
+ {
+ string body = string.Join("&", from arg in data select $"{HttpUtility.UrlEncode(arg.Key)}={HttpUtility.UrlEncode(arg.Value)}");
+ return new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
+ }
+ }
+}