diff options
| author | 2018-07-25 01:11:55 -0400 | |
|---|---|---|
| committer | 2018-07-25 01:11:55 -0400 | |
| commit | 2cf56837693fd0ea14181ac52a0b6080296a9223 (patch) | |
| tree | 01acb9cdd1d8cd696579fd42c39d18ba6f9b221b | |
| parent | Add 'edit reason' command (diff) | |
| parent | Attempt to speed up build (diff) | |
Merge branch 'master' into feature/rowboat-replacement
| -rw-r--r-- | .gitlab-ci.yml | 2 | ||||
| -rw-r--r-- | Pipfile | 1 | ||||
| -rw-r--r-- | Pipfile.lock | 315 | ||||
| -rw-r--r-- | bot/__main__.py | 5 | ||||
| -rw-r--r-- | bot/cogs/bigbrother.py | 182 | ||||
| -rw-r--r-- | bot/cogs/bot.py | 10 | ||||
| -rw-r--r-- | bot/cogs/cogs.py | 8 | ||||
| -rw-r--r-- | bot/cogs/doc.py | 11 | ||||
| -rw-r--r-- | bot/cogs/modlog.py | 658 | ||||
| -rw-r--r-- | bot/cogs/snekbox.py | 50 | ||||
| -rw-r--r-- | bot/cogs/verification.py | 6 | ||||
| -rw-r--r-- | bot/constants.py | 88 | ||||
| -rw-r--r-- | bot/utils/time.py | 59 | ||||
| -rw-r--r-- | config-default.yml | 158 |
14 files changed, 1302 insertions, 251 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea398d508..88ab5d927 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,7 @@ test: stage: test script: - - pipenv sync --dev + - pipenv install --dev --deploy - pipenv run lint build: @@ -22,6 +22,7 @@ python-levenshtein = "*" pillow = "*" aio-pika = "*" python-dateutil = "*" +deepdiff = "*" [dev-packages] "flake8" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 9f4c3a1d4..864eb574a 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "2475ab039e5ce97636b9a255dd7e21b67e7b66d832f341b4065876f7535b2d88" + "sha256": "c7d1bad1549c322484f6751447115ded9299df039cb6321bcc1a1fa1359481dc" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "aio-pika": { "hashes": [ - "sha256:2e44d888956bf64e3d1edd624c02279a19faed3dc0cc3ff2d5af03f4fe15b18a", - "sha256:c453f233fb734daeb15c7191d7e5aa20a5c5c317d6d2c26607cb419c807fffd8" + "sha256:c19c38155f4972f6a9f3f0f1095ce261bfb4e8b89553ead240486593aafd9431", + "sha256:d41748994e2f809c440a04a1eb809aaae00691caa8e2dab7376d640131754aa4" ], "index": "pypi", - "version": "==2.8.3" + "version": "==3.0.1" }, "aiodns": { "hashes": [ @@ -53,10 +53,10 @@ }, "alabaster": { "hashes": [ - "sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732", - "sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0" + "sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456", + "sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7" ], - "version": "==0.7.10" + "version": "==0.7.11" }, "async-timeout": { "hashes": [ @@ -94,6 +94,15 @@ ], "version": "==3.0.4" }, + "deepdiff": { + "hashes": [ + "sha256:152b29dd9cd97cc78403121fb394925ec47377d4a410751e56547c3930ba2b39", + "sha256:b4150052e610b231885c4c0be3eea86e4c029df91550ec51b9fc14dd209a5055", + "sha256:ecad8e16a96ffd27e8f40c9801a6ab16ec6a7e7e6e6859a7710ba4695f22702c" + ], + "index": "pypi", + "version": "==3.3.0" + }, "discord": { "egg": "discord.py[voice]", "file": "https://github.com/Rapptz/discord.py/archive/rewrite.zip" @@ -108,10 +117,10 @@ }, "dulwich": { "hashes": [ - "sha256:c51e10c260543240e0806052af046e1a78b98cbe1ac1ef3880a78d2269e09da4" + "sha256:34f99e575fe1f1e89cca92cec1ddd50b4991199cb00609203b28df9eb83ce259" ], "index": "pypi", - "version": "==0.19.2" + "version": "==0.19.5" }, "fuzzywuzzy": { "hashes": [ @@ -123,10 +132,10 @@ }, "idna": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" ], - "version": "==2.6" + "version": "==2.7" }, "imagesize": { "hashes": [ @@ -142,6 +151,12 @@ ], "version": "==2.10" }, + "jsonpickle": { + "hashes": [ + "sha256:545b3bee0d65e1abb4baa1818edcc9ec239aa9f2ffbfde8084d71c056180054f" + ], + "version": "==0.9.6" + }, "logmatic-python": { "hashes": [ "sha256:0c15ac9f5faa6a60059b28910db642c3dc7722948c3cc940923f8c9039604342" @@ -151,37 +166,35 @@ }, "lxml": { "hashes": [ - "sha256:01c45df6d90497c20aa2a07789a41941f9a1029faa30bf725fc7f6d515b1afe9", - "sha256:0c9fef4f8d444e337df96c54544aeb85b7215b2ed7483bb6c35de97ac99f1bcd", - "sha256:0e3cd94c95d30ba9ca3cff40e9b2a14e1a10a4fd8131105b86c6b61648f57e4b", - "sha256:0e7996e9b46b4d8b4ac1c329a00e2d10edcd8380b95d2a676fccabf4c1dd0512", - "sha256:1858b1933d483ec5727549d3fe166eeb54229fbd6a9d3d7ea26d2c8a28048058", - "sha256:1b164bba1320b14905dcff77da10d5ce9c411ac4acc4fb4ed9a2a4d10fae38c9", - "sha256:1b46f37927fa6cd1f3fe34b54f1a23bd5bea1d905657289e08e1297069a1a597", - "sha256:231047b05907315ae9a9b6925751f9fd2c479cf7b100fff62485a25e382ca0d4", - "sha256:28f0c6652c1b130f1e576b60532f84b19379485eb8da6185c29bd8c9c9bc97bf", - "sha256:34d49d0f72dd82b9530322c48b70ac78cca0911275da741c3b1d2f3603c5f295", - "sha256:3682a17fbf72d56d7e46db2e80ca23850b79c28cfe75dcd9b82f58808f730909", - "sha256:3cf2830b9a6ad7f6e965fa53a768d4d2372a7856f20ffa6ce43d2fe9c0d34b19", - "sha256:5b653c9379ce29ce271fbe1010c5396670f018e78b643e21beefbb3dc6d291de", - "sha256:65a272821d5d8194358d6b46f3ca727fa56a6b63981606eac737c86d27309cdd", - "sha256:691f2cd97cf026c611df1ea5055755eec7f878f2d4f4330dc8686583de6fc5fd", - "sha256:6b6379495d3baacf7ed755ac68547c8dff6ce5d37bf370f0b7678888dc1283f9", - "sha256:75322a531504d4f383264391d89993a42e286da8821ddc5ac315e57305cb84f0", - "sha256:7f457cbda964257f443bac861d3a36732dcba8183149e7818ee2fb7c86901b94", - "sha256:7ff1fc76d8804e0f870c343a72007ff587090c218b0f92d8ee784ac2b6eaf5b9", - "sha256:8523fbde9c2216f3f2b950cb01ebe52e785eaa8a07ffeb456dd3576ca1b4fb9b", - "sha256:8f37627f16e026523fca326f1b5c9a43534862fede6c3e99c2ba6a776d75c1ab", - "sha256:a7182ea298cc3555ea56ffbb0748fe0d5e0d81451e2bc16d7f4645cd01b1ca70", - "sha256:abbd2fb4a5a04c11b5e04eb146659a0cf67bb237dd3d7ca3b9994d3a9f826e55", - "sha256:accc9f6b77bed0a6f267b4fae120f6008a951193d548cdbe9b61fc98a08b1cf8", - "sha256:bd88c8ce0d1504fdfd96a35911dd4f3edfb2e560d7cfdb5a3d09aa571ae5fbae", - "sha256:c557ad647facb3c0027a9d0af58853f905e85a0a2f04dcb73f8e665272fcdc3a", - "sha256:defabb7fbb99f9f7b3e0b24b286a46855caef4776495211b066e9e6592d12b04", - "sha256:e2629cdbcad82b83922a3488937632a4983ecc0fed3e5cfbf430d069382eeb9b" + "sha256:0941f4313208c07734410414d8308812b044fd3fb98573454e3d3a0d2e201f3d", + "sha256:0b18890aa5730f9d847bc5469e8820f782d72af9985a15a7552109a86b01c113", + "sha256:21f427945f612ac75576632b1bb8c21233393c961f2da890d7be3927a4b6085f", + "sha256:24cf6f622a4d49851afcf63ac4f0f3419754d4e98a7a548ab48dd03c635d9bd3", + "sha256:2dc6705486b8abee1af9e2a3761e30a3cb19e8276f20ca7e137ee6611b93707c", + "sha256:2e43b2e5b7d2b9abe6e0301eef2c2c122ab45152b968910eae68bdee2c4cfae0", + "sha256:329a6d8b6d36f7d6f8b6c6a1db3b2c40f7e30a19d3caf62023c9d6a677c1b5e1", + "sha256:423cde55430a348bda6f1021faad7235c2a95a6bdb749e34824e5758f755817a", + "sha256:4651ea05939374cfb5fe87aab5271ed38c31ea47997e17ec3834b75b94bd9f15", + "sha256:4be3bbfb2968d7da6e5c2cd4104fc5ec1caf9c0794f6cae724da5a53b4d9f5a3", + "sha256:622f7e40faef13d232fb52003661f2764ce6cdef3edb0a59af7c1559e4cc36d1", + "sha256:664dfd4384d886b239ef0d7ee5cff2b463831079d250528b10e394a322f141f9", + "sha256:697c0f58ac637b11991a1bc92e07c34da4a72e2eda34d317d2c1c47e2f24c1b3", + "sha256:6ec908b4c8a4faa7fe1a0080768e2ce733f268b287dfefb723273fb34141475f", + "sha256:7ec3fe795582b75bb49bb1685ffc462dbe38d74312dac07ce386671a28b5316b", + "sha256:8c39babd923c431dcf1e5874c0f778d3a5c745a62c3a9b6bd755efd489ee8a1d", + "sha256:949ca5bc56d6cb73d956f4862ba06ad3c5d2808eac76304284f53ae0c8b2334a", + "sha256:9f0daddeefb0791a600e6195441910bdf01eac470be596b9467e6122b51239a6", + "sha256:a359893b01c30e949eae0e8a85671a593364c9f0b8162afe0cb97317af0953bf", + "sha256:ad5d5d8efed59e6b1d4c50c1eac59fb6ecec91b2073676af1e15fc4d43e9b6c5", + "sha256:bc1a36f95a6b3667c09b34995fc3a46a82e4cf0dc3e7ab281e4c77b15bd7af05", + "sha256:be37b3f55b6d7d923f43bf74c356fc1878eb36e28505f38e198cb432c19c7b1a", + "sha256:c45bca5e544eb75f7500ffd730df72922eb878a2f0213b0dc5a5f357ded3a85d", + "sha256:ccee7ebbb4735ebc341d347fca9ee09f2fa6c0580528c1414bc4e1d31372835c", + "sha256:dc62c0840b2fc7753550b40405532a3e125c0d3761f34af948873393aa688160", + "sha256:f7d9d5aa1c7e54167f1a3cba36b5c52c7c540f30952c9bd7d9302a1eda318424" ], "index": "pypi", - "version": "==4.2.1" + "version": "==4.2.3" }, "markdownify": { "hashes": [ @@ -228,61 +241,41 @@ ], "version": "==17.1" }, - "pika": { - "hashes": [ - "sha256:63131aaeec48a6c8f1db1fe657e1e74cf384c3927eb7d1725e31edae4220dea4", - "sha256:7277b4d12a99efa4058782614d84138983f9f89d690bdfcea66290d810806459" - ], - "version": "==0.10.0" - }, "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" + "sha256:00def5b638994f888d1058e4d17c86dec8e1113c3741a0a8a659039aec59a83a", + "sha256:026449b64e559226cdb8e6d8c931b5965d8fc90ec18ebbb0baa04c5b36503c72", + "sha256:03dbb224ee196ef30ed2156d41b579143e1efeb422974719a5392fc035e4f574", + "sha256:03eb0e04f929c102ae24bc436bf1c0c60a4e63b07ebd388e84d8b219df3e6acd", + "sha256:1be66b9a89e367e7d20d6cae419794997921fe105090fafd86ef39e20a3baab2", + "sha256:1e977a3ed998a599bda5021fb2c2889060617627d3ae228297a529a082a3cd5c", + "sha256:22cf3406d135cfcc13ec6228ade774c8461e125c940e80455f500638429be273", + "sha256:24adccf1e834f82718c7fc8e3ec1093738da95144b8b1e44c99d5fc7d3e9c554", + "sha256:2a3e362c97a5e6a259ee9cd66553292a1f8928a5bdfa3622fdb1501570834612", + "sha256:3832e26ecbc9d8a500821e3a1d3765bda99d04ae29ffbb2efba49f5f788dc934", + "sha256:4fd1f0c2dc02aaec729d91c92cd85a2df0289d88e9f68d1e8faba750bb9c4786", + "sha256:4fda62030f2c515b6e2e673c57caa55cb04026a81968f3128aae10fc28e5cc27", + "sha256:5044d75a68b49ce36a813c82d8201384207112d5d81643937fc758c05302f05b", + "sha256:522184556921512ec484cb93bd84e0bab915d0ac5a372d49571c241a7f73db62", + "sha256:5914cff11f3e920626da48e564be6818831713a3087586302444b9c70e8552d9", + "sha256:6661a7908d68c4a133e03dac8178287aa20a99f841ea90beeb98a233ae3fd710", + "sha256:79258a8df3e309a54c7ef2ef4a59bb8e28f7e4a8992a3ad17c24b1889ced44f3", + "sha256:7d74c20b8f1c3e99d3f781d3b8ff5abfefdd7363d61e23bdeba9992ff32cc4b4", + "sha256:81918afeafc16ba5d9d0d4e9445905f21aac969a4ebb6f2bff4b9886da100f4b", + "sha256:8194d913ca1f459377c8a4ed8f9b7ad750068b8e0e3f3f9c6963fcc87a84515f", + "sha256:84d5d31200b11b3c76fab853b89ac898bf2d05c8b3da07c1fcc23feb06359d6e", + "sha256:989981db57abffb52026b114c9a1f114c7142860a6d30a352d28f8cbf186500b", + "sha256:a3d7511d3fad1618a82299aab71a5fceee5c015653a77ffea75ced9ef917e71a", + "sha256:b3ef168d4d6fd4fa6685aef7c91400f59f7ab1c0da734541f7031699741fb23f", + "sha256:c1c5792b6e74bbf2af0f8e892272c2a6c48efa895903211f11b8342e03129fea", + "sha256:c5dcb5a56aebb8a8f2585042b2f5c496d7624f0bcfe248f0cc33ceb2fd8d39e7", + "sha256:e2bed4a04e2ca1050bb5f00865cf2f83c0b92fd62454d9244f690fcd842e27a4", + "sha256:e87a527c06319428007e8c30511e1f0ce035cb7f14bb4793b003ed532c3b9333", + "sha256:f63e420180cbe22ff6e32558b612e75f50616fc111c5e095a4631946c782e109", + "sha256:f8b3d413c5a8f84b12cd4c5df1d8e211777c9852c6be3ee9c094b626644d3eab" ], "index": "pypi", - "version": "==5.1.0" + "version": "==5.2.0" }, "pycares": { "hashes": [ @@ -320,11 +313,6 @@ "pyparsing": { "hashes": [ "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", - "sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07", - "sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18", - "sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e", - "sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5", - "sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58", "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" ], "version": "==2.2.0" @@ -339,10 +327,11 @@ }, "python-json-logger": { "hashes": [ - "sha256:30999d1d742ecf6645991a2ce9273188505e98b713ad63be06aabff47dd1b3c4", - "sha256:8205cfe7061715de5cd1b37e3565d5b97d0ac13b30ff3ee612554abb6093d640" + "sha256:a292e22c5e03105a05a746ade6209d43db1c4c763b91c75c8486e81d10904d85", + "sha256:e3636824d35ba6a15fc39f573588cba63cf46322a5dc86fb2f280229077e9fbe" ], - "version": "==0.1.8" + "markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==0.1.9" }, "python-levenshtein": { "hashes": [ @@ -353,37 +342,34 @@ }, "pytz": { "hashes": [ - "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", - "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" + "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", + "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" ], - "version": "==2018.4" + "version": "==2018.5" }, "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" + "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", + "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", + "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", + "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", + "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", + "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", + "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", + "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", + "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", + "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", + "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" ], "index": "pypi", - "version": "==3.12" + "version": "==3.13" }, "requests": { "hashes": [ - "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" ], - "version": "==2.18.4" + "version": "==2.19.1" }, "shortuuid": { "hashes": [ @@ -407,32 +393,34 @@ }, "sphinx": { "hashes": [ - "sha256:85f7e32c8ef07f4ba5aeca728e0f7717bef0789fba8458b8d9c5c294cad134f3", - "sha256:d45480a229edf70d84ca9fae3784162b1bc75ee47e480ffe04a4b7f21a95d76d" + "sha256:217ad9ece2156ed9f8af12b5d2c82a499ddf2c70a33c5f81864a08d8c67b9efc", + "sha256:a765c6db1e5b62aae857697cd4402a5c1a315a7b0854bbcd0fc8cdc524da5896" ], "index": "pypi", - "version": "==1.7.5" + "version": "==1.7.6" }, "sphinxcontrib-websupport": { "hashes": [ - "sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9", - "sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2" + "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", + "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" ], - "version": "==1.0.1" + "markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==1.1.0" }, "sympy": { "hashes": [ - "sha256:ac5b57691bc43919dcc21167660a57cc51797c28a4301a6144eff07b751216a4" + "sha256:286ca070d72e250861dea7a21ab44f541cb2341e8268c70264cf8642dbd9225f" ], "index": "pypi", - "version": "==1.1.1" + "version": "==1.2" }, "urllib3": { "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" ], - "version": "==1.22" + "markers": "python_version != '3.0.*' and python_version < '4' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.6' and python_version != '3.1.*'", + "version": "==1.23" }, "websockets": { "hashes": [ @@ -537,11 +525,11 @@ }, "flake8-import-order": { "hashes": [ - "sha256:40d2a39ed91e080f3285f4c16256b252d7c31070e7f11b7854415bb9f924ea81", - "sha256:68d430781a9ef15c85a0121500cf8462f1a4bc7672acb2a32bfdbcab044ae0b7" + "sha256:9be5ca10d791d458eaa833dd6890ab2db37be80384707b0f76286ddd13c16cbf", + "sha256:feca2fd0a17611b33b7fa84449939196c2c82764e262486d5c3e143ed77d387b" ], "index": "pypi", - "version": "==0.17.1" + "version": "==0.18" }, "flake8-string-format": { "hashes": [ @@ -568,10 +556,10 @@ }, "idna": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" ], - "version": "==2.6" + "version": "==2.7" }, "mccabe": { "hashes": [ @@ -604,49 +592,41 @@ "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" + "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", + "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", + "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", + "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", + "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", + "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", + "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", + "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", + "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", + "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", + "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" ], "index": "pypi", - "version": "==3.12" + "version": "==3.13" }, "requests": { "hashes": [ - "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" ], - "version": "==2.18.4" + "version": "==2.19.1" }, "safety": { "hashes": [ - "sha256:0bd2a26b872668767c6db8efecfc8869b547463bedff5e7cd7b52f037aa6f200", - "sha256:fc3fc55656f1c909d65311b49a38211c42c937f57a05393289fb3f17cadfa4a1" + "sha256:32d41b8bbd736db749aa2162de6c0bb11c2113c7bc0357476491f96cd5d58299", + "sha256:34227360409ffb1bc2657e5b6ff3472a32d72b917617cd3d2914ddf078c263b9" ], "index": "pypi", - "version": "==1.8.1" + "version": "==1.8.2" }, "six": { "hashes": [ @@ -657,10 +637,11 @@ }, "urllib3": { "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" ], - "version": "==1.22" + "markers": "python_version != '3.0.*' and python_version < '4' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.6' and python_version != '3.1.*'", + "version": "==1.23" } } } diff --git a/bot/__main__.py b/bot/__main__.py index e4fe78c40..ceab87f72 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -24,7 +24,8 @@ bot = Bot( "aliases": ["help"] }, formatter=Formatter(), - case_insensitive=True + case_insensitive=True, + max_messages=10_000 ) # Global aiohttp session for all cogs @@ -47,10 +48,12 @@ else: # Internal/debug bot.load_extension("bot.cogs.logging") +bot.load_extension("bot.cogs.modlog") bot.load_extension("bot.cogs.security") bot.load_extension("bot.cogs.events") # Commands, etc +bot.load_extension("bot.cogs.bigbrother") bot.load_extension("bot.cogs.bot") bot.load_extension("bot.cogs.cogs") diff --git a/bot/cogs/bigbrother.py b/bot/cogs/bigbrother.py new file mode 100644 index 000000000..4d0996122 --- /dev/null +++ b/bot/cogs/bigbrother.py @@ -0,0 +1,182 @@ +import logging +from typing import List, Union + +from discord import Color, Embed, Guild, Member, Message, TextChannel, User +from discord.ext.commands import Bot, Context, command + +from bot.constants import Channels, Emojis, Guild as GuildConfig, Keys, Roles, URLs +from bot.decorators import with_role +from bot.pagination import LinePaginator + + +log = logging.getLogger(__name__) + + +class BigBrother: + """User monitoring to assist with moderation.""" + + HEADERS = {'X-API-Key': Keys.site_api} + + def __init__(self, bot: Bot): + self.bot = bot + self.watched_users = {} + + def update_cache(self, api_response: List[dict]): + """ + Updates the internal cache of watched users from the given `api_response`. + This function will only add (or update) existing keys, it will not delete + keys that were not present in the API response. + A user is only added if the bot can find a channel + with the given `channel_id` in its channel cache. + """ + + for entry in api_response: + user_id = int(entry['user_id']) + channel_id = int(entry['channel_id']) + channel = self.bot.get_channel(channel_id) + + if channel is not None: + self.watched_users[user_id] = channel + else: + log.error( + f"Site specified to relay messages by `{user_id}` in `{channel_id}`, " + "but the given channel could not be found. Ignoring." + ) + + async def on_ready(self): + async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: + data = await response.json() + self.update_cache(data) + + async def on_member_ban(self, guild: Guild, user: Union[User, Member]): + if guild.id == GuildConfig.id and user.id in self.watched_users: + url = f"{URLs.site_bigbrother_api}?user_id={user.id}" + channel = self.watched_users[user.id] + + async with self.bot.http_session.delete(url, headers=self.HEADERS) as response: + del self.watched_users[user.id] + if response.status == 204: + await channel.send( + f"{Emojis.lemoneye2}:hammer: {user} got banned, so " + f"`BigBrother` will no longer relay their messages to {channel}" + ) + + else: + data = await response.json() + reason = data.get('error_message', "no message provided") + await channel.send( + f"{Emojis.lemoneye2}:x: {user} got banned, but trying to remove them from" + f"BigBrother's user dictionary on the API returned an error: {reason}" + ) + + async def on_message(self, msg: Message): + if msg.author.id in self.watched_users: + channel = self.watched_users[msg.author.id] + relay_content = (f"{Emojis.lemoneye2} {msg.author} sent the following " + f"in {msg.channel.mention}: {msg.clean_content}") + if msg.attachments: + relay_content += f" (with {len(msg.attachments)} attachment(s))" + + await channel.send(relay_content) + + @command(name='bigbrother.watched()', aliases=('bigbrother.watched',)) + @with_role(Roles.owner, Roles.admin, Roles.moderator) + async def watched_command(self, ctx: Context, from_cache: bool = True): + """ + Shows all users that are currently monitored and in which channel. + By default, the users are returned from the cache. + If this is not desired, `from_cache` can be given as a falsy value, e.g. e.g. 'no'. + """ + + if from_cache: + lines = tuple( + f"• <@{user_id}> in <#{self.watched_users[user_id].id}>" + for user_id in self.watched_users + ) + await LinePaginator.paginate( + lines or ("There's nothing here yet.",), + ctx, + Embed(title="Watched users (cached)", color=Color.blue()), + empty=False + ) + + else: + async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: + if response.status == 200: + data = await response.json() + self.update_cache(data) + lines = tuple(f"• <@{entry['user_id']}> in <#{entry['channel_id']}>" for entry in data) + + await LinePaginator.paginate( + lines or ("There's nothing here yet.",), + ctx, + Embed(title="Watched users", color=Color.blue()), + empty=False + ) + + else: + await ctx.send(f":x: got non-200 response from the API") + + @command(name='bigbrother.watch()', aliases=('bigbrother.watch',)) + @with_role(Roles.owner, Roles.admin, Roles.moderator) + async def watch_command(self, ctx: Context, user: User, channel: TextChannel = None): + """ + Relay messages sent by the given `user` in the given `channel`. + If `channel` is not specified, logs to the mod log channel. + """ + + if channel is not None: + channel_id = channel.id + else: + channel_id = Channels.big_brother_logs + + post_data = { + 'user_id': str(user.id), + 'channel_id': str(channel_id) + } + + async with self.bot.http_session.post( + URLs.site_bigbrother_api, + headers=self.HEADERS, + json=post_data + ) as response: + if response.status == 204: + await ctx.send(f":ok_hand: will now relay messages sent by {user} in <#{channel_id}>") + + channel = self.bot.get_channel(channel_id) + if channel is None: + log.error( + f"could not update internal cache, failed to find a channel with ID {channel_id}" + ) + else: + self.watched_users[user.id] = channel + + else: + data = await response.json() + reason = data.get('error_message', "no message provided") + await ctx.send(f":x: the API returned an error: {reason}") + + @command(name='bigbrother.unwatch()', aliases=('bigbrother.unwatch',)) + @with_role(Roles.owner, Roles.admin, Roles.moderator) + async def unwatch_command(self, ctx: Context, user: User): + """Stop relaying messages by the given `user`.""" + + url = f"{URLs.site_bigbrother_api}?user_id={user.id}" + async with self.bot.http_session.delete(url, headers=self.HEADERS) as response: + if response.status == 204: + await ctx.send(f":ok_hand: will no longer relay messages sent by {user}") + + if user.id in self.watched_users: + del self.watched_users[user.id] + else: + log.warning(f"user {user.id} was unwatched but was not found in the cache") + + else: + data = await response.json() + reason = data.get('error_message', "no message provided") + await ctx.send(f":x: the API returned an error: {reason}") + + +def setup(bot: Bot): + bot.add_cog(BigBrother(bot)) + log.info("Cog loaded: BigBrother") diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index af9abd7cb..e79fc7ada 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -25,10 +25,12 @@ class Bot: # Stores allowed channels plus epoch time since last call. self.channel_cooldowns = { - Channels.help0: 0, - Channels.help1: 0, - Channels.help2: 0, - Channels.help3: 0, + Channels.help_0: 0, + Channels.help_1: 0, + Channels.help_2: 0, + Channels.help_3: 0, + Channels.help_4: 0, + Channels.help_5: 0, Channels.python: 0, } diff --git a/bot/cogs/cogs.py b/bot/cogs/cogs.py index 7eaf5005c..ef13aef3f 100644 --- a/bot/cogs/cogs.py +++ b/bot/cogs/cogs.py @@ -12,6 +12,8 @@ from bot.pagination import LinePaginator log = logging.getLogger(__name__) +KEEP_LOADED = ["bot.cogs.cogs", "bot.cogs.modlog"] + class Cogs: """ @@ -122,9 +124,9 @@ class Cogs: embed.description = f"Unknown cog: {cog}" if full_cog: - if full_cog == "bot.cogs.cogs": - log.warning(f"{ctx.author} requested we unload the cog management cog, that sneaky pete. We said no.") - embed.description = "You may not unload the cog management cog!" + if full_cog in KEEP_LOADED: + log.warning(f"{ctx.author} requested we unload `{full_cog}`, that sneaky pete. We said no.") + embed.description = f"You may not unload `{full_cog}`!" elif full_cog in self.bot.extensions: try: self.bot.unload_extension(full_cog) diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index 26079e3ec..e6d108720 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -17,6 +17,7 @@ from sphinx.ext import intersphinx from bot.constants import ERROR_REPLIES, Keys, Roles, URLs from bot.converters import ValidPythonIdentifier, ValidURL from bot.decorators import with_role +from bot.pagination import LinePaginator log = logging.getLogger(__name__) @@ -371,15 +372,13 @@ class Doc: """ if symbol is None: - all_inventories = "\n".join( - f"• [`{name}`]({url})" for name, url in self.base_urls.items() - ) inventory_embed = discord.Embed( - title="All inventories", - description=all_inventories or "*Seems like there's nothing here yet.*", + title=f"All inventories (`{len(self.base_urls)}` total)", colour=discord.Colour.blue() ) - await ctx.send(embed=inventory_embed) + + lines = sorted(f"• [`{name}`]({url})" for name, url in self.base_urls.items()) + await LinePaginator.paginate(lines, ctx, inventory_embed, max_size=400, empty=False) else: # Fetching documentation for a symbol (at least for the first time, since diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py new file mode 100644 index 000000000..87cea2b5a --- /dev/null +++ b/bot/cogs/modlog.py @@ -0,0 +1,658 @@ +import asyncio +import datetime +import logging +from typing import List, Optional, Union + +from dateutil.relativedelta import relativedelta +from deepdiff import DeepDiff +from discord import ( + CategoryChannel, Colour, Embed, File, Guild, + Member, Message, NotFound, RawBulkMessageDeleteEvent, + RawMessageDeleteEvent, RawMessageUpdateEvent, Role, + TextChannel, User, VoiceChannel) +from discord.abc import GuildChannel +from discord.ext.commands import Bot + +from bot.constants import Channels, Emojis, Icons +from bot.constants import Guild as GuildConstant +from bot.utils.time import humanize + + +log = logging.getLogger(__name__) + +BULLET_POINT = "\u2022" +COLOUR_RED = Colour(0xcd6d6d) +COLOUR_GREEN = Colour(0x68c290) +GUILD_CHANNEL = Union[CategoryChannel, TextChannel, VoiceChannel] + +CHANNEL_CHANGES_UNSUPPORTED = ("permissions",) +CHANNEL_CHANGES_SUPPRESSED = ("_overwrites", "position") +MEMBER_CHANGES_SUPPRESSED = ("activity", "status") +ROLE_CHANGES_UNSUPPORTED = ("colour", "permissions") + + +class ModLog: + """ + Logging for server events and staff actions + """ + + def __init__(self, bot: Bot): + self.bot = bot + self._ignored_deletions = [] + + self._cached_deletes = [] + self._cached_edits = [] + + def ignore_message_deletion(self, *message_ids: int): + for message_id in message_ids: + if message_id not in self._ignored_deletions: + self._ignored_deletions.append(message_id) + + async def send_log_message( + self, icon_url: Optional[str], colour: Colour, title: Optional[str], text: str, thumbnail: str = None, + channel_id: int = Channels.modlog, ping_everyone: bool = False, files: List[File] = None + ): + embed = Embed(description=text) + + if title and icon_url: + embed.set_author(name=title, icon_url=icon_url) + + embed.colour = colour + embed.timestamp = datetime.datetime.utcnow() + + if thumbnail is not None: + embed.set_thumbnail(url=thumbnail) + + content = None + + if ping_everyone: + content = "@everyone" + + await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) + + async def on_guild_channel_create(self, channel: GUILD_CHANNEL): + if channel.guild.id != GuildConstant.id: + return + + if isinstance(channel, CategoryChannel): + title = "Category created" + message = f"{channel.name} (`{channel.id}`)" + elif isinstance(channel, VoiceChannel): + title = "Voice channel created" + + if channel.category: + message = f"{channel.category}/{channel.name} (`{channel.id}`)" + else: + message = f"{channel.name} (`{channel.id}`)" + else: + title = "Text channel created" + + if channel.category: + message = f"{channel.category}/{channel.name} (`{channel.id}`)" + else: + message = f"{channel.name} (`{channel.id}`)" + + await self.send_log_message(Icons.hash_green, COLOUR_GREEN, title, message) + + async def on_guild_channel_delete(self, channel: GUILD_CHANNEL): + if channel.guild.id != GuildConstant.id: + return + + if isinstance(channel, CategoryChannel): + title = "Category deleted" + elif isinstance(channel, VoiceChannel): + title = "Voice channel deleted" + else: + title = "Text channel deleted" + + if channel.category and not isinstance(channel, CategoryChannel): + message = f"{channel.category}/{channel.name} (`{channel.id}`)" + else: + message = f"{channel.name} (`{channel.id}`)" + + await self.send_log_message( + Icons.hash_red, COLOUR_RED, + title, message + ) + + async def on_guild_channel_update(self, before: GUILD_CHANNEL, after: GuildChannel): + if before.guild.id != GuildConstant.id: + return + + diff = DeepDiff(before, after) + changes = [] + done = [] + + diff_values = diff.get("values_changed", {}) + diff_values.update(diff.get("type_changes", {})) + + for key, value in diff_values.items(): + if not key: # Not sure why, but it happens + continue + + key = key[5:] # Remove "root." prefix + + if "[" in key: + key = key.split("[", 1)[0] + + if "." in key: + key = key.split(".", 1)[0] + + if key in done or key in CHANNEL_CHANGES_SUPPRESSED: + continue + + if key in CHANNEL_CHANGES_UNSUPPORTED: + changes.append(f"**{key.title()}** updated") + else: + new = value["new_value"] + old = value["old_value"] + + changes.append(f"**{key.title()}:** `{old}` **->** `{new}`") + + done.append(key) + + if not changes: + return + + message = "" + + for item in sorted(changes): + message += f"{BULLET_POINT} {item}\n" + + if after.category: + message = f"**{after.category}/#{after.name} (`{after.id}`)**\n{message}" + else: + message = f"**#{after.name}** (`{after.id}`)\n{message}" + + await self.send_log_message( + Icons.hash_blurple, Colour.blurple(), + "Channel updated", message + ) + + async def on_guild_role_create(self, role: Role): + if role.guild.id != GuildConstant.id: + return + + await self.send_log_message( + Icons.crown_green, COLOUR_GREEN, + "Role created", f"`{role.id}`" + ) + + async def on_guild_role_delete(self, role: Role): + if role.guild.id != GuildConstant.id: + return + + await self.send_log_message( + Icons.crown_red, COLOUR_RED, + "Role removed", f"{role.name} (`{role.id}`)" + ) + + async def on_guild_role_update(self, before: Role, after: Role): + if before.guild.id != GuildConstant.id: + return + + diff = DeepDiff(before, after) + changes = [] + done = [] + + diff_values = diff.get("values_changed", {}) + diff_values.update(diff.get("type_changes", {})) + + for key, value in diff_values.items(): + if not key: # Not sure why, but it happens + continue + + key = key[5:] # Remove "root." prefix + + if "[" in key: + key = key.split("[", 1)[0] + + if "." in key: + key = key.split(".", 1)[0] + + if key in done or key == "color": + continue + + if key in ROLE_CHANGES_UNSUPPORTED: + changes.append(f"**{key.title()}** updated") + else: + new = value["new_value"] + old = value["old_value"] + + changes.append(f"**{key.title()}:** `{old}` **->** `{new}`") + + done.append(key) + + if not changes: + return + + message = "" + + for item in sorted(changes): + message += f"{BULLET_POINT} {item}\n" + + message = f"**{after.name}** (`{after.id}`)\n{message}" + + await self.send_log_message( + Icons.crown_blurple, Colour.blurple(), + "Role updated", message + ) + + async def on_guild_update(self, before: Guild, after: Guild): + if before.id != GuildConstant.id: + return + + diff = DeepDiff(before, after) + changes = [] + done = [] + + diff_values = diff.get("values_changed", {}) + diff_values.update(diff.get("type_changes", {})) + + for key, value in diff_values.items(): + if not key: # Not sure why, but it happens + continue + + key = key[5:] # Remove "root." prefix + + if "[" in key: + key = key.split("[", 1)[0] + + if "." in key: + key = key.split(".", 1)[0] + + if key in done: + continue + + new = value["new_value"] + old = value["old_value"] + + changes.append(f"**{key.title()}:** `{old}` **->** `{new}`") + + done.append(key) + + if not changes: + return + + message = "" + + for item in sorted(changes): + message += f"{BULLET_POINT} {item}\n" + + message = f"**{after.name}** (`{after.id}`)\n{message}" + + await self.send_log_message( + Icons.guild_update, Colour.blurple(), + "Guild updated", message, + thumbnail=after.icon_url_as(format="png") + ) + + async def on_member_ban(self, guild: Guild, member: Union[Member, User]): + if guild.id != GuildConstant.id: + return + + await self.send_log_message( + Icons.user_ban, COLOUR_RED, + "User banned", f"{member.name}#{member.discriminator} (`{member.id}`)", + thumbnail=member.avatar_url_as(static_format="png") + ) + + async def on_member_join(self, member: Member): + if member.guild.id != GuildConstant.id: + return + + message = f"{member.name}#{member.discriminator} (`{member.id}`)" + + now = datetime.datetime.utcnow() + difference = abs(relativedelta(now, member.created_at)) + + message += "\n\n**Account age:** " + humanize(difference) + + if difference.days < 1 and difference.months < 1 and difference.years < 1: # New user account! + message = f"{Emojis.new} {message}" + + await self.send_log_message( + Icons.sign_in, COLOUR_GREEN, + "User joined", message, + thumbnail=member.avatar_url_as(static_format="png") + ) + + async def on_member_remove(self, member: Member): + if member.guild.id != GuildConstant.id: + return + + await self.send_log_message( + Icons.sign_out, COLOUR_RED, + "User left", f"{member.name}#{member.discriminator} (`{member.id}`)", + thumbnail=member.avatar_url_as(static_format="png") + ) + + async def on_member_unban(self, guild: Guild, member: User): + if guild.id != GuildConstant.id: + return + + await self.send_log_message( + Icons.user_unban, Colour.blurple(), + "User unbanned", f"{member.name}#{member.discriminator} (`{member.id}`)", + thumbnail=member.avatar_url_as(static_format="png") + ) + + async def on_member_update(self, before: Member, after: Member): + if before.guild.id != GuildConstant.id: + return + + diff = DeepDiff(before, after) + changes = [] + done = [] + + diff_values = {} + + diff_values.update(diff.get("values_changed", {})) + diff_values.update(diff.get("type_changes", {})) + diff_values.update(diff.get("iterable_item_removed", {})) + diff_values.update(diff.get("iterable_item_added", {})) + + diff_user = DeepDiff(before._user, after._user) + + diff_values.update(diff_user.get("values_changed", {})) + diff_values.update(diff_user.get("type_changes", {})) + diff_values.update(diff_user.get("iterable_item_removed", {})) + diff_values.update(diff_user.get("iterable_item_added", {})) + + for key, value in diff_values.items(): + if not key: # Not sure why, but it happens + continue + + key = key[5:] # Remove "root." prefix + + if "[" in key: + key = key.split("[", 1)[0] + + if "." in key: + key = key.split(".", 1)[0] + + if key in done or key in MEMBER_CHANGES_SUPPRESSED: + continue + + if key == "roles": + new_roles = after.roles + old_roles = before.roles + + for role in old_roles: + if role not in new_roles: + changes.append(f"**Role removed:** {role.name} (`{role.id}`)") + + for role in new_roles: + if role not in old_roles: + changes.append(f"**Role added:** {role.name} (`{role.id}`)") + + else: + new = value["new_value"] + old = value["old_value"] + + changes.append(f"**{key.title()}:** `{old}` **->** `{new}`") + + done.append(key) + + if before.name != after.name: + changes.append( + f"**Username:** `{before.name}` **->** `{after.name}`" + ) + + if before.discriminator != after.discriminator: + changes.append( + f"**Discriminator:** `{before.discriminator}` **->** `{after.discriminator}`" + ) + + if not changes: + return + + message = "" + + for item in sorted(changes): + message += f"{BULLET_POINT} {item}\n" + + message = f"**{after.name}#{after.discriminator}** (`{after.id}`)\n{message}" + + await self.send_log_message( + Icons.user_update, Colour.blurple(), + "Member updated", message, + thumbnail=after.avatar_url_as(static_format="png") + ) + + async def on_raw_bulk_message_delete(self, event: RawBulkMessageDeleteEvent): + if event.guild_id != GuildConstant.id or event.channel_id in GuildConstant.ignored: + return + + # Could upload the log to the site - maybe we should store all the messages somewhere? + # Currently if messages aren't in the cache, we ain't gonna have 'em. + + ignored_messages = 0 + + for message_id in event.message_ids: + if message_id in self._ignored_deletions: + self._ignored_deletions.remove(message_id) + ignored_messages += 1 + + if ignored_messages >= len(event.message_ids): + return + + channel = self.bot.get_channel(event.channel_id) + + if channel.category: + message = f"{len(event.message_ids)} deleted in {channel.category}/#{channel.name} (`{channel.id}`)" + else: + message = f"{len(event.message_ids)} deleted in #{channel.name} (`{channel.id}`)" + + await self.send_log_message( + Icons.message_bulk_delete, Colour.orange(), + "Bulk message delete", + message, channel_id=Channels.devalerts, + ping_everyone=True + ) + + async def on_message_delete(self, message: Message): + channel = message.channel + author = message.author + + if message.guild.id != GuildConstant.id or channel.id in GuildConstant.ignored: + return + + self._cached_deletes.append(message.id) + + if message.id in self._ignored_deletions: + self._ignored_deletions.remove(message.id) + return + + if author.bot: + return + + if channel.category: + response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{message.id}`\n" + "\n" + f"{message.clean_content}" + ) + else: + response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** #{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{message.id}`\n" + "\n" + f"{message.clean_content}" + ) + + if message.attachments: + # Prepend the message metadata with the number of attachments + response = f"**Attachments:** {len(message.attachments)}\n" + response + + await self.send_log_message( + Icons.message_delete, COLOUR_RED, + "Message deleted", + response, + channel_id=Channels.message_log + ) + + async def on_raw_message_delete(self, event: RawMessageDeleteEvent): + if event.guild_id != GuildConstant.id or event.channel_id in GuildConstant.ignored: + return + + await asyncio.sleep(1) # Wait here in case the normal event was fired + + if event.message_id in self._cached_deletes: + # It was in the cache and the normal event was fired, so we can just ignore it + self._cached_deletes.remove(event.message_id) + return + + if event.message_id in self._ignored_deletions: + self._ignored_deletions.remove(event.message_id) + return + + channel = self.bot.get_channel(event.channel_id) + + if channel.category: + response = ( + f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{event.message_id}`\n" + "\n" + "This message was not cached, so the message content cannot be displayed." + ) + else: + response = ( + f"**Channel:** #{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{event.message_id}`\n" + "\n" + "This message was not cached, so the message content cannot be displayed." + ) + + await self.send_log_message( + Icons.message_delete, COLOUR_RED, + "Message deleted", + response, + channel_id=Channels.message_log + ) + + async def on_message_edit(self, before: Message, after: Message): + if before.guild.id != GuildConstant.id or before.channel.id in GuildConstant.ignored or before.author.bot: + return + + self._cached_edits.append(before.id) + + if before.content == after.content: + return + + author = before.author + channel = before.channel + + if channel.category: + before_response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{before.id}`\n" + "\n" + f"{before.clean_content}" + ) + + after_response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{before.id}`\n" + "\n" + f"{after.clean_content}" + ) + else: + before_response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** #{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{before.id}`\n" + "\n" + f"{before.clean_content}" + ) + + after_response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** #{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{before.id}`\n" + "\n" + f"{after.clean_content}" + ) + + await self.send_log_message( + Icons.message_edit, Colour.blurple(), "Message edited (Before)", + before_response, channel_id=Channels.message_log + ) + + await self.send_log_message( + Icons.message_edit, Colour.blurple(), "Message edited (After)", + after_response, channel_id=Channels.message_log + ) + + async def on_raw_message_edit(self, event: RawMessageUpdateEvent): + try: + channel = self.bot.get_channel(int(event.data["channel_id"])) + message = await channel.get_message(event.message_id) + except NotFound: # Was deleted before we got the event + return + + if message.guild.id != GuildConstant.id or message.channel.id in GuildConstant.ignored or message.author.bot: + return + + await asyncio.sleep(1) # Wait here in case the normal event was fired + + if event.message_id in self._cached_edits: + # It was in the cache and the normal event was fired, so we can just ignore it + self._cached_edits.remove(event.message_id) + return + + author = message.author + channel = message.channel + + if channel.category: + before_response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{message.id}`\n" + "\n" + "This message was not cached, so the message content cannot be displayed." + ) + + after_response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{message.id}`\n" + "\n" + f"{message.clean_content}" + ) + else: + before_response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** #{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{message.id}`\n" + "\n" + "This message was not cached, so the message content cannot be displayed." + ) + + after_response = ( + f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Channel:** #{channel.name} (`{channel.id}`)\n" + f"**Message ID:** `{message.id}`\n" + "\n" + f"{message.clean_content}" + ) + + await self.send_log_message( + Icons.message_edit, Colour.blurple(), "Message edited (Before)", + before_response, channel_id=Channels.message_log + ) + + await self.send_log_message( + Icons.message_edit, Colour.blurple(), "Message edited (After)", + after_response, channel_id=Channels.message_log + ) + + +def setup(bot): + bot.add_cog(ModLog(bot)) + log.info("Cog loaded: ModLog") diff --git a/bot/cogs/snekbox.py b/bot/cogs/snekbox.py index 6bc333ff6..69a2ed59e 100644 --- a/bot/cogs/snekbox.py +++ b/bot/cogs/snekbox.py @@ -1,11 +1,16 @@ import datetime import logging +import random import re -from discord.ext.commands import Bot, Context, command +from discord import Colour, Embed +from discord.ext.commands import ( + Bot, CommandError, Context, MissingPermissions, + NoPrivateMessage, check, command, guild_only +) from bot.cogs.rmq import RMQ -from bot.constants import URLs +from bot.constants import Channels, ERROR_REPLIES, NEGATIVE_REPLIES, Roles, URLs log = logging.getLogger(__name__) @@ -26,6 +31,21 @@ except Exception as e: """ ESCAPE_REGEX = re.compile("[`\u202E\u200B]{3,}") +BYPASS_ROLES = (Roles.owner, Roles.admin, Roles.moderator, Roles.helpers) +WHITELISTED_CHANNELS = (Channels.bot,) +WHITELISTED_CHANNELS_STRING = ', '.join(f"<#{channel_id}>" for channel_id in WHITELISTED_CHANNELS) + + +async def channel_is_whitelisted_or_author_can_bypass(ctx: Context): + """ + Checks that the author is either helper or above + or the channel is a whitelisted channel. + """ + + if ctx.channel.id not in WHITELISTED_CHANNELS and ctx.author.top_role.id not in BYPASS_ROLES: + raise MissingPermissions("You are not allowed to do that here.") + + return True class Snekbox: @@ -44,6 +64,8 @@ class Snekbox: return self.bot.get_cog("RMQ") @command(name="snekbox.eval()", aliases=["snekbox.eval", "eval()", "eval"]) + @guild_only() + @check(channel_is_whitelisted_or_author_can_bypass) async def do_eval(self, ctx: Context, code: str): """ Run some code. get the result back. We've done our best to make this safe, but do let us know if you @@ -81,6 +103,9 @@ class Snekbox: if "<@" in output: output = output.replace("<@", "<@\u200B") # Zero-width space + if "<!@" in output: + output = output.replace("<!@", "<!@\u200B") # Zero-width space + if ESCAPE_REGEX.findall(output): output = "Code block escape attempt detected; will not output result" else: @@ -134,6 +159,27 @@ class Snekbox: del self.jobs[ctx.author.id] raise + @do_eval.error + async def eval_command_error(self, ctx: Context, error: CommandError): + embed = Embed(colour=Colour.red()) + + if isinstance(error, NoPrivateMessage): + embed.title = random.choice(NEGATIVE_REPLIES) + embed.description = "You're not allowed to use this command in private messages." + await ctx.send(embed=embed) + + elif isinstance(error, MissingPermissions): + embed.title = random.choice(NEGATIVE_REPLIES) + embed.description = f"Sorry, but you may only use this command within {WHITELISTED_CHANNELS_STRING}." + await ctx.send(embed=embed) + + else: + original_error = getattr(error, 'original', "no original error") + log.error(f"Unhandled error in snekbox eval: {error} ({original_error})") + embed.title = random.choice(ERROR_REPLIES) + embed.description = "Some unhandled error occurred. Sorry for that!" + await ctx.send(embed=embed) + def setup(bot): bot.add_cog(Snekbox(bot)) diff --git a/bot/cogs/verification.py b/bot/cogs/verification.py index 198fe861d..621610903 100644 --- a/bot/cogs/verification.py +++ b/bot/cogs/verification.py @@ -3,6 +3,7 @@ import logging from discord import Message, NotFound, Object from discord.ext.commands import Bot, Context, command +from bot.cogs.modlog import ModLog from bot.constants import Channels, Roles from bot.decorators import in_channel, without_role @@ -35,6 +36,10 @@ class Verification: def __init__(self, bot: Bot): self.bot = bot + @property + def modlog(self) -> ModLog: + return self.bot.get_cog("ModLog") + async def on_message(self, message: Message): if message.author.bot: return # They're a bot, ignore @@ -85,6 +90,7 @@ class Verification: log.trace(f"Deleting the message posted by {ctx.author}.") try: + self.modlog.ignore_message_deletion(ctx.message.id) await ctx.message.delete() except NotFound: log.trace("No message found, it must have been deleted by another bot.") diff --git a/bot/constants.py b/bot/constants.py index 163066fc0..4a3b4f133 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -14,9 +14,10 @@ import logging import os from collections.abc import Mapping from pathlib import Path +from typing import List import yaml - +from yaml.constructor import ConstructorError log = logging.getLogger(__name__) @@ -59,12 +60,42 @@ def _env_var_constructor(loader, node): key: !ENV 'MY_APP_KEY' """ - value = loader.construct_scalar(node) - return os.getenv(value) + default = None + + try: + # Try to construct a list from this YAML node + value = loader.construct_sequence(node) + + if len(value) >= 2: + # If we have at least two values, then we have both a key and a default value + default = value[1] + key = value[0] + else: + # Otherwise, we just have a key + key = value[0] + except ConstructorError: + # This YAML node is a plain value rather than a list, so we just have a key + value = loader.construct_scalar(node) + + key = str(value) + + return os.getenv(key, default) + + +def _join_var_constructor(loader, node): + """ + Implements a custom YAML tag for concatenating other tags in + the document to strings. This allows for a much more DRY configuration + file. + """ + + fields = loader.construct_sequence(node) + return "".join(str(x) for x in fields) -yaml.SafeLoader.add_constructor("!REQUIRED_ENV", _required_env_var_constructor) yaml.SafeLoader.add_constructor("!ENV", _env_var_constructor) +yaml.SafeLoader.add_constructor("!JOIN", _join_var_constructor) +yaml.SafeLoader.add_constructor("!REQUIRED_ENV", _required_env_var_constructor) with open("config-default.yml") as f: @@ -175,23 +206,60 @@ class Emojis(metaclass=YAMLGetter): red_chevron: str white_chevron: str + new: str + pencil: str + + +class Icons(metaclass=YAMLGetter): + section = "bot" + subsection = "icons" + + crown_blurple: str + crown_green: str + crown_red: str + + guild_update: str + + hash_blurple: str + hash_green: str + hash_red: str + + message_bulk_delete: str + message_delete: str + message_edit: str + + sign_in: str + sign_out: str + + user_ban: str + user_unban: str + user_update: str + class Channels(metaclass=YAMLGetter): section = "guild" subsection = "channels" + admins: int announcements: int + big_brother_logs: int bot: int checkpoint_test: int + devalerts: int devlog: int devtest: int - help0: int - help1: int - help2: int - help3: int - help4: int + help_0: int + help_1: int + help_2: int + help_3: int + help_4: int + help_5: int helpers: int + message_log: int modlog: int + off_topic_1: int + off_topic_2: int + off_topic_3: int python: int verification: int @@ -216,6 +284,7 @@ class Guild(metaclass=YAMLGetter): section = "guild" id: int + ignored: List[int] class Keys(metaclass=YAMLGetter): @@ -258,6 +327,7 @@ class URLs(metaclass=YAMLGetter): site_idioms_api: str site_names_api: str site_quiz_api: str + site_schema: str site_settings_api: str site_special_api: str site_tags_api: str diff --git a/bot/utils/time.py b/bot/utils/time.py new file mode 100644 index 000000000..b3f55932c --- /dev/null +++ b/bot/utils/time.py @@ -0,0 +1,59 @@ +from dateutil.relativedelta import relativedelta + + +def _plural_timestring(value: int, unit: str) -> str: + """ + Takes a value and a unit type, + such as 24 and "hours". + + Returns a string that takes + the correct plural into account. + + >>> _plural_timestring(1, "seconds") + "1 second" + >>> _plural_timestring(24, "hours") + "24 hours" + """ + + if value == 1: + return f"{value} {unit[:-1]}" + else: + return f"{value} {unit}" + + +def humanize(delta: relativedelta, accuracy: str = "seconds") -> str: + """ + This takes a relativedelta and + returns a nice human readable string. + + "4 days, 12 hours and 1 second" + + :param delta: A dateutils.relativedelta.relativedelta object + :param accuracy: The smallest unit that should be included. + :return: A humanized string. + """ + + units = { + "years": delta.years, + "months": delta.months, + "days": delta.days, + "hours": delta.hours, + "minutes": delta.minutes, + "seconds": delta.seconds + } + + # Add the time units that are >0, but stop at accuracy. + time_strings = [] + for unit, value in units.items(): + if value: + time_strings.append(_plural_timestring(value, unit)) + + if unit == accuracy: + break + + # Add the 'and' between the last two units + if len(time_strings) > 1: + time_strings[-1] = f"{time_strings[-2]} and {time_strings[-1]}" + del time_strings[-2] + + return ", ".join(time_strings) diff --git a/config-default.yml b/config-default.yml index b7efb0010..dc193f149 100644 --- a/config-default.yml +++ b/config-default.yml @@ -1,38 +1,71 @@ bot: - help_prefix: 'bot.' - token: !REQUIRED_ENV 'BOT_TOKEN' + 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>' + green_chevron: "<:greenchevron:418104310329769993>" + red_chevron: "<:redchevron:418112778184818698>" + white_chevron: "<:whitechevron:418110396973711363>" + lemoneye2: "<:lemoneye2:435193765582340098>" + + pencil: "\u270F" + new: "\U0001F195" + + icons: + crown_blurple: "https://cdn.discordapp.com/emojis/469964153289965568.png" + crown_green: "https://cdn.discordapp.com/emojis/469964154719961088.png" + crown_red: "https://cdn.discordapp.com/emojis/469964154879344640.png" + + guild_update: "https://cdn.discordapp.com/emojis/469954765141442561.png" + + hash_blurple: "https://cdn.discordapp.com/emojis/469950142942806017.png" + hash_green: "https://cdn.discordapp.com/emojis/469950144918585344.png" + hash_red: "https://cdn.discordapp.com/emojis/469950145413251072.png" + + message_bulk_delete: "https://cdn.discordapp.com/emojis/469952898994929668.png" + message_delete: "https://cdn.discordapp.com/emojis/469952898516779008.png" + message_edit: "https://cdn.discordapp.com/emojis/469952898143485972.png" + + sign_in: "https://cdn.discordapp.com/emojis/469952898181234698.png" + sign_out: "https://cdn.discordapp.com/emojis/469952898089091082.png" + + user_ban: "https://cdn.discordapp.com/emojis/469952898026045441.png" + user_unban: "https://cdn.discordapp.com/emojis/469952898692808704.png" + user_update: "https://cdn.discordapp.com/emojis/469952898684551168.png" guild: id: 267624335836053506 channels: - announcements: 354619224620138496 - bot: 267659945086812160 - checkpoint_test: 422077681434099723 - devlog: 409308876241108992 - devtest: 414574275865870337 - help0: 303906576991780866 - help1: 303906556754395136 - help2: 303906514266226689 - help3: 439702951246692352 - help4: 451312046647148554 - helpers: 385474242440986624 - modlog: 282638479504965634 - python: 267624335836053506 - verification: 352442727016693763 - off_topic_0: 291284109232308226 - off_topic_1: 463035241142026251 - off_topic_2: 463035268514185226 + admins: &ADMINS 365960823622991872 + announcements: 354619224620138496 + big_brother_logs: 468507907357409333 + bot: 267659945086812160 + checkpoint_test: 422077681434099723 + devalerts: 460181980097675264 + devlog: 409308876241108992 + devtest: 414574275865870337 + help_0: 303906576991780866 + help_1: 303906556754395136 + help_2: 303906514266226689 + help_3: 439702951246692352 + help_4: 451312046647148554 + help_5: 454941769734422538 + helpers: 385474242440986624 + message_log: &MESSAGE_LOG 467752170159079424 + modlog: &MODLOG 282638479504965634 + off_topic_0: 291284109232308226 + off_topic_1: 463035241142026251 + off_topic_2: 463035268514185226 + python: 267624335836053506 + verification: 352442727016693763 + + ignored: [*ADMINS, *MESSAGE_LOG, *MODLOG] roles: admin: 267628507062992896 @@ -44,52 +77,61 @@ guild: moderator: 267629731250176001 owner: 267627879762755584 verified: 352427296948486144 + helpers: 267630620367257601 muted: 0 keys: - deploy_bot: !ENV 'DEPLOY_BOT_KEY' - deploy_site: !ENV 'DEPLOY_SITE' - omdb: !ENV 'OMDB_API_KEY' - site_api: !ENV 'BOT_API_KEY' - youtube: !ENV 'YOUTUBE_API_KEY' + deploy_bot: !ENV "DEPLOY_BOT_KEY" + deploy_site: !ENV "DEPLOY_SITE" + omdb: !ENV "OMDB_API_KEY" + site_api: !ENV "BOT_API_KEY" + youtube: !ENV "YOUTUBE_API_KEY" clickup: - key: !ENV 'CLICKUP_KEY' - space: 757069 - team: 754996 + key: !ENV "CLICKUP_KEY" + space: 757069 + team: 754996 rabbitmq: - host: "pdrmq" - password: !ENV "RABBITMQ_DEFAULT_PASS" - port: 5672 - username: !ENV "RABBITMQ_DEFAULT_USER" + host: "pdrmq" + password: !ENV ["RABBITMQ_DEFAULT_PASS", "guest"] + port: 5672 + username: !ENV ["RABBITMQ_DEFAULT_USER", "guest"] urls: - bot_avatar: 'https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle/logo_circle.png' - deploy: !ENV 'DEPLOY_URL' - gitlab_bot_repo: 'https://gitlab.com/discord-python/projects/bot' - omdb: 'http://omdbapi.com' - site: 'pythondiscord.com' - site_docs_api: 'https://api.pythondiscord.com/bot/docs' - site_facts_api: 'https://api.pythondiscord.com/bot/snake_facts' - site_hiphopify_api: 'https://api.pythondiscord.com/bot/hiphopify' - site_idioms_api: 'https://api.pythondiscord.com/bot/snake_idioms' - site_names_api: 'https://api.pythondiscord.com/bot/snake_names' - site_off_topic_names_api: 'https://api.pythondiscord.com/bot/off-topic-names' - site_quiz_api: 'https://api.pythondiscord.com/bot/snake_quiz' - site_settings_api: 'https://api.pythondiscord.com/bot/settings' - site_special_api: 'https://api.pythondiscord.com/bot/special_snakes' - site_tags_api: 'https://api.pythondiscord.com/bot/tags' - site_user_api: 'https://api.pythondiscord.com/bot/users' - site_user_complete_api: 'https://api.pythondiscord.com/bot/users/complete' - site_infractions: 'https://api.pythondiscord.com/bot/infractions' - site_infractions_user: 'https://api.pythondiscord.com/bot/infractions/user/{user_id}' - site_infractions_type: 'https://api.pythondiscord.com/bot/infractions/type/{infraction_type}' - site_infractions_by_id: 'https://api.pythondiscord.com/bot/infractions/id/{infraction_id}' - site_infractions_user_type_current: 'https://api.pythondiscord.com/bot/infractions/user/{user_id}/{infraction_type}/current' - status: !ENV 'STATUS_URL' - paste_service: 'https://paste.pydis.com/{key}' + # PyDis site vars + site: &DOMAIN "api.pythondiscord.com" + site_schema: &SCHEMA "https://" + + site_bigbrother_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/bigbrother"] + site_docs_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/docs"] + site_facts_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/snake_facts"] + site_hiphopify_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/hiphopify"] + site_idioms_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/snake_idioms"] + site_names_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/snake_names"] + site_off_topic_names_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/off-topic-names"] + site_quiz_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/snake_quiz"] + site_settings_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/settings"] + site_special_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/special_snakes"] + site_tags_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/tags"] + site_user_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/users"] + site_user_complete_api: !JOIN [*SCHEMA, *DOMAIN, "/bot/users/complete"] + site_infractions: !JOIN [*SCHEMA, *DOMAIN, "/bot/infractions"] + site_infractions_user: !JOIN [*SCHEMA, *DOMAIN, "/bot/infractions/user/{user_id}"] + site_infractions_type: !JOIN [*SCHEMA, *DOMAIN, "/bot/infractions/type/{infraction_type}"] + site_infractions_by_id: !JOIN [*SCHEMA, *DOMAIN, "/bot/infractions/id/{infraction_id}"] + site_infractions_user_type_current: !JOIN [*SCHEMA, *DOMAIN, "/bot/infractions/user/{user_id}/{infraction_type}/current"] + + # Env vars + deploy: !ENV "DEPLOY_URL" + status: !ENV "STATUS_URL" + + # Misc URLs + bot_avatar: "https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle/logo_circle.png" + gitlab_bot_repo: "https://gitlab.com/python-discord/projects/bot" + omdb: "http://omdbapi.com" + paste_service: "https://paste.pydis.com/{key}" |