You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

557 lines
13 KiB
C

#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 <string.h>
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;
}
bool quote_url_callback( void* field_data, bool is_read, char** val )
{
if( is_read ) { return false; }
struct status* s = field_data;
if( s->quote_id == 0 ) { return false; }
struct status* quoted_post = status_from_id(s->quote_id);
*val = strdup(quoted_post->url );
status_free(quoted_post);
return true;
}
bool quote_visible_callback( void* field_data, bool is_read, bool* val )
{
if( is_read ) { return false; }
struct status* s = field_data;
*val = ( s->quote_id != 0 );
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 ),
{
.key = "quote",
.offset = offsetof( OBJ_TYPE, quote_id ),
.type = &Status_reference_type,
},
{
.key = "quote_url",
.offset = 0,
.type = &json_field_callback_string,
.string_callback = quote_url_callback,
},
{
.key = "quote_visible",
.offset = 0,
.type = &json_field_bool_callback,
.bool_callback = quote_visible_callback,
},
{
.key = "quotes_count",
.offset = offsetof( OBJ_TYPE, quotes.count ),
.type = &json_field_integer,
},
/*
"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);
}