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.

340 lines
9.0 KiB
C

#include "model/account.h"
// Model
#include "model/fetch.h"
// Submodules
#include "collections/array.h"
#include "util/format.h"
#include "ap/object.h"
#include "ap/collection.h"
// Standard Library
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
struct ap_object* account_get_activity_pub_data( struct account* a )
{
if( !a ) { return NULL; }
char path[512];
account_get_path( a->id, path, 512 );
char filename[512];
snprintf( filename, 512, "%s/ap.json", path );
printf( "ap_object_from_file( %s )\n", filename );
if( a->account_id ) {
pull_remote_file_if_older( filename, a->account_id, 60*60*24*3 );
} else if( a->account_url ) {
pull_remote_file_if_older( filename, a->account_url, 60*60*24*3 );
} else {
printf( "? No URL to sync ap data from\n" );
}
return ap_object_from_file( filename );
}
bool account_sync_from_activity_pub( unsigned int account_id, bool force )
{
bool result = false;
struct account* a = account_from_id(account_id);
struct ap_object* obj = NULL;
printf( "account_sync_from_activity_pub( account_id=%d )\n", account_id );
if( !a ) {
printf( "! Failed to load account %d\n", account_id );
goto failed;
} else if( !force && ( a->next_update > time(NULL) ) ) {
printf( "? No update required\n" );
goto succeeded;
} else if( a->local ) {
printf( "? Is local account\n" );
goto succeeded;
}
obj = account_get_activity_pub_data(a);
if( !obj ) {
a->account_type = at_stub;
account_save(a);
printf( "! Failed to load AP data for %s (%d)\n", a->account_url, a->id );
goto failed;
}
if( !account_sync_from_activity( a, obj ) ) {
printf( "! Failed to sync from activity pub data\n" );
goto failed;
}
// If we got here, this account can't be a stub any more
a->stub = false;
a->next_update = time(NULL) + (60*60*24*3) + (rand() % (60*60*24*2)); // Next update in 3-5 days
goto succeeded;
succeeded:
result = true;
goto cleanup;
cleanup:
if( a ) {
//a->next_update = time(NULL) + (60*60*24*1) + (rand() % (60*60*24*2)); // Next update in 1-3 days
a->next_update = time(NULL) + (60*60*3); // 3 hours until next update
printf( "Account saved.\n" );
account_save(a);
account_index_webfinger(a);
}
ap_object_free(obj);
account_free(a);
return result;
failed:
result = false;
goto cleanup;
}
bool account_sync_from_activity( struct account* a, struct ap_object* obj )
{
printf( "account_sync_from_activity( a->id = %d, obj=%p )\n", a->id, obj );
if( obj->url.count > 0 ) {
a->account_url = strdup(obj->url.items[0].ref); // TODO: make this more robust
} else if( obj->id ) {
a->account_url = strdup(obj->id);
} else {
printf( "! No id or url in AP data\n" );
return false;
}
a->account_id = strdup(obj->id);
for( int i = 0; i < a->aliases.count; ++i ) {
free( a->aliases.items[i] );
}
a->aliases.count = 0;
for( int i = 0; i < obj->also_known_as.count; ++i ) {
char* str = strdup(obj->also_known_as.items[i]);
array_append( &a->aliases, sizeof(str), &str );
}
if( obj->preferred_username ) {
a->handle = strdup(obj->preferred_username);
} else {
a->handle = strdup("no-name");
}
if( obj->name ) {
a->display_name = strdup(obj->name);
} else {
a->display_name = strdup(obj->preferred_username);
}
if( obj->icon && obj->icon->url.count > 0 ) {
a->avatar.url = strdup(obj->icon->url.items[0].ref); // TODO: make this more robust
a->avatar.static_url = strdup(a->avatar.url);
}
if( obj->image && obj->image->url.count > 0 ) {
a->banner = strdup(obj->image->url.items[0].ref); // TODO: make this more robust
}
a->bot = ( obj->type != ap_Person );
a->account_type = at_remote_activity_pub;
if( obj->inbox ) {
a->inbox = strdup(obj->inbox);
}
if( obj->summary ) {
a->note = strdup(obj->summary);
}
if( obj->endpoints.shared_inbox ) {
a->shared_inbox = strdup(obj->endpoints.shared_inbox);
}
if( 0 == strncmp( obj->id, "https://", 8 ) ) {
char* server_name = strdup(&obj->id[8]);
char* discard;
strtok_r(server_name,"/",&discard);
a->server = server_name;
} else if( 0 == strncmp( obj->id, "http://", 7 ) ) {
char* server_name = strdup(&obj->id[7]);
char* discard;
strtok_r(server_name,"/",&discard);
a->server = server_name;
}
// Clear out existing emoji
for( int i = 0; i < a->emoji.count; ++i ) {
free( a->emoji.items[i].key );
free( a->emoji.items[i].value );
}
a->emoji.count = 0;
// Pull in emoji
for( int i = 0; i < obj->tags.count; ++i ) {
struct ap_activity_tag* tag = obj->tags.items[i];
if( tag->type == aptag_emoji ) {
struct string_pair item;
memset(&item,0,sizeof(item));
item.key = strndup(&tag->name[1],strlen(tag->name)-2);
item.value = strdup(tag->icon.url);
array_append( &a->emoji, sizeof(item), &item );
}
}
// Extract out the public key if present
if( obj->public_key ) {
char filename[512];
char* id = strdup(obj->public_key->id);
char* key_id = NULL;
strtok_r( id, "#", &key_id );
char path[512];
account_get_path( a->id, path, 512 );
FILE* key_pem = fopen( format(filename,sizeof(filename),"%s/%s.pem", path, key_id), "w" );
if( !key_pem ) {
printf( "Unable to save public key to %s\n", filename );
} else {
printf( "Writing public key to %s\n", filename );
fprintf( key_pem, "%s", obj->public_key->public_key );
fclose(key_pem);
}
free(id);
} else {
printf( "? No public key for account\n" );
}
return true;
}
static struct ap_object* fetch_ap_object_with_delay( const char* uri )
{
// Sleep for 150 milliseconds to reduce load on remote servers
usleep( 150 * 1000 );
return fetch_ap_object_ref(uri);
}
void account_sync_following_list( struct account* a )
{
ap_object_set_fetch_callback( fetch_ap_object_with_delay );
struct ap_object* obj = NULL;
struct ap_object* following = NULL;
if( a->local ) { goto cleanup; }
obj = account_get_activity_pub_data(a);
if( !obj ) { goto cleanup; }
following = ap_collection_from_uri( obj->following );
if( !following ) { goto cleanup; }
struct {
int* items;
int count;
} existing;
account_list_following( a, 0, INT_MAX, &existing );
for( struct ap_object* page = following->first.ptr; page; ap_collection_iterate(&page) ) {
for( int i = 0; i < page->ordered_items.count; ++i ) {
// ap_object_force_to_ref( &page->ordered_items.items[i] );
const char* account_uri = page->ordered_items.items[i].ref;
struct account* following = account_from_uri_or_fetch( account_uri );
if( !following ) { continue; }
int account_id = following->id;
printf( "Follower local account at %d\n", account_id );
if( -1 != array_find( &existing, sizeof(account_id), &account_id ) ) {
array_delete( &existing, sizeof(account_id), &account_id );
} else {
printf( "Adding following %s (%d)\n", account_uri, account_id );
account_add_follower( following, a );
}
account_free(following);
}
}
printf( "existing.count = %d\n", existing.count );
for( int i = 0; i < existing.count; ++i ) {
printf( "Need to remove %d as following\n", existing.items[i] );
}
free(existing.items);
cleanup:
ap_object_free(obj);
ap_object_free(following);
}
void account_sync_followers_list( struct account* a )
{
struct ap_object* obj = NULL;
struct ap_object* followers = NULL;
ap_object_set_fetch_callback( fetch_ap_object_with_delay );
struct {
int* items;
int count;
} existing;
account_list_followers( a, 0, INT_MAX, &existing );
obj = account_get_activity_pub_data(a);
if( !obj ) {
printf( "Unable to get activity pub object for account id=%d\n", a->id );
goto cleanup;
}
if( !obj->followers ) { goto cleanup; }
followers = ap_collection_from_uri( obj->followers );
if( !followers ) { goto cleanup; }
for( struct ap_object* page = followers->first.ptr; page; ap_collection_iterate(&page) ) {
for( int i = 0; i < page->ordered_items.count; ++i ) {
// ap_object_force_to_ref( &page->ordered_items.items[i] );
const char* account_uri = page->ordered_items.items[i].ref;
struct account* follower = account_from_uri_or_fetch( account_uri );
if( !follower ) { continue; }
int account_id = follower->id;
printf( "Follower local account at %d\n", follower->id );
if( -1 != array_find( &existing, sizeof(account_id), &account_id ) ) {
array_delete( &existing, sizeof(account_id), &account_id );
} else {
printf( "Adding follower %s (%d)\n", account_uri, account_id );
account_add_follower( a, follower );
}
account_free(follower);
}
}
printf( "existing.count = %d\n", existing.count );
for( int i = 0; i < existing.count; ++i ) {
printf( "Need to remove %d as follower\n", existing.items[i] );
}
free(existing.items);
cleanup:
ap_object_free(followers);
ap_object_free(obj);
}
void account_pull_friends_of_friends( struct account* a )
{
struct {
int* items;
int count;
} following;
account_list_following( a, 0, INT_MAX, &following );
for( int i = 0; i < following.count; ++i ) {
struct account* friend = account_from_id( following.items[i] );
account_sync_following_list( friend );
account_sync_followers_list( friend );
account_free(friend);
}
free( following.items );
}