| @@ -1,3 +1,7 @@ | |||
| """ | |||
| 日次で実行し,ぼざクリ DB を最新に更新する. | |||
| """ | |||
| from __future__ import annotations | |||
| import json | |||
| @@ -7,7 +11,7 @@ import string | |||
| import time | |||
| from dataclasses import dataclass | |||
| from datetime import datetime, timedelta | |||
| from typing import TypedDict, cast | |||
| from typing import Any, TypedDict, cast | |||
| import mysql.connector | |||
| import requests | |||
| @@ -16,15 +20,6 @@ DbNull = (None,) | |||
| DbNullType = tuple[None] | |||
| class VideoResult (TypedDict): | |||
| contentId: str | |||
| title: str | |||
| tags: list[str] | |||
| description: str | |||
| viewCounter: int | |||
| startTime: str | |||
| class VideoSearchParam (TypedDict): | |||
| q: str | |||
| targets: str | |||
| @@ -34,8 +29,29 @@ class VideoSearchParam (TypedDict): | |||
| jsonFilter: str | |||
| class VideoResult (TypedDict): | |||
| contentId: str | |||
| title: str | |||
| tags: list[str] | |||
| description: str | |||
| viewCounter: int | |||
| startTime: str | |||
| class CommentResult (TypedDict): | |||
| pass | |||
| id: str | |||
| no: int | |||
| vposMs: int | |||
| body: str | |||
| commands: list[str] | |||
| userId: str | |||
| isPremium: bool | |||
| score: int | |||
| postedAt: str | |||
| nicoruCount: int | |||
| nicoruId: Any | |||
| source: str | |||
| isMyPost: bool | |||
| def main ( | |||
| @@ -50,10 +66,13 @@ def main ( | |||
| tag_dao = TagDao (conn) | |||
| video_tag_dao = VideoTagDao (conn) | |||
| video_history_dao = VideoHistoryDao (conn) | |||
| comment_dao = CommentDao (conn) | |||
| user_dao = UserDao (conn) | |||
| api_data = search_nico_by_tags (['伊地知ニジカ', 'ぼざろクリーチャーシリーズ']) | |||
| update_tables (video_dao, tag_dao, video_tag_dao, video_history_dao, api_data, now) | |||
| update_tables (video_dao, tag_dao, video_tag_dao, video_history_dao, comment_dao, user_dao, | |||
| api_data, now) | |||
| # TODO: 書くこと | |||
| @@ -63,9 +82,12 @@ def update_tables ( | |||
| tag_dao: TagDao, | |||
| video_tag_dao: VideoTagDao, | |||
| video_history_dao: VideoHistoryDao, | |||
| comment_dao: CommentDao, | |||
| user_dao: UserDao, | |||
| api_data: list[VideoResult], | |||
| now: datetime, | |||
| ) -> None: | |||
| video_ids: list[int] = [] | |||
| for datum in api_data: | |||
| video = VideoDto (code = datum['contentId'], | |||
| title = datum['title'], | |||
| @@ -73,6 +95,7 @@ def update_tables ( | |||
| uploaded_at = datetime.fromisoformat (datum['startTime'])) | |||
| video_dao.upsert (video, False) | |||
| if video.id_ is not None: | |||
| video_ids.append (video.id_) | |||
| video_history = VideoHistoryDto (video_id = video.id_, | |||
| fetched_at = now, | |||
| views_count = datum['viewCounter']) | |||
| @@ -99,17 +122,37 @@ def update_tables ( | |||
| tag_id = tag.id_, | |||
| tagged_at = now) | |||
| video_tag_dao.insert (video_tag, False) | |||
| # TODO: コメント取得すること | |||
| # TODO: 削除処理,存在しなぃ動画リストを取得した上で行ふこと | |||
| # video_dao.delete (video_ids, now) | |||
| # TODO: 書くこと | |||
| for com in fetch_comments (video.code): | |||
| user = user_dao.fetch_by_code (com['userId']) | |||
| if user is None: | |||
| user = UserDto (code = com['userId']) | |||
| user_dao.insert (user) | |||
| if video.id_ is not None and user.id_ is not None: | |||
| comment = CommentDto (video_id = video.id_, | |||
| comment_no = com['no'], | |||
| user_id = user.id_, | |||
| content = com['body'], | |||
| posted_at = datetime.fromisoformat (com['postedAt']), | |||
| nico_count = com['nicoruCount'], | |||
| vpos_ms = com['vposMs']) | |||
| comment_dao.upsert (comment, False) | |||
| alive_video_ids = [d['contentId'] for d in api_data] | |||
| lost_video_ids: list[int] = [] | |||
| videos = video_dao.fetch_alive () | |||
| for video in videos: | |||
| if video.id_ is not None and video.id_ not in alive_video_ids: | |||
| lost_video_ids.append (video.id_) | |||
| video_dao.delete (lost_video_ids, now) | |||
| def fetch_comments ( | |||
| video_id: str, | |||
| ) -> list: | |||
| video_code: str, | |||
| ) -> list[CommentResult]: | |||
| time.sleep (1.2) | |||
| headers = { 'X-Frontend-Id': '6', | |||
| 'X-Frontend-Version': '0' } | |||
| @@ -119,7 +162,7 @@ def fetch_comments ( | |||
| + '_' | |||
| + str (random.randrange (10 ** 12, 10 ** 13))) | |||
| url = (f"https://www.nicovideo.jp/api/watch/v3_guest/{ video_id }" | |||
| url = (f"https://www.nicovideo.jp/api/watch/v3_guest/{ video_code }" | |||
| + f"?actionTrackId={ action_track_id }") | |||
| res = requests.post (url, headers = headers, timeout = 60).json () | |||
| @@ -253,6 +296,27 @@ class VideoDao: | |||
| videos.append (self._create_dto_from_row (row, with_relation_tables)) | |||
| return videos | |||
| def fetch_alive ( | |||
| self, | |||
| ) -> list[VideoDto]: | |||
| with self.conn.cursor () as c: | |||
| c.execute (""" | |||
| SELECT | |||
| id, | |||
| code, | |||
| title, | |||
| description, | |||
| uploaded_at, | |||
| deleted_at | |||
| FROM | |||
| videos | |||
| WHERE | |||
| deleted_at IS NULL""") | |||
| videos: list[VideoDto] = [] | |||
| for row in c.fetchall (): | |||
| videos.append (self._create_dto_from_row (row, False)) | |||
| return videos | |||
| def upsert ( | |||
| self, | |||
| video: VideoDto, | |||
| @@ -836,6 +900,58 @@ class CommentDto: | |||
| nico_count: int = 0 | |||
| vpos_ms: int | DbNullType = DbNull | |||
| video: VideoDto | None = None | |||
| user: UserDto | None = None | |||
| class UserDao: | |||
| def __init__ ( | |||
| self, | |||
| conn, | |||
| ): | |||
| self.conn = conn | |||
| def fetch_by_code ( | |||
| self, | |||
| user_code: str | |||
| ) -> UserDto | None: | |||
| with self.conn.cursor () as c: | |||
| c.execute (""" | |||
| SELECT | |||
| id, | |||
| code | |||
| FROM | |||
| users | |||
| WHERE | |||
| code = %s""", user_code) | |||
| row = c.fetchone () | |||
| if row is None: | |||
| return None | |||
| return self._create_dto_from_row (row) | |||
| def insert ( | |||
| self, | |||
| user: UserDto, | |||
| ) -> None: | |||
| with self.conn.cursor () as c: | |||
| c.execute (""" | |||
| INSERT INTO | |||
| users(code) | |||
| VALUES | |||
| (%s)""", user.code) | |||
| user.id_ = c.lastrowid | |||
| def _create_dto_from_row ( | |||
| self, | |||
| row, | |||
| ) -> UserDto: | |||
| return UserDto (id_ = row['id'], | |||
| code = row['code']) | |||
| @dataclass (slots = True) | |||
| class UserDto: | |||
| code: str | |||
| id_: int | None = None | |||
| if __name__ == '__main__': | |||