From fd8c2073f01cb9f008a1d315ed2666c45cc39c57 Mon Sep 17 00:00:00 2001 From: Gordon Pedersen Date: Wed, 11 Oct 2023 16:25:14 +1100 Subject: [PATCH] pivot away from 11ty and instead store data in sqlite The idea is going to be to build an 11ty frontend that pulls from this back-end (or perhaps just the database, we'll see) Also considering a mastodon-compatible api wrapper, or at the very least creating a separate wrapper and completely dropping the existing admin routes. ... ooh, I could do a micropub wrapper, too... tl;dr: microservices. This is just the activitypub server now. --- .eleventy.js | 95 ---- .gitignore | 1 + _content/_data/_inbox/.gitkeep | 0 _content/_data/_outbox/.gitkeep | 0 _content/_data/disliked.json | 1 - _content/_data/followers.json | 1 - _content/_data/following.json | 1 - _content/_data/layout.js | 1 - _content/_data/liked.json | 1 - _content/_data/shared.json | 1 - _content/_includes/layout-default.njk | 13 - _content/_includes/layout-feed.njk | 15 - _content/_includes/layout-main.njk | 38 -- _content/_includes/macro-author.njk | 10 - _content/_includes/macro-card-head.njk | 9 - _content/_includes/macro-entry.njk | 14 - _content/_includes/macro-summary.njk | 83 ---- _content/_includes/partial-pagination.njk | 15 - _content/_includes/summary-article.njk | 6 - _content/_includes/summary-reply.njk | 0 _content/atom.njk | 36 -- _content/index.html | 76 --- _content/json.njk | 36 -- _content/posts/.gitkeep | 0 _content/posts/index.njk | 18 - _content/rss.njk | 36 -- actor.ts | 4 +- bun.lockb | Bin 80379 -> 2734 bytes css/styles.css | 211 -------- img/Fediverse_logo_proposal.svg | 170 ------- img/Obsidian.svg | 6 - img/avatar-tiny.png | Bin 1306 -> 0 bytes img/avatar-tt-trans.svg | 37 -- img/avatar-tt.svg | 38 -- img/avatar-tt@800.png | Bin 14695 -> 0 bytes img/banner-1500x500.jpg | Bin 77426 -> 0 bytes js/button-input.js | 61 --- js/follow.js | 35 -- js/relative-time.js | 6 - package.json | 6 +- src/activitypub.ts | 43 +- src/admin.ts | 87 ++-- src/db.ts | 575 +++++++++++++++------- src/env.ts | 3 +- src/inbox.ts | 27 +- src/index.ts | 44 +- src/outbox.ts | 91 ++-- 47 files changed, 536 insertions(+), 1415 deletions(-) delete mode 100644 .eleventy.js delete mode 100644 _content/_data/_inbox/.gitkeep delete mode 100644 _content/_data/_outbox/.gitkeep delete mode 100644 _content/_data/disliked.json delete mode 100644 _content/_data/followers.json delete mode 100644 _content/_data/following.json delete mode 100644 _content/_data/layout.js delete mode 100644 _content/_data/liked.json delete mode 100644 _content/_data/shared.json delete mode 100644 _content/_includes/layout-default.njk delete mode 100644 _content/_includes/layout-feed.njk delete mode 100644 _content/_includes/layout-main.njk delete mode 100644 _content/_includes/macro-author.njk delete mode 100644 _content/_includes/macro-card-head.njk delete mode 100644 _content/_includes/macro-entry.njk delete mode 100644 _content/_includes/macro-summary.njk delete mode 100644 _content/_includes/partial-pagination.njk delete mode 100644 _content/_includes/summary-article.njk delete mode 100644 _content/_includes/summary-reply.njk delete mode 100644 _content/atom.njk delete mode 100644 _content/index.html delete mode 100644 _content/json.njk delete mode 100644 _content/posts/.gitkeep delete mode 100644 _content/posts/index.njk delete mode 100644 _content/rss.njk delete mode 100644 css/styles.css delete mode 100644 img/Fediverse_logo_proposal.svg delete mode 100644 img/Obsidian.svg delete mode 100644 img/avatar-tiny.png delete mode 100644 img/avatar-tt-trans.svg delete mode 100644 img/avatar-tt.svg delete mode 100644 img/avatar-tt@800.png delete mode 100644 img/banner-1500x500.jpg delete mode 100644 js/button-input.js delete mode 100644 js/follow.js delete mode 100644 js/relative-time.js diff --git a/.eleventy.js b/.eleventy.js deleted file mode 100644 index 35e3615..0000000 --- a/.eleventy.js +++ /dev/null @@ -1,95 +0,0 @@ -const ACTOR = require("./actor").default - -module.exports = function(eleventyConfig) { - // I'm .gitignoring my content for now, so 11ty should not ignore that - eleventyConfig.setUseGitIgnore(false) - - // Filters are in a separate function to try and keep this config less cluttered - addFilters(eleventyConfig) - // same with shortcodes - addShortcodes(eleventyConfig) - // and collections - addCollections(eleventyConfig) - - // add the actor data, accessible globally - eleventyConfig.addGlobalData("ACTOR", ACTOR); - - // TODO: assets? - // files to passthrough copy - eleventyConfig.addPassthroughCopy({"img":"assets/img"}) - eleventyConfig.addPassthroughCopy({"js":"assets/js"}) - eleventyConfig.addPassthroughCopy("css") - eleventyConfig.addPassthroughCopy("CNAME") - - // plugins - eleventyConfig.addPlugin(require("@11ty/eleventy-plugin-rss")) - const { EleventyHtmlBasePlugin } = require("@11ty/eleventy") - eleventyConfig.addPlugin(EleventyHtmlBasePlugin) - - // Return your Object options: - return { - dir: { - input: "_content", - output: "_site" - }, - htmlTemplateEngine: "njk", - templateFormats: ["md","html","njk"] - } -} - -function addCollections(eleventyConfig) { - eleventyConfig.addCollection("feed", function(collectionApi) { - return collectionApi.getAllSorted().reverse().filter(item => { - if(!item.data.published) return false - return item.filePathStem.startsWith('/posts/') - }).map(item => { - item.data.author = ACTOR - return item - }).sort((a, b) => new Date(b.published).getTime() - new Date(a.published).getTime()) - }) -} - -function addShortcodes(eleventyConfig) { - eleventyConfig.addNunjucksShortcode("getVar", function(varString){ return this.ctx[varString] }) - eleventyConfig.addShortcode('renderlayoutblock', function(name){ return (this.page.layoutblock || {})[name] || '' }) - eleventyConfig.addPairedShortcode('layoutblock', function(content, name) { - if (!this.page.layoutblock) this.page.layoutblock = {} - this.page.layoutblock[name] = content - return '' - }) -} - -function addFilters(eleventyConfig) { - eleventyConfig.addFilter("formatDate", formatDateFilter) - eleventyConfig.addFilter("dateISOString", dateISOStringFilter) - eleventyConfig.addFilter("dateObj", (value) => new Date(value)) - eleventyConfig.addFilter("log", (value) => { console.log(`[11ty] 📄LOG: `, value); return value }) - eleventyConfig.addFilter("concat", (value, other) => value + '' + other) - eleventyConfig.addNunjucksAsyncFilter("await", (promise) => promise.then(res => callback(null, res)).catch(err => callback(err))) -} - -// default date formatting -function formatDateFilter(value) { - try{ - const date = new Date(value) - if(date) return date.toISOString().replace('T', ' ').slice(0, -5) - else throw 'Unrecognized data format' - } - catch(e) { - console.error(`Could not convert "${value}"`, e) - return value; - } -} - -// dates as iso string -function dateISOStringFilter(value) { - try{ - const date = new Date(value) - if(date) return date.toISOString() - else throw 'Unrecognized data format' - } - catch(e) { - console.error(`Could not convert "${value}"`, e) - return value; - } -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2d8c964..38aec2f 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,4 @@ _content/_data/_inbox _content/_data/_outbox _content/posts _site +db.sqlite* diff --git a/_content/_data/_inbox/.gitkeep b/_content/_data/_inbox/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/_content/_data/_outbox/.gitkeep b/_content/_data/_outbox/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/_content/_data/disliked.json b/_content/_data/disliked.json deleted file mode 100644 index 0637a08..0000000 --- a/_content/_data/disliked.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/_content/_data/followers.json b/_content/_data/followers.json deleted file mode 100644 index 0637a08..0000000 --- a/_content/_data/followers.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/_content/_data/following.json b/_content/_data/following.json deleted file mode 100644 index 0637a08..0000000 --- a/_content/_data/following.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/_content/_data/layout.js b/_content/_data/layout.js deleted file mode 100644 index c98ebc5..0000000 --- a/_content/_data/layout.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = "layout-default.njk" \ No newline at end of file diff --git a/_content/_data/liked.json b/_content/_data/liked.json deleted file mode 100644 index 0637a08..0000000 --- a/_content/_data/liked.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/_content/_data/shared.json b/_content/_data/shared.json deleted file mode 100644 index 0637a08..0000000 --- a/_content/_data/shared.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/_content/_includes/layout-default.njk b/_content/_includes/layout-default.njk deleted file mode 100644 index 07f180f..0000000 --- a/_content/_includes/layout-default.njk +++ /dev/null @@ -1,13 +0,0 @@ ----js -{ - layout: "layout-main.njk", - ctx: function() { return this.ctx } -} ---- -{% from "macro-entry.njk" import entryMacro %} - -{{ entryMacro(ctx(), author, url, content) }} - -{% layoutblock 'foot' %} - -{% endlayoutblock %} diff --git a/_content/_includes/layout-feed.njk b/_content/_includes/layout-feed.njk deleted file mode 100644 index 9977d86..0000000 --- a/_content/_includes/layout-feed.njk +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Feed -layout: layout-main.njk -scripts_foot: '' ---- -
-

{{ title }}

- {{ content | safe }} -
- -{% include 'partial-pagination.njk' %} - -{% layoutblock 'foot' %} - -{% endlayoutblock %} \ No newline at end of file diff --git a/_content/_includes/layout-main.njk b/_content/_includes/layout-main.njk deleted file mode 100644 index c0f0795..0000000 --- a/_content/_includes/layout-main.njk +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Mon Repos (Death's Domain) ---- - - - - - - - - - - - - {# - - - - #} - - - - - - - {{ title }} - {% renderlayoutblock 'head' %} - - - - {% renderlayoutblock 'header' %} -
- {{ content | safe }} -
- -{% renderlayoutblock 'footer' %} -{% renderlayoutblock 'foot' %} - diff --git a/_content/_includes/macro-author.njk b/_content/_includes/macro-author.njk deleted file mode 100644 index 141d191..0000000 --- a/_content/_includes/macro-author.njk +++ /dev/null @@ -1,10 +0,0 @@ -{% macro authorMacro(author) %} - {# {{ author | getAuthorData | log }} #} - - {{ author.icon.name if author.icon.name else - - {{ author.name }} - {{ author.preferredUsername }} - - -{% endmacro %} \ No newline at end of file diff --git a/_content/_includes/macro-card-head.njk b/_content/_includes/macro-card-head.njk deleted file mode 100644 index 0260e5b..0000000 --- a/_content/_includes/macro-card-head.njk +++ /dev/null @@ -1,9 +0,0 @@ -{% from "macro-author.njk" import authorMacro %} -{% macro cardHeadMacro(author, date, url) %} -
- - {{ authorMacro(author) }} -
-{% endmacro %} \ No newline at end of file diff --git a/_content/_includes/macro-entry.njk b/_content/_includes/macro-entry.njk deleted file mode 100644 index 623a5ce..0000000 --- a/_content/_includes/macro-entry.njk +++ /dev/null @@ -1,14 +0,0 @@ -{% from "macro-card-head.njk" import cardHeadMacro %} -{% from "macro-summary.njk" import summaryMacro %} -{% macro entryMacro(item, author, url, content, summaryOnly=false) %} -
- {{ cardHeadMacro(author, item.published, url) }} -
- {{ summaryMacro(item, url) }} - {% if item.type == 'article' and summaryOnly %} - {% elseif content %} -
{{ content | safe }}
- {% endif %} -
-
-{% endmacro %} \ No newline at end of file diff --git a/_content/_includes/macro-summary.njk b/_content/_includes/macro-summary.njk deleted file mode 100644 index 6ad8083..0000000 --- a/_content/_includes/macro-summary.njk +++ /dev/null @@ -1,83 +0,0 @@ -{% macro summaryMacro(item, url) %} -{% switch item.type %} - {% case "article" %} {# article summary: #} -

- {{ item.name if item.name else item.title }} -

- {% if item.summary %} -

{{ item.summary | safe }}

- {% endif %} - - {% case "reply" %} {# reply summary: #} -

Reply to {{ item["in-reply-to"] }}

- - {% case "like" %} {# like summary: #} -

Favourited {{ item['like-of'] }}

- - {% case "boost" %} {# boost summary: #} -

Boosted {{ item["repost-of"] }}

- - {% case "bookmark" %} {# bookmark summary: #} -

Bookmarked {{ item["bookmark-of"] }}

- - {% case "read" %} {# read summary: #} -

- {% if item["read-status"].toLowerCase() == "to-read" %} - To Read: - - {% elseif item["read-status"].toLowerCase() == "reading" %} - Currently Reading: - - {% elseif item["read-status"].toLowerCase() == "finished" %} - Finished Reading: - - {% endif %} - - {% if item["read-of"].startsWith("http") %} - {{ item["read-of"] }} - {% else %} - {{ item["read-of"] }} - {% endif %} -

- - {% case "watch" %} {# watch summary: #} -

- {% if item["watch-status"].toLowerCase() == "to-watch" %} - To Watch: - {% elseif item["watch-status"].toLowerCase() == "watching" %} - Currently Watching: - {% elseif item["watch-status"].toLowerCase() == "watched" or item["watch-status"].toLowerCase() == "finished" %} - Finished watching: - {% else %} - Watched: - {% endif %} - - - {% if item["watch-of"].startsWith("http") %} - {{ item["watch-of"] }} - {% else %} - {{ item["watch-of"] }} - {% endif %} -

- - {% case "rsvp" %} {# rsvp summary: #} -

- - {% if item.rsvp.toLowerCase() == "yes" %} - - Will attend - {% elseif item.rsvp.toLowerCase() == "maybe" %} - - Might attend - {% elseif item.rsvp.toLowerCase() == "no" %} - - Won't attend - {% elseif item.rsvp.toLowerCase() == "interested" %} - - Interested in - {% endif %} - - {{ item["in-reply-to"] }} -

- {% endswitch %} -{% endmacro %} \ No newline at end of file diff --git a/_content/_includes/partial-pagination.njk b/_content/_includes/partial-pagination.njk deleted file mode 100644 index 10f0194..0000000 --- a/_content/_includes/partial-pagination.njk +++ /dev/null @@ -1,15 +0,0 @@ -{% if pagination %} - -{% endif %} \ No newline at end of file diff --git a/_content/_includes/summary-article.njk b/_content/_includes/summary-article.njk deleted file mode 100644 index edb3ccb..0000000 --- a/_content/_includes/summary-article.njk +++ /dev/null @@ -1,6 +0,0 @@ -

- {{ item.name if item.name else item.title }} -

-{% if item.summary %} -

{{ item.summary | safe }}

-{% endif %} \ No newline at end of file diff --git a/_content/_includes/summary-reply.njk b/_content/_includes/summary-reply.njk deleted file mode 100644 index e69de29..0000000 diff --git a/_content/atom.njk b/_content/atom.njk deleted file mode 100644 index e053b60..0000000 --- a/_content/atom.njk +++ /dev/null @@ -1,36 +0,0 @@ ----json -{ - "layout": null, - "permalink": "atom.xml", - "eleventyExcludeFromCollections": true, - "metadata": { - "subtitle": "A feed of all my posts on the fediverse", - "language": "en" - } -} ---- - -{% from "macro-summary.njk" import summaryMacro %} - - {{ ACTOR.name }}'s feed - {{ metadata.subtitle }} - - {{ collections.feed[0].date | dateToRfc3339 }} - {{ ACTOR.id | absoluteUrl(ACTOR.url) }} - - {{ ACTOR.name }} - - {%- for post in collections.feed %} - {%- set absolutePostUrl = post.url | absoluteUrl(ACTOR.url) %} - - {{ post.data.title }} - - {{ post.data.published | dateObj | dateToRfc3339 }} - {{ absolutePostUrl }} - - {{ summaryMacro(post.data, post.url) | htmlToAbsoluteUrls(absolutePostUrl) }} - {{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }} - - - {%- endfor %} - \ No newline at end of file diff --git a/_content/index.html b/_content/index.html deleted file mode 100644 index b663fd1..0000000 --- a/_content/index.html +++ /dev/null @@ -1,76 +0,0 @@ ---- -layout: layout-main.njk -eleventyExcludeFromCollections: true ---- - - - -{% layoutblock 'foot' %} - - - -{% endlayoutblock %} diff --git a/_content/json.njk b/_content/json.njk deleted file mode 100644 index 623d805..0000000 --- a/_content/json.njk +++ /dev/null @@ -1,36 +0,0 @@ ----json -{ - "layout": null, - "permalink": "feed.json", - "eleventyExcludeFromCollections": true, - "metadata": { - "subtitle": "A feed of all my posts on the fediverse", - "language": "en" - } -} ---- -{ {% from "macro-summary.njk" import summaryMacro %} - "version": "https://jsonfeed.org/version/1.1", - "title": "{{ ACTOR.name }}'s feed", - "language": "{{ metadata.language }}", - "home_page_url": "{{ ACTOR.url }}/", - "feed_url": "{{ permalink | absoluteUrl(ACTOR.url) }}", - "description": "{{ metadata.subtitle }}", - "author": { - "name": "{{ ACTOR.name }}", - "url": "{{ ACTOR.url }}/" - }, - "items": [ - {%- for post in collections.feed %} - {%- set absolutePostUrl = post.url | absoluteUrl(ACTOR.url) %} - { - "id": "{{ absolutePostUrl }}", - "url": "{{ absolutePostUrl }}", - "title": "{{ post.data.title }}", - "content_html": {{ summaryMacro(post.data, post.url) | concat(post.templateContent) | htmlToAbsoluteUrls(absolutePostUrl) | dump | safe }}, - "date_published": "{{ post.data.published | dateObj | dateToRfc3339 }}" - } - {% if not loop.last %},{% endif %} - {%- endfor %} - ] -} \ No newline at end of file diff --git a/_content/posts/.gitkeep b/_content/posts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/_content/posts/index.njk b/_content/posts/index.njk deleted file mode 100644 index bbd4c63..0000000 --- a/_content/posts/index.njk +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: My Feed -layout: layout-feed.njk -pagination: - data: collections.feed - size: 20 -eleventyExcludeFromCollections: true ---- -{% from "macro-entry.njk" import entryMacro %} - - - diff --git a/_content/rss.njk b/_content/rss.njk deleted file mode 100644 index 115fc47..0000000 --- a/_content/rss.njk +++ /dev/null @@ -1,36 +0,0 @@ ----js -{ - "layout": null, - "permalink": "rss.xml", - "eleventyExcludeFromCollections": true, - "metadata": { - "subtitle": "A feed of all my posts on the fediverse", - "language": "en" - } -} ---- - -{% from "macro-summary.njk" import summaryMacro %} - - - {{ ACTOR.name }}'s feed - {{ ACTOR.url }} - - {{ metadata.subtitle }} - {{ metadata.language }} - {%- for post in collections.feed | reverse %} - {%- set absolutePostUrl = post.url | absoluteUrl(ACTOR.url) %} - - {{ post.data.title }} - {{ absolutePostUrl }} - - {{ summaryMacro(post.data, post.url) | htmlToAbsoluteUrls(absolutePostUrl) }} - {{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }} - - {{ post.data.published | dateObj | dateToRfc822 }} - {{ ACTOR.name }} - {{ absolutePostUrl }} - - {%- endfor %} - - diff --git a/actor.ts b/actor.ts index 04bfb4e..29a7a5f 100644 --- a/actor.ts +++ b/actor.ts @@ -8,8 +8,8 @@ export const summary = "" // avatar image const icon:{ type:"Image", mediaType:string, url:string, name:string } = { type: "Image", - mediaType: "image/svg+xml", - url: BASE_URL + "/assets/img/avatar-tt.svg", + mediaType: "image/png", + url: "https://s.gravatar.com/avatar/bc0f8c2d2ecc533cb236ce1f858365a25dbbc80ce42df49b9a95e88f07f91720?s=800", name: "My profile photo — a pixelated version of me" } diff --git a/bun.lockb b/bun.lockb index 941a179f4201e03bdcab96a40afd5b5bca95c2f2..e22d87338c6ebddbd26b348bb1f3ab35bd3781be 100755 GIT binary patch delta 666 zcmezUnPr{m1U=3Fp^~kV?0pFWPiH zUvMd5d9a9+NWGNRb8CIYi2!jARJ$Zq-h#EcxAUUAp7}yvl-wG^ts1clZ{tZ*A z&-N#-dlk=L5OwP}Vf8q2s_d8y+co7$9+oRED(sN_c%)!)j_7ik)2*jIT$z;pwrkqT zo8`L93>$o{C-(+PO!f`(W@MgRIXir^M=OE!sIz?zj1AV2I>*E$^Wm}Nd5Z{0U*^NH-3Ohv#?J-c3VjfdM0{C(>Jm(%1>VP&u05R3r1T;0M{O}9smFU literal 80379 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 { - max-height: 24px; - vertical-align: bottom; -} - -.button-input-container>label{ - display:block; -} -.button-input-container>input{ - background-color: #666289; - color:#fff; - border:none; - border-radius: 4px; - padding: 4px 66px 4px 6px; - margin-right: -60px; -} -.button-input-container>input:focus{ - border:none; -} -.button-input-container>button{ - background-color: #666289; - color:#fff; - border-radius: 4px; - border-color: #fff; -} -i>img{ - max-height: 1em; - vertical-align: middle; -} -img.tiny-avatar { - max-height: 17px; -} - -img.small-avatar { - max-height: 40px; -} - -/* Pagination */ -.pagination { - margin: 50px auto; -} -.pagination li { - display: inline-block; -} -.pagination a { - padding: 8px 16px; - text-decoration: none; - border-radius: 5px; - border: 1px solid var(--on-bg-fade); -} -.pagination a.active { - background-color: var(--primary); - color: var(--on-primary); - } - -.pagination a:hover:not(.active) { background-color: var(--on-bg-fade); } - -.pagination a.disabled { - color: var(--primary-fade); - pointer-events: none; - } - -/* End Pagination */ - -/* Feed Entries */ - -.h-entry { - max-width: 500px; - min-width: 320px; - background-color: var(--primary-fade); - padding: 20px; - margin: 0; - border: 1px solid var(--on-bg-fade); - text-align: left; -} - -.h-entry:not(:last-child) { - border-bottom: none; -} - -.h-entry .p-author { - max-width: calc(100% - 80px); - display: flex; - align-items: center; - gap: 10px; - overflow: hidden; -} - -.h-entry .u-photo { - vertical-align: baseline; -} - -.h-entry .display-name { - display: block; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.h-entry .display-name .p-name { - white-space: nowrap; - display: block; - overflow: hidden; - text-overflow: ellipsis; - font-weight: 700; -} - -.h-entry .display-name .p-nickname { - white-space: nowrap; - display: block; - overflow: hidden; - text-overflow: ellipsis; - color: var(--on-primary-muted); -} - -.h-entry .permalink { - float:right; - text-align: right; - font-size: 0.8em; - color: var(--on-primary-muted); - display: block; - overflow: hidden; - text-overflow: ellipsis; - max-width:80px; -} - -/* End Feed Entries */ \ No newline at end of file diff --git a/img/Fediverse_logo_proposal.svg b/img/Fediverse_logo_proposal.svg deleted file mode 100644 index 854e6f0..0000000 --- a/img/Fediverse_logo_proposal.svg +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/img/Obsidian.svg b/img/Obsidian.svg deleted file mode 100644 index 57c05a2..0000000 --- a/img/Obsidian.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/img/avatar-tiny.png b/img/avatar-tiny.png deleted file mode 100644 index 390531ea52d8b4cfea563d6c6cda343ecd3fd407..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1306 zcmbVMT}T{P6dq&KCK>~w!4^r|$t(>~vpYY#Gn*N9BlEX$Y1ZiOf?BFL&Wt+I`RUB) ztd>&KSZW}_rjW*%mo(6lA7V*CDUnu4X}VM+ZSqhkrIeBv6GHRSmnzyj>#psCsq--*?VE_nbN1+;p_6qNW0ZpsHk}kOuQ!|JhLv#@nq=8o*TMq>pmY^wPd%U_95$ zrdSBNHBq@LZ3nj2d@L*Wbe~QK%36@J(H87v8b8|^KhqIA+ZlT=ji2d^tt~99FDxw2 z&aN*mt}QMu&&&YcHP2hg=dTY9Elo`=UAnZI&(E}1KlBb<_q-qV*Hf|2>+0(2>+1=E z2#3RgKmZ~pq433v^C#{B@=mL<-GQLp-}+D4_vTdvg33>7**3Q=b&QcsJtQe+w;Ian z7C=K#tRZJfa=+@r-DwARTQq3@L+A2(j!a*6sahRe)c!Z=V z{18lFIDul|rh<5cAt{E4!o>>#?qE1(D?LnFNEF=xD~|NJuEn6J=XoJ795U@*6sKw0 zmm!ECKm?s(!TcVxpu%)7D)iDQcSq1Pwi41c1Y)3v2M8h1nHE=r8fMCjl zx@x#dA&!7AAw^S|!vaYQkuXM3EGe=$ewd3=f=ChqP4O%kOB!2ou>_qU2r)u%0*B)w z&qWg~ut<^<69i31O5CL3xRN2OCBGW*TjYkH$z^z3m0Z)#n&v>M5}Nx=*L3KPuP23Gv9nVbO zpX5d#FzB9Jck15RQM9F0lXZh3`K3`J;xhm*<{8_uISc`}h9A zcgQbrS56FkaQ{=SW;H*&XSV&+H`%fB$@7)Q)A_H*e&@!Q&~3e!t6!4GZ@l=`m7sOw te9JG}zL+L{eX{cQzPVjqI(Ei;DP@?gOXyCFwJ$n{|{kjuap1) diff --git a/img/avatar-tt-trans.svg b/img/avatar-tt-trans.svg deleted file mode 100644 index 90e8520..0000000 --- a/img/avatar-tt-trans.svg +++ /dev/null @@ -1,37 +0,0 @@ - -Made with Pixels to Svg https://codepen.io/shshaw/pen/XbxvNj - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/img/avatar-tt.svg b/img/avatar-tt.svg deleted file mode 100644 index 3712fd0..0000000 --- a/img/avatar-tt.svg +++ /dev/null @@ -1,38 +0,0 @@ - -Made with Pixels to Svg https://codepen.io/shshaw/pen/XbxvNj - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/img/avatar-tt@800.png b/img/avatar-tt@800.png deleted file mode 100644 index 4929c9c206dd46ff146f514785b934f02ca4044b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14695 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 q@mGXa}Z<2$Tl{yo#)KLX~-73@0 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
T+A+FSJzi1D(?q_U~Sl`&}YP_&lvf5=P){@iG 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
eFX8+@j%Y; z=!WtbvBqKFNUr5Uj0RQF5}=tR0Lcr7P8`4$>L>cl30Ja`!L;$=i9s=S?LCW@d4l(&*DTrmQWUe&EA_?O{p z{nXY)*0j%hqXf)Fisst{fdEi}zu|x#Q=cPbP8X9CH)qh|D*kDnzg4kUZq=U3*s}i1 zcHGi^a~mS}q}C~$q^}Iw=f(DW)%S%$^^&*)2LcHVt(GdvS}t%^md7t1NFPWrTOz-b zbxD`fHL~u*J;V|NurzY#b(6X2wtT!wq*rDo*hX@lcv3@I6Ij2^3gWD?dFt3|_%0?Z z`)1#QPbkZKBbfZ6?^ek5MjHiAF3 z5@5qf8ZDehk2uG4>iAehrLmQUW)jaIy6kNjqL1}xOGtShdtH?vy8`lOXPvJf*%iCW zVzhh?BzcQ5B}wMyj|XvgC6#uS^6o(H$ZgZta;Mi<4<6SVNvRDl z)}%U-|F*tOc>C6^a2L%uiP6({jAYOisP=Hp8Euf zdN6z%L#&MYuq`AykT~UPh20@^#9mj*ER2Glu;Qd~_-p=gnvd-Vf*J0>bsU3V%xHO>Qfi9B1>W%aJUbl6k3hlS;2)Ns*&<8*nZXu)Hb$K!S` z{pAEH3PJ)b;|w0ai}DCuhbpR1{tw!~M76J9b~Lo_v$x~0kn#*LEd)RR6KEA-Xp5ms zc%!(Cf_qKh?S3NCE))_^yc_8B%o-(WsR=>sA@alb4LLsPU?IEVj2EcmDLb42li=+5 znu727+_5gc>5@gXf{^RG+mFo(R3HssaqJJ+!MPAswSxJc#vaA#TjTR z<>c|@DcNQ{u!js@-fu)h$i{;9fFCrcW#VXPFd!N7DLN*WEC@T~!GEr(^g&MWrL3m- z*Kb8EfJ_FFi5qYUNn!~5%I!P05X6e#ifqR!tE7v`@?+=FN>aAY-Q8|tp6YG*{E%M~ z;@bYX(EL7;u61}*wB(s^!N)=DW)gMz!gQV#Us}hzHU#4ED}(Vuxw6X{@G=}x^<-wx z7LXu3*t{ybycFiIcBXCa@Mx^JP40Q1cab)$)<pDhidCi}f4pNb#}clL9> z=bYEE4;#iMP0saK1*a9?4#wv_Y#*dPYE-oSRKxqRwQth6A@jcf)IA=YbKAy;)k-SN8}_%}OJm*WYbRHLWX00-{yE*XmL` zmtHmadJd~J-Y+ofkJ+{Q#*wTO-2=Il3UMVRh%*(@MIv5NKC<@Owq#*D#K`u`a1v;g3ChraNNe0T~yJ-k@~glg5kP z>kn!MXnU1~9bk5R{-ChftaXN{`kWj0(KU4sL{@rk>stDXNqbasSly?r8AX-S?#=R8 zclT2y9_BRUt?S56ySIhfjSc1!kzo(h4W49C6MBtEo73$X{Ea@TUoGgyq);{zo^TCw2-+z00tS$83G2AbaJqxzMMqge}uo-%Lx$ z`a?hsdiRxFk7XXiu``BG9~(8>F6Kfz58;{G9BKD%M|D z%FLUvX|Ssvbps{A9XuZOujPf18jLdJ?Tld;@DuiTt>Kl75TK8>S|CG}gg9P+9Y0LI zfM!U9k(vB%0Kfz#l9JtssMAc2T<(-5;_f_dQ`lFd5?!BTvwZIbdtZI4R-KV@y61r2 zMt@BA-Qm7vN!uV+*xpM*$UBDwB384rWh__2e_E^M)_3YM|-Q>F}`!zskDMsVPY&o!)+qQlZ;174a<6oszVH<-Ql(n>u!2Uo%ufw z)dH?!w~z%E;yzA(eVvRi2{SJi?~yoUaozo6wFU(2Qpf5o-?b_6wDnPpU3p;7_Il~@ zm?x4$2aV$I)JdFCa!;!Wm*GavYVPrj}0~bozGoS|Ca5~8$PNXq}QgV zUUZ5XYOJc1y6cM44sExCp4lb=`Q?+jDXTYyHzD#B?M%O}>Dsm*(VJepHu(5CEjx5R zz={7&<=G8`>$2@tG(-3m3PVCYHV5X>f}t(x; zgBIi4Q*C6pN_f3_9Wrl@u<7?7RxcTwU*fdl~aLv2N-QNK->{~2~ zcG;JUAAB>=Wj$G?Q63?pvdekho7G6oJH^7?Yu4feE|cGOS?&4jS%S2}i^T73mue#9 zLWT4gJEeRfX#M=FYSODA$LZpjE>q-V&G6-uV|_)&Dq^!AMe9;b_qzHzFWpsi9z+t! ztKLafjCwCPJH1rg=pSe6!e!A<#PxHrwfJ6rzf7=1e50$MkW}0X#h(+PROGZz{MiuW zZbJ`bHRR&|7JtT*MGY=_srB59OcRJ_**}cXOs{DPpU$jNZ`ObRY>LT!NVL=<0T5xe zg{NY^-JZBfNZVPC!#8|avyiO+H4rLZ&}lTLOdg;Uq|PQvcrwqCfdduJt^SSZSeB_J z6Q03w_y|A(3~k`?aJ6G5QN}+(T)oMrKO_!z?I1_ zUSwWUUD^KM{$UJ^|0!V^hFckr%0tk7C zHr>j7o7FMc2m4&VjorNGsXXSH2m|pJKRi;@KN_y@`Z~Hsns5Ndr3!cP8kO>woNC6| zWQo+p7ME(otjQxR`#U{`DUBH;^L8^A*rtf~Xr#xq9*W(AoGWMt%nKzVmN6-}`cu1ZWTECIM8v+EJvHMPqzr9psy83}>tv*IG-zCKc zCzI;JCHZQITr+BYM9+Q}PmftR&B)iEmMy*6x|w+JyBI%9p4}Jh?J_aaBp`8)8*#2P zle~Y~y3)7TPW+f>Lo-i_9wUw`Gg=S98HnD}<|`zy4e{puZ8gLY|u{g2AUQZhGee-Ynmc+#ry9ekOQY^Ug|pv%MlsYUPZF8fO%x<5WHNb#NhNx@1OHY$q3LM!2Cyc^o|M>Ni5 zCvaW9164|wof?}WT#Nv{%kWm@*<~`(0h1sIMu5#c1&q`n#?W7@ZuPK)zDfq_FoT{XDybay-}pZ@)C11 zZZ{N9(^UZ!$2ixbUOku)%AUp?H?OdH_UgIN^!kFHkF6qi0Yis-m}Q<6B7n{UY{}5o zD1iBl>HwyAyva2XoZm%FGgVf>3@a#h`g( z_;hnwMuXGtpvu>7bnXj!%Yz-MTT-`oh<460g&ySK!-zR}A-g?4SAqj!tqA&h`&wv6 zM*R$GHWa6Y7za+Y(6JgefK1s)U)MEUQi73yh~1UG8e)>E669%lK2*^;dgT9Is**5g zwoJRi+!9VGk4GZnnsG!X;<%UKg*GvO3S+CpQMe-8lb<3)$d57*J5xliaQoR(b6AQ9 zEfw6c+E>W1v7pO3um9*gg{!dOpy*fIhMa>g-$PadlCHYrgUk%uF~q~V$5%ObEBk%O zSP_x6ez7FX&|7gztG6J|^Ig9+H@kI~be-4XT(H9eWh=Wa&buQ-fG0N~lK?1PZ3D3h z$>_+ zuV7r3+{qe?=SS>=ip_qBlQC7c^8>7)a?+EYG=y;4c1&l`=49LFi2^*v#X{#;M&5&* zW1UumB$AgGo^ybOn$lT+XSl(*Y1Zu{)_1PtkL(>Cb4>}gzcA)$eeBIaV`hB(qeljq zy?w0FkjTy;Q)JKM_OmUa`A(_}DY~u6VVKCLSgDIM4u!H;lm7Hhs6Q9izf0jr!`|bIWv3Z=uxZ-*> zEG+(|F90p_gz}J6?-ol+u&RYgk4m3Z#ue;yJ8|k4VwcfgW!o0DP*&iR-)XVl)>|$< zQmNXu#%oFCt5Du@x|Ms0pKKVze`u8TO0-(>=6y>w%ijo)$xmHRkL~zU*73?*d#Wos zO@B>jbO)e|T?8yXfsFOPS)R({b7xQ3uj&;9B(ZY0Rfr#TT?EfG;kw-8xbu9l&Pj00 zA!5Yt1{CIzBEQvpcqVXe5o4+n5$*E#p$Ns~ogJNH#WyO1c*YnCBL^O{+@t$kjl56w zpS&pJXzsUVu2nKig0)%qsQtC8A^!*?1lpCwxg}+zR?(#?`~mJoYr6HRM)GTMJlG9%^-!a#i5r9z2pBNP^cd zXNoPk(w{^OZ24&)bXWEVRidVQr{1<;$DlH?C2Lr!+-I|Ncr@ert>rAjtossC@TxZ+ zOpsPa2i=SikIp+m4^`x0bFzkD&tD5R`i=e?aYQ4_50q0~0O<=U;TOVDMid!}B0 zo4j|@Mh+?d&R>Hob^2*;yzrhZpbXrq9Iz7G;~<62!zz{~V#z8;cI09hqOuuja{Ny{ z-w4p+N&gTr26OcnNyf$%wquE8VVy&f);dD|2#kh{IAK`dYcolJF$C-WiX5ipX(3{v zAo<`HSTK;A4*5v#Lm1MFiZq3ariVEh1h~X}rK_8;?>T%&>mg`mWL2vRT@pqsndx*b zQL&Jd6Rw9TOZ*N+dYpV7WR59VDPuX?3MwY}J=8dj-fwXu3#cp16v#hOT0XAv8IReJ zM*1g2#sBXouRM^0UqWP*Zj!1ODA<&>i>%E|@kWHlJL}z{I=Xlh!NqO|cwm$DvozBh z_+`v@u$2wXj(TifL z8X+zZAHDQl?KVur^u||g)tcVmRg361g2Evtf5-q7ag%ID&X&}?IVxrsLqC2>E8)P8 z$XD@o`1I;q$iDUb%bVzRI`pNwXn{iO4cZ6KgV$oR zq?@hFcn$LH3$kp3?rJz7=A>PR1cBYh=E`YG6?`7z^p#`-3+h2KaET|VCZ!lb!v%bgSf z%oW%-x%!p7!$05JPkL)1Nyq6jsg!L15ayL|)!pZH_BmZ?|KmeUsbbm8wUUs3?&;9* zmpVMU4mP8xka2cb3@*>x!w6{;*m;PmS)-zP`=BcIw;?J#N;6@Hy9v?yqvJlunAW?i zud15_JFT5b@Xu(Vvq&ZN1xxvCmTVqCj`Kr29uzDUkV^OVOk{o`bVRa0^qbAx6?sSI zU%p%271ENp=oC`XlY?lKZVk{@nu#AxMb6G79#3Ahc}hjttYd@g?{06831LB$EnIRpSSiUdIm?35K!?2n}?jM~_d@%3>uYx4(b*PJWDG>X*S|X4*GLN!&M0Ox;!;K_mT) zqEzj3n|-%#p+)jlpbI8ga)dm{`{v8cd~IM?iO-0OiR(V@*&K?_1?P=kAN3t?ntWvA zk(;>OpG~QU3D04)@2L<$2b($)9hzu@ve+r?62bd`X+wKfKKR=23@*hyB~88|MI&0n zLZp5M+bg5PT5lJ~xYEouXp`NvG_3pJkTr4#0xW@--@bjz@aI$xh!AB7+L|;X`mj^1 z1v7HFJuQ^XQxnkQ=hl7`6WiPx_z!UStsuUof~`Sj{pMUloxzSt2%54v4#f*QYk&j^ zplh^n3a7I=S&)tu#z|38fG@aONLU=TlZq+#pfv%4qEXiZHe&#u4-M-6`w~ULnP;PQ zWQ-xWTQf|)d``9&a3=rfZTbHfpJ1##kXS7N#KpbGi^A<`MdsfLO4k{P&+tZHXGO;3 zccXe4*rntT+fay@Pe9-Uf0zTNgsMYe2%9qT0x-k{?gIs6?7iVkxlq?=4 zQnLJg zCv8`Gxx=-^%Hig28U zRIDB<^cZN$g0d>SiGTz8^^(9XK2P{hx7a}ezz(QLrk?6KPu~Dgh3izTDJsM;uXPh5 zGepGzP#eHCfc;d10$DQrKssJ+10Kmtf}$A%Iba8_vlUs8%;e_8=7MN;9+0H)0sKKWqnMK zCaftAxKSp3`pVMsA<;PrJ^g)gQKfJE*SLC+d)Wq-M}7(19TY!gwmxWU$n&a;b$ZSZ zKi*JN>T{NZT^T%77*_jwI?HGZNhy#!dngGbU2mu&P8QaJV}m}uP3U^SlbDR$4k^hn>WexIb_!lf)9=-yHV2}t^4xnJ|QxRn#s~WQZ@-hYTo&UNo ztn*R`;|6`rEuDo5&KSbAMF>nGVj+O>fC-*h(CKbyI0LQ`RHOy>FmXV5ArKZRK!X4w z8h21|^gLJkDuy)(5c)s--N^>11oFXS7z=My!>>=r@gP0TY>O`VHLh4q?? z#S$Al2{M;5u54Q~c&u|%aJlZX>flyY>M1LkvP6lUYw@?J7)ct`BjJel?3u!>cRW#q zvc+)@mIX};o5_HP3p0@O54CWm(a`4WS-XQl-p)X*DZc1bgc@QtdT<~W|DNv+x~f&i zR+e8bXTb0ZMe^6#MM0ITuZUAz0)z9OW6#hM)e3R`oIXkduA89wLfh%<{9FF~b;bCt zykgDhZ-m7Hy}I&H7#-y+1Jd^?xF0cv!mnUEUAEdEqDyf9?WGWIJ#aZ?Irb0l12zyi z-u81XqS$H$cL2faD&RX{ERZQwyYVNcx*&D{S4Kqy=)*~o^PqeK?*pb?#IDh>$reD) za3^&des6Jopjjoox z6xZv!H8tlY_QH)d)v4p_Kg{me`8R3h(-N zNEKLHkYP@ZPBN#-6fx@g$@=1H&Ls~Jq!AlcvhZi95<}q|2w}&OC(aFo!d(`5vdc0# zyWP;!B=Z1(@n2MXU%q9@d+o-pYz-pw-TrCKY#S z9VlM>Xd+HT5HS(rpHpLLG7w9|llei5g5avDp?VIT1_1R)PSJrw_M17i2r6krOh8LB z?tP4>Z4(udZlT-Cmm&eNKSqeB*B&fW7|_qpMiw;VmsMHmd(4_br0N2aLjNf@np60V zRHlk0juq4y*L;q0jQU!o{bDds=V=|uYy1`8XoTd0H15;Fk**A3N*ElEn>Qp@VY1Em zlom#CD!bizl&;WUCvw!((1jc13SPnz?FH|jb-uk6!e%l9HIxSC(NK;~!MO-I7~Dk+GL?4)PLgrC0gh^tGWnP%TCV6BaqkaZY+&334aa%s%p0NC zK)q{35LYE}grVfmC;riOe`v>1Pgnp}z?qpTLFM)jEpZ$z#Cfwdr>lAk@-fP}C^sv= zAq#jY6TuW=0$?#yCS48;e^Mr0`YCM+K0pwa0zeL#*cGEu9V!f&bXietJ?R1Z<~#i! zPLg=!9z8}mdX-^5R`Jpus`(E{EZ>w-4ugBRuv_;JSN$Oy$QN+gTC`yrp61+ENeMD{ zwR4+;TL)bvt>9U4>m8=(#W`LN*I%X61MgD5j!+FXB|=_)$k9D-G(p$@3VFNCI-l2CzzIicK*a3CC-{>KFe@{>uIKU0E!N1z*6 z6+jS!c}E{%TtAPMsklK~wpJ2qdw_}}+Cm)l=ioj>7K35#CLnBY+Bvy3hj8?Dj$nFL zI0^c1Dp^Cd$X~H@B&ml_xxv^d(#5~ijDdDi+DDzbp^hk~<$h7CtE!GJ^*lbGQ!3oz zL4%p^9CiX%0+cE@AmGsX#HM;EBSC*kO`pWkJ*EgxVx9}KVA~s1_}7wt3!ZncJ1vIC z?>MQYLPJiqG`wd?V5_O?tg>iy=K8Q(teDF*iKF{BY&N*Gtx@jwhg!9sU-wIwU%LHx zii}Ja1=a=q&3mdn4$#_K(~^Z^8|liQmWUYN{4`@GpNvLJ3R$2;#}bLI8Z|=?2@;4{ zR1YP>c|%zeyqK@BY+&wDB?HE!r&FiN$?iyNyM+X99;i24lL z^TYgG*Ljk~5@Sl*H&D1l`qF`E;zBOc#T#WW#plQ-VeH+K8%lCnay0MXaDsefEU0haU4JR*L@|6MfL#n1ZTs<0#Y@Qc=UJFq;Ppr zFgA%fs5t|<1DS-?O+Z6kD#m6Lvc)3?5S|?zvkKhdSw8_nw6rNB0Ict zm^=@;&y(+t6+$boqTP@k36BwDQm~-L&JDr4f|msr4D8r$RK5jPupI#REEH?T74Qia z#dpw+GJd7iC-)l4I1}sCHV)oie*e>jXMyq&2|mr%zSfm=jXyyCbi?Jj$8+t$1-|3! zIyO0;h*Mp)KYpk^G=(It?h~m0q1jX__L1AAo}R=0^JVwfudV?Me4&^&cyAGOxx^gf z6*8Xq#R7PqgVic3GGgxEjDU(-!O;SzDHAcjmWL}vyR3YD+qAuKs znjDkw)59*_ev8VF!H!7zD?rWenwCx@V%Pk1`dox;&B&3baA`VFQT+&N4emUGE(5&E Y;Ni`I^|>ozwIXR8A^Fb>;rFNi0ezEcQ2+n{ diff --git a/js/button-input.js b/js/button-input.js deleted file mode 100644 index 40b18e9..0000000 --- a/js/button-input.js +++ /dev/null @@ -1,61 +0,0 @@ -function buttonInputClick() { - this.style.display = 'none' - - const buttonInput = document.createElement('div') - buttonInput.classList.add('button-input-container') - - if(this.dataset.instruction){ - const label = document.createElement('label') - buttonInput.appendChild(label) - label.innerText = this.dataset.instruction - } - - const input = document.createElement('input') - buttonInput.appendChild(input) - input.type = 'text' - if(this.dataset.placeholder){ - input.placeholder = this.dataset.placeholder - } - - const button = document.createElement('button') - buttonInput.appendChild(button) - button.innerText = "Submit" - button.addEventListener('click', () => { - if(this.onsubmit){ - const event = new Event('button-input-value') - event.value = input.value - this.onsubmit(event) - } - - buttonInput.parentNode.removeChild(buttonInput) - if(this.dataset.success){ - const span = document.createElement('span') - span.classList.add('success') - span.innerText = this.dataset.success - setTimeout(() => { - span.parentNode.removeChild(span) - this.style.display = null - }, 5000); - this.parentNode.insertBefore(span, this.nextSibling) - } - else{ - this.style.display = null - } - }) - - input.addEventListener("keypress", function(event) { - if (event.key === "Enter") { - event.preventDefault(); - button.click(); - } - }); - - this.parentNode.insertBefore(buttonInput, this.nextSibling) - input.focus() -} - -document.addEventListener('DOMContentLoaded', () => { - document.querySelectorAll('.button-input').forEach(button => { - button.addEventListener('click', buttonInputClick) - }); -}, false); \ No newline at end of file diff --git a/js/follow.js b/js/follow.js deleted file mode 100644 index f8891c1..0000000 --- a/js/follow.js +++ /dev/null @@ -1,35 +0,0 @@ -const SUBSCRIBE_LINK_REL = 'http://ostatus.org/schema/1.0/subscribe' -function follow(username, handle) { - if(!handle){ - handle = prompt("Please enter your fediverse / mastodon handle (e.g. '@user@domain.social')", "@") - } - - if(handle) { - const input = handle - handle = handle.trim().replace(/^@/,'') - const split = handle.split('@') - if(split.length == 2) { - const resource = `acct:${handle}` - const domain = split[1] - - // look up remote user via webfinger - const url = `https://${domain}/.well-known/webfinger?resource=${resource}` - fetch(url, {headers: { - 'Content-Type': 'application/activity+json' - }}).then(async result => { - const json = await result.json() - const subscribe = json.links.find(link => link.rel && link.rel == SUBSCRIBE_LINK_REL) - let template = subscribe.template - window.open(template.replace("{uri}", username), '_blank').focus() - }) - .catch(e => { - console.error(e) - throw `Sorry, we couldn't find a subscribe uri for ${input}.\n\nTry searching for "${username}" on ${domain} (or in your fediverse client of choice)` - }) - - } - else { - throw 'Please enter your fediverse address in @user@domain.social format' - } - } -} \ No newline at end of file diff --git a/js/relative-time.js b/js/relative-time.js deleted file mode 100644 index 0dfba51..0000000 --- a/js/relative-time.js +++ /dev/null @@ -1,6 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - document.querySelectorAll('time').forEach(time => { - const datetime = luxon.DateTime.fromISO(time.getAttribute('datetime')) - time.innerText = datetime.toRelative() - }); -}, false); \ No newline at end of file diff --git a/package.json b/package.json index 1fd791f..4240160 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,7 @@ "typescript": "^5.0.0" }, "dependencies": { - "@11ty/eleventy": "^2.0.1", - "@11ty/eleventy-plugin-rss": "^1.2.0", - "gray-matter": "^4.0.3", - "node-forge": "^1.3.1" + "node-forge": "^1.3.1", + "using-statement": "^0.4.2" } } \ No newline at end of file diff --git a/src/activitypub.ts b/src/activitypub.ts index dbb03d9..21d4ea7 100644 --- a/src/activitypub.ts +++ b/src/activitypub.ts @@ -1,11 +1,14 @@ -import * as db from "./db" import { verify } from "./request" import outbox from "./outbox" import inbox from "./inbox" import ACTOR from "../actor" import { activityPubTypes } from "./env" +import ActivityPubDB from "./db" -export default (req: Request): Response | Promise | undefined => { +let db:ActivityPubDB + +export default (req: Request, database:ActivityPubDB): Response | Promise | undefined => { + db = database const url = new URL(req.url) let match @@ -24,7 +27,7 @@ export default (req: Request): Response | Promise | undefined => { } export function reqIsActivityPub(req:Request) { - const contentType = req.headers.get("Accept") + const contentType = req.headers.get("Accept") + ',' + req.headers.get('Content-Type') return activityPubTypes.some(t => contentType?.includes(t)) } @@ -47,7 +50,7 @@ const postOutbox = async (req:Request):Promise => { // ensure that the verified actor matches the actor in the request body if (ACTOR.id !== body.actor) return new Response("", { status: 401 }) - return await outbox(body) + return await outbox(body, db) } const postInbox = async (req:Request):Promise => { @@ -69,21 +72,21 @@ const postInbox = async (req:Request):Promise => { // ensure that the verified actor matches the actor in the request body if (from !== body.actor) return new Response("", { status: 401 }) - return await inbox(body) + return await inbox(body, db) } const getOutbox = async (req:Request):Promise => { console.log("GetOutbox") // TODO: Paging? - const posts = await db.listOutboxActivities() + const posts = db.listOutboxActivities() return Response.json({ "@context": "https://www.w3.org/ns/activitystreams", id: ACTOR.outbox, type: "OrderedCollection", - totalItems: posts.length, - orderedItems: posts.map((post) => ({ + totalItems: posts?.length, + orderedItems: posts?.map((post) => ({ ...post, actor: ACTOR.id })).sort( (a,b) => new Date(b.published).getTime() - new Date(a.published).getTime()) @@ -95,13 +98,13 @@ const getFollowers = async (req:Request):Promise => { const url = new URL(req.url) const page = url.searchParams.get("page") - const followers = await db.listFollowers() + const followers = db.listFollowers() if(!page) return Response.json({ "@context": "https://www.w3.org/ns/activitystreams", id: ACTOR.followers, type: "OrderedCollection", - totalItems: followers.length, + totalItems: followers?.length, first: `${ACTOR.followers}?page=1`, }) else return Response.json({ @@ -109,8 +112,8 @@ const getFollowers = async (req:Request):Promise => { id: `${ACTOR.followers}?page=${page}`, type: "OrderedCollectionPage", partOf: ACTOR.followers, - totalItems: followers.length, - orderedItems: followers.map(follower => follower.actor) + totalItems: followers?.length, + orderedItems: followers?.map(follower => follower?.id) }) } @@ -119,13 +122,13 @@ const getFollowing = async (req:Request):Promise => { const url = new URL(req.url) const page = url.searchParams.get("page") - const following = await db.listFollowing() + const following = db.listFollowing() if(!page) return Response.json({ "@context": "https://www.w3.org/ns/activitystreams", id: ACTOR.following, type: "OrderedCollection", - totalItems: following.length, + totalItems: following?.length, first: `${ACTOR.following}?page=1`, }) else return Response.json({ @@ -133,8 +136,8 @@ const getFollowing = async (req:Request):Promise => { id: `${ACTOR.following}?page=${page}`, type: "OrderedCollectionPage", partOf: ACTOR.following, - totalItems: following.length, - orderedItems: following.map(follow => follow.actor) + totalItems: following?.length, + orderedItems: following?.map(follow => follow.id) }) } @@ -142,18 +145,18 @@ const getActor = async (req:Request):Promise => { console.log("GetActor") if(reqIsActivityPub(req)) return Response.json(ACTOR, { headers: { "Content-Type": "application/activity+json"}}) - else return Response.json(await db.listPosts()) + else return Response.json(db.listPosts()) } const getPost = async (req:Request, id:string):Promise => { console.log("GetPost", id) - if(reqIsActivityPub(req)) return Response.json((await db.getOutboxActivity(id)).object, { headers: { "Content-Type": "application/activity+json"}}) - else return Response.json(await db.getPost(id)) + if(reqIsActivityPub(req)) return Response.json((db.getOutboxActivity(id))?.object, { headers: { "Content-Type": "application/activity+json"}}) + else return Response.json(db.getPost(id)) } const getOutboxActivity = async (req:Request, id:string):Promise => { console.log("GetOutboxActivity", id) - return Response.json((await db.getOutboxActivity(id)), { headers: { "Content-Type": "application/activity+json"}}) + return Response.json((db.getOutboxActivity(id)), { headers: { "Content-Type": "application/activity+json"}}) } \ No newline at end of file diff --git a/src/admin.ts b/src/admin.ts index 28d7ce6..83b4797 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -1,11 +1,14 @@ import { idsFromValue } from "./activitypub" -import * as db from "./db" import { ADMIN_PASSWORD, ADMIN_USERNAME, activityPubTypes } from "./env" import outbox from "./outbox" import { fetchObject } from "./request" import ACTOR from "../actor" +import ActivityPubDB from "./db" -export default (req: Request): Response | Promise | undefined => { +let db:ActivityPubDB + +export default (req: Request, database: ActivityPubDB): Response | Promise | undefined => { + db = database const url = new URL(req.url) if(!url.pathname.startsWith('/admin')) return undefined @@ -15,7 +18,7 @@ export default (req: Request): Response | Promise | undefined => { let match if(req.method === "GET" && (match = url.pathname.match(/^\/test\/?$/i))) return new Response("", { status: 204 }) - else if(req.method == "POST" && (match = url.pathname.match(/^\/rebuild\/?$/i))) return rebuild(req) + // else if(req.method == "POST" && (match = url.pathname.match(/^\/rebuild\/?$/i))) return rebuild(req) else if(req.method == "POST" && (match = url.pathname.match(/^\/create\/?$/i))) return create(req) else if(req.method == "POST" && (match = url.pathname.match(/^\/follow\/([^\/]+)\/?$/i))) return follow(req, match[1]) else if(req.method == "DELETE" && (match = url.pathname.match(/^\/follow\/([^\/]+)\/?$/i))) return unfollow(req, match[1]) @@ -43,11 +46,11 @@ const checkAuth = (headers: Headers): Boolean => { return username === ADMIN_USERNAME && password === ADMIN_PASSWORD } -// rebuild the 11ty static pages -export const rebuild = async(req:Request):Promise => { - await db.rebuild() - return new Response("", { status: 201 }) -} +// // rebuild the 11ty static pages +// export const rebuild = async(req:Request):Promise => { +// db.rebuild() +// return new Response("", { status: 201 }) +// } // create an activity const create = async (req:Request, inReplyTo:string|null = null):Promise => { @@ -82,10 +85,10 @@ const create = async (req:Request, inReplyTo:string|null = null):Promise => { +const idFromHandle = async(handle:string) => { let url if(handle.startsWith('@')) handle = handle.substring(1) try { @@ -106,6 +109,11 @@ const follow = async (req:Request, handle:string):Promise => { url = actorLink.href } + return url +} + +const follow = async (req:Request, handle:string):Promise => { + const url = await idFromHandle(handle) console.log(`Following ${url}`) // send the follow request to the supplied actor @@ -115,22 +123,23 @@ const follow = async (req:Request, handle:string):Promise => { actor: ACTOR.id, object: url, to: [url, "https://www.w3.org/ns/activitystreams#Public"] - }) + }, db) } const unfollow = async (req:Request, handle:string):Promise => { + const url = await idFromHandle(handle) // check to see if we are already following. If not, just return success - const existing = await db.getFollowing(handle) + const existing = db.getFollowing(url) if (!existing) return new Response("", { status: 204 }) - const activity = await db.getOutboxActivity(existing.id) + const activity = db.getOutboxActivity(existing.activity_id) // outbox will also take care of the deletion return await outbox({ "@context": "https://www.w3.org/ns/activitystreams", type: "Undo", actor: ACTOR.id, object: activity, - to: activity.to - }) + to: activity?.to + }, db) } const like = async (req:Request, object_url:string):Promise => { @@ -143,22 +152,22 @@ const like = async (req:Request, object_url:string):Promise => { object: object, to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"], cc: [ACTOR.followers] - }) + }, db) } -const unlike = async (req:Request, object_id:string):Promise => { +const unlike = async (req:Request, activity_id:string):Promise => { // check to see if we are already following. If not, just return success - const liked = await db.listLiked() - let existing = liked.find(o => o.object_id === object_id) + const liked = db.listLiked() + let existing = liked?.find(o => o?.activity_id === activity_id) if (!existing){ - const object = await (await fetchObject(object_id)).json() + const object = await (await fetchObject(activity_id)).json() idsFromValue(object).forEach(id => { - const e = liked.find(o => o.object_id === id) + const e = liked?.find(o => o.activity_id === id) if(e) existing = e }) } if (!existing) return new Response("No like found to delete", { status: 204 }) - const activity = await db.getOutboxActivity(existing.id) + const activity = db.getOutboxActivity(existing.activity_id) return undo(activity) } @@ -172,22 +181,22 @@ const dislike = async (req:Request, object_url:string):Promise => { object: object, to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"], cc: [ACTOR.followers] - }) + }, db) } const undislike = async (req:Request, object_id:string):Promise => { // check to see if we are already following. If not, just return success - const disliked = await db.listDisliked() - let existing = disliked.find(o => o.object_id === object_id) + const disliked = db.listDisliked() + let existing = disliked?.find(o => o.activity_id === object_id) if (!existing){ const object = await (await fetchObject(object_id)).json() idsFromValue(object).forEach(id => { - const e = disliked.find(o => o.object_id === id) + const e = disliked?.find(o => o.activity_id === id) if(e) existing = e }) } if (!existing) return new Response("No dislike found to delete", { status: 204 }) - const activity = await db.getOutboxActivity(existing.id) + const activity = db.getOutboxActivity(existing.activity_id) return undo(activity) } @@ -201,22 +210,22 @@ const share = async (req:Request, object_url:string):Promise => { object: object, to: [...idsFromValue(object.attributedTo), "https://www.w3.org/ns/activitystreams#Public"], cc: [ACTOR.followers] - }) + }, db) } const unshare = async (req:Request, object_id:string):Promise => { // check to see if we are already following. If not, just return success - const shared = await db.listShared() - let existing = shared.find(o => o.object_id === object_id) + const shared = db.listShared() + let existing = shared?.find(o => o.activity_id === object_id) if (!existing){ const object = await (await fetchObject(object_id)).json() idsFromValue(object).forEach(id => { - const e = shared.find(o => o.object_id === id) + const e = shared?.find(o => o.activity_id === id) if(e) existing = e }) } if (!existing) return new Response("No share found to delete", { status: 204 }) - const activity = await db.getOutboxActivity(existing.id) + const activity = db.getOutboxActivity(existing.activity_id) return undo(activity) } @@ -229,23 +238,23 @@ const undo = async(activity:any):Promise => { object: activity, to: activity.to, cc: activity.cc - }) + }, db) } const deletePost = async (req:Request, id:string):Promise => { - const post = await db.getPostByURL(id) + const post = db.getPostByURL(id) if(!post) return new Response("", { status: 404 }) - const activity = await db.getOutboxActivity(post.local_id) + const activity = db.getOutboxActivity(post.activity_id) return await outbox({ "@context": "https://www.w3.org/ns/activitystreams", type: "Delete", actor: ACTOR.id, - to: activity.to, - cc: activity.cc, - audience: activity.audience, + to: activity?.to, + cc: activity?.cc, + // audience: activity?.audience, object: { id, type: "Tombstone" } - }) + }, db) } \ No newline at end of file diff --git a/src/db.ts b/src/db.ts index b786cf2..ffae1fe 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,229 +1,426 @@ -import { ACTIVITY_INBOX_PATH, ACTIVITY_OUTBOX_PATH, CONTENT_PATH, DATA_PATH, POSTS_PATH, STATIC_PATH } from "./env" -import path from "path" -import { readdir } from "fs/promises" -import { unlinkSync } from "node:fs" -import { fetchObject } from "./request" -import { idsFromValue } from "./activitypub" -import ACTOR from "../actor" -const matter = require('gray-matter') -const Eleventy = require("@11ty/eleventy") +import { Database, Statement } from "bun:sqlite" -// rebuild the 11ty static pages -export async function rebuild() { - console.info(`Building 11ty from ${CONTENT_PATH}, to ${STATIC_PATH}`) - await new Eleventy(CONTENT_PATH, STATIC_PATH, { configPath: '.eleventy.js' }).write() +export interface ActivityObject { + activity_id: string + id: string + published: string } -export async function createInboxActivity(activity:any, object_id:any) { - const activityFile = Bun.file(path.join(ACTIVITY_INBOX_PATH, `${object_id}.activity.json`)) - await Bun.write(activityFile, JSON.stringify(activity)) +export interface Activity extends ActivityObject { + type: string + actor: string + published: string + to: string[] + cc?: string[] + object?: any } -export async function createOutboxActivity(activity:any, object_id:any) { - const activityFile = Bun.file(path.join(ACTIVITY_OUTBOX_PATH, `${object_id}.activity.json`)) - await Bun.write(activityFile, JSON.stringify(activity)) +export interface Following extends ActivityObject { + accepted?: boolean } -export async function getInboxActivity(id:string) { - const file = Bun.file(path.join(ACTIVITY_INBOX_PATH, `${id}.activity.json`)) - return await file.json() +export interface Post extends ActivityObject { + attributedTo: string + type: string + content?: string } -export async function getOutboxActivity(id:string) { - const file = Bun.file(path.join(ACTIVITY_OUTBOX_PATH, `${id}.activity.json`)) - return await file.json() -} +export default class ActivityPubDB { + // The Database + db: Database -export async function listInboxActivities() { - return await Promise.all( - (await readdir(ACTIVITY_INBOX_PATH)).filter(v => v.endsWith('.activity.json')) - .map(async filename => await Bun.file(path.join(ACTIVITY_INBOX_PATH, filename)).json()) - ) -} + // Cached Statements -export async function listOutboxActivities() { - return await Promise.all( - (await readdir(ACTIVITY_OUTBOX_PATH)).filter(v => v.endsWith('.activity.json')) - .map(async filename => await Bun.file(path.join(ACTIVITY_OUTBOX_PATH, filename)).json()) - ) -} - -export async function createPost(post_object:any, object_id:string) { - const file = Bun.file(path.join(POSTS_PATH, `${object_id}.md`)) - let {type, object, inReplyTo} = post_object - if(inReplyTo && typeof inReplyTo === 'string') inReplyTo = await fetchObject(inReplyTo) - - if(object){ - let { content, published, id, attributedTo } = object - if(content as string) content = '> ' + content.replace('\n', '\n> ') + '\n' - else content = "" - - content += post_object.content || "" - //TODO: add appropriate content for different types (e.g. like, etc) - const data:any = { id, published, attributedTo, type } - if(inReplyTo) data.inReplyTo = idsFromValue(inReplyTo).at(0) - await Bun.write(file, matter.stringify(content, data)) + constructor() { + this.db = new Database('./db.sqlite') + this.migrate() + this.cacheQueries() } - else { - const { content, published, id, attributedTo } = post_object - let reply_content = "" - if(!object && inReplyTo) { - reply_content = inReplyTo.content - if(reply_content as string) reply_content = '> ' + reply_content.replace('\n', '\n> ') + '\n' - else reply_content = "" + close() { + this.db.close() + } + + migrate() { + let version = (this.db.query("PRAGMA user_version;").get() as {user_version:number})?.user_version + console.log(`Hi from migrate! User version: ${version}`) + + const statements:Statement[] = [] + + switch(version) { + case 0: + console.log("migrating db version 1") + this.db.exec("PRAGMA journal_mode = WAL;") + + // Create the inbox table + statements.push(this.db.prepare(`CREATE TABLE [inbox] ( + [activity_id] TEXT NOT NULL PRIMARY KEY, + [id] TEXT NOT NULL, + [type] TEXT NOT NULL, + [actor] TEXT NOT NULL, + [published] TEXT NOT NULL, + [to] TEXT NOT NULL, + [cc] TEXT, + [activity] TEXT NOT NULL + )`)) + + // Create the outbox table + statements.push(this.db.prepare(`CREATE TABLE [outbox] ( + [activity_id] TEXT NOT NULL PRIMARY KEY, + [id] TEXT NOT NULL, + [type] TEXT NOT NULL, + [actor] TEXT NOT NULL, + [published] TEXT NOT NULL, + [to] TEXT NOT NULL, + [cc] TEXT, + [activity] TEXT NOT NULL + )`)) + + // Create the following table + statements.push(this.db.prepare(`CREATE TABLE [following] ( + [activity_id] TEXT NOT NULL PRIMARY KEY, + [id] TEXT NOT NULL, + [published] TEXT NOT NULL, + [accepted] INTEGER + )`)) + + // Create the followers table + statements.push(this.db.prepare(`CREATE TABLE [followers] ( + [activity_id] TEXT NOT NULL PRIMARY KEY, + [id] TEXT NOT NULL, + [published] TEXT NOT NULL + )`)) + + // Create the liked table + statements.push(this.db.prepare(`CREATE TABLE [liked] ( + [activity_id] TEXT NOT NULL PRIMARY KEY, + [id] TEXT NOT NULL, + [published] TEXT NOT NULL + )`)) + + // Create the disliked table + statements.push(this.db.prepare(`CREATE TABLE [disliked] ( + [activity_id] TEXT NOT NULL PRIMARY KEY, + [id] TEXT NOT NULL, + [published] TEXT NOT NULL + )`)) + + // Create the shared table + statements.push(this.db.prepare(`CREATE TABLE [shared] ( + [activity_id] TEXT NOT NULL PRIMARY KEY, + [id] TEXT NOT NULL, + [published] TEXT NOT NULL + )`)) + + version = 1 + case 1: + // Create the posts table + statements.push(this.db.prepare(`CREATE TABLE [posts] ( + [activity_id] TEXT NOT NULL PRIMARY KEY, + [id] TEXT NOT NULL, + [published] TEXT NOT NULL, + [attributedTo] TEXT, + [type] TEXT NOT NULL, + [content] TEXT, + [object] TEXT + )`)) + version = 2 + case 2: break; + default: break; } - const data:any = { id, published, attributedTo, type } - if(inReplyTo) data.inReplyTo = idsFromValue(inReplyTo).at(0) - await Bun.write(file, matter.stringify((reply_content || "") + (content || ""), data)) + const migration = this.db.transaction((statements:Statement[]) => { + statements.forEach(s => { + console.log(s.toString()); + s.run(); + s.finalize() + }) + return true + }) + if(migration(statements)) this.db.run(`PRAGMA user_version=${version}`) } - rebuild() -} -export async function getPost(id:string) { - const file = Bun.file(path.join(POSTS_PATH, `${id}.md`)) - const { data, content } = matter(await file.text()) - return { - ...data, - content: content.trim(), - local_id: id + + queries:{ + createInboxActivity?:Statement, + getInboxActivity?:Statement, + listInboxActivities?:Statement, + createOutboxActivity?:Statement, + getOutboxActivity?:Statement, + listOutboxActivities?:Statement, + createFollowing?:Statement, + getFollowing?:Statement, + deleteFollowing?:Statement, + listFollowing?:Statement, + acceptFollowing?:Statement, + createFollower?:Statement, + getFollower?:Statement, + deleteFollower?:Statement, + listFollowers?:Statement, + createLiked?:Statement, + getLiked?:Statement, + deleteLiked?:Statement, + listLiked?:Statement, + createDisliked?:Statement, + getDisliked?:Statement, + deleteDisliked?:Statement, + listDisliked?:Statement, + createShared?:Statement, + getShared?:Statement, + deleteShared?:Statement, + listShared?:Statement, + createPost?:Statement, + getPost?:Statement, + getPostByURL?:Statement, + deletePost?:Statement, + listPosts?:Statement + } = {} + + cacheQueries() { + // inbox queries + this.queries.createInboxActivity = this.db.query(`INSERT INTO [inbox] + ([activity_id], [id], [type], [actor], [published], [to], [cc], [activity]) + VALUES ($activity_id, $id, $type, $actor, $published, $to, $cc, $activity)`) + this.queries.getInboxActivity = this.db.query(`SELECT * FROM [inbox] WHERE [activity_id]=$activity_id`) + this.queries.listInboxActivities = this.db.query(`SELECT * FROM [inbox] ORDER BY [published] DESC`) + + // outbox queries + this.queries.createOutboxActivity = this.db.query(`INSERT INTO [outbox] + ([activity_id], [id], [type], [actor], [published], [to], [cc], [activity]) + VALUES ($activity_id, $id, $type, $actor, $published, $to, $cc, $activity)`) + this.queries.getOutboxActivity = this.db.query(`SELECT * FROM [outbox] WHERE [activity_id]=$activity_id`) + this.queries.listOutboxActivities = this.db.query(`SELECT * FROM [outbox] ORDER BY [published] DESC`) + + // following queries + this.queries.createFollowing = this.db.query(`INSERT INTO [following] + ([activity_id], [id], [published], [accepted]) + VALUES ($activity_id, $id, $published, NULL)`) + this.queries.getFollowing = this.db.query('SELECT * FROM [following] WHERE [id]=$id') + this.queries.deleteFollowing = this.db.query('DELETE FROM [following] WHERE [id]=$id') + this.queries.listFollowing = this.db.query('SELECT * FROM [following] ORDER BY [published] DESC') + this.queries.acceptFollowing = this.db.query('UPDATE [following] SET [accepted]=TRUE WHERE [id]=$id') + + // follower queries + this.queries.createFollower = this.db.query(`INSERT INTO [followers] + ([activity_id], [id], [published]) + VALUES ($activity_id, $id, $published)`) + this.queries.getFollower = this.db.query('SELECT * FROM [followers] WHERE [id]=$id') + this.queries.deleteFollower = this.db.query('DELETE FROM [followers] WHERE [id]=$id') + this.queries.listFollowers = this.db.query('SELECT * FROM [followers] ORDER BY [published] DESC') + + // liked queries + this.queries.createLiked = this.db.query(`INSERT INTO [liked] + ([activity_id], [id], [published]) + VALUES ($activity_id, $id, $published)`) + this.queries.getLiked = this.db.query('SELECT * FROM [liked] WHERE [id]=$id') + this.queries.deleteLiked = this.db.query('DELETE FROM [liked] WHERE [id]=$id') + this.queries.listLiked = this.db.query('SELECT * FROM [liked] ORDER BY [published] DESC') + + // disliked queries + this.queries.createDisliked = this.db.query(`INSERT INTO [disliked] + ([activity_id], [id], [published]) + VALUES ($activity_id, $id, $published)`) + this.queries.getDisliked = this.db.query('SELECT * FROM [disliked] WHERE [id]=$id') + this.queries.deleteDisliked = this.db.query('DELETE FROM [disliked] WHERE [id]=$id') + this.queries.listDisliked = this.db.query('SELECT * FROM [disliked] ORDER BY [published] DESC') + + // shared queries + this.queries.createShared = this.db.query(`INSERT INTO [shared] + ([activity_id], [id], [published]) + VALUES ($activity_id, $id, $published)`) + this.queries.getShared = this.db.query('SELECT * FROM [shared] WHERE [id]=$id') + this.queries.deleteShared = this.db.query('DELETE FROM [shared] WHERE [id]=$id') + this.queries.listShared = this.db.query('SELECT * FROM [shared] ORDER BY [published] DESC') + + // post queries + this.queries.createPost = this.db.query(`INSERT INTO [posts] + ([activity_id], [id], [published], [attributedTo], [type], [content], [object]) + VALUES ($activity_id, $id, $published, $attributedTo, $type, $content, $object)`) + this.queries.getPost = this.db.query('SELECT * FROM [posts] WHERE [activity_id]=$activity_id') + this.queries.getPostByURL = this.db.query('SELECT * FROM [posts] WHERE [id]=$id') + this.queries.deletePost = this.db.query('DELETE FROM [posts] WHERE [id]=$id') + this.queries.listPosts = this.db.query('SELECT * FROM [posts] ORDER BY [published] DESC') } -} -export async function getPostByURL(url_id:string) { - if(!url_id || !url_id.startsWith(ACTOR.url + '/posts/')) return null - const match = url_id.match(/\/([0-9a-f]+)\/?$/) - const local_id = match ? match[1] : url_id - return await getPost(local_id) -} + createInboxActivity(activity_id:string, activity:any) { + //new Date(activity.published).getTime().toString(36) + return this.queries.createInboxActivity?.run({ + $activity_id: activity_id, + $id: activity.id, + $type: activity.type, + $actor: activity.actor, + $published: activity.published || new Date().toISOString(), + $to: JSON.stringify(activity.to), + $cc: JSON.stringify(activity.cc), + $activity: JSON.stringify(activity) + }) + } -export async function deletePost(id:string) { - unlinkSync(path.join(POSTS_PATH, id + '.md')) - rebuild() -} + getInboxActivity(activity_id:string):Activity | undefined { + return JSON.parse((this.queries.getInboxActivity?.get(activity_id) as any).activity) + } -export async function listPosts() { - return await Promise.all((await readdir(POSTS_PATH)).filter(v => v.endsWith('.md')).map(async filename => await getPost(filename.slice(0, -3)))) -} + listInboxActivities():Activity[] | undefined { + return this.queries.listInboxActivities?.all().map(r => JSON.parse((r as any).activity)) + } -export async function createFollowing(handle:string, id:string) { - const file = Bun.file(path.join(DATA_PATH, `following.json`)) - const following_list = await file.json() as Array - if(!following_list.find(v => v.id === id || v.handle === handle)) following_list.push({id, handle, createdAt: new Date().toISOString()}) - await Bun.write(file, JSON.stringify(following_list)) - rebuild() -} + createOutboxActivity(activity_id:string, activity:any) { + //new Date(activity.published).getTime().toString(36) + return this.queries.createOutboxActivity?.run({ + $activity_id: activity_id, + $id: activity.id, + $type: activity.type, + $actor: activity.actor, + $published: activity.published, + $to: JSON.stringify(activity.to), + $cc: JSON.stringify(activity.cc), + $activity: JSON.stringify(activity) + }) + } -export async function deleteFollowing(handle:string) { - const file = Bun.file(path.join(DATA_PATH, `following.json`)) - const following_list = await file.json() as Array - await Bun.write(file, JSON.stringify(following_list.filter(v => v.handle !== handle))) - rebuild() -} + getOutboxActivity(activity_id:string): Activity | undefined { + return JSON.parse((this.queries.getOutboxActivity?.get(activity_id) as any).activity) + } -export async function getFollowing(handle:string) { - const file = Bun.file(path.join(DATA_PATH, `following.json`)) - const following_list = await file.json() as Array - return following_list.find(v => v.handle === handle) -} + listOutboxActivities(): Activity[] | undefined { + return this.queries.listOutboxActivities?.all().map(r => JSON.parse((r as any).activity)) + } -export async function listFollowing(onlyAccepted = true) { - const file = Bun.file(path.join(DATA_PATH, `following.json`)) - return ((await file.json()) as Array).filter(f => !onlyAccepted || f.accepted) -} + createFollowing(activity_id:string, id:string, published:string | Date) { + if(published instanceof Date) published = published.toISOString() + return this.queries.createFollowing?.run({ + $activity_id: activity_id, + $id: id, + $published: published + }) + } -export async function acceptFollowing(handle:string) { - const file = Bun.file(path.join(DATA_PATH, `following.json`)) - const following_list = await file.json() as Array - const following = following_list.find(v => v.handle === handle) - if(following) following.accepted = new Date().toISOString() - await Bun.write(file, JSON.stringify(following_list)) - rebuild() -} + getFollowing(id:string): Following | undefined { + return this.queries.getFollowing?.get(id) as any + } -export async function createFollower(actor:string, id:string) { - const file = Bun.file(path.join(DATA_PATH, `followers.json`)) - const followers_list = await file.json() as Array - if(!followers_list.find(v => v.id === id || v.actor === actor)) followers_list.push({id, actor, createdAt: new Date().toISOString()}) - await Bun.write(file, JSON.stringify(followers_list)) - rebuild() -} + deleteFollowing(id:string) { + return this.queries.deleteFollowing?.run(id) + } -export async function deleteFollower(actor:string) { - const file = Bun.file(path.join(DATA_PATH, `followers.json`)) - const followers_list = await file.json() as Array - await Bun.write(file, JSON.stringify(followers_list.filter(v => v.actor !== actor))) - rebuild() -} + listFollowing(): Following[] | undefined { + return this.queries.listFollowing?.all() as Following[] + } -export async function getFollower(actor:string) { - const file = Bun.file(path.join(DATA_PATH, `followers.json`)) - const followers_list = await file.json() as Array - return followers_list.find(v => v.actor === actor) -} + acceptFollowing(id:string) { + return this.queries.acceptFollowing?.run(id) + } -export async function listFollowers() { - const file = Bun.file(path.join(DATA_PATH, `followers.json`)) - return await file.json() as Array -} + createFollower(activity_id:string, id:string, published:string | Date) { + if(published instanceof Date) published = published.toISOString() + return this.queries.createFollower?.run({ + $activity_id: activity_id, + $id: id, + $published: published + }) + } -export async function createLiked(object_id:string, id:string) { - const file = Bun.file(path.join(DATA_PATH, `liked.json`)) - const liked_list = await file.json() as Array - if(!liked_list.find(v => v.object_id === object_id)) liked_list.push({id, object_id, createdAt: new Date().toISOString()}) - await Bun.write(file, JSON.stringify(liked_list)) - rebuild() -} + getFollower(id:string): ActivityObject | undefined { + return this.queries.getFollower?.get(id) as ActivityObject + } -export async function deleteLiked(object_id:string) { - const file = Bun.file(path.join(DATA_PATH, `liked.json`)) - const liked_list = await file.json() as Array - await Bun.write(file, JSON.stringify(liked_list.filter(v => v.object_id !== object_id))) - rebuild() -} + deleteFollower(id:string) { + console.log("! DELETE Follower ", id) + return this.queries.deleteFollower?.run(id) + } -export async function listLiked() { - const file = Bun.file(path.join(DATA_PATH, `liked.json`)) - return await file.json() as Array -} + listFollowers(): ActivityObject[] | undefined { + return this.queries.listFollowers?.all().map(x => x as ActivityObject) + } -export async function createDisliked(object_id:string, id:string) { - const file = Bun.file(path.join(DATA_PATH, `disliked.json`)) - const disliked_list = await file.json() as Array - if(!disliked_list.find(v => v.object_id === object_id)) disliked_list.push({id, object_id, createdAt: new Date().toISOString()}) - await Bun.write(file, JSON.stringify(disliked_list)) - rebuild() -} + createLiked(activity_id:string, id:string, published:string | Date) { + if(published instanceof Date) published = published.toISOString() + return this.queries.createLiked?.run({ + $activity_id: activity_id, + $id: id, + $published: published + }) + } -export async function deleteDisliked(object_id:string) { - const file = Bun.file(path.join(DATA_PATH, `disliked.json`)) - const disliked_list = await file.json() as Array - await Bun.write(file, JSON.stringify(disliked_list.filter(v => v.object_id !== object_id))) - rebuild() -} + getLiked(id:string): ActivityObject | undefined { + return this.queries.getLiked?.get(id) as ActivityObject + } -export async function listDisliked() { - const file = Bun.file(path.join(DATA_PATH, `disliked.json`)) - return await file.json() as Array -} + deleteLiked(id:string) { + return this.queries.deleteLiked?.run(id) + } -export async function createShared(object_id:string, id:string) { - const file = Bun.file(path.join(DATA_PATH, `shared.json`)) - const shared_list = await file.json() as Array - if(!shared_list.find(v => v.object_id === object_id)) shared_list.push({id, object_id, createdAt: new Date().toISOString()}) - await Bun.write(file, JSON.stringify(shared_list)) - rebuild() -} + listLiked(): ActivityObject[] | undefined { + return this.queries.listLiked?.all().map(x => x as ActivityObject) + } -export async function deleteShared(object_id:string) { - const file = Bun.file(path.join(DATA_PATH, `shared.json`)) - const shared_list = await file.json() as Array - await Bun.write(file, JSON.stringify(shared_list.filter(v => v.object_id !== object_id))) - rebuild() -} + createDisliked(activity_id:string, id:string, published:string | Date) { + if(published instanceof Date) published = published.toISOString() + return this.queries.createDisliked?.run({ + $activity_id: activity_id, + $id: id, + $published: published + }) + } -export async function listShared() { - const file = Bun.file(path.join(DATA_PATH, `shared.json`)) - return await file.json() as Array + getDisliked(id:string): ActivityObject | undefined { + return this.queries.getDisliked?.get(id) as ActivityObject + } + + deleteDisliked(id:string) { + return this.queries.deleteDisliked?.run(id) + } + + listDisliked(): ActivityObject[] | undefined { + return this.queries.listDisliked?.all().map(x => x as ActivityObject) + } + + createShared(activity_id:string, id:string, published:string | Date) { + if(published instanceof Date) published = published.toISOString() + return this.queries.createShared?.run({ + $activity_id: activity_id, + $id: id, + $published: published + }) + } + + getShared(id:string): ActivityObject | undefined { + return this.queries.getShared?.get(id) as ActivityObject + } + + deleteShared(id:string) { + return this.queries.deleteShared?.run(id) + } + + listShared(): ActivityObject[] | undefined { + return this.queries.listShared?.all().map(x => x as ActivityObject) + } + + createPost(activity_id:string, object:any) { + return this.queries.createPost?.run({ + $activity_id: activity_id, + $id: object.id, + $published: object.published, + $attributedTo: object.attributedTo, + $type: object.type, + $content: object.content, + $object: JSON.stringify(object) + }) + } + + getPost(activity_id:string): Post | undefined { + return JSON.parse((this.queries.getPost?.get(activity_id) as any).object) + } + + getPostByURL(id:string): Post | undefined { + return JSON.parse((this.queries.getPostByURL?.get(id) as any).object) + } + + deletePost(activity_id:string) { + return this.queries.deletePost?.run(activity_id) + } + + listPosts(): Post[] | undefined { + return this.queries.listPosts?.all().map(p => JSON.parse((p as any).object)) + } } \ No newline at end of file diff --git a/src/env.ts b/src/env.ts index faaada0..4e3f0be 100644 --- a/src/env.ts +++ b/src/env.ts @@ -31,6 +31,7 @@ export const STATIC_PATH = path.join('.', '_site') export const CONTENT_PATH = path.join('.', '_content') export const POSTS_PATH = path.join(CONTENT_PATH, "posts") export const DATA_PATH = path.join(CONTENT_PATH, "_data") +export const DB_PATH = path.join(DATA_PATH, "db.sqlite") export const ACTIVITY_INBOX_PATH = path.join(DATA_PATH, "_inbox") export const ACTIVITY_OUTBOX_PATH = path.join(DATA_PATH, "_outbox") @@ -49,4 +50,4 @@ export const activityPubTypes = [ 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'application/activity+json' ] -export const contentTypeHeader = { 'Content-Type': activityPubTypes[0]} \ No newline at end of file +export const contentTypeHeader = { 'Content-Type': activityPubTypes[0] } \ No newline at end of file diff --git a/src/inbox.ts b/src/inbox.ts index fd15ec8..f8559e0 100644 --- a/src/inbox.ts +++ b/src/inbox.ts @@ -1,26 +1,29 @@ import { idsFromValue } from "./activitypub" -import * as db from "./db" import outbox from "./outbox" import { send } from "./request" import ACTOR from "../actor" +import ActivityPubDB from "./db" -export default async function inbox(activity:any) { +let db:ActivityPubDB +export default async function inbox(activity:any, database:ActivityPubDB) { + db = database const date = new Date() // get the main recipients ([...new Set()] is to dedupe) const recipientList = [...new Set([...idsFromValue(activity.to), ...idsFromValue(activity.cc), ...idsFromValue(activity.audience)])] // if my list of followers in the list of recipients, then forward to them as well if(recipientList.includes(ACTOR.url + "/followers")) { - (await db.listFollowers()).forEach(f => send(f, activity, activity.attributedTo)) + (db.listFollowers())?.forEach(f => send(f.id, activity, activity.attributedTo)) } // save this activity to my inbox - const id = `${date.getTime().toString(16)}` - db.createInboxActivity(activity, id) + const activity_id = `${date.getTime().toString(32)}` + console.log(`New inbox activity ${activity_id} (${activity.type})`, activity) + db.createInboxActivity(activity_id, activity) // TODO: process the activity and update local data switch(activity.type) { - case "Follow": follow(activity, id); break; + case "Follow": follow(activity, activity_id); break; case "Accept": accept(activity); break; case "Reject": reject(activity); break; case "Undo": undo(activity); break; @@ -29,10 +32,10 @@ export default async function inbox(activity:any) { return new Response("", { status: 204 }) } -const follow = async (activity:any, id:string) => { +const follow = async (activity:any, activity_id:string) => { // someone is following me // save this follower locally - db.createFollower(activity.actor, id) + db.createFollower(activity_id, activity.actor, activity.published || new Date().toISOString()) // send an accept message to the outbox await outbox({ "@context": "https://www.w3.org/ns/activitystreams", @@ -40,26 +43,26 @@ const follow = async (activity:any, id:string) => { actor: ACTOR.id, to: [activity.actor], object: activity, - }); + }, db); } const undo = async (activity:any) => { switch (activity.object.type) { // someone is undoing their follow of me - case "Follow": await db.deleteFollower(activity.actor); break + case "Follow": db.deleteFollower(activity.actor); break } } const accept = async (activity:any) => { switch (activity.object.type) { // someone accepted my follow of them - case "Follow": await db.acceptFollowing(activity.actor); break + case "Follow": db.acceptFollowing(activity.actor); break } } const reject = async (activity:any) => { switch (activity.object.type) { // someone rejected my follow of them - case "Follow": await db.deleteFollowing(activity.actor); break + case "Follow": db.deleteFollowing(activity.actor); break } } diff --git a/src/index.ts b/src/index.ts index 53dd718..489ebee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,10 @@ -import { DEFAULT_DOCUMENTS, STATIC_PATH } from "./env" import admin from './admin' -import activitypub from "./activitypub" +import activitypub, { reqIsActivityPub } from "./activitypub" import { fetchObject } from "./request" -import path from "path" -import { BunFile } from "bun" -import { rebuild } from "./db" import { handle, webfinger } from "../actor" +import ActivityPubDB from './db' -rebuild() +const db = new ActivityPubDB() const server = Bun.serve({ port: 3000, @@ -15,7 +12,7 @@ const server = Bun.serve({ const url = new URL(req.url) // log the incoming request info - console.info(`${new Date().toISOString()} 📥 ${req.method} ${req.url}`) + console.info(`${new Date().toISOString()} 📥 ${req.method} ${reqIsActivityPub(req)?'[AP] ':''}${req.url}`) // CORS route (for now, any domain has access) if(req.method === "OPTIONS") { @@ -36,6 +33,7 @@ const server = Bun.serve({ // return the webfinger return Response.json(webfinger, { headers: { 'content-type': 'application/jrd+json' }}) } + // Debugging route for fetching ActivityPub documents else if(req.method === "GET" && url.pathname.startsWith("/fetch/")) { const object_url = url.pathname.substring(7) if(!object_url) return new Response("No url supplied", { status: 400}) @@ -43,33 +41,9 @@ const server = Bun.serve({ return fetchObject(object_url) } - return admin(req) || activitypub(req) || staticFile(req) + // admin and activitypub routes + return admin(req, db) || activitypub(req, db) || new Response("", { status: 404 }) }, -}); -const getDefaultDocument = async(base_path: string) => { - for(const d of DEFAULT_DOCUMENTS){ - const filePath = path.join(base_path, d) - const file = Bun.file(filePath) - if(await file.exists()) return file - } -} +}) -const staticFile = async (req:Request): Promise => { - try{ - const url = new URL(req.url) - const filePath = path.join(STATIC_PATH, url.pathname) - let file:BunFile|undefined = Bun.file(filePath) - // if the file doesn't exist, attempt to get the default document for the path - if(!(await file.exists())) file = await getDefaultDocument(filePath) - - if(file && await file.exists()) return new Response(file) - // if the file still doesn't exist, just return a 404 - else return new Response("", { status: 404 }) - } - catch(err) { - console.error(err) - return new Response("", { status: 404 }) - } -} - -console.log(`Listening on http://localhost:${server.port} ...`); \ No newline at end of file +console.log(`Listening on http://localhost:${server.port} ...`) \ No newline at end of file diff --git a/src/outbox.ts b/src/outbox.ts index 314ca5c..576484b 100644 --- a/src/outbox.ts +++ b/src/outbox.ts @@ -1,12 +1,15 @@ import { idsFromValue } from "./activitypub" -import * as db from "./db" import { fetchObject, send } from "./request" import ACTOR from "../actor" +import ActivityPubDB from "./db" -export default async function outbox(activity:any):Promise { +let db:ActivityPubDB + +export default async function outbox(activity:any, database:ActivityPubDB):Promise { + db = database const date = new Date() - const id = `${date.getTime().toString(16)}` - console.log('outbox', id, activity) + const activity_id = `${date.getTime().toString(32)}` + console.log('outbox', activity_id, activity) // https://www.w3.org/TR/activitypub/#object-without-create if(!activity.actor && !(activity.object || activity.target || activity.result || activity.origin || activity.instrument)) { @@ -25,7 +28,7 @@ export default async function outbox(activity:any):Promise { if(audience) activity.audience = audience } - activity.id = `${ACTOR.url}/outbox/${id}` + activity.id = `${ACTOR.url}/outbox/${activity_id}` if(!activity.published) activity.published = date.toISOString() if(activity.type === 'Create' && activity.object && Object(activity.object) === activity.object) { @@ -44,22 +47,22 @@ export default async function outbox(activity:any):Promise { // now that has been taken care of, it's time to update our local data, depending on the contents of the activity switch(activity.type) { - case "Accept": await accept(activity, id); break; - case "Follow": await follow(activity, id); break; - case "Like": await like(activity, id); break; - case "Dislike": await dislike(activity, id); break; - case "Annouce": await announce(activity, id); break; - case "Create": await create(activity, id); break; + case "Accept": await accept(activity, activity_id); break; + case "Follow": await follow(activity, activity_id); break; + case "Like": await like(activity, activity_id); break; + case "Dislike": await dislike(activity, activity_id); break; + case "Annouce": await announce(activity, activity_id); break; + case "Create": await create(activity, activity_id); break; case "Undo": await undo(activity); break; case "Delete": await deletePost(activity); break; // TODO: case "Anncounce": return await share(activity) } // save the activity data for the outbox - await db.createOutboxActivity(activity, id) + db.createOutboxActivity(activity_id, activity) // send to the appropriate recipients finalRecipientList.forEach((to) => { - if (to.startsWith(ACTOR.url + "/followers")) db.listFollowers().then(followers => followers.forEach(f => send(f.actor, activity))) + if (to.startsWith(ACTOR.url + "/followers")) db.listFollowers()?.forEach(f => send(f.id, activity)) else if (to === "https://www.w3.org/ns/activitystreams#Public") return // there's nothing to "send" to here else if (to) send(to, activity) }) @@ -67,57 +70,59 @@ export default async function outbox(activity:any):Promise { return new Response("", { status: 201, headers: { location: activity.id } }) } -async function create(activity:any, id:string) { - activity.object.id = activity.object.url = `${ACTOR.url}/posts/${id}` - await db.createPost(activity.object, id) +async function create(activity:any, activity_id:string) { + activity.object.id = activity.object.url = `${ACTOR.url}/posts/${activity_id}` + db.createPost(activity_id, activity.object) return true } -async function accept(activity:any, id:string) { +async function accept(activity:any, activity_id:string) { return true } -async function follow(activity:any, id:string) { - await db.createFollowing(activity.object , id) +async function follow(activity:any, activity_id:string) { + idsFromValue(activity.object).forEach(id => { + db.createFollowing(activity_id, id, activity.published) + }); return true } -async function like(activity:any, id:string) { +async function like(activity:any, activity_id:string) { if(typeof activity.object === 'string'){ - await db.createLiked(activity.object, id) - activity.object = await fetchObject(activity.object) + db.createLiked(activity_id, activity.object, activity.published || new Date()) + activity.object = fetchObject(activity.object) } else { - const liked = await idsFromValue(activity.object) - liked.forEach(l => db.createLiked(l, id)) + const liked = idsFromValue(activity.object) + liked.forEach(l => db.createLiked(activity_id, l, activity.published || new Date())) } - await db.createPost(activity, id) + db.createPost(activity_id, activity) return true } -async function dislike(activity:any, id:string) { +async function dislike(activity:any, activity_id:string) { if(typeof activity.object === 'string'){ - await db.createDisliked(activity.object, id) + db.createDisliked(activity_id, activity.object, activity.published || new Date()) activity.object = await fetchObject(activity.object) } else { const disliked = await idsFromValue(activity.object) - disliked.forEach(l => db.createDisliked(l, id)) + disliked.forEach(l => db.createDisliked(activity_id, l, activity.published || new Date())) } - await db.createPost(activity, id) + db.createPost(activity_id, activity) return true } -async function announce(activity:any, id:string) { +async function announce(activity:any, activity_id:string) { if(typeof activity.object === 'string'){ - await db.createShared(activity.object, id) + db.createShared(activity_id, activity.object, activity.published || new Date()) activity.object = await fetchObject(activity.object) } else { const shared = await idsFromValue(activity.object) - shared.forEach(l => db.createShared(l, id)) + shared.forEach(l => db.createShared(activity_id, l, activity.published || new Date())) } - await db.createPost(activity, id) + db.createPost(activity_id, activity) return true } @@ -136,17 +141,17 @@ async function announce(activity:any, id:string) { async function undo(activity:any) { const id = await idsFromValue(activity.object).at(0) if (!id) return true - const match = id.match(/\/([0-9a-f]+)\/?$/) - const local_id = match ? match[1] : id - console.log('undo', local_id) + const match = id.match(/\/([0-9a-z]+)\/?$/) + const activity_id = match ? match[1] : id + console.log('undo', activity_id) try{ - const existing = await db.getOutboxActivity(local_id) + const existing = db.getOutboxActivity(activity_id) switch(activity.object.type) { - case "Follow": await db.deleteFollowing(existing.object); break; - case "Like": idsFromValue(existing.object).forEach(async id => await db.deleteLiked(id)); await db.deletePost(local_id); break; - case "Dislike": idsFromValue(existing.object).forEach(async id => await db.deleteDisliked(id)); await db.deletePost(local_id); break; - case "Announce": idsFromValue(existing.object).forEach(async id => await db.deleteShared(id)); await db.deletePost(local_id); break; + case "Follow": idsFromValue(existing?.object).forEach(async id => db.deleteFollowing(id)); break; + case "Like": idsFromValue(existing?.object).forEach(async id => db.deleteLiked(id)); db.deletePost(activity_id); break; + case "Dislike": idsFromValue(existing?.object).forEach(async id => db.deleteDisliked(id)); db.deletePost(activity_id); break; + case "Announce": idsFromValue(existing?.object).forEach(async id => db.deleteShared(id)); db.deletePost(activity_id); break; } } @@ -160,8 +165,8 @@ async function undo(activity:any) { async function deletePost(activity:any) { const id = await idsFromValue(activity.object).at(0) if(!id) return false - const post = await db.getPostByURL(id) + const post = db.getPostByURL(id) if(!post) return false - await db.deletePost(post.local_id) + db.deletePost(post.activity_id) return true } \ No newline at end of file