ニジカ AI 共通サービス
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.

193 lines
6.9 KiB

  1. from __future__ import annotations
  2. import asyncio
  3. import random
  4. from asyncio import Lock
  5. from datetime import date, datetime, time, timedelta
  6. from typing import TypedDict, cast
  7. import nicolib
  8. import queries_to_answers as q2a
  9. from db.models import Comment, Video, VideoHistory
  10. from nicolib import VideoInfo
  11. from nizika_ai.consts import Character, GPTModel, QueryType
  12. from nizika_ai.models import Query
  13. KIRIBAN_VIEWS_COUNTS: list[int] = sorted ({ *range (1_000, 10_000, 1_000),
  14. *range (10_000, 1_000_001, 10_000),
  15. 114_514, 1_940, 2_450, 5_100,
  16. 19_400, 24_500, 51_000, 93_194, 2_424, 242_424, 1_919,
  17. 4_545, 194_245, 245_194, 510_245 },
  18. reverse = True)
  19. kiriban_list: list[tuple[int, VideoInfo, datetime]]
  20. lock = Lock ()
  21. async def main (
  22. ) -> None:
  23. await asyncio.gather (
  24. queries_to_answers (),
  25. report_kiriban (),
  26. report_nico (),
  27. update_kiriban_list ())
  28. async def queries_to_answers (
  29. ) -> None:
  30. while True:
  31. loop = asyncio.get_running_loop ()
  32. await loop.run_in_executor (None, q2a.main)
  33. await asyncio.sleep (10)
  34. async def report_kiriban (
  35. ) -> None:
  36. while True:
  37. if not kiriban_list:
  38. await wait_until (time (15, 0))
  39. continue
  40. # キリ番祝ひ
  41. async with lock:
  42. (views_count, video_info, uploaded_at) = (
  43. kiriban_list.pop (random.randint (0, len (kiriban_list) - 1)))
  44. since_posted = datetime.now () - uploaded_at
  45. _days = since_posted.days
  46. _seconds = since_posted.seconds
  47. (_hours, _seconds) = divmod (_seconds, 3600)
  48. (_mins, _seconds) = divmod (_seconds, 60)
  49. video_code = video_info['contentId']
  50. uri = f"https://www.nicovideo.jp/watch/{ video_code }"
  51. (title, description, _) = nicolib.fetch_embed_info (uri)
  52. comments = fetch_comments (video_code)
  53. popular_comments = sorted (comments,
  54. key = lambda c: c.nico_count,
  55. reverse = True)[:10]
  56. latest_comments = sorted (comments,
  57. key = lambda c: c.posted_at,
  58. reverse = True)[:10]
  59. prompt = f"{ _days }日{ _hours }時間{ _mins }分{ _seconds }秒前にニコニコに投稿された『{ video_info['title'] }』という動画が{ views_count }再生を突破しました。\n"
  60. prompt += f"コメント数は{ len (comments) }件です。\n"
  61. if video_info['tags']:
  62. prompt += f"つけられたタグは「{ '」、「'.join (video_info['tags']) }」です。\n"
  63. if comments:
  64. prompt += f"人気のコメントは次の通りです:「{ '」、「'.join (c.content for c in popular_comments) }」\n"
  65. prompt += f"最新のコメントは次の通りです:「{ '」、「'.join (c.content for c in latest_comments) }」\n"
  66. prompt += f"""
  67. 概要には次のように書かれています:
  68. ```html
  69. { video_info['description'] }
  70. ```
  71. このことについて、何かお祝いメッセージを下さい。
  72. ただし、そのメッセージ内には再生数の数値を添えてください。
  73. また、つけられたタグ、コメントからどのような動画か想像し、説明してください。"""
  74. query = Query ()
  75. query.user_id = None
  76. query.target_character = Character.DEERJIKA.value
  77. query.content = prompt
  78. query.query_type = QueryType.KIRIBAN.value
  79. query.model = GPTModel.GPT3_TURBO.value
  80. query.sent_at = datetime.now ()
  81. query.answered = False
  82. query.transfer_data = { 'video_code': video_code }
  83. query.save ()
  84. # 待ち時間計算
  85. dt = datetime.now ()
  86. d = dt.date ()
  87. if dt.hour >= 15:
  88. d += timedelta (days = 1)
  89. remain = max (len (kiriban_list), 1)
  90. td = (datetime.combine (d, time (15, 0)) - dt) / remain
  91. # まれに時刻跨ぎでマイナスになるため
  92. if td.total_seconds () < 0:
  93. td = timedelta (seconds = 0)
  94. await asyncio.sleep (td.total_seconds ())
  95. async def update_kiriban_list (
  96. ) -> None:
  97. while True:
  98. await wait_until (time (15, 0))
  99. new_list = fetch_kiriban_list (datetime.now ().date ())
  100. if not new_list:
  101. continue
  102. async with lock:
  103. have = { k[1]['contentId'] for k in kiriban_list }
  104. for item in new_list:
  105. if item[1]['contentId'] not in have:
  106. kiriban_list.append (item)
  107. have.add (item[1]['contentId'])
  108. def fetch_kiriban_list (
  109. base_date: date,
  110. ) -> list[tuple[int, VideoInfo, datetime]]:
  111. _kiriban_list: list[tuple[int, VideoInfo, datetime]] = []
  112. latest_fetched_at = cast (date, (VideoHistory
  113. .where ('fetched_at', '<=', base_date)
  114. .max ('fetched_at')))
  115. for kiriban_views_count in KIRIBAN_VIEWS_COUNTS:
  116. targets = { vh.video.code for vh in (VideoHistory
  117. .where ('fetched_at', latest_fetched_at)
  118. .where ('views_count', '>=', kiriban_views_count)
  119. .get ()) }
  120. for code in targets:
  121. if code in [kiriban[1]['contentId'] for kiriban in _kiriban_list]:
  122. continue
  123. previous_views_count: int | None = (
  124. VideoHistory
  125. .where_has ('video', lambda q: q.where ('code', code))
  126. .where ('fetched_at', '<', latest_fetched_at)
  127. .max ('views_count'))
  128. if previous_views_count is None:
  129. previous_views_count = 0
  130. if previous_views_count >= kiriban_views_count:
  131. continue
  132. video_info = nicolib.fetch_video_info (code)
  133. if video_info is not None:
  134. _kiriban_list.append ((kiriban_views_count, video_info,
  135. cast (Video, Video.where ('code', code).first ()).uploaded_at))
  136. return _kiriban_list
  137. def fetch_comments (
  138. video_code: str,
  139. ) -> list[Comment]:
  140. video = Video.where ('code', video_code).first ()
  141. if video is None:
  142. return []
  143. return video.comments
  144. async def report_nico (
  145. ) -> None:
  146. ...
  147. async def wait_until (
  148. t: time,
  149. ):
  150. dt = datetime.now ()
  151. d = dt.date ()
  152. if dt.time () >= t:
  153. d += timedelta (days = 1)
  154. await asyncio.sleep ((datetime.combine (d, t) - dt).total_seconds ())
  155. kiriban_list = (
  156. fetch_kiriban_list ((d := datetime.now ()).date ()
  157. - timedelta (days = d.hour < 15)))
  158. if __name__ == '__main__':
  159. asyncio.run (main ())