ぼざろクリーチャーシリーズ DB 兼 API(自分用)
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.
 
 
 

313 lines
9.8 KiB

  1. import json
  2. import os
  3. import random
  4. import string
  5. import time
  6. from dataclasses import dataclass
  7. from datetime import datetime
  8. import mysql.connector
  9. import requests
  10. def main (
  11. ) -> None:
  12. conn = mysql.connector.connect (host = os.environ['MYSQL_HOST'],
  13. user = os.environ['MYSQL_USER'],
  14. password = os.environ['MYSQL_PASS'])
  15. video_dao = VideoDao (conn)
  16. api_data = search_nico_by_tags (['伊地知ニジカ', 'ぼざろクリーチャーシリーズ'])
  17. update_video_table (video_dao, api_data)
  18. # TODO: 書くこと
  19. def update_video_table (
  20. video_dao,
  21. api_data: list[dict],
  22. ) -> None:
  23. # TODO: 書くこと
  24. video_dao.upsert_all (videos)
  25. video_dao.delete_nonexistent_data (video_id_list)
  26. # TODO: 書くこと
  27. def fetch_comments (
  28. video_id: str,
  29. ) -> list:
  30. headers = { 'X-Frontend-Id': '6',
  31. 'X-Frontend-Version': '0' }
  32. action_track_id = (
  33. ''.join (random.choice (string.ascii_letters + string.digits)
  34. for _ in range (10))
  35. + '_'
  36. + str (random.randrange (10 ** 12, 10 ** 13)))
  37. url = (f"https://www.nicovideo.jp/api/watch/v3_guest/{ video_id }"
  38. + f"?actionTrackId={ action_track_id }")
  39. res = requests.post (url, headers = headers, timeout = 60).json ()
  40. try:
  41. nv_comment = res['data']['comment']['nvComment']
  42. except KeyError:
  43. return []
  44. if nv_comment is None:
  45. return []
  46. headers = { 'X-Frontend-Id': '6',
  47. 'X-Frontend-Version': '0',
  48. 'Content-Type': 'application/json' }
  49. params = { 'params': nv_comment['params'],
  50. 'additionals': { },
  51. 'threadKey': nv_comment['threadKey'] }
  52. url = nv_comment['server'] + '/v1/threads'
  53. res = (requests.post (url, json.dumps (params),
  54. headers = headers,
  55. timeout = 60)
  56. .json ())
  57. try:
  58. return res['data']['threads'][1]['comments']
  59. except (IndexError, KeyError):
  60. return []
  61. def search_nico_by_tag (
  62. tag: str,
  63. ) -> list[dict]:
  64. return search_nico_by_tags ([tag])
  65. def search_nico_by_tags (
  66. tags: list[str],
  67. ) -> list[dict]:
  68. url = ('https://snapshot.search.nicovideo.jp'
  69. + '/api/v2/snapshot/video/contents/search')
  70. # TODO: 年月日の設定ができてゐなぃのと,100 件までしか取得できなぃので何とかすること
  71. query_filter = json.dumps ({ 'type': 'or',
  72. 'filters': [
  73. { 'type': 'range',
  74. 'field': 'startTime',
  75. 'from': f"{year}-{start}T00:00:00+09:00",
  76. 'to': f"{year}-{end}T23:59:59+09:00",
  77. 'include_lower': True }] })
  78. params: dict[str, int | str]
  79. params = { 'q': ' OR '.join (tags),
  80. 'targets': 'tagsExact',
  81. '_sort': '-viewCounter',
  82. 'fields': 'contentId,title,tags,viewCounter,startTime',
  83. '_limit': 100,
  84. 'jsonFilter': query_filter }
  85. res = requests.get (url, params = params, timeout = 60).json ()
  86. return res['data']
  87. class VideoDao:
  88. def __init__ (
  89. self,
  90. conn
  91. ):
  92. self.conn = conn
  93. def fetch (
  94. self,
  95. video_id: int,
  96. with_relation_tables: bool = True,
  97. ) -> VideoDto | None:
  98. with self.conn.cursor () as c:
  99. c.execute ("""
  100. SELECT
  101. id,
  102. code,
  103. title,
  104. description,
  105. uploaded_at,
  106. deleted_at
  107. FROM
  108. videos
  109. WHERE
  110. id = %s
  111. ORDER BY
  112. id""", video_id)
  113. row = c.fetchone ()
  114. if row is None:
  115. return None
  116. return self._create_dto_from_row (row, with_relation_tables)
  117. def fetch_all (
  118. self,
  119. with_relation_tables: bool = True,
  120. ) -> list[VideoDto]:
  121. with self.conn.cursor () as c:
  122. c.execute ("""
  123. SELECT
  124. id,
  125. code,
  126. title,
  127. description,
  128. uploaded_at,
  129. deleted_at
  130. FROM
  131. videos
  132. ORDER BY
  133. id""")
  134. videos: list[VideoDto] = []
  135. for row in c.fetchall ():
  136. videos.append (self._create_dto_from_row (row, with_relation_tables))
  137. return videos
  138. def upsert_all (
  139. self,
  140. videos: list[VideoDto],
  141. with_relation_tables: bool = True,
  142. ) -> int:
  143. with self.conn.cursor () as c:
  144. for video in videos:
  145. c.execute ("""
  146. INSERT INTO
  147. videos(
  148. code,
  149. title,
  150. description,
  151. uploaded_at,
  152. deleted_at)
  153. VALUES
  154. (
  155. %s,
  156. %s,
  157. %s,
  158. %s,
  159. %s)
  160. ON DUPLICATE KEY UPDATE
  161. code = VALUES(code),
  162. title = VALUES(title),
  163. description = VALUES(description),
  164. uploaded_at = VALUES(uploaded_at),
  165. deleted_at = VALUES(deleted_at)""", (
  166. video.code,
  167. video.title,
  168. video.description,
  169. video.uploaded_at,
  170. video.deleted_at))
  171. video.id_ = c.lastrowid
  172. # TODO: with_relation_tables が True の場合,子テーブルも UPSERT すること
  173. def _create_dto_from_row (
  174. self,
  175. row,
  176. with_relation_tables: bool,
  177. ) -> VideoDto:
  178. video = VideoDto (id_ = row['id'],
  179. code = row['code'],
  180. title = row['title'],
  181. description = row['description'],
  182. uploaded_at = row['uploaded_at'],
  183. deleted_at = row['deleted_at'],
  184. video_tags = None,
  185. comments = None,
  186. video_histories = None)
  187. if with_relation_tables:
  188. video.video_tags = VideoTagDao (self.conn).fetch_by_video_id (video.id_, False)
  189. for i in range (len (video.video_tags)):
  190. video.video_tags[i].video = video
  191. video.comments = CommentDao (self.conn).fetch_by_video_id (video.id_, False)
  192. for i in range (len (video.comments)):
  193. video.comments[i].video = video
  194. video.video_histories = VideoHistoryDao (self.conn).fetch_by_video_id (video.id_, False)
  195. for i in range (len (video.video_histories)):
  196. video.video_histories[i].video = video
  197. return video
  198. @dataclass (slots = True)
  199. class VideoDto:
  200. id_: int
  201. code: str
  202. title: str
  203. description: str
  204. uploaded_at: datetime
  205. deleted_at: datetime | None
  206. video_tags: list[VideoTagDto] | None
  207. comments: list[CommentDto] | None
  208. video_histories: list[VideoHistoryDto] | None
  209. class VideoTagDao:
  210. def __init__ (
  211. self,
  212. conn,
  213. ):
  214. self.conn = conn
  215. def fetch_by_video_id (
  216. self,
  217. video_id: int,
  218. with_relation_tables: bool = True,
  219. ) -> list[VideoTagDto]:
  220. with self.conn.cursor () as c:
  221. c.execute ("""
  222. SELECT
  223. id,
  224. video_id,
  225. tag_id,
  226. tagged_at,
  227. untagged_at
  228. FROM
  229. video_tags
  230. WHERE
  231. video_id = %s
  232. ORDER BY
  233. id""", video_id)
  234. video_tags: list[VideoTagDto] = []
  235. for row in c.fetchall ():
  236. video_tags.append (self._create_dto_from_row (row, with_relation_tables))
  237. return video_tags
  238. def _create_dto_from_row (
  239. self,
  240. row,
  241. with_relation_tables: bool,
  242. ) -> VideoTagDto:
  243. video_tag = VideoTagDto (id_ = row['id'],
  244. video_id = row['video_id'],
  245. tag_id = row['tag_id'],
  246. tagged_at = row['tagged_at'],
  247. untagged_at = row['untagged_at'],
  248. video = None,
  249. tag = None)
  250. if with_relation_tables:
  251. video_tag.video = VideoDao (self.conn).fetch (video_tag.video_id, True)
  252. video_tag.tag = TagDao (self.conn).fetch (video_tag.tag_id, True)
  253. return video_tag
  254. @dataclass (slots = True)
  255. class VideoTagDto:
  256. id_: int
  257. video_id: int
  258. tag_id: int
  259. tagged_at: datetime
  260. untagged_at: datetime
  261. video: VideoDto
  262. tag: TagDto
  263. if __name__ == '__main__':
  264. main ()