diff options
Diffstat (limited to 'src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs')
-rw-r--r-- | src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs | 134 |
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"); + } + } +} |