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.

160 lines
4.0 KiB

  1. from __future__ import annotations
  2. import json
  3. import random
  4. import string
  5. import time
  6. from typing import Any, TypedDict
  7. import requests
  8. from bs4 import BeautifulSoup
  9. from requests.exceptions import Timeout
  10. def fetch_video_info (
  11. video_code: str,
  12. ) -> VideoInfo | None:
  13. bs = _create_bs_from_url (f"https://www.nicovideo.jp/watch/{ video_code }")
  14. if bs is None:
  15. return None
  16. try:
  17. title_tag = bs.find ('title')
  18. if title_tag is None:
  19. return None
  20. title = '-'.join (title_tag.text.split ('-')[:(-1)]).strip ()
  21. tags_str: str = bs.find ('meta', attrs = { 'name': 'keywords' }).get ('content') # type: ignore
  22. tags = tags_str.split (',')
  23. description = bs.find ('meta', attrs = { 'name': 'description' }).get ('content') # type: ignore
  24. except (AttributeError, TypeError):
  25. return None
  26. return { 'contentId': video_code,
  27. 'title': title,
  28. 'tags': tags,
  29. 'description': str (description) }
  30. def fetch_comments (
  31. video_code: str,
  32. ) -> list[Comment]:
  33. time.sleep (1.2)
  34. headers = { 'X-Frontend-Id': '6',
  35. 'X-Frontend-Version': '0' }
  36. action_track_id = (
  37. ''.join (random.choice (string.ascii_letters + string.digits)
  38. for _ in range (10))
  39. + '_'
  40. + str (random.randrange (10 ** 12, 10 ** 13)))
  41. url = (f"https://www.nicovideo.jp/api/watch/v3_guest/{ video_code }"
  42. + f"?actionTrackId={ action_track_id }")
  43. res = requests.post (url, headers = headers, timeout = 60).json ()
  44. try:
  45. nv_comment = res['data']['comment']['nvComment']
  46. except KeyError:
  47. return []
  48. if nv_comment is None:
  49. return []
  50. headers = { 'X-Frontend-Id': '6',
  51. 'X-Frontend-Version': '0',
  52. 'Content-Type': 'application/json' }
  53. params = { 'params': nv_comment['params'],
  54. 'additionals': { },
  55. 'threadKey': nv_comment['threadKey'] }
  56. url = nv_comment['server'] + '/v1/threads'
  57. res = (requests.post (url, json.dumps (params),
  58. headers = headers,
  59. timeout = 60)
  60. .json ())
  61. try:
  62. return res['data']['threads'][1]['comments']
  63. except (IndexError, KeyError):
  64. return []
  65. def fetch_embed_info (
  66. url: str,
  67. ) -> tuple[str, str, str]:
  68. title: str = ''
  69. description: str = ''
  70. thumbnail: str = ''
  71. bs = _create_bs_from_url (url)
  72. if bs is None:
  73. return ('', '', '')
  74. tmp = bs.find ('title')
  75. if tmp is not None:
  76. title = tmp.text
  77. tmp = bs.find ('meta', attrs = { 'name': 'description' })
  78. if tmp is not None and hasattr (tmp, 'get'):
  79. try:
  80. description = str (tmp.get ('content'))
  81. except Exception:
  82. pass
  83. tmp = bs.find ('meta', attrs = { 'name': 'thumbnail' })
  84. if tmp is not None and hasattr (tmp, 'get'):
  85. try:
  86. thumbnail = str (tmp.get ('content'))
  87. except Exception:
  88. pass
  89. return (title, description, thumbnail)
  90. def _create_bs_from_url (
  91. url: str,
  92. params: dict | None = None,
  93. ) -> BeautifulSoup | None:
  94. if params is None:
  95. params = { }
  96. try:
  97. req = requests.get (url, params = params, timeout = 60)
  98. except Timeout:
  99. return None
  100. if req.status_code != 200:
  101. return None
  102. req.encoding = req.apparent_encoding
  103. return BeautifulSoup (req.text, 'html.parser')
  104. class VideoInfo (TypedDict):
  105. contentId: str
  106. title: str
  107. tags: list[str]
  108. description: str
  109. class Comment (TypedDict):
  110. id: str
  111. no: int
  112. vposMs: int
  113. body: str
  114. commands: list[str]
  115. userId: str
  116. isPremium: bool
  117. score: int
  118. postedAt: str
  119. nicoruCount: int
  120. nicoruId: Any
  121. source: str
  122. isMyPost: bool