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.
375 lines
9.1 KiB
C
375 lines
9.1 KiB
C
#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 <stddef.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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;
|
|
}
|
|
|