|
|
|
@@ -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 |