Closed9

ongoing

kawarimidollkawarimidoll

blueskyでリプライチャットボットを作る

想定する流れ

  • 通知を取得する
  • 通知からreply / mentionを抽出する
  • 抽出したものに返答する
  • 通知を既読にする
  • 上記を一定期間で繰り返す
kawarimidollkawarimidoll

ログインのおまじない

import AtprotoAPI from "npm:@atproto/api";
const { BskyAgent } = AtprotoAPI;

import "https://deno.land/std@0.183.0/dotenv/load.ts";

const service = "https://bsky.social";
const agent = new BskyAgent({ service });

const identifier = Deno.env.get("BLUESKY_IDENTIFIER");
const password = Deno.env.get("BLUESKY_PASSWORD");
if (!identifier || !password) {
  throw "BLUESKY_IDENTIFIER and BLUESKY_PASSWORD must be set";
}
await agent.login({ identifier, password });
kawarimidollkawarimidoll

メインはlistNotificationsを使う
ただし最大でも100件なので、メンションが集中すると取りこぼしが発生する可能性がある
ということでいったんgetUnreadCountを取得する方法が良いのではないか

と思ったけど最初は100件以上の通知がたまることは考えなくてよいか

error: Uncaught Error: The seenAt parameter is unsupportedが出るのが解せん…

const seenAt = new Date().toISOString();

// 未読件数取得
const { data: { count } } = await agent.app.bsky.notification.getUnreadCount({
  // seenAt,
});
const limit = Math.min(count, 100);

if (limit < 1) {
  console.log("no notification");
  Deno.exit(0);
}

// 未読通知取得
const { data: { notifications, cursor } } = await agent.app.bsky.notification
  .listNotifications({
    limit,
    // seenAt,
  });
const targets = notifications.filter((item) =>
  !item.isRead && ["mention", "reply"].includes(item.reason)
);
console.log({ targets, seenAt, limit, cursor });

// 既読更新
await agent.app.bsky.notification.updateSeen({ seenAt });

これでtargetsが取得できる
limitが100を超えるような場合はcursorを起点にして再取得すればよさそうだけどいまは考えない

kawarimidollkawarimidoll

単一のpost取得はこちら

const url = "https://bsky.app/profile/handle_or_did/post/id...";
const repo = url.match(/profile\/([^\/]+)/)?.at(1);
const rkey = url.match(/post\/(\w+)/)?.at(1);

if (repo && rkey) {
  console.log("invalid url");
  Deno.exit(0);
}

// type 1
const p1 = await agent.getPost({ repo, rkey });
console.log(p1);

// type 2
const p2 = await agent.com.atproto.repo.getRecord({
  repo,
  rkey,
  collection: "app.bsky.feed.post",
});
console.log(p2.data);
kawarimidollkawarimidoll

こんな感じのイメージ

for await (const target of targets) {
  const { uri, cid, reason, author: { did, handle, displayName }, record: { text } } = target;
  const response = await callAI(text);
  await reply({ did, text: response.text });
}

まあawaitではなく並列でやりたい

kawarimidollkawarimidoll

https://bsky.app/profile/did:plc:okalufxun5rpqzdrwf5bpu3d/post/3jwerw6p6wp2f

notificationのreasonは"like", "repost", "follow", "mention", "reply", "quote"が用意されているけど、"mention", "reply", "quote"は重複しうる
具体的には、「自分の投稿がquoteされて」「自分の投稿に返信されて」「本文に自分への@メンションがついてる」 という複合状態は発生する
実験してみたところ、どうやら力関係はmention > quote > replyで、上記の3つが重なるとreasonは"mention"になり、mention & replyの場合は"mention"に、quote & replyの場合は"quote"になった

ただし通知欄には全て単にメンションとして表示される
reasonよりもrecord.replyが存在するかどうかが重要視されているみたい

[{
  uri: "at://did:plc:6e6u4bo4vuye5h4pvh3i3z3t/app.bsky.feed.post/3jweqwxdjd52v",
  cid: "bafyreichubodi5xkd2o5nbxykrlu74pb4fcwenazip4j5dgv7ptkqgg6xq",
  author: {
    did: "did:plc:6e6u4bo4vuye5h4pvh3i3z3t",
    handle: "zgz.bsky.social",
    viewer: { muted: false, blockedBy: false },
    labels: [],
  },
  reason: "quote",
  reasonSubject:
    "at://did:plc:okalufxun5rpqzdrwf5bpu3d/app.bsky.feed.post/3jwdpizjiss2y",
  record: {
    text:
      "test https://bsky.app/profile/kawarimidoll.bsky.social/post/3jwdpizjiss2y",
    "$type": "app.bsky.feed.post",
    embed: { "$type": "app.bsky.embed.record", record: [Object] },
    reply: { root: [Object], parent: [Object] },
    facets: [[Object]],
    createdAt: "2023-05-23T05:33:23.165Z",
  },
  isRead: false,
  indexedAt: "2023-05-23T05:33:23.709Z",
  labels: [],
}, {
  uri: "at://did:plc:6e6u4bo4vuye5h4pvh3i3z3t/app.bsky.feed.post/3jweqwpewtp2f",
  cid: "bafyreibc3s5ttxt4vdfutyhrwj3mmoxpszn76y4wmvyynbfgaddyppjjoy",
  author: {
    did: "did:plc:6e6u4bo4vuye5h4pvh3i3z3t",
    handle: "zgz.bsky.social",
    viewer: { muted: false, blockedBy: false },
    labels: [],
  },
  reason: "mention",
  record: {
    text:
      "@kawarimidoll.bsky.social test https://bsky.app/profile/kawarimidoll.bsky.social/post/3jwdpizjiss2y",
    "$type": "app.bsky.feed.post",
    embed: { "$type": "app.bsky.embed.record", record: [Object] },
    reply: { root: [Object], parent: [Object] },
    facets: [[Object], [Object]],
    createdAt: "2023-05-23T05:33:14.804Z",
  },
  isRead: false,
  indexedAt: "2023-05-23T05:33:15.406Z",
  labels: [],
}]

あと@メンションは投稿のどの部分に入ってもメンションになるみたい

kawarimidollkawarimidoll

xrpcにAPIがあるからnpmモジュールを使わなくてもできる可能性はあるな
https://bsky.social/xrpc/app.bsky.notification.listNotifications?limit=3

このスクラップは3ヶ月前にクローズされました