pythonでBlueSkyのAPIを使ってリプに反応する処理を作ってみました
実際に見てみよう
実際にはこのように動きます。いい感じですね
https://bsky.app/profile/quizwiki.bsky.social
作る
今回はフォローしているかつ、メンション付きの投稿を検出して、それに対してクイズをリプライする機能を作りました。
ざっくりの流れ
- 通知欄を取得
- メンション付きの投稿を対象に処理
- フォローしているかどうか確認
- 「ポケモンシルエットクイズ」が投稿内容に含まれているか確認
- リプ
- 通知を既読
参考
公式のサンプル(ほとんどこのまま使える。フォローしているかどうかは自前)
https://github.com/MarshalX/atproto/blob/main/examples/process_notifications.py
かなり久々にドキュメントを見ながら作った
https://atproto.blue/en/latest/readme.html
実装
準備
プログラムの中でログインが必要です。
他のAPIなどではAPIキーなどを発行したりしますが、今回はもっと簡単です。
設定>アプリパスワード>アプリパスワードを追加
適当な名前を付ける
アプリパスワードをコピー この後使います!!!
ここまで出来たら準備完了です
コーディング
まずはサンプルコードをコピーしてきてさっき作ったアプリパスワードとhandle(xxxx.bsky.social)を入力しましょう!
from time import sleep
from atproto import Client
# how often we should check for new notifications
FETCH_NOTIFICATIONS_DELAY_SEC = 3
def main() -> None:
client = Client()
client.login('my-handle', 'my-password') # ここにさっき作ったパスワードとhandle(xxxx.bsky.social)
# fetch new notifications
while True:
# save the time in UTC when we fetch notifications
last_seen_at = client.get_current_time_iso()
response = client.app.bsky.notification.list_notifications()
for notification in response.notifications:
if not notification.is_read:
print(f'Got new notification! Type: {notification.reason}; from: {notification.author.did}')
# example: "Got new notification! Type: like; from: did:plc:hlorqa2iqfooopmyzvb4byaz"
# mark notifications as processed (isRead=True)
client.app.bsky.notification.update_seen({'seen_at': last_seen_at})
print('Successfully process notification. Last seen at:', last_seen_at)
sleep(FETCH_NOTIFICATIONS_DELAY_SEC)
if __name__ == '__main__':
main()
簡単に解説します
client.app.bsky.notification.list_notifications()
通知のリスト(ベルのマークを押したときに出てくるあれ)を取得します
if not notification.is_read:
通知を読んだかどうかを表しています。同じ通知に対して何度も反応しないようにするためのものです
client.app.bsky.notification.update_seen({'seen_at': last_seen_at})
引数に指定したseen_at(2024-02-12T00:19:08.912187+00:00)までの通知をすべて既読にします
この時刻ですがUTCにしてください。内部的にはこのタイムゾーンで管理してるかもです。(日本時間にするとうまく動かない)
for notification in response.notifications:
取得した情報を見ていきます。
author.viewer.followed_byがNoneではない場合この人は私のことをフォローしています。
uri, cid を使うとこの投稿が特定できていいねをしたりできます。
display_nameはユーザー名(設定していなければ''です)
record.textは本文です。この中に「ポケモンシルエットクイズ」という文言がある場合にリプを返します。
notification = {
"author": ProfileView(
did='did:plc:prn7imjv3flekrh4k4z5mpof',
handle='cpord.bsky.social',
avatar=None,
description=None,
display_name='',
indexed_at='2024-02-12T05:39:57.466Z',
labels=[],
viewer=ViewerState(
blocked_by=False,
blocking=None,
blocking_by_list=None,
followed_by='at://did:plc:prn7imjv3flekrh4k4z5mpof/app.bsky.graph.follow/3kl7psks5ig2x',
following=None,
muted=False,
muted_by_list=None,
py_type='app.bsky.actor.defs#viewerState'
),
py_type='app.bsky.actor.defs#profileView'
),
"cid": 'bafyreicxyuwd5m7pbxd3kin3xddll4dkti4h3q4tyjqxj2lm3tlhq3ekgi',
"indexed_at": '2024-02-13T23:47:53.344Z',
"is_read": False,
"reason": 'mention',
"record": Record(
created_at='2024-02-13T23:47:53.344Z',
text='@quizwiki.bsky.social',
embed=None,
entities=None,
facets=[
Main(
features=[
Mention(
did='did:plc:owqmnbrxfvl4ktlzkswjlxfr',
py_type='app.bsky.richtext.facet#mention'
)
],
index=ByteSlice(
byte_end=21,
byte_start=0,
py_type='app.bsky.richtext.facet#byteSlice'
),
py_type='app.bsky.richtext.facet'
)
],
labels=None,
langs=['ja'],
reply=None,
tags=None,
py_type='app.bsky.feed.post'
),
"uri": 'at://did:plc:prn7imjv3flekrh4k4z5mpof/app.bsky.feed.post/3kldk4xicw22p',
"labels": [],
"reason_subject": None,
"py_type": 'app.bsky.notification.listNotifications#notification'
}
変更点
リプを返すための情報とフォローしているかどうかの判定を行います
for notification in response.notifications:
if not notification.is_read:
if notification.reason == "mention":
is_follower = notification.author.viewer.followed_by is not None
user_name = notification.author.display_name if notification.author.display_name else notification.author.handle
root_ref = models.create_strong_ref(notification)
if not is_follower:
print("[INFO]" + user_name + "にフォローするように依頼しました")
if notification.reason == "mention":
これによって通知がメンションであることを確認します。
‘like’, ‘repost’, ‘follow’, ‘mention’, ‘reply’, ‘quote’ があります
is_follower = notification.author.viewer.followed_by is not None
メンションしてきた人がフォローしているか確認します
user_name = notification.author.display_name if notification.author.display_name else notification.author.handle
名前を取得します(ログ用)display_nameが設定されていない場合はhandle(xxxx.bluesky.social)を取得
root_ref = models.create_strong_ref(notification)
結構大事。
これを使ってリプライをします。これを指定してあげないとリプになりません。
client.send_post(
text="問題を受け取るにはフォローをお願いします!",
reply_to=models.AppBskyFeedPost.ReplyRef(parent=root_ref, root=root_ref),
)
いったんおしまい
こんなところでしょうかサンプルコードとドキュメントを見ながら開発するのは
新しいものをやっている感じがして楽しいですね。
Twitterのような魔境とは違う環境。かなりいいね。
反響があればもっと詳しく書きますのでコメントとかください!
リンクカードの投稿方法とかもまた教えますね