Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a9837adca | ||
|
|
f4bf30c0e9 | ||
|
|
acfba74821 | ||
|
|
f46845c777 | ||
|
|
af4c2fe4b9 | ||
|
|
b7c656536d | ||
|
|
b3ebe3879d | ||
|
|
da79895e55 | ||
|
|
aaa7421fdf | ||
|
|
b9f213f047 | ||
|
|
fee070b299 | ||
|
|
275e28b635 | ||
|
|
808e599be6 | ||
|
|
5cb6f7f123 | ||
|
|
a2f1c658f0 | ||
|
|
05de644d77 | ||
|
|
b908855566 | ||
|
|
8d93bfcb95 | ||
|
|
bf68859f38 | ||
|
|
78fbe97b66 | ||
|
|
166a256c1c | ||
|
|
b7f2967a4f | ||
|
|
0a8ff3abdc | ||
|
|
9b38a5428d | ||
|
|
9311bf1993 | ||
|
|
ee839da7c9 | ||
|
|
2ae77b3850 | ||
|
|
afd7cea635 | ||
|
|
9018ff9ee4 |
2
.github/workflows/run_test.yml
vendored
2
.github/workflows/run_test.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
max-parallel: 4
|
max-parallel: 4
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.7, 3.8]
|
python-version: [3.7, 3.8, 3.9]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
235
Pipfile.lock
generated
235
Pipfile.lock
generated
@@ -16,17 +16,17 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
|
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||||
"sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
|
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||||
],
|
],
|
||||||
"version": "==2020.11.8"
|
"version": "==2020.12.5"
|
||||||
},
|
},
|
||||||
"h11": {
|
"h11": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3c6c61d69c6f13d41f1b80ab0322f1872702a3ba26e12aa864c928f6a43fbaab",
|
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
|
||||||
"sha256:ab6c335e1b6ef34b205d5ca3e228c9299cc7218b049819ec84a388c2525e5d87"
|
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
|
||||||
],
|
],
|
||||||
"version": "==0.11.0"
|
"version": "==0.12.0"
|
||||||
},
|
},
|
||||||
"h2": {
|
"h2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -44,11 +44,10 @@
|
|||||||
},
|
},
|
||||||
"httpcore": {
|
"httpcore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:420700af11db658c782f7e8fda34f9dcd95e3ee93944dd97d78cb70247e0cd06",
|
"sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9",
|
||||||
"sha256:dd1d762d4f7c2702149d06be2597c35fb154c5eff9789a8c5823fbcf4d2978d6"
|
"sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"version": "==0.12.3"
|
||||||
"version": "==0.12.2"
|
|
||||||
},
|
},
|
||||||
"httpx": {
|
"httpx": {
|
||||||
"extras": [
|
"extras": [
|
||||||
@@ -70,10 +69,10 @@
|
|||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
"sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16",
|
||||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
"sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"
|
||||||
],
|
],
|
||||||
"version": "==2.10"
|
"version": "==3.1"
|
||||||
},
|
},
|
||||||
"rfc3986": {
|
"rfc3986": {
|
||||||
"extras": [
|
"extras": [
|
||||||
@@ -90,79 +89,127 @@
|
|||||||
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
|
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
|
||||||
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
|
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5'",
|
|
||||||
"version": "==1.2.0"
|
"version": "==1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
"atomicwrites": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197",
|
|
||||||
"sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"
|
|
||||||
],
|
|
||||||
"markers": "sys_platform == 'win32'",
|
|
||||||
"version": "==1.4.0"
|
|
||||||
},
|
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
|
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
|
||||||
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
|
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
|
||||||
"version": "==20.3.0"
|
"version": "==20.3.0"
|
||||||
},
|
},
|
||||||
"bleach": {
|
"bleach": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080",
|
"sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125",
|
||||||
"sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"
|
"sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
"index": "pypi",
|
||||||
"version": "==3.2.1"
|
"version": "==3.3.0"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
|
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||||
"sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
|
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||||
],
|
],
|
||||||
"version": "==2020.11.8"
|
"version": "==2020.12.5"
|
||||||
|
},
|
||||||
|
"cffi": {
|
||||||
|
"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"
|
||||||
|
],
|
||||||
|
"version": "==1.14.4"
|
||||||
},
|
},
|
||||||
"chardet": {
|
"chardet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||||
],
|
],
|
||||||
"version": "==3.0.4"
|
"version": "==4.0.0"
|
||||||
},
|
},
|
||||||
"colorama": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||||
],
|
],
|
||||||
"markers": "sys_platform == 'win32'",
|
|
||||||
"version": "==0.4.4"
|
"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": {
|
"docutils": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
||||||
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
|
||||||
"version": "==0.16"
|
"version": "==0.16"
|
||||||
},
|
},
|
||||||
"h11": {
|
"h11": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3c6c61d69c6f13d41f1b80ab0322f1872702a3ba26e12aa864c928f6a43fbaab",
|
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
|
||||||
"sha256:ab6c335e1b6ef34b205d5ca3e228c9299cc7218b049819ec84a388c2525e5d87"
|
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
|
||||||
],
|
],
|
||||||
"version": "==0.11.0"
|
"version": "==0.12.0"
|
||||||
},
|
},
|
||||||
"httpcore": {
|
"httpcore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:420700af11db658c782f7e8fda34f9dcd95e3ee93944dd97d78cb70247e0cd06",
|
"sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9",
|
||||||
"sha256:dd1d762d4f7c2702149d06be2597c35fb154c5eff9789a8c5823fbcf4d2978d6"
|
"sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"version": "==0.12.3"
|
||||||
"version": "==0.12.2"
|
|
||||||
},
|
},
|
||||||
"httpx": {
|
"httpx": {
|
||||||
"extras": [
|
"extras": [
|
||||||
@@ -177,10 +224,10 @@
|
|||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
"sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16",
|
||||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
"sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"
|
||||||
],
|
],
|
||||||
"version": "==2.10"
|
"version": "==3.1"
|
||||||
},
|
},
|
||||||
"iniconfig": {
|
"iniconfig": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -189,68 +236,76 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.1.1"
|
"version": "==1.1.1"
|
||||||
},
|
},
|
||||||
|
"jeepney": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657",
|
||||||
|
"sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform == 'linux'",
|
||||||
|
"version": "==0.6.0"
|
||||||
|
},
|
||||||
"keyring": {
|
"keyring": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:12de23258a95f3b13e5b167f7a641a878e91eab8ef16fafc077720a95e6115bb",
|
"sha256:9acb3e1452edbb7544822b12fd25459078769e560fa51f418b6d00afaa6178df",
|
||||||
"sha256:207bd66f2a9881c835dad653da04e196c678bf104f8252141d2d3c4f31051579"
|
"sha256:9f44660a5d4931bdc14c08a1d01ef30b18a7a8147380710d8c9f9531e1f6c3c0"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"version": "==22.0.1"
|
||||||
"version": "==21.5.0"
|
|
||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236",
|
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
|
||||||
"sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"
|
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"version": "==20.9"
|
||||||
"version": "==20.7"
|
|
||||||
},
|
},
|
||||||
"pkginfo": {
|
"pkginfo": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a6a4ac943b496745cec21f14f021bbd869d5e9b4f6ec06918cffea5a2f4b9193",
|
"sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4",
|
||||||
"sha256:ce14d7296c673dc4c61c759a0b6c14bae34e34eb819c0017bb6ca5b7292c56e9"
|
"sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"
|
||||||
],
|
],
|
||||||
"version": "==1.6.1"
|
"version": "==1.7.0"
|
||||||
},
|
},
|
||||||
"pluggy": {
|
"pluggy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
|
||||||
"version": "==0.13.1"
|
"version": "==0.13.1"
|
||||||
},
|
},
|
||||||
"py": {
|
"py": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
|
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
|
||||||
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
|
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"version": "==1.10.0"
|
||||||
"version": "==1.9.0"
|
},
|
||||||
|
"pycparser": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
||||||
|
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
||||||
|
],
|
||||||
|
"version": "==2.20"
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0",
|
"sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
|
||||||
"sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"
|
"sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5'",
|
"version": "==2.7.4"
|
||||||
"version": "==2.7.2"
|
|
||||||
},
|
},
|
||||||
"pyparsing": {
|
"pyparsing": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
|
||||||
"version": "==2.4.7"
|
"version": "==2.4.7"
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe",
|
"sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
|
||||||
"sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"
|
"sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5'",
|
"version": "==6.2.2"
|
||||||
"version": "==6.1.2"
|
|
||||||
},
|
},
|
||||||
"pytest-httpx": {
|
"pytest-httpx": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -268,14 +323,6 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.3.1"
|
"version": "==3.3.1"
|
||||||
},
|
},
|
||||||
"pywin32-ctypes": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942",
|
|
||||||
"sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"
|
|
||||||
],
|
|
||||||
"markers": "sys_platform == 'win32'",
|
|
||||||
"version": "==0.2.0"
|
|
||||||
},
|
|
||||||
"readme-renderer": {
|
"readme-renderer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:267854ac3b1530633c2394ead828afcd060fc273217c42ac36b6be9c42cd9a9d",
|
"sha256:267854ac3b1530633c2394ead828afcd060fc273217c42ac36b6be9c42cd9a9d",
|
||||||
@@ -285,11 +332,10 @@
|
|||||||
},
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
|
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||||
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
|
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
"version": "==2.25.1"
|
||||||
"version": "==2.25.0"
|
|
||||||
},
|
},
|
||||||
"requests-toolbelt": {
|
"requests-toolbelt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -308,12 +354,19 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.4.0"
|
"version": "==1.4.0"
|
||||||
},
|
},
|
||||||
|
"secretstorage": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:30cfdef28829dad64d6ea1ed08f8eff6aa115a77068926bcc9f5225d5a3246aa",
|
||||||
|
"sha256:5c36f6537a523ec5f969ef9fad61c98eb9e017bc601d811e53aa25bece64892f"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform == 'linux'",
|
||||||
|
"version": "==3.3.0"
|
||||||
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
|
||||||
"version": "==1.15.0"
|
"version": "==1.15.0"
|
||||||
},
|
},
|
||||||
"sniffio": {
|
"sniffio": {
|
||||||
@@ -321,7 +374,6 @@
|
|||||||
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
|
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
|
||||||
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
|
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5'",
|
|
||||||
"version": "==1.2.0"
|
"version": "==1.2.0"
|
||||||
},
|
},
|
||||||
"toml": {
|
"toml": {
|
||||||
@@ -329,16 +381,14 @@
|
|||||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
|
||||||
"version": "==0.10.2"
|
"version": "==0.10.2"
|
||||||
},
|
},
|
||||||
"tqdm": {
|
"tqdm": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5c0d04e06ccc0da1bd3fa5ae4550effcce42fcad947b4a6cafa77bdc9b09ff22",
|
"sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a",
|
||||||
"sha256:9e7b8ab0ecbdbf0595adadd5f0ebbb9e69010e0bd48bbb0c15e550bf2a5292df"
|
"sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"version": "==4.56.0"
|
||||||
"version": "==4.54.0"
|
|
||||||
},
|
},
|
||||||
"twine": {
|
"twine": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -350,11 +400,10 @@
|
|||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
|
"sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
|
||||||
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
|
"sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
|
||||||
],
|
],
|
||||||
"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.3"
|
||||||
"version": "==1.26.2"
|
|
||||||
},
|
},
|
||||||
"webencodings": {
|
"webencodings": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -188,19 +188,3 @@ Structure of author object.
|
|||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
|
|
||||||
## Contributes
|
|
||||||
Great thanks:
|
|
||||||
|
|
||||||
Most of source code of CLI refer to:
|
|
||||||
|
|
||||||
[PetterKraabol / Twitch-Chat-Downloader](https://github.com/PetterKraabol/Twitch-Chat-Downloader)
|
|
||||||
|
|
||||||
Progress bar in CLI is based on:
|
|
||||||
|
|
||||||
[vladignatyev/progress.py](https://gist.github.com/vladignatyev/06860ec2040cb497f0f3)
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
[taizan-hokuto](https://github.com/taizan-hokuto)
|
|
||||||
|
|
||||||
[twitter:@taizan205](https://twitter.com/taizan205)
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
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, 2020 taizan-hokuto'
|
__copyright__ = 'Copyright (C) 2019, 2020 taizan-hokuto'
|
||||||
__version__ = '0.4.9'
|
__version__ = '0.5.3'
|
||||||
__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'
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from .. import util
|
|||||||
headers = config.headers
|
headers = config.headers
|
||||||
MAX_RETRY = 10
|
MAX_RETRY = 10
|
||||||
|
|
||||||
|
|
||||||
class PytchatCore:
|
class PytchatCore:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -45,6 +44,10 @@ class PytchatCore:
|
|||||||
If True, when exceptions occur, the exception is held internally,
|
If True, when exceptions occur, the exception is held internally,
|
||||||
and can be raised by raise_for_status().
|
and can be raised by raise_for_status().
|
||||||
|
|
||||||
|
replay_continuation : str
|
||||||
|
If this parameter is not None, the processor will attempt to get chat data from continuation.
|
||||||
|
This parameter is only allowed in archived mode.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
---------
|
---------
|
||||||
_is_alive : bool
|
_is_alive : bool
|
||||||
@@ -59,6 +62,7 @@ class PytchatCore:
|
|||||||
topchat_only=False,
|
topchat_only=False,
|
||||||
hold_exception=True,
|
hold_exception=True,
|
||||||
logger=config.logger(__name__),
|
logger=config.logger(__name__),
|
||||||
|
replay_continuation=None
|
||||||
):
|
):
|
||||||
self._video_id = util.extract_video_id(video_id)
|
self._video_id = util.extract_video_id(video_id)
|
||||||
self.seektime = seektime
|
self.seektime = seektime
|
||||||
@@ -67,32 +71,36 @@ class PytchatCore:
|
|||||||
else:
|
else:
|
||||||
self.processor = processor
|
self.processor = processor
|
||||||
self._is_alive = True
|
self._is_alive = True
|
||||||
self._is_replay = force_replay
|
self._is_replay = force_replay or (replay_continuation is not None)
|
||||||
self._hold_exception = hold_exception
|
self._hold_exception = hold_exception
|
||||||
self._exception_holder = None
|
self._exception_holder = None
|
||||||
self._parser = Parser(
|
self._parser = Parser(
|
||||||
is_replay=self._is_replay,
|
is_replay=self._is_replay,
|
||||||
exception_holder=self._exception_holder
|
exception_holder=self._exception_holder
|
||||||
)
|
)
|
||||||
self._first_fetch = True
|
self._first_fetch = replay_continuation is None
|
||||||
self._fetch_url = config._sml
|
self._fetch_url = config._sml if replay_continuation is None else config._smr
|
||||||
self._topchat_only = topchat_only
|
self._topchat_only = topchat_only
|
||||||
self._dat = ''
|
self._dat = ''
|
||||||
self._last_offset_ms = 0
|
self._last_offset_ms = 0
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
|
self.continuation = replay_continuation
|
||||||
if interruptable:
|
if interruptable:
|
||||||
signal.signal(signal.SIGINT, lambda a, b: self.terminate())
|
signal.signal(signal.SIGINT, lambda a, b: self.terminate())
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
time.sleep(0.1) # sleep shortly to prohibit skipping fetching data
|
if not self.continuation:
|
||||||
"""Fetch first continuation parameter,
|
time.sleep(0.1) # sleep shortly to prohibit skipping fetching data
|
||||||
create and start _listen loop.
|
"""Fetch first continuation parameter,
|
||||||
"""
|
create and start _listen loop.
|
||||||
self.continuation = liveparam.getparam(self._video_id, 3)
|
"""
|
||||||
|
self.continuation = liveparam.getparam(
|
||||||
|
self._video_id,
|
||||||
|
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
|
||||||
|
past_sec=3)
|
||||||
|
|
||||||
def _get_chat_component(self):
|
def _get_chat_component(self):
|
||||||
|
|
||||||
''' Fetch chat data and store them into buffer,
|
''' Fetch chat data and store them into buffer,
|
||||||
get next continuaiton parameter and loop.
|
get next continuaiton parameter and loop.
|
||||||
|
|
||||||
@@ -143,8 +151,8 @@ class PytchatCore:
|
|||||||
self._parser.is_replay = True
|
self._parser.is_replay = True
|
||||||
self._fetch_url = config._smr
|
self._fetch_url = config._smr
|
||||||
continuation = arcparam.getparam(
|
continuation = arcparam.getparam(
|
||||||
self._video_id, self.seektime, self._topchat_only)
|
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(
|
reload_continuation = self._parser.reload_continuation(
|
||||||
self._parser.get_contents(livechat_json)[0])
|
self._parser.get_contents(livechat_json)[0])
|
||||||
if reload_continuation:
|
if reload_continuation:
|
||||||
@@ -168,7 +176,7 @@ class PytchatCore:
|
|||||||
with httpx.Client(http2=True) as client:
|
with httpx.Client(http2=True) as client:
|
||||||
try:
|
try:
|
||||||
response = client.post(self._fetch_url, json=param)
|
response = client.post(self._fetch_url, json=param)
|
||||||
livechat_json = json.loads(response.text, encoding='utf-8')
|
livechat_json = json.loads(response.text)
|
||||||
break
|
break
|
||||||
except (json.JSONDecodeError, httpx.ConnectTimeout, httpx.ReadTimeout, httpx.ConnectError) as e:
|
except (json.JSONDecodeError, httpx.ConnectTimeout, httpx.ReadTimeout, httpx.ConnectError) as e:
|
||||||
err = e
|
err = e
|
||||||
@@ -179,7 +187,7 @@ class PytchatCore:
|
|||||||
f"Exceeded retry count. Last error: {str(err)}")
|
f"Exceeded retry count. Last error: {str(err)}")
|
||||||
self._raise_exception(exceptions.RetryExceedMaxCount())
|
self._raise_exception(exceptions.RetryExceedMaxCount())
|
||||||
return livechat_json
|
return livechat_json
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
if self.is_alive():
|
if self.is_alive():
|
||||||
chat_component = self._get_chat_component()
|
chat_component = self._get_chat_component()
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ class LiveChatAsync:
|
|||||||
topchat_only : bool
|
topchat_only : bool
|
||||||
If True, get only top chat.
|
If True, get only top chat.
|
||||||
|
|
||||||
|
replay_continuation : str
|
||||||
|
If this parameter is not None, the processor will attempt to get chat data from continuation.
|
||||||
|
This parameter is only allowed in archived mode.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
---------
|
---------
|
||||||
_is_alive : bool
|
_is_alive : bool
|
||||||
@@ -82,6 +86,7 @@ class LiveChatAsync:
|
|||||||
force_replay=False,
|
force_replay=False,
|
||||||
topchat_only=False,
|
topchat_only=False,
|
||||||
logger=config.logger(__name__),
|
logger=config.logger(__name__),
|
||||||
|
replay_continuation=None
|
||||||
):
|
):
|
||||||
self._video_id = util.extract_video_id(video_id)
|
self._video_id = util.extract_video_id(video_id)
|
||||||
self.seektime = seektime
|
self.seektime = seektime
|
||||||
@@ -95,17 +100,18 @@ class LiveChatAsync:
|
|||||||
self._exception_handler = exception_handler
|
self._exception_handler = exception_handler
|
||||||
self._direct_mode = direct_mode
|
self._direct_mode = direct_mode
|
||||||
self._is_alive = True
|
self._is_alive = True
|
||||||
self._is_replay = force_replay
|
self._is_replay = force_replay or (replay_continuation is not None)
|
||||||
self._parser = Parser(is_replay=self._is_replay)
|
self._parser = Parser(is_replay=self._is_replay)
|
||||||
self._pauser = Queue()
|
self._pauser = Queue()
|
||||||
self._pauser.put_nowait(None)
|
self._pauser.put_nowait(None)
|
||||||
self._first_fetch = True
|
self._first_fetch = replay_continuation is None
|
||||||
self._fetch_url = config._sml
|
self._fetch_url = config._sml if replay_continuation is None else config._smr
|
||||||
self._topchat_only = topchat_only
|
self._topchat_only = topchat_only
|
||||||
self._dat = ''
|
self._dat = ''
|
||||||
self._last_offset_ms = 0
|
self._last_offset_ms = 0
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
self.exception = None
|
self.exception = None
|
||||||
|
self.continuation = replay_continuation
|
||||||
LiveChatAsync._logger = logger
|
LiveChatAsync._logger = logger
|
||||||
|
|
||||||
if exception_handler:
|
if exception_handler:
|
||||||
@@ -145,8 +151,13 @@ class LiveChatAsync:
|
|||||||
"""Fetch first continuation parameter,
|
"""Fetch first continuation parameter,
|
||||||
create and start _listen loop.
|
create and start _listen loop.
|
||||||
"""
|
"""
|
||||||
initial_continuation = liveparam.getparam(self._video_id, 3)
|
if not self.continuation:
|
||||||
await self._listen(initial_continuation)
|
self.continuation = liveparam.getparam(
|
||||||
|
self._video_id,
|
||||||
|
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
|
||||||
|
past_sec=3)
|
||||||
|
|
||||||
|
await self._listen(self.continuation)
|
||||||
|
|
||||||
async def _listen(self, continuation):
|
async def _listen(self, continuation):
|
||||||
''' Fetch chat data and store them into buffer,
|
''' Fetch chat data and store them into buffer,
|
||||||
@@ -163,6 +174,9 @@ class LiveChatAsync:
|
|||||||
continuation = await self._check_pause(continuation)
|
continuation = await self._check_pause(continuation)
|
||||||
contents = await self._get_contents(continuation, client, headers)
|
contents = await self._get_contents(continuation, client, headers)
|
||||||
metadata, chatdata = self._parser.parse(contents)
|
metadata, chatdata = self._parser.parse(contents)
|
||||||
|
continuation = metadata.get('continuation')
|
||||||
|
if continuation:
|
||||||
|
self.continuation = continuation
|
||||||
timeout = metadata['timeoutMs'] / 1000
|
timeout = metadata['timeoutMs'] / 1000
|
||||||
chat_component = {
|
chat_component = {
|
||||||
"video_id": self._video_id,
|
"video_id": self._video_id,
|
||||||
@@ -181,7 +195,6 @@ class LiveChatAsync:
|
|||||||
await self._buffer.put(chat_component)
|
await self._buffer.put(chat_component)
|
||||||
diff_time = timeout - (time.time() - time_mark)
|
diff_time = timeout - (time.time() - time_mark)
|
||||||
await asyncio.sleep(diff_time)
|
await asyncio.sleep(diff_time)
|
||||||
continuation = metadata.get('continuation')
|
|
||||||
self._last_offset_ms = metadata.get('last_offset_ms', 0)
|
self._last_offset_ms = metadata.get('last_offset_ms', 0)
|
||||||
except exceptions.ChatParseException as e:
|
except exceptions.ChatParseException as e:
|
||||||
self._logger.debug(f"[{self._video_id}]{str(e)}")
|
self._logger.debug(f"[{self._video_id}]{str(e)}")
|
||||||
@@ -201,8 +214,11 @@ class LiveChatAsync:
|
|||||||
'''
|
'''
|
||||||
self._pauser.put_nowait(None)
|
self._pauser.put_nowait(None)
|
||||||
if not self._is_replay:
|
if not self._is_replay:
|
||||||
continuation = liveparam.getparam(
|
async with httpx.AsyncClient(http2=True) as client:
|
||||||
self._video_id, 3, self._topchat_only)
|
continuation = await liveparam.getparam(self._video_id,
|
||||||
|
channel_id=util.get_channelid_async(client, self.video_id),
|
||||||
|
past_sec=3)
|
||||||
|
|
||||||
return continuation
|
return continuation
|
||||||
|
|
||||||
async def _get_contents(self, continuation, client, headers):
|
async def _get_contents(self, continuation, client, headers):
|
||||||
@@ -223,8 +239,9 @@ class LiveChatAsync:
|
|||||||
'''Try to fetch archive chat data.'''
|
'''Try to fetch archive chat data.'''
|
||||||
self._parser.is_replay = True
|
self._parser.is_replay = True
|
||||||
self._fetch_url = config._smr
|
self._fetch_url = config._smr
|
||||||
|
channelid = await util.get_channelid_async(client, self._video_id)
|
||||||
continuation = arcparam.getparam(
|
continuation = arcparam.getparam(
|
||||||
self._video_id, self.seektime, self._topchat_only)
|
self._video_id, self.seektime, self._topchat_only, channelid)
|
||||||
livechat_json = (await self._get_livechat_json(
|
livechat_json = (await self._get_livechat_json(
|
||||||
continuation, client, replay=True, offset_ms=self.seektime * 1000))
|
continuation, client, replay=True, offset_ms=self.seektime * 1000))
|
||||||
reload_continuation = self._parser.reload_continuation(
|
reload_continuation = self._parser.reload_continuation(
|
||||||
@@ -241,7 +258,6 @@ class LiveChatAsync:
|
|||||||
'''
|
'''
|
||||||
Get json which includes chat data.
|
Get json which includes chat data.
|
||||||
'''
|
'''
|
||||||
# continuation = urllib.parse.quote(continuation)
|
|
||||||
livechat_json = None
|
livechat_json = None
|
||||||
if offset_ms < 0:
|
if offset_ms < 0:
|
||||||
offset_ms = 0
|
offset_ms = 0
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ class LiveChat:
|
|||||||
topchat_only : bool
|
topchat_only : bool
|
||||||
If True, get only top chat.
|
If True, get only top chat.
|
||||||
|
|
||||||
|
replay_continuation : str
|
||||||
|
If this parameter is not None, the processor will attempt to get chat data from continuation.
|
||||||
|
This parameter is only allowed in archived mode.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
---------
|
---------
|
||||||
_executor : ThreadPoolExecutor
|
_executor : ThreadPoolExecutor
|
||||||
@@ -81,7 +85,8 @@ class LiveChat:
|
|||||||
direct_mode=False,
|
direct_mode=False,
|
||||||
force_replay=False,
|
force_replay=False,
|
||||||
topchat_only=False,
|
topchat_only=False,
|
||||||
logger=config.logger(__name__)
|
logger=config.logger(__name__),
|
||||||
|
replay_continuation=None
|
||||||
):
|
):
|
||||||
self._video_id = util.extract_video_id(video_id)
|
self._video_id = util.extract_video_id(video_id)
|
||||||
self.seektime = seektime
|
self.seektime = seektime
|
||||||
@@ -95,17 +100,19 @@ class LiveChat:
|
|||||||
self._executor = ThreadPoolExecutor(max_workers=2)
|
self._executor = ThreadPoolExecutor(max_workers=2)
|
||||||
self._direct_mode = direct_mode
|
self._direct_mode = direct_mode
|
||||||
self._is_alive = True
|
self._is_alive = True
|
||||||
self._is_replay = force_replay
|
self._is_replay = force_replay or (replay_continuation is not None)
|
||||||
self._parser = Parser(is_replay=self._is_replay)
|
self._parser = Parser(is_replay=self._is_replay)
|
||||||
self._pauser = Queue()
|
self._pauser = Queue()
|
||||||
self._pauser.put_nowait(None)
|
self._pauser.put_nowait(None)
|
||||||
self._first_fetch = True
|
self._first_fetch = replay_continuation is None
|
||||||
self._fetch_url = config._sml
|
self._fetch_url = config._sml if replay_continuation is None else config._smr
|
||||||
self._topchat_only = topchat_only
|
self._topchat_only = topchat_only
|
||||||
self._dat = ''
|
self._dat = ''
|
||||||
self._last_offset_ms = 0
|
self._last_offset_ms = 0
|
||||||
self._event = Event()
|
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
|
self._event = Event()
|
||||||
|
self.continuation = replay_continuation
|
||||||
|
|
||||||
self.exception = None
|
self.exception = None
|
||||||
if interruptable:
|
if interruptable:
|
||||||
signal.signal(signal.SIGINT, lambda a, b: self.terminate())
|
signal.signal(signal.SIGINT, lambda a, b: self.terminate())
|
||||||
@@ -140,8 +147,12 @@ class LiveChat:
|
|||||||
"""Fetch first continuation parameter,
|
"""Fetch first continuation parameter,
|
||||||
create and start _listen loop.
|
create and start _listen loop.
|
||||||
"""
|
"""
|
||||||
initial_continuation = liveparam.getparam(self._video_id, 3)
|
if not self.continuation:
|
||||||
self._listen(initial_continuation)
|
self.continuation = liveparam.getparam(
|
||||||
|
self._video_id,
|
||||||
|
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
|
||||||
|
past_sec=3)
|
||||||
|
self._listen(self.continuation)
|
||||||
|
|
||||||
def _listen(self, continuation):
|
def _listen(self, continuation):
|
||||||
''' Fetch chat data and store them into buffer,
|
''' Fetch chat data and store them into buffer,
|
||||||
@@ -158,6 +169,9 @@ class LiveChat:
|
|||||||
continuation = self._check_pause(continuation)
|
continuation = self._check_pause(continuation)
|
||||||
contents = self._get_contents(continuation, client, headers)
|
contents = self._get_contents(continuation, client, headers)
|
||||||
metadata, chatdata = self._parser.parse(contents)
|
metadata, chatdata = self._parser.parse(contents)
|
||||||
|
continuation = metadata.get('continuation')
|
||||||
|
if continuation:
|
||||||
|
self.continuation = continuation
|
||||||
timeout = metadata['timeoutMs'] / 1000
|
timeout = metadata['timeoutMs'] / 1000
|
||||||
chat_component = {
|
chat_component = {
|
||||||
"video_id": self._video_id,
|
"video_id": self._video_id,
|
||||||
@@ -176,7 +190,6 @@ class LiveChat:
|
|||||||
self._buffer.put(chat_component)
|
self._buffer.put(chat_component)
|
||||||
diff_time = timeout - (time.time() - time_mark)
|
diff_time = timeout - (time.time() - time_mark)
|
||||||
self._event.wait(diff_time if diff_time > 0 else 0)
|
self._event.wait(diff_time if diff_time > 0 else 0)
|
||||||
continuation = metadata.get('continuation')
|
|
||||||
self._last_offset_ms = metadata.get('last_offset_ms', 0)
|
self._last_offset_ms = metadata.get('last_offset_ms', 0)
|
||||||
except exceptions.ChatParseException as e:
|
except exceptions.ChatParseException as e:
|
||||||
self._logger.debug(f"[{self._video_id}]{str(e)}")
|
self._logger.debug(f"[{self._video_id}]{str(e)}")
|
||||||
@@ -196,7 +209,10 @@ class LiveChat:
|
|||||||
'''
|
'''
|
||||||
self._pauser.put_nowait(None)
|
self._pauser.put_nowait(None)
|
||||||
if not self._is_replay:
|
if not self._is_replay:
|
||||||
continuation = liveparam.getparam(self._video_id, 3)
|
continuation = liveparam.getparam(
|
||||||
|
self._video_id, channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id),
|
||||||
|
past_sec=3, topchat_only=self._topchat_only)
|
||||||
|
|
||||||
return continuation
|
return continuation
|
||||||
|
|
||||||
def _get_contents(self, continuation, client, headers):
|
def _get_contents(self, continuation, client, headers):
|
||||||
@@ -208,7 +224,7 @@ class LiveChat:
|
|||||||
-------
|
-------
|
||||||
'continuationContents' which includes metadata & chat data.
|
'continuationContents' which includes metadata & chat data.
|
||||||
'''
|
'''
|
||||||
livechat_json = self._get_livechat_json(continuation, client, headers)
|
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)
|
contents, dat = self._parser.get_contents(livechat_json)
|
||||||
if self._dat == '' and dat:
|
if self._dat == '' and dat:
|
||||||
self._dat = dat
|
self._dat = dat
|
||||||
@@ -218,7 +234,7 @@ class LiveChat:
|
|||||||
self._parser.is_replay = True
|
self._parser.is_replay = True
|
||||||
self._fetch_url = config._smr
|
self._fetch_url = config._smr
|
||||||
continuation = arcparam.getparam(
|
continuation = arcparam.getparam(
|
||||||
self._video_id, self.seektime, self._topchat_only)
|
self._video_id, self.seektime, self._topchat_only, util.get_channelid(client, self._video_id))
|
||||||
livechat_json = (self._get_livechat_json(
|
livechat_json = (self._get_livechat_json(
|
||||||
continuation, client, replay=True, offset_ms=self.seektime * 1000))
|
continuation, client, replay=True, offset_ms=self.seektime * 1000))
|
||||||
reload_continuation = self._parser.reload_continuation(
|
reload_continuation = self._parser.reload_continuation(
|
||||||
@@ -235,15 +251,14 @@ class LiveChat:
|
|||||||
'''
|
'''
|
||||||
Get json which includes chat data.
|
Get json which includes chat data.
|
||||||
'''
|
'''
|
||||||
# continuation = urllib.parse.quote(continuation)
|
|
||||||
livechat_json = None
|
livechat_json = None
|
||||||
if offset_ms < 0:
|
if offset_ms < 0:
|
||||||
offset_ms = 0
|
offset_ms = 0
|
||||||
param = util.get_param(continuation, dat=self._dat, replay=replay, offsetms=offset_ms)
|
param = util.get_param(continuation, dat=self._dat, replay=replay, offsetms=offset_ms)
|
||||||
for _ in range(MAX_RETRY + 1):
|
for _ in range(MAX_RETRY + 1):
|
||||||
try:
|
try:
|
||||||
resp = client.post(self._fetch_url, json=param)
|
response = client.post(self._fetch_url, json=param)
|
||||||
livechat_json = resp.json()
|
livechat_json = response.json()
|
||||||
break
|
break
|
||||||
except (json.JSONDecodeError, httpx.HTTPError):
|
except (json.JSONDecodeError, httpx.HTTPError):
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ from base64 import urlsafe_b64encode as b64enc
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
|
||||||
def _header(video_id) -> str:
|
def _header(video_id, channel_id) -> str:
|
||||||
channel_id = '_' * 24
|
|
||||||
S1_3 = enc.rs(1, video_id)
|
S1_3 = enc.rs(1, video_id)
|
||||||
S1_5 = enc.rs(1, channel_id) + enc.rs(2, video_id)
|
S1_5 = enc.rs(1, channel_id) + enc.rs(2, video_id)
|
||||||
S1 = enc.rs(3, S1_3) + enc.rs(5, S1_5)
|
S1 = enc.rs(3, S1_3) + enc.rs(5, S1_5)
|
||||||
@@ -13,31 +12,26 @@ def _header(video_id) -> str:
|
|||||||
return b64enc(header_replay)
|
return b64enc(header_replay)
|
||||||
|
|
||||||
|
|
||||||
def _build(video_id, seektime, topchat_only) -> str:
|
def _build(video_id, seektime, topchat_only, channel_id) -> str:
|
||||||
chattype = 4 if topchat_only else 1
|
chattype = 4 if topchat_only else 1
|
||||||
fetch_before_start = 3
|
|
||||||
timestamp = 1000
|
|
||||||
if seektime < 0:
|
if seektime < 0:
|
||||||
fetch_before_start = 4
|
seektime = 0
|
||||||
elif seektime == 0:
|
timestamp = int(seektime * 1000000)
|
||||||
timestamp = 1000
|
header = enc.rs(3, _header(video_id, channel_id))
|
||||||
else:
|
|
||||||
timestamp = int(seektime * 1000000)
|
|
||||||
header = enc.rs(3, _header(video_id))
|
|
||||||
timestamp = enc.nm(5, timestamp)
|
timestamp = enc.nm(5, timestamp)
|
||||||
s6 = enc.nm(6, 0)
|
s6 = enc.nm(6, 0)
|
||||||
s7 = enc.nm(7, 0)
|
s7 = enc.nm(7, 0)
|
||||||
s8 = enc.nm(8, 0)
|
s8 = enc.nm(8, 0)
|
||||||
s9 = enc.nm(9, fetch_before_start)
|
s9 = enc.nm(9, 4)
|
||||||
s10 = enc.rs(10, enc.nm(4, 0))
|
s10 = enc.rs(10, enc.nm(4, 0))
|
||||||
chattype = enc.rs(14, enc.nm(1, chattype))
|
chattype = enc.rs(14, enc.nm(1, 4))
|
||||||
s15 = enc.nm(15, 0)
|
s15 = enc.nm(15, 0)
|
||||||
entity = b''.join((header, timestamp, s6, s7, s8, s9, s10, chattype, s15))
|
entity = b''.join((header, timestamp, s6, s7, s8, s9, s10, chattype, s15))
|
||||||
continuation = enc.rs(156074452, entity)
|
continuation = enc.rs(156074452, entity)
|
||||||
return quote(b64enc(continuation).decode())
|
return quote(b64enc(continuation).decode())
|
||||||
|
|
||||||
|
|
||||||
def getparam(video_id, seektime=-1, topchat_only=False) -> str:
|
def getparam(video_id, seektime=0, topchat_only=False, channel_id='') -> str:
|
||||||
'''
|
'''
|
||||||
Parameter
|
Parameter
|
||||||
---------
|
---------
|
||||||
@@ -47,4 +41,4 @@ def getparam(video_id, seektime=-1, topchat_only=False) -> str:
|
|||||||
topchat_only : bool
|
topchat_only : bool
|
||||||
if True, fetch only 'top chat'
|
if True, fetch only 'top chat'
|
||||||
'''
|
'''
|
||||||
return _build(video_id, seektime, topchat_only)
|
return _build(video_id, seektime, topchat_only, channel_id)
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ from base64 import urlsafe_b64encode as b64enc
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
|
||||||
def _header(video_id) -> str:
|
def _header(video_id, channel_id) -> str:
|
||||||
return b64enc(enc.rs(1, enc.rs(1, enc.rs(1, video_id))) + enc.nm(4, 1))
|
S1_3 = enc.rs(1, video_id)
|
||||||
|
S1_5 = enc.rs(1, channel_id) + enc.rs(2, video_id)
|
||||||
|
S1 = enc.rs(3, S1_3) + enc.rs(5, S1_5)
|
||||||
|
S3 = enc.rs(48687757, enc.rs(1, video_id))
|
||||||
|
header_replay = enc.rs(1, S1) + enc.rs(3, S3) + enc.nm(4, 1)
|
||||||
|
return b64enc(header_replay)
|
||||||
|
|
||||||
|
|
||||||
def _build(video_id, ts1, ts2, ts3, ts4, ts5, topchat_only) -> str:
|
def _build(video_id, channel_id, ts1, ts2, ts3, ts4, ts5, topchat_only) -> str:
|
||||||
chattype = 4 if topchat_only else 1
|
chattype = 4 if topchat_only else 1
|
||||||
|
|
||||||
b1 = enc.nm(1, 0)
|
b1 = enc.nm(1, 0)
|
||||||
@@ -23,7 +28,7 @@ def _build(video_id, ts1, ts2, ts3, ts4, ts5, topchat_only) -> str:
|
|||||||
b11 = enc.nm(11, 3)
|
b11 = enc.nm(11, 3)
|
||||||
b15 = enc.nm(15, 0)
|
b15 = enc.nm(15, 0)
|
||||||
|
|
||||||
header = enc.rs(3, _header(video_id))
|
header = enc.rs(3, _header(video_id, channel_id))
|
||||||
timestamp1 = enc.nm(5, ts1)
|
timestamp1 = enc.nm(5, ts1)
|
||||||
s6 = enc.nm(6, 0)
|
s6 = enc.nm(6, 0)
|
||||||
s7 = enc.nm(7, 0)
|
s7 = enc.nm(7, 0)
|
||||||
@@ -53,7 +58,7 @@ def _times(past_sec):
|
|||||||
return list(map(lambda x: int(x * 1000000), [_ts1, _ts2, _ts3, _ts4, _ts5]))
|
return list(map(lambda x: int(x * 1000000), [_ts1, _ts2, _ts3, _ts4, _ts5]))
|
||||||
|
|
||||||
|
|
||||||
def getparam(video_id, past_sec=0, topchat_only=False) -> str:
|
def getparam(video_id, channel_id, past_sec=0, topchat_only=False) -> str:
|
||||||
'''
|
'''
|
||||||
Parameter
|
Parameter
|
||||||
---------
|
---------
|
||||||
@@ -62,4 +67,4 @@ def getparam(video_id, past_sec=0, topchat_only=False) -> str:
|
|||||||
topchat_only : bool
|
topchat_only : bool
|
||||||
if True, fetch only 'top chat'
|
if True, fetch only 'top chat'
|
||||||
'''
|
'''
|
||||||
return _build(video_id, *_times(past_sec), topchat_only)
|
return _build(video_id, channel_id, *_times(past_sec), topchat_only)
|
||||||
@@ -28,7 +28,7 @@ class Parser:
|
|||||||
def get_contents(self, jsn):
|
def get_contents(self, jsn):
|
||||||
if jsn is None:
|
if jsn is None:
|
||||||
self.raise_exception(exceptions.IllegalFunctionCall('Called with none JSON object.'))
|
self.raise_exception(exceptions.IllegalFunctionCall('Called with none JSON object.'))
|
||||||
if jsn.get("error") or jsn.get("responseContext", {}).get("errors"):
|
if jsn.get("responseContext", {}).get("errors"):
|
||||||
raise exceptions.ResponseContextError(
|
raise exceptions.ResponseContextError(
|
||||||
'The video_id would be wrong, or video is deleted or private.')
|
'The video_id would be wrong, or video is deleted or private.')
|
||||||
contents = jsn.get('continuationContents')
|
contents = jsn.get('continuationContents')
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import httpx
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from urllib.parse import quote
|
||||||
from .. import config
|
from .. import config
|
||||||
from .. exceptions import InvalidVideoIdException
|
from .. exceptions import InvalidVideoIdException
|
||||||
|
|
||||||
@@ -10,6 +11,8 @@ PATTERN = re.compile(r"(.*)\(([0-9]+)\)$")
|
|||||||
|
|
||||||
PATTERN_YTURL = re.compile(r"((?<=(v|V)/)|(?<=be/)|(?<=(\?|\&)v=)|(?<=embed/))([\w-]+)")
|
PATTERN_YTURL = re.compile(r"((?<=(v|V)/)|(?<=be/)|(?<=(\?|\&)v=)|(?<=embed/))([\w-]+)")
|
||||||
|
|
||||||
|
PATTERN_CHANNEL = re.compile(r"\\\"channelId\\\":\\\"(.{24})\\\"")
|
||||||
|
|
||||||
YT_VIDEO_ID_LENGTH = 11
|
YT_VIDEO_ID_LENGTH = 11
|
||||||
|
|
||||||
CLIENT_VERSION = ''.join(("2.", (datetime.datetime.today() - datetime.timedelta(days=1)).strftime("%Y%m%d"), ".01.00"))
|
CLIENT_VERSION = ''.join(("2.", (datetime.datetime.today() - datetime.timedelta(days=1)).strftime("%Y%m%d"), ".01.00"))
|
||||||
@@ -92,3 +95,26 @@ def extract_video_id(url_or_id: str) -> str:
|
|||||||
if ret is None or len(ret) != YT_VIDEO_ID_LENGTH:
|
if ret is None or len(ret) != YT_VIDEO_ID_LENGTH:
|
||||||
raise InvalidVideoIdException(f"Invalid video id: {url_or_id}")
|
raise InvalidVideoIdException(f"Invalid video id: {url_or_id}")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
if match is None:
|
||||||
|
raise InvalidVideoIdException(f"Cannot find channel id for video id:{video_id}. This video id seems to be invalid.")
|
||||||
|
try:
|
||||||
|
ret = match.group(1)
|
||||||
|
except IndexError:
|
||||||
|
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)
|
||||||
|
if match is None:
|
||||||
|
raise InvalidVideoIdException(f"Cannot find channel id for video id:{video_id}. This video id seems to be invalid.")
|
||||||
|
try:
|
||||||
|
ret = match.group(1)
|
||||||
|
except IndexError:
|
||||||
|
raise InvalidVideoIdException(f"Invalid video id: {video_id}")
|
||||||
|
return ret
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import json
|
|
||||||
import httpx
|
|
||||||
import pytchat.config as config
|
|
||||||
from pytchat.paramgen import arcparam
|
|
||||||
from pytchat.parser.live import Parser
|
|
||||||
|
|
||||||
|
|
||||||
def test_arcparam_0(mocker):
|
|
||||||
param = arcparam.getparam("01234567890", -1)
|
|
||||||
assert param == "op2w0wSDARpsQ2pnYURRb0xNREV5TXpRMU5qYzRPVEFxSndvWVgxOWZYMTlmWDE5ZlgxOWZYMTlmWDE5ZlgxOWZYMTlmRWdzd01USXpORFUyTnpnNU1Cb1Q2cWpkdVFFTkNnc3dNVEl6TkRVMk56ZzVNQ0FCKOgHMAA4AEAASARSAiAAcgIIAXgA"
|
|
||||||
|
|
||||||
|
|
||||||
def test_arcparam_1(mocker):
|
|
||||||
param = arcparam.getparam("01234567890", seektime=100000)
|
|
||||||
assert param == "op2w0wSHARpsQ2pnYURRb0xNREV5TXpRMU5qYzRPVEFxSndvWVgxOWZYMTlmWDE5ZlgxOWZYMTlmWDE5ZlgxOWZYMTlmRWdzd01USXpORFUyTnpnNU1Cb1Q2cWpkdVFFTkNnc3dNVEl6TkRVMk56ZzVNQ0FCKIDQ28P0AjAAOABAAEgDUgIgAHICCAF4AA%3D%3D"
|
|
||||||
|
|
||||||
def test_arcparam_3(mocker):
|
|
||||||
param = arcparam.getparam("01234567890")
|
|
||||||
assert param == "op2w0wSDARpsQ2pnYURRb0xNREV5TXpRMU5qYzRPVEFxSndvWVgxOWZYMTlmWDE5ZlgxOWZYMTlmWDE5ZlgxOWZYMTlmRWdzd01USXpORFUyTnpnNU1Cb1Q2cWpkdVFFTkNnc3dNVEl6TkRVMk56ZzVNQ0FCKOgHMAA4AEAASARSAiAAcgIIAXgA"
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import json
|
|
||||||
from pytest_httpx import HTTPXMock
|
|
||||||
from concurrent.futures import CancelledError
|
|
||||||
from pytchat.core_multithread.livechat import LiveChat
|
|
||||||
from pytchat.core_async.livechat import LiveChatAsync
|
|
||||||
from pytchat.exceptions import ResponseContextError
|
|
||||||
|
|
||||||
|
|
||||||
def _open_file(path):
|
|
||||||
with open(path, mode='r', encoding='utf-8') as f:
|
|
||||||
return f.read()
|
|
||||||
|
|
||||||
|
|
||||||
def add_response_file(httpx_mock: HTTPXMock, jsonfile_path: str):
|
|
||||||
testdata = json.loads(_open_file(jsonfile_path))
|
|
||||||
httpx_mock.add_response(json=testdata)
|
|
||||||
|
|
||||||
|
|
||||||
def test_async(httpx_mock: HTTPXMock):
|
|
||||||
add_response_file(httpx_mock, 'tests/testdata/paramgen_firstread.json')
|
|
||||||
|
|
||||||
async def test_loop():
|
|
||||||
try:
|
|
||||||
chat = LiveChatAsync(video_id='__test_id__')
|
|
||||||
_ = await chat.get()
|
|
||||||
assert chat.is_alive()
|
|
||||||
chat.terminate()
|
|
||||||
assert not chat.is_alive()
|
|
||||||
except ResponseContextError:
|
|
||||||
assert False
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
try:
|
|
||||||
loop.run_until_complete(test_loop())
|
|
||||||
except CancelledError:
|
|
||||||
assert True
|
|
||||||
|
|
||||||
|
|
||||||
def test_multithread(httpx_mock: HTTPXMock):
|
|
||||||
add_response_file(httpx_mock, 'tests/testdata/paramgen_firstread.json')
|
|
||||||
try:
|
|
||||||
chat = LiveChat(video_id='__test_id__')
|
|
||||||
_ = chat.get()
|
|
||||||
assert chat.is_alive()
|
|
||||||
chat.terminate()
|
|
||||||
assert not chat.is_alive()
|
|
||||||
except ResponseContextError:
|
|
||||||
assert False
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import json
|
|
||||||
from pytest_httpx import HTTPXMock
|
|
||||||
from concurrent.futures import CancelledError
|
|
||||||
from pytchat.core_multithread.livechat import LiveChat
|
|
||||||
from pytchat.core_async.livechat import LiveChatAsync
|
|
||||||
from pytchat.processors.dummy_processor import DummyProcessor
|
|
||||||
|
|
||||||
|
|
||||||
def _open_file(path):
|
|
||||||
with open(path, mode='r', encoding='utf-8') as f:
|
|
||||||
return f.read()
|
|
||||||
|
|
||||||
|
|
||||||
def add_response_file(httpx_mock: HTTPXMock, jsonfile_path: str):
|
|
||||||
testdata = json.loads(_open_file(jsonfile_path))
|
|
||||||
httpx_mock.add_response(json=testdata)
|
|
||||||
|
|
||||||
|
|
||||||
def test_async_live_stream(httpx_mock: HTTPXMock):
|
|
||||||
add_response_file(httpx_mock, 'tests/testdata/test_stream.json')
|
|
||||||
|
|
||||||
async def test_loop():
|
|
||||||
chat = LiveChatAsync(video_id='__test_id__', processor=DummyProcessor())
|
|
||||||
chats = await chat.get()
|
|
||||||
rawdata = chats[0]["chatdata"]
|
|
||||||
assert list(rawdata[0]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTextMessageRenderer"
|
|
||||||
assert list(rawdata[1]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTextMessageRenderer"
|
|
||||||
assert list(rawdata[2]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatPlaceholderItemRenderer"
|
|
||||||
assert list(rawdata[3]["addLiveChatTickerItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTickerPaidMessageItemRenderer"
|
|
||||||
assert list(rawdata[4]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatPaidMessageRenderer"
|
|
||||||
assert list(rawdata[5]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatPaidStickerRenderer"
|
|
||||||
assert list(rawdata[6]["addLiveChatTickerItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTickerSponsorItemRenderer"
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
try:
|
|
||||||
loop.run_until_complete(test_loop())
|
|
||||||
except CancelledError:
|
|
||||||
assert True
|
|
||||||
|
|
||||||
|
|
||||||
def test_async_replay_stream(httpx_mock: HTTPXMock):
|
|
||||||
add_response_file(httpx_mock, 'tests/testdata/finished_live.json')
|
|
||||||
add_response_file(httpx_mock, 'tests/testdata/chatreplay.json')
|
|
||||||
|
|
||||||
async def test_loop():
|
|
||||||
chat = LiveChatAsync(video_id='__test_id__', processor=DummyProcessor())
|
|
||||||
chats = await chat.get()
|
|
||||||
rawdata = chats[0]["chatdata"]
|
|
||||||
# assert fetching replaychat data
|
|
||||||
assert list(rawdata[0]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTextMessageRenderer"
|
|
||||||
assert list(rawdata[14]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatPaidMessageRenderer"
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
try:
|
|
||||||
loop.run_until_complete(test_loop())
|
|
||||||
except CancelledError:
|
|
||||||
assert True
|
|
||||||
|
|
||||||
|
|
||||||
def test_async_force_replay(httpx_mock: HTTPXMock):
|
|
||||||
add_response_file(httpx_mock, 'tests/testdata/test_stream.json')
|
|
||||||
add_response_file(httpx_mock, 'tests/testdata/chatreplay.json')
|
|
||||||
|
|
||||||
async def test_loop():
|
|
||||||
chat = LiveChatAsync(
|
|
||||||
video_id='__test_id__', processor=DummyProcessor(), force_replay=True)
|
|
||||||
chats = await chat.get()
|
|
||||||
rawdata = chats[0]["chatdata"]
|
|
||||||
# assert fetching replaychat data
|
|
||||||
assert list(rawdata[14]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatPaidMessageRenderer"
|
|
||||||
# assert not mix livechat data
|
|
||||||
assert list(rawdata[2]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] != "liveChatPlaceholderItemRenderer"
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
try:
|
|
||||||
loop.run_until_complete(test_loop())
|
|
||||||
except CancelledError:
|
|
||||||
assert True
|
|
||||||
|
|
||||||
|
|
||||||
def test_multithread_live_stream(httpx_mock: HTTPXMock):
|
|
||||||
add_response_file(httpx_mock, 'tests/testdata/test_stream.json')
|
|
||||||
chat = LiveChat(video_id='__test_id__', processor=DummyProcessor())
|
|
||||||
chats = chat.get()
|
|
||||||
rawdata = chats[0]["chatdata"]
|
|
||||||
# assert fetching livachat data
|
|
||||||
assert list(rawdata[0]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTextMessageRenderer"
|
|
||||||
assert list(rawdata[1]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTextMessageRenderer"
|
|
||||||
assert list(rawdata[2]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatPlaceholderItemRenderer"
|
|
||||||
assert list(rawdata[3]["addLiveChatTickerItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTickerPaidMessageItemRenderer"
|
|
||||||
assert list(rawdata[4]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatPaidMessageRenderer"
|
|
||||||
assert list(rawdata[5]["addChatItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatPaidStickerRenderer"
|
|
||||||
assert list(rawdata[6]["addLiveChatTickerItemAction"]["item"].keys())[
|
|
||||||
0] == "liveChatTickerSponsorItemRenderer"
|
|
||||||
chat.terminate()
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from pytchat.paramgen import liveparam
|
|
||||||
|
|
||||||
def test_liveparam_0(mocker):
|
|
||||||
_ts1= 1546268400
|
|
||||||
param = liveparam._build("01234567890",
|
|
||||||
*([_ts1*1000000 for i in range(5)]), topchat_only=False)
|
|
||||||
test_param="0ofMyAN1GhxDZzhLRFFvTE1ERXlNelExTmpjNE9UQWdBUT09KIC41tWqyt8CMAA4AEABShsIABAAGAAgADoAQABKAFCAuNbVqsrfAlgDeABQgLjW1arK3wJYgLjW1arK3wJoAYIBAggBiAEAmgECCACgAYC41tWqyt8C"
|
|
||||||
assert test_param == param
|
|
||||||
Reference in New Issue
Block a user