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

151 lines
4.6 KiB

  1. """
  2. ニコニコのニジカ動画取得モヂュール
  3. """
  4. from __future__ import annotations
  5. import os
  6. from datetime import date, datetime, timedelta
  7. from typing import TypedDict, cast
  8. import requests
  9. from bs4 import BeautifulSoup
  10. from requests.exceptions import Timeout
  11. from eloquent import DatabaseManager, Model
  12. import nicolib
  13. from db.models import Comment, Tag, Video, VideoHistory, VideoTag
  14. CONFIG: dict[str, DbConfig] = { 'mysql': { 'driver': 'mysql',
  15. 'host': 'localhost',
  16. 'database': 'nizika_nico',
  17. 'user': os.environ['MYSQL_USER'],
  18. 'password': os.environ['MYSQL_PASS'],
  19. 'prefix': '' } }
  20. DB = DatabaseManager (CONFIG)
  21. Model.set_connection_resolver (DB)
  22. KIRIBAN_VIEWS_COUNTS: list[int] = sorted ({ *range (1_000, 10_000, 1_000),
  23. *range (10_000, 1_000_001, 10_000),
  24. 194, 245, 510, 810, 114_514, 1_940, 2_450, 5_100,
  25. 19_400, 24_500, 51_000, 93_194, 2_424, 242_424, 1_919,
  26. 4_545, 194_245, 245_194, 510_245 },
  27. reverse = True)
  28. class VideoInfo (TypedDict):
  29. contentId: str
  30. title: str
  31. tags: list[str]
  32. description: str
  33. def get_latest_deerjika (
  34. ) -> VideoInfo | None:
  35. tag = '伊地知ニジカ OR ぼざろクリーチャーシリーズ'
  36. url = f"https://www.nicovideo.jp/tag/{ tag }"
  37. params = { 'sort': 'f',
  38. 'order': 'd' }
  39. video_info = { }
  40. bs = get_bs_from_url (url, params)
  41. if bs is None:
  42. return None
  43. try:
  44. video = (bs.find_all ('ul', class_ = 'videoListInner')[1]
  45. .find ('li', class_ = 'item'))
  46. video_info['contentId'] = video['data-video-id']
  47. except Exception:
  48. return None
  49. return get_video_info (video_info['contentId'])
  50. def get_bs_from_url (
  51. url: str,
  52. params: dict = { },
  53. ) -> BeautifulSoup | None:
  54. """
  55. URL から BeautifulSoup インスタンス生成
  56. Parameters
  57. ----------
  58. url: str
  59. 捜査する URL
  60. params: dict
  61. パラメータ
  62. Return
  63. ------
  64. BeautifulSoup | None
  65. BeautifulSoup オブゼクト(失敗したら None)
  66. """
  67. try:
  68. req = requests.get (url, params = params, timeout = 60)
  69. except Timeout:
  70. return None
  71. if req.status_code != 200:
  72. return None
  73. req.encoding = req.apparent_encoding
  74. return BeautifulSoup (req.text, 'html.parser')
  75. def get_kiriban_list (
  76. base_date: date,
  77. ) -> list[tuple[int, VideoInfo, datetime]]:
  78. kiriban_list: list[tuple[int, VideoInfo, datetime]] = []
  79. latest_fetched_at = cast (date, (VideoHistory
  80. .where ('fetched_at', '<=', base_date)
  81. .max ('fetched_at')))
  82. for kiriban_views_count in KIRIBAN_VIEWS_COUNTS:
  83. targets = { vh.video.code for vh in (VideoHistory
  84. .where ('fetched_at', latest_fetched_at)
  85. .where ('views_count', '>=', kiriban_views_count)
  86. .get ()) }
  87. for code in targets:
  88. if code in [kiriban[1]['contentId'] for kiriban in kiriban_list]:
  89. continue
  90. previous_views_count: int | None = (
  91. VideoHistory
  92. .where_has ('video', lambda q: q.where ('code', code))
  93. .where ('fetched_at', '<', latest_fetched_at)
  94. .max ('views_count'))
  95. if previous_views_count is None:
  96. previous_views_count = 0
  97. if previous_views_count >= kiriban_views_count:
  98. continue
  99. video_info = get_video_info (code)
  100. if video_info is not None:
  101. kiriban_list.append ((kiriban_views_count, video_info,
  102. cast (Video, Video.where ('code', code).first ()).uploaded_at))
  103. return kiriban_list
  104. def get_comments (
  105. video_code: str,
  106. ) -> list[Comment]:
  107. video = Video.where ('code', video_code).first ()
  108. if video is None:
  109. return []
  110. return video.comments
  111. class DbConfig (TypedDict):
  112. driver: str
  113. host: str
  114. database: str
  115. user: str
  116. password: str
  117. prefix: str