From 47dc88153790f3d1820b0cdf8d02bbef7466e5d1 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 17 Aug 2025 05:37:36 +0900 Subject: [PATCH] #11 --- .gitmodules | 6 +- ai | 1 - nizika_ai | 1 + test.py | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 4 deletions(-) delete mode 160000 ai create mode 160000 nizika_ai create mode 100644 test.py diff --git a/.gitmodules b/.gitmodules index bd56793..83bfcb8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "nizika_nico"] path = nizika_nico url = https://git.miteruzo.com/miteruzo/nizika_nico -[submodule "ai"] - path = ai - url = https://git.miteruzo.com/miteruzo/nizika_broadcast +[submodule "nizika_ai"] + path = nizika_ai + url = https://git.miteruzo.com/miteruzo/nizika_ai.git diff --git a/ai b/ai deleted file mode 160000 index 299a3ac..0000000 --- a/ai +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 299a3acdff6312f4ea7188f3606bf66e751669c1 diff --git a/nizika_ai b/nizika_ai new file mode 160000 index 0000000..4e5bd13 --- /dev/null +++ b/nizika_ai @@ -0,0 +1 @@ +Subproject commit 4e5bd13ab45ec024bda6746db4959d32fced56da diff --git a/test.py b/test.py new file mode 100644 index 0000000..bb678d5 --- /dev/null +++ b/test.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +import asyncio +import time +from typing import TypedDict + +import atproto +from atproto import Client +from atproto_client.models.app.bsky.feed.get_timeline import Response +from atproto_client.models.com.atproto.repo.strong_ref import Main + +import account +from nizika_ai.consts import QueryType +from nizika_ai.models import Answer, AnsweredFlag, Query, User + +TARGET_WORDS = ['deerjika', 'ニジカ', 'ぼっち', '虹夏', '郁代', 'バーカ', + 'kfif', 'kita-flatten-ikuyo-flatten', 'ラマ田', 'ゴートう', + 'ぼざクリ', 'オオミソカ', '伊地知', '喜多ちゃん', + '喜タイ', '洗澡鹿', 'シーザオ', '今日は本当に', + 'ダイソーで', '変なチンチン', 'daisoで', 'だね~(笑)', + 'おやつタイム', 'わさしが', 'わさび県', 'たぬマ', 'にくまる', + 'ルイズマリー', '餅', 'ニジゴ', 'ゴニジ', 'ニジニジ', + '新年だよね', 'うんこじゃん', 'ほくほくのジャガイモ'] + +time.sleep (60) + +client = Client (base_url = 'https://bsky.social') +client.login (account.USER_ID, account.PASSWORD) + + +async def main ( +) -> None: + """ + メーン処理 + """ + + await asyncio.gather (like_posts (), + reply ()) + + +async def like_posts ( +) -> None: + while True: + for post in fetch_target_posts (): + client.like (**post) + + await asyncio.sleep (60) + + +async def check_mentions ( +) -> None: + while True: + for uri in check_notifications (): + records = fetch_thread_contents (uri, 20) + if records: + record = records[0] + content = record['text'] + image_url: str | None = None + if record['embed'] and hasattr (record['embed'], 'images'): + image_url = ('https://cdn.bsky.app/img/feed_fullsize/plain' + f"/{ record['did'] }" + f"/{ record['embed'].images[0].image.ref.link }") + user = _fetch_user (record['did'], record['name']) + _add_query (user, record['text'], image_url) + + await asyncio.sleep (60) + + +def check_notifications ( +) -> list[str]: + uris: list[str] = [] + last_seen_at = client.get_current_time_iso () + + notifications = client.app.bsky.notification.list_notifications() + for notification in notifications.notifications: + if not notification.is_read: + match notification.reason: + case 'mention' | 'reply' | 'quote': + uris.append (notification.uri) + case 'follow': + client.follow (notification.author.did) + + client.app.bsky.notification.update_seen ({ 'seen_at': last_seen_at }) + + return uris + + +def fetch_thread_contents ( + uri: str, + parent_height: int, +) -> list[Record]: + post_thread = client.get_post_thread (uri = uri, parent_height = parent_height) + if not post_thread: + return [] + + res = post_thread.thread + + records: list[Record] = [] + while res: + records.append ({ 'strong_ref': atproto.models.create_strong_ref (res.post), + 'did': res.post.author.did, + 'handle': response.post.author.handle, + 'name': response.post.author.display_name, + 'datetime': response.post.record.created_at, + 'text': response.post.record.text, + 'embed': response.post.record.embed }) + res = res.parent + + return records + + +def fetch_target_posts ( +) -> list[LikeParams]: + posts: list[LikeParams] = [] + + timeline: Response = client.get_timeline () + for feed in timeline.feed: + if (feed.post.author.did != client.me.did + and (feed.post.viewer.like is None) + and any (target_word in feed.post.record.text.lower () + for target_word in TARGET_WORDS)): + posts.append (LikeParams ({ 'uri': feed.post.uri, 'cid': feed.post.cid })) + + return posts + + +def _add_query ( + user: User, + content: str, + image_url: str | None = None, +) -> None: + query = Query () + query.user_id = user.id + query.target_character = Character.DEERJIKA.value + query.content = content + query.image_url = image_url or None + query.query_type = QueryType.BLUESKY_COMMENT.value + query.model = GPTModel.GPT3_TURBO.value + query.sent_at = datetime.now () + query.answered = False + query.save () + # TODO: 履歴情報の追加 + + +def _fetch_user ( + did: str, + name: str, +) -> User: + user = User.where ('platform', Platform.BLUESKY).where ('code', did).first () + if user is None: + user = User () + user.platform = Platfrom.BLUESKY + user.code = did + user.name = name + user.save () + return user + + +class LikeParams (TypedDict): + uri: str + cid: str + + +class Record (TypedDict): + strong_ref: Main + did: str + handle: str + name: str + datetime: str + text: str + embed: object + + +if __name__ == '__main__': + asyncio.run (main ())