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.LogParser { /// An API client for Pastebin. internal class PastebinClient : IDisposable { /********* ** Properties *********/ /// The underlying HTTP client. private readonly IClient Client; /// The user key used to authenticate with the Pastebin API. private readonly string UserKey; /// The developer key used to authenticate with the Pastebin API. private readonly string DevKey; /********* ** Public methods *********/ /// Construct an instance. /// The base URL for the Pastebin API. /// The user agent for the API client. /// The user key used to authenticate with the Pastebin API. /// The developer key used to authenticate with the Pastebin API. public PastebinClient(string baseUrl, string userAgent, string userKey, string devKey) { this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent); this.UserKey = userKey; this.DevKey = devKey; } /// Fetch a saved paste. /// The paste ID. public async Task 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 GetPasteResponse { Error = "Received an empty response from Pastebin." }; if (content.StartsWith("Save a paste to Pastebin. /// The paste content. public async Task PostAsync(string content) { try { // validate if (string.IsNullOrWhiteSpace(content)) return new SavePasteResponse { 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 { ["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 SavePasteResponse { Error = "Received an empty response from Pastebin." }; if (response.StartsWith("Bad API request")) return new SavePasteResponse { Error = response }; if (!response.Contains("/")) return new SavePasteResponse { Error = $"Received an unknown response: {response}" }; // return paste ID string pastebinID = response.Split("/").Last(); return new SavePasteResponse { Success = true, ID = pastebinID }; } catch (Exception ex) { return new SavePasteResponse { Success = false, Error = ex.ToString() }; } } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { this.Client.Dispose(); } /********* ** Private methods *********/ /// Build an HTTP content body with form-url-encoded content. /// The content to encode. /// This bypasses an issue where restricts the body length to the maximum size of a URL, which isn't applicable here. private HttpContent GetFormUrlEncodedContent(IDictionary 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"); } } }