Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Commit

Permalink
Merge branch 'release/v0.0.5.1.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
taizan-hokuto committed Jan 18, 2020
2 parents fb0edef + 76b126f commit f7d1830
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 106 deletions.
88 changes: 52 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ 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.

pytchatはAPIを使わずにYouTubeチャットを取得するための軽量pythonライブラリです。

Other features:
+ Customizable chat data processors including youtube api compatible one.
+ Available on asyncio context.
+ Quick fetching of initial chat data by generating continuation params
instead of web scraping.

For more detailed information, see [wiki](https://github.com/taizan-hokuto/pytchat/wiki).
For more detailed information, see [wiki](https://github.com/taizan-hokuto/pytchat/wiki). <br>
より詳細な解説は[wiki](https://github.com/taizan-hokuto/pytchat/wiki/Home-:)を参照してください。

## Install
```python
Expand All @@ -26,31 +29,39 @@ pip install pytchat
### on-demand mode
```python
from pytchat import LiveChat
livechat = LiveChat(video_id = "Zvp1pJpie4I")

chat = LiveChat("DSGyEsJ17cI")
while chat.is_alive():
data = chat.get()
for c in data.items:
print(f"{c.datetime} [{c.author.name}]-{c.message} {c.amountString}")
data.tick()
while livechat.is_alive():
try:
chatdata = livechat.get()
for c in chatdata.items:
print(f"{c.datetime} [{c.author.name}]- {c.message}")
chatdata.tick()
except KeyboardInterrupt:
livechat.terminate()
break
```

### callback mode
```python
from pytchat import LiveChat
import time

#callback function is automatically called.
def display(data):
for c in data.items:
print(f"{c.datetime} [{c.author.name}]-{c.message} {c.amountString}")
data.tick()
def main():
livechat = LiveChat(video_id = "Zvp1pJpie4I", callback = disp)
while livechat.is_alive():
#other background operation.
time.sleep(1)
livechat.terminate()

#callback function (automatically called)
def disp(chatdata):
for c in chatdata.items:
print(f"{c.datetime} [{c.author.name}]- {c.message}")
chatdata.tick()

if __name__ == '__main__':
chat = LiveChat("DSGyEsJ17cI", callback = display)
while chat.is_alive():
#other background operation.
time.sleep(3)
main()

```

Expand All @@ -61,16 +72,16 @@ from concurrent.futures import CancelledError
import asyncio

async def main():
chat = LiveChatAsync("DSGyEsJ17cI", callback = func)
while chat.is_alive():
livechat = LiveChatAsync("Zvp1pJpie4I", callback = func)
while livechat.is_alive():
#other background operation.
await asyncio.sleep(3)

#callback function is automatically called.
async def func(data):
for c in data.items:
async def func(chatdata):
for c in chatdata.items:
print(f"{c.datetime} [{c.author.name}]-{c.message} {c.amountString}")
await data.tick_async()
await chatdata.tick_async()

if __name__ == '__main__':
try:
Expand All @@ -86,18 +97,20 @@ if __name__ == '__main__':
from pytchat import LiveChat, CompatibleProcessor
import time

chat = LiveChat("DSGyEsJ17cI",
chat = LiveChat("Zvp1pJpie4I",
processor = CompatibleProcessor() )

while chat.is_alive():
data = chat.get()
polling = data['pollingIntervalMillis']/1000
for c in data['items']:
if c.get('snippet'):
print(f"[{c['authorDetails']['displayName']}]"
f"-{c['snippet']['displayMessage']}")
time.sleep(polling/len(data['items']))

try:
data = chat.get()
polling = data['pollingIntervalMillis']/1000
for c in data['items']:
if c.get('snippet'):
print(f"[{c['authorDetails']['displayName']}]"
f"-{c['snippet']['displayMessage']}")
time.sleep(polling/len(data['items']))
except KeyboardInterrupt:
chat.terminate()
```
### replay:
If specified video is not live,
Expand All @@ -110,18 +123,21 @@ def main():
#seektime (seconds): start position of chat.
chat = LiveChat("ojes5ULOqhc", seektime = 60*30)
print('Replay from 30:00')
while chat.is_alive():
data = chat.get()
for c in data.items:
print(f"{c.elapsedTime} [{c.author.name}]-{c.message} {c.amountString}")
data.tick()
try:
while chat.is_alive():
data = chat.get()
for c in data.items:
print(f"{c.elapsedTime} [{c.author.name}]-{c.message} {c.amountString}")
data.tick()
except KeyboardInterrupt:
chat.terminate()

if __name__ == '__main__':
main()
```

## Structure of Default Processor
Each item can be got with items() function.
Each item can be got with `items` function.
<table>
<tr>
<th>name</th>
Expand Down
2 changes: 1 addition & 1 deletion pytchat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pytchat is a python library for fetching youtube live chat without using yt api, Selenium, or BeautifulSoup.
"""
__copyright__ = 'Copyright (C) 2019 taizan-hokuto'
__version__ = '0.0.5.0'
__version__ = '0.0.5.1.3'
__license__ = 'MIT'
__author__ = 'taizan-hokuto'
__author_email__ = '55448286+taizan-hokuto@users.noreply.github.com'
Expand Down
6 changes: 2 additions & 4 deletions pytchat/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import logging
from . import mylogger

LOGGER_MODE = None

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'}

def logger(module_name: str):
module_logger = mylogger.get_logger(module_name, mode = LOGGER_MODE)
def logger(module_name: str, loglevel = None):
module_logger = mylogger.get_logger(module_name, loglevel = loglevel)
return module_logger


10 changes: 5 additions & 5 deletions pytchat/config/mylogger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
import datetime


def get_logger(modname,mode=logging.DEBUG):
def get_logger(modname,loglevel=logging.DEBUG):
logger = getLogger(modname)
if mode == None:
if loglevel == None:
logger.addHandler(NullHandler())
return logger
logger.setLevel(mode)
logger.setLevel(loglevel)
#create handler1 for showing info
handler1 = StreamHandler()
my_formatter = MyFormatter()
handler1.setFormatter(my_formatter)

handler1.setLevel(mode)
handler1.setLevel(loglevel)
logger.addHandler(handler1)
#create handler2 for recording log file
if mode <= logging.DEBUG:
if loglevel <= logging.DEBUG:
handler2 = FileHandler(filename="log.txt", encoding='utf-8')
handler2.setLevel(logging.ERROR)
handler2.setFormatter(my_formatter)
Expand Down
35 changes: 15 additions & 20 deletions pytchat/core_async/livechat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from ..processors.default.processor import DefaultProcessor
from ..processors.combinator import Combinator

logger = config.logger(__name__)
headers = config.headers
MAX_RETRY = 10

Expand Down Expand Up @@ -74,6 +73,7 @@ class LiveChatAsync:
'''

_setup_finished = False
_logger = config.logger(__name__)

def __init__(self, video_id,
seektime = 0,
Expand All @@ -85,7 +85,8 @@ def __init__(self, video_id,
exception_handler = None,
direct_mode = False,
force_replay = False,
topchat_only = False
topchat_only = False,
logger = config.logger(__name__),
):
self.video_id = video_id
self.seektime = seektime
Expand All @@ -107,11 +108,12 @@ def __init__(self, video_id,
self._first_fetch = True
self._fetch_url = "live_chat/get_live_chat?continuation="
self._topchat_only = topchat_only
self._logger = logger
LiveChatAsync._logger = logger

if not LiveChatAsync._setup_finished:
LiveChatAsync._setup_finished = True
if exception_handler == None:
self._set_exception_handler(self._handle_exception)
else:
if exception_handler:
self._set_exception_handler(exception_handler)
if interruptable:
signal.signal(signal.SIGINT,
Expand Down Expand Up @@ -187,14 +189,14 @@ async def _listen(self, continuation):
continuation = metadata.get('continuation')
except ChatParseException as e:
#self.terminate()
logger.debug(f"[{self.video_id}]{str(e)}")
self._logger.debug(f"[{self.video_id}]{str(e)}")
return
except (TypeError , json.JSONDecodeError) :
#self.terminate()
logger.error(f"{traceback.format_exc(limit = -1)}")
self._logger.error(f"{traceback.format_exc(limit = -1)}")
return

logger.debug(f"[{self.video_id}]finished fetching chat.")
self._logger.debug(f"[{self.video_id}]finished fetching chat.")

async def _check_pause(self, continuation):
if self._pauser.empty():
Expand Down Expand Up @@ -254,7 +256,7 @@ async def _get_livechat_json(self, continuation, session, headers):
await asyncio.sleep(1)
continue
else:
logger.error(f"[{self.video_id}]"
self._logger.error(f"[{self.video_id}]"
f"Exceeded retry count. status_code={status_code}")
return None
return livechat_json
Expand Down Expand Up @@ -309,7 +311,7 @@ def finish(self,sender):
try:
self.terminate()
except CancelledError:
logger.debug(f'[{self.video_id}]cancelled:{sender}')
self._logger.debug(f'[{self.video_id}]cancelled:{sender}')

def terminate(self):
'''
Expand All @@ -319,28 +321,21 @@ def terminate(self):
if self._direct_mode == False:
#bufferにダミーオブジェクトを入れてis_alive()を判定させる
self._buffer.put_nowait({'chatdata':'','timeout':0})
logger.info(f'[{self.video_id}]finished.')
self._logger.info(f'[{self.video_id}]finished.')

@classmethod
def _set_exception_handler(cls, handler):
loop = asyncio.get_event_loop()
loop.set_exception_handler(handler)

@classmethod
def _handle_exception(cls, loop, context):
if not isinstance(context["exception"],CancelledError):
logger.error(f"Caught exception: {context}")
loop= asyncio.get_event_loop()
loop.create_task(cls.shutdown(None,None,None))

@classmethod
async def shutdown(cls, event, sig = None, handler=None):
logger.debug("shutdown...")
cls._logger.debug("shutdown...")
tasks = [t for t in asyncio.all_tasks() if t is not
asyncio.current_task()]
[task.cancel() for task in tasks]

logger.debug(f"complete remaining tasks...")
cls._logger.debug(f"complete remaining tasks...")
await asyncio.gather(*tasks,return_exceptions=True)
loop = asyncio.get_event_loop()
loop.stop()
Loading

0 comments on commit f7d1830

Please sign in to comment.