#define _GNU_SOURCE #include "activity.h" // Submodules #include "util/format.h" #include "ffdb/fs_list.h" #include "collections/array.h" #include "ap/object.h" // Model #include "model/server.h" #include "model/account.h" #include "model/status.h" #include "model/emoji.h" #include "model/media.h" #include "model/outbox_envelope.h" // Standard Library #include #include #include void activity_allocate_local_id( struct ap_object* obj ) { int id = fs_list_get("data/activities/HEAD") + 1; fs_list_set( "data/activities/HEAD", id ); obj->local_id = id; } void activity_save( struct ap_object* act ) { char filename[512]; snprintf( filename, sizeof(filename), "data/activities/%d.json", act->local_id ); json_write_object_layout_to_file( filename, "\t", ap_object_layout, act ); } struct ap_object* activity_from_local_id( int id ) { struct ap_object* act = ap_object_new(); char filename[512]; snprintf( filename, sizeof(filename), "data/activities/%d.json", id ); if( !json_read_object_layout_from_file( filename, ap_object_layout, act ) ) { ap_object_free(act); return NULL; } 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 ) { struct ap_object* accept; accept = malloc(sizeof(*accept)); memset(accept,0,sizeof(*accept)); activity_allocate_local_id(accept); ap_object_add_context( accept, "https://www.w3.org/ns/activitystreams"); ap_object_add_context( accept, "https://apogee.polaris-1.work/schemas/litepub-0.1.jsonld"); asprintf( &accept->id,"https://%s/activity/%d", g_server->domain, accept->local_id ); accept->type = ap_Accept; asprintf( &accept->actor, "https://%s/owner/actor", g_server->domain ); char* new_act_actor = strdup(act->actor); array_append( &accept->to, sizeof(char*), &new_act_actor ); accept->object.tag = apaot_activity; accept->object.ptr = ap_object_dup(act); return accept; } void activity_accept( struct ap_object* act, int deliver_to_account_id ) { struct ap_object* accept = activity_create_Accept(act); activity_save(accept); struct outbox_envelope* env = outbox_envelope_new(); env->activity_id = accept->local_id; env->account_id = deliver_to_account_id; outbox_envelope_save( env ); outbox_envelope_free( env ); ap_object_free(accept); } struct ap_object* activity_create_EmojiReact( struct status* s, const char* react ) { if( !*react ) { return NULL; } struct account* a = account_from_id( s->account_id ); 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 = pleroma_EmojiReact; act->content.content = safe_strdup(react); act->published = time(NULL); act->object.tag = apaot_ref; act->object.ref = strdup( s->url ); char* to = aformat( "%s/followers", a->account_url ); array_append( &act->to, sizeof(to), &to ); char* cc = strdup( "https://www.w3.org/ns/activitystreams#Public" ); array_append( &act->cc, sizeof(cc), &cc ); cc = strdup( a->account_url ); array_append( &act->cc, sizeof(cc), &cc ); account_free(a); return act; } void activity_react( struct status* s, const char* react ) { struct ap_object* act = activity_create_EmojiReact(s,react); if( !act ) { return; } activity_save(act); struct outbox_envelope* env = outbox_envelope_new(); env->activity_id = act->local_id; env->account_id = s->account_id; outbox_envelope_save( env ); outbox_envelope_free( env ); ap_object_free(act); } struct ap_object* activity_create_Follow( struct account* follower, struct account* following ) { struct ap_object* act = ap_object_new(); 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 = strdup( follower->account_url ); act->type = ap_Follow; act->published = time(NULL); act->object.tag = apaot_ref; act->state = strdup("pending"); act->object.ref = strdup( following->account_url ); char* to = strdup(following->account_url); array_append( &act->to, sizeof(to), &to ); return act; } int activity_follow( struct account* follower, struct account* following ) { int res = -1; struct ap_object* act = activity_create_Follow( follower, following ); if( !act ) { goto failed; } activity_save(act); res = act->local_id; struct outbox_envelope* env = outbox_envelope_new(); if( !env ) { goto failed; } env->activity_id = act->local_id; env->account_id = following->id; outbox_envelope_save( env ); outbox_envelope_free( env ); cleanup: ap_object_free(act); return res; failed: res = -1; goto cleanup; } struct ap_object* activity_create_Undo( struct ap_object* act_to_undo ) { struct ap_object* act = ap_object_new(); 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 = strdup( act_to_undo->actor ); act->type = ap_Undo; act->published = time(NULL); act->object.tag = apaot_activity; act->object.ptr = ap_object_dup(act_to_undo); char* to = strdup(act_to_undo->object.ref); array_append( &act->to, sizeof(to), &to ); return act; } void activity_undo( struct ap_object* act, int deliver_to_account_id ) { struct ap_object* undo_act = activity_create_Undo( act ); if( !undo_act ) { goto failed; } activity_save(undo_act); /* printf( "act=" ); ap_activity_write_to_FILE( act, stdout ); printf( "\nundo_act="); ap_activity_write_to_FILE( undo_act, stdout ); printf( "\n" ); */ struct outbox_envelope* env = outbox_envelope_new(); if( !env ) { goto failed; } env->activity_id = undo_act->local_id; env->account_id = deliver_to_account_id; outbox_envelope_save( env ); outbox_envelope_free( env ); cleanup: ap_object_free(undo_act); return; failed: goto cleanup; } struct ap_object* activity_create_Like( struct status* s ) { 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 ) { struct ap_object* act = activity_create_Like( s ); if( !act ) { return 0; } activity_save(act); activity_deliver( act ); return 1; } struct ap_object* activity_create_Note( struct status* s ) { 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; if( s->source ) { act->source.content = strdup(s->source); } act->content.content = strdup(status_render_source(s)); if( s->in_reply_to ) { struct status* s_in_reply_to = status_from_id( s->in_reply_to ); act->in_reply_to = strdup( s_in_reply_to->url ); status_free(s_in_reply_to); } /* set account related parameters */ { struct account* a = account_from_id( s->account_id ); act->actor = strdup( a->account_url ); act->attributed_to = strdup( a->account_url ); account_free(a); } char* str = strdup("https://www.w3.org/ns/activitystreams#Public"); array_append( &act->to, sizeof(str), &str ); for( int i = 0; i < s->media.count; ++i ) { struct media* m = media_from_local_uri( s->media.items[i] ); if( m ) { struct ap_object* att; att = malloc(sizeof(*att)); memset(att,0,sizeof(*att)); att->type = ap_Document; att->url = strdup(s->media.items[i]); att->media_type = strdup(m->content_type); att->name = strdup(""); array_append( &act->attachments, sizeof(att), &att ); } media_free(m); } for( int i = 0; i < s->emoji.count; ++i ) { struct emoji* e = s->emoji.items[i]; struct ap_activity_tag* tag; tag = malloc(sizeof(*tag)); memset(tag,0,sizeof(*tag)); tag->type = aptag_emoji; tag->icon.url = strdup(e->url); tag->id = strdup(e->url); tag->icon.type = ap_Image; tag->name = aformat(":%s:", e->shortcode ); array_append( &act->tags, sizeof(tag), &tag ); } str = aformat( "https://%s/owner/followers", g_server->domain ); array_append( &act->cc, sizeof(str), &str ); for( int i = 0; i < s->mentions.count; ++i ) { struct account* mentioned = account_from_id( s->mentions.items[i] ); str = strdup(mentioned->account_url); array_append( &act->to, sizeof(str), &str ); struct ap_activity_tag* tag; tag = malloc(sizeof(*tag)); memset(tag,0,sizeof(*tag)); tag->type = aptag_mention; tag->href = strdup(mentioned->account_url); tag->name = aformat( "%s@%s", mentioned->handle, mentioned->server ); array_append( &act->tags, sizeof(tag), &tag ); account_free(mentioned); } return act; } struct ap_object* activity_create_Create( struct ap_object* object ) { struct ap_object* act = ap_object_new(); 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->type = ap_Create; act->id = aformat( "https://%s/activity/%d", g_server->domain, act->local_id ); act->object.tag = apaot_activity; act->object.ptr = ap_object_dup(object); act->actor = strdup( object->actor ); act->published = object->published; char* str; for( int i = 0; i < object->to.count; ++i ) { str = strdup( object->to.items[i] ); array_append( &act->to, sizeof(str), &str ); } for( int i = 0; i < object->cc.count; ++i ) { str = strdup( object->cc.items[i] ); array_append( &act->cc, sizeof(str), &str ); } return act; } static void deliver_to( struct outbox_envelope_list* oel, struct ap_object* act, const char* uri ) { char owner_followers[512]; snprintf( owner_followers,512, "https://%s/owner/followers", g_server->domain ); if( 0 == strcmp( owner_followers, uri ) ) { struct account* owner = account_from_id( owner_account_id ); account_deliver_activity_to_followers( owner, act, oel ); account_free(owner); return; } struct account* a = account_from_uri( uri ); if( a ) { account_deliver_activity( a, act, oel ); account_free(a); return; } } void activity_deliver( struct ap_object* act ) { printf( "activity_deliver( act = %s )\n", act->id ); struct outbox_envelope_list oel; memset(&oel,0,sizeof(oel)); // Build the outbox list based on to,cc,bcc for( int i = 0; i < act->to.count; ++i ) { deliver_to( &oel, act, act->to.items[i] ); } for( int i = 0; i < act->cc.count; ++i ) { deliver_to( &oel, act, act->cc.items[i] ); } for( int i = 0; i < act->bcc.count; ++i ) { deliver_to( &oel, act, act->bcc.items[i] ); } printf( "Delivering to %d inboxes\n", oel.count ); outbox_envelope_list_save(&oel); outbox_envelope_list_free_composite(&oel); activity_save(act); ap_object_write_to_FILE( act, stdout ); printf( "Delivery scheduling finished.\n" ); }