Add start of CLI controller, put ActivityPub endpoints /following and followers in the right spot, implement account follow/unfollow (flaky), show status publish date

master
teknomunk 1 year ago
parent 229ebacd78
commit 1bcb3af898

@ -0,0 +1,57 @@
#include "cli.h"
#include "model/account.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
static char* binary_name = NULL;
static bool handle_command_follow( char** argv, int argc )
{
if( 0 != strcmp(argv[0],"follow") ) { return false; }
if( argc < 2 ) {
printf( "Usage: %s follow [account webfinger]\n", binary_name );
return true;
}
struct account* owner = account_from_id( owner_account_id );
struct account* to_follow = account_from_webfinger( argv[1] );
account_follow( owner, to_follow );
account_free(to_follow);
account_free(owner);
return true;
}
static bool handle_command_unfollow( char** argv, int argc )
{
if( 0 != strcmp(argv[0],"unfollow") ) { return false; }
if( argc < 2 ) {
printf( "Usage: %s unfollow [account webfinger]\n", binary_name );
return true;
}
struct account* owner = account_from_id( owner_account_id );
struct account* to_unfollow = account_from_webfinger( argv[1] );
account_unfollow( owner, to_unfollow );
account_free(to_unfollow);
account_free(owner);
return true;
}
void handle_command( char** argv, int argc )
{
binary_name = argv[0];
false
|| handle_command_follow(&argv[1],argc-1)
|| handle_command_unfollow(&argv[1],argc-1)
;
}

@ -0,0 +1,4 @@
#pragma once
void handle_command( char** argv, int argc );

@ -123,15 +123,44 @@ static bool route_undo_activity( struct ap_activity* act )
return false;
}
void add_post_to_timeline( int timeline_id, struct status* s )
{
struct timeline* tl = timeline_from_id(timeline_id);
timeline_add_post( tl, s );
timeline_free(tl);
}
static bool route_follower_post( struct ap_activity* act )
{
// TODO: check if the activity is by a follower
// Requires an object
// TODO: if there is a ref here, fetch the object
if( act->object.tag != apaot_activity ) { return false; }
struct ap_activity* obj = act->object.ptr;
// Is this activity from a follower?
struct account* actor_account = account_from_uri( act->actor );
if( !actor_account ) {
printf( "No local data, can't be follower\n" );
return false;
}
if( !account_does_follow( actor_account, owner_account_id ) ) {
printf( "Doesn't follower owner\n" );
return false;
}
// Create local status
struct status* s = status_from_activity(obj);
status_save_new(s);
// Create notification
// Add to timelines
add_post_to_timeline( 1, s );
add_post_to_timeline( 2, s );
add_post_to_timeline( actor_account->id, s );
return false;
// TODO: create notification if user notifications are on or this is part of a watched conversation
return true;
}
static bool route_mention( struct ap_activity* act )
{
@ -249,6 +278,13 @@ static bool route_create( struct ap_activity* act )
return false;
}
static bool route_accept( struct ap_activity* act )
{
// TODO: actually handle accepts
// In particular, this needs to change a follow request to a following accepted
return true;
}
static bool route_activity( struct ap_activity* act )
{
printf( "Handling %s\n", act->id );
@ -258,6 +294,7 @@ static bool route_activity( struct ap_activity* act )
case apat_follow: return route_follow(act);
case apat_like: return route_like(act);
case apat_create: return route_create(act);
case apat_accept: return route_accept(act);
case apat_emoji_react: return route_emoji_react(act);
default:
printf( "Unhandled activity type: %d\n", act->type );

@ -83,11 +83,11 @@ bool route_owner( struct http_request* req )
if( http_request_route( req, "/actor" ) ) {
if( http_request_route_term( req, "" ) ) {
return handle_owner_actor(req);
} else if( http_request_route_term( req, "/following" ) ) {
return handle_following(req);
} else if( http_request_route_term( req, "/followers" ) ) {
return handle_followers(req);
}
} else if( http_request_route_term( req, "/following" ) ) {
return handle_following(req);
} else if( http_request_route_term( req, "/followers" ) ) {
return handle_followers(req);
} else if( http_request_route_term( req, "/collections/featured" ) ) {
return handle_featured(req);
} else if( http_request_route_term( req, "" ) ) {

@ -1,6 +1,7 @@
#define _GNU_SOURCE
#include "ffdb/fs_list.h"
#include "ffdb/trie.h"
#include "collections/array.h"
#include "model/account.h"
@ -14,16 +15,13 @@
#include "format.h"
#include <string.h>
#include <stdlib.h>
#include <time.h>
void develop()
{
struct account* owner = account_from_id( owner_account_id );
struct account* to_follow = account_from_webfinger( "tester@pl.polaris-1.work" );
ap_activity_follow( owner, to_follow );
account_free( owner );
account_free( to_follow );
// TODO: remove memory leak
struct ap_activity* act = ap_activity_from_local_id(27);
ap_activity_free(act);
}

@ -1 +1 @@
Subproject commit 3408ca78fe4323e95addb18675569a714f7ae66a
Subproject commit e8b0c2ccde301586e53f20563718308a1fc51fff

@ -1 +1 @@
Subproject commit 05780d2140b239538f23f666c998130f3ec32cb8
Subproject commit 49b21a92cef63f7e35bdc1c78079310d78cd7fac

@ -15,6 +15,7 @@
#include "controller/outbox.h"
#include "controller/test.h"
#include "controller/indexer.h"
#include "controller/cli.h"
#include <curl/curl.h>
@ -101,6 +102,11 @@ int main( int argc, char* argv[] )
int section = -1;
if( ( argc > 1 ) && ( 0 != strncmp(argv[1],"--",2) ) ) {
handle_command( argv, argc );
goto exit;
}
for( int i = 1; i < argc; ++i ) {
const char* arg = argv[i];

@ -13,6 +13,7 @@
// Model
#include "model/server.h"
#include "model/ap/account.h"
#include "model/ap/activity.h"
#include "model/crypto/keys.h"
#include "model/notification.h"
@ -374,6 +375,56 @@ void account_remove_follower( struct account* a, struct account* follower )
notification_save( note );
notification_free( note );
}
void account_follow( struct account* a, struct account* to_follow )
{
char index[512];
char key[32];
char value[32];
// Federate Follow Activity
int act_id = ap_activity_follow( a, to_follow );
// Update following list
ffdb_trie_set(
format(index,512,"data/accounts/%d/following", a->id),
format(key,32,"%d", to_follow->id ),
format(value,32,"%d", act_id )
);
a->following_count = ffdb_trie_count(index);
// Save account data
account_save(a);
}
void account_unfollow( struct account* a, struct account* to_unfollow )
{
char index[512];
char key[32];
// Make sure the account to unfollow has previously been followed
char* res = ffdb_trie_get(
format(index,512,"data/accounts/%d/following", a->id),
format(key,32,"%d", to_unfollow->id)
);
if( !res ) {
printf( "%s is not following %s\n", a->account_url, to_unfollow->account_url );
return;
}
// Lookup the Activity used to federate following this account
struct ap_activity* act = ap_activity_from_local_id( atoi(res) );
free(res);
// Federate Undo Activity
ap_activity_undo(act,to_unfollow->id);
ap_activity_free(act);
// Update following list
ffdb_trie_remove( index, key );
a->following_count = ffdb_trie_count(index);
// Save account data
account_save(a);
}
void account_list_followers( struct account* a, int offset, int limit, void* id_array )
{
struct int_array {
@ -401,6 +452,21 @@ void account_list_followers( struct account* a, int offset, int limit, void* id_
free(keys.items);
array->count = keys.count;
}
bool account_does_follow( struct account* a, int account_id )
{
char index[512];
char key[32];
// Make sure the account to unfollow has previously been followed
char* value = ffdb_trie_get(
format(index,512,"data/accounts/%d/following", a->id),
format(key,32,"%d", account_id)
);
bool result = !!value;
free(value);
return result;
}
// TODO: move to controller/view
void api_account_write_as_json( struct account* a, FILE* f )

@ -57,9 +57,13 @@ struct crypto_keys* account_get_private_key( struct account* a );
void account_add_follower( struct account* a, struct account* follower );
void account_remove_follower( struct account* a, struct account* follower );
void account_follow( struct account* a, struct account* to_follow );
void account_unfollow( struct account* a, struct account* to_unfollow );
void account_list_followers( struct account* a, int offset, int limit, void* id_array );
void account_list_following( struct account* a, int offset, int limit, void* id_array );
bool account_does_follow( struct account* a, int account_id );
// TODO: move to controller/view and rename api_account_write_as_json
void api_account_write_as_json( struct account* a, FILE* f );

@ -27,10 +27,13 @@ struct ap_activity* ap_activity_new()
struct ap_activity* ap_activity_dup( struct ap_activity* act )
{
struct ap_activity* new_act = ap_activity_new();
memset(new_act,0,sizeof(*new_act));
new_act->id = strdup(act->id);
new_act->local_id = act->local_id;
new_act->type = act->type;
new_act->actor = strdup(act->actor);
new_act->published = act->published;
array_dup( &new_act->to, sizeof(char*), &act->to );
for( int i = 0; i < new_act->to.count; ++i ) {
@ -192,7 +195,7 @@ void ap_activity_react( struct status* s, const char* react )
struct ap_activity* ap_activity_create_follow( struct account* follower, struct account* following )
{
int id = fs_list_get("data/activities/HEAD") + 1;
//fs_list_set( "data/activities/HEAD", id );
fs_list_set( "data/activities/HEAD", id );
struct ap_activity* act = ap_activity_new();
act->local_id = id;
@ -209,28 +212,74 @@ struct ap_activity* ap_activity_create_follow( struct account* follower, struct
return act;
}
void ap_activity_follow( struct account* follower, struct account* following )
int ap_activity_follow( struct account* follower, struct account* following )
{
int res = -1;
struct ap_activity* act = ap_activity_create_follow( follower, following );
/*
ap_activity_write_to_FILE( act, stdout );
printf("\nRDF:\n");
ap_activity_write_normalized_rdf(act, stdout);
//*/
if( !act ) { goto failed; }
ap_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_activity_free(act);
return res;
failed:
res = -1;
goto cleanup;
}
struct ap_activity* ap_activity_create_undo( struct ap_activity* act_to_undo )
{
int id = fs_list_get("data/activities/HEAD") + 1;
fs_list_set( "data/activities/HEAD", id );
struct ap_activity* act = ap_activity_new();
act->local_id = id;
act->id = aformat( "https://%s/activity/%d", g_server_name, id );
act->actor = strdup( act_to_undo->actor );
act->type = apat_undo;
act->published = time(NULL);
act->object.tag = apaot_activity;
act->object.ptr = ap_activity_dup(act_to_undo);
char* to = strdup(act_to_undo->object.ref);
array_append( &act->to, sizeof(to), &to );
return act;
}
void ap_activity_undo( struct activity* act )
void ap_activity_undo( struct ap_activity* act, int deliver_to_account_id )
{
// TODO: implement
struct ap_activity* undo_act = ap_activity_create_undo( act );
if( !undo_act ) { goto failed; }
ap_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_activity_free(undo_act);
return;
failed:
goto cleanup;
}

@ -132,7 +132,9 @@ struct ap_activity* ap_activity_create_emoji_react( struct status* s, const char
void ap_activity_react( struct status* s, const char* react );
struct account;
void ap_activity_follow( struct account* follower, struct account* following );
int ap_activity_follow( struct account* follower, struct account* following );
void ap_activity_unfollow( struct account* follower, struct account* to_unfollow );
void ap_activity_undo( struct activity* act );
struct ap_activity* ap_activity_create_undo( struct ap_activity* act );
void ap_activity_undo( struct ap_activity* act, int deliver_to_account_id );

@ -25,7 +25,7 @@ static struct json_object_field status_layout[] = {
//{ "content", offsetof( struct status, source ), false, &json_field_string },
{ "source", offsetof( struct status, source ), false, &json_field_string },
{ "sensitive", offsetof( struct status, sensitive ), true, &json_field_bool },
{ "published", offsetof( struct status, published ), false, &json_field_date_time },
{ "media", offsetof( struct status, media ), false, &json_field_array_of, &json_field_string },
{ "reacts", offsetof( struct status, reacts ), false, &json_field_array_of, &status_react_type },
@ -116,6 +116,7 @@ struct status* status_from_activity( struct ap_activity* act )
struct account* a = account_from_uri(act->actor);
s->account_id = a->id;
s->published = act->published;
s->source = strdup(act->source);
//s->content = status_render_source(s);

@ -2,6 +2,7 @@
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
struct account;
@ -14,6 +15,7 @@ struct status
char* content; // Deprecate from file system data and render when loading
char* source;
bool sensitive;
time_t published;
struct {
char** items;

@ -0,0 +1,7 @@
{
"@context":"https://www.w3.org/ns/activitystreams",
"first":%( activity_pub_write_followers_page( 1, f ); ),
"id":"https://%s{g_server_name}/owner/following",
"totalItems":%d{ a->following_count },
"type":"OrderedCollection"
}

@ -0,0 +1,10 @@
{
"id":"https://%s{server_name}/owner/following?page=%d{page_number}",
"next":"https://%s{server_name}/owner/following?page=%{page_number}",
"orderedItems":[%( for( int i = 0; i < accounts.count; ++i ) { )
%( json_write_string( accounts.items[i]->account_url, f ); )%s{ i == accounts.count-1 ? "" : "," }
%( } )],
"partOf":"https://%s{server_name}/owner/following",
"totalItems":%{ a->following_count },
"type":"OrderedCollectionPage"
}

@ -4,7 +4,7 @@
"application": null,
"bookmarked": false,
"card": null,
"created_at": "2022-12-12T02:52:24.000Z",
"created_at": %( json_write_date_time_string( s->published, f ); ),
"emojis": [],
"favourited": false,
"favourites_count": %d{ s->likes.count },

Loading…
Cancel
Save