Compare commits

..

44 Commits

Author SHA1 Message Date
taizan-hokuto
128a834841 Merge branch 'hotfix/fix' 2020-11-15 16:54:24 +09:00
taizan-hokuto
6a392f3e1a Increment version 2020-11-15 16:53:36 +09:00
taizan-hokuto
93127a703c Revert 2020-11-15 16:53:03 +09:00
taizan-hokuto
e4ddbaf8ae Merge branch 'develop' 2020-11-15 16:39:07 +09:00
taizan-hokuto
ec75058605 Merge pull request #22 from wakamezake/github_actions
Add GitHub actions
2020-11-15 16:05:13 +09:00
taizan-hokouto
2b62e5dc5e Merge branch 'feature/pr_22' into develop 2020-11-15 15:59:52 +09:00
taizan-hokouto
8d7874096e Fix datetime tests 2020-11-15 15:59:28 +09:00
taizan-hokouto
99fcab83c8 Revert 2020-11-15 15:49:39 +09:00
wakamezake
3027bc0579 change timezone utc to jst 2020-11-15 15:39:16 +09:00
wakamezake
b1b70a4e76 delete cache 2020-11-15 15:39:16 +09:00
wakamezake
de41341d84 typo 2020-11-15 15:39:16 +09:00
wakamezake
a03d43b081 version up 2020-11-15 15:39:16 +09:00
wakamezake
f60aaade7f init 2020-11-15 15:39:16 +09:00
wakamezake
d3c34086ff change timezone utc to jst 2020-11-15 11:29:12 +09:00
wakamezake
6b58c9bcf5 delete cache 2020-11-15 10:50:14 +09:00
wakamezake
c2cba1651e Merge remote-tracking branch 'upstream/master' into github_actions 2020-11-15 10:40:00 +09:00
taizan-hokouto
ada3eb437d Merge branch 'hotfix/test_requirements' 2020-11-15 09:22:38 +09:00
taizan-hokouto
c1517d5be8 Merge branch 'master' into develop 2020-11-15 09:22:38 +09:00
taizan-hokouto
351034d1e6 Increment version 2020-11-15 09:21:58 +09:00
taizan-hokouto
c1db5a0c47 Update requirements.txt and requirements_test.txt 2020-11-15 09:18:01 +09:00
wakamezake
088dce712a typo 2020-11-14 18:08:41 +09:00
wakamezake
425e880b09 version up 2020-11-14 18:07:30 +09:00
wakamezake
62ec78abee init 2020-11-14 18:04:49 +09:00
taizan-hokouto
c84a32682c Merge branch 'hotfix/fix_prompt' 2020-11-08 12:31:52 +09:00
taizan-hokouto
74277b2afe Merge branch 'master' into develop 2020-11-08 12:31:52 +09:00
taizan-hokouto
cd20b74b2a Increment version 2020-11-08 12:31:16 +09:00
taizan-hokouto
06f54fd985 Remove unnecessary console output 2020-11-08 12:30:40 +09:00
taizan-hokouto
98b0470703 Merge tag 'emoji' into develop
v0.4.1
2020-11-06 19:58:45 +09:00
taizan-hokouto
bb4113b53c Merge branch 'hotfix/emoji' 2020-11-06 19:58:44 +09:00
taizan-hokouto
07f4382ed4 Increment version 2020-11-06 19:57:16 +09:00
taizan-hokouto
d40720616b Fix emoji encoding 2020-11-06 19:56:54 +09:00
taizan-hokouto
eebe7c79bd Merge branch 'master' into develop 2020-11-05 22:19:11 +09:00
taizan-hokouto
6c9e327e36 Merge branch 'hotfix/fix_readme' 2020-11-05 22:19:11 +09:00
taizan-hokouto
e9161c0ddd Update README 2020-11-05 22:18:54 +09:00
taizan-hokouto
c8b75dcf0e Merge branch 'master' into develop 2020-11-05 00:14:50 +09:00
taizan-hokouto
30cb7d7043 Merge branch 'hotfix/fix_readme' 2020-11-05 00:14:50 +09:00
taizan-hokouto
19d5b74beb Update README 2020-11-05 00:14:36 +09:00
taizan-hokouto
d5c3e45edc Merge branch 'master' into develop 2020-11-03 20:21:53 +09:00
taizan-hokouto
1d479fc15c Merge branch 'hotfix/fix_readme' 2020-11-03 20:21:52 +09:00
taizan-hokouto
20a20ddd08 Update README 2020-11-03 20:21:39 +09:00
taizan-hokouto
00c239f974 Merge branch 'master' into develop 2020-11-03 20:10:48 +09:00
taizan-hokouto
67b766b32c Merge branch 'hotfix/fix_readme' 2020-11-03 20:10:48 +09:00
taizan-hokouto
249aa0d147 Update README 2020-11-03 20:10:34 +09:00
taizan-hokouto
c708a588d8 Merge tag 'v0.4.0' into develop
v0.4.0
2020-11-03 18:20:10 +09:00
7 changed files with 64 additions and 42 deletions

27
.github/workflows/run_test.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Run All UnitTest
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.7, 3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements_test.txt
- name: Test with pytest
run: |
export PYTHONPATH=./
pytest --verbose --color=yes

View File

@@ -24,12 +24,14 @@ pip install pytchat
### CLI ### CLI
One-liner command. + One-liner command.
+ Save chat data to html with embedded custom emojis.
+ Show chat stream (--echo option).
Save chat data to html with embedded custom emojis.
Show chat stream (--echo option).
```bash ```bash
$ pytchat -v https://www.youtube.com/watch?v=uIx8l2xlYVY -o "c:/temp/" $ pytchat -v uIx8l2xlYVY -o "c:/temp/"
# options: # options:
# -v : Video ID or URL that includes ID # -v : Video ID or URL that includes ID
# -o : output directory (default path: './') # -o : output directory (default path: './')
@@ -38,7 +40,7 @@ $ pytchat -v https://www.youtube.com/watch?v=uIx8l2xlYVY -o "c:/temp/"
``` ```
### On-demand mode with simple non-buffered object. ### Fetch chat data (see [wiki](https://github.com/taizan-hokuto/pytchat/wiki/PytchatCore))
```python ```python
import pytchat import pytchat
chat = pytchat.create(video_id="uIx8l2xlYVY") chat = pytchat.create(video_id="uIx8l2xlYVY")
@@ -47,7 +49,8 @@ while chat.is_alive():
print(f"{c.datetime} [{c.author.name}]- {c.message}") print(f"{c.datetime} [{c.author.name}]- {c.message}")
``` ```
### Output JSON format (feature of [DefaultProcessor](DefaultProcessor))
### Output JSON format string (feature of [DefaultProcessor](https://github.com/taizan-hokuto/pytchat/wiki/DefaultProcessor))
```python ```python
import pytchat import pytchat
import time import time
@@ -58,35 +61,21 @@ while chat.is_alive():
time.sleep(5) time.sleep(5)
''' '''
# Each chat item can also be output in JSON format. # Each chat item can also be output in JSON format.
for c in chat.get().sync_items(): for c in chat.get().items:
print(c.json()) print(c.json())
''' '''
``` ```
### other ### other
#### Fetch chat with buffer. + Fetch chat with a buffer ([LiveChat](https://github.com/taizan-hokuto/pytchat/wiki/LiveChat))
[LiveChat](https://github.com/taizan-hokuto/pytchat/wiki/LiveChat)
#### Asyncio Context + Use with asyncio ([LiveChatAsync](https://github.com/taizan-hokuto/pytchat/wiki/LiveChatAsync))
[LiveChatAsync](https://github.com/taizan-hokuto/pytchat/wiki/LiveChatAsync)
#### [YT API compatible chat processor]https://github.com/taizan-hokuto/pytchat/wiki/CompatibleProcessor) + YT API compatible chat processor ([CompatibleProcessor](https://github.com/taizan-hokuto/pytchat/wiki/CompatibleProcessor))
### [Extract archived chat data](https://github.com/taizan-hokuto/pytchat/wiki/Extractor) + Extract archived chat data ([Extractor](https://github.com/taizan-hokuto/pytchat/wiki/Extractor))
```python
from pytchat import HTMLArchiver, Extractor
video_id = "*******"
ex = Extractor(
video_id,
div=10,
processor=HTMLArchiver("c:/test.html")
)
ex.extract()
print("finished.")
```
## Structure of Default Processor ## Structure of Default Processor
Each item can be got with `sync_items()` function. Each item can be got with `sync_items()` function.

View File

@@ -1,8 +1,8 @@
""" """
pytchat is a lightweight python library to browse youtube livechat without Selenium or BeautifulSoup. pytchat is a lightweight python library to browse youtube livechat without Selenium or BeautifulSoup.
""" """
__copyright__ = 'Copyright (C) 2019 taizan-hokuto' __copyright__ = 'Copyright (C) 2019, 2020 taizan-hokuto'
__version__ = '0.4.0' __version__ = '0.4.4'
__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'

View File

@@ -51,7 +51,7 @@ class HTMLArchiver(ChatProcessor):
self.client = httpx.Client(http2=True) self.client = httpx.Client(http2=True)
self.save_path = self._checkpath(save_path) self.save_path = self._checkpath(save_path)
self.processor = DefaultProcessor() self.processor = DefaultProcessor()
self.emoji_table = {} # tuble for custom emojis. key: emoji_id, value: base64 encoded image binary. self.emoji_table = {} # dict for custom emojis. key: emoji_id, value: base64 encoded image binary.
self.header = [HEADER_HTML] self.header = [HEADER_HTML]
self.body = ['<body>\n', '<table class="css">\n', self._parse_table_header(fmt_headers)] self.body = ['<body>\n', '<table class="css">\n', self._parse_table_header(fmt_headers)]
self.callback = callback self.callback = callback
@@ -123,7 +123,6 @@ class HTMLArchiver(ChatProcessor):
resp = self.client.get(url, timeout=30) resp = self.client.get(url, timeout=30)
break break
except httpx.HTTPError as e: except httpx.HTTPError as e:
print("Network Error. retrying...")
err = e err = e
time.sleep(3) time.sleep(3)
else: else:
@@ -132,7 +131,7 @@ class HTMLArchiver(ChatProcessor):
return standard_b64encode(resp.content).decode() return standard_b64encode(resp.content).decode()
def _set_emoji_table(self, item: dict): def _set_emoji_table(self, item: dict):
emoji_id = item['id'] emoji_id = ''.join(('Z', item['id'])) if 48 <= ord(item['id'][0]) <= 57 else item['id']
if emoji_id not in self.emoji_table: if emoji_id not in self.emoji_table:
self.emoji_table.setdefault(emoji_id, self.executor.submit(self._encode_img, item['url'])) self.emoji_table.setdefault(emoji_id, self.executor.submit(self._encode_img, item['url']))
return emoji_id return emoji_id

View File

@@ -1,4 +1,4 @@
httpx[http2]==0.14.1 httpx[http2]==0.16.1
protobuf==3.13.0 protobuf==3.14.0
pytz pytz
urllib3 urllib3

View File

@@ -1,4 +1,2 @@
mock pytest-mock==3.3.1
mocker pytest-httpx==0.10.0
pytest
pytest_httpx

View File

@@ -1,8 +1,17 @@
import json import json
from datetime import datetime
from pytchat.parser.live import Parser from pytchat.parser.live import Parser
from pytchat.processors.default.processor import DefaultProcessor from pytchat.processors.default.processor import DefaultProcessor
TEST_TIMETSTAMP = 1570678496000000
def get_local_datetime(timestamp):
dt = datetime.fromtimestamp(timestamp / 1000000)
return dt.strftime('%Y-%m-%d %H:%M:%S')
def test_textmessage(mocker): def test_textmessage(mocker):
'''text message''' '''text message'''
processor = DefaultProcessor() processor = DefaultProcessor()
@@ -20,7 +29,7 @@ def test_textmessage(mocker):
assert ret.id == "dummy_id" assert ret.id == "dummy_id"
assert ret.message == "dummy_message" assert ret.message == "dummy_message"
assert ret.timestamp == 1570678496000 assert ret.timestamp == 1570678496000
assert ret.datetime == "2019-10-10 12:34:56" assert ret.datetime == get_local_datetime(TEST_TIMETSTAMP)
assert ret.author.name == "author_name" assert ret.author.name == "author_name"
assert ret.author.channelId == "author_channel_id" assert ret.author.channelId == "author_channel_id"
assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id" assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id"
@@ -51,7 +60,7 @@ def test_textmessage_replay_member(mocker):
assert ret.message == "dummy_message" assert ret.message == "dummy_message"
assert ret.messageEx == ["dummy_message"] assert ret.messageEx == ["dummy_message"]
assert ret.timestamp == 1570678496000 assert ret.timestamp == 1570678496000
assert ret.datetime == "2019-10-10 12:34:56" assert ret.datetime == get_local_datetime(TEST_TIMETSTAMP)
assert ret.elapsedTime == "1:23:45" assert ret.elapsedTime == "1:23:45"
assert ret.author.name == "author_name" assert ret.author.name == "author_name"
assert ret.author.channelId == "author_channel_id" assert ret.author.channelId == "author_channel_id"
@@ -83,7 +92,7 @@ def test_superchat(mocker):
assert ret.message == "dummy_message" assert ret.message == "dummy_message"
assert ret.messageEx == ["dummy_message"] assert ret.messageEx == ["dummy_message"]
assert ret.timestamp == 1570678496000 assert ret.timestamp == 1570678496000
assert ret.datetime == "2019-10-10 12:34:56" assert ret.datetime == get_local_datetime(TEST_TIMETSTAMP)
assert ret.elapsedTime == "" assert ret.elapsedTime == ""
assert ret.amountValue == 800 assert ret.amountValue == 800
assert ret.amountString == "¥800" assert ret.amountString == "¥800"
@@ -125,7 +134,7 @@ def test_supersticker(mocker):
assert ret.message == "" assert ret.message == ""
assert ret.messageEx == [] assert ret.messageEx == []
assert ret.timestamp == 1570678496000 assert ret.timestamp == 1570678496000
assert ret.datetime == "2019-10-10 12:34:56" assert ret.datetime == get_local_datetime(TEST_TIMETSTAMP)
assert ret.elapsedTime == "" assert ret.elapsedTime == ""
assert ret.amountValue == 200 assert ret.amountValue == 200
assert ret.amountString == "¥200" assert ret.amountString == "¥200"
@@ -166,7 +175,7 @@ def test_sponsor(mocker):
assert ret.message == "新規メンバー" assert ret.message == "新規メンバー"
assert ret.messageEx == ["新規メンバー"] assert ret.messageEx == ["新規メンバー"]
assert ret.timestamp == 1570678496000 assert ret.timestamp == 1570678496000
assert ret.datetime == "2019-10-10 12:34:56" assert ret.datetime == get_local_datetime(TEST_TIMETSTAMP)
assert ret.elapsedTime == "" assert ret.elapsedTime == ""
assert ret.bgColor == 0 assert ret.bgColor == 0
assert ret.author.name == "author_name" assert ret.author.name == "author_name"
@@ -199,7 +208,7 @@ def test_sponsor_legacy(mocker):
assert ret.message == "新規メンバー / ようこそ、author_name" assert ret.message == "新規メンバー / ようこそ、author_name"
assert ret.messageEx == ["新規メンバー / ようこそ、author_name"] assert ret.messageEx == ["新規メンバー / ようこそ、author_name"]
assert ret.timestamp == 1570678496000 assert ret.timestamp == 1570678496000
assert ret.datetime == "2019-10-10 12:34:56" assert ret.datetime == get_local_datetime(TEST_TIMETSTAMP)
assert ret.elapsedTime == "" assert ret.elapsedTime == ""
assert ret.bgColor == 0 assert ret.bgColor == 0
assert ret.author.name == "author_name" assert ret.author.name == "author_name"