#include "mastodon_api.h" #include "http/server/request.h" // Submodules #include "form.h" #include "json/json.h" #include "http/query.h" #include "ffdb/fs_list.h" #include "model/status.h" #include "model/account.h" #include "model/notification.h" #include "model/timeline.h" #include "model/server.h" #include "controller/pleroma_api.h" #include "api/client_apps.h" #include "api/status.h" #include "api/notice.h" #include #include #include #include #include enum timeline_ids { tli_owner = 0, tli_public = 1, tli_home = 2, }; bool handle_timeline( struct http_request* req, int timeline_id ) { struct params_t { int since_id; int max_id; int limit; bool hide_muted; bool pinned; } params = { .since_id = 0, .limit = 32, .max_id = 2147483647, .hide_muted = true, .pinned = false, }; #define OBJ_TYPE struct params_t static struct http_query_fields fields[] = { HTTP_QUERY_FIELD_INT( since_id ) HTTP_QUERY_FIELD_INT( limit ) HTTP_QUERY_FIELD_INT( max_id ) HTTP_QUERY_FIELD_BOOL( hide_muted ) HTTP_QUERY_FIELD_BOOL( pinned ) HTTP_QUERY_FIELD_END }; #undef OBJ_TYPE // handle query parameters if( http_request_route( req, "?" ) ) { http_query_parse( req, fields, ¶ms ); } if( params.limit > 32 ) { params.limit = 32; } struct timeline* tl = timeline_from_id( timeline_id ); if( !tl ) { http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); fprintf( f, "[]" ); return true; } struct status* ss[params.limit]; struct status* items[params.limit]; struct { struct status** items; int count; } show = { .items = items, .count = 0 }; int offset = 0; int count = 0; int max_loops = 5; load_statuses: printf( "loading offset=%d,limit=%d\n", offset, params.limit ); count = timeline_load_statuses( tl, offset, params.limit, ss ); printf( "count=%d\n", count ); if( count == 0 ) { goto done; } // Filter for( int i = 0; i < count; ++i ) { struct status* s = ss[i]; if( s->id <= params.since_id ) { goto filtered; } if( s->id >= params.max_id ) { goto filtered; } if( s->pinned != params.pinned ) { goto filtered; } show.items[show.count] = s; show.count += 1; if( 0 ) { filtered: //printf( "Filtered out %d\n", s->id ); } } // Try to load more statuses if( count < params.limit ) { offset += params.limit; params.limit -= show.count; if( max_loops > 0 ) { max_loops -= 1; goto load_statuses; } } done: show_statuses( req, show.items, show.count ); for( int i = 0; i < count; ++i ) { status_free( ss[i] ); } timeline_free(tl); return true; } bool handle_mastodon_api_show_account( 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_account_write_as_json(a,f); return true; } static bool handle_search( struct http_request* req ) { struct params_t { const char* q; int limit; bool resolve; bool following; } params; memset(¶ms,0,sizeof(params)); static struct http_query_fields fields[] = { { "q", offsetof(struct params_t,q), qf_string }, { "limit", offsetof(struct params_t,limit), qf_integer }, { "resolve", offsetof(struct params_t,resolve), qf_bool }, { "following", offsetof(struct params_t,following), qf_bool }, { NULL }, }; http_query_parse( req, fields, ¶ms ); http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); struct account* a = account_from_webfinger( params.q ); if( !a && params.resolve ) { printf( "TODO: perform webfinger lookup\n" ); return false; } fprintf( f, "[" ); api_account_write_as_json(a,f); fprintf( f, "]" ); account_free(a); return true; } static bool handle_relationships( struct http_request* req ) { http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); fprintf( f, "[" ); bool first = true; const char* key; while( (key=http_request_route_query_key(req)) && (0 == strcmp(key,"id[]") ) ) { int id = atoi( http_request_route_query_value(req) ); if( id > 0 ) { struct account* a = account_from_id(id); if( !a ) { continue; } fprintf( f, first ? "\n" : ",\n" ); first = false; #include "src/view/api/relationship.json.inc" account_free(a); } } fprintf( f, "]" ); 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_1f( struct http_request* req ) { bool result = false; struct status* s = NULL; if( http_request_route( req, "" ) && http_request_route_method( req, "POST" ) ) { struct account* owner = account_from_id(0); bool res = handle_post(req, owner); account_free(owner); return res; } 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( req, "" ) ) { show_status( req, s ); goto success; } failed: result = false; cleanup: status_free(s); return result; success: result = true; goto cleanup; } bool route_mastodon_api( struct http_request* req ) { if( http_request_route_term( req, "apps" ) ) { if( http_request_route_method( req, "POST" ) ) { return handle_mastodon_api_apps(req); } } else if( http_request_route( req, "timelines/public" ) ) { return handle_timeline( req, tli_public ); } else if( http_request_route( req, "instance" ) ) { http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); #include "src/view/api/instance_data.json.inc" return true; } if( !check_bearer_token(req) ) { return false; } if( http_request_route( req, "pleroma" ) ) { return route_pleroma_api( req ); } if( http_request_route( req, "notifications" ) ) { return handle_notifications(req); } else if( http_request_route( req, "filters" ) ) { http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); fprintf( f, "[]" ); return true; } else if( http_request_route( req, "statuses" ) ) { return route_statuses_1f(req); } else if( http_request_route( req, "timelines/" ) ) { if( http_request_route( req, "home" ) ) { return handle_timeline( req, tli_home ); } else if( http_request_route( req, "public" ) ) { return handle_timeline( req, tli_public ); } } else if( http_request_route( req, "apps/" ) ) { if( http_request_route( req, "verify_credentials" ) ) { http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); #include "src/view/api/verify_credentials.json.inc" return true; } } else if( http_request_route( req, "accounts/" ) ) { if( http_request_route( req, "verify_credentials" ) ) { struct account* owner = account_from_id(0); bool res = handle_mastodon_api_show_account( req, owner ); account_free(owner); return res; } else if( http_request_route( req, "relationships?" ) ) { return handle_relationships(req); } else if( http_request_route( req, "statuses" ) ) { return handle_timeline( req, tli_owner ); } else if( http_request_route( req, "search?" ) ) { return handle_search( req ); } else if( http_request_route_term(req,"") ) { struct account* owner = account_from_id(0); bool res = handle_mastodon_api_show_account( req, owner ); account_free(owner); return res; } int id = 0; if( !http_request_route_id( req, &id ) ) { return false; } struct account* a = account_from_id( id ); if( !a ) { return false; } if( http_request_route( req, "statuses" ) ) { bool res = handle_timeline( req, id ); account_free(a); return res; } else if( http_request_route( req, "following" ) ) { // TODO: implement stub http_request_send_headers( req, 200, "application/json", true ); FILE* f = http_request_get_response_body( req ); fprintf( f, "[" ); fprintf( f, "]" ); return true; } else if( http_request_route( req, "followers" ) ) { // TODO: implement stub http_request_send_headers( req, 200, "application/json", true ); struct { int* items; int count; } accounts; account_list_followers( a, 0, 32, &accounts ); FILE* f = http_request_get_response_body( req ); fprintf( f, "[" ); bool first = true; for( int i = 0; i < accounts.count; ++i ) { struct account* a2 = account_from_id( accounts.items[i] ); if( a2 ) { fprintf( f, first ? "\n" : ",\n" ); api_account_write_as_json( a2, f ); account_free(a2); first = false; } } fprintf( f, "]" ); free(accounts.items); return true; } else { bool res = handle_mastodon_api_show_account( req, a ); account_free(a); return res; } } return false; }