diff --git a/.gitignore b/.gitignore index bfc80a1..961c009 100644 --- a/.gitignore +++ b/.gitignore @@ -1,113 +1,113 @@ -# Custom -*.json -*.pyo -*.pyc -*.DS_Store -*.pkl -*.png -*.bat - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -.static_storage/ -.media/ -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ +# Custom +*.json +*.pyo +*.pyc +*.DS_Store +*.pkl +*.png +*.bat + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/Pipfile b/Pipfile index 25d5275..ae1d739 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,8 @@ url = "https://pypi.python.org/simple" verify_ssl = true name = "pypi" +[requires] +python_version = "3.6" [packages] @@ -13,7 +15,15 @@ google-api-python-client = "*" pyrasite = "*" "discord.py" = {extras = ["voice"], git = "https://github.com/Rapptz/discord.py", ref = "rewrite"} aiohttp = "*" +"hurry.filesize" = "*" +gitpython = "*" +gmusicapi = "*" +python-telegram-bot = "*" +requests = "*" +requests-oauthlib = "*" +yarl = "*" +hurry = "*" +pynacl = "*" [dev-packages] - diff --git a/Pipfile.lock b/Pipfile.lock index 5da63a9..b492a8a 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "968a2d3c16bf72fd0a52baaa3bbdac69c5dc46dd953874af00479bbe854b678c" + "sha256": "c8426f63f07b00a7dcc5220e1210acfc6279bb1eccb40d9a884d34e203bf8f85" }, "host-environment-markers": { "implementation_name": "cpython", @@ -9,15 +9,17 @@ "os_name": "posix", "platform_machine": "x86_64", "platform_python_implementation": "CPython", - "platform_release": "17.2.0", + "platform_release": "17.4.0", "platform_system": "Darwin", - "platform_version": "Darwin Kernel Version 17.2.0: Fri Sep 29 18:27:05 PDT 2017; root:xnu-4570.20.62~3/RELEASE_X86_64", + "platform_version": "Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64", "python_full_version": "3.6.2", "python_version": "3.6", "sys_platform": "darwin" }, "pipfile-spec": 6, - "requires": {}, + "requires": { + "python_version": "3.6" + }, "sources": [ { "name": "pypi", @@ -29,30 +31,30 @@ "default": { "aiohttp": { "hashes": [ - "sha256:cb281a30655efe939550b7dfbe8c5d6e84951caf52510528a88ae7fe483109ae", - "sha256:7ff6173cc691a44845e10f452d52932f12b45c6d5db85f5702b17d15addda808", - "sha256:e5c2c16ae815d883d4072f2d4542effdeae1d9a464b56bd06186d9eeca1981fe", - "sha256:bd920a8b5da1e0ed89c1a688d1ed252ba71189ff747747d38803728a9fba17d4", - "sha256:34dc28c31786f43dcd5a25e19da1dd8a5289d338f4ed1a1a9e5d1431ad4b79e2", - "sha256:8b5087310db23b1976776beedab05c8aa8e490755e9f3473fc1f07aa0cbe3bb9", - "sha256:3654f4913a6c02e742051881f94b7b8a9dfe527318e8d7a7dd2f2d8474b0c082", - "sha256:9af1260050bb29db245d284c659e710161c45575f8fb365202551c5fd43ed1c1", - "sha256:0589a94051aa0573b28eb5d330a0d12cee891d2ed9fd9323c9ddbe322e88fc15", - "sha256:b3b047ecd8b899f99d669f5e22aeabcc77e78f3eb4ee985755ec49c53fcccfc2", - "sha256:b73d90381c915c8f873da65520af3e8ac8d26e054b9bc849faa802b25623172a", - "sha256:e698a369200b6d4e50a493cf1da4a25f5457f61afd5e7fe91b40f9859f632d1f", - "sha256:c58ddcb3ef4dde692e98738f6b3d06650d2b2722910e4d18184530ff8d092d95", - "sha256:0965fa5e798b7faacd6417eeb5c476aa6a7d730ba2793106e5bae610fdb043cb", - "sha256:3979ef9071bd3c909d33cc333bde356f7063555ad8a0383e53f18dc31131b7d8", - "sha256:85caf471c5216d615b01443293cc5ead7502053dedd804da32dd35de816a7436", - "sha256:63de1a47530b7bf770f1ad26f342ea94babb451ae04cd3119e87cbfea1c970e5", - "sha256:04a8c24a376bb547c0c9b6e721d6ced23062d262d24d5e63a3e383c8b49e14ef", - "sha256:9e70efab3bfea1493f3099031e3731a6519adad62e6d3f464b429a80a6e2ff6f", - "sha256:c7cb8d2be2f3351a28bee9deca1194e1402171c47b52f16708321ef4ce682333", - "sha256:ab9e7ecf375387ae3d17ac25492801860f2726f541d73cf1108fa157e1da75a3", - "sha256:42373fbdbe8f09233c17e74f53cee877bc7d5b495b4fc14c32a119255e85e736" + "sha256:2e8be4c46083ced9d9bc9ff4d77f31bfcd3e7486613f6138c5aa302d33ea54ed", + "sha256:4634dd3bbb68d0c7e5e4bca7571369d53c497b3300d9d678f939038e1b1231ee", + "sha256:25825c61688fc95e09d6be19e513e925cb4f08aae4d7a7c38a1fa75e0e4c22bd", + "sha256:9e6d6f0bca955923b515f8b5631c4c4f43aa152763852284cbefc89bd544069e", + "sha256:6eef1d7eff9e6fa1029f7a62504f88b2b0afce89ced5c95d3a4cf1c2faef1231", + "sha256:040eecbc37aa5bd007108388fab6c42b2a01b964c4feac26bdffc8fe8af6c110", + "sha256:53988a8cf76c3fb74a759e77b1c2f55ab36880d57c6e7d0d59ad28743a2535fe", + "sha256:d51673140330c660e68c182e14164ddba47810dca873bbd28662f31d7d8c0185", + "sha256:2fe26e836a1803c7414613c376fe29fc4ae0e5145e3813e1db1854cb05c91a3c", + "sha256:15ad4d76bddfd98bf9e48263c70f6603e96d823c5a5c0c842646e9871be72c64", + "sha256:7910089093296b5c8f683965044f553b0c5c9c2dbf310a219db76c6e793fea55", + "sha256:a19b96f77763ddf0249420438ebfc4d9a470daeb26f6614366d913ff520fa29b", + "sha256:b53bc7b44b1115af50bd18d9671972603e5a4934e98dd3f4d671104c070e331d", + "sha256:4b6fa00885ec778154244b010acecb862d277e6652b87fcd85c0f4735d26451c", + "sha256:7aee5c0750584946fde40da70f0b28fe769f85182f1171acef18a35fd8ecd221" ], - "version": "==2.3.2" + "version": "==3.0.1" + }, + "appdirs": { + "hashes": [ + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e", + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92" + ], + "version": "==1.4.3" }, "async-timeout": { "hashes": [ @@ -61,6 +63,13 @@ ], "version": "==2.0.0" }, + "attrs": { + "hashes": [ + "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", + "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" + ], + "version": "==17.4.0" + }, "beautifulsoup4": { "hashes": [ "sha256:7015e76bf32f1f574636c4288399a6de66ce08fb7b2457f628a8d70c0fbabb11", @@ -69,6 +78,45 @@ ], "version": "==4.6.0" }, + "certifi": { + "hashes": [ + "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", + "sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d" + ], + "version": "==2018.1.18" + }, + "cffi": { + "hashes": [ + "sha256:5d0d7023b72794ea847725680e2156d1d01bc698a9007fccce46d03c904fe093", + "sha256:86903c0afab4a3390170aca61f753f5adad8ffff947030719ee44dedc5b68403", + "sha256:7d35678a54da0d3f1bc30e3a58a232043753d57c691875b5a75e4e062793bc9a", + "sha256:824cac33906be5c8e976f0d950924d88ec058989ef9cd2f77f5cd53cec417635", + "sha256:6ca52651f6bd4b8647cb7dee15c82619de3e13490f8e0bc0620830a2245b51d1", + "sha256:a183959a4b1e01d6172aeed356e2523ec8682596075aa6cf0003fe08da959a49", + "sha256:9532c5bc0108bd0fe43c0eb3faa2ef98a2db60fc0d4019f106b88d46803dd663", + "sha256:96652215ef328262b5f1d5647632bd342ac6b31dfbc495b21f1ab27cb06d621d", + "sha256:6c99d19225e3135f6190a3bfce2a614cae8eaa5dcaf9e0705d4ccb79a3959a3f", + "sha256:12cbf4c04c1ad07124bfc9e928c01e282feac9ec7dd72a18042d4fc56456289a", + "sha256:69c37089ccf10692361c8d14dbf4138b00b46741ffe9628755054499f06ed548", + "sha256:b8d1454ef627098dc76ccfd6211a08065e6f84efe3754d8d112049fec3768e71", + "sha256:cd13f347235410c592f6e36395ee1c136a64b66534f10173bfa4df1dc88f47d0", + "sha256:0640f12f04f257c4467075a804a4920a5d07ef91e11c525fc65d715c08231c81", + "sha256:89a8d05b96bdeca8fdc89c5fa9469a357d30f6c066262e92c0c8d2e4d3c53cae", + "sha256:a67c430a9bde73ae85b0c885fcf41b556760e42ea74c16dc70431a349989b448", + "sha256:7a831170b621e98f45ed1d5758325be19619a593924127a0a47af9a72a117319", + "sha256:796d0379102e6da5215acfcd20e8e69cca9d97309215b4ce088fe175b1c2f586", + "sha256:0fe3b3d571543a4065059d1d3d6d39f4ca6da0f2207ad13547094522e32ead46", + "sha256:678135090c311780382b1dd3f828f715583ea8a69687ed053c047d3cec6625d6", + "sha256:f4992cd7b4c867f453d44c213ee29e8fd484cf81cfece4b6e836d0982b6fa1cf", + "sha256:6d191fb20138fe1948727b20e7b96582b7b7e676135eabf72d910e10bf7bfa65", + "sha256:ec208ca16e57904dd7f4c7568665f80b1f7eb7e3214be014560c28def219060d", + "sha256:b3653644d6411bf4bd64c1f2ca3cb1b093f98c68439ade5cef328609bbfabf8c", + "sha256:f4719d0bafc5f0a67b2ec432086d40f653840698d41fa6e9afa679403dea9d78", + "sha256:87f837459c3c78d75cb4f5aadf08a7104db15e8c7618a5c732e60f252279c7a6", + "sha256:df9083a992b17a28cd4251a3f5c879e0198bb26c9e808c4647e0a18739f1d11d" + ], + "version": "==1.11.4" + }, "chardet": { "hashes": [ "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", @@ -76,6 +124,13 @@ ], "version": "==3.0.4" }, + "decorator": { + "hashes": [ + "sha256:94d1d8905f5010d74bbbd86c30471255661a14187c45f8d7f3e5aa8540fdb2e5", + "sha256:7d46dd9f3ea1cf5f06ee0e4e1277ae618cf48dfb10ada7c8427cd46c42702a0e" + ], + "version": "==4.2.1" + }, "discord.py": { "extras": [ "voice" @@ -83,12 +138,36 @@ "git": "https://github.com/Rapptz/discord.py", "ref": "rewrite" }, + "docopt": { + "hashes": [ + "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" + ], + "version": "==0.6.2" + }, + "future": { + "hashes": [ + "sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb" + ], + "version": "==0.16.0" + }, + "gmusicapi": { + "hashes": [ + "sha256:fcee09c2f3cf274f514589eabf82f2281fb184526106432079e641a6bcb697f2" + ], + "version": "==11.0.0" + }, "google-api-python-client": { "hashes": [ - "sha256:441d638e5fff7d9f97587aa99387efd0ee13f577c9d4d50820dfda4bb80d0e64", - "sha256:bb1f27740f6596f8272a2e1033d93d68e27e8ed5d22d6ab957e3f1d3f8ce05f6" + "sha256:2cf9ab83fa62e06717363e8855fb027864caeb35a3197cadb7f0de38356881c4", + "sha256:95ce394028754ec537e5791e811511fdd5fabe6f1f8879407a8daed71ecb0b4c" ], - "version": "==1.6.4" + "version": "==1.6.5" + }, + "gpsoauth": { + "hashes": [ + "sha256:1c3f45824d45ac3d06b9d9a0c0eccafe1052505d31ac9a698aef8b00fb0dfc37" + ], + "version": "==0.4.1" }, "httplib2": { "hashes": [ @@ -96,6 +175,31 @@ ], "version": "==0.10.3" }, + "hurry": { + "hashes": [ + "sha256:d94d3bb94573c17b215257f3f77a9279f2dbb2e794c1862bdb180529edeb8d3f" + ], + "version": "==1.0" + }, + "hurry.filesize": { + "hashes": [ + "sha256:f5368329adbef86accd3bc9490522340bb79260455ae89b1a42c10f63801b9a6" + ], + "version": "==0.9" + }, + "idna": { + "hashes": [ + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" + ], + "version": "==2.6" + }, + "idna-ssl": { + "hashes": [ + "sha256:1227e44039bd31e02adaeafdbba61281596d623d222643fb021f87f2144ea147" + ], + "version": "==1.0.0" + }, "lxml": { "hashes": [ "sha256:41f59cbdab232f11680d5d4dec9f2e6782fd24d78e37ee833447702e34e675f4", @@ -129,32 +233,51 @@ ], "version": "==4.1.1" }, + "mechanicalsoup": { + "hashes": [ + "sha256:22423efd025c3eedb06f41d3ff1127174a59f40dc560e82dce143956976195bf" + ], + "version": "==0.10.0" + }, + "mock": { + "hashes": [ + "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", + "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" + ], + "version": "==2.0.0" + }, "multidict": { "hashes": [ - "sha256:d12dfcff45b5c0eb3d586289cbf928012e75f93f10f4b9d7af903acb07b3c226", - "sha256:f7deb65a184cbe757faaf4c6d2b8f203cb1a11dd44603a34d7befc343253d5bf", - "sha256:80ad69b8330135b52a6da7a1f0ab6a278025bb72ea08768966785c829f515a6f", - "sha256:3261b631cd6d079e6a20def3306b433794f800ea896dce4f1d5e833ad9bf6f26", - "sha256:80e2bd17ea98fe77867771e1ee03433a54fa492a8413966a6caa766bdd6d6e62", - "sha256:8eb59892040198741944eafca0c34be4da6b58be7fddf6e491a2d9e7e1767548", - "sha256:24ec0d645ca70981e1c84e97e48c874b47f4970a173c8c751be9c213a8658e40", - "sha256:167558cfb7c43077b3d8602142109f2b52fae0841a7ff97eb0a7443db199f303", - "sha256:80abc93d197e24fb7c5aa5a8be3eacbcbc7115a2ff64e46162a4c2b46cd2f75a", - "sha256:ff2b92a41df2e4c5cafaefb782469f3e6053ed00026d75a398521096192d0408", - "sha256:5cfb63f0ddfa86ce6e80eee64e4fe886f9dfdb6dbbd724997bccb66513b0e29f", - "sha256:50dac1151e34974b04419073ee9726f4180c6daf3454fd4af258824a19ad8c1d", - "sha256:ebf1ba2af62dbaa37cf3db346830bb43d40d05a5f1f1966888a620f9b795e7c7", - "sha256:3f513f3bf933d7cb6f5741f6676d4fac1e96aa634161c071974f9bb86a7bbac9", - "sha256:ef6dc6e2d51b6058aa62cdd44dbf02c250c19eee6ff0065babc0ac126b068d43", - "sha256:90dc3b8fefc58a865d64957f8f901d724250ba2a40e02f49d0df0103e96f5afa", - "sha256:70630854b820d73ae102440123df38c983d77cd4ae444f3930a6bb6bbda87b76", - "sha256:faf2b6447521d2075d03fb5e7c5467bac68f67df1c69e034ebca3afb6b3c619d", - "sha256:eb7d5d39463137726138fc14c8458131136b8f4b06ba65f1cecd7aa6abe91df1", - "sha256:fa04df1503fae7045883c57db47ba0c07de2ebe4a91c3e64d56f20d3d99e5dc8", - "sha256:bce16633f3ee88863e4c9bcd7e037a6133c56fd9e7e7c0776bbaeeddcf154ac4", - "sha256:f82e61c7408ed0dce1862100db55595481911f159d6ddec0b375d35b6449509b" + "sha256:0fd4d255adcbab3341d64a2fff5acce23409e57bb94e626485dea3db70ddc35e", + "sha256:93f1af99bbe75c854370460a60823d6726f9af2196818a64346000d02e074ed7", + "sha256:65546242d0c481c0daf0ef20c1be81c075fb763c5f4346f18f748b422fc40f32", + "sha256:0462372fc74e4c061335118a4a5992b9a618d6c584b028ef03cf3e9b88a960e2", + "sha256:63663541d395ffe4d51a3c021467d0a7b46c965b63fa1646cb46e2e2f1f36415", + "sha256:84a1cb5320f1494cd444ca3bd09ddba2e0af0cb210f9263bcf17357ab22671a1", + "sha256:241c11614f64535e213ea143efa8b7e598793256601fc795e77075bdfa54f5d6", + "sha256:ea8a18ea02bf84981ec93faded773a866554666f13955c92139127892c4bb45c", + "sha256:b46ec31bb7729eaa678a3bb1c999460902df1e295fcc093b9aa5f2c7e68d5803", + "sha256:608f7eef60e6558418d7da6551dd3d07ccc1290ecc85755d781bd8100322ea5b", + "sha256:068e91060e3e211441b1a31f5e65de88fc346490e1fae583c35a75a5295c8ef7", + "sha256:288e8f94fb6f586e7386c1f22c979ce3ec866ab23371fa8fef1dd526cd4dfde1", + "sha256:503ae54582601b0ff647731fee5efcdff5db1f4da0350febb31b628236a5f0b5", + "sha256:6d5f6f26f9025756035c473167b39c5a72e4e519a2286c9399d21f6682e4e5bc", + "sha256:e13265feabb1fa26f9cd49cbafd9b5de70ad768093ddb092af477c9823f44f0e", + "sha256:50de6f3786ba868ffb7d78d4bcacf0928321f9892366b2f4a0426bba644e3f25", + "sha256:16c78b10e897a512aa34ab1969982e42246e53077ae903c1b334926e1ea832d1", + "sha256:e04b5bf8581718cf84c1c60bda40221d926ceb06f942ebabfc3baf467a1e34be", + "sha256:d99819e9e15e1295a31a757360cab65bc96162870f90c29432564bd8e8999aca", + "sha256:cd172509bfc9144395204dd2c0eb305ae5e89f8ad1714ffd7d793607c53c3244", + "sha256:3508bea4974ee30fabcf7c8852fca7d9d54d496eaa068bee8311e0ac4df4ade3", + "sha256:fb4412490324705dcd2172baa8a3ea58ae23c5f982476805cad58ae929fe2a52" ], - "version": "==3.3.2" + "version": "==4.1.0" + }, + "mutagen": { + "hashes": [ + "sha256:b2a2c2ce87863af12ed7896f341419cd051a3c72c3c6733db9e83060dcadee5e" + ], + "version": "==1.40.0" }, "oauth2client": { "hashes": [ @@ -163,39 +286,140 @@ ], "version": "==4.1.2" }, + "oauthlib": { + "hashes": [ + "sha256:ce57b501e906ff4f614e71c36a3ab9eacbb96d35c24d1970d2539bbc3ec70ce1" + ], + "version": "==2.0.6" + }, + "pbr": { + "hashes": [ + "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac", + "sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1" + ], + "version": "==3.1.1" + }, + "proboscis": { + "hashes": [ + "sha256:b822b243a7c82030fce0de97bdc432345941306d2c24ef227ca561dd019cd238" + ], + "version": "==1.2.6.0" + }, + "protobuf": { + "hashes": [ + "sha256:11788df3e176f44e0375fe6361342d7258a457b346504ea259a21b77ffc18a90", + "sha256:50c24f0d00b7efb3a72ae638ddc118e713cfe8cef40527afe24f7ebcb878e46d", + "sha256:41661f9a442eba2f1967f15333ebe9ecc7e7c51bcbaa2972303ad33a4ca0168e", + "sha256:06ec363b74bceb7d018f2171e0892f03ab6816530e2b0f77d725a58264551e48", + "sha256:b20f861b55efd8206428c13e017cc8e2c34b40b2a714446eb202bbf0ff7597a6", + "sha256:c1f9c36004a7ae6f1ce4a23f06070f6b07f57495f251851aa15cc4da16d08378", + "sha256:4d2e665410b0a278d2eb2c0a529ca2366bb325eb2ae34e189a826b71fb1b28cd", + "sha256:95b78959572de7d7fafa3acb718ed71f482932ddddddbd29ba8319c10639d863" + ], + "version": "==3.5.1" + }, + "py": { + "hashes": [ + "sha256:2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a", + "sha256:0f2d585d22050e90c7d293b6451c83db097df77871974d90efd5a30dc12fcde3" + ], + "version": "==1.4.34" + }, "pyasn1": { "hashes": [ - "sha256:5eac8d0c1c1282842c9145ae134990a1baf7616eb4cee9129213fad76eba4f54", - "sha256:960a603e677897ea29b9a1327b789b7b8a9e187a89e05fed9c3152b3b7c22954", - "sha256:4bb562b9f1fc4526028b2aa4dc027dce08f3ade0780abc593a02ce6dbade6e6c", - "sha256:8fa8884056bd5b2c92ca1685e6344121b6b43718b44f0c6eb223958003c9d14a", - "sha256:16e896433f84575f0636cd9aa8b24659689268a62e00f17235e1fc23c6b00b25", - "sha256:bb6f5d5507621e0298794bc3e75b8f963e886cee388a378ab58e5c35ac024276", - "sha256:60bf78784b117979f5517c38308f6965fff6c803660fbb16368d94433953b62a", - "sha256:4a677c6c9e484977ed6c6a93714ff06ac374220408afeaeef4ef2af652af0f3d", - "sha256:b16fb6097d00bbafc114861b16ea41cfe63e32fed1bdc7cd5905a3e02a992fa3", - "sha256:8212bde51ec192e30654efe10e636082738ed728e316049f3685d66b8c92941c", - "sha256:8d4f0971682203bdfc93740ee7d3fcba0a7f55629451dbe2d32af2335c55b2be", - "sha256:187f2a66d617683f8e82d5c00033b7c8a0287e1da88a9d577aebec321cad4965" + "sha256:f81c96761fca60d64b1c9b79ec2e40cf9495a745cf570613079ef324aeb9672b", + "sha256:7d626683e3d792cccc608da02498aff37ab4f3dafd8905d6bf755d11f9b26b43", + "sha256:e85895087905c65b5b594eb91f7522664c85545b147d5f4d4e7b1b07da8dcbdc", + "sha256:5a0db897b311d265cde49615cf783f1c78613138605cdd0f907ecfa5b2aba3ee", + "sha256:d5cd6ed995dba16fad0c521cfe31cd2d68400b53fcc2bce93326829be73ab6d1", + "sha256:a7efe807c4b83a859e2735c692b92ed7b567cfddc4163763412920041d876c2b", + "sha256:b5a9ca48055b9a20f6d1b3d68e38692e5431c86a0f99ea602e61294e891fee5b", + "sha256:c07d6e587b2f928366b1f67c09bda026a3e6fcc99e80a744dc67f8fca3895626", + "sha256:d84c2aea3cf43780e9e6a19f4e4dddee9f6976519020e64e47c57e5c7a8c3dd2", + "sha256:758cb50abddc03e4563fd9e7f03db56e3e87b58c0bd01247360326e5c0c7ffa5", + "sha256:0d7f6e959fe53f3960a23d73f35e1fce61348b30915b6664309ca756de7c1f89", + "sha256:d258b0a71994f7770599835249cece1caef3c70def868c4915e6e5ca49b67d15" ], - "version": "==0.3.7" + "version": "==0.4.2" }, "pyasn1-modules": { "hashes": [ - "sha256:ea8b89f79724c3cf4ca88bc7327964f0750e5219618805dcc85ca0fdae9e5b34", - "sha256:16b086729c7af47a67c9e64cea2f763975b602155319b6c63f1f20e5f0179be7", - "sha256:7fc70766b8ef5a62eb43767cd7a1c1b3a6aa5095b263a6f2a734987fd90a35d6", - "sha256:92caf877c06c033786f0149dd37ea1abfd2c398a007bb40ae6b1f2c96804c1b2", - "sha256:018225e6718cfff7e515bd23efe8c0956e5226e3a416ba829e695c607e8ac58f", - "sha256:773641c73f6eaac19b5ed7c3e6c3e733dc43b494282ef067325ea6583763f531", - "sha256:3350c74c22eb821acfd22ecf4bbb9abfc1de2bd5befb5befd5b1b7ede2d92ace", - "sha256:6c457b5037e6a145a43bf3b5b1db622d20a08d4d1ad9a9bdc22dbef7229b250c", - "sha256:d37774d5de3887b1cdce7415209e92da49fcd13b99db1c44c179a93a5f87c8b2", - "sha256:6d8ad92e399b3140259b2c5249c49e67806a3eee332ed3734da807733925f04e", - "sha256:b437be576bdf440fc0e9307a4334303d117a577f2d809ecb9abd715539cb0109", - "sha256:1d303eed5aa54cafeca209d16b8c7ea2c6064735fb61f1bee2e0ed63a0816988" + "sha256:b1f395cae2d669e0830cb023aa86f9f283b7a9aa32317d7f80d8e78aa2745812", + "sha256:854700bbdd01394e2ada9c1bfbd0ed9f5d0c551350dbbd023e88b11d2771ae06", + "sha256:598a6004ec26a8ab40a39ea955068cf2a3949ad9c0030da970f2e1ca4c9f1cc9", + "sha256:f53fe5bcebdf318f51399b250fe8325ef3a26d927f012cc0c8e0f9e9af7f9deb", + "sha256:47fb6757ab78fe966e7c58b2030b546854f78416d653163f0ce9290cf2278e8b", + "sha256:041e9fbafac548d095f5b6c3b328b80792f006196e15a232b731a83c93d59493", + "sha256:0cea139045c38f84abaa803bcb4b5e8775ea12a42af10019d942f227acc426c3", + "sha256:0cdca76a68dcb701fff58c397de0ef9922b472b1cb3ea9695ca19d03f1869787", + "sha256:72fd8b0c11191da088147c6e4678ec53e573923ecf60b57eeac9e97433e09fc2", + "sha256:c6747146e95d2b14cc2a8399b2b0bde3f93778f8f9ec704690d2b589c376c137", + "sha256:0f2e50d20bc670be170966638fa0ae603f0bc9ed6ebe8e97a6d1d4cef30cc889", + "sha256:af00ea8f2022b6287dc375b2c70f31ab5af83989fc6fe9eacd4976ce26cd7ccc" ], - "version": "==0.1.5" + "version": "==0.2.1" + }, + "pycparser": { + "hashes": [ + "sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226" + ], + "version": "==2.18" + }, + "pycryptodomex": { + "hashes": [ + "sha256:a2ac2eaafd7dc52920c902dae225badfbd9323f0d1166db0a1614fa55e2dc5b6", + "sha256:7464c5e2751e9fa9e3b66f3d7054edf4d837498a151d9a3ba156cb6532344455", + "sha256:3588078356c6d6f1a5765fe1b254882e1ed7ce5cd8a8a00fdc4cef507e46868d", + "sha256:88f6dfb6b3efd084df29d4ed312dde085f4a7cc9efada59998f75a8826e255a2", + "sha256:565fa996de2ab58cae9a7b7241c7fd84e6bd705a05ff89e2ea5c4d3e6c755c58", + "sha256:b95adb32f32bc85f1c6c1d4d344a73a5fddf7c69b3a6551e499963022200fc5e", + "sha256:14bdfc08d38a17fa54027b51c122ce175b84f979781a2e11e86015dd0dfa4157", + "sha256:0307cfa4c8aff4dd943933704fc4eae2119f42439d1fe456a594bee44a7d860b", + "sha256:2b64915a430fbf992c75751c13cabecf27ba8dd43fdcc8bc0551302d87ef4c91", + "sha256:acc287965dd1f08bb5b2e662289111e24fc5e7754c79571a20803fd5ce307c47", + "sha256:25d038c3d18b59e07acb0be516396d0c8ed3b098761b0f597c7f03c7516e7699", + "sha256:3ebc14df23b8bcc2734aff6255b7047b8c2f28779923f0f36f17c2d2d9fc32dc", + "sha256:da75c00a761e456fb99be7e28b60568163c6d3d631f305ffc9f1365c8991c669", + "sha256:1a1a4b604dd9dab1a55d43972e853be0c2fd0c50f3589f8c0e18febdb361235c", + "sha256:90c9a70005d5c02886ab98ef4e2d577347e3c41399aa0c35a4eac4ee27b1d718", + "sha256:51b789c2ccdc30ebf0b1dfcb2095a5e10ebaf68e2cd89bc051b1887a8e8e14d6", + "sha256:e27f557e8aab1dbfdd1a51f0053437baf5e13fc6f04c07fbe3a0da7a5c8c51f2", + "sha256:626463deb7f760507f98504413fb6035dcff7c8f0b36c6e216605680caa337f0", + "sha256:1088099e88ba7238cebd3c44b38edbe7130b229a3d7d7b4c9b0ed06533dd13e6", + "sha256:bf2a5b6eee8ca198eea13bcbd2df62721ed9b5f5c8660ac56be7249ff349f582", + "sha256:46a3c64f377f14fe9ccaa7b3307de76e312edd0cf7543edb6f159e519081254c", + "sha256:bc30c42399b268788407a1ac692ad4011e774809034e7a0bc9559b8feb727ea1", + "sha256:02dda17f54cbb37dcad6bd52c21f1208be1e435c7c8b48922366dad989a0c597" + ], + "version": "==3.4.12" + }, + "pynacl": { + "hashes": [ + "sha256:0bfa0d94d2be6874e40f896e0a67e290749151e7de767c5aefbad1121cad7512", + "sha256:1d33e775fab3f383167afb20b9927aaf4961b953d76eeb271a5703a6d756b65b", + "sha256:eb2acabbd487a46b38540a819ef67e477a674481f84a82a7ba2234b9ba46f752", + "sha256:14339dc233e7a9dda80a3800e64e7ff89d0878ba23360eea24f1af1b13772cac", + "sha256:cf6877124ae6a0698404e169b3ba534542cfbc43f939d46b927d956daf0a373a", + "sha256:eeee629828d0eb4f6d98ac41e9a3a6461d114d1d0aa111a8931c049359298da0", + "sha256:d0eb5b2795b7ee2cbcfcadacbe95a13afbda048a262bd369da9904fecb568975", + "sha256:11aa4e141b2456ce5cecc19c130e970793fa3a2c2e6fbb8ad65b28f35aa9e6b6", + "sha256:8ac1167195b32a8755de06efd5b2d2fe76fc864517dab66aaf65662cc59e1988", + "sha256:d795f506bcc9463efb5ebb0f65ed77921dcc9e0a50499dedd89f208445de9ecb", + "sha256:be71cd5fce04061e1f3d39597f93619c80cdd3558a6c9ba99a546f144a8d8101", + "sha256:2a42b2399d0428619e58dac7734838102d35f6dcdee149e0088823629bf99fbb", + "sha256:73a5a96fb5fbf2215beee2353a128d382dbca83f5341f0d3c750877a236569ef", + "sha256:d8aaf7e5d6b0e0ef7d6dbf7abeb75085713d0100b4eb1a4e4e857de76d77ac45", + "sha256:2dce05ac8b3c37b9e2f65eab56c544885607394753e9613fd159d5e2045c2d98", + "sha256:f5ce9e26d25eb0b2d96f3ef0ad70e1d3ae89b5d60255c462252a3e456a48c053", + "sha256:6453b0dae593163ffc6db6f9c9c1597d35c650598e2c39c0590d1757207a1ac2", + "sha256:fabf73d5d0286f9e078774f3435601d2735c94ce9e514ac4fb945701edead7e4", + "sha256:13bdc1fe084ff9ac7653ae5a924cae03bf4bb07c6667c9eb5b6eb3c570220776", + "sha256:8f505f42f659012794414fa57c498404e64db78f1d98dfd40e318c569f3c783b", + "sha256:04e30e5bdeeb2d5b34107f28cd2f5bbfdc6c616f3be88fc6f53582ff1669eeca", + "sha256:8abb4ef79161a5f58848b30ab6fb98d8c466da21fdd65558ce1d7afc02c70b5f", + "sha256:e0d38fa0a75f65f556fb912f2c6790d1fa29b7dd27a1d9cc5591b281321eaaa9" + ], + "version": "==1.2.1" }, "pyrasite": { "hashes": [ @@ -203,6 +427,34 @@ ], "version": "==2.0" }, + "python-dateutil": { + "hashes": [ + "sha256:95511bae634d69bc7329ba55e646499a842bc4ec342ad54a8cdb65645a0aad3c", + "sha256:891c38b2a02f5bb1be3e4793866c8df49c7d19baabf9c1bad62547e0b4866aca" + ], + "version": "==2.6.1" + }, + "python-telegram-bot": { + "hashes": [ + "sha256:ca5e1d257702d194ad5a5a0e6ccffb31203f1f783bf1c5da60c128f083032c44", + "sha256:f5c3233bea7c7adf165e31225bbe9f28717e9f1f5070ebe99a4757c31c27ab28" + ], + "version": "==9.0.0" + }, + "requests": { + "hashes": [ + "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", + "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + ], + "version": "==2.18.4" + }, + "requests-oauthlib": { + "hashes": [ + "sha256:50a8ae2ce8273e384895972b56193c7409601a66d4975774c60c2aed869639ca", + "sha256:883ac416757eada6d3d07054ec7092ac21c7f35cb1d2cf82faf205637081f468" + ], + "version": "==0.8.0" + }, "rsa": { "hashes": [ "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd", @@ -225,23 +477,37 @@ ], "version": "==3.0.0" }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" + }, + "validictory": { + "hashes": [ + "sha256:eb7ec9d811f3cf062fb943ce369a9b34a9f291037217ecf5a40a5f2421d29c0a", + "sha256:3a87b84658592f75f37d6bab77ac223774c9989dc7349c8aad19a424770835ba" + ], + "version": "==1.1.2" + }, "yarl": { "hashes": [ - "sha256:8b6a495e754269ba3817957dfe713532b7d5485cbcf3fce3e8624832f1156e3c", - "sha256:14ff708e9813b961d5c0b9a30ae8323591deffca0ef4fa93fbaba94ec44f3560", - "sha256:8756000c3ccf91acf9557401892864d2c869c0ae7c62e5d4fc3dd20ed5e8a3a7", - "sha256:728b9b26d5927fd35b102c62c476bace5c5e0fb1275e7d4bb169d50720b1d687", - "sha256:3a68281e8031b9eae9a1c186eff725ed31a66d4dbfd4dc7c0ceb1a15d4a0b09f", - "sha256:dfacdfd8b350a10d66d2af0ef59fa56564cc03bcdcdd93a1aa6e65e9306f5882", - "sha256:42d329be7cafa9210c17349fd2db90b967b76fc6fd411f9abaf450a9e7f7e2e4", - "sha256:8d4a3ad001747e1225af47d46da935e507e7fe5e315bb4dbd235d5460b722488", - "sha256:4dcf2e89e65745c4ae597a724a612dbdb2f38e92c0a5f145c9ec1701d4ddf384", - "sha256:44e4ce0f59abbf7745ee450c3c4aa261a4deef83c7e1a3624dcc9db094d630e5", - "sha256:626113e8649ff2e205ea49432c4c94f730326852905ba411fb82006e443f3231", - "sha256:bfb4256496da1b1475a72e669464db539eca7b0fc3bddd231a64cf8afadb7efb", - "sha256:25fe681a982f2cec567df8abac7cbd2ac27016e4aec89193945cab0643bfdb42" + "sha256:e938046ce94bcb21e4641712eee3f3c211fc1e5a4601fd89c031e638022097a3", + "sha256:42a7fc3d31c4c5b7fb7102380e1518e5af88e3bec7e89acf1213973e880bff6f", + "sha256:d861e697e4f1dbd614c29c7594f0dd3dbc48144f1e1f9ed8380604c7dde6cb19", + "sha256:e2dbf932bc9519281393973187075f1c2261a9e58032265ae47b2e79ece30ad8", + "sha256:f00d067a08836c03646ace00baaf59697e978d2f1958b090c3a098c59221e175", + "sha256:0a5d90f6d6ed6204e58b9d10882ef1a1193010e76e744ba39e848e8ddf96ab7a", + "sha256:1f96aa307067766c3edfc075739e5da8c790178d98f95e033fc8e22b8f2bc920", + "sha256:c9adaa61e1d9487c69f40561a89fc68640dba05e014ed29315b586e106a95671", + "sha256:9d25d6d6865979a5216b5bc2b9bd913dbaff636f74411baaa3e79c708ebeafbb", + "sha256:c4c7682551c0b10b5c6fe0e359745759bd6298ab5839d9433ad0ed820def0d40", + "sha256:2aec0ae0c50997e1a7af3ada4983e11caf212eee73eff6876ce26cf3a5072644", + "sha256:f96d8bd39ee85cb9e842e3c7ad8c7cfb875bebfe07d63e87ca3e3984cb2f1546", + "sha256:6af895b45bd49254cc309ac0fe6e1595636a024953d710e01114257736184698" ], - "version": "==0.13.0" + "version": "==1.1.0" } }, "develop": {} diff --git a/README.md b/README.md index 209e0d0..6256cb2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Modufur -Discord booru bot with a side of management and tasking. - -Credits: -Rapptz/discord.py +# Modufur +Discord booru bot with a side of management and tasking. + +Credits: +Rapptz/discord.py diff --git a/src/cogs/management.py b/src/cogs/management.py index 7d6b2f2..ec62cce 100644 --- a/src/cogs/management.py +++ b/src/cogs/management.py @@ -1,235 +1,235 @@ -import asyncio -import traceback as tb -from contextlib import suppress -from datetime import datetime as dt - -import discord as d -from discord import errors as err -from discord.ext import commands as cmds -from discord.ext.commands import errors as errext - -from misc import exceptions as exc -from misc import checks -from utils import utils as u - - -class Administration: - - def __init__(self, bot): - self.bot = bot - self.RATE_LIMIT = u.RATE_LIMIT - self.queue = asyncio.Queue() - self.deleting = False - - if u.tasks['auto_del']: - for channel in u.tasks['auto_del']: - temp = self.bot.get_channel(channel) - self.bot.loop.create_task(self.queue_for_deletion(temp)) - print('STARTED : auto-deleting in #{}'.format(temp.name)) - self.deleting = True - self.bot.loop.create_task(self.delete()) - - @cmds.group(aliases=['pru', 'purge', 'pur', 'clear', 'cl'], hidden=True) - @cmds.is_owner() - async def prune(self, ctx): - pass - - @prune.group(name='user', aliases=['u', 'member', 'm']) - async def _prune_user(self, ctx): - pass - - @_prune_user.command(name='channel', aliases=['channels', 'chans', 'chan', 'ch', 'c']) - async def _prune_user_channel(self, ctx, user: d.User, *channels: d.TextChannel): - def confirm(r, u): - if u is ctx.author: - if r.emoji == '\N{OCTAGONAL SIGN}': - raise exc.Abort - if r.emoji == '\N{THUMBS UP SIGN}': - return True - return False - - if not channels: - channels = [ctx.channel] - - try: - pruning = await ctx.send(f'\N{HOURGLASS} **Pruning** {user.mention}**\'s messages from** {"**,** ".join([channel.mention for channel in channels])} **might take some time.** Proceed, {ctx.author.mention}?') - await pruning.add_reaction('\N{THUMBS UP SIGN}') - await pruning.add_reaction('\N{OCTAGONAL SIGN}') - await asyncio.sleep(1) - - await self.bot.wait_for('reaction_add', check=confirm, timeout=10 * 60) - - deleting = await ctx.send(f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages...**') - await asyncio.sleep(1) - - c = 0 - for channel in channels: - await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}') - - deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None) - c += len(deleted) - - await asyncio.sleep(1) - - for channel in channels: - missed = 0 - async for message in channel.history(before=pruning, limit=None): - if message.author.id == user.id: - missed += 1 - - if missed > 0: - await ctx.send(f'\N{DOUBLE EXCLAMATION MARK} `{missed}` **messages were not deleted in** {channel.mention}') - - await ctx.send(f'\N{WHITE HEAVY CHECK MARK} **Finished deleting** `{c}` **of** {user.mention}**\'s messages**') - - except exc.Abort: - await ctx.send('**Deletion aborted**', delete_after=7) - await ctx.message.add_reaction('\N{CROSS MARK}') - except TimeoutError: - await ctx.send('**Deletion timed out**', delete_after=7) - await ctx.message.add_reaction('\N{CROSS MARK}') - - @_prune_user.command(name='all', aliases=['a'], brief='Prune a user\'s messages from the guild', description='about flag centers on message 50 of 101 messages\n\npfg \{user id\} [before|after|about] [\{message id\}]\n\nExample:\npfg \{user id\} before \{message id\}', hidden=True) - @cmds.is_owner() - async def _prune_user_all(self, ctx, user: d.User): - def confirm(r, u): - if u is ctx.author: - if r.emoji == '\N{OCTAGONAL SIGN}': - raise exc.Abort - if r.emoji == '\N{THUMBS UP SIGN}': - return True - return False - - try: - pruning = await ctx.send(f'\N{HOURGLASS} **Pruning** {user.mention}**\'s messages might take some time.** Proceed, {ctx.author.mention}?') - await pruning.add_reaction('\N{THUMBS UP SIGN}') - await pruning.add_reaction('\N{OCTAGONAL SIGN}') - await asyncio.sleep(1) - - await self.bot.wait_for('reaction_add', check=confirm, timeout=10 * 60) - - deleting = await ctx.send(f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages...**') - await asyncio.sleep(1) - - c = 0 - for channel in ctx.guild.text_channels: - await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}') - - deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None) - c += len(deleted) - - await asyncio.sleep(1) - - for channel in ctx.guild.text_channels: - missed = 0 - async for message in channel.history(before=pruning, limit=None): - if message.author.id == user.id: - missed += 1 - - if missed > 0: - await ctx.send(f'\N{DOUBLE EXCLAMATION MARK} `{missed}` **messages were not deleted in** {channel.mention}') - - await ctx.send(f'\N{WHITE HEAVY CHECK MARK} **Finished deleting** `{c}` **of** {user.mention}**\'s messages**') - - except exc.Abort: - await ctx.send('**Deletion aborted**', delete_after=7) - await ctx.message.add_reaction('\N{CROSS MARK}') - except TimeoutError: - await ctx.send('**Deletion timed out**', delete_after=7) - await ctx.message.add_reaction('\N{CROSS MARK}') - - @cmds.group(aliases=['task', 'tsk']) - async def tasks(self): - pass - - async def delete(self): - while self.deleting: - message = await self.queue.get() - await asyncio.sleep(self.RATE_LIMIT) - with suppress(err.NotFound): - if not message.pinned: - await message.delete() - - print('STOPPED : deleting') - - async def queue_for_deletion(self, channel): - def check(msg): - if 'stop d' in msg.content.lower() and msg.channel is channel and msg.author.guild_permissions.administrator: - raise exc.Abort - elif msg.channel is channel and not msg.pinned: - return True - return False - - try: - async for message in channel.history(limit=None): - if 'stop d' in message.content.lower() and message.author.guild_permissions.administrator: - raise exc.Abort - if not message.pinned: - await self.queue.put(message) - - while not self.bot.is_closed(): - message = await self.bot.wait_for('message', check=check) - await self.queue.put(message) - - except exc.Abort: - u.tasks['auto_del'].remove(channel.id) - u.dump(u.tasks, 'cogs/tasks.pkl') - if not u.tasks['auto_del']: - self.deleting = False - print('STOPPED : deleting #{}'.format(channel.name)) - await channel.send('**Stopped queueing messages for deletion in** {}'.format(channel.mention), delete_after=5) - - @cmds.command(name='autodelete', aliases=['autodel']) - @cmds.has_permissions(administrator=True) - async def auto_delete(self, ctx): - try: - if ctx.channel.id not in u.tasks['auto_del']: - u.tasks['auto_del'].append(ctx.channel.id) - u.dump(u.tasks, 'cogs/tasks.pkl') - self.bot.loop.create_task(self.queue_for_deletion(ctx.channel)) - if not self.deleting: - self.bot.loop.create_task(self.delete()) - self.deleting = True - print('STARTED : auto-deleting in #{}'.format(ctx.channel.name)) - await ctx.send('**Auto-deleting all messages in {}**'.format(ctx.channel.mention), delete_after=5) - else: - raise exc.Exists - - except exc.Exists: - await ctx.send('**Already auto-deleting in {}.** Type `stop d(eleting)` to stop.'.format(ctx.channel.mention), delete_after=7) - await ctx.message.add_reaction('\N{CROSS MARK}') - - @cmds.group(aliases=['setting', 'set', 's']) - @cmds.has_permissions(administrator=True) - async def settings(self, ctx): - pass - - @settings.command(name='deletecommands', aliases=['delcmds', 'delcmd']) - async def _settings_deletecommands(self, ctx): - if ctx.guild.id not in u.settings['del_ctx']: - u.settings['del_ctx'].append(ctx.guild.id) - else: - u.settings['del_ctx'].remove(ctx.guild.id) - u.dump(u.settings, 'settings.pkl') - - await ctx.send('**Delete command invocations:** `{}`'.format(ctx.guild.id in u.settings['del_ctx'])) - - @settings.command(name='prefix', aliases=['pre', 'p']) - async def _settings_prefix(self, ctx, *prefixes): - if prefixes: - u.settings['prefixes'][ctx.guild.id] = prefixes - else: - with suppress(KeyError): - del u.settings['prefixes'][ctx.guild.id] - - await ctx.send(f'**Prefix set to:** `{"` or `".join(prefixes if ctx.guild.id in u.settings["prefixes"] else u.config["prefix"])}`') - - @settings.command(name='deleteresponses', aliases=['delresps', 'delresp']) - async def _settings_deleteresponses(self, ctx): - if ctx.guild.id not in u.settings['del_resp']: - u.settings['del_resp'].append(ctx.guild.id) - else: - u.settings['del_resp'].remove(ctx.guild.id) - u.dump(u.settings, 'settings.pkl') - - await ctx.send(f'**Delete command responses:** `{ctx.guild.id in u.settings["del_resp"]}`') +import asyncio +import traceback as tb +from contextlib import suppress +from datetime import datetime as dt + +import discord as d +from discord import errors as err +from discord.ext import commands as cmds +from discord.ext.commands import errors as errext + +from misc import exceptions as exc +from misc import checks +from utils import utils as u + + +class Administration: + + def __init__(self, bot): + self.bot = bot + self.RATE_LIMIT = u.RATE_LIMIT + self.queue = asyncio.Queue() + self.deleting = False + + if u.tasks['auto_del']: + for channel in u.tasks['auto_del']: + temp = self.bot.get_channel(channel) + self.bot.loop.create_task(self.queue_for_deletion(temp)) + print('STARTED : auto-deleting in #{}'.format(temp.name)) + self.deleting = True + self.bot.loop.create_task(self.delete()) + + @cmds.group(aliases=['pru', 'purge', 'pur', 'clear', 'cl'], hidden=True) + @cmds.is_owner() + async def prune(self, ctx): + pass + + @prune.group(name='user', aliases=['u', 'member', 'm']) + async def _prune_user(self, ctx): + pass + + @_prune_user.command(name='channel', aliases=['channels', 'chans', 'chan', 'ch', 'c']) + async def _prune_user_channel(self, ctx, user: d.User, *channels: d.TextChannel): + def confirm(r, u): + if u is ctx.author: + if r.emoji == '\N{OCTAGONAL SIGN}': + raise exc.Abort + if r.emoji == '\N{THUMBS UP SIGN}': + return True + return False + + if not channels: + channels = [ctx.channel] + + try: + pruning = await ctx.send(f'\N{HOURGLASS} **Pruning** {user.mention}**\'s messages from** {"**,** ".join([channel.mention for channel in channels])} **might take some time.** Proceed, {ctx.author.mention}?') + await pruning.add_reaction('\N{THUMBS UP SIGN}') + await pruning.add_reaction('\N{OCTAGONAL SIGN}') + await asyncio.sleep(1) + + await self.bot.wait_for('reaction_add', check=confirm, timeout=10 * 60) + + deleting = await ctx.send(f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages...**') + await asyncio.sleep(1) + + c = 0 + for channel in channels: + await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}') + + deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None) + c += len(deleted) + + await asyncio.sleep(1) + + for channel in channels: + missed = 0 + async for message in channel.history(before=pruning, limit=None): + if message.author.id == user.id: + missed += 1 + + if missed > 0: + await ctx.send(f'\N{DOUBLE EXCLAMATION MARK} `{missed}` **messages were not deleted in** {channel.mention}') + + await ctx.send(f'\N{WHITE HEAVY CHECK MARK} **Finished deleting** `{c}` **of** {user.mention}**\'s messages**') + + except exc.Abort: + await ctx.send('**Deletion aborted**', delete_after=7) + await ctx.message.add_reaction('\N{CROSS MARK}') + except TimeoutError: + await ctx.send('**Deletion timed out**', delete_after=7) + await ctx.message.add_reaction('\N{CROSS MARK}') + + @_prune_user.command(name='all', aliases=['a'], brief='Prune a user\'s messages from the guild', description='about flag centers on message 50 of 101 messages\n\npfg \{user id\} [before|after|about] [\{message id\}]\n\nExample:\npfg \{user id\} before \{message id\}', hidden=True) + @cmds.is_owner() + async def _prune_user_all(self, ctx, user: d.User): + def confirm(r, u): + if u is ctx.author: + if r.emoji == '\N{OCTAGONAL SIGN}': + raise exc.Abort + if r.emoji == '\N{THUMBS UP SIGN}': + return True + return False + + try: + pruning = await ctx.send(f'\N{HOURGLASS} **Pruning** {user.mention}**\'s messages might take some time.** Proceed, {ctx.author.mention}?') + await pruning.add_reaction('\N{THUMBS UP SIGN}') + await pruning.add_reaction('\N{OCTAGONAL SIGN}') + await asyncio.sleep(1) + + await self.bot.wait_for('reaction_add', check=confirm, timeout=10 * 60) + + deleting = await ctx.send(f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages...**') + await asyncio.sleep(1) + + c = 0 + for channel in ctx.guild.text_channels: + await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}') + + deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None) + c += len(deleted) + + await asyncio.sleep(1) + + for channel in ctx.guild.text_channels: + missed = 0 + async for message in channel.history(before=pruning, limit=None): + if message.author.id == user.id: + missed += 1 + + if missed > 0: + await ctx.send(f'\N{DOUBLE EXCLAMATION MARK} `{missed}` **messages were not deleted in** {channel.mention}') + + await ctx.send(f'\N{WHITE HEAVY CHECK MARK} **Finished deleting** `{c}` **of** {user.mention}**\'s messages**') + + except exc.Abort: + await ctx.send('**Deletion aborted**', delete_after=7) + await ctx.message.add_reaction('\N{CROSS MARK}') + except TimeoutError: + await ctx.send('**Deletion timed out**', delete_after=7) + await ctx.message.add_reaction('\N{CROSS MARK}') + + @cmds.group(aliases=['task', 'tsk']) + async def tasks(self): + pass + + async def delete(self): + while self.deleting: + message = await self.queue.get() + await asyncio.sleep(self.RATE_LIMIT) + with suppress(err.NotFound): + if not message.pinned: + await message.delete() + + print('STOPPED : deleting') + + async def queue_for_deletion(self, channel): + def check(msg): + if 'stop d' in msg.content.lower() and msg.channel is channel and msg.author.guild_permissions.administrator: + raise exc.Abort + elif msg.channel is channel and not msg.pinned: + return True + return False + + try: + async for message in channel.history(limit=None): + if 'stop d' in message.content.lower() and message.author.guild_permissions.administrator: + raise exc.Abort + if not message.pinned: + await self.queue.put(message) + + while not self.bot.is_closed(): + message = await self.bot.wait_for('message', check=check) + await self.queue.put(message) + + except exc.Abort: + u.tasks['auto_del'].remove(channel.id) + u.dump(u.tasks, 'cogs/tasks.pkl') + if not u.tasks['auto_del']: + self.deleting = False + print('STOPPED : deleting #{}'.format(channel.name)) + await channel.send('**Stopped queueing messages for deletion in** {}'.format(channel.mention), delete_after=5) + + @cmds.command(name='autodelete', aliases=['autodel']) + @cmds.has_permissions(administrator=True) + async def auto_delete(self, ctx): + try: + if ctx.channel.id not in u.tasks['auto_del']: + u.tasks['auto_del'].append(ctx.channel.id) + u.dump(u.tasks, 'cogs/tasks.pkl') + self.bot.loop.create_task(self.queue_for_deletion(ctx.channel)) + if not self.deleting: + self.bot.loop.create_task(self.delete()) + self.deleting = True + print('STARTED : auto-deleting in #{}'.format(ctx.channel.name)) + await ctx.send('**Auto-deleting all messages in {}**'.format(ctx.channel.mention), delete_after=5) + else: + raise exc.Exists + + except exc.Exists: + await ctx.send('**Already auto-deleting in {}.** Type `stop d(eleting)` to stop.'.format(ctx.channel.mention), delete_after=7) + await ctx.message.add_reaction('\N{CROSS MARK}') + + @cmds.group(aliases=['setting', 'set', 's']) + @cmds.has_permissions(administrator=True) + async def settings(self, ctx): + pass + + @settings.command(name='deletecommands', aliases=['delcmds', 'delcmd']) + async def _settings_deletecommands(self, ctx): + if ctx.guild.id not in u.settings['del_ctx']: + u.settings['del_ctx'].append(ctx.guild.id) + else: + u.settings['del_ctx'].remove(ctx.guild.id) + u.dump(u.settings, 'settings.pkl') + + await ctx.send('**Delete command invocations:** `{}`'.format(ctx.guild.id in u.settings['del_ctx'])) + + @settings.command(name='prefix', aliases=['pre', 'p']) + async def _settings_prefix(self, ctx, *prefixes): + if prefixes: + u.settings['prefixes'][ctx.guild.id] = prefixes + else: + with suppress(KeyError): + del u.settings['prefixes'][ctx.guild.id] + + await ctx.send(f'**Prefix set to:** `{"` or `".join(prefixes if ctx.guild.id in u.settings["prefixes"] else u.config["prefix"])}`') + + @settings.command(name='deleteresponses', aliases=['delresps', 'delresp']) + async def _settings_deleteresponses(self, ctx): + if ctx.guild.id not in u.settings['del_resp']: + u.settings['del_resp'].append(ctx.guild.id) + else: + u.settings['del_resp'].remove(ctx.guild.id) + u.dump(u.settings, 'settings.pkl') + + await ctx.send(f'**Delete command responses:** `{ctx.guild.id in u.settings["del_resp"]}`') diff --git a/src/run.py b/src/run.py index 1d72368..6712217 100644 --- a/src/run.py +++ b/src/run.py @@ -1,266 +1,272 @@ -import asyncio -from datetime import datetime as dt -import json -import logging as log -import subprocess -import sys -import traceback as tb -from contextlib import suppress -from pprint import pprint -from hurry.filesize import size, alternative -from urllib.parse import urlparse - -import discord as d -from discord import errors as err -from discord import utils -from discord.ext import commands as cmds -from discord.ext.commands import errors as errext - -from misc import exceptions as exc -from misc import checks -from utils import utils as u - -log.basicConfig(level=log.WARNING) - - -# class HelpFormatter(cmds.HelpFormatter): -# -# async def format(self): -# self._paginator = cmds.Paginator() -# -# # we need a padding of ~80 or so -# -# description = self.command.description if not self.is_cog() else inspect.getdoc(self.command) -# -# if description: -# # portion -# self._paginator.add_line(description, empty=True) -# -# if isinstance(self.command, cmds.Command): -# # -# signature = self.get_command_signature() -# self._paginator.add_line(signature, empty=True) -# -# # section -# if self.command.help: -# self._paginator.add_line(self.command.help, empty=True) -# -# # end it here if it's just a regular command -# if not self.has_subcommands(): -# self._paginator.close_page() -# return self._paginator.pages -# -# max_width = self.max_name_size - - -def get_prefix(bot, message): - with suppress(AttributeError): - return u.settings['prefixes'].get(message.guild.id, u.config['prefix']) - return u.config['prefix'] - -bot = cmds.Bot(command_prefix=get_prefix, self_bot=u.config['selfbot'], formatter=cmds.HelpFormatter(show_check_failure=True), description='Modufur - A booru bot with a side of management and automated tasking\nMade by @Myned#3985\n\nNSFW for Not Safe For Wumpus commands\n(G) for group commands\n@permission@ for required permissions\n!notice! for important information\np for prefix\n\n\{\} for mandatory argument\n[] for optional argument\n... for one or more arguments', help_attrs={'aliases': ['h']}, pm_help=None) - -@bot.command(help='help', brief='brief', description='description', usage='usage', hidden=True) -async def test(ctx): - await ctx.send('test') - -# Send and print ready message to #testing and console after logon - - -@bot.event -async def on_ready(): - if not checks.ready: - # d.opus.load_opus('opuslib') - - from cogs import booru, info, management, owner, tools - - for cog in (tools.Utils(bot), owner.Bot(bot), owner.Tools(bot), management.Administration(bot), info.Info(bot), booru.MsG(bot)): - bot.add_cog(cog) - print(f'COG : {type(cog).__name__}') - - # bot.loop.create_task(u.clear(booru.temp_urls, 30*60)) - - if u.config['playing'] is not '': - await bot.change_presence(game=d.Game(name=u.config['playing'])) - - print('\n> > > > > > > > >\nC O N N E C T E D : {}\n> > > > > > > > >\n'.format(bot.user.name)) - await bot.get_channel(u.config['info_channel']).send(f'**Started** \N{BLACK SUN WITH RAYS} `{"` or `".join(u.config["prefix"])}`') - # u.notify('C O N N E C T E D') - - if u.temp['startup']: - with suppress(err.NotFound): - if u.temp['startup'][0] == 'guild': - dest = bot.get_channel(u.temp['startup'][1]) - else: - dest = bot.get_user(u.temp['startup'][1]) - message = await dest.get_message(u.temp['startup'][2]) - - await message.add_reaction('\N{WHITE HEAVY CHECK MARK}') - - u.temp['startup'] = () - u.dump(u.temp, 'temp/temp.pkl') - - checks.ready = True - else: - print('\n- - - -\nI N F O : reconnected, skipping initialization\n- - - -') - - -@bot.event -async def on_message(message): - if not u.config['selfbot']: - if message.author is not bot.user and not message.author.bot: - await bot.process_commands(message) - else: - if not message.author.bot: - await bot.process_commands(message) - - -@bot.event -async def on_error(error, *args, **kwargs): - print('\n! ! ! ! !\nE R R O R : {}\n! ! ! ! !\n'.format(error), file=sys.stderr) - tb.print_exc() - await bot.get_user(u.config['owner_id']).send('**ERROR** \N{WARNING SIGN}\n```\n{}```'.format(error)) - await bot.get_channel(u.config['info_channel']).send('**ERROR** \N{WARNING SIGN}\n```\n{}```'.format(error)) - - if u.temp['startup']: - with suppress(err.NotFound): - if u.temp['startup'][0] == 'guild': - dest = bot.get_channel(u.temp['startup'][1]) - else: - dest = bot.get_user(u.temp['startup'][1]) - message = await dest.get_message(u.temp['startup'][2]) - - await message.add_reaction('\N{WARNING SIGN}') - - u.temp.clear() - u.dump(u.temp, 'temp/temp.pkl') - # u.notify('E R R O R') - await bot.logout() - u.close(bot.loop) - - -@bot.event -async def on_command_error(ctx, error): - if isinstance(error, err.NotFound): - print('NOT FOUND') - elif isinstance(error, errext.MissingRequiredArgument): - await ctx.send('**Missing required argument**', delete_after=7) - await ctx.message.add_reaction('\N{CROSS MARK}') - elif isinstance(error, errext.BadArgument): - await ctx.send(f'**Invalid argument.** {error}', delete_after=7) - await ctx.message.add_reaction('\N{CROSS MARK}') - elif isinstance(error, errext.CheckFailure): - await ctx.send('**Insufficient permissions**', delete_after=7) - await ctx.message.add_reaction('\N{NO ENTRY}') - elif isinstance(error, errext.CommandNotFound): - print('INVALID COMMAND : {}'.format(error), file=sys.stderr) - await ctx.message.add_reaction('\N{BLACK QUESTION MARK ORNAMENT}') - else: - print('\n! ! ! ! ! ! ! ! ! ! ! !\nC O M M A N D E R R O R : {}\n! ! ! ! ! ! ! ! ! ! ! !\n'.format( - error), file=sys.stderr) - tb.print_exception(type(error), error, error.__traceback__, file=sys.stderr) - await bot.get_user(u.config['owner_id']).send('**COMMAND ERROR** \N{WARNING SIGN} `{}` from {} in {}\n```\n{}```'.format(ctx.message.content, ctx.author.mention, ctx.channel.mention, ''.join(tb.format_exception(type(error), error, error.__traceback__ if len(str(error.__traceback__)) < 1500 else str(error.__traceback__)[:1500])))) - await bot.get_channel(u.config['info_channel']).send('**COMMAND ERROR** \N{WARNING SIGN} `{}` from {} in {}\n```\n{}```'.format(ctx.message.content, ctx.author.mention, ctx.channel.mention, error)) - await exc.send_error(ctx, error) - await ctx.message.add_reaction('\N{WARNING SIGN}') - # u.notify('C O M M A N D E R R O R') - -# @bot.event -# async def on_command(ctx): -# if ctx.guild.id in u.settings['del_resp']: -# pass - -@bot.event -async def on_command_completion(ctx): - with suppress(err.NotFound): - with suppress(AttributeError): - if ctx.guild.id in u.settings['del_ctx'] and ctx.me.permissions_in(ctx.channel).manage_messages and isinstance(ctx.message.channel, d.TextChannel): - await ctx.message.delete() - - await ctx.message.add_reaction('\N{WHITE HEAVY CHECK MARK}') - - for command in ('lastcommand', ',restart', ',die'): - if ctx.command.name == command: - return - - u.last_commands[ctx.author.id] = ctx - -@bot.event -async def on_guild_remove(guild): - print(f'LEFT : {guild.name}') - - for task, idents in u.tasks.items(): - for channel in guild.channels: - if channel.id in idents: - idents.remove(channel.id) - print(f'STOPPED : {task} in #{channel.id}') - u.dump(u.tasks, 'cogs/tasks.pkl') - - -async def wait(voice): - asyncio.sleep(5) - await voice.disconnect() - - -def after(voice, error): - coro = voice.disconnect() - future = asyncio.run_coroutine_threadsafe(coro, voice.loop) - future.result() - -# suggested = u.setdefault('cogs/suggested.pkl', {'last_update': 'None', 'tags': {}, 'total': 0}) -@bot.command(name=',test', hidden=True) -@cmds.is_owner() -async def test(ctx): - post = await u.fetch('https://e621.net/post/show.json?id=1145042', json=True) - - tags = [] - if post['tags']: - temptags = post['tags'].split(' ') - cis = [] - for tag in suggested: - pass - for tag in temptags: - tags.append(f'[{tag}](https://e621.net/post?tags={tag})') - # tags = ' '.join(tags) - else: - tags = 'None' - - if post['description']: - post_description = post['description'] if len(post['description']) < 200 else f'{post["description"][:200]}...' - else: - post_description = 'None' - - title = ', '.join(post['artist']) - description = f'posted by: *[{post["author"]}](https://e621.net/post?tags=user:{post["author"]})*' - url = f'https://e621.net/post?tags={",".join(post["artist"])}' - # timestamp = dt.utcnow() - color = ctx.me.color - footer = {'text': post['score'], 'icon_url': 'https://images-ext-1.discordapp.net/external/W2k0ZzhU7ngvN_-CdqAa3H3FmkfCNYQTxPG_DsvacB4/https/emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/sparkles_2728.png'} - # image = 'https://e621.net/post/show/54360' - thumbnail = post['file_url'] - author = {'name': post['id'], 'url': f'https://e621.net/post/show/{post["id"]}', 'icon_url': ctx.author.avatar_url} - - fields = [] - names = ('File', 'Sources', 'Description', 'tags', 'tags (ext.)') - values = (f'[{post["md5"]}]({post["file_url"]}) | [{post["file_ext"]}](https://e621.net/post?tags=type:{post["file_ext"]})\n\n**Size** [{size(post["file_size"], system=alternative)}](https://e621.net/post?tags=filesize:{post["file_size"]})\n**Resolution** [{post["width"]} x {post["height"]}](https://e621.net/post?tags=width:{post["width"]},height:{post["height"]}) | [{u.get_aspectratio(post["width"], post["height"])}](https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f})', '\n'.join([f'[{urlparse(source).netloc}]({source})' for source in post['sources']]), post_description, ' '.join(tags[:20]), ' '.join(tags[20:])) - inlines = (False, False, False, True, True) - for name, value, inline in zip(names, values, inlines): - fields.append({'name': name, 'value': value, 'inline': inline}) - - embed = u.generate_embed(ctx, title=title, description=description, url=url, colour=color, footer=footer, thumbnail=thumbnail, author=author, fields=fields) - - await ctx.send(embed=embed) - # print(ctx.args) - # print(ctx.kwargs) - # if '<:N_:368917475531816962>' in message: - # await ctx.send('<:N_:368917475531816962>') - # logs = [] - # async for entry in ctx.guild.audit_logs(limit=None, action=d.AuditLogAction.message_delete): - # logs.append( - # f'@{entry.user.name} deleted {entry.extra.count} messages from @{entry.target.name} in #{entry.extra.channel.name}') - # pprint(logs) - # channel = bot.get_channel(int(cid)) - # voice = await channel.connect() - # voice.play(d.AudioSource, after=lambda: after(voice)) - -bot.run(u.config['token'], bot=not u.config['selfbot']) +import asyncio +from datetime import datetime as dt +import json +import logging as log +import subprocess +import sys +import traceback as tb +from contextlib import suppress +from pprint import pprint +from hurry.filesize import size, alternative +from urllib.parse import urlparse + +import discord as d +from discord import errors as err +from discord import utils +from discord.ext import commands as cmds +from discord.ext.commands import errors as errext + +from misc import exceptions as exc +from misc import checks +from utils import utils as u + +log.basicConfig(level=log.WARNING) + + +# class HelpFormatter(cmds.HelpFormatter): +# +# async def format(self): +# self._paginator = cmds.Paginator() +# +# # we need a padding of ~80 or so +# +# description = self.command.description if not self.is_cog() else inspect.getdoc(self.command) +# +# if description: +# # portion +# self._paginator.add_line(description, empty=True) +# +# if isinstance(self.command, cmds.Command): +# # +# signature = self.get_command_signature() +# self._paginator.add_line(signature, empty=True) +# +# # section +# if self.command.help: +# self._paginator.add_line(self.command.help, empty=True) +# +# # end it here if it's just a regular command +# if not self.has_subcommands(): +# self._paginator.close_page() +# return self._paginator.pages +# +# max_width = self.max_name_size + + +def get_prefix(bot, message): + with suppress(AttributeError): + return u.settings['prefixes'].get(message.guild.id, u.config['prefix']) + return u.config['prefix'] + +bot = cmds.Bot(command_prefix=get_prefix, self_bot=u.config['selfbot'], formatter=cmds.HelpFormatter(show_check_failure=True), description='Modufur - A booru bot with a side of management and automated tasking\nMade by @Myned#3985\n\nNSFW for Not Safe For Wumpus commands\n(G) for group commands\n@permission@ for required permissions\n!notice! for important information\np for prefix\n\n\{\} for mandatory argument\n[] for optional argument\n... for one or more arguments', help_attrs={'aliases': ['h']}, pm_help=None) + +@bot.command(help='help', brief='brief', description='description', usage='usage', hidden=True) +async def test(ctx): + await ctx.send('test') + +# Send and print ready message to #testing and console after logon + + +@bot.event +async def on_ready(): + if not checks.ready: + # d.opus.load_opus('opuslib') + + from cogs import booru, info, management, owner, tools + + for cog in (tools.Utils(bot), owner.Bot(bot), owner.Tools(bot), management.Administration(bot), info.Info(bot), booru.MsG(bot)): + bot.add_cog(cog) + print(f'COG : {type(cog).__name__}') + + # bot.loop.create_task(u.clear(booru.temp_urls, 30*60)) + + if u.config['playing'] is not '': + await bot.change_presence(game=d.Game(name=u.config['playing'])) + + print('\n> > > > > > > > >\nC O N N E C T E D : {}\n> > > > > > > > >\n'.format(bot.user.name)) + await bot.get_channel(u.config['info_channel']).send(f'**Started** \N{BLACK SUN WITH RAYS} `{"` or `".join(u.config["prefix"])}`') + # u.notify('C O N N E C T E D') + + if u.temp['startup']: + with suppress(err.NotFound): + if u.temp['startup'][0] == 'guild': + dest = bot.get_channel(u.temp['startup'][1]) + else: + dest = bot.get_user(u.temp['startup'][1]) + message = await dest.get_message(u.temp['startup'][2]) + + await message.add_reaction('\N{WHITE HEAVY CHECK MARK}') + + u.temp['startup'] = () + u.dump(u.temp, 'temp/temp.pkl') + + checks.ready = True + else: + print('\n- - - -\nI N F O : reconnected, reinitializing\n- - - -') + + for cog in (tools.Utils(bot), owner.Bot(bot), owner.Tools(bot), management.Administration(bot), info.Info(bot), booru.MsG(bot)): + bot.add_cog(cog) + print(f'COG : {type(cog).__name__}') + + if u.config['playing'] is not '': + await bot.change_presence(game=d.Game(name=u.config['playing'])) + + +@bot.event +async def on_message(message): + if not u.config['selfbot']: + if message.author is not bot.user and not message.author.bot: + await bot.process_commands(message) + else: + if not message.author.bot: + await bot.process_commands(message) + + +@bot.event +async def on_error(error, *args, **kwargs): + print('\n! ! ! ! !\nE R R O R : {}\n! ! ! ! !\n'.format(error), file=sys.stderr) + tb.print_exc() + await bot.get_user(u.config['owner_id']).send('**ERROR** \N{WARNING SIGN}\n```\n{}```'.format(error)) + await bot.get_channel(u.config['info_channel']).send('**ERROR** \N{WARNING SIGN}\n```\n{}```'.format(error)) + + if u.temp['startup']: + with suppress(err.NotFound): + if u.temp['startup'][0] == 'guild': + dest = bot.get_channel(u.temp['startup'][1]) + else: + dest = bot.get_user(u.temp['startup'][1]) + message = await dest.get_message(u.temp['startup'][2]) + + await message.add_reaction('\N{WARNING SIGN}') + + u.temp.clear() + u.dump(u.temp, 'temp/temp.pkl') + # u.notify('E R R O R') + await bot.logout() + + +@bot.event +async def on_command_error(ctx, error): + if isinstance(error, err.NotFound): + print('NOT FOUND') + elif isinstance(error, errext.MissingRequiredArgument): + await ctx.send('**Missing required argument**', delete_after=7) + await ctx.message.add_reaction('\N{CROSS MARK}') + elif isinstance(error, errext.BadArgument): + await ctx.send(f'**Invalid argument.** {error}', delete_after=7) + await ctx.message.add_reaction('\N{CROSS MARK}') + elif isinstance(error, errext.CheckFailure): + await ctx.send('**Insufficient permissions**', delete_after=7) + await ctx.message.add_reaction('\N{NO ENTRY}') + elif isinstance(error, errext.CommandNotFound): + print('INVALID COMMAND : {}'.format(error), file=sys.stderr) + await ctx.message.add_reaction('\N{BLACK QUESTION MARK ORNAMENT}') + else: + print('\n! ! ! ! ! ! ! ! ! ! ! !\nC O M M A N D E R R O R : {}\n! ! ! ! ! ! ! ! ! ! ! !\n'.format( + error), file=sys.stderr) + tb.print_exception(type(error), error, error.__traceback__, file=sys.stderr) + await bot.get_user(u.config['owner_id']).send('**COMMAND ERROR** \N{WARNING SIGN} `{}` from {} in {}\n```\n{}```'.format(ctx.message.content, ctx.author.mention, ctx.channel.mention, ''.join(tb.format_exception(type(error), error, error.__traceback__ if len(str(error.__traceback__)) < 1500 else str(error.__traceback__)[:1500])))) + await bot.get_channel(u.config['info_channel']).send('**COMMAND ERROR** \N{WARNING SIGN} `{}` from {} in {}\n```\n{}```'.format(ctx.message.content, ctx.author.mention, ctx.channel.mention, error)) + await exc.send_error(ctx, error) + await ctx.message.add_reaction('\N{WARNING SIGN}') + # u.notify('C O M M A N D E R R O R') + +# @bot.event +# async def on_command(ctx): +# if ctx.guild.id in u.settings['del_resp']: +# pass + +@bot.event +async def on_command_completion(ctx): + with suppress(err.NotFound): + with suppress(AttributeError): + if ctx.guild.id in u.settings['del_ctx'] and ctx.me.permissions_in(ctx.channel).manage_messages and isinstance(ctx.message.channel, d.TextChannel): + await ctx.message.delete() + + await ctx.message.add_reaction('\N{WHITE HEAVY CHECK MARK}') + + for command in ('lastcommand', ',restart', ',die'): + if ctx.command.name == command: + return + + u.last_commands[ctx.author.id] = ctx + +@bot.event +async def on_guild_remove(guild): + print(f'LEFT : {guild.name}') + + for task, idents in u.tasks.items(): + for channel in guild.channels: + if channel.id in idents: + idents.remove(channel.id) + print(f'STOPPED : {task} in #{channel.id}') + u.dump(u.tasks, 'cogs/tasks.pkl') + + +async def wait(voice): + asyncio.sleep(5) + await voice.disconnect() + + +def after(voice, error): + coro = voice.disconnect() + future = asyncio.run_coroutine_threadsafe(coro, voice.loop) + future.result() + +# suggested = u.setdefault('cogs/suggested.pkl', {'last_update': 'None', 'tags': {}, 'total': 0}) +@bot.command(name=',test', hidden=True) +@cmds.is_owner() +async def test(ctx): + post = await u.fetch('https://e621.net/post/show.json?id=1145042', json=True) + + tags = [] + if post['tags']: + temptags = post['tags'].split(' ') + cis = [] + for tag in suggested: + pass + for tag in temptags: + tags.append(f'[{tag}](https://e621.net/post?tags={tag})') + # tags = ' '.join(tags) + else: + tags = 'None' + + if post['description']: + post_description = post['description'] if len(post['description']) < 200 else f'{post["description"][:200]}...' + else: + post_description = 'None' + + title = ', '.join(post['artist']) + description = f'posted by: *[{post["author"]}](https://e621.net/post?tags=user:{post["author"]})*' + url = f'https://e621.net/post?tags={",".join(post["artist"])}' + # timestamp = dt.utcnow() + color = ctx.me.color + footer = {'text': post['score'], 'icon_url': 'https://images-ext-1.discordapp.net/external/W2k0ZzhU7ngvN_-CdqAa3H3FmkfCNYQTxPG_DsvacB4/https/emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/sparkles_2728.png'} + # image = 'https://e621.net/post/show/54360' + thumbnail = post['file_url'] + author = {'name': post['id'], 'url': f'https://e621.net/post/show/{post["id"]}', 'icon_url': ctx.author.avatar_url} + + fields = [] + names = ('File', 'Sources', 'Description', 'tags', 'tags (ext.)') + values = (f'[{post["md5"]}]({post["file_url"]}) | [{post["file_ext"]}](https://e621.net/post?tags=type:{post["file_ext"]})\n\n**Size** [{size(post["file_size"], system=alternative)}](https://e621.net/post?tags=filesize:{post["file_size"]})\n**Resolution** [{post["width"]} x {post["height"]}](https://e621.net/post?tags=width:{post["width"]},height:{post["height"]}) | [{u.get_aspectratio(post["width"], post["height"])}](https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f})', '\n'.join([f'[{urlparse(source).netloc}]({source})' for source in post['sources']]), post_description, ' '.join(tags[:20]), ' '.join(tags[20:])) + inlines = (False, False, False, True, True) + for name, value, inline in zip(names, values, inlines): + fields.append({'name': name, 'value': value, 'inline': inline}) + + embed = u.generate_embed(ctx, title=title, description=description, url=url, colour=color, footer=footer, thumbnail=thumbnail, author=author, fields=fields) + + await ctx.send(embed=embed) + # print(ctx.args) + # print(ctx.kwargs) + # if '<:N_:368917475531816962>' in message: + # await ctx.send('<:N_:368917475531816962>') + # logs = [] + # async for entry in ctx.guild.audit_logs(limit=None, action=d.AuditLogAction.message_delete): + # logs.append( + # f'@{entry.user.name} deleted {entry.extra.count} messages from @{entry.target.name} in #{entry.extra.channel.name}') + # pprint(logs) + # channel = bot.get_channel(int(cid)) + # voice = await channel.connect() + # voice.play(d.AudioSource, after=lambda: after(voice)) + +bot.run(u.config['token'], bot=not u.config['selfbot']) diff --git a/src/utils/utils.py b/src/utils/utils.py index a2d8f92..f7236f1 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -1,191 +1,191 @@ -import asyncio -import json as jsn -import os -import pickle as pkl -import subprocess -from contextlib import suppress -from fractions import gcd -import math -import gmusicapi as gpm - -import aiohttp -import discord as d - -from misc import exceptions as exc - -# from pync import Notifier - - -print('\nPID : {}\n'.format(os.getpid())) - - -# def notify(message): -# subprocess.run(['terminal-notifier', '-message', message, '-title', -# 'Modumind', '-activate', 'com.apple.Terminal', '-appIcon', 'icon.png', '-sound', 'Ping'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - -try: - with open('config.json') as infile: - config = jsn.load(infile) - print('LOADED : config.json') - -except FileNotFoundError: - with open('config.json', 'w') as outfile: - jsn.dump({'client_id': 0, 'info_channel': 0, 'owner_id': 0, 'permissions': 126016, - 'playing': 'a game', 'prefix': [',', 'm,'], 'selfbot': False, 'token': 'str'}, outfile, indent=4, sort_keys=True) - print('FILE NOT FOUND : config.json created with abstract values. Restart run.py with correct values') - - -def setdefault(filename, default=None, json=False): - if json: - try: - with open(filename, 'r') as infile: - print(f'LOADED : {filename}') - return jsn.load(infile) - - except FileNotFoundError: - with open(filename, 'w+') as iofile: - print(f'FILE NOT FOUND : {filename} created and loaded with default values') - jsn.dump(default, iofile) - iofile.seek(0) - return jsn.load(iofile) - else: - try: - with open(filename, 'rb') as infile: - print(f'LOADED : {filename}') - return pkl.load(infile) - - except FileNotFoundError: - with open(filename, 'wb+') as iofile: - print(f'FILE NOT FOUND : {filename} created and loaded with default values') - pkl.dump(default, iofile) - iofile.seek(0) - return pkl.load(iofile) - - -def load(filename, *, json=False): - if not json: - with open(filename, 'rb') as infile: - return pkl.load(infile) - else: - with open(filename) as infile: - return jsn.load(infile) - - -def dump(obj, filename, *, json=False): - if not json: - with open(filename, 'wb') as outfile: - pkl.dump(obj, outfile) - else: - with open(filename, 'w') as outfile: - jsn.dump(obj, outfile, indent=4, sort_keys=True) - - -settings = setdefault('misc/settings.pkl', default={'del_ctx': [], 'del_resp': [], 'prefixes': {}}) -tasks = setdefault('cogs/tasks.pkl', default={'auto_del': [], 'auto_hrt': [], 'auto_rev': [], 'periodic_gpm': []}) -temp = setdefault('temp/temp.pkl', default={'startup': ()}) -secrets = setdefault('secrets.json', default={'client_secrets': {'client_id': '', 'client_secret': ''}}, json=True) - -RATE_LIMIT = 2.2 -color = d.Color(0x1A1A1A) -session = aiohttp.ClientSession() -last_commands = {} - - -async def fetch(url, *, params={}, json=False, response=False): - async with session.get(url, params=params, headers={'User-Agent': 'Myned/Modufur'}) as r: - if response: - return r - elif json: - return await r.json() - return await r.read() - - -# async def clear(obj, interval=10 * 60, replace=None): -# if replace is None: -# if type(obj) is list: -# replace = [] -# elif type(obj) is dict: -# replace = {} -# elif type(obj) is int: -# replace = 0 -# elif type(obj) is str: -# replace = '' -# -# while True: -# obj = replace -# asyncio.sleep(interval) - - -def close(loop): - if session: - session.close() - - loop.stop() - pending = asyncio.Task.all_tasks() - for task in pending: - task.cancel() - # with suppress(asyncio.CancelledError): - # loop.run_until_complete(task) - # loop.close() - - print('Finished cancelling tasks.') - - -def generate_embed(ctx, *, title=d.Embed.Empty, kind='rich', description=d.Embed.Empty, url=d.Embed.Empty, timestamp=d.Embed.Empty, colour=color, footer={}, image=d.Embed.Empty, thumbnail=d.Embed.Empty, author={}, fields=[]): - embed = d.Embed(title=title, type=kind, description=description, url=url, timestamp=timestamp, colour=colour if isinstance(ctx.channel, d.TextChannel) else color) - - if footer: - embed.set_footer(text=footer.get('text', d.Embed.Empty), icon_url=footer.get('icon_url', d.Embed.Empty)) - if image: - embed.set_image(url=image) - if thumbnail: - embed.set_thumbnail(url=thumbnail) - if author: - embed.set_author(name=author.get('name', d.Embed.Empty), url=author.get('url', d.Embed.Empty), icon_url=author.get('icon_url', d.Embed.Empty)) - for field in fields: - embed.add_field(name=field.get('name', d.Embed.Empty), value=field.get('value', d.Embed.Empty), inline=field.get('inline', True)) - - return embed - -def get_kwargs(ctx, args, *, limit=False): - destination = ctx - remaining = list(args[:]) - rm = False - lim = 1 - - for flag in ('-d', '-dm'): - if flag in remaining: - destination = ctx.author - - remaining.remove(flag) - - for flag in ('-r', '-rm', '-remove', '-re', '-repl', '-replace'): - if flag in remaining and ctx.author.permissions_in(ctx.channel).manage_messages: - rm = True - - remaining.remove(flag) - - if limit: - for arg in remaining: - if arg.isdigit(): - if 1 <= int(arg) <= limit: - lim = int(arg) - remaining.remove(arg) - break - else: - raise exc.BoundsError(arg) - - return {'destination': destination, 'remaining': remaining, 'remove': rm, 'limit': lim} - - -def get_aspectratio(a, b): - divisor = gcd(a, b) - return f'{int(a / divisor)}:{int(b / divisor)}' - - -def ci(pos, n): - z = 1.96 - phat = float(pos) / n - - return (phat + z*z/(2*n) - z * math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n) +import asyncio +import json as jsn +import os +import pickle as pkl +import subprocess +from contextlib import suppress +from fractions import gcd +import math +import gmusicapi as gpm + +import aiohttp +import discord as d + +from misc import exceptions as exc + +# from pync import Notifier + + +print('\nPID : {}\n'.format(os.getpid())) + + +# def notify(message): +# subprocess.run(['terminal-notifier', '-message', message, '-title', +# 'Modumind', '-activate', 'com.apple.Terminal', '-appIcon', 'icon.png', '-sound', 'Ping'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + +try: + with open('config.json') as infile: + config = jsn.load(infile) + print('LOADED : config.json') + +except FileNotFoundError: + with open('config.json', 'w') as outfile: + jsn.dump({'client_id': 0, 'info_channel': 0, 'owner_id': 0, 'permissions': 126016, + 'playing': 'a game', 'prefix': [',', 'm,'], 'selfbot': False, 'token': 'str'}, outfile, indent=4, sort_keys=True) + print('FILE NOT FOUND : config.json created with abstract values. Restart run.py with correct values') + + +def setdefault(filename, default=None, json=False): + if json: + try: + with open(filename, 'r') as infile: + print(f'LOADED : {filename}') + return jsn.load(infile) + + except FileNotFoundError: + with open(filename, 'w+') as iofile: + print(f'FILE NOT FOUND : {filename} created and loaded with default values') + jsn.dump(default, iofile) + iofile.seek(0) + return jsn.load(iofile) + else: + try: + with open(filename, 'rb') as infile: + print(f'LOADED : {filename}') + return pkl.load(infile) + + except FileNotFoundError: + with open(filename, 'wb+') as iofile: + print(f'FILE NOT FOUND : {filename} created and loaded with default values') + pkl.dump(default, iofile) + iofile.seek(0) + return pkl.load(iofile) + + +def load(filename, *, json=False): + if not json: + with open(filename, 'rb') as infile: + return pkl.load(infile) + else: + with open(filename) as infile: + return jsn.load(infile) + + +def dump(obj, filename, *, json=False): + if not json: + with open(filename, 'wb') as outfile: + pkl.dump(obj, outfile) + else: + with open(filename, 'w') as outfile: + jsn.dump(obj, outfile, indent=4, sort_keys=True) + + +settings = setdefault('misc/settings.pkl', default={'del_ctx': [], 'del_resp': [], 'prefixes': {}}) +tasks = setdefault('cogs/tasks.pkl', default={'auto_del': [], 'auto_hrt': [], 'auto_rev': [], 'periodic_gpm': []}) +temp = setdefault('temp/temp.pkl', default={'startup': ()}) +secrets = setdefault('secrets.json', default={'client_secrets': {'client_id': '', 'client_secret': ''}}, json=True) + +RATE_LIMIT = 2.2 +color = d.Color(0x1A1A1A) +session = aiohttp.ClientSession() +last_commands = {} + + +async def fetch(url, *, params={}, json=False, response=False): + async with session.get(url, params=params, headers={'User-Agent': 'Myned/Modufur'}) as r: + if response: + return r + elif json: + return await r.json() + return await r.read() + + +# async def clear(obj, interval=10 * 60, replace=None): +# if replace is None: +# if type(obj) is list: +# replace = [] +# elif type(obj) is dict: +# replace = {} +# elif type(obj) is int: +# replace = 0 +# elif type(obj) is str: +# replace = '' +# +# while True: +# obj = replace +# asyncio.sleep(interval) + + +def close(loop): + if session: + session.close() + + loop.stop() + pending = asyncio.Task.all_tasks() + for task in pending: + task.cancel() + # with suppress(asyncio.CancelledError): + # loop.run_until_complete(task) + # loop.close() + + print('Finished cancelling tasks.') + + +def generate_embed(ctx, *, title=d.Embed.Empty, kind='rich', description=d.Embed.Empty, url=d.Embed.Empty, timestamp=d.Embed.Empty, colour=color, footer={}, image=d.Embed.Empty, thumbnail=d.Embed.Empty, author={}, fields=[]): + embed = d.Embed(title=title, type=kind, description=description, url=url, timestamp=timestamp, colour=colour if isinstance(ctx.channel, d.TextChannel) else color) + + if footer: + embed.set_footer(text=footer.get('text', d.Embed.Empty), icon_url=footer.get('icon_url', d.Embed.Empty)) + if image: + embed.set_image(url=image) + if thumbnail: + embed.set_thumbnail(url=thumbnail) + if author: + embed.set_author(name=author.get('name', d.Embed.Empty), url=author.get('url', d.Embed.Empty), icon_url=author.get('icon_url', d.Embed.Empty)) + for field in fields: + embed.add_field(name=field.get('name', d.Embed.Empty), value=field.get('value', d.Embed.Empty), inline=field.get('inline', True)) + + return embed + +def get_kwargs(ctx, args, *, limit=False): + destination = ctx + remaining = list(args[:]) + rm = False + lim = 1 + + for flag in ('-d', '-dm'): + if flag in remaining: + destination = ctx.author + + remaining.remove(flag) + + for flag in ('-r', '-rm', '-remove', '-re', '-repl', '-replace'): + if flag in remaining and ctx.author.permissions_in(ctx.channel).manage_messages: + rm = True + + remaining.remove(flag) + + if limit: + for arg in remaining: + if arg.isdigit(): + if 1 <= int(arg) <= limit: + lim = int(arg) + remaining.remove(arg) + break + else: + raise exc.BoundsError(arg) + + return {'destination': destination, 'remaining': remaining, 'remove': rm, 'limit': lim} + + +def get_aspectratio(a, b): + divisor = gcd(a, b) + return f'{int(a / divisor)}:{int(b / divisor)}' + + +def ci(pos, n): + z = 1.96 + phat = float(pos) / n + + return (phat + z*z/(2*n) - z * math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)