From cc9e32b7dcbdfb560b4433826d70adaada547536 Mon Sep 17 00:00:00 2001 From: teknomunk Date: Fri, 13 Jan 2023 20:14:29 -0600 Subject: [PATCH] Convert all api responses from text templates to JSON layouts, fix memory leaks, discard more unsupported Activities, server owner banner --- src/controller/api/notice.c | 7 +- src/controller/api/status.c | 40 +-- src/controller/api/status.h | 2 - src/controller/cli.c | 20 +- src/controller/inbox.c | 8 +- src/controller/outbox.c | 3 +- src/controller/owner.c | 12 + src/model/account.c | 5 + src/model/account.h | 1 + src/model/notification.c | 28 +- src/model/notification.h | 2 +- src/model/notification.json.template | 26 -- src/model/status.c | 2 + src/view/api/Account.c | 27 +- src/view/api/Notification.c | 88 ++++++ src/view/api/Notification.h | 8 + src/view/api/Status.c | 430 +++++++++++++++++++++++++++ src/view/api/Status.h | 10 + src/view/api/status.json.template | 102 ------- 19 files changed, 613 insertions(+), 208 deletions(-) delete mode 100644 src/model/notification.json.template create mode 100644 src/view/api/Notification.c create mode 100644 src/view/api/Notification.h create mode 100644 src/view/api/Status.c create mode 100644 src/view/api/Status.h delete mode 100644 src/view/api/status.json.template diff --git a/src/controller/api/notice.c b/src/controller/api/notice.c index 258350b..010aebc 100644 --- a/src/controller/api/notice.c +++ b/src/controller/api/notice.c @@ -8,7 +8,10 @@ #include "collections/array.h" // Model -#include "status.h" +#include "model/status.h" + +// View +#include "view/api/Notification.h" // Stdlib #include @@ -26,7 +29,7 @@ void show_notifications( struct http_request* req, struct notification** ns, int if( i > 0 ) { fprintf( f, "," ); } - notification_write_as_json(ns[i],f); + api_Notification_write( ns[i], f, 1 ); } fprintf( f, "]" ); } diff --git a/src/controller/api/status.c b/src/controller/api/status.c index 079f3ba..1ae50a1 100644 --- a/src/controller/api/status.c +++ b/src/controller/api/status.c @@ -12,6 +12,7 @@ #include "model/ap/outbox_envelope.h" #include "view/api/Account.h" +#include "view/api/Status.h" #include #include @@ -20,41 +21,12 @@ void write_json_escaped( FILE* f, const char* str ); -void api_status_write_as_json( struct status* s, FILE* f ) -{ - if( s->stub ) { - struct status* system_status = status_new_system_stub( s ); - s = system_status; - } - - struct account* account = account_from_id( s->account_id ); - - struct status* in_reply_to = NULL; - struct account* in_reply_to_account = NULL; - if( s->in_reply_to ) { - in_reply_to = status_from_id( s->in_reply_to ); - in_reply_to_account = account_from_id( in_reply_to->account_id ); - } - - struct status* repost = NULL; - if( s->repost_id ) { - repost = status_from_id( s->repost_id ); - } - - #include "src/view/api/status.json.inc" - -cleanup: - account_free(account); - account_free(in_reply_to_account); - status_free(in_reply_to); -} - void show_status( struct http_request* req, struct status* s ) { http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); - api_status_write_as_json(s,f); + api_Status_write( s, f, 0 ); } // Route: /api/v1/statuses/%d{s->id}/context @@ -72,7 +44,7 @@ void show_status_context( struct http_request* req, struct status* s ) fprintf( f, "{\"ancestors\":[" ); for( int i = 0; i < ancestors.count; ++i ) { struct status* s2 = ancestors.items[i]; - api_status_write_as_json(s2,f); + api_Status_write( s2, f, 1 ); status_free(s2); if( i != ancestors.count - 1 ) { @@ -84,7 +56,7 @@ void show_status_context( struct http_request* req, struct status* s ) fprintf( f, "],\"descendants\":[" ); for( int i = 0; i < replies.count; ++i ) { struct status* s2 = replies.items[i]; - api_status_write_as_json(s2,f); + api_Status_write( s2, f, 1 ); status_free(s2); if( i != replies.count - 1 ) { @@ -105,7 +77,7 @@ void show_statuses( struct http_request* req, struct status** ss, int count ) if( i > 0 ) { fprintf( f, "," ); } - api_status_write_as_json(ss[i],f); + api_Status_write( ss[i], f, 1 ); } fprintf( f, "]" ); } @@ -184,7 +156,7 @@ bool handle_post( struct http_request* req, struct account* a ) http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body(req); - api_status_write_as_json(s,f); + api_Status_write( s, f, 0 ); result = true; cleanup: free(params.status); diff --git a/src/controller/api/status.h b/src/controller/api/status.h index 03bd18c..3e9c2fe 100644 --- a/src/controller/api/status.h +++ b/src/controller/api/status.h @@ -14,5 +14,3 @@ void show_statuses( struct http_request* req, struct status** ss, int count ); bool handle_post( struct http_request* req, struct account* a ); bool handle_repost( struct http_request* req, struct status* s ); -void api_status_write_as_json( struct status* s, FILE* f ); - diff --git a/src/controller/cli.c b/src/controller/cli.c index 6d9af75..c89199c 100644 --- a/src/controller/cli.c +++ b/src/controller/cli.c @@ -1,10 +1,15 @@ #include "cli.h" +#include "format.h" + #include "model/account.h" #include "model/status.h" +#include "model/notification.h" #include "model/ap/activity.h" #include "controller/inbox.h" -#include "format.h" + +#include "view/api/Status.h" +#include "view/api/Notification.h" #include #include @@ -21,7 +26,7 @@ struct cli_request static int cli_route_command( struct cli_request* req, const char* cmd, int count, const char* usage_args ) { if( 0 != strcmp(req->argv[0], cmd ) ) { - printf( "No match for %s==%s\n", req->argv[0], cmd ); + //printf( "No match for %s==%s\n", req->argv[0], cmd ); return 0; } req->argv += 1; @@ -142,6 +147,14 @@ static bool handle_command_reindex( struct cli_request* req ) } static bool handle_command_test( struct cli_request* req ) { + for( int i = 0; i < 10; ++i ) { + struct notification* n = notification_from_id( 320 - i ); + api_Notification_write( n, stdout, 0 ); + notification_free(n); + printf( "\n\n----------------\n\n" ); + } + + /* int res = cli_route_command( req, "test", 0, "" ); if( res != 1 ) { return !!res; } @@ -170,13 +183,13 @@ static bool handle_command_test( struct cli_request* req ) ap_activity_free(note2); status_free(s); account_free(owner_account); + */ return true; } bool handle_command_account_sync( struct cli_request* req, int account_id ) { - printf( "sync cmd = %s\n", req->argv[0] ); int res = cli_route_command( req, "sync", 0, "" ); if( res != 1 ) { return !!res; } @@ -198,7 +211,6 @@ bool handle_command_update( struct cli_request* req ) bool handle_command_account( struct cli_request* req ) { - printf( "cmd = %s\n", req->argv[0] ); int res = cli_route_command( req, "account", 1, "account (id)" ); if( res != 1 ) { return !!res; } diff --git a/src/controller/inbox.c b/src/controller/inbox.c index 6a379da..f494797 100644 --- a/src/controller/inbox.c +++ b/src/controller/inbox.c @@ -91,8 +91,10 @@ static bool route_undo_Announce( struct ap_activity* act ) s = status_from_uri( act->object.ptr->id ); if( !s ) { return true; } // Status not local, discard + printf( "TODO: delete post\n" ); + status_free(s); - return false; + return true; } static bool route_undo_Like( struct ap_activity* act ) { @@ -239,6 +241,9 @@ static bool route_update( struct ap_activity* act ) if( !s ) { return true; } // Status not available locally, discard } else if( act->object.ptr->type == apat_person ) { return route_update_Person(act); + } else if( act->object.ptr->type == apat_question ) { + // TODO: update Poll + return true; } else { return false; } @@ -447,6 +452,7 @@ bool route_activity( struct ap_activity* act ) case apat_update: return route_update(act); case apat_move: return route_move(act); case apat_block: return route_block(act); + case apat_remove: return true; default: printf( "Unhandled activity type: %d\n", act->type ); } diff --git a/src/controller/outbox.c b/src/controller/outbox.c index e2c9c06..abb7e00 100644 --- a/src/controller/outbox.c +++ b/src/controller/outbox.c @@ -24,7 +24,7 @@ static bool process_envelope( struct outbox_envelope* env ) { bool result = false; char* postdata = NULL; - struct crypto_keys* keys = crypto_keys_new(); + struct crypto_keys* keys = NULL; FILE* f = NULL; struct ap_activity* act = NULL; @@ -39,6 +39,7 @@ static bool process_envelope( struct outbox_envelope* env ) struct account* to_account = account_from_id( env->account_id ); // Load crypto keys + keys = crypto_keys_new(); if( !crypto_keys_load_private( keys, "data/owner/private.pem" ) ) { printf( "Failed to load private key\n" ); return false; diff --git a/src/controller/owner.c b/src/controller/owner.c index 0b8b157..48e67be 100644 --- a/src/controller/owner.c +++ b/src/controller/owner.c @@ -88,6 +88,16 @@ static bool handle_avatar( struct http_request* req ) return false; } +static bool handle_banner( struct http_request* req ) +{ + FILE* f = fopen("data/owner/banner.png","r"); + if( f ) { + fclose(f); + return http_request_send_file( req, "data/owner/banner.png", "image/png" ); + } + + return false; +} bool route_owner( struct http_request* req ) { @@ -103,6 +113,8 @@ bool route_owner( struct http_request* req ) return handle_featured(req); } else if( http_request_route_term( req, "/avatar.blob" ) ) { return handle_avatar(req); + } else if( http_request_route_term( req, "/banner.blob" ) ) { + return handle_banner(req); } else if( http_request_route_term( req, "" ) ) { return show_owner_profile_page(req); } diff --git a/src/model/account.c b/src/model/account.c index b524bb4..c2e89e4 100644 --- a/src/model/account.c +++ b/src/model/account.c @@ -66,6 +66,7 @@ static struct json_object_field account_layout[] = { .required = false, .type = &json_field_string }, + JSON_FIELD_STRING( banner, false ), JSON_FIELD_ARRAY_OF_STRINGS( aliases, false ), JSON_FIELD_ENUM( account_type, account_types_enum, true ), JSON_FIELD_STRING( inbox, false ), @@ -142,6 +143,10 @@ struct account* account_from_id( int id ) return NULL; } + if( !a->banner ) { + a->banner = aformat( "https://%s/server/default-banner.blob", g_server_name ); + } + return a; } diff --git a/src/model/account.h b/src/model/account.h index f8e4624..4e9adeb 100644 --- a/src/model/account.h +++ b/src/model/account.h @@ -41,6 +41,7 @@ struct account char* url; char* static_url; } avatar; + char* banner; struct { char** items; diff --git a/src/model/notification.c b/src/model/notification.c index 0569b71..5ab4d0f 100644 --- a/src/model/notification.c +++ b/src/model/notification.c @@ -7,8 +7,7 @@ #include "model/account.h" #include "model/status.h" -#include "controller/api/status.h" - +#include "view/api/Status.h" #include "view/api/Account.h" #include @@ -32,6 +31,7 @@ static struct json_object_field notification_layout[] = { JSON_FIELD_INTEGER( account_id, true ), JSON_FIELD_INTEGER( status_id, false ), JSON_FIELD_INTEGER( ref_account_id, false ), + JSON_FIELD_DATETIME( created_at, false ), JSON_FIELD_STRING( react, false ), JSON_FIELD_ENUM( type, notification_type_enum, true ), JSON_FIELD_END, @@ -60,6 +60,7 @@ struct notification* notification_new() struct notification* note = malloc(sizeof(struct notification)); memset(note,0,sizeof(*note)); note->id = id; + note->created_at = time(NULL); fs_list_set( "data/notices/HEAD", id ); return note; @@ -80,26 +81,3 @@ void notification_free( struct notification* note ) free(note); } -void notification_write_as_json( struct notification* n, FILE* f ) -{ - // TODO: move to controller/view - struct account* a = account_from_id( n->account_id ); - struct account* owner = account_from_id( 0 ); - struct status* s = status_from_id( n->status_id ); - - switch( n->type ) { - case nt_unfollow: - s = status_new_system_unfollow( n->ref_account_id ); - break; - case nt_block: - s = status_new_system_block( n->ref_account_id ); - break; - } - - #include "src/model/notification.json.inc" - - status_free(s); - account_free(owner); - account_free(a); -} - diff --git a/src/model/notification.h b/src/model/notification.h index 2899a83..e66ee7a 100644 --- a/src/model/notification.h +++ b/src/model/notification.h @@ -37,5 +37,5 @@ void notification_save( struct notification* note ); void notification_free( struct notification* note ); // TODO: move to controler/view -void notification_write_as_json( struct notification* n, FILE* f ); +//void notification_write_as_json( struct notification* n, FILE* f ); diff --git a/src/model/notification.json.template b/src/model/notification.json.template deleted file mode 100644 index 8ca7dfb..0000000 --- a/src/model/notification.json.template +++ /dev/null @@ -1,26 +0,0 @@ -{ - "account": %( api_Account_write( a, f, 1 ); ), - "created_at": "2022-12-12T15:31:54.000Z", - "id": "%d{n->id}", - "pleroma": { - "is_muted": false, - "is_seen": false - }, - %( if(s) { ) - "status": %( api_status_write_as_json(s,f); ), - %( } ) - %( if( n->type == nt_react ) { ) - "emoji": %( json_write_string( f, n->react ); ), - %( } ) - "type": "%( - switch(n->type) { - case nt_favorite: fprintf(f,"favourite"); break; - case nt_follow: fprintf(f,"follow"); break; - case nt_like: fprintf(f,"favourite"); break; - case nt_unfollow: - case nt_block: - case nt_mention: fprintf(f,"mention"); break; - case nt_react: fprintf(f,"pleroma:emoji_reaction"); break; - default: fprintf(f,"unknown-%d",n->type); break; - }; )" -} diff --git a/src/model/status.c b/src/model/status.c index 4a17e39..2283e9b 100644 --- a/src/model/status.c +++ b/src/model/status.c @@ -93,6 +93,8 @@ static FILE* open_status_data_file( unsigned int id, const char* mode ) struct status* status_from_id( unsigned int id ) { + if( id == 0 ) { return NULL; } + struct status* s = NULL; FILE* f = open_status_data_file( id, "r" ); diff --git a/src/view/api/Account.c b/src/view/api/Account.c index e04bdb4..c2fe25d 100644 --- a/src/view/api/Account.c +++ b/src/view/api/Account.c @@ -29,8 +29,8 @@ static struct json_object_field pleroma_layout[] = { .offset = offsetof( OBJ_TYPE, account_url ), .type = &json_field_string, }, - // background_image: null, - // favicon: null, + JSON_FIELD_FIXED_NULL( background_image ), + JSON_FIELD_FIXED_NULL( favicon ), JSON_FIELD_FIXED_BOOL( deactivated, false ), JSON_FIELD_FIXED_BOOL( hide_favorites, true ), JSON_FIELD_FIXED_BOOL( hide_followers, false ), @@ -92,7 +92,7 @@ static bool fqn_callback( void* field_data, bool is_read, char** res ) return true; } } -static bool int_to_string_callback( void* field_data, bool is_read, char** res ) +bool int_to_string_callback( void* field_data, bool is_read, char** res ) { int* field = field_data; if( is_read ) { @@ -102,7 +102,7 @@ static bool int_to_string_callback( void* field_data, bool is_read, char** res ) return true; } } -static struct json_object_field api_Account_layout[] = { +struct json_object_field api_Account_layout[] = { { .key = "acct", .offset = 0, @@ -139,8 +139,16 @@ static struct json_object_field api_Account_layout[] = { .type = &json_field_callback_string, .string_callback = fqn_callback, }, - JSON_FIELD_FIXED_STRING( header, "https://pl.polaris-1.work/images/banner.png", true ), - JSON_FIELD_FIXED_STRING( header_static, "https://pl.polaris-1.work/images/banner.png", true ), + { + .key = "header", + .offset = offsetof( OBJ_TYPE, banner ), + .type = &json_field_string + }, + { + .key = "header_static", + .offset = offsetof( OBJ_TYPE, banner ), + .type = &json_field_string + }, { .key = "id", .offset = offsetof( OBJ_TYPE, id ), @@ -189,12 +197,11 @@ static bool api_Account_writer( struct json_writer* jw, const char* field_name, return false; } - FILE* f = jw->f; - struct account* owner_account = account_from_id( owner_account_id ); - //#include "view/api/account.json.inc" + //struct account* owner_account = account_from_id( owner_account_id ); + json_write_field_name(jw,field_name); json_write_pretty_object_layout( jw, api_Account_layout, a ); - account_free(owner_account); + //account_free(owner_account); account_free(a); return true; diff --git a/src/view/api/Notification.c b/src/view/api/Notification.c new file mode 100644 index 0000000..c78ca53 --- /dev/null +++ b/src/view/api/Notification.c @@ -0,0 +1,88 @@ +#include "Notification.h" + +#include "json/json.h" +#include "json/layout.h" +#include "format.h" + +#include "model/notification.h" + +#include "view/api/Status.h" +#include "view/api/Account.h" + +bool int_to_string_callback( void* field_data, bool is_read, char** res ); + +static struct json_enum type_enum[] = { + { "favourite", nt_favorite }, + { "follow", nt_follow }, + { "favourite", nt_like }, + { "mention", nt_unfollow }, + { "mention", nt_block }, + { "mention", nt_mention }, + { "pleroma:emoji_raction", nt_react }, + + { NULL, 0 }, +}; + +#define OBJ_TYPE struct notification +static struct json_object_field Notification_Pleroma_layout[] = { + JSON_FIELD_FIXED_BOOL( is_muted, false ), + JSON_FIELD_FIXED_BOOL( is_seen, false ), + JSON_FIELD_END, +}; +static struct json_object_field api_Notification_layout[] = { + { + .key = "account", + .offset = offsetof( OBJ_TYPE, account_id ), + .type = &api_Account_type, + }, + JSON_FIELD_DATETIME( created_at, true ), + { + .key = "id", + .offset = offsetof( OBJ_TYPE, id ), + .type = &json_field_callback_string, + .string_callback = int_to_string_callback + }, + { + .key = "pleroma", + .offset = 0, + .type = &json_field_object_composite, + .composite_layout = Notification_Pleroma_layout, + }, + { + .key = "status", + .offset = offsetof( OBJ_TYPE, status_id ), + .required = false, + .type = &api_Status_type, + }, + { + .key = "emoji", + .offset = offsetof( OBJ_TYPE, react ), + .required = false, + .type = &json_field_string, + }, + JSON_FIELD_ENUM( type, type_enum, true ), + JSON_FIELD_END, +}; +#undef OBJ_TYPE + +void api_Notification_write( struct notification* note, FILE* f, int indent ) +{ + struct json_writer jw = { + .f = f, + .indentation = "\t", + .indent = indent, + }; + /* + switch( n->type ) { + case nt_unfollow: + s = status_new_system_unfollow( n->ref_account_id ); + break; + case nt_block: + s = status_new_system_block( n->ref_account_id ); + break; + } + */ + + json_write_pretty_object_layout( &jw, api_Notification_layout, note ); +} + diff --git a/src/view/api/Notification.h b/src/view/api/Notification.h new file mode 100644 index 0000000..2c9ece5 --- /dev/null +++ b/src/view/api/Notification.h @@ -0,0 +1,8 @@ +#pragma once + +struct notification; + +#include + +void api_Notification_write( struct notification* note, FILE* f, int indent ); + diff --git a/src/view/api/Status.c b/src/view/api/Status.c new file mode 100644 index 0000000..c1a664f --- /dev/null +++ b/src/view/api/Status.c @@ -0,0 +1,430 @@ +#include "Status.h" + +#include "json/json.h" +#include "json/layout.h" +#include "format.h" + +#include "model/status.h" +#include "model/status/react.h" +#include "model/account.h" + +#include "view/api/Account.h" + +#include + +bool int_to_string_callback( void* field_data, bool is_read, char** res ); +bool int_non_zero( void* field_data, bool is_read, bool* val ) +{ + if( !is_read ) { + *val = *(int*)field_data > 0; + return true; + } +} +extern struct json_object_field api_Status_layout[]; + +static bool write_in_reply_to( struct json_writer* jw, const char* field_name, void* field_data, struct json_object_field* layout_field_data ) +{ + struct status* s = field_data; + struct status* in_reply_to = status_from_id( s->in_reply_to ); + if( in_reply_to ) { + json_write_field_name(jw,"in_reply_to_account_id"); + fprintf( jw->f, "\"%d\"", in_reply_to->account_id ); + jw->need_comma = true; + + json_write_indention(jw); + json_write_field_name(jw,"in_reply_to_id"); + fprintf( jw->f, "%d", in_reply_to->id ); + } else { + json_write_field_name(jw,"in_reply_to_account_id"); + fprintf( jw->f, "null" ); + jw->need_comma = true; + + json_write_indention(jw); + json_write_field_name(jw,"in_reply_to_id"); + fprintf( jw->f, "null" ); + } + return true; +} + +static struct json_field_type in_reply_to = { + .writer = write_in_reply_to, +}; + + +static bool write_status_reference( struct json_writer* jw, const char* field_name, void* field_data, struct json_object_field* layout_field_data ) +{ + json_write_field_name(jw,field_name); + int id = *(int*)field_data; + struct status* s = status_from_id(id); + if( !s ) { + fprintf( jw->f, "null" ); + } else { + json_write_pretty_object_layout( jw, api_Status_layout, s ); + status_free(s); + } + return true; +} +static struct json_field_type Status_reference_type = { + .writer = write_status_reference, +}; + +bool does_react_include_me( void* field_data, bool is_read, bool* val ) +{ + struct status_react* sr = field_data; + for( int i = 0; i < sr->accounts.count; ++i ) { + if( sr->accounts.items[i] == owner_account_id ) { + *val = true; + return true; + } + } + *val = false; + return true; +} +static struct json_object_field MediaAttachment_Pleroma_layout[] = { + JSON_FIELD_FIXED_STRING( mime_type, "image/png", true ), + JSON_FIELD_END +}; +static struct json_object_field MediaAttachment_layout[] = { + JSON_FIELD_FIXED_STRING( blurhash, "eRH.A}xs0Kxv00xYR,R+t5R+9Gt6xaNG%%2-;xaM{NGRjD%%Rjs:xaxu", true ), + JSON_FIELD_FIXED_NULL( description ), + { + .key = "id", + .offset = 0, + .type = &json_field_string, + }, + { + .key = "pleroma", + .offset = 0, + .type = &json_field_object_composite, + .composite_layout = MediaAttachment_Pleroma_layout, + }, + { + .key = "preview_url", + .offset = 0, + .type = &json_field_string, + }, + { + .key = "remote_url", + .offset = 0, + .type = &json_field_string, + }, + { + .key = "text_url", + .offset = 0, + .type = &json_field_string, + }, + JSON_FIELD_FIXED_STRING( type, "image", true ), + { + .key = "url", + .offset = 0, + .type = &json_field_string, + }, + JSON_FIELD_END, +}; + +static struct json_field_type MediaAttachment_type = { + .layout = MediaAttachment_layout, + .reader = json_field_object_composite_reader, + .writer = json_field_object_composite_writer, + .size = sizeof(char*), +}; + +#define OBJ_TYPE struct account +bool account_acct_callback( void* field_data, bool is_read, char** res ) +{ + if( is_read ) { return false; } + + struct account* a = field_data; + *res = aformat( "%s@%s", a->handle, a->server ); + return true; +} +static struct json_object_field api_Mention_layout[] = { + { + .key = "acct", + .offset = 0, + .type = &json_field_callback_string, + .string_callback = account_acct_callback, + }, + { + .key = "id", + .offset = offsetof( OBJ_TYPE, id ), + .type = &json_field_callback_string, + .string_callback = int_to_string_callback, + }, + { + .key = "url", + .offset = offsetof( OBJ_TYPE, account_url ), + .type = &json_field_string + }, + { + .key = "username", + .offset = offsetof( OBJ_TYPE, handle ), + .type = &json_field_string, + }, + JSON_FIELD_END, +}; +#undef OBJ_TYPE + +static bool Mention_from_id_writer( struct json_writer* jw, const char* field_name, void* field_data, struct json_object_field* layout_field_data ) +{ + json_write_field_name(jw,field_name); + int id = *(int*)field_data; + struct account* a = account_from_id(id); + if( !a ) { + fprintf( jw->f, "null" ); + } else { + json_write_pretty_object_layout( jw, api_Mention_layout, a); + account_free(a); + } + return true; +} + +static struct json_field_type Mention_type = { + .writer = Mention_from_id_writer, + .size = sizeof(int), +}; + +#define OBJ_TYPE struct status_react +static struct json_object_field emoji_react_layout[] = { + { + .key = "count", + .offset = offsetof( OBJ_TYPE, accounts.count ), + .type = &json_field_integer, + }, + { + .key = "me", + .offset = 0, + .type = &json_field_bool_callback, + .bool_callback = does_react_include_me, + }, + { + .key = "name", + .offset = offsetof( OBJ_TYPE, code ), + .type = &json_field_string, + }, + JSON_FIELD_END, +}; +#undef OBJ_TYPE + +static struct json_field_type EmojiReact_type = { + .layout = emoji_react_layout, + .reader = json_field_object_type_reader, + .writer = json_field_object_type_writer, + .size = sizeof(struct status_react*), +}; + +bool render_source_callback( void* field_data, bool is_read, char** res ) +{ + struct status* s = field_data; + if( !s->content ) { + s->content = status_render_source(s); + } + *res = strdup(s->content); + return true; +} + +#define OBJ_TYPE struct status +static struct json_object_field content_layout[] = { + { + .key = "text/plain", + .offset = 0, + .type = &json_field_callback_string, + .string_callback = render_source_callback, + }, + JSON_FIELD_END, +}; +static struct json_object_field pleroma_layout[] = { + { + .key = "content", + .offset = 0, + .type = &json_field_object_composite, + .composite_layout = content_layout, + }, + { + .key = "conversation_id", + .offset = offsetof( OBJ_TYPE, root_status_id ), + .type = &json_field_integer, + }, + JSON_FIELD_FIXED_NULL( direct_conversation_id ), // Maybe this is breaking things in the home feed? + { + .key = "emoji_reactions", + .offset = offsetof( OBJ_TYPE, reacts ), + .type = &json_field_array_of, + .array_item_type = &EmojiReact_type, + }, + JSON_FIELD_FIXED_NULL( expires_at ), + /*%( + if( in_reply_to_account ) { ) + "in_reply_to_account_acct": "%s{in_reply_to_account->handle}@%s{in_reply_to_account->server}", + "local": %s{ s->remote ? "false" : "true" }, + "parent_visible": true,%( + } else { ) + "in_reply_to_account_acct": null, + "local": %s{ s->remote ? "false" : "true" }, + "parent_visible": false,%( + } ) + */ + JSON_FIELD_FIXED_NULL( pinned_at ), + /* + "spoiler_text": { + "text/plain": "" + }, + */ + JSON_FIELD_FIXED_BOOL( thread_muted, false ), + JSON_FIELD_END, +}; +struct json_object_field api_Status_layout[] = { + { + .key = "account", + .required = true, + .offset = offsetof( OBJ_TYPE, account_id ), + .type = &api_Account_type, + }, + JSON_FIELD_FIXED_NULL( application ), + JSON_FIELD_FIXED_BOOL( bookmarked, false ), + JSON_FIELD_FIXED_NULL( card ), + { + .key = "content", + .offset = 0, + .type = &json_field_callback_string, + .string_callback = render_source_callback, + }, + { + .key = "crated_at", + .required = true, + .offset = offsetof( OBJ_TYPE, published ), + .type = &json_field_date_time + }, + JSON_FIELD_FIXED_NULL( edited_at ), + JSON_FIELD_EMPTY_ARRAY( emoji, true ), + { + .key = "favourited", + .required = true, + .offset = 0, + .type = &json_field_fixed_bool, + .fixed_bool = false, + }, + { + .key = "favourited_count", + .required = true, + .offset = offsetof( OBJ_TYPE, likes.count ), + .type = &json_field_integer, + }, + { + .key = "id", + .required = true, + .offset = offsetof( OBJ_TYPE, id ), + .type = &json_field_callback_string, + .string_callback = int_to_string_callback, + }, + { + .key = "@in_reply_to_inline", + .required = true, + .offset = 0, + .type = &in_reply_to, + }, + JSON_FIELD_FIXED_NULL( language ), + { + .key = "media_attachments", + .offset = offsetof( OBJ_TYPE, media ), + .type = &json_field_array_of, + .array_item_type = &MediaAttachment_type, + }, + { + .key = "mentions", + .offset = offsetof( OBJ_TYPE, mentions ), + .type = &json_field_array_of, + .array_item_type = &Mention_type, + }, + JSON_FIELD_FIXED_BOOL( muted, false ), + JSON_FIELD_FIXED_BOOL( pinned, false ), + { + .key = "pleroma", + .offset = 0, + .type = &json_field_object_composite, + .composite_layout = pleroma_layout, + }, + JSON_FIELD_FIXED_NULL( poll ), + { + .key = "reblog", + .offset = offsetof(OBJ_TYPE, repost_id), + .type = &Status_reference_type, + }, + { + .key = "reblogged", + .offset = offsetof( OBJ_TYPE, reposts.count ), + .type = &json_field_bool_callback, + .bool_callback = int_non_zero, + }, + { + .key = "reblogs_count", + .offset = offsetof( OBJ_TYPE, reposts.count ), + .type = &json_field_integer, + }, + { + .key = "replies_count", + .offset = offsetof( OBJ_TYPE, replies.count ), + .type = &json_field_integer, + }, + JSON_FIELD_FIXED_BOOL( sensitive, false ), + JSON_FIELD_FIXED_STRING( spoiler_text, "", true ), + JSON_FIELD_EMPTY_ARRAY( tags, true ), + JSON_FIELD_FIXED_NULL( text ), + { + .key = "uri", + .offset = offsetof( OBJ_TYPE, url ), + .type = &json_field_string, + }, + JSON_FIELD_STRING( url, true ), + JSON_FIELD_FIXED_STRING( visibility, "public", true ), + JSON_FIELD_END, +}; +#undef OBJ_TYPE + +static bool api_Status_writer( struct json_writer* jw, const char* field_name, void* field_data, struct json_object_field* layout_field_data ) +{ + int status_id = *(int*)field_data; + + struct status* s = status_from_id(status_id); + struct status* system_status = NULL; + if( !s ) { + return false; + } + + json_write_field_name(jw,field_name); + + if( s->stub ) { + system_status = status_new_system_stub( s ); + json_write_pretty_object_layout( jw, api_Status_layout, system_status ); + status_free(system_status); + } else { + json_write_pretty_object_layout( jw, api_Status_layout, s ); + } + + status_free(s); + + return true; +} +struct json_field_type api_Status_type = { + .writer = api_Status_writer, + .size = sizeof(int), + .layout = api_Status_layout, +}; + +void api_Status_write( struct status* s, FILE* f, int indent ) +{ + struct json_writer jw = { + .f = f, + .indentation = "\t", + .indent = indent, + }; + + struct status* system_status = NULL; + if( s->stub ) { + system_status = status_new_system_stub( s ); + s = system_status; + } + + json_write_pretty_object_layout( &jw, api_Status_layout, s ); + status_free(system_status); +} diff --git a/src/view/api/Status.h b/src/view/api/Status.h new file mode 100644 index 0000000..23fe2b6 --- /dev/null +++ b/src/view/api/Status.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +struct json_field_type; +struct status; +extern struct json_field_type api_Status_type; + +void api_Status_write( struct status* s, FILE* f, int indent ); + diff --git a/src/view/api/status.json.template b/src/view/api/status.json.template deleted file mode 100644 index f960e3d..0000000 --- a/src/view/api/status.json.template +++ /dev/null @@ -1,102 +0,0 @@ -{ - "account": %( api_Account_write(account,f,1); ), - "application": null, - "bookmarked": false, - "card": null, - "content": %( json_write_string(f,status_render_source(s)); ), - "created_at": %( json_write_date_time_string( s->published, f ); ), - "edited_at": null, - "emojis": [], - "favourited": false, - "favourites_count": %d{ s->likes.count }, - "id": "%d{ s->id }",%( - if( in_reply_to ) { ) - "in_reply_to_account_id": "%d{ in_reply_to->account_id }", - "in_reply_to_id": "%d{in_reply_to->id}",%( - } else { ) - "in_reply_to_account_id": null, - "in_reply_to_id": null,%( - } ) - "language": null, - "media_attachments": [%( for( int i = 0; i < s->media.count; ++i ) { ) - { - "blurhash": "eRH.A}xs0Kxv00xYR,R+t5R+9Gt6xaNG%%2-;xaM{NGRjD%%Rjs:xaxu", - "description": null, - "id": "%d{s->id}.media[%d{i}]", - "pleroma": { - "mime_type": "image/png" - }, - "preview_url": "%s{s->media.items[i]}", - "remote_url": "%s{s->media.items[i]}", - "text_url": "%s{s->media.items[i]}", - "type": "image", - "url": "%s{s->media.items[i]}" - }%s{ i < s->media.count - 1 ? "," : "" } - %( } )], - "mentions": [%( for( int i = 0; i < s->mentions.count; ++i ) { ) - %( struct account* mention = account_from_id( s->mentions.items[i] ); ) - %s{ i != 0 ? "," : "" }{ - "acct": "%s{mention->handle}@%s{mention->server}", - "id": "%d{mention->id}", - "url": "%s{mention->account_url}", - "username": "%s{mention->handle}" - } - %( account_free(mention); ) - %( } )], - "muted": false, - "pinned": false, - "pleroma": { - "content": { - "text/plain": %( json_write_string(f,s->source ? s->source : ""); ) - }, - "conversation_id": %d{s->root_status_id}, - "direct_conversation_id": null, - "emoji_reactions": [%( for( int i = 0; i < s->reacts.count; ++i ) { struct status_react* sr = s->reacts.items[i];) - %( - bool includes_me( struct status_react* sr ) { - for( int i = 0; i < sr->accounts.count; ++i ) { - if( sr->accounts.items[i] == owner_account_id ) { return true; } - } - return false; - } - ) - { - "count": %d{ sr->accounts.count }, - "me": %s{ includes_me(sr) ? "true" : "false" }, - "name": %( json_write_string( f, sr->code ); ) - }%s{ i != s->reacts.count-1 ? "," : "" } - %( } )], - "expires_at": null,%( - if( in_reply_to_account ) { ) - "in_reply_to_account_acct": "%s{in_reply_to_account->handle}@%s{in_reply_to_account->server}", - "local": %s{ s->remote ? "false" : "true" }, - "parent_visible": true,%( - } else { ) - "in_reply_to_account_acct": null, - "local": %s{ s->remote ? "false" : "true" }, - "parent_visible": false,%( - } ) - "pinned_at": null, - "spoiler_text": { - "text/plain": "" - }, - "thread_muted": false - }, - "poll": null, - %( - if( repost ) { ) - "reblog": %( api_status_write_as_json( repost, f ); ),%( - } else { ) - "reblog": null,%( - } ) - "reblogged": %s{ s->reposts.count > 0 ? "true": "false" }, - "reblogs_count": %d{ s->reposts.count }, - "replies_count": 0, - "sensitive": false, - "spoiler_text": "", - "tags": [], - "text": null, - "uri": "%s{s->url}", - "url": "%s{s->url}", - "visibility": "public" -}