Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c7dc03d06 | ||
|
|
f511049eaa | ||
|
|
76118ba196 | ||
|
|
83b10ab2f3 | ||
|
|
604c52e608 | ||
|
|
8949599232 | ||
|
|
c9c235061c | ||
|
|
328889689f | ||
|
|
e865c25a4e | ||
|
|
6d581c22f9 | ||
|
|
87aadeeb58 | ||
|
|
d331131cfe | ||
|
|
0dce1b009a | ||
|
|
bd77cd2058 | ||
|
|
8e47e8d5db | ||
|
|
946dd155d8 | ||
|
|
3565153597 | ||
|
|
f6c6ec5603 | ||
|
|
1cd0bab60b | ||
|
|
1c246eea6d | ||
|
|
3a365243d9 | ||
|
|
7f8882c9e9 | ||
|
|
74f1536553 |
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
|
||||
#logs
|
||||
log.txt
|
||||
*.log
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# VSCode
|
||||
.vscode
|
||||
|
||||
# develop
|
||||
*.egg-info/
|
||||
|
||||
# Pypi dist
|
||||
dist/
|
||||
README.rst
|
||||
temporary/
|
||||
# Pypi wheel
|
||||
build/
|
||||
exclude/
|
||||
2
Pipfile
2
Pipfile
@@ -4,7 +4,7 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
httpx = {extras = ["http2"], version = "0.16.1"}
|
||||
httpx = {extras = ["http2"]}
|
||||
|
||||
[dev-packages]
|
||||
pytest-mock = "*"
|
||||
|
||||
328
Pipfile.lock
generated
328
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "e1eb34f14c75998519a90838b283ccd23bd168afa8e4837f956c5c4df66376f9"
|
||||
"sha256": "74b83f2e50bc16f8d90c06ddc775d24ee427f8481a2501f62170bf5b76a2f1bd"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -14,18 +14,27 @@
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"anyio": {
|
||||
"hashes": [
|
||||
"sha256:929a6852074397afe1d989002aa96d457e3e1e5441357c60d03e7eea0e65e1b0",
|
||||
"sha256:ae57a67583e5ff8b4af47666ff5651c3732d45fd26c929253748e796af860374"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.2'",
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
|
||||
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
|
||||
],
|
||||
"version": "==2020.12.5"
|
||||
"version": "==2021.5.30"
|
||||
},
|
||||
"h11": {
|
||||
"hashes": [
|
||||
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
|
||||
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.12.0"
|
||||
},
|
||||
"h2": {
|
||||
@@ -44,21 +53,22 @@
|
||||
},
|
||||
"httpcore": {
|
||||
"hashes": [
|
||||
"sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9",
|
||||
"sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc"
|
||||
"sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e",
|
||||
"sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff"
|
||||
],
|
||||
"version": "==0.12.3"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.13.6"
|
||||
},
|
||||
"httpx": {
|
||||
"extras": [
|
||||
"http2"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537",
|
||||
"sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b"
|
||||
"sha256:979afafecb7d22a1d10340bafb403cf2cb75aff214426ff206521fc79d26408c",
|
||||
"sha256:9f99c15d33642d38bce8405df088c1c4cfd940284b4290cacbfb02e64f4877c6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.16.1"
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"hyperframe": {
|
||||
"hashes": [
|
||||
@@ -69,165 +79,135 @@
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16",
|
||||
"sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"
|
||||
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
|
||||
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
|
||||
],
|
||||
"version": "==3.1"
|
||||
"version": "==3.2"
|
||||
},
|
||||
"rfc3986": {
|
||||
"extras": [
|
||||
"idna2008"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d",
|
||||
"sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"
|
||||
"sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
|
||||
"sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
|
||||
],
|
||||
"version": "==1.4.0"
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"sniffio": {
|
||||
"hashes": [
|
||||
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
|
||||
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.2.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"anyio": {
|
||||
"hashes": [
|
||||
"sha256:929a6852074397afe1d989002aa96d457e3e1e5441357c60d03e7eea0e65e1b0",
|
||||
"sha256:ae57a67583e5ff8b4af47666ff5651c3732d45fd26c929253748e796af860374"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.2'",
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197",
|
||||
"sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
|
||||
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
|
||||
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
|
||||
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
|
||||
],
|
||||
"version": "==20.3.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==21.2.0"
|
||||
},
|
||||
"bleach": {
|
||||
"hashes": [
|
||||
"sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125",
|
||||
"sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"
|
||||
"sha256:306483a5a9795474160ad57fce3ddd1b50551e981eed8e15a582d34cef28aafa",
|
||||
"sha256:ae976d7174bba988c0b632def82fdc94235756edfb14e6558a9c5be555c9fb78"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.3.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
|
||||
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
|
||||
],
|
||||
"version": "==2020.12.5"
|
||||
"version": "==2021.5.30"
|
||||
},
|
||||
"cffi": {
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e",
|
||||
"sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d",
|
||||
"sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a",
|
||||
"sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec",
|
||||
"sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362",
|
||||
"sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668",
|
||||
"sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c",
|
||||
"sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b",
|
||||
"sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06",
|
||||
"sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698",
|
||||
"sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2",
|
||||
"sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c",
|
||||
"sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7",
|
||||
"sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009",
|
||||
"sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03",
|
||||
"sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b",
|
||||
"sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e",
|
||||
"sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909",
|
||||
"sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53",
|
||||
"sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35",
|
||||
"sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26",
|
||||
"sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b",
|
||||
"sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01",
|
||||
"sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb",
|
||||
"sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293",
|
||||
"sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd",
|
||||
"sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d",
|
||||
"sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3",
|
||||
"sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d",
|
||||
"sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e",
|
||||
"sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca",
|
||||
"sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d",
|
||||
"sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775",
|
||||
"sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375",
|
||||
"sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b",
|
||||
"sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b",
|
||||
"sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"
|
||||
"sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1",
|
||||
"sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"
|
||||
],
|
||||
"version": "==1.14.4"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"version": "==4.0.0"
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==2.0.3"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
],
|
||||
"markers": "platform_system == 'Windows' and sys_platform == 'win32'",
|
||||
"version": "==0.4.4"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d",
|
||||
"sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7",
|
||||
"sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901",
|
||||
"sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c",
|
||||
"sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244",
|
||||
"sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6",
|
||||
"sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5",
|
||||
"sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e",
|
||||
"sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c",
|
||||
"sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0",
|
||||
"sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812",
|
||||
"sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a",
|
||||
"sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030",
|
||||
"sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302"
|
||||
],
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
||||
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
||||
"sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125",
|
||||
"sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"
|
||||
],
|
||||
"version": "==0.16"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.17.1"
|
||||
},
|
||||
"h11": {
|
||||
"hashes": [
|
||||
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
|
||||
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.12.0"
|
||||
},
|
||||
"httpcore": {
|
||||
"hashes": [
|
||||
"sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9",
|
||||
"sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc"
|
||||
"sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e",
|
||||
"sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff"
|
||||
],
|
||||
"version": "==0.12.3"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.13.6"
|
||||
},
|
||||
"httpx": {
|
||||
"extras": [
|
||||
"http2"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537",
|
||||
"sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b"
|
||||
"sha256:979afafecb7d22a1d10340bafb403cf2cb75aff214426ff206521fc79d26408c",
|
||||
"sha256:9f99c15d33642d38bce8405df088c1c4cfd940284b4290cacbfb02e64f4877c6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.16.1"
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16",
|
||||
"sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"
|
||||
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
|
||||
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
|
||||
],
|
||||
"version": "==3.1"
|
||||
"version": "==3.2"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac",
|
||||
"sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.6.1"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
@@ -236,40 +216,35 @@
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"jeepney": {
|
||||
"hashes": [
|
||||
"sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657",
|
||||
"sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"
|
||||
],
|
||||
"markers": "sys_platform == 'linux'",
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"keyring": {
|
||||
"hashes": [
|
||||
"sha256:9acb3e1452edbb7544822b12fd25459078769e560fa51f418b6d00afaa6178df",
|
||||
"sha256:9f44660a5d4931bdc14c08a1d01ef30b18a7a8147380710d8c9f9531e1f6c3c0"
|
||||
"sha256:045703609dd3fccfcdb27da201684278823b72af515aedec1a8515719a038cb8",
|
||||
"sha256:8f607d7d1cc502c43a932a275a56fe47db50271904513a379d39df1af277ac48"
|
||||
],
|
||||
"version": "==22.0.1"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==23.0.1"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
|
||||
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
|
||||
"sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
|
||||
"sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
|
||||
],
|
||||
"version": "==20.9"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==21.0"
|
||||
},
|
||||
"pkginfo": {
|
||||
"hashes": [
|
||||
"sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4",
|
||||
"sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"
|
||||
"sha256:37ecd857b47e5f55949c41ed061eb51a0bee97a87c969219d144c0e023982779",
|
||||
"sha256:e7432f81d08adec7297633191bbf0bd47faf13cd8724c3a13250e51d542635bd"
|
||||
],
|
||||
"version": "==1.7.0"
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"py": {
|
||||
@@ -277,65 +252,71 @@
|
||||
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
|
||||
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
||||
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
||||
],
|
||||
"version": "==2.20"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
|
||||
"sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
|
||||
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
|
||||
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
|
||||
],
|
||||
"version": "==2.7.4"
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.9.0"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
|
||||
"sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
|
||||
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
|
||||
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
|
||||
],
|
||||
"version": "==6.2.2"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==6.2.4"
|
||||
},
|
||||
"pytest-httpx": {
|
||||
"hashes": [
|
||||
"sha256:0a7c56e559b23efbf857054cd74de60a7c540694a162423f89c70da6ad358d8e",
|
||||
"sha256:d32e8f6fb7e028f0313f5f5a2d463c8673eb43fd11a9bfe8527299717a7764c4"
|
||||
"sha256:1e135b8779060091fa1c87d8aff7904921e8bea95fce5e971a0262764d064b12",
|
||||
"sha256:e262932f2d3ce380da8273c7bacbcfdc2c94e167fa94da29571caaf1f4d3ba27"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.10.1"
|
||||
"version": "==0.12.0"
|
||||
},
|
||||
"pytest-mock": {
|
||||
"hashes": [
|
||||
"sha256:024e405ad382646318c4281948aadf6fe1135632bea9cc67366ea0c4098ef5f2",
|
||||
"sha256:a4d6d37329e4a893e77d9ffa89e838dd2b45d5dc099984cf03c703ac8411bb82"
|
||||
"sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3",
|
||||
"sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.3.1"
|
||||
"version": "==3.6.1"
|
||||
},
|
||||
"pywin32-ctypes": {
|
||||
"hashes": [
|
||||
"sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942",
|
||||
"sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"readme-renderer": {
|
||||
"hashes": [
|
||||
"sha256:267854ac3b1530633c2394ead828afcd060fc273217c42ac36b6be9c42cd9a9d",
|
||||
"sha256:6b7e5aa59210a40de72eb79931491eaf46fefca2952b9181268bd7c7c65c260a"
|
||||
"sha256:63b4075c6698fcfa78e584930f07f39e05d46f3ec97f65006e430b595ca6348c",
|
||||
"sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db"
|
||||
],
|
||||
"version": "==28.0"
|
||||
"version": "==29.0"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
||||
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
|
||||
],
|
||||
"version": "==2.25.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==2.26.0"
|
||||
},
|
||||
"requests-toolbelt": {
|
||||
"hashes": [
|
||||
@@ -349,31 +330,25 @@
|
||||
"idna2008"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d",
|
||||
"sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"
|
||||
"sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
|
||||
"sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
|
||||
],
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"secretstorage": {
|
||||
"hashes": [
|
||||
"sha256:30cfdef28829dad64d6ea1ed08f8eff6aa115a77068926bcc9f5225d5a3246aa",
|
||||
"sha256:5c36f6537a523ec5f969ef9fad61c98eb9e017bc601d811e53aa25bece64892f"
|
||||
],
|
||||
"markers": "sys_platform == 'linux'",
|
||||
"version": "==3.3.0"
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"version": "==1.15.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"sniffio": {
|
||||
"hashes": [
|
||||
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
|
||||
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"toml": {
|
||||
@@ -381,29 +356,32 @@
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a",
|
||||
"sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"
|
||||
"sha256:5aa445ea0ad8b16d82b15ab342de6b195a722d75fc1ef9934a46bba6feafbc64",
|
||||
"sha256:8bb94db0d4468fea27d004a0f1d1c02da3cdedc00fe491c0de986b76a04d6b0a"
|
||||
],
|
||||
"version": "==4.56.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==4.61.2"
|
||||
},
|
||||
"twine": {
|
||||
"hashes": [
|
||||
"sha256:34352fd52ec3b9d29837e6072d5a2a7c6fe4290e97bba46bb8d478b5c598f7ab",
|
||||
"sha256:ba9ff477b8d6de0c89dd450e70b2185da190514e91c42cc62f96850025c10472"
|
||||
"sha256:087328e9bb405e7ce18527a2dca4042a84c7918658f951110b38bc135acab218",
|
||||
"sha256:4caec0f1ed78dc4c9b83ad537e453d03ce485725f2aea57f1bb3fdde78dae936"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.0"
|
||||
"version": "==3.4.2"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
|
||||
"sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
|
||||
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
|
||||
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
|
||||
],
|
||||
"version": "==1.26.3"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.6"
|
||||
},
|
||||
"webencodings": {
|
||||
"hashes": [
|
||||
@@ -414,11 +392,19 @@
|
||||
},
|
||||
"wheel": {
|
||||
"hashes": [
|
||||
"sha256:906864fb722c0ab5f2f9c35b2c65e3af3c009402c108a709c0aca27bc2c9187b",
|
||||
"sha256:aaef9b8c36db72f8bf7f1e54f85f875c4d466819940863ca0b3f3f77f0a1646f"
|
||||
"sha256:78b5b185f0e5763c26ca1e324373aadd49182ca90e825f7853f4b2509215dc0e",
|
||||
"sha256:e11eefd162658ea59a60a0f6c7d493a7190ea4b9a85e335b33489d9f17e0245e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.36.1"
|
||||
"version": "==0.36.2"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
|
||||
"sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
pytchat is a lightweight python library to browse youtube livechat without Selenium or BeautifulSoup.
|
||||
"""
|
||||
__copyright__ = 'Copyright (C) 2019, 2020 taizan-hokuto'
|
||||
__version__ = '0.5.3'
|
||||
__copyright__ = 'Copyright (C) 2019, 2020, 2021 taizan-hokuto'
|
||||
__version__ = '0.5.5'
|
||||
__license__ = 'MIT'
|
||||
__author__ = 'taizan-hokuto'
|
||||
__author_email__ = '55448286+taizan-hokuto@users.noreply.github.com'
|
||||
|
||||
@@ -4,7 +4,9 @@ from base64 import a85decode as dc
|
||||
headers = {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36 Edg/86.0.622.63,gzip(gfe)',
|
||||
}
|
||||
|
||||
m_headers = {
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36 Edg/91.0.864.59',
|
||||
}
|
||||
_sml = dc(b"BQS?8F#ks-GB\\6`H#IhIF^eo7@rH3;H#IhIF^eor06T''Ch\\'(?XmbXF>%9<FC/iuG%G#jBOQ!ICLqcS5tQB2;gCZ)?UdXC;f$GR3)MM2<(0>O7mh!,G@+K5?SO9T@okV").decode()
|
||||
_smr = dc(b"BQS?8F#ks-GB\\6`H#IhIF^eo7@rH3;H#IhIF^eor06T''Ch\\'(?XmbXF>%9<FC/iuG%G#jBOQ!iEb03+@<k(QAU-F)8U=fDGsP557S5F7CiNH7;)D3N77^*B6YU@\\?WfBr0emZX=#^").decode()
|
||||
|
||||
|
||||
@@ -29,9 +29,13 @@ class PytchatCore:
|
||||
|
||||
processor : ChatProcessor
|
||||
|
||||
client : httpx.Client
|
||||
The client for connecting youtube.
|
||||
You can specify any customized httpx client (e.g. coolies, user agent).
|
||||
|
||||
interruptable : bool
|
||||
Allows keyboard interrupts.
|
||||
Set this parameter to False if your own threading program causes
|
||||
Set this parameter to False if your own multi-threading program causes
|
||||
the problem.
|
||||
|
||||
force_replay : bool
|
||||
@@ -57,6 +61,7 @@ class PytchatCore:
|
||||
def __init__(self, video_id,
|
||||
seektime=-1,
|
||||
processor=DefaultProcessor(),
|
||||
client = httpx.Client(http2=True),
|
||||
interruptable=True,
|
||||
force_replay=False,
|
||||
topchat_only=False,
|
||||
@@ -64,6 +69,7 @@ class PytchatCore:
|
||||
logger=config.logger(__name__),
|
||||
replay_continuation=None
|
||||
):
|
||||
self._client = client
|
||||
self._video_id = util.extract_video_id(video_id)
|
||||
self.seektime = seektime
|
||||
if isinstance(processor, tuple):
|
||||
@@ -97,7 +103,7 @@ class PytchatCore:
|
||||
"""
|
||||
self.continuation = liveparam.getparam(
|
||||
self._video_id,
|
||||
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
|
||||
channel_id=util.get_channelid(self._client, self._video_id),
|
||||
past_sec=3)
|
||||
|
||||
def _get_chat_component(self):
|
||||
@@ -110,19 +116,18 @@ class PytchatCore:
|
||||
parameter for next chat data
|
||||
'''
|
||||
try:
|
||||
with httpx.Client(http2=True) as client:
|
||||
if self.continuation and self._is_alive:
|
||||
contents = self._get_contents(self.continuation, client, headers)
|
||||
metadata, chatdata = self._parser.parse(contents)
|
||||
timeout = metadata['timeoutMs'] / 1000
|
||||
chat_component = {
|
||||
"video_id": self._video_id,
|
||||
"timeout": timeout,
|
||||
"chatdata": chatdata
|
||||
}
|
||||
self.continuation = metadata.get('continuation')
|
||||
self._last_offset_ms = metadata.get('last_offset_ms', 0)
|
||||
return chat_component
|
||||
if self.continuation and self._is_alive:
|
||||
contents = self._get_contents(self.continuation, self._client, headers)
|
||||
metadata, chatdata = self._parser.parse(contents)
|
||||
timeout = metadata['timeoutMs'] / 1000
|
||||
chat_component = {
|
||||
"video_id": self._video_id,
|
||||
"timeout": timeout,
|
||||
"chatdata": chatdata
|
||||
}
|
||||
self.continuation = metadata.get('continuation')
|
||||
self._last_offset_ms = metadata.get('last_offset_ms', 0)
|
||||
return chat_component
|
||||
except exceptions.ChatParseException as e:
|
||||
self._logger.debug(f"[{self._video_id}]{str(e)}")
|
||||
self._raise_exception(e)
|
||||
@@ -139,9 +144,8 @@ class PytchatCore:
|
||||
-------
|
||||
'continuationContents' which includes metadata & chat data.
|
||||
'''
|
||||
livechat_json = (
|
||||
self._get_livechat_json(continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms)
|
||||
)
|
||||
livechat_json = self._get_livechat_json(
|
||||
continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms)
|
||||
contents, dat = self._parser.get_contents(livechat_json)
|
||||
if self._dat == '' and dat:
|
||||
self._dat = dat
|
||||
@@ -152,7 +156,8 @@ class PytchatCore:
|
||||
self._fetch_url = config._smr
|
||||
continuation = arcparam.getparam(
|
||||
self._video_id, self.seektime, self._topchat_only, util.get_channelid(client, self._video_id))
|
||||
livechat_json = self._get_livechat_json(continuation, client, replay=True, offset_ms=self.seektime * 1000)
|
||||
livechat_json = self._get_livechat_json(
|
||||
continuation, client, replay=True, offset_ms=self.seektime * 1000)
|
||||
reload_continuation = self._parser.reload_continuation(
|
||||
self._parser.get_contents(livechat_json)[0])
|
||||
if reload_continuation:
|
||||
@@ -173,15 +178,14 @@ class PytchatCore:
|
||||
offset_ms = 0
|
||||
param = util.get_param(continuation, dat=self._dat, replay=replay, offsetms=offset_ms)
|
||||
for _ in range(MAX_RETRY + 1):
|
||||
with httpx.Client(http2=True) as client:
|
||||
try:
|
||||
response = client.post(self._fetch_url, json=param)
|
||||
livechat_json = json.loads(response.text)
|
||||
break
|
||||
except (json.JSONDecodeError, httpx.ConnectTimeout, httpx.ReadTimeout, httpx.ConnectError) as e:
|
||||
err = e
|
||||
time.sleep(2)
|
||||
continue
|
||||
try:
|
||||
response = client.post(self._fetch_url, json=param)
|
||||
livechat_json = response.json()
|
||||
break
|
||||
except (json.JSONDecodeError, httpx.ConnectTimeout, httpx.ReadTimeout, httpx.ConnectError) as e:
|
||||
err = e
|
||||
time.sleep(2)
|
||||
continue
|
||||
else:
|
||||
self._logger.error(f"[{self._video_id}]"
|
||||
f"Exceeded retry count. Last error: {str(err)}")
|
||||
@@ -202,6 +206,8 @@ class PytchatCore:
|
||||
return self._is_alive
|
||||
|
||||
def terminate(self):
|
||||
if not self.is_alive():
|
||||
return
|
||||
self._is_alive = False
|
||||
self.processor.finalize()
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ class LiveChatAsync:
|
||||
seektime=-1,
|
||||
processor=DefaultProcessor(),
|
||||
buffer=None,
|
||||
client = httpx.AsyncClient(http2=True),
|
||||
interruptable=True,
|
||||
callback=None,
|
||||
done_callback=None,
|
||||
@@ -88,6 +89,7 @@ class LiveChatAsync:
|
||||
logger=config.logger(__name__),
|
||||
replay_continuation=None
|
||||
):
|
||||
self._client:httpx.AsyncClient = client
|
||||
self._video_id = util.extract_video_id(video_id)
|
||||
self.seektime = seektime
|
||||
if isinstance(processor, tuple):
|
||||
@@ -152,9 +154,10 @@ class LiveChatAsync:
|
||||
create and start _listen loop.
|
||||
"""
|
||||
if not self.continuation:
|
||||
channel_id = await util.get_channelid_async(self._client, self._video_id)
|
||||
self.continuation = liveparam.getparam(
|
||||
self._video_id,
|
||||
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
|
||||
channel_id,
|
||||
past_sec=3)
|
||||
|
||||
await self._listen(self.continuation)
|
||||
@@ -169,10 +172,10 @@ class LiveChatAsync:
|
||||
parameter for next chat data
|
||||
'''
|
||||
try:
|
||||
async with httpx.AsyncClient(http2=True) as client:
|
||||
async with self._client as client:
|
||||
while(continuation and self._is_alive):
|
||||
continuation = await self._check_pause(continuation)
|
||||
contents = await self._get_contents(continuation, client, headers)
|
||||
contents = await self._get_contents(continuation, client, headers) #Q#
|
||||
metadata, chatdata = self._parser.parse(contents)
|
||||
continuation = metadata.get('continuation')
|
||||
if continuation:
|
||||
@@ -214,9 +217,10 @@ class LiveChatAsync:
|
||||
'''
|
||||
self._pauser.put_nowait(None)
|
||||
if not self._is_replay:
|
||||
async with httpx.AsyncClient(http2=True) as client:
|
||||
continuation = await liveparam.getparam(self._video_id,
|
||||
channel_id=util.get_channelid_async(client, self.video_id),
|
||||
async with self._client as client:
|
||||
channel_id = await util.get_channelid_async(client, self.video_id)
|
||||
continuation = liveparam.getparam(self._video_id,
|
||||
channel_id,
|
||||
past_sec=3)
|
||||
|
||||
return continuation
|
||||
@@ -338,12 +342,14 @@ class LiveChatAsync:
|
||||
self._logger.debug(f'[{self._video_id}] cancelled:{sender}')
|
||||
|
||||
def terminate(self):
|
||||
if not self.is_alive():
|
||||
return
|
||||
if self._pauser.empty():
|
||||
self._pauser.put_nowait(None)
|
||||
self._is_alive = False
|
||||
self._buffer.put_nowait({})
|
||||
self.processor.finalize()
|
||||
|
||||
|
||||
def _keyboard_interrupt(self):
|
||||
self.exception = exceptions.ChatDataFinished()
|
||||
self.terminate()
|
||||
|
||||
@@ -78,6 +78,7 @@ class LiveChat:
|
||||
def __init__(self, video_id,
|
||||
seektime=-1,
|
||||
processor=DefaultProcessor(),
|
||||
client = httpx.Client(http2=True),
|
||||
buffer=None,
|
||||
interruptable=True,
|
||||
callback=None,
|
||||
@@ -88,6 +89,7 @@ class LiveChat:
|
||||
logger=config.logger(__name__),
|
||||
replay_continuation=None
|
||||
):
|
||||
self._client = client
|
||||
self._video_id = util.extract_video_id(video_id)
|
||||
self.seektime = seektime
|
||||
if isinstance(processor, tuple):
|
||||
@@ -150,7 +152,7 @@ class LiveChat:
|
||||
if not self.continuation:
|
||||
self.continuation = liveparam.getparam(
|
||||
self._video_id,
|
||||
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
|
||||
channel_id=util.get_channelid(self._client, self._video_id),
|
||||
past_sec=3)
|
||||
self._listen(self.continuation)
|
||||
|
||||
@@ -164,7 +166,7 @@ class LiveChat:
|
||||
parameter for next chat data
|
||||
'''
|
||||
try:
|
||||
with httpx.Client(http2=True) as client:
|
||||
with self._client as client:
|
||||
while(continuation and self._is_alive):
|
||||
continuation = self._check_pause(continuation)
|
||||
contents = self._get_contents(continuation, client, headers)
|
||||
@@ -224,7 +226,8 @@ class LiveChat:
|
||||
-------
|
||||
'continuationContents' which includes metadata & chat data.
|
||||
'''
|
||||
livechat_json = self._get_livechat_json(continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms)
|
||||
livechat_json = self._get_livechat_json(
|
||||
continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms)
|
||||
contents, dat = self._parser.get_contents(livechat_json)
|
||||
if self._dat == '' and dat:
|
||||
self._dat = dat
|
||||
@@ -235,8 +238,8 @@ class LiveChat:
|
||||
self._fetch_url = config._smr
|
||||
continuation = arcparam.getparam(
|
||||
self._video_id, self.seektime, self._topchat_only, util.get_channelid(client, self._video_id))
|
||||
livechat_json = (self._get_livechat_json(
|
||||
continuation, client, replay=True, offset_ms=self.seektime * 1000))
|
||||
livechat_json = self._get_livechat_json(
|
||||
continuation, client, replay=True, offset_ms=self.seektime * 1000)
|
||||
reload_continuation = self._parser.reload_continuation(
|
||||
self._parser.get_contents(livechat_json)[0])
|
||||
if reload_continuation:
|
||||
@@ -331,6 +334,8 @@ class LiveChat:
|
||||
self._logger.debug(f'[{self._video_id}] cancelled:{sender}')
|
||||
|
||||
def terminate(self):
|
||||
if not self.is_alive():
|
||||
return
|
||||
if self._pauser.empty():
|
||||
self._pauser.put_nowait(None)
|
||||
self._is_alive = False
|
||||
|
||||
@@ -7,6 +7,7 @@ from .renderer.paidmessage import LiveChatPaidMessageRenderer
|
||||
from .renderer.paidsticker import LiveChatPaidStickerRenderer
|
||||
from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer
|
||||
from .renderer.membership import LiveChatMembershipItemRenderer
|
||||
from .renderer.donation import LiveChatDonationAnnouncementRenderer
|
||||
from .. chat_processor import ChatProcessor
|
||||
from ... import config
|
||||
|
||||
@@ -124,7 +125,8 @@ class DefaultProcessor(ChatProcessor):
|
||||
"liveChatPaidMessageRenderer": LiveChatPaidMessageRenderer(),
|
||||
"liveChatPaidStickerRenderer": LiveChatPaidStickerRenderer(),
|
||||
"liveChatLegacyPaidMessageRenderer": LiveChatLegacyPaidMessageRenderer(),
|
||||
"liveChatMembershipItemRenderer": LiveChatMembershipItemRenderer()
|
||||
"liveChatMembershipItemRenderer": LiveChatMembershipItemRenderer(),
|
||||
"liveChatDonationAnnouncementRenderer": LiveChatDonationAnnouncementRenderer(),
|
||||
}
|
||||
|
||||
def process(self, chat_components: list):
|
||||
|
||||
6
pytchat/processors/default/renderer/donation.py
Normal file
6
pytchat/processors/default/renderer/donation.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .base import BaseRenderer
|
||||
|
||||
|
||||
class LiveChatDonationAnnouncementRenderer(BaseRenderer):
|
||||
def settype(self):
|
||||
self.chat.type = "donation"
|
||||
@@ -82,16 +82,17 @@ class RingQueue:
|
||||
|
||||
class SpeedCalculator(ChatProcessor, RingQueue):
|
||||
"""
|
||||
チャットの勢いを計算する。
|
||||
Calculate the momentum of the chat.
|
||||
|
||||
一定期間のチャットデータのうち、最初のチャットの投稿時刻と
|
||||
最後のチャットの投稿時刻の差を、チャット数で割り返し
|
||||
1分あたりの速度に換算する。
|
||||
Divide the difference between the time of the first chat and
|
||||
the time of the last chat in the chat data over a period of
|
||||
time by the number of chats and convert it to speed per minute.
|
||||
|
||||
Parameter
|
||||
----------
|
||||
capacity : int
|
||||
RingQueueに格納するチャット勢い算出用データの最大数
|
||||
Maximum number of data for calculating chat momentum
|
||||
to be stored in RingQueue.
|
||||
"""
|
||||
|
||||
def __init__(self, capacity=10):
|
||||
@@ -111,17 +112,17 @@ class SpeedCalculator(ChatProcessor, RingQueue):
|
||||
|
||||
def _calc_speed(self):
|
||||
"""
|
||||
RingQueue内のチャット勢い算出用データリストを元に、
|
||||
チャット速度を計算して返す
|
||||
Calculates the chat speed based on the data list for calculating
|
||||
the chat momentum in RingQueue.
|
||||
|
||||
Return
|
||||
---------------------------
|
||||
チャット速度(1分間で換算したチャット数)
|
||||
Chat speed (number of chats converted per minute)
|
||||
"""
|
||||
try:
|
||||
# キュー内の総チャット数
|
||||
# Total number of chats in the queue
|
||||
total = sum(item['chat_count'] for item in self.items)
|
||||
# キュー内の最初と最後のチャットの時間差
|
||||
# Interval between the first and last chats in the queue
|
||||
duration = (self.items[self.last_pos]['endtime'] - self.items[self.first_pos]['starttime'])
|
||||
if duration != 0:
|
||||
return int(total * 60 / duration)
|
||||
@@ -131,19 +132,12 @@ class SpeedCalculator(ChatProcessor, RingQueue):
|
||||
|
||||
def _put_chatdata(self, actions):
|
||||
"""
|
||||
チャットデータからタイムスタンプを読み取り、勢い測定用のデータを組み立て、
|
||||
RingQueueに投入する。
|
||||
200円以上のスパチャはtickerとmessageの2つのデータが生成されるが、
|
||||
tickerの方は時刻データの場所が異なることを利用し、勢いの集計から除外している。
|
||||
Parameter
|
||||
---------
|
||||
actions : List[dict]
|
||||
チャットデータ(addChatItemAction) のリスト
|
||||
List of addChatItemActions
|
||||
"""
|
||||
def _put_emptydata():
|
||||
'''
|
||||
チャットデータがない場合に空のデータをキューに投入する。
|
||||
'''
|
||||
timestamp_now = int(time.time())
|
||||
self.put({
|
||||
'chat_count': 0,
|
||||
@@ -152,9 +146,6 @@ class SpeedCalculator(ChatProcessor, RingQueue):
|
||||
})
|
||||
|
||||
def _get_timestamp(action: dict):
|
||||
"""
|
||||
チャットデータから時刻データを取り出す。
|
||||
"""
|
||||
try:
|
||||
item = action['addChatItemAction']['item']
|
||||
timestamp = int(item[list(item.keys())[0]]['timestampUsec'])
|
||||
@@ -166,32 +157,24 @@ class SpeedCalculator(ChatProcessor, RingQueue):
|
||||
_put_emptydata()
|
||||
return
|
||||
|
||||
# actions内の時刻データを持つチャットデータの数
|
||||
counter = 0
|
||||
# actions内の最初のチャットデータの時刻
|
||||
starttime = None
|
||||
# actions内の最後のチャットデータの時刻
|
||||
endtime = None
|
||||
|
||||
for action in actions:
|
||||
# チャットデータからtimestampUsecを読み取る
|
||||
# Get timestampUsec from chatdata
|
||||
gettime = _get_timestamp(action)
|
||||
|
||||
# 時刻のないデータだった場合は次の行のデータで読み取り試行
|
||||
if gettime is None:
|
||||
continue
|
||||
|
||||
# 最初に有効な時刻を持つデータのtimestampをstarttimeに設定
|
||||
if starttime is None:
|
||||
starttime = gettime
|
||||
|
||||
# 最後のtimestampを設定(途中で時刻のないデータの場合もあるので上書きしていく)
|
||||
endtime = gettime
|
||||
|
||||
# チャットの数をインクリメント
|
||||
counter += 1
|
||||
|
||||
# チャット速度用のデータをRingQueueに送る
|
||||
if starttime is None or endtime is None:
|
||||
_put_emptydata()
|
||||
return
|
||||
|
||||
@@ -13,6 +13,8 @@ PATTERN_YTURL = re.compile(r"((?<=(v|V)/)|(?<=be/)|(?<=(\?|\&)v=)|(?<=embed/))([
|
||||
|
||||
PATTERN_CHANNEL = re.compile(r"\\\"channelId\\\":\\\"(.{24})\\\"")
|
||||
|
||||
PATTERN_M_CHANNEL = re.compile(r"\"channelId\":\"(.{24})\"")
|
||||
|
||||
YT_VIDEO_ID_LENGTH = 11
|
||||
|
||||
CLIENT_VERSION = ''.join(("2.", (datetime.datetime.today() - datetime.timedelta(days=1)).strftime("%Y%m%d"), ".01.00"))
|
||||
@@ -100,6 +102,19 @@ def extract_video_id(url_or_id: str) -> str:
|
||||
def get_channelid(client, video_id):
|
||||
resp = client.get("https://www.youtube.com/embed/{}".format(quote(video_id)), headers=config.headers)
|
||||
match = re.search(PATTERN_CHANNEL, resp.text)
|
||||
try:
|
||||
if match is None:
|
||||
raise IndexError
|
||||
ret = match.group(1)
|
||||
except IndexError:
|
||||
ret = get_channelid_2nd(client, video_id)
|
||||
return ret
|
||||
|
||||
|
||||
def get_channelid_2nd(client, video_id):
|
||||
resp = client.get("https://m.youtube.com/watch?v={}".format(quote(video_id)), headers=config.m_headers)
|
||||
|
||||
match = re.search(PATTERN_M_CHANNEL, resp.text)
|
||||
if match is None:
|
||||
raise InvalidVideoIdException(f"Cannot find channel id for video id:{video_id}. This video id seems to be invalid.")
|
||||
try:
|
||||
@@ -108,9 +123,21 @@ def get_channelid(client, video_id):
|
||||
raise InvalidVideoIdException(f"Invalid video id: {video_id}")
|
||||
return ret
|
||||
|
||||
|
||||
async def get_channelid_async(client, video_id):
|
||||
resp = await client.get("https://www.youtube.com/embed/{}".format(quote(video_id)), headers=config.headers)
|
||||
match = re.search(PATTERN_CHANNEL, resp.text)
|
||||
try:
|
||||
if match is None:
|
||||
raise IndexError
|
||||
ret = match.group(1)
|
||||
except IndexError:
|
||||
ret = await get_channelid_async_2nd(client, video_id)
|
||||
return ret
|
||||
|
||||
async def get_channelid_async_2nd(client, video_id):
|
||||
resp = await client.get("https://m.youtube.com/watch?v={}".format(quote(video_id)), headers=config.m_headers)
|
||||
match = re.search(PATTERN_M_CHANNEL, resp.text)
|
||||
if match is None:
|
||||
raise InvalidVideoIdException(f"Cannot find channel id for video id:{video_id}. This video id seems to be invalid.")
|
||||
try:
|
||||
|
||||
@@ -1 +1 @@
|
||||
httpx[http2]==0.16.1
|
||||
httpx[http2]
|
||||
5
setup.py
5
setup.py
@@ -50,8 +50,9 @@ setup(
|
||||
'Natural Language :: Japanese',
|
||||
'Development Status :: 4 - Beta',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
],
|
||||
description="a python library for fetching youtube live chat.",
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
from pytchat.processors.superchat.calculator import SuperchatCalculator
|
||||
|
||||
get_item = SuperchatCalculator()._get_item
|
||||
|
||||
dict_test = {
|
||||
'root':{
|
||||
'node0' : 'value0',
|
||||
'node1' : 'value1',
|
||||
'node2' : {
|
||||
'node2-0' : 'value2-0'
|
||||
},
|
||||
|
||||
'node3' : [
|
||||
{'node3-0' : 'value3-0'},
|
||||
{'node3-1' :
|
||||
{'node3-1-0' : 'value3-1-0'}
|
||||
}
|
||||
],
|
||||
'node4' : [],
|
||||
'node5' : [
|
||||
[
|
||||
{'node5-1-0' : 'value5-1-0'},
|
||||
{'node5-1-1' : 'value5-1-1'},
|
||||
],
|
||||
{'node5-0' : 'value5-0'},
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
items_test0 = [
|
||||
'root',
|
||||
'node1'
|
||||
]
|
||||
|
||||
|
||||
items_test_not_found0 = [
|
||||
'root',
|
||||
'other_data'
|
||||
]
|
||||
|
||||
|
||||
items_test_nest = [
|
||||
'root',
|
||||
'node2',
|
||||
'node2-0'
|
||||
]
|
||||
|
||||
items_test_list0 = [
|
||||
'root',
|
||||
'node3',
|
||||
1,
|
||||
'node3-1'
|
||||
]
|
||||
|
||||
items_test_list1 = [
|
||||
'root',
|
||||
'node3',
|
||||
1,
|
||||
'node3-1',
|
||||
'node3-1-0'
|
||||
]
|
||||
|
||||
items_test_list2 = [
|
||||
'root',
|
||||
'node4',
|
||||
None
|
||||
]
|
||||
|
||||
items_test_list3 = [
|
||||
'root',
|
||||
'node4'
|
||||
]
|
||||
|
||||
items_test_list_nest = [
|
||||
'root',
|
||||
'node5',
|
||||
0,
|
||||
1,
|
||||
'node5-1-1'
|
||||
]
|
||||
|
||||
items_test_list_nest_not_found1 = [
|
||||
'root',
|
||||
'node5',
|
||||
0,
|
||||
1,
|
||||
'node5-1-1',
|
||||
'nodez'
|
||||
]
|
||||
|
||||
items_test_not_found1 = [
|
||||
'root',
|
||||
'node3',
|
||||
2,
|
||||
'node3-1',
|
||||
'node3-1-0'
|
||||
]
|
||||
|
||||
items_test_not_found2 = [
|
||||
'root',
|
||||
'node3',
|
||||
2,
|
||||
'node3-1',
|
||||
'node3-1-0',
|
||||
'nodex'
|
||||
]
|
||||
def test_get_items_0():
|
||||
assert get_item(dict_test, items_test0) == 'value1'
|
||||
|
||||
def test_get_items_1():
|
||||
assert get_item(dict_test, items_test_not_found0) is None
|
||||
|
||||
def test_get_items_2():
|
||||
assert get_item(dict_test, items_test_nest) == 'value2-0'
|
||||
|
||||
def test_get_items_3():
|
||||
assert get_item(
|
||||
dict_test, items_test_list0) == {'node3-1-0' : 'value3-1-0'}
|
||||
|
||||
def test_get_items_4():
|
||||
assert get_item(dict_test, items_test_list1) == 'value3-1-0'
|
||||
|
||||
def test_get_items_5():
|
||||
assert get_item(dict_test, items_test_not_found1) == None
|
||||
|
||||
def test_get_items_6():
|
||||
assert get_item(dict_test, items_test_not_found2) == None
|
||||
|
||||
def test_get_items_7():
|
||||
assert get_item(dict_test, items_test_list2) == None
|
||||
|
||||
def test_get_items_8():
|
||||
assert get_item(dict_test, items_test_list_nest) == 'value5-1-1'
|
||||
|
||||
def test_get_items_9():
|
||||
assert get_item(dict_test, items_test_list_nest_not_found1) == None
|
||||
|
||||
def test_get_items_10():
|
||||
assert get_item(dict_test, items_test_list3) == []
|
||||
@@ -67,6 +67,5 @@ def test_process_2():
|
||||
'chatdata': load_chatdata(r"tests/testdata/calculator/replay_end.json")
|
||||
}
|
||||
assert False
|
||||
SuperchatCalculator().process([chat_component])
|
||||
except ChatParseException:
|
||||
assert True
|
||||
|
||||
24
tests/test_compatible_processor2.py
Normal file
24
tests/test_compatible_processor2.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import pytchat
|
||||
from pytchat.processors.compatible.processor import CompatibleProcessor
|
||||
|
||||
|
||||
root_keys = ('kind', 'etag', 'nextPageToken', 'pollingIntervalMillis', 'pageInfo', 'items')
|
||||
item_keys = ('kind', 'etag', 'id', 'snippet', 'authorDetails')
|
||||
snippet_keys = ('type', 'liveChatId', 'authorChannelId', 'publishedAt', 'hasDisplayContent', 'displayMessage', 'textMessageDetails')
|
||||
author_details_keys = ('channelId', 'channelUrl', 'displayName', 'profileImageUrl', 'isVerified', 'isChatOwner', 'isChatSponsor', 'isChatModerator')
|
||||
|
||||
def test_compatible_processor():
|
||||
stream = pytchat.create("Hj-wnLIYKjw", seektime = 6000, processor=CompatibleProcessor())
|
||||
while stream.is_alive():
|
||||
chat = stream.get()
|
||||
for key in chat.keys():
|
||||
assert key in root_keys
|
||||
for key in chat["items"][0].keys():
|
||||
assert key in item_keys
|
||||
for key in chat["items"][0]["snippet"].keys():
|
||||
assert key in snippet_keys
|
||||
for key in chat["items"][0]["authorDetails"].keys():
|
||||
assert key in author_details_keys
|
||||
break
|
||||
|
||||
|
||||
94
tests/test_default_processor2.py
Normal file
94
tests/test_default_processor2.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import asyncio
|
||||
import pytchat
|
||||
from concurrent.futures import CancelledError
|
||||
from pytchat.core_multithread.livechat import LiveChat
|
||||
from pytchat.core_async.livechat import LiveChatAsync
|
||||
|
||||
cases = [
|
||||
{
|
||||
"video_id":"1X7oL0hDnMg", "seektime":1620,
|
||||
"result1":{'textMessage': 84},
|
||||
"result2":{'': 83, 'MODERATOR': 1}
|
||||
},
|
||||
{
|
||||
"video_id":"Hj-wnLIYKjw", "seektime":420,
|
||||
"result1":{'superChat': 1, 'newSponsor': 6, 'textMessage': 63, 'donation': 1},
|
||||
"result2":{'': 59, 'MEMBER': 12}
|
||||
},
|
||||
{
|
||||
"video_id":"S8dmq5YIUoc", "seektime":3,
|
||||
"result1":{'textMessage': 86},
|
||||
"result2":{'': 62, 'MEMBER': 21, 'OWNER': 2, 'VERIFIED': 1}
|
||||
},{
|
||||
"video_id":"yLrstz80MKs", "seektime":30,
|
||||
"result1":{'superSticker': 8, 'superChat': 2, 'textMessage': 67},
|
||||
"result2":{'': 73, 'MEMBER': 4}
|
||||
}
|
||||
]
|
||||
|
||||
def test_archived_stream():
|
||||
for case in cases:
|
||||
stream = pytchat.create(video_id=case["video_id"], seektime=case["seektime"])
|
||||
while stream.is_alive():
|
||||
chat = stream.get()
|
||||
agg1 = {}
|
||||
agg2 = {}
|
||||
for c in chat.items:
|
||||
if c.type in agg1:
|
||||
agg1[c.type] += 1
|
||||
else:
|
||||
agg1[c.type] = 1
|
||||
if c.author.type in agg2:
|
||||
agg2[c.author.type] += 1
|
||||
else:
|
||||
agg2[c.author.type] = 1
|
||||
break
|
||||
assert agg1 == case["result1"]
|
||||
assert agg2 == case["result2"]
|
||||
|
||||
|
||||
def test_archived_stream_multithread():
|
||||
for case in cases:
|
||||
stream = LiveChat(video_id=case["video_id"], seektime=case["seektime"])
|
||||
while stream.is_alive():
|
||||
chat = stream.get()
|
||||
agg1 = {}
|
||||
agg2 = {}
|
||||
for c in chat.items:
|
||||
if c.type in agg1:
|
||||
agg1[c.type] += 1
|
||||
else:
|
||||
agg1[c.type] = 1
|
||||
if c.author.type in agg2:
|
||||
agg2[c.author.type] += 1
|
||||
else:
|
||||
agg2[c.author.type] = 1
|
||||
break
|
||||
assert agg1 == case["result1"]
|
||||
assert agg2 == case["result2"]
|
||||
|
||||
def test_async_live_stream():
|
||||
async def test_loop():
|
||||
for case in cases:
|
||||
stream = LiveChatAsync(video_id=case["video_id"], seektime=case["seektime"])
|
||||
while stream.is_alive():
|
||||
chat = await stream.get()
|
||||
agg1 = {}
|
||||
agg2 = {}
|
||||
for c in chat.items:
|
||||
if c.type in agg1:
|
||||
agg1[c.type] += 1
|
||||
else:
|
||||
agg1[c.type] = 1
|
||||
if c.author.type in agg2:
|
||||
agg2[c.author.type] += 1
|
||||
else:
|
||||
agg2[c.author.type] = 1
|
||||
break
|
||||
assert agg1 == case["result1"]
|
||||
assert agg2 == case["result2"]
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.run_until_complete(test_loop())
|
||||
except CancelledError:
|
||||
assert True
|
||||
16
tests/test_speed_calculator2.py
Normal file
16
tests/test_speed_calculator2.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import json
|
||||
import pytchat
|
||||
from pytchat.parser.live import Parser
|
||||
from pytchat.processors.speed.calculator import SpeedCalculator
|
||||
|
||||
parser = Parser(is_replay=False)
|
||||
|
||||
|
||||
def test_speed_1():
|
||||
stream = pytchat.create("Hj-wnLIYKjw", seektime = 6000,processor=SpeedCalculator())
|
||||
while stream.is_alive():
|
||||
speed = stream.get()
|
||||
assert speed > 100
|
||||
break
|
||||
test_speed_1()
|
||||
|
||||
Reference in New Issue
Block a user