Loop outbox processing, add incoming mentions to that account's local timeline, add emoji reactions inbox processing, implement followers listing, add reindex command

master
teknomunk 1 year ago
parent 9292f5d15e
commit 0325fc898c

@ -13,6 +13,7 @@
#include "model/ap/activity.h"
#include "model/ap/inbox_envelope.h"
#include "model/crypto/http_sign.h"
#include "model/timeline.h"
// Stdlib
#include <stdio.h>
@ -168,6 +169,12 @@ does_mention:
struct status* s = status_from_activity(obj);
status_save_new(s);
// Add status to account timeline
struct timeline* tl = timeline_from_id( mentioner->id );
printf( "tl->id=%d, s->id=%d\n", tl->id, s->id );
timeline_add_post(tl,s);
timeline_free(tl);
// Create notification
struct notification* note = notification_new();
note->type = nt_mention;
@ -186,7 +193,7 @@ failed:
goto cleanup;
}
static bool route_like( struct ap_activity* act )
static struct status* lookup_object_status( struct ap_activity* act )
{
struct status* s = NULL;
@ -196,24 +203,44 @@ static bool route_like( struct ap_activity* act )
break;
case apaot_activity:
s = status_from_uri( act->object.ptr->id );
if( !s ) {
printf( "! Status %s doesn't exist locally\n", act->object.ptr->id );
return false;
}
break;
}
// Get actor account
struct account* liker = account_from_uri( act->actor );
if( !liker ) {
liker = account_fetch_from_uri( act->actor );
}
if( s ) { return s; }
printf( "! Status %s doesn't exist locally\n", act->object.ptr->id );
return NULL;
}
static struct account* lookup_actor_account( struct ap_activity* act )
{
struct account* a = account_from_uri( act->actor );
if( a ) { return a; }
a = account_fetch_from_uri( act->actor );
return a;
}
static bool route_like( struct ap_activity* act )
{
struct status* s = lookup_object_status(act);
struct account* liker = lookup_actor_account(act);
status_add_like( s, liker );
return true;
}
static bool route_emoji_react( struct ap_activity* act )
{
struct status* s = lookup_object_status(act);
struct account* reactor = lookup_actor_account(act);
if( !s || !reactor ) { return false; }
status_add_react( s, act->content, reactor );
return true;
}
static bool route_create( struct ap_activity* act )
{
return route_follower_post(act)
@ -227,10 +254,11 @@ static bool route_activity( struct ap_activity* act )
printf( "Handling %s\n", act->id );
switch( act->type ) {
case apat_undo: return route_undo_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_undo: return route_undo_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_emoji_react: return route_emoji_react(act);
default:
printf( "Unhandled activity type: %d\n", act->type );
}

@ -0,0 +1,9 @@
#include "indexer.h"
#include "model/account.h"
void reindex()
{
account_reindex();
}

@ -0,0 +1,4 @@
#pragma once
void reindex();

@ -35,6 +35,7 @@ bool handle_timeline( struct http_request* req, int timeline_id )
int since_id = 0;
// handle query parameters
// TODO: split this off into a generic filter handling
if( http_request_route( req, "?" ) ) {
const char* key;
while( key = http_request_route_query_key(req) ) {
@ -191,32 +192,69 @@ bool route_mastodon_api( struct http_request* req )
account_free(owner);
return res;
} else if( http_request_route( req, "relationships" ) ) {
// TODO: implement stub
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "[]" );
return true;
} else if( http_request_route( req, "statuses" ) ) {
return handle_timeline( req, tli_owner );
} else if( http_request_route_term(req,"") ) {
struct account* owner = account_from_id(0);
bool res = handle_mastodon_api_show_account( req, owner );
account_free(owner);
return res;
}
int id = 0;
if( http_request_route_id( req, &id ) ) {
struct account* a = account_from_id( id );
if( !a ) { return false; }
if( http_request_route( req, "statuses" ) ) {
bool res = handle_timeline( req, id );
account_free(a);
return res;
} else {
bool res = handle_mastodon_api_show_account( req, a );
account_free(a);
return res;
if( !http_request_route_id( req, &id ) ) {
return false;
}
struct account* a = account_from_id( id );
if( !a ) { return false; }
if( http_request_route( req, "statuses" ) ) {
bool res = handle_timeline( req, id );
account_free(a);
return res;
} else if( http_request_route( req, "following" ) ) {
// TODO: implement stub
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "[" );
fprintf( f, "]" );
return true;
} else if( http_request_route( req, "followers" ) ) {
// TODO: implement stub
http_request_send_headers( req, 200, "application/json", true );
struct {
int* items;
int count;
} accounts;
account_list_followers( a, 0, 32, &accounts );
FILE* f = http_request_get_response_body( req );
fprintf( f, "[" );
bool first = true;
for( int i = 0; i < accounts.count; ++i ) {
struct account* a2 = account_from_id( accounts.items[i] );
if( a2 ) {
fprintf( f, first ? "\n" : ",\n" );
api_account_write_as_json( a2, f );
account_free(a2);
first = false;
}
}
} else if( id == 0 ) {
struct account* owner = account_from_id(0);
bool res = handle_mastodon_api_show_account( req, owner );
account_free(owner);
fprintf( f, "]" );
free(accounts.items);
return true;
} else {
bool res = handle_mastodon_api_show_account( req, a );
account_free(a);
return res;
}
}

@ -18,8 +18,9 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static bool process_one( int id )
static bool process_envelope( int id )
{
bool result = false;
char* postdata = NULL;
@ -130,7 +131,7 @@ discard:
goto cleanup;
}
void process_outbox()
static bool process_one()
{
int head = fs_list_get("data/outbox/HEAD");
int tail = fs_list_get("data/outbox/TAIL");
@ -140,9 +141,23 @@ void process_outbox()
printf( "Done with outbox/%d.json\n", tail );
fs_list_set( "data/outbox/TAIL", tail + 1 );
}
} else {
printf( "? No processing done\n" );
return true;
}
exit(0);
return false;
}
extern bool terminate;
void process_outbox()
{
while( !terminate ) {
bool activity = false;
activity |= process_one();
//activity |= cleanup_outbox();
if( !activity ) {
fflush(stdout);
sleep(1);
}
}
}

@ -2,11 +2,35 @@
#include "http_server/http_request.h"
#include "model/account.h"
#include "model/status.h"
#include "src/controller/api/client_apps.h"
#include <stdlib.h>
#include <string.h>
char* cgi_unescape( const char* str )
{
int size = strlen(str);
char* result = malloc(size+1);
char* o = result;
for( const char* i = str; *i; ++i ) {
if( *i == '%' ) {
int code;
sscanf( &i[1], "%02X", &code );
*o = code;
++o;
i += 2;
} else {
*o = *i;
}
}
*o = '\0';
return result;
}
bool http_request_route_id( struct http_request* req, int* id );
@ -14,9 +38,14 @@ static bool route_status( struct http_request* req, struct status* s )
{
if( http_request_route( req, "reactions/" ) ) {
if( http_request_route_method( req, "PUT" ) ) {
char* react = http_request_route_get_dir_or_file( req );
status_add_react( s, react );
char* react_cgi = http_request_route_get_dir_or_file( req );
char* react = cgi_unescape(react_cgi);
free(react_cgi);
struct account* owner = account_from_id( owner_account_id );
status_add_react( s, react, owner );
free(react);
account_free(owner);
return true;
} else if( http_request_route_method( req, "GET" ) ) {
printf( "TODO: get who react information\n" );

@ -1 +1 @@
Subproject commit 0b4becf5ae27c8fdd941b8559b797ec74c9ef4a3
Subproject commit 5cc08a251f63cf6e88d1d68d62e7ea82599976c8

@ -14,6 +14,7 @@
#include "controller/inbox.h"
#include "controller/outbox.h"
#include "controller/test.h"
#include "controller/indexer.h"
#include <curl/curl.h>
@ -114,6 +115,7 @@ int main( int argc, char* argv[] )
case 1: process_inbox(); break;
case 2: process_outbox(); break;
case 3: built_in_test(); break;
case 4: reindex(); break;
case 100: develop(); return 0;
default: code = !run_everything(args); break;

@ -45,11 +45,13 @@ static struct json_object_field account_layout[] = {
{ "handle", offsetof( struct account, handle ), true, &json_field_string },
{ "server", offsetof( struct account, server ), true, &json_field_string },
{ "display_name", offsetof( struct account, display_name ), true, &json_field_string },
{ "avatar", offsetof( struct account, avatar.url ), true, &json_field_string },
{ "avatar_static", offsetof( struct account, avatar.static_url ), true, &json_field_string },
{ "avatar", offsetof( struct account, avatar.url ), false, &json_field_string },
{ "avatar_static", offsetof( struct account, avatar.static_url ), false, &json_field_string },
{ "account_type", offsetof( struct account, account_type ), true, &json_field_enum, account_types_enum },
{ "inbox", offsetof( struct account, inbox ), false, &json_field_string },
{ "note", offsetof( struct account, note ), false, &json_field_string },
{ "followers", offsetof( struct account, followers_count ), false, &json_field_integer },
{ "following", offsetof( struct account, following_count ), false, &json_field_integer },
{ "account_url", offsetof( struct account, account_url ), true, &json_field_string },
{ NULL },
@ -71,6 +73,25 @@ static struct account* new_system_account()
return a;
}
static bool index_uri_to_account_id( const char* uri, int account_id );
void account_reindex()
{
int max_account_id = fs_list_get( "data/accounts/HEAD" );
for( int i = 0; i < max_account_id+1; ++i ) {
struct account* a = account_from_id(i);
if( !a ) { continue; }
if( a->account_url ) {
index_uri_to_account_id( a->account_url, a->id );
}
char webfinger_name[512];
snprintf( webfinger_name, 512, "%s@%s", a->handle, a->server );
hash_index_set( "data/accounts/webfinger", webfinger_name, a->id );
account_free(a);
}
}
struct account* account_from_id( int id )
{
@ -156,8 +177,10 @@ bool account_sync_from_acitvity_pub( unsigned int account_id )
a->id = account_id;
a->handle = strdup(ap->preferredUsername);
a->display_name = strdup(ap->name);
a->avatar.url = strdup(ap->avatar);
a->avatar.static_url = strdup(ap->avatar);
if( ap->avatar ) {
a->avatar.url = strdup(ap->avatar);
a->avatar.static_url = strdup(ap->avatar);
}
a->bot = ( ap->type != apacct_Person );
a->account_type = at_remote_activity_pub;
a->account_url = strdup(ap->url);
@ -345,6 +368,33 @@ void account_remove_follower( struct account* a, struct account* follower )
notification_save( note );
notification_free( note );
}
void account_list_followers( struct account* a, int offset, int limit, void* id_array )
{
struct int_array {
int* items;
int count;
} *array = id_array;
char filename[512];
snprintf( filename, sizeof(filename), "data/accounts/%d/followers", a->id );
struct {
char** items;
int count;
} keys;
memset( &keys, 0, sizeof(keys) );
ffdb_trie_list( filename, offset, limit, &keys, NULL );
array->items = malloc( sizeof(int) * keys.count );
for( int i = 0; i < keys.count; ++i ) {
char* str = keys.items[i];
array->items[i] = atoi( str );
free( str );
}
free(keys.items);
array->count = keys.count;
}
// TODO: move to controller/view
void api_account_write_as_json( struct account* a, FILE* f )

@ -5,6 +5,10 @@
struct crypto_keys;
enum {
owner_account_id = 0,
};
enum account_type
{
at_owner = 1,
@ -38,6 +42,8 @@ struct account
bool locked;
};
void account_reindex();
struct account* account_from_id( int id );
struct account* account_from_uri( const char* uri );
struct account* account_from_webfinger( const char* handle );
@ -51,6 +57,8 @@ 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_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 );
// TODO: move to controller/view and rename api_account_write_as_json
void api_account_write_as_json( struct account* a, FILE* f );

@ -27,6 +27,7 @@ static struct json_object_field notification_layout[] = {
{ "account_id", offsetof( struct notification, account_id ), true, &json_field_integer },
{ "status_id", offsetof( struct notification, status_id ), false, &json_field_integer },
{ "ref_account_id", offsetof( struct notification, ref_account_id ), false, &json_field_integer },
{ "react", offsetof( struct notification, react ), false, &json_field_string },
{ "type", offsetof( struct notification, type ), true, &json_field_enum, notification_type_enum },
{ NULL }
};
@ -66,6 +67,10 @@ void notification_save( struct notification* note )
}
void notification_free( struct notification* note )
{
if( !note ) { return; }
free(note->react);
free(note);
}

@ -14,6 +14,8 @@ struct notification
bool is_muted;
bool is_seen;
int type;
char* react;
};
enum notification_type
@ -24,6 +26,7 @@ enum notification_type
nt_unfollow = 4,
nt_block = 5,
nt_like = 6,
nt_react = 7,
};
struct notification* notification_from_id( int id );

@ -3,7 +3,6 @@
#include "status/react.h"
#include "model/account.h"
#include "model/timeline.h"
#include "model/ap/activity.h"
#include "model/notification.h"
@ -12,6 +11,7 @@
#include "json/layout.h"
#include "ffdb/hash_index.h"
#include "sha256/sha256.h"
#include "collections/array.h"
#include <stdio.h>
#include <string.h>
@ -117,13 +117,6 @@ struct status* status_from_activity( struct ap_activity* act )
struct account* a = account_from_uri(act->actor);
s->account_id = a->id;
// Add status to account timeline
struct timeline* tl = timeline_from_id( a->id );
printf( "tl->id=%d\n", tl->id );
timeline_add_post(tl,s);
timeline_free(tl);
account_free(a);
s->source = strdup(act->source);
//s->content = status_render_source(s);
s->url = strdup( act->id );
@ -184,13 +177,46 @@ void status_free( struct status* s )
free(s);
}
void status_add_react( struct status* s, const char* react )
void status_add_react( struct status* s, const char* react, struct account* a )
{
printf( "TODO: add reaction '%s' to status #%d\n", react, s->id );
// TODO generate outbox element
printf( "TODO: generate outbox activity for adding reaction '%s' to status #%d by account #%d\n", react, s->id, a->id );
if( s->url ) {
// TODO generate outbox element
// TODO: generate notification
if( s->account_id == owner_account_id && a->id != owner_account_id ) {
// Create notification for liking the owner's post
struct notification* note = notification_new();
note->type = nt_react;
note->account_id = a->id;
note->react = strdup(react);
note->created_at = time(NULL);
notification_save( note );
notification_free( note );
}
struct status_react* re = NULL;
for( int i = 0; i < s->reacts.count; ++i ) {
if( 0 == strcmp( s->reacts.items[i]->code, react ) ) {
re = s->reacts.items[i];
goto update_entry;
}
}
re = malloc(sizeof(*re));
memset(re,0,sizeof(*re));
re->code = strdup(react);
array_append( &s->reacts, sizeof(re), &re );
update_entry:
for( int i = 0; i < re->accounts.count; ++i ) {
if( re->accounts.items[i] == a->id ) {
// Already present
goto done;
}
}
array_append( &re->accounts, sizeof(a->id), &a->id );
done:
status_save(s);
}
void status_add_like( struct status* s, struct account* a )
{
@ -200,7 +226,7 @@ void status_add_like( struct status* s, struct account* a )
}
}
if( s->account_id == 0 ) {
if( s->account_id == owner_account_id && a->id != owner_account_id ) {
// Create notification for liking the owner's post
struct notification* note = notification_new();
note->type = nt_like;

@ -48,7 +48,7 @@ bool status_save_new( struct status* s );
void status_save( struct status* s );
void status_free( struct status* s );
void status_add_react( struct status* s, const char* react );
void status_add_react( struct status* s, const char* react, struct account* a );
void status_add_like( struct status* s, struct account* a );
char* status_render_source( struct status* s );

@ -47,7 +47,7 @@ static bool writer( struct json_writer* jw, const char* field_name, void* field_
FILE* f = jw->f;
fprintf( f, "{" );
json_write_string( f, sr->code );
fprintf( f, ",[" );
fprintf( f, ":[" );
for( int i = 0; i < sr->accounts.count; ++i ) {
fprintf(f, (i!=0)?",%d":"%d", sr->accounts.items[i] );
}

@ -11,3 +11,5 @@ struct status_react
extern struct json_field_type status_react_type;
void status_react_free( struct status_react* re );

Loading…
Cancel
Save