▍ humdrum codex / soft
1.6 KB raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import type { SourceModule } from "../registry";
import {
  MastodonConfig,
  MastodonPayload,
  type MastodonConfig as Config,
  type MastodonPayload as Payload,
} from "@/lib/schemas/sources/mastodon";

const stripHtml = (html: string) =>
  html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();

export const mastodonModule: SourceModule<Config, Payload> = {
  kind: "mastodon",
  label: "Mastodon",
  keyless: false,
  defaultRefreshSeconds: 600,
  configSchema: MastodonConfig,
  payloadSchema: MastodonPayload,

  isConfigured: (_c, env) => Boolean(env.MASTODON_INSTANCE && env.MASTODON_TOKEN),

  async fetch({ config, env, signal }) {
    const instance = env.MASTODON_INSTANCE?.replace(/\/$/, "");
    const token = env.MASTODON_TOKEN;
    if (!instance || !token) throw new Error("Set MASTODON_INSTANCE + MASTODON_TOKEN");

    const url = new URL(`${instance}/api/v1/notifications`);
    url.searchParams.set("limit", String(config.limit));
    for (const t of ["mention", "reblog", "favourite"]) url.searchParams.append("types[]", t);

    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
      signal,
    });
    if (!res.ok) throw new Error(`Mastodon ${res.status}`);
    const j = (await res.json()) as Array<{
      type: string;
      created_at: string;
      account: { acct: string };
      status?: { content: string; url: string };
    }>;

    return {
      notifications: j.map((n) => ({
        type: n.type,
        account: `@${n.account.acct}`,
        text: n.status ? stripHtml(n.status.content).slice(0, 200) : "",
        url: n.status?.url ?? null,
        createdAt: n.created_at,
      })),
    };
  },
};