From 022fceb21b0fdedf978c24219f0f3e6df7699d49 Mon Sep 17 00:00:00 2001 From: Alec Murphy Date: Thu, 20 Mar 2025 09:54:24 -0400 Subject: [PATCH] Slon/Api/V1/Notifications: Add initial support for Notifications --- Slon/Api/V1/Notifications.HC | 32 +++++++++++++++++++++++--- Slon/Api/V2/Notifications.HC | 16 +++++++++++++ Slon/Endpoints/Get/Notifications.HC | 5 ++++ Slon/Endpoints/Post/Notifications.HC | 4 ++++ Slon/Http/Server.HC | 1 + Slon/MakeSlon.HC | 1 + Slon/Modules/Api.HC | 33 +++++++++++++++++++++++++++ Slon/Modules/Db.HC | 34 ++++++++++++++++++++++++++++ 8 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 Slon/Api/V2/Notifications.HC create mode 100644 Slon/Endpoints/Post/Notifications.HC diff --git a/Slon/Api/V1/Notifications.HC b/Slon/Api/V1/Notifications.HC index 6e4327c..e874bd9 100644 --- a/Slon/Api/V1/Notifications.HC +++ b/Slon/Api/V1/Notifications.HC @@ -3,12 +3,38 @@ U0 @slon_api_v1_notifications_get(SlonHttpSession* session) // SLON_SCRATCH_BUFFER_AND_REQUEST_JSON if (@slon_api_authorized(session)) { - // SLON_AUTH_ACCOUNT_ID - // FIXME: Implement this + SLON_AUTH_ACCOUNT_ID + + JsonObject* acct = @slon_api_account_by_id(account_id); + if (String.EndsWith("policy", session->path())) { session->send(SLON_EMPTY_JSON_OBJECT); } else { - session->send(SLON_EMPTY_JSON_ARRAY); + if (!db->o("notifications")->@(acct->@("username"))) { + db->o("notifications")->set(acct->@("username"), Json.CreateArray(slon_db_mem_task), JSON_ARRAY); + } + session->send(db->o("notifications")->a(acct->@("username"))); + } + } else { + session->status(401); + } +} + +U0 @slon_api_v1_notifications_post(SlonHttpSession* session) +{ + // SLON_SCRATCH_BUFFER_AND_REQUEST_JSON + + if (@slon_api_authorized(session)) { + SLON_AUTH_ACCOUNT_ID + + JsonObject* acct = @slon_api_account_by_id(account_id); + + if (String.EndsWith("/clear", session->path())) { + db->o("notifications")->set(acct->@("username"), Json.CreateArray(slon_db_mem_task), JSON_ARRAY); + @slon_db_save_notifications_to_disk; + session->send(SLON_EMPTY_JSON_OBJECT); + } else { + session->send(400); } } else { session->status(401); diff --git a/Slon/Api/V2/Notifications.HC b/Slon/Api/V2/Notifications.HC new file mode 100644 index 0000000..f001056 --- /dev/null +++ b/Slon/Api/V2/Notifications.HC @@ -0,0 +1,16 @@ +U0 @slon_api_v2_notifications_get(SlonHttpSession* session) +{ + // SLON_SCRATCH_BUFFER_AND_REQUEST_JSON + + if (@slon_api_authorized(session)) { + // SLON_AUTH_ACCOUNT_ID + // FIXME: Implement this + if (String.EndsWith("policy", session->path())) { + session->send(SLON_EMPTY_JSON_OBJECT); + } else { + session->send(SLON_EMPTY_JSON_ARRAY); + } + } else { + session->status(401); + } +} diff --git a/Slon/Endpoints/Get/Notifications.HC b/Slon/Endpoints/Get/Notifications.HC index d894df9..1e2665b 100644 --- a/Slon/Endpoints/Get/Notifications.HC +++ b/Slon/Endpoints/Get/Notifications.HC @@ -2,3 +2,8 @@ if (String.BeginsWith("/api/v1/notifications", session->path())) { @slon_api_v1_notifications_get(session); return; } + +if (String.BeginsWith("/api/v2/notifications", session->path())) { + @slon_api_v2_notifications_get(session); + return; +} diff --git a/Slon/Endpoints/Post/Notifications.HC b/Slon/Endpoints/Post/Notifications.HC new file mode 100644 index 0000000..ca61786 --- /dev/null +++ b/Slon/Endpoints/Post/Notifications.HC @@ -0,0 +1,4 @@ +if (String.BeginsWith("/api/v1/notifications", session->path())) { + @slon_api_v1_notifications_post(session); + return; +} diff --git a/Slon/Http/Server.HC b/Slon/Http/Server.HC index 701bdfc..7802e25 100644 --- a/Slon/Http/Server.HC +++ b/Slon/Http/Server.HC @@ -743,6 +743,7 @@ U0 @slon_http_handle_post_request(SlonHttpSession* session) #include "Endpoints/Post/Apps"; #include "Endpoints/Post/Markers"; #include "Endpoints/Post/Media"; + #include "Endpoints/Post/Notifications"; #include "Endpoints/Post/OAuth"; #include "Endpoints/Post/Polls"; #include "Endpoints/Post/Statuses"; diff --git a/Slon/MakeSlon.HC b/Slon/MakeSlon.HC index 71c2638..cf79f02 100644 --- a/Slon/MakeSlon.HC +++ b/Slon/MakeSlon.HC @@ -29,6 +29,7 @@ WinMax(Fs); #include "Api/V2/Filters"; #include "Api/V2/Instance"; #include "Api/V2/Media"; +#include "Api/V2/Notifications"; #include "Api/V2/Search"; #include "Api/V2/Suggestions"; diff --git a/Slon/Modules/Api.HC b/Slon/Modules/Api.HC index aa7ee1f..9d64956 100644 --- a/Slon/Modules/Api.HC +++ b/Slon/Modules/Api.HC @@ -5,6 +5,9 @@ account_id = session->auth->@("account_id"); \ }; +SlonHttpSession* SLON_API_DUMMY_SESSION = CAlloc(sizeof(SlonHttpSession)); +SLON_API_DUMMY_SESSION->mem_task = slon_mem_task; + extern @http_response* @slon_activitypub_signed_request(U8* url_string, U8* fetch_buffer, JsonObject* request_object = NULL, I64 verb = SLON_HTTP_VERB_POST, U8* signatory = NULL); class SlonCatboxUpload { @@ -262,6 +265,36 @@ JsonObject* @slon_api_find_status_by_uri(U8* uri, U8* account_id = NULL) return NULL; } +U0 @slon_api_notify_account(JsonObject* target_acct, JsonObject* source_acct, U8* type, JsonObject* status = NULL) +{ + U8 scratch_buffer[256]; + SlonHttpSession* session = SLON_API_DUMMY_SESSION; + + U8* id = @slon_api_generate_unique_id(session); + U8* created_at = @slon_api_timestamp_from_cdate(session, Now); + + if (!db->o("notifications")->@(target_acct->@("username"))) { + db->o("notifications")->set(target_acct->@("username"), Json.CreateArray(slon_db_mem_task), JSON_ARRAY); + } + + JsonArray* notifications = db->o("notifications")->a(target_acct->@("username")); + + JsonObject* notification = Json.CreateObject(slon_db_mem_task); + notification->set("id", id, JSON_STRING); + notification->set("type", type, JSON_STRING); + notification->set("created_at", created_at, JSON_STRING); + StrPrint(scratch_buffer, "ungrouped-%s", id); + notification->set("group_key", scratch_buffer, JSON_STRING); + notification->set("account", source_acct, JSON_OBJECT); + if (status) { + notification->set("status", status, JSON_OBJECT); + } + notifications->append(notification); + @slon_db_save_notifications_to_disk; + @slon_free(session, created_at); + @slon_free(session, id); +} + U0 @slon_api_create_status(JsonObject* status, U8* account_id, U8* to_ap_user = NULL) { if (!status || !account_id) { diff --git a/Slon/Modules/Db.HC b/Slon/Modules/Db.HC index 8a00740..96d3cfd 100644 --- a/Slon/Modules/Db.HC +++ b/Slon/Modules/Db.HC @@ -226,6 +226,26 @@ U0 @slon_db_load_markers_from_disk() db->set("markers", markers, JSON_OBJECT); } +U0 @slon_db_load_notifications_from_disk() +{ + JsonObject* notifications = Json.CreateObject(slon_db_mem_task); + U8 scratch_buffer[256]; + StrPrint(scratch_buffer, "%s/notifications/*.json", SLON_DB_PATH); + CDirEntry* files = FilesFind(scratch_buffer); + CDirEntry* de = files; + JsonArray* notification_array = NULL; + while (de) { + notification_array = Json.ParseFile(de->full_name, slon_db_mem_task); + if (notification_array) { + StrFind(".json", de->name)[0] = NULL; + notifications->set(de->name, notification_array, JSON_ARRAY); + } + de = de->next; + } + DirTreeDel(files); + db->set("notifications", notifications, JSON_OBJECT); +} + U0 @slon_db_load_settings_from_disk() { U8 scratch_buffer[256]; @@ -436,6 +456,17 @@ U0 @slon_db_save_markers_to_disk() } } +U0 @slon_db_save_notifications_to_disk() +{ + U8 scratch_buffer[256]; + JsonKey* key = db->o("notifications")->keys; + while (key) { + StrPrint(scratch_buffer, "%s/notifications/%s.json", SLON_DB_PATH, key->name); + Json.DumpToFile(scratch_buffer, key->value, slon_db_mem_task); + key = key->next; + } +} + U0 @slon_db_save_settings_to_disk() { U8 scratch_buffer[256]; @@ -489,6 +520,7 @@ U0 @slon_db_save_to_disk() @slon_db_save_following_to_disk(); @slon_db_save_instance_to_disk(); @slon_db_save_markers_to_disk(); + @slon_db_save_notifications_to_disk(); @slon_db_save_oauth_to_disk(); @slon_db_save_private_keys_to_disk(); @slon_db_save_reblogs_to_disk(); @@ -516,6 +548,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("notifications", 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); @@ -550,6 +583,7 @@ U0 @slon_db_load_from_disk() @slon_db_load_instance_from_disk(); @slon_db_load_markers_from_disk(); db->set("media", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); + @slon_db_load_notifications_from_disk(); @slon_db_load_oauth_from_disk(); @slon_db_load_reblogs_from_disk(); @slon_db_load_settings_from_disk();