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.

312 lines
7.1 KiB
C

#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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
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(&params,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, &params ) ) { 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;
}