diff --git a/pytchat/core/pytchat.py b/pytchat/core/pytchat.py index 7ab76f6..23d0441 100644 --- a/pytchat/core/pytchat.py +++ b/pytchat/core/pytchat.py @@ -42,6 +42,10 @@ class PytchatCore: topchat_only : bool If True, get only top chat. + hold_exception : bool [default:True] + If True, when exceptions occur, the exception is holded internally, + and can be raised by raise_for_status(). + Attributes --------- _is_alive : bool @@ -56,7 +60,8 @@ class PytchatCore: interruptable=True, force_replay=False, topchat_only=False, - logger=config.logger(__name__) + hold_exception=True, + logger=config.logger(__name__), ): self._video_id = extract_video_id(video_id) self.seektime = seektime @@ -66,12 +71,16 @@ class PytchatCore: self.processor = processor self._is_alive = True self._is_replay = force_replay - self._parser = Parser(is_replay=self._is_replay) + self._hold_exception = hold_exception + self._exception_holder = None + self._parser = Parser( + is_replay=self._is_replay, + exception_holder=self._exception_holder + ) self._first_fetch = True self._fetch_url = "live_chat/get_live_chat?continuation=" self._topchat_only = topchat_only self._logger = logger - self.exception = None if interruptable: signal.signal(signal.SIGINT, lambda a, b: self.terminate()) self._setup() @@ -108,13 +117,13 @@ class PytchatCore: return chat_component except exceptions.ChatParseException as e: self._logger.debug(f"[{self._video_id}]{str(e)}") - raise - except (TypeError, json.JSONDecodeError): + self._raise_exception(e) + except (TypeError, json.JSONDecodeError) as e: self._logger.error(f"{traceback.format_exc(limit=-1)}") - raise + self._raise_exception(e) self._logger.debug(f"[{self._video_id}]finished fetching chat.") - raise exceptions.ChatDataFinished + self._raise_exception(exceptions.ChatDataFinished) def _get_contents(self, continuation, client, headers): '''Get 'continuationContents' from livechat json. @@ -167,7 +176,7 @@ class PytchatCore: else: self._logger.error(f"[{self._video_id}]" f"Exceeded retry count. Last error: {str(err)}") - raise exceptions.RetryExceedMaxCount() + self._raise_exception(exceptions.RetryExceedMaxCount()) return livechat_json def get(self): @@ -188,5 +197,11 @@ class PytchatCore: self.processor.finalize() def raise_for_status(self): - if self.exception is not None: - raise self.exception + if self._exception_holder is not None: + raise self._exception_holder + + def _raise_exception(self, exception: Exception = None): + self._is_alive = False + if self._hold_exception is False: + raise exception + self._exception_holder = exception diff --git a/pytchat/parser/live.py b/pytchat/parser/live.py index 13540cb..c3e10b3 100644 --- a/pytchat/parser/live.py +++ b/pytchat/parser/live.py @@ -8,15 +8,26 @@ from .. import exceptions class Parser: + ''' + Parser of chat json. + + Parameter + ---------- + is_replay : bool - __slots__ = ['is_replay'] + 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): + 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: - raise exceptions.IllegalFunctionCall('Called with none JSON object.') + self.raise_exception(exceptions.IllegalFunctionCall('Called with none JSON object.')) if jsn['response']['responseContext'].get('errors'): raise exceptions.ResponseContextError( 'The video_id would be wrong, or video is deleted or private.') @@ -42,11 +53,11 @@ class Parser: if contents is None: '''Broadcasting end or cannot fetch chat stream''' - raise exceptions.NoContents('Chat data stream is empty.') + self.raise_exception(exceptions.NoContents('Chat data stream is empty.')) cont = contents['liveChatContinuation']['continuations'][0] if cont is None: - raise exceptions.NoContinuation('No Continuation') + self.raise_exception(exceptions.NoContinuation('No Continuation')) metadata = (cont.get('invalidationContinuationData') or cont.get('timedContinuationData') or cont.get('reloadContinuationData') @@ -54,13 +65,13 @@ class Parser: ) if metadata is None: if cont.get("playerSeekContinuationData"): - raise exceptions.ChatDataFinished('Finished chat data') + self.raise_exception(exceptions.ChatDataFinished('Finished chat data')) unknown = list(cont.keys())[0] if unknown: - raise exceptions.ReceivedUnknownContinuation( - f"Received unknown continuation type:{unknown}") + self.raise_exception(exceptions.ReceivedUnknownContinuation( + f"Received unknown continuation type:{unknown}")) else: - raise exceptions.FailedExtractContinuation('Cannot extract continuation data') + self.raise_exception(exceptions.FailedExtractContinuation('Cannot extract continuation data')) return self._create_data(metadata, contents) def reload_continuation(self, contents): @@ -72,7 +83,7 @@ class Parser: """ if contents is None: '''Broadcasting end or cannot fetch chat stream''' - raise exceptions.NoContents('Chat data stream is empty.') + self.raise_exception(exceptions.NoContents('Chat data stream is empty.')) cont = contents['liveChatContinuation']['continuations'][0] if cont.get("liveChatReplayContinuationData"): # chat data exist. @@ -81,7 +92,7 @@ class Parser: init_cont = cont.get("playerSeekContinuationData") if init_cont: return init_cont.get("continuation") - raise exceptions.ChatDataFinished('Finished chat data') + self.raise_exception(exceptions.ChatDataFinished('Finished chat data')) def _create_data(self, metadata, contents): actions = contents['liveChatContinuation'].get('actions') @@ -103,3 +114,8 @@ class Parser: start = int(actions[0]["replayChatItemAction"]["videoOffsetTimeMsec"]) last = int(actions[-1]["replayChatItemAction"]["videoOffsetTimeMsec"]) return (last - start) + + def raise_exception(self, exception): + if self.exception_holder is None: + raise exception + self.exception_holder = exception