ニジカのスカトロ,ニジカトロ. https://bsky.app/profile/deerjika-bot.bsky.social
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
5.6 KiB

  1. from __future__ import annotations
  2. import asyncio
  3. import os
  4. import time
  5. from datetime import datetime
  6. from typing import TypedDict
  7. import atproto
  8. from atproto import Client
  9. from atproto_client.models.app.bsky.feed.get_timeline import Response
  10. from atproto_client.models.com.atproto.repo.strong_ref import Main
  11. import account
  12. from nizika_ai.consts import Character, GPTModel, Platform, QueryType
  13. from nizika_ai.models import Answer, AnsweredFlag, Query, User
  14. TARGET_WORDS = ['deerjika', 'ニジカ', 'ぼっち', '虹夏', '郁代', 'バーカ',
  15. 'kfif', 'kita-flatten-ikuyo-flatten', 'ラマ田', 'ゴートう',
  16. 'ぼざクリ', 'オオミソカ', '伊地知', '喜多ちゃん',
  17. '喜タイ', '洗澡鹿', 'シーザオ', '今日は本当に',
  18. 'ダイソーで', '変なチンチン', 'daisoで', 'だね~(笑)',
  19. 'おやつタイム', 'わさしが', 'わさび県', 'たぬマ', 'にくまる',
  20. 'ルイズマリー', '餅', 'ニジゴ', 'ゴニジ', 'ニジニジ',
  21. '新年だよね', 'うんこじゃん', 'ほくほくのジャガイモ']
  22. time.sleep (60)
  23. client = Client (base_url = 'https://bsky.social')
  24. client.login (account.USER_ID, account.PASSWORD)
  25. async def main (
  26. ) -> None:
  27. """
  28. メーン処理
  29. """
  30. await asyncio.gather (like_posts (),
  31. check_mentions ())
  32. async def like_posts (
  33. ) -> None:
  34. while True:
  35. try:
  36. for post in fetch_target_posts ():
  37. client.like (**post)
  38. except Exception as e:
  39. print (f"[like_posts] { type (e).__name__ }: { e }")
  40. await asyncio.sleep (60)
  41. async def check_mentions (
  42. ) -> None:
  43. while True:
  44. try:
  45. for uri in check_notifications ():
  46. records = fetch_thread_contents (uri, 20)
  47. if records:
  48. record = records[0]
  49. content = record['text']
  50. image_url: str | None = None
  51. if record['embed'] and hasattr (record['embed'], 'images'):
  52. image_url = ('https://cdn.bsky.app/img/feed_fullsize/plain'
  53. f"/{ record['did'] }"
  54. f"/{ record['embed'].images[0].image.ref.link }")
  55. user = _fetch_user (record['did'], record['name'])
  56. _add_query (user, record['text'], image_url)
  57. except Exception as e:
  58. print (f"[check_mentions] { type (e).__name__ }: { e }")
  59. await asyncio.sleep (60)
  60. def check_notifications (
  61. ) -> list[str]:
  62. uris: list[str] = []
  63. last_seen_at = client.get_current_time_iso ()
  64. notifications = client.app.bsky.notification.list_notifications()
  65. for notification in notifications.notifications:
  66. if not notification.is_read:
  67. match notification.reason:
  68. case 'mention' | 'reply' | 'quote':
  69. uris.append (notification.uri)
  70. case 'follow':
  71. client.follow (notification.author.did)
  72. client.app.bsky.notification.update_seen ({ 'seen_at': last_seen_at })
  73. return uris
  74. def fetch_thread_contents (
  75. uri: str,
  76. parent_height: int,
  77. ) -> list[Record]:
  78. post_thread = client.get_post_thread (uri = uri, parent_height = parent_height)
  79. if not post_thread:
  80. return []
  81. res = post_thread.thread
  82. records: list[Record] = []
  83. while res:
  84. records.append ({ 'strong_ref': atproto.models.create_strong_ref (res.post),
  85. 'did': res.post.author.did,
  86. 'handle': res.post.author.handle,
  87. 'name': res.post.author.display_name,
  88. 'datetime': res.post.record.created_at,
  89. 'text': res.post.record.text,
  90. 'embed': res.post.record.embed })
  91. res = res.parent
  92. return records
  93. def fetch_target_posts (
  94. ) -> list[LikeParams]:
  95. posts: list[LikeParams] = []
  96. timeline: Response = client.get_timeline ()
  97. for feed in timeline.feed:
  98. if (feed.post.author.did != client.me.did
  99. and (feed.post.viewer.like is None)
  100. and any (target_word in (feed.post.record.text or '').casefold ()
  101. for target_word in TARGET_WORDS)):
  102. posts.append (LikeParams ({ 'uri': feed.post.uri, 'cid': feed.post.cid }))
  103. return posts
  104. def _add_query (
  105. user: User,
  106. content: str,
  107. image_url: str | None = None,
  108. ) -> None:
  109. query = Query ()
  110. query.user_id = user.id
  111. query.target_character = Character.DEERJIKA.value
  112. query.content = content
  113. query.image_url = image_url or None
  114. query.query_type = QueryType.BLUESKY_COMMENT.value
  115. query.model = GPTModel.GPT3_TURBO.value
  116. query.sent_at = datetime.now ()
  117. query.answered = False
  118. query.save ()
  119. # TODO: 履歴情報の追加
  120. def _fetch_user (
  121. did: str,
  122. name: str,
  123. ) -> User:
  124. user = User.where ('platform', Platform.BLUESKY.value).where ('code', did).first ()
  125. if user is None:
  126. user = User ()
  127. user.platform = Platform.BLUESKY.value
  128. user.code = did
  129. user.name = name
  130. user.save ()
  131. return user
  132. class LikeParams (TypedDict):
  133. uri: str
  134. cid: str
  135. class Record (TypedDict):
  136. strong_ref: Main
  137. did: str
  138. handle: str
  139. name: str
  140. datetime: str
  141. text: str
  142. embed: object
  143. if __name__ == '__main__':
  144. asyncio.run (main ())