@fedify/botkit 0.3.1-dev.169 → 0.4.0-dev.170

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
2
+ Date.prototype.toTemporalInstant = toTemporalInstant;
3
+ import { BotImpl } from "../bot-impl.js";
4
+ import * as hono_jsx_jsx_dev_runtime1 from "hono/jsx/jsx-dev-runtime";
5
+
6
+ //#region src/components/FollowButton.d.ts
7
+ interface FollowButtonProps {
8
+ readonly bot: BotImpl<unknown>;
9
+ }
10
+ declare function FollowButton({
11
+ bot
12
+ }: FollowButtonProps): hono_jsx_jsx_dev_runtime1.JSX.Element;
13
+ //# sourceMappingURL=FollowButton.d.ts.map
14
+
15
+ //#endregion
16
+ export { FollowButton, FollowButtonProps };
17
+ //# sourceMappingURL=FollowButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FollowButton.d.ts","names":[],"sources":["../../src/components/FollowButton.tsx"],"sourcesContent":[],"mappings":";;;;;;UAGiB,iBAAA;gBACD;;iBAGA,YAAA;;GAAsB,oBAAiB,yBAAA,CAAA,GAAA,CAAA"}
@@ -0,0 +1,61 @@
1
+
2
+ import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
3
+ Date.prototype.toTemporalInstant = toTemporalInstant;
4
+
5
+ import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
6
+
7
+ //#region src/components/FollowButton.tsx
8
+ function FollowButton({ bot }) {
9
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
10
+ /* @__PURE__ */ jsx("button", {
11
+ id: "follow-btn",
12
+ type: "button",
13
+ style: "padding: 0.5rem 1rem; background: var(--pico-primary); color: var(--pico-primary-inverse); border: none; border-radius: 0.25rem; cursor: pointer;",
14
+ onclick: "showFollowModal()",
15
+ children: "Follow"
16
+ }),
17
+ /* @__PURE__ */ jsx("dialog", {
18
+ id: "follow-modal",
19
+ children: /* @__PURE__ */ jsxs("article", {
20
+ style: "width: 400px;",
21
+ children: [/* @__PURE__ */ jsxs("header", {
22
+ style: "display: flex; align-items: center; justify-content:space-between",
23
+ children: [/* @__PURE__ */ jsxs("h3", { children: ["Follow ", bot.name ?? bot.username] }), /* @__PURE__ */ jsx("button", {
24
+ "aria-label": "Close",
25
+ rel: "prev",
26
+ type: "button",
27
+ onclick: "closeFollowModal()"
28
+ })]
29
+ }), /* @__PURE__ */ jsxs("main", { children: [/* @__PURE__ */ jsx("p", { children: "Enter your fediverse handle to follow this account:" }), /* @__PURE__ */ jsxs("form", {
30
+ action: "/follow",
31
+ method: "post",
32
+ children: [/* @__PURE__ */ jsx("input", {
33
+ type: "text",
34
+ id: "fediverse-handle",
35
+ name: "handle",
36
+ placeholder: "@username@instance.com",
37
+ required: true,
38
+ style: "width: 100%; margin-bottom: 1rem;"
39
+ }), /* @__PURE__ */ jsx("button", {
40
+ type: "submit",
41
+ style: "width: 100%;",
42
+ children: "Follow"
43
+ })]
44
+ })] })]
45
+ })
46
+ }),
47
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `
48
+ function showFollowModal() {
49
+ document.getElementById('follow-modal').showModal();
50
+ }
51
+
52
+ function closeFollowModal() {
53
+ document.getElementById('follow-modal').close();
54
+ }
55
+ ` } })
56
+ ] });
57
+ }
58
+
59
+ //#endregion
60
+ export { FollowButton };
61
+ //# sourceMappingURL=FollowButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FollowButton.js","names":[],"sources":["../../src/components/FollowButton.tsx"],"sourcesContent":["/** @jsxImportSource hono/jsx */\nimport type { BotImpl } from \"../bot-impl.ts\";\n\nexport interface FollowButtonProps {\n readonly bot: BotImpl<unknown>;\n}\n\nexport function FollowButton({ bot }: FollowButtonProps) {\n return (\n <>\n <button\n id=\"follow-btn\"\n type=\"button\"\n style=\"padding: 0.5rem 1rem; background: var(--pico-primary); color: var(--pico-primary-inverse); border: none; border-radius: 0.25rem; cursor: pointer;\"\n onclick=\"showFollowModal()\"\n >\n Follow\n </button>\n <dialog id=\"follow-modal\">\n <article style=\"width: 400px;\">\n <header style=\"display: flex; align-items: center; justify-content:space-between\">\n <h3>Follow {bot.name ?? bot.username}</h3>\n <button\n aria-label=\"Close\"\n rel=\"prev\"\n type=\"button\"\n onclick=\"closeFollowModal()\"\n />\n </header>\n <main>\n <p>Enter your fediverse handle to follow this account:</p>\n <form action=\"/follow\" method=\"post\">\n <input\n type=\"text\"\n id=\"fediverse-handle\"\n name=\"handle\"\n placeholder=\"@username@instance.com\"\n required\n style=\"width: 100%; margin-bottom: 1rem;\"\n />\n <button type=\"submit\" style=\"width: 100%;\">\n Follow\n </button>\n </form>\n </main>\n </article>\n </dialog>\n <script\n dangerouslySetInnerHTML={{\n __html: `\n function showFollowModal() {\n document.getElementById('follow-modal').showModal();\n }\n \n function closeFollowModal() {\n document.getElementById('follow-modal').close();\n }\n `,\n }}\n />\n </>\n );\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,aAAa,EAAE,KAAwB,EAAE;AACvD,wBACE;kBACE,IAAC;GACC,IAAG;GACH,MAAK;GACL,OAAM;GACN,SAAQ;aACT;IAEQ;kBACT,IAAC;GAAO,IAAG;6BACT,KAAC;IAAQ,OAAM;+BACb,KAAC;KAAO,OAAM;gCACZ,KAAC,mBAAG,WAAQ,IAAI,QAAQ,IAAI,YAAc,kBAC1C,IAAC;MACC,cAAW;MACX,KAAI;MACJ,MAAK;MACL,SAAQ;OACR;MACK,kBACT,KAAC,qCACC,IAAC,iBAAE,wDAAuD,kBAC1D,KAAC;KAAK,QAAO;KAAU,QAAO;gCAC5B,IAAC;MACC,MAAK;MACL,IAAG;MACH,MAAK;MACL,aAAY;MACZ;MACA,OAAM;OACN,kBACF,IAAC;MAAO,MAAK;MAAS,OAAM;gBAAe;OAElC;MACJ,IACF;KACC;IACH;kBACT,IAAC,YACC,yBAAyB,EACvB,SAAS;;;;;;;;UASV,IACD;KACD;AAEN"}
@@ -2,7 +2,7 @@ import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
2
2
  Date.prototype.toTemporalInstant = toTemporalInstant;
3
3
  import { Session } from "../session.js";
4
4
  import { Actor } from "@fedify/fedify/vocab";
5
- import * as hono_utils_html2 from "hono/utils/html";
5
+ import * as hono_utils_html0 from "hono/utils/html";
6
6
 
7
7
  //#region src/components/Follower.d.ts
8
8
  interface FollowerProps {
@@ -12,7 +12,7 @@ interface FollowerProps {
12
12
  declare function Follower({
13
13
  actor,
14
14
  session
15
- }: FollowerProps): Promise<hono_utils_html2.HtmlEscapedString>;
15
+ }: FollowerProps): Promise<hono_utils_html0.HtmlEscapedString>;
16
16
  //# sourceMappingURL=Follower.d.ts.map
17
17
 
18
18
  //#endregion
@@ -2,7 +2,7 @@ import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
2
2
  Date.prototype.toTemporalInstant = toTemporalInstant;
3
3
  import { MessageClass } from "../message.js";
4
4
  import { Session } from "../session.js";
5
- import * as hono_utils_html1 from "hono/utils/html";
5
+ import * as hono_utils_html3 from "hono/utils/html";
6
6
 
7
7
  //#region src/components/Message.d.ts
8
8
  interface MessageProps {
@@ -12,7 +12,7 @@ interface MessageProps {
12
12
  declare function Message({
13
13
  session,
14
14
  message
15
- }: MessageProps): Promise<hono_utils_html1.HtmlEscapedString>;
15
+ }: MessageProps): Promise<hono_utils_html3.HtmlEscapedString>;
16
16
  declare function renderCustomEmojis(html: string, emojis: Record<string, string>): string;
17
17
  //# sourceMappingURL=Message.d.ts.map
18
18
 
package/dist/deno.js CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  //#region deno.json
6
6
  var name = "@fedify/botkit";
7
- var version = "0.3.1-dev.169+581b2899";
7
+ var version = "0.4.0-dev.170+765d25d4";
8
8
  var license = "AGPL-3.0-only";
9
9
  var exports = {
10
10
  ".": "./src/mod.ts",
@@ -27,6 +27,7 @@ var imports = {
27
27
  "markdown-it": "npm:markdown-it@^14.1.0",
28
28
  "mime-db": "npm:mime-db@^1.54.0",
29
29
  "tsdown": "npm:tsdown@^0.12.8",
30
+ "url-template": "npm:url-template@^3.1.1",
30
31
  "uuid": "npm:uuid@^11.1.0",
31
32
  "xss": "npm:xss@^1.0.15"
32
33
  };
package/dist/deno.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"deno.js","names":[],"sources":["../deno.json"],"sourcesContent":["{\n \"name\": \"@fedify/botkit\",\n \"version\": \"0.3.1-dev.169+581b2899\",\n \"license\": \"AGPL-3.0-only\",\n \"exports\": {\n \".\": \"./src/mod.ts\",\n \"./bot\": \"./src/bot.ts\",\n \"./emoji\": \"./src/emoji.ts\",\n \"./events\": \"./src/events.ts\",\n \"./follow\": \"./src/follow.ts\",\n \"./message\": \"./src/message.ts\",\n \"./poll\": \"./src/poll.ts\",\n \"./reaction\": \"./src/reaction.ts\",\n \"./repository\": \"./src/repository.ts\",\n \"./session\": \"./src/session.ts\",\n \"./text\": \"./src/text.ts\"\n },\n \"imports\": {\n \"@fedify/markdown-it-hashtag\": \"jsr:@fedify/markdown-it-hashtag@^0.3.0\",\n \"@fedify/markdown-it-mention\": \"jsr:@fedify/markdown-it-mention@^0.3.0\",\n \"@phensley/language-tag\": \"npm:@phensley/language-tag@^1.12.2\",\n \"html-entities\": \"npm:html-entities@^2.6.0\",\n \"markdown-it\": \"npm:markdown-it@^14.1.0\",\n \"mime-db\": \"npm:mime-db@^1.54.0\",\n \"tsdown\": \"npm:tsdown@^0.12.8\",\n \"uuid\": \"npm:uuid@^11.1.0\",\n \"xss\": \"npm:xss@^1.0.15\"\n },\n \"exclude\": [\n \"dist\",\n \"junit.xml\",\n \"src/css\"\n ],\n \"fmt\": {\n \"exclude\": [\n \"*.md\",\n \"*.yaml\",\n \"*.yml\",\n \"src/static/*.ts\"\n ]\n },\n \"tasks\": {\n \"test\": \"deno test --allow-env=NODE_V8_COVERAGE,JEST_WORKER_ID --allow-net=hollo.social --parallel\",\n \"test:node\": \"pnpm install && pnpm test\",\n \"test-all\": {\n \"dependencies\": [\n \"check\",\n \"test\",\n \"test:node\"\n ]\n },\n \"coverage\": \"deno task test --coverage --clean && deno coverage --html\"\n }\n}\n"],"mappings":";;;;;WACU;cACG;cACA;cACA;CACT,KAAK;CACL,SAAS;CACT,WAAW;CACX,YAAY;CACZ,YAAY;CACZ,aAAa;CACb,UAAU;CACV,cAAc;CACd,gBAAgB;CAChB,aAAa;CACb,UAAU;AACX;cACU;CACT,+BAA+B;CAC/B,+BAA+B;CAC/B,0BAA0B;CAC1B,iBAAiB;CACjB,eAAe;CACf,WAAW;CACX,UAAU;CACV,QAAQ;CACR,OAAO;AACR;cACU;CACT;CACA;CACA;AACD;UACM,EACL,WAAW;CACT;CACA;CACA;CACA;AACD,EACF;YACQ;CACP,QAAQ;CACR,aAAa;CACb,YAAY,EACV,gBAAgB;EACd;EACA;EACA;CACD,EACF;CACD,YAAY;AACb;mBApDH;;;;;;;;;AAqDC"}
1
+ {"version":3,"file":"deno.js","names":[],"sources":["../deno.json"],"sourcesContent":["{\n \"name\": \"@fedify/botkit\",\n \"version\": \"0.4.0-dev.170+765d25d4\",\n \"license\": \"AGPL-3.0-only\",\n \"exports\": {\n \".\": \"./src/mod.ts\",\n \"./bot\": \"./src/bot.ts\",\n \"./emoji\": \"./src/emoji.ts\",\n \"./events\": \"./src/events.ts\",\n \"./follow\": \"./src/follow.ts\",\n \"./message\": \"./src/message.ts\",\n \"./poll\": \"./src/poll.ts\",\n \"./reaction\": \"./src/reaction.ts\",\n \"./repository\": \"./src/repository.ts\",\n \"./session\": \"./src/session.ts\",\n \"./text\": \"./src/text.ts\"\n },\n \"imports\": {\n \"@fedify/markdown-it-hashtag\": \"jsr:@fedify/markdown-it-hashtag@^0.3.0\",\n \"@fedify/markdown-it-mention\": \"jsr:@fedify/markdown-it-mention@^0.3.0\",\n \"@phensley/language-tag\": \"npm:@phensley/language-tag@^1.12.2\",\n \"html-entities\": \"npm:html-entities@^2.6.0\",\n \"markdown-it\": \"npm:markdown-it@^14.1.0\",\n \"mime-db\": \"npm:mime-db@^1.54.0\",\n \"tsdown\": \"npm:tsdown@^0.12.8\",\n \"url-template\": \"npm:url-template@^3.1.1\",\n \"uuid\": \"npm:uuid@^11.1.0\",\n \"xss\": \"npm:xss@^1.0.15\"\n },\n \"exclude\": [\n \"dist\",\n \"junit.xml\",\n \"src/css\"\n ],\n \"fmt\": {\n \"exclude\": [\n \"*.md\",\n \"*.yaml\",\n \"*.yml\",\n \"src/static/*.ts\"\n ]\n },\n \"tasks\": {\n \"test\": \"deno test --allow-env=NODE_V8_COVERAGE,JEST_WORKER_ID --allow-net=hollo.social --parallel\",\n \"test:node\": \"pnpm install && pnpm test\",\n \"test-all\": {\n \"dependencies\": [\n \"check\",\n \"test\",\n \"test:node\"\n ]\n },\n \"coverage\": \"deno task test --coverage --clean && deno coverage --html\"\n }\n}\n"],"mappings":";;;;;WACU;cACG;cACA;cACA;CACT,KAAK;CACL,SAAS;CACT,WAAW;CACX,YAAY;CACZ,YAAY;CACZ,aAAa;CACb,UAAU;CACV,cAAc;CACd,gBAAgB;CAChB,aAAa;CACb,UAAU;AACX;cACU;CACT,+BAA+B;CAC/B,+BAA+B;CAC/B,0BAA0B;CAC1B,iBAAiB;CACjB,eAAe;CACf,WAAW;CACX,UAAU;CACV,gBAAgB;CAChB,QAAQ;CACR,OAAO;AACR;cACU;CACT;CACA;CACA;AACD;UACM,EACL,WAAW;CACT;CACA;CACA;CACA;AACD,EACF;YACQ;CACP,QAAQ;CACR,aAAa;CACb,YAAY,EACV,gBAAgB;EACd;EACA;EACA;CACD,EACF;CACD,YAAY;AACb;mBArDH;;;;;;;;;AAsDC"}
package/dist/pages.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
2
2
  Date.prototype.toTemporalInstant = toTemporalInstant;
3
3
  import { BotImpl } from "./bot-impl.js";
4
4
  import { Hono } from "hono";
5
- import * as hono_types0 from "hono/types";
5
+ import * as hono_types2 from "hono/types";
6
6
 
7
7
  //#region src/pages.d.ts
8
8
  interface Bindings {
@@ -12,7 +12,7 @@ interface Bindings {
12
12
  interface Env {
13
13
  readonly Bindings: Bindings;
14
14
  }
15
- declare const app: Hono<Env, hono_types0.BlankSchema, "/">;
15
+ declare const app: Hono<Env, hono_types2.BlankSchema, "/">;
16
16
  //# sourceMappingURL=pages.d.ts.map
17
17
 
18
18
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"pages.d.ts","names":[],"sources":["../src/pages.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAsCiB,QAAA;gBACD;;;UAIC,GAAA;qBACI;;AANJ,cASJ,GATY,EAST,IARA,CAQA,GARO,EAQP,WAAA,CAAA,WAAA,EARO,GAAA,CAAA;AAIvB"}
1
+ {"version":3,"file":"pages.d.ts","names":[],"sources":["../src/pages.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAwCiB,QAAA;gBACD;;;UAIC,GAAA;qBACI;;AANJ,cASJ,GATY,EAST,IARA,CAQA,GARO,EAQP,WAAA,CAAA,WAAA,EARO,GAAA,CAAA;AAIvB"}
package/dist/pages.js CHANGED
@@ -3,12 +3,14 @@
3
3
  Date.prototype.toTemporalInstant = toTemporalInstant;
4
4
 
5
5
  import { getMessageClass, isMessageObject, textXss } from "./message-impl.js";
6
+ import { FollowButton } from "./components/FollowButton.js";
7
+ import { Follower } from "./components/Follower.js";
6
8
  import { Layout } from "./components/Layout.js";
7
9
  import { Message } from "./components/Message.js";
8
- import { Follower } from "./components/Follower.js";
9
10
  import { Hashtag, Image, Link, PUBLIC_COLLECTION, getActorHandle } from "@fedify/fedify/vocab";
10
11
  import { decode } from "html-entities";
11
12
  import { Hono } from "hono";
13
+ import { parseTemplate } from "url-template";
12
14
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
13
15
 
14
16
  //#region src/pages.tsx
@@ -106,7 +108,9 @@ app.get("/", async (c) => {
106
108
  "·",
107
109
  " ",
108
110
  /* @__PURE__ */ jsx("span", { children: postsCount === 1 ? `1 post` : `${postsCount.toLocaleString("en")} posts` }),
109
- " "
111
+ " ",
112
+ "· ",
113
+ /* @__PURE__ */ jsx(FollowButton, { bot })
110
114
  ] })
111
115
  ] }),
112
116
  summary && /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: summary } }),
@@ -337,6 +341,99 @@ app.get("/feed.xml", async (c) => {
337
341
  response.headers.set("Content-Type", "application/atom+xml; charset=utf-8");
338
342
  return response;
339
343
  });
344
+ app.post("/follow", async (c) => {
345
+ const { bot } = c.env;
346
+ const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);
347
+ const url = new URL(c.req.url);
348
+ const formData = await c.req.formData();
349
+ let followerHandle = formData.get("handle")?.toString();
350
+ try {
351
+ if (!followerHandle) return c.html(/* @__PURE__ */ jsx(Layout, {
352
+ bot,
353
+ host: url.host,
354
+ title: "Error",
355
+ children: /* @__PURE__ */ jsxs("main", {
356
+ class: "container",
357
+ children: [
358
+ /* @__PURE__ */ jsx("h1", { children: "Error" }),
359
+ /* @__PURE__ */ jsx("p", { children: "Follower handle is required." }),
360
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", {
361
+ href: "/",
362
+ children: "Go back"
363
+ }) })
364
+ ]
365
+ })
366
+ }), 400);
367
+ if (followerHandle.startsWith("@")) followerHandle = followerHandle.slice(1);
368
+ const webfingerData = await ctx.lookupWebFinger(`acct:${followerHandle}`);
369
+ if (!webfingerData?.links) return c.html(/* @__PURE__ */ jsx(Layout, {
370
+ bot,
371
+ host: url.host,
372
+ title: "Error",
373
+ children: /* @__PURE__ */ jsxs("main", {
374
+ class: "container",
375
+ children: [
376
+ /* @__PURE__ */ jsx("h1", { children: "Error" }),
377
+ /* @__PURE__ */ jsxs("p", { children: [
378
+ "No links found in webfinger data for",
379
+ " ",
380
+ /* @__PURE__ */ jsxs("code", { children: ["@", followerHandle] }),
381
+ "."
382
+ ] }),
383
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", {
384
+ href: "/",
385
+ children: "Go back"
386
+ }) })
387
+ ]
388
+ })
389
+ }), 400);
390
+ const subscribeLink = webfingerData.links.find((link) => link.rel === "http://ostatus.org/schema/1.0/subscribe");
391
+ if (subscribeLink?.template) {
392
+ const botActorUri = ctx.getActorUri(bot.identifier);
393
+ const followUrlTemplate = parseTemplate(subscribeLink.template);
394
+ const followUrl = followUrlTemplate.expand({ uri: botActorUri.href });
395
+ return c.redirect(followUrl);
396
+ }
397
+ return c.html(/* @__PURE__ */ jsx(Layout, {
398
+ bot,
399
+ host: url.host,
400
+ title: "Error",
401
+ children: /* @__PURE__ */ jsxs("main", {
402
+ class: "container",
403
+ children: [
404
+ /* @__PURE__ */ jsx("h1", { children: "Error" }),
405
+ /* @__PURE__ */ jsxs("p", { children: [
406
+ "No follow link found in WebFinger data for",
407
+ " ",
408
+ /* @__PURE__ */ jsxs("code", { children: ["@", followerHandle] }),
409
+ "."
410
+ ] }),
411
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", {
412
+ href: "/",
413
+ children: "Go back"
414
+ }) })
415
+ ]
416
+ })
417
+ }), 400);
418
+ } catch (_error) {
419
+ return c.html(/* @__PURE__ */ jsx(Layout, {
420
+ bot,
421
+ host: url.host,
422
+ title: "Error",
423
+ children: /* @__PURE__ */ jsxs("main", {
424
+ class: "container",
425
+ children: [
426
+ /* @__PURE__ */ jsx("h1", { children: "Internal Server Error" }),
427
+ /* @__PURE__ */ jsx("p", { children: "An internal server error occurred while processing your request." }),
428
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", {
429
+ href: "/",
430
+ children: "Go back"
431
+ }) })
432
+ ]
433
+ })
434
+ }), 500);
435
+ }
436
+ });
340
437
  async function getPosts(bot, ctx, options = {}) {
341
438
  const { offset, window = 15 } = options;
342
439
  let posts = await Array.fromAsync(bot.repository.getMessages({
package/dist/pages.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pages.js","names":["properties: Record<string, string>","nextLink: URL | undefined","bot: BotImpl<unknown>","ctx: Context<unknown>","options: GetPostsOptions","lastPost: Announce | Create | undefined","nextPost: Object | undefined","post: Create | Announce","context: Context<unknown>","hashtag?: string","hashtag: string"],"sources":["../src/pages.tsx"],"sourcesContent":["// BotKit by Fedify: A framework for creating ActivityPub bots\n// Copyright (C) 2025 Hong Minhee <https://hongminhee.org/>\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as\n// published by the Free Software Foundation, either version 3 of the\n// License, or (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n/** @jsx react-jsx */\n/** @jsxImportSource hono/jsx */\nimport type { Context } from \"@fedify/fedify/federation\";\nimport {\n type Announce,\n type Create,\n getActorHandle,\n Hashtag,\n Image,\n Link,\n type Object,\n PUBLIC_COLLECTION,\n} from \"@fedify/fedify/vocab\";\nimport { Hono } from \"hono\";\nimport { decode } from \"html-entities\";\nimport type { BotImpl } from \"./bot-impl.ts\";\nimport { Layout } from \"./components/Layout.tsx\";\nimport { Message } from \"./components/Message.tsx\";\nimport { Follower } from \"./components/Follower.tsx\";\nimport { getMessageClass, isMessageObject, textXss } from \"./message-impl.ts\";\nimport type { MessageClass } from \"./message.ts\";\nimport type { Uuid } from \"./repository.ts\";\n\nexport interface Bindings {\n readonly bot: BotImpl<unknown>;\n readonly contextData: unknown;\n}\n\nexport interface Env {\n readonly Bindings: Bindings;\n}\n\nexport const app = new Hono<Env>();\n\napp.get(\"/\", async (c) => {\n const { bot } = c.env;\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const url = new URL(c.req.url);\n const handle = `@${bot.username}@${url.host}`;\n const icon = bot.icon instanceof Image\n ? bot.icon.url instanceof Link ? bot.icon.url.href : bot.icon.url\n : bot.icon;\n const iconWidth = bot.icon instanceof Image ? bot.icon.width : null;\n const iconHeight = bot.icon instanceof Image ? bot.icon.height : null;\n const image = bot.image instanceof Image\n ? bot.image.url instanceof Link ? bot.image.url.href : bot.image.url\n : bot.image;\n const imageWidth = bot.image instanceof Image ? bot.image.width : null;\n const imageHeight = bot.image instanceof Image ? bot.image.height : null;\n const followersCount = await bot.repository.countFollowers();\n const summaryChunks = bot.summary?.getHtml(session);\n const postsCount = await bot.repository.countMessages();\n const summary = summaryChunks == null\n ? null\n : (await Array.fromAsync(summaryChunks)).join(\"\");\n const properties: Record<string, string> = {};\n for (const name in bot.properties) {\n const value = bot.properties[name];\n const valueHtml = (await Array.fromAsync(value.getHtml(session))).join(\"\");\n properties[name] = valueHtml;\n }\n const offset = c.req.query(\"offset\");\n const { posts: messages, nextPost } = await getPosts(\n bot,\n ctx,\n offset ? { offset: Temporal.Instant.from(offset) } : {},\n );\n const activityLink = ctx.getActorUri(bot.identifier);\n const feedLink = new URL(\"/feed.xml\", url);\n let nextLink: URL | undefined;\n if (nextPost?.published != null) {\n nextLink = new URL(\"/\", url);\n nextLink.searchParams.set(\"offset\", nextPost.published.toString());\n }\n return c.html(\n <Layout\n bot={bot}\n host={url.host}\n activityLink={activityLink}\n feedLink={feedLink}\n >\n <header class=\"container\">\n {image && (\n <img\n src={image.href}\n width={imageWidth ?? undefined}\n height={imageHeight ?? undefined}\n alt={image instanceof Image\n ? image.name?.toString() ?? undefined\n : undefined}\n style=\"width: 100%; margin-bottom: 1em;\"\n />\n )}\n <hgroup>\n {icon && (\n <img\n src={icon.href}\n width={iconWidth ?? undefined}\n height={iconHeight ?? undefined}\n style=\"float: left; margin-right: 1em; height: 72;\"\n />\n )}\n <h1>\n <a href=\"/\">{bot.name ?? bot.username}</a>\n </h1>\n <p>\n <span style=\"user-select: all;\">{handle}</span> &middot;{\" \"}\n <a\n href=\"/feed.xml\"\n rel=\"alternate\"\n type=\"application/atom+xml\"\n title=\"Atom feed\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={18}\n height={18}\n viewBox=\"0 0 16 16\"\n aria-label=\"Atom feed\"\n >\n <path\n fill=\"currentColor\"\n d=\"M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm1.5 2.5c5.523 0 10 4.477 10 10a1 1 0 1 1-2 0a8 8 0 0 0-8-8a1 1 0 0 1 0-2m0 4a6 6 0 0 1 6 6a1 1 0 1 1-2 0a4 4 0 0 0-4-4a1 1 0 0 1 0-2m.5 7a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3\"\n >\n </path>\n </svg>\n </a>{\" \"}\n &middot;{\" \"}\n <span>\n <a href=\"/followers\">\n {followersCount === 1\n ? `1 follower`\n : `${followersCount.toLocaleString(\"en\")} followers`}\n </a>\n </span>{\" \"}\n &middot;{\" \"}\n <span>\n {postsCount === 1\n ? `1 post`\n : `${postsCount.toLocaleString(\"en\")} posts`}\n </span>\n {\" \"}\n </p>\n </hgroup>\n {summary &&\n (\n <div\n dangerouslySetInnerHTML={{ __html: summary }}\n />\n )}\n {globalThis.Object.keys(properties).length > 0 && (\n <table>\n <tbody>\n {globalThis.Object.entries(properties).map(([name, value]) => (\n <tr>\n <th scope=\"row\" style=\"width: 1%; white-space: nowrap;\">\n <strong>{name}</strong>\n </th>\n <td\n dangerouslySetInnerHTML={{ __html: value }}\n />\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </header>\n <main class=\"container\">\n {messages.map((message) => (\n <Message message={message} session={session} />\n ))}\n </main>\n <footer class=\"container\">\n <nav style=\"display: block; text-align: end;\">\n {nextLink && (\n <a rel=\"next\" href={nextLink.href}>\n Older posts &rarr;\n </a>\n )}\n </nav>\n </footer>\n </Layout>,\n {\n headers: {\n Link:\n `<${activityLink.href}>; rel=\"alternate\"; type=\"application/activity+json\", ` +\n `<${feedLink.href}>; rel=\"alternate\"; type=\"application/atom+xml\"` +\n (nextLink\n ? `, <${nextLink.href}>; rel=\"next\"; type=\"text/html\"`\n : \"\"),\n },\n },\n );\n});\n\napp.get(\"/followers\", async (c) => {\n const { bot } = c.env;\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const followersCount = await bot.repository.countFollowers();\n const followers = await Array.fromAsync(bot.repository.getFollowers());\n\n const url = new URL(c.req.url);\n const activityLink = ctx.getActorUri(bot.identifier);\n const feedLink = new URL(\"/feed.xml\", url);\n\n return c.html(\n <Layout\n bot={bot}\n host={url.host}\n activityLink={activityLink}\n feedLink={feedLink}\n >\n <header class=\"container\">\n <h1>\n <a href=\"/\">&larr;</a>{\" \"}\n {followersCount === 1\n ? `1 follower`\n : `${followersCount.toLocaleString(\"en\")} followers`}\n </h1>\n </header>\n <main class=\"container\">\n {followers.map((follower, index) => (\n <Follower\n key={follower.id?.href ?? index}\n actor={follower}\n session={session}\n />\n ))}\n </main>\n </Layout>,\n );\n});\n\napp.get(\"/tags/:hashtag\", async (c) => {\n const hashtag = c.req.param(\"hashtag\");\n const { bot } = c.env;\n const url = new URL(c.req.url);\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const offset = c.req.query(\"offset\");\n const { posts, nextPost } = await getPosts(bot, ctx, {\n hashtag,\n offset: offset == null ? undefined : Temporal.Instant.from(offset),\n });\n let nextLink: URL | undefined;\n if (nextPost?.published != null) {\n nextLink = new URL(`/tags/${encodeURIComponent(hashtag)}`, url);\n nextLink.searchParams.set(\"offset\", nextPost.published.toString());\n }\n return c.html(\n <Layout bot={bot} host={url.host} title={`#${hashtag}`}>\n <header class=\"container\">\n <h1>#{hashtag}</h1>\n </header>\n <main class=\"container\">\n {posts.map((message) => (\n <Message message={message} session={session} />\n ))}\n </main>\n <footer class=\"container\">\n <nav style=\"display: block; text-align: end;\">\n {nextLink && (\n <a rel=\"next\" href={nextLink.href}>Older posts &rarr;</a>\n )}\n </nav>\n </footer>\n </Layout>,\n {\n headers: nextLink == null ? {} : {\n Link: `<${nextLink.href}>; rel=\"next\"; type=\"text/html\"`,\n },\n },\n );\n});\n\napp.get(\"/message/:id\", async (c) => {\n const id = c.req.param(\"id\");\n const { bot } = c.env;\n const url = new URL(c.req.url);\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const post = await bot.repository.getMessage(id as Uuid);\n if (post == null || !isPublic(post)) return c.notFound();\n const message = await post.getObject(ctx);\n if (message == null || !isMessageObject(message)) return c.notFound();\n const activityLink = ctx.getObjectUri<MessageClass>(\n getMessageClass(message),\n { id },\n );\n const feedLink = new URL(\"/feed.xml\", url);\n let title = message.name;\n if (title == null) {\n title = message.summary ?? message.content;\n if (title != null) {\n title = decode(textXss.process(title.toString()));\n }\n }\n return c.html(\n <Layout\n bot={bot}\n host={url.host}\n activityLink={activityLink}\n feedLink={feedLink}\n title={title?.toString() ?? undefined}\n >\n <main class=\"container\">\n <Message message={message} session={session} />\n </main>\n </Layout>,\n {\n headers: {\n Link:\n `<${activityLink.href}>; rel=\"alternate\"; type=\"application/activity+json\", ` +\n `<${feedLink.href}>; rel=\"alternate\"; type=\"application/atom+xml\"`,\n },\n },\n );\n});\n\napp.get(\"/feed.xml\", async (c) => {\n const { bot } = c.env;\n const url = new URL(c.req.url);\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const { posts } = await getPosts(bot, ctx, { window: 30 });\n const botName = bot.name ?? bot.username;\n const canonicalUrl = new URL(\"/feed.xml\", url);\n const profileUrl = new URL(\"/\", url);\n const actorUrl = ctx.getActorUri(bot.identifier);\n c.header(\n \"Link\",\n `<${actorUrl.href}>; rel=\"alternate\"; type=\"application/activity+json\", ` +\n `<${profileUrl.href}>; rel=\"alternate\"; type=\"text/html\"`,\n );\n const response = await c.render(\n <feed xmlns=\"http://www.w3.org/2005/Atom\">\n <id>{canonicalUrl.href}</id>\n <link rel=\"self\" type=\"application/atom+xml\" href={canonicalUrl.href} />\n <link rel=\"alternate\" type=\"text/html\" href={profileUrl.href} />\n <link\n rel=\"alternate\"\n type=\"application/activity+json\"\n href={actorUrl.href}\n />\n <title>{botName} (@{bot.username}@{url.host})</title>\n <author>\n <name>{botName}</name>\n <uri>{profileUrl.href}</uri>\n </author>\n {posts.length > 0 && (\n <updated>\n {(posts[0].updated ?? posts[0].published)?.toString()}\n </updated>\n )}\n {posts.map(async (post) => {\n const activityUrl = post.id;\n if (activityUrl == null) return undefined;\n const permalink =\n (post.url instanceof Link ? post.url.href : post.url) ?? activityUrl;\n const author = post.attributionId?.href === session.actorId?.href\n ? await session.getActor()\n : await post.getAttribution({\n documentLoader: ctx.documentLoader,\n contextLoader: ctx.contextLoader,\n suppressError: true,\n });\n const authorName = author?.name ?? author?.preferredUsername ??\n (author == null ? undefined : await getActorHandle(author));\n const authorUrl =\n (author?.url instanceof Link ? author.url.href : author?.url) ??\n author?.id;\n const updated = post.updated ?? post.published;\n let title = post.name;\n if (title == null) {\n title = post.summary ?? post.content;\n if (title != null) {\n title = decode(textXss.process(title.toString()));\n }\n }\n return (\n <entry>\n <id>{permalink.href}</id>\n <link rel=\"alternate\" type=\"text/html\" href={permalink.href} />\n <link\n rel=\"alternate\"\n type=\"application/activity+json\"\n href={activityUrl.href}\n />\n {authorName &&\n (\n <author>\n <name>{authorName}</name>\n {authorUrl &&\n <uri>{authorUrl.href}</uri>}\n </author>\n )}\n {post.published && (\n <published>{post.published.toString()}</published>\n )}\n {updated && <updated>{updated.toString()}</updated>}\n {title && <title>{title}</title>}\n {post.summary && (\n <summary type=\"html\">{post.summary.toString()}</summary>\n )}\n {post.content && (\n <content type=\"html\">{post.content.toString()}</content>\n )}\n </entry>\n );\n })}\n </feed>,\n );\n response.headers.set(\"Content-Type\", \"application/atom+xml; charset=utf-8\");\n return response;\n});\n\ninterface GetPostsOptions {\n readonly hashtag?: string;\n readonly offset?: Temporal.Instant;\n readonly window?: number;\n}\n\nasync function getPosts(\n bot: BotImpl<unknown>,\n ctx: Context<unknown>,\n options: GetPostsOptions = {},\n): Promise<{ posts: MessageClass[]; nextPost?: Object }> {\n const { offset, window = 15 } = options;\n let posts = await Array.fromAsync(\n bot.repository.getMessages({\n order: \"newest\",\n until: offset,\n limit: window * 2,\n }),\n );\n let lastPost: Announce | Create | undefined = posts[posts.length - 1];\n posts = posts.slice(0, posts.length - 1);\n posts = posts.filter(isPublic);\n if (options.hashtag != null) {\n const taggedPosts = [];\n for (const post of posts) {\n if (await hasHashtag(ctx, post, options.hashtag)) {\n taggedPosts.push(post);\n }\n }\n posts = taggedPosts;\n }\n while (lastPost != null && posts.length < window) {\n const limit = (window - posts.length) * 2;\n const until = lastPost.published ??\n (await lastPost.getObject(ctx))?.published ??\n undefined;\n if (until == null) break;\n const nextPosts = bot.repository.getMessages({\n order: \"newest\",\n until,\n limit,\n });\n let i = 0;\n lastPost = undefined;\n for await (const post of nextPosts) {\n if (\n isPublic(post) && await hasHashtag(ctx, post, options.hashtag) &&\n posts.length < window + 1\n ) posts.push(post);\n lastPost = post;\n i++;\n }\n if (i < limit) break;\n }\n const nextPost: Object | undefined = await posts[window]?.getObject(ctx) ??\n undefined;\n posts = posts.slice(0, window);\n const messages = (await Promise.all(posts.map((p) => p.getObject(ctx))))\n .filter(isMessageObject);\n return { posts: messages, nextPost };\n}\n\nfunction isPublic(post: Create | Announce): boolean {\n return post.toIds.some((url) => url.href === PUBLIC_COLLECTION.href) ||\n post.ccIds.some((url) => url.href === PUBLIC_COLLECTION.href);\n}\n\nasync function hasHashtag(\n context: Context<unknown>,\n post: Create | Announce,\n hashtag?: string,\n): Promise<boolean> {\n if (hashtag == null) return true;\n hashtag = normalizeHashtag(hashtag);\n const object = await post.getObject(context);\n if (object == null) return false;\n for await (const tag of object.getTags(context)) {\n if (\n tag instanceof Hashtag && tag.name != null &&\n normalizeHashtag(tag.name.toString()) === hashtag\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction normalizeHashtag(hashtag: string): string {\n return hashtag\n .toLowerCase()\n .trimStart()\n .replace(/^#/, \"\")\n .trim()\n .replace(/\\s+/g, \"\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pages.js","names":["properties: Record<string, string>","nextLink: URL | undefined","bot: BotImpl<unknown>","ctx: Context<unknown>","options: GetPostsOptions","lastPost: Announce | Create | undefined","nextPost: Object | undefined","post: Create | Announce","context: Context<unknown>","hashtag?: string","hashtag: string"],"sources":["../src/pages.tsx"],"sourcesContent":["// BotKit by Fedify: A framework for creating ActivityPub bots\n// Copyright (C) 2025 Hong Minhee <https://hongminhee.org/>\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as\n// published by the Free Software Foundation, either version 3 of the\n// License, or (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n/** @jsx react-jsx */\n/** @jsxImportSource hono/jsx */\nimport type { Context } from \"@fedify/fedify/federation\";\nimport {\n type Announce,\n type Create,\n getActorHandle,\n Hashtag,\n Image,\n Link,\n type Object,\n PUBLIC_COLLECTION,\n} from \"@fedify/fedify/vocab\";\nimport { Hono } from \"hono\";\nimport { decode } from \"html-entities\";\nimport { parseTemplate } from \"url-template\";\nimport type { BotImpl } from \"./bot-impl.ts\";\nimport { FollowButton } from \"./components/FollowButton.tsx\";\nimport { Follower } from \"./components/Follower.tsx\";\nimport { Layout } from \"./components/Layout.tsx\";\nimport { Message } from \"./components/Message.tsx\";\nimport { getMessageClass, isMessageObject, textXss } from \"./message-impl.ts\";\nimport type { MessageClass } from \"./message.ts\";\nimport type { Uuid } from \"./repository.ts\";\n\nexport interface Bindings {\n readonly bot: BotImpl<unknown>;\n readonly contextData: unknown;\n}\n\nexport interface Env {\n readonly Bindings: Bindings;\n}\n\nexport const app = new Hono<Env>();\n\napp.get(\"/\", async (c) => {\n const { bot } = c.env;\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const url = new URL(c.req.url);\n const handle = `@${bot.username}@${url.host}`;\n const icon = bot.icon instanceof Image\n ? bot.icon.url instanceof Link ? bot.icon.url.href : bot.icon.url\n : bot.icon;\n const iconWidth = bot.icon instanceof Image ? bot.icon.width : null;\n const iconHeight = bot.icon instanceof Image ? bot.icon.height : null;\n const image = bot.image instanceof Image\n ? bot.image.url instanceof Link ? bot.image.url.href : bot.image.url\n : bot.image;\n const imageWidth = bot.image instanceof Image ? bot.image.width : null;\n const imageHeight = bot.image instanceof Image ? bot.image.height : null;\n const followersCount = await bot.repository.countFollowers();\n const summaryChunks = bot.summary?.getHtml(session);\n const postsCount = await bot.repository.countMessages();\n const summary = summaryChunks == null\n ? null\n : (await Array.fromAsync(summaryChunks)).join(\"\");\n const properties: Record<string, string> = {};\n for (const name in bot.properties) {\n const value = bot.properties[name];\n const valueHtml = (await Array.fromAsync(value.getHtml(session))).join(\"\");\n properties[name] = valueHtml;\n }\n const offset = c.req.query(\"offset\");\n const { posts: messages, nextPost } = await getPosts(\n bot,\n ctx,\n offset ? { offset: Temporal.Instant.from(offset) } : {},\n );\n const activityLink = ctx.getActorUri(bot.identifier);\n const feedLink = new URL(\"/feed.xml\", url);\n let nextLink: URL | undefined;\n if (nextPost?.published != null) {\n nextLink = new URL(\"/\", url);\n nextLink.searchParams.set(\"offset\", nextPost.published.toString());\n }\n return c.html(\n <Layout\n bot={bot}\n host={url.host}\n activityLink={activityLink}\n feedLink={feedLink}\n >\n <header class=\"container\">\n {image && (\n <img\n src={image.href}\n width={imageWidth ?? undefined}\n height={imageHeight ?? undefined}\n alt={image instanceof Image\n ? image.name?.toString() ?? undefined\n : undefined}\n style=\"width: 100%; margin-bottom: 1em;\"\n />\n )}\n <hgroup>\n {icon && (\n <img\n src={icon.href}\n width={iconWidth ?? undefined}\n height={iconHeight ?? undefined}\n style=\"float: left; margin-right: 1em; height: 72;\"\n />\n )}\n <h1>\n <a href=\"/\">{bot.name ?? bot.username}</a>\n </h1>\n <p>\n <span style=\"user-select: all;\">{handle}</span> &middot;{\" \"}\n <a\n href=\"/feed.xml\"\n rel=\"alternate\"\n type=\"application/atom+xml\"\n title=\"Atom feed\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={18}\n height={18}\n viewBox=\"0 0 16 16\"\n aria-label=\"Atom feed\"\n >\n <path\n fill=\"currentColor\"\n d=\"M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm1.5 2.5c5.523 0 10 4.477 10 10a1 1 0 1 1-2 0a8 8 0 0 0-8-8a1 1 0 0 1 0-2m0 4a6 6 0 0 1 6 6a1 1 0 1 1-2 0a4 4 0 0 0-4-4a1 1 0 0 1 0-2m.5 7a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3\"\n >\n </path>\n </svg>\n </a>{\" \"}\n &middot;{\" \"}\n <span>\n <a href=\"/followers\">\n {followersCount === 1\n ? `1 follower`\n : `${followersCount.toLocaleString(\"en\")} followers`}\n </a>\n </span>{\" \"}\n &middot;{\" \"}\n <span>\n {postsCount === 1\n ? `1 post`\n : `${postsCount.toLocaleString(\"en\")} posts`}\n </span>{\" \"}\n &middot; <FollowButton bot={bot} />\n </p>\n </hgroup>\n {summary &&\n (\n <div\n dangerouslySetInnerHTML={{ __html: summary }}\n />\n )}\n {globalThis.Object.keys(properties).length > 0 && (\n <table>\n <tbody>\n {globalThis.Object.entries(properties).map(([name, value]) => (\n <tr>\n <th scope=\"row\" style=\"width: 1%; white-space: nowrap;\">\n <strong>{name}</strong>\n </th>\n <td\n dangerouslySetInnerHTML={{ __html: value }}\n />\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </header>\n <main class=\"container\">\n {messages.map((message) => (\n <Message message={message} session={session} />\n ))}\n </main>\n <footer class=\"container\">\n <nav style=\"display: block; text-align: end;\">\n {nextLink && (\n <a rel=\"next\" href={nextLink.href}>\n Older posts &rarr;\n </a>\n )}\n </nav>\n </footer>\n </Layout>,\n {\n headers: {\n Link:\n `<${activityLink.href}>; rel=\"alternate\"; type=\"application/activity+json\", ` +\n `<${feedLink.href}>; rel=\"alternate\"; type=\"application/atom+xml\"` +\n (nextLink\n ? `, <${nextLink.href}>; rel=\"next\"; type=\"text/html\"`\n : \"\"),\n },\n },\n );\n});\n\napp.get(\"/followers\", async (c) => {\n const { bot } = c.env;\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const followersCount = await bot.repository.countFollowers();\n const followers = await Array.fromAsync(bot.repository.getFollowers());\n\n const url = new URL(c.req.url);\n const activityLink = ctx.getActorUri(bot.identifier);\n const feedLink = new URL(\"/feed.xml\", url);\n\n return c.html(\n <Layout\n bot={bot}\n host={url.host}\n activityLink={activityLink}\n feedLink={feedLink}\n >\n <header class=\"container\">\n <h1>\n <a href=\"/\">&larr;</a>{\" \"}\n {followersCount === 1\n ? `1 follower`\n : `${followersCount.toLocaleString(\"en\")} followers`}\n </h1>\n </header>\n <main class=\"container\">\n {followers.map((follower, index) => (\n <Follower\n key={follower.id?.href ?? index}\n actor={follower}\n session={session}\n />\n ))}\n </main>\n </Layout>,\n );\n});\n\napp.get(\"/tags/:hashtag\", async (c) => {\n const hashtag = c.req.param(\"hashtag\");\n const { bot } = c.env;\n const url = new URL(c.req.url);\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const offset = c.req.query(\"offset\");\n const { posts, nextPost } = await getPosts(bot, ctx, {\n hashtag,\n offset: offset == null ? undefined : Temporal.Instant.from(offset),\n });\n let nextLink: URL | undefined;\n if (nextPost?.published != null) {\n nextLink = new URL(`/tags/${encodeURIComponent(hashtag)}`, url);\n nextLink.searchParams.set(\"offset\", nextPost.published.toString());\n }\n return c.html(\n <Layout bot={bot} host={url.host} title={`#${hashtag}`}>\n <header class=\"container\">\n <h1>#{hashtag}</h1>\n </header>\n <main class=\"container\">\n {posts.map((message) => (\n <Message message={message} session={session} />\n ))}\n </main>\n <footer class=\"container\">\n <nav style=\"display: block; text-align: end;\">\n {nextLink && (\n <a rel=\"next\" href={nextLink.href}>Older posts &rarr;</a>\n )}\n </nav>\n </footer>\n </Layout>,\n {\n headers: nextLink == null ? {} : {\n Link: `<${nextLink.href}>; rel=\"next\"; type=\"text/html\"`,\n },\n },\n );\n});\n\napp.get(\"/message/:id\", async (c) => {\n const id = c.req.param(\"id\");\n const { bot } = c.env;\n const url = new URL(c.req.url);\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const post = await bot.repository.getMessage(id as Uuid);\n if (post == null || !isPublic(post)) return c.notFound();\n const message = await post.getObject(ctx);\n if (message == null || !isMessageObject(message)) return c.notFound();\n const activityLink = ctx.getObjectUri<MessageClass>(\n getMessageClass(message),\n { id },\n );\n const feedLink = new URL(\"/feed.xml\", url);\n let title = message.name;\n if (title == null) {\n title = message.summary ?? message.content;\n if (title != null) {\n title = decode(textXss.process(title.toString()));\n }\n }\n return c.html(\n <Layout\n bot={bot}\n host={url.host}\n activityLink={activityLink}\n feedLink={feedLink}\n title={title?.toString() ?? undefined}\n >\n <main class=\"container\">\n <Message message={message} session={session} />\n </main>\n </Layout>,\n {\n headers: {\n Link:\n `<${activityLink.href}>; rel=\"alternate\"; type=\"application/activity+json\", ` +\n `<${feedLink.href}>; rel=\"alternate\"; type=\"application/atom+xml\"`,\n },\n },\n );\n});\n\napp.get(\"/feed.xml\", async (c) => {\n const { bot } = c.env;\n const url = new URL(c.req.url);\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const session = bot.getSession(ctx);\n const { posts } = await getPosts(bot, ctx, { window: 30 });\n const botName = bot.name ?? bot.username;\n const canonicalUrl = new URL(\"/feed.xml\", url);\n const profileUrl = new URL(\"/\", url);\n const actorUrl = ctx.getActorUri(bot.identifier);\n c.header(\n \"Link\",\n `<${actorUrl.href}>; rel=\"alternate\"; type=\"application/activity+json\", ` +\n `<${profileUrl.href}>; rel=\"alternate\"; type=\"text/html\"`,\n );\n const response = await c.render(\n <feed xmlns=\"http://www.w3.org/2005/Atom\">\n <id>{canonicalUrl.href}</id>\n <link rel=\"self\" type=\"application/atom+xml\" href={canonicalUrl.href} />\n <link rel=\"alternate\" type=\"text/html\" href={profileUrl.href} />\n <link\n rel=\"alternate\"\n type=\"application/activity+json\"\n href={actorUrl.href}\n />\n <title>{botName} (@{bot.username}@{url.host})</title>\n <author>\n <name>{botName}</name>\n <uri>{profileUrl.href}</uri>\n </author>\n {posts.length > 0 && (\n <updated>\n {(posts[0].updated ?? posts[0].published)?.toString()}\n </updated>\n )}\n {posts.map(async (post) => {\n const activityUrl = post.id;\n if (activityUrl == null) return undefined;\n const permalink =\n (post.url instanceof Link ? post.url.href : post.url) ?? activityUrl;\n const author = post.attributionId?.href === session.actorId?.href\n ? await session.getActor()\n : await post.getAttribution({\n documentLoader: ctx.documentLoader,\n contextLoader: ctx.contextLoader,\n suppressError: true,\n });\n const authorName = author?.name ?? author?.preferredUsername ??\n (author == null ? undefined : await getActorHandle(author));\n const authorUrl =\n (author?.url instanceof Link ? author.url.href : author?.url) ??\n author?.id;\n const updated = post.updated ?? post.published;\n let title = post.name;\n if (title == null) {\n title = post.summary ?? post.content;\n if (title != null) {\n title = decode(textXss.process(title.toString()));\n }\n }\n return (\n <entry>\n <id>{permalink.href}</id>\n <link rel=\"alternate\" type=\"text/html\" href={permalink.href} />\n <link\n rel=\"alternate\"\n type=\"application/activity+json\"\n href={activityUrl.href}\n />\n {authorName &&\n (\n <author>\n <name>{authorName}</name>\n {authorUrl &&\n <uri>{authorUrl.href}</uri>}\n </author>\n )}\n {post.published && (\n <published>{post.published.toString()}</published>\n )}\n {updated && <updated>{updated.toString()}</updated>}\n {title && <title>{title}</title>}\n {post.summary && (\n <summary type=\"html\">{post.summary.toString()}</summary>\n )}\n {post.content && (\n <content type=\"html\">{post.content.toString()}</content>\n )}\n </entry>\n );\n })}\n </feed>,\n );\n response.headers.set(\"Content-Type\", \"application/atom+xml; charset=utf-8\");\n return response;\n});\n\napp.post(\"/follow\", async (c) => {\n const { bot } = c.env;\n const ctx = bot.federation.createContext(c.req.raw, c.env.contextData);\n const url = new URL(c.req.url);\n\n const formData = await c.req.formData();\n let followerHandle = formData.get(\"handle\")?.toString();\n\n try {\n if (!followerHandle) {\n return c.html(\n <Layout bot={bot} host={url.host} title=\"Error\">\n <main class=\"container\">\n <h1>Error</h1>\n <p>Follower handle is required.</p>\n <p>\n <a href=\"/\">Go back</a>\n </p>\n </main>\n </Layout>,\n 400,\n );\n }\n\n if (followerHandle.startsWith(\"@\")) {\n followerHandle = followerHandle.slice(1);\n }\n\n const webfingerData = await ctx\n .lookupWebFinger(`acct:${followerHandle}`);\n\n if (!webfingerData?.links) {\n return c.html(\n <Layout bot={bot} host={url.host} title=\"Error\">\n <main class=\"container\">\n <h1>Error</h1>\n <p>\n No links found in webfinger data for{\" \"}\n <code>@{followerHandle}</code>.\n </p>\n <p>\n <a href=\"/\">Go back</a>\n </p>\n </main>\n </Layout>,\n 400,\n );\n }\n\n const subscribeLink = webfingerData.links.find(\n (link) => link.rel === \"http://ostatus.org/schema/1.0/subscribe\",\n );\n\n if (subscribeLink?.template) {\n const botActorUri = ctx.getActorUri(bot.identifier);\n const followUrlTemplate = parseTemplate(subscribeLink.template);\n const followUrl = followUrlTemplate.expand({\n uri: botActorUri.href,\n });\n return c.redirect(followUrl);\n }\n\n return c.html(\n <Layout bot={bot} host={url.host} title=\"Error\">\n <main class=\"container\">\n <h1>Error</h1>\n <p>\n No follow link found in WebFinger data for{\" \"}\n <code>@{followerHandle}</code>.\n </p>\n <p>\n <a href=\"/\">Go back</a>\n </p>\n </main>\n </Layout>,\n 400,\n );\n } catch (_error) {\n return c.html(\n <Layout bot={bot} host={url.host} title=\"Error\">\n <main class=\"container\">\n <h1>Internal Server Error</h1>\n <p>\n An internal server error occurred while processing your request.\n </p>\n <p>\n <a href=\"/\">Go back</a>\n </p>\n </main>\n </Layout>,\n 500,\n );\n }\n});\n\ninterface GetPostsOptions {\n readonly hashtag?: string;\n readonly offset?: Temporal.Instant;\n readonly window?: number;\n}\n\nasync function getPosts(\n bot: BotImpl<unknown>,\n ctx: Context<unknown>,\n options: GetPostsOptions = {},\n): Promise<{ posts: MessageClass[]; nextPost?: Object }> {\n const { offset, window = 15 } = options;\n let posts = await Array.fromAsync(\n bot.repository.getMessages({\n order: \"newest\",\n until: offset,\n limit: window * 2,\n }),\n );\n let lastPost: Announce | Create | undefined = posts[posts.length - 1];\n posts = posts.slice(0, posts.length - 1);\n posts = posts.filter(isPublic);\n if (options.hashtag != null) {\n const taggedPosts = [];\n for (const post of posts) {\n if (await hasHashtag(ctx, post, options.hashtag)) {\n taggedPosts.push(post);\n }\n }\n posts = taggedPosts;\n }\n while (lastPost != null && posts.length < window) {\n const limit = (window - posts.length) * 2;\n const until = lastPost.published ??\n (await lastPost.getObject(ctx))?.published ??\n undefined;\n if (until == null) break;\n const nextPosts = bot.repository.getMessages({\n order: \"newest\",\n until,\n limit,\n });\n let i = 0;\n lastPost = undefined;\n for await (const post of nextPosts) {\n if (\n isPublic(post) && await hasHashtag(ctx, post, options.hashtag) &&\n posts.length < window + 1\n ) posts.push(post);\n lastPost = post;\n i++;\n }\n if (i < limit) break;\n }\n const nextPost: Object | undefined = await posts[window]?.getObject(ctx) ??\n undefined;\n posts = posts.slice(0, window);\n const messages = (await Promise.all(posts.map((p) => p.getObject(ctx))))\n .filter(isMessageObject);\n return { posts: messages, nextPost };\n}\n\nfunction isPublic(post: Create | Announce): boolean {\n return post.toIds.some((url) => url.href === PUBLIC_COLLECTION.href) ||\n post.ccIds.some((url) => url.href === PUBLIC_COLLECTION.href);\n}\n\nasync function hasHashtag(\n context: Context<unknown>,\n post: Create | Announce,\n hashtag?: string,\n): Promise<boolean> {\n if (hashtag == null) return true;\n hashtag = normalizeHashtag(hashtag);\n const object = await post.getObject(context);\n if (object == null) return false;\n for await (const tag of object.getTags(context)) {\n if (\n tag instanceof Hashtag && tag.name != null &&\n normalizeHashtag(tag.name.toString()) === hashtag\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction normalizeHashtag(hashtag: string): string {\n return hashtag\n .toLowerCase()\n .trimStart()\n .replace(/^#/, \"\")\n .trim()\n .replace(/\\s+/g, \"\");\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/botkit",
3
- "version": "0.3.1-dev.169+581b2899",
3
+ "version": "0.4.0-dev.170+765d25d4",
4
4
  "description": "A framework for creating ActivityPub bots",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": {
@@ -84,7 +84,7 @@
84
84
  "README.md"
85
85
  ],
86
86
  "dependencies": {
87
- "@fedify/fedify": "^1.8.8",
87
+ "@fedify/fedify": "1.9.0-dev.1516+8f42bff1",
88
88
  "@fedify/markdown-it-hashtag": "^0.3.0",
89
89
  "@fedify/markdown-it-mention": "^0.3.0",
90
90
  "@js-temporal/polyfill": "^0.5.1",
@@ -94,6 +94,7 @@
94
94
  "html-entities": "^2.6.0",
95
95
  "markdown-it": "^14.1.0",
96
96
  "mime-db": "^1.54.0",
97
+ "url-template": "^3.1.1",
97
98
  "uuid": "^11.1.0",
98
99
  "x-forwarded-fetch": "^0.2.0",
99
100
  "xss": "^1.0.15"