aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--Pipfile1
-rw-r--r--Pipfile.lock1049
-rw-r--r--bot/__init__.py16
-rw-r--r--bot/__main__.py10
-rw-r--r--bot/cogs/bot.py33
-rw-r--r--bot/cogs/clickup.py46
-rw-r--r--bot/cogs/cogs.py36
-rw-r--r--bot/cogs/deployment.py14
-rw-r--r--bot/cogs/eval.py4
-rw-r--r--bot/cogs/events.py27
-rw-r--r--bot/cogs/fun.py4
-rw-r--r--bot/cogs/hiphopify.py22
-rw-r--r--bot/cogs/logging.py5
-rw-r--r--bot/cogs/snakes.py29
-rw-r--r--bot/cogs/tags.py32
-rw-r--r--bot/cogs/verification.py13
-rw-r--r--bot/constants.py258
-rw-r--r--bot/converters.py11
-rw-r--r--bot/formatter.py8
-rw-r--r--config-default.yml74
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
diff --git a/Pipfile b/Pipfile
index 370b77973..c7c4de512 100644
--- a/Pipfile
+++ b/Pipfile
@@ -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'