Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd6d522055 | ||
|
|
aa8a4fb592 | ||
|
|
dbde072828 | ||
|
|
92a01aa4d9 | ||
|
|
e3f9f95fb1 | ||
|
|
fa02116ab4 | ||
|
|
d8656161cd | ||
|
|
174d9f27c0 | ||
|
|
0abf8dd9f0 | ||
|
|
5ab653a1b2 | ||
|
|
6e6bb8e019 | ||
|
|
ee4b696fc5 | ||
|
|
fd1d283caa | ||
|
|
85966186b5 | ||
|
|
71341d2876 | ||
|
|
8882c82f8b | ||
|
|
584b9c5591 |
10
README.md
10
README.md
@@ -7,7 +7,7 @@ pytchat is a python library for fetching youtube live chat.
|
|||||||
pytchat is a python library for fetching youtube live chat
|
pytchat is a python library for fetching youtube live chat
|
||||||
without using youtube api, Selenium or BeautifulSoup.
|
without using youtube api, Selenium or BeautifulSoup.
|
||||||
|
|
||||||
pytchatはAPIを使わずにYouTubeチャットを取得するためのpythonライブラリです。
|
pytchatは、YouTubeチャットを閲覧するためのpythonライブラリです。
|
||||||
|
|
||||||
Other features:
|
Other features:
|
||||||
+ Customizable [chat data processors](https://github.com/taizan-hokuto/pytchat/wiki/ChatProcessor) including youtube api compatible one.
|
+ Customizable [chat data processors](https://github.com/taizan-hokuto/pytchat/wiki/ChatProcessor) including youtube api compatible one.
|
||||||
@@ -30,10 +30,9 @@ One-liner command.
|
|||||||
Save chat data to html, with embedded custom emojis.
|
Save chat data to html, with embedded custom emojis.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ pytchat -v ZJ6Q4U_Vg6s -o "c:/temp/"
|
$ pytchat -v https://www.youtube.com/watch?v=ZJ6Q4U_Vg6s -o "c:/temp/"
|
||||||
|
|
||||||
# options:
|
# options:
|
||||||
# -v : video_id
|
# -v : Video ID or URL that includes ID
|
||||||
# -o : output directory (default path: './')
|
# -o : output directory (default path: './')
|
||||||
# saved filename is [video_id].html
|
# saved filename is [video_id].html
|
||||||
```
|
```
|
||||||
@@ -43,7 +42,8 @@ $ pytchat -v ZJ6Q4U_Vg6s -o "c:/temp/"
|
|||||||
```python
|
```python
|
||||||
from pytchat import LiveChat
|
from pytchat import LiveChat
|
||||||
livechat = LiveChat(video_id = "Zvp1pJpie4I")
|
livechat = LiveChat(video_id = "Zvp1pJpie4I")
|
||||||
|
# It is also possible to specify a URL that includes the video ID:
|
||||||
|
# livechat = LiveChat("https://www.youtube.com/watch?v=Zvp1pJpie4I")
|
||||||
while livechat.is_alive():
|
while livechat.is_alive():
|
||||||
try:
|
try:
|
||||||
chatdata = livechat.get()
|
chatdata = livechat.get()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
pytchat is a lightweight python library to browse youtube livechat without Selenium or BeautifulSoup.
|
pytchat is a lightweight python library to browse youtube livechat without Selenium or BeautifulSoup.
|
||||||
"""
|
"""
|
||||||
__copyright__ = 'Copyright (C) 2019 taizan-hokuto'
|
__copyright__ = 'Copyright (C) 2019 taizan-hokuto'
|
||||||
__version__ = '0.0.9'
|
__version__ = '0.1.0'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
__author__ = 'taizan-hokuto'
|
__author__ = 'taizan-hokuto'
|
||||||
__author_email__ = '55448286+taizan-hokuto@users.noreply.github.com'
|
__author_email__ = '55448286+taizan-hokuto@users.noreply.github.com'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from pytchat.util.extract_video_id import extract_video_id
|
||||||
from .arguments import Arguments
|
from .arguments import Arguments
|
||||||
from .. exceptions import InvalidVideoIdException, NoContents
|
from .. exceptions import InvalidVideoIdException, NoContents
|
||||||
from .. processors.html_archiver import HTMLArchiver
|
from .. processors.html_archiver import HTMLArchiver
|
||||||
@@ -19,16 +20,19 @@ https://github.com/PetterKraabol/Twitch-Chat-Downloader
|
|||||||
def main():
|
def main():
|
||||||
# Arguments
|
# Arguments
|
||||||
parser = argparse.ArgumentParser(description=f'pytchat v{__version__}')
|
parser = argparse.ArgumentParser(description=f'pytchat v{__version__}')
|
||||||
parser.add_argument('-v', f'--{Arguments.Name.VIDEO}', type=str,
|
# parser.add_argument('VideoID_or_URL', type=str, default='__NONE__',nargs='?',
|
||||||
help='Video IDs separated by commas without space.\n'
|
# help='Video ID, or URL that includes id.\n'
|
||||||
|
# 'If ID starts with a hyphen (-), enclose the ID in square brackets.')
|
||||||
|
parser.add_argument('-v', f'--{Arguments.Name.VIDEO_IDS}', type=str,
|
||||||
|
help='Video ID (or URL that includes Video ID). You can specify multiple video IDs by separating them with commas without spaces.\n'
|
||||||
'If ID starts with a hyphen (-), enclose the ID in square brackets.')
|
'If ID starts with a hyphen (-), enclose the ID in square brackets.')
|
||||||
parser.add_argument('-o', f'--{Arguments.Name.OUTPUT}', type=str,
|
parser.add_argument('-o', f'--{Arguments.Name.OUTPUT}', type=str,
|
||||||
help='Output directory (end with "/"). default="./"', default='./')
|
help='Output directory (end with "/"). default="./"', default='./')
|
||||||
parser.add_argument(f'--{Arguments.Name.VERSION}', action='store_true',
|
parser.add_argument(f'--{Arguments.Name.VERSION}', action='store_true',
|
||||||
help='Settings version')
|
help='Show version')
|
||||||
Arguments(parser.parse_args().__dict__)
|
Arguments(parser.parse_args().__dict__)
|
||||||
if Arguments().print_version:
|
if Arguments().print_version:
|
||||||
print(f'pytchat v{__version__}')
|
print(f'pytchat v{__version__} © 2019 taizan-hokuto')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Extractor
|
# Extractor
|
||||||
@@ -43,14 +47,16 @@ def main():
|
|||||||
f" channel: {info.get_channel_name()}\n"
|
f" channel: {info.get_channel_name()}\n"
|
||||||
f" title: {info.get_title()}")
|
f" title: {info.get_title()}")
|
||||||
path = Path(Arguments().output + video_id + '.html')
|
path = Path(Arguments().output + video_id + '.html')
|
||||||
print(f"output path: {path.resolve()}")
|
print(f" output path: {path.resolve()}")
|
||||||
Extractor(video_id,
|
Extractor(video_id,
|
||||||
processor=HTMLArchiver(
|
processor=HTMLArchiver(
|
||||||
Arguments().output + video_id + '.html'),
|
Arguments().output + video_id + '.html'),
|
||||||
callback=_disp_progress
|
callback=_disp_progress
|
||||||
).extract()
|
).extract()
|
||||||
print("\nExtraction end.\n")
|
print("\nExtraction end.\n")
|
||||||
except (InvalidVideoIdException, NoContents) as e:
|
except InvalidVideoIdException:
|
||||||
|
print("Invalid Video ID or URL:", video_id)
|
||||||
|
except (TypeError, NoContents) as e:
|
||||||
print(e)
|
print(e)
|
||||||
return
|
return
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ class Arguments(metaclass=Singleton):
|
|||||||
|
|
||||||
class Name:
|
class Name:
|
||||||
VERSION: str = 'version'
|
VERSION: str = 'version'
|
||||||
OUTPUT: str = 'output'
|
OUTPUT: str = 'output_dir'
|
||||||
VIDEO: str = 'video'
|
VIDEO_IDS: str = 'video_id'
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
arguments: Optional[Dict[str, Union[str, bool, int]]] = None):
|
arguments: Optional[Dict[str, Union[str, bool, int]]] = None):
|
||||||
@@ -35,6 +35,9 @@ class Arguments(metaclass=Singleton):
|
|||||||
self.output: str = arguments[Arguments.Name.OUTPUT]
|
self.output: str = arguments[Arguments.Name.OUTPUT]
|
||||||
self.video_ids: List[int] = []
|
self.video_ids: List[int] = []
|
||||||
# Videos
|
# Videos
|
||||||
if arguments[Arguments.Name.VIDEO]:
|
if arguments[Arguments.Name.VIDEO_IDS]:
|
||||||
self.video_ids = [video_id
|
self.video_ids = [video_id
|
||||||
for video_id in arguments[Arguments.Name.VIDEO].split(',')]
|
for video_id in arguments[Arguments.Name.VIDEO_IDS].split(',')]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from .. import exceptions
|
|||||||
from ..paramgen import liveparam, arcparam
|
from ..paramgen import liveparam, arcparam
|
||||||
from ..processors.default.processor import DefaultProcessor
|
from ..processors.default.processor import DefaultProcessor
|
||||||
from ..processors.combinator import Combinator
|
from ..processors.combinator import Combinator
|
||||||
|
from ..util.extract_video_id import extract_video_id
|
||||||
|
|
||||||
headers = config.headers
|
headers = config.headers
|
||||||
MAX_RETRY = 10
|
MAX_RETRY = 10
|
||||||
@@ -86,7 +87,7 @@ class LiveChatAsync:
|
|||||||
topchat_only=False,
|
topchat_only=False,
|
||||||
logger=config.logger(__name__),
|
logger=config.logger(__name__),
|
||||||
):
|
):
|
||||||
self._video_id = video_id
|
self._video_id = extract_video_id(video_id)
|
||||||
self.seektime = seektime
|
self.seektime = seektime
|
||||||
if isinstance(processor, tuple):
|
if isinstance(processor, tuple):
|
||||||
self.processor = Combinator(processor)
|
self.processor = Combinator(processor)
|
||||||
@@ -333,12 +334,12 @@ class LiveChatAsync:
|
|||||||
'''
|
'''
|
||||||
if self.is_alive():
|
if self.is_alive():
|
||||||
self.terminate()
|
self.terminate()
|
||||||
try:
|
try:
|
||||||
self.listen_task.result()
|
self.listen_task.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.exception = e
|
self.exception = e
|
||||||
if not isinstance(e, exceptions.ChatParseException):
|
if not isinstance(e, exceptions.ChatParseException):
|
||||||
self._logger.error(f'Internal exception - {type(e)}{str(e)}')
|
self._logger.error(f'Internal exception - {type(e)}{str(e)}')
|
||||||
self._logger.info(f'[{self._video_id}]終了しました')
|
self._logger.info(f'[{self._video_id}]終了しました')
|
||||||
|
|
||||||
def raise_for_status(self):
|
def raise_for_status(self):
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from .. import exceptions
|
|||||||
from ..paramgen import liveparam, arcparam
|
from ..paramgen import liveparam, arcparam
|
||||||
from ..processors.default.processor import DefaultProcessor
|
from ..processors.default.processor import DefaultProcessor
|
||||||
from ..processors.combinator import Combinator
|
from ..processors.combinator import Combinator
|
||||||
|
from ..util.extract_video_id import extract_video_id
|
||||||
|
|
||||||
headers = config.headers
|
headers = config.headers
|
||||||
MAX_RETRY = 10
|
MAX_RETRY = 10
|
||||||
@@ -84,7 +85,7 @@ class LiveChat:
|
|||||||
topchat_only=False,
|
topchat_only=False,
|
||||||
logger=config.logger(__name__)
|
logger=config.logger(__name__)
|
||||||
):
|
):
|
||||||
self._video_id = video_id
|
self._video_id = extract_video_id(video_id)
|
||||||
self.seektime = seektime
|
self.seektime = seektime
|
||||||
if isinstance(processor, tuple):
|
if isinstance(processor, tuple):
|
||||||
self.processor = Combinator(processor)
|
self.processor = Combinator(processor)
|
||||||
@@ -324,12 +325,12 @@ class LiveChat:
|
|||||||
'''
|
'''
|
||||||
if self.is_alive():
|
if self.is_alive():
|
||||||
self.terminate()
|
self.terminate()
|
||||||
try:
|
try:
|
||||||
self.listen_task.result()
|
self.listen_task.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.exception = e
|
self.exception = e
|
||||||
if not isinstance(e, exceptions.ChatParseException):
|
if not isinstance(e, exceptions.ChatParseException):
|
||||||
self._logger.error(f'Internal exception - {type(e)}{str(e)}')
|
self._logger.error(f'Internal exception - {type(e)}{str(e)}')
|
||||||
self._logger.info(f'[{self._video_id}]終了しました')
|
self._logger.info(f'[{self._video_id}]終了しました')
|
||||||
|
|
||||||
def raise_for_status(self):
|
def raise_for_status(self):
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ class LiveChatLegacyPaidMessageRenderer(BaseRenderer):
|
|||||||
def get_message(self, renderer):
|
def get_message(self, renderer):
|
||||||
message = (renderer["eventText"]["runs"][0]["text"]
|
message = (renderer["eventText"]["runs"][0]["text"]
|
||||||
) + ' / ' + (renderer["detailText"]["simpleText"])
|
) + ' / ' + (renderer["detailText"]["simpleText"])
|
||||||
return message
|
return message, [message]
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ from .base import BaseRenderer
|
|||||||
superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$")
|
superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$")
|
||||||
|
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LiveChatPaidMessageRenderer(BaseRenderer):
|
class LiveChatPaidMessageRenderer(BaseRenderer):
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
super().__init__(item, "superChat")
|
super().__init__(item, "superChat")
|
||||||
@@ -18,6 +22,7 @@ class LiveChatPaidMessageRenderer(BaseRenderer):
|
|||||||
self.currency = currency.symbols[symbol]["fxtext"] if currency.symbols.get(
|
self.currency = currency.symbols[symbol]["fxtext"] if currency.symbols.get(
|
||||||
symbol) else symbol
|
symbol) else symbol
|
||||||
self.bgColor = self.renderer.get("bodyBackgroundColor", 0)
|
self.bgColor = self.renderer.get("bodyBackgroundColor", 0)
|
||||||
|
self.colors = self.get_colors()
|
||||||
|
|
||||||
def get_amountdata(self, renderer):
|
def get_amountdata(self, renderer):
|
||||||
amountDisplayString = renderer["purchaseAmountText"]["simpleText"]
|
amountDisplayString = renderer["purchaseAmountText"]["simpleText"]
|
||||||
@@ -29,3 +34,13 @@ class LiveChatPaidMessageRenderer(BaseRenderer):
|
|||||||
symbol = ""
|
symbol = ""
|
||||||
amount = 0.0
|
amount = 0.0
|
||||||
return amountDisplayString, symbol, amount
|
return amountDisplayString, symbol, amount
|
||||||
|
|
||||||
|
def get_colors(self):
|
||||||
|
colors = Colors()
|
||||||
|
colors.headerBackgroundColor = self.renderer.get("headerBackgroundColor", 0)
|
||||||
|
colors.headerTextColor = self.renderer.get("headerTextColor", 0)
|
||||||
|
colors.bodyBackgroundColor = self.renderer.get("bodyBackgroundColor", 0)
|
||||||
|
colors.bodyTextColor = self.renderer.get("bodyTextColor", 0)
|
||||||
|
colors.timestampColor = self.renderer.get("timestampColor", 0)
|
||||||
|
colors.authorNameTextColor = self.renderer.get("authorNameTextColor", 0)
|
||||||
|
return colors
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ from .base import BaseRenderer
|
|||||||
superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$")
|
superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$")
|
||||||
|
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LiveChatPaidStickerRenderer(BaseRenderer):
|
class LiveChatPaidStickerRenderer(BaseRenderer):
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
super().__init__(item, "superSticker")
|
super().__init__(item, "superSticker")
|
||||||
@@ -18,8 +22,9 @@ class LiveChatPaidStickerRenderer(BaseRenderer):
|
|||||||
self.currency = currency.symbols[symbol]["fxtext"] if currency.symbols.get(
|
self.currency = currency.symbols[symbol]["fxtext"] if currency.symbols.get(
|
||||||
symbol) else symbol
|
symbol) else symbol
|
||||||
self.bgColor = self.renderer.get("moneyChipBackgroundColor", 0)
|
self.bgColor = self.renderer.get("moneyChipBackgroundColor", 0)
|
||||||
self.sticker = "https:" + \
|
self.sticker = "".join(("https:",
|
||||||
self.renderer["sticker"]["thumbnails"][0]["url"]
|
self.renderer["sticker"]["thumbnails"][0]["url"]))
|
||||||
|
self.colors = self.get_colors()
|
||||||
|
|
||||||
def get_amountdata(self, renderer):
|
def get_amountdata(self, renderer):
|
||||||
amountDisplayString = renderer["purchaseAmountText"]["simpleText"]
|
amountDisplayString = renderer["purchaseAmountText"]["simpleText"]
|
||||||
@@ -31,3 +36,11 @@ class LiveChatPaidStickerRenderer(BaseRenderer):
|
|||||||
symbol = ""
|
symbol = ""
|
||||||
amount = 0.0
|
amount = 0.0
|
||||||
return amountDisplayString, symbol, amount
|
return amountDisplayString, symbol, amount
|
||||||
|
|
||||||
|
def get_colors(self):
|
||||||
|
colors = Colors()
|
||||||
|
colors.moneyChipBackgroundColor = self.renderer.get("moneyChipBackgroundColor", 0)
|
||||||
|
colors.moneyChipTextColor = self.renderer.get("moneyChipTextColor", 0)
|
||||||
|
colors.backgroundColor = self.renderer.get("backgroundColor", 0)
|
||||||
|
colors.authorNameTextColor = self.renderer.get("authorNameTextColor", 0)
|
||||||
|
return colors
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ fmt_headers = ['datetime', 'elapsed', 'authorName',
|
|||||||
'message', 'superchat', 'type', 'authorChannel']
|
'message', 'superchat', 'type', 'authorChannel']
|
||||||
|
|
||||||
HEADER_HTML = '''
|
HEADER_HTML = '''
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||||
'''
|
'''
|
||||||
|
|
||||||
TABLE_CSS = '''
|
TABLE_CSS = '''
|
||||||
@@ -47,7 +47,7 @@ class HTMLArchiver(ChatProcessor):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.save_path = self._checkpath(save_path)
|
self.save_path = self._checkpath(save_path)
|
||||||
self.processor = DefaultProcessor()
|
self.processor = DefaultProcessor()
|
||||||
self.emoji_table = {} # table for custom emojis. key: emoji_id, value: base64 encoded image binary.
|
self.emoji_table = {} # tuble for custom emojis. key: emoji_id, value: base64 encoded image binary.
|
||||||
self.header = [HEADER_HTML]
|
self.header = [HEADER_HTML]
|
||||||
self.body = ['<body>\n', '<table class="css">\n', self._parse_table_header(fmt_headers)]
|
self.body = ['<body>\n', '<table class="css">\n', self._parse_table_header(fmt_headers)]
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from . import duplcheck
|
|||||||
from .. videoinfo import VideoInfo
|
from .. videoinfo import VideoInfo
|
||||||
from ... import config
|
from ... import config
|
||||||
from ... exceptions import InvalidVideoIdException
|
from ... exceptions import InvalidVideoIdException
|
||||||
|
from ... util.extract_video_id import extract_video_id
|
||||||
|
|
||||||
logger = config.logger(__name__)
|
logger = config.logger(__name__)
|
||||||
headers = config.headers
|
headers = config.headers
|
||||||
@@ -14,7 +15,7 @@ class Extractor:
|
|||||||
raise ValueError('div must be positive integer.')
|
raise ValueError('div must be positive integer.')
|
||||||
elif div > 10:
|
elif div > 10:
|
||||||
div = 10
|
div = 10
|
||||||
self.video_id = video_id
|
self.video_id = extract_video_id(video_id)
|
||||||
self.div = div
|
self.div = div
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.processor = processor
|
self.processor = processor
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import re
|
|||||||
import requests
|
import requests
|
||||||
from .. import config
|
from .. import config
|
||||||
from ..exceptions import InvalidVideoIdException
|
from ..exceptions import InvalidVideoIdException
|
||||||
|
from ..util.extract_video_id import extract_video_id
|
||||||
|
|
||||||
headers = config.headers
|
headers = config.headers
|
||||||
|
|
||||||
@@ -78,8 +79,8 @@ class VideoInfo:
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, video_id):
|
def __init__(self, video_id):
|
||||||
self.video_id = video_id
|
self.video_id = extract_video_id(video_id)
|
||||||
text = self._get_page_text(video_id)
|
text = self._get_page_text(self.video_id)
|
||||||
self._parse(text)
|
self._parse(text)
|
||||||
|
|
||||||
def _get_page_text(self, video_id):
|
def _get_page_text(self, video_id):
|
||||||
|
|||||||
25
pytchat/util/extract_video_id.py
Normal file
25
pytchat/util/extract_video_id.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import re
|
||||||
|
from .. exceptions import InvalidVideoIdException
|
||||||
|
|
||||||
|
|
||||||
|
PATTERN = re.compile(r"((?<=(v|V)/)|(?<=be/)|(?<=(\?|\&)v=)|(?<=embed/))([\w-]+)")
|
||||||
|
YT_VIDEO_ID_LENGTH = 11
|
||||||
|
|
||||||
|
|
||||||
|
def extract_video_id(url_or_id: str) -> str:
|
||||||
|
ret = ''
|
||||||
|
if type(url_or_id) != str:
|
||||||
|
raise TypeError(f"{url_or_id}: URL or VideoID must be str, but {type(url_or_id)} is passed.")
|
||||||
|
if len(url_or_id) == YT_VIDEO_ID_LENGTH:
|
||||||
|
return url_or_id
|
||||||
|
match = re.search(PATTERN, url_or_id)
|
||||||
|
if match is None:
|
||||||
|
raise InvalidVideoIdException(url_or_id)
|
||||||
|
try:
|
||||||
|
ret = match.group(4)
|
||||||
|
except IndexError:
|
||||||
|
raise InvalidVideoIdException(url_or_id)
|
||||||
|
|
||||||
|
if ret is None or len(ret) != YT_VIDEO_ID_LENGTH:
|
||||||
|
raise InvalidVideoIdException(url_or_id)
|
||||||
|
return ret
|
||||||
228
tests/test_default_processor.py
Normal file
228
tests/test_default_processor.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import json
|
||||||
|
from pytchat.parser.live import Parser
|
||||||
|
from pytchat.processors.default.processor import DefaultProcessor
|
||||||
|
|
||||||
|
|
||||||
|
def test_textmessage(mocker):
|
||||||
|
'''text message'''
|
||||||
|
processor = DefaultProcessor()
|
||||||
|
parser = Parser(is_replay=False)
|
||||||
|
_json = _open_file("tests/testdata/default/textmessage.json")
|
||||||
|
|
||||||
|
_, chatdata = parser.parse(parser.get_contents(json.loads(_json)))
|
||||||
|
data = {
|
||||||
|
"video_id": "",
|
||||||
|
"timeout": 7,
|
||||||
|
"chatdata": chatdata
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = processor.process([data]).items[0]
|
||||||
|
assert ret.chattype == "textMessage"
|
||||||
|
assert ret.id == "dummy_id"
|
||||||
|
assert ret.message == "dummy_message"
|
||||||
|
assert ret.timestamp == 1570678496000
|
||||||
|
assert ret.datetime == "2019-10-10 12:34:56"
|
||||||
|
assert ret.author.name == "author_name"
|
||||||
|
assert ret.author.channelId == "author_channel_id"
|
||||||
|
assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id"
|
||||||
|
assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg"
|
||||||
|
assert ret.author.badgeUrl == ""
|
||||||
|
assert ret.author.isVerified is False
|
||||||
|
assert ret.author.isChatOwner is False
|
||||||
|
assert ret.author.isChatSponsor is False
|
||||||
|
assert ret.author.isChatModerator is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_textmessage_replay_member(mocker):
|
||||||
|
'''text message replay member'''
|
||||||
|
processor = DefaultProcessor()
|
||||||
|
parser = Parser(is_replay=True)
|
||||||
|
_json = _open_file("tests/testdata/default/replay_member_text.json")
|
||||||
|
|
||||||
|
_, chatdata = parser.parse(parser.get_contents(json.loads(_json)))
|
||||||
|
data = {
|
||||||
|
"video_id": "",
|
||||||
|
"timeout": 7,
|
||||||
|
"chatdata": chatdata
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = processor.process([data]).items[0]
|
||||||
|
assert ret.chattype == "textMessage"
|
||||||
|
assert ret.type == "textMessage"
|
||||||
|
assert ret.id == "dummy_id"
|
||||||
|
assert ret.message == "dummy_message"
|
||||||
|
assert ret.messageEx == ["dummy_message"]
|
||||||
|
assert ret.timestamp == 1570678496000
|
||||||
|
assert ret.datetime == "2019-10-10 12:34:56"
|
||||||
|
assert ret.elapsedTime == "1:23:45"
|
||||||
|
assert ret.author.name == "author_name"
|
||||||
|
assert ret.author.channelId == "author_channel_id"
|
||||||
|
assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id"
|
||||||
|
assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg"
|
||||||
|
assert ret.author.badgeUrl == "https://yt3.ggpht.com/X=s16-c-k"
|
||||||
|
assert ret.author.isVerified is False
|
||||||
|
assert ret.author.isChatOwner is False
|
||||||
|
assert ret.author.isChatSponsor is True
|
||||||
|
assert ret.author.isChatModerator is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_superchat(mocker):
|
||||||
|
'''superchat'''
|
||||||
|
processor = DefaultProcessor()
|
||||||
|
parser = Parser(is_replay=False)
|
||||||
|
_json = _open_file("tests/testdata/default/superchat.json")
|
||||||
|
|
||||||
|
_, chatdata = parser.parse(parser.get_contents(json.loads(_json)))
|
||||||
|
data = {
|
||||||
|
"video_id": "",
|
||||||
|
"timeout": 7,
|
||||||
|
"chatdata": chatdata
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = processor.process([data]).items[0]
|
||||||
|
print(json.dumps(chatdata, ensure_ascii=False))
|
||||||
|
assert ret.chattype == "superChat"
|
||||||
|
assert ret.type == "superChat"
|
||||||
|
assert ret.id == "dummy_id"
|
||||||
|
assert ret.message == "dummy_message"
|
||||||
|
assert ret.messageEx == ["dummy_message"]
|
||||||
|
assert ret.timestamp == 1570678496000
|
||||||
|
assert ret.datetime == "2019-10-10 12:34:56"
|
||||||
|
assert ret.elapsedTime == ""
|
||||||
|
assert ret.amountValue == 800
|
||||||
|
assert ret.amountString == "¥800"
|
||||||
|
assert ret.currency == "JPY"
|
||||||
|
assert ret.bgColor == 4280150454
|
||||||
|
assert ret.author.name == "author_name"
|
||||||
|
assert ret.author.channelId == "author_channel_id"
|
||||||
|
assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id"
|
||||||
|
assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg"
|
||||||
|
assert ret.author.badgeUrl == ""
|
||||||
|
assert ret.author.isVerified is False
|
||||||
|
assert ret.author.isChatOwner is False
|
||||||
|
assert ret.author.isChatSponsor is False
|
||||||
|
assert ret.author.isChatModerator is False
|
||||||
|
assert ret.colors.headerBackgroundColor == 4278239141
|
||||||
|
assert ret.colors.headerTextColor == 4278190080
|
||||||
|
assert ret.colors.bodyBackgroundColor == 4280150454
|
||||||
|
assert ret.colors.bodyTextColor == 4278190080
|
||||||
|
assert ret.colors.authorNameTextColor == 2315255808
|
||||||
|
assert ret.colors.timestampColor == 2147483648
|
||||||
|
|
||||||
|
|
||||||
|
def test_supersticker(mocker):
|
||||||
|
'''supersticker'''
|
||||||
|
processor = DefaultProcessor()
|
||||||
|
parser = Parser(is_replay=False)
|
||||||
|
_json = _open_file("tests/testdata/default/supersticker.json")
|
||||||
|
|
||||||
|
_, chatdata = parser.parse(parser.get_contents(json.loads(_json)))
|
||||||
|
data = {
|
||||||
|
"video_id": "",
|
||||||
|
"timeout": 7,
|
||||||
|
"chatdata": chatdata
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = processor.process([data]).items[0]
|
||||||
|
print(json.dumps(chatdata, ensure_ascii=False))
|
||||||
|
assert ret.chattype == "superSticker"
|
||||||
|
assert ret.type == "superSticker"
|
||||||
|
assert ret.id == "dummy_id"
|
||||||
|
assert ret.message == ""
|
||||||
|
assert ret.messageEx == []
|
||||||
|
assert ret.timestamp == 1570678496000
|
||||||
|
assert ret.datetime == "2019-10-10 12:34:56"
|
||||||
|
assert ret.elapsedTime == ""
|
||||||
|
assert ret.amountValue == 200
|
||||||
|
assert ret.amountString == "¥200"
|
||||||
|
assert ret.currency == "JPY"
|
||||||
|
assert ret.bgColor == 4278248959
|
||||||
|
assert ret.sticker == "https://lh3.googleusercontent.com/param_s=s72-rp"
|
||||||
|
assert ret.author.name == "author_name"
|
||||||
|
assert ret.author.channelId == "author_channel_id"
|
||||||
|
assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id"
|
||||||
|
assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg"
|
||||||
|
assert ret.author.badgeUrl == ""
|
||||||
|
assert ret.author.isVerified is False
|
||||||
|
assert ret.author.isChatOwner is False
|
||||||
|
assert ret.author.isChatSponsor is False
|
||||||
|
assert ret.author.isChatModerator is False
|
||||||
|
assert ret.colors.backgroundColor == 4278237396
|
||||||
|
assert ret.colors.moneyChipBackgroundColor == 4278248959
|
||||||
|
assert ret.colors.moneyChipTextColor == 4278190080
|
||||||
|
assert ret.colors.authorNameTextColor == 3003121664
|
||||||
|
|
||||||
|
|
||||||
|
def test_sponsor(mocker):
|
||||||
|
'''sponsor(membership)'''
|
||||||
|
processor = DefaultProcessor()
|
||||||
|
parser = Parser(is_replay=False)
|
||||||
|
_json = _open_file("tests/testdata/default/newSponsor_current.json")
|
||||||
|
|
||||||
|
_, chatdata = parser.parse(parser.get_contents(json.loads(_json)))
|
||||||
|
data = {
|
||||||
|
"video_id": "",
|
||||||
|
"timeout": 7,
|
||||||
|
"chatdata": chatdata
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = processor.process([data]).items[0]
|
||||||
|
print(json.dumps(chatdata, ensure_ascii=False))
|
||||||
|
assert ret.chattype == "newSponsor"
|
||||||
|
assert ret.type == "newSponsor"
|
||||||
|
assert ret.id == "dummy_id"
|
||||||
|
assert ret.message == "新規メンバー"
|
||||||
|
assert ret.messageEx == ["新規メンバー"]
|
||||||
|
assert ret.timestamp == 1570678496000
|
||||||
|
assert ret.datetime == "2019-10-10 12:34:56"
|
||||||
|
assert ret.elapsedTime == ""
|
||||||
|
assert ret.bgColor == 0
|
||||||
|
assert ret.author.name == "author_name"
|
||||||
|
assert ret.author.channelId == "author_channel_id"
|
||||||
|
assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id"
|
||||||
|
assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg"
|
||||||
|
assert ret.author.badgeUrl == "https://yt3.ggpht.com/X=s32-c-k"
|
||||||
|
assert ret.author.isVerified is False
|
||||||
|
assert ret.author.isChatOwner is False
|
||||||
|
assert ret.author.isChatSponsor is True
|
||||||
|
assert ret.author.isChatModerator is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_sponsor_legacy(mocker):
|
||||||
|
'''lagacy sponsor(membership)'''
|
||||||
|
processor = DefaultProcessor()
|
||||||
|
parser = Parser(is_replay=False)
|
||||||
|
_json = _open_file("tests/testdata/default/newSponsor_lagacy.json")
|
||||||
|
|
||||||
|
_, chatdata = parser.parse(parser.get_contents(json.loads(_json)))
|
||||||
|
data = {
|
||||||
|
"video_id": "",
|
||||||
|
"timeout": 7,
|
||||||
|
"chatdata": chatdata
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = processor.process([data]).items[0]
|
||||||
|
print(json.dumps(chatdata, ensure_ascii=False))
|
||||||
|
assert ret.chattype == "newSponsor"
|
||||||
|
assert ret.type == "newSponsor"
|
||||||
|
assert ret.id == "dummy_id"
|
||||||
|
assert ret.message == "新規メンバー / ようこそ、author_name!"
|
||||||
|
assert ret.messageEx == ["新規メンバー / ようこそ、author_name!"]
|
||||||
|
assert ret.timestamp == 1570678496000
|
||||||
|
assert ret.datetime == "2019-10-10 12:34:56"
|
||||||
|
assert ret.elapsedTime == ""
|
||||||
|
assert ret.bgColor == 0
|
||||||
|
assert ret.author.name == "author_name"
|
||||||
|
assert ret.author.channelId == "author_channel_id"
|
||||||
|
assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id"
|
||||||
|
assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg"
|
||||||
|
assert ret.author.badgeUrl == ""
|
||||||
|
assert ret.author.isVerified is False
|
||||||
|
assert ret.author.isChatOwner is False
|
||||||
|
assert ret.author.isChatSponsor is True
|
||||||
|
assert ret.author.isChatModerator is False
|
||||||
|
|
||||||
|
|
||||||
|
def _open_file(path):
|
||||||
|
with open(path, mode='r', encoding='utf-8') as f:
|
||||||
|
return f.read()
|
||||||
55
tests/test_extract_video_id.py
Normal file
55
tests/test_extract_video_id.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from pytchat.util.extract_video_id import extract_video_id
|
||||||
|
from pytchat.exceptions import InvalidVideoIdException
|
||||||
|
|
||||||
|
VALID_TEST_PATTERNS = (
|
||||||
|
("ABC_EFG_IJK", "ABC_EFG_IJK"),
|
||||||
|
("vid_test_be", "vid_test_be"),
|
||||||
|
("https://www.youtube.com/watch?v=123_456_789", "123_456_789"),
|
||||||
|
("https://www.youtube.com/watch?v=123_456_789&t=123s", "123_456_789"),
|
||||||
|
("www.youtube.com/watch?v=123_456_789", "123_456_789"),
|
||||||
|
("watch?v=123_456_789", "123_456_789"),
|
||||||
|
("youtube.com/watch?v=123_456_789", "123_456_789"),
|
||||||
|
("http://youtu.be/ABC_EFG_IJK", "ABC_EFG_IJK"),
|
||||||
|
("youtu.be/ABC_EFG_IJK", "ABC_EFG_IJK"),
|
||||||
|
("https://www.youtube.com/watch?v=ABC_EFG_IJK&list=XYZ_ABC_12345&start_radio=1&t=1", "ABC_EFG_IJK"),
|
||||||
|
("https://www.youtube.com/embed/ABC_EFG_IJK", "ABC_EFG_IJK"),
|
||||||
|
("www.youtube.com/embed/ABC_EFG_IJK", "ABC_EFG_IJK"),
|
||||||
|
("youtube.com/embed/ABC_EFG_IJK", "ABC_EFG_IJK")
|
||||||
|
)
|
||||||
|
|
||||||
|
INVALID_TEST_PATTERNS = (
|
||||||
|
("", ""),
|
||||||
|
("0123456789", "0123456789"), # less than 11 letters id
|
||||||
|
("more_than_11_letter_string", "more_than_11_letter_string"),
|
||||||
|
("https://www.youtube.com/watch?v=more_than_11_letter_string", "more_than_11_letter_string"),
|
||||||
|
("https://www.youtube.com/channel/123_456_789", "123_456_789"),
|
||||||
|
)
|
||||||
|
|
||||||
|
TYPEERROR_TEST_PATTERNS = (
|
||||||
|
(100, 100), # not string
|
||||||
|
(["123_456_789"], "123_456_789"), # not string
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_valid_pattern():
|
||||||
|
for pattern in VALID_TEST_PATTERNS:
|
||||||
|
ret = extract_video_id(pattern[0])
|
||||||
|
assert ret == pattern[1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_invalid_pattern():
|
||||||
|
for pattern in INVALID_TEST_PATTERNS:
|
||||||
|
try:
|
||||||
|
extract_video_id(pattern[0])
|
||||||
|
assert False
|
||||||
|
except InvalidVideoIdException:
|
||||||
|
assert True
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_typeerror_pattern():
|
||||||
|
for pattern in TYPEERROR_TEST_PATTERNS:
|
||||||
|
try:
|
||||||
|
extract_video_id(pattern[0])
|
||||||
|
assert False
|
||||||
|
except TypeError:
|
||||||
|
assert True
|
||||||
@@ -11,13 +11,13 @@ def _open_file(path):
|
|||||||
|
|
||||||
@aioresponses()
|
@aioresponses()
|
||||||
def test_Async(*mock):
|
def test_Async(*mock):
|
||||||
vid = ''
|
vid = '__test_id__'
|
||||||
_text = _open_file('tests/testdata/paramgen_firstread.json')
|
_text = _open_file('tests/testdata/paramgen_firstread.json')
|
||||||
_text = json.loads(_text)
|
_text = json.loads(_text)
|
||||||
mock[0].get(
|
mock[0].get(
|
||||||
f"https://www.youtube.com/live_chat?v={vid}&is_popout=1", status=200, body=_text)
|
f"https://www.youtube.com/live_chat?v={vid}&is_popout=1", status=200, body=_text)
|
||||||
try:
|
try:
|
||||||
chat = LiveChatAsync(video_id='')
|
chat = LiveChatAsync(video_id='__test_id__')
|
||||||
assert chat.is_alive()
|
assert chat.is_alive()
|
||||||
chat.terminate()
|
chat.terminate()
|
||||||
assert not chat.is_alive()
|
assert not chat.is_alive()
|
||||||
@@ -33,7 +33,7 @@ def test_MultiThread(mocker):
|
|||||||
responseMock.text = _text
|
responseMock.text = _text
|
||||||
mocker.patch('requests.Session.get').return_value = responseMock
|
mocker.patch('requests.Session.get').return_value = responseMock
|
||||||
try:
|
try:
|
||||||
chat = LiveChatAsync(video_id='')
|
chat = LiveChatAsync(video_id='__test_id__')
|
||||||
assert chat.is_alive()
|
assert chat.is_alive()
|
||||||
chat.terminate()
|
chat.terminate()
|
||||||
assert not chat.is_alive()
|
assert not chat.is_alive()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def test_async_live_stream(*mock):
|
|||||||
r'^https://www.youtube.com/live_chat/get_live_chat\?continuation=.*$')
|
r'^https://www.youtube.com/live_chat/get_live_chat\?continuation=.*$')
|
||||||
_text = _open_file('tests/testdata/test_stream.json')
|
_text = _open_file('tests/testdata/test_stream.json')
|
||||||
mock[0].get(pattern, status=200, body=_text)
|
mock[0].get(pattern, status=200, body=_text)
|
||||||
chat = LiveChatAsync(video_id='', processor=DummyProcessor())
|
chat = LiveChatAsync(video_id='__test_id__', processor=DummyProcessor())
|
||||||
chats = await chat.get()
|
chats = await chat.get()
|
||||||
rawdata = chats[0]["chatdata"]
|
rawdata = chats[0]["chatdata"]
|
||||||
# assert fetching livachat data
|
# assert fetching livachat data
|
||||||
@@ -60,7 +60,7 @@ def test_async_replay_stream(*mock):
|
|||||||
mock[0].get(pattern_live, status=200, body=_text_live)
|
mock[0].get(pattern_live, status=200, body=_text_live)
|
||||||
mock[0].get(pattern_replay, status=200, body=_text_replay)
|
mock[0].get(pattern_replay, status=200, body=_text_replay)
|
||||||
|
|
||||||
chat = LiveChatAsync(video_id='', processor=DummyProcessor())
|
chat = LiveChatAsync(video_id='__test_id__', processor=DummyProcessor())
|
||||||
chats = await chat.get()
|
chats = await chat.get()
|
||||||
rawdata = chats[0]["chatdata"]
|
rawdata = chats[0]["chatdata"]
|
||||||
# assert fetching replaychat data
|
# assert fetching replaychat data
|
||||||
@@ -93,7 +93,7 @@ def test_async_force_replay(*mock):
|
|||||||
mock[0].get(pattern_replay, status=200, body=_text_replay)
|
mock[0].get(pattern_replay, status=200, body=_text_replay)
|
||||||
# force replay
|
# force replay
|
||||||
chat = LiveChatAsync(
|
chat = LiveChatAsync(
|
||||||
video_id='', processor=DummyProcessor(), force_replay=True)
|
video_id='__test_id__', processor=DummyProcessor(), force_replay=True)
|
||||||
chats = await chat.get()
|
chats = await chat.get()
|
||||||
rawdata = chats[0]["chatdata"]
|
rawdata = chats[0]["chatdata"]
|
||||||
# assert fetching replaychat data
|
# assert fetching replaychat data
|
||||||
@@ -119,7 +119,7 @@ def test_multithread_live_stream(mocker):
|
|||||||
mocker.patch(
|
mocker.patch(
|
||||||
'requests.Session.get').return_value.__enter__.return_value = responseMock
|
'requests.Session.get').return_value.__enter__.return_value = responseMock
|
||||||
|
|
||||||
chat = LiveChat(video_id='test_id', processor=DummyProcessor())
|
chat = LiveChat(video_id='__test_id__', processor=DummyProcessor())
|
||||||
chats = chat.get()
|
chats = chat.get()
|
||||||
rawdata = chats[0]["chatdata"]
|
rawdata = chats[0]["chatdata"]
|
||||||
# assert fetching livachat data
|
# assert fetching livachat data
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from pytchat.tool.videoinfo import VideoInfo
|
from pytchat.tool.videoinfo import VideoInfo
|
||||||
from pytchat.exceptions import InvalidVideoIdException
|
from pytchat.exceptions import InvalidVideoIdException
|
||||||
import pytest
|
|
||||||
|
|
||||||
def _open_file(path):
|
def _open_file(path):
|
||||||
with open(path,mode ='r',encoding = 'utf-8') as f:
|
with open(path, mode='r', encoding='utf-8') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
def _set_test_data(filepath, mocker):
|
def _set_test_data(filepath, mocker):
|
||||||
_text = _open_file(filepath)
|
_text = _open_file(filepath)
|
||||||
response_mock = mocker.Mock()
|
response_mock = mocker.Mock()
|
||||||
@@ -13,23 +14,25 @@ def _set_test_data(filepath, mocker):
|
|||||||
response_mock.text = _text
|
response_mock.text = _text
|
||||||
mocker.patch('requests.get').return_value = response_mock
|
mocker.patch('requests.get').return_value = response_mock
|
||||||
|
|
||||||
|
|
||||||
def test_archived_page(mocker):
|
def test_archived_page(mocker):
|
||||||
_set_test_data('tests/testdata/videoinfo/archived_page.txt', mocker)
|
_set_test_data('tests/testdata/videoinfo/archived_page.txt', mocker)
|
||||||
info = VideoInfo('test_id')
|
info = VideoInfo('__test_id__')
|
||||||
actual_thumbnail_url = 'https://i.ytimg.com/vi/fzI9FNjXQ0o/hqdefault.jpg'
|
actual_thumbnail_url = 'https://i.ytimg.com/vi/fzI9FNjXQ0o/hqdefault.jpg'
|
||||||
assert info.video_id == 'test_id'
|
assert info.video_id == '__test_id__'
|
||||||
assert info.get_channel_name() == 'GitHub'
|
assert info.get_channel_name() == 'GitHub'
|
||||||
assert info.get_thumbnail() == actual_thumbnail_url
|
assert info.get_thumbnail() == actual_thumbnail_url
|
||||||
assert info.get_title() == 'GitHub Arctic Code Vault'
|
assert info.get_title() == 'GitHub Arctic Code Vault'
|
||||||
assert info.get_channel_id() == 'UC7c3Kb6jYCRj4JOHHZTxKsQ'
|
assert info.get_channel_id() == 'UC7c3Kb6jYCRj4JOHHZTxKsQ'
|
||||||
assert info.get_duration() == 148
|
assert info.get_duration() == 148
|
||||||
|
|
||||||
|
|
||||||
def test_live_page(mocker):
|
def test_live_page(mocker):
|
||||||
_set_test_data('tests/testdata/videoinfo/live_page.txt', mocker)
|
_set_test_data('tests/testdata/videoinfo/live_page.txt', mocker)
|
||||||
info = VideoInfo('test_id')
|
info = VideoInfo('__test_id__')
|
||||||
'''live page :duration = 0'''
|
'''live page :duration = 0'''
|
||||||
assert info.get_duration() == 0
|
assert info.get_duration() == 0
|
||||||
assert info.video_id == 'test_id'
|
assert info.video_id == '__test_id__'
|
||||||
assert info.get_channel_name() == 'BGM channel'
|
assert info.get_channel_name() == 'BGM channel'
|
||||||
assert info.get_thumbnail() == \
|
assert info.get_thumbnail() == \
|
||||||
'https://i.ytimg.com/vi/fEvM-OUbaKs/hqdefault_live.jpg'
|
'https://i.ytimg.com/vi/fEvM-OUbaKs/hqdefault_live.jpg'
|
||||||
@@ -38,25 +41,26 @@ def test_live_page(mocker):
|
|||||||
' - 24/7 Live Stream - Slow Jazz')
|
' - 24/7 Live Stream - Slow Jazz')
|
||||||
assert info.get_channel_id() == 'UCQINXHZqCU5i06HzxRkujfg'
|
assert info.get_channel_id() == 'UCQINXHZqCU5i06HzxRkujfg'
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_video_id(mocker):
|
def test_invalid_video_id(mocker):
|
||||||
'''Test case invalid video_id is specified.'''
|
'''Test case invalid video_id is specified.'''
|
||||||
_set_test_data(
|
_set_test_data(
|
||||||
'tests/testdata/videoinfo/invalid_video_id_page.txt', mocker)
|
'tests/testdata/videoinfo/invalid_video_id_page.txt', mocker)
|
||||||
try:
|
try:
|
||||||
_ = VideoInfo('test_id')
|
_ = VideoInfo('__test_id__')
|
||||||
assert False
|
assert False
|
||||||
except InvalidVideoIdException:
|
except InvalidVideoIdException:
|
||||||
assert True
|
assert True
|
||||||
|
|
||||||
|
|
||||||
def test_no_info(mocker):
|
def test_no_info(mocker):
|
||||||
'''Test case the video page has renderer, but no info.'''
|
'''Test case the video page has renderer, but no info.'''
|
||||||
_set_test_data(
|
_set_test_data(
|
||||||
'tests/testdata/videoinfo/no_info_page.txt', mocker)
|
'tests/testdata/videoinfo/no_info_page.txt', mocker)
|
||||||
info = VideoInfo('test_id')
|
info = VideoInfo('__test_id__')
|
||||||
assert info.video_id == 'test_id'
|
assert info.video_id == '__test_id__'
|
||||||
assert info.get_channel_name() is None
|
assert info.get_channel_name() is None
|
||||||
assert info.get_thumbnail() is None
|
assert info.get_thumbnail() is None
|
||||||
assert info.get_title() is None
|
assert info.get_title() is None
|
||||||
assert info.get_channel_id() is None
|
assert info.get_channel_id() is None
|
||||||
assert info.get_duration() is None
|
assert info.get_duration() is None
|
||||||
|
|
||||||
|
|||||||
100
tests/testdata/default/newSponsor_current.json
vendored
Normal file
100
tests/testdata/default/newSponsor_current.json
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"responseContext": {
|
||||||
|
"webResponseContextExtensionData": ""
|
||||||
|
},
|
||||||
|
"continuationContents": {
|
||||||
|
"liveChatContinuation": {
|
||||||
|
"continuations": [
|
||||||
|
{
|
||||||
|
"invalidationContinuationData": {
|
||||||
|
"invalidationId": {
|
||||||
|
"objectSource": 1000,
|
||||||
|
"objectId": "___objectId___",
|
||||||
|
"topic": "chat~00000000000~0000000",
|
||||||
|
"subscribeToGcmTopics": true,
|
||||||
|
"protoCreationTimestampMs": "1577804400000"
|
||||||
|
},
|
||||||
|
"timeoutMs": 10000,
|
||||||
|
"continuation": "___continuation___"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"addChatItemAction": {
|
||||||
|
"item": {
|
||||||
|
"liveChatMembershipItemRenderer": {
|
||||||
|
"id": "dummy_id",
|
||||||
|
"timestampUsec": 1570678496000000,
|
||||||
|
"authorExternalChannelId": "author_channel_id",
|
||||||
|
"headerSubtext": {
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"text": "新規メンバー"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"authorName": {
|
||||||
|
"simpleText": "author_name"
|
||||||
|
},
|
||||||
|
"authorPhoto": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 64,
|
||||||
|
"height": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"authorBadges": [
|
||||||
|
{
|
||||||
|
"liveChatAuthorBadgeRenderer": {
|
||||||
|
"customThumbnail": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/X=s32-c-k"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/X=s64-c-k"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tooltip": "新規メンバー",
|
||||||
|
"accessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "新規メンバー"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contextMenuEndpoint": {
|
||||||
|
"commandMetadata": {
|
||||||
|
"webCommandMetadata": {
|
||||||
|
"ignoreNavigation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"liveChatItemContextMenuEndpoint": {
|
||||||
|
"params": "___params___"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contextMenuAccessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "コメントの操作"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
tests/testdata/default/newSponsor_lagacy.json
vendored
Normal file
82
tests/testdata/default/newSponsor_lagacy.json
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"responseContext": {
|
||||||
|
"webResponseContextExtensionData": ""
|
||||||
|
},
|
||||||
|
"continuationContents": {
|
||||||
|
"liveChatContinuation": {
|
||||||
|
"continuations": [
|
||||||
|
{
|
||||||
|
"invalidationContinuationData": {
|
||||||
|
"invalidationId": {
|
||||||
|
"objectSource": 1000,
|
||||||
|
"objectId": "___objectId___",
|
||||||
|
"topic": "chat~00000000000~0000000",
|
||||||
|
"subscribeToGcmTopics": true,
|
||||||
|
"protoCreationTimestampMs": "1577804400000"
|
||||||
|
},
|
||||||
|
"timeoutMs": 10000,
|
||||||
|
"continuation": "___continuation___"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"addChatItemAction": {
|
||||||
|
"item": {
|
||||||
|
"liveChatLegacyPaidMessageRenderer": {
|
||||||
|
"id": "dummy_id",
|
||||||
|
"timestampUsec": 1570678496000000,
|
||||||
|
"eventText": {
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"text": "新規メンバー"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"detailText": {
|
||||||
|
"simpleText": "ようこそ、author_name!"
|
||||||
|
},
|
||||||
|
"authorName": {
|
||||||
|
"simpleText": "author_name"
|
||||||
|
},
|
||||||
|
"authorPhoto": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 64,
|
||||||
|
"height": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"authorExternalChannelId": "author_channel_id",
|
||||||
|
"contextMenuEndpoint": {
|
||||||
|
"clickTrackingParams": "___clickTrackingParams___",
|
||||||
|
"commandMetadata": {
|
||||||
|
"webCommandMetadata": {
|
||||||
|
"ignoreNavigation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"liveChatItemContextMenuEndpoint": {
|
||||||
|
"params": "___params___"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contextMenuAccessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "コメントの操作"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
tests/testdata/default/replay_member_text.json
vendored
Normal file
112
tests/testdata/default/replay_member_text.json
vendored
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"responseContext": {
|
||||||
|
"webResponseContextExtensionData": "data"
|
||||||
|
},
|
||||||
|
"continuationContents": {
|
||||||
|
"liveChatContinuation": {
|
||||||
|
"continuations": [
|
||||||
|
{
|
||||||
|
"liveChatReplayContinuationData": {
|
||||||
|
"invalidationId": {
|
||||||
|
"objectSource": 1000,
|
||||||
|
"objectId": "___objectId___",
|
||||||
|
"topic": "chat~00000000000~0000000",
|
||||||
|
"subscribeToGcmTopics": true,
|
||||||
|
"protoCreationTimestampMs": "1577804400000"
|
||||||
|
},
|
||||||
|
"timeoutMs": 10000,
|
||||||
|
"continuation": "___continuation___"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"replayChatItemAction": {
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"addChatItemAction": {
|
||||||
|
"item": {
|
||||||
|
"liveChatTextMessageRenderer": {
|
||||||
|
"message": {
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"text": "dummy_message"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"authorName": {
|
||||||
|
"simpleText": "author_name"
|
||||||
|
},
|
||||||
|
"authorPhoto": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 64,
|
||||||
|
"height": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contextMenuEndpoint": {
|
||||||
|
"clickTrackingParams": "___clickTrackingParams___",
|
||||||
|
"commandMetadata": {
|
||||||
|
"webCommandMetadata": {
|
||||||
|
"ignoreNavigation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"liveChatItemContextMenuEndpoint": {
|
||||||
|
"params": "___params___"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "dummy_id",
|
||||||
|
"timestampUsec": 1570678496000000,
|
||||||
|
"authorBadges": [
|
||||||
|
{
|
||||||
|
"liveChatAuthorBadgeRenderer": {
|
||||||
|
"customThumbnail": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/X=s16-c-k"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/X=s32-c-k"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tooltip": "メンバー(1 か月)",
|
||||||
|
"accessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "メンバー(1 か月)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authorExternalChannelId": "author_channel_id",
|
||||||
|
"contextMenuAccessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "コメントの操作"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestampText": {
|
||||||
|
"simpleText": "1:23:45"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clientId": "dummy_client_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"videoOffsetTimeMsec": "5025120"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
184
tests/testdata/default/superchat.json
vendored
Normal file
184
tests/testdata/default/superchat.json
vendored
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"responseContext": {
|
||||||
|
"webResponseContextExtensionData": ""
|
||||||
|
},
|
||||||
|
"continuationContents": {
|
||||||
|
"liveChatContinuation": {
|
||||||
|
"continuations": [
|
||||||
|
{
|
||||||
|
"invalidationContinuationData": {
|
||||||
|
"invalidationId": {
|
||||||
|
"objectSource": 1000,
|
||||||
|
"objectId": "___objectId___",
|
||||||
|
"topic": "chat~00000000000~0000000",
|
||||||
|
"subscribeToGcmTopics": true,
|
||||||
|
"protoCreationTimestampMs": "1577804400000"
|
||||||
|
},
|
||||||
|
"timeoutMs": 10000,
|
||||||
|
"continuation": "___continuation___"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"addChatItemAction": {
|
||||||
|
"item": {
|
||||||
|
"liveChatPaidMessageRenderer": {
|
||||||
|
"id": "dummy_id",
|
||||||
|
"timestampUsec": 1570678496000000,
|
||||||
|
"authorName": {
|
||||||
|
"simpleText": "author_name"
|
||||||
|
},
|
||||||
|
"authorPhoto": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 64,
|
||||||
|
"height": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"purchaseAmountText": {
|
||||||
|
"simpleText": "¥800"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"text": "dummy_message"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"headerBackgroundColor": 4278239141,
|
||||||
|
"headerTextColor": 4278190080,
|
||||||
|
"bodyBackgroundColor": 4280150454,
|
||||||
|
"bodyTextColor": 4278190080,
|
||||||
|
"authorExternalChannelId": "author_channel_id",
|
||||||
|
"authorNameTextColor": 2315255808,
|
||||||
|
"contextMenuEndpoint": {
|
||||||
|
"commandMetadata": {
|
||||||
|
"webCommandMetadata": {
|
||||||
|
"ignoreNavigation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"liveChatItemContextMenuEndpoint": {
|
||||||
|
"params": "___params___"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestampColor": 2147483648,
|
||||||
|
"contextMenuAccessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "コメントの操作"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addLiveChatTickerItemAction": {
|
||||||
|
"item": {
|
||||||
|
"liveChatTickerPaidMessageItemRenderer": {
|
||||||
|
"id": "dummy_id",
|
||||||
|
"amount": {
|
||||||
|
"simpleText": "¥846"
|
||||||
|
},
|
||||||
|
"amountTextColor": 4278190080,
|
||||||
|
"startBackgroundColor": 4280150454,
|
||||||
|
"endBackgroundColor": 4278239141,
|
||||||
|
"authorPhoto": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 64,
|
||||||
|
"height": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"durationSec": 120,
|
||||||
|
"showItemEndpoint": {
|
||||||
|
"commandMetadata": {
|
||||||
|
"webCommandMetadata": {
|
||||||
|
"ignoreNavigation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"showLiveChatItemEndpoint": {
|
||||||
|
"renderer": {
|
||||||
|
"liveChatPaidMessageRenderer": {
|
||||||
|
"id": "dummy_id",
|
||||||
|
"timestampUsec": 1570678496000000,
|
||||||
|
"authorName": {
|
||||||
|
"simpleText": "author_name"
|
||||||
|
},
|
||||||
|
"authorPhoto": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 64,
|
||||||
|
"height": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"purchaseAmountText": {
|
||||||
|
"simpleText": "¥846"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"text": "dummy_message"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"headerBackgroundColor": 4278239141,
|
||||||
|
"headerTextColor": 4278190080,
|
||||||
|
"bodyBackgroundColor": 4280150454,
|
||||||
|
"bodyTextColor": 4278190080,
|
||||||
|
"authorExternalChannelId": "author_channel_id",
|
||||||
|
"authorNameTextColor": 2315255808,
|
||||||
|
"contextMenuEndpoint": {
|
||||||
|
"commandMetadata": {
|
||||||
|
"webCommandMetadata": {
|
||||||
|
"ignoreNavigation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"liveChatItemContextMenuEndpoint": {
|
||||||
|
"params": "___params___"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestampColor": 2147483648,
|
||||||
|
"contextMenuAccessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "コメントの操作"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"authorExternalChannelId": "http://www.youtube.com/channel/author_channel_url",
|
||||||
|
"fullDurationSec": 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"durationSec": "120"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
tests/testdata/default/supersticker.json
vendored
Normal file
99
tests/testdata/default/supersticker.json
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"responseContext": {
|
||||||
|
"webResponseContextExtensionData": ""
|
||||||
|
},
|
||||||
|
"continuationContents": {
|
||||||
|
"liveChatContinuation": {
|
||||||
|
"continuations": [
|
||||||
|
{
|
||||||
|
"invalidationContinuationData": {
|
||||||
|
"invalidationId": {
|
||||||
|
"objectSource": 1000,
|
||||||
|
"objectId": "___objectId___",
|
||||||
|
"topic": "chat~00000000000~0000000",
|
||||||
|
"subscribeToGcmTopics": true,
|
||||||
|
"protoCreationTimestampMs": "1577804400000"
|
||||||
|
},
|
||||||
|
"timeoutMs": 10000,
|
||||||
|
"continuation": "___continuation___"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"addChatItemAction": {
|
||||||
|
"item": {
|
||||||
|
"liveChatPaidStickerRenderer": {
|
||||||
|
"id": "dummy_id",
|
||||||
|
"contextMenuEndpoint": {
|
||||||
|
"commandMetadata": {
|
||||||
|
"webCommandMetadata": {
|
||||||
|
"ignoreNavigation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"liveChatItemContextMenuEndpoint": {
|
||||||
|
"params": "___params___"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contextMenuAccessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "コメントの操作"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestampUsec": 1570678496000000,
|
||||||
|
"authorPhoto": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 64,
|
||||||
|
"height": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"authorName": {
|
||||||
|
"simpleText": "author_name"
|
||||||
|
},
|
||||||
|
"authorExternalChannelId": "author_channel_id",
|
||||||
|
"sticker": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "//lh3.googleusercontent.com/param_s=s72-rp",
|
||||||
|
"width": 72,
|
||||||
|
"height": 72
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "//lh3.googleusercontent.com/param_s=s144-rp",
|
||||||
|
"width": 144,
|
||||||
|
"height": 144
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "___sticker_label___"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"moneyChipBackgroundColor": 4278248959,
|
||||||
|
"moneyChipTextColor": 4278190080,
|
||||||
|
"purchaseAmountText": {
|
||||||
|
"simpleText": "¥200"
|
||||||
|
},
|
||||||
|
"stickerDisplayWidth": 72,
|
||||||
|
"stickerDisplayHeight": 72,
|
||||||
|
"backgroundColor": 4278237396,
|
||||||
|
"authorNameTextColor": 3003121664
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
tests/testdata/default/textmessage.json
vendored
Normal file
79
tests/testdata/default/textmessage.json
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"responseContext": {
|
||||||
|
"webResponseContextExtensionData": ""
|
||||||
|
},
|
||||||
|
"continuationContents": {
|
||||||
|
"liveChatContinuation": {
|
||||||
|
"continuations": [
|
||||||
|
{
|
||||||
|
"invalidationContinuationData": {
|
||||||
|
"invalidationId": {
|
||||||
|
"objectSource": 1000,
|
||||||
|
"objectId": "___objectId___",
|
||||||
|
"topic": "chat~00000000000~0000000",
|
||||||
|
"subscribeToGcmTopics": true,
|
||||||
|
"protoCreationTimestampMs": "1577804400000"
|
||||||
|
},
|
||||||
|
"timeoutMs": 10000,
|
||||||
|
"continuation": "___continuation___"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"addChatItemAction": {
|
||||||
|
"item": {
|
||||||
|
"liveChatTextMessageRenderer": {
|
||||||
|
"message": {
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"text": "dummy_message"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"authorName": {
|
||||||
|
"simpleText": "author_name"
|
||||||
|
},
|
||||||
|
"authorPhoto": {
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg",
|
||||||
|
"width": 64,
|
||||||
|
"height": 64
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contextMenuEndpoint": {
|
||||||
|
"commandMetadata": {
|
||||||
|
"webCommandMetadata": {
|
||||||
|
"ignoreNavigation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"liveChatItemContextMenuEndpoint": {
|
||||||
|
"params": "___params___"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "dummy_id",
|
||||||
|
"timestampUsec": 1570678496000000,
|
||||||
|
"authorExternalChannelId": "author_channel_id",
|
||||||
|
"contextMenuAccessibility": {
|
||||||
|
"accessibilityData": {
|
||||||
|
"label": "コメントの操作"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clientId": "dummy_client_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user