#include "status.h" #include "json/json.h" #include "json/layout.h" #include "collections/array.h" #include "http/server/request.h" #include "model/status.h" #include "model/status/react.h" #include "model/account.h" #include "model/ap/activity.h" #include "model/ap/outbox_envelope.h" #include "view/api/Account.h" #include #include #include #include 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); } // 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_as_json(s2,f); 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_as_json(s2,f); 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_as_json(ss[i],f); } 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 { int* 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_INTS( 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 ) ) { return false; } s = malloc(sizeof(struct status)); memset(s,0,sizeof(*s)); s->published = time(NULL); s->source = strdup( params.status ); status_save_new(s); if( params.in_reply_to_id ) { status_make_reply_to( s, atoi( params.in_reply_to_id ) ); } status_save(s); // Add to owner timeline, public timeline and home timelines status_add_to_timeline( s, a->id ); status_add_to_timeline( s, public_timeline_id ); status_add_to_timeline( s, home_timeline_id ); //status_add_post_to_timeline( s, federated_timeline_id ); // Federate struct ap_activity* note = ap_activity_create_note(s); struct ap_activity* create = ap_activity_create_Create(note); ap_activity_save(create); struct outbox_envelope_list oel; memset(&oel,0,sizeof(oel)); account_deliver_activity_to_followers( a, create, &oel ); printf( "Delivering to %d inboxes\n", oel.count ); outbox_envelope_list_save(&oel); outbox_envelope_list_free_composite(&oel); ap_activity_free(create); ap_activity_free(note); http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body(req); api_status_write_as_json(s,f); result = true; cleanup: free(params.status); free(params.visibility); free(params.in_reply_to_id); free(params.spoiler_text); 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); struct status* repost = status_new_repost( s, owner ); // Flag the status as reposted s->reposted_status_id = repost->id; status_save(s); // Add repost to timelines status_add_to_timeline( repost, owner->id ); status_add_to_timeline( repost, home_timeline_id ); status_add_to_timeline( repost, public_timeline_id ); // Federate account_announce( owner, s ); // Show the new status as the response show_status( req, repost ); success: result = true; cleanup: account_free(owner); status_free(repost); return result; failed: result = false; goto cleanup; }