diff --git a/src/controller/api/accounts.c b/src/controller/api/accounts.c index 16775cc..cfd8c15 100644 --- a/src/controller/api/accounts.c +++ b/src/controller/api/accounts.c @@ -20,9 +20,7 @@ bool get_local_account_id( struct http_request* req, int* id ) char* id_str = http_request_route_get_dir_or_file(req); if( !id_str || !*id_str ) { goto failed; } - *id = -1; - sscanf( id_str, "%d", id ); - if( *id == -1 ) { + if( 1 != sscanf( id_str, "%d", id ) ) { struct account* a = account_from_webfinger(id_str); if( a ) { *id = a->id; diff --git a/src/controller/api/status.c b/src/controller/api/status.c index b0edd61..6637757 100644 --- a/src/controller/api/status.c +++ b/src/controller/api/status.c @@ -232,6 +232,19 @@ bool handle_show_bookmarks( struct http_request* req ) 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); @@ -277,16 +290,20 @@ bool route_statuses( struct http_request* req ) 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; } -failed: - result = false; + + goto failed; cleanup: status_free(s); return result; success: result = true; goto cleanup; +failed: + result = false; } diff --git a/src/model/account.c b/src/model/account.c index 1c644ea..c6dd4f4 100644 --- a/src/model/account.c +++ b/src/model/account.c @@ -65,6 +65,7 @@ static struct json_field_type string_pair_type = { static struct json_object_field account_layout[] = { JSON_FIELD_STRING( handle, true ), JSON_FIELD_BOOL( local, false ), + JSON_FIELD_BOOL( defunct, false ), JSON_FIELD_STRING( server, true ), JSON_FIELD_STRING( display_name, true ), JSON_FIELD_BOOL( stub, false ), @@ -118,7 +119,7 @@ static struct account* new_system_account() a = malloc(sizeof(*a)); memset(a,0,sizeof(*a)); - a->id = -1; + a->id = system_account_id; a->handle = strdup("system"); a->display_name = strdup("Apogee System"); a->account_type = at_bot, @@ -151,6 +152,7 @@ struct account* account_from_id( int id ) { switch( id ) { case -1: + case system_account_id: // System account return new_system_account(); } diff --git a/src/model/account.h b/src/model/account.h index 7ca1590..fd3bbd2 100644 --- a/src/model/account.h +++ b/src/model/account.h @@ -3,6 +3,7 @@ #include #include #include +#include struct crypto_keys; struct status; @@ -11,7 +12,7 @@ struct outbox_envelope; struct outbox_envelope_list; enum { - system_account_id = -1, + system_account_id = INT_MAX, owner_account_id = 0, home_timeline_id = 1, public_timeline_id = 2, @@ -39,6 +40,7 @@ struct account char* server; char* display_name; bool stub; + bool defunct; time_t next_stub_recheck; int account_type; diff --git a/src/model/account/fed_act.c b/src/model/account/fed_act.c index 3d6190b..b92f3ad 100644 --- a/src/model/account/fed_act.c +++ b/src/model/account/fed_act.c @@ -19,6 +19,10 @@ void account_deliver_activity( struct account* a, struct ap_object* act, struct outbox_envelope_list* oel ) { + if( a->defunct ) { + printf( "Account %s id defunct, not delivering activity\n" ); + return; + } printf( "Delivering activity %s to account %s\n", act->id, a->account_url ); if( a->shared_inbox ) { diff --git a/src/model/activity.c b/src/model/activity.c index cc0f976..d2e12b5 100644 --- a/src/model/activity.c +++ b/src/model/activity.c @@ -49,6 +49,14 @@ struct ap_object* activity_from_local_id( int id ) return act; } +static struct ap_object* new_local_activity() +{ + struct ap_object* act = ap_object_new(); + ap_object_add_context( act, "https://www.w3.org/ns/activitystreams"); + char buffer[512]; + ap_object_add_context( act, format( buffer, 512, "https://%s/schemas/litepub-0.1.jsonld", g_server->domain ) ); + return act; +} struct ap_object* activity_create_Accept( struct ap_object* act ) { @@ -91,10 +99,8 @@ struct ap_object* activity_create_EmojiReact( struct status* s, const char* reac struct account* a = account_from_id( s->account_id ); - struct ap_object* act = ap_object_new(); + struct ap_object* act = new_local_activity(); activity_allocate_local_id(act); - ap_object_add_context( act, "https://www.w3.org/ns/activitystreams"); - ap_object_add_context( act, "https://apogee.polaris-1.work/schemas/litepub-0.1.jsonld"); act->id = aformat( "https://%s/activity/%d", g_server->domain, act->local_id ); act->actor = aformat( "https://%s/owner/actor", g_server->domain ); act->type = pleroma_EmojiReact; @@ -222,21 +228,48 @@ failed: struct ap_object* activity_create_Like( struct status* s ) { - return NULL; + struct ap_object* act = new_local_activity(); + activity_allocate_local_id(act); + act->id = aformat( "https://%s/activity/%d", g_server->domain, act->local_id ); + act->actor = aformat( "https://%s/owner/actor", g_server->domain ); + act->type = ap_Like; + act->published = time(NULL); + act->object.tag = apaot_ref; + act->object.ref = strdup( s->url ); + + struct account* a = account_from_id( s->account_id ); + char* to = aformat( "https://%s/owner/followers", g_server->domain ); + array_append( &act->to, sizeof(to), &to ); + to = strdup(a->account_url); + array_append( &act->to, sizeof(to), &to ); + for( int i = 0; i < s->mentions.count; ++i ) { + struct account* mentioned = account_from_id( s->mentions.items[i] ); + to = strdup(mentioned->account_url); + array_append( &act->to, sizeof(to), &to ); + account_free(mentioned); + } + + char* cc = strdup( "https://www.w3.org/ns/activitystreams#Public" ); + array_append( &act->cc, sizeof(cc), &cc ); + + account_free(a); + return act; } int activity_like( struct status* s ) { - printf( "TODO: like\n" ); + struct ap_object* act = activity_create_Like( s ); + if( !act ) { return 0; } + + activity_save(act); + activity_deliver( act ); - return -1; + return 1; } struct ap_object* activity_create_Note( struct status* s ) { - struct ap_object* act = ap_object_new(); - ap_object_add_context( act, "https://www.w3.org/ns/activitystreams"); - ap_object_add_context( act, "https://apogee.polaris-1.work/schemas/litepub-0.1.jsonld"); + struct ap_object* act = new_local_activity(); act->id = aformat( "https://%s/note/%d", g_server->domain, s->id ); act->type = ap_Note; act->published = s->published; diff --git a/src/model/notification.c b/src/model/notification.c index 5ab4d0f..d40d04f 100644 --- a/src/model/notification.c +++ b/src/model/notification.c @@ -77,6 +77,7 @@ void notification_free( struct notification* note ) if( !note ) { return; } free(note->react); + status_free(note->system_status); free(note); } diff --git a/src/model/notification.h b/src/model/notification.h index e66ee7a..67270e5 100644 --- a/src/model/notification.h +++ b/src/model/notification.h @@ -4,6 +4,7 @@ #include #include +struct status; struct notification { int debug; @@ -11,6 +12,7 @@ struct notification int id; int account_id; int status_id; + struct status* system_status; int ref_account_id; time_t created_at; bool is_muted; diff --git a/src/model/status.c b/src/model/status.c index 842aa9a..def393b 100644 --- a/src/model/status.c +++ b/src/model/status.c @@ -423,6 +423,7 @@ struct status* status_new_system_unfollow( int account_id ) memset(s,0,sizeof(*s)); s->id = -1; + s->published = time(NULL); s->account_id = -1; asprintf( &s->content, "%s unfollowed you\n", a->display_name ); s->sensitive = true; @@ -441,9 +442,10 @@ struct status* status_new_system_block( int account_id ) memset(s,0,sizeof(*s)); s->id = -1; + s->published = time(NULL); s->account_id = -1; - asprintf( &s->content, "%s blocked you\n", a->display_name ); - s->sensitive = true; + asprintf( &s->content, "%s blocked you. View their account here.", a->display_name, a->account_url ); + s->sensitive = false; account_free(a); diff --git a/src/view/api/Account.c b/src/view/api/Account.c index 5bdd4ac..45c006b 100644 --- a/src/view/api/Account.c +++ b/src/view/api/Account.c @@ -98,7 +98,7 @@ bool int_to_string_callback( void* field_data, bool is_read, char** res ) if( is_read ) { return 1 == sscanf( *res, "%d", field ); } else { - *res = aformat( "%018d", *field ); + *res = aformat( "%018u", *field ); return true; } } diff --git a/src/view/api/Notification.c b/src/view/api/Notification.c index 3b88e7c..8c34782 100644 --- a/src/view/api/Notification.c +++ b/src/view/api/Notification.c @@ -5,6 +5,7 @@ #include "format.h" #include "model/notification.h" +#include "model/status.h" #include "view/api/Status.h" #include "view/api/Account.h" @@ -52,6 +53,12 @@ static struct json_object_field api_Notification_layout[] = { .key = "status", .offset = offsetof( OBJ_TYPE, status_id ), .required = false, + .type = &api_Status_from_id_type, + }, + { + .key = "status", + .offset = offsetof( OBJ_TYPE, system_status ), + .required = false, .type = &api_Status_type, }, { @@ -73,16 +80,15 @@ void api_Notification_write( struct notification* note, FILE* f, int indent ) .indent = indent, }; - /* - switch( n->type ) { + // Handle notifications requiring a system-generated note as they are not part of Mastodon or Pleroma + switch( note->type ) { case nt_unfollow: - s = status_new_system_unfollow( n->ref_account_id ); + note->system_status = status_new_system_unfollow( note->ref_account_id ); break; case nt_block: - s = status_new_system_block( n->ref_account_id ); + note->system_status = status_new_system_block( note->ref_account_id ); break; } - */ json_write_pretty_object_layout( &jw, api_Notification_layout, note ); } diff --git a/src/view/api/Relationship.c b/src/view/api/Relationship.c index 9415c56..5cde043 100644 --- a/src/view/api/Relationship.c +++ b/src/view/api/Relationship.c @@ -31,7 +31,7 @@ static struct json_object_field Relationship_layout[] = { void api_Relationship_init( struct Relationship* r, struct account* a, struct account* rel ) { memset( r, 0, sizeof(*r) ); - r->id = aformat( "%d",rel->id ); + r->id = aformat( "%018u",rel->id ); r->followed_by = account_does_follow( rel, a->id ); r->following = account_does_follow( a, rel->id ); } diff --git a/src/view/api/Status.c b/src/view/api/Status.c index 22a6e09..71de983 100644 --- a/src/view/api/Status.c +++ b/src/view/api/Status.c @@ -38,7 +38,7 @@ static bool write_in_reply_to( struct json_writer* jw, const char* field_name, v json_write_indention(jw); json_write_field_name(jw,"in_reply_to_id"); - fprintf( jw->f, "\"%018d\"", in_reply_to->id ); + fprintf( jw->f, "\"%018u\"", in_reply_to->id ); } else { json_write_field_name(jw,"in_reply_to_account_id"); fprintf( jw->f, "null" ); @@ -357,7 +357,7 @@ struct json_object_field api_Status_layout[] = { .fixed_bool = false, }, { - .key = "favourited_count", + .key = "favourites_count", .required = true, .offset = offsetof( OBJ_TYPE, likes.count ), .type = &json_field_integer, @@ -457,11 +457,16 @@ static bool api_Status_writer( struct json_writer* jw, const char* field_name, v return true; } -struct json_field_type api_Status_type = { +struct json_field_type api_Status_from_id_type = { .writer = api_Status_writer, .size = sizeof(int), .layout = api_Status_layout, }; +struct json_field_type api_Status_type = { + .writer = json_field_object_type_writer, + .size = sizeof(struct status*), + .layout = api_Status_layout, +}; void api_Status_write( struct status* s, FILE* f, int indent ) { diff --git a/src/view/api/Status.h b/src/view/api/Status.h index 23fe2b6..af51603 100644 --- a/src/view/api/Status.h +++ b/src/view/api/Status.h @@ -4,6 +4,7 @@ struct json_field_type; struct status; +extern struct json_field_type api_Status_from_id_type; extern struct json_field_type api_Status_type; void api_Status_write( struct status* s, FILE* f, int indent );