# 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_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 ) ;
}