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.

186 lines
4.6 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 fetch_latest_video (
  91. tags: list[str],
  92. ) -> VideoInfo | None:
  93. tag = ' OR '.join (tags)
  94. url = f"https://www.nicovideo.jp/tag/{ tag }"
  95. params = { 'sort': 'f',
  96. 'order': 'd' }
  97. video_info = { }
  98. bs = _create_bs_from_url (url, params)
  99. if bs is None:
  100. return None
  101. try:
  102. video = (bs.find_all ('ul', class_ = 'videoListInner')[1]
  103. .find ('li', class_ = 'item'))
  104. video_info['contentId'] = video['data-video-id']
  105. except Exception:
  106. return None
  107. return fetch_video_info (video_info['contentId'])
  108. def _create_bs_from_url (
  109. url: str,
  110. params: dict | None = None,
  111. ) -> BeautifulSoup | None:
  112. if params is None:
  113. params = { }
  114. try:
  115. req = requests.get (url, params = params, timeout = 60)
  116. except Timeout:
  117. return None
  118. if req.status_code != 200:
  119. return None
  120. req.encoding = req.apparent_encoding
  121. return BeautifulSoup (req.text, 'html.parser')
  122. class VideoInfo (TypedDict):
  123. contentId: str
  124. title: str
  125. tags: list[str]
  126. description: str
  127. class Comment (TypedDict):
  128. id: str
  129. no: int
  130. vposMs: int
  131. body: str
  132. commands: list[str]
  133. userId: str
  134. isPremium: bool
  135. score: int
  136. postedAt: str
  137. nicoruCount: int
  138. nicoruId: Any
  139. source: str
  140. isMyPost: bool