#include "Status.h" #include "json/json.h" #include "json/layout.h" #include "util/format.h" #include "model/server.h" #include "model/status.h" #include "model/status/react.h" #include "model/account.h" #include "model/media.h" #include "view/api/Account.h" #include extern struct json_object_field api_Status_layout[]; extern struct json_field_type api_Emoji_type; bool int_to_string_callback( void* field_data, bool is_read, char** res ); bool post_reposted( void* field_data, bool is_read, bool* val ) { struct status* s = field_data; if( !is_read ) { *val = ( s->reposted_status_id != 0 ); } return true; } bool owner_favorited_callback( void* field_data, bool is_read, bool* value ) { struct status* s = field_data; if( !is_read ) { for( int i = 0; i < s->likes.count; ++i ) { if( s->likes.items[i] == owner_account_id ) { *value = true; return true; } } *value = false; return true; } return false; } static bool write_in_reply_to( struct json_writer* jw, const char* field_name, void* field_data, struct json_reflection* layout_field_data, int offset ) { 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, "\"%018u\"", 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" ); } status_free(in_reply_to); 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_reflection* layout_field_data, int offset ) { 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 }; #define OBJ_TYPE struct media 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 = offsetof( OBJ_TYPE, remote_url ), .type = &json_field_string, }, { .key = "pleroma", .offset = 0, .type = &json_field_object_composite, .composite_layout = MediaAttachment_Pleroma_layout, }, { .key = "preview_url", .offset = offsetof( OBJ_TYPE, preview_url ), .type = &json_field_string, }, { .key = "remote_url", .offset = offsetof( OBJ_TYPE, remote_url ), .type = &json_field_string, }, { .key = "text_url", .offset = offsetof( OBJ_TYPE, remote_url ), .type = &json_field_string, }, JSON_FIELD_FIXED_STRING( type, "image", true ), { .key = "url", .offset = offsetof( OBJ_TYPE, remote_url ), .type = &json_field_string, }, JSON_FIELD_END, }; #undef OBJ_TYPE static struct json_field_type MediaAttachment_type = { .layout = MediaAttachment_layout, .reader = json_field_object_type_reader, .writer = json_field_object_type_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_reflection* layout_field_data, int offset ) { 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; } bool in_reply_to_account_acct_callback( void* field_data, bool is_read, char** res ) { struct status* s = field_data; if( s->mentions.count > 0 ) { struct account* a = account_from_id( s->mentions.items[0] ); *res = aformat( "%s@%s", a->handle, a->server ); account_free(a); return true; } *res = strdup(""); return false; } bool context_url_callback( void* field_data, bool is_read, char** res ) { struct status* s = field_data; if( !is_read ) { *res = aformat( "https://%s/contexts/%d", g_server->domain, s->root_status_id ); return true; } return false; } bool is_local_callback( void* field_data, bool is_read, bool* val ) { struct status* s = field_data; *val = !s->remote; 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, }, JSON_FIELD_FIXED_NULL( content_type ), { .key = "context", .offset = 0, .type = &json_field_callback_string, .string_callback = context_url_callback, }, { .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 ), .required = true, .type = &json_field_array_of, .array_item_type = &EmojiReact_type, }, JSON_FIELD_FIXED_NULL( event ), JSON_FIELD_FIXED_NULL( expires_at ), { .key = "in_reply_to_account_acct", .offset = 0, .type = &json_field_callback_string, .string_callback = in_reply_to_account_acct_callback, }, { .key = "local", .offset = 0, .type = &json_field_bool_callback, .bool_callback = is_local_callback, }, JSON_FIELD_FIXED_BOOL( parent_visible, true ), JSON_FIELD_FIXED_NULL( pinned_at ), JSON_FIELD_FIXED_NULL( quote ), JSON_FIELD_FIXED_NULL( quote_url ), JSON_FIELD_FIXED_BOOL( quote_visible, false ), /* "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_BOOL( bookmarked, true ), JSON_FIELD_FIXED_NULL( card ), { .key = "content", .offset = 0, .type = &json_field_callback_string, .string_callback = render_source_callback, }, { .key = "created_at", .required = true, .offset = offsetof( OBJ_TYPE, published ), .type = &json_field_date_time }, JSON_FIELD_FIXED_NULL( edited_at ), { .key = "emojis", .offset = offsetof( OBJ_TYPE, emoji ), .required = true, .type = &json_field_array_of, .array_item_type = &api_Emoji_type, }, { .key = "favourited", .required = true, .offset = 0, .type = &json_field_bool_callback, .bool_callback = owner_favorited_callback, }, { .key = "favourites_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, media2 ), .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 = 0, .type = &json_field_bool_callback, .bool_callback = post_reposted, }, { .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_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_reflection* layout_field_data, int offset ) { 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_from_id_type = { .writer = api_Status_writer, .size = sizeof(int), .layout = api_Status_layout, }; struct json_field_type api_Status_type = { .writer = json_field_object_type_writer, .size = sizeof(struct status*), .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, }; if( !s ) { fprintf( f, "null" ); return; } 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); }