diff --git a/Slon/Api/V1/CustomEmojis.HC b/Slon/Api/V1/CustomEmojis.HC index 0d2c9e0..9667d91 100644 --- a/Slon/Api/V1/CustomEmojis.HC +++ b/Slon/Api/V1/CustomEmojis.HC @@ -1,12 +1,4 @@ U0 @slon_api_v1_custom_emojis_get(SlonHttpSession* session) { - // SLON_SCRATCH_BUFFER_AND_REQUEST_JSON - - if (@slon_api_authorized(session)) { - // SLON_AUTH_ACCOUNT_ID - // FIXME: Implement this - session->send(SLON_EMPTY_JSON_ARRAY); - } else { - session->status(401); - } + session->send(db->a("custom_emojis")); } diff --git a/Slon/Http/AdminServer.HC b/Slon/Http/AdminServer.HC index 7a098f1..04b1fc3 100644 --- a/Slon/Http/AdminServer.HC +++ b/Slon/Http/AdminServer.HC @@ -270,6 +270,30 @@ U0 @slon_admin_delete_announcement(SlonHttpSession* session) session->send(SLON_EMPTY_JSON_OBJECT); } +U0 @slon_admin_delete_custom_emoji(SlonHttpSession* session) +{ + SLON_SCRATCH_BUFFER_AND_REQUEST_JSON + no_warn scratch_buffer; + + U8* shortcode = request_json->@("shortcode"); + U8* filename = request_json->@("filename"); + JsonArray* custom_emojis = db->a("custom_emojis"); + JsonObject* emoji = NULL; + I64 i; + if (shortcode && filename) { + @slon_api_async_delete_from_catbox(filename); + for (i = 0; i < custom_emojis->length; i++) { + emoji = custom_emojis->o(i); + if (!StrICmp(shortcode, emoji->@("shortcode"))) { + custom_emojis->remove(i); + break; + } + } + @slon_db_save_custom_emojis_to_disk; + } + session->send(SLON_EMPTY_JSON_OBJECT); +} + U0 @slon_admin_new_account(SlonHttpSession* session) { SLON_SCRATCH_BUFFER_AND_REQUEST_JSON @@ -315,6 +339,60 @@ U0 @slon_admin_new_announcement(SlonHttpSession* session) session->send(SLON_EMPTY_JSON_OBJECT); } +U0 @slon_admin_new_custom_emoji(SlonHttpSession* session) +{ + SLON_SCRATCH_BUFFER_AND_REQUEST_JSON + no_warn scratch_buffer; + + SlonMultipartFile* file = request_json->@("image-file"); + if (!file || !file->buffer || !file->size || !file->content_type) { + session->send(Json.Parse("{\"error\":\"image file not present or corrupt/invalid type\"}", session->mem_task)); + return; + } + + JsonObject* emoji = Json.CreateObject(slon_db_mem_task); + emoji->set("shortcode", request_json->@("shortcode"), JSON_STRING); + emoji->set("visible_in_picker", request_json->@("visible-in-picker"), JSON_BOOLEAN); + + U8* category = request_json->@("category"); + if (category && StrLen(category)) { + emoji->set("category", category, JSON_STRING); + } + + U8* image_id = @slon_api_generate_unique_id(session); + U8* ext = StrFind("image/", file->content_type) + 6; + + StrPrint(scratch_buffer, "%s/%s.%s", SLON_MEDIA_PATH, image_id, ext); + FileWrite(scratch_buffer, file->buffer, file->size); + + JsonKey* key = @slon_calloc(session, sizeof(JsonKey)); + + SlonCatboxUpload* cb = CAlloc(sizeof(SlonCatboxUpload), slon_mem_task); + cb->key = key; + cb->filepath = StrNew(scratch_buffer, slon_mem_task); + + session->send(SLON_EMPTY_JSON_OBJECT); + + // NOTE: This is synchronous, despite the function name + @slon_api_async_upload_to_catbox(cb); + Del(scratch_buffer); + + if (key->value) { + AdamLog("cb->key->value: %s\n", key->value); + emoji->set("url", key->value, JSON_STRING); + emoji->set("static_url", key->value, JSON_STRING); + db->a("custom_emojis")->append(emoji); + @slon_db_save_custom_emojis_to_disk; + session->send(SLON_EMPTY_JSON_OBJECT); + } else { + session->send(Json.Parse("{\"error\":\"upload to catbox failed\"}", session->mem_task)); + } + + @slon_free(session, image_id); + Free(key->value); + @slon_free(session, key); +} + U0 @slon_admin_manage_accounts(SlonHttpSession* session) { SLON_SCRATCH_BUFFER_AND_REQUEST_JSON @@ -382,6 +460,11 @@ U0 @slon_admin_server_get(SlonHttpSession* session) return; } + if (!StrICmp("/delete/custom-emoji", session->path())) { + @slon_admin_delete_custom_emoji(session); + return; + } + if (!StrICmp("/manage/accounts", session->path())) { @slon_admin_manage_accounts(session); return; @@ -392,6 +475,11 @@ U0 @slon_admin_server_get(SlonHttpSession* session) return; } + if (!StrICmp("/manage/custom-emojis", session->path())) { + session->send(db->a("custom_emojis")); + return; + } + if (!StrICmp("/manage/instance", session->path())) { session->send(db->o("instance")); return; @@ -445,6 +533,10 @@ U0 @slon_admin_server_post(SlonHttpSession* session) @slon_http_parse_request_as_json(session); } + if (String.BeginsWith("multipart/form-data", session->header("content-type"))) { + @slon_http_parse_request_as_multipart_form_data(session); + } + if (!StrICmp("/setup/instance", session->path()) || !StrICmp("/save/instance", session->path())) { @slon_admin_setup_instance(session); return; @@ -465,6 +557,11 @@ U0 @slon_admin_server_post(SlonHttpSession* session) return; } + if (!StrICmp("/new/custom-emoji", session->path())) { + @slon_admin_new_custom_emoji(session); + return; + } + session->status(404); } diff --git a/Slon/Modules/Db.HC b/Slon/Modules/Db.HC index 8fcfc6e..2e5bb50 100644 --- a/Slon/Modules/Db.HC +++ b/Slon/Modules/Db.HC @@ -58,6 +58,13 @@ U0 @slon_db_load_announcements_from_disk() db->set("announcements", Json.ParseFile(scratch_buffer, slon_db_mem_task), JSON_ARRAY); } +U0 @slon_db_load_custom_emojis_from_disk() +{ + U8 scratch_buffer[256]; + StrPrint(scratch_buffer, "%s/custom_emojis.json", SLON_DB_PATH); + db->set("custom_emojis", Json.ParseFile(scratch_buffer, slon_db_mem_task), JSON_ARRAY); +} + U0 @slon_db_load_instance_from_disk() { U8 scratch_buffer[256]; @@ -264,6 +271,13 @@ U0 @slon_db_save_announcements_to_disk() Json.DumpToFile(scratch_buffer, db->a("announcements"), slon_db_mem_task); } +U0 @slon_db_save_custom_emojis_to_disk() +{ + U8 scratch_buffer[256]; + StrPrint(scratch_buffer, "%s/custom_emojis.json", SLON_DB_PATH); + Json.DumpToFile(scratch_buffer, db->a("custom_emojis"), slon_db_mem_task); +} + U0 @slon_db_save_instance_to_disk() { U8 scratch_buffer[256]; @@ -375,6 +389,7 @@ U0 @slon_db_save_to_disk() @slon_db_save_actors_to_disk(); @slon_db_save_announcements_to_disk(); @slon_db_save_apps_to_disk(); + @slon_db_save_custom_emojis_to_disk(); @slon_db_save_favourites_to_disk(); @slon_db_save_followers_to_disk(); @slon_db_save_following_to_disk(); @@ -393,6 +408,7 @@ U0 @slon_db_load_from_defaults() db->set("actors", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("announcements", Json.CreateArray(slon_db_mem_task), JSON_ARRAY); db->set("apps", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); + db->set("custom_emojis", Json.CreateArray(slon_db_mem_task), JSON_ARRAY); db->set("idempotency_keys", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("private_keys", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("private_keys_binary", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); @@ -423,6 +439,7 @@ U0 @slon_db_load_from_disk() @slon_db_load_actors_from_disk(); @slon_db_load_announcements_from_disk(); @slon_db_load_apps_from_disk(); + @slon_db_load_custom_emojis_from_disk(); db->set("idempotency_keys", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); @slon_db_load_private_keys_from_disk(); db->set("private_keys_binary", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); diff --git a/Slon/Static/html/admin/main.html b/Slon/Static/html/admin/main.html index 29d47c9..ae00ceb 100644 --- a/Slon/Static/html/admin/main.html +++ b/Slon/Static/html/admin/main.html @@ -36,6 +36,7 @@ @@ -115,6 +116,34 @@ setContent(html); setActiveLink("announcements"); } + async function manageCustomEmojis() { + clearActiveLinks(); + const request = new Request("/manage/custom-emojis"); + const response = await fetch(request); + const custom_emojis = await response.json(); + let html = "

Custom Emojis

"; + + for (let i = 0; i < custom_emojis.length; i++) { + let filename_split = custom_emojis[i]["url"].split('/') + html += "

" + custom_emojis[i]["shortcode"] + "
"; + } + + html += "
"; + + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + + html += "
"; + html += "
" + html += "
"; + + setContent(html); + setActiveLink("customemojis"); + } async function manageInstance() { clearActiveLinks(); const request = new Request("/manage/instance"); @@ -218,6 +247,14 @@ manageAccounts(0); } } + async function confirmDeleteCustomEmoji(shortcode, filename) { + if (confirm("Are you sure you want to delete :" + shortcode + ": ?")) { + const request = new Request("/delete/custom-emoji?shortcode=" + shortcode + "&filename=" + filename); + const response = await fetch(request); + const empty_json = await response.json(); + manageCustomEmojis(); + } + } function createNewAnnouncement() { clearActiveLinks(); let html = "

New Announcement

"; @@ -307,6 +344,17 @@ alert(JSON.stringify(json)); } } + async function saveNewCustomEmoji() { + const form = document.getElementById('emoji-form'); + const formData = new FormData(form); + const response = await fetch('/new/custom-emoji', { method: 'POST', body: formData, signal: AbortSignal.timeout(15000) }); + const json = await response.json(); + if (!Object.keys(json).length) { + manageCustomEmojis(); + } else { + alert(JSON.stringify(json)); + } + } async function saveInstance() { let data = {}; let fields = document.getElementsByTagName("input"); @@ -339,6 +387,18 @@ } ); } + function updateBase64Image(el) { + let reader = new FileReader(); + reader.readAsDataURL(el.files[0]); + reader.addEventListener( + "load", + () => { + el.base64 = reader.result; + document.getElementById("image-thumb").src = reader.result; + document.getElementById("image-thumb").style = ""; + } + ); + } const formatTime = milliseconds => { const seconds = Math.floor((milliseconds / 1000) % 60); const minutes = Math.floor((milliseconds / 1000 / 60) % 60);