diff --git a/__init__.py b/__init__.py index d8e1cd7..9589e36 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1 @@ -from .module.py import fetch_comments +from .module import VideoInfo, fetch_comments, fetch_embed_info, fetch_video_info diff --git a/module.py b/module.py index a912d8b..9a9dd70 100644 --- a/module.py +++ b/module.py @@ -1,3 +1,16 @@ +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: @@ -6,20 +19,141 @@ def fetch_video_info ( return None try: - title = bs.find ('title') - if title is None: + title_tag = bs.find ('title') + if title_tag is None: return None - title = '-'.join (title.text.split ('-')[:(-1)])[:(-1)] + 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 Exception: + except (AttributeError, TypeError): return None return { 'contentId': video_code, 'title': title, 'tags': tags, - 'description': description } + '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 diff --git a/py.typed b/py.typed new file mode 100644 index 0000000..e69de29