| 
				
				
				
				 | 
			
			 | 
			@@ -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 ()) |