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.
489 lines
13 KiB
C
489 lines
13 KiB
C
#define _GNU_SOURCE
|
|
#include "activity.h"
|
|
|
|
// Submodules
|
|
#include "util/format.h"
|
|
#include "ffdb/fs_list.h"
|
|
#include "collections/array.h"
|
|
#include "collections/sort/bubble.h"
|
|
#include "ap/object.h"
|
|
|
|
// Model
|
|
#include "model/server.h"
|
|
#include "model/account.h"
|
|
#include "model/status.h"
|
|
#include "model/status/poll.h"
|
|
#include "model/emoji.h"
|
|
#include "model/media.h"
|
|
#include "model/outbox_envelope.h"
|
|
|
|
// Standard Library
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
void activity_allocate_local_id( struct ap_object* obj )
|
|
{
|
|
int id = fs_list_inc("data/activities/HEAD");
|
|
|
|
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;
|
|
}
|
|
|
|
struct ap_object* activity_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 = activity_new_local_activity();
|
|
activity_allocate_local_id(accept);
|
|
|
|
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);
|
|
activity_deliver(accept);
|
|
|
|
ap_object_free(accept);
|
|
}
|
|
|
|
struct ap_object* activity_create_EmojiReact( struct status* s, struct account* reactor, const char* react )
|
|
{
|
|
if( !*react ) {
|
|
return NULL;
|
|
}
|
|
|
|
struct account* a = account_from_id( s->account_id );
|
|
|
|
struct ap_object* act = activity_new_local_activity();
|
|
activity_allocate_local_id(act);
|
|
act->id = aformat( "https://%s/activity/%d", g_server->domain, act->local_id );
|
|
act->actor = strdup(reactor->account_id);
|
|
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_id );
|
|
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_id );
|
|
array_append( &act->cc, sizeof(cc), &cc );
|
|
|
|
account_free(a);
|
|
return act;
|
|
}
|
|
|
|
void activity_react( struct account* reactor, struct status* s, const char* react )
|
|
{
|
|
struct ap_object* act = activity_create_EmojiReact(s,reactor,react);
|
|
if( !act ) { return; }
|
|
|
|
activity_save(act);
|
|
activity_deliver(act);
|
|
ap_object_free(act);
|
|
}
|
|
|
|
struct ap_object* activity_create_Follow( struct account* follower, struct account* following )
|
|
{
|
|
struct ap_object* act = activity_new_local_activity();
|
|
activity_allocate_local_id(act);
|
|
act->id = aformat( "https://%s/activity/%d", g_server->domain, act->local_id );
|
|
act->actor = strdup( follower->account_id );
|
|
act->type = ap_Follow;
|
|
act->published = time(NULL);
|
|
act->object.tag = apaot_ref;
|
|
act->state = strdup("pending");
|
|
act->object.ref = strdup( following->account_id );
|
|
|
|
char* to = strdup(following->account_id);
|
|
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);
|
|
activity_deliver(act);
|
|
res = act->local_id;
|
|
|
|
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 = activity_new_local_activity();
|
|
activity_allocate_local_id(act);
|
|
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);
|
|
activity_deliver(undo_act);
|
|
cleanup:
|
|
ap_object_free(undo_act);
|
|
return;
|
|
failed:
|
|
goto cleanup;
|
|
}
|
|
|
|
struct ap_object* activity_create_Like( struct status* s )
|
|
{
|
|
struct account* a = account_from_id( s->account_id );
|
|
|
|
struct ap_object* act = activity_new_local_activity();
|
|
activity_allocate_local_id(act);
|
|
act->id = aformat( "https://%s/activity/%d", g_server->domain, act->local_id );
|
|
act->actor = strdup(a->account_id);
|
|
act->type = ap_Like;
|
|
act->published = time(NULL);
|
|
act->object.tag = apaot_ref;
|
|
act->object.ref = strdup( s->url );
|
|
|
|
char* to = aformat( "https://%s/owner/followers", g_server->domain );
|
|
array_append( &act->to, sizeof(to), &to );
|
|
to = strdup(a->account_id);
|
|
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_id);
|
|
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);
|
|
ap_object_free(act);
|
|
|
|
return 1;
|
|
}
|
|
|
|
struct ap_object* activity_create_Note( struct status* s )
|
|
{
|
|
struct ap_object* act = activity_new_local_activity();
|
|
act->id = strdup( s->url );
|
|
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,g_server->domain));
|
|
if( s->in_reply_to ) {
|
|
struct status* s_in_reply_to = status_from_id( s->in_reply_to );
|
|
if( s_in_reply_to ) {
|
|
act->in_reply_to = strdup( s_in_reply_to->url );
|
|
status_free(s_in_reply_to);
|
|
}
|
|
}
|
|
if( s->quote_id ) {
|
|
struct status* s_quote_post = status_from_id( s->quote_id );
|
|
if( s_quote_post ) {
|
|
act->quote_url = strdup( s_quote_post->url );
|
|
status_free(s_quote_post);
|
|
}
|
|
}
|
|
|
|
// Set the replies
|
|
act->replies.tag = apaot_ref;
|
|
act->replies.ref = aformat( "https://%s/note/%d/replies", g_server->domain, s->id );
|
|
|
|
/* set account related parameters */ {
|
|
struct account* a = account_from_id( s->account_id );
|
|
act->actor = strdup( a->account_id );
|
|
act->attributed_to = strdup( a->account_id );
|
|
account_free(a);
|
|
}
|
|
|
|
char* str = NULL;
|
|
switch( s->visibility ) {
|
|
case status_visibility_public:
|
|
{
|
|
str = strdup("https://www.w3.org/ns/activitystreams#Public");
|
|
array_append( &act->to, sizeof(str), &str );
|
|
}; break;
|
|
}
|
|
|
|
for( int i = 0; i < s->media2.count; ++i ) {
|
|
struct media* m = s->media2.items[i];
|
|
|
|
struct ap_object* att;
|
|
att = malloc(sizeof(*att));
|
|
memset(att,0,sizeof(*att));
|
|
|
|
att->type = ap_Document;
|
|
ap_object_array_append_ref( &att->url, strdup(m->remote_url) );
|
|
att->media_type = strdup(m->content_type);
|
|
att->name = strdup("");
|
|
|
|
array_append( &act->attachments, sizeof(att), &att );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
for( int i = 0; i < s->tags.count; ++i ) {
|
|
struct ap_activity_tag* tag;
|
|
tag = malloc(sizeof(*tag));
|
|
memset(tag,0,sizeof(*tag));
|
|
|
|
tag->type = aptag_hashtag;
|
|
tag->name = aformat("#%s", s->tags.items[i] );
|
|
tag->href = aformat( "https://%s/tag/%s", g_server->domain, s->tags.items[i] );
|
|
|
|
array_append( &act->tags, sizeof(tag), &tag );
|
|
}
|
|
|
|
switch( s->visibility ) {
|
|
case status_visibility_public:
|
|
case status_visibility_unlisted:
|
|
str = aformat( "https://%s/owner/followers", g_server->domain );
|
|
array_append( &act->cc, sizeof(str), &str );
|
|
break;
|
|
}
|
|
|
|
for( int i = 0; i < s->mentions.count; ++i ) {
|
|
struct account* mentioned = account_from_id( s->mentions.items[i] );
|
|
|
|
str = strdup(mentioned->account_id);
|
|
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_id);
|
|
tag->name = aformat( "%s@%s", mentioned->handle, mentioned->server );
|
|
array_append( &act->tags, sizeof(tag), &tag );
|
|
|
|
account_free(mentioned);
|
|
}
|
|
|
|
// Poll data
|
|
if( s->poll ) {
|
|
struct {
|
|
struct ap_object_ptr_or_ref* items;
|
|
int count;
|
|
} *options_list;
|
|
if( s->poll->multiple_choice ) {
|
|
options_list = (void*)&act->any_of;
|
|
} else {
|
|
options_list = (void*)&act->one_of;
|
|
}
|
|
|
|
// Change from Note to Question if this post includes a poll
|
|
act->type = ap_Question;
|
|
act->closed = s->poll->expires_at;
|
|
act->end_time = s->poll->expires_at;
|
|
|
|
for( int i = 0; i < s->poll->options.count; ++i ) {
|
|
struct status_poll_option* poll_option = s->poll->options.items[i];
|
|
struct ap_object_ptr_or_ref option;
|
|
option.tag = apaot_object;
|
|
option.ptr = ap_object_new();
|
|
option.ptr->type = ap_Note;
|
|
option.ptr->name = strdup( poll_option->title );
|
|
|
|
//if( poll_option->votes.count > 0 ) {
|
|
struct ap_object* replies = ap_object_new();
|
|
option.ptr->replies.tag = apaot_object;
|
|
option.ptr->replies.ptr = replies;
|
|
replies->type = ap_Collection;
|
|
replies->total_items = poll_option->votes.count;
|
|
//}
|
|
|
|
array_append( options_list, sizeof(option), &option );
|
|
}
|
|
}
|
|
|
|
return act;
|
|
}
|
|
|
|
struct ap_object* activity_create_Create( struct ap_object* object )
|
|
{
|
|
struct ap_object* act = activity_new_local_activity();
|
|
activity_allocate_local_id(act);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static int compare_envelope_counts( struct collection it, void* a, void* b )
|
|
{
|
|
struct outbox_envelope* ea = a;
|
|
struct outbox_envelope* eb = b;
|
|
|
|
if( ea->count < eb->count ) {
|
|
return -1;
|
|
} else if( ea->count > eb->count ) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
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] );
|
|
}
|
|
|
|
// Sort so that oel[0] has the lowest count and oel[size-1] has the highest
|
|
struct collection c;
|
|
static struct item_type_vtable itable = {
|
|
.size = sizeof(oel.items[0]),
|
|
.compare = compare_envelope_counts,
|
|
};
|
|
c.ptr = &oel;
|
|
c.vtable = &array_vtable;
|
|
c.itable = &itable;
|
|
collection_sort_bubble( c, false );
|
|
|
|
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" );
|
|
}
|
|
|
|
|