diff --git a/Slon/Api/V1/Statuses.HC b/Slon/Api/V1/Statuses.HC index 3b4aac3..d589e3e 100644 --- a/Slon/Api/V1/Statuses.HC +++ b/Slon/Api/V1/Statuses.HC @@ -1,3 +1,4 @@ +U0 (*@slon_api_status_boost_fedi)(JsonObject* status) = NULL; U0 (*@slon_api_status_create_fedi)(JsonObject* status) = NULL; U0 (*@slon_api_status_delete_fedi)(JsonObject* status) = NULL; @@ -62,7 +63,14 @@ U0 @slon_api_v1_statuses_query(SlonHttpSession* session, JsonArray* status_array for (i = status_array->length - 1; i > -1; i--) { status = Json.Clone(status_array->o(i), session->mem_task); status_id = Str2I64(status->@("id")); + status->set("bookmarked", @slon_api_status_is_bookmarked(session, status, account_id), JSON_BOOLEAN); status->set("favourited", @slon_api_status_is_favourited(session, status, account_id), JSON_BOOLEAN); + if (@slon_api_status_is_reblogged(session, status, account_id)) { + status->set("reblogged", TRUE, JSON_BOOLEAN); + if (status->@("reblog")) { + status->o("reblog")->set("reblogged", TRUE, JSON_BOOLEAN); + } + } exclude_status = FALSE; if (status->@("deleted")) { exclude_status = TRUE; @@ -82,6 +90,9 @@ U0 @slon_api_v1_statuses_query(SlonHttpSession* session, JsonArray* status_array if (pinned && !status->@("pinned")) { exclude_status = TRUE; } + if (exclude_reblogs && status->@("reblogged")) { + exclude_status = TRUE; + } if (!exclude_status) { statuses->append(status); count++; @@ -199,7 +210,14 @@ U0 @slon_api_v1_statuses_get(SlonHttpSession* session) status = @slon_api_find_status_by_id(id, NULL); if (status) { status = Json.Clone(status, session->mem_task); + status->set("bookmarked", @slon_api_status_is_bookmarked(session, status, account_id), JSON_BOOLEAN); status->set("favourited", @slon_api_status_is_favourited(session, status, account_id), JSON_BOOLEAN); + if (@slon_api_status_is_reblogged(session, status, account_id)) { + status->set("reblogged", TRUE, JSON_BOOLEAN); + if (status->@("reblog")) { + status->o("reblog")->set("reblogged", TRUE, JSON_BOOLEAN); + } + } session->send(status); return; } @@ -223,6 +241,7 @@ U0 @slon_api_v1_statuses_post(SlonHttpSession* session) U8* id = NULL; JsonObject* status = NULL; + JsonObject* boost = NULL; if (session->path_count() > 4) { id = session->path(3); @@ -266,6 +285,25 @@ U0 @slon_api_v1_statuses_post(SlonHttpSession* session) return; } + if (!StrICmp("reblog", verb)) { + status = Json.Clone(status, slon_db_mem_task); + boost = Json.Clone(@slon_api_reblog_status(session, status, account_id), session->mem_task); + boost->set("reblogged", TRUE, JSON_BOOLEAN); + session->send(boost); + if (@slon_api_status_boost_fedi) { + @slon_api_status_boost_fedi(Json.Clone(boost, slon_mem_task)); + } + return; + } + + if (!StrICmp("unreblog", verb)) { + status = Json.Clone(status, session->mem_task); + @slon_api_unreblog_status(session, status, account_id); + status->set("reblogged", FALSE, JSON_BOOLEAN); + session->send(status); + return; + } + session->status(400); return; } diff --git a/Slon/Modules/ActivityPub.HC b/Slon/Modules/ActivityPub.HC index ac24436..5c96588 100644 --- a/Slon/Modules/ActivityPub.HC +++ b/Slon/Modules/ActivityPub.HC @@ -523,6 +523,54 @@ U0 @slon_activitypub_async_create_status_to(JsonObject* status, U8* dest) Free(fetch_buffer); } +U0 @slon_activitypub_async_boost_status_to(JsonObject* status, U8* dest) +{ + Sleep(1000); + I64 i; + + U8 scratch_buffer[2048]; + + // U8* this_actor = StrNew(status->@("uri"), slon_mem_task); + // StrFind("/statuses/", this_actor)[0] = NULL; + U8* this_actor = db->o("actors")->o(status->o("account")->@("acct"))->@("id"); + if (!this_actor) { + AdamLog("slon_activitypub_async_boost_status_to: this_actor is NULL\n"); + return; + } + + JsonObject* announce_object = Json.CreateObject(slon_mem_task); + + announce_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING); + StrPrint(scratch_buffer, "%s/activity", status->@("uri")); + announce_object->set("id", scratch_buffer, JSON_STRING); + announce_object->set("type", "Announce", JSON_STRING); + announce_object->set("actor", this_actor, JSON_STRING); + announce_object->set("object", status->o("reblog")->@("uri"), JSON_STRING); + announce_object->set("published", status->@("created_at"), JSON_STRING); + announce_object->set("to", Json.Parse("[\"https://www.w3.org/ns/activitystreams#Public\"]", slon_mem_task), JSON_ARRAY); + JsonArray* cc = Json.CreateArray(slon_mem_task); + StrPrint(scratch_buffer, "%s/followers", this_actor); + cc->append(scratch_buffer, JSON_STRING); + announce_object->set("cc", cc, JSON_ARRAY); + + U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, slon_mem_task); + StrPrint(scratch_buffer, "%s/inbox", dest); + @slon_activitypub_signed_request(scratch_buffer, fetch_buffer, announce_object); + Free(fetch_buffer); +} + +U0 @slon_activitypub_async_boost_status(JsonObject* status) +{ + I64 i; + JsonArray* followers = db->o("followers")->a(status->o("account")->@("username")); + if (!followers) { + return; + } + for (i = 0; i < followers->length; i++) { + @slon_activitypub_async_boost_status_to(status, followers->@(i)); + } +} + U0 @slon_activitypub_async_create_status(JsonObject* status) { I64 i; @@ -591,6 +639,11 @@ U0 @slon_activitypub_follow_fedi(JsonObject* follow) Spawn(&@slon_activitypub_async_follow, follow, "SlonAsyncFollowTask"); } +U0 @slon_activitypub_boost_status_fedi(JsonObject* status) +{ + Spawn(&@slon_activitypub_async_boost_status, status, "SlonAsyncBoostTask"); +} + U0 @slon_activitypub_create_status_fedi(JsonObject* status) { Spawn(&@slon_activitypub_async_create_status, status, "SlonAsyncCreateTask"); @@ -602,6 +655,7 @@ U0 @slon_activitypub_delete_status_fedi(JsonObject* status) } @slon_api_follow_fedi = &@slon_activitypub_follow_fedi; +@slon_api_status_boost_fedi = &@slon_activitypub_boost_status_fedi; @slon_api_status_create_fedi = &@slon_activitypub_create_status_fedi; @slon_api_status_delete_fedi = &@slon_activitypub_delete_status_fedi; diff --git a/Slon/Modules/Api.HC b/Slon/Modules/Api.HC index 74679a0..3973fec 100644 --- a/Slon/Modules/Api.HC +++ b/Slon/Modules/Api.HC @@ -147,6 +147,126 @@ JsonObject* @slon_api_announcement_by_id(U8* id) return NULL; } +JsonObject* @slon_api_status_lookup_by_id(U8* id, JsonArray* statuses) +{ + if (!id || !statuses) { + return NULL; + } + I64 i; + JsonObject* status; + for (i = 0; i < statuses->length; i++) { + status = statuses->@(i); + if (!status->@("deleted") && status->@("id") && !StrICmp(status->@("id"), id)) { + return status; + } + } + return NULL; +} + +JsonObject* @slon_api_status_lookup_by_in_reply_to_id(U8* id, JsonArray* statuses) +{ + if (!id || !statuses) { + return NULL; + } + I64 i; + JsonObject* status; + for (i = 0; i < statuses->length; i++) { + status = statuses->@(i); + if (!status->@("deleted") && status->@("in_reply_to_id") && !StrICmp(status->@("in_reply_to_id"), id)) { + return status; + } + } + return NULL; +} + +JsonObject* @slon_api_status_lookup_by_uri(U8* uri, JsonArray* statuses) +{ + if (!uri || !statuses) { + return NULL; + } + I64 i; + JsonObject* status; + for (i = 0; i < statuses->length; i++) { + status = statuses->@(i); + if (!status->@("deleted") && status->@("uri") && !StrICmp(status->@("uri"), uri)) { + return status; + } + } + return NULL; +} + +JsonObject* @slon_api_find_status_by_id(U8* id, U8* account_id = NULL) +{ + if (account_id) { + return @slon_api_status_lookup_by_id(id, db->o("statuses")->a(account_id)); + } + JsonObject* status = NULL; + JsonKey* key = db->o("statuses")->keys; + while (key) { + status = @slon_api_status_lookup_by_id(id, key->value); + if (status) { + return status; + } + key = key->next; + } + return NULL; +} + +JsonObject* @slon_api_find_status_by_uri(U8* uri, U8* account_id = NULL) +{ + if (account_id) { + return @slon_api_status_lookup_by_uri(uri, db->o("statuses")->a(account_id)); + } + JsonObject* status = NULL; + JsonKey* key = db->o("statuses")->keys; + while (key) { + status = @slon_api_status_lookup_by_uri(uri, key->value); + if (status) { + return status; + } + key = key->next; + } + return NULL; +} + +U0 @slon_api_create_status(JsonObject* status, U8* account_id, U8* to_ap_user = NULL) +{ + if (!status || !account_id) { + return; + } + if (!db->o("statuses")->a(account_id)) { + db->o("statuses")->set(account_id, Json.CreateArray(slon_mem_task), JSON_ARRAY); + } + db->o("statuses")->a(account_id)->append(status); + @slon_db_save_status_to_disk(status); + @slon_db_instance_increment_status_count; + @slon_db_save_instance_to_disk; + + JsonObject* status_item = Json.CreateObject(slon_mem_task); + status_item->set("account_id", account_id, JSON_STRING); + status_item->set("status_id", status->@("id"), JSON_STRING); + + // If account_id is a local account, publish to public timeline + JsonObject* acct = @slon_api_account_by_id(account_id); + if (!acct->@("remote_actor") && !StrICmp("public", status->@("visibility"))) { + if (!db->o("timelines")->a("public")) { + db->o("timelines")->set("public", Json.CreateArray(slon_mem_task), JSON_ARRAY); + } + db->o("timelines")->a("public")->append(status_item); + } + // If account_id is a remote account, and we have an ActivityPub user, post to their timeline + if (acct->@("remote_actor") && to_ap_user) { + JsonObject* acct_for_ap_user = @slon_api_account_by_username(to_ap_user); + if (acct_for_ap_user) { + if (!db->o("timelines")->o("home")->a(acct_for_ap_user->@("id"))) { + db->o("timelines")->o("home")->set(acct_for_ap_user->@("id"), Json.CreateArray(slon_mem_task), JSON_ARRAY); + } + db->o("timelines")->o("home")->a(acct_for_ap_user->@("id"))->append(status_item); + } + } + @slon_db_save_timelines_to_disk; +} + Bool @slon_api_status_is_bookmarked(SlonHttpSession* session, JsonObject* status, U8* account_id) { no_warn session; @@ -183,7 +303,7 @@ U0 @slon_api_bookmark_status(SlonHttpSession* session, JsonObject* status, U8* a } } if (!is_already_bookmarked) { - bookmark = Json.CreateObject(session->mem_task); + bookmark = Json.CreateObject(slon_db_mem_task); bookmark->set("status_id", status->@("id"), JSON_STRING); bookmark->set("account_id", status->o("account")->@("id"), JSON_STRING); bookmarks->append(bookmark); @@ -229,6 +349,24 @@ Bool @slon_api_status_is_favourited(SlonHttpSession* session, JsonObject* status return FALSE; } +Bool @slon_api_status_is_reblogged(SlonHttpSession* session, JsonObject* status, U8* account_id) +{ + no_warn session; + JsonArray* reblogs = db->o("reblogs")->a(account_id); + JsonObject* reblog = NULL; + if (!reblogs) { + return FALSE; + } + I64 i; + for (i = 0; i < reblogs->length; i++) { + reblog = reblogs->o(i); + if (!StrICmp(reblog->@("boost_id"), status->@("id")) || !StrICmp(reblog->@("status_id"), status->@("id"))) { + return TRUE; + } + } + return FALSE; +} + U0 @slon_api_favourite_status(SlonHttpSession* session, JsonObject* status, U8* account_id) { Bool is_already_favourited = FALSE; @@ -247,7 +385,7 @@ U0 @slon_api_favourite_status(SlonHttpSession* session, JsonObject* status, U8* } } if (!is_already_favourited) { - favourite = Json.CreateObject(session->mem_task); + favourite = Json.CreateObject(slon_db_mem_task); favourite->set("status_id", status->@("id"), JSON_STRING); favourite->set("account_id", status->o("account")->@("id"), JSON_STRING); favourites->append(favourite); @@ -275,6 +413,97 @@ U0 @slon_api_unfavourite_status(SlonHttpSession* session, JsonObject* status, U8 } } +JsonObject* @slon_api_reblog_status(SlonHttpSession* session, JsonObject* status, U8* account_id) +{ + Bool is_already_reblogged = FALSE; + JsonArray* reblogs = db->o("reblogs")->a(account_id); + JsonObject* reblog = NULL; + if (!reblogs) { + reblogs = Json.CreateArray(slon_db_mem_task); + db->o("reblogs")->set(account_id, reblogs, JSON_ARRAY); + @slon_db_save_reblogs_to_disk; + } + I64 i; + for (i = 0; i < reblogs->length; i++) { + reblog = reblogs->o(i); + if (!StrICmp(reblog->@("status_id"), status->@("id")) && !StrICmp(reblog->@("account_id"), status->o("account")->@("id"))) { + return @slon_api_find_status_by_id(reblog->@("boost_id"), account_id); + } + } + + // Create boost object, which is our reblog status with the original status attached as a "reblog" object + JsonObject* boost = Json.CreateObject(slon_db_mem_task); + U8* boost_id = @slon_api_generate_unique_id(session); + U8* boost_created_at = @slon_api_timestamp_from_cdate(session, Now); + + boost->set("id", boost_id, JSON_STRING); + boost->set("in_reply_to_id", NULL, JSON_NULL); + boost->set("in_reply_to_account_id", NULL, JSON_NULL); + boost->set("content", "", JSON_STRING); + boost->set("created_at", boost_created_at, JSON_STRING); + boost->set("visibility", "public", JSON_STRING); + boost->set("uri", status->@("uri"), JSON_STRING); + boost->set("url", status->@("url"), JSON_STRING); + boost->set("account", @slon_api_account_by_id(account_id), JSON_OBJECT); + boost->set("reblogs_count", 0, JSON_NUMBER); + boost->set("favourites_count", 0, JSON_NUMBER); + boost->set("emojis", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY); + boost->set("tags", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY); + boost->set("mentions", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY); + boost->set("media_attachments", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY); + boost->set("replies_count", 0, JSON_NUMBER); + boost->set("spoiler_text", "", JSON_STRING); + boost->set("sensitive", status->@("sensitive"), JSON_BOOLEAN); + boost->set("reblog", status, JSON_OBJECT); + + @slon_api_create_status(boost, account_id); + + // Create a reblog object which is a lookup to status_id, account_id, boost_id + reblog = Json.CreateObject(slon_db_mem_task); + reblog->set("status_id", status->@("id"), JSON_STRING); + reblog->set("account_id", status->o("account")->@("id"), JSON_STRING); + reblog->set("boost_id", boost->@("id"), JSON_STRING); + reblogs->append(reblog); + @slon_db_save_reblogs_to_disk; + + status->set("reblogs_count", status->@("reblogs_count") + 1, JSON_NUMBER); + @slon_db_save_status_to_disk(status); + + @slon_free(session, boost_created_at); + @slon_free(session, boost_id); + + return boost; +} + +U0 @slon_api_unreblog_status(SlonHttpSession* session, JsonObject* status, U8* account_id) +{ + no_warn session; + JsonArray* reblogs = db->o("reblogs")->a(account_id); + JsonObject* reblog = NULL; + JsonObject* boost = NULL; + if (!reblogs) { + reblogs = Json.CreateArray(slon_db_mem_task); + db->o("reblogs")->set(account_id, reblogs, JSON_ARRAY); + @slon_db_save_reblogs_to_disk; + } + I64 i; + for (i = 0; i < reblogs->length; i++) { + reblog = reblogs->o(i); + if (!StrICmp(reblog->@("status_id"), status->@("id")) && !StrICmp(reblog->@("account_id"), status->o("account")->@("id"))) { + reblogs->remove(i); + @slon_db_save_reblogs_to_disk; + status->set("reblogs_count", status->@("reblogs_count") - 1, JSON_NUMBER); + @slon_db_save_status_to_disk(status); + boost = @slon_api_find_status_by_id(reblog->@("boost_id"), account_id); + if (boost) { + boost->set("deleted", TRUE, JSON_BOOLEAN); + @slon_db_save_status_to_disk(status); + } + break; + } + } +} + U0 @slon_api_async_upload_to_catbox(SlonCatboxUpload* cb) { if (!cb) { @@ -445,126 +674,6 @@ U0 @slon_api_async_delete_from_catbox(U8* filename) Free(filename); } -JsonObject* @slon_api_status_lookup_by_id(U8* id, JsonArray* statuses) -{ - if (!id || !statuses) { - return NULL; - } - I64 i; - JsonObject* status; - for (i = 0; i < statuses->length; i++) { - status = statuses->@(i); - if (!status->@("deleted") && status->@("id") && !StrICmp(status->@("id"), id)) { - return status; - } - } - return NULL; -} - -JsonObject* @slon_api_status_lookup_by_in_reply_to_id(U8* id, JsonArray* statuses) -{ - if (!id || !statuses) { - return NULL; - } - I64 i; - JsonObject* status; - for (i = 0; i < statuses->length; i++) { - status = statuses->@(i); - if (!status->@("deleted") && status->@("in_reply_to_id") && !StrICmp(status->@("in_reply_to_id"), id)) { - return status; - } - } - return NULL; -} - -JsonObject* @slon_api_status_lookup_by_uri(U8* uri, JsonArray* statuses) -{ - if (!uri || !statuses) { - return NULL; - } - I64 i; - JsonObject* status; - for (i = 0; i < statuses->length; i++) { - status = statuses->@(i); - if (!status->@("deleted") && status->@("uri") && !StrICmp(status->@("uri"), uri)) { - return status; - } - } - return NULL; -} - -JsonObject* @slon_api_find_status_by_id(U8* id, U8* account_id = NULL) -{ - if (account_id) { - return @slon_api_status_lookup_by_id(id, db->o("statuses")->a(account_id)); - } - JsonObject* status = NULL; - JsonKey* key = db->o("statuses")->keys; - while (key) { - status = @slon_api_status_lookup_by_id(id, key->value); - if (status) { - return status; - } - key = key->next; - } - return NULL; -} - -JsonObject* @slon_api_find_status_by_uri(U8* uri, U8* account_id = NULL) -{ - if (account_id) { - return @slon_api_status_lookup_by_uri(uri, db->o("statuses")->a(account_id)); - } - JsonObject* status = NULL; - JsonKey* key = db->o("statuses")->keys; - while (key) { - status = @slon_api_status_lookup_by_uri(uri, key->value); - if (status) { - return status; - } - key = key->next; - } - return NULL; -} - -U0 @slon_api_create_status(JsonObject* status, U8* account_id, U8* to_ap_user = NULL) -{ - if (!status || !account_id) { - return; - } - if (!db->o("statuses")->a(account_id)) { - db->o("statuses")->set(account_id, Json.CreateArray(slon_mem_task), JSON_ARRAY); - } - db->o("statuses")->a(account_id)->append(status); - @slon_db_save_status_to_disk(status); - @slon_db_instance_increment_status_count; - @slon_db_save_instance_to_disk; - - JsonObject* status_item = Json.CreateObject(slon_mem_task); - status_item->set("account_id", account_id, JSON_STRING); - status_item->set("status_id", status->@("id"), JSON_STRING); - - // If account_id is a local account, publish to public timeline - JsonObject* acct = @slon_api_account_by_id(account_id); - if (!acct->@("remote_actor") && !StrICmp("public", status->@("visibility"))) { - if (!db->o("timelines")->a("public")) { - db->o("timelines")->set("public", Json.CreateArray(slon_mem_task), JSON_ARRAY); - } - db->o("timelines")->a("public")->append(status_item); - } - // If account_id is a remote account, and we have an ActivityPub user, post to their timeline - if (acct->@("remote_actor") && to_ap_user) { - JsonObject* acct_for_ap_user = @slon_api_account_by_username(to_ap_user); - if (acct_for_ap_user) { - if (!db->o("timelines")->o("home")->a(acct_for_ap_user->@("id"))) { - db->o("timelines")->o("home")->set(acct_for_ap_user->@("id"), Json.CreateArray(slon_mem_task), JSON_ARRAY); - } - db->o("timelines")->o("home")->a(acct_for_ap_user->@("id"))->append(status_item); - } - } - @slon_db_save_timelines_to_disk; -} - JsonObject* @slon_api_get_timeline_item(JsonObject* timeline_item) { if (!timeline_item) { diff --git a/Slon/Modules/Db.HC b/Slon/Modules/Db.HC index 7207505..20305e6 100644 --- a/Slon/Modules/Db.HC +++ b/Slon/Modules/Db.HC @@ -126,6 +126,26 @@ U0 @slon_db_load_favourites_from_disk() db->set("favourites", favourites, JSON_OBJECT); } +U0 @slon_db_load_reblogs_from_disk() +{ + JsonObject* reblogs = Json.CreateObject(slon_db_mem_task); + U8 scratch_buffer[256]; + StrPrint(scratch_buffer, "%s/reblogs/*.json", SLON_DB_PATH); + CDirEntry* files = FilesFind(scratch_buffer); + CDirEntry* de = files; + JsonArray* reblog_array = NULL; + while (de) { + reblog_array = Json.ParseFile(de->full_name, slon_db_mem_task); + if (reblog_array) { + StrFind(".json", de->name)[0] = NULL; + reblogs->set(de->name, reblog_array, JSON_ARRAY); + } + de = de->next; + } + DirTreeDel(files); + db->set("reblogs", reblogs, JSON_OBJECT); +} + U0 @slon_db_load_followers_from_disk() { JsonObject* followers = Json.CreateObject(slon_db_mem_task); @@ -341,6 +361,17 @@ U0 @slon_db_save_favourites_to_disk() } } +U0 @slon_db_save_reblogs_to_disk() +{ + U8 scratch_buffer[256]; + JsonKey* key = db->o("reblogs")->keys; + while (key) { + StrPrint(scratch_buffer, "%s/reblogs/%s.json", SLON_DB_PATH, key->name); + Json.DumpToFile(scratch_buffer, key->value, slon_db_mem_task); + key = key->next; + } +} + U0 @slon_db_save_followers_to_disk() { U8 scratch_buffer[256]; @@ -429,6 +460,7 @@ U0 @slon_db_save_to_disk() @slon_db_save_markers_to_disk(); @slon_db_save_oauth_to_disk(); @slon_db_save_private_keys_to_disk(); + @slon_db_save_reblogs_to_disk(); @slon_db_save_settings_to_disk(); @slon_db_save_statuses_to_disk(); @slon_db_save_timelines_to_disk(); @@ -452,6 +484,7 @@ U0 @slon_db_load_from_defaults() db->set("instance", Json.ParseFile("M:/Slon/Static/defaults/instance.json", slon_db_mem_task), JSON_OBJECT); db->set("markers", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("media", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); + db->set("reblogs", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("settings", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("statuses", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("timelines", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); @@ -485,6 +518,7 @@ U0 @slon_db_load_from_disk() @slon_db_load_markers_from_disk(); db->set("media", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); @slon_db_load_oauth_from_disk(); + @slon_db_load_reblogs_from_disk(); @slon_db_load_settings_from_disk(); @slon_db_load_statuses_from_disk(); @slon_db_load_timelines_from_disk();