diff --git a/lib/activitypub/index.ts b/lib/activitypub/index.ts index 901f969..f5c6199 100644 --- a/lib/activitypub/index.ts +++ b/lib/activitypub/index.ts @@ -4,6 +4,7 @@ import comments from "./comments"; import federate from "./federate"; import followers from "./followers"; import inbox from "./inbox"; +import interact from "./interact"; import nodeinfo from "./nodeinfo"; import webfinger from "./webfinger"; @@ -14,6 +15,7 @@ export = { federate, followers, inbox, + interact, nodeinfo, webfinger }; diff --git a/lib/activitypub/interact.ts b/lib/activitypub/interact.ts new file mode 100644 index 0000000..70a0524 --- /dev/null +++ b/lib/activitypub/interact.ts @@ -0,0 +1,18 @@ +import {Router} from "express"; +import {queryWebfinger} from "./webfinger"; + +const domain = process.env.DOMAIN; + +export default async function interact(router: Router) { + router.post("/interact", async (req, res) => { + const permalink = req.body.permalink; + const acct = req.body.remote_follow.acct; + const webfingerResult = await queryWebfinger(acct); + const link = webfingerResult.links.find((l) => l.rel === "http://ostatus.org/schema/1.0/subscribe"); + if (link && 'template' in link) { + res.redirect(link.template.replace("{uri}", `https://${domain}${permalink}`)); + } else { + res.status(400).send("Unable to find remote subscribe URL"); + } + }); +} diff --git a/lib/activitypub/webfinger.ts b/lib/activitypub/webfinger.ts index a78c3d4..1dc3293 100644 --- a/lib/activitypub/webfinger.ts +++ b/lib/activitypub/webfinger.ts @@ -1,9 +1,21 @@ import express, { Router } from "express"; +import fetch from "node-fetch"; const domain = process.env.DOMAIN; export default function webfinger(router: Router) { router.get("/.well-known/webfinger", (req, res) => { + res.json({ + subject: `acct:block@${domain}`, + aliases: [`https://${domain}/ap/actor`], + links: [ + { + rel: "self", + type: "application/activity+json", + href: `https://${domain}/ap/actor`, + } + ] + } as WebfingerResult); res.json({ "subject": `acct:blog@${domain}`, "links": [ @@ -16,4 +28,23 @@ export default function webfinger(router: Router) { }); res.end(); }); -} \ No newline at end of file +} + +export async function queryWebfinger(acct: string): Promise { + if (acct.startsWith("@")) { + acct = acct.substring(1); + } + const parts = acct.split("@"); + if (parts.length !== 2) { + throw "Invalid account"; + } + const response = await fetch(`https://${parts[1]}/.well-known/webfinger?resource=${acct}`); + const json = await response.json(); + return json as WebfingerResult; +} + +export interface WebfingerResult { + subject: string; + aliases: string[]; + links: Array<{rel: string, type: string, href: string} | {rel: "https://ostatus.org/schema/1.0/subscribe", template: string}>; +} diff --git a/lib/index.ts b/lib/index.ts index 0ac702f..d6f1458 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -48,6 +48,7 @@ function watch() { const app = express(); app.use(morgan("dev")); app.use(bodyParser.json({ type: "application/activity+json" })); + app.use(bodyParser.urlencoded()); const connection = await createConnection({ "type": "postgres", @@ -82,6 +83,8 @@ function watch() { await activitypub.articles.setup(posts); + await activitypub.interact(app); + const apRouter = Router(); apRouter.use(validateHttpSig); await activitypub.actor(apRouter); diff --git a/package-lock.json b/package-lock.json index eba7322..3f945aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "markdown-it": "^8.4.2", "markdown-it-footnote": "^3.0.2", "morgan": "^1.9.1", + "node-fetch": "^2.6.7", "pg": "^8.5.1", "reflect-metadata": "^0.1.13", "request": "^2.88.0", @@ -1746,6 +1747,25 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2511,6 +2531,11 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "node_modules/ts-node": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", @@ -2877,6 +2902,20 @@ "extsprintf": "^1.2.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4305,6 +4344,14 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4911,6 +4958,11 @@ } } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "ts-node": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", @@ -5169,6 +5221,20 @@ "extsprintf": "^1.2.0" } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 06e3035..10f90b8 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "markdown-it": "^8.4.2", "markdown-it-footnote": "^3.0.2", "morgan": "^1.9.1", + "node-fetch": "^2.6.7", "pg": "^8.5.1", "reflect-metadata": "^0.1.13", "request": "^2.88.0", diff --git a/site/css/main.scss b/site/css/main.scss index ee1cfd7..61d599e 100644 --- a/site/css/main.scss +++ b/site/css/main.scss @@ -354,7 +354,47 @@ article { } #comments-info { - margin-top: 0; + margin: 0; + } + + #remote-interact { + display: flex; + flex-direction: row; + align-items: baseline; + + input { + margin-left: 4px; + } + + input[type=text] { + flex-grow: 1; + padding: 0 4px; + background-color: var(--content-background-color); + border: 1px solid var(--accent-color); + font-size: 1rem; + line-height: 2rem; + color: var(--content-text-color); + } + + input[type=submit] { + background-color: var(--ui-background-color); + border: 1px solid var(--accent-color); + color: var(--accent-color); + line-height: 2rem; + padding: 0 1rem; + text-decoration: none; + font-weight: bold; + text-transform: uppercase; + + -webkit-transition: 0.3s ease-out; + transition: 0.3s ease-out; + + &:hover { + background-color: var(--accent-color); + color: var(--ui-background-color); + cursor: pointer; + } + } } #comments-js-warning { diff --git a/site/layouts/article.html.ejs b/site/layouts/article.html.ejs index c8c4f5d..5b50c0e 100644 --- a/site/layouts/article.html.ejs +++ b/site/layouts/article.html.ejs @@ -23,9 +23,20 @@ metadata.layout = "default.html.ejs"

Comments

- Comments powered by ActivityPub. To respond to this post or to another comment, copy its URL into the search interface of your client for Mastodon, Pleroma, or other compatible software. + Comments powered by ActivityPub. To respond to this post enter your username and instance below, or copy its URL into the search interface of your client for Mastodon, Pleroma, or other compatible software. Learn more.

+
+ Reply from your instance: + <% if (metadata.useOldPermalinkForComments) { %> + + <% } else { %> + + <% } %> + + + +