diff --git a/Pipfile b/Pipfile index 1f1e602..fdbb6de 100644 --- a/Pipfile +++ b/Pipfile @@ -6,9 +6,8 @@ name = "pypi" [packages] requests = "*" pygame = "*" - -[dev-packages] -python-language-server = {extras = ["all"], version = "*"} +websocket-client = "*" +miniirc = "*" [requires] python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index 0adda6f..9446338 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "659dc7baae3e246c60460dd8167ae448e6980ede9ccb2b10b2d6e0b995d73eeb" + "sha256": "c6f4727f4fbf44c5155fd9276c2a575224bd4ce1914e7051b84c01dea7168336" }, "pipfile-spec": 6, "requires": { @@ -18,17 +18,18 @@ "default": { "certifi": { "hashes": [ - "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", - "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" ], - "version": "==2020.11.8" + "version": "==2020.12.5" }, "chardet": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], - "version": "==3.0.4" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.0.0" }, "idna": { "hashes": [ @@ -38,209 +39,68 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, - "pygame": { + "miniirc": { "hashes": [ - "sha256:00268b75204519628760030d256b17e557710a859fadf7cbf80ead80efc22779", - "sha256:0ba624e8cc2056f0c8f7f819f2564284bb675dd9a8097e03762763a63f99e1b3", - "sha256:0fa2dfa9daacaa5533ab733c0fd038e319b83d685f92e986fe3233dd06e508e4", - "sha256:19b66c7546261996e2533b922f78b04edc1901586d6e023af7ec0aef4d6d4a9f", - "sha256:1bc6e76e31e7ce6b734f12a06d975af2ef29da93e5ccbecbc8ed2d38d1bea8ea", - "sha256:208fbc0f25a641b953c7ec1c537a3c0b43a4faf6a8f303be15d02e308c671bab", - "sha256:21f4e50c1f13e143c590a48e63644ed2d0cfb50f9ce699c5e8b7af7caaa5cbe3", - "sha256:2c2475f706e49b4a67d6958e5cce1157f3875083eba92ab298bdf7ba33780524", - "sha256:3a2e7a0e6377224d3956ac9791382f7baadf66957679be8a9cc493f488245584", - "sha256:3d0be0275b1d4939c9c9074d879407f330cbac0c837cd0f4b24ab0377959f6da", - "sha256:411fa087a21f065d720d199b848652bf02d61cb39ff4d62745be37257fd84361", - "sha256:4e58cc6f47412cf441a0c9b98dcb8141e1f2cdb6bbd0b00d1b89572d54195002", - "sha256:5450704eab972951cce02231e21dbbdffcf7e7c569cfb8d26346e2b18197ae0f", - "sha256:579d2162591241dc98c80c796576bb93edbd8107a8a43e13a18bf3c9c0dfb446", - "sha256:5a3f0f436174e95027c60a37f06d84d25d2275de3255fe85846baa47c0e36e6b", - "sha256:5ed4b00612b11c1ebbfbff1379616de2fa4d1acd9830232cc9a4a2ac2aa55e84", - "sha256:63b038da116a643046181b02173fd894d87d2f85ecfd6aa7d5ece73c6ef501e9", - "sha256:697599838b559b9f6573c3064357b404d3fd7af145168e2f4e3ed551bc4400ec", - "sha256:69f9452365eef5ef476eca2657dcc488d8f93423a2f6e80e6dc5ada9fa813d21", - "sha256:6bd16df20e5b9bc82a4dae7342573a7917530c2e4713c576f2684ebc79a9aa73", - "sha256:6f5b10de20a6d6a4c84371fd55d25038ef3fa3ceb1a20a61bcab1b1f2459a115", - "sha256:7c228e067b5e212acc2ee1412e2cf29f5900fd290b09556014aee74665ffaded", - "sha256:7ee08691262b1a22dfd28b9b0e126cfc0276b349ddfd7ef8964a0e54d000ab0e", - "sha256:87795c07c1de05150759b52e7bc3ccb38e76148ae8e40f07bf2b1368e7fabf0e", - "sha256:8a240f598c21a4d2218eaeccfdd77bf0d0f6789133a154b07667285157b95ffa", - "sha256:9d10a197518f8b50d3a61b923762a37063b9d6e27d1abe5167bb3a762e816833", - "sha256:a03f0357ad4a9daf6e70b239804b77088d0b1543fc32e672f97f38e35fb1c206", - "sha256:a7d298ebc7239dcf98e093b3510abc5279c8c7c149c60d2a8a81a1b44a671da3", - "sha256:abed99f21c11d7f8deb0b3a41e2e4b0d00a98c17c23edd4dd344f169b317a586", - "sha256:b5ac39ef5453252cb4b4ce2956ad9832688476a7a99bbdee1a7e17bf40e0a11f", - "sha256:c2aea8cfa8156016d5316a601e804e8eacc77facd550d6bff893130f8f506a45", - "sha256:cb6dda696df8326eebf8514aa7d6e18977d37822241d8661069ca42a45f957d6", - "sha256:d1b9279133bb9f1ff5e7f5369a7a25749279724f64428980f6cec58eb7ce18da", - "sha256:d700ffe744efde7eba1709950066429bdd21e9eec2288e29d39c3a9f21a04a7c", - "sha256:d768ff1f2dd3de84585a68b491821badc35b2c9c4453c6cbcf48949be99ea781", - "sha256:db05ce075aed6a9ac6b5613f273eb134a643a7b910ded4f6395b09a7f24edacf", - "sha256:dc7cbfc0a0a0f1f8b1be4dbf0788a4ac7d3dd2991067e1cfd869c65c323a24a5", - "sha256:e21e68b1110bea64ed1dee6e644b49423f9539fb3e29d27ca6c68011f999c8f9", - "sha256:e716b3a5ca469959ad0d631cbb7846386741fc056968a3abb097ba5b43757214", - "sha256:e75fe7624d2fc03b93a706f4d8f8204cb9c83c8f16245553546b3bd0a733556c", - "sha256:e8effa8b5a8d2bb9c07dccb18337b07174d4956516978a35350bc23e254189cc" + "sha256:3734fde677785fd1f735a1710b94b93643f6ecd769b8b41c084ef42515a70c47", + "sha256:7be5d125b02275508b65421de91af6a2e3efeac0f5b7708454ddd2cbac9a5250" ], "index": "pypi", - "version": "==2.0.0" + "version": "==1.6.3" + }, + "pygame": { + "hashes": [ + "sha256:0571dde0277483f5060c8ee43cbfd8df5776b12505e3948eee241c8ce9b93371", + "sha256:07ed57062be4bb9741f57dab1751d95574c091c9958ed7e39cdb246d50903283", + "sha256:107d5f82f471baee4b9522a691cb320dd52dbf329ed7a0e9ab25f75cd3caf890", + "sha256:10ca736eecedadf492ba1191f9fa3a5e6f30db2b9f8882b3ee7706d5a89c14e0", + "sha256:19357c826ab94f9ae5b4ec5cb752cc806cfc29ea32cf7bdaacb65fa2615607e8", + "sha256:21475405bcdeb20b8a796a3da6704ebb816e06b29749dd64ff619e80816b7932", + "sha256:2d9b0a66034fed390ee367a549435853502c9d4fe82ac0fa3a520f0ad5648e6e", + "sha256:30eb5c7adb0b3362024cec2c461be6978fbfc99c3bca974e438b1b540cd09438", + "sha256:3270cbcff40ca2b5622a145346298a33285c91b6d50097e0b85123d9a2bc7c9b", + "sha256:328d70d40bc9a6defb9f330f5e7f3d0726af1e7c2308ebca582e69480db2950d", + "sha256:3b31d088129977885f72037c55cfa1140e9bcf3468e68b46141f6cc2b33d456b", + "sha256:3c2676b4fd278d632037eacd3b0524ce1a592c048e8e5eb5830475f83585cb3a", + "sha256:44f3ff8224d7cb998642400371c685005c8316b55e87794cbf1f6407b88ec424", + "sha256:49c2f58559c1fbf4ba258e4b141578ccb0e83da3d4f823894f6171a8f0d594ed", + "sha256:4d3135a1f8c76c3fff1ef8b7a51e4c6523748e9bdbd7bca6daa69790ce0e798a", + "sha256:4f41252dfa1e8bb95f2ea51fba710827dde9820a535623d002a65621bafe7e3f", + "sha256:5aeeb6659a7fe7760a78e449566553ae8c949ae29dd907a8eb4171fa0a274c16", + "sha256:5afc34f0af0cec09a20b6bb09090054fac5169ab01909e01b06e7e0752ab0153", + "sha256:5f057e5aa4c383fcf18560dcae2c5593e37e3fc941083a0a00a17f7cf25ee522", + "sha256:72625dc949c6d08ba7ce7c37a33163bb498d90ca0d7e626db3cfbf486df4db1d", + "sha256:7ea518d8eeb072c77c16977cdee3c59d9fffa750ed9c7c9c533ba520b6b08af7", + "sha256:81510ffb1c31a3827c6be047b1926d81caf36dc734564ca0e14903d6bce60c6f", + "sha256:845385caf99f8d941607791c60e560d24b4a35c70eef0b01c30cfde0b913ff92", + "sha256:898874521a9be1f9dbc5b036a9755803287c2664e335afd3e10963f7f4ccb853", + "sha256:8b1e7b63f47aafcdd8849933b206778747ef1802bd3d526aca45ed77141e4001", + "sha256:9aead3f2eed90260136b201f398965900c5335c974bb7b47c381d98e39284018", + "sha256:9de559462aaa68c40bb7625dcd587584b4eb85c4208528dc97b9ee7254945294", + "sha256:9f48277de1daa83fd58a722b2e3423201b5eb39842227f32702fb78e4bba5a71", + "sha256:9fd0691c4fe58b932674bb6a91d2808790e8269c3183ef16052f13e1c602ac00", + "sha256:a4e35d89b6754941e82df1ce980a1c370943d3c076938d94ed1e48165dd6a11b", + "sha256:ad230911d61f448c09886d3c92b2eae44ca7530babe9c48e74e02a0622ce2d34", + "sha256:b812285d23b5644c643a6ae30553a772f935f47f61826660b108b8727936384b", + "sha256:bf833c853a0568738ee5d88e1345c17bf3e8db626c36fb895327a35bb1827b0b", + "sha256:c188ce4bf1544f2758e8b651f4349a0f3dc441e09d8ab7c4863db1ae8f084a32", + "sha256:d8059084ce54b2c3d7b2c8bacd5f6490db849b2d2d6e7368c160b08504c87e73", + "sha256:e72cdc97a49509ca2298350c2c3a0ac26bc8e943ce003a7d245df42e91439d5d", + "sha256:eab18df58dcc8512f1b694f7218146828d7e3dd3f4e73bfd6942a11810293fd5", + "sha256:ece424c83a575c2e0ba25815871458d3bbade46d76b7997236fb51a0251229ab", + "sha256:ed80b40da839d60f4c03915bb3638e3c96ea8c30e689d0cc309b7597d82cc217", + "sha256:fd5ee0f42d59a290c049f91894e0739f62c2908e7edc028ffb847a105e68bfc3", + "sha256:fd6acd09d2a0fd3f616b18f977f399ed3dd95e2d6754f115837f026d19d62e10" + ], + "index": "pypi", + "version": "==2.0.1" }, "requests": { "hashes": [ - "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8", - "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998" + "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", + "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" ], "index": "pypi", - "version": "==2.25.0" - }, - "urllib3": { - "hashes": [ - "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", - "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" - ], - "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.2" - } - }, - "develop": { - "astroid": { - "hashes": [ - "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", - "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" - ], - "markers": "python_version >= '3.5'", - "version": "==2.4.2" - }, - "autopep8": { - "hashes": [ - "sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094" - ], - "version": "==1.5.4" - }, - "flake8": { - "hashes": [ - "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", - "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" - ], - "version": "==3.8.4" - }, - "isort": { - "hashes": [ - "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7", - "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58" - ], - "markers": "python_version >= '3.6' and python_version < '4.0'", - "version": "==5.6.4" - }, - "jedi": { - "hashes": [ - "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20", - "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.17.2" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", - "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", - "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", - "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", - "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", - "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", - "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", - "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", - "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", - "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", - "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", - "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", - "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", - "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", - "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", - "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", - "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", - "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", - "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", - "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", - "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.4.3" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "parso": { - "hashes": [ - "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea", - "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.7.1" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.13.1" - }, - "pycodestyle": { - "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" - ], - "version": "==2.6.0" - }, - "pydocstyle": { - "hashes": [ - "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325", - "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678" - ], - "version": "==5.1.1" - }, - "pyflakes": { - "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" - ], - "version": "==2.2.0" - }, - "pylint": { - "hashes": [ - "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210", - "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f" - ], - "version": "==2.6.0" - }, - "python-jsonrpc-server": { - "hashes": [ - "sha256:62c543e541f101ec5b57dc654efc212d2c2e3ea47ff6f54b2e7dcb36ecf20595", - "sha256:e5a908ff182e620aac07db5f57887eeb0afe33993008f57dc1b85b594cea250c" - ], - "version": "==0.4.0" - }, - "python-language-server": { - "extras": [ - "all" - ], - "hashes": [ - "sha256:0c672aec88fa73f47c36aa4d0b7717868b316a1398d3fb73554955675ce54bae", - "sha256:c85d718ef6860319ad59fd6f2acb1166e9349b782ee8e8908e08ecf241615f52" - ], - "index": "pypi", - "version": "==0.36.1" - }, - "rope": { - "hashes": [ - "sha256:786b5c38c530d4846aa68a42604f61b4e69a493390e3ca11b88df0fbfdc3ed04" - ], - "version": "==0.18.0" + "version": "==2.25.1" }, "six": { "hashes": [ @@ -250,60 +110,22 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, - "snowballstemmer": { + "urllib3": { "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", + "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" ], - "version": "==2.0.0" + "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.4" }, - "toml": { + "websocket-client": { "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663", + "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.2" - }, - "ujson": { - "hashes": [ - "sha256:078808c385036cba73cad96f498310c61e9b5ae5ac9ea01e7c3996ece544b556", - "sha256:0a2e1b211714eb1ec0772a013ec9967f8f95f21c84e8f46382e9f8a32ae781fe", - "sha256:0f412c3f59b1ab0f40018235224ca0cf29232d0201ff5085618565a8a9c810ed", - "sha256:26cf6241b36ff5ce4539ae687b6b02673109c5e3efc96148806a7873eaa229d3", - "sha256:2b2d9264ac76aeb11f590f7a1ccff0689ba1313adacbb6d38d3b15f21a392897", - "sha256:4f12b0b4e235b35d49f15227b0a827e614c52dda903c58a8f5523936c233dfc7", - "sha256:4fe8c6112b732cba5a722f7cbe22f18d405f6f44415794a5b46473a477635233", - "sha256:51480048373cf97a6b97fcd70c3586ca0a31f27e22ab680fb14c1f22bedbf743", - "sha256:568bb3e7f035006147af4ce3a9ced7d126c92e1a8607c7b2266007b1c1162c53", - "sha256:5fe1536465b1c86e32a47113abd3178001b7c2dcd61f95f336fe2febf4661e74", - "sha256:71703a269f074ff65b9d7746662e4b3e76a4af443e532218af1e8ce15d9b1e7b", - "sha256:7a1545ac2476db4cc1f0f236603ccbb50991fc1bba480cda1bc06348cc2a2bf0", - "sha256:a5200a68f1dcf3ce275e1cefbcfa3914b70c2b5e2f71c2e31556aa1f7244c845", - "sha256:a618af22407baeadb3f046f81e7a5ee5e9f8b0b716d2b564f92276a54d26a823", - "sha256:a79bca47eafb31c74b38e68623bc9b2bb930cb48fab1af31c8f2cb68cf473421", - "sha256:b87379a3f8046d6d111762d81f3384bf38ab24b1535c841fe867a4a097d84523", - "sha256:bd4c77aee3ffb920e2dbc21a9e0c7945a400557ce671cfd57dbd569f5ebc619d", - "sha256:c354c1617b0a4378b6279d0cd511b769500cf3fa7c42e8e004cbbbb6b4c2a875", - "sha256:c604024bd853b5df6be7d933e934da8dd139e6159564db7c55b92a9937678093", - "sha256:e7ab24942b2d57920d75b817b8eead293026db003247e26f99506bdad86c61b4", - "sha256:f8a60928737a9a47e692fcd661ef2b5d75ba22c7c930025bd95e338f2a6e15bc" - ], - "markers": "python_version >= '3.1'", - "version": "==4.0.1" - }, - "wrapt": { - "hashes": [ - "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" - ], - "version": "==1.12.1" - }, - "yapf": { - "hashes": [ - "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427", - "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9" - ], - "version": "==0.30.0" + "index": "pypi", + "version": "==0.58.0" } - } + }, + "develop": {} } diff --git a/chats/ChatProcess.py b/chats/ChatProcess.py index 5901f83..5e73f9a 100644 --- a/chats/ChatProcess.py +++ b/chats/ChatProcess.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from multiprocessing import Process, Pipe, Queue -from aenum import Enum, auto +from enum import Enum, auto from traceback import format_exception import time @@ -40,10 +40,6 @@ class ChatProcess(Process, ABC): def control_pipe(self): return self._caller_pipe - @property - def deamon(self): - return True - @property def state(self): pass diff --git a/chats/Twitch.py b/chats/Twitch.py new file mode 100644 index 0000000..3282b72 --- /dev/null +++ b/chats/Twitch.py @@ -0,0 +1,142 @@ +import websocket +from multiprocessing import Process, Pipe +from enum import Enum, auto +from traceback import format_exception +from miniirc import ircv3_message_parser + + +from . import AUTHOR_TYPES +from .ChatProcess import ChatProcess + +class CONTROL_MESSAGES(Enum): + RESTART = auto() + + +class STATES(Enum): + DISCONNECTED = auto() + READING = auto() + FAILURE = auto() + + +class NonBlockingWebsocket(Process): + def __init__(self): + super().__init__() + self._ws = websocket.WebSocket() + self._pipe, self._caller_pipe = Pipe() + + def send(self, data): + return self._ws.send(data) + + def poll(self, *args): + return self._caller_pipe.poll(*args) + + def recv(self, *args): + return self._caller_pipe.recv(*args) + + def connect(self, *args): + self._ws.connect(*args) + + def run(self): + try: + while self.is_alive(): + data = self._ws.recv() + self._pipe.send(data) + except Exception as e: + print(f'Failure in {self.__class__.__name__}: {e}') + print(''.join(format_exception(None, e, e.__traceback__))) + self._caller_pipe.close() + return -1 + + +class TwitchIRCProcess(ChatProcess): + WEBSOCKET_ADDRESS = 'wss://irc-ws.chat.twitch.tv:443' + + def __init__(self, username, token, *args): + super().__init__(*args) + self._state_machine = self.bind_to_states(STATES) + self._username = username + self._token = token + + self.state = STATES.DISCONNECTED + + @property + def keybinds(self): + return { + ord('r'): {'type': CONTROL_MESSAGES.RESTART} + } + + def loop(self, next_state): + return self._state_machine(self.state, next_state) + + def on_state_enter(self, new_state): + status_messages = { + STATES.READING: 'Connected to channel!', + STATES.FAILURE: 'Connection closed - see terminal', + } + + message = status_messages.get(new_state) + if message is not None: + self._message_queue.put({ + 'text': message, + 'author_name': 'Twitch IRC Bot', 'author_id': self.__class__.__name__, 'author_type': AUTHOR_TYPES.SYSTEM, + }) + + def on_disconnected(self, next_state): + self._ws = NonBlockingWebsocket() + self._ws.connect(TwitchIRCProcess.WEBSOCKET_ADDRESS) + self._ws.start() + self._ws.send(f'PASS {self._token}') + self._ws.send(f'NICK {self._username}') + response = self._ws.recv() + if any('Welcome, GLHF!' in msg for msg in response.splitlines()): + self._ws.send(f'JOIN #{self._username.lower()}') + self._ws.send('CAP REQ :twitch.tv/tags') + # Not currently using the data we get back from these, so leaving it to READING state to ignore + return STATES.READING + else: + print(response) + return STATES.FAILURE + + def on_reading(self, next_state): + if self._ws.poll(0.1): + messages = self._ws.recv() + for message in messages.splitlines(): + cmd, hostmask, tags, args = ircv3_message_parser(message) + if cmd == 'PRIVMSG': + normalized_message = TwitchIRCProcess.normalize_message(hostmask, tags, args) + self._message_queue.put(normalized_message) + elif cmd == 'PING': + self._ws.send(f"PONG {' '.join(args)}") + else: + # HACK: ignoring quite a lot of messages rn + pass + return 0 + + def on_failure(self, next_state): + self._ws.terminate() + return None + + @classmethod + def normalize_message(cls, hostmask, tags, args): + badges = [badge.split('/')[0] for badge in tags.get('badges', '').split(',')] + + # First arg is the room (which we dont care about, since it should only ever be #user) + # First character of the second arg is the data delimiter (:) + message = ' '.join(args[1:])[1:] + + if 'broadcaster' in badges: + author_type = AUTHOR_TYPES.OWNER + elif any(target_badge in badges for target_badge in ['admin', 'moderator', 'global_mod']): + author_type = AUTHOR_TYPES.MODERATOR + elif 'subscriber' in badges: + author_type = AUTHOR_TYPES.PATRON + else: + author_type = AUTHOR_TYPES.USER + + return { + 'text': message, + 'author_name': tags.get('display-name', hostmask[0]), + 'author_id': tags.get('id'), + 'author_type': author_type, + 'author_color': tags.get('color'), + } diff --git a/main.py b/main.py index 05c7002..259b0f4 100644 --- a/main.py +++ b/main.py @@ -5,11 +5,14 @@ import time from chats import YoutubeLive from chats.ChatProcess import LIFECYCLE_MESSAGES from chats.FakeChat import FakeChat +from chats.Twitch import TwitchIRCProcess + from ui.App import App from plugins.PogCount import PogCounter EVENT_POLL_FREQ = 0.1 GOOGLE_API_SECRETS_PATH = 'googleapi.secret.json' +TWITCH_SECRETS_PATH = 'twitch.secret.json' if __name__ == '__main__': chat_processes = [FakeChat(EVENT_POLL_FREQ)] @@ -18,7 +21,16 @@ if __name__ == '__main__': client_secrets = json.load(f)['installed'] chat_processes.append(YoutubeLive.YoutubeLiveProcess(client_secrets, EVENT_POLL_FREQ)) else: - print('No client secrets - disabling youtube chat client. Hit "f" to enable a testing client') + print('No client secrets - disabling youtube chat client') + if os.path.exists(TWITCH_SECRETS_PATH): + with open(TWITCH_SECRETS_PATH ,'r') as f: + secrets = json.load(f) + chat_processes.append(TwitchIRCProcess(secrets.get('username'), secrets.get('oauth'), EVENT_POLL_FREQ)) + else: + print('No twitch config - disabling twitch chat client') + + if len(chat_processes) == 1: + print('Hit "f" with the window focused to enable a testing client') plugins = [PogCounter('../live-status.txt', prefix='Pog count: ')] diff --git a/ui/MessageView.py b/ui/MessageView.py index 520f2c3..654a125 100644 --- a/ui/MessageView.py +++ b/ui/MessageView.py @@ -1,4 +1,5 @@ from hashlib import md5 +import re import pygame from pygame.locals import Rect @@ -8,6 +9,20 @@ from chats import AUTHOR_TYPES from . import BG_COLOR, XTERM_COLORS from .TextFragment import TextFragment +hex_color_regex = re.compile('^#[a-f0-9]{6}$') + +def closest_xterm(target_color): + r, g, b = (int(target_color[i:i+2], 16) for i in range(1, len(target_color), 2)) + + distances = [] + for xterm_color in XTERM_COLORS: + xr, xg, xb = xterm_color + distance = math.sqrt(abs(r - xr) ** 2 + abs(g - xg) ** 2 + abs(b - xb) ** 2) + if distance == 0: + return xterm_color + distances.append((distance, xterm_color)) + return min(distances)[1] + class MessageView: # TODO: Superchat and chat sponsor handling @@ -26,8 +41,12 @@ class MessageView: author_id = message['author_id'] author_type = message['author_type'] - author_hash = int(md5(author_id.encode('utf-8')).hexdigest(), 16) - author_color = (author_hash % 144) + 88 + author_color = message.get('author_color') + if author_color is not None and hex_color_regex.match(author_color): + author_color = closest_xterm(author_color) + else: + author_hash = int(md5(author_id.encode('utf-8')).hexdigest(), 16) + author_color = (author_hash % 144) + 88 tag_fg = XTERM_COLORS[15] tag_bg = BG_COLOR