ニジカのスカトロ,ニジカトロ. 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.

176 lines
5.2 KiB

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