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.LogParser
{
/// <summary>An API client for Pastebin.</summary>
internal class PastebinClient : IDisposable
{
/*********
** 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<GetPasteResponse> 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("<!DOCTYPE"))
return new GetPasteResponse { Error = $"Received a captcha challenge from Pastebin. Please visit https://pastebin.com/{id} in a new window to solve it." };
return new GetPasteResponse { Success = true, Content = content };
}
catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
{
return new GetPasteResponse { Error = "There's no log with that ID." };
}
catch (Exception ex)
{
return new GetPasteResponse { Error = ex.ToString() };
}
}
/// <summary>Save a paste to Pastebin.</summary>
/// <param name="content">The paste content.</param>
public async Task<SavePasteResponse> 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<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 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() };
}
}
/// <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");
}
}
}
|