Compare commits

...

26 Commits

Author SHA1 Message Date
taizan-hokuto
044fe97aa5 Merge branch 'release/0.0.4.2' 2020-01-02 10:25:26 +09:00
taizan-hokuto
585a4be7dc Increment version 2020-01-02 10:22:29 +09:00
taizan-hokuto
b84a82341e Fix README 2020-01-01 21:13:39 +09:00
taizan-hokuto
b4f3307b1c Fix comment 2020-01-01 20:08:07 +09:00
taizan-hokuto
be7ac97c62 Modify tuple comprehension 2019-12-31 01:01:27 +09:00
taizan-hokuto
f8de4e7e39 Merge branch 'hotfix' into develop 2019-12-30 19:12:17 +09:00
taizan-hokuto
ac0f052aa0 Merge branch 'hotfix' 2019-12-30 19:11:39 +09:00
taizan-hokuto
1cc0338a8e Export DefaultProcessor 2019-12-30 19:09:12 +09:00
taizan-hokuto
f6b8229998 Merge branch 'release' 2019-12-30 18:33:41 +09:00
taizan-hokuto
f8bcc8a453 Merge branch 'release' into develop 2019-12-30 18:32:43 +09:00
taizan-hokuto
f24c5f9e30 Increment version 2019-12-30 18:32:17 +09:00
taizan-hokuto
5268961854 Delete unnecessary lines of old logger 2019-12-30 17:54:53 +09:00
taizan-hokuto
733f754e11 Merge branch 'feature/fix_replaychat_is_alive' into develop 2019-12-30 17:32:50 +09:00
taizan-hokuto
582d0b749d Add comment 2019-12-30 17:32:23 +09:00
taizan-hokuto
b8bc00d880 Fix README 2019-12-30 17:27:25 +09:00
taizan-hokuto
ce96d94e23 Fix termination of fetching chat data 2019-12-30 17:10:34 +09:00
taizan-hokuto
7af92f14c0 Merge branch 'feature/config_logger' into develop 2019-12-30 15:01:32 +09:00
taizan-hokuto
7305e4178b Change description of getting logger 2019-12-30 14:38:02 +09:00
taizan-hokuto
a835d58e10 Merge branch 'feature/combinator' into develop 2019-12-30 11:04:25 +09:00
taizan-hokuto
4e956b8d84 Implement Combinator, DummyProcessor 2019-12-30 11:02:29 +09:00
taizan-hokuto
c4f1194a53 Merge branch 'feature/fix_massage_ex' into develop 2019-12-30 10:36:22 +09:00
taizan-hokuto
90b10a9f8f Integrate rendering message and message_ex 2019-12-27 02:19:08 +09:00
taizan-hokuto
b576c3f928 Merge branch 'develop' 2019-12-24 00:57:06 +09:00
taizan-hokuto
c0728e1366 Increment version 2019-12-24 00:55:00 +09:00
taizan-hokuto
fff09d4c27 Fix README 2019-12-24 00:52:53 +09:00
taizan-hokuto
810b6c8c6b Fix README 2019-12-24 00:29:10 +09:00
18 changed files with 220 additions and 122 deletions

View File

@@ -27,7 +27,7 @@ pip install pytchat
```python ```python
from pytchat import LiveChat from pytchat import LiveChat
chat = LiveChat("G1w62uEMZ74") chat = LiveChat("rsHWP7IjMiw")
while chat.is_alive(): while chat.is_alive():
data = chat.get() data = chat.get()
for c in data.items: for c in data.items:
@@ -40,82 +40,77 @@ while chat.is_alive():
from pytchat import LiveChat from pytchat import LiveChat
import time import time
def main() #callback function is automatically called.
chat = LiveChat("G1w62uEMZ74", callback = func) def display(data):
while chat.is_alive():
time.sleep(3)
#other background operation.
#callback function is automatically called periodically.
def func(data):
for c in data.items: for c in data.items:
print(f"{c.datetime} [{c.author.name}]-{c.message} {c.amountString}") print(f"{c.datetime} [{c.author.name}]-{c.message} {c.amountString}")
data.tick() data.tick()
#entry point
chat = LiveChat("rsHWP7IjMiw", callback = display)
while chat.is_alive():
time.sleep(3)
#other background operation.
``` ```
### asyncio context: ### asyncio context:
```python ```python
from pytchat import LiveChatAsync from pytchat import LiveChatAsync
from concurrent.futures import CancelledError
import asyncio import asyncio
async def main(): async def main():
chat = LiveChatAsync("G1w62uEMZ74", callback = func) chat = LiveChatAsync("rsHWP7IjMiw", callback = func)
while chat.is_alive(): while chat.is_alive():
await asyncio.sleep(3) await asyncio.sleep(3)
#other background operation. #other background operation.
#callback function is automatically called periodically. #callback function is automatically called.
async def func(data): async def func(data):
for c in data.items: for c in data.items:
print(f"{c.datetime} [{c.author.name}]-{c.message} {c.amountString}") print(f"{c.datetime} [{c.author.name}]-{c.message} {c.amountString}")
await data.tick_async() await data.tick_async()
loop = asyncio.get_event_loop() try:
loop.run_until_complete(main()) loop = asyncio.get_event_loop()
loop.run_until_complete(main())
except CancelledError:
pass
``` ```
### youtube api compatible processor: ### youtube api compatible processor:
```python ```python
from pytchat import LiveChat, CompatibleProcessor from pytchat import LiveChat, CompatibleProcessor
import time
chat = LiveChat("G1w62uEMZ74", chat = LiveChat("rsHWP7IjMiw",
processor = CompatibleProcessor() ) processor = CompatibleProcessor() )
while chat.is_alive(): while chat.is_alive():
data = chat.get() data = chat.get()
polling = data["pollingIntervalMillis"]/1000 polling = data['pollingIntervalMillis']/1000
for c in data["items"]: for c in data['items']:
if c.get("snippet"): if c.get('snippet'):
print(f"[{c['authorDetails']['displayName']}]" print(f"[{c['authorDetails']['displayName']}]"
f"-{c['snippet']['displayMessage']}") f"-{c['snippet']['displayMessage']}")
time.sleep(polling/len(data["items"])) time.sleep(polling/len(data['items']))
``` ```
### replay: ### replay:
```python ```python
from pytchat import ReplayChatAsync from pytchat import ReplayChat
import asyncio
async def main(): def main():
chat = ReplayChatAsync("G1w62uEMZ74", seektime = 1000, callback = func) #seektime (seconds): start position of chat.
chat = ReplayChat("ojes5ULOqhc", seektime = 60*30)
while chat.is_alive(): while chat.is_alive():
await asyncio.sleep(3) data = chat.get()
#other background operation here. for c in data.items:
print(f"{c.elapsedTime} [{c.author.name}]-{c.message} {c.amountString}")
data.tick()
#callback function is automatically called periodically. main()
async def func(data):
for count in range(0,len(data.items)):
c= data.items[count]
if count!=len(data.items):
tick=data.items[count+1].timestamp -data.items[count].timestamp
else:
tick=0
print(f"<{c.elapsedTime}> [{c.author.name}]-{c.message} {c.amountString}")
await asyncio.sleep(tick/1000)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
``` ```
## Structure of Default Processor ## Structure of Default Processor
@@ -154,26 +149,26 @@ Each item can be got with items() function.
<tr> <tr>
<td>datetime</td> <td>datetime</td>
<td>str</td> <td>str</td>
<td>ex. "2019-10-10 12:34:56"</td> <td>e.g. "2019-10-10 12:34:56"</td>
</tr> </tr>
<td>elapsedTime</td> <td>elapsedTime</td>
<td>str</td> <td>str</td>
<td>elapsed time. (ex. "1:02:27") *Replay Only.</td> <td>elapsed time. (e.g. "1:02:27") *Replay Only.</td>
</tr> </tr>
<tr> <tr>
<td>amountValue</td> <td>amountValue</td>
<td>float</td> <td>float</td>
<td>ex. 1,234.0</td> <td>e.g. 1,234.0</td>
</tr> </tr>
<tr> <tr>
<td>amountString</td> <td>amountString</td>
<td>str</td> <td>str</td>
<td>ex. "$ 1,234"</td> <td>e.g. "$ 1,234"</td>
</tr> </tr>
<tr> <tr>
<td>currency</td> <td>currency</td>
<td>str</td> <td>str</td>
<td><a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 currency codes</a> (ex. "USD")</td> <td><a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 currency codes</a> (e.g. "USD")</td>
</tr> </tr>
<tr> <tr>
<td>bgColor</td> <td>bgColor</td>
@@ -202,7 +197,7 @@ Structure of author object.
<tr> <tr>
<td>channelId</td> <td>channelId</td>
<td>str</td> <td>str</td>
<td>*chatter's channel ID. NOT broadcasting video's channel ID.</td> <td>*chatter's channel ID.</td>
</tr> </tr>
<tr> <tr>
<td>channelUrl</td> <td>channelUrl</td>

View File

@@ -2,7 +2,7 @@
pytchat is a python library for fetching youtube live chat without using yt api, Selenium, or BeautifulSoup. pytchat is a python library for fetching youtube live chat without using yt api, Selenium, or BeautifulSoup.
""" """
__copyright__ = 'Copyright (C) 2019 taizan-hokuto' __copyright__ = 'Copyright (C) 2019 taizan-hokuto'
__version__ = '0.0.3.7' __version__ = '0.0.4.2'
__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'
@@ -11,13 +11,16 @@ __url__ = 'https://github.com/taizan-hokuto/pytchat'
__all__ = ["core_async","core_multithread","processors"] __all__ = ["core_async","core_multithread","processors"]
from .api import ( from .api import (
config,
LiveChat, LiveChat,
LiveChatAsync, LiveChatAsync,
ReplayChat, ReplayChat,
ReplayChatAsync, ReplayChatAsync,
ChatProcessor, ChatProcessor,
CompatibleProcessor, CompatibleProcessor,
DefaultProcessor,
SimpleDisplayProcessor, SimpleDisplayProcessor,
JsonfileArchiveProcessor, JsonfileArchiveProcessor,
SpeedCalculator SpeedCalculator,
) DummyProcessor
)

View File

@@ -8,3 +8,5 @@ from .processors.compatible.processor import CompatibleProcessor
from .processors.simple_display_processor import SimpleDisplayProcessor from .processors.simple_display_processor import SimpleDisplayProcessor
from .processors.jsonfile_archive_processor import JsonfileArchiveProcessor from .processors.jsonfile_archive_processor import JsonfileArchiveProcessor
from .processors.speed_calculator import SpeedCalculator from .processors.speed_calculator import SpeedCalculator
from .processors.dummy_processor import DummyProcessor
from . import config

View File

@@ -1,4 +1,13 @@
import logging import logging
from . import mylogger
LOGGER_MODE = None LOGGER_MODE = None
headers = { headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'} 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'}
def logger(module_name: str):
module_logger = mylogger.get_logger(module_name, mode = LOGGER_MODE)
return module_logger

View File

@@ -0,0 +1,32 @@
from logging import NullHandler, getLogger, StreamHandler, FileHandler, Formatter
import logging
import datetime
def get_logger(modname,mode=logging.DEBUG):
logger = getLogger(modname)
if mode == None:
logger.addHandler(NullHandler())
return logger
logger.setLevel(mode)
#create handler1 for showing info
handler1 = StreamHandler()
my_formatter = MyFormatter()
handler1.setFormatter(my_formatter)
handler1.setLevel(mode)
logger.addHandler(handler1)
#create handler2 for recording log file
if mode <= logging.DEBUG:
handler2 = FileHandler(filename="log.txt", encoding='utf-8')
handler2.setLevel(logging.ERROR)
handler2.setFormatter(my_formatter)
logger.addHandler(handler2)
return logger
class MyFormatter(logging.Formatter):
def format(self, record):
s =(datetime.datetime.fromtimestamp(record.created)).strftime("%m-%d %H:%M:%S")+'| '+ (record.module).ljust(15)+(' { '+record.funcName).ljust(20) +":"+str(record.lineno).rjust(4)+'} - '+record.getMessage()
return s

View File

@@ -11,15 +11,14 @@ from concurrent.futures import CancelledError
from .buffer import Buffer from .buffer import Buffer
from ..parser.live import Parser from ..parser.live import Parser
from .. import config from .. import config
from .. import mylogger
from ..exceptions import ChatParseException,IllegalFunctionCall from ..exceptions import ChatParseException,IllegalFunctionCall
from ..paramgen import liveparam from ..paramgen import liveparam
from ..processors.default.processor import DefaultProcessor from ..processors.default.processor import DefaultProcessor
from ..processors.combinator import Combinator
logger = mylogger.get_logger(__name__,mode=config.LOGGER_MODE) logger = config.logger(__name__)
MAX_RETRY = 10
headers = config.headers headers = config.headers
MAX_RETRY = 10
class LiveChatAsync: class LiveChatAsync:
@@ -72,7 +71,10 @@ class LiveChatAsync:
exception_handler = None, exception_handler = None,
direct_mode = False): direct_mode = False):
self.video_id = video_id self.video_id = video_id
self.processor = processor if isinstance(processor, tuple):
self.processor = Combinator(processor)
else:
self.processor = processor
self._buffer = buffer self._buffer = buffer
self._callback = callback self._callback = callback
self._done_callback = done_callback self._done_callback = done_callback
@@ -148,7 +150,7 @@ class LiveChatAsync:
async def _listen(self, continuation): async def _listen(self, continuation):
''' continuationに紐付いたチャットデータを取得し ''' continuationに紐付いたチャットデータを取得し
チャットデータを格納、 Bufferにチャットデータを格納、
次のcontinuaitonを取得してループする。 次のcontinuaitonを取得してループする。
Parameter Parameter
@@ -180,9 +182,11 @@ class LiveChatAsync:
await asyncio.sleep(diff_time) await asyncio.sleep(diff_time)
continuation = metadata.get('continuation') continuation = metadata.get('continuation')
except ChatParseException as e: except ChatParseException as e:
logger.info(f"{str(e)}video_id:\"{self.video_id}\"") self.terminate()
logger.error(f"{str(e)}video_id:\"{self.video_id}\"")
return return
except (TypeError , json.JSONDecodeError) : except (TypeError , json.JSONDecodeError) :
self.terminate()
logger.error(f"{traceback.format_exc(limit = -1)}") logger.error(f"{traceback.format_exc(limit = -1)}")
return return
@@ -211,6 +215,7 @@ class LiveChatAsync:
else: else:
logger.error(f"[{self.video_id}]" logger.error(f"[{self.video_id}]"
f"Exceeded retry count. status_code={status_code}") f"Exceeded retry count. status_code={status_code}")
self.terminate()
return None return None
return livechat_json return livechat_json

View File

@@ -12,12 +12,12 @@ from queue import Queue
from .buffer import Buffer from .buffer import Buffer
from ..parser.replay import Parser from ..parser.replay import Parser
from .. import config from .. import config
from .. import mylogger
from ..exceptions import ChatParseException,IllegalFunctionCall from ..exceptions import ChatParseException,IllegalFunctionCall
from ..paramgen import arcparam from ..paramgen import arcparam
from ..processors.default.processor import DefaultProcessor from ..processors.default.processor import DefaultProcessor
from ..processors.combinator import Combinator
logger = mylogger.get_logger(__name__,mode=config.LOGGER_MODE) logger = config.logger(__name__)
MAX_RETRY = 10 MAX_RETRY = 10
headers = config.headers headers = config.headers
@@ -78,7 +78,10 @@ class ReplayChatAsync:
direct_mode = False): direct_mode = False):
self.video_id = video_id self.video_id = video_id
self.seektime = seektime self.seektime = seektime
self.processor = processor if isinstance(processor, tuple):
self.processor = Combinator(processor)
else:
self.processor = processor
self._buffer = buffer self._buffer = buffer
self._callback = callback self._callback = callback
self._done_callback = done_callback self._done_callback = done_callback
@@ -194,13 +197,15 @@ class ReplayChatAsync:
await asyncio.sleep(diff_time) await asyncio.sleep(diff_time)
continuation = metadata.get('continuation') continuation = metadata.get('continuation')
except ChatParseException as e: except ChatParseException as e:
logger.info(f"{str(e)}video_id:\"{self.video_id}\"") logger.error(f"{str(e)}video_id:\"{self.video_id}\"")
return return
except (TypeError , json.JSONDecodeError) : except (TypeError , json.JSONDecodeError) :
logger.error(f"{traceback.format_exc(limit = -1)}") logger.error(f"{traceback.format_exc(limit = -1)}")
self.terminate()
return return
logger.debug(f"[{self.video_id}]チャット取得を終了しました。") logger.debug(f"[{self.video_id}]チャット取得を終了しました。")
self.terminate()
async def _get_livechat_json(self, continuation, session, headers): async def _get_livechat_json(self, continuation, session, headers):
''' '''
@@ -282,7 +287,7 @@ class ReplayChatAsync:
if self._direct_mode == False: if self._direct_mode == False:
#bufferにダミーオブジェクトを入れてis_alive()を判定させる #bufferにダミーオブジェクトを入れてis_alive()を判定させる
self._buffer.put_nowait({'chatdata':'','timeout':1}) self._buffer.put_nowait({'chatdata':'','timeout':1})
logger.info(f'終了しました:[{self.video_id}]') logger.info(f'[{self.video_id}]終了しました')
@classmethod @classmethod
def _set_exception_handler(cls, handler): def _set_exception_handler(cls, handler):

View File

@@ -10,15 +10,14 @@ from concurrent.futures import CancelledError, ThreadPoolExecutor
from .buffer import Buffer from .buffer import Buffer
from ..parser.live import Parser from ..parser.live import Parser
from .. import config from .. import config
from .. import mylogger
from ..exceptions import ChatParseException,IllegalFunctionCall from ..exceptions import ChatParseException,IllegalFunctionCall
from ..paramgen import liveparam from ..paramgen import liveparam
from ..processors.default.processor import DefaultProcessor from ..processors.default.processor import DefaultProcessor
from ..processors.combinator import Combinator
logger = mylogger.get_logger(__name__,mode=config.LOGGER_MODE) logger = config.logger(__name__)
MAX_RETRY = 10
headers = config.headers headers = config.headers
MAX_RETRY = 10
class LiveChat: class LiveChat:
@@ -65,14 +64,17 @@ class LiveChat:
_listeners= [] _listeners= []
def __init__(self, video_id, def __init__(self, video_id,
processor = DefaultProcessor(), processor = DefaultProcessor(),
buffer = Buffer(maxsize = 20), buffer = None,
interruptable = True, interruptable = True,
callback = None, callback = None,
done_callback = None, done_callback = None,
direct_mode = False direct_mode = False
): ):
self.video_id = video_id self.video_id = video_id
self.processor = processor if isinstance(processor, tuple):
self.processor = Combinator(processor)
else:
self.processor = processor
self._buffer = buffer self._buffer = buffer
self._callback = callback self._callback = callback
self._done_callback = done_callback self._done_callback = done_callback
@@ -142,8 +144,8 @@ class LiveChat:
def _listen(self, continuation): def _listen(self, continuation):
''' continuationに紐付いたチャットデータを取得し ''' continuationに紐付いたチャットデータを取得し
BUfferにチャットデータを格納、 Bufferにチャットデータを格納、
次のcontinuaitonを取得してループする 次のcontinuaitonを取得してループする
Parameter Parameter
--------- ---------
@@ -175,9 +177,11 @@ class LiveChat:
time.sleep(diff_time) time.sleep(diff_time)
continuation = metadata.get('continuation') continuation = metadata.get('continuation')
except ChatParseException as e: except ChatParseException as e:
logger.info(f"{str(e)}video_id:\"{self.video_id}\"") self.terminate()
logger.error(f"{str(e)}video_id:\"{self.video_id}\"")
return return
except (TypeError , json.JSONDecodeError) : except (TypeError , json.JSONDecodeError) :
self.terminate()
logger.error(f"{traceback.format_exc(limit = -1)}") logger.error(f"{traceback.format_exc(limit = -1)}")
return return
@@ -206,6 +210,7 @@ class LiveChat:
else: else:
logger.error(f"[{self.video_id}]" logger.error(f"[{self.video_id}]"
f"Exceeded retry count. status_code={status_code}") f"Exceeded retry count. status_code={status_code}")
self.terminate()
return None return None
return livechat_json return livechat_json
@@ -254,18 +259,10 @@ class LiveChat:
if self._direct_mode == False: if self._direct_mode == False:
#bufferにダミーオブジェクトを入れてis_alive()を判定させる #bufferにダミーオブジェクトを入れてis_alive()を判定させる
self._buffer.put({'chatdata':'','timeout':1}) self._buffer.put({'chatdata':'','timeout':1})
logger.info(f'終了しました:[{self.video_id}]') logger.info(f'[{self.video_id}]終了しました')
@classmethod @classmethod
def shutdown(cls, event, sig = None, handler=None): def shutdown(cls, event, sig = None, handler=None):
logger.debug("シャットダウンしています") logger.debug("シャットダウンしています")
for t in LiveChat._listeners: for t in LiveChat._listeners:
t._is_alive = False t._is_alive = False

View File

@@ -11,15 +11,14 @@ from queue import Queue
from .buffer import Buffer from .buffer import Buffer
from ..parser.replay import Parser from ..parser.replay import Parser
from .. import config from .. import config
from .. import mylogger
from ..exceptions import ChatParseException,IllegalFunctionCall from ..exceptions import ChatParseException,IllegalFunctionCall
from ..paramgen import arcparam from ..paramgen import arcparam
from ..processors.default.processor import DefaultProcessor from ..processors.default.processor import DefaultProcessor
from ..processors.combinator import Combinator
logger = mylogger.get_logger(__name__,mode=config.LOGGER_MODE) logger = config.logger(__name__)
MAX_RETRY = 10
headers = config.headers headers = config.headers
MAX_RETRY = 10
class ReplayChat: class ReplayChat:
@@ -61,7 +60,7 @@ class ReplayChat:
チャットデータ取得ループ_listen用のスレッド チャットデータ取得ループ_listen用のスレッド
_is_alive : bool _is_alive : bool
チャット取得を終了したか チャット取得を停止するためのフラグ
''' '''
_setup_finished = False _setup_finished = False
@@ -70,7 +69,7 @@ class ReplayChat:
def __init__(self, video_id, def __init__(self, video_id,
seektime = 0, seektime = 0,
processor = DefaultProcessor(), processor = DefaultProcessor(),
buffer = Buffer(maxsize = 20), buffer = None,
interruptable = True, interruptable = True,
callback = None, callback = None,
done_callback = None, done_callback = None,
@@ -78,7 +77,10 @@ class ReplayChat:
): ):
self.video_id = video_id self.video_id = video_id
self.seektime = seektime self.seektime = seektime
self.processor = processor if isinstance(processor, tuple):
self.processor = Combinator(processor)
else:
self.processor = processor
self._buffer = buffer self._buffer = buffer
self._callback = callback self._callback = callback
self._done_callback = done_callback self._done_callback = done_callback
@@ -90,6 +92,7 @@ class ReplayChat:
self._pauser.put_nowait(None) self._pauser.put_nowait(None)
self._setup() self._setup()
if not ReplayChat._setup_finished: if not ReplayChat._setup_finished:
ReplayChat._setup_finished = True ReplayChat._setup_finished = True
if interruptable: if interruptable:
@@ -150,7 +153,7 @@ class ReplayChat:
def _listen(self, continuation): def _listen(self, continuation):
''' continuationに紐付いたチャットデータを取得し ''' continuationに紐付いたチャットデータを取得し
にチャットデータを格納、 BUfferにチャットデータを格納、
次のcontinuaitonを取得してループする 次のcontinuaitonを取得してループする
Parameter Parameter
@@ -189,9 +192,11 @@ class ReplayChat:
time.sleep(diff_time) time.sleep(diff_time)
continuation = metadata.get('continuation') continuation = metadata.get('continuation')
except ChatParseException as e: except ChatParseException as e:
logger.error(f"{str(e)}動画ID:\"{self.video_id}\"") self.terminate()
logger.error(f"{str(e)}video_id:\"{self.video_id}\"")
return return
except (TypeError , json.JSONDecodeError) : except (TypeError , json.JSONDecodeError) :
self.terminate()
logger.error(f"{traceback.format_exc(limit = -1)}") logger.error(f"{traceback.format_exc(limit = -1)}")
return return
@@ -220,6 +225,7 @@ class ReplayChat:
else: else:
logger.error(f"[{self.video_id}]" logger.error(f"[{self.video_id}]"
f"Exceeded retry count. status_code={status_code}") f"Exceeded retry count. status_code={status_code}")
self.terminate()
return None return None
return livechat_json return livechat_json
@@ -266,7 +272,7 @@ class ReplayChat:
'''Listener終了時のコールバック''' '''Listener終了時のコールバック'''
try: try:
self.terminate() self.terminate()
except CancelledError: except RuntimeError:
logger.debug(f'[{self.video_id}]cancelled:{sender}') logger.debug(f'[{self.video_id}]cancelled:{sender}')
def terminate(self): def terminate(self):
@@ -277,7 +283,7 @@ class ReplayChat:
if self._direct_mode == False: if self._direct_mode == False:
#bufferにダミーオブジェクトを入れてis_alive()を判定させる #bufferにダミーオブジェクトを入れてis_alive()を判定させる
self._buffer.put({'chatdata':'','timeout':1}) self._buffer.put({'chatdata':'','timeout':1})
logger.info(f'終了しました:[{self.video_id}]') logger.info(f'[{self.video_id}]終了しました')
@classmethod @classmethod
def shutdown(cls, event, sig = None, handler=None): def shutdown(cls, event, sig = None, handler=None):

View File

@@ -6,14 +6,13 @@ This module is parser of live chat JSON.
import json import json
from .. import config from .. import config
from .. import mylogger
from .. exceptions import ( from .. exceptions import (
ResponseContextError, ResponseContextError,
NoContentsException, NoContentsException,
NoContinuationsException ) NoContinuationsException )
logger = mylogger.get_logger(__name__,mode=config.LOGGER_MODE) logger = config.logger(__name__)
class Parser: class Parser:
@@ -59,7 +58,7 @@ class Parser:
if metadata is None: if metadata is None:
unknown = list(cont.keys())[0] unknown = list(cont.keys())[0]
if unknown: if unknown:
logger.error(f"Received unknown continuation type:{unknown}") logger.debug(f"Received unknown continuation type:{unknown}")
metadata = cont.get(unknown) metadata = cont.get(unknown)
metadata.setdefault('timeoutMs', 10000) metadata.setdefault('timeoutMs', 10000)
chatdata = contents['liveChatContinuation'].get('actions') chatdata = contents['liveChatContinuation'].get('actions')

View File

@@ -1,14 +1,12 @@
import json import json
from .. import config from .. import config
from .. import mylogger
from .. exceptions import ( from .. exceptions import (
ResponseContextError, ResponseContextError,
NoContentsException, NoContentsException,
NoContinuationsException ) NoContinuationsException )
logger = mylogger.get_logger(__name__,mode=config.LOGGER_MODE) logger = config.logger(__name__)
class Parser: class Parser:
def parse(self, jsn): def parse(self, jsn):
@@ -53,12 +51,13 @@ class Parser:
metadata = cont.get('liveChatReplayContinuationData') metadata = cont.get('liveChatReplayContinuationData')
if metadata is None: if metadata is None:
unknown = list(cont.keys())[0] unknown = list(cont.keys())[0]
if unknown: if unknown != "playerSeekContinuationData":
logger.debug(f"Received unknown continuation type:{unknown}")
metadata = cont.get(unknown) metadata = cont.get(unknown)
actions = contents['liveChatContinuation'].get('actions') actions = contents['liveChatContinuation'].get('actions')
if actions is None: if actions is None:
raise NoContentsException('チャットデータを取得できませんでした。') #後続のチャットデータなし
return {"continuation":None,"timeout":0,"chatdata":[]}
interval = self.get_interval(actions) interval = self.get_interval(actions)
metadata.setdefault("timeoutMs",interval) metadata.setdefault("timeoutMs",interval)
"""アーカイブ済みチャットはライブチャットと構造が異なっているため、以下の行により """アーカイブ済みチャットはライブチャットと構造が異なっているため、以下の行により

View File

@@ -0,0 +1,39 @@
from .chat_processor import ChatProcessor
class Combinator(ChatProcessor):
'''
Combinator combines multiple chat processors.
Specify processors as tuple at `processor` params of LiveChat object.
For example:
[constructor]
chat = LiveChat("video_id", processor = ( Processor1(), Processor2(), Processor3() ) )
[receive return values]
ret1, ret2, ret3 = chat.get()
The return values are tuple of processed chat data,
the order of return depends on parameter order.
Parameter
---------
processors : Tuple[ChatProcessor]
multiple processors for processing chat data
'''
def __init__(self, processors: tuple):
self.processors = processors
def process(self, chat_components: list):
'''
Called from LiveChat.get() function by user,
or LiveChat._listen() automatically.
Returns
-------
Tuple of chat data processed by each chat processor.
'''
return tuple(processor.process(chat_components)
for processor in self.processors)

View File

@@ -5,9 +5,8 @@ from .renderer.paidmessage import LiveChatPaidMessageRenderer
from .renderer.paidsticker import LiveChatPaidStickerRenderer from .renderer.paidsticker import LiveChatPaidStickerRenderer
from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer
from .. chat_processor import ChatProcessor from .. chat_processor import ChatProcessor
from ... import mylogger
from ... import config from ... import config
logger = mylogger.get_logger(__name__,mode=config.LOGGER_MODE) logger = config.logger(__name__)
class CompatibleProcessor(ChatProcessor): class CompatibleProcessor(ChatProcessor):

View File

@@ -6,8 +6,7 @@ from .renderer.paidsticker import LiveChatPaidStickerRenderer
from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer
from .. chat_processor import ChatProcessor from .. chat_processor import ChatProcessor
from ... import config from ... import config
from ... import mylogger logger = config.logger(__name__)
logger = mylogger.get_logger(__name__,mode=config.LOGGER_MODE)
class Chatdata: class Chatdata:
def __init__(self,chatlist:list, timeout:float): def __init__(self,chatlist:list, timeout:float):

View File

@@ -19,8 +19,7 @@ class BaseRenderer:
else: else:
self.elapsedTime = "" self.elapsedTime = ""
self.datetime = self.get_datetime(timestampUsec) self.datetime = self.get_datetime(timestampUsec)
self.message = self.get_message(self.renderer) self.message ,self.messageEx = self.get_message(self.renderer)
self.messageEx = self.get_message_ex(self.renderer)
self.id = self.renderer.get('id') self.id = self.renderer.get('id')
self.amountValue= 0.0 self.amountValue= 0.0
self.amountString = "" self.amountString = ""
@@ -44,6 +43,7 @@ class BaseRenderer:
def get_message(self,renderer): def get_message(self,renderer):
message = '' message = ''
message_ex = []
if renderer.get("message"): if renderer.get("message"):
runs=renderer["message"].get("runs") runs=renderer["message"].get("runs")
if runs: if runs:
@@ -51,22 +51,13 @@ class BaseRenderer:
if r: if r:
if r.get('emoji'): if r.get('emoji'):
message += r['emoji'].get('shortcuts',[''])[0] message += r['emoji'].get('shortcuts',[''])[0]
message_ex.append(r['emoji']['image']['thumbnails'][1].get('url'))
else: else:
message += r.get('text','') message += r.get('text','')
return message message_ex.append(r.get('text',''))
return message, message_ex
def get_message_ex(self,renderer):
message = []
if renderer.get("message"):
runs=renderer["message"].get("runs")
if runs:
for r in runs:
if r:
if r.get('emoji'):
message.append(r['emoji']['image']['thumbnails'][1].get('url'))
else:
message.append(r.get('text',''))
return message
def get_badges(self,renderer): def get_badges(self,renderer):
isVerified = False isVerified = False

View File

@@ -0,0 +1,8 @@
from .chat_processor import ChatProcessor
class DummyProcessor(ChatProcessor):
'''
Dummy processor just returns received chat_components directly.
'''
def process(self, chat_components: list):
return chat_components

View File

@@ -9,7 +9,7 @@ def download(url):
json.dump(html.json(),f,ensure_ascii=False) json.dump(html.json(),f,ensure_ascii=False)
def save(data,filename): def save(data,filename,extention):
with open(str(datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S') with open(filename+"_"+(datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S')
)+filename,mode ='w',encoding='utf-8') as f: )+extention,mode ='w',encoding='utf-8') as f:
f.writelines(data) f.writelines(data)

View File

@@ -1,6 +1,6 @@
from setuptools import setup, find_packages, Command from setuptools import setup, find_packages, Command
from codecs import open #from codecs import open as open_c
from os import path, system from os import path, system, remove, rename
import re import re
package_name = "pytchat" package_name = "pytchat"
@@ -28,6 +28,16 @@ assert author
assert author_email assert author_email
assert url assert url
with open('README.MD', 'r', encoding='utf-8') as f:
txt = f.read()
with open('README1.MD', 'w', encoding='utf-8', newline='\n') as f:
f.write(txt)
remove("README.MD")
rename("README1.MD","README.MD")
with open('README.md', encoding='utf-8') as f: with open('README.md', encoding='utf-8') as f:
long_description = f.read() long_description = f.read()