ニジカ 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.

186 lines
5.2 KiB

  1. from __future__ import annotations
  2. import asyncio
  3. from datetime import date, datetime, time, timedelta
  4. from typing import TypedDict, cast
  5. import requests
  6. from bs4 import BeautifulSoup
  7. from requests.exceptions import Timeout
  8. import queries_to_answers as q2a
  9. from db.models import Video, VideoHistory
  10. KIRIBAN_VIEWS_COUNTS: list[int] = sorted ({ *range (1_000, 10_000, 1_000),
  11. *range (10_000, 1_000_001, 10_000),
  12. 114_514, 1_940, 2_450, 5_100,
  13. 19_400, 24_500, 51_000, 93_194, 2_424, 242_424, 1_919,
  14. 4_545, 194_245, 245_194, 510_245 },
  15. reverse = True)
  16. kiriban_list: list[tuple[int, VideoInfo, datetime]]
  17. async def main (
  18. ) -> None:
  19. await asyncio.gather (
  20. queries_to_answers (),
  21. report_kiriban (),
  22. report_nico (),
  23. update_kiriban_list ())
  24. async def queries_to_answers (
  25. ) -> None:
  26. while True:
  27. q2a.main ()
  28. await asyncio.sleep (10)
  29. async def report_kiriban (
  30. ) -> None:
  31. while True:
  32. # キリ番祝ひ
  33. ...
  34. # 待ち時間計算
  35. dt = datetime.now ()
  36. d = dt.date ()
  37. if dt.hour >= 15:
  38. d += timedelta (days = 1)
  39. td = datetime.combine (d, time (15, 0)) - dt
  40. if kiriban_list:
  41. td /= len (kiriban_list)
  42. await asyncio.sleep (td.total_seconds ())
  43. async def update_kiriban_list (
  44. ) -> None:
  45. while True:
  46. await wait_until (time (15, 0))
  47. kiriban_list += fetch_kiriban_list (datetime.now ().date ())
  48. def fetch_kiriban_list (
  49. base_date: date,
  50. ) -> list[tuple[int, VideoInfo, datetime]]:
  51. _kiriban_list: list[tuple[int, VideoInfo, datetime]] = []
  52. latest_fetched_at = cast (date, (VideoHistory
  53. .where ('fetched_at', '<=', base_date)
  54. .max ('fetched_at')))
  55. for kiriban_views_count in KIRIBAN_VIEWS_COUNTS:
  56. targets = { vh.video.code for vh in (VideoHistory
  57. .where ('fetched_at', latest_fetched_at)
  58. .where ('views_count', '>=', kiriban_views_count)
  59. .get ()) }
  60. for code in targets:
  61. if code in [kiriban[1]['contentId'] for kiriban in _kiriban_list]:
  62. continue
  63. previous_views_count: int | None = (
  64. VideoHistory
  65. .where_has ('video', lambda q: q.where ('code', code))
  66. .where ('fetched_at', '<', latest_fetched_at)
  67. .max ('views_count'))
  68. if previous_views_count is None:
  69. previous_views_count = 0
  70. if previous_views_count >= kiriban_views_count:
  71. continue
  72. video_info = fetch_video_info (code)
  73. if video_info is not None:
  74. _kiriban_list.append ((kiriban_views_count, video_info,
  75. cast (Video, Video.where ('code', code).first ()).uploaded_at))
  76. return _kiriban_list
  77. def fetch_video_info (
  78. video_code: str,
  79. ) -> VideoInfo | None:
  80. video_info: dict[str, str | list[str]] = { 'contentId': video_code }
  81. bs = create_bs_from_url (f"https://www.nicovideo.jp/watch/{ video_code }")
  82. if bs is None:
  83. return None
  84. try:
  85. title = bs.find ('title')
  86. if title is None:
  87. return None
  88. video_info['title'] = '-'.join (title.text.split ('-')[:(-1)])[:(-1)]
  89. tags: str = bs.find ('meta', attrs = { 'name': 'keywords' }).get ('content') # type: ignore
  90. video_info['tags'] = tags.split (',')
  91. video_info['description'] = bs.find ('meta', attrs = { 'name': 'description' }).get ('content') # type: ignore
  92. except Exception:
  93. return None
  94. return cast (VideoInfo, video_info)
  95. def create_bs_from_url (
  96. url: str,
  97. params: dict | None = None,
  98. ) -> BeautifulSoup | None:
  99. """
  100. URL から BeautifulSoup インスタンス生成
  101. Parameters
  102. ----------
  103. url: str
  104. 捜査する URL
  105. params: dict
  106. パラメータ
  107. Return
  108. ------
  109. BeautifulSoup | None
  110. BeautifulSoup オブゼクト(失敗したら None)
  111. """
  112. if params is None:
  113. params = { }
  114. try:
  115. req = requests.get (url, params = params, timeout = 60)
  116. except Timeout:
  117. return None
  118. if req.status_code != 200:
  119. return None
  120. req.encoding = req.apparent_encoding
  121. return BeautifulSoup (req.text, 'hecoml.parser')
  122. async def report_nico (
  123. ) -> None:
  124. ...
  125. async def wait_until (
  126. t: time,
  127. ):
  128. dt = datetime.now ()
  129. d = dt.date ()
  130. if dt.time () >= t:
  131. d += timedelta (days = 1)
  132. await asyncio.sleep ((datetime.combine (d, t) - dt).total_seconds ())
  133. class VideoInfo (TypedDict):
  134. contentId: str
  135. title: str
  136. tags: list[str]
  137. description: str
  138. kiriban_list = (
  139. fetch_kiriban_list ((d := datetime.now ()).date ()
  140. - timedelta (days = d.hour < 15)))
  141. if __name__ == '__main__':
  142. asyncio.run (main ())