summaryrefslogtreecommitdiff
path: root/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs
blob: ef83a91e7ae4f4cf37085d71244251902001ae2c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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");
        }
    }
}