2024-12-16 04:50:54 +01:00
using System.Net ;
using System.Net.Http.Headers ;
using System.Text ;
2024-11-22 09:55:08 +01:00
namespace Uwaa.Pleroma ;
2024-12-16 04:50:54 +01:00
delegate void AddPair ( string key , string value ) ;
2024-11-22 09:55:08 +01:00
/// <summary>
/// A pleroma client.
/// </summary>
public class Pleroma
{
static readonly JsonSerializerOptions SerializerOptions = new ( )
{
PropertyNameCaseInsensitive = true ,
2024-12-16 04:50:54 +01:00
NumberHandling = JsonNumberHandling . AllowReadingFromString ,
2024-11-22 09:55:08 +01:00
} ;
2025-01-04 15:54:59 +01:00
internal static string CreateQuery ( Action < AddPair > generator )
2024-12-16 04:50:54 +01:00
{
StringBuilder sb = new StringBuilder ( ) ;
void addPair ( string key , string value )
{
if ( sb . Length = = 0 )
sb . Append ( '?' ) ;
else
sb . Append ( '&' ) ;
sb . Append ( WebUtility . UrlEncode ( key ) ) ;
sb . Append ( '=' ) ;
sb . Append ( WebUtility . UrlEncode ( value ) ) ;
}
generator ( addPair ) ;
return sb . ToString ( ) ;
}
2024-11-22 09:55:08 +01:00
2024-12-16 04:50:54 +01:00
public HttpClient HttpClient ;
2024-11-22 09:55:08 +01:00
2024-12-16 04:54:10 +01:00
public Pleroma ( string host , string? authorization , string? userAgent = "Uwaa.Pleroma/0.0" )
2024-11-30 14:14:00 +01:00
{
2024-12-16 04:50:54 +01:00
UriBuilder builder = new UriBuilder ( ) ;
builder . Scheme = "https" ;
builder . Host = host ;
HttpClient = new HttpClient ( ) ;
2024-12-16 04:54:10 +01:00
HttpClient . DefaultRequestHeaders . Add ( "User-agent" , userAgent ) ;
2024-12-16 04:50:54 +01:00
HttpClient . BaseAddress = builder . Uri ;
HttpClient . DefaultRequestHeaders . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/json" ) ) ;
if ( authorization ! = null )
HttpClient . DefaultRequestHeaders . Authorization = AuthenticationHeaderValue . Parse ( authorization ) ;
2024-11-30 14:14:00 +01:00
}
2025-01-04 15:54:59 +01:00
public Pleroma ( HttpClient client )
{
HttpClient = client ;
}
internal Task Retry ( Func < HttpRequestMessage > reqFactory )
2024-12-26 02:22:13 +01:00
{
return Retry < object? > ( reqFactory ) ;
}
2025-01-04 15:54:59 +01:00
internal async Task < T ? > Retry < T > ( Func < HttpRequestMessage > reqFactory )
2024-11-22 09:55:08 +01:00
{
2024-12-11 03:49:46 +01:00
while ( true )
{
2024-12-21 20:01:57 +01:00
HttpResponseMessage res = await HttpClient . SendAsync ( reqFactory ( ) ) ;
2024-11-22 09:55:08 +01:00
2024-12-16 04:50:54 +01:00
if ( res . StatusCode = = HttpStatusCode . NotFound )
return default ;
2024-11-22 09:55:08 +01:00
2024-12-16 04:50:54 +01:00
if ( res . Content = = null )
2024-12-26 02:22:13 +01:00
{
if ( typeof ( T ) = = typeof ( object ) )
return default ;
else
throw new HttpRequestException ( "Server responded with no content" ) ;
}
2024-11-22 09:55:08 +01:00
2024-12-16 04:50:54 +01:00
string text = await res . Content . ReadAsStringAsync ( ) ;
2024-11-22 09:55:08 +01:00
2024-12-18 17:04:26 +01:00
if ( res . StatusCode is > = ( HttpStatusCode ) 300 )
2024-11-22 09:55:08 +01:00
{
2024-12-11 03:49:46 +01:00
try
{
2024-12-26 00:08:58 +01:00
PleromaSimpleException ? err = JsonSerializer . Deserialize < PleromaSimpleException > ( text , SerializerOptions ) ;
2024-12-11 03:49:46 +01:00
if ( err ! = null & & err . Text ! = null )
{
if ( err . Text = = "Throttled" )
{
await Task . Delay ( 5000 ) ;
continue ; //Retry
}
else
{
throw err ;
}
}
}
catch ( JsonException )
{
2024-12-26 00:08:58 +01:00
//Not a simple error
2024-12-11 03:49:46 +01:00
}
2024-12-18 17:04:26 +01:00
try
{
PleromaAggregateException ? err = JsonSerializer . Deserialize < PleromaAggregateException > ( text , SerializerOptions ) ;
if ( err ! = null & & err . Text ! = null )
throw err ;
}
catch ( JsonException )
{
2024-12-26 00:08:58 +01:00
//Not an aggregate error
2024-12-18 17:04:26 +01:00
}
2024-12-26 00:08:58 +01:00
throw new HttpRequestException ( text ) ;
2024-11-22 09:55:08 +01:00
}
2024-12-16 04:50:54 +01:00
if ( res . StatusCode is > = ( HttpStatusCode ) 200 and < ( HttpStatusCode ) 300 )
return JsonSerializer . Deserialize < T > ( text , SerializerOptions ) ? ? throw new HttpRequestException ( "Couldn't deserialize response" ) ;
2024-12-11 03:49:46 +01:00
else
2024-12-16 04:50:54 +01:00
throw new HttpRequestException ( "Unknown error occurred" ) ;
2024-12-11 03:49:46 +01:00
}
2024-11-22 09:55:08 +01:00
}
2024-12-18 10:56:02 +01:00
/// <summary>
/// Fetches the account of the pleroma client.
/// </summary>
2024-12-21 20:01:57 +01:00
public Task < Account > GetAccount ( ) = > Retry < Account > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , "/api/v1/accounts/verify_credentials" ) ) ! ;
2024-12-18 10:56:02 +01:00
/// <summary>
/// Fetches an account by ID.
/// </summary>
/// <param name="id">Account ID to fetch</param>
2024-12-21 20:01:57 +01:00
public Task < Account ? > GetAccountByID ( string id ) = > Retry < Account > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , $"/api/v1/accounts/{WebUtility.UrlEncode(id)}" ) ) ;
2024-12-18 10:56:02 +01:00
/// <summary>
/// Fetches an account by nickname.
/// </summary>
/// <param name="acct">User nickname</param>
2024-12-21 20:01:57 +01:00
public Task < Account ? > GetAccountByName ( string acct ) = > Retry < Account > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , $"/api/v1/accounts/lookup?acct={WebUtility.UrlEncode(acct)}" ) ) ;
2024-12-18 10:56:02 +01:00
/// <summary>
/// Follows the given account.
/// </summary>
/// <param name="id">Account to follow</param>
/// <returns>The new relationship, if the provided account exists.</returns>
2024-12-21 20:01:57 +01:00
public Task < Relationship ? > Follow ( AccountID account ) = > Retry < Relationship > ( ( ) = > new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/accounts/{WebUtility.UrlEncode(account.ID)}/follow" ) ) ;
2024-12-18 10:56:02 +01:00
/// <summary>
/// Unfollows the given account.
/// </summary>
/// <param name="id">Account to unfollow</param>
/// <returns>The new state of the relationship, if the provided account exists.</returns>
2024-12-21 20:01:57 +01:00
public Task < Relationship ? > Unfollow ( AccountID account ) = > Retry < Relationship > ( ( ) = > new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/accounts/{WebUtility.UrlEncode(account.ID)}/unfollow" ) ) ;
/// <summary>
/// Gets accounts which the given account is following, if network is not hidden by the account owner.
/// </summary>
/// <param name="account">Account to get the follows of.</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>
public Task < Account [ ] > GetFollowing ( AccountID account ,
string? max_id = null ,
string? min_id = null ,
string? since_id = null ,
int offset = 0 ,
int limit = 20 )
{
2025-01-14 10:59:21 +01:00
string path = $"/api/v1/accounts/{account.ID}/following" + CreateQuery ( addPair = >
2024-12-21 20:01:57 +01:00
{
if ( max_id ! = null ) addPair ( "max_id" , max_id ) ;
if ( min_id ! = null ) addPair ( "min_id" , min_id ) ;
if ( since_id ! = null ) addPair ( "since_id" , since_id ) ;
if ( offset > 0 ) addPair ( "offset" , offset . ToString ( ) ) ;
if ( limit ! = 20 ) addPair ( "limit" , limit . ToString ( ) ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry < Account [ ] > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , path ) ) ! ;
2024-12-21 20:01:57 +01:00
}
2024-12-18 10:56:02 +01:00
/// <summary>
/// Sets a private note for the given account.
/// </summary>
2025-01-14 10:59:21 +01:00
/// <param name="account">Account ID</param>
2024-12-18 10:56:02 +01:00
/// <param name="comment">Account note body</param>
2025-01-14 10:59:21 +01:00
/// <returns>The new relationship with the given account.</returns>
2024-12-18 10:56:02 +01:00
public Task < Relationship ? > SetUserNote ( AccountID account , string comment )
{
MemoryStream mem = new MemoryStream ( ) ;
2025-01-14 10:59:21 +01:00
using ( Utf8JsonWriter writer = new Utf8JsonWriter ( mem , new JsonWriterOptions ( ) { SkipValidation = true } ) )
2024-12-18 10:56:02 +01:00
{
writer . WriteStartObject ( ) ;
writer . WritePropertyName ( "comment" ) ;
writer . WriteStringValue ( comment ) ;
writer . WriteEndObject ( ) ;
writer . Flush ( ) ;
}
2024-12-21 20:01:57 +01:00
return Retry < Relationship > ( ( ) = >
{
mem . Position = 0 ;
HttpRequestMessage req = new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/accounts/{WebUtility.UrlEncode(account.ID)}/note" ) ;
req . Content = new StreamContent ( mem ) ;
req . Content . Headers . ContentType = new MediaTypeHeaderValue ( "application/json" ) ;
return req ;
} ) ! ;
2024-12-18 10:56:02 +01:00
}
/// <summary>
/// Gets the account's relationship with the given account.
/// </summary>
2025-01-14 10:59:21 +01:00
/// <param name="account">Account ID</param>
2024-12-18 10:56:02 +01:00
/// <returns>A relationship object for the requested account, if the account exists.</returns>
public async Task < Relationship ? > GetRelationship ( AccountID account )
{
2024-12-21 20:01:57 +01:00
Relationship [ ] ? rels = await Retry < Relationship [ ] > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , $"/api/v1/accounts/relationships?id={WebUtility.UrlEncode(account.ID)}" ) ) ;
2024-12-18 10:56:02 +01:00
if ( rels = = null | | rels . Length = = 0 )
return null ;
else
return rels [ 0 ] ;
}
2024-11-22 09:55:08 +01:00
/// <summary>
/// Posts a status.
/// </summary>
2024-12-16 04:57:06 +01:00
/// <param name="content">Text content of the status.</param>
2025-01-08 14:42:14 +01:00
/// <param name="content_type">The MIME type of the <paramref name="content"/>.</param>
2024-12-16 04:57:06 +01:00
/// <param name="sensitive">Mark status and attached media as sensitive?</param>
/// <param name="expiresIn">The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour.</param>
/// <param name="replyTo">ID of the status being replied to, if status is a reply</param>
/// <param name="quoting"> ID of the status being quoted.</param>
/// <param name="language">ISO 639 language code for this status.</param>
2024-12-18 17:04:26 +01:00
/// <param name="attachments">Array of Attachment ids to be attached as media.</param>
2025-01-07 00:57:48 +01:00
/// <param name="to">A list of nicknames (like <c>lain@soykaf.club</c> or <c>lain</c> on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the <paramref name="content"/>, only the people in the <paramref name="to"/> list will be addressed. The normal rules for post visibility are not affected by this and will still apply</param>
2024-12-16 04:57:06 +01:00
/// <param name="visibility">Visibility of the posted status.</param>
2024-12-18 17:04:26 +01:00
/// <exception cref="HttpRequestException">Thrown if something goes wrong while publishing the status.</exception>
/// <returns>The newly published status if posting was successful.</returns>
public Task < Status > PostStatus ( string? content = null ,
2025-01-08 14:42:14 +01:00
string content_type = "text/plain" ,
2024-12-16 04:50:54 +01:00
bool sensitive = false ,
int? expiresIn = null ,
2024-12-27 18:46:14 +01:00
StatusID ? replyTo = null ,
2025-01-14 10:59:21 +01:00
StatusID ? quoting = null ,
2024-12-16 04:50:54 +01:00
string? language = null ,
2024-12-18 17:04:26 +01:00
MediaID [ ] ? attachments = null ,
2025-01-07 00:57:48 +01:00
string [ ] ? to = null ,
2024-12-16 04:50:54 +01:00
StatusVisibility visibility = StatusVisibility . Public )
2024-11-22 09:55:08 +01:00
{
2024-12-18 17:04:26 +01:00
if ( content = = null & & ( attachments = = null | | attachments . Length = = 0 ) )
throw new ArgumentException ( "Cannot post nothing. Content and/or attachments must be provided." ) ;
2024-12-16 04:50:54 +01:00
MemoryStream mem = new MemoryStream ( ) ;
2025-01-14 10:59:21 +01:00
using ( Utf8JsonWriter writer = new Utf8JsonWriter ( mem , new JsonWriterOptions ( ) { SkipValidation = true } ) )
2024-12-16 04:50:54 +01:00
{
writer . WriteStartObject ( ) ;
2024-12-18 17:04:26 +01:00
if ( content ! = null )
{
writer . WritePropertyName ( "status" ) ;
writer . WriteStringValue ( content ) ;
2024-12-16 04:50:54 +01:00
2025-01-08 14:42:14 +01:00
writer . WritePropertyName ( "content_type" ) ;
writer . WriteStringValue ( content_type ) ;
2024-12-18 17:04:26 +01:00
}
2024-12-16 04:50:54 +01:00
if ( sensitive )
{
writer . WritePropertyName ( "sensitive" ) ;
writer . WriteBooleanValue ( true ) ;
}
if ( expiresIn . HasValue )
{
writer . WritePropertyName ( "expires_in" ) ;
writer . WriteNumberValue ( expiresIn . Value ) ;
}
2024-12-27 18:46:14 +01:00
if ( replyTo . HasValue )
2024-12-16 04:50:54 +01:00
{
writer . WritePropertyName ( "in_reply_to_id" ) ;
2024-12-27 18:46:14 +01:00
writer . WriteStringValue ( replyTo . Value . ID ) ;
2024-12-16 04:50:54 +01:00
}
2025-01-14 10:59:21 +01:00
if ( quoting . HasValue )
2024-12-16 04:50:54 +01:00
{
writer . WritePropertyName ( "quote_id" ) ;
2025-01-14 10:59:21 +01:00
writer . WriteStringValue ( quoting . Value . ID ) ;
2024-12-16 04:50:54 +01:00
}
if ( language ! = null )
{
writer . WritePropertyName ( "language" ) ;
writer . WriteStringValue ( language ) ;
}
2024-12-18 17:04:26 +01:00
if ( attachments ! = null )
{
writer . WritePropertyName ( "media_ids" ) ;
writer . WriteStartArray ( ) ;
foreach ( MediaID media in attachments )
writer . WriteStringValue ( media . ID ) ;
writer . WriteEndArray ( ) ;
}
2025-01-07 00:57:48 +01:00
if ( to ! = null )
{
writer . WritePropertyName ( "to" ) ;
writer . WriteStartArray ( ) ;
foreach ( string name in to )
writer . WriteStringValue ( name ) ;
writer . WriteEndArray ( ) ;
}
2024-12-16 04:50:54 +01:00
if ( visibility ! = StatusVisibility . Public )
{
writer . WritePropertyName ( "visibility" ) ;
writer . WriteStringValue ( visibility . ToString ( ) . ToLowerInvariant ( ) ) ;
}
writer . WriteEndObject ( ) ;
writer . Flush ( ) ;
}
2024-12-21 20:01:57 +01:00
return Retry < Status > ( ( ) = >
{
mem . Position = 0 ;
HttpRequestMessage req = new HttpRequestMessage ( HttpMethod . Post , "/api/v1/statuses" ) ;
req . Content = new StreamContent ( mem ) ;
req . Content . Headers . ContentType = new MediaTypeHeaderValue ( "application/json" ) ;
return req ;
} ) ! ;
2024-11-22 09:55:08 +01:00
}
2024-12-18 17:04:26 +01:00
static string CommonMIMEString ( CommonMIMEs mime ) = > mime switch
{
CommonMIMEs . PNG = > "image/png" ,
CommonMIMEs . JPEG = > "image/jpeg" ,
CommonMIMEs . GIF = > "image/gif" ,
CommonMIMEs . MP4 = > "video/mp4" ,
CommonMIMEs . WEBM = > "video/webm" ,
CommonMIMEs . MOV = > "video/quicktime" ,
CommonMIMEs . WAV = > "audio/vnd.wav" ,
CommonMIMEs . MP3 = > "audio/mpeg" ,
CommonMIMEs . OGG = > "audio/ogg" ,
CommonMIMEs . Text = > "text/plain" ,
_ = > throw new ArgumentException ( "Unknown common MIME" , nameof ( mime ) ) ,
} ;
/// <summary>
/// Uploads a file to the server. The resulting attachment can then be included in a status as an attachment.
/// </summary>
/// <param name="mime">The MIME type of the file.</param>
/// <param name="file">An array containing the file's contents.</param>
/// <returns>A handle of the uploaded file, including its ID and URLs.</returns>
2025-01-14 12:58:51 +01:00
public Task < Attachment > Upload ( CommonMIMEs mime , byte [ ] file , string name = "file" ) = > Upload ( CommonMIMEString ( mime ) , file , name ) ;
2024-12-18 17:04:26 +01:00
/// <summary>
/// Uploads a file to the server. The resulting attachment can then be included in a status as an attachment.
/// </summary>
/// <param name="mime">The MIME type of the file.</param>
/// <param name="file">An array containing the file's contents.</param>
/// <returns>A handle of the uploaded file, including its ID and URLs.</returns>
2025-01-14 12:58:51 +01:00
public Task < Attachment > Upload ( string mime , byte [ ] file , string name = "file" ) = > Upload ( mime , new ByteArrayContent ( file ) , name ) ;
2024-12-18 17:04:26 +01:00
/// <summary>
/// Uploads a file to the server. The resulting attachment can then be included in a status as an attachment.
/// </summary>
/// <param name="mime">The MIME type of the file.</param>
/// <param name="stream">A stream of the file's contents.</param>
/// <returns>A handle of the uploaded file, including its ID and URLs.</returns>
2025-01-14 12:58:51 +01:00
public Task < Attachment > Upload ( CommonMIMEs mime , Stream stream , string name = "file" ) = > Upload ( CommonMIMEString ( mime ) , stream , name ) ;
2024-12-18 17:04:26 +01:00
/// <summary>
/// Uploads a file to the server. The resulting attachment can then be included in a status as an attachment.
/// </summary>
/// <param name="mime">The MIME type of the file.</param>
/// <param name="stream">A stream of the file's contents.</param>
/// <returns>A handle of the uploaded file, including its ID and URLs.</returns>
2025-01-14 12:58:51 +01:00
public Task < Attachment > Upload ( string mime , Stream stream , string name = "file" ) = > Upload ( mime , new StreamContent ( stream ) , name ) ;
2024-12-18 17:04:26 +01:00
2025-01-14 12:58:51 +01:00
async Task < Attachment > Upload ( string mime , HttpContent content , string name )
2024-12-18 17:04:26 +01:00
{
2024-12-21 20:01:57 +01:00
return ( await Retry < Attachment > ( ( ) = >
2024-12-18 17:04:26 +01:00
{
2024-12-21 20:01:57 +01:00
content . Headers . ContentType = new MediaTypeHeaderValue ( mime ) ;
content . Headers . ContentDisposition = new ContentDispositionHeaderValue ( "form-data" )
{
Name = "\"file\"" ,
2025-01-14 12:58:51 +01:00
FileName = $"\" { name } \ "" ,
2024-12-21 20:01:57 +01:00
} ;
2024-12-18 17:04:26 +01:00
2024-12-21 20:01:57 +01:00
MultipartFormDataContent form = [ content ] ;
HttpRequestMessage req = new HttpRequestMessage ( HttpMethod . Post , "/api/v1/media" )
{
Content = form
} ;
return req ;
} ) ) ! ;
2024-12-18 17:04:26 +01:00
}
2025-01-07 00:58:20 +01:00
/// <summary>
/// Feature one of your own public statuses at the top of your profile
/// </summary>
/// <param name="status">The status to pin.</param>
/// <returns>The pinned status, if it exists.</returns>
public Task < Status ? > Pin ( StatusID status )
{
return Retry < Status ? > ( ( ) = > new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/statuses/{WebUtility.UrlEncode(status.ID)}/pin" ) ) ;
}
/// <summary>
/// Unfeature a status from the top of your profile
/// </summary>
/// <param name="status">The status to unpin.</param>
/// <returns>The unpinned status, if it exists.</returns>
public Task < Status ? > Unpin ( StatusID status )
{
return Retry < Status ? > ( ( ) = > new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/statuses/{WebUtility.UrlEncode(status.ID)}/unpin" ) ) ;
}
2024-11-22 09:55:08 +01:00
/// <summary>
2024-12-18 10:56:02 +01:00
/// Reposts/boosts/shares a status.
2024-11-22 09:55:08 +01:00
/// </summary>
2024-12-18 10:56:02 +01:00
/// <param name="status">The status to reblog.</param>
/// <returns>The reblogged status, if it exists.</returns>
public Task < Status ? > Reblog ( StatusID status , StatusVisibility ? visibility = null )
2024-11-22 09:55:08 +01:00
{
2024-12-18 10:56:02 +01:00
MemoryStream mem = new MemoryStream ( ) ;
2024-12-16 04:50:54 +01:00
2025-01-14 10:59:21 +01:00
using ( Utf8JsonWriter writer = new Utf8JsonWriter ( mem , new JsonWriterOptions ( ) { SkipValidation = true } ) )
2024-12-18 10:56:02 +01:00
{
writer . WriteStartObject ( ) ;
if ( visibility . HasValue )
{
writer . WritePropertyName ( "visibility" ) ;
writer . WriteStringValue ( visibility . Value . ToString ( ) . ToLowerInvariant ( ) ) ;
}
writer . WriteEndObject ( ) ;
writer . Flush ( ) ;
}
2024-12-21 20:01:57 +01:00
return Retry < Status > ( ( ) = >
{
mem . Position = 0 ;
HttpRequestMessage req = new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/statuses/{WebUtility.UrlEncode(status.ID)}/reblog" ) ;
req . Content = new StreamContent ( mem ) ;
req . Content . Headers . ContentType = new MediaTypeHeaderValue ( "application/json" ) ;
return req ;
} ) ;
2024-11-22 09:55:08 +01:00
}
/// <summary>
2024-12-18 10:56:02 +01:00
/// Removes a reblog.
2024-11-22 09:55:08 +01:00
/// </summary>
2024-12-18 10:56:02 +01:00
/// <param name="status">The reblogged status to undo.</param>
/// <returns>The status, if it exists.</returns>
2024-12-21 20:01:57 +01:00
public Task < Status ? > Unreblog ( StatusID status ) = > Retry < Status > ( ( ) = > new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/statuses/{WebUtility.UrlEncode(status.ID)}/unreblog" ) ) ! ;
2024-12-18 10:56:02 +01:00
2024-11-22 09:55:08 +01:00
/// <summary>
2024-12-18 10:56:02 +01:00
/// Likes/favourites a status, adding it to the account's favourite list.
2024-11-22 09:55:08 +01:00
/// </summary>
2024-12-18 10:56:02 +01:00
/// <param name="status">The status to favourite.</param>
/// <returns>The favourited status, if it exists.</returns>
2024-12-21 20:01:57 +01:00
public Task < Status ? > Favourite ( StatusID status ) = > Retry < Status > ( ( ) = > new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/statuses/{WebUtility.UrlEncode(status.ID)}/favourite" ) ) ! ;
2024-11-22 09:55:08 +01:00
/// <summary>
2024-12-18 10:56:02 +01:00
/// Unfavourites a favorited status, removing it from the account's favourite list.
2024-11-22 09:55:08 +01:00
/// </summary>
2024-12-18 10:56:02 +01:00
/// <param name="status">The status to unfavourite.</param>
/// <returns>The status, if it exists.</returns>
2024-12-21 20:01:57 +01:00
public Task < Status ? > Unfavourite ( StatusID status ) = > Retry < Status > ( ( ) = > new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/statuses/{WebUtility.UrlEncode(status.ID)}/unfavourite" ) ) ! ;
2024-11-22 09:55:08 +01:00
/// <summary>
2024-12-18 10:56:02 +01:00
/// Fetches the latest statuses from a standard timeline.
2024-11-22 09:55:08 +01:00
/// </summary>
2024-12-21 20:01:57 +01:00
/// <param name="timeline">The timeline to fetch.</param>
/// <param name="local">Show only local statuses?</param>
/// <param name="remote">Show only remote statuses?</param>
/// <param name="instance">Show only statuses from the given domain</param>
/// <param name="only_media">Show only statuses with media attached?</param>
/// <param name="with_muted">Include activities by muted users</param>
/// <param name="reply_visibility">Filter replies.</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>
2024-12-18 10:56:02 +01:00
public Task < Status [ ] > GetTimeline ( Timeline timeline ,
bool local = false ,
2024-12-21 20:01:57 +01:00
bool remote = false ,
2024-12-18 10:56:02 +01:00
string? instance = null ,
bool only_media = false ,
bool with_muted = false ,
2024-12-21 20:01:57 +01:00
ReplyVisibility reply_visibility = ReplyVisibility . All ,
2024-12-18 10:56:02 +01:00
string? max_id = null ,
string? min_id = null ,
string? since_id = null ,
int offset = 0 ,
int limit = 20 )
2024-11-22 09:55:08 +01:00
{
2024-12-18 10:56:02 +01:00
string timelineName ;
switch ( timeline )
{
case Timeline . Direct :
timelineName = "direct" ;
break ;
case Timeline . Home :
timelineName = "home" ;
break ;
case Timeline . Local :
timelineName = "public" ;
local = true ;
break ;
case Timeline . Bubble :
timelineName = "bubble" ;
break ;
2025-01-16 19:38:35 +01:00
case Timeline . All :
2024-12-18 10:56:02 +01:00
timelineName = "public" ;
break ;
default :
throw new ArgumentException ( "Invalid timeline" , nameof ( timeline ) ) ;
}
2025-01-14 10:59:21 +01:00
string path = $"/api/v1/timelines/{timelineName}" + CreateQuery ( addPair = >
2024-12-18 10:56:02 +01:00
{
if ( local ) addPair ( "local" , "true" ) ;
if ( instance ! = null ) addPair ( "instance" , instance ) ;
if ( only_media ) addPair ( "only_media" , "true" ) ;
if ( remote ) addPair ( "remote" , "true" ) ;
if ( with_muted ) addPair ( "with_muted" , "true" ) ;
2024-12-21 20:01:57 +01:00
if ( reply_visibility ! = ReplyVisibility . All ) addPair ( "reply_visibility" , reply_visibility . ToString ( ) . ToLowerInvariant ( ) ) ;
2024-12-18 10:56:02 +01:00
if ( max_id ! = null ) addPair ( "max_id" , max_id ) ;
if ( min_id ! = null ) addPair ( "min_id" , min_id ) ;
if ( since_id ! = null ) addPair ( "since_id" , since_id ) ;
if ( offset > 0 ) addPair ( "offset" , offset . ToString ( ) ) ;
if ( limit ! = 20 ) addPair ( "limit" , limit . ToString ( ) ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry < Status [ ] > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , path ) ) ! ;
2024-11-22 09:55:08 +01:00
}
/// <summary>
2024-12-18 10:56:02 +01:00
/// Fetches the latest statuses from a user's timeline.
2024-11-22 09:55:08 +01:00
/// </summary>
2024-12-21 20:01:57 +01:00
/// <param name="account">The user to fetch the timeline of.</param>
/// <param name="pinned">Include only pinned statuses</param>
/// <param name="tagged">With tag</param>
/// <param name="only_media">Show only statuses with media attached?</param>
/// <param name="with_muted">Include activities by muted users</param>
/// <param name="exclude_reblogs">Exclude reblogs</param>
/// <param name="exclude_replies">Exclude replies</param>
/// <param name="exclude_visibilities">Exclude the statuses with the given visibilities</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>
2024-12-18 10:56:02 +01:00
public Task < Status [ ] ? > GetTimeline ( AccountID account ,
bool pinned = false ,
string? tagged = null ,
bool only_media = false ,
bool with_muted = false ,
bool exclude_reblogs = false ,
bool exclude_replies = false ,
StatusVisibility [ ] ? exclude_visibilities = null ,
string? max_id = null ,
string? min_id = null ,
string? since_id = null ,
int offset = 0 ,
int limit = 20 )
2024-11-22 09:55:08 +01:00
{
2025-01-14 10:59:21 +01:00
string path = $"/api/v1/accounts/{WebUtility.UrlEncode(account.ID)}/statuses" + CreateQuery ( addPair = >
2024-12-18 10:56:02 +01:00
{
if ( pinned ) addPair ( "pinned" , "true" ) ;
if ( tagged ! = null ) addPair ( "tagged" , tagged ) ;
if ( only_media ) addPair ( "only_media" , "true" ) ;
if ( with_muted ) addPair ( "with_muted" , "true" ) ;
if ( exclude_reblogs ) addPair ( "exclude_reblogs" , "true" ) ;
if ( exclude_replies ) addPair ( "exclude_replies" , "true" ) ;
2024-12-26 00:09:07 +01:00
if ( exclude_visibilities ! = null )
foreach ( StatusVisibility visibility in exclude_visibilities )
addPair ( "exclude_visibilities[]" , visibility . ToString ( ) . ToLowerInvariant ( ) ) ;
2024-12-18 10:56:02 +01:00
if ( max_id ! = null ) addPair ( "max_id" , max_id ) ;
if ( min_id ! = null ) addPair ( "min_id" , min_id ) ;
if ( since_id ! = null ) addPair ( "since_id" , since_id ) ;
if ( offset > 0 ) addPair ( "offset" , offset . ToString ( ) ) ;
if ( limit ! = 20 ) addPair ( "limit" , limit . ToString ( ) ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry < Status [ ] > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , path ) ) ;
2024-12-11 03:49:46 +01:00
}
/// <summary>
2024-12-18 10:56:02 +01:00
/// Fetches the context of a status.
2024-12-11 03:49:46 +01:00
/// </summary>
2024-12-21 20:01:57 +01:00
public Task < Context ? > GetContext ( StatusID status ) = > Retry < Context > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , $"/api/v1/statuses/{WebUtility.UrlEncode(status.ID)}/context" ) ) ;
2024-11-30 14:14:20 +01:00
/// <summary>
2024-12-18 10:56:02 +01:00
/// Fetches a status by ID.
2024-11-30 14:14:20 +01:00
/// </summary>
2024-12-21 20:01:57 +01:00
public Task < Status ? > GetStatus ( string id ) = > Retry < Status > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , $"/api/v1/statuses/{WebUtility.UrlEncode(id)}" ) ) ;
2024-11-30 14:14:20 +01:00
/// <summary>
2024-12-18 10:56:02 +01:00
/// Deletes a status.
2024-11-30 14:14:20 +01:00
/// </summary>
2024-12-21 20:01:57 +01:00
public Task < Status ? > Delete ( StatusID status ) = > Retry < Status > ( ( ) = > new HttpRequestMessage ( HttpMethod . Delete , $"/api/v1/statuses/{WebUtility.UrlEncode(status.ID)}" ) ) ;
2024-12-11 03:49:46 +01:00
/// <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>
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 )
{
2025-01-14 10:59:21 +01:00
string path = "/api/v2/search" + CreateQuery ( addPair = >
2024-12-16 04:50:54 +01:00
{
addPair ( "q" , query ) ;
if ( account_id ! = null ) addPair ( "account_id" , account_id ) ;
if ( type ! = SearchType . All ) addPair ( "type" , type . ToString ( ) . ToLowerInvariant ( ) ) ;
if ( resolve ) addPair ( "resolve" , "true" ) ;
if ( following ) addPair ( "following" , "true" ) ;
if ( max_id ! = null ) addPair ( "max_id" , max_id ) ;
if ( min_id ! = null ) addPair ( "min_id" , min_id ) ;
if ( since_id ! = null ) addPair ( "since_id" , since_id ) ;
if ( offset > 0 ) addPair ( "offset" , offset . ToString ( ) ) ;
if ( limit ! = 20 ) addPair ( "limit" , limit . ToString ( ) ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry < SearchResults > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , path ) ) ! ;
2024-11-22 09:55:08 +01:00
}
2024-12-26 02:22:13 +01:00
2024-12-27 18:46:14 +01:00
/// <summary>
/// Downloads an attachment.
/// </summary>
public async Task < byte [ ] > Download ( Attachment attachment )
{
HttpResponseMessage res = await HttpClient . GetAsync ( attachment . URL ) ;
res . EnsureSuccessStatusCode ( ) ;
return await res . Content . ReadAsByteArrayAsync ( ) ;
}
/// <summary>
/// Downloads an attachment as a <see cref="Stream"/>.
/// </summary>
public async Task < Stream > DownloadStream ( Attachment attachment )
{
HttpResponseMessage res = await HttpClient . GetAsync ( attachment . URL ) ;
res . EnsureSuccessStatusCode ( ) ;
return await res . Content . ReadAsStreamAsync ( ) ;
}
2024-12-26 02:22:13 +01:00
/// <summary>
/// Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and <c>id</c> values.
/// </summary>
/// <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>
public Task < Notification [ ] > GetNotifications ( NotificationType [ ] ? exclude_types = null ,
string? account_id = null ,
StatusVisibility [ ] ? exclude_visibilities = null ,
NotificationType [ ] ? types = null ,
bool with_muted = false ,
string? max_id = null ,
string? min_id = null ,
string? since_id = null ,
int offset = 0 ,
int limit = 20 )
{
2025-01-14 10:59:21 +01:00
string path = "/api/v1/notifications" + CreateQuery ( addPair = >
2024-12-26 02:22:13 +01:00
{
if ( exclude_types ! = null )
foreach ( NotificationType type in exclude_types )
addPair ( "exclude_types[]" , NotificationTypes . ToString ( type ) ) ;
if ( account_id ! = null ) addPair ( "account_id" , account_id ) ;
if ( exclude_visibilities ! = null )
foreach ( StatusVisibility visibility in exclude_visibilities )
addPair ( "exclude_visibilities[]" , visibility . ToString ( ) . ToLowerInvariant ( ) ) ;
if ( types ! = null )
foreach ( NotificationType type in types )
addPair ( "types[]" , NotificationTypes . ToString ( type ) ) ;
if ( with_muted ) addPair ( "with_muted" , "true" ) ;
if ( max_id ! = null ) addPair ( "max_id" , max_id ) ;
if ( min_id ! = null ) addPair ( "min_id" , min_id ) ;
if ( since_id ! = null ) addPair ( "since_id" , since_id ) ;
if ( offset > 0 ) addPair ( "offset" , offset . ToString ( ) ) ;
if ( limit ! = 20 ) addPair ( "limit" , limit . ToString ( ) ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry < Notification [ ] > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , path ) ) ! ;
2024-12-26 02:22:13 +01:00
}
/// <summary>
/// View information about a notification with a given ID.
/// </summary>
/// <param name="id">Notification ID</param>
public Task < Notification ? > GetNotification ( NotificationID id )
{
2025-01-14 10:59:21 +01:00
return Retry < Notification ? > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , $"/api/v1/notifications/{WebUtility.UrlEncode(id.ID)}" ) ) ;
2024-12-26 02:22:13 +01:00
}
/// <summary>
/// Clear a single notification from the server.
/// </summary>
/// <param name="notification">Notification ID</param>
public Task Dismiss ( NotificationID notification )
{
2025-01-14 10:59:21 +01:00
return Retry ( ( ) = > new HttpRequestMessage ( HttpMethod . Post , $"/api/v1/notifications/{WebUtility.UrlEncode(notification.ID)}/dismiss" ) ) ;
2024-12-26 02:22:13 +01:00
}
/// <summary>
/// Clears multiple notifications from the server.
/// </summary>
/// <param name="notifications">Array of notification IDs to dismiss</param>
public Task Dismiss ( NotificationID [ ] notifications )
{
2024-12-27 18:46:14 +01:00
if ( notifications . Length = = 0 )
return Task . CompletedTask ;
2025-01-14 10:59:21 +01:00
string path = $"/api/v1/notifications/destroy_multiple" + CreateQuery ( addPair = >
2024-12-27 18:46:14 +01:00
{
foreach ( NotificationID id in notifications )
addPair ( "ids[]" , id . ID ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry ( ( ) = > new HttpRequestMessage ( HttpMethod . Delete , path ) ) ;
2024-12-27 18:46:14 +01:00
}
/// <summary>
/// Clears multiple notifications from the server.
/// </summary>
/// <param name="notifications">Array of notifications to dismiss</param>
public Task Dismiss ( Notification [ ] notifications )
{
if ( notifications . Length = = 0 )
return Task . CompletedTask ;
2025-01-14 10:59:21 +01:00
string path = $"/api/v1/notifications/destroy_multiple" + CreateQuery ( addPair = >
2024-12-26 02:22:13 +01:00
{
foreach ( NotificationID id in notifications )
addPair ( "ids[]" , id . ID ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry ( ( ) = > new HttpRequestMessage ( HttpMethod . Delete , path ) ) ;
}
/// <summary>
2025-01-14 12:58:40 +01:00
/// Adds a reaction to a status.
2025-01-14 10:59:21 +01:00
/// </summary>
/// <param name="status">The status to react to.</param>
2025-01-14 12:58:40 +01:00
/// <param name="emoji">The unicode emoji or custom emoji.</param>
2025-01-14 10:59:21 +01:00
/// <returns>The new state of the status, if it exists.</returns>
2025-01-14 12:58:40 +01:00
public Task < Status ? > React ( StatusID status , string emoji )
2025-01-14 10:59:21 +01:00
{
return Retry < Status ? > ( ( ) = > new HttpRequestMessage ( HttpMethod . Put , $"/api/v1/pleroma/statuses/{WebUtility.UrlEncode(status.ID)}/reactions/{WebUtility.UrlEncode(emoji)}" ) ) ;
2024-12-26 02:22:13 +01:00
}
2024-11-22 09:55:08 +01:00
}
2025-01-04 15:54:59 +01:00
/// <summary>
/// A pleroma client permitted to use the admin scope.
/// </summary>
public class PleromaAdmin : Pleroma
{
public PleromaAdmin ( string host , string? authorization , string? userAgent = "Uwaa.Pleroma/0.0" ) : base ( host , authorization , userAgent )
{
}
/// <summary>
/// Gets users known to the instance.
/// </summary>
/// <param name="type">Filter by local/external type</param>
/// <param name="status">Filter by account status</param>
/// <param name="query">Search users query</param>
/// <param name="name">Search by display name</param>
/// <param name="email">Search by email</param>
/// <param name="page">Page Number</param>
/// <param name="page_size">Number of users to return per page</param>
/// <param name="actor_types">Filter by actor type</param>
/// <param name="tags">Filter by tags</param>
public Task < UsersPage > GetUsers ( AccountType ? type = null ,
AccountStatus ? status = null ,
string? query = null ,
string? name = null ,
string? email = null ,
int? page = null ,
int? page_size = null ,
ActorType [ ] ? actor_types = null ,
string [ ] ? tags = null )
{
2025-01-14 10:59:21 +01:00
string path = "/api/v1/pleroma/admin/users" + CreateQuery ( addPair = >
2025-01-04 15:54:59 +01:00
{
string? typeStr = type switch
{
AccountType . Local = > "local" ,
AccountType . External = > "external" ,
null = > null ,
_ = > throw new ArgumentException ( "Invalid account type" , nameof ( type ) ) ,
} ;
string? statusStr = status switch
{
AccountStatus . Active = > "active" ,
AccountStatus . Deactivated = > "deactivated" ,
AccountStatus . PendingApproval = > "need_approval" ,
AccountStatus . Unconfirmed = > "unconfirmed" ,
null = > null ,
_ = > throw new ArgumentException ( "Invalid account status" , nameof ( type ) ) ,
} ;
if ( type ! = null & & status ! = null )
addPair ( "filters" , typeStr + "," + statusStr ) ;
else if ( typeStr ! = null )
addPair ( "filters" , typeStr ) ;
else if ( statusStr ! = null )
addPair ( "filters" , statusStr ) ;
if ( query ! = null ) addPair ( "query" , query ) ;
if ( name ! = null ) addPair ( "name" , name ) ;
if ( email ! = null ) addPair ( "email" , email ) ;
if ( page ! = null ) addPair ( "page" , page . Value . ToString ( ) ) ;
if ( page_size ! = null ) addPair ( "page_size" , page_size . Value . ToString ( ) ) ;
if ( actor_types ! = null )
foreach ( ActorType type in actor_types )
addPair ( "actor_types[]" , type . ToString ( ) ) ;
if ( tags ! = null )
foreach ( string tag in tags )
addPair ( "tags[]" , tag ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry < UsersPage > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , path ) ) ! ;
2025-01-04 15:54:59 +01:00
}
2025-01-14 10:59:21 +01:00
/// <summary>
/// Modifies the sensitive and/or visibility of a status.
/// </summary>
/// <param name="status">The status ID to modify.</param>
/// <param name="sensitive">If non-null, the status will be made sensitive (if true) or not sensitive (if false).</param>
/// <param name="visibility">If non-null, the status's visibility will be set to this.</param>
/// <returns>Returns the new state of the status. Returns null if the status doesn't exist.</returns>
public Task < Status ? > ChangeScope ( StatusID status , bool? sensitive = null , StatusVisibility ? visibility = null )
2025-01-04 15:54:59 +01:00
{
MemoryStream mem = new MemoryStream ( ) ;
2025-01-14 10:59:21 +01:00
using ( Utf8JsonWriter writer = new Utf8JsonWriter ( mem , new JsonWriterOptions ( ) { SkipValidation = true } ) )
2025-01-04 15:54:59 +01:00
{
writer . WriteStartObject ( ) ;
if ( sensitive . HasValue )
{
writer . WritePropertyName ( "sensitive" ) ;
writer . WriteBooleanValue ( sensitive . Value ) ;
}
if ( visibility . HasValue )
{
writer . WritePropertyName ( "visibility" ) ;
writer . WriteStringValue ( visibility . Value . ToString ( ) . ToLowerInvariant ( ) ) ;
}
writer . WriteEndObject ( ) ;
writer . Flush ( ) ;
}
return Retry < Status > ( ( ) = >
{
mem . Position = 0 ;
2025-01-14 10:59:21 +01:00
HttpRequestMessage req = new HttpRequestMessage ( HttpMethod . Put , $"/api/v1/pleroma/admin/statuses/{WebUtility.UrlEncode(status.ID)}" ) ;
2025-01-04 15:54:59 +01:00
req . Content = new StreamContent ( mem ) ;
req . Content . Headers . ContentType = new MediaTypeHeaderValue ( "application/json" ) ;
return req ;
2025-01-14 10:59:21 +01:00
} ) ;
2025-01-04 15:54:59 +01:00
}
2025-01-07 00:58:20 +01:00
public Task < ModerationLog > GetModerationLog ( int page = 1 )
{
2025-01-14 10:59:21 +01:00
string path = "/api/v1/pleroma/admin/moderation_log" + CreateQuery ( addPair = >
2025-01-07 00:58:20 +01:00
{
addPair ( "page" , page . ToString ( ) ) ;
2025-01-14 10:59:21 +01:00
} ) ;
return Retry < ModerationLog > ( ( ) = > new HttpRequestMessage ( HttpMethod . Get , path ) ) ! ;
}
/// <summary>
/// Deactivates or deletes one or more users by nickname.
/// </summary>
/// <remarks>If the user is pending approval, this will delete the user entirely. If the user is active, the user will be deactivated. Does nothing if the user is already deactivated.</remarks>
/// <param name="nicknames">The nicknames of the users to deactivate or delete.</param>
/// <returns>An array of nicknames which were successfully deactivated or deleted.</returns>
public Task < string [ ] > DeleteUsers ( params string [ ] nicknames )
{
MemoryStream mem = new MemoryStream ( ) ;
using ( Utf8JsonWriter writer = new Utf8JsonWriter ( mem , new JsonWriterOptions ( ) { SkipValidation = true } ) )
{
writer . WriteStartObject ( ) ;
writer . WriteStartArray ( "nicknames" ) ;
foreach ( string nickname in nicknames )
writer . WriteStringValue ( nickname ) ;
writer . WriteEndArray ( ) ;
writer . WriteEndObject ( ) ;
writer . Flush ( ) ;
}
return Retry < string [ ] > ( ( ) = >
{
mem . Position = 0 ;
HttpRequestMessage req = new HttpRequestMessage ( HttpMethod . Delete , "/api/v1/pleroma/admin/users" ) ;
req . Content = new StreamContent ( mem ) ;
req . Content . Headers . ContentType = new MediaTypeHeaderValue ( "application/json" ) ;
return req ;
} ) ! ;
2025-01-07 00:58:20 +01:00
}
2025-01-04 15:54:59 +01:00
}
[JsonConverter(typeof(EnumLowerCaseConverter<ActorType>))]
public enum ActorType
{
Application ,
Group ,
Organization ,
Person ,
Service
}
[JsonConverter(typeof(EnumLowerCaseConverter<AccountType>))]
public enum AccountType
{
Local ,
External ,
}
[JsonConverter(typeof(EnumLowerCaseConverter<AccountStatus>))]
public enum AccountStatus
{
Active ,
Deactivated ,
PendingApproval ,
Unconfirmed ,
}