Uwaa/Pleroma/Pleroma.cs

284 lines
10 KiB
C#
Raw Normal View History

2024-12-13 13:25:20 +01:00
using Uwaa.HTTP;
2024-11-22 09:55:08 +01:00
using Uwaa.Pleroma.API;
namespace Uwaa.Pleroma;
/// <summary>
/// A pleroma client.
/// </summary>
public class Pleroma
{
static readonly JsonSerializerOptions SerializerOptions = new()
{
PropertyNameCaseInsensitive = true,
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString,
};
static readonly MIMEType JsonMIMEType = new("application", "json");
/// <summary>
/// The hostname of the pleroma instance.
/// </summary>
public string Host;
/// <summary>
/// The full token, including the "Bearer" string.
/// </summary>
public string Authorization;
/// <summary>
/// The user agent string.
/// </summary>
public string UserAgent = "Uwaa.Pleroma/0.0";
public Pleroma(string host, string authorization)
{
Host = host;
Authorization = authorization;
}
2024-12-11 03:49:46 +01:00
async Task RequestJSON(HttpRequest req)
2024-11-30 14:14:00 +01:00
{
2024-12-11 03:49:46 +01:00
req.Fields.UserAgent = UserAgent;
req.Fields.Authorization = Authorization;
2024-11-30 14:14:00 +01:00
while (true)
{
2024-12-11 03:49:46 +01:00
HttpResponse res = await HttpClient.Request(Host, true, req);
if (res.StatusCode == 404)
2024-11-30 14:14:00 +01:00
return;
2024-12-11 03:49:46 +01:00
if (res.Content.HasValue)
2024-11-30 14:14:00 +01:00
{
2024-12-11 03:49:46 +01:00
if (res.Fields.ContentType.HasValue && !res.Fields.ContentType.Value.Match(JsonMIMEType))
throw new HttpException(res.Content.Value.AsText);
if (res.StatusCode is >= 400 and < 600)
2024-11-30 14:14:00 +01:00
{
2024-12-11 03:49:46 +01:00
try
{
string text = res.Content.Value.AsText;
PleromaException? err = JsonSerializer.Deserialize<PleromaException>(text, SerializerOptions);
if (err != null && err.Text != null)
{
if (err.Text == "Throttled")
{
await Task.Delay(5000);
continue; //Retry
}
else
{
throw err;
}
}
}
catch (JsonException)
{
//Not an error
}
2024-11-30 14:14:00 +01:00
}
}
2024-12-11 03:49:46 +01:00
if (res.StatusCode is not >= 200 or not < 300)
throw new HttpException("Unknown error occurred");
2024-11-30 14:14:00 +01:00
}
}
2024-11-22 09:55:08 +01:00
async Task<T?> RequestJSON<T>(HttpRequest req) where T : class
{
req.Fields.UserAgent = UserAgent;
req.Fields.Authorization = Authorization;
2024-12-11 03:49:46 +01:00
req.Fields.Accept = [JsonMIMEType];
while (true)
{
HttpResponse res = await HttpClient.Request(Host, true, req);
2024-11-22 09:55:08 +01:00
2024-12-11 03:49:46 +01:00
if (res.StatusCode == 404)
return null;
2024-11-22 09:55:08 +01:00
2024-12-11 03:49:46 +01:00
if (!res.Fields.ContentType.HasValue || !res.Fields.ContentType.Value.Match(JsonMIMEType))
throw new HttpException("Server did not respond with JSON" + (res.Content.HasValue ? ", got: " + res.Content.Value.AsText : null));
2024-11-22 09:55:08 +01:00
2024-12-11 03:49:46 +01:00
if (!res.Content.HasValue)
throw new HttpException("Server responded with no content");
2024-11-22 09:55:08 +01:00
2024-12-11 03:49:46 +01:00
string text = res.Content.Value.AsText;
2024-11-22 09:55:08 +01:00
2024-12-11 03:49:46 +01:00
if (res.StatusCode is >= 400 and < 600)
2024-11-22 09:55:08 +01:00
{
2024-12-11 03:49:46 +01:00
try
{
PleromaException? err = JsonSerializer.Deserialize<PleromaException>(text, SerializerOptions);
if (err != null && err.Text != null)
{
if (err.Text == "Throttled")
{
await Task.Delay(5000);
continue; //Retry
}
else
{
throw err;
}
}
}
catch (JsonException)
{
//Not an error
}
2024-11-22 09:55:08 +01:00
}
2024-12-11 03:49:46 +01:00
if (res.StatusCode is >= 200 and < 300)
return JsonSerializer.Deserialize<T>(text, SerializerOptions) ?? throw new HttpException("Couldn't deserialize response");
else
throw new HttpException("Unknown error occurred");
}
2024-11-22 09:55:08 +01:00
}
/// <summary>
/// Posts a status.
/// </summary>
/// <param name="status">The status data to send, including the content, visibility, etc.</param>
2024-12-11 03:49:46 +01:00
/// <exception cref="HttpException">Thrown if something goes wrong while uploading the status.</exception>
/// <returns>The status if posting was successful.</returns>
public async Task<Status> PostStatus(PublishStatus status)
2024-11-22 09:55:08 +01:00
{
HttpRequest req = new HttpRequest(HttpMethod.POST, "/api/v1/statuses");
req.Content = new HttpContent(JsonMIMEType, JsonSerializer.SerializeToUtf8Bytes(status, SerializerOptions));
req.Fields.Accept = [JsonMIMEType];
2024-12-11 03:49:46 +01:00
Status result = (await RequestJSON<Status>(req))!;
status.OnPublish?.Invoke();
return result;
2024-11-22 09:55:08 +01:00
}
/// <summary>
/// Fetches the latest statuses from the public timeline.
/// </summary>
public Task<Status[]> GetTimeline()
{
//TODO: Parameters and selecting different timelines (home, public, bubble)
HttpRequest req = new HttpRequest(HttpMethod.GET, "/api/v1/timelines/public");
req.Fields.Accept = [ JsonMIMEType ];
2024-12-11 03:49:46 +01:00
return RequestJSON<Status[]>(req)!;
2024-11-22 09:55:08 +01:00
}
/// <summary>
/// Fetches the latest statuses from a user's timeline.
/// </summary>
public Task<Status[]> GetTimeline(Account account)
=> GetTimeline(account.ID);
/// <summary>
/// Fetches the latest statuses from a user's timeline.
/// </summary>
public Task<Status[]> GetTimeline(string account_id)
{
HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/accounts/{account_id}/statuses");
req.Fields.Accept = [JsonMIMEType];
2024-12-11 03:49:46 +01:00
return RequestJSON<Status[]>(req)!;
2024-11-22 09:55:08 +01:00
}
/// <summary>
/// Fetches the account of the pleroma client.
/// </summary>
public Task<Account> GetAccount()
{
HttpRequest req = new HttpRequest(HttpMethod.GET, "/api/v1/accounts/verify_credentials");
req.Fields.Accept = [JsonMIMEType];
2024-12-11 03:49:46 +01:00
return RequestJSON<Account>(req)!;
2024-11-22 09:55:08 +01:00
}
/// <summary>
/// Fetches an account.
/// </summary>
public Task<Account?> GetAccount(string id)
{
HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/accounts/{id}");
req.Fields.Accept = [JsonMIMEType];
2024-12-11 03:49:46 +01:00
return RequestJSON<Account>(req);
2024-11-22 09:55:08 +01:00
}
/// <summary>
/// Fetches the context of a status.
/// </summary>
public Task<Context?> GetContext(Status status)
=> GetContext(status.ID);
/// <summary>
/// Fetches the context of a status.
/// </summary>
public Task<Context?> GetContext(string status_id)
{
HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/statuses/{status_id}/context");
req.Fields.Accept = [JsonMIMEType];
2024-12-11 03:49:46 +01:00
return RequestJSON<Context>(req);
}
/// <summary>
/// Fetches a status.
/// </summary>
public Task<Status?> GetStatus(string status_id)
{
HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/statuses/{status_id}");
req.Fields.Accept = [JsonMIMEType];
return RequestJSON<Status>(req);
2024-11-30 14:14:00 +01:00
}
2024-11-30 14:14:20 +01:00
/// <summary>
/// Deletes a status.
/// </summary>
public Task Delete(Status status)
=> Delete(status.ID);
/// <summary>
/// Deletes a status by ID.
/// </summary>
public Task Delete(string status_id)
{
HttpRequest req = new HttpRequest(HttpMethod.DELETE, $"/api/v1/statuses/{status_id}");
req.Fields.Accept = [JsonMIMEType];
2024-12-11 03:49:46 +01:00
return RequestJSON(req);
}
/// <summary>
/// Searches for an accounts, hashtags, and/or statuses.
/// </summary>
/// <param name="query">What to search for</param>
/// <param name="account_id">If provided, statuses returned will be authored only by this account</param>
/// <param name="type">Search type</param>
/// <param name="resolve">Attempt WebFinger lookup</param>
/// <param name="following">Only include accounts that the user is following</param>
/// <param name="max_id">Return items older than this ID</param>
/// <param name="min_id">Return the oldest items newer than this ID</param>
/// <param name="since_id">Return the newest items newer than this ID</param>
/// <param name="offset">Return items past this number of items</param>
/// <param name="limit">Maximum number of items to return. Will be ignored if it's more than 40</param>
/// <returns></returns>
public Task<SearchResults> Search(string query,
string? account_id = null,
SearchType type = SearchType.All,
bool resolve = false,
bool following = false,
string? max_id = null,
string? min_id = null,
string? since_id = null,
int offset = 0,
int limit = 20)
{
HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v2/search");
req.Fields.Accept = [JsonMIMEType];
req.Query["q"] = query;
if (account_id != null) req.Query["account_id"] = account_id;
if (type != SearchType.All) req.Query["type"] = type.ToString().ToLowerInvariant();
if (resolve) req.Query["resolve"] = "true";
if (following) req.Query["following"] = "true";
if (max_id != null) req.Query["max_id"] = max_id;
if (min_id != null) req.Query["min_id"] = min_id;
if (since_id != null) req.Query["since_id"] = since_id;
if (offset > 0) req.Query["offset"] = offset.ToString();
if (limit != 20) req.Query["limit"] = limit.ToString();
return RequestJSON<SearchResults>(req)!;
2024-11-22 09:55:08 +01:00
}
}