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.

362 lines
8.5 KiB
C

#define _GNU_SOURCE
#include "status.h"
#include "status/react.h"
#include "model/account.h"
#include "model/ap/activity.h"
#include "model/notification.h"
#include "model/timeline.h"
// Submodules
#include "json/json.h"
#include "json/layout.h"
#include "ffdb/hash_index.h"
#include "sha256/sha256.h"
#include "collections/array.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#define OBJ_TYPE struct status
static struct json_object_field status_layout[] = {
JSON_FIELD_INTEGER( account_id, true ),
JSON_FIELD_STRING( url, false ),
JSON_FIELD_BOOL( stub, false ),
JSON_FIELD_BOOL( remote, false ),
JSON_FIELD_STRING( source, false ),
JSON_FIELD_BOOL( sensitive, true ),
JSON_FIELD_DATETIME( published, false ),
JSON_FIELD_INTEGER( in_reply_to, false ),
JSON_FIELD_INTEGER( root_status_id, false ),
JSON_FIELD_ARRAY_OF_STRINGS( media, false ),
JSON_FIELD_ARRAY_OF_TYPE( reacts, false, status_react_type ),
JSON_FIELD_ARRAY_OF_INTS( likes, false ),
JSON_FIELD_ARRAY_OF_INTS( replies, false ),
JSON_FIELD_ARRAY_OF_INTS( reposts, false ),
JSON_FIELD_END
};
#undef OBJ_TYPE
void* allocate( size_t s )
{
void* ptr = malloc(s);
memset( ptr, 0, s );
return ptr;
}
struct status* status_from_id( unsigned int id )
{
struct status* s = NULL;
char filename[512];
snprintf( filename, 512, "data/statuses/%d.json", id );
s = allocate(sizeof(struct status));
s->id = id;
if( !json_read_object_layout_from_file( filename, status_layout, s ) ) {
printf( "Failed to load status %d\n", id );
status_free(s);
return NULL;
}
if( !s->source ) {
s->source = strdup("");
}
return s;
}
struct status* status_from_uri( const char* uri )
{
int id = -1;
if( !hash_index_get( "data/statuses/uri", uri, &id ) ) { return NULL; }
return status_from_id(id);
}
struct status* status_new_system_unfollow( int account_id )
{
struct account* a = account_from_id(account_id);
if( !a ) { return NULL; }
struct status* s;
s = malloc(sizeof(*s));
memset(s,0,sizeof(*s));
s->id = -1;
s->account_id = -1;
asprintf( &s->content, "%s unfollowed you\n", a->display_name );
s->sensitive = true;
account_free(a);
return s;
}
struct status* status_new_system_block( int account_id )
{
struct account* a = account_from_id(account_id);
if( !a ) { return NULL; }
struct status* s;
s = malloc(sizeof(*s));
memset(s,0,sizeof(*s));
s->id = -1;
s->account_id = -1;
asprintf( &s->content, "%s blocked you\n", a->display_name );
s->sensitive = true;
account_free(a);
return s;
}
struct status* status_from_activity( struct ap_activity* act )
{
struct status* s;
s = malloc(sizeof(*s));
memset(s,0,sizeof(*s));
struct account* a = account_from_uri(act->actor);
s->account_id = a->id;
s->published = act->published;
s->remote = true;
s->source = strdup(act->source);
//s->content = status_render_source(s);
s->url = strdup( act->id );
return s;
}
bool status_save_new( struct status* s )
{
int head = -1;
FILE* f = fopen("data/statuses/HEAD","r");
fscanf(f,"%d",&head);
if( head == -1 ) { return false; }
fclose(f);
s->id = head + 1;
s->root_status_id = s->id;
f = fopen("data/statuses/HEAD.tmp","w");
fprintf( f, "%d", s->id );
fclose(f);
rename( "data/statuses/HEAD.tmp", "data/statuses/HEAD" );
status_save( s );
return true;
}
void status_save( struct status* s )
{
char filename[512];
snprintf( filename, 512, "data/statuses/%d.json", s->id );
json_write_object_layout_to_file( filename, "\t", status_layout, s );
// Index the status
if( s->url ) {
hash_index_set( "data/statuses/uri", s->url, s->id );
}
}
void status_free( struct status* s )
{
if( !s ) { return; }
free(s->url);
free(s->content);
free(s->source);
// Free media
for( int i = 0; i < s->media.count; ++i ) {
free(s->media.items[i]);
}
free(s->media.items);
// Free reactions
for( int i = 0; i < s->reacts.count; ++i ) {
status_react_free(s->reacts.items[i]);
}
free(s->reacts.items);
free(s->likes.items);
free(s->reposts.items);
free(s);
}
void status_add_to_timeline( struct status* s, int timeline_id )
{
struct timeline* tl = timeline_from_id(timeline_id);
timeline_add_post( tl, s );
timeline_free(tl);
}
void status_make_reply_to( struct status* s, int in_reply_to_id )
{
// Add this status to the other
struct status* in_reply_to = status_from_id( in_reply_to_id );
if( !in_reply_to ) {
s->in_reply_to = 0;
} else {
s->in_reply_to = in_reply_to_id;
s->root_status_id = in_reply_to->root_status_id;
array_append( &in_reply_to->replies, sizeof(s->id), &s->id );
status_save(in_reply_to);
}
}
void status_get_context( struct status* s, void* ancestors_ptr, void* replies_ptr )
{
struct array_of_status {
struct status** items;
int count;
};
struct array_of_status* ancestors = ancestors_ptr;
struct array_of_status* replies = replies_ptr;
memset(ancestors,0,sizeof(*ancestors));
memset(replies,0,sizeof(*replies));
struct status* parent = NULL;
for( int i = s->in_reply_to; i != 0; i = parent->in_reply_to ) {
parent = status_from_id( i );
array_append( ancestors, sizeof(parent), &parent );
};
// reverse ancestors
for( int i = 0; i < ancestors->count / 2; ++i ) {
struct status* a = ancestors->items[i];
struct status* b = ancestors->items[ ancestors->count - i - 1 ];
ancestors->items[i] = b;
ancestors->items[ ancestors->count - i - 1 ] = a;
}
for( int i = 0; i < s->replies.count; ++i ) {
struct status* reply = status_from_id( s->replies.items[i] );
array_append( replies, sizeof(reply), &reply );
}
}
void status_add_react( struct status* s, const char* react, struct account* a )
{
// generate outbox element
if( a->id == owner_account_id ) {
if( s->account_id == owner_account_id ) {
// De
printf( "TODO: generate outbox activity for adding reaction '%s' to status #%d by account #%d, deliver to followers\n", react, s->id );
} else {
// Deliver react to post owner
//printf( "TODO: generate outbox activity for adding reaction '%s' to status #%d by account #%d, deliver to account #%d\n", react, s->id, a->id, s->account_id );
ap_activity_react( s, react );
}
}
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_remove_react( struct status* s, const char* react, struct account* a )
{
// TODO: generate outbox element
printf( "TODO: generate outbox activity for removing reaction '%s' to status #%d by account #%d\n", react, s->id, a->id );
/// Find react activity we need to Undo
/// Generate Undo Activity
// 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;
}
}
return;
update_entry:
array_delete( &re->accounts, sizeof(a->id), &a->id );
if( re->accounts.count == 0 ) {
array_delete( &s->reacts, sizeof(re), &re );
status_react_free(re);
}
status_save(s);
}
void status_add_like( struct status* s, struct account* a )
{
for( int i = 0; i < s->likes.count; ++i ) {
if( s->likes.items[i] == a->id ) {
return;
}
}
if( s->account_id == owner_account_id ) {
if( a->id != owner_account_id ) {
// Create notification for liking the owner's post
struct notification* note = notification_new();
note->type = nt_like;
note->account_id = a->id;
note->created_at = time(NULL);
notification_save( note );
notification_free( note );
}
} else {
ap_activity_like( s );
}
status_save(s);
}