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

111 lines
4.3 KiB

  1. from datetime import datetime, timedelta
  2. import time
  3. import sys
  4. from atproto import Client, models
  5. from ai.talk import Talk
  6. import account
  7. def check_notifications (
  8. client: Client,
  9. ) -> list:
  10. (uris, last_seen_at) = ([], client.get_current_time_iso ())
  11. for notification in (client.app.bsky.notification.list_notifications ()
  12. .notifications):
  13. if not notification.is_read:
  14. if notification.reason in ['mention', 'reply']:
  15. uris += [notification.uri]
  16. elif notification.reason == 'follow':
  17. client.follow (notification.author.did)
  18. client.app.bsky.notification.update_seen ({ 'seen_at': last_seen_at })
  19. return uris
  20. def get_thread_contents (
  21. client: Client,
  22. uri: str,
  23. parent_height: int,
  24. ) -> list:
  25. response = (client.get_post_thread (uri = uri,
  26. parent_height = parent_height)
  27. .thread)
  28. records = []
  29. while response is not None:
  30. records += [{ 'strong_ref': models.create_strong_ref (response.post),
  31. 'did': response.post.author.did,
  32. 'handle': response.post.author.handle,
  33. 'name': response.post.author.display_name,
  34. 'datetime': response.post.record.created_at,
  35. 'text': response.post.record.text,
  36. 'embed': response.post.record.embed }]
  37. response = response.parent
  38. return records
  39. def main () -> None:
  40. client = Client (base_url = 'https://bsky.social')
  41. client.login (account.USER_ID, account.PASSWORD)
  42. last_posted_at = datetime.now () - timedelta (hours = 6)
  43. has_got_snack_time = False
  44. while True:
  45. now = datetime.now ()
  46. for uri in check_notifications (client):
  47. records = get_thread_contents (client, uri, 20)
  48. if len (records) > 0:
  49. answer = Talk.main ((records[0]['text']
  50. if (records[0]['embed'] is None
  51. or not hasattr (records[0]['embed'],
  52. 'images'))
  53. else [
  54. { 'type': 'text', 'text': records[0]['text'] },
  55. { 'type': 'image_url', 'image_url': {
  56. 'url': f"https://cdn.bsky.app/img/feed_fullsize/plain/{ records[0]['did'] }/{ records[0]['embed'].images[0].image.ref.link }" } }]),
  57. records[0]['name'],
  58. [*map (lambda record: {
  59. 'role': ('assistant'
  60. if (record['handle']
  61. == account.USER_ID)
  62. else 'user'),
  63. 'content':
  64. record['text']},
  65. reversed (records[1:]))])
  66. client.send_post (answer,
  67. reply_to = models.AppBskyFeedPost.ReplyRef (
  68. parent = records[0]['strong_ref'],
  69. root = records[-1]['strong_ref']))
  70. if now.hour == 14 and has_got_snack_time:
  71. has_got_snack_time = False
  72. if now.hour == 15 and not has_got_snack_time:
  73. with open ('./assets/snack-time.jpg', 'rb') as f:
  74. image = models.AppBskyEmbedImages.Image (
  75. alt = 'おやつタイムだ!!!!',
  76. image = client.com.atproto.repo.upload_blob (f).blob)
  77. client.send_post (Talk.main ('おやつタイムだ!!!!'),
  78. embed = models.app.bsky.embed.images.Main (
  79. images = [image]))
  80. last_posted_at = now
  81. has_got_snack_time = True
  82. if now - last_posted_at >= timedelta (hours = 6):
  83. client.send_post (Talk.main ('今どうしてる?'))
  84. last_posted_at = now
  85. time.sleep (60)
  86. if __name__ == '__main__':
  87. main (*sys.argv[1:])