123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
"""
|
|
pytchat.parser.live
|
|
~~~~~~~~~~~~~~~~~~~
|
|
Parser of live chat JSON.
|
|
"""
|
|
|
|
from .. import exceptions
|
|
|
|
|
|
class Parser:
|
|
'''
|
|
Parser of chat json.
|
|
|
|
Parameter
|
|
----------
|
|
is_replay : bool
|
|
|
|
exception_holder : Object [default:Npne]
|
|
The object holding exceptions.
|
|
This is passed from the parent livechat object.
|
|
'''
|
|
__slots__ = ['is_replay', 'exception_holder']
|
|
|
|
def __init__(self, is_replay, exception_holder=None):
|
|
self.is_replay = is_replay
|
|
self.exception_holder = exception_holder
|
|
|
|
def get_contents(self, jsn):
|
|
if jsn is None:
|
|
self.raise_exception(exceptions.IllegalFunctionCall('Called with none JSON object.'))
|
|
if jsn.get("error") or jsn.get("responseContext", {}).get("errors"):
|
|
raise exceptions.ResponseContextError(
|
|
'The video_id would be wrong, or video is deleted or private.')
|
|
contents = jsn.get('continuationContents')
|
|
visitor_data = jsn.get("responseContext", {}).get("visitorData")
|
|
return contents, visitor_data
|
|
|
|
def parse(self, contents):
|
|
"""
|
|
Parameter
|
|
----------
|
|
+ contents : dict
|
|
+ JSON of chat data from YouTube.
|
|
|
|
Returns
|
|
-------
|
|
tuple:
|
|
+ metadata : dict
|
|
+ timeout
|
|
+ video_id
|
|
+ continuation
|
|
+ chatdata : List[dict]
|
|
"""
|
|
|
|
if contents is None:
|
|
'''Broadcasting end or cannot fetch chat stream'''
|
|
self.raise_exception(exceptions.NoContents('Chat data stream is empty.'))
|
|
|
|
cont = contents['liveChatContinuation']['continuations'][0]
|
|
if cont is None:
|
|
self.raise_exception(exceptions.NoContinuation('No Continuation'))
|
|
metadata = (cont.get('invalidationContinuationData')
|
|
or cont.get('timedContinuationData')
|
|
or cont.get('reloadContinuationData')
|
|
or cont.get('liveChatReplayContinuationData')
|
|
)
|
|
if metadata is None:
|
|
if cont.get("playerSeekContinuationData"):
|
|
self.raise_exception(exceptions.ChatDataFinished('Finished chat data'))
|
|
unknown = list(cont.keys())[0]
|
|
if unknown:
|
|
self.raise_exception(exceptions.ReceivedUnknownContinuation(
|
|
f"Received unknown continuation type:{unknown}"))
|
|
else:
|
|
self.raise_exception(exceptions.FailedExtractContinuation('Cannot extract continuation data'))
|
|
return self._create_data(metadata, contents)
|
|
|
|
def reload_continuation(self, contents):
|
|
"""
|
|
When `seektime == 0` or seektime is abbreviated ,
|
|
check if fetched chat json has no chat data.
|
|
If so, try to fetch playerSeekContinuationData.
|
|
This function must be run only first fetching.
|
|
"""
|
|
if contents is None:
|
|
'''Broadcasting end or cannot fetch chat stream'''
|
|
self.raise_exception(exceptions.NoContents('Chat data stream is empty.'))
|
|
cont = contents['liveChatContinuation']['continuations'][0]
|
|
|
|
if cont.get("liveChatReplayContinuationData"):
|
|
# chat data exist.
|
|
return None
|
|
# chat data do not exist, get playerSeekContinuationData.
|
|
init_cont = cont.get("playerSeekContinuationData")
|
|
if init_cont:
|
|
return init_cont.get("continuation")
|
|
self.raise_exception(exceptions.ChatDataFinished('Finished chat data'))
|
|
|
|
def _create_data(self, metadata, contents):
|
|
actions = contents['liveChatContinuation'].get('actions')
|
|
if self.is_replay:
|
|
last_offset_ms = self._get_lastoffset(actions)
|
|
metadata.setdefault("timeoutMs", 5000)
|
|
metadata.setdefault("last_offset_ms", last_offset_ms)
|
|
"""Archived chat has different structures than live chat,
|
|
so make it the same format."""
|
|
chatdata = [action["replayChatItemAction"]["actions"][0]
|
|
for action in actions]
|
|
else:
|
|
metadata.setdefault('timeoutMs', 5000)
|
|
chatdata = actions
|
|
return metadata, chatdata
|
|
|
|
def _get_lastoffset(self, actions: list):
|
|
if actions:
|
|
return int(actions[-1]["replayChatItemAction"]["videoOffsetTimeMsec"])
|
|
return 0
|
|
|
|
def raise_exception(self, exception):
|
|
if self.exception_holder is None:
|
|
raise exception
|
|
self.exception_holder = exception
|