#include "status.h" // Submodules #include "http/server/request.h" #include "json/json.h" #include "json/layout.h" #include "collections/array.h" #include "util/format.h" // Model #include "model/server.h" #include "model/status.h" #include "model/status/react.h" #include "model/account.h" #include "model/media.h" #include "model/outbox_envelope.h" // View #include "view/api/Account.h" #include "view/api/Status.h" // Standard Library #include #include #include #include void write_json_escaped( FILE* f, const char* str ); 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( s, f, 0 ); } // Route: /api/v1/statuses/%d{s->id}/context void show_status_context( struct http_request* req, struct status* s ) { http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); struct { struct status** items; int count; } ancestors, replies; status_get_context( s, &ancestors, &replies ); fprintf( f, "{\"ancestors\":[" ); for( int i = 0; i < ancestors.count; ++i ) { struct status* s2 = ancestors.items[i]; api_Status_write( s2, f, 1 ); status_free(s2); if( i != ancestors.count - 1 ) { fprintf( f, "," ); } } free(ancestors.items); fprintf( f, "],\"descendants\":[" ); for( int i = 0; i < replies.count; ++i ) { struct status* s2 = replies.items[i]; api_Status_write( s2, f, 1 ); status_free(s2); if( i != replies.count - 1 ) { fprintf( f, "," ); } } free(replies.items); fprintf( f, "]}" ); } void show_statuses( struct http_request* req, struct status** ss, int count ) { http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); fprintf( f, "[" ); for( int i = 0; i < count; ++i ) { if( i > 0 ) { fprintf( f, "," ); } api_Status_write( ss[i], f, 1 ); } fprintf( f, "]" ); } // Route: POST /api/v1/statuses bool handle_post( struct http_request* req, struct account* a ) { bool result = false; struct status* s = NULL; struct params_t { struct { char** items; int count; } media_ids; bool sensitive; char* status; char* visibility; char* in_reply_to_id; char* spoiler_text; } params; memset(¶ms,0,sizeof(params)); #define OBJ_TYPE struct params_t static struct json_object_field layout[] = { JSON_FIELD_ARRAY_OF_STRINGS( media_ids, false ), JSON_FIELD_BOOL( sensitive, false ), JSON_FIELD_STRING( status, true ), JSON_FIELD_STRING( visibility, true ), JSON_FIELD_STRING( in_reply_to_id, false ), JSON_FIELD_STRING( spoiler_text, false ), JSON_FIELD_END, }; #undef OBJ_TYPE FILE* data = http_request_get_request_data( req ); if( !json_read_object_layout_from_FILE( data, layout, ¶ms ) ) { goto failed; } s = malloc(sizeof(struct status)); memset(s,0,sizeof(*s)); s->published = time(NULL); s->source = strdup( params.status ); status_save_new(s); for( int i = 0; i < params.media_ids.count; ++i ) { struct media* m = media_from_id( atoi(params.media_ids.items[i]) ); if( !m ) { continue; } char* url = aformat( "https://%s/media/%d/blob", g_server->domain, m->id ); array_append( &s->media, sizeof(url), &url ); media_free(m); } if( params.in_reply_to_id ) { status_make_reply_to( s, atoi( params.in_reply_to_id ) ); } // Save status data status_save(s); // Federate account_create( a, s ); http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body(req); api_Status_write( s, f, 0 ); result = true; cleanup: free(params.status); free(params.visibility); free(params.in_reply_to_id); free(params.spoiler_text); for( int i = 0; i < params.media_ids.count; ++i ) { free( params.media_ids.items[i] ); } free( params.media_ids.items); status_free(s); return result; failed: result = false; goto cleanup; } // Route: POST /api/v1/statuses/%d{id}/reblog bool handle_repost( struct http_request* req, struct status* s ) { bool result = false; struct account* owner = account_from_id(owner_account_id); if( !owner ) { goto failed; } // Federate struct status* repost = account_announce( owner, s, NULL, NULL ); if( !repost ) { goto failed; } // Show the new status as the response show_status( req, repost ); goto success; success: result = true; cleanup: account_free(owner); status_free(repost); return result; failed: result = false; goto cleanup; } // Route: POST /api/v1/statuses/%d{id}/bookmark bool handle_bookmark( struct http_request* req, struct status* s ) { status_set_bookmark( s ); show_status( req, s ); return true; } // Route: POST /api/v1/statuses/%d{id}/unbookmark bool handle_unbookmark( struct http_request* req, struct status* s ) { status_clear_bookmark( s ); show_status( req, s ); return true; } // route: GET /api/v1/bookmarks bool handle_show_bookmarks( struct http_request* req ) { struct { struct status** items; int count; } results; memset(&results,0,sizeof(results)); status_get_bookmarks( 0, 100, &results ); show_statuses( req, results.items, results.count ); for( int i = 0; i < results.count; ++i ) { status_free(results.items[i]); } free(results.items); return true; } // route: GET /api/v1/statuses/%d{id}/favourite bool handle_favorite( struct http_request* req, struct status* s ) { struct account* owner = account_from_id( owner_account_id ); status_add_like(s,owner); status_save(s); show_status( req, s ); account_free(owner); return true; } bool http_request_route_id( struct http_request* req, int* id ) { char* id_str = http_request_route_get_dir_or_file(req); if( !id_str || !*id_str ) { return false; } *id = -1; sscanf( id_str, "%d", id ); free(id_str); if( *id == -1 ) { return false; } return true; } // Route: /api/v1/statuses/%d{id}/ bool route_statuses( struct http_request* req ) { bool result = false; struct status* s = NULL; if( http_request_route_term( req, "" ) ) { if( http_request_route_method( req, "POST" ) ) { struct account* owner = account_from_id(owner_account_id); bool res = handle_post(req, owner); account_free(owner); return res; } return false; } if( !http_request_route( req, "/" ) ) { return false; } int id = -1; if( !http_request_route_id( req, &id ) ) { goto failed; } s = status_from_id(id); if( !s ) { goto failed; } if( http_request_route( req, "context" ) ) { show_status_context( req, s ); goto success; } else if( http_request_route_term( req, "reblog" ) ) { if( handle_repost( req, s ) ) { goto success; } } else if( http_request_route_term( req, "bookmark" ) ) { if( handle_bookmark( req, s ) ) { goto success; } } else if( http_request_route_term( req, "unbookmark" ) ) { if( handle_unbookmark( req, s ) ) { goto success; } } else if( http_request_route_term( req, "favourite" ) ) { if( handle_favorite( req, s ) ) { goto success; } } else if( http_request_route_term( req, "" ) ) { show_status( req, s ); goto success; } goto failed; cleanup: status_free(s); return result; success: result = true; goto cleanup; failed: result = false; goto cleanup; }