from __future__ import annotations import json import random import string import time from typing import Any, TypedDict import requests from bs4 import BeautifulSoup from requests.exceptions import Timeout def fetch_video_info ( video_code: str, ) -> VideoInfo | None: bs = _create_bs_from_url (f"https://www.nicovideo.jp/watch/{ video_code }") if bs is None: return None try: title_tag = bs.find ('title') if title_tag is None: return None title = '-'.join (title_tag.text.split ('-')[:(-1)]).strip () tags_str: str = bs.find ('meta', attrs = { 'name': 'keywords' }).get ('content') # type: ignore tags = tags_str.split (',') description = bs.find ('meta', attrs = { 'name': 'description' }).get ('content') # type: ignore except (AttributeError, TypeError): return None return { 'contentId': video_code, 'title': title, 'tags': tags, 'description': str (description) } def fetch_comments ( video_code: str, ) -> list[Comment]: time.sleep (1.2) headers = { 'X-Frontend-Id': '6', 'X-Frontend-Version': '0' } action_track_id = ( ''.join (random.choice (string.ascii_letters + string.digits) for _ in range (10)) + '_' + str (random.randrange (10 ** 12, 10 ** 13))) 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 () try: nv_comment = res['data']['comment']['nvComment'] except KeyError: return [] if nv_comment is None: return [] headers = { 'X-Frontend-Id': '6', 'X-Frontend-Version': '0', 'Content-Type': 'application/json' } params = { 'params': nv_comment['params'], 'additionals': { }, 'threadKey': nv_comment['threadKey'] } url = nv_comment['server'] + '/v1/threads' res = (requests.post (url, json.dumps (params), headers = headers, timeout = 60) .json ()) try: return res['data']['threads'][1]['comments'] except (IndexError, KeyError): return [] def fetch_embed_info ( url: str, ) -> tuple[str, str, str]: title: str = '' description: str = '' thumbnail: str = '' bs = _create_bs_from_url (url) if bs is None: return ('', '', '') tmp = bs.find ('title') if tmp is not None: title = tmp.text tmp = bs.find ('meta', attrs = { 'name': 'description' }) if tmp is not None and hasattr (tmp, 'get'): try: description = str (tmp.get ('content')) except Exception: pass tmp = bs.find ('meta', attrs = { 'name': 'thumbnail' }) if tmp is not None and hasattr (tmp, 'get'): try: thumbnail = str (tmp.get ('content')) except Exception: pass return (title, description, thumbnail) def _create_bs_from_url ( url: str, params: dict | None = None, ) -> BeautifulSoup | None: if params is None: params = { } try: req = requests.get (url, params = params, timeout = 60) except Timeout: return None if req.status_code != 200: return None req.encoding = req.apparent_encoding return BeautifulSoup (req.text, 'html.parser') class VideoInfo (TypedDict): contentId: str title: str tags: list[str] description: str class Comment (TypedDict): 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