Compare commits

...

8 Commits

Author SHA1 Message Date
taizan-hokouto
6c7dc03d06 Merge branch 'release/v0.5.5' 2021-07-24 23:00:30 +09:00
taizan-hokouto
f511049eaa Increment version 2021-07-24 22:59:46 +09:00
taizan-hokouto
76118ba196 Update classifiers 2021-07-24 22:59:26 +09:00
taizan-hokouto
83b10ab2f3 Merge branch 'feature/client' into develop 2021-07-24 22:50:28 +09:00
taizan-hokouto
604c52e608 Update requirements 2021-07-24 22:46:56 +09:00
taizan-hokouto
8949599232 Update pipfile 2021-07-24 22:46:56 +09:00
taizan-hokouto
c9c235061c Add tests 2021-07-24 22:46:56 +09:00
taizan-hokouto
328889689f Make it possible to use customized client 2021-07-24 22:46:56 +09:00
16 changed files with 330 additions and 366 deletions

View File

@@ -4,7 +4,7 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
httpx = {extras = ["http2"], version = "0.16.1"} httpx = {extras = ["http2"]}
[dev-packages] [dev-packages]
pytest-mock = "*" pytest-mock = "*"

258
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "e1eb34f14c75998519a90838b283ccd23bd168afa8e4837f956c5c4df66376f9" "sha256": "74b83f2e50bc16f8d90c06ddc775d24ee427f8481a2501f62170bf5b76a2f1bd"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@@ -14,6 +14,14 @@
] ]
}, },
"default": { "default": {
"anyio": {
"hashes": [
"sha256:929a6852074397afe1d989002aa96d457e3e1e5441357c60d03e7eea0e65e1b0",
"sha256:ae57a67583e5ff8b4af47666ff5651c3732d45fd26c929253748e796af860374"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==3.3.0"
},
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
@@ -26,6 +34,7 @@
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
], ],
"markers": "python_version >= '3.6'",
"version": "==0.12.0" "version": "==0.12.0"
}, },
"h2": { "h2": {
@@ -44,21 +53,22 @@
}, },
"httpcore": { "httpcore": {
"hashes": [ "hashes": [
"sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9", "sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e",
"sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc" "sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff"
], ],
"version": "==0.12.3" "markers": "python_version >= '3.6'",
"version": "==0.13.6"
}, },
"httpx": { "httpx": {
"extras": [ "extras": [
"http2" "http2"
], ],
"hashes": [ "hashes": [
"sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537", "sha256:979afafecb7d22a1d10340bafb403cf2cb75aff214426ff206521fc79d26408c",
"sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b" "sha256:9f99c15d33642d38bce8405df088c1c4cfd940284b4290cacbfb02e64f4877c6"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.16.1" "version": "==0.18.2"
}, },
"hyperframe": { "hyperframe": {
"hashes": [ "hashes": [
@@ -89,23 +99,42 @@
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de" "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
], ],
"markers": "python_version >= '3.5'",
"version": "==1.2.0" "version": "==1.2.0"
} }
}, },
"develop": { "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": { "attrs": {
"hashes": [ "hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0" "version": "==21.2.0"
}, },
"bleach": { "bleach": {
"hashes": [ "hashes": [
"sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125", "sha256:306483a5a9795474160ad57fce3ddd1b50551e981eed8e15a582d34cef28aafa",
"sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433" "sha256:ae976d7174bba988c0b632def82fdc94235756edfb14e6558a9c5be555c9fb78"
], ],
"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": { "certifi": {
"hashes": [ "hashes": [
@@ -114,96 +143,28 @@
], ],
"version": "==2021.5.30" "version": "==2021.5.30"
}, },
"cffi": { "charset-normalizer": {
"hashes": [ "hashes": [
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", "sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1",
"sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373", "sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"
"sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69",
"sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f",
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
"sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05",
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
"sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0",
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
"sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7",
"sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f",
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
"sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76",
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
"sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed",
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
"sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
"sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55",
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
"sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
"sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
"sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
"sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
"sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
"sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
"sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
"sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
"sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
"sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
"sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
"sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc",
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
"sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333",
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
], ],
"version": "==1.14.5" "markers": "python_version >= '3'",
}, "version": "==2.0.3"
"chardet": {
"hashes": [
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
],
"version": "==4.0.0"
}, },
"colorama": { "colorama": {
"hashes": [ "hashes": [
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
], ],
"markers": "platform_system == 'Windows' and sys_platform == 'win32'",
"version": "==0.4.4" "version": "==0.4.4"
}, },
"cryptography": {
"hashes": [
"sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d",
"sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959",
"sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6",
"sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873",
"sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2",
"sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713",
"sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1",
"sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177",
"sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250",
"sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca",
"sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d",
"sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"
],
"version": "==3.4.7"
},
"docutils": { "docutils": {
"hashes": [ "hashes": [
"sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125", "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125",
"sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61" "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.17.1" "version": "==0.17.1"
}, },
"h11": { "h11": {
@@ -211,25 +172,27 @@
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
], ],
"markers": "python_version >= '3.6'",
"version": "==0.12.0" "version": "==0.12.0"
}, },
"httpcore": { "httpcore": {
"hashes": [ "hashes": [
"sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9", "sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e",
"sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc" "sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff"
], ],
"version": "==0.12.3" "markers": "python_version >= '3.6'",
"version": "==0.13.6"
}, },
"httpx": { "httpx": {
"extras": [ "extras": [
"http2" "http2"
], ],
"hashes": [ "hashes": [
"sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537", "sha256:979afafecb7d22a1d10340bafb403cf2cb75aff214426ff206521fc79d26408c",
"sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b" "sha256:9f99c15d33642d38bce8405df088c1c4cfd940284b4290cacbfb02e64f4877c6"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.16.1" "version": "==0.18.2"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
@@ -240,10 +203,11 @@
}, },
"importlib-metadata": { "importlib-metadata": {
"hashes": [ "hashes": [
"sha256:960d52ba7c21377c990412aca380bf3642d734c2eaab78a2c39319f67c6a5786", "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac",
"sha256:e592faad8de1bda9fe920cf41e15261e7131bcf266c30306eec00e8e225c1dd5" "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"
], ],
"version": "==4.4.0" "markers": "python_version >= '3.6'",
"version": "==4.6.1"
}, },
"iniconfig": { "iniconfig": {
"hashes": [ "hashes": [
@@ -252,40 +216,35 @@
], ],
"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:045703609dd3fccfcdb27da201684278823b72af515aedec1a8515719a038cb8", "sha256:045703609dd3fccfcdb27da201684278823b72af515aedec1a8515719a038cb8",
"sha256:8f607d7d1cc502c43a932a275a56fe47db50271904513a379d39df1af277ac48" "sha256:8f607d7d1cc502c43a932a275a56fe47db50271904513a379d39df1af277ac48"
], ],
"markers": "python_version >= '3.6'",
"version": "==23.0.1" "version": "==23.0.1"
}, },
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
], ],
"version": "==20.9" "markers": "python_version >= '3.6'",
"version": "==21.0"
}, },
"pkginfo": { "pkginfo": {
"hashes": [ "hashes": [
"sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4", "sha256:37ecd857b47e5f55949c41ed061eb51a0bee97a87c969219d144c0e023982779",
"sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75" "sha256:e7432f81d08adec7297633191bbf0bd47faf13cd8724c3a13250e51d542635bd"
], ],
"version": "==1.7.0" "version": "==1.7.1"
}, },
"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": {
@@ -293,20 +252,15 @@
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" "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.10.0"
}, },
"pycparser": {
"hashes": [
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"version": "==2.20"
},
"pygments": { "pygments": {
"hashes": [ "hashes": [
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f", "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e" "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
], ],
"markers": "python_version >= '3.5'",
"version": "==2.9.0" "version": "==2.9.0"
}, },
"pyparsing": { "pyparsing": {
@@ -314,6 +268,7 @@
"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": {
@@ -321,23 +276,32 @@
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
], ],
"markers": "python_version >= '3.6'",
"version": "==6.2.4" "version": "==6.2.4"
}, },
"pytest-httpx": { "pytest-httpx": {
"hashes": [ "hashes": [
"sha256:0a7c56e559b23efbf857054cd74de60a7c540694a162423f89c70da6ad358d8e", "sha256:1e135b8779060091fa1c87d8aff7904921e8bea95fce5e971a0262764d064b12",
"sha256:d32e8f6fb7e028f0313f5f5a2d463c8673eb43fd11a9bfe8527299717a7764c4" "sha256:e262932f2d3ce380da8273c7bacbcfdc2c94e167fa94da29571caaf1f4d3ba27"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.10.1" "version": "==0.12.0"
}, },
"pytest-mock": { "pytest-mock": {
"hashes": [ "hashes": [
"sha256:024e405ad382646318c4281948aadf6fe1135632bea9cc67366ea0c4098ef5f2", "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3",
"sha256:a4d6d37329e4a893e77d9ffa89e838dd2b45d5dc099984cf03c703ac8411bb82" "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"
], ],
"index": "pypi", "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": { "readme-renderer": {
"hashes": [ "hashes": [
@@ -348,10 +312,11 @@
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" "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": { "requests-toolbelt": {
"hashes": [ "hashes": [
@@ -370,19 +335,12 @@
], ],
"version": "==1.5.0" "version": "==1.5.0"
}, },
"secretstorage": {
"hashes": [
"sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f",
"sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"
],
"markers": "sys_platform == 'linux'",
"version": "==3.3.1"
},
"six": { "six": {
"hashes": [ "hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0" "version": "==1.16.0"
}, },
"sniffio": { "sniffio": {
@@ -390,6 +348,7 @@
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de" "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
], ],
"markers": "python_version >= '3.5'",
"version": "==1.2.0" "version": "==1.2.0"
}, },
"toml": { "toml": {
@@ -397,30 +356,32 @@
"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:736524215c690621b06fc89d0310a49822d75e599fcd0feb7cc742b98d692493", "sha256:5aa445ea0ad8b16d82b15ab342de6b195a722d75fc1ef9934a46bba6feafbc64",
"sha256:cd5791b5d7c3f2f1819efc81d36eb719a38e0906a7380365c556779f585ea042" "sha256:8bb94db0d4468fea27d004a0f1d1c02da3cdedc00fe491c0de986b76a04d6b0a"
], ],
"version": "==4.61.0" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==4.61.2"
}, },
"twine": { "twine": {
"hashes": [ "hashes": [
"sha256:34352fd52ec3b9d29837e6072d5a2a7c6fe4290e97bba46bb8d478b5c598f7ab", "sha256:087328e9bb405e7ce18527a2dca4042a84c7918658f951110b38bc135acab218",
"sha256:ba9ff477b8d6de0c89dd450e70b2185da190514e91c42cc62f96850025c10472" "sha256:4caec0f1ed78dc4c9b83ad537e453d03ce485725f2aea57f1bb3fdde78dae936"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.2.0" "version": "==3.4.2"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
], ],
"index": "pypi", "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.5" "version": "==1.26.6"
}, },
"webencodings": { "webencodings": {
"hashes": [ "hashes": [
@@ -431,18 +392,19 @@
}, },
"wheel": { "wheel": {
"hashes": [ "hashes": [
"sha256:906864fb722c0ab5f2f9c35b2c65e3af3c009402c108a709c0aca27bc2c9187b", "sha256:78b5b185f0e5763c26ca1e324373aadd49182ca90e825f7853f4b2509215dc0e",
"sha256:aaef9b8c36db72f8bf7f1e54f85f875c4d466819940863ca0b3f3f77f0a1646f" "sha256:e11eefd162658ea59a60a0f6c7d493a7190ea4b9a85e335b33489d9f17e0245e"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.36.1" "version": "==0.36.2"
}, },
"zipp": { "zipp": {
"hashes": [ "hashes": [
"sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
"sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
], ],
"version": "==3.4.1" "markers": "python_version >= '3.6'",
"version": "==3.5.0"
} }
} }
} }

View File

@@ -1,8 +1,8 @@
""" """
pytchat is a lightweight python library to browse youtube livechat without Selenium or BeautifulSoup. pytchat is a lightweight python library to browse youtube livechat without Selenium or BeautifulSoup.
""" """
__copyright__ = 'Copyright (C) 2019, 2020 taizan-hokuto' __copyright__ = 'Copyright (C) 2019, 2020, 2021 taizan-hokuto'
__version__ = '0.5.4' __version__ = '0.5.5'
__license__ = 'MIT' __license__ = 'MIT'
__author__ = 'taizan-hokuto' __author__ = 'taizan-hokuto'
__author_email__ = '55448286+taizan-hokuto@users.noreply.github.com' __author_email__ = '55448286+taizan-hokuto@users.noreply.github.com'

View File

@@ -29,9 +29,13 @@ class PytchatCore:
processor : ChatProcessor processor : ChatProcessor
client : httpx.Client
The client for connecting youtube.
You can specify any customized httpx client (e.g. coolies, user agent).
interruptable : bool interruptable : bool
Allows keyboard interrupts. 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. the problem.
force_replay : bool force_replay : bool
@@ -57,6 +61,7 @@ class PytchatCore:
def __init__(self, video_id, def __init__(self, video_id,
seektime=-1, seektime=-1,
processor=DefaultProcessor(), processor=DefaultProcessor(),
client = httpx.Client(http2=True),
interruptable=True, interruptable=True,
force_replay=False, force_replay=False,
topchat_only=False, topchat_only=False,
@@ -64,6 +69,7 @@ class PytchatCore:
logger=config.logger(__name__), logger=config.logger(__name__),
replay_continuation=None replay_continuation=None
): ):
self._client = client
self._video_id = util.extract_video_id(video_id) self._video_id = util.extract_video_id(video_id)
self.seektime = seektime self.seektime = seektime
if isinstance(processor, tuple): if isinstance(processor, tuple):
@@ -97,7 +103,7 @@ class PytchatCore:
""" """
self.continuation = liveparam.getparam( self.continuation = liveparam.getparam(
self._video_id, 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) past_sec=3)
def _get_chat_component(self): def _get_chat_component(self):
@@ -110,19 +116,18 @@ class PytchatCore:
parameter for next chat data parameter for next chat data
''' '''
try: try:
with httpx.Client(http2=True) as client: if self.continuation and self._is_alive:
if self.continuation and self._is_alive: contents = self._get_contents(self.continuation, self._client, headers)
contents = self._get_contents(self.continuation, client, headers) metadata, chatdata = self._parser.parse(contents)
metadata, chatdata = self._parser.parse(contents) timeout = metadata['timeoutMs'] / 1000
timeout = metadata['timeoutMs'] / 1000 chat_component = {
chat_component = { "video_id": self._video_id,
"video_id": self._video_id, "timeout": timeout,
"timeout": timeout, "chatdata": chatdata
"chatdata": chatdata }
} self.continuation = metadata.get('continuation')
self.continuation = metadata.get('continuation') self._last_offset_ms = metadata.get('last_offset_ms', 0)
self._last_offset_ms = metadata.get('last_offset_ms', 0) return chat_component
return chat_component
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)}")
self._raise_exception(e) self._raise_exception(e)
@@ -139,9 +144,8 @@ class PytchatCore:
------- -------
'continuationContents' which includes metadata & chat data. 'continuationContents' which includes metadata & chat data.
''' '''
livechat_json = ( livechat_json = self._get_livechat_json(
self._get_livechat_json(continuation, client, replay=self._is_replay, offset_ms=self._last_offset_ms) 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
@@ -152,7 +156,8 @@ class PytchatCore:
self._fetch_url = config._smr self._fetch_url = config._smr
continuation = arcparam.getparam( continuation = arcparam.getparam(
self._video_id, self.seektime, self._topchat_only, util.get_channelid(client, self._video_id)) 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:
@@ -173,15 +178,14 @@ class PytchatCore:
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):
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 = response.json()
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 time.sleep(2)
time.sleep(2) continue
continue
else: else:
self._logger.error(f"[{self._video_id}]" self._logger.error(f"[{self._video_id}]"
f"Exceeded retry count. Last error: {str(err)}") f"Exceeded retry count. Last error: {str(err)}")
@@ -202,6 +206,8 @@ class PytchatCore:
return self._is_alive return self._is_alive
def terminate(self): def terminate(self):
if not self.is_alive():
return
self._is_alive = False self._is_alive = False
self.processor.finalize() self.processor.finalize()

View File

@@ -78,6 +78,7 @@ class LiveChatAsync:
seektime=-1, seektime=-1,
processor=DefaultProcessor(), processor=DefaultProcessor(),
buffer=None, buffer=None,
client = httpx.AsyncClient(http2=True),
interruptable=True, interruptable=True,
callback=None, callback=None,
done_callback=None, done_callback=None,
@@ -88,6 +89,7 @@ class LiveChatAsync:
logger=config.logger(__name__), logger=config.logger(__name__),
replay_continuation=None replay_continuation=None
): ):
self._client:httpx.AsyncClient = client
self._video_id = util.extract_video_id(video_id) self._video_id = util.extract_video_id(video_id)
self.seektime = seektime self.seektime = seektime
if isinstance(processor, tuple): if isinstance(processor, tuple):
@@ -152,9 +154,10 @@ class LiveChatAsync:
create and start _listen loop. create and start _listen loop.
""" """
if not self.continuation: if not self.continuation:
channel_id = await util.get_channelid_async(self._client, self._video_id)
self.continuation = liveparam.getparam( self.continuation = liveparam.getparam(
self._video_id, self._video_id,
channel_id=util.get_channelid(httpx.Client(http2=True), self._video_id), channel_id,
past_sec=3) past_sec=3)
await self._listen(self.continuation) await self._listen(self.continuation)
@@ -169,10 +172,10 @@ class LiveChatAsync:
parameter for next chat data parameter for next chat data
''' '''
try: try:
async with httpx.AsyncClient(http2=True) as client: async with self._client as client:
while(continuation and self._is_alive): while(continuation and self._is_alive):
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) #Q#
metadata, chatdata = self._parser.parse(contents) metadata, chatdata = self._parser.parse(contents)
continuation = metadata.get('continuation') continuation = metadata.get('continuation')
if continuation: if continuation:
@@ -214,9 +217,10 @@ class LiveChatAsync:
''' '''
self._pauser.put_nowait(None) self._pauser.put_nowait(None)
if not self._is_replay: if not self._is_replay:
async with httpx.AsyncClient(http2=True) as client: async with self._client as client:
continuation = await liveparam.getparam(self._video_id, channel_id = await util.get_channelid_async(client, self.video_id)
channel_id=util.get_channelid_async(client, self.video_id), continuation = liveparam.getparam(self._video_id,
channel_id,
past_sec=3) past_sec=3)
return continuation return continuation
@@ -338,12 +342,14 @@ class LiveChatAsync:
self._logger.debug(f'[{self._video_id}] cancelled:{sender}') self._logger.debug(f'[{self._video_id}] cancelled:{sender}')
def terminate(self): def terminate(self):
if not self.is_alive():
return
if self._pauser.empty(): if self._pauser.empty():
self._pauser.put_nowait(None) self._pauser.put_nowait(None)
self._is_alive = False self._is_alive = False
self._buffer.put_nowait({}) self._buffer.put_nowait({})
self.processor.finalize() self.processor.finalize()
def _keyboard_interrupt(self): def _keyboard_interrupt(self):
self.exception = exceptions.ChatDataFinished() self.exception = exceptions.ChatDataFinished()
self.terminate() self.terminate()

View File

@@ -78,6 +78,7 @@ class LiveChat:
def __init__(self, video_id, def __init__(self, video_id,
seektime=-1, seektime=-1,
processor=DefaultProcessor(), processor=DefaultProcessor(),
client = httpx.Client(http2=True),
buffer=None, buffer=None,
interruptable=True, interruptable=True,
callback=None, callback=None,
@@ -88,6 +89,7 @@ class LiveChat:
logger=config.logger(__name__), logger=config.logger(__name__),
replay_continuation=None replay_continuation=None
): ):
self._client = client
self._video_id = util.extract_video_id(video_id) self._video_id = util.extract_video_id(video_id)
self.seektime = seektime self.seektime = seektime
if isinstance(processor, tuple): if isinstance(processor, tuple):
@@ -150,7 +152,7 @@ class LiveChat:
if not self.continuation: if not self.continuation:
self.continuation = liveparam.getparam( self.continuation = liveparam.getparam(
self._video_id, 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) past_sec=3)
self._listen(self.continuation) self._listen(self.continuation)
@@ -164,7 +166,7 @@ class LiveChat:
parameter for next chat data parameter for next chat data
''' '''
try: try:
with httpx.Client(http2=True) as client: with self._client as client:
while(continuation and self._is_alive): while(continuation and self._is_alive):
continuation = self._check_pause(continuation) continuation = self._check_pause(continuation)
contents = self._get_contents(continuation, client, headers) contents = self._get_contents(continuation, client, headers)
@@ -224,7 +226,8 @@ class LiveChat:
------- -------
'continuationContents' which includes metadata & chat data. '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) contents, dat = self._parser.get_contents(livechat_json)
if self._dat == '' and dat: if self._dat == '' and dat:
self._dat = dat self._dat = dat
@@ -235,8 +238,8 @@ class LiveChat:
self._fetch_url = config._smr self._fetch_url = config._smr
continuation = arcparam.getparam( continuation = arcparam.getparam(
self._video_id, self.seektime, self._topchat_only, util.get_channelid(client, self._video_id)) 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(
self._parser.get_contents(livechat_json)[0]) self._parser.get_contents(livechat_json)[0])
if reload_continuation: if reload_continuation:
@@ -331,6 +334,8 @@ class LiveChat:
self._logger.debug(f'[{self._video_id}] cancelled:{sender}') self._logger.debug(f'[{self._video_id}] cancelled:{sender}')
def terminate(self): def terminate(self):
if not self.is_alive():
return
if self._pauser.empty(): if self._pauser.empty():
self._pauser.put_nowait(None) self._pauser.put_nowait(None)
self._is_alive = False self._is_alive = False

View File

@@ -7,6 +7,7 @@ from .renderer.paidmessage import LiveChatPaidMessageRenderer
from .renderer.paidsticker import LiveChatPaidStickerRenderer from .renderer.paidsticker import LiveChatPaidStickerRenderer
from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer
from .renderer.membership import LiveChatMembershipItemRenderer from .renderer.membership import LiveChatMembershipItemRenderer
from .renderer.donation import LiveChatDonationAnnouncementRenderer
from .. chat_processor import ChatProcessor from .. chat_processor import ChatProcessor
from ... import config from ... import config
@@ -124,7 +125,8 @@ class DefaultProcessor(ChatProcessor):
"liveChatPaidMessageRenderer": LiveChatPaidMessageRenderer(), "liveChatPaidMessageRenderer": LiveChatPaidMessageRenderer(),
"liveChatPaidStickerRenderer": LiveChatPaidStickerRenderer(), "liveChatPaidStickerRenderer": LiveChatPaidStickerRenderer(),
"liveChatLegacyPaidMessageRenderer": LiveChatLegacyPaidMessageRenderer(), "liveChatLegacyPaidMessageRenderer": LiveChatLegacyPaidMessageRenderer(),
"liveChatMembershipItemRenderer": LiveChatMembershipItemRenderer() "liveChatMembershipItemRenderer": LiveChatMembershipItemRenderer(),
"liveChatDonationAnnouncementRenderer": LiveChatDonationAnnouncementRenderer(),
} }
def process(self, chat_components: list): def process(self, chat_components: list):

View File

@@ -0,0 +1,6 @@
from .base import BaseRenderer
class LiveChatDonationAnnouncementRenderer(BaseRenderer):
def settype(self):
self.chat.type = "donation"

View File

@@ -82,16 +82,17 @@ class RingQueue:
class SpeedCalculator(ChatProcessor, RingQueue): class SpeedCalculator(ChatProcessor, RingQueue):
""" """
チャットの勢いを計算する。 Calculate the momentum of the chat.
一定期間のチャットデータのうち、最初のチャットの投稿時刻と 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
1分あたりの速度に換算する。 time by the number of chats and convert it to speed per minute.
Parameter Parameter
---------- ----------
capacity : int capacity : int
RingQueueに格納するチャット勢い算出用データの最大数 Maximum number of data for calculating chat momentum
to be stored in RingQueue.
""" """
def __init__(self, capacity=10): def __init__(self, capacity=10):
@@ -111,17 +112,17 @@ class SpeedCalculator(ChatProcessor, RingQueue):
def _calc_speed(self): def _calc_speed(self):
""" """
RingQueue内のチャット勢い算出用データリストを元に、 Calculates the chat speed based on the data list for calculating
チャット速度を計算して返す the chat momentum in RingQueue.
Return Return
--------------------------- ---------------------------
チャット速度(1分間で換算したチャット数) Chat speed (number of chats converted per minute)
""" """
try: try:
# キュー内の総チャット数 # Total number of chats in the queue
total = sum(item['chat_count'] for item in self.items) 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']) duration = (self.items[self.last_pos]['endtime'] - self.items[self.first_pos]['starttime'])
if duration != 0: if duration != 0:
return int(total * 60 / duration) return int(total * 60 / duration)
@@ -131,19 +132,12 @@ class SpeedCalculator(ChatProcessor, RingQueue):
def _put_chatdata(self, actions): def _put_chatdata(self, actions):
""" """
チャットデータからタイムスタンプを読み取り、勢い測定用のデータを組み立て、
RingQueueに投入する。
200円以上のスパチャはtickerとmessageの2つのデータが生成されるが、
tickerの方は時刻データの場所が異なることを利用し、勢いの集計から除外している。
Parameter Parameter
--------- ---------
actions : List[dict] actions : List[dict]
チャットデータ(addChatItemAction) のリスト List of addChatItemActions
""" """
def _put_emptydata(): def _put_emptydata():
'''
チャットデータがない場合に空のデータをキューに投入する。
'''
timestamp_now = int(time.time()) timestamp_now = int(time.time())
self.put({ self.put({
'chat_count': 0, 'chat_count': 0,
@@ -152,9 +146,6 @@ class SpeedCalculator(ChatProcessor, RingQueue):
}) })
def _get_timestamp(action: dict): def _get_timestamp(action: dict):
"""
チャットデータから時刻データを取り出す。
"""
try: try:
item = action['addChatItemAction']['item'] item = action['addChatItemAction']['item']
timestamp = int(item[list(item.keys())[0]]['timestampUsec']) timestamp = int(item[list(item.keys())[0]]['timestampUsec'])
@@ -166,32 +157,24 @@ class SpeedCalculator(ChatProcessor, RingQueue):
_put_emptydata() _put_emptydata()
return return
# actions内の時刻データを持つチャットデータの数
counter = 0 counter = 0
# actions内の最初のチャットデータの時刻
starttime = None starttime = None
# actions内の最後のチャットデータの時刻
endtime = None endtime = None
for action in actions: for action in actions:
# チャットデータからtimestampUsecを読み取る # Get timestampUsec from chatdata
gettime = _get_timestamp(action) gettime = _get_timestamp(action)
# 時刻のないデータだった場合は次の行のデータで読み取り試行
if gettime is None: if gettime is None:
continue continue
# 最初に有効な時刻を持つデータのtimestampをstarttimeに設定
if starttime is None: if starttime is None:
starttime = gettime starttime = gettime
# 最後のtimestampを設定(途中で時刻のないデータの場合もあるので上書きしていく)
endtime = gettime endtime = gettime
# チャットの数をインクリメント
counter += 1 counter += 1
# チャット速度用のデータをRingQueueに送る
if starttime is None or endtime is None: if starttime is None or endtime is None:
_put_emptydata() _put_emptydata()
return return

View File

@@ -1 +1 @@
httpx[http2]==0.16.1 httpx[http2]

View File

@@ -50,8 +50,9 @@ setup(
'Natural Language :: Japanese', 'Natural Language :: Japanese',
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
], ],
description="a python library for fetching youtube live chat.", description="a python library for fetching youtube live chat.",

View File

@@ -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) == []

View File

@@ -67,6 +67,5 @@ def test_process_2():
'chatdata': load_chatdata(r"tests/testdata/calculator/replay_end.json") 'chatdata': load_chatdata(r"tests/testdata/calculator/replay_end.json")
} }
assert False assert False
SuperchatCalculator().process([chat_component])
except ChatParseException: except ChatParseException:
assert True assert True

View 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

View 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

View 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()