▍ humdrum codex / soft
2.0 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
54
55
56
57
58
59
60
61
62
import type { SourceModule } from "../registry";
import {
  BlueskyConfig,
  BlueskyPayload,
  type BlueskyConfig as Config,
  type BlueskyPayload as Payload,
} from "@/lib/schemas/sources/bluesky";

const XRPC = "https://bsky.social/xrpc";
const KEEP = new Set(["reply", "mention", "quote"]);

export const blueskyModule: SourceModule<Config, Payload> = {
  kind: "bluesky",
  label: "Bluesky",
  keyless: false,
  defaultRefreshSeconds: 600,
  configSchema: BlueskyConfig,
  payloadSchema: BlueskyPayload,

  isConfigured: (_c, env) => Boolean(env.BLUESKY_HANDLE && env.BLUESKY_APP_PASSWORD),

  async fetch({ config, env, signal }) {
    const identifier = env.BLUESKY_HANDLE;
    const password = env.BLUESKY_APP_PASSWORD;
    if (!identifier || !password) throw new Error("Set BLUESKY_HANDLE + BLUESKY_APP_PASSWORD");

    const sessRes = await fetch(`${XRPC}/com.atproto.server.createSession`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ identifier, password }),
      signal,
    });
    if (!sessRes.ok) throw new Error(`Bluesky auth ${sessRes.status}`);
    const { accessJwt } = (await sessRes.json()) as { accessJwt: string };

    const notifRes = await fetch(
      `${XRPC}/app.bsky.notification.listNotifications?limit=40`,
      { headers: { Authorization: `Bearer ${accessJwt}` }, signal },
    );
    if (!notifRes.ok) throw new Error(`Bluesky ${notifRes.status}`);
    const j = (await notifRes.json()) as {
      notifications: Array<{
        reason: string;
        author: { handle: string; displayName?: string };
        record?: { text?: string };
        indexedAt: string;
      }>;
    };

    const notifications = j.notifications
      .filter((n) => KEEP.has(n.reason))
      .slice(0, config.limit)
      .map((n) => ({
        reason: n.reason,
        author: n.author.displayName || `@${n.author.handle}`,
        text: (n.record?.text ?? "").slice(0, 200),
        createdAt: n.indexedAt,
      }));

    return { notifications };
  },
};