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

Commit 6c7dc03

Browse files
committed
Merge branch 'release/v0.5.5'
2 parents e865c25 + f511049 commit 6c7dc03

16 files changed

Lines changed: 332 additions & 368 deletions

Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ verify_ssl = true
44
name = "pypi"
55

66
[packages]
7-
httpx = {extras = ["http2"], version = "0.16.1"}
7+
httpx = {extras = ["http2"]}
88

99
[dev-packages]
1010
pytest-mock = "*"

Pipfile.lock

Lines changed: 112 additions & 150 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pytchat/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""
22
pytchat is a lightweight python library to browse youtube livechat without Selenium or BeautifulSoup.
33
"""
4-
__copyright__ = 'Copyright (C) 2019, 2020 taizan-hokuto'
5-
__version__ = '0.5.4'
4+
__copyright__ = 'Copyright (C) 2019, 2020, 2021 taizan-hokuto'
5+
__version__ = '0.5.5'
66
__license__ = 'MIT'
77
__author__ = 'taizan-hokuto'
88
__author_email__ = '55448286+taizan-hokuto@users.noreply.github.com'

pytchat/core/pytchat.py

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ class PytchatCore:
2929
3030
processor : ChatProcessor
3131
32+
client : httpx.Client
33+
The client for connecting youtube.
34+
You can specify any customized httpx client (e.g. coolies, user agent).
35+
3236
interruptable : bool
3337
Allows keyboard interrupts.
34-
Set this parameter to False if your own threading program causes
38+
Set this parameter to False if your own multi-threading program causes
3539
the problem.
3640
3741
force_replay : bool
@@ -57,13 +61,15 @@ class PytchatCore:
5761
def __init__(self, video_id,
5862
seektime=-1,
5963
processor=DefaultProcessor(),
64+
client = httpx.Client(http2=True),
6065
interruptable=True,
6166
force_replay=False,
6267
topchat_only=False,
6368
hold_exception=True,
6469
logger=config.logger(__name__),
6570
replay_continuation=None
6671
):
72+
self._client = client
6773
self._video_id = util.extract_video_id(video_id)
6874
self.seektime = seektime
6975
if isinstance(processor, tuple):
@@ -97,7 +103,7 @@ def _setup(self):
97103
"""
98104
self.continuation = liveparam.getparam(
99105
self._video_id,
100-
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
106+
channel_id=util.get_channelid(self._client, self._video_id),
101107
past_sec=3)
102108

103109
def _get_chat_component(self):
@@ -110,19 +116,18 @@ def _get_chat_component(self):
110116
parameter for next chat data
111117
'''
112118
try:
113-
with httpx.Client(http2=True) as client:
114-
if self.continuation and self._is_alive:
115-
contents = self._get_contents(self.continuation, client, headers)
116-
metadata, chatdata = self._parser.parse(contents)
117-
timeout = metadata['timeoutMs'] / 1000
118-
chat_component = {
119-
"video_id": self._video_id,
120-
"timeout": timeout,
121-
"chatdata": chatdata
122-
}
123-
self.continuation = metadata.get('continuation')
124-
self._last_offset_ms = metadata.get('last_offset_ms', 0)
125-
return chat_component
119+
if self.continuation and self._is_alive:
120+
contents = self._get_contents(self.continuation, self._client, headers)
121+
metadata, chatdata = self._parser.parse(contents)
122+
timeout = metadata['timeoutMs'] / 1000
123+
chat_component = {
124+
"video_id": self._video_id,
125+
"timeout": timeout,
126+
"chatdata": chatdata
127+
}
128+
self.continuation = metadata.get('continuation')
129+
self._last_offset_ms = metadata.get('last_offset_ms', 0)
130+
return chat_component
126131
except exceptions.ChatParseException as e:
127132
self._logger.debug(f"[{self._video_id}]{str(e)}")
128133
self._raise_exception(e)
@@ -139,9 +144,8 @@ def _get_contents(self, continuation, client, headers):
139144
-------
140145
'continuationContents' which includes metadata & chat data.
141146
'''
142-
livechat_json = (
143-
self._get_livechat_json(continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms)
144-
)
147+
livechat_json = self._get_livechat_json(
148+
continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms)
145149
contents, dat = self._parser.get_contents(livechat_json)
146150
if self._dat == '' and dat:
147151
self._dat = dat
@@ -152,7 +156,8 @@ def _get_contents(self, continuation, client, headers):
152156
self._fetch_url = config._smr
153157
continuation = arcparam.getparam(
154158
self._video_id, self.seektime, self._topchat_only, util.get_channelid(client, self._video_id))
155-
livechat_json = self._get_livechat_json(continuation, client, replay=True, offset_ms=self.seektime * 1000)
159+
livechat_json = self._get_livechat_json(
160+
continuation, client, replay=True, offset_ms=self.seektime * 1000)
156161
reload_continuation = self._parser.reload_continuation(
157162
self._parser.get_contents(livechat_json)[0])
158163
if reload_continuation:
@@ -173,15 +178,14 @@ def _get_livechat_json(self, continuation, client, replay: bool, offset_ms: int
173178
offset_ms = 0
174179
param = util.get_param(continuation, dat=self._dat, replay=replay, offsetms=offset_ms)
175180
for _ in range(MAX_RETRY + 1):
176-
with httpx.Client(http2=True) as client:
177-
try:
178-
response = client.post(self._fetch_url, json=param)
179-
livechat_json = json.loads(response.text)
180-
break
181-
except (json.JSONDecodeError, httpx.ConnectTimeout, httpx.ReadTimeout, httpx.ConnectError) as e:
182-
err = e
183-
time.sleep(2)
184-
continue
181+
try:
182+
response = client.post(self._fetch_url, json=param)
183+
livechat_json = response.json()
184+
break
185+
except (json.JSONDecodeError, httpx.ConnectTimeout, httpx.ReadTimeout, httpx.ConnectError) as e:
186+
err = e
187+
time.sleep(2)
188+
continue
185189
else:
186190
self._logger.error(f"[{self._video_id}]"
187191
f"Exceeded retry count. Last error: {str(err)}")
@@ -202,6 +206,8 @@ def is_alive(self):
202206
return self._is_alive
203207

204208
def terminate(self):
209+
if not self.is_alive():
210+
return
205211
self._is_alive = False
206212
self.processor.finalize()
207213

pytchat/core_async/livechat.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(self, video_id,
7878
seektime=-1,
7979
processor=DefaultProcessor(),
8080
buffer=None,
81+
client = httpx.AsyncClient(http2=True),
8182
interruptable=True,
8283
callback=None,
8384
done_callback=None,
@@ -88,6 +89,7 @@ def __init__(self, video_id,
8889
logger=config.logger(__name__),
8990
replay_continuation=None
9091
):
92+
self._client:httpx.AsyncClient = client
9193
self._video_id = util.extract_video_id(video_id)
9294
self.seektime = seektime
9395
if isinstance(processor, tuple):
@@ -152,9 +154,10 @@ async def _startlisten(self):
152154
create and start _listen loop.
153155
"""
154156
if not self.continuation:
157+
channel_id = await util.get_channelid_async(self._client, self._video_id)
155158
self.continuation = liveparam.getparam(
156159
self._video_id,
157-
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
160+
channel_id,
158161
past_sec=3)
159162

160163
await self._listen(self.continuation)
@@ -169,10 +172,10 @@ async def _listen(self, continuation):
169172
parameter for next chat data
170173
'''
171174
try:
172-
async with httpx.AsyncClient(http2=True) as client:
175+
async with self._client as client:
173176
while(continuation and self._is_alive):
174177
continuation = await self._check_pause(continuation)
175-
contents = await self._get_contents(continuation, client, headers)
178+
contents = await self._get_contents(continuation, client, headers) #Q#
176179
metadata, chatdata = self._parser.parse(contents)
177180
continuation = metadata.get('continuation')
178181
if continuation:
@@ -214,9 +217,10 @@ async def _check_pause(self, continuation):
214217
'''
215218
self._pauser.put_nowait(None)
216219
if not self._is_replay:
217-
async with httpx.AsyncClient(http2=True) as client:
218-
continuation = await liveparam.getparam(self._video_id,
219-
channel_id=util.get_channelid_async(client, self.video_id),
220+
async with self._client as client:
221+
channel_id = await util.get_channelid_async(client, self.video_id)
222+
continuation = liveparam.getparam(self._video_id,
223+
channel_id,
220224
past_sec=3)
221225

222226
return continuation
@@ -338,12 +342,14 @@ def _finish(self, sender):
338342
self._logger.debug(f'[{self._video_id}] cancelled:{sender}')
339343

340344
def terminate(self):
345+
if not self.is_alive():
346+
return
341347
if self._pauser.empty():
342348
self._pauser.put_nowait(None)
343349
self._is_alive = False
344350
self._buffer.put_nowait({})
345351
self.processor.finalize()
346-
352+
347353
def _keyboard_interrupt(self):
348354
self.exception = exceptions.ChatDataFinished()
349355
self.terminate()

pytchat/core_multithread/livechat.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class LiveChat:
7878
def __init__(self, video_id,
7979
seektime=-1,
8080
processor=DefaultProcessor(),
81+
client = httpx.Client(http2=True),
8182
buffer=None,
8283
interruptable=True,
8384
callback=None,
@@ -88,6 +89,7 @@ def __init__(self, video_id,
8889
logger=config.logger(__name__),
8990
replay_continuation=None
9091
):
92+
self._client = client
9193
self._video_id = util.extract_video_id(video_id)
9294
self.seektime = seektime
9395
if isinstance(processor, tuple):
@@ -150,7 +152,7 @@ def _startlisten(self):
150152
if not self.continuation:
151153
self.continuation = liveparam.getparam(
152154
self._video_id,
153-
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
155+
channel_id=util.get_channelid(self._client, self._video_id),
154156
past_sec=3)
155157
self._listen(self.continuation)
156158

@@ -164,7 +166,7 @@ def _listen(self, continuation):
164166
parameter for next chat data
165167
'''
166168
try:
167-
with httpx.Client(http2=True) as client:
169+
with self._client as client:
168170
while(continuation and self._is_alive):
169171
continuation = self._check_pause(continuation)
170172
contents = self._get_contents(continuation, client, headers)
@@ -224,7 +226,8 @@ def _get_contents(self, continuation, client, headers):
224226
-------
225227
'continuationContents' which includes metadata & chat data.
226228
'''
227-
livechat_json = self._get_livechat_json(continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms)
229+
livechat_json = self._get_livechat_json(
230+
continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms)
228231
contents, dat = self._parser.get_contents(livechat_json)
229232
if self._dat == '' and dat:
230233
self._dat = dat
@@ -235,8 +238,8 @@ def _get_contents(self, continuation, client, headers):
235238
self._fetch_url = config._smr
236239
continuation = arcparam.getparam(
237240
self._video_id, self.seektime, self._topchat_only, util.get_channelid(client, self._video_id))
238-
livechat_json = (self._get_livechat_json(
239-
continuation, client, replay=True, offset_ms=self.seektime * 1000))
241+
livechat_json = self._get_livechat_json(
242+
continuation, client, replay=True, offset_ms=self.seektime * 1000)
240243
reload_continuation = self._parser.reload_continuation(
241244
self._parser.get_contents(livechat_json)[0])
242245
if reload_continuation:
@@ -331,6 +334,8 @@ def _finish(self, sender):
331334
self._logger.debug(f'[{self._video_id}] cancelled:{sender}')
332335

333336
def terminate(self):
337+
if not self.is_alive():
338+
return
334339
if self._pauser.empty():
335340
self._pauser.put_nowait(None)
336341
self._is_alive = False

pytchat/processors/default/processor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .renderer.paidsticker import LiveChatPaidStickerRenderer
88
from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer
99
from .renderer.membership import LiveChatMembershipItemRenderer
10+
from .renderer.donation import LiveChatDonationAnnouncementRenderer
1011
from .. chat_processor import ChatProcessor
1112
from ... import config
1213

@@ -124,7 +125,8 @@ def __init__(self):
124125
"liveChatPaidMessageRenderer": LiveChatPaidMessageRenderer(),
125126
"liveChatPaidStickerRenderer": LiveChatPaidStickerRenderer(),
126127
"liveChatLegacyPaidMessageRenderer": LiveChatLegacyPaidMessageRenderer(),
127-
"liveChatMembershipItemRenderer": LiveChatMembershipItemRenderer()
128+
"liveChatMembershipItemRenderer": LiveChatMembershipItemRenderer(),
129+
"liveChatDonationAnnouncementRenderer": LiveChatDonationAnnouncementRenderer(),
128130
}
129131

130132
def process(self, chat_components: list):
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .base import BaseRenderer
2+
3+
4+
class LiveChatDonationAnnouncementRenderer(BaseRenderer):
5+
def settype(self):
6+
self.chat.type = "donation"

0 commit comments

Comments
 (0)