ongoing
blueskyでリプライチャットボットを作る
想定する流れ
- 通知を取得する
- 通知からreply / mentionを抽出する
- 抽出したものに返答する
- 通知を既読にする
- 上記を一定期間で繰り返す
既存のbotで公開されているものがほとんど見つからないがvvvotのreadmeにある概念図が上記のものと概ね一致しているのでこの想定でよさそう
ログインのおまじない
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 });
メインは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を起点にして再取得すればよさそうだけどいまは考えない
単一の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);
こんな感じのイメージ
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ではなく並列でやりたい
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: [],
}]
あと@メンションは投稿のどの部分に入ってもメンションになるみたい
xrpcにAPIがあるからnpmモジュールを使わなくてもできる可能性はあるな
https://bsky.social/xrpc/app.bsky.notification.listNotifications?limit=3
実行環境どうしよう