diff options
author | 2018-05-22 19:12:01 +0200 | |
---|---|---|
committer | 2018-05-22 19:12:01 +0200 | |
commit | 1a2997fe353344eae6815031357856ac7b128b77 (patch) | |
tree | c7fe94c1570115ba34be7966c1da78175f742794 | |
parent | Fix typing issue (diff) |
Configure constants with YAML file(s) (#57)
* Load various constants from `.yml` file.
* Ignore user-provided `config.yml`.
* Support environment variables in YAML configuration.
* Use "class-based" configuration.
* Configure logging properly.
* Move the last few constants to YAML.
* Fix recursive update accidentally overriding mappings.
* Read bot token from YAML configuration.
* Add documentation strings and `KeyError` handling.
* Remove coding setting from constants module.
* Add `help4` channel ID to `config-default.yml`.
* Fix a few linting issues from merge.
* s/Channels.help1/Channels.help0/g
* s/Channels.help2/Channels.help1/g
* s/Channels.help3/Channels.help2/g
* s/Channels.help4/Channels.help3/g
* Adress @JoeBanks13's review comments.
* Put a block comment above dataclasses.
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Pipfile | 1 | ||||
-rw-r--r-- | Pipfile.lock | 1049 | ||||
-rw-r--r-- | bot/__init__.py | 16 | ||||
-rw-r--r-- | bot/__main__.py | 10 | ||||
-rw-r--r-- | bot/cogs/bot.py | 33 | ||||
-rw-r--r-- | bot/cogs/clickup.py | 46 | ||||
-rw-r--r-- | bot/cogs/cogs.py | 36 | ||||
-rw-r--r-- | bot/cogs/deployment.py | 14 | ||||
-rw-r--r-- | bot/cogs/eval.py | 4 | ||||
-rw-r--r-- | bot/cogs/events.py | 27 | ||||
-rw-r--r-- | bot/cogs/fun.py | 4 | ||||
-rw-r--r-- | bot/cogs/hiphopify.py | 22 | ||||
-rw-r--r-- | bot/cogs/logging.py | 5 | ||||
-rw-r--r-- | bot/cogs/snakes.py | 29 | ||||
-rw-r--r-- | bot/cogs/tags.py | 32 | ||||
-rw-r--r-- | bot/cogs/verification.py | 13 | ||||
-rw-r--r-- | bot/constants.py | 258 | ||||
-rw-r--r-- | bot/converters.py | 11 | ||||
-rw-r--r-- | bot/formatter.py | 8 | ||||
-rw-r--r-- | config-default.yml | 74 |
21 files changed, 959 insertions, 738 deletions
diff --git a/.gitignore b/.gitignore index f8625a961..3202baa92 100644 --- a/.gitignore +++ b/.gitignore @@ -107,4 +107,7 @@ ENV/ .vagrant # JSON logfile -log.json
\ No newline at end of file +log.json + +# Custom user configuration +config.yml @@ -12,6 +12,7 @@ aiodns = "*" logmatic-python = "*" aiohttp = "<2.3.0,>=2.0.0" websockets = ">=4.0,<5.0" +pyyaml = "*" yarl = "==1.1.1" fuzzywuzzy = "*" python-levenshtein = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 59ead2ca7..f84c9e7ae 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,514 +1,535 @@ -{
- "_meta": {
- "hash": {
- "sha256": "5bfa36b78c3ea77506186a786438cd0b0e78247e7216bc1875630d4401069689"
- },
- "pipfile-spec": 6,
- "requires": {
- "python_version": "3.6"
- },
- "sources": [
- {
- "name": "pypi",
- "url": "https://pypi.python.org/simple",
- "verify_ssl": true
- }
- ]
- },
- "default": {
- "aiodns": {
- "hashes": [
- "sha256:99d0652f2c02f73bfa646bf44af82705260a523014576647d7959e664830b26b",
- "sha256:d8677adc679ce8d0ef706c14d9c3d2f27a0e0cc11d59730cdbaf218ad52dd9ea"
- ],
- "index": "pypi",
- "version": "==1.1.1"
- },
- "aiohttp": {
- "hashes": [
- "sha256:129d83dd067760cec3cfd4456b5c6d7ac29f2c639d856884568fd539bed5a51f",
- "sha256:33c62afd115c456b0cf1e890fe6753055effe0f31a28321efd4f787378d6f4ab",
- "sha256:666756e1d4cf161ed1486b82f65fdd386ac07dd20fb10f025abf4be54be12746",
- "sha256:9705ded5a0faa25c8f14c6afb7044002d66c9120ed7eadb4aa9ca4aad32bd00c",
- "sha256:af5bfdd164256118a0a306b3f7046e63207d1f8cba73a67dcc0bd858dcfcd3bc",
- "sha256:b80f44b99fa3c9b4530fcfa324a99b84843043c35b084e0b653566049974435d",
- "sha256:c67e105ec74b85c8cb666b6877569dee6f55b9548f982983b9bee80b3d47e6f3",
- "sha256:d15c6658de5b7783c2538407278fa062b079a46d5f814a133ae0f09bbb2cfbc4",
- "sha256:d611ebd1ef48498210b65486306e065fde031040a1f3c455ca1b6baa7bf32ad3",
- "sha256:dcc7e4dcec6b0012537b9f8a0726f8b111188894ab0f924b680d40b13d3298a0",
- "sha256:de8ef106e130b94ca143fdfc6f27cda1d8ba439462542377738af4d99d9f5dd2",
- "sha256:eb6f1405b607fff7e44168e3ceb5d3c8a8c5a2d3effe0a27f843b16ec047a6d7",
- "sha256:f0e2ac69cb709367400008cebccd5d48161dd146096a009a632a132babe5714c"
- ],
- "index": "pypi",
- "version": "==2.2.5"
- },
- "async-timeout": {
- "hashes": [
- "sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c",
- "sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287"
- ],
- "version": "==3.0.0"
- },
- "certifi": {
- "hashes": [
- "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
- "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
- ],
- "version": "==2018.4.16"
- },
- "chardet": {
- "hashes": [
- "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
- "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
- ],
- "version": "==3.0.4"
- },
- "discord": {
- "egg": "discord.py[voice]",
- "file": "https://github.com/Rapptz/discord.py/archive/rewrite.zip"
- },
- "dulwich": {
- "hashes": [
- "sha256:c51e10c260543240e0806052af046e1a78b98cbe1ac1ef3880a78d2269e09da4"
- ],
- "index": "pypi",
- "version": "==0.19.2"
- },
- "fuzzywuzzy": {
- "hashes": [
- "sha256:d40c22d2744dff84885b30bbfc07fab7875f641d070374331777a4d1808b8d4e",
- "sha256:ecf490216fb4d76b558a03042ff8f45a8782f17326caca1384d834cbaa2c7e6f"
- ],
- "index": "pypi",
- "version": "==0.16.0"
- },
- "idna": {
- "hashes": [
- "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
- "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
- ],
- "version": "==2.6"
- },
- "logmatic-python": {
- "hashes": [
- "sha256:0c15ac9f5faa6a60059b28910db642c3dc7722948c3cc940923f8c9039604342"
- ],
- "index": "pypi",
- "version": "==0.1.7"
- },
- "mpmath": {
- "hashes": [
- "sha256:04d14803b6875fe6d69e6dccea87d5ae5599802e4b1df7997bddd2024001050c"
- ],
- "version": "==1.0.0"
- },
- "multidict": {
- "hashes": [
- "sha256:1a1d76374a1e7fe93acef96b354a03c1d7f83e7512e225a527d283da0d7ba5e0",
- "sha256:1d6e191965505652f194bc4c40270a842922685918a4f45e6936a6b15cc5816d",
- "sha256:295961a6a88f1199e19968e15d9b42f3a191c89ec13034dbc212bf9c394c3c82",
- "sha256:2be5af084de6c3b8e20d6421cb0346378a9c867dcf7c86030d6b0b550f9888e4",
- "sha256:2eb99617c7a0e9f2b90b64bc1fb742611718618572747d6f3d6532b7b78755ab",
- "sha256:4ba654c6b5ad1ae4a4d792abeb695b29ce981bb0f157a41d0fd227b385f2bef0",
- "sha256:5ba766433c30d703f6b2c17eb0b6826c6f898e5f58d89373e235f07764952314",
- "sha256:a59d58ee85b11f337b54933e8d758b2356fcdcc493248e004c9c5e5d11eedbe4",
- "sha256:a6e35d28900cf87bcc11e6ca9e474db0099b78f0be0a41d95bef02d49101b5b2",
- "sha256:b4df7ca9c01018a51e43937eaa41f2f5dce17a6382fda0086403bcb1f5c2cf8e",
- "sha256:bbd5a6bffd3ba8bfe75b16b5e28af15265538e8be011b0b9fddc7d86a453fd4a",
- "sha256:d870f399fcd58a1889e93008762a3b9a27cf7ea512818fc6e689f59495648355",
- "sha256:e9404e2e19e901121c3c5c6cffd5a8ae0d1d67919c970e3b3262231175713068"
- ],
- "index": "pypi",
- "version": "==4.3.1"
- },
- "pillow": {
- "hashes": [
- "sha256:00633bc2ec40313f4daf351855e506d296ec3c553f21b66720d0f1225ca84c6f",
- "sha256:03514478db61b034fc5d38b9bf060f994e5916776e93f02e59732a8270069c61",
- "sha256:040144ba422216aecf7577484865ade90e1a475f867301c48bf9fbd7579efd76",
- "sha256:16246261ff22368e5e32ad74d5ef40403ab6895171a7fc6d34f6c17cfc0f1943",
- "sha256:1cb38df69362af35c14d4a50123b63c7ff18ec9a6d4d5da629a6f19d05e16ba8",
- "sha256:2400e122f7b21d9801798207e424cbe1f716cee7314cd0c8963fdb6fc564b5fb",
- "sha256:2ee6364b270b56a49e8b8a51488e847ab130adc1220c171bed6818c0d4742455",
- "sha256:3b4560c3891b05022c464b09121bd507c477505a4e19d703e1027a3a7c68d896",
- "sha256:41374a6afb3f44794410dab54a0d7175e6209a5a02d407119c81083f1a4c1841",
- "sha256:438a3faf5f702c8d0f80b9f9f9b8382cfa048ca6a0d64ef71b86b563b0ee0359",
- "sha256:472a124c640bde4d5468f6991c9fa7e30b723d84ac4195a77c6ab6aea30f2b9c",
- "sha256:4d32c8e3623a61d6e29ccd024066cd1ba556555abfb4cd714155020e00107e3f",
- "sha256:4d8077fd649ac40a5c4165f2c22fa2a4ad18c668e271ecb2f9d849d1017a9313",
- "sha256:62ec7ae98357fcd46002c110bb7cad15fce532776f0cbe7ca1d44c49b837d49d",
- "sha256:6c7cab6a05351cf61e469937c49dbf3cdf5ffb3eeac71f8d22dc9be3507598d8",
- "sha256:6eca36905444c4b91fe61f1b9933a47a30480738a1dd26501ff67d94fc2bc112",
- "sha256:74e2ebfd19c16c28ad43b8a28ff73b904ed382ea4875188838541751986e8c9a",
- "sha256:7673e7473a13107059377c96c563aa36f73184c29d2926882e0a0210b779a1e7",
- "sha256:81762cf5fca9a82b53b7b2d0e6b420e0f3b06167b97678c81d00470daa622d58",
- "sha256:8554bbeb4218d9cfb1917c69e6f2d2ad0be9b18a775d2162547edf992e1f5f1f",
- "sha256:9b66e968da9c4393f5795285528bc862c7b97b91251f31a08004a3c626d18114",
- "sha256:a00edb2dec0035e98ac3ec768086f0b06dfabb4ad308592ede364ef573692f55",
- "sha256:b48401752496757e95304a46213c3155bc911ac884bed2e9b275ce1c1df3e293",
- "sha256:b6cf18f9e653a8077522bb3aa753a776b117e3e0cc872c25811cfdf1459491c2",
- "sha256:bb8adab1877e9213385cbb1adc297ed8337e01872c42a30cfaa66ff8c422779c",
- "sha256:c8a4b39ba380b57a31a4b5449a9d257b1302d8bc4799767e645dcee25725efe1",
- "sha256:cee9bc75bff455d317b6947081df0824a8f118de2786dc3d74a3503fd631f4ef",
- "sha256:d0dc1313dff48af64517cbbd85e046d6b477fbe5e9d69712801f024dcb08c62b",
- "sha256:d5bf527ed83617edd1855a5c923eeeaf68bcb9ac0ceb28e3f19b575b3a424984",
- "sha256:df5863a21f91de5ecdf7d32a32f406dd9867ebb35d41033b8bd9607a21887599",
- "sha256:e39142332541ed2884c257495504858b22c078a5d781059b07aba4c3a80d7551",
- "sha256:e52e8f675ba0b2b417fa98579e7286a41a8e23871f17f4793772f5aa884fea79",
- "sha256:e6dd55d5d94b9e36929325dd0c9ab85bfde84a5fc35947c334c32af1af668944",
- "sha256:e87cc1acbebf263f308a8494272c2d42016aa33c32bf14d209c81e1f65e11868",
- "sha256:ea0091cd4100519cedfeea2c659f52291f535ac6725e2368bcf59e874f270efa",
- "sha256:eeb247f4f4d962942b3b555530b0c63b77473c7bfe475e51c6b75b7344b49ce3",
- "sha256:f0d4433adce6075efd24fc0285135248b0b50f5a58129c7e552030e04fe45c7f",
- "sha256:f1f3bd92f8e12dc22884935a73c9f94c4d9bd0d34410c456540713d6b7832b8c",
- "sha256:f42a87cbf50e905f49f053c0b1fb86c911c730624022bf44c8857244fc4cdaca",
- "sha256:f5f302db65e2e0ae96e26670818157640d3ca83a3054c290eff3631598dcf819",
- "sha256:f7634d534662bbb08976db801ba27a112aee23e597eeaf09267b4575341e45bf",
- "sha256:fdd374c02e8bb2d6468a85be50ea66e1c4ef9e809974c30d8576728473a6ed03",
- "sha256:fe6931db24716a0845bd8c8915bd096b77c2a7043e6fc59ae9ca364fe816f08b"
- ],
- "index": "pypi",
- "version": "==5.1.0"
- },
- "pycares": {
- "hashes": [
- "sha256:0e81c971236bb0767354f1456e67ab6ae305f248565ce77cd413a311f9572bf5",
- "sha256:11c0ff3ccdb5a838cbd59a4e59df35d31355a80a61393bca786ca3b44569ba10",
- "sha256:170d62bd300999227e64da4fa85459728cc96e62e44780bbc86a915fdae01f78",
- "sha256:36f4c03df57c41a87eb3d642201684eb5a8bc194f4bafaa9f60ee6dc0aef8e40",
- "sha256:371ce688776da984c4105c8ca760cc60944b9b49ccf8335c71dc7669335e6173",
- "sha256:3a2234516f7db495083d8bba0ccdaabae587e62cfcd1b8154d5d0b09d3a48dfc",
- "sha256:3f288586592c697109b2b06e3988b7e17d9765887b5fc367010ee8500cbddc86",
- "sha256:40134cee03c8bbfbc644d4c0bc81796e12dd012a5257fb146c5a5417812ee5f7",
- "sha256:722f5d2c5f78d47b13b0112f6daff43ce4e08e8152319524d14f1f917cc5125e",
- "sha256:7b18fab0ed534a898552df91bc804bd62bb3a2646c11e054baca14d23663e1d6",
- "sha256:8a39d03bd99ea191f86b990ef67ecce878d6bf6518c5cde9173fb34fb36beb5e",
- "sha256:8ea263de8bf1a30b0d87150b4aa0e3203cf93bc1723ea3e7408a7d25e1299217",
- "sha256:943e2dc67ff45ab4c81d628c959837d01561d7e185080ab7a276b8ca67573fb5",
- "sha256:9d56a54c93e64b30c0d31f394d9890f175edec029cd846221728f99263cdee82",
- "sha256:b95b339c11d824f0bb789d31b91c8534916fcbdce248cccce216fa2630bb8a90",
- "sha256:bbfd9aba1e172cd2ab7b7142d49b28cf44d6451c4a66a870aff1dc3cb84849c7",
- "sha256:d8637bcc2f901aa61ec1d754abc862f9f145cb0346a0249360df4c159377018e",
- "sha256:e2446577eeea79d2179c9469d9d4ce3ab8a07d7985465c3cb91e7d74abc329b6",
- "sha256:e72fa163f37ae3b09f143cc6690a36f012d13e905d142e1beed4ec0e593ff657",
- "sha256:f32b7c63094749fbc0c1106c9a785666ec8afd49ecfe7002a30bb7c42e62b47c",
- "sha256:f50be4dd53f009cfb4b98c3c6b240e18ff9b17e3f1c320bd594bb83eddabfcb2"
- ],
- "version": "==2.3.0"
- },
- "python-json-logger": {
- "hashes": [
- "sha256:30999d1d742ecf6645991a2ce9273188505e98b713ad63be06aabff47dd1b3c4",
- "sha256:8205cfe7061715de5cd1b37e3565d5b97d0ac13b30ff3ee612554abb6093d640"
- ],
- "version": "==0.1.8"
- },
- "python-levenshtein": {
- "hashes": [
- "sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
- ],
- "index": "pypi",
- "version": "==0.12.0"
- },
- "sympy": {
- "hashes": [
- "sha256:ac5b57691bc43919dcc21167660a57cc51797c28a4301a6144eff07b751216a4"
- ],
- "index": "pypi",
- "version": "==1.1.1"
- },
- "urllib3": {
- "hashes": [
- "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
- "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
- ],
- "version": "==1.22"
- },
- "websockets": {
- "hashes": [
- "sha256:0c31bc832d529dc7583d324eb6c836a4f362032a1902723c112cf57883488d8c",
- "sha256:1f3e5a52cab6daa3d432c7b0de0a14109be39d2bfaad033ee5de4a3d3e11dcdf",
- "sha256:341824d8c9ad53fc43cca3fa9407f294125fa258592f7676640396501448e57e",
- "sha256:367ff945bc0950ad9634591e2afe50bf2222bc4fad1088a386c4bb700888026e",
- "sha256:3859ca16c229ddb0fa21c5090e4efcb037c08ce69b0c1dfed6122c3f98cd0c22",
- "sha256:3d425ae081fb4ba1eef9ecf30472ffd79f8e868297ccc7a47993c96dbf2a819c",
- "sha256:64896a6b3368c959b8096b655e46f03dfa65b96745249f374bd6a35705cc3489",
- "sha256:6df87698022aef2596bffdfecc96d656db59c8d719708c8a471daa815ee61656",
- "sha256:80188abdadd23edaaea05ce761dc9a2e1df31a74a0533967f0dcd9560c85add0",
- "sha256:d1a0572b6edb22c9208e3e5381064e09d287d2a915f90233fef994ee7a14a935",
- "sha256:da4d4fbe059b0453e726d6d993760065d69b823a27efc3040402a6fcfe6a1ed9",
- "sha256:da7610a017f5343fdf765f4e0eb6fd0dfd08264ca1565212b110836d9367fc9c",
- "sha256:ebdd4f18fe7e3bea9bd3bf446b0f4117739478caa2c76e4f0fb72cc45b03cbd7",
- "sha256:f5192da704535a7cbf76d6e99c1ec4af7e8d1288252bf5a2385d414509ded0cf",
- "sha256:fd81af8cf3e69f9a97f3a6c0623a0527de0f922c2df725f00cd7646d478af632",
- "sha256:fecf51c13195c416c22422353b306dddb9c752e4b80b21e0fa1fccbe38246677"
- ],
- "index": "pypi",
- "version": "==4.0.1"
- },
- "yarl": {
- "hashes": [
- "sha256:045dbba18c9142278113d5dc62622978a6f718ba662392d406141c59b540c514",
- "sha256:17e57a495efea42bcfca08b49e16c6d89e003acd54c99c903ea1cb3de0ba1248",
- "sha256:213e8f54b4a942532d6ac32314c69a147d3b82fa1725ca05061b7c1a19a1d9b1",
- "sha256:3353fae45d93cc3e7e41bfcb1b633acc37db821d368e660b03068dbfcf68f8c8",
- "sha256:51a084ff8756811101f8b5031a14d1c2dd26c666976e1b18579c6b1c8761a102",
- "sha256:5580f22ac1298261cd24e8e584180d83e2cca9a6167113466d2d16cb2aa1f7b1",
- "sha256:64727a2593fdba5d6ef69e94eba793a196deeda7152c7bd3a64edda6b1f95f6e",
- "sha256:6e75753065c310befab71c5077a59b7cb638d2146b1cfbb1c3b8f08b51362714",
- "sha256:7236eba4911a5556b497235828e7a4bc5d90957efa63b7c4b3e744d2d2cf1b94",
- "sha256:a69dd7e262cdb265ac7d5e929d55f2f3d07baaadd158c8f19caebf8dde08dfe8",
- "sha256:d9ca55a5a297408f08e5401c23ad22bd9f580dab899212f0d5dc1830f0909404",
- "sha256:e072edbd1c5628c0b8f97d00cf6c9fcd6a4ee2b5ded10d463fcb6eaa066cf40c",
- "sha256:e9a6a319c4bbfb57618f207e86a7c519ab0f637be3d2366e4cdac271577834b8"
- ],
- "index": "pypi",
- "version": "==1.1.1"
- }
- },
- "develop": {
- "attrs": {
- "hashes": [
- "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
- "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
- ],
- "version": "==18.1.0"
- },
- "bandit": {
- "hashes": [
- "sha256:cb977045497f83ec3a02616973ab845c829cdab8144ce2e757fe031104a9abd4",
- "sha256:de4cc19d6ba32d6f542c6a1ddadb4404571347d83ef1ed1e7afb7d0b38e0c25b"
- ],
- "version": "==1.4.0"
- },
- "certifi": {
- "hashes": [
- "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
- "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
- ],
- "version": "==2018.4.16"
- },
- "chardet": {
- "hashes": [
- "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
- "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
- ],
- "version": "==3.0.4"
- },
- "click": {
- "hashes": [
- "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
- "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
- ],
- "version": "==6.7"
- },
- "dodgy": {
- "hashes": [
- "sha256:65e13cf878d7aff129f1461c13cb5fd1bb6dfe66bb5327e09379c3877763280c"
- ],
- "index": "pypi",
- "version": "==0.1.9"
- },
- "dparse": {
- "hashes": [
- "sha256:00a5fdfa900629e5159bf3600d44905b333f4059a3366f28e0dbd13eeab17b19",
- "sha256:cef95156fa0adedaf042cd42f9990974bec76f25dfeca4dc01f381a243d5aa5b"
- ],
- "version": "==0.4.1"
- },
- "flake8": {
- "hashes": [
- "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
- "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
- ],
- "index": "pypi",
- "version": "==3.5.0"
- },
- "flake8-bandit": {
- "hashes": [
- "sha256:a66c7b42af9530d5e988851ccee02958a51a85d46f1f4609ecc3546948f809b8",
- "sha256:f7c3421fd9aebc63689c0693511e16dcad678fd4a0ce624b78ca91ae713eacdc"
- ],
- "index": "pypi",
- "version": "==1.0.2"
- },
- "flake8-bugbear": {
- "hashes": [
- "sha256:541746f0f3b2f1a8d7278e1d2d218df298996b60b02677708560db7c7e620e3b",
- "sha256:5f14a99d458e29cb92be9079c970030e0dd398b2decb179d76d39a5266ea1578"
- ],
- "index": "pypi",
- "version": "==18.2.0"
- },
- "flake8-import-order": {
- "hashes": [
- "sha256:40d2a39ed91e080f3285f4c16256b252d7c31070e7f11b7854415bb9f924ea81",
- "sha256:68d430781a9ef15c85a0121500cf8462f1a4bc7672acb2a32bfdbcab044ae0b7"
- ],
- "index": "pypi",
- "version": "==0.17.1"
- },
- "flake8-polyfill": {
- "hashes": [
- "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9",
- "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"
- ],
- "version": "==1.0.2"
- },
- "flake8-string-format": {
- "hashes": [
- "sha256:68ea72a1a5b75e7018cae44d14f32473c798cf73d75cbaed86c6a9a907b770b2",
- "sha256:774d56103d9242ed968897455ef49b7d6de272000cfa83de5814273a868832f1"
- ],
- "index": "pypi",
- "version": "==0.2.3"
- },
- "flake8-tidy-imports": {
- "hashes": [
- "sha256:5fc28c82bba16abb4f1154dc59a90487f5491fbdb27e658cbee241e8fddc1b91",
- "sha256:c05c9f7dadb5748a04b6fa1c47cb6ae5a8170f03cfb1dca8b37aec58c1ee6d15"
- ],
- "index": "pypi",
- "version": "==1.1.0"
- },
- "flake8-todo": {
- "hashes": [
- "sha256:6e4c5491ff838c06fe5a771b0e95ee15fc005ca57196011011280fc834a85915"
- ],
- "index": "pypi",
- "version": "==0.7"
- },
- "gitdb2": {
- "hashes": [
- "sha256:b60e29d4533e5e25bb50b7678bbc187c8f6bcff1344b4f293b2ba55c85795f09",
- "sha256:cf9a4b68e8c4da8d42e48728c944ff7af2d8c9db303ac1ab32eac37aa4194b0e"
- ],
- "version": "==2.0.3"
- },
- "gitpython": {
- "hashes": [
- "sha256:1ec4c44846cf76a1e55769560673a97731849c9d05401e035e607495f10db959",
- "sha256:b60b045cf64a321e5b620debb49890099fa6c7be6dfb7fb249027e5d34227301"
- ],
- "version": "==2.1.10"
- },
- "idna": {
- "hashes": [
- "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
- "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
- ],
- "version": "==2.6"
- },
- "mccabe": {
- "hashes": [
- "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
- "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
- ],
- "version": "==0.6.1"
- },
- "packaging": {
- "hashes": [
- "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
- "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
- ],
- "version": "==17.1"
- },
- "pbr": {
- "hashes": [
- "sha256:680bf5ba9b28dd56e08eb7c267991a37c7a5f90a92c2e07108829931a50ff80a",
- "sha256:6874feb22334a1e9a515193cba797664e940b763440c88115009ec323a7f2df5"
- ],
- "version": "==4.0.3"
- },
- "pycodestyle": {
- "hashes": [
- "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
- "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
- ],
- "version": "==2.3.1"
- },
- "pyflakes": {
- "hashes": [
- "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
- "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
- ],
- "version": "==1.6.0"
- },
- "pyparsing": {
- "hashes": [
- "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
- "sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
- "sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
- "sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
- "sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
- "sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58",
- "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
- ],
- "version": "==2.2.0"
- },
- "pyyaml": {
- "hashes": [
- "sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8",
- "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736",
- "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f",
- "sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608",
- "sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8",
- "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab",
- "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7",
- "sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3",
- "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1",
- "sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6",
- "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8",
- "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4",
- "sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca",
- "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269"
- ],
- "version": "==3.12"
- },
- "requests": {
- "hashes": [
- "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
- "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
- ],
- "index": "pypi",
- "version": "==2.18.4"
- },
- "safety": {
- "hashes": [
- "sha256:0bd2a26b872668767c6db8efecfc8869b547463bedff5e7cd7b52f037aa6f200",
- "sha256:fc3fc55656f1c909d65311b49a38211c42c937f57a05393289fb3f17cadfa4a1"
- ],
- "index": "pypi",
- "version": "==1.8.1"
- },
- "six": {
- "hashes": [
- "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
- "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
- ],
- "version": "==1.11.0"
- },
- "smmap2": {
- "hashes": [
- "sha256:b78ee0f1f5772d69ff50b1cbdb01b8c6647a8354f02f23b488cf4b2cfc923956",
- "sha256:c7530db63f15f09f8251094b22091298e82bf6c699a6b8344aaaef3f2e1276c3"
- ],
- "version": "==2.0.3"
- },
- "stevedore": {
- "hashes": [
- "sha256:e3d96b2c4e882ec0c1ff95eaebf7b575a779fd0ccb4c741b9832bed410d58b3d",
- "sha256:f1c7518e7b160336040fee272174f1f7b29a46febb3632502a8f2055f973d60b"
- ],
- "version": "==1.28.0"
- },
- "urllib3": {
- "hashes": [
- "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
- "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
- ],
- "version": "==1.22"
- }
- }
-}
+{ + "_meta": { + "hash": { + "sha256": "4d01fc2c93d23bfdeddf3dc68c723aa7eb8598380c5fdb0a6bf67a39f84b03a6" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aiodns": { + "hashes": [ + "sha256:99d0652f2c02f73bfa646bf44af82705260a523014576647d7959e664830b26b", + "sha256:d8677adc679ce8d0ef706c14d9c3d2f27a0e0cc11d59730cdbaf218ad52dd9ea" + ], + "index": "pypi", + "version": "==1.1.1" + }, + "aiohttp": { + "hashes": [ + "sha256:129d83dd067760cec3cfd4456b5c6d7ac29f2c639d856884568fd539bed5a51f", + "sha256:33c62afd115c456b0cf1e890fe6753055effe0f31a28321efd4f787378d6f4ab", + "sha256:666756e1d4cf161ed1486b82f65fdd386ac07dd20fb10f025abf4be54be12746", + "sha256:9705ded5a0faa25c8f14c6afb7044002d66c9120ed7eadb4aa9ca4aad32bd00c", + "sha256:af5bfdd164256118a0a306b3f7046e63207d1f8cba73a67dcc0bd858dcfcd3bc", + "sha256:b80f44b99fa3c9b4530fcfa324a99b84843043c35b084e0b653566049974435d", + "sha256:c67e105ec74b85c8cb666b6877569dee6f55b9548f982983b9bee80b3d47e6f3", + "sha256:d15c6658de5b7783c2538407278fa062b079a46d5f814a133ae0f09bbb2cfbc4", + "sha256:d611ebd1ef48498210b65486306e065fde031040a1f3c455ca1b6baa7bf32ad3", + "sha256:dcc7e4dcec6b0012537b9f8a0726f8b111188894ab0f924b680d40b13d3298a0", + "sha256:de8ef106e130b94ca143fdfc6f27cda1d8ba439462542377738af4d99d9f5dd2", + "sha256:eb6f1405b607fff7e44168e3ceb5d3c8a8c5a2d3effe0a27f843b16ec047a6d7", + "sha256:f0e2ac69cb709367400008cebccd5d48161dd146096a009a632a132babe5714c" + ], + "index": "pypi", + "version": "==2.2.5" + }, + "async-timeout": { + "hashes": [ + "sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c", + "sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287" + ], + "version": "==3.0.0" + }, + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "discord": { + "egg": "discord.py[voice]", + "file": "https://github.com/Rapptz/discord.py/archive/rewrite.zip" + }, + "dulwich": { + "hashes": [ + "sha256:c51e10c260543240e0806052af046e1a78b98cbe1ac1ef3880a78d2269e09da4" + ], + "index": "pypi", + "version": "==0.19.2" + }, + "fuzzywuzzy": { + "hashes": [ + "sha256:d40c22d2744dff84885b30bbfc07fab7875f641d070374331777a4d1808b8d4e", + "sha256:ecf490216fb4d76b558a03042ff8f45a8782f17326caca1384d834cbaa2c7e6f" + ], + "index": "pypi", + "version": "==0.16.0" + }, + "idna": { + "hashes": [ + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + ], + "version": "==2.6" + }, + "logmatic-python": { + "hashes": [ + "sha256:0c15ac9f5faa6a60059b28910db642c3dc7722948c3cc940923f8c9039604342" + ], + "index": "pypi", + "version": "==0.1.7" + }, + "mpmath": { + "hashes": [ + "sha256:04d14803b6875fe6d69e6dccea87d5ae5599802e4b1df7997bddd2024001050c" + ], + "version": "==1.0.0" + }, + "multidict": { + "hashes": [ + "sha256:1a1d76374a1e7fe93acef96b354a03c1d7f83e7512e225a527d283da0d7ba5e0", + "sha256:1d6e191965505652f194bc4c40270a842922685918a4f45e6936a6b15cc5816d", + "sha256:295961a6a88f1199e19968e15d9b42f3a191c89ec13034dbc212bf9c394c3c82", + "sha256:2be5af084de6c3b8e20d6421cb0346378a9c867dcf7c86030d6b0b550f9888e4", + "sha256:2eb99617c7a0e9f2b90b64bc1fb742611718618572747d6f3d6532b7b78755ab", + "sha256:4ba654c6b5ad1ae4a4d792abeb695b29ce981bb0f157a41d0fd227b385f2bef0", + "sha256:5ba766433c30d703f6b2c17eb0b6826c6f898e5f58d89373e235f07764952314", + "sha256:a59d58ee85b11f337b54933e8d758b2356fcdcc493248e004c9c5e5d11eedbe4", + "sha256:a6e35d28900cf87bcc11e6ca9e474db0099b78f0be0a41d95bef02d49101b5b2", + "sha256:b4df7ca9c01018a51e43937eaa41f2f5dce17a6382fda0086403bcb1f5c2cf8e", + "sha256:bbd5a6bffd3ba8bfe75b16b5e28af15265538e8be011b0b9fddc7d86a453fd4a", + "sha256:d870f399fcd58a1889e93008762a3b9a27cf7ea512818fc6e689f59495648355", + "sha256:e9404e2e19e901121c3c5c6cffd5a8ae0d1d67919c970e3b3262231175713068" + ], + "index": "pypi", + "version": "==4.3.1" + }, + "pillow": { + "hashes": [ + "sha256:00633bc2ec40313f4daf351855e506d296ec3c553f21b66720d0f1225ca84c6f", + "sha256:03514478db61b034fc5d38b9bf060f994e5916776e93f02e59732a8270069c61", + "sha256:040144ba422216aecf7577484865ade90e1a475f867301c48bf9fbd7579efd76", + "sha256:16246261ff22368e5e32ad74d5ef40403ab6895171a7fc6d34f6c17cfc0f1943", + "sha256:1cb38df69362af35c14d4a50123b63c7ff18ec9a6d4d5da629a6f19d05e16ba8", + "sha256:2400e122f7b21d9801798207e424cbe1f716cee7314cd0c8963fdb6fc564b5fb", + "sha256:2ee6364b270b56a49e8b8a51488e847ab130adc1220c171bed6818c0d4742455", + "sha256:3b4560c3891b05022c464b09121bd507c477505a4e19d703e1027a3a7c68d896", + "sha256:41374a6afb3f44794410dab54a0d7175e6209a5a02d407119c81083f1a4c1841", + "sha256:438a3faf5f702c8d0f80b9f9f9b8382cfa048ca6a0d64ef71b86b563b0ee0359", + "sha256:472a124c640bde4d5468f6991c9fa7e30b723d84ac4195a77c6ab6aea30f2b9c", + "sha256:4d32c8e3623a61d6e29ccd024066cd1ba556555abfb4cd714155020e00107e3f", + "sha256:4d8077fd649ac40a5c4165f2c22fa2a4ad18c668e271ecb2f9d849d1017a9313", + "sha256:62ec7ae98357fcd46002c110bb7cad15fce532776f0cbe7ca1d44c49b837d49d", + "sha256:6c7cab6a05351cf61e469937c49dbf3cdf5ffb3eeac71f8d22dc9be3507598d8", + "sha256:6eca36905444c4b91fe61f1b9933a47a30480738a1dd26501ff67d94fc2bc112", + "sha256:74e2ebfd19c16c28ad43b8a28ff73b904ed382ea4875188838541751986e8c9a", + "sha256:7673e7473a13107059377c96c563aa36f73184c29d2926882e0a0210b779a1e7", + "sha256:81762cf5fca9a82b53b7b2d0e6b420e0f3b06167b97678c81d00470daa622d58", + "sha256:8554bbeb4218d9cfb1917c69e6f2d2ad0be9b18a775d2162547edf992e1f5f1f", + "sha256:9b66e968da9c4393f5795285528bc862c7b97b91251f31a08004a3c626d18114", + "sha256:a00edb2dec0035e98ac3ec768086f0b06dfabb4ad308592ede364ef573692f55", + "sha256:b48401752496757e95304a46213c3155bc911ac884bed2e9b275ce1c1df3e293", + "sha256:b6cf18f9e653a8077522bb3aa753a776b117e3e0cc872c25811cfdf1459491c2", + "sha256:bb8adab1877e9213385cbb1adc297ed8337e01872c42a30cfaa66ff8c422779c", + "sha256:c8a4b39ba380b57a31a4b5449a9d257b1302d8bc4799767e645dcee25725efe1", + "sha256:cee9bc75bff455d317b6947081df0824a8f118de2786dc3d74a3503fd631f4ef", + "sha256:d0dc1313dff48af64517cbbd85e046d6b477fbe5e9d69712801f024dcb08c62b", + "sha256:d5bf527ed83617edd1855a5c923eeeaf68bcb9ac0ceb28e3f19b575b3a424984", + "sha256:df5863a21f91de5ecdf7d32a32f406dd9867ebb35d41033b8bd9607a21887599", + "sha256:e39142332541ed2884c257495504858b22c078a5d781059b07aba4c3a80d7551", + "sha256:e52e8f675ba0b2b417fa98579e7286a41a8e23871f17f4793772f5aa884fea79", + "sha256:e6dd55d5d94b9e36929325dd0c9ab85bfde84a5fc35947c334c32af1af668944", + "sha256:e87cc1acbebf263f308a8494272c2d42016aa33c32bf14d209c81e1f65e11868", + "sha256:ea0091cd4100519cedfeea2c659f52291f535ac6725e2368bcf59e874f270efa", + "sha256:eeb247f4f4d962942b3b555530b0c63b77473c7bfe475e51c6b75b7344b49ce3", + "sha256:f0d4433adce6075efd24fc0285135248b0b50f5a58129c7e552030e04fe45c7f", + "sha256:f1f3bd92f8e12dc22884935a73c9f94c4d9bd0d34410c456540713d6b7832b8c", + "sha256:f42a87cbf50e905f49f053c0b1fb86c911c730624022bf44c8857244fc4cdaca", + "sha256:f5f302db65e2e0ae96e26670818157640d3ca83a3054c290eff3631598dcf819", + "sha256:f7634d534662bbb08976db801ba27a112aee23e597eeaf09267b4575341e45bf", + "sha256:fdd374c02e8bb2d6468a85be50ea66e1c4ef9e809974c30d8576728473a6ed03", + "sha256:fe6931db24716a0845bd8c8915bd096b77c2a7043e6fc59ae9ca364fe816f08b" + ], + "index": "pypi", + "version": "==5.1.0" + }, + "pycares": { + "hashes": [ + "sha256:0e81c971236bb0767354f1456e67ab6ae305f248565ce77cd413a311f9572bf5", + "sha256:11c0ff3ccdb5a838cbd59a4e59df35d31355a80a61393bca786ca3b44569ba10", + "sha256:170d62bd300999227e64da4fa85459728cc96e62e44780bbc86a915fdae01f78", + "sha256:36f4c03df57c41a87eb3d642201684eb5a8bc194f4bafaa9f60ee6dc0aef8e40", + "sha256:371ce688776da984c4105c8ca760cc60944b9b49ccf8335c71dc7669335e6173", + "sha256:3a2234516f7db495083d8bba0ccdaabae587e62cfcd1b8154d5d0b09d3a48dfc", + "sha256:3f288586592c697109b2b06e3988b7e17d9765887b5fc367010ee8500cbddc86", + "sha256:40134cee03c8bbfbc644d4c0bc81796e12dd012a5257fb146c5a5417812ee5f7", + "sha256:722f5d2c5f78d47b13b0112f6daff43ce4e08e8152319524d14f1f917cc5125e", + "sha256:7b18fab0ed534a898552df91bc804bd62bb3a2646c11e054baca14d23663e1d6", + "sha256:8a39d03bd99ea191f86b990ef67ecce878d6bf6518c5cde9173fb34fb36beb5e", + "sha256:8ea263de8bf1a30b0d87150b4aa0e3203cf93bc1723ea3e7408a7d25e1299217", + "sha256:943e2dc67ff45ab4c81d628c959837d01561d7e185080ab7a276b8ca67573fb5", + "sha256:9d56a54c93e64b30c0d31f394d9890f175edec029cd846221728f99263cdee82", + "sha256:b95b339c11d824f0bb789d31b91c8534916fcbdce248cccce216fa2630bb8a90", + "sha256:bbfd9aba1e172cd2ab7b7142d49b28cf44d6451c4a66a870aff1dc3cb84849c7", + "sha256:d8637bcc2f901aa61ec1d754abc862f9f145cb0346a0249360df4c159377018e", + "sha256:e2446577eeea79d2179c9469d9d4ce3ab8a07d7985465c3cb91e7d74abc329b6", + "sha256:e72fa163f37ae3b09f143cc6690a36f012d13e905d142e1beed4ec0e593ff657", + "sha256:f32b7c63094749fbc0c1106c9a785666ec8afd49ecfe7002a30bb7c42e62b47c", + "sha256:f50be4dd53f009cfb4b98c3c6b240e18ff9b17e3f1c320bd594bb83eddabfcb2" + ], + "version": "==2.3.0" + }, + "python-json-logger": { + "hashes": [ + "sha256:30999d1d742ecf6645991a2ce9273188505e98b713ad63be06aabff47dd1b3c4", + "sha256:8205cfe7061715de5cd1b37e3565d5b97d0ac13b30ff3ee612554abb6093d640" + ], + "version": "==0.1.8" + }, + "python-levenshtein": { + "hashes": [ + "sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1" + ], + "index": "pypi", + "version": "==0.12.0" + }, + "pyyaml": { + "hashes": [ + "sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8", + "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736", + "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f", + "sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608", + "sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8", + "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab", + "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7", + "sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3", + "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1", + "sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6", + "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8", + "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4", + "sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca", + "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269" + ], + "index": "pypi", + "version": "==3.12" + }, + "sympy": { + "hashes": [ + "sha256:ac5b57691bc43919dcc21167660a57cc51797c28a4301a6144eff07b751216a4" + ], + "index": "pypi", + "version": "==1.1.1" + }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" + }, + "websockets": { + "hashes": [ + "sha256:0c31bc832d529dc7583d324eb6c836a4f362032a1902723c112cf57883488d8c", + "sha256:1f3e5a52cab6daa3d432c7b0de0a14109be39d2bfaad033ee5de4a3d3e11dcdf", + "sha256:341824d8c9ad53fc43cca3fa9407f294125fa258592f7676640396501448e57e", + "sha256:367ff945bc0950ad9634591e2afe50bf2222bc4fad1088a386c4bb700888026e", + "sha256:3859ca16c229ddb0fa21c5090e4efcb037c08ce69b0c1dfed6122c3f98cd0c22", + "sha256:3d425ae081fb4ba1eef9ecf30472ffd79f8e868297ccc7a47993c96dbf2a819c", + "sha256:64896a6b3368c959b8096b655e46f03dfa65b96745249f374bd6a35705cc3489", + "sha256:6df87698022aef2596bffdfecc96d656db59c8d719708c8a471daa815ee61656", + "sha256:80188abdadd23edaaea05ce761dc9a2e1df31a74a0533967f0dcd9560c85add0", + "sha256:d1a0572b6edb22c9208e3e5381064e09d287d2a915f90233fef994ee7a14a935", + "sha256:da4d4fbe059b0453e726d6d993760065d69b823a27efc3040402a6fcfe6a1ed9", + "sha256:da7610a017f5343fdf765f4e0eb6fd0dfd08264ca1565212b110836d9367fc9c", + "sha256:ebdd4f18fe7e3bea9bd3bf446b0f4117739478caa2c76e4f0fb72cc45b03cbd7", + "sha256:f5192da704535a7cbf76d6e99c1ec4af7e8d1288252bf5a2385d414509ded0cf", + "sha256:fd81af8cf3e69f9a97f3a6c0623a0527de0f922c2df725f00cd7646d478af632", + "sha256:fecf51c13195c416c22422353b306dddb9c752e4b80b21e0fa1fccbe38246677" + ], + "index": "pypi", + "version": "==4.0.1" + }, + "yarl": { + "hashes": [ + "sha256:045dbba18c9142278113d5dc62622978a6f718ba662392d406141c59b540c514", + "sha256:17e57a495efea42bcfca08b49e16c6d89e003acd54c99c903ea1cb3de0ba1248", + "sha256:213e8f54b4a942532d6ac32314c69a147d3b82fa1725ca05061b7c1a19a1d9b1", + "sha256:3353fae45d93cc3e7e41bfcb1b633acc37db821d368e660b03068dbfcf68f8c8", + "sha256:51a084ff8756811101f8b5031a14d1c2dd26c666976e1b18579c6b1c8761a102", + "sha256:5580f22ac1298261cd24e8e584180d83e2cca9a6167113466d2d16cb2aa1f7b1", + "sha256:64727a2593fdba5d6ef69e94eba793a196deeda7152c7bd3a64edda6b1f95f6e", + "sha256:6e75753065c310befab71c5077a59b7cb638d2146b1cfbb1c3b8f08b51362714", + "sha256:7236eba4911a5556b497235828e7a4bc5d90957efa63b7c4b3e744d2d2cf1b94", + "sha256:a69dd7e262cdb265ac7d5e929d55f2f3d07baaadd158c8f19caebf8dde08dfe8", + "sha256:d9ca55a5a297408f08e5401c23ad22bd9f580dab899212f0d5dc1830f0909404", + "sha256:e072edbd1c5628c0b8f97d00cf6c9fcd6a4ee2b5ded10d463fcb6eaa066cf40c", + "sha256:e9a6a319c4bbfb57618f207e86a7c519ab0f637be3d2366e4cdac271577834b8" + ], + "index": "pypi", + "version": "==1.1.1" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", + "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" + ], + "version": "==18.1.0" + }, + "bandit": { + "hashes": [ + "sha256:cb977045497f83ec3a02616973ab845c829cdab8144ce2e757fe031104a9abd4", + "sha256:de4cc19d6ba32d6f542c6a1ddadb4404571347d83ef1ed1e7afb7d0b38e0c25b" + ], + "version": "==1.4.0" + }, + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "version": "==6.7" + }, + "dodgy": { + "hashes": [ + "sha256:65e13cf878d7aff129f1461c13cb5fd1bb6dfe66bb5327e09379c3877763280c" + ], + "index": "pypi", + "version": "==0.1.9" + }, + "dparse": { + "hashes": [ + "sha256:00a5fdfa900629e5159bf3600d44905b333f4059a3366f28e0dbd13eeab17b19", + "sha256:cef95156fa0adedaf042cd42f9990974bec76f25dfeca4dc01f381a243d5aa5b" + ], + "version": "==0.4.1" + }, + "flake8": { + "hashes": [ + "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", + "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" + ], + "index": "pypi", + "version": "==3.5.0" + }, + "flake8-bandit": { + "hashes": [ + "sha256:a66c7b42af9530d5e988851ccee02958a51a85d46f1f4609ecc3546948f809b8", + "sha256:f7c3421fd9aebc63689c0693511e16dcad678fd4a0ce624b78ca91ae713eacdc" + ], + "index": "pypi", + "version": "==1.0.2" + }, + "flake8-bugbear": { + "hashes": [ + "sha256:541746f0f3b2f1a8d7278e1d2d218df298996b60b02677708560db7c7e620e3b", + "sha256:5f14a99d458e29cb92be9079c970030e0dd398b2decb179d76d39a5266ea1578" + ], + "index": "pypi", + "version": "==18.2.0" + }, + "flake8-import-order": { + "hashes": [ + "sha256:40d2a39ed91e080f3285f4c16256b252d7c31070e7f11b7854415bb9f924ea81", + "sha256:68d430781a9ef15c85a0121500cf8462f1a4bc7672acb2a32bfdbcab044ae0b7" + ], + "index": "pypi", + "version": "==0.17.1" + }, + "flake8-polyfill": { + "hashes": [ + "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9", + "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda" + ], + "version": "==1.0.2" + }, + "flake8-string-format": { + "hashes": [ + "sha256:68ea72a1a5b75e7018cae44d14f32473c798cf73d75cbaed86c6a9a907b770b2", + "sha256:774d56103d9242ed968897455ef49b7d6de272000cfa83de5814273a868832f1" + ], + "index": "pypi", + "version": "==0.2.3" + }, + "flake8-tidy-imports": { + "hashes": [ + "sha256:5fc28c82bba16abb4f1154dc59a90487f5491fbdb27e658cbee241e8fddc1b91", + "sha256:c05c9f7dadb5748a04b6fa1c47cb6ae5a8170f03cfb1dca8b37aec58c1ee6d15" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "flake8-todo": { + "hashes": [ + "sha256:6e4c5491ff838c06fe5a771b0e95ee15fc005ca57196011011280fc834a85915" + ], + "index": "pypi", + "version": "==0.7" + }, + "gitdb2": { + "hashes": [ + "sha256:b60e29d4533e5e25bb50b7678bbc187c8f6bcff1344b4f293b2ba55c85795f09", + "sha256:cf9a4b68e8c4da8d42e48728c944ff7af2d8c9db303ac1ab32eac37aa4194b0e" + ], + "version": "==2.0.3" + }, + "gitpython": { + "hashes": [ + "sha256:1ec4c44846cf76a1e55769560673a97731849c9d05401e035e607495f10db959", + "sha256:b60b045cf64a321e5b620debb49890099fa6c7be6dfb7fb249027e5d34227301" + ], + "version": "==2.1.10" + }, + "idna": { + "hashes": [ + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + ], + "version": "==2.6" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "packaging": { + "hashes": [ + "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0", + "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b" + ], + "version": "==17.1" + }, + "pbr": { + "hashes": [ + "sha256:680bf5ba9b28dd56e08eb7c267991a37c7a5f90a92c2e07108829931a50ff80a", + "sha256:6874feb22334a1e9a515193cba797664e940b763440c88115009ec323a7f2df5" + ], + "version": "==4.0.3" + }, + "pycodestyle": { + "hashes": [ + "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", + "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" + ], + "version": "==2.3.1" + }, + "pyflakes": { + "hashes": [ + "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", + "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" + ], + "version": "==1.6.0" + }, + "pyparsing": { + "hashes": [ + "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", + "sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07", + "sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18", + "sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e", + "sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5", + "sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58", + "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" + ], + "version": "==2.2.0" + }, + "pyyaml": { + "hashes": [ + "sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8", + "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736", + "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f", + "sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608", + "sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8", + "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab", + "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7", + "sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3", + "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1", + "sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6", + "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8", + "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4", + "sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca", + "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269" + ], + "index": "pypi", + "version": "==3.12" + }, + "requests": { + "hashes": [ + "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", + "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + ], + "index": "pypi", + "version": "==2.18.4" + }, + "safety": { + "hashes": [ + "sha256:0bd2a26b872668767c6db8efecfc8869b547463bedff5e7cd7b52f037aa6f200", + "sha256:fc3fc55656f1c909d65311b49a38211c42c937f57a05393289fb3f17cadfa4a1" + ], + "index": "pypi", + "version": "==1.8.1" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "smmap2": { + "hashes": [ + "sha256:b78ee0f1f5772d69ff50b1cbdb01b8c6647a8354f02f23b488cf4b2cfc923956", + "sha256:c7530db63f15f09f8251094b22091298e82bf6c699a6b8344aaaef3f2e1276c3" + ], + "version": "==2.0.3" + }, + "stevedore": { + "hashes": [ + "sha256:e3d96b2c4e882ec0c1ff95eaebf7b575a779fd0ccb4c741b9832bed410d58b3d", + "sha256:f1c7518e7b160336040fee272174f1f7b29a46febb3632502a8f2055f973d60b" + ], + "version": "==1.28.0" + }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" + } + } +} diff --git a/bot/__init__.py b/bot/__init__.py index afc16e37f..a8272981c 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -8,8 +8,6 @@ from logging.handlers import SysLogHandler import discord.ext.commands.view from logmatic import JsonFormatter -from bot.constants import PAPERTRAIL_ADDRESS, PAPERTRAIL_PORT - logging.TRACE = 5 logging.addLevelName(logging.TRACE, "TRACE") @@ -32,10 +30,6 @@ Logger.trace = monkeypatch_trace # Set up logging logging_handlers = [] -if PAPERTRAIL_ADDRESS: - papertrail_handler = SysLogHandler(address=(PAPERTRAIL_ADDRESS, PAPERTRAIL_PORT)) - papertrail_handler.setLevel(logging.DEBUG) - logging_handlers.append(papertrail_handler) logging_handlers.append(StreamHandler(stream=sys.stderr)) @@ -52,6 +46,16 @@ logging.basicConfig( log = logging.getLogger(__name__) +# We need to defer the import from `constants.py` +# because otherwise the logging config would not be applied +# to any logging done in the module. +from bot.constants import Papertrail # noqa +if Papertrail.address: + papertrail_handler = SysLogHandler(address=(Papertrail.address, Papertrail.port)) + papertrail_handler.setLevel(logging.DEBUG) + logging.getLogger('bot').addHandler(papertrail_handler) + + # Silence discord and websockets logging.getLogger("discord.client").setLevel(logging.ERROR) logging.getLogger("discord.gateway").setLevel(logging.ERROR) diff --git a/bot/__main__.py b/bot/__main__.py index 60553a0ea..c4e42dd45 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,14 +1,14 @@ import logging -import os import socket from aiohttp import AsyncResolver, ClientSession, TCPConnector from discord import Game from discord.ext.commands import AutoShardedBot, when_mentioned_or -from bot.constants import CLICKUP_KEY +from bot.constants import Bot, ClickUp from bot.formatter import Formatter + log = logging.getLogger(__name__) bot = AutoShardedBot( @@ -48,10 +48,10 @@ bot.load_extension("bot.cogs.cogs") # Local setups usually don't have the clickup key set, # and loading the cog would simply spam errors in the console. -if CLICKUP_KEY is not None: +if ClickUp.key is not None: bot.load_extension("bot.cogs.clickup") else: - log.warning("`CLICKUP_KEY` not set in the environment, not loading the ClickUp cog.") + log.info("`CLICKUP_KEY` not set in the environment, not loading the ClickUp cog.") bot.load_extension("bot.cogs.deployment") bot.load_extension("bot.cogs.eval") @@ -61,6 +61,6 @@ bot.load_extension("bot.cogs.snakes") bot.load_extension("bot.cogs.tags") bot.load_extension("bot.cogs.verification") -bot.run(os.environ.get("BOT_TOKEN")) +bot.run(Bot.token) bot.http_session.close() # Close the aiohttp session when the bot finishes running diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index e1571313f..80cd7d3b0 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -8,10 +8,7 @@ from discord.ext.commands import AutoShardedBot, Context, command, group from dulwich.repo import Repo from bot.constants import ( - ADMIN_ROLE, BOT_AVATAR_URL, BOT_COMMANDS_CHANNEL, - DEVTEST_CHANNEL, HELP1_CHANNEL, HELP2_CHANNEL, - HELP3_CHANNEL, HELP4_CHANNEL, MODERATOR_ROLE, OWNER_ROLE, - PYTHON_CHANNEL, PYTHON_GUILD, VERIFIED_ROLE + Channels, Guild, Roles, URLs ) from bot.decorators import with_role @@ -28,21 +25,21 @@ class Bot: # Stores allowed channels plus epoch time since last call. self.channel_cooldowns = { - HELP1_CHANNEL: 0, - HELP2_CHANNEL: 0, - HELP3_CHANNEL: 0, - HELP4_CHANNEL: 0, - PYTHON_CHANNEL: 0, + Channels.help0: 0, + Channels.help1: 0, + Channels.help2: 0, + Channels.help3: 0, + Channels.python: 0, } # These channels will also work, but will not be subject to cooldown self.channel_whitelist = ( - BOT_COMMANDS_CHANNEL, - DEVTEST_CHANNEL, + Channels.bot, + Channels.devtest, ) @group(invoke_without_command=True, name="bot", hidden=True) - @with_role(VERIFIED_ROLE) + @with_role(Roles.verified) async def bot_group(self, ctx: Context): """ Bot informational commands @@ -51,7 +48,7 @@ class Bot: await ctx.invoke(self.bot.get_command("help"), "bot") @bot_group.command(aliases=["about"], hidden=True) - @with_role(VERIFIED_ROLE) + @with_role(Roles.verified) async def info(self, ctx: Context): """ Get information about the bot @@ -65,20 +62,20 @@ class Bot: repo = Repo(".") sha = repo[repo.head()].sha().hexdigest() - embed.add_field(name="Total Users", value=str(len(self.bot.get_guild(PYTHON_GUILD).members))) + embed.add_field(name="Total Users", value=str(len(self.bot.get_guild(Guild.id).members))) embed.add_field(name="Git SHA", value=str(sha)[:7]) embed.set_author( name="Python Bot", url="https://github.com/discord-python/bot", - icon_url=BOT_AVATAR_URL + icon_url=URLs.bot_avatar ) log.info(f"{ctx.author} called bot.about(). Returning information about the bot.") await ctx.send(embed=embed) @command(name="info()", aliases=["info", "about()", "about"]) - @with_role(VERIFIED_ROLE) + @with_role(Roles.verified) async def info_wrapper(self, ctx: Context): """ Get information about the bot @@ -87,7 +84,7 @@ class Bot: await ctx.invoke(self.info) @command(name="print()", aliases=["print", "echo", "echo()"]) - @with_role(OWNER_ROLE, ADMIN_ROLE, MODERATOR_ROLE) + @with_role(Roles.owner, Roles.admin, Roles.moderator) async def echo_command(self, ctx: Context, text: str): """ Send the input verbatim to the current channel @@ -96,7 +93,7 @@ class Bot: await ctx.send(text) @command(name="embed()", aliases=["embed"]) - @with_role(OWNER_ROLE, ADMIN_ROLE, MODERATOR_ROLE) + @with_role(Roles.owner, Roles.admin, Roles.moderator) async def embed_command(self, ctx: Context, text: str): """ Send the input within an embed to the current channel diff --git a/bot/cogs/clickup.py b/bot/cogs/clickup.py index 19cae596f..a6a3d3caf 100644 --- a/bot/cogs/clickup.py +++ b/bot/cogs/clickup.py @@ -4,9 +4,7 @@ from discord import Colour, Embed from discord.ext.commands import AutoShardedBot, Context, command from multidict import MultiDict -from bot.constants import ( - ADMIN_ROLE, CLICKUP_KEY, CLICKUP_SPACE, CLICKUP_TEAM, CONTRIBUTOR_ROLE, DEVOPS_ROLE, MODERATOR_ROLE, OWNER_ROLE -) +from bot.constants import ClickUp as ClickUpConfig, Roles from bot.decorators import with_role from bot.pagination import LinePaginator from bot.utils import CaseInsensitiveDict @@ -19,7 +17,7 @@ SPACES_URL = "https://api.clickup.com/api/v1/team/{team_id}/space" TEAM_URL = "https://api.clickup.com/api/v1/team/{team_id}" HEADERS = { - "Authorization": CLICKUP_KEY, + "Authorization": ClickUpConfig.key, "Content-Type": "application/json" } @@ -39,7 +37,7 @@ class ClickUp: async def on_ready(self): response = await self.bot.http_session.get( - PROJECTS_URL.format(space_id=CLICKUP_SPACE), headers=HEADERS + PROJECTS_URL.format(space_id=ClickUpConfig.space), headers=HEADERS ) result = await response.json() @@ -56,7 +54,7 @@ class ClickUp: self.lists.update({v: k for k, v in self.lists.items()}) @command(name="clickup.tasks()", aliases=["clickup.tasks", "tasks", "list_tasks"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE, CONTRIBUTOR_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops, Roles.contributor) async def tasks_command(self, ctx: Context, status: str = None, task_list: str = None): """ Get a list of tasks, optionally on a specific list or with a specific status @@ -73,7 +71,7 @@ class ClickUp: embed.set_author( name="ClickUp Tasks", icon_url="https://clickup.com/landing/favicons/favicon-32x32.png", - url=f"https://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/" + url=f"https://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/" ) if task_list: @@ -89,7 +87,7 @@ class ClickUp: params["statuses[]"] = status response = await self.bot.http_session.get( - GET_TASKS_URL.format(team_id=CLICKUP_TEAM), headers=HEADERS, params=params + GET_TASKS_URL.format(team_id=ClickUpConfig.team), headers=HEADERS, params=params ) result = await response.json() @@ -112,7 +110,7 @@ class ClickUp: lines = [] for task in tasks: - task_url = f"http://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/t/{task['id']}" + task_url = f"http://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/t/{task['id']}" id_fragment = f"[`#{task['id']: <5}`]({task_url})" status = f"{task['status']['status'].title()}" @@ -123,7 +121,7 @@ class ClickUp: return await ctx.send(embed=embed) @command(name="clickup.task()", aliases=["clickup.task", "task", "get_task"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE, CONTRIBUTOR_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops, Roles.contributor) async def task_command(self, ctx: Context, task_id: str): """ Get a task and return information specific to it @@ -136,7 +134,7 @@ class ClickUp: embed.set_author( name=f"ClickUp Task: #{task_id}", icon_url="https://clickup.com/landing/favicons/favicon-32x32.png", - url=f"https://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/t/{task_id}" + url=f"https://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/t/{task_id}" ) params = MultiDict() @@ -146,7 +144,7 @@ class ClickUp: params.add("statuses[]", "Closed") response = await self.bot.http_session.get( - GET_TASKS_URL.format(team_id=CLICKUP_TEAM), headers=HEADERS, params=params + GET_TASKS_URL.format(team_id=ClickUpConfig.team), headers=HEADERS, params=params ) result = await response.json() @@ -199,14 +197,14 @@ class ClickUp: return await ctx.send(embed=embed) @command(name="clickup.team()", aliases=["clickup.team", "team", "list_team"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops) async def team_command(self, ctx: Context): """ Get a list of every member of the team """ response = await self.bot.http_session.get( - TEAM_URL.format(team_id=CLICKUP_TEAM), headers=HEADERS + TEAM_URL.format(team_id=ClickUpConfig.team), headers=HEADERS ) result = await response.json() @@ -233,21 +231,21 @@ class ClickUp: embed.set_author( name="ClickUp Members", icon_url="https://clickup.com/landing/favicons/favicon-32x32.png", - url=f"https://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/" + url=f"https://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/" ) log.debug("List fully prepared, returning list to channel.") await ctx.send(embed=embed) @command(name="clickup.lists()", aliases=["clickup.lists", "lists"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE, CONTRIBUTOR_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops, Roles.contributor) async def lists_command(self, ctx: Context): """ Get all the lists belonging to the ClickUp space """ response = await self.bot.http_session.get( - PROJECTS_URL.format(space_id=CLICKUP_SPACE), headers=HEADERS + PROJECTS_URL.format(space_id=ClickUpConfig.space), headers=HEADERS ) result = await response.json() @@ -281,14 +279,14 @@ class ClickUp: embed.set_author( name="ClickUp Projects", icon_url="https://clickup.com/landing/favicons/favicon-32x32.png", - url=f"https://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/" + url=f"https://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/" ) log.debug(f"List fully prepared, returning list to channel.") await ctx.send(embed=embed) @command(name="clickup.open()", aliases=["clickup.open", "open", "open_task"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE, CONTRIBUTOR_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops, Roles.contributor) async def open_command(self, ctx: Context, task_list: str, title: str): """ Open a new task under a specific task list, with a title @@ -301,7 +299,7 @@ class ClickUp: embed.set_author( name="ClickUp Tasks", icon_url="https://clickup.com/landing/favicons/favicon-32x32.png", - url=f"https://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/" + url=f"https://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/" ) if task_list in self.lists: @@ -329,7 +327,7 @@ class ClickUp: embed.description = f"`{result['ECODE']}`: {result['err']}" else: task_id = result.get("id") - task_url = f"https://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/t/{task_id}" + task_url = f"https://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/t/{task_id}" project, task_list = self.lists[task_list].split("/", 1) task_list = f"{project.title()}/{task_list.title()}" @@ -340,7 +338,7 @@ class ClickUp: await ctx.send(embed=embed) @command(name="clickup.set_status()", aliases=["clickup.set_status", "set_status", "set_task_status"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE, CONTRIBUTOR_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops, Roles.contributor) async def set_status_command(self, ctx: Context, task_id: str, status: str): """ Update the status of a specific task @@ -350,7 +348,7 @@ class ClickUp: embed.set_author( name="ClickUp Tasks", icon_url="https://clickup.com/landing/favicons/favicon-32x32.png", - url=f"https://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/" + url=f"https://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/" ) if status.lower() not in STATUSES: @@ -371,7 +369,7 @@ class ClickUp: embed.colour = Colour.red() else: log.debug(f"{ctx.author} updated a task on ClickUp: #{task_id}") - task_url = f"https://app.clickup.com/{CLICKUP_TEAM}/{CLICKUP_SPACE}/t/{task_id}" + task_url = f"https://app.clickup.com/{ClickUpConfig.team}/{ClickUpConfig.space}/t/{task_id}" embed.description = f"Task updated: [`#{task_id}`]({task_url})" await ctx.send(embed=embed) diff --git a/bot/cogs/cogs.py b/bot/cogs/cogs.py index 92887d0b3..d26f6b2c5 100644 --- a/bot/cogs/cogs.py +++ b/bot/cogs/cogs.py @@ -5,9 +5,7 @@ from discord import ClientException, Colour, Embed from discord.ext.commands import AutoShardedBot, Context, command from bot.constants import ( - ADMIN_ROLE, BOT_AVATAR_URL, DEVOPS_ROLE, GITHUB_URL_BOT, - GREEN_CHEVRON, MODERATOR_ROLE, OWNER_ROLE, RED_CHEVRON, - WHITE_CHEVRON + Emojis, Roles, URLs, ) from bot.decorators import with_role from bot.pagination import LinePaginator @@ -37,7 +35,7 @@ class Cogs: self.cogs.update({v: k for k, v in self.cogs.items()}) @command(name="cogs.load()", aliases=["cogs.load", "load_cog"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops) async def load_command(self, ctx: Context, cog: str): """ Load up an unloaded cog, given the module containing it @@ -53,8 +51,8 @@ class Cogs: embed.set_author( name="Python Bot (Cogs)", - url=GITHUB_URL_BOT, - icon_url=BOT_AVATAR_URL + url=URLs.github_bot_repo, + icon_url=URLs.bot_avatar ) if cog in self.cogs: @@ -94,7 +92,7 @@ class Cogs: await ctx.send(embed=embed) @command(name="cogs.unload()", aliases=["cogs.unload", "unload_cog"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops) async def unload_command(self, ctx: Context, cog: str): """ Unload an already-loaded cog, given the module containing it @@ -110,8 +108,8 @@ class Cogs: embed.set_author( name="Python Bot (Cogs)", - url=GITHUB_URL_BOT, - icon_url=BOT_AVATAR_URL + url=URLs.github_bot_repo, + icon_url=URLs.bot_avatar ) if cog in self.cogs: @@ -146,7 +144,7 @@ class Cogs: await ctx.send(embed=embed) @command(name="cogs.reload()", aliases=["cogs.reload", "reload_cog"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops) async def reload_command(self, ctx: Context, cog: str): """ Reload an unloaded cog, given the module containing it @@ -165,8 +163,8 @@ class Cogs: embed.set_author( name="Python Bot (Cogs)", - url=GITHUB_URL_BOT, - icon_url=BOT_AVATAR_URL + url=URLs.github_bot_repo, + icon_url=URLs.bot_avatar ) if cog == "*": @@ -218,13 +216,13 @@ class Cogs: lines.append("\n**Unload failures**") for cog, error in failed_unloads: - lines.append(f"`{cog}` {WHITE_CHEVRON} `{error}`") + lines.append(f"`{cog}` {Emojis.white_chevron} `{error}`") if failed_loads: lines.append("\n**Load failures**") for cog, error in failed_loads: - lines.append(f"`{cog}` {WHITE_CHEVRON} `{error}`") + lines.append(f"`{cog}` {Emojis.white_chevron} `{error}`") log.debug(f"{ctx.author} requested we reload all cogs. Here are the results: \n" f"{lines}") @@ -251,7 +249,7 @@ class Cogs: await ctx.send(embed=embed) @command(name="cogs.list()", aliases=["cogs", "cogs.list", "cogs()"]) - @with_role(MODERATOR_ROLE, ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE) + @with_role(Roles.moderator, Roles.admin, Roles.owner, Roles.devops) async def list_command(self, ctx: Context): """ Get a list of all cogs, including their loaded status. @@ -266,8 +264,8 @@ class Cogs: embed.colour = Colour.blurple() embed.set_author( name="Python Bot (Cogs)", - url=GITHUB_URL_BOT, - icon_url=BOT_AVATAR_URL + url=URLs.github_bot_repo, + icon_url=URLs.bot_avatar ) for key, _value in self.cogs.items(): @@ -288,9 +286,9 @@ class Cogs: cog = self.cogs[cog] if loaded: - chevron = GREEN_CHEVRON + chevron = Emojis.green_chevron else: - chevron = RED_CHEVRON + chevron = Emojis.red_chevron lines.append(f"{chevron} {cog}") diff --git a/bot/cogs/deployment.py b/bot/cogs/deployment.py index b16b6e63d..c33120af3 100644 --- a/bot/cogs/deployment.py +++ b/bot/cogs/deployment.py @@ -3,7 +3,7 @@ import logging from discord import Colour, Embed from discord.ext.commands import AutoShardedBot, Context, command -from bot.constants import ADMIN_ROLE, DEPLOY_BOT_KEY, DEPLOY_SITE_KEY, DEPLOY_URL, DEVOPS_ROLE, OWNER_ROLE, STATUS_URL +from bot.constants import Keys, Roles, URLs from bot.decorators import with_role log = logging.getLogger(__name__) @@ -18,13 +18,13 @@ class Deployment: self.bot = bot @command(name="redeploy()", aliases=["bot.redeploy", "bot.redeploy()", "redeploy"]) - @with_role(ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE) + @with_role(Roles.admin, Roles.owner, Roles.devops) async def redeploy(self, ctx: Context): """ Trigger bot deployment on the server - will only redeploy if there were changes to deploy """ - response = await self.bot.http_session.get(DEPLOY_URL, headers={"token": DEPLOY_BOT_KEY}) + response = await self.bot.http_session.get(URLs.deploy, headers={"token": Keys.deploy_bot}) result = await response.text() if result == "True": @@ -35,13 +35,13 @@ class Deployment: await ctx.send(f"{ctx.author.mention} Bot deployment failed - check the logs!") @command(name="deploy_site()", aliases=["bot.deploy_site", "bot.deploy_site()", "deploy_site"]) - @with_role(ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE) + @with_role(Roles.admin, Roles.owner, Roles.devops) async def deploy_site(self, ctx: Context): """ Trigger website deployment on the server - will only redeploy if there were changes to deploy """ - response = await self.bot.http_session.get(DEPLOY_URL, headers={"token": DEPLOY_SITE_KEY}) + response = await self.bot.http_session.get(URLs.deploy, headers={"token": Keys.deploy_bot}) result = await response.text() if result == "True": @@ -52,14 +52,14 @@ class Deployment: await ctx.send(f"{ctx.author.mention} Site deployment failed - check the logs!") @command(name="uptimes()", aliases=["bot.uptimes", "bot.uptimes()", "uptimes"]) - @with_role(ADMIN_ROLE, OWNER_ROLE, DEVOPS_ROLE) + @with_role(Roles.admin, Roles.owner, Roles.devops) async def uptimes(self, ctx: Context): """ Check the various deployment uptimes for each service """ log.debug(f"{ctx.author} requested service uptimes.") - response = await self.bot.http_session.get(STATUS_URL) + response = await self.bot.http_session.get(URLs.status) data = await response.json() embed = Embed( diff --git a/bot/cogs/eval.py b/bot/cogs/eval.py index 55c67bcfa..9b8c50bb4 100644 --- a/bot/cogs/eval.py +++ b/bot/cogs/eval.py @@ -10,7 +10,7 @@ from io import StringIO import discord from discord.ext.commands import AutoShardedBot, command -from bot.constants import ADMIN_ROLE, OWNER_ROLE +from bot.constants import Roles from bot.decorators import with_role from bot.interpreter import Interpreter @@ -174,7 +174,7 @@ async def func(): # (None,) -> Any await ctx.send(f"```py\n{out}```", embed=embed) @command(name="eval()", aliases=["eval"]) - @with_role(ADMIN_ROLE, OWNER_ROLE) + @with_role(Roles.admin, Roles.owner) async def eval(self, ctx, *, code: str): """ Run eval in a REPL-like format. """ code = code.strip("`") diff --git a/bot/cogs/events.py b/bot/cogs/events.py index 3241e0bd8..213d8e0ad 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -8,12 +8,12 @@ from discord.ext.commands import ( ) from bot.constants import ( - DEBUG_MODE, DEVLOG_CHANNEL, PYTHON_GUILD, - SITE_API_KEY, SITE_API_URL, - VERIFIED_ROLE) + Channels, DEBUG_MODE, Guild, + Keys, Roles, URLs +) + log = logging.getLogger(__name__) -USERS_URL = f"{SITE_API_URL}/bot/users" class Events: @@ -25,20 +25,20 @@ class Events: self.bot = bot async def send_updated_users(self, *users, replace_all=False): - users = filter(lambda user: str(VERIFIED_ROLE) in user["roles"], users) + users = filter(lambda user: str(Roles.verified) in user["roles"], users) try: if replace_all: response = await self.bot.http_session.post( - url=USERS_URL, + url=URLs.site_user_api, json=list(users), - headers={"X-API-Key": SITE_API_KEY} + headers={"X-API-Key": Keys.site_api} ) else: response = await self.bot.http_session.put( - url=USERS_URL, + url=URLs.site_user_api, json=list(users), - headers={"X-API-Key": SITE_API_KEY} + headers={"X-API-Key": Keys.site_api} ) return await response.json() @@ -49,10 +49,9 @@ class Events: async def send_delete_users(self, *users): try: response = await self.bot.http_session.delete( - url=USERS_URL, - + url=URLs.site_user_api, json=list(users), - headers={"X-API-Key": SITE_API_KEY} + headers={"X-API-Key": Keys.site_api} ) return await response.json() @@ -100,7 +99,7 @@ class Events: async def on_ready(self): users = [] - for member in self.bot.get_guild(PYTHON_GUILD).members: # type: Member + for member in self.bot.get_guild(Guild.id).members: # type: Member roles = [str(r.id) for r in member.roles] # type: List[int] users.append({ @@ -138,7 +137,7 @@ class Events: ) if not DEBUG_MODE: - await self.bot.get_channel(DEVLOG_CHANNEL).send( + await self.bot.get_channel(Channels.devlog).send( embed=embed ) diff --git a/bot/cogs/fun.py b/bot/cogs/fun.py index 4ce3fc27f..0abaf2469 100644 --- a/bot/cogs/fun.py +++ b/bot/cogs/fun.py @@ -3,7 +3,7 @@ import logging from discord import Message from discord.ext.commands import AutoShardedBot -from bot.constants import BOT_COMMANDS_CHANNEL +from bot.constants import Channels RESPONSES = { "_pokes {us}_": "_Pokes {them}_", @@ -33,7 +33,7 @@ class Fun: del RESPONSES[key] async def on_message(self, message: Message): - if message.channel.id != BOT_COMMANDS_CHANNEL: + if message.channel.id != Channels.bot: return content = message.content diff --git a/bot/cogs/hiphopify.py b/bot/cogs/hiphopify.py index bb8dc1300..d1037616d 100644 --- a/bot/cogs/hiphopify.py +++ b/bot/cogs/hiphopify.py @@ -6,12 +6,13 @@ from discord.errors import Forbidden from discord.ext.commands import AutoShardedBot, Context, command from bot.constants import ( - ADMIN_ROLE, MODERATOR_ROLE, MOD_LOG_CHANNEL, - NEGATIVE_REPLIES, OWNER_ROLE, POSITIVE_REPLIES, - SITE_API_KEY, SITE_API_URL + Channels, Keys, + NEGATIVE_REPLIES, POSITIVE_REPLIES, + Roles, URLs ) from bot.decorators import with_role + log = logging.getLogger(__name__) @@ -22,8 +23,7 @@ class Hiphopify: def __init__(self, bot: AutoShardedBot): self.bot = bot - self.headers = {"X-API-KEY": SITE_API_KEY} - self.url = f"{SITE_API_URL}/bot/hiphopify" + self.headers = {"X-API-KEY": Keys.site_api} async def on_member_update(self, before, after): """ @@ -43,7 +43,7 @@ class Hiphopify: ) response = await self.bot.http_session.get( - self.url, + URLs.site_hiphopify_api, headers=self.headers, params={"user_id": str(before.id)} ) @@ -75,7 +75,7 @@ class Hiphopify: "to DM them, and a discord.errors.Forbidden error was incurred." ) - @with_role(ADMIN_ROLE, OWNER_ROLE, MODERATOR_ROLE) + @with_role(Roles.admin, Roles.owner, Roles.moderator) @command(name="hiphopify()", aliases=["hiphopify", "force_nick()", "force_nick"]) async def hiphopify(self, ctx: Context, member: Member, duration: str, forced_nick: str = None): """ @@ -105,7 +105,7 @@ class Hiphopify: params["forced_nick"] = forced_nick response = await self.bot.http_session.post( - self.url, + URLs.site_hiphopify_api, headers=self.headers, json=params ) @@ -139,7 +139,7 @@ class Hiphopify: # Log to the mod_log channel log.trace("Logging to the #mod-log channel. This could fail because of channel permissions.") - mod_log = self.bot.get_channel(MOD_LOG_CHANNEL) + mod_log = self.bot.get_channel(Channels.modlog) await mod_log.send( f":middle_finger: {member.name}#{member.discriminator} (`{member.id}`) " f"has been hiphopified by **{ctx.author.name}**. Their new nickname is `{forced_nick}`. " @@ -151,7 +151,7 @@ class Hiphopify: await member.edit(nick=forced_nick) await ctx.send(embed=embed) - @with_role(ADMIN_ROLE, OWNER_ROLE, MODERATOR_ROLE) + @with_role(Roles.admin, Roles.owner, Roles.moderator) @command(name="unhiphopify()", aliases=["unhiphopify", "release_nick()", "release_nick"]) async def unhiphopify(self, ctx: Context, member: Member): """ @@ -168,7 +168,7 @@ class Hiphopify: embed.colour = Colour.blurple() response = await self.bot.http_session.delete( - self.url, + URLs.site_hiphopify_api, headers=self.headers, json={"user_id": str(member.id)} ) diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index 60403ec2d..03f3dda06 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -3,7 +3,8 @@ import logging from discord import Embed from discord.ext.commands import AutoShardedBot -from bot.constants import DEBUG_MODE, DEVLOG_CHANNEL +from bot.constants import Channels, DEBUG_MODE + log = logging.getLogger(__name__) @@ -27,7 +28,7 @@ class Logging: ) if not DEBUG_MODE: - await self.bot.get_channel(DEVLOG_CHANNEL).send(embed=embed) + await self.bot.get_channel(Channels.devlog).send(embed=embed) def setup(bot): diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 31890dd94..2af18d267 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -17,16 +17,15 @@ from discord import Colour, Embed, File, Member, Message, Reaction from discord.ext.commands import AutoShardedBot, BadArgument, Context, bot_has_permissions, command from PIL import Image, ImageDraw, ImageFont -from bot.constants import ( - ERROR_REPLIES, OMDB_API_KEY, SITE_API_KEY, - SITE_API_URL, YOUTUBE_API_KEY -) +from bot.constants import ERROR_REPLIES, Keys, URLs from bot.converters import Snake from bot.decorators import locked from bot.utils.snakes import hatching, perlin, perlinsneks, sal + log = logging.getLogger(__name__) + # region: Constants # Color SNAKE_COLOR = 0x399600 @@ -150,13 +149,7 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.active_sal = {} self.bot = bot - self.headers = {"X-API-KEY": SITE_API_KEY} - - # Build API urls. - self.quiz_url = f"{SITE_API_URL}/bot/snake_quiz" - self.facts_url = f"{SITE_API_URL}/bot/snake_facts" - self.names_url = f"{SITE_API_URL}/bot/snake_names" - self.idioms_url = f"{SITE_API_URL}/bot/snake_idioms" + self.headers = {"X-API-KEY": Keys.site_api} # region: Helper methods @staticmethod @@ -426,7 +419,7 @@ class Snakes: :return: A random snake name, as a string. """ - response = await self.bot.http_session.get(self.names_url, headers=self.headers) + response = await self.bot.http_session.get(URLs.site_names_api, headers=self.headers) name_data = await response.json() return name_data @@ -635,7 +628,7 @@ class Snakes: ) # Get a snake idiom from the API - response = await self.bot.http_session.get(self.idioms_url, headers=self.headers) + response = await self.bot.http_session.get(URLs.site_idioms_api, headers=self.headers) text = await response.json() # Build and send the snek @@ -790,7 +783,7 @@ class Snakes: "s": "snake", "page": page, "type": "movie", - "apikey": OMDB_API_KEY + "apikey": Keys.omdb } ) data = await response.json() @@ -800,7 +793,7 @@ class Snakes: url, params={ "i": movie, - "apikey": OMDB_API_KEY + "apikey": Keys.omdb } ) data = await response.json() @@ -853,7 +846,7 @@ class Snakes: """ # Prepare a question. - response = await self.bot.http_session.get(self.quiz_url, headers=self.headers) + response = await self.bot.http_session.get(URLs.site_quiz_api, headers=self.headers) question = await response.json() answer = question["answerkey"] options = {key: question["options"][key] for key in ANSWERS_EMOJI.keys()} @@ -1056,7 +1049,7 @@ class Snakes: """ # Get a fact from the API. - response = await self.bot.http_session.get(self.facts_url, headers=self.headers) + response = await self.bot.http_session.get(URLs.site_facts_api, headers=self.headers) question = await response.json() # Build and send the embed. @@ -1144,7 +1137,7 @@ class Snakes: "part": "snippet", "q": urllib.parse.quote(query), "type": "video", - "key": YOUTUBE_API_KEY + "key": Keys.youtube } ) response = await response.json() diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index 46be1c44a..b441e3d6a 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -4,24 +4,23 @@ import time from discord import Colour, Embed from discord.ext.commands import ( - AutoShardedBot, BadArgument, Context, - Converter, command + AutoShardedBot, BadArgument, + Context, Converter, command ) from bot.constants import ( - ADMIN_ROLE, BOT_COMMANDS_CHANNEL, DEVTEST_CHANNEL, - ERROR_REPLIES, HELPERS_CHANNEL, MODERATOR_ROLE, OWNER_ROLE, - SITE_API_KEY, SITE_API_URL, TAG_COOLDOWN + Channels, Cooldowns, ERROR_REPLIES, Keys, Roles, URLs ) from bot.decorators import with_role from bot.pagination import LinePaginator + log = logging.getLogger(__name__) TEST_CHANNELS = ( - DEVTEST_CHANNEL, - BOT_COMMANDS_CHANNEL, - HELPERS_CHANNEL + Channels.devtest, + Channels.bot, + Channels.helpers ) @@ -86,8 +85,7 @@ class Tags: def __init__(self, bot: AutoShardedBot): self.bot = bot self.tag_cooldowns = {} - self.headers = {"X-API-KEY": SITE_API_KEY} - self.url = f"{SITE_API_URL}/bot/tags" + self.headers = {"X-API-KEY": Keys.site_api} async def get_tag_data(self, tag_name=None) -> dict: """ @@ -104,7 +102,7 @@ class Tags: if tag_name: params["tag_name"] = tag_name - response = await self.bot.http_session.get(self.url, headers=self.headers, params=params) + response = await self.bot.http_session.get(URLs.site_tags_api, headers=self.headers, params=params) tag_data = await response.json() return tag_data @@ -126,7 +124,7 @@ class Tags: 'tag_content': tag_content } - response = await self.bot.http_session.post(self.url, headers=self.headers, json=params) + response = await self.bot.http_session.post(URLs.site_tags_api, headers=self.headers, json=params) tag_data = await response.json() return tag_data @@ -147,7 +145,7 @@ class Tags: if tag_name: params['tag_name'] = tag_name - response = await self.bot.http_session.delete(self.url, headers=self.headers, json=params) + response = await self.bot.http_session.delete(URLs.site_tags_api, headers=self.headers, json=params) tag_data = await response.json() return tag_data @@ -189,7 +187,7 @@ class Tags: cooldown_conditions = ( tag_name and tag_name in self.tag_cooldowns - and (now - self.tag_cooldowns[tag_name]["time"]) < TAG_COOLDOWN + and (now - self.tag_cooldowns[tag_name]["time"]) < Cooldowns.tags and self.tag_cooldowns[tag_name]["channel"] == ctx.channel.id ) @@ -198,7 +196,7 @@ class Tags: return False if _command_on_cooldown(tag_name): - time_left = TAG_COOLDOWN - (time.time() - self.tag_cooldowns[tag_name]["time"]) + time_left = Cooldowns.tags - (time.time() - self.tag_cooldowns[tag_name]["time"]) log.warning(f"{ctx.author} tried to get the '{tag_name}' tag, but the tag is on cooldown. " f"Cooldown ends in {time_left:.1f} seconds.") return @@ -269,7 +267,7 @@ class Tags: return await ctx.send(embed=embed) - @with_role(ADMIN_ROLE, OWNER_ROLE, MODERATOR_ROLE) + @with_role(Roles.admin, Roles.owner, Roles.moderator) @command(name="tags.set()", aliases=["tags.set", "tags.add", "tags.add()", "tags.edit", "tags.edit()", "add_tag"]) async def set_command(self, ctx: Context, tag_name: TagNameConverter, tag_content: TagContentConverter): """ @@ -305,7 +303,7 @@ class Tags: return await ctx.send(embed=embed) - @with_role(ADMIN_ROLE, OWNER_ROLE) + @with_role(Roles.admin, Roles.owner) @command(name="tags.delete()", aliases=["tags.delete", "tags.remove", "tags.remove()", "remove_tag"]) async def delete_command(self, ctx: Context, tag_name: TagNameConverter): """ diff --git a/bot/cogs/verification.py b/bot/cogs/verification.py index 1c5b37894..38021b5e9 100644 --- a/bot/cogs/verification.py +++ b/bot/cogs/verification.py @@ -3,9 +3,10 @@ import logging from discord import Message, Object from discord.ext.commands import AutoShardedBot, Context, command -from bot.constants import VERIFICATION_CHANNEL, VERIFIED_ROLE +from bot.constants import Channels, Roles from bot.decorators import in_channel, without_role + log = logging.getLogger(__name__) @@ -25,9 +26,9 @@ class Verification: if ctx.command is not None and ctx.command.name == "accept": return # They used the accept command - if ctx.channel.id == VERIFICATION_CHANNEL: # We're in the verification channel + if ctx.channel.id == Channels.verification: # We're in the verification channel for role in ctx.author.roles: - if role.id == VERIFIED_ROLE: + if role.id == Roles.verified: log.warning(f"{ctx.author} posted '{ctx.message.content}' " "in the verification channel, but is already verified.") return # They're already verified @@ -44,15 +45,15 @@ class Verification: await ctx.message.delete() @command(name="accept", hidden=True, aliases=["verify", "verified", "accepted", "accept()"]) - @without_role(VERIFIED_ROLE) - @in_channel(VERIFICATION_CHANNEL) + @without_role(Roles.verified) + @in_channel(Channels.verification) async def accept(self, ctx: Context, *_): # We don't actually care about the args """ Accept our rules and gain access to the rest of the server """ log.debug(f"{ctx.author} called self.accept(). Assigning the user 'Developer' role.") - await ctx.author.add_roles(Object(VERIFIED_ROLE), reason="Accepted the rules") + await ctx.author.add_roles(Object(Roles.verified), reason="Accepted the rules") log.trace(f"Deleting the message posted by {ctx.author}.") await ctx.message.delete() diff --git a/bot/constants.py b/bot/constants.py index a11be7506..df380e7fe 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -1,69 +1,205 @@ +""" +Loads bot configuration from YAML files. +By default, this simply loads the default +configuration located at `config-default.yml`. +If a file called `config.yml` is found in the +project directory, the default configuration +is recursively updated with any settings from +the custom configuration. Any settings left +out in the custom user configuration will stay +their default values from `config-default.yml`. +""" + +import logging import os +from collections.abc import Mapping +from pathlib import Path + +import yaml + + +log = logging.getLogger(__name__) + + +def _required_env_var_constructor(loader, node): + """ + Implements a custom YAML tag for loading required environment + variables. If the environment variable is set, this function + will simply return it. Otherwise, a `CRITICAL` log message is + given and the `KeyError` is re-raised. + + Example usage in the YAML configuration: + + bot: + token: !REQUIRED_ENV 'BOT_TOKEN' + """ + + value = loader.construct_scalar(node) + + try: + return os.environ[value] + except KeyError: + log.critical( + f"Environment variable `{value}` is required, but was not set. " + "Set it in your environment or override the option using it in your `config.yml`." + ) + raise + + +def _env_var_constructor(loader, node): + """ + Implements a custom YAML tag for loading optional environment + variables. If the environment variable is set, returns the + value of it. Otherwise, returns `None`. + + Example usage in the YAML configuration: + + # Optional app configuration. Set `MY_APP_KEY` in the environment to use it. + application: + key: !ENV 'MY_APP_KEY' + """ + + value = loader.construct_scalar(node) + return os.getenv(value) + + +yaml.SafeLoader.add_constructor("!REQUIRED_ENV", _required_env_var_constructor) +yaml.SafeLoader.add_constructor("!ENV", _env_var_constructor) + + +with open("config-default.yml") as f: + _CONFIG_YAML = yaml.safe_load(f) + + +def _recursive_update(original, new): + """ + Helper method which implements a recursive `dict.update` + method, used for updating the original configuration with + configuration specified by the user. + """ + + for key, value in original.items(): + if key not in new: + continue + + if isinstance(value, Mapping): + if not any(isinstance(subvalue, Mapping) for subvalue in value.values()): + original[key].update(new[key]) + _recursive_update(original[key], new[key]) + else: + original[key] = new[key] + + +if Path("config.yml").exists(): + log.info("Found `config.yml` file, loading constants from it.") + with open("config.yml") as f: + user_config = yaml.safe_load(f) + _recursive_update(_CONFIG_YAML, user_config) + + +class YAMLGetter(type): + """ + Implements a custom metaclass used for accessing + configuration data by simply accessing class attributes. + Supports getting configuration from up to two levels + of nested configuration through `section` and `subsection`. + + `section` specifies the YAML configuration section (or "key") + in which the configuration lives, and must be set. + + `subsection` is an optional attribute specifying the section + within the section from which configuration should be loaded. + + Example Usage: + + # config.yml + bot: + prefixes: + direct_message: '' + guild: '!' + + # config.py + class Prefixes(metaclass=YAMLGetter): + section = "bot" + subsection = "prefixes" + + # Usage in Python code + from config import Prefixes + def get_prefix(bot, message): + if isinstance(message.channel, PrivateChannel): + return Prefixes.direct_message + return Prefixes.guild + """ + + subsection = None + + def __getattr__(cls, name): + name = name.lower() + + try: + if cls.subsection is not None: + return _CONFIG_YAML[cls.section][cls.subsection][name] + return _CONFIG_YAML[cls.section][name] + except KeyError: + dotted_path = '.'.join( + (cls.section, cls.subsection, name) + if cls.subsection is not None else (cls.section, name) + ) + log.critical(f"Tried accessing configuration variable at `{dotted_path}`, but it could not be found.") + raise + + def __getitem__(cls, name): + return cls.__getattr__(name) + + +# Dataclasses +class Bot(metaclass=YAMLGetter): + section = "bot" + + +class Cooldowns(metaclass=YAMLGetter): + section = "bot" + subsection = "cooldowns" + + +class Emojis(metaclass=YAMLGetter): + section = "bot" + subsection = "emojis" + + +class Channels(metaclass=YAMLGetter): + section = "guild" + subsection = "channels" + + +class Roles(metaclass=YAMLGetter): + section = "guild" + subsection = "roles" + + +class Guild(metaclass=YAMLGetter): + section = "guild" + + +class Keys(metaclass=YAMLGetter): + section = "keys" + + +class ClickUp(metaclass=YAMLGetter): + section = "clickup" + + +class Papertrail(metaclass=YAMLGetter): + section = "papertrail" + + +class URLs(metaclass=YAMLGetter): + section = "urls" + # Debug mode DEBUG_MODE = True if 'local' in os.environ.get("SITE_URL", "local") else False -# Server -PYTHON_GUILD = 267624335836053506 - -# Channels -BOT_COMMANDS_CHANNEL = 267659945086812160 -CHECKPOINT_TEST_CHANNEL = 422077681434099723 -DEVLOG_CHANNEL = 409308876241108992 -DEVTEST_CHANNEL = 414574275865870337 -HELP1_CHANNEL = 303906576991780866 -HELP2_CHANNEL = 303906556754395136 -HELP3_CHANNEL = 303906514266226689 -HELP4_CHANNEL = 439702951246692352 -HELPERS_CHANNEL = 385474242440986624 -MOD_LOG_CHANNEL = 282638479504965634 -PYTHON_CHANNEL = 267624335836053506 -VERIFICATION_CHANNEL = 352442727016693763 - -# Roles -ADMIN_ROLE = 267628507062992896 -MODERATOR_ROLE = 267629731250176001 -VERIFIED_ROLE = 352427296948486144 -OWNER_ROLE = 267627879762755584 -DEVOPS_ROLE = 409416496733880320 -CONTRIBUTOR_ROLE = 295488872404484098 - -# Clickup -CLICKUP_KEY = os.environ.get("CLICKUP_KEY") -CLICKUP_SPACE = 757069 -CLICKUP_TEAM = 754996 - -# URLs -DEPLOY_URL = os.environ.get("DEPLOY_URL") -STATUS_URL = os.environ.get("STATUS_URL") -SITE_URL = os.environ.get("SITE_URL", "pythondiscord.local:8080") -SITE_PROTOCOL = 'http' if DEBUG_MODE else 'https' -SITE_API_URL = f"{SITE_PROTOCOL}://api.{SITE_URL}" -GITHUB_URL_BOT = "https://github.com/discord-python/bot" -BOT_AVATAR_URL = "https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle/logo_circle.png" -OMDB_URL = "http://www.omdbapi.com/" - -# Keys -DEPLOY_BOT_KEY = os.environ.get("DEPLOY_BOT_KEY") -DEPLOY_SITE_KEY = os.environ.get("DEPLOY_SITE_KEY") -SITE_API_KEY = os.environ.get("BOT_API_KEY") -YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY") -OMDB_API_KEY = os.getenv("OMDB_API_KEY") - -# Bot internals -HELP_PREFIX = "bot." -TAG_COOLDOWN = 60 # Per channel, per tag - -# There are Emoji objects, but they're not usable until the bot is connected, -# so we're using string constants instead -GREEN_CHEVRON = "<:greenchevron:418104310329769993>" -RED_CHEVRON = "<:redchevron:418112778184818698>" -WHITE_CHEVRON = "<:whitechevron:418110396973711363>" - -# PaperTrail logging -PAPERTRAIL_ADDRESS = os.environ.get("PAPERTRAIL_ADDRESS") or None -PAPERTRAIL_PORT = int(os.environ.get("PAPERTRAIL_PORT") or 0) - # Paths BOT_DIR = os.path.dirname(__file__) PROJECT_ROOT = os.path.abspath(os.path.join(BOT_DIR, os.pardir)) diff --git a/bot/converters.py b/bot/converters.py index a629768b7..dc78e5e08 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -6,12 +6,9 @@ from aiohttp import AsyncResolver, ClientSession, TCPConnector from discord.ext.commands import Converter from fuzzywuzzy import fuzz -from bot.constants import DEBUG_MODE, SITE_API_KEY, SITE_API_URL +from bot.constants import DEBUG_MODE, Keys, URLs from bot.utils import disambiguate -NAMES_URL = f"{SITE_API_URL}/bot/snake_names" -SPECIAL_URL = f"{SITE_API_URL}/bot/special_snakes" - class Snake(Converter): snakes = None @@ -57,7 +54,7 @@ class Snake(Converter): @classmethod async def build_list(cls): - headers = {"X-API-KEY": SITE_API_KEY} + headers = {"X-API-KEY": Keys.site_api} # Set up the session if DEBUG_MODE: @@ -78,7 +75,7 @@ class Snake(Converter): # Get all the snakes if cls.snakes is None: response = await http_session.get( - NAMES_URL, + URLs.site_names_api, params={"get_all": "true"}, headers=headers ) @@ -87,7 +84,7 @@ class Snake(Converter): # Get the special cases if cls.special_cases is None: response = await http_session.get( - SPECIAL_URL, + URLs.site_special_api, headers=headers ) special_cases = await response.json() diff --git a/bot/formatter.py b/bot/formatter.py index 7c966108a..5ec23dcb2 100644 --- a/bot/formatter.py +++ b/bot/formatter.py @@ -10,7 +10,7 @@ from inspect import formatargspec, getfullargspec from discord.ext.commands import Command, HelpFormatter, Paginator -from bot.constants import HELP_PREFIX +from bot.constants import Bot log = logging.getLogger(__name__) @@ -31,13 +31,13 @@ class Formatter(HelpFormatter): # skip aliases continue - entry = " {0}{1:<{width}} # {2}".format(HELP_PREFIX, name, command.short_doc, width=max_width) + entry = " {0}{1:<{width}} # {2}".format(Bot.help_prefix, name, command.short_doc, width=max_width) shortened = self.shorten(entry) self._paginator.add_line(shortened) if name.endswith('get()'): alternate_syntax_entry = " {0}{1:<{width}} # {2}".format( - HELP_PREFIX, name.split('.')[0] + '[<arg>]', + Bot.help_prefix, name.split('.')[0] + '[<arg>]', f"Alternative syntax for {name}", width=max_width ) self._paginator.add_line(self.shorten(alternate_syntax_entry)) @@ -71,7 +71,7 @@ class Formatter(HelpFormatter): log.trace(f"Help command is on specific command {self.command.name}{cog_string}.") # strip the command off bot. and () - stripped_command = self.command.name.replace(HELP_PREFIX, "").replace("()", "") + stripped_command = self.command.name.replace(Bot.help_prefix, "").replace("()", "") # get the args using the handy inspect module argspec = getfullargspec(self.command.callback) diff --git a/config-default.yml b/config-default.yml new file mode 100644 index 000000000..969fb8260 --- /dev/null +++ b/config-default.yml @@ -0,0 +1,74 @@ +bot: + help_prefix: 'bot.' + token: !REQUIRED_ENV 'BOT_TOKEN' + + cooldowns: + # Per channel, per tag. + tags: 60 + + emojis: + green_chevron: '<:greenchevron:418104310329769993>' + red_chevron: '<:redchevron:418112778184818698>' + white_chevron: '<:whitechevron:418110396973711363>' + + +guild: + id: 267624335836053506 + + channels: + bot: 267659945086812160 + help0: 303906576991780866 + help1: 303906556754395136 + help2: 303906514266226689 + help3: 439702951246692352 + python: 267624335836053506 + devlog: 409308876241108992 + devtest: 414574275865870337 + verification: 352442727016693763 + checkpoint_test: 422077681434099723 + helpers: 385474242440986624 + modlog: 282638479504965634 + + roles: + admin: 267628507062992896 + moderator: 267629731250176001 + verified: 352427296948486144 + owner: 267627879762755584 + devops: 409416496733880320 + contributor: 295488872404484098 + + +keys: + deploy_bot: !ENV 'DEPLOY_BOT_KEY' + deploy_site: !ENV 'DEPLOY_SITE' + site_api: !ENV 'BOT_API_KEY' + youtube: !ENV 'YOUTUBE_API_KEY' + omdb: !ENV 'OMDB_API_KEY' + + +clickup: + space: 757069 + team: 754996 + key: !ENV 'CLICKUP_KEY' + + +papertrail: + address: !ENV 'PAPERTRAIL_ADDRESS' + port: !ENV 'PAPERTRAIL_PORT' + + +urls: + deploy: !ENV 'DEPLOY_URL' + status: !ENV 'STATUS_URL' + site: 'pythondiscord.com' + site_hiphopify_api: 'https://api.pythondiscord.com/bot/hiphopify' + site_tags_api: 'https://api.pythondiscord.com/bot/tags' + site_user_api: 'https://api.pythondiscord.com/bot/users' + site_quiz_api: 'https://api.pythondiscord.com/bot/snake_quiz' + site_facts_api: 'https://api.pythondiscord.com/bot/snake_facts' + site_names_api: 'https://api.pythondiscord.com/bot/snake_names' + site_idioms_api: 'https://api.pythondiscord.com/bot/snake_idioms' + site_special_api: 'https://api.pythondiscord.com/bot/special_snakes' + github_bot_repo: 'https://github.com/discord-python/bot' + bot_avatar: 'https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle/logo_circle.png' + omdb: 'http://omdbapi.com' |