mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-07 09:52:30 -05:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9752a9c719 | ||
|
|
7973babe4b | ||
|
|
78e4b578e2 | ||
|
|
8eb8848898 | ||
|
|
aac1460240 | ||
|
|
dde5582669 | ||
|
|
aa854cf1f9 | ||
|
|
2bd05a5a04 | ||
|
|
3a8da1f82b | ||
|
|
811634a2b0 | ||
|
|
2512320b30 | ||
|
|
db03faf042 | ||
|
|
701259158f | ||
|
|
a5efdc6492 | ||
|
|
38b15ded87 | ||
|
|
351749dff6 | ||
|
|
2d9912cea7 | ||
|
|
c4ab34a049 | ||
|
|
985e7b3c6d | ||
|
|
7546c50226 | ||
|
|
6435f6b882 | ||
|
|
bbccb671b8 | ||
|
|
8abb24bc01 | ||
|
|
419008f644 | ||
|
|
d17c2430d7 | ||
|
|
ca533f8937 | ||
|
|
9d22d5b7b5 | ||
|
|
2846dce6ea | ||
|
|
9973b2e3b8 | ||
|
|
d008a2559a | ||
|
|
7f3a0b8a88 | ||
|
|
d0fca373ba | ||
|
|
451c4c9d54 | ||
|
|
a59002275d | ||
|
|
99bbde7be9 | ||
|
|
92dbd14006 | ||
|
|
6e9243f6e9 | ||
|
|
8bba860f85 | ||
|
|
d2d26835c3 | ||
|
|
aff62a8006 | ||
|
|
b5fd28ef7c | ||
|
|
c510ebe5e5 | ||
|
|
5ba95090d9 | ||
|
|
ad51fa830b | ||
|
|
1ba922eba2 | ||
|
|
9588a5740c | ||
|
|
7cd765d548 | ||
|
|
6022c0f7d7 | ||
|
|
0548744e94 | ||
|
|
8b2d115335 | ||
|
|
094735566d | ||
|
|
f7b1f9f0dc | ||
|
|
ce25011f0d | ||
|
|
f85034eb27 | ||
|
|
849755ecd2 | ||
|
|
9217275908 | ||
|
|
9e13ca45e6 | ||
|
|
46c38a28eb | ||
|
|
76bbcf2f8c | ||
|
|
ee7e8aa782 | ||
|
|
fd0abc250d | ||
|
|
847f9fc8d1 | ||
|
|
046e98565e | ||
|
|
71eddc89ea | ||
|
|
9730a424ec | ||
|
|
7b260cdafc | ||
|
|
4369095a51 | ||
|
|
1c706e8c45 | ||
|
|
91029b73e5 | ||
|
|
de4b42a11e |
@@ -54,7 +54,7 @@ jobs:
|
|||||||
- echo "deb https://artifacts.crowdin.com/repo/deb/ /" | sudo tee -a /etc/apt/sources.list
|
- echo "deb https://artifacts.crowdin.com/repo/deb/ /" | sudo tee -a /etc/apt/sources.list
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -y crowdin
|
- sudo apt-get install -y crowdin
|
||||||
- pip install redgettext==2.1
|
- pip install redgettext==2.2
|
||||||
deploy:
|
deploy:
|
||||||
- provider: script
|
- provider: script
|
||||||
script: make gettext
|
script: make gettext
|
||||||
|
|||||||
453
Pipfile.lock
generated
453
Pipfile.lock
generated
@@ -57,11 +57,10 @@
|
|||||||
},
|
},
|
||||||
"async-timeout": {
|
"async-timeout": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c",
|
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||||
"sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287"
|
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5.3'",
|
"version": "==3.0.1"
|
||||||
"version": "==3.0.0"
|
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -79,15 +78,20 @@
|
|||||||
},
|
},
|
||||||
"colorama": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
|
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
||||||
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
|
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
||||||
],
|
],
|
||||||
"version": "==0.3.9"
|
"version": "==0.4.1"
|
||||||
|
},
|
||||||
|
"discord-py": {
|
||||||
|
"editable": true,
|
||||||
|
"git": "git://github.com/Rapptz/discord.py",
|
||||||
|
"ref": "7f4c57dd5ad20b7fa10aea485f674a4bc24b9547"
|
||||||
},
|
},
|
||||||
"discord.py": {
|
"discord.py": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"git": "git://github.com/Rapptz/discord.py",
|
"git": "git://github.com/Rapptz/discord.py",
|
||||||
"ref": "836ae730401ea370aa10127bb9c86854c8b516ac"
|
"ref": "rewrite"
|
||||||
},
|
},
|
||||||
"distro": {
|
"distro": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -98,10 +102,10 @@
|
|||||||
},
|
},
|
||||||
"dnspython": {
|
"dnspython": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c",
|
"sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01",
|
||||||
"sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31"
|
"sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"
|
||||||
],
|
],
|
||||||
"version": "==1.15.0"
|
"version": "==1.16.0"
|
||||||
},
|
},
|
||||||
"e1839a8": {
|
"e1839a8": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
@@ -120,10 +124,10 @@
|
|||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||||
],
|
],
|
||||||
"version": "==2.7"
|
"version": "==2.8"
|
||||||
},
|
},
|
||||||
"idna-ssl": {
|
"idna-ssl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -140,72 +144,76 @@
|
|||||||
},
|
},
|
||||||
"multidict": {
|
"multidict": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:05eeab69bf2b0664644c62bd92fabb045163e5b8d4376a31dfb52ce0210ced7b",
|
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
|
||||||
"sha256:0c85880efa7cadb18e3b5eef0aa075dc9c0a3064cbbaef2e20be264b9cf47a64",
|
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
|
||||||
"sha256:136f5a4a6a4adeacc4dc820b8b22f0a378fb74f326e259c54d1817639d1d40a0",
|
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
|
||||||
"sha256:14906ad3347c7d03e9101749b16611cf2028547716d0840838d3c5e2b3b0f2d3",
|
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
|
||||||
"sha256:1ade4a3b71b1bf9e90c5f3d034a87fe4949c087ef1f6cd727fdd766fe8bbd121",
|
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
|
||||||
"sha256:22939a00a511a59f9ecc0158b8db728afef57975ce3782b3a265a319d05b9b12",
|
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
|
||||||
"sha256:2b86b02d872bc5ba5b3a4530f6a7ba0b541458ab4f7c1429a12ac326231203f7",
|
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
|
||||||
"sha256:3c11e92c3dfc321014e22fb442bc9eb70e01af30d6ce442026b0c35723448c66",
|
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
|
||||||
"sha256:4ba3bd26f282b201fdbce351f1c5d17ceb224cbedb73d6e96e6ce391b354aacc",
|
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
|
||||||
"sha256:4c6e78d042e93751f60672989efbd6a6bc54213ed7ff695fff82784bbb9ea035",
|
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
|
||||||
"sha256:4d80d1901b89cc935a6cf5b9fd89df66565272722fe2e5473168927a9937e0ca",
|
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
|
||||||
"sha256:4fcf71d33178a00cc34a57b29f5dab1734b9ce0f1c97fb34666deefac6f92037",
|
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
|
||||||
"sha256:52f7670b41d4b4d97866ebc38121de8bcb9813128b7c4942b07794d08193c0ab",
|
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
|
||||||
"sha256:5368e2b7649a26b7253c6c9e53241248aab9da49099442f5be238fde436f18c9",
|
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
|
||||||
"sha256:5bb65fbb48999044938f0c0508e929b14a9b8bf4939d8263e9ea6691f7b54663",
|
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
|
||||||
"sha256:60672bb5577472800fcca1ac9dae232d1461db9f20f055184be8ce54b0052572",
|
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
|
||||||
"sha256:669e9be6d148fc0283f53e17dd140cde4dc7c87edac8319147edd5aa2a830771",
|
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
|
||||||
"sha256:6a0b7a804e8d1716aa2c72e73210b48be83d25ba9ec5cf52cf91122285707bb1",
|
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
|
||||||
"sha256:79034ea3da3cf2a815e3e52afdc1f6c1894468c98bdce5d2546fa2342585497f",
|
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
|
||||||
"sha256:79247feeef6abcc11137ad17922e865052f23447152059402fc320f99ff544bb",
|
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
|
||||||
"sha256:81671c2049e6bf42c7fd11a060f8bc58f58b7b3d6f3f951fc0b15e376a6a5a98",
|
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
|
||||||
"sha256:82ac4a5cb56cc9280d4ae52c2d2ebcd6e0668dd0f9ef17f0a9d7c82bd61e24fa",
|
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
|
||||||
"sha256:9436267dbbaa49dad18fbbb54f85386b0f5818d055e7b8e01d219661b6745279",
|
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
|
||||||
"sha256:94e4140bb1343115a1afd6d84ebf8fca5fb7bfb50e1c2cbd6f2fb5d3117ef102",
|
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
|
||||||
"sha256:a2cab366eae8a0ffe0813fd8e335cf0d6b9bb6c5227315f53bb457519b811537",
|
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
|
||||||
"sha256:a596019c3eafb1b0ae07db9f55a08578b43c79adb1fe1ab1fd818430ae59ee6f",
|
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
|
||||||
"sha256:e8848ae3cd6a784c29fae5055028bee9bffcc704d8bcad09bd46b42b44a833e2",
|
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
|
||||||
"sha256:e8a048bfd7d5a280f27527d11449a509ddedf08b58a09a24314828631c099306",
|
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
|
||||||
"sha256:f6dd28a0ac60e2426a6918f36f1b4e2620fc785a0de7654cd206ba842eee57fd"
|
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
|
||||||
],
|
],
|
||||||
"version": "==4.4.2"
|
"version": "==4.5.2"
|
||||||
},
|
},
|
||||||
"pymongo": {
|
"pymongo": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:08dea6dbff33363419af7af3bf2e9a373ff71eb22833dd7063f9b953f09a0bdf",
|
"sha256:025f94fc1e1364f00e50badc88c47f98af20012f23317234e51a11333ef986e6",
|
||||||
"sha256:0949110db76eb1b54cecfc0c0f8468a8b9a7fd42ba23fd0d4a37d97e0b4ca203",
|
"sha256:02aa7fb282606331aefbc0586e2cf540e9dbe5e343493295e7f390936ad2738e",
|
||||||
"sha256:0c31a39f440801cc8603547ccaacf4cb1f02b81af6ba656621c13677b27f4426",
|
"sha256:057210e831573e932702cf332012ed39da78edf0f02d24a3f0b213264a87a397",
|
||||||
"sha256:1e10b3fda5677d360440ebd12a1185944dc81d9ea9acf0c6b0681013b3fb9bc2",
|
"sha256:0d946b79c56187fe139276d4c8ed612a27a616966c8b9779d6b79e2053587c8b",
|
||||||
"sha256:1f59440b993666a417ba1954cfb1b7fb11cb4dea1a1d2777897009f688d000ee",
|
"sha256:104790893b928d310aae8a955e0bdbaa442fb0ac0a33d1bbb0741c791a407778",
|
||||||
"sha256:2b5a3806d9f656c14e9d9b693a344fc5684fdd045155594be0c505c6e9410a94",
|
"sha256:15527ef218d95a8717486106553b0d54ff2641e795b65668754e17ab9ca6e381",
|
||||||
"sha256:4a14e2d7c2c0e07b5affcfbfc5c395d767f94bb1a822934a41a3b5371cde1458",
|
"sha256:1826527a0b032f6e20e7ac7f72d7c26dd476a5e5aa82c04aa1c7088a59fded7d",
|
||||||
"sha256:4cb50541225208b37786fdb0de632e475c4f00ec4792579df551ef48d6999d69",
|
"sha256:22e3aa4ce1c3eebc7f70f9ca7fd4ce1ea33e8bdb7b61996806cd312f08f84a3a",
|
||||||
"sha256:52999666ad01de885653e1f74a86c2a6520d1004afec475180bebf3d7393a8fc",
|
"sha256:244e1101e9a48615b9a16cbd194f73c115fdfefc96894803158608115f703b26",
|
||||||
"sha256:562c353079e8ce7e2ad611fd7436a72f5df97be72bca59ae9ebf789a724afd5c",
|
"sha256:24b8c04fdb633a84829d03909752c385faef249c06114cc8d8e1700b95aae5c8",
|
||||||
"sha256:5ce2a71f473f4703daa8d6c61a00b35ce625a7f5015b4371e3af728dafca296a",
|
"sha256:2c276696350785d3104412cbe3ac70ab1e3a10c408e7b20599ee41403a3ed630",
|
||||||
"sha256:6613e633676168a4500e5e6bb6e3e64d3fdb96d2dc472eb4b99235fb4141adb1",
|
"sha256:2d8474dc833b1182b651b184ace997a7bd83de0f51244de988d3c30e49f07de3",
|
||||||
"sha256:8330406f294df118399c721f80979f2516447bcc73e4262826687872c864751e",
|
"sha256:3119b57fe1d964781e91a53e81532c85ed1701baaddec592e22f6b77a9fdf3df",
|
||||||
"sha256:8e939dfa7d16609b99eb4d1fd2fc74f7a90f4fd0aaf31d611822daaff456236f",
|
"sha256:3bee8e7e0709b0fcdaa498a3e513bde9ffc7cd09dbceb11e425bd91c89dbd5b6",
|
||||||
"sha256:8fa4303e1f50d9f0c8f2f7833b5a370a94d19d41449def62b34ae072126b4dfd",
|
"sha256:436c071e01a464753d30dbfc8768dd93aecf2a8e378e5314d130b95e77b4d612",
|
||||||
"sha256:966d987975aa3b4cfcdf1495930ff6ecb152fafe8e544e40633e41b24ca3e1c5",
|
"sha256:46635e3f19ad04d5a7d7cf23d232388ddbfccf46d9a3b7436b6abadda4e84813",
|
||||||
"sha256:aec4ea43a1b8e9782246a259410f66692f2d3aa0f03c54477e506193b0781cb6",
|
"sha256:4772e0b679717e7ac4608d996f57b6f380748a919b457cb05bb941467b888b22",
|
||||||
"sha256:b73f889f032fbef05863f5056b46468a8262ae83628898e20b10bbbb79a3617e",
|
"sha256:4e2cd80e16f481a62c3175b607373200e714ed29025f21559ebf7524f295689f",
|
||||||
"sha256:b752088a2f819f163d11dfdbbe627b27eef9d8478c7e57d42c5e7c600fee434e",
|
"sha256:52732960efa0e003ca1c092dc0a3c65276e897681287a788a01ca78dda3b41f0",
|
||||||
"sha256:c8669f96277f140797e0ff99f80bd706271674942672a38ed694e2bfa66f3900",
|
"sha256:55a7de51ec7d1731b2431886d0349146645f2816e5b8eb982d7c49f89472c9f3",
|
||||||
"sha256:ccf00549efaf6f8d5b35b654beb9aed2b788a5b33b05606eb818ddaa4e924ea3",
|
"sha256:5f8ed5934197a2d4b2087646e98de3e099a237099dcf498b9e38dd3465f74ef4",
|
||||||
"sha256:ce7c91463ad21ac72fc795188292b01c8366cf625e2d1e5ed473ce127b844f60",
|
"sha256:64b064124fcbc8eb04a155117dc4d9a336e3cda3f069958fbc44fe70c3c3d1e9",
|
||||||
"sha256:d776d8d47884e6ad39ff8a301f1ae6b7d2186f209218cf024f43334dbba79c64",
|
"sha256:65958b8e4319f992e85dad59d8081888b97fcdbde5f0d14bc28f2848b92d3ef1",
|
||||||
"sha256:dab0f63841aebb2b421fadb31f3c7eef27898f21274a8e5b45c4f2bccb40f9ed",
|
"sha256:7683428862e20c6a790c19e64f8ccf487f613fbc83d47e3d532df9c81668d451",
|
||||||
"sha256:daedcfbf3b24b2b687e35b33252a9315425c2dd06a085a36906d516135bdd60e",
|
"sha256:78566d5570c75a127c2491e343dc006798a384f06be588fe9b0cbe5595711559",
|
||||||
"sha256:e7ad1ec621db2c5ad47924f63561f75abfd4fff669c62c8cc99c169c90432f59",
|
"sha256:7d1cb00c093dbf1d0b16ccf123e79dee3b82608e4a2a88947695f0460eef13ff",
|
||||||
"sha256:f14fb6c4058772a0d74d82874d3b89d7264d89b4ed7fa0413ea0ef8112b268b9",
|
"sha256:8c74e2a9b594f7962c62cef7680a4cb92a96b4e6e3c2f970790da67cc0213a7e",
|
||||||
"sha256:f16c7b6b98bc400d180f05e65e2236ef4ee9d71f3815280558582670e1e67536",
|
"sha256:8e60aa7699170f55f4b0f56ee6f8415229777ac7e4b4b1aa41fc61eec08c1f1d",
|
||||||
"sha256:f2d9eb92b26600ae6e8092f66da4bcede1b61a647c9080d6b44c148aff3a8ea4",
|
"sha256:9447b561529576d89d3bf973e5241a88cf76e45bd101963f5236888713dea774",
|
||||||
"sha256:ffe94f9d17800610dda5282d7f6facfc216d79a93dd728a03d2f21cff3af7cc6"
|
"sha256:970055bfeb0be373f2f5299a3db8432444bad3bc2f198753ee6c2a3a781e0959",
|
||||||
|
"sha256:a6344b8542e584e140dc3c651d68bde51270e79490aa9320f9e708f9b2c39bd5",
|
||||||
|
"sha256:ce309ca470d747b02ba6069d286a17b7df8e9c94d10d727d9cf3a64e51d85184",
|
||||||
|
"sha256:cfbd86ed4c2b2ac71bbdbcea6669bf295def7152e3722ddd9dda94ac7981f33d",
|
||||||
|
"sha256:d7929c513732dff093481f4a0954ed5ff16816365842136b17caa0b4992e49d3"
|
||||||
],
|
],
|
||||||
"version": "==3.7.1"
|
"version": "==3.7.2"
|
||||||
},
|
},
|
||||||
"python-levenshtein": {
|
"python-levenshtein": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -231,10 +239,10 @@
|
|||||||
},
|
},
|
||||||
"raven": {
|
"raven": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3fd787d19ebb49919268f06f19310e8112d619ef364f7989246fc8753d469888",
|
"sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54",
|
||||||
"sha256:95f44f3ea2c1b176d5450df4becdb96c15bf2632888f9ab193e9dd22300ce46a"
|
"sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4"
|
||||||
],
|
],
|
||||||
"version": "==6.9.0"
|
"version": "==6.10.0"
|
||||||
},
|
},
|
||||||
"raven-aiohttp": {
|
"raven-aiohttp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -280,23 +288,23 @@
|
|||||||
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
||||||
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.4'",
|
|
||||||
"version": "==6.0"
|
"version": "==6.0"
|
||||||
},
|
},
|
||||||
"yarl": {
|
"yarl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9",
|
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
|
||||||
"sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee",
|
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
|
||||||
"sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308",
|
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
|
||||||
"sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357",
|
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
|
||||||
"sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78",
|
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
|
||||||
"sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8",
|
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
|
||||||
"sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1",
|
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
|
||||||
"sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4",
|
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
|
||||||
"sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7"
|
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
|
||||||
|
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
|
||||||
|
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.4.1'",
|
"version": "==1.3.0"
|
||||||
"version": "==1.2.6"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
@@ -336,10 +344,10 @@
|
|||||||
},
|
},
|
||||||
"alabaster": {
|
"alabaster": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456",
|
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
|
||||||
"sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7"
|
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
|
||||||
],
|
],
|
||||||
"version": "==0.7.11"
|
"version": "==0.7.12"
|
||||||
},
|
},
|
||||||
"appdirs": {
|
"appdirs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -350,18 +358,16 @@
|
|||||||
},
|
},
|
||||||
"async-timeout": {
|
"async-timeout": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c",
|
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||||
"sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287"
|
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5.3'",
|
"version": "==3.0.1"
|
||||||
"version": "==3.0.0"
|
|
||||||
},
|
},
|
||||||
"atomicwrites": {
|
"atomicwrites": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
|
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
|
||||||
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
|
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*'",
|
|
||||||
"version": "==1.2.1"
|
"version": "==1.2.1"
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
@@ -387,10 +393,10 @@
|
|||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
|
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
|
||||||
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
|
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
|
||||||
],
|
],
|
||||||
"version": "==2018.8.24"
|
"version": "==2018.11.29"
|
||||||
},
|
},
|
||||||
"chardet": {
|
"chardet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -408,10 +414,10 @@
|
|||||||
},
|
},
|
||||||
"colorama": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
|
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
||||||
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
|
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
||||||
],
|
],
|
||||||
"version": "==0.3.9"
|
"version": "==0.4.1"
|
||||||
},
|
},
|
||||||
"distro": {
|
"distro": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -437,6 +443,13 @@
|
|||||||
],
|
],
|
||||||
"path": "."
|
"path": "."
|
||||||
},
|
},
|
||||||
|
"filelock": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b8d5ca5ca1c815e1574aee746650ea7301de63d87935b3463d26368b76e31633",
|
||||||
|
"sha256:d610c1bb404daf85976d7a82eb2ada120f04671007266b708606565dd03b5be6"
|
||||||
|
],
|
||||||
|
"version": "==3.0.10"
|
||||||
|
},
|
||||||
"fuzzywuzzy": {
|
"fuzzywuzzy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
|
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
|
||||||
@@ -446,10 +459,10 @@
|
|||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||||
],
|
],
|
||||||
"version": "==2.7"
|
"version": "==2.8"
|
||||||
},
|
},
|
||||||
"idna-ssl": {
|
"idna-ssl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -462,7 +475,6 @@
|
|||||||
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
|
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
|
||||||
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
|
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*'",
|
|
||||||
"version": "==1.1.0"
|
"version": "==1.1.0"
|
||||||
},
|
},
|
||||||
"jinja2": {
|
"jinja2": {
|
||||||
@@ -474,51 +486,78 @@
|
|||||||
},
|
},
|
||||||
"markupsafe": {
|
"markupsafe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
|
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
|
||||||
|
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
|
||||||
|
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
|
||||||
|
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
|
||||||
|
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
|
||||||
|
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
|
||||||
|
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
|
||||||
|
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
|
||||||
|
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
|
||||||
|
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
|
||||||
|
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
|
||||||
|
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
|
||||||
|
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
|
||||||
|
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
|
||||||
|
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
|
||||||
|
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
|
||||||
|
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
|
||||||
|
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
|
||||||
|
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
|
||||||
|
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
|
||||||
|
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
|
||||||
|
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
|
||||||
|
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
|
||||||
|
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
|
||||||
|
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
|
||||||
|
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
|
||||||
|
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
|
||||||
|
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
|
||||||
],
|
],
|
||||||
"version": "==1.0"
|
"version": "==1.1.0"
|
||||||
},
|
},
|
||||||
"more-itertools": {
|
"more-itertools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
|
"sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4",
|
||||||
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
|
"sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc",
|
||||||
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
|
"sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"
|
||||||
],
|
],
|
||||||
"version": "==4.3.0"
|
"version": "==5.0.0"
|
||||||
},
|
},
|
||||||
"multidict": {
|
"multidict": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:05eeab69bf2b0664644c62bd92fabb045163e5b8d4376a31dfb52ce0210ced7b",
|
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
|
||||||
"sha256:0c85880efa7cadb18e3b5eef0aa075dc9c0a3064cbbaef2e20be264b9cf47a64",
|
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
|
||||||
"sha256:136f5a4a6a4adeacc4dc820b8b22f0a378fb74f326e259c54d1817639d1d40a0",
|
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
|
||||||
"sha256:14906ad3347c7d03e9101749b16611cf2028547716d0840838d3c5e2b3b0f2d3",
|
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
|
||||||
"sha256:1ade4a3b71b1bf9e90c5f3d034a87fe4949c087ef1f6cd727fdd766fe8bbd121",
|
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
|
||||||
"sha256:22939a00a511a59f9ecc0158b8db728afef57975ce3782b3a265a319d05b9b12",
|
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
|
||||||
"sha256:2b86b02d872bc5ba5b3a4530f6a7ba0b541458ab4f7c1429a12ac326231203f7",
|
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
|
||||||
"sha256:3c11e92c3dfc321014e22fb442bc9eb70e01af30d6ce442026b0c35723448c66",
|
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
|
||||||
"sha256:4ba3bd26f282b201fdbce351f1c5d17ceb224cbedb73d6e96e6ce391b354aacc",
|
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
|
||||||
"sha256:4c6e78d042e93751f60672989efbd6a6bc54213ed7ff695fff82784bbb9ea035",
|
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
|
||||||
"sha256:4d80d1901b89cc935a6cf5b9fd89df66565272722fe2e5473168927a9937e0ca",
|
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
|
||||||
"sha256:4fcf71d33178a00cc34a57b29f5dab1734b9ce0f1c97fb34666deefac6f92037",
|
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
|
||||||
"sha256:52f7670b41d4b4d97866ebc38121de8bcb9813128b7c4942b07794d08193c0ab",
|
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
|
||||||
"sha256:5368e2b7649a26b7253c6c9e53241248aab9da49099442f5be238fde436f18c9",
|
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
|
||||||
"sha256:5bb65fbb48999044938f0c0508e929b14a9b8bf4939d8263e9ea6691f7b54663",
|
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
|
||||||
"sha256:60672bb5577472800fcca1ac9dae232d1461db9f20f055184be8ce54b0052572",
|
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
|
||||||
"sha256:669e9be6d148fc0283f53e17dd140cde4dc7c87edac8319147edd5aa2a830771",
|
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
|
||||||
"sha256:6a0b7a804e8d1716aa2c72e73210b48be83d25ba9ec5cf52cf91122285707bb1",
|
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
|
||||||
"sha256:79034ea3da3cf2a815e3e52afdc1f6c1894468c98bdce5d2546fa2342585497f",
|
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
|
||||||
"sha256:79247feeef6abcc11137ad17922e865052f23447152059402fc320f99ff544bb",
|
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
|
||||||
"sha256:81671c2049e6bf42c7fd11a060f8bc58f58b7b3d6f3f951fc0b15e376a6a5a98",
|
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
|
||||||
"sha256:82ac4a5cb56cc9280d4ae52c2d2ebcd6e0668dd0f9ef17f0a9d7c82bd61e24fa",
|
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
|
||||||
"sha256:9436267dbbaa49dad18fbbb54f85386b0f5818d055e7b8e01d219661b6745279",
|
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
|
||||||
"sha256:94e4140bb1343115a1afd6d84ebf8fca5fb7bfb50e1c2cbd6f2fb5d3117ef102",
|
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
|
||||||
"sha256:a2cab366eae8a0ffe0813fd8e335cf0d6b9bb6c5227315f53bb457519b811537",
|
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
|
||||||
"sha256:a596019c3eafb1b0ae07db9f55a08578b43c79adb1fe1ab1fd818430ae59ee6f",
|
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
|
||||||
"sha256:e8848ae3cd6a784c29fae5055028bee9bffcc704d8bcad09bd46b42b44a833e2",
|
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
|
||||||
"sha256:e8a048bfd7d5a280f27527d11449a509ddedf08b58a09a24314828631c099306",
|
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
|
||||||
"sha256:f6dd28a0ac60e2426a6918f36f1b4e2620fc785a0de7654cd206ba842eee57fd"
|
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
|
||||||
],
|
],
|
||||||
"version": "==4.4.2"
|
"version": "==4.5.2"
|
||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -529,48 +568,45 @@
|
|||||||
},
|
},
|
||||||
"pluggy": {
|
"pluggy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
|
"sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616",
|
||||||
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
|
"sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
"version": "==0.8.1"
|
||||||
"version": "==0.7.1"
|
|
||||||
},
|
},
|
||||||
"py": {
|
"py": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
|
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
|
||||||
"sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
|
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*'",
|
"version": "==1.7.0"
|
||||||
"version": "==1.6.0"
|
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
|
"sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a",
|
||||||
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
|
"sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d"
|
||||||
],
|
],
|
||||||
"version": "==2.2.0"
|
"version": "==2.3.1"
|
||||||
},
|
},
|
||||||
"pyparsing": {
|
"pyparsing": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a",
|
"sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b",
|
||||||
"sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401"
|
"sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592"
|
||||||
],
|
],
|
||||||
"version": "==2.2.2"
|
"version": "==2.3.0"
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e258ee50338f4e46957f9e09a0f10fb1c2d05493fa901d113a8dafd0790de4e",
|
"sha256:3e65a22eb0d4f1bdbc1eacccf4a3198bf8d4049dea5112d70a0c61b00e748d02",
|
||||||
"sha256:9332147e9af2dcf46cd7ceb14d5acadb6564744ddff1fe8c17f0ce60ece7d9a2"
|
"sha256:5924060b374f62608a078494b909d341720a050b5224ff87e17e12377486a71d"
|
||||||
],
|
],
|
||||||
"version": "==3.8.2"
|
"version": "==4.1.0"
|
||||||
},
|
},
|
||||||
"pytest-asyncio": {
|
"pytest-asyncio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a962e8e1b6ec28648c8fe214edab4e16bacdb37b52df26eb9d63050af309b2a9",
|
"sha256:9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf",
|
||||||
"sha256:fbd92c067c16111174a1286bfb253660f1e564e5146b39eeed1133315cf2c2cf"
|
"sha256:d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
"version": "==0.10.0"
|
||||||
"version": "==0.9.0"
|
|
||||||
},
|
},
|
||||||
"python-levenshtein": {
|
"python-levenshtein": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -580,10 +616,10 @@
|
|||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
|
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
|
||||||
"sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
|
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
|
||||||
],
|
],
|
||||||
"version": "==2018.5"
|
"version": "==2018.9"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -603,10 +639,10 @@
|
|||||||
},
|
},
|
||||||
"raven": {
|
"raven": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3fd787d19ebb49919268f06f19310e8112d619ef364f7989246fc8753d469888",
|
"sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54",
|
||||||
"sha256:95f44f3ea2c1b176d5450df4becdb96c15bf2632888f9ab193e9dd22300ce46a"
|
"sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4"
|
||||||
],
|
],
|
||||||
"version": "==6.9.0"
|
"version": "==6.10.0"
|
||||||
},
|
},
|
||||||
"raven-aiohttp": {
|
"raven-aiohttp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -617,10 +653,10 @@
|
|||||||
},
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||||
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
|
||||||
],
|
],
|
||||||
"version": "==2.19.1"
|
"version": "==2.21.0"
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -631,10 +667,10 @@
|
|||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||||
],
|
],
|
||||||
"version": "==1.11.0"
|
"version": "==1.12.0"
|
||||||
},
|
},
|
||||||
"snowballstemmer": {
|
"snowballstemmer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -645,17 +681,17 @@
|
|||||||
},
|
},
|
||||||
"sphinx": {
|
"sphinx": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:217a7705adcb573da5bbe1e0f5cab4fa0bd89fd9342c9159121746f593c2d5a4",
|
"sha256:429e3172466df289f0f742471d7e30ba3ee11f3b5aecd9a840480d03f14bcfe5",
|
||||||
"sha256:a602513f385f1d5785ff1ca420d9c7eb1a1b63381733b2f0ea8188a391314a86"
|
"sha256:c4cb17ba44acffae3d3209646b6baec1e215cad3065e852c68cc569d4df1b9f8"
|
||||||
],
|
],
|
||||||
"version": "==1.7.9"
|
"version": "==1.8.3"
|
||||||
},
|
},
|
||||||
"sphinx-rtd-theme": {
|
"sphinx-rtd-theme": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3b49758a64f8a1ebd8a33cb6cc9093c3935a908b716edfaa5772fd86aac27ef6",
|
"sha256:02f02a676d6baabb758a20c7a479d58648e0f64f13e07d1b388e9bb2afe86a09",
|
||||||
"sha256:80e01ec0eb711abacb1fa507f3eae8b805ae8fa3e8b057abfdf497e3f644c82c"
|
"sha256:d0f6bc70f98961145c5b0e26a992829363a197321ba571b31b24ea91879e0c96"
|
||||||
],
|
],
|
||||||
"version": "==0.4.1"
|
"version": "==0.4.2"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-asyncio": {
|
"sphinxcontrib-asyncio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -668,39 +704,36 @@
|
|||||||
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
|
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
|
||||||
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
|
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
|
||||||
"version": "==1.1.0"
|
"version": "==1.1.0"
|
||||||
},
|
},
|
||||||
"toml": {
|
"toml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42",
|
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||||
"sha256:a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957"
|
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||||
],
|
],
|
||||||
"version": "==0.9.6"
|
"version": "==0.10.0"
|
||||||
},
|
},
|
||||||
"tox": {
|
"tox": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7f802b37fffd3b5ef2aab104943fa5dad24bf9564bb7e732e54b8d0cfec2fca0",
|
"sha256:2a8d8a63660563e41e64e3b5b677e81ce1ffa5e2a93c2c565d3768c287445800",
|
||||||
"sha256:cc97859bd7f38aa5b3b8ba55ffe7ee9952e7050faad1aedc0829cd3db2fb61d6"
|
"sha256:edfca7809925f49bdc110d0a2d9966bbf35a0c25637216d9586e7a5c5de17bfb"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.4.0"
|
"version": "==3.6.1"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
|
||||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version < '4' and python_version >= '2.6' and python_version != '3.1.*'",
|
"version": "==1.24.1"
|
||||||
"version": "==1.23"
|
|
||||||
},
|
},
|
||||||
"virtualenv": {
|
"virtualenv": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
|
"sha256:34b9ae3742abed2f95d3970acf4d80533261d6061b51160b197f84e5b4c98b4c",
|
||||||
"sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
|
"sha256:fa736831a7b18bd2bfeef746beb622a92509e9733d645952da136b0639cd40cd"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
"version": "==16.2.0"
|
||||||
"version": "==16.0.0"
|
|
||||||
},
|
},
|
||||||
"websockets": {
|
"websockets": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -726,23 +759,23 @@
|
|||||||
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
||||||
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.4'",
|
|
||||||
"version": "==6.0"
|
"version": "==6.0"
|
||||||
},
|
},
|
||||||
"yarl": {
|
"yarl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9",
|
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
|
||||||
"sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee",
|
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
|
||||||
"sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308",
|
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
|
||||||
"sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357",
|
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
|
||||||
"sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78",
|
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
|
||||||
"sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8",
|
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
|
||||||
"sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1",
|
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
|
||||||
"sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4",
|
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
|
||||||
"sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7"
|
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
|
||||||
|
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
|
||||||
|
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.4.1'",
|
"version": "==1.3.0"
|
||||||
"version": "==1.2.6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
https://github.com/Rapptz/discord.py/tarball/836ae730401ea370aa10127bb9c86854c8b516ac#egg=discord.py-1.0.0a0
|
https://github.com/Rapptz/discord.py/tarball/7f4c57dd5ad20b7fa10aea485f674a4bc24b9547#egg=discord.py-1.0.0a0
|
||||||
|
|||||||
@@ -374,6 +374,21 @@ API Reference
|
|||||||
inside the bot itself! Simply take a peek inside of the :code:`tests/core/test_config.py` file for examples of using
|
inside the bot itself! Simply take a peek inside of the :code:`tests/core/test_config.py` file for examples of using
|
||||||
Config in all kinds of ways.
|
Config in all kinds of ways.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
When getting, setting or clearing values in Config, all keys are casted to `str` for you. This
|
||||||
|
includes keys within a `dict` when one is being set, as well as keys in nested dictionaries
|
||||||
|
within that `dict`. For example::
|
||||||
|
|
||||||
|
>>> conf = Config.get_conf(self, identifier=999)
|
||||||
|
>>> conf.register_global(foo={})
|
||||||
|
>>> await conf.foo.set_raw(123, value=True)
|
||||||
|
>>> await conf.foo()
|
||||||
|
{'123': True}
|
||||||
|
>>> await conf.foo.set({123: True, 456: {789: False}}
|
||||||
|
>>> await conf.foo()
|
||||||
|
{'123': True, '456': {'789': False}}
|
||||||
|
|
||||||
.. automodule:: redbot.core.config
|
.. automodule:: redbot.core.config
|
||||||
|
|
||||||
Config
|
Config
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ class Admin(commands.Cog):
|
|||||||
selfroles = await self._valid_selfroles(ctx.guild)
|
selfroles = await self._valid_selfroles(ctx.guild)
|
||||||
fmt_selfroles = "\n".join(["+ " + r.name for r in selfroles])
|
fmt_selfroles = "\n".join(["+ " + r.name for r in selfroles])
|
||||||
|
|
||||||
msg = _("Available Selfroles: {selfroles}").format(selfroles=fmt_selfroles)
|
msg = _("Available Selfroles:\n{selfroles}").format(selfroles=fmt_selfroles)
|
||||||
await ctx.send(box(msg, "diff"))
|
await ctx.send(box(msg, "diff"))
|
||||||
|
|
||||||
async def _serverlock_check(self, guild: discord.Guild) -> bool:
|
async def _serverlock_check(self, guild: discord.Guild) -> bool:
|
||||||
|
|||||||
@@ -288,7 +288,10 @@ class Alias(commands.Cog):
|
|||||||
"""Try to execute help for the base command of the alias."""
|
"""Try to execute help for the base command of the alias."""
|
||||||
is_alias, alias = await self.is_alias(ctx.guild, alias_name=alias_name)
|
is_alias, alias = await self.is_alias(ctx.guild, alias_name=alias_name)
|
||||||
if is_alias:
|
if is_alias:
|
||||||
base_cmd = alias.command[0]
|
if self.is_command(alias.command):
|
||||||
|
base_cmd = alias.command
|
||||||
|
else:
|
||||||
|
base_cmd = alias.command.rsplit(" ", 1)[0]
|
||||||
|
|
||||||
new_msg = copy(ctx.message)
|
new_msg = copy(ctx.message)
|
||||||
new_msg.content = _("{prefix}help {command}").format(
|
new_msg.content = _("{prefix}help {command}").format(
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ async def download_lavalink(session):
|
|||||||
|
|
||||||
async def maybe_download_lavalink(loop, cog):
|
async def maybe_download_lavalink(loop, cog):
|
||||||
jar_exists = LAVALINK_JAR_FILE.exists()
|
jar_exists = LAVALINK_JAR_FILE.exists()
|
||||||
current_build = redbot.core.VersionInfo(*await cog.config.current_build())
|
current_build = redbot.core.VersionInfo.from_json(await cog.config.current_version())
|
||||||
|
|
||||||
if not jar_exists or current_build < redbot.core.version_info:
|
if not jar_exists or current_build < redbot.core.version_info:
|
||||||
log.info("Downloading Lavalink.jar")
|
log.info("Downloading Lavalink.jar")
|
||||||
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
async with ClientSession(loop=loop) as session:
|
async with ClientSession(loop=loop) as session:
|
||||||
await download_lavalink(session)
|
await download_lavalink(session)
|
||||||
await cog.config.current_build.set(redbot.core.version_info.to_json())
|
await cog.config.current_version.set(redbot.core.version_info.to_json())
|
||||||
|
|
||||||
shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
|
shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class Audio(commands.Cog):
|
|||||||
"ws_port": "2332",
|
"ws_port": "2332",
|
||||||
"password": "youshallnotpass",
|
"password": "youshallnotpass",
|
||||||
"status": False,
|
"status": False,
|
||||||
"current_build": [3, 0, 0, "alpha", 0],
|
"current_version": redbot.core.VersionInfo.from_str("3.0.0a0").to_json(),
|
||||||
"use_external_lavalink": False,
|
"use_external_lavalink": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,9 @@ class Audio(commands.Cog):
|
|||||||
|
|
||||||
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
||||||
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
|
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
|
||||||
await self._embed_msg(ctx, "DJ role enabled: {}.".format(not dj_enabled))
|
await self._embed_msg(
|
||||||
|
ctx, _("DJ role enabled: {true_or_false}.".format(true_or_false=not dj_enabled))
|
||||||
|
)
|
||||||
|
|
||||||
@audioset.command()
|
@audioset.command()
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
@@ -332,7 +334,7 @@ class Audio(commands.Cog):
|
|||||||
jarbuild = redbot.core.__version__
|
jarbuild = redbot.core.__version__
|
||||||
|
|
||||||
vote_percent = data["vote_percent"]
|
vote_percent = data["vote_percent"]
|
||||||
msg = "----" + _("Server Settings") + "----"
|
msg = "----" + _("Server Settings") + "----\n"
|
||||||
if emptydc_enabled:
|
if emptydc_enabled:
|
||||||
msg += _("Disconnect timer: [{num_seconds}]\n").format(
|
msg += _("Disconnect timer: [{num_seconds}]\n").format(
|
||||||
num_seconds=self._dynamic_time(emptydc_timer)
|
num_seconds=self._dynamic_time(emptydc_timer)
|
||||||
@@ -370,7 +372,9 @@ class Audio(commands.Cog):
|
|||||||
"""Toggle displaying a thumbnail on audio messages."""
|
"""Toggle displaying a thumbnail on audio messages."""
|
||||||
thumbnail = await self.config.guild(ctx.guild).thumbnail()
|
thumbnail = await self.config.guild(ctx.guild).thumbnail()
|
||||||
await self.config.guild(ctx.guild).thumbnail.set(not thumbnail)
|
await self.config.guild(ctx.guild).thumbnail.set(not thumbnail)
|
||||||
await self._embed_msg(ctx, _("Thumbnail display: {}.").format(not thumbnail))
|
await self._embed_msg(
|
||||||
|
ctx, _("Thumbnail display: {true_or_false}.").format(true_or_false=not thumbnail)
|
||||||
|
)
|
||||||
|
|
||||||
@audioset.command()
|
@audioset.command()
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
@@ -567,6 +571,8 @@ class Audio(commands.Cog):
|
|||||||
return await menu(ctx, folder_page_list, DEFAULT_CONTROLS)
|
return await menu(ctx, folder_page_list, DEFAULT_CONTROLS)
|
||||||
else:
|
else:
|
||||||
await menu(ctx, folder_page_list, LOCAL_FOLDER_CONTROLS)
|
await menu(ctx, folder_page_list, LOCAL_FOLDER_CONTROLS)
|
||||||
|
else:
|
||||||
|
await menu(ctx, folder_page_list, LOCAL_FOLDER_CONTROLS)
|
||||||
|
|
||||||
@local.command(name="search")
|
@local.command(name="search")
|
||||||
async def local_search(self, ctx, *, search_words):
|
async def local_search(self, ctx, *, search_words):
|
||||||
@@ -1097,7 +1103,7 @@ class Audio(commands.Cog):
|
|||||||
(
|
(
|
||||||
bold(playlist_name),
|
bold(playlist_name),
|
||||||
_("Tracks: {num}").format(num=len(tracks)),
|
_("Tracks: {num}").format(num=len(tracks)),
|
||||||
_("Author: {name}").format(self.bot.get_user(author)),
|
_("Author: {name}\n").format(name=self.bot.get_user(author)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1254,6 +1260,9 @@ class Audio(commands.Cog):
|
|||||||
try:
|
try:
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
for track in playlists[playlist_name]["tracks"]:
|
for track in playlists[playlist_name]["tracks"]:
|
||||||
|
if track["info"]["uri"].startswith("localtracks/"):
|
||||||
|
if not os.path.isfile(track["info"]["uri"]):
|
||||||
|
continue
|
||||||
player.add(author_obj, lavalink.rest_api.Track(data=track))
|
player.add(author_obj, lavalink.rest_api.Track(data=track))
|
||||||
track_count = track_count + 1
|
track_count = track_count + 1
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
@@ -1974,6 +1983,7 @@ class Audio(commands.Cog):
|
|||||||
async def seek(self, ctx, seconds: int = 30):
|
async def seek(self, ctx, seconds: int = 30):
|
||||||
"""Seek ahead or behind on a track by seconds."""
|
"""Seek ahead or behind on a track by seconds."""
|
||||||
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
||||||
|
vote_enabled = await self.config.guild(ctx.guild).vote_enabled()
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
return await self._embed_msg(ctx, _("Nothing playing."))
|
return await self._embed_msg(ctx, _("Nothing playing."))
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
@@ -1986,6 +1996,13 @@ class Audio(commands.Cog):
|
|||||||
ctx, ctx.author
|
ctx, ctx.author
|
||||||
):
|
):
|
||||||
return await self._embed_msg(ctx, _("You need the DJ role to use seek."))
|
return await self._embed_msg(ctx, _("You need the DJ role to use seek."))
|
||||||
|
if vote_enabled:
|
||||||
|
if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
|
||||||
|
ctx, ctx.author
|
||||||
|
):
|
||||||
|
return await self._embed_msg(
|
||||||
|
ctx, _("There are other people listening - vote to skip instead.")
|
||||||
|
)
|
||||||
if player.current:
|
if player.current:
|
||||||
if player.current.is_stream:
|
if player.current.is_stream:
|
||||||
return await self._embed_msg(ctx, _("Can't seek on a stream."))
|
return await self._embed_msg(ctx, _("Can't seek on a stream."))
|
||||||
@@ -2360,7 +2377,7 @@ class Audio(commands.Cog):
|
|||||||
if await self._check_external():
|
if await self._check_external():
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=await ctx.embed_colour(),
|
colour=await ctx.embed_colour(),
|
||||||
title=_("Websocket port set to {}.").format(ws_port),
|
title=_("Websocket port set to {port}.").format(port=ws_port),
|
||||||
)
|
)
|
||||||
embed.set_footer(text=_("External lavalink server set to True."))
|
embed.set_footer(text=_("External lavalink server set to True."))
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|||||||
@@ -71,13 +71,19 @@ async def get_java_version(loop) -> _JavaVersion:
|
|||||||
# ... version "MAJOR.MINOR.PATCH[_BUILD]" ...
|
# ... version "MAJOR.MINOR.PATCH[_BUILD]" ...
|
||||||
# ...
|
# ...
|
||||||
# We only care about the major and minor parts though.
|
# We only care about the major and minor parts though.
|
||||||
version_line_re = re.compile(r'version "(?P<major>\d+).(?P<minor>\d+).\d+(?:_\d+)?"')
|
version_line_re = re.compile(
|
||||||
|
r'version "(?P<major>\d+).(?P<minor>\d+).\d+(?:_\d+)?(?:-[A-Za-z0-9]+)?"'
|
||||||
|
)
|
||||||
|
short_version_re = re.compile(r'version "(?P<major>\d+)"')
|
||||||
|
|
||||||
lines = version_info.splitlines()
|
lines = version_info.splitlines()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
match = version_line_re.search(line)
|
match = version_line_re.search(line)
|
||||||
|
short_match = short_version_re.search(line)
|
||||||
if match:
|
if match:
|
||||||
return int(match["major"]), int(match["minor"])
|
return int(match["major"]), int(match["minor"])
|
||||||
|
elif short_match:
|
||||||
|
return int(short_match["major"]), 0
|
||||||
|
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"The output of `java -version` was unexpected. Please report this issue on Red's "
|
"The output of `java -version` was unexpected. Please report this issue on Red's "
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Union, List, Callable
|
from typing import Union, List, Callable, Set
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ class Cleanup(commands.Cog):
|
|||||||
):
|
):
|
||||||
if message.created_at < two_weeks_ago:
|
if message.created_at < two_weeks_ago:
|
||||||
break
|
break
|
||||||
if check(message):
|
if message_filter(message):
|
||||||
collected.append(message)
|
collected.append(message)
|
||||||
if number and number <= len(collected):
|
if number and number <= len(collected):
|
||||||
break
|
break
|
||||||
@@ -133,8 +133,6 @@ class Cleanup(commands.Cog):
|
|||||||
def check(m):
|
def check(m):
|
||||||
if text in m.content:
|
if text in m.content:
|
||||||
return True
|
return True
|
||||||
elif m == ctx.message:
|
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -145,6 +143,7 @@ class Cleanup(commands.Cog):
|
|||||||
before=ctx.message,
|
before=ctx.message,
|
||||||
delete_pinned=delete_pinned,
|
delete_pinned=delete_pinned,
|
||||||
)
|
)
|
||||||
|
to_delete.append(ctx.message)
|
||||||
|
|
||||||
reason = "{}({}) deleted {} messages containing '{}' in channel {}.".format(
|
reason = "{}({}) deleted {} messages containing '{}' in channel {}.".format(
|
||||||
author.name, author.id, len(to_delete), text, channel.id
|
author.name, author.id, len(to_delete), text, channel.id
|
||||||
@@ -169,7 +168,7 @@ class Cleanup(commands.Cog):
|
|||||||
|
|
||||||
member = None
|
member = None
|
||||||
try:
|
try:
|
||||||
member = await commands.converter.MemberConverter().convert(ctx, user)
|
member = await commands.MemberConverter().convert(ctx, user)
|
||||||
except commands.BadArgument:
|
except commands.BadArgument:
|
||||||
try:
|
try:
|
||||||
_id = int(user)
|
_id = int(user)
|
||||||
@@ -188,8 +187,6 @@ class Cleanup(commands.Cog):
|
|||||||
def check(m):
|
def check(m):
|
||||||
if m.author.id == _id:
|
if m.author.id == _id:
|
||||||
return True
|
return True
|
||||||
elif m == ctx.message:
|
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -200,6 +197,8 @@ class Cleanup(commands.Cog):
|
|||||||
before=ctx.message,
|
before=ctx.message,
|
||||||
delete_pinned=delete_pinned,
|
delete_pinned=delete_pinned,
|
||||||
)
|
)
|
||||||
|
to_delete.append(ctx.message)
|
||||||
|
|
||||||
reason = (
|
reason = (
|
||||||
"{}({}) deleted {} messages "
|
"{}({}) deleted {} messages "
|
||||||
" made by {}({}) in channel {}."
|
" made by {}({}) in channel {}."
|
||||||
@@ -263,6 +262,7 @@ class Cleanup(commands.Cog):
|
|||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
channel=channel, number=number, before=before, delete_pinned=delete_pinned
|
channel=channel, number=number, before=before, delete_pinned=delete_pinned
|
||||||
)
|
)
|
||||||
|
to_delete.append(ctx.message)
|
||||||
|
|
||||||
reason = "{}({}) deleted {} messages in channel {}.".format(
|
reason = "{}({}) deleted {} messages in channel {}.".format(
|
||||||
author.name, author.id, len(to_delete), channel.name
|
author.name, author.id, len(to_delete), channel.name
|
||||||
@@ -323,15 +323,35 @@ class Cleanup(commands.Cog):
|
|||||||
if "" in prefixes:
|
if "" in prefixes:
|
||||||
prefixes.remove("")
|
prefixes.remove("")
|
||||||
|
|
||||||
|
cc_cog = self.bot.get_cog("CustomCommands")
|
||||||
|
if cc_cog is not None:
|
||||||
|
command_names: Set[str] = await cc_cog.get_command_names(ctx.guild)
|
||||||
|
is_cc = lambda name: name in command_names
|
||||||
|
else:
|
||||||
|
is_cc = lambda name: False
|
||||||
|
alias_cog = self.bot.get_cog("Alias")
|
||||||
|
if alias_cog is not None:
|
||||||
|
alias_names: Set[str] = (
|
||||||
|
set((a.name for a in await alias_cog.unloaded_global_aliases()))
|
||||||
|
| set(a.name for a in await alias_cog.unloaded_aliases(ctx.guild))
|
||||||
|
)
|
||||||
|
is_alias = lambda name: name in alias_names
|
||||||
|
else:
|
||||||
|
is_alias = lambda name: False
|
||||||
|
|
||||||
|
bot_id = self.bot.user.id
|
||||||
|
|
||||||
def check(m):
|
def check(m):
|
||||||
if m.author.id == self.bot.user.id:
|
if m.author.id == bot_id:
|
||||||
return True
|
return True
|
||||||
elif m == ctx.message:
|
elif m == ctx.message:
|
||||||
return True
|
return True
|
||||||
p = discord.utils.find(m.content.startswith, prefixes)
|
p = discord.utils.find(m.content.startswith, prefixes)
|
||||||
if p and len(p) > 0:
|
if p and len(p) > 0:
|
||||||
cmd_name = m.content[len(p) :].split(" ")[0]
|
cmd_name = m.content[len(p) :].split(" ")[0]
|
||||||
return bool(self.bot.get_command(cmd_name))
|
return (
|
||||||
|
bool(self.bot.get_command(cmd_name)) or is_alias(cmd_name) or is_cc(cmd_name)
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import random
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from inspect import Parameter
|
from inspect import Parameter
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Mapping, Tuple, Dict
|
from typing import Mapping, Tuple, Dict, Set
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.core import Config, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.utils.chat_formatting import box, pagify
|
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
|
from redbot.core.utils import menus
|
||||||
|
from redbot.core.utils.chat_formatting import box, pagify, escape
|
||||||
from redbot.core.utils.predicates import MessagePredicate
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
_ = Translator("CustomCommands", __file__)
|
_ = Translator("CustomCommands", __file__)
|
||||||
@@ -43,11 +44,8 @@ class CommandObj:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_commands(config) -> dict:
|
async def get_commands(config) -> dict:
|
||||||
commands = await config.commands()
|
_commands = await config.commands()
|
||||||
customcommands = {k: v for k, v in commands.items() if commands[k]}
|
return {k: v for k, v in _commands.items() if _commands[k]}
|
||||||
if len(customcommands) == 0:
|
|
||||||
return None
|
|
||||||
return customcommands
|
|
||||||
|
|
||||||
async def get_responses(self, ctx):
|
async def get_responses(self, ctx):
|
||||||
intro = _(
|
intro = _(
|
||||||
@@ -79,7 +77,8 @@ class CommandObj:
|
|||||||
responses.append(msg.content)
|
responses.append(msg.content)
|
||||||
return responses
|
return responses
|
||||||
|
|
||||||
def get_now(self) -> str:
|
@staticmethod
|
||||||
|
def get_now() -> str:
|
||||||
# Get current time as a string, for 'created_at' and 'edited_at' fields
|
# Get current time as a string, for 'created_at' and 'edited_at' fields
|
||||||
# in the ccinfo dict
|
# in the ccinfo dict
|
||||||
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
|
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
|
||||||
@@ -116,7 +115,7 @@ class CommandObj:
|
|||||||
*,
|
*,
|
||||||
response=None,
|
response=None,
|
||||||
cooldowns: Mapping[str, int] = None,
|
cooldowns: Mapping[str, int] = None,
|
||||||
ask_for: bool = True
|
ask_for: bool = True,
|
||||||
):
|
):
|
||||||
"""Edit an already existing custom command"""
|
"""Edit an already existing custom command"""
|
||||||
ccinfo = await self.db(ctx.guild).commands.get_raw(command, default=None)
|
ccinfo = await self.db(ctx.guild).commands.get_raw(command, default=None)
|
||||||
@@ -312,8 +311,6 @@ class CustomCommands(commands.Cog):
|
|||||||
Example:
|
Example:
|
||||||
- `[p]customcom edit yourcommand Text you want`
|
- `[p]customcom edit yourcommand Text you want`
|
||||||
"""
|
"""
|
||||||
command = command.lower()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.commandobj.edit(ctx=ctx, command=command, response=text)
|
await self.commandobj.edit(ctx=ctx, command=command, response=text)
|
||||||
await ctx.send(_("Custom command successfully edited."))
|
await ctx.send(_("Custom command successfully edited."))
|
||||||
@@ -327,12 +324,16 @@ class CustomCommands(commands.Cog):
|
|||||||
await ctx.send(e.args[0])
|
await ctx.send(e.args[0])
|
||||||
|
|
||||||
@customcom.command(name="list")
|
@customcom.command(name="list")
|
||||||
async def cc_list(self, ctx):
|
@checks.bot_has_permissions(add_reactions=True)
|
||||||
"""List all available custom commands."""
|
async def cc_list(self, ctx: commands.Context):
|
||||||
|
"""List all available custom commands.
|
||||||
|
|
||||||
response = await CommandObj.get_commands(self.config.guild(ctx.guild))
|
The list displays a preview of each command's response, with
|
||||||
|
markdown escaped and newlines replaced with spaces.
|
||||||
|
"""
|
||||||
|
cc_dict = await CommandObj.get_commands(self.config.guild(ctx.guild))
|
||||||
|
|
||||||
if not response:
|
if not cc_dict:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"There are no custom commands in this server."
|
"There are no custom commands in this server."
|
||||||
@@ -342,8 +343,7 @@ class CustomCommands(commands.Cog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
for command, body in sorted(cc_dict.items(), key=lambda t: t[0]):
|
||||||
for command, body in response.items():
|
|
||||||
responses = body["response"]
|
responses = body["response"]
|
||||||
if isinstance(responses, list):
|
if isinstance(responses, list):
|
||||||
result = ", ".join(responses)
|
result = ", ".join(responses)
|
||||||
@@ -351,15 +351,33 @@ class CustomCommands(commands.Cog):
|
|||||||
result = responses
|
result = responses
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
results.append("{command:<15} : {result}".format(command=command, result=result))
|
# Cut preview to 52 characters max
|
||||||
|
if len(result) > 52:
|
||||||
|
result = result[:49] + "..."
|
||||||
|
# Replace newlines with spaces
|
||||||
|
result = result.replace("\n", " ")
|
||||||
|
# Escape markdown and mass mentions
|
||||||
|
result = escape(result, formatting=True, mass_mentions=True)
|
||||||
|
results.append((f"{ctx.clean_prefix}{command}", result))
|
||||||
|
|
||||||
commands = "\n".join(results)
|
if await ctx.embed_requested():
|
||||||
|
# We need a space before the newline incase the CC preview ends in link (GH-2295)
|
||||||
if len(commands) < 1500:
|
content = " \n".join(map("**{0[0]}** {0[1]}".format, results))
|
||||||
await ctx.send(box(commands))
|
pages = list(pagify(content, page_length=1024))
|
||||||
|
embed_pages = []
|
||||||
|
for idx, page in enumerate(pages, start=1):
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=_("Custom Command List"),
|
||||||
|
description=page,
|
||||||
|
colour=await ctx.embed_colour(),
|
||||||
|
)
|
||||||
|
embed.set_footer(text=_("Page {num}/{total}").format(num=idx, total=len(pages)))
|
||||||
|
embed_pages.append(embed)
|
||||||
|
await menus.menu(ctx, embed_pages, menus.DEFAULT_CONTROLS)
|
||||||
else:
|
else:
|
||||||
for page in pagify(commands, delims=[" ", "\n"]):
|
content = "\n".join(map("{0[0]:<12} : {0[1]}".format, results))
|
||||||
await ctx.author.send(box(page))
|
pages = list(map(box, pagify(content, page_length=2000, shorten_by=10)))
|
||||||
|
await menus.menu(ctx, pages, menus.DEFAULT_CONTROLS)
|
||||||
|
|
||||||
async def on_message(self, message):
|
async def on_message(self, message):
|
||||||
is_private = isinstance(message.channel, discord.abc.PrivateChannel)
|
is_private = isinstance(message.channel, discord.abc.PrivateChannel)
|
||||||
@@ -411,11 +429,11 @@ class CustomCommands(commands.Cog):
|
|||||||
|
|
||||||
async def cc_command(self, ctx, *cc_args, raw_response, **cc_kwargs) -> None:
|
async def cc_command(self, ctx, *cc_args, raw_response, **cc_kwargs) -> None:
|
||||||
cc_args = (*cc_args, *cc_kwargs.values())
|
cc_args = (*cc_args, *cc_kwargs.values())
|
||||||
results = re.findall(r"\{([^}]+)\}", raw_response)
|
results = re.findall(r"{([^}]+)\}", raw_response)
|
||||||
for result in results:
|
for result in results:
|
||||||
param = self.transform_parameter(result, ctx.message)
|
param = self.transform_parameter(result, ctx.message)
|
||||||
raw_response = raw_response.replace("{" + result + "}", param)
|
raw_response = raw_response.replace("{" + result + "}", param)
|
||||||
results = re.findall(r"\{((\d+)[^\.}]*(\.[^:}]+)?[^}]*)\}", raw_response)
|
results = re.findall(r"{((\d+)[^.}]*(\.[^:}]+)?[^}]*)\}", raw_response)
|
||||||
if results:
|
if results:
|
||||||
low = min(int(result[1]) for result in results)
|
low = min(int(result[1]) for result in results)
|
||||||
for result in results:
|
for result in results:
|
||||||
@@ -424,9 +442,10 @@ class CustomCommands(commands.Cog):
|
|||||||
raw_response = raw_response.replace("{" + result[0] + "}", arg)
|
raw_response = raw_response.replace("{" + result[0] + "}", arg)
|
||||||
await ctx.send(raw_response)
|
await ctx.send(raw_response)
|
||||||
|
|
||||||
def prepare_args(self, raw_response) -> Mapping[str, Parameter]:
|
@staticmethod
|
||||||
args = re.findall(r"\{(\d+)[^:}]*(:[^\.}]*)?[^}]*\}", raw_response)
|
def prepare_args(raw_response) -> Mapping[str, Parameter]:
|
||||||
default = [["ctx", Parameter("ctx", Parameter.POSITIONAL_OR_KEYWORD)]]
|
args = re.findall(r"{(\d+)[^:}]*(:[^.}]*)?[^}]*\}", raw_response)
|
||||||
|
default = [("ctx", Parameter("ctx", Parameter.POSITIONAL_OR_KEYWORD))]
|
||||||
if not args:
|
if not args:
|
||||||
return OrderedDict(default)
|
return OrderedDict(default)
|
||||||
allowed_builtins = {
|
allowed_builtins = {
|
||||||
@@ -466,7 +485,7 @@ class CustomCommands(commands.Cog):
|
|||||||
try:
|
try:
|
||||||
anno = getattr(discord, anno)
|
anno = getattr(discord, anno)
|
||||||
# force an AttributeError if there's no discord.py converter
|
# force an AttributeError if there's no discord.py converter
|
||||||
getattr(commands.converter, anno.__name__ + "Converter")
|
getattr(commands, anno.__name__ + "Converter")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
anno = allowed_builtins.get(anno.lower(), Parameter.empty)
|
anno = allowed_builtins.get(anno.lower(), Parameter.empty)
|
||||||
if (
|
if (
|
||||||
@@ -520,7 +539,8 @@ class CustomCommands(commands.Cog):
|
|||||||
# only update cooldowns if the command isn't on cooldown
|
# only update cooldowns if the command isn't on cooldown
|
||||||
self.cooldowns.update(new_cooldowns)
|
self.cooldowns.update(new_cooldowns)
|
||||||
|
|
||||||
def transform_arg(self, result, attr, obj) -> str:
|
@staticmethod
|
||||||
|
def transform_arg(result, attr, obj) -> str:
|
||||||
attr = attr[1:] # strip initial dot
|
attr = attr[1:] # strip initial dot
|
||||||
if not attr:
|
if not attr:
|
||||||
return str(obj)
|
return str(obj)
|
||||||
@@ -530,7 +550,8 @@ class CustomCommands(commands.Cog):
|
|||||||
return raw_result
|
return raw_result
|
||||||
return str(getattr(obj, attr, raw_result))
|
return str(getattr(obj, attr, raw_result))
|
||||||
|
|
||||||
def transform_parameter(self, result, message) -> str:
|
@staticmethod
|
||||||
|
def transform_parameter(result, message) -> str:
|
||||||
"""
|
"""
|
||||||
For security reasons only specific objects are allowed
|
For security reasons only specific objects are allowed
|
||||||
Internals are ignored
|
Internals are ignored
|
||||||
@@ -554,3 +575,14 @@ class CustomCommands(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
return raw_result
|
return raw_result
|
||||||
return str(getattr(first, second, raw_result))
|
return str(getattr(first, second, raw_result))
|
||||||
|
|
||||||
|
async def get_command_names(self, guild: discord.Guild) -> Set[str]:
|
||||||
|
"""Get all custom command names in a guild.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Set[str]
|
||||||
|
A set of all custom command names.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return set(await CommandObj.get_commands(self.config.guild(guild)))
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import discord
|
import discord
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.i18n import Translator
|
||||||
from .installable import Installable
|
from .installable import Installable
|
||||||
|
|
||||||
|
_ = Translator("Koala", __file__)
|
||||||
|
|
||||||
|
|
||||||
class InstalledCog(Installable):
|
class InstalledCog(Installable):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -325,13 +325,12 @@ class Downloader(commands.Cog):
|
|||||||
You may only uninstall cogs which were previously installed
|
You may only uninstall cogs which were previously installed
|
||||||
by Downloader.
|
by Downloader.
|
||||||
"""
|
"""
|
||||||
# noinspection PyUnresolvedReferences,PyProtectedMember
|
|
||||||
real_name = cog.name
|
real_name = cog.name
|
||||||
|
|
||||||
poss_installed_path = (await self.cog_install_path()) / real_name
|
poss_installed_path = (await self.cog_install_path()) / real_name
|
||||||
if poss_installed_path.exists():
|
if poss_installed_path.exists():
|
||||||
|
ctx.bot.unload_extension(real_name)
|
||||||
await self._delete_cog(poss_installed_path)
|
await self._delete_cog(poss_installed_path)
|
||||||
# noinspection PyTypeChecker
|
|
||||||
await self._remove_from_installed(cog)
|
await self._remove_from_installed(cog)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Cog `{cog_name}` was successfully uninstalled.").format(cog_name=real_name)
|
_("Cog `{cog_name}` was successfully uninstalled.").format(cog_name=real_name)
|
||||||
@@ -344,7 +343,7 @@ class Downloader(commands.Cog):
|
|||||||
" files manually if it is still usable."
|
" files manually if it is still usable."
|
||||||
" Also make sure you've unloaded the cog"
|
" Also make sure you've unloaded the cog"
|
||||||
" with `{prefix}unload {cog_name}`."
|
" with `{prefix}unload {cog_name}`."
|
||||||
).format(cog_name=real_name)
|
).format(prefix=ctx.prefix, cog_name=real_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
@cog.command(name="update")
|
@cog.command(name="update")
|
||||||
@@ -372,13 +371,18 @@ class Downloader(commands.Cog):
|
|||||||
await self._reinstall_libraries(installed_and_updated)
|
await self._reinstall_libraries(installed_and_updated)
|
||||||
message = _("Cog update completed successfully.")
|
message = _("Cog update completed successfully.")
|
||||||
|
|
||||||
cognames = [c.name for c in installed_and_updated]
|
cognames = {c.name for c in installed_and_updated}
|
||||||
message += _("\nUpdated: ") + humanize_list(tuple(map(inline, cognames)))
|
message += _("\nUpdated: ") + humanize_list(tuple(map(inline, cognames)))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("All installed cogs are already up to date."))
|
await ctx.send(_("All installed cogs are already up to date."))
|
||||||
return
|
return
|
||||||
await ctx.send(message)
|
await ctx.send(message)
|
||||||
|
|
||||||
|
cognames &= set(ctx.bot.extensions.keys()) # only reload loaded cogs
|
||||||
|
if not cognames:
|
||||||
|
return await ctx.send(
|
||||||
|
_("None of the updated cogs were previously loaded. Update complete.")
|
||||||
|
)
|
||||||
message = _("Would you like to reload the updated cogs?")
|
message = _("Would you like to reload the updated cogs?")
|
||||||
can_react = ctx.channel.permissions_for(ctx.me).add_reactions
|
can_react = ctx.channel.permissions_for(ctx.me).add_reactions
|
||||||
if not can_react:
|
if not can_react:
|
||||||
@@ -402,7 +406,6 @@ class Downloader(commands.Cog):
|
|||||||
if can_react:
|
if can_react:
|
||||||
with contextlib.suppress(discord.Forbidden):
|
with contextlib.suppress(discord.Forbidden):
|
||||||
await query.clear_reactions()
|
await query.clear_reactions()
|
||||||
|
|
||||||
await ctx.invoke(ctx.bot.get_cog("Core").reload, *cognames)
|
await ctx.invoke(ctx.bot.get_cog("Core").reload, *cognames)
|
||||||
else:
|
else:
|
||||||
if can_react:
|
if can_react:
|
||||||
@@ -499,7 +502,7 @@ class Downloader(commands.Cog):
|
|||||||
if isinstance(cog_installable, Installable):
|
if isinstance(cog_installable, Installable):
|
||||||
made_by = ", ".join(cog_installable.author) or _("Missing from info.json")
|
made_by = ", ".join(cog_installable.author) or _("Missing from info.json")
|
||||||
repo = self._repo_manager.get_repo(cog_installable.repo_name)
|
repo = self._repo_manager.get_repo(cog_installable.repo_name)
|
||||||
repo_url = repo.url
|
repo_url = _("Missing from installed repos") if repo is None else repo.url
|
||||||
cog_name = cog_installable.name
|
cog_name = cog_installable.name
|
||||||
else:
|
else:
|
||||||
made_by = "26 & co."
|
made_by = "26 & co."
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from typing import cast, Iterable
|
|||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
|
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
|
||||||
from redbot.core import Config, bank, commands
|
from redbot.core import Config, bank, commands, errors
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.utils.chat_formatting import box
|
||||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||||
@@ -171,7 +171,7 @@ class Economy(commands.Cog):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await bank.transfer_credits(from_, to, amount)
|
await bank.transfer_credits(from_, to, amount)
|
||||||
except ValueError as e:
|
except (ValueError, errors.BalanceTooHigh) as e:
|
||||||
return await ctx.send(str(e))
|
return await ctx.send(str(e))
|
||||||
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
@@ -195,36 +195,35 @@ class Economy(commands.Cog):
|
|||||||
author = ctx.author
|
author = ctx.author
|
||||||
currency = await bank.get_currency_name(ctx.guild)
|
currency = await bank.get_currency_name(ctx.guild)
|
||||||
|
|
||||||
|
try:
|
||||||
if creds.operation == "deposit":
|
if creds.operation == "deposit":
|
||||||
await bank.deposit_credits(to, creds.sum)
|
await bank.deposit_credits(to, creds.sum)
|
||||||
await ctx.send(
|
msg = _("{author} added {num} {currency} to {user}'s account.").format(
|
||||||
_("{author} added {num} {currency} to {user}'s account.").format(
|
|
||||||
author=author.display_name,
|
author=author.display_name,
|
||||||
num=creds.sum,
|
num=creds.sum,
|
||||||
currency=currency,
|
currency=currency,
|
||||||
user=to.display_name,
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
elif creds.operation == "withdraw":
|
elif creds.operation == "withdraw":
|
||||||
await bank.withdraw_credits(to, creds.sum)
|
await bank.withdraw_credits(to, creds.sum)
|
||||||
await ctx.send(
|
msg = _("{author} removed {num} {currency} from {user}'s account.").format(
|
||||||
_("{author} removed {num} {currency} from {user}'s account.").format(
|
|
||||||
author=author.display_name,
|
author=author.display_name,
|
||||||
num=creds.sum,
|
num=creds.sum,
|
||||||
currency=currency,
|
currency=currency,
|
||||||
user=to.display_name,
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await bank.set_balance(to, creds.sum)
|
await bank.set_balance(to, creds.sum)
|
||||||
await ctx.send(
|
msg = _("{author} set {user}'s account balance to {num} {currency}.").format(
|
||||||
_("{author} set {users}'s account balance to {num} {currency}.").format(
|
|
||||||
author=author.display_name,
|
author=author.display_name,
|
||||||
num=creds.sum,
|
num=creds.sum,
|
||||||
currency=currency,
|
currency=currency,
|
||||||
user=to.display_name,
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
except (ValueError, errors.BalanceTooHigh) as e:
|
||||||
|
await ctx.send(str(e))
|
||||||
|
else:
|
||||||
|
await ctx.send(msg)
|
||||||
|
|
||||||
@_bank.command()
|
@_bank.command()
|
||||||
@check_global_setting_guildowner()
|
@check_global_setting_guildowner()
|
||||||
@@ -260,7 +259,18 @@ class Economy(commands.Cog):
|
|||||||
if await bank.is_global(): # Role payouts will not be used
|
if await bank.is_global(): # Role payouts will not be used
|
||||||
next_payday = await self.config.user(author).next_payday()
|
next_payday = await self.config.user(author).next_payday()
|
||||||
if cur_time >= next_payday:
|
if cur_time >= next_payday:
|
||||||
|
try:
|
||||||
await bank.deposit_credits(author, await self.config.PAYDAY_CREDITS())
|
await bank.deposit_credits(author, await self.config.PAYDAY_CREDITS())
|
||||||
|
except errors.BalanceTooHigh as exc:
|
||||||
|
await bank.set_balance(author, exc.max_balance)
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You've reached the maximum amount of {currency}! (**{balance:,}**) "
|
||||||
|
"Please spend some more \N{GRIMACING FACE}\n\n"
|
||||||
|
"You currently have {new_balance} {currency}."
|
||||||
|
).format(currency=credits_name, new_balance=exc.max_balance)
|
||||||
|
)
|
||||||
|
return
|
||||||
next_payday = cur_time + await self.config.PAYDAY_TIME()
|
next_payday = cur_time + await self.config.PAYDAY_TIME()
|
||||||
await self.config.user(author).next_payday.set(next_payday)
|
await self.config.user(author).next_payday.set(next_payday)
|
||||||
|
|
||||||
@@ -268,7 +278,7 @@ class Economy(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"{author.mention} Here, take some {currency}. "
|
"{author.mention} Here, take some {currency}. "
|
||||||
"Enjoy! (+{amount} {new_balance}!)\n\n"
|
"Enjoy! (+{amount} {currency}!)\n\n"
|
||||||
"You currently have {new_balance} {currency}.\n\n"
|
"You currently have {new_balance} {currency}.\n\n"
|
||||||
"You are currently #{pos} on the global leaderboard!"
|
"You are currently #{pos} on the global leaderboard!"
|
||||||
).format(
|
).format(
|
||||||
@@ -297,14 +307,25 @@ class Economy(commands.Cog):
|
|||||||
).PAYDAY_CREDITS() # Nice variable name
|
).PAYDAY_CREDITS() # Nice variable name
|
||||||
if role_credits > credit_amount:
|
if role_credits > credit_amount:
|
||||||
credit_amount = role_credits
|
credit_amount = role_credits
|
||||||
|
try:
|
||||||
await bank.deposit_credits(author, credit_amount)
|
await bank.deposit_credits(author, credit_amount)
|
||||||
|
except errors.BalanceTooHigh as exc:
|
||||||
|
await bank.set_balance(author, exc.max_balance)
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You've reached the maximum amount of {currency}! "
|
||||||
|
"Please spend some more \N{GRIMACING FACE}\n\n"
|
||||||
|
"You currently have {new_balance} {currency}."
|
||||||
|
).format(currency=credits_name, new_balance=exc.max_balance)
|
||||||
|
)
|
||||||
|
return
|
||||||
next_payday = cur_time + await self.config.guild(guild).PAYDAY_TIME()
|
next_payday = cur_time + await self.config.guild(guild).PAYDAY_TIME()
|
||||||
await self.config.member(author).next_payday.set(next_payday)
|
await self.config.member(author).next_payday.set(next_payday)
|
||||||
pos = await bank.get_leaderboard_position(author)
|
pos = await bank.get_leaderboard_position(author)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"{author.mention} Here, take some {currency}. "
|
"{author.mention} Here, take some {currency}. "
|
||||||
"Enjoy! (+{amount} {new_balance}!)\n\n"
|
"Enjoy! (+{amount} {currency}!)\n\n"
|
||||||
"You currently have {new_balance} {currency}.\n\n"
|
"You currently have {new_balance} {currency}.\n\n"
|
||||||
"You are currently #{pos} on the global leaderboard!"
|
"You are currently #{pos} on the global leaderboard!"
|
||||||
).format(
|
).format(
|
||||||
@@ -367,7 +388,7 @@ class Economy(commands.Cog):
|
|||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
async def payouts(self, ctx: commands.Context):
|
async def payouts(self, ctx: commands.Context):
|
||||||
"""Show the payouts for the slot machine."""
|
"""Show the payouts for the slot machine."""
|
||||||
await ctx.author.send(SLOT_PAYOUTS_MSG())
|
await ctx.author.send(SLOT_PAYOUTS_MSG)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
@@ -444,7 +465,21 @@ class Economy(commands.Cog):
|
|||||||
then = await bank.get_balance(author)
|
then = await bank.get_balance(author)
|
||||||
pay = payout["payout"](bid)
|
pay = payout["payout"](bid)
|
||||||
now = then - bid + pay
|
now = then - bid + pay
|
||||||
|
try:
|
||||||
await bank.set_balance(author, now)
|
await bank.set_balance(author, now)
|
||||||
|
except errors.BalanceTooHigh as exc:
|
||||||
|
await bank.set_balance(author, exc.max_balance)
|
||||||
|
await channel.send(
|
||||||
|
_(
|
||||||
|
"You've reached the maximum amount of {currency}! "
|
||||||
|
"Please spend some more \N{GRIMACING FACE}\n{old_balance} -> {new_balance}!"
|
||||||
|
).format(
|
||||||
|
currency=await bank.get_currency_name(getattr(channel, "guild", None)),
|
||||||
|
old_balance=then,
|
||||||
|
new_balance=exc.max_balance,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
phrase = T_(payout["phrase"])
|
phrase = T_(payout["phrase"])
|
||||||
else:
|
else:
|
||||||
then = await bank.get_balance(author)
|
then = await bank.get_balance(author)
|
||||||
@@ -561,10 +596,10 @@ class Economy(commands.Cog):
|
|||||||
async def paydayamount(self, ctx: commands.Context, creds: int):
|
async def paydayamount(self, ctx: commands.Context, creds: int):
|
||||||
"""Set the amount earned each payday."""
|
"""Set the amount earned each payday."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
credits_name = await bank.get_currency_name(guild)
|
if creds <= 0 or creds > bank.MAX_BALANCE:
|
||||||
if creds <= 0:
|
|
||||||
await ctx.send(_("Har har so funny."))
|
await ctx.send(_("Har har so funny."))
|
||||||
return
|
return
|
||||||
|
credits_name = await bank.get_currency_name(guild)
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
await self.config.PAYDAY_CREDITS.set(creds)
|
await self.config.PAYDAY_CREDITS.set(creds)
|
||||||
else:
|
else:
|
||||||
@@ -579,6 +614,9 @@ class Economy(commands.Cog):
|
|||||||
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
||||||
"""Set the amount earned each payday for a role."""
|
"""Set the amount earned each payday for a role."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
if creds <= 0 or creds > bank.MAX_BALANCE:
|
||||||
|
await ctx.send(_("Har har so funny."))
|
||||||
|
return
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
await ctx.send(_("The bank must be per-server for per-role paydays to work."))
|
await ctx.send(_("The bank must be per-server for per-role paydays to work."))
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class RPSParser:
|
|||||||
elif argument == "scissors":
|
elif argument == "scissors":
|
||||||
self.choice = RPS.scissors
|
self.choice = RPS.scissors
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
self.choice = None
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
@@ -121,6 +121,8 @@ class General(commands.Cog):
|
|||||||
"""Play Rock Paper Scissors."""
|
"""Play Rock Paper Scissors."""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
player_choice = your_choice.choice
|
player_choice = your_choice.choice
|
||||||
|
if not player_choice:
|
||||||
|
return await ctx.send("This isn't a valid option. Try rock, paper, or scissors.")
|
||||||
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
||||||
cond = {
|
cond = {
|
||||||
(RPS.rock, RPS.paper): False,
|
(RPS.rock, RPS.paper): False,
|
||||||
@@ -263,12 +265,13 @@ class General(commands.Cog):
|
|||||||
|
|
||||||
except aiohttp.ClientError:
|
except aiohttp.ClientError:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("No Urban dictionary entries were found, or there was an error in the process")
|
_("No Urban Dictionary entries were found, or there was an error in the process.")
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if data.get("error") != 404:
|
if data.get("error") != 404:
|
||||||
|
if not data["list"]:
|
||||||
|
return await ctx.send(_("No Urban Dictionary entries were found."))
|
||||||
if await ctx.embed_requested():
|
if await ctx.embed_requested():
|
||||||
# a list of embeds
|
# a list of embeds
|
||||||
embeds = []
|
embeds = []
|
||||||
@@ -303,14 +306,14 @@ class General(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
messages = []
|
messages = []
|
||||||
for ud in data["list"]:
|
for ud in data["list"]:
|
||||||
ud.set_default("example", "N/A")
|
ud.setdefault("example", "N/A")
|
||||||
description = _("{definition}\n\n**Example:** {example}").format(**ud)
|
description = _("{definition}\n\n**Example:** {example}").format(**ud)
|
||||||
if len(description) > 2048:
|
if len(description) > 2048:
|
||||||
description = "{}...".format(description[:2045])
|
description = "{}...".format(description[:2045])
|
||||||
|
|
||||||
message = _(
|
message = _(
|
||||||
"<{permalink}>\n {word} by {author}\n\n{description}\n\n"
|
"<{permalink}>\n {word} by {author}\n\n{description}\n\n"
|
||||||
"{thumbs_down} Down / {thumbs_up} Up, Powered by urban dictionary"
|
"{thumbs_down} Down / {thumbs_up} Up, Powered by Urban Dictionary."
|
||||||
).format(word=ud.pop("word").capitalize(), description=description, **ud)
|
).format(word=ud.pop("word").capitalize(), description=description, **ud)
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|
||||||
@@ -325,6 +328,5 @@ class General(commands.Cog):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("No Urban dictionary entries were found, or there was an error in the process.")
|
_("No Urban Dictionary entries were found, or there was an error in the process.")
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
from redbot.core import commands
|
|
||||||
|
|
||||||
|
|
||||||
def mod_or_voice_permissions(**perms):
|
|
||||||
async def pred(ctx: commands.Context):
|
|
||||||
author = ctx.author
|
|
||||||
guild = ctx.guild
|
|
||||||
if await ctx.bot.is_owner(author) or guild.owner == author:
|
|
||||||
# Author is bot owner or guild owner
|
|
||||||
return True
|
|
||||||
|
|
||||||
admin_role = guild.get_role(await ctx.bot.db.guild(guild).admin_role())
|
|
||||||
mod_role = guild.get_role(await ctx.bot.db.guild(guild).mod_role())
|
|
||||||
|
|
||||||
if admin_role in author.roles or mod_role in author.roles:
|
|
||||||
return True
|
|
||||||
|
|
||||||
for vc in guild.voice_channels:
|
|
||||||
resolved = vc.permissions_for(author)
|
|
||||||
good = resolved.administrator or all(
|
|
||||||
getattr(resolved, name, None) == value for name, value in perms.items()
|
|
||||||
)
|
|
||||||
if not good:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return commands.permissions_check(pred)
|
|
||||||
|
|
||||||
|
|
||||||
def admin_or_voice_permissions(**perms):
|
|
||||||
async def pred(ctx: commands.Context):
|
|
||||||
author = ctx.author
|
|
||||||
guild = ctx.guild
|
|
||||||
if await ctx.bot.is_owner(author) or guild.owner == author:
|
|
||||||
return True
|
|
||||||
admin_role = guild.get_role(await ctx.bot.db.guild(guild).admin_role())
|
|
||||||
if admin_role in author.roles:
|
|
||||||
return True
|
|
||||||
for vc in guild.voice_channels:
|
|
||||||
resolved = vc.permissions_for(author)
|
|
||||||
good = resolved.administrator or all(
|
|
||||||
getattr(resolved, name, None) == value for name, value in perms.items()
|
|
||||||
)
|
|
||||||
if not good:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return commands.permissions_check(pred)
|
|
||||||
|
|
||||||
|
|
||||||
def bot_has_voice_permissions(**perms):
|
|
||||||
async def pred(ctx: commands.Context):
|
|
||||||
guild = ctx.guild
|
|
||||||
for vc in guild.voice_channels:
|
|
||||||
resolved = vc.permissions_for(guild.me)
|
|
||||||
good = resolved.administrator or all(
|
|
||||||
getattr(resolved, name, None) == value for name, value in perms.items()
|
|
||||||
)
|
|
||||||
if not good:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return commands.check(pred)
|
|
||||||
@@ -2,19 +2,18 @@ import asyncio
|
|||||||
import contextlib
|
import contextlib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from collections import deque, defaultdict, namedtuple
|
from collections import deque, defaultdict, namedtuple
|
||||||
from typing import cast
|
from typing import cast, Optional
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.core import checks, Config, modlog, commands
|
from redbot.core import checks, Config, modlog, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.chat_formatting import box, escape
|
from redbot.core.utils.chat_formatting import box, escape, format_perms_list
|
||||||
from .checks import mod_or_voice_permissions, admin_or_voice_permissions, bot_has_voice_permissions
|
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
||||||
from redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, get_audit_reason
|
from redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, get_audit_reason
|
||||||
from .log import log
|
from .log import log
|
||||||
|
|
||||||
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
|
||||||
|
|
||||||
_ = T_ = Translator("Mod", __file__)
|
_ = T_ = Translator("Mod", __file__)
|
||||||
|
|
||||||
@@ -193,7 +192,7 @@ class Mod(commands.Cog):
|
|||||||
yes_or_no=_("Yes") if respect_hierarchy else _("No")
|
yes_or_no=_("Yes") if respect_hierarchy else _("No")
|
||||||
)
|
)
|
||||||
msg += _("Delete delay: {num_seconds}\n").format(
|
msg += _("Delete delay: {num_seconds}\n").format(
|
||||||
num_seconds=_("{num} seconds").format(delete_delay)
|
num_seconds=_("{num} seconds").format(num=delete_delay)
|
||||||
if delete_delay != -1
|
if delete_delay != -1
|
||||||
else _("None")
|
else _("None")
|
||||||
)
|
)
|
||||||
@@ -311,13 +310,15 @@ class Mod(commands.Cog):
|
|||||||
if not cur_setting:
|
if not cur_setting:
|
||||||
await self.settings.guild(guild).reinvite_on_unban.set(True)
|
await self.settings.guild(guild).reinvite_on_unban.set(True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Users unbanned with {command} will be reinvited.").format(f"{ctx.prefix}unban")
|
_("Users unbanned with {command} will be reinvited.").format(
|
||||||
|
command=f"{ctx.prefix}unban"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self.settings.guild(guild).reinvite_on_unban.set(False)
|
await self.settings.guild(guild).reinvite_on_unban.set(False)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Users unbanned with {command} will not be reinvited.").format(
|
_("Users unbanned with {command} will not be reinvited.").format(
|
||||||
f"{ctx.prefix}unban"
|
command=f"{ctx.prefix}unban"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -748,7 +749,8 @@ class Mod(commands.Cog):
|
|||||||
to send the newly unbanned user
|
to send the newly unbanned user
|
||||||
:returns: :class:`Invite`"""
|
:returns: :class:`Invite`"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if guild.me.permissions.manage_guild:
|
my_perms: discord.Permissions = guild.me.guild_permissions
|
||||||
|
if my_perms.manage_guild or my_perms.administrator:
|
||||||
if "VANITY_URL" in guild.features:
|
if "VANITY_URL" in guild.features:
|
||||||
# guild has a vanity url so use it as the one to send
|
# guild has a vanity url so use it as the one to send
|
||||||
return await guild.vanity_invite()
|
return await guild.vanity_invite()
|
||||||
@@ -778,15 +780,60 @@ class Mod(commands.Cog):
|
|||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _voice_perm_check(
|
||||||
|
ctx: commands.Context, user_voice_state: Optional[discord.VoiceState], **perms: bool
|
||||||
|
) -> bool:
|
||||||
|
"""Check if the bot and user have sufficient permissions for voicebans.
|
||||||
|
|
||||||
|
This also verifies that the user's voice state and connected
|
||||||
|
channel are not ``None``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
``True`` if the permissions are sufficient and the user has
|
||||||
|
a valid voice state.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if user_voice_state is None or user_voice_state.channel is None:
|
||||||
|
await ctx.send(_("That user is not in a voice channel."))
|
||||||
|
return False
|
||||||
|
voice_channel: discord.VoiceChannel = user_voice_state.channel
|
||||||
|
required_perms = discord.Permissions()
|
||||||
|
required_perms.update(**perms)
|
||||||
|
if not voice_channel.permissions_for(ctx.me) >= required_perms:
|
||||||
|
await ctx.send(
|
||||||
|
_("I require the {perms} permission(s) in that user's channel to do that.").format(
|
||||||
|
perms=format_perms_list(required_perms)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
if (
|
||||||
|
ctx.permission_state is commands.PermState.NORMAL
|
||||||
|
and not voice_channel.permissions_for(ctx.author) >= required_perms
|
||||||
|
):
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You must have the {perms} permission(s) in that user's channel to use this "
|
||||||
|
"command."
|
||||||
|
).format(perms=format_perms_list(required_perms))
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
@checks.admin_or_permissions(mute_members=True, deafen_members=True)
|
||||||
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
|
|
||||||
async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||||
"""Ban a user from speaking and listening in the server's voice channels."""
|
"""Ban a user from speaking and listening in the server's voice channels."""
|
||||||
user_voice_state = user.voice
|
user_voice_state: discord.VoiceState = user.voice
|
||||||
if user_voice_state is None:
|
if (
|
||||||
await ctx.send(_("No voice state for that user!"))
|
await self._voice_perm_check(
|
||||||
|
ctx, user_voice_state, deafen_members=True, mute_members=True
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
):
|
||||||
return
|
return
|
||||||
needs_mute = True if user_voice_state.mute is False else False
|
needs_mute = True if user_voice_state.mute is False else False
|
||||||
needs_deafen = True if user_voice_state.deaf is False else False
|
needs_deafen = True if user_voice_state.deaf is False else False
|
||||||
@@ -821,13 +868,15 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
|
||||||
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
|
|
||||||
async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||||
"""Unban a the user from speaking and listening in the server's voice channels."""
|
"""Unban a user from speaking and listening in the server's voice channels."""
|
||||||
user_voice_state = user.voice
|
user_voice_state = user.voice
|
||||||
if user_voice_state is None:
|
if (
|
||||||
await ctx.send(_("No voice state for that user!"))
|
await self._voice_perm_check(
|
||||||
|
ctx, user_voice_state, deafen_members=True, mute_members=True
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
):
|
||||||
return
|
return
|
||||||
needs_unmute = True if user_voice_state.mute else False
|
needs_unmute = True if user_voice_state.mute else False
|
||||||
needs_undeafen = True if user_voice_state.deaf else False
|
needs_undeafen = True if user_voice_state.deaf else False
|
||||||
@@ -863,49 +912,79 @@ class Mod(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_nicknames=True)
|
@commands.bot_has_permissions(manage_nicknames=True)
|
||||||
@checks.admin_or_permissions(manage_nicknames=True)
|
@checks.admin_or_permissions(manage_nicknames=True)
|
||||||
async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname=""):
|
async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname: str = ""):
|
||||||
"""Change a user's nickname.
|
"""Change a user's nickname.
|
||||||
|
|
||||||
Leaving the nickname empty will remove it.
|
Leaving the nickname empty will remove it.
|
||||||
"""
|
"""
|
||||||
nickname = nickname.strip()
|
nickname = nickname.strip()
|
||||||
if nickname == "":
|
me = cast(discord.Member, ctx.me)
|
||||||
|
if not nickname:
|
||||||
nickname = None
|
nickname = None
|
||||||
|
elif not 2 <= len(nickname) <= 32:
|
||||||
|
await ctx.send(_("Nicknames must be between 2 and 32 characters long."))
|
||||||
|
return
|
||||||
|
if not (
|
||||||
|
(me.guild_permissions.manage_nicknames or me.guild_permissions.administrator)
|
||||||
|
and me.top_role > user.top_role
|
||||||
|
and user != ctx.guild.owner
|
||||||
|
):
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"I do not have permission to rename that member. They may be higher than or "
|
||||||
|
"equal to me in the role hierarchy."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
|
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
|
||||||
await ctx.send("Done.")
|
except discord.Forbidden:
|
||||||
|
# Just in case we missed something in the permissions check above
|
||||||
|
await ctx.send(_("I do not have permission to rename that member."))
|
||||||
|
except discord.HTTPException as exc:
|
||||||
|
if exc.status == 400: # BAD REQUEST
|
||||||
|
await ctx.send(_("That nickname is invalid."))
|
||||||
|
else:
|
||||||
|
await ctx.send(_("An unexpected error has occured."))
|
||||||
|
else:
|
||||||
|
await ctx.send(_("Done."))
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.mod_or_permissions(manage_channel=True)
|
@checks.mod_or_permissions(manage_channels=True)
|
||||||
async def mute(self, ctx: commands.Context):
|
async def mute(self, ctx: commands.Context):
|
||||||
"""Mute users."""
|
"""Mute users."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@mute.command(name="voice")
|
@mute.command(name="voice")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@mod_or_voice_permissions(mute_members=True)
|
|
||||||
@bot_has_voice_permissions(mute_members=True)
|
|
||||||
async def voice_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
async def voice_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||||
"""Mute a user in their current voice channel."""
|
"""Mute a user in their current voice channel."""
|
||||||
user_voice_state = user.voice
|
user_voice_state = user.voice
|
||||||
|
if (
|
||||||
|
await self._voice_perm_check(
|
||||||
|
ctx, user_voice_state, mute_members=True, manage_channels=True
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
):
|
||||||
|
return
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
if user_voice_state:
|
|
||||||
channel = user_voice_state.channel
|
channel = user_voice_state.channel
|
||||||
if channel and channel.permissions_for(user).speak:
|
audit_reason = get_audit_reason(author, reason)
|
||||||
overwrites = channel.overwrites_for(user)
|
|
||||||
overwrites.speak = False
|
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||||
audit_reason = get_audit_reason(ctx.author, reason)
|
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
if success:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Muted {user} in channel {channel.name}").format(user, channel=channel)
|
_("Muted {user} in channel {channel.name}").format(user=user, channel=channel)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
self.bot,
|
self.bot,
|
||||||
guild,
|
guild,
|
||||||
ctx.message.created_at,
|
ctx.message.created_at,
|
||||||
"boicemute",
|
"vmute",
|
||||||
user,
|
user,
|
||||||
author,
|
author,
|
||||||
reason,
|
reason,
|
||||||
@@ -914,17 +993,8 @@ class Mod(commands.Cog):
|
|||||||
)
|
)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
await ctx.send(e)
|
await ctx.send(e)
|
||||||
return
|
|
||||||
elif channel.permissions_for(user).speak is False:
|
|
||||||
await ctx.send(
|
|
||||||
_("That user is already muted in {channel}!").format(channel=channel.name)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That user is not in a voice channel right now!"))
|
await ctx.send(issue)
|
||||||
else:
|
|
||||||
await ctx.send(_("No voice state for the target!"))
|
|
||||||
return
|
|
||||||
|
|
||||||
@mute.command(name="channel")
|
@mute.command(name="channel")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@@ -937,13 +1007,7 @@ class Mod(commands.Cog):
|
|||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
channel = ctx.message.channel
|
channel = ctx.message.channel
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
audit_reason = get_audit_reason(author, reason)
|
||||||
if reason is None:
|
|
||||||
audit_reason = "Channel mute requested by {a} (ID {a.id})".format(a=author)
|
|
||||||
else:
|
|
||||||
audit_reason = "Channel mute requested by {a} (ID {a.id}). Reason: {r}".format(
|
|
||||||
a=author, r=reason
|
|
||||||
)
|
|
||||||
|
|
||||||
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||||
|
|
||||||
@@ -974,24 +1038,10 @@ class Mod(commands.Cog):
|
|||||||
"""Mutes user in the server"""
|
"""Mutes user in the server"""
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if reason is None:
|
audit_reason = get_audit_reason(author, reason)
|
||||||
audit_reason = "server mute requested by {author} (ID {author.id})".format(
|
|
||||||
author=author
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
audit_reason = (
|
|
||||||
"server mute requested by {author} (ID {author.id}). Reason: {reason}"
|
|
||||||
).format(author=author, reason=reason)
|
|
||||||
|
|
||||||
mute_success = []
|
mute_success = []
|
||||||
for channel in guild.channels:
|
for channel in guild.channels:
|
||||||
if not isinstance(channel, discord.TextChannel):
|
|
||||||
if channel.permissions_for(user).speak:
|
|
||||||
overwrites = channel.overwrites_for(user)
|
|
||||||
overwrites.speak = False
|
|
||||||
audit_reason = get_audit_reason(ctx.author, reason)
|
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
|
||||||
else:
|
|
||||||
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||||
mute_success.append((success, issue))
|
mute_success.append((success, issue))
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
@@ -1014,7 +1064,7 @@ class Mod(commands.Cog):
|
|||||||
async def mute_user(
|
async def mute_user(
|
||||||
self,
|
self,
|
||||||
guild: discord.Guild,
|
guild: discord.Guild,
|
||||||
channel: discord.TextChannel,
|
channel: discord.abc.GuildChannel,
|
||||||
author: discord.Member,
|
author: discord.Member,
|
||||||
user: discord.Member,
|
user: discord.Member,
|
||||||
reason: str,
|
reason: str,
|
||||||
@@ -1022,64 +1072,73 @@ class Mod(commands.Cog):
|
|||||||
"""Mutes the specified user in the specified channel"""
|
"""Mutes the specified user in the specified channel"""
|
||||||
overwrites = channel.overwrites_for(user)
|
overwrites = channel.overwrites_for(user)
|
||||||
permissions = channel.permissions_for(user)
|
permissions = channel.permissions_for(user)
|
||||||
perms_cache = await self.settings.member(user).perms_cache()
|
|
||||||
|
|
||||||
if overwrites.send_messages is False or permissions.send_messages is False:
|
if permissions.administrator:
|
||||||
|
return False, T_(mute_unmute_issues["is_admin"])
|
||||||
|
|
||||||
|
new_overs = {}
|
||||||
|
if not isinstance(channel, discord.TextChannel):
|
||||||
|
new_overs.update(speak=False)
|
||||||
|
if not isinstance(channel, discord.VoiceChannel):
|
||||||
|
new_overs.update(send_messages=False, add_reactions=False)
|
||||||
|
|
||||||
|
if all(getattr(permissions, p) is False for p in new_overs.keys()):
|
||||||
return False, T_(mute_unmute_issues["already_muted"])
|
return False, T_(mute_unmute_issues["already_muted"])
|
||||||
|
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||||
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
||||||
|
|
||||||
perms_cache[str(channel.id)] = {
|
old_overs = {k: getattr(overwrites, k) for k in new_overs}
|
||||||
"send_messages": overwrites.send_messages,
|
overwrites.update(**new_overs)
|
||||||
"add_reactions": overwrites.add_reactions,
|
|
||||||
}
|
|
||||||
overwrites.update(send_messages=False, add_reactions=False)
|
|
||||||
try:
|
try:
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
return False, T_(mute_unmute_issues["permissions_issue"])
|
return False, T_(mute_unmute_issues["permissions_issue"])
|
||||||
else:
|
else:
|
||||||
await self.settings.member(user).perms_cache.set(perms_cache)
|
await self.settings.member(user).set_raw(
|
||||||
|
"perms_cache", str(channel.id), value=old_overs
|
||||||
|
)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_roles=True)
|
@commands.bot_has_permissions(manage_roles=True)
|
||||||
@checks.mod_or_permissions(manage_channel=True)
|
@checks.mod_or_permissions(manage_channels=True)
|
||||||
async def unmute(self, ctx: commands.Context):
|
async def unmute(self, ctx: commands.Context):
|
||||||
"""Unmute users."""
|
"""Unmute users."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@unmute.command(name="voice")
|
@unmute.command(name="voice")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@mod_or_voice_permissions(mute_members=True)
|
|
||||||
@bot_has_voice_permissions(mute_members=True)
|
|
||||||
async def unmute_voice(
|
async def unmute_voice(
|
||||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
||||||
):
|
):
|
||||||
"""Unmute a user in their current voice channel."""
|
"""Unmute a user in their current voice channel."""
|
||||||
user_voice_state = user.voice
|
user_voice_state = user.voice
|
||||||
if user_voice_state:
|
if (
|
||||||
channel = user_voice_state.channel
|
await self._voice_perm_check(
|
||||||
if channel and channel.permissions_for(user).speak is False:
|
ctx, user_voice_state, mute_members=True, manage_channels=True
|
||||||
overwrites = channel.overwrites_for(user)
|
|
||||||
overwrites.speak = None
|
|
||||||
audit_reason = get_audit_reason(ctx.author, reason)
|
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
|
||||||
author = ctx.author
|
|
||||||
guild = ctx.guild
|
|
||||||
await ctx.send(
|
|
||||||
_("Unmuted {}#{} in channel {}").format(
|
|
||||||
user.name, user.discriminator, channel.name
|
|
||||||
)
|
)
|
||||||
|
is False
|
||||||
|
):
|
||||||
|
return
|
||||||
|
guild = ctx.guild
|
||||||
|
author = ctx.author
|
||||||
|
channel = user_voice_state.channel
|
||||||
|
audit_reason = get_audit_reason(author, reason)
|
||||||
|
|
||||||
|
success, message = await self.unmute_user(guild, channel, author, user, audit_reason)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
await ctx.send(
|
||||||
|
_("Unmuted {user} in channel {channel.name}").format(user=user, channel=channel)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
self.bot,
|
self.bot,
|
||||||
guild,
|
guild,
|
||||||
ctx.message.created_at,
|
ctx.message.created_at,
|
||||||
"voiceunmute",
|
"vunmute",
|
||||||
user,
|
user,
|
||||||
author,
|
author,
|
||||||
reason,
|
reason,
|
||||||
@@ -1088,14 +1147,8 @@ class Mod(commands.Cog):
|
|||||||
)
|
)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
await ctx.send(e)
|
await ctx.send(e)
|
||||||
elif channel.permissions_for(user).speak:
|
|
||||||
await ctx.send(_("That user is already unmuted in {}!").format(channel.name))
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That user is not in a voice channel right now!"))
|
await ctx.send(_("Unmute failed. Reason: {}").format(message))
|
||||||
else:
|
|
||||||
await ctx.send(_("No voice state for the target!"))
|
|
||||||
return
|
|
||||||
|
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
@unmute.command(name="channel")
|
@unmute.command(name="channel")
|
||||||
@@ -1108,8 +1161,9 @@ class Mod(commands.Cog):
|
|||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
audit_reason = get_audit_reason(author, reason)
|
||||||
|
|
||||||
success, message = await self.unmute_user(guild, channel, author, user)
|
success, message = await self.unmute_user(guild, channel, author, user, audit_reason)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
await ctx.send(_("User unmuted in this channel."))
|
await ctx.send(_("User unmuted in this channel."))
|
||||||
@@ -1140,16 +1194,11 @@ class Mod(commands.Cog):
|
|||||||
"""Unmute a user in this server."""
|
"""Unmute a user in this server."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
|
audit_reason = get_audit_reason(author, reason)
|
||||||
|
|
||||||
unmute_success = []
|
unmute_success = []
|
||||||
for channel in guild.channels:
|
for channel in guild.channels:
|
||||||
if not isinstance(channel, discord.TextChannel):
|
success, message = await self.unmute_user(guild, channel, author, user, audit_reason)
|
||||||
if channel.permissions_for(user).speak is False:
|
|
||||||
overwrites = channel.overwrites_for(user)
|
|
||||||
overwrites.speak = None
|
|
||||||
audit_reason = get_audit_reason(author, reason)
|
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
|
||||||
success, message = await self.unmute_user(guild, channel, author, user)
|
|
||||||
unmute_success.append((success, message))
|
unmute_success.append((success, message))
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
await ctx.send(_("User has been unmuted in this server."))
|
await ctx.send(_("User has been unmuted in this server."))
|
||||||
@@ -1170,45 +1219,37 @@ class Mod(commands.Cog):
|
|||||||
async def unmute_user(
|
async def unmute_user(
|
||||||
self,
|
self,
|
||||||
guild: discord.Guild,
|
guild: discord.Guild,
|
||||||
channel: discord.TextChannel,
|
channel: discord.abc.GuildChannel,
|
||||||
author: discord.Member,
|
author: discord.Member,
|
||||||
user: discord.Member,
|
user: discord.Member,
|
||||||
|
reason: str,
|
||||||
) -> (bool, str):
|
) -> (bool, str):
|
||||||
overwrites = channel.overwrites_for(user)
|
overwrites = channel.overwrites_for(user)
|
||||||
permissions = channel.permissions_for(user)
|
|
||||||
perms_cache = await self.settings.member(user).perms_cache()
|
perms_cache = await self.settings.member(user).perms_cache()
|
||||||
|
|
||||||
if overwrites.send_messages or permissions.send_messages:
|
if channel.id in perms_cache:
|
||||||
|
old_values = perms_cache[channel.id]
|
||||||
|
else:
|
||||||
|
old_values = {"send_messages": None, "add_reactions": None, "speak": None}
|
||||||
|
|
||||||
|
if all(getattr(overwrites, k) == v for k, v in old_values.items()):
|
||||||
return False, T_(mute_unmute_issues["already_unmuted"])
|
return False, T_(mute_unmute_issues["already_unmuted"])
|
||||||
|
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||||
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
||||||
|
|
||||||
if channel.id in perms_cache:
|
overwrites.update(**old_values)
|
||||||
old_values = perms_cache[channel.id]
|
|
||||||
else:
|
|
||||||
old_values = {"send_messages": None, "add_reactions": None}
|
|
||||||
overwrites.update(
|
|
||||||
send_messages=old_values["send_messages"], add_reactions=old_values["add_reactions"]
|
|
||||||
)
|
|
||||||
is_empty = self.are_overwrites_empty(overwrites)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not is_empty:
|
if overwrites.is_empty():
|
||||||
await channel.set_permissions(user, overwrite=overwrites)
|
|
||||||
else:
|
|
||||||
await channel.set_permissions(
|
await channel.set_permissions(
|
||||||
user, overwrite=cast(discord.PermissionOverwrite, None)
|
user, overwrite=cast(discord.PermissionOverwrite, None), reason=reason
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
return False, T_(mute_unmute_issues["permissions_issue"])
|
return False, T_(mute_unmute_issues["permissions_issue"])
|
||||||
else:
|
else:
|
||||||
try:
|
await self.settings.member(user).clear_raw("perms_cache", str(channel.id))
|
||||||
del perms_cache[channel.id]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
await self.settings.member(user).perms_cache.set(perms_cache)
|
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@@ -1591,8 +1632,9 @@ class Mod(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
An event for modlog case creation
|
An event for modlog case creation
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
mod_channel = await modlog.get_modlog_channel(case.guild)
|
mod_channel = await modlog.get_modlog_channel(case.guild)
|
||||||
if mod_channel is None:
|
except RuntimeError:
|
||||||
return
|
return
|
||||||
use_embeds = await case.bot.embed_requested(mod_channel, case.guild.me)
|
use_embeds = await case.bot.embed_requested(mod_channel, case.guild.me)
|
||||||
case_content = await case.message_content(use_embeds)
|
case_content = await case.message_content(use_embeds)
|
||||||
@@ -1694,20 +1736,15 @@ class Mod(commands.Cog):
|
|||||||
while len(nick_list) > 20:
|
while len(nick_list) > 20:
|
||||||
nick_list.pop(0)
|
nick_list.pop(0)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def are_overwrites_empty(overwrites):
|
|
||||||
"""There is currently no cleaner way to check if a
|
|
||||||
PermissionOverwrite object is empty"""
|
|
||||||
return [p for p in iter(overwrites)] == [p for p in iter(discord.PermissionOverwrite())]
|
|
||||||
|
|
||||||
|
|
||||||
_ = lambda s: s
|
_ = lambda s: s
|
||||||
mute_unmute_issues = {
|
mute_unmute_issues = {
|
||||||
"already_muted": _("That user can't send messages in this channel."),
|
"already_muted": _("That user can't send messages in this channel."),
|
||||||
"already_unmuted": _("That user isn't muted in this channel!"),
|
"already_unmuted": _("That user isn't muted in this channel."),
|
||||||
"hierarchy_problem": _(
|
"hierarchy_problem": _(
|
||||||
"I cannot let you do that. You are not higher than " "the user in the role hierarchy."
|
"I cannot let you do that. You are not higher than the user in the role hierarchy."
|
||||||
),
|
),
|
||||||
|
"is_admin": _("That user cannot be muted, as they have the Administrator permission."),
|
||||||
"permissions_issue": _(
|
"permissions_issue": _(
|
||||||
"Failed to mute user. I need the manage roles "
|
"Failed to mute user. I need the manage roles "
|
||||||
"permission and the user I'm muting must be "
|
"permission and the user I'm muting must be "
|
||||||
|
|||||||
@@ -1,10 +1,133 @@
|
|||||||
from typing import NamedTuple, Union, Optional, cast, Type
|
import itertools
|
||||||
|
import re
|
||||||
|
from typing import NamedTuple, Union, Optional
|
||||||
|
|
||||||
|
import discord
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
_ = Translator("PermissionsConverters", __file__)
|
_ = Translator("PermissionsConverters", __file__)
|
||||||
|
|
||||||
|
MENTION_RE = re.compile(r"^<?(?:(?:@[!&]?)?|#)(\d{15,21})>?$")
|
||||||
|
|
||||||
|
|
||||||
|
def _match_id(arg: str) -> Optional[int]:
|
||||||
|
m = MENTION_RE.match(arg)
|
||||||
|
if m:
|
||||||
|
return int(m.group(1))
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalUniqueObjectFinder(commands.Converter):
|
||||||
|
async def convert(
|
||||||
|
self, ctx: commands.Context, arg: str
|
||||||
|
) -> Union[discord.Guild, discord.abc.GuildChannel, discord.abc.User, discord.Role]:
|
||||||
|
bot: commands.Bot = ctx.bot
|
||||||
|
_id = _match_id(arg)
|
||||||
|
|
||||||
|
if _id is not None:
|
||||||
|
guild: discord.Guild = bot.get_guild(_id)
|
||||||
|
if guild is not None:
|
||||||
|
return guild
|
||||||
|
channel: discord.abc.GuildChannel = bot.get_channel(_id)
|
||||||
|
if channel is not None:
|
||||||
|
return channel
|
||||||
|
|
||||||
|
user: discord.User = bot.get_user(_id)
|
||||||
|
if user is not None:
|
||||||
|
return user
|
||||||
|
|
||||||
|
for guild in bot.guilds:
|
||||||
|
role: discord.Role = guild.get_role(_id)
|
||||||
|
if role is not None:
|
||||||
|
return role
|
||||||
|
|
||||||
|
objects = itertools.chain(
|
||||||
|
bot.get_all_channels(),
|
||||||
|
bot.users,
|
||||||
|
bot.guilds,
|
||||||
|
*(filter(lambda r: not r.is_default(), guild.roles) for guild in bot.guilds),
|
||||||
|
)
|
||||||
|
|
||||||
|
maybe_matches = []
|
||||||
|
for obj in objects:
|
||||||
|
if obj.name == arg or str(obj) == arg:
|
||||||
|
maybe_matches.append(obj)
|
||||||
|
|
||||||
|
if ctx.guild is not None:
|
||||||
|
for member in ctx.guild.members:
|
||||||
|
if member.nick == arg and not any(obj.id == member.id for obj in maybe_matches):
|
||||||
|
maybe_matches.append(member)
|
||||||
|
|
||||||
|
if not maybe_matches:
|
||||||
|
raise commands.BadArgument(
|
||||||
|
_(
|
||||||
|
'"{arg}" was not found. It must be the ID, mention, or name of a server, '
|
||||||
|
"channel, user or role which the bot can see."
|
||||||
|
).format(arg=arg)
|
||||||
|
)
|
||||||
|
elif len(maybe_matches) == 1:
|
||||||
|
return maybe_matches[0]
|
||||||
|
else:
|
||||||
|
raise commands.BadArgument(
|
||||||
|
_(
|
||||||
|
'"{arg}" does not refer to a unique server, channel, user or role. Please use '
|
||||||
|
"the ID for whatever/whoever you're trying to specify, or mention it/them."
|
||||||
|
).format(arg=arg)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GuildUniqueObjectFinder(commands.Converter):
|
||||||
|
async def convert(
|
||||||
|
self, ctx: commands.Context, arg: str
|
||||||
|
) -> Union[discord.abc.GuildChannel, discord.Member, discord.Role]:
|
||||||
|
guild: discord.Guild = ctx.guild
|
||||||
|
_id = _match_id(arg)
|
||||||
|
|
||||||
|
if _id is not None:
|
||||||
|
channel: discord.abc.GuildChannel = guild.get_channel(_id)
|
||||||
|
if channel is not None:
|
||||||
|
return channel
|
||||||
|
|
||||||
|
member: discord.Member = guild.get_member(_id)
|
||||||
|
if member is not None:
|
||||||
|
return member
|
||||||
|
|
||||||
|
role: discord.Role = guild.get_role(_id)
|
||||||
|
if role is not None and not role.is_default():
|
||||||
|
return role
|
||||||
|
|
||||||
|
objects = itertools.chain(
|
||||||
|
guild.channels, guild.members, filter(lambda r: not r.is_default(), guild.roles)
|
||||||
|
)
|
||||||
|
|
||||||
|
maybe_matches = []
|
||||||
|
for obj in objects:
|
||||||
|
if obj.name == arg or str(obj) == arg:
|
||||||
|
maybe_matches.append(obj)
|
||||||
|
try:
|
||||||
|
if obj.nick == arg:
|
||||||
|
maybe_matches.append(obj)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not maybe_matches:
|
||||||
|
raise commands.BadArgument(
|
||||||
|
_(
|
||||||
|
'"{arg}" was not found. It must be the ID, mention, or name of a channel, '
|
||||||
|
"user or role in this server."
|
||||||
|
).format(arg=arg)
|
||||||
|
)
|
||||||
|
elif len(maybe_matches) == 1:
|
||||||
|
return maybe_matches[0]
|
||||||
|
else:
|
||||||
|
raise commands.BadArgument(
|
||||||
|
_(
|
||||||
|
'"{arg}" does not refer to a unique channel, user or role. Please use the ID '
|
||||||
|
"for whatever/whoever you're trying to specify, or mention it/them."
|
||||||
|
).format(arg=arg)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CogOrCommand(NamedTuple):
|
class CogOrCommand(NamedTuple):
|
||||||
type: str
|
type: str
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ from redbot.core.utils.chat_formatting import box
|
|||||||
from redbot.core.utils.menus import start_adding_reactions
|
from redbot.core.utils.menus import start_adding_reactions
|
||||||
from redbot.core.utils.predicates import ReactionPredicate, MessagePredicate
|
from redbot.core.utils.predicates import ReactionPredicate, MessagePredicate
|
||||||
|
|
||||||
from .converters import CogOrCommand, RuleType, ClearableRuleType
|
from .converters import (
|
||||||
|
CogOrCommand,
|
||||||
|
RuleType,
|
||||||
|
ClearableRuleType,
|
||||||
|
GuildUniqueObjectFinder,
|
||||||
|
GlobalUniqueObjectFinder,
|
||||||
|
)
|
||||||
|
|
||||||
_ = Translator("Permissions", __file__)
|
_ = Translator("Permissions", __file__)
|
||||||
|
|
||||||
@@ -142,23 +148,20 @@ class Permissions(commands.Cog):
|
|||||||
if not command:
|
if not command:
|
||||||
return await ctx.send_help()
|
return await ctx.send_help()
|
||||||
|
|
||||||
message = copy(ctx.message)
|
fake_message = copy(ctx.message)
|
||||||
message.author = user
|
fake_message.author = user
|
||||||
message.content = "{}{}".format(ctx.prefix, command)
|
fake_message.content = "{}{}".format(ctx.prefix, command)
|
||||||
|
|
||||||
com = ctx.bot.get_command(command)
|
com = ctx.bot.get_command(command)
|
||||||
if com is None:
|
if com is None:
|
||||||
out = _("No such command")
|
out = _("No such command")
|
||||||
else:
|
else:
|
||||||
|
fake_context = await ctx.bot.get_context(fake_message)
|
||||||
try:
|
try:
|
||||||
testcontext = await ctx.bot.get_context(message, cls=commands.Context)
|
can = await com.can_run(
|
||||||
to_check = [*reversed(com.parents)] + [com]
|
fake_context, check_all_parents=True, change_permission_state=False
|
||||||
can = False
|
)
|
||||||
for cmd in to_check:
|
except commands.CommandError:
|
||||||
can = await cmd.can_run(testcontext)
|
|
||||||
if can is False:
|
|
||||||
break
|
|
||||||
except commands.CheckFailure:
|
|
||||||
can = False
|
can = False
|
||||||
|
|
||||||
out = (
|
out = (
|
||||||
@@ -275,7 +278,7 @@ class Permissions(commands.Cog):
|
|||||||
ctx: commands.Context,
|
ctx: commands.Context,
|
||||||
allow_or_deny: RuleType,
|
allow_or_deny: RuleType,
|
||||||
cog_or_command: CogOrCommand,
|
cog_or_command: CogOrCommand,
|
||||||
who_or_what: commands.GlobalPermissionModel,
|
who_or_what: GlobalUniqueObjectFinder,
|
||||||
):
|
):
|
||||||
"""Add a global rule to a command.
|
"""Add a global rule to a command.
|
||||||
|
|
||||||
@@ -303,7 +306,7 @@ class Permissions(commands.Cog):
|
|||||||
ctx: commands.Context,
|
ctx: commands.Context,
|
||||||
allow_or_deny: RuleType,
|
allow_or_deny: RuleType,
|
||||||
cog_or_command: CogOrCommand,
|
cog_or_command: CogOrCommand,
|
||||||
who_or_what: commands.GuildPermissionModel,
|
who_or_what: GuildUniqueObjectFinder,
|
||||||
):
|
):
|
||||||
"""Add a rule to a command in this server.
|
"""Add a rule to a command in this server.
|
||||||
|
|
||||||
@@ -328,7 +331,7 @@ class Permissions(commands.Cog):
|
|||||||
self,
|
self,
|
||||||
ctx: commands.Context,
|
ctx: commands.Context,
|
||||||
cog_or_command: CogOrCommand,
|
cog_or_command: CogOrCommand,
|
||||||
who_or_what: commands.GlobalPermissionModel,
|
who_or_what: GlobalUniqueObjectFinder,
|
||||||
):
|
):
|
||||||
"""Remove a global rule from a command.
|
"""Remove a global rule from a command.
|
||||||
|
|
||||||
@@ -351,7 +354,7 @@ class Permissions(commands.Cog):
|
|||||||
ctx: commands.Context,
|
ctx: commands.Context,
|
||||||
cog_or_command: CogOrCommand,
|
cog_or_command: CogOrCommand,
|
||||||
*,
|
*,
|
||||||
who_or_what: commands.GuildPermissionModel,
|
who_or_what: GuildUniqueObjectFinder,
|
||||||
):
|
):
|
||||||
"""Remove a server rule from a command.
|
"""Remove a server rule from a command.
|
||||||
|
|
||||||
@@ -542,7 +545,8 @@ class Permissions(commands.Cog):
|
|||||||
continue
|
continue
|
||||||
conf = self.config.custom(category)
|
conf = self.config.custom(category)
|
||||||
for cmd_name, cmd_rules in rules_dict.items():
|
for cmd_name, cmd_rules in rules_dict.items():
|
||||||
await conf.set_raw(cmd_name, guild_id, value=cmd_rules)
|
cmd_rules = {str(model_id): rule for model_id, rule in cmd_rules.items()}
|
||||||
|
await conf.set_raw(cmd_name, str(guild_id), value=cmd_rules)
|
||||||
cmd_obj = getter(cmd_name)
|
cmd_obj = getter(cmd_name)
|
||||||
if cmd_obj is not None:
|
if cmd_obj is not None:
|
||||||
self._load_rules_for(cmd_obj, {guild_id: cmd_rules})
|
self._load_rules_for(cmd_obj, {guild_id: cmd_rules})
|
||||||
@@ -651,14 +655,14 @@ class Permissions(commands.Cog):
|
|||||||
if category in old_rules:
|
if category in old_rules:
|
||||||
for name, rules in old_rules[category].items():
|
for name, rules in old_rules[category].items():
|
||||||
these_rules = new_rules.setdefault(name, {})
|
these_rules = new_rules.setdefault(name, {})
|
||||||
guild_rules = these_rules.setdefault(guild_id, {})
|
guild_rules = these_rules.setdefault(str(guild_id), {})
|
||||||
# Since allow rules would take precedence if the same model ID
|
# Since allow rules would take precedence if the same model ID
|
||||||
# sat in both the allow and deny list, we add the deny entries
|
# sat in both the allow and deny list, we add the deny entries
|
||||||
# first and let any conflicting allow entries overwrite.
|
# first and let any conflicting allow entries overwrite.
|
||||||
for model_id in rules.get("deny", []):
|
for model_id in rules.get("deny", []):
|
||||||
guild_rules[model_id] = False
|
guild_rules[str(model_id)] = False
|
||||||
for model_id in rules.get("allow", []):
|
for model_id in rules.get("allow", []):
|
||||||
guild_rules[model_id] = True
|
guild_rules[str(model_id)] = True
|
||||||
if "default" in rules:
|
if "default" in rules:
|
||||||
default = rules["default"]
|
default = rules["default"]
|
||||||
if default == "allow":
|
if default == "allow":
|
||||||
@@ -689,7 +693,9 @@ class Permissions(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
for guild_id, guild_dict in _int_key_map(rule_dict.items()):
|
for guild_id, guild_dict in _int_key_map(rule_dict.items()):
|
||||||
for model_id, rule in _int_key_map(guild_dict.items()):
|
for model_id, rule in _int_key_map(guild_dict.items()):
|
||||||
if rule is True:
|
if model_id == "default":
|
||||||
|
cog_or_command.set_default_rule(rule, guild_id=guild_id)
|
||||||
|
elif rule is True:
|
||||||
cog_or_command.allow_for(model_id, guild_id=guild_id)
|
cog_or_command.allow_for(model_id, guild_id=guild_id)
|
||||||
elif rule is False:
|
elif rule is False:
|
||||||
cog_or_command.deny_to(model_id, guild_id=guild_id)
|
cog_or_command.deny_to(model_id, guild_id=guild_id)
|
||||||
@@ -724,9 +730,16 @@ class Permissions(commands.Cog):
|
|||||||
rules.
|
rules.
|
||||||
"""
|
"""
|
||||||
for guild_id, guild_dict in _int_key_map(rule_dict.items()):
|
for guild_id, guild_dict in _int_key_map(rule_dict.items()):
|
||||||
for model_id in map(int, guild_dict.keys()):
|
for model_id in guild_dict.keys():
|
||||||
cog_or_command.clear_rule_for(model_id, guild_id)
|
if model_id == "default":
|
||||||
|
cog_or_command.set_default_rule(None, guild_id=guild_id)
|
||||||
|
else:
|
||||||
|
cog_or_command.clear_rule_for(int(model_id), guild_id=guild_id)
|
||||||
|
|
||||||
|
|
||||||
def _int_key_map(items_view: ItemsView[str, Any]) -> Iterator[Tuple[int, Any]]:
|
def _int_key_map(items_view: ItemsView[str, Any]) -> Iterator[Tuple[Union[str, int], Any]]:
|
||||||
return map(lambda tup: (int(tup[0]), tup[1]), items_view)
|
for k, v in items_view:
|
||||||
|
if k == "default":
|
||||||
|
yield k, v
|
||||||
|
else:
|
||||||
|
yield int(k), v
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ class Reports(commands.Cog):
|
|||||||
self.tunnel_store[k]["msgs"] = msgs
|
self.tunnel_store[k]["msgs"] = msgs
|
||||||
|
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.mod_or_permissions(manage_members=True)
|
@checks.mod_or_permissions(manage_roles=True)
|
||||||
@report.command(name="interact")
|
@report.command(name="interact")
|
||||||
async def response(self, ctx, ticket_number: int):
|
async def response(self, ctx, ticket_number: int):
|
||||||
"""Open a message tunnel.
|
"""Open a message tunnel.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from . import streamtypes as _streamtypes
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
from typing import Optional, List
|
from typing import Optional, List, Tuple
|
||||||
|
|
||||||
CHECK_DELAY = 60
|
CHECK_DELAY = 60
|
||||||
|
|
||||||
@@ -320,6 +320,7 @@ class Streams(commands.Cog):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.mod()
|
@checks.mod()
|
||||||
async def streamset(self, ctx: commands.Context):
|
async def streamset(self, ctx: commands.Context):
|
||||||
|
"""Set tokens for accessing streams."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@streamset.command()
|
@streamset.command()
|
||||||
@@ -396,9 +397,6 @@ class Streams(commands.Cog):
|
|||||||
async def role(self, ctx: commands.Context, *, role: discord.Role):
|
async def role(self, ctx: commands.Context, *, role: discord.Role):
|
||||||
"""Toggle a role mention."""
|
"""Toggle a role mention."""
|
||||||
current_setting = await self.db.role(role).mention()
|
current_setting = await self.db.role(role).mention()
|
||||||
if not role.mentionable:
|
|
||||||
await ctx.send("That role is not mentionable!")
|
|
||||||
return
|
|
||||||
if current_setting:
|
if current_setting:
|
||||||
await self.db.role(role).mention.set(False)
|
await self.db.role(role).mention.set(False)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
@@ -408,11 +406,17 @@ class Streams(commands.Cog):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self.db.role(role).mention.set(True)
|
await self.db.role(role).mention.set(True)
|
||||||
await ctx.send(
|
msg = _(
|
||||||
_(
|
|
||||||
"When a stream or community is live, `@\u200b{role.name}` will be mentioned."
|
"When a stream or community is live, `@\u200b{role.name}` will be mentioned."
|
||||||
).format(role=role)
|
).format(role=role)
|
||||||
|
if not role.mentionable:
|
||||||
|
msg += " " + _(
|
||||||
|
"Since the role is not mentionable, it will be momentarily made mentionable "
|
||||||
|
"when announcing a streamalert. Please make sure I have the correct "
|
||||||
|
"permissions to manage this role, or else members of this role won't receive "
|
||||||
|
"a notification."
|
||||||
)
|
)
|
||||||
|
await ctx.send(msg)
|
||||||
|
|
||||||
@streamset.command()
|
@streamset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@@ -535,30 +539,46 @@ class Streams(commands.Cog):
|
|||||||
continue
|
continue
|
||||||
for channel_id in stream.channels:
|
for channel_id in stream.channels:
|
||||||
channel = self.bot.get_channel(channel_id)
|
channel = self.bot.get_channel(channel_id)
|
||||||
mention_str = await self._get_mention_str(channel.guild)
|
mention_str, edited_roles = await self._get_mention_str(channel.guild)
|
||||||
|
|
||||||
if mention_str:
|
if mention_str:
|
||||||
content = _("{mention}, {stream.name} is live!").format(
|
content = _("{mention}, {stream.name} is live!").format(
|
||||||
mention=mention_str, stream=stream
|
mention=mention_str, stream=stream
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
content = _("{stream.name} is live!").format(stream=stream.name)
|
content = _("{stream.name} is live!").format(stream=stream)
|
||||||
|
|
||||||
m = await channel.send(content, embed=embed)
|
m = await channel.send(content, embed=embed)
|
||||||
stream._messages_cache.append(m)
|
stream._messages_cache.append(m)
|
||||||
|
if edited_roles:
|
||||||
|
for role in edited_roles:
|
||||||
|
await role.edit(mentionable=False)
|
||||||
await self.save_streams()
|
await self.save_streams()
|
||||||
|
|
||||||
async def _get_mention_str(self, guild: discord.Guild):
|
async def _get_mention_str(self, guild: discord.Guild) -> Tuple[str, List[discord.Role]]:
|
||||||
|
"""Returns a 2-tuple with the string containing the mentions, and a list of
|
||||||
|
all roles which need to have their `mentionable` property set back to False.
|
||||||
|
"""
|
||||||
settings = self.db.guild(guild)
|
settings = self.db.guild(guild)
|
||||||
mentions = []
|
mentions = []
|
||||||
|
edited_roles = []
|
||||||
if await settings.mention_everyone():
|
if await settings.mention_everyone():
|
||||||
mentions.append("@everyone")
|
mentions.append("@everyone")
|
||||||
if await settings.mention_here():
|
if await settings.mention_here():
|
||||||
mentions.append("@here")
|
mentions.append("@here")
|
||||||
|
can_manage_roles = guild.me.guild_permissions.manage_roles
|
||||||
for role in guild.roles:
|
for role in guild.roles:
|
||||||
if await self.db.role(role).mention():
|
if await self.db.role(role).mention():
|
||||||
|
if can_manage_roles and not role.mentionable:
|
||||||
|
try:
|
||||||
|
await role.edit(mentionable=True)
|
||||||
|
except discord.Forbidden:
|
||||||
|
# Might still be unable to edit role based on hierarchy
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
edited_roles.append(role)
|
||||||
mentions.append(role.mention)
|
mentions.append(role.mention)
|
||||||
return " ".join(mentions)
|
return " ".join(mentions), edited_roles
|
||||||
|
|
||||||
async def check_communities(self):
|
async def check_communities(self):
|
||||||
for community in self.communities:
|
for community in self.communities:
|
||||||
@@ -589,12 +609,15 @@ class Streams(commands.Cog):
|
|||||||
emb = await community.make_embed(streams)
|
emb = await community.make_embed(streams)
|
||||||
chn_msg = [m for m in community._messages_cache if m.channel == chn]
|
chn_msg = [m for m in community._messages_cache if m.channel == chn]
|
||||||
if not chn_msg:
|
if not chn_msg:
|
||||||
mentions = await self._get_mention_str(chn.guild)
|
mentions, roles = await self._get_mention_str(chn.guild)
|
||||||
if mentions:
|
if mentions:
|
||||||
msg = await chn.send(mentions, embed=emb)
|
msg = await chn.send(mentions, embed=emb)
|
||||||
else:
|
else:
|
||||||
msg = await chn.send(embed=emb)
|
msg = await chn.send(embed=emb)
|
||||||
community._messages_cache.append(msg)
|
community._messages_cache.append(msg)
|
||||||
|
if roles:
|
||||||
|
for role in roles:
|
||||||
|
await role.edit(mentionable=False)
|
||||||
await self.save_communities()
|
await self.save_communities()
|
||||||
else:
|
else:
|
||||||
chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0]
|
chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0]
|
||||||
@@ -626,7 +649,12 @@ class Streams(commands.Cog):
|
|||||||
raw_stream["_messages_cache"] = []
|
raw_stream["_messages_cache"] = []
|
||||||
for raw_msg in raw_msg_cache:
|
for raw_msg in raw_msg_cache:
|
||||||
chn = self.bot.get_channel(raw_msg["channel"])
|
chn = self.bot.get_channel(raw_msg["channel"])
|
||||||
|
if chn is not None:
|
||||||
|
try:
|
||||||
msg = await chn.get_message(raw_msg["message"])
|
msg = await chn.get_message(raw_msg["message"])
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
raw_stream["_messages_cache"].append(msg)
|
raw_stream["_messages_cache"].append(msg)
|
||||||
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
||||||
if token is not None:
|
if token is not None:
|
||||||
@@ -646,7 +674,12 @@ class Streams(commands.Cog):
|
|||||||
raw_community["_messages_cache"] = []
|
raw_community["_messages_cache"] = []
|
||||||
for raw_msg in raw_msg_cache:
|
for raw_msg in raw_msg_cache:
|
||||||
chn = self.bot.get_channel(raw_msg["channel"])
|
chn = self.bot.get_channel(raw_msg["channel"])
|
||||||
|
if chn is not None:
|
||||||
|
try:
|
||||||
msg = await chn.get_message(raw_msg["message"])
|
msg = await chn.get_message(raw_msg["message"])
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
raw_community["_messages_cache"].append(msg)
|
raw_community["_messages_cache"].append(msg)
|
||||||
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
||||||
communities.append(_class(token=token, **raw_community))
|
communities.append(_class(token=token, **raw_community))
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class TriviaSession:
|
|||||||
async with self.ctx.typing():
|
async with self.ctx.typing():
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
self.count += 1
|
self.count += 1
|
||||||
msg = bold(_("**Question number {num}!").format(num=self.count)) + "\n\n" + question
|
msg = bold(_("Question number {num}!").format(num=self.count)) + "\n\n" + question
|
||||||
await self.ctx.send(msg)
|
await self.ctx.send(msg)
|
||||||
continue_ = await self.wait_for_answer(answers, delay, timeout)
|
continue_ = await self.wait_for_answer(answers, delay, timeout)
|
||||||
if continue_ is False:
|
if continue_ is False:
|
||||||
@@ -322,9 +322,9 @@ def _parse_answers(answers):
|
|||||||
for answer in answers:
|
for answer in answers:
|
||||||
if isinstance(answer, bool):
|
if isinstance(answer, bool):
|
||||||
if answer is True:
|
if answer is True:
|
||||||
ret.extend(["True", "Yes", _("Yes")])
|
ret.extend(["True", "Yes", "On"])
|
||||||
else:
|
else:
|
||||||
ret.extend(["False", "No", _("No")])
|
ret.extend(["False", "No", "Off"])
|
||||||
else:
|
else:
|
||||||
ret.append(str(answer))
|
ret.append(str(answer))
|
||||||
# Uniquify list
|
# Uniquify list
|
||||||
|
|||||||
@@ -111,16 +111,14 @@ class Trivia(commands.Cog):
|
|||||||
await settings.allow_override.set(enabled)
|
await settings.allow_override.set(enabled)
|
||||||
if enabled:
|
if enabled:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_("Done. Trivia lists can now override the trivia settings for this server.")
|
||||||
"Done. Trivia lists can now override the trivia settings for this server."
|
|
||||||
).format(now=enabled)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"Done. Trivia lists can no longer override the trivia settings for this "
|
"Done. Trivia lists can no longer override the trivia settings for this "
|
||||||
"server."
|
"server."
|
||||||
).format(now=enabled)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@triviaset.command(name="botplays", usage="<true_or_false>")
|
@triviaset.command(name="botplays", usage="<true_or_false>")
|
||||||
@@ -506,7 +504,7 @@ class Trivia(commands.Cog):
|
|||||||
|
|
||||||
with path.open(encoding="utf-8") as file:
|
with path.open(encoding="utf-8") as file:
|
||||||
try:
|
try:
|
||||||
dict_ = yaml.load(file)
|
dict_ = yaml.safe_load(file)
|
||||||
except yaml.error.YAMLError as exc:
|
except yaml.error.YAMLError as exc:
|
||||||
raise InvalidListError("YAML parsing failed.") from exc
|
raise InvalidListError("YAML parsing failed.") from exc
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ async def warning_points_add_check(
|
|||||||
act = {}
|
act = {}
|
||||||
async with guild_settings.actions() as registered_actions:
|
async with guild_settings.actions() as registered_actions:
|
||||||
for a in registered_actions:
|
for a in registered_actions:
|
||||||
|
# Actions are sorted in decreasing order of points.
|
||||||
|
# The first action we find where the user is above the threshold will be the
|
||||||
|
# highest action we can take.
|
||||||
if points >= a["points"]:
|
if points >= a["points"]:
|
||||||
act = a
|
act = a
|
||||||
else:
|
|
||||||
break
|
break
|
||||||
if act and act["exceed_command"] is not None: # some action needs to be taken
|
if act and act["exceed_command"] is not None: # some action needs to be taken
|
||||||
await create_and_invoke_context(ctx, act["exceed_command"], user)
|
await create_and_invoke_context(ctx, act["exceed_command"], user)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from redbot.cogs.warnings.helpers import (
|
|||||||
get_command_for_dropping_points,
|
get_command_for_dropping_points,
|
||||||
warning_points_remove_check,
|
warning_points_remove_check,
|
||||||
)
|
)
|
||||||
from redbot.core import Config, modlog, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.mod import is_admin_or_superior
|
from redbot.core.utils.mod import is_admin_or_superior
|
||||||
@@ -34,15 +34,14 @@ class Warnings(commands.Cog):
|
|||||||
self.config.register_guild(**self.default_guild)
|
self.config.register_guild(**self.default_guild)
|
||||||
self.config.register_member(**self.default_member)
|
self.config.register_member(**self.default_member)
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.create_task(self.register_warningtype())
|
|
||||||
|
|
||||||
@staticmethod
|
# We're not utilising modlog yet - no need to register a casetype
|
||||||
async def register_warningtype():
|
# @staticmethod
|
||||||
try:
|
# async def register_warningtype():
|
||||||
await modlog.register_casetype("warning", True, "\N{WARNING SIGN}", "Warning", None)
|
# try:
|
||||||
except RuntimeError:
|
# await modlog.register_casetype("warning", True, "\N{WARNING SIGN}", "Warning", None)
|
||||||
pass
|
# except RuntimeError:
|
||||||
|
# pass
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
|||||||
@@ -1,40 +1,152 @@
|
|||||||
|
import re as _re
|
||||||
|
from math import inf as _inf
|
||||||
|
from typing import (
|
||||||
|
Any as _Any,
|
||||||
|
ClassVar as _ClassVar,
|
||||||
|
Dict as _Dict,
|
||||||
|
List as _List,
|
||||||
|
Optional as _Optional,
|
||||||
|
Pattern as _Pattern,
|
||||||
|
Tuple as _Tuple,
|
||||||
|
Union as _Union,
|
||||||
|
)
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
|
||||||
__all__ = ["Config", "__version__"]
|
__all__ = ["Config", "__version__", "version_info", "VersionInfo"]
|
||||||
|
|
||||||
|
|
||||||
class VersionInfo:
|
class VersionInfo:
|
||||||
def __init__(self, major, minor, micro, releaselevel, serial):
|
ALPHA = "alpha"
|
||||||
self._levels = ["alpha", "beta", "release candidate", "final"]
|
BETA = "beta"
|
||||||
self.major = major
|
RELEASE_CANDIDATE = "release candidate"
|
||||||
self.minor = minor
|
FINAL = "final"
|
||||||
self.micro = micro
|
|
||||||
|
|
||||||
if releaselevel not in self._levels:
|
_VERSION_STR_PATTERN: _ClassVar[_Pattern[str]] = _re.compile(
|
||||||
raise TypeError("'releaselevel' must be one of: {}".format(", ".join(self._levels)))
|
r"^"
|
||||||
|
r"(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<micro>0|[1-9]\d*)"
|
||||||
|
r"(?:(?P<releaselevel>a|b|rc)(?P<serial>0|[1-9]\d*))?"
|
||||||
|
r"(?:\.post(?P<post_release>0|[1-9]\d*))?"
|
||||||
|
r"(?:\.dev(?P<dev_release>0|[1-9]\d*))?"
|
||||||
|
r"$",
|
||||||
|
flags=_re.IGNORECASE,
|
||||||
|
)
|
||||||
|
_RELEASE_LEVELS: _ClassVar[_List[str]] = [ALPHA, BETA, RELEASE_CANDIDATE, FINAL]
|
||||||
|
_SHORT_RELEASE_LEVELS: _ClassVar[_Dict[str, str]] = {
|
||||||
|
"a": ALPHA,
|
||||||
|
"b": BETA,
|
||||||
|
"rc": RELEASE_CANDIDATE,
|
||||||
|
}
|
||||||
|
|
||||||
self.releaselevel = releaselevel
|
def __init__(
|
||||||
self.serial = serial
|
self,
|
||||||
|
major: int,
|
||||||
|
minor: int,
|
||||||
|
micro: int,
|
||||||
|
releaselevel: str,
|
||||||
|
serial: _Optional[int] = None,
|
||||||
|
post_release: _Optional[int] = None,
|
||||||
|
dev_release: _Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
|
self.major: int = major
|
||||||
|
self.minor: int = minor
|
||||||
|
self.micro: int = micro
|
||||||
|
|
||||||
def __lt__(self, other):
|
if releaselevel not in self._RELEASE_LEVELS:
|
||||||
my_index = self._levels.index(self.releaselevel)
|
raise TypeError(f"'releaselevel' must be one of: {', '.join(self._RELEASE_LEVELS)}")
|
||||||
other_index = self._levels.index(other.releaselevel)
|
|
||||||
return (self.major, self.minor, self.micro, my_index, self.serial) < (
|
self.releaselevel: str = releaselevel
|
||||||
other.major,
|
self.serial: _Optional[int] = serial
|
||||||
other.minor,
|
self.post_release: _Optional[int] = post_release
|
||||||
other.micro,
|
self.dev_release: _Optional[int] = dev_release
|
||||||
other_index,
|
|
||||||
other.serial,
|
@classmethod
|
||||||
|
def from_str(cls, version_str: str) -> "VersionInfo":
|
||||||
|
"""Parse a string into a VersionInfo object.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If the version info string is invalid.
|
||||||
|
|
||||||
|
"""
|
||||||
|
match = cls._VERSION_STR_PATTERN.match(version_str)
|
||||||
|
if not match:
|
||||||
|
raise ValueError(f"Invalid version string: {version_str}")
|
||||||
|
|
||||||
|
kwargs: _Dict[str, _Union[str, int]] = {}
|
||||||
|
for key in ("major", "minor", "micro"):
|
||||||
|
kwargs[key] = int(match[key])
|
||||||
|
releaselevel = match["releaselevel"]
|
||||||
|
if releaselevel is not None:
|
||||||
|
kwargs["releaselevel"] = cls._SHORT_RELEASE_LEVELS[releaselevel]
|
||||||
|
else:
|
||||||
|
kwargs["releaselevel"] = cls.FINAL
|
||||||
|
for key in ("serial", "post_release", "dev_release"):
|
||||||
|
if match[key] is not None:
|
||||||
|
kwargs[key] = int(match[key])
|
||||||
|
return cls(**kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(
|
||||||
|
cls, data: _Union[_Dict[str, _Union[int, str]], _List[_Union[int, str]]]
|
||||||
|
) -> "VersionInfo":
|
||||||
|
if isinstance(data, _List):
|
||||||
|
# For old versions, data was stored as a list:
|
||||||
|
# [MAJOR, MINOR, MICRO, RELEASELEVEL, SERIAL]
|
||||||
|
return cls(*data)
|
||||||
|
else:
|
||||||
|
return cls(**data)
|
||||||
|
|
||||||
|
def to_json(self) -> _Dict[str, _Union[int, str]]:
|
||||||
|
return {
|
||||||
|
"major": self.major,
|
||||||
|
"minor": self.minor,
|
||||||
|
"micro": self.micro,
|
||||||
|
"releaselevel": self.releaselevel,
|
||||||
|
"serial": self.serial,
|
||||||
|
"post_release": self.post_release,
|
||||||
|
"dev_release": self.dev_release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __lt__(self, other: _Any) -> bool:
|
||||||
|
if not isinstance(other, VersionInfo):
|
||||||
|
return NotImplemented
|
||||||
|
tups: _List[_Tuple[int, int, int, int, int, int, int]] = []
|
||||||
|
for obj in (self, other):
|
||||||
|
tups.append(
|
||||||
|
(
|
||||||
|
obj.major,
|
||||||
|
obj.minor,
|
||||||
|
obj.micro,
|
||||||
|
obj._RELEASE_LEVELS.index(obj.releaselevel),
|
||||||
|
obj.serial if obj.serial is not None else _inf,
|
||||||
|
obj.post_release if obj.post_release is not None else -_inf,
|
||||||
|
obj.dev_release if obj.dev_release is not None else _inf,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return tups[0] < tups[1]
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
ret = f"{self.major}.{self.minor}.{self.micro}"
|
||||||
|
if self.releaselevel != self.FINAL:
|
||||||
|
short = next(
|
||||||
|
k for k, v in self._SHORT_RELEASE_LEVELS.items() if v == self.releaselevel
|
||||||
|
)
|
||||||
|
ret += f"{short}{self.serial}"
|
||||||
|
if self.post_release is not None:
|
||||||
|
ret += f".post{self.post_release}"
|
||||||
|
if self.dev_release is not None:
|
||||||
|
ret += f".dev{self.dev_release}"
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
"VersionInfo(major={major}, minor={minor}, micro={micro}, "
|
||||||
|
"releaselevel={releaselevel}, serial={serial}, post={post_release}, "
|
||||||
|
"dev={dev_release})".format(**self.to_json())
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "VersionInfo(major={}, minor={}, micro={}, releaselevel={}, serial={})".format(
|
|
||||||
self.major, self.minor, self.micro, self.releaselevel, self.serial
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_json(self):
|
__version__ = "3.0.0rc3"
|
||||||
return [self.major, self.minor, self.micro, self.releaselevel, self.serial]
|
version_info = VersionInfo.from_str(__version__)
|
||||||
|
|
||||||
|
|
||||||
__version__ = "3.0.0rc1"
|
|
||||||
version_info = VersionInfo(3, 0, 0, "release candidate", 1)
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ from typing import Union, List, Optional
|
|||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.core import Config
|
from . import Config, errors
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"MAX_BALANCE",
|
||||||
"Account",
|
"Account",
|
||||||
"get_balance",
|
"get_balance",
|
||||||
"set_balance",
|
"set_balance",
|
||||||
@@ -26,6 +27,8 @@ __all__ = [
|
|||||||
"set_default_balance",
|
"set_default_balance",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MAX_BALANCE = 2 ** 63 - 1
|
||||||
|
|
||||||
_DEFAULT_GLOBAL = {
|
_DEFAULT_GLOBAL = {
|
||||||
"is_global": False,
|
"is_global": False,
|
||||||
"bank_name": "Twentysix bank",
|
"bank_name": "Twentysix bank",
|
||||||
@@ -170,10 +173,22 @@ async def set_balance(member: discord.Member, amount: int) -> int:
|
|||||||
------
|
------
|
||||||
ValueError
|
ValueError
|
||||||
If attempting to set the balance to a negative number.
|
If attempting to set the balance to a negative number.
|
||||||
|
BalanceTooHigh
|
||||||
|
If attempting to set the balance to a value greater than
|
||||||
|
``bank.MAX_BALANCE``
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if amount < 0:
|
if amount < 0:
|
||||||
raise ValueError("Not allowed to have negative balance.")
|
raise ValueError("Not allowed to have negative balance.")
|
||||||
|
if amount > MAX_BALANCE:
|
||||||
|
currency = (
|
||||||
|
await get_currency_name()
|
||||||
|
if await is_global()
|
||||||
|
else await get_currency_name(member.guild)
|
||||||
|
)
|
||||||
|
raise errors.BalanceTooHigh(
|
||||||
|
user=member.display_name, max_balance=MAX_BALANCE, currency_name=currency
|
||||||
|
)
|
||||||
if await is_global():
|
if await is_global():
|
||||||
group = _conf.user(member)
|
group = _conf.user(member)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
@@ -11,10 +12,10 @@ import discord
|
|||||||
import sys
|
import sys
|
||||||
from discord.ext.commands import when_mentioned_or
|
from discord.ext.commands import when_mentioned_or
|
||||||
|
|
||||||
|
from . import Config, i18n, commands, errors
|
||||||
from .cog_manager import CogManager
|
from .cog_manager import CogManager
|
||||||
from . import Config, i18n, commands
|
|
||||||
from .rpc import RPCMixin
|
|
||||||
from .help_formatter import Help, help as help_
|
from .help_formatter import Help, help as help_
|
||||||
|
from .rpc import RPCMixin
|
||||||
from .sentry import SentryManager
|
from .sentry import SentryManager
|
||||||
from .utils import common_filters
|
from .utils import common_filters
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
|
|
||||||
self.main_dir = bot_dir
|
self.main_dir = bot_dir
|
||||||
|
|
||||||
self.cog_mgr = CogManager(paths=(str(self.main_dir / "cogs"),))
|
self.cog_mgr = CogManager()
|
||||||
|
|
||||||
super().__init__(*args, formatter=Help(), **kwargs)
|
super().__init__(*args, formatter=Help(), **kwargs)
|
||||||
|
|
||||||
@@ -185,13 +186,23 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
async def is_admin(self, member: discord.Member):
|
async def is_admin(self, member: discord.Member):
|
||||||
"""Checks if a member is an admin of their guild."""
|
"""Checks if a member is an admin of their guild."""
|
||||||
admin_role = await self.db.guild(member.guild).admin_role()
|
admin_role = await self.db.guild(member.guild).admin_role()
|
||||||
return any(role.id == admin_role for role in member.roles)
|
try:
|
||||||
|
if any(role.id == admin_role for role in member.roles):
|
||||||
|
return True
|
||||||
|
except AttributeError: # someone passed a webhook to this
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def is_mod(self, member: discord.Member):
|
async def is_mod(self, member: discord.Member):
|
||||||
"""Checks if a member is a mod or admin of their guild."""
|
"""Checks if a member is a mod or admin of their guild."""
|
||||||
mod_role = await self.db.guild(member.guild).mod_role()
|
mod_role = await self.db.guild(member.guild).mod_role()
|
||||||
admin_role = await self.db.guild(member.guild).admin_role()
|
admin_role = await self.db.guild(member.guild).admin_role()
|
||||||
return any(role.id in (mod_role, admin_role) for role in member.roles)
|
try:
|
||||||
|
if any(role.id in (mod_role, admin_role) for role in member.roles):
|
||||||
|
return True
|
||||||
|
except AttributeError: # someone passed a webhook to this
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def get_context(self, message, *, cls=commands.Context):
|
async def get_context(self, message, *, cls=commands.Context):
|
||||||
return await super().get_context(message, cls=cls)
|
return await super().get_context(message, cls=cls)
|
||||||
@@ -217,7 +228,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
async def load_extension(self, spec: ModuleSpec):
|
async def load_extension(self, spec: ModuleSpec):
|
||||||
name = spec.name.split(".")[-1]
|
name = spec.name.split(".")[-1]
|
||||||
if name in self.extensions:
|
if name in self.extensions:
|
||||||
raise discord.ClientException(f"there is already a package named {name} loaded")
|
raise errors.PackageAlreadyLoaded(spec)
|
||||||
|
|
||||||
lib = spec.loader.load_module()
|
lib = spec.loader.load_module()
|
||||||
if not hasattr(lib, "setup"):
|
if not hasattr(lib, "setup"):
|
||||||
@@ -236,16 +247,9 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
if cog is None:
|
if cog is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
for when in ("before", "after"):
|
for cls in inspect.getmro(cog.__class__):
|
||||||
try:
|
try:
|
||||||
hook = getattr(cog, f"_{cog.__class__.__name__}__red_permissions_{when}")
|
hook = getattr(cog, f"_{cls.__name__}__permissions_hook")
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.remove_permissions_hook(hook, when)
|
|
||||||
|
|
||||||
try:
|
|
||||||
hook = getattr(cog, f"_{cog.__class__.__name__}__red_permissions_before")
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -340,7 +344,13 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
ids_to_check = [to_check.id]
|
ids_to_check = [to_check.id]
|
||||||
else:
|
else:
|
||||||
author = getattr(to_check, "author", to_check)
|
author = getattr(to_check, "author", to_check)
|
||||||
|
try:
|
||||||
ids_to_check = [r.id for r in author.roles]
|
ids_to_check = [r.id for r in author.roles]
|
||||||
|
except AttributeError:
|
||||||
|
# webhook messages are a user not member,
|
||||||
|
# cheaper than isinstance
|
||||||
|
return True # webhooks require significant permissions to enable.
|
||||||
|
else:
|
||||||
ids_to_check.append(author.id)
|
ids_to_check.append(author.id)
|
||||||
|
|
||||||
immune_ids = await self.db.guild(guild).autoimmune_ids()
|
immune_ids = await self.db.guild(guild).autoimmune_ids()
|
||||||
@@ -390,10 +400,17 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
|||||||
)
|
)
|
||||||
if not hasattr(cog, "requires"):
|
if not hasattr(cog, "requires"):
|
||||||
commands.Cog.__init__(cog)
|
commands.Cog.__init__(cog)
|
||||||
|
|
||||||
|
for cls in inspect.getmro(cog.__class__):
|
||||||
|
try:
|
||||||
|
hook = getattr(cog, f"_{cls.__name__}__permissions_hook")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.add_permissions_hook(hook)
|
||||||
|
|
||||||
for attr in dir(cog):
|
for attr in dir(cog):
|
||||||
_attr = getattr(cog, attr)
|
_attr = getattr(cog, attr)
|
||||||
if attr == f"_{cog.__class__.__name__}__permissions_hook":
|
|
||||||
self.add_permissions_hook(_attr)
|
|
||||||
if isinstance(_attr, discord.ext.commands.Command) and not isinstance(
|
if isinstance(_attr, discord.ext.commands.Command) and not isinstance(
|
||||||
_attr, commands.Command
|
_attr, commands.Command
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import pkgutil
|
|||||||
from importlib import import_module, invalidate_caches
|
from importlib import import_module, invalidate_caches
|
||||||
from importlib.machinery import ModuleSpec
|
from importlib.machinery import ModuleSpec
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Tuple, Union, List, Optional
|
from typing import Union, List, Optional
|
||||||
|
|
||||||
import redbot.cogs
|
import redbot.cogs
|
||||||
from redbot.core.utils import deduplicate_iterables
|
from redbot.core.utils import deduplicate_iterables
|
||||||
@@ -25,8 +25,6 @@ class NoSuchCog(ImportError):
|
|||||||
Different from ImportError because some ImportErrors can happen inside cogs.
|
Different from ImportError because some ImportErrors can happen inside cogs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CogManager:
|
class CogManager:
|
||||||
"""Directory manager for Red's cogs.
|
"""Directory manager for Red's cogs.
|
||||||
@@ -39,30 +37,27 @@ class CogManager:
|
|||||||
|
|
||||||
CORE_PATH = Path(redbot.cogs.__path__[0])
|
CORE_PATH = Path(redbot.cogs.__path__[0])
|
||||||
|
|
||||||
def __init__(self, paths: Tuple[str] = ()):
|
def __init__(self):
|
||||||
self.conf = Config.get_conf(self, 2938473984732, True)
|
self.conf = Config.get_conf(self, 2938473984732, True)
|
||||||
tmp_cog_install_path = cog_data_path(self) / "cogs"
|
tmp_cog_install_path = cog_data_path(self) / "cogs"
|
||||||
tmp_cog_install_path.mkdir(parents=True, exist_ok=True)
|
tmp_cog_install_path.mkdir(parents=True, exist_ok=True)
|
||||||
self.conf.register_global(paths=[], install_path=str(tmp_cog_install_path))
|
self.conf.register_global(paths=[], install_path=str(tmp_cog_install_path))
|
||||||
self._paths = [Path(p) for p in paths]
|
|
||||||
|
|
||||||
async def paths(self) -> Tuple[Path, ...]:
|
async def paths(self) -> List[Path]:
|
||||||
"""Get all currently valid path directories.
|
"""Get all currently valid path directories, in order of priority
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
`tuple` of `pathlib.Path`
|
List[pathlib.Path]
|
||||||
All valid cog paths.
|
A list of paths where cog packages can be found. The
|
||||||
|
install path is highest priority, followed by the
|
||||||
|
user-defined paths, and the core path has the lowest
|
||||||
|
priority.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
conf_paths = [Path(p) for p in await self.conf.paths()]
|
return deduplicate_iterables(
|
||||||
other_paths = self._paths
|
[await self.install_path()], await self.user_defined_paths(), [self.CORE_PATH]
|
||||||
|
)
|
||||||
all_paths = deduplicate_iterables(conf_paths, other_paths, [self.CORE_PATH])
|
|
||||||
|
|
||||||
if self.install_path not in all_paths:
|
|
||||||
all_paths.insert(0, await self.install_path())
|
|
||||||
return tuple(p.resolve() for p in all_paths if p.is_dir())
|
|
||||||
|
|
||||||
async def install_path(self) -> Path:
|
async def install_path(self) -> Path:
|
||||||
"""Get the install path for 3rd party cogs.
|
"""Get the install path for 3rd party cogs.
|
||||||
@@ -73,8 +68,20 @@ class CogManager:
|
|||||||
The path to the directory where 3rd party cogs are stored.
|
The path to the directory where 3rd party cogs are stored.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
p = Path(await self.conf.install_path())
|
return Path(await self.conf.install_path()).resolve()
|
||||||
return p.resolve()
|
|
||||||
|
async def user_defined_paths(self) -> List[Path]:
|
||||||
|
"""Get a list of user-defined cog paths.
|
||||||
|
|
||||||
|
All paths will be absolute and unique, in order of priority.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[pathlib.Path]
|
||||||
|
A list of user-defined paths.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return list(map(Path, deduplicate_iterables(await self.conf.paths())))
|
||||||
|
|
||||||
async def set_install_path(self, path: Path) -> Path:
|
async def set_install_path(self, path: Path) -> Path:
|
||||||
"""Set the install path for 3rd party cogs.
|
"""Set the install path for 3rd party cogs.
|
||||||
@@ -125,11 +132,10 @@ class CogManager:
|
|||||||
path = Path(path)
|
path = Path(path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
async def add_path(self, path: Union[Path, str]):
|
async def add_path(self, path: Union[Path, str]) -> None:
|
||||||
"""Add a cog path to current list.
|
"""Add a cog path to current list.
|
||||||
|
|
||||||
This will ignore duplicates. Does have a side effect of removing all
|
This will ignore duplicates.
|
||||||
invalid paths from the saved path list.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -156,11 +162,12 @@ class CogManager:
|
|||||||
if path == self.CORE_PATH:
|
if path == self.CORE_PATH:
|
||||||
raise ValueError("Cannot add the core path as an additional path.")
|
raise ValueError("Cannot add the core path as an additional path.")
|
||||||
|
|
||||||
async with self.conf.paths() as paths:
|
current_paths = await self.user_defined_paths()
|
||||||
if not any(Path(p) == path for p in paths):
|
if path not in current_paths:
|
||||||
paths.append(str(path))
|
current_paths.append(path)
|
||||||
|
await self.set_paths(current_paths)
|
||||||
|
|
||||||
async def remove_path(self, path: Union[Path, str]) -> Tuple[Path, ...]:
|
async def remove_path(self, path: Union[Path, str]) -> None:
|
||||||
"""Remove a path from the current paths list.
|
"""Remove a path from the current paths list.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -168,21 +175,13 @@ class CogManager:
|
|||||||
path : `pathlib.Path` or `str`
|
path : `pathlib.Path` or `str`
|
||||||
Path to remove.
|
Path to remove.
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
`tuple` of `pathlib.Path`
|
|
||||||
Tuple of new valid paths.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
path = self._ensure_path_obj(path).resolve()
|
path = self._ensure_path_obj(path).resolve()
|
||||||
|
paths = await self.user_defined_paths()
|
||||||
|
|
||||||
paths = [Path(p) for p in await self.conf.paths()]
|
|
||||||
if path in paths:
|
|
||||||
paths.remove(path)
|
paths.remove(path)
|
||||||
await self.set_paths(paths)
|
await self.set_paths(paths)
|
||||||
|
|
||||||
return tuple(paths)
|
|
||||||
|
|
||||||
async def set_paths(self, paths_: List[Path]):
|
async def set_paths(self, paths_: List[Path]):
|
||||||
"""Set the current paths list.
|
"""Set the current paths list.
|
||||||
|
|
||||||
@@ -192,7 +191,7 @@ class CogManager:
|
|||||||
List of paths to set.
|
List of paths to set.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
str_paths = [str(p) for p in paths_]
|
str_paths = list(map(str, paths_))
|
||||||
await self.conf.paths.set(str_paths)
|
await self.conf.paths.set(str_paths)
|
||||||
|
|
||||||
async def _find_ext_cog(self, name: str) -> ModuleSpec:
|
async def _find_ext_cog(self, name: str) -> ModuleSpec:
|
||||||
@@ -213,9 +212,9 @@ class CogManager:
|
|||||||
------
|
------
|
||||||
NoSuchCog
|
NoSuchCog
|
||||||
When no cog with the requested name was found.
|
When no cog with the requested name was found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
resolved_paths = await self.paths()
|
real_paths = list(map(str, [await self.install_path()] + await self.user_defined_paths()))
|
||||||
real_paths = [str(p) for p in resolved_paths if p != self.CORE_PATH]
|
|
||||||
|
|
||||||
for finder, module_name, _ in pkgutil.iter_modules(real_paths):
|
for finder, module_name, _ in pkgutil.iter_modules(real_paths):
|
||||||
if name == module_name:
|
if name == module_name:
|
||||||
@@ -287,10 +286,8 @@ class CogManager:
|
|||||||
return await self._find_core_cog(name)
|
return await self._find_core_cog(name)
|
||||||
|
|
||||||
async def available_modules(self) -> List[str]:
|
async def available_modules(self) -> List[str]:
|
||||||
"""Finds the names of all available modules to load.
|
"""Finds the names of all available modules to load."""
|
||||||
"""
|
paths = list(map(str, await self.paths()))
|
||||||
paths = (await self.install_path(),) + await self.paths()
|
|
||||||
paths = [str(p) for p in paths]
|
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
for finder, module_name, _ in pkgutil.iter_modules(paths):
|
for finder, module_name, _ in pkgutil.iter_modules(paths):
|
||||||
@@ -314,13 +311,6 @@ _ = Translator("CogManagerUI", __file__)
|
|||||||
class CogManagerUI(commands.Cog):
|
class CogManagerUI(commands.Cog):
|
||||||
"""Commands to interface with Red's cog manager."""
|
"""Commands to interface with Red's cog manager."""
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def visible_paths(ctx):
|
|
||||||
install_path = await ctx.bot.cog_mgr.install_path()
|
|
||||||
cog_paths = await ctx.bot.cog_mgr.paths()
|
|
||||||
cog_paths = [p for p in cog_paths if p != install_path]
|
|
||||||
return cog_paths
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def paths(self, ctx: commands.Context):
|
async def paths(self, ctx: commands.Context):
|
||||||
@@ -330,8 +320,7 @@ class CogManagerUI(commands.Cog):
|
|||||||
cog_mgr = ctx.bot.cog_mgr
|
cog_mgr = ctx.bot.cog_mgr
|
||||||
install_path = await cog_mgr.install_path()
|
install_path = await cog_mgr.install_path()
|
||||||
core_path = cog_mgr.CORE_PATH
|
core_path = cog_mgr.CORE_PATH
|
||||||
cog_paths = await cog_mgr.paths()
|
cog_paths = await cog_mgr.user_defined_paths()
|
||||||
cog_paths = [p for p in cog_paths if p not in (install_path, core_path)]
|
|
||||||
|
|
||||||
msg = _("Install Path: {install_path}\nCore Path: {core_path}\n\n").format(
|
msg = _("Install Path: {install_path}\nCore Path: {core_path}\n\n").format(
|
||||||
install_path=install_path, core_path=core_path
|
install_path=install_path, core_path=core_path
|
||||||
@@ -369,7 +358,11 @@ class CogManagerUI(commands.Cog):
|
|||||||
from !paths
|
from !paths
|
||||||
"""
|
"""
|
||||||
path_number -= 1
|
path_number -= 1
|
||||||
cog_paths = await self.visible_paths(ctx)
|
if path_number < 0:
|
||||||
|
await ctx.send(_("Path numbers must be positive."))
|
||||||
|
return
|
||||||
|
|
||||||
|
cog_paths = await ctx.bot.cog_mgr.user_defined_paths()
|
||||||
try:
|
try:
|
||||||
to_remove = cog_paths.pop(path_number)
|
to_remove = cog_paths.pop(path_number)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
@@ -388,8 +381,11 @@ class CogManagerUI(commands.Cog):
|
|||||||
# Doing this because in the paths command they're 1 indexed
|
# Doing this because in the paths command they're 1 indexed
|
||||||
from_ -= 1
|
from_ -= 1
|
||||||
to -= 1
|
to -= 1
|
||||||
|
if from_ < 0 or to < 0:
|
||||||
|
await ctx.send(_("Path numbers must be positive."))
|
||||||
|
return
|
||||||
|
|
||||||
all_paths = await self.visible_paths(ctx)
|
all_paths = await ctx.bot.cog_mgr.user_defined_paths()
|
||||||
try:
|
try:
|
||||||
to_move = all_paths.pop(from_)
|
to_move = all_paths.pop(from_)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class Command(CogCommandMixin, commands.Command):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def parents(self) -> List["Group"]:
|
def parents(self) -> List["Group"]:
|
||||||
"""List[Group] : Returns all parent commands of this command.
|
"""List[commands.Group] : Returns all parent commands of this command.
|
||||||
|
|
||||||
This is sorted by the length of :attr:`.qualified_name` from highest to lowest.
|
This is sorted by the length of :attr:`.qualified_name` from highest to lowest.
|
||||||
If the command has no parents, this will be an empty list.
|
If the command has no parents, this will be an empty list.
|
||||||
@@ -157,12 +157,31 @@ class Command(CogCommandMixin, commands.Command):
|
|||||||
cmd = cmd.parent
|
cmd = cmd.parent
|
||||||
return sorted(entries, key=lambda x: len(x.qualified_name), reverse=True)
|
return sorted(entries, key=lambda x: len(x.qualified_name), reverse=True)
|
||||||
|
|
||||||
async def can_run(self, ctx: "Context") -> bool:
|
# noinspection PyMethodOverriding
|
||||||
|
async def can_run(
|
||||||
|
self,
|
||||||
|
ctx: "Context",
|
||||||
|
*,
|
||||||
|
check_all_parents: bool = False,
|
||||||
|
change_permission_state: bool = False,
|
||||||
|
) -> bool:
|
||||||
"""Check if this command can be run in the given context.
|
"""Check if this command can be run in the given context.
|
||||||
|
|
||||||
This function first checks if the command can be run using
|
This function first checks if the command can be run using
|
||||||
discord.py's method `discord.ext.commands.Command.can_run`,
|
discord.py's method `discord.ext.commands.Command.can_run`,
|
||||||
then will return the result of `Requires.verify`.
|
then will return the result of `Requires.verify`.
|
||||||
|
|
||||||
|
Keyword Arguments
|
||||||
|
-----------------
|
||||||
|
check_all_parents : bool
|
||||||
|
If ``True``, this will check permissions for all of this
|
||||||
|
command's parents and its cog as well as the command
|
||||||
|
itself. Defaults to ``False``.
|
||||||
|
change_permission_state : bool
|
||||||
|
Whether or not the permission state should be changed as
|
||||||
|
a result of this call. For most cases this should be
|
||||||
|
``False``. Defaults to ``False``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ret = await super().can_run(ctx)
|
ret = await super().can_run(ctx)
|
||||||
if ret is False:
|
if ret is False:
|
||||||
@@ -171,8 +190,21 @@ class Command(CogCommandMixin, commands.Command):
|
|||||||
# This is so contexts invoking other commands can be checked with
|
# This is so contexts invoking other commands can be checked with
|
||||||
# this command as well
|
# this command as well
|
||||||
original_command = ctx.command
|
original_command = ctx.command
|
||||||
|
original_state = ctx.permission_state
|
||||||
ctx.command = self
|
ctx.command = self
|
||||||
|
|
||||||
|
if check_all_parents is True:
|
||||||
|
# Since we're starting from the beginning, we should reset the state to normal
|
||||||
|
ctx.permission_state = PermState.NORMAL
|
||||||
|
for parent in reversed(self.parents):
|
||||||
|
try:
|
||||||
|
result = await parent.can_run(ctx, change_permission_state=True)
|
||||||
|
except commands.CommandError:
|
||||||
|
result = False
|
||||||
|
|
||||||
|
if result is False:
|
||||||
|
return False
|
||||||
|
|
||||||
if self.parent is None and self.instance is not None:
|
if self.parent is None and self.instance is not None:
|
||||||
# For top-level commands, we need to check the cog's requires too
|
# For top-level commands, we need to check the cog's requires too
|
||||||
ret = await self.instance.requires.verify(ctx)
|
ret = await self.instance.requires.verify(ctx)
|
||||||
@@ -183,6 +215,17 @@ class Command(CogCommandMixin, commands.Command):
|
|||||||
return await self.requires.verify(ctx)
|
return await self.requires.verify(ctx)
|
||||||
finally:
|
finally:
|
||||||
ctx.command = original_command
|
ctx.command = original_command
|
||||||
|
if not change_permission_state:
|
||||||
|
ctx.permission_state = original_state
|
||||||
|
|
||||||
|
async def _verify_checks(self, ctx):
|
||||||
|
if not self.enabled:
|
||||||
|
raise commands.DisabledCommand(f"{self.name} command is disabled")
|
||||||
|
|
||||||
|
if not (await self.can_run(ctx, change_permission_state=True)):
|
||||||
|
raise commands.CheckFailure(
|
||||||
|
f"The check functions for command {self.qualified_name} failed."
|
||||||
|
)
|
||||||
|
|
||||||
async def do_conversion(
|
async def do_conversion(
|
||||||
self, ctx: "Context", converter, argument: str, param: inspect.Parameter
|
self, ctx: "Context", converter, argument: str, param: inspect.Parameter
|
||||||
@@ -238,7 +281,9 @@ class Command(CogCommandMixin, commands.Command):
|
|||||||
if cmd.hidden:
|
if cmd.hidden:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
can_run = await self.can_run(ctx)
|
can_run = await self.can_run(
|
||||||
|
ctx, check_all_parents=True, change_permission_state=False
|
||||||
|
)
|
||||||
except commands.CheckFailure:
|
except commands.CheckFailure:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -281,12 +281,14 @@ class Requires:
|
|||||||
|
|
||||||
if isinstance(user_perms, dict):
|
if isinstance(user_perms, dict):
|
||||||
self.user_perms: Optional[discord.Permissions] = discord.Permissions.none()
|
self.user_perms: Optional[discord.Permissions] = discord.Permissions.none()
|
||||||
|
_validate_perms_dict(user_perms)
|
||||||
self.user_perms.update(**user_perms)
|
self.user_perms.update(**user_perms)
|
||||||
else:
|
else:
|
||||||
self.user_perms = user_perms
|
self.user_perms = user_perms
|
||||||
|
|
||||||
if isinstance(bot_perms, dict):
|
if isinstance(bot_perms, dict):
|
||||||
self.bot_perms: discord.Permissions = discord.Permissions.none()
|
self.bot_perms: discord.Permissions = discord.Permissions.none()
|
||||||
|
_validate_perms_dict(bot_perms)
|
||||||
self.bot_perms.update(**bot_perms)
|
self.bot_perms.update(**bot_perms)
|
||||||
else:
|
else:
|
||||||
self.bot_perms = bot_perms
|
self.bot_perms = bot_perms
|
||||||
@@ -311,6 +313,7 @@ class Requires:
|
|||||||
if user_perms is None:
|
if user_perms is None:
|
||||||
func.requires.user_perms = None
|
func.requires.user_perms = None
|
||||||
else:
|
else:
|
||||||
|
_validate_perms_dict(user_perms)
|
||||||
func.requires.user_perms.update(**user_perms)
|
func.requires.user_perms.update(**user_perms)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
@@ -417,9 +420,13 @@ class Requires:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
await self._verify_bot(ctx)
|
await self._verify_bot(ctx)
|
||||||
# Owner-only commands are non-overrideable
|
|
||||||
|
# Owner should never be locked out of commands for user permissions.
|
||||||
|
if await ctx.bot.is_owner(ctx.author):
|
||||||
|
return True
|
||||||
|
# Owner-only commands are non-overrideable, and we already checked for owner.
|
||||||
if self.privilege_level is PrivilegeLevel.BOT_OWNER:
|
if self.privilege_level is PrivilegeLevel.BOT_OWNER:
|
||||||
return await ctx.bot.is_owner(ctx.author)
|
return False
|
||||||
|
|
||||||
hook_result = await ctx.bot.verify_permissions_hooks(ctx)
|
hook_result = await ctx.bot.verify_permissions_hooks(ctx)
|
||||||
if hook_result is not None:
|
if hook_result is not None:
|
||||||
@@ -445,7 +452,20 @@ class Requires:
|
|||||||
should_invoke = await self._verify_user(ctx)
|
should_invoke = await self._verify_user(ctx)
|
||||||
elif isinstance(next_state, dict):
|
elif isinstance(next_state, dict):
|
||||||
# NORMAL to PASSIVE_ALLOW; should we proceed as normal or transition?
|
# NORMAL to PASSIVE_ALLOW; should we proceed as normal or transition?
|
||||||
next_state = next_state[await self._verify_user(ctx)]
|
# We must check what would happen normally, if no explicit rules were set.
|
||||||
|
default_rule = PermState.NORMAL
|
||||||
|
if ctx.guild is not None:
|
||||||
|
default_rule = self.get_default_guild_rule(guild_id=ctx.guild.id)
|
||||||
|
if default_rule is PermState.NORMAL:
|
||||||
|
default_rule = self.default_global_rule
|
||||||
|
|
||||||
|
if default_rule == PermState.ACTIVE_DENY:
|
||||||
|
would_invoke = False
|
||||||
|
elif default_rule == PermState.ACTIVE_ALLOW:
|
||||||
|
would_invoke = True
|
||||||
|
else:
|
||||||
|
would_invoke = await self._verify_user(ctx)
|
||||||
|
next_state = next_state[would_invoke]
|
||||||
|
|
||||||
ctx.permission_state = next_state
|
ctx.permission_state = next_state
|
||||||
return should_invoke
|
return should_invoke
|
||||||
@@ -584,6 +604,7 @@ def bot_has_permissions(**perms: bool):
|
|||||||
if asyncio.iscoroutinefunction(func):
|
if asyncio.iscoroutinefunction(func):
|
||||||
func.__requires_bot_perms__ = perms
|
func.__requires_bot_perms__ = perms
|
||||||
else:
|
else:
|
||||||
|
_validate_perms_dict(perms)
|
||||||
func.requires.bot_perms.update(**perms)
|
func.requires.bot_perms.update(**perms)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
@@ -595,6 +616,8 @@ def has_permissions(**perms: bool):
|
|||||||
|
|
||||||
This check can be overridden by rules.
|
This check can be overridden by rules.
|
||||||
"""
|
"""
|
||||||
|
if perms is None:
|
||||||
|
raise TypeError("Must provide at least one keyword argument to has_permissions")
|
||||||
return Requires.get_decorator(None, perms)
|
return Requires.get_decorator(None, perms)
|
||||||
|
|
||||||
|
|
||||||
@@ -666,3 +689,20 @@ class _IntKeyDict(Dict[int, _T]):
|
|||||||
if not isinstance(key, int):
|
if not isinstance(key, int):
|
||||||
raise TypeError("Keys must be of type `int`")
|
raise TypeError("Keys must be of type `int`")
|
||||||
return super().__setitem__(key, value)
|
return super().__setitem__(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_perms_dict(perms: Dict[str, bool]) -> None:
|
||||||
|
for perm, value in perms.items():
|
||||||
|
try:
|
||||||
|
attr = getattr(discord.Permissions, perm)
|
||||||
|
except AttributeError:
|
||||||
|
attr = None
|
||||||
|
|
||||||
|
if attr is None or not isinstance(attr, property):
|
||||||
|
# We reject invalid permissions
|
||||||
|
raise TypeError(f"Unknown permission name '{perm}'")
|
||||||
|
|
||||||
|
if value is not True:
|
||||||
|
# We reject any permission not specified as 'True', since this is the only value which
|
||||||
|
# makes practical sense.
|
||||||
|
raise TypeError(f"Permission {perm} may only be specified as 'True', not {value}")
|
||||||
|
|||||||
@@ -39,17 +39,21 @@ class _ValueCtxManager(Awaitable[_T], AsyncContextManager[_T]):
|
|||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
self.raw_value = await self
|
self.raw_value = await self
|
||||||
self.__original_value = deepcopy(self.raw_value)
|
|
||||||
if not isinstance(self.raw_value, (list, dict)):
|
if not isinstance(self.raw_value, (list, dict)):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Type of retrieved value must be mutable (i.e. "
|
"Type of retrieved value must be mutable (i.e. "
|
||||||
"list or dict) in order to use a config value as "
|
"list or dict) in order to use a config value as "
|
||||||
"a context manager."
|
"a context manager."
|
||||||
)
|
)
|
||||||
|
self.__original_value = deepcopy(self.raw_value)
|
||||||
return self.raw_value
|
return self.raw_value
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc, tb):
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
if self.raw_value != self.__original_value:
|
if isinstance(self.raw_value, dict):
|
||||||
|
raw_value = _str_key_dict(self.raw_value)
|
||||||
|
else:
|
||||||
|
raw_value = self.raw_value
|
||||||
|
if raw_value != self.__original_value:
|
||||||
await self.value_obj.set(self.raw_value)
|
await self.value_obj.set(self.raw_value)
|
||||||
|
|
||||||
|
|
||||||
@@ -58,7 +62,7 @@ class Value:
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
identifiers : `tuple` of `str`
|
identifiers : Tuple[str]
|
||||||
This attribute provides all the keys necessary to get a specific data
|
This attribute provides all the keys necessary to get a specific data
|
||||||
element from a json document.
|
element from a json document.
|
||||||
default
|
default
|
||||||
@@ -69,15 +73,10 @@ class Value:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, identifiers: Tuple[str], default_value, driver):
|
def __init__(self, identifiers: Tuple[str], default_value, driver):
|
||||||
self._identifiers = identifiers
|
self.identifiers = identifiers
|
||||||
self.default = default_value
|
self.default = default_value
|
||||||
|
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
@property
|
|
||||||
def identifiers(self):
|
|
||||||
return tuple(str(i) for i in self._identifiers)
|
|
||||||
|
|
||||||
async def _get(self, default=...):
|
async def _get(self, default=...):
|
||||||
try:
|
try:
|
||||||
ret = await self.driver.get(*self.identifiers)
|
ret = await self.driver.get(*self.identifiers)
|
||||||
@@ -149,6 +148,8 @@ class Value:
|
|||||||
The new literal value of this attribute.
|
The new literal value of this attribute.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = _str_key_dict(value)
|
||||||
await self.driver.set(*self.identifiers, value=value)
|
await self.driver.set(*self.identifiers, value=value)
|
||||||
|
|
||||||
async def clear(self):
|
async def clear(self):
|
||||||
@@ -192,7 +193,10 @@ class Group(Value):
|
|||||||
async def _get(self, default: Dict[str, Any] = ...) -> Dict[str, Any]:
|
async def _get(self, default: Dict[str, Any] = ...) -> Dict[str, Any]:
|
||||||
default = default if default is not ... else self.defaults
|
default = default if default is not ... else self.defaults
|
||||||
raw = await super()._get(default)
|
raw = await super()._get(default)
|
||||||
|
if isinstance(raw, dict):
|
||||||
return self.nested_update(raw, default)
|
return self.nested_update(raw, default)
|
||||||
|
else:
|
||||||
|
return raw
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
def __getattr__(self, item: str) -> Union["Group", Value]:
|
def __getattr__(self, item: str) -> Union["Group", Value]:
|
||||||
@@ -238,7 +242,7 @@ class Group(Value):
|
|||||||
else:
|
else:
|
||||||
return Value(identifiers=new_identifiers, default_value=None, driver=self.driver)
|
return Value(identifiers=new_identifiers, default_value=None, driver=self.driver)
|
||||||
|
|
||||||
async def clear_raw(self, *nested_path: str):
|
async def clear_raw(self, *nested_path: Any):
|
||||||
"""
|
"""
|
||||||
Allows a developer to clear data as if it was stored in a standard
|
Allows a developer to clear data as if it was stored in a standard
|
||||||
Python dictionary.
|
Python dictionary.
|
||||||
@@ -254,44 +258,44 @@ class Group(Value):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
nested_path : str
|
nested_path : Any
|
||||||
Multiple arguments that mirror the arguments passed in for nested
|
Multiple arguments that mirror the arguments passed in for nested
|
||||||
dict access.
|
dict access. These are casted to `str` for you.
|
||||||
"""
|
"""
|
||||||
path = [str(p) for p in nested_path]
|
path = [str(p) for p in nested_path]
|
||||||
await self.driver.clear(*self.identifiers, *path)
|
await self.driver.clear(*self.identifiers, *path)
|
||||||
|
|
||||||
def is_group(self, item: str) -> bool:
|
def is_group(self, item: Any) -> bool:
|
||||||
"""A helper method for `__getattr__`. Most developers will have no need
|
"""A helper method for `__getattr__`. Most developers will have no need
|
||||||
to use this.
|
to use this.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
item : str
|
item : Any
|
||||||
See `__getattr__`.
|
See `__getattr__`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
default = self._defaults.get(item)
|
default = self._defaults.get(str(item))
|
||||||
return isinstance(default, dict)
|
return isinstance(default, dict)
|
||||||
|
|
||||||
def is_value(self, item: str) -> bool:
|
def is_value(self, item: Any) -> bool:
|
||||||
"""A helper method for `__getattr__`. Most developers will have no need
|
"""A helper method for `__getattr__`. Most developers will have no need
|
||||||
to use this.
|
to use this.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
item : str
|
item : Any
|
||||||
See `__getattr__`.
|
See `__getattr__`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
default = self._defaults[item]
|
default = self._defaults[str(item)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return not isinstance(default, dict)
|
return not isinstance(default, dict)
|
||||||
|
|
||||||
def get_attr(self, item: str):
|
def get_attr(self, item: Union[int, str]):
|
||||||
"""Manually get an attribute of this Group.
|
"""Manually get an attribute of this Group.
|
||||||
|
|
||||||
This is available to use as an alternative to using normal Python
|
This is available to use as an alternative to using normal Python
|
||||||
@@ -312,7 +316,8 @@ class Group(Value):
|
|||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
item : str
|
item : str
|
||||||
The name of the data field in `Config`.
|
The name of the data field in `Config`. This is casted to
|
||||||
|
`str` for you.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -320,9 +325,11 @@ class Group(Value):
|
|||||||
The attribute which was requested.
|
The attribute which was requested.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if isinstance(item, int):
|
||||||
|
item = str(item)
|
||||||
return self.__getattr__(item)
|
return self.__getattr__(item)
|
||||||
|
|
||||||
async def get_raw(self, *nested_path: str, default=...):
|
async def get_raw(self, *nested_path: Any, default=...):
|
||||||
"""
|
"""
|
||||||
Allows a developer to access data as if it was stored in a standard
|
Allows a developer to access data as if it was stored in a standard
|
||||||
Python dictionary.
|
Python dictionary.
|
||||||
@@ -345,7 +352,7 @@ class Group(Value):
|
|||||||
----------
|
----------
|
||||||
nested_path : str
|
nested_path : str
|
||||||
Multiple arguments that mirror the arguments passed in for nested
|
Multiple arguments that mirror the arguments passed in for nested
|
||||||
dict access.
|
dict access. These are casted to `str` for you.
|
||||||
default
|
default
|
||||||
Default argument for the value attempting to be accessed. If the
|
Default argument for the value attempting to be accessed. If the
|
||||||
value does not exist the default will be returned.
|
value does not exist the default will be returned.
|
||||||
@@ -410,7 +417,6 @@ class Group(Value):
|
|||||||
|
|
||||||
If no defaults are passed, then the instance attribute 'defaults'
|
If no defaults are passed, then the instance attribute 'defaults'
|
||||||
will be used.
|
will be used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if defaults is ...:
|
if defaults is ...:
|
||||||
defaults = self.defaults
|
defaults = self.defaults
|
||||||
@@ -428,7 +434,7 @@ class Group(Value):
|
|||||||
raise ValueError("You may only set the value of a group to be a dict.")
|
raise ValueError("You may only set the value of a group to be a dict.")
|
||||||
await super().set(value)
|
await super().set(value)
|
||||||
|
|
||||||
async def set_raw(self, *nested_path: str, value):
|
async def set_raw(self, *nested_path: Any, value):
|
||||||
"""
|
"""
|
||||||
Allows a developer to set data as if it was stored in a standard
|
Allows a developer to set data as if it was stored in a standard
|
||||||
Python dictionary.
|
Python dictionary.
|
||||||
@@ -444,13 +450,15 @@ class Group(Value):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
nested_path : str
|
nested_path : Any
|
||||||
Multiple arguments that mirror the arguments passed in for nested
|
Multiple arguments that mirror the arguments passed in for nested
|
||||||
dict access.
|
`dict` access. These are casted to `str` for you.
|
||||||
value
|
value
|
||||||
The value to store.
|
The value to store.
|
||||||
"""
|
"""
|
||||||
path = [str(p) for p in nested_path]
|
path = [str(p) for p in nested_path]
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = _str_key_dict(value)
|
||||||
await self.driver.set(*self.identifiers, *path, value=value)
|
await self.driver.set(*self.identifiers, *path, value=value)
|
||||||
|
|
||||||
|
|
||||||
@@ -461,9 +469,11 @@ class Config:
|
|||||||
`get_core_conf` for Config used in the core package.
|
`get_core_conf` for Config used in the core package.
|
||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
Most config data should be accessed through its respective group method (e.g. :py:meth:`guild`)
|
Most config data should be accessed through its respective
|
||||||
however the process for accessing global data is a bit different. There is no :python:`global` method
|
group method (e.g. :py:meth:`guild`) however the process for
|
||||||
because global data is accessed by normal attribute access::
|
accessing global data is a bit different. There is no
|
||||||
|
:python:`global` method because global data is accessed by
|
||||||
|
normal attribute access::
|
||||||
|
|
||||||
await conf.foo()
|
await conf.foo()
|
||||||
|
|
||||||
@@ -548,7 +558,7 @@ class Config:
|
|||||||
A new Config object.
|
A new Config object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if cog_instance is None and not cog_name is None:
|
if cog_instance is None and cog_name is not None:
|
||||||
cog_path_override = cog_data_path(raw_name=cog_name)
|
cog_path_override = cog_data_path(raw_name=cog_name)
|
||||||
else:
|
else:
|
||||||
cog_path_override = cog_data_path(cog_instance=cog_instance)
|
cog_path_override = cog_data_path(cog_instance=cog_instance)
|
||||||
@@ -635,11 +645,8 @@ class Config:
|
|||||||
def _get_defaults_dict(key: str, value) -> dict:
|
def _get_defaults_dict(key: str, value) -> dict:
|
||||||
"""
|
"""
|
||||||
Since we're allowing nested config stuff now, not storing the
|
Since we're allowing nested config stuff now, not storing the
|
||||||
_defaults as a flat dict sounds like a good idea. May turn
|
_defaults as a flat dict sounds like a good idea. May turn out
|
||||||
out to be an awful one but we'll see.
|
to be an awful one but we'll see.
|
||||||
:param key:
|
|
||||||
:param value:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
ret = {}
|
ret = {}
|
||||||
partial = ret
|
partial = ret
|
||||||
@@ -655,15 +662,12 @@ class Config:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _update_defaults(to_add: dict, _partial: dict):
|
def _update_defaults(to_add: Dict[str, Any], _partial: Dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
This tries to update the _defaults dictionary with the nested
|
This tries to update the _defaults dictionary with the nested
|
||||||
partial dict generated by _get_defaults_dict. This WILL
|
partial dict generated by _get_defaults_dict. This WILL
|
||||||
throw an error if you try to have both a value and a group
|
throw an error if you try to have both a value and a group
|
||||||
registered under the same name.
|
registered under the same name.
|
||||||
:param to_add:
|
|
||||||
:param _partial:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
for k, v in to_add.items():
|
for k, v in to_add.items():
|
||||||
val_is_dict = isinstance(v, dict)
|
val_is_dict = isinstance(v, dict)
|
||||||
@@ -679,7 +683,7 @@ class Config:
|
|||||||
else:
|
else:
|
||||||
_partial[k] = v
|
_partial[k] = v
|
||||||
|
|
||||||
def _register_default(self, key: str, **kwargs):
|
def _register_default(self, key: str, **kwargs: Any):
|
||||||
if key not in self._defaults:
|
if key not in self._defaults:
|
||||||
self._defaults[key] = {}
|
self._defaults[key] = {}
|
||||||
|
|
||||||
@@ -720,8 +724,8 @@ class Config:
|
|||||||
**_defaults
|
**_defaults
|
||||||
)
|
)
|
||||||
|
|
||||||
You can do the same thing without a :python:`_defaults` dict by using double underscore as a variable
|
You can do the same thing without a :python:`_defaults` dict by
|
||||||
name separator::
|
using double underscore as a variable name separator::
|
||||||
|
|
||||||
# This is equivalent to the previous example
|
# This is equivalent to the previous example
|
||||||
conf.register_global(
|
conf.register_global(
|
||||||
@@ -802,7 +806,7 @@ class Config:
|
|||||||
The guild's Group object.
|
The guild's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.GUILD, guild.id)
|
return self._get_base_group(self.GUILD, str(guild.id))
|
||||||
|
|
||||||
def channel(self, channel: discord.TextChannel) -> Group:
|
def channel(self, channel: discord.TextChannel) -> Group:
|
||||||
"""Returns a `Group` for the given channel.
|
"""Returns a `Group` for the given channel.
|
||||||
@@ -820,7 +824,7 @@ class Config:
|
|||||||
The channel's Group object.
|
The channel's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.CHANNEL, channel.id)
|
return self._get_base_group(self.CHANNEL, str(channel.id))
|
||||||
|
|
||||||
def role(self, role: discord.Role) -> Group:
|
def role(self, role: discord.Role) -> Group:
|
||||||
"""Returns a `Group` for the given role.
|
"""Returns a `Group` for the given role.
|
||||||
@@ -836,7 +840,7 @@ class Config:
|
|||||||
The role's Group object.
|
The role's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.ROLE, role.id)
|
return self._get_base_group(self.ROLE, str(role.id))
|
||||||
|
|
||||||
def user(self, user: discord.abc.User) -> Group:
|
def user(self, user: discord.abc.User) -> Group:
|
||||||
"""Returns a `Group` for the given user.
|
"""Returns a `Group` for the given user.
|
||||||
@@ -852,7 +856,7 @@ class Config:
|
|||||||
The user's Group object.
|
The user's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.USER, user.id)
|
return self._get_base_group(self.USER, str(user.id))
|
||||||
|
|
||||||
def member(self, member: discord.Member) -> Group:
|
def member(self, member: discord.Member) -> Group:
|
||||||
"""Returns a `Group` for the given member.
|
"""Returns a `Group` for the given member.
|
||||||
@@ -866,8 +870,9 @@ class Config:
|
|||||||
-------
|
-------
|
||||||
`Group <redbot.core.config.Group>`
|
`Group <redbot.core.config.Group>`
|
||||||
The member's Group object.
|
The member's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(self.MEMBER, member.guild.id, member.id)
|
return self._get_base_group(self.MEMBER, str(member.guild.id), str(member.id))
|
||||||
|
|
||||||
def custom(self, group_identifier: str, *identifiers: str):
|
def custom(self, group_identifier: str, *identifiers: str):
|
||||||
"""Returns a `Group` for the given custom group.
|
"""Returns a `Group` for the given custom group.
|
||||||
@@ -876,17 +881,17 @@ class Config:
|
|||||||
----------
|
----------
|
||||||
group_identifier : str
|
group_identifier : str
|
||||||
Used to identify the custom group.
|
Used to identify the custom group.
|
||||||
|
|
||||||
identifiers : str
|
identifiers : str
|
||||||
The attributes necessary to uniquely identify an entry in the
|
The attributes necessary to uniquely identify an entry in the
|
||||||
custom group.
|
custom group. These are casted to `str` for you.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
`Group <redbot.core.config.Group>`
|
`Group <redbot.core.config.Group>`
|
||||||
The custom group's Group object.
|
The custom group's Group object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_base_group(group_identifier, *identifiers)
|
return self._get_base_group(str(group_identifier), *map(str, identifiers))
|
||||||
|
|
||||||
async def _all_from_scope(self, scope: str) -> Dict[int, Dict[Any, Any]]:
|
async def _all_from_scope(self, scope: str) -> Dict[int, Dict[Any, Any]]:
|
||||||
"""Get a dict of all values from a particular scope of data.
|
"""Get a dict of all values from a particular scope of data.
|
||||||
@@ -982,7 +987,8 @@ class Config:
|
|||||||
"""
|
"""
|
||||||
return await self._all_from_scope(self.USER)
|
return await self._all_from_scope(self.USER)
|
||||||
|
|
||||||
def _all_members_from_guild(self, group: Group, guild_data: dict) -> dict:
|
@staticmethod
|
||||||
|
def _all_members_from_guild(group: Group, guild_data: dict) -> dict:
|
||||||
ret = {}
|
ret = {}
|
||||||
for member_id, member_data in guild_data.items():
|
for member_id, member_data in guild_data.items():
|
||||||
new_member_data = group.defaults
|
new_member_data = group.defaults
|
||||||
@@ -1026,7 +1032,7 @@ class Config:
|
|||||||
for guild_id, guild_data in dict_.items():
|
for guild_id, guild_data in dict_.items():
|
||||||
ret[int(guild_id)] = self._all_members_from_guild(group, guild_data)
|
ret[int(guild_id)] = self._all_members_from_guild(group, guild_data)
|
||||||
else:
|
else:
|
||||||
group = self._get_base_group(self.MEMBER, guild.id)
|
group = self._get_base_group(self.MEMBER, str(guild.id))
|
||||||
try:
|
try:
|
||||||
guild_data = await self.driver.get(*group.identifiers)
|
guild_data = await self.driver.get(*group.identifiers)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -1054,7 +1060,8 @@ class Config:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not scopes:
|
if not scopes:
|
||||||
group = Group(identifiers=[], defaults={}, driver=self.driver)
|
# noinspection PyTypeChecker
|
||||||
|
group = Group(identifiers=(), defaults={}, driver=self.driver)
|
||||||
else:
|
else:
|
||||||
group = self._get_base_group(*scopes)
|
group = self._get_base_group(*scopes)
|
||||||
await group.clear()
|
await group.clear()
|
||||||
@@ -1119,7 +1126,7 @@ class Config:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if guild is not None:
|
if guild is not None:
|
||||||
await self._clear_scope(self.MEMBER, guild.id)
|
await self._clear_scope(self.MEMBER, str(guild.id))
|
||||||
return
|
return
|
||||||
await self._clear_scope(self.MEMBER)
|
await self._clear_scope(self.MEMBER)
|
||||||
|
|
||||||
@@ -1127,5 +1134,34 @@ class Config:
|
|||||||
"""Clear all custom group data.
|
"""Clear all custom group data.
|
||||||
|
|
||||||
This resets all custom group data to its registered defaults.
|
This resets all custom group data to its registered defaults.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
group_identifier : str
|
||||||
|
The identifier for the custom group. This is casted to
|
||||||
|
`str` for you.
|
||||||
"""
|
"""
|
||||||
await self._clear_scope(group_identifier)
|
await self._clear_scope(str(group_identifier))
|
||||||
|
|
||||||
|
|
||||||
|
def _str_key_dict(value: Dict[Any, _T]) -> Dict[str, _T]:
|
||||||
|
"""
|
||||||
|
Recursively casts all keys in the given `dict` to `str`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
value : Dict[Any, Any]
|
||||||
|
The `dict` to cast keys to `str`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Dict[str, Any]
|
||||||
|
The `dict` with keys (and nested keys) casted to `str`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
ret = {}
|
||||||
|
for k, v in value.items():
|
||||||
|
if isinstance(v, dict):
|
||||||
|
v = _str_key_dict(v)
|
||||||
|
ret[str(k)] = v
|
||||||
|
return ret
|
||||||
|
|||||||
@@ -13,17 +13,21 @@ from collections import namedtuple
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from string import ascii_letters, digits
|
from string import ascii_letters, digits
|
||||||
from distutils.version import StrictVersion
|
from typing import TYPE_CHECKING, Union, Tuple, List, Optional, Iterable, Sequence, Dict
|
||||||
from typing import TYPE_CHECKING, Union
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
from redbot.core import __version__
|
from redbot.core import (
|
||||||
from redbot.core import checks
|
__version__,
|
||||||
from redbot.core import i18n
|
version_info as red_version_info,
|
||||||
from redbot.core import commands
|
VersionInfo,
|
||||||
|
checks,
|
||||||
|
commands,
|
||||||
|
errors,
|
||||||
|
i18n,
|
||||||
|
)
|
||||||
from .utils.predicates import MessagePredicate
|
from .utils.predicates import MessagePredicate
|
||||||
from .utils.chat_formatting import pagify, box, inline
|
from .utils.chat_formatting import pagify, box, inline
|
||||||
|
|
||||||
@@ -56,7 +60,9 @@ class CoreLogic:
|
|||||||
self.bot.register_rpc_handler(self._version_info)
|
self.bot.register_rpc_handler(self._version_info)
|
||||||
self.bot.register_rpc_handler(self._invite_url)
|
self.bot.register_rpc_handler(self._invite_url)
|
||||||
|
|
||||||
async def _load(self, cog_names: list):
|
async def _load(
|
||||||
|
self, cog_names: Iterable[str]
|
||||||
|
) -> Tuple[List[str], List[str], List[str], List[str]]:
|
||||||
"""
|
"""
|
||||||
Loads cogs by name.
|
Loads cogs by name.
|
||||||
Parameters
|
Parameters
|
||||||
@@ -66,11 +72,12 @@ class CoreLogic:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
tuple
|
tuple
|
||||||
3 element tuple of loaded, failed, and not found cogs.
|
4-tuple of loaded, failed, not found and already loaded cogs.
|
||||||
"""
|
"""
|
||||||
failed_packages = []
|
failed_packages = []
|
||||||
loaded_packages = []
|
loaded_packages = []
|
||||||
notfound_packages = []
|
notfound_packages = []
|
||||||
|
alreadyloaded_packages = []
|
||||||
|
|
||||||
bot = self.bot
|
bot = self.bot
|
||||||
|
|
||||||
@@ -95,6 +102,8 @@ class CoreLogic:
|
|||||||
try:
|
try:
|
||||||
self._cleanup_and_refresh_modules(spec.name)
|
self._cleanup_and_refresh_modules(spec.name)
|
||||||
await bot.load_extension(spec)
|
await bot.load_extension(spec)
|
||||||
|
except errors.PackageAlreadyLoaded:
|
||||||
|
alreadyloaded_packages.append(name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Package loading failed", exc_info=e)
|
log.exception("Package loading failed", exc_info=e)
|
||||||
|
|
||||||
@@ -106,9 +115,10 @@ class CoreLogic:
|
|||||||
await bot.add_loaded_package(name)
|
await bot.add_loaded_package(name)
|
||||||
loaded_packages.append(name)
|
loaded_packages.append(name)
|
||||||
|
|
||||||
return loaded_packages, failed_packages, notfound_packages
|
return loaded_packages, failed_packages, notfound_packages, alreadyloaded_packages
|
||||||
|
|
||||||
def _cleanup_and_refresh_modules(self, module_name: str):
|
@staticmethod
|
||||||
|
def _cleanup_and_refresh_modules(module_name: str) -> None:
|
||||||
"""Interally reloads modules so that changes are detected"""
|
"""Interally reloads modules so that changes are detected"""
|
||||||
splitted = module_name.split(".")
|
splitted = module_name.split(".")
|
||||||
|
|
||||||
@@ -120,6 +130,7 @@ class CoreLogic:
|
|||||||
else:
|
else:
|
||||||
importlib._bootstrap._exec(lib.__spec__, lib)
|
importlib._bootstrap._exec(lib.__spec__, lib)
|
||||||
|
|
||||||
|
# noinspection PyTypeChecker
|
||||||
modules = itertools.accumulate(splitted, "{}.{}".format)
|
modules = itertools.accumulate(splitted, "{}.{}".format)
|
||||||
for m in modules:
|
for m in modules:
|
||||||
maybe_reload(m)
|
maybe_reload(m)
|
||||||
@@ -128,7 +139,10 @@ class CoreLogic:
|
|||||||
for child_name, lib in children.items():
|
for child_name, lib in children.items():
|
||||||
importlib._bootstrap._exec(lib.__spec__, lib)
|
importlib._bootstrap._exec(lib.__spec__, lib)
|
||||||
|
|
||||||
def _get_package_strings(self, packages: list, fmt: str, other: tuple = None):
|
@staticmethod
|
||||||
|
def _get_package_strings(
|
||||||
|
packages: List[str], fmt: str, other: Optional[Tuple[str, ...]] = None
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Gets the strings needed for the load, unload and reload commands
|
Gets the strings needed for the load, unload and reload commands
|
||||||
"""
|
"""
|
||||||
@@ -144,7 +158,7 @@ class CoreLogic:
|
|||||||
final_string = fmt.format(**form)
|
final_string = fmt.format(**form)
|
||||||
return final_string
|
return final_string
|
||||||
|
|
||||||
async def _unload(self, cog_names: list):
|
async def _unload(self, cog_names: Iterable[str]) -> Tuple[List[str], List[str]]:
|
||||||
"""
|
"""
|
||||||
Unloads cogs with the given names.
|
Unloads cogs with the given names.
|
||||||
|
|
||||||
@@ -172,14 +186,16 @@ class CoreLogic:
|
|||||||
|
|
||||||
return unloaded_packages, failed_packages
|
return unloaded_packages, failed_packages
|
||||||
|
|
||||||
async def _reload(self, cog_names):
|
async def _reload(
|
||||||
|
self, cog_names: Sequence[str]
|
||||||
|
) -> Tuple[List[str], List[str], List[str], List[str]]:
|
||||||
await self._unload(cog_names)
|
await self._unload(cog_names)
|
||||||
|
|
||||||
loaded, load_failed, not_found = await self._load(cog_names)
|
loaded, load_failed, not_found, already_loaded = await self._load(cog_names)
|
||||||
|
|
||||||
return loaded, load_failed, not_found
|
return loaded, load_failed, not_found, already_loaded
|
||||||
|
|
||||||
async def _name(self, name: str = None):
|
async def _name(self, name: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Gets or sets the bot's username.
|
Gets or sets the bot's username.
|
||||||
|
|
||||||
@@ -198,7 +214,7 @@ class CoreLogic:
|
|||||||
|
|
||||||
return self.bot.user.name
|
return self.bot.user.name
|
||||||
|
|
||||||
async def _prefixes(self, prefixes: list = None):
|
async def _prefixes(self, prefixes: Optional[Sequence[str]] = None) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Gets or sets the bot's global prefixes.
|
Gets or sets the bot's global prefixes.
|
||||||
|
|
||||||
@@ -217,7 +233,8 @@ class CoreLogic:
|
|||||||
await self.bot.db.prefix.set(prefixes)
|
await self.bot.db.prefix.set(prefixes)
|
||||||
return await self.bot.db.prefix()
|
return await self.bot.db.prefix()
|
||||||
|
|
||||||
async def _version_info(self):
|
@classmethod
|
||||||
|
async def _version_info(cls) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Version information for Red and discord.py
|
Version information for Red and discord.py
|
||||||
|
|
||||||
@@ -228,7 +245,7 @@ class CoreLogic:
|
|||||||
"""
|
"""
|
||||||
return {"redbot": __version__, "discordpy": discord.__version__}
|
return {"redbot": __version__, "discordpy": discord.__version__}
|
||||||
|
|
||||||
async def _invite_url(self):
|
async def _invite_url(self) -> str:
|
||||||
"""
|
"""
|
||||||
Generates the invite URL for the bot.
|
Generates the invite URL for the bot.
|
||||||
|
|
||||||
@@ -245,11 +262,8 @@ class CoreLogic:
|
|||||||
class Core(commands.Cog, CoreLogic):
|
class Core(commands.Cog, CoreLogic):
|
||||||
"""Commands related to core functions"""
|
"""Commands related to core functions"""
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
super().__init__(bot)
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
@commands.command(hidden=True)
|
||||||
async def ping(self, ctx):
|
async def ping(self, ctx: commands.Context):
|
||||||
"""Pong."""
|
"""Pong."""
|
||||||
await ctx.send("Pong.")
|
await ctx.send("Pong.")
|
||||||
|
|
||||||
@@ -274,7 +288,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get("{}/json".format(red_pypi)) as r:
|
async with session.get("{}/json".format(red_pypi)) as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
outdated = StrictVersion(data["info"]["version"]) > StrictVersion(__version__)
|
outdated = VersionInfo.from_str(data["info"]["version"]) > red_version_info
|
||||||
about = (
|
about = (
|
||||||
"This is an instance of [Red, an open source Discord bot]({}) "
|
"This is an instance of [Red, an open source Discord bot]({}) "
|
||||||
"created by [Twentysix]({}) and [improved by many]({}).\n\n"
|
"created by [Twentysix]({}) and [improved by many]({}).\n\n"
|
||||||
@@ -310,7 +324,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
passed = self.get_bot_uptime()
|
passed = self.get_bot_uptime()
|
||||||
await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since))
|
await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since))
|
||||||
|
|
||||||
def get_bot_uptime(self, *, brief=False):
|
def get_bot_uptime(self, *, brief: bool = False):
|
||||||
# Courtesy of Danny
|
# Courtesy of Danny
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
delta = now - self.bot.uptime
|
delta = now - self.bot.uptime
|
||||||
@@ -413,7 +427,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def traceback(self, ctx, public: bool = False):
|
async def traceback(self, ctx: commands.Context, public: bool = False):
|
||||||
"""Sends to the owner the last command exception that has occurred
|
"""Sends to the owner the last command exception that has occurred
|
||||||
|
|
||||||
If public (yes is specified), it will be sent to the chat instead"""
|
If public (yes is specified), it will be sent to the chat instead"""
|
||||||
@@ -430,20 +444,20 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def invite(self, ctx):
|
async def invite(self, ctx: commands.Context):
|
||||||
"""Show's Red's invite url"""
|
"""Show's Red's invite url"""
|
||||||
await ctx.author.send(await self._invite_url())
|
await ctx.author.send(await self._invite_url())
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def leave(self, ctx):
|
async def leave(self, ctx: commands.Context):
|
||||||
"""Leaves server"""
|
"""Leaves server"""
|
||||||
await ctx.send("Are you sure you want me to leave this server? (y/n)")
|
await ctx.send("Are you sure you want me to leave this server? (y/n)")
|
||||||
|
|
||||||
pred = MessagePredicate.yes_or_no(ctx)
|
pred = MessagePredicate.yes_or_no(ctx)
|
||||||
try:
|
try:
|
||||||
await self.bot.wait_for("message", check=MessagePredicate.yes_or_no(ctx))
|
await self.bot.wait_for("message", check=pred)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await ctx.send("Response timed out.")
|
await ctx.send("Response timed out.")
|
||||||
return
|
return
|
||||||
@@ -457,7 +471,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def servers(self, ctx):
|
async def servers(self, ctx: commands.Context):
|
||||||
"""Lists and allows to leave servers"""
|
"""Lists and allows to leave servers"""
|
||||||
guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower())
|
guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower())
|
||||||
msg = ""
|
msg = ""
|
||||||
@@ -499,18 +513,21 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def load(self, ctx, *, cog_name: str):
|
async def load(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Loads packages"""
|
"""Loads packages"""
|
||||||
|
|
||||||
cog_names = [c.strip() for c in cog_name.split(" ")]
|
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
loaded, failed, not_found = await self._load(cog_names)
|
loaded, failed, not_found, already_loaded = await self._load(cogs)
|
||||||
|
|
||||||
if loaded:
|
if loaded:
|
||||||
fmt = "Loaded {packs}."
|
fmt = "Loaded {packs}."
|
||||||
formed = self._get_package_strings(loaded, fmt)
|
formed = self._get_package_strings(loaded, fmt)
|
||||||
await ctx.send(formed)
|
await ctx.send(formed)
|
||||||
|
|
||||||
|
if already_loaded:
|
||||||
|
fmt = "The package{plural} {packs} {other} already loaded."
|
||||||
|
formed = self._get_package_strings(already_loaded, fmt, ("is", "are"))
|
||||||
|
await ctx.send(formed)
|
||||||
|
|
||||||
if failed:
|
if failed:
|
||||||
fmt = (
|
fmt = (
|
||||||
"Failed to load package{plural} {packs}. Check your console or "
|
"Failed to load package{plural} {packs}. Check your console or "
|
||||||
@@ -526,12 +543,9 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def unload(self, ctx, *, cog_name: str):
|
async def unload(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Unloads packages"""
|
"""Unloads packages"""
|
||||||
|
unloaded, failed = await self._unload(cogs)
|
||||||
cog_names = [c.strip() for c in cog_name.split(" ")]
|
|
||||||
|
|
||||||
unloaded, failed = await self._unload(cog_names)
|
|
||||||
|
|
||||||
if unloaded:
|
if unloaded:
|
||||||
fmt = "Package{plural} {packs} {other} unloaded."
|
fmt = "Package{plural} {packs} {other} unloaded."
|
||||||
@@ -545,10 +559,10 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command(name="reload")
|
@commands.command(name="reload")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def reload(self, ctx, *cogs: str):
|
async def reload(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Reloads packages"""
|
"""Reloads packages"""
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
loaded, failed, not_found = await self._reload(cogs)
|
loaded, failed, not_found, already_loaded = await self._reload(cogs)
|
||||||
|
|
||||||
if loaded:
|
if loaded:
|
||||||
fmt = "Package{plural} {packs} {other} reloaded."
|
fmt = "Package{plural} {packs} {other} reloaded."
|
||||||
@@ -567,34 +581,30 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command(name="shutdown")
|
@commands.command(name="shutdown")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _shutdown(self, ctx, silently: bool = False):
|
async def _shutdown(self, ctx: commands.Context, silently: bool = False):
|
||||||
"""Shuts down the bot"""
|
"""Shuts down the bot"""
|
||||||
wave = "\N{WAVING HAND SIGN}"
|
wave = "\N{WAVING HAND SIGN}"
|
||||||
skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}"
|
skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}"
|
||||||
try: # We don't want missing perms to stop our shutdown
|
with contextlib.suppress(discord.HTTPException):
|
||||||
if not silently:
|
if not silently:
|
||||||
await ctx.send(_("Shutting down... ") + wave + skin)
|
await ctx.send(_("Shutting down... ") + wave + skin)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
await ctx.bot.shutdown()
|
await ctx.bot.shutdown()
|
||||||
|
|
||||||
@commands.command(name="restart")
|
@commands.command(name="restart")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _restart(self, ctx, silently: bool = False):
|
async def _restart(self, ctx: commands.Context, silently: bool = False):
|
||||||
"""Attempts to restart Red
|
"""Attempts to restart Red
|
||||||
|
|
||||||
Makes Red quit with exit code 26
|
Makes Red quit with exit code 26
|
||||||
The restart is not guaranteed: it must be dealt
|
The restart is not guaranteed: it must be dealt
|
||||||
with by the process manager in use"""
|
with by the process manager in use"""
|
||||||
try:
|
with contextlib.suppress(discord.HTTPException):
|
||||||
if not silently:
|
if not silently:
|
||||||
await ctx.send(_("Restarting..."))
|
await ctx.send(_("Restarting..."))
|
||||||
except:
|
|
||||||
pass
|
|
||||||
await ctx.bot.shutdown(restart=True)
|
await ctx.bot.shutdown(restart=True)
|
||||||
|
|
||||||
@commands.group(name="set")
|
@commands.group(name="set")
|
||||||
async def _set(self, ctx):
|
async def _set(self, ctx: commands.Context):
|
||||||
"""Changes Red's settings"""
|
"""Changes Red's settings"""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
if ctx.guild:
|
if ctx.guild:
|
||||||
@@ -626,7 +636,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.guildowner()
|
@checks.guildowner()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def adminrole(self, ctx, *, role: discord.Role):
|
async def adminrole(self, ctx: commands.Context, *, role: discord.Role):
|
||||||
"""Sets the admin role for this server"""
|
"""Sets the admin role for this server"""
|
||||||
await ctx.bot.db.guild(ctx.guild).admin_role.set(role.id)
|
await ctx.bot.db.guild(ctx.guild).admin_role.set(role.id)
|
||||||
await ctx.send(_("The admin role for this guild has been set."))
|
await ctx.send(_("The admin role for this guild has been set."))
|
||||||
@@ -634,7 +644,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.guildowner()
|
@checks.guildowner()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def modrole(self, ctx, *, role: discord.Role):
|
async def modrole(self, ctx: commands.Context, *, role: discord.Role):
|
||||||
"""Sets the mod role for this server"""
|
"""Sets the mod role for this server"""
|
||||||
await ctx.bot.db.guild(ctx.guild).mod_role.set(role.id)
|
await ctx.bot.db.guild(ctx.guild).mod_role.set(role.id)
|
||||||
await ctx.send(_("The mod role for this guild has been set."))
|
await ctx.send(_("The mod role for this guild has been set."))
|
||||||
@@ -642,7 +652,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(aliases=["usebotcolor"])
|
@_set.command(aliases=["usebotcolor"])
|
||||||
@checks.guildowner()
|
@checks.guildowner()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def usebotcolour(self, ctx):
|
async def usebotcolour(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Toggle whether to use the bot owner-configured colour for embeds.
|
Toggle whether to use the bot owner-configured colour for embeds.
|
||||||
|
|
||||||
@@ -660,7 +670,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.guildowner()
|
@checks.guildowner()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def serverfuzzy(self, ctx):
|
async def serverfuzzy(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Toggle whether to enable fuzzy command search for the server.
|
Toggle whether to enable fuzzy command search for the server.
|
||||||
|
|
||||||
@@ -676,7 +686,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def fuzzy(self, ctx):
|
async def fuzzy(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Toggle whether to enable fuzzy command search in DMs.
|
Toggle whether to enable fuzzy command search in DMs.
|
||||||
|
|
||||||
@@ -692,7 +702,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command(aliases=["color"])
|
@_set.command(aliases=["color"])
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def colour(self, ctx, *, colour: discord.Colour = None):
|
async def colour(self, ctx: commands.Context, *, colour: discord.Colour = None):
|
||||||
"""
|
"""
|
||||||
Sets a default colour to be used for the bot's embeds.
|
Sets a default colour to be used for the bot's embeds.
|
||||||
|
|
||||||
@@ -710,7 +720,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def avatar(self, ctx, url: str):
|
async def avatar(self, ctx: commands.Context, url: str):
|
||||||
"""Sets Red's avatar"""
|
"""Sets Red's avatar"""
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as r:
|
async with session.get(url) as r:
|
||||||
@@ -734,7 +744,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(name="game")
|
@_set.command(name="game")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _game(self, ctx, *, game: str = None):
|
async def _game(self, ctx: commands.Context, *, game: str = None):
|
||||||
"""Sets Red's playing status"""
|
"""Sets Red's playing status"""
|
||||||
|
|
||||||
if game:
|
if game:
|
||||||
@@ -748,7 +758,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(name="listening")
|
@_set.command(name="listening")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _listening(self, ctx, *, listening: str = None):
|
async def _listening(self, ctx: commands.Context, *, listening: str = None):
|
||||||
"""Sets Red's listening status"""
|
"""Sets Red's listening status"""
|
||||||
|
|
||||||
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
||||||
@@ -762,7 +772,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(name="watching")
|
@_set.command(name="watching")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _watching(self, ctx, *, watching: str = None):
|
async def _watching(self, ctx: commands.Context, *, watching: str = None):
|
||||||
"""Sets Red's watching status"""
|
"""Sets Red's watching status"""
|
||||||
|
|
||||||
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
||||||
@@ -776,7 +786,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def status(self, ctx, *, status: str):
|
async def status(self, ctx: commands.Context, *, status: str):
|
||||||
"""Sets Red's status
|
"""Sets Red's status
|
||||||
|
|
||||||
Available statuses:
|
Available statuses:
|
||||||
@@ -805,7 +815,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def stream(self, ctx, streamer=None, *, stream_title=None):
|
async def stream(self, ctx: commands.Context, streamer=None, *, stream_title=None):
|
||||||
"""Sets Red's streaming status
|
"""Sets Red's streaming status
|
||||||
Leaving both streamer and stream_title empty will clear it."""
|
Leaving both streamer and stream_title empty will clear it."""
|
||||||
|
|
||||||
@@ -826,7 +836,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command(name="username", aliases=["name"])
|
@_set.command(name="username", aliases=["name"])
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _username(self, ctx, *, username: str):
|
async def _username(self, ctx: commands.Context, *, username: str):
|
||||||
"""Sets Red's username"""
|
"""Sets Red's username"""
|
||||||
try:
|
try:
|
||||||
await self._name(name=username)
|
await self._name(name=username)
|
||||||
@@ -845,7 +855,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(name="nickname")
|
@_set.command(name="nickname")
|
||||||
@checks.admin()
|
@checks.admin()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _nickname(self, ctx, *, nickname: str = None):
|
async def _nickname(self, ctx: commands.Context, *, nickname: str = None):
|
||||||
"""Sets Red's nickname"""
|
"""Sets Red's nickname"""
|
||||||
try:
|
try:
|
||||||
await ctx.guild.me.edit(nick=nickname)
|
await ctx.guild.me.edit(nick=nickname)
|
||||||
@@ -856,7 +866,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command(aliases=["prefixes"])
|
@_set.command(aliases=["prefixes"])
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def prefix(self, ctx, *prefixes):
|
async def prefix(self, ctx: commands.Context, *prefixes: str):
|
||||||
"""Sets Red's global prefix(es)"""
|
"""Sets Red's global prefix(es)"""
|
||||||
if not prefixes:
|
if not prefixes:
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
@@ -867,7 +877,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@_set.command(aliases=["serverprefixes"])
|
@_set.command(aliases=["serverprefixes"])
|
||||||
@checks.admin()
|
@checks.admin()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def serverprefix(self, ctx, *prefixes):
|
async def serverprefix(self, ctx: commands.Context, *prefixes: str):
|
||||||
"""Sets Red's server prefix(es)"""
|
"""Sets Red's server prefix(es)"""
|
||||||
if not prefixes:
|
if not prefixes:
|
||||||
await ctx.bot.db.guild(ctx.guild).prefix.set([])
|
await ctx.bot.db.guild(ctx.guild).prefix.set([])
|
||||||
@@ -879,7 +889,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
|
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
|
||||||
async def owner(self, ctx):
|
async def owner(self, ctx: commands.Context):
|
||||||
"""Sets Red's main owner"""
|
"""Sets Red's main owner"""
|
||||||
# According to the Python docs this is suitable for cryptographic use
|
# According to the Python docs this is suitable for cryptographic use
|
||||||
random = SystemRandom()
|
random = SystemRandom()
|
||||||
@@ -923,7 +933,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def token(self, ctx, token: str):
|
async def token(self, ctx: commands.Context, token: str):
|
||||||
"""Change bot token."""
|
"""Change bot token."""
|
||||||
|
|
||||||
if not isinstance(ctx.channel, discord.DMChannel):
|
if not isinstance(ctx.channel, discord.DMChannel):
|
||||||
@@ -1068,7 +1078,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def backup(self, ctx, backup_path: str = None):
|
async def backup(self, ctx: commands.Context, backup_path: str = None):
|
||||||
"""Creates a backup of all data for the instance."""
|
"""Creates a backup of all data for the instance."""
|
||||||
from redbot.core.data_manager import basic_config, instance_name
|
from redbot.core.data_manager import basic_config, instance_name
|
||||||
from redbot.core.drivers.red_json import JSON
|
from redbot.core.drivers.red_json import JSON
|
||||||
@@ -1077,20 +1087,19 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
if basic_config["STORAGE_TYPE"] == "MongoDB":
|
if basic_config["STORAGE_TYPE"] == "MongoDB":
|
||||||
from redbot.core.drivers.red_mongo import Mongo
|
from redbot.core.drivers.red_mongo import Mongo
|
||||||
|
|
||||||
m = Mongo("Core", **basic_config["STORAGE_DETAILS"])
|
m = Mongo("Core", "0", **basic_config["STORAGE_DETAILS"])
|
||||||
db = m.db
|
db = m.db
|
||||||
collection_names = await db.collection_names(include_system_collections=False)
|
collection_names = await db.list_collection_names()
|
||||||
for c_name in collection_names:
|
for c_name in collection_names:
|
||||||
if c_name == "Core":
|
if c_name == "Core":
|
||||||
c_data_path = data_dir / basic_config["CORE_PATH_APPEND"]
|
c_data_path = data_dir / basic_config["CORE_PATH_APPEND"]
|
||||||
else:
|
else:
|
||||||
c_data_path = data_dir / basic_config["COG_PATH_APPEND"]
|
c_data_path = data_dir / basic_config["COG_PATH_APPEND"] / c_name
|
||||||
output = {}
|
|
||||||
docs = await db[c_name].find().to_list(None)
|
docs = await db[c_name].find().to_list(None)
|
||||||
for item in docs:
|
for item in docs:
|
||||||
item_id = str(item.pop("_id"))
|
item_id = str(item.pop("_id"))
|
||||||
output[item_id] = item
|
output = item
|
||||||
target = JSON(c_name, data_path_override=c_data_path)
|
target = JSON(c_name, item_id, data_path_override=c_data_path)
|
||||||
await target.jsonIO._threadsafe_save_json(output)
|
await target.jsonIO._threadsafe_save_json(output)
|
||||||
backup_filename = "redv3-{}-{}.tar.gz".format(
|
backup_filename = "redv3-{}-{}.tar.gz".format(
|
||||||
instance_name, ctx.message.created_at.strftime("%Y-%m-%d %H-%M-%S")
|
instance_name, ctx.message.created_at.strftime("%Y-%m-%d %H-%M-%S")
|
||||||
@@ -1131,8 +1140,11 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
tar.add(str(f), recursive=False)
|
tar.add(str(f), recursive=False)
|
||||||
print(str(backup_file))
|
print(str(backup_file))
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("A backup has been made of this instance. It is at {}.").format((backup_file))
|
_("A backup has been made of this instance. It is at {}.").format(backup_file)
|
||||||
)
|
)
|
||||||
|
if backup_file.stat().st_size > 8_000_000:
|
||||||
|
await ctx.send(_("This backup is to large to send via DM."))
|
||||||
|
return
|
||||||
await ctx.send(_("Would you like to receive a copy via DM? (y/n)"))
|
await ctx.send(_("Would you like to receive a copy via DM? (y/n)"))
|
||||||
|
|
||||||
pred = MessagePredicate.yes_or_no(ctx)
|
pred = MessagePredicate.yes_or_no(ctx)
|
||||||
@@ -1143,10 +1155,18 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
else:
|
else:
|
||||||
if pred.result is True:
|
if pred.result is True:
|
||||||
await ctx.send(_("OK, it's on its way!"))
|
await ctx.send(_("OK, it's on its way!"))
|
||||||
|
try:
|
||||||
async with ctx.author.typing():
|
async with ctx.author.typing():
|
||||||
await ctx.author.send(
|
await ctx.author.send(
|
||||||
_("Here's a copy of the backup"), file=discord.File(str(backup_file))
|
_("Here's a copy of the backup"),
|
||||||
|
file=discord.File(str(backup_file)),
|
||||||
)
|
)
|
||||||
|
except discord.Forbidden:
|
||||||
|
await ctx.send(
|
||||||
|
_("I don't seem to be able to DM you. Do you have closed DMs?")
|
||||||
|
)
|
||||||
|
except discord.HTTPException:
|
||||||
|
await ctx.send(_("I could not send the backup file."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("OK then."))
|
await ctx.send(_("OK then."))
|
||||||
else:
|
else:
|
||||||
@@ -1154,7 +1174,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.cooldown(1, 60, commands.BucketType.user)
|
@commands.cooldown(1, 60, commands.BucketType.user)
|
||||||
async def contact(self, ctx, *, message: str):
|
async def contact(self, ctx: commands.Context, *, message: str):
|
||||||
"""Sends a message to the owner"""
|
"""Sends a message to the owner"""
|
||||||
guild = ctx.message.guild
|
guild = ctx.message.guild
|
||||||
owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id)
|
owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id)
|
||||||
@@ -1197,7 +1217,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I cannot send your message, I'm unable to find my owner... *sigh*")
|
_("I cannot send your message, I'm unable to find my owner... *sigh*")
|
||||||
)
|
)
|
||||||
except:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Your message has been sent."))
|
await ctx.send(_("Your message has been sent."))
|
||||||
@@ -1209,14 +1229,14 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I cannot send your message, I'm unable to find my owner... *sigh*")
|
_("I cannot send your message, I'm unable to find my owner... *sigh*")
|
||||||
)
|
)
|
||||||
except:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Your message has been sent."))
|
await ctx.send(_("Your message has been sent."))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def dm(self, ctx, user_id: int, *, message: str):
|
async def dm(self, ctx: commands.Context, user_id: int, *, message: str):
|
||||||
"""Sends a DM to a user
|
"""Sends a DM to a user
|
||||||
|
|
||||||
This command needs a user id to work.
|
This command needs a user id to work.
|
||||||
@@ -1250,7 +1270,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await destination.send(embed=e)
|
await destination.send(embed=e)
|
||||||
except:
|
except discord.HTTPException:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Sorry, I couldn't deliver your message to {}").format(destination)
|
_("Sorry, I couldn't deliver your message to {}").format(destination)
|
||||||
)
|
)
|
||||||
@@ -1260,7 +1280,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
response = "{}\nMessage:\n\n{}".format(description, message)
|
response = "{}\nMessage:\n\n{}".format(description, message)
|
||||||
try:
|
try:
|
||||||
await destination.send("{}\n{}".format(box(response), content))
|
await destination.send("{}\n{}".format(box(response), content))
|
||||||
except:
|
except discord.HTTPException:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Sorry, I couldn't deliver your message to {}").format(destination)
|
_("Sorry, I couldn't deliver your message to {}").format(destination)
|
||||||
)
|
)
|
||||||
@@ -1269,7 +1289,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def whitelist(self, ctx):
|
async def whitelist(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Whitelist management commands.
|
Whitelist management commands.
|
||||||
"""
|
"""
|
||||||
@@ -1287,7 +1307,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("User added to whitelist."))
|
await ctx.send(_("User added to whitelist."))
|
||||||
|
|
||||||
@whitelist.command(name="list")
|
@whitelist.command(name="list")
|
||||||
async def whitelist_list(self, ctx):
|
async def whitelist_list(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Lists whitelisted users.
|
Lists whitelisted users.
|
||||||
"""
|
"""
|
||||||
@@ -1301,7 +1321,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@whitelist.command(name="remove")
|
@whitelist.command(name="remove")
|
||||||
async def whitelist_remove(self, ctx, user: discord.User):
|
async def whitelist_remove(self, ctx: commands.Context, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Removes user from whitelist.
|
Removes user from whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1318,7 +1338,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("User was not in the whitelist."))
|
await ctx.send(_("User was not in the whitelist."))
|
||||||
|
|
||||||
@whitelist.command(name="clear")
|
@whitelist.command(name="clear")
|
||||||
async def whitelist_clear(self, ctx):
|
async def whitelist_clear(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears the whitelist.
|
Clears the whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1327,19 +1347,19 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def blacklist(self, ctx):
|
async def blacklist(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
blacklist management commands.
|
blacklist management commands.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@blacklist.command(name="add")
|
@blacklist.command(name="add")
|
||||||
async def blacklist_add(self, ctx, user: discord.User):
|
async def blacklist_add(self, ctx: commands.Context, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Adds a user to the blacklist.
|
Adds a user to the blacklist.
|
||||||
"""
|
"""
|
||||||
if await ctx.bot.is_owner(user):
|
if await ctx.bot.is_owner(user):
|
||||||
ctx.send(_("You cannot blacklist an owner!"))
|
await ctx.send(_("You cannot blacklist an owner!"))
|
||||||
return
|
return
|
||||||
|
|
||||||
async with ctx.bot.db.blacklist() as curr_list:
|
async with ctx.bot.db.blacklist() as curr_list:
|
||||||
@@ -1349,7 +1369,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("User added to blacklist."))
|
await ctx.send(_("User added to blacklist."))
|
||||||
|
|
||||||
@blacklist.command(name="list")
|
@blacklist.command(name="list")
|
||||||
async def blacklist_list(self, ctx):
|
async def blacklist_list(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Lists blacklisted users.
|
Lists blacklisted users.
|
||||||
"""
|
"""
|
||||||
@@ -1363,7 +1383,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@blacklist.command(name="remove")
|
@blacklist.command(name="remove")
|
||||||
async def blacklist_remove(self, ctx, user: discord.User):
|
async def blacklist_remove(self, ctx: commands.Context, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Removes user from blacklist.
|
Removes user from blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1380,7 +1400,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("User was not in the blacklist."))
|
await ctx.send(_("User was not in the blacklist."))
|
||||||
|
|
||||||
@blacklist.command(name="clear")
|
@blacklist.command(name="clear")
|
||||||
async def blacklist_clear(self, ctx):
|
async def blacklist_clear(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears the blacklist.
|
Clears the blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1390,14 +1410,14 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(administrator=True)
|
@checks.admin_or_permissions(administrator=True)
|
||||||
async def localwhitelist(self, ctx):
|
async def localwhitelist(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Whitelist management commands.
|
Whitelist management commands.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@localwhitelist.command(name="add")
|
@localwhitelist.command(name="add")
|
||||||
async def localwhitelist_add(self, ctx, *, user_or_role: str):
|
async def localwhitelist_add(self, ctx: commands.Context, *, user_or_role: str):
|
||||||
"""
|
"""
|
||||||
Adds a user or role to the whitelist.
|
Adds a user or role to the whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1418,7 +1438,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Role added to whitelist."))
|
await ctx.send(_("Role added to whitelist."))
|
||||||
|
|
||||||
@localwhitelist.command(name="list")
|
@localwhitelist.command(name="list")
|
||||||
async def localwhitelist_list(self, ctx):
|
async def localwhitelist_list(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Lists whitelisted users and roles.
|
Lists whitelisted users and roles.
|
||||||
"""
|
"""
|
||||||
@@ -1432,7 +1452,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@localwhitelist.command(name="remove")
|
@localwhitelist.command(name="remove")
|
||||||
async def localwhitelist_remove(self, ctx, *, user_or_role: str):
|
async def localwhitelist_remove(self, ctx: commands.Context, *, user_or_role: str):
|
||||||
"""
|
"""
|
||||||
Removes user or role from whitelist.
|
Removes user or role from whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1462,7 +1482,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Role was not in the whitelist."))
|
await ctx.send(_("Role was not in the whitelist."))
|
||||||
|
|
||||||
@localwhitelist.command(name="clear")
|
@localwhitelist.command(name="clear")
|
||||||
async def localwhitelist_clear(self, ctx):
|
async def localwhitelist_clear(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears the whitelist.
|
Clears the whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1472,14 +1492,14 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(administrator=True)
|
@checks.admin_or_permissions(administrator=True)
|
||||||
async def localblacklist(self, ctx):
|
async def localblacklist(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
blacklist management commands.
|
blacklist management commands.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@localblacklist.command(name="add")
|
@localblacklist.command(name="add")
|
||||||
async def localblacklist_add(self, ctx, *, user_or_role: str):
|
async def localblacklist_add(self, ctx: commands.Context, *, user_or_role: str):
|
||||||
"""
|
"""
|
||||||
Adds a user or role to the blacklist.
|
Adds a user or role to the blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1492,7 +1512,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
user = True
|
user = True
|
||||||
|
|
||||||
if user and await ctx.bot.is_owner(obj):
|
if user and await ctx.bot.is_owner(obj):
|
||||||
ctx.send(_("You cannot blacklist an owner!"))
|
await ctx.send(_("You cannot blacklist an owner!"))
|
||||||
return
|
return
|
||||||
|
|
||||||
async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list:
|
async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list:
|
||||||
@@ -1505,7 +1525,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Role added to blacklist."))
|
await ctx.send(_("Role added to blacklist."))
|
||||||
|
|
||||||
@localblacklist.command(name="list")
|
@localblacklist.command(name="list")
|
||||||
async def localblacklist_list(self, ctx):
|
async def localblacklist_list(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Lists blacklisted users and roles.
|
Lists blacklisted users and roles.
|
||||||
"""
|
"""
|
||||||
@@ -1519,7 +1539,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@localblacklist.command(name="remove")
|
@localblacklist.command(name="remove")
|
||||||
async def localblacklist_remove(self, ctx, *, user_or_role: str):
|
async def localblacklist_remove(self, ctx: commands.Context, *, user_or_role: str):
|
||||||
"""
|
"""
|
||||||
Removes user or role from blacklist.
|
Removes user or role from blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1549,7 +1569,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Role was not in the blacklist."))
|
await ctx.send(_("Role was not in the blacklist."))
|
||||||
|
|
||||||
@localblacklist.command(name="clear")
|
@localblacklist.command(name="clear")
|
||||||
async def localblacklist_clear(self, ctx):
|
async def localblacklist_clear(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears the blacklist.
|
Clears the blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1689,7 +1709,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(manage_server=True)
|
@checks.guildowner_or_permissions(manage_guild=True)
|
||||||
@commands.group(name="autoimmune")
|
@commands.group(name="autoimmune")
|
||||||
async def autoimmune_group(self, ctx: commands.Context):
|
async def autoimmune_group(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import sys
|
import inspect
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List
|
|
||||||
from copy import deepcopy
|
|
||||||
import hashlib
|
|
||||||
import shutil
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from copy import deepcopy
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import appdirs
|
import appdirs
|
||||||
import tempfile
|
from discord.utils import deprecated
|
||||||
|
|
||||||
|
from . import commands
|
||||||
from .json_io import JsonIO
|
from .json_io import JsonIO
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -153,124 +153,28 @@ def core_data_path() -> Path:
|
|||||||
return core_path.resolve()
|
return core_path.resolve()
|
||||||
|
|
||||||
|
|
||||||
def _find_data_files(init_location: str) -> (Path, List[Path]):
|
# noinspection PyUnusedLocal
|
||||||
"""
|
@deprecated("bundled_data_path() without calling this function")
|
||||||
Discovers all files in the bundled data folder of an installed cog.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
init_location
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
(pathlib.Path, list of pathlib.Path)
|
|
||||||
"""
|
|
||||||
init_file = Path(init_location)
|
|
||||||
if not init_file.is_file():
|
|
||||||
return []
|
|
||||||
|
|
||||||
package_folder = init_file.parent.resolve() / "data"
|
|
||||||
if not package_folder.is_dir():
|
|
||||||
return []
|
|
||||||
|
|
||||||
all_files = list(package_folder.rglob("*"))
|
|
||||||
|
|
||||||
return package_folder, [p.resolve() for p in all_files if p.is_file()]
|
|
||||||
|
|
||||||
|
|
||||||
def _compare_and_copy(to_copy: List[Path], bundled_data_dir: Path, cog_data_dir: Path):
|
|
||||||
"""
|
|
||||||
Filters out files from ``to_copy`` that already exist, and are the
|
|
||||||
same, in ``data_dir``. The files that are different are copied into
|
|
||||||
``data_dir``.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
to_copy : list of pathlib.Path
|
|
||||||
bundled_data_dir : pathlib.Path
|
|
||||||
cog_data_dir : pathlib.Path
|
|
||||||
"""
|
|
||||||
|
|
||||||
def hash_bytestr_iter(bytesiter, hasher, ashexstr=False):
|
|
||||||
for block in bytesiter:
|
|
||||||
hasher.update(block)
|
|
||||||
return hasher.hexdigest() if ashexstr else hasher.digest()
|
|
||||||
|
|
||||||
def file_as_blockiter(afile, blocksize=65536):
|
|
||||||
with afile:
|
|
||||||
block = afile.read(blocksize)
|
|
||||||
while len(block) > 0:
|
|
||||||
yield block
|
|
||||||
block = afile.read(blocksize)
|
|
||||||
|
|
||||||
lookup = {p: cog_data_dir.joinpath(p.relative_to(bundled_data_dir)) for p in to_copy}
|
|
||||||
|
|
||||||
for orig, poss_existing in lookup.items():
|
|
||||||
if not poss_existing.is_file():
|
|
||||||
poss_existing.parent.mkdir(exist_ok=True, parents=True)
|
|
||||||
exists_checksum = None
|
|
||||||
else:
|
|
||||||
exists_checksum = hash_bytestr_iter(
|
|
||||||
file_as_blockiter(poss_existing.open("rb")), hashlib.sha256()
|
|
||||||
)
|
|
||||||
|
|
||||||
orig_checksum = ...
|
|
||||||
if exists_checksum is not None:
|
|
||||||
orig_checksum = hash_bytestr_iter(file_as_blockiter(orig.open("rb")), hashlib.sha256())
|
|
||||||
|
|
||||||
if exists_checksum != orig_checksum:
|
|
||||||
shutil.copy(str(orig), str(poss_existing))
|
|
||||||
log.debug("Copying {} to {}".format(orig, poss_existing))
|
|
||||||
|
|
||||||
|
|
||||||
def load_bundled_data(cog_instance, init_location: str):
|
def load_bundled_data(cog_instance, init_location: str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def bundled_data_path(cog_instance: commands.Cog) -> Path:
|
||||||
"""
|
"""
|
||||||
This function copies (and overwrites) data from the ``data/`` folder
|
Get the path to the "data" directory bundled with this cog.
|
||||||
of the installed cog.
|
|
||||||
|
The bundled data folder must be located alongside the ``.py`` file
|
||||||
|
which contains the cog class.
|
||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
|
|
||||||
This function MUST be called from the ``setup()`` function of your
|
You should *NEVER* write to this directory.
|
||||||
cog.
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
>>> from redbot.core import data_manager
|
|
||||||
>>>
|
|
||||||
>>> def setup(bot):
|
|
||||||
>>> cog = MyCog()
|
|
||||||
>>> data_manager.load_bundled_data(cog, __file__)
|
|
||||||
>>> bot.add_cog(cog)
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
cog_instance
|
|
||||||
An instance of your cog class.
|
|
||||||
init_location : str
|
|
||||||
The ``__file__`` attribute of the file where your ``setup()``
|
|
||||||
function exists.
|
|
||||||
"""
|
|
||||||
bundled_data_folder, to_copy = _find_data_files(init_location)
|
|
||||||
|
|
||||||
cog_data_folder = cog_data_path(cog_instance) / "bundled_data"
|
|
||||||
|
|
||||||
_compare_and_copy(to_copy, bundled_data_folder, cog_data_folder)
|
|
||||||
|
|
||||||
|
|
||||||
def bundled_data_path(cog_instance) -> Path:
|
|
||||||
"""
|
|
||||||
The "data" directory that has been copied from installed cogs.
|
|
||||||
|
|
||||||
.. important::
|
|
||||||
|
|
||||||
You should *NEVER* write to this directory. Data manager will
|
|
||||||
overwrite files in this directory each time `load_bundled_data`
|
|
||||||
is called. You should instead write to the directory provided by
|
|
||||||
`cog_data_path`.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
cog_instance
|
cog_instance
|
||||||
|
An instance of your cog. If calling from a command or method of
|
||||||
|
your cog, this should be ``self``.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -280,10 +184,10 @@ def bundled_data_path(cog_instance) -> Path:
|
|||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
FileNotFoundError
|
FileNotFoundError
|
||||||
If no bundled data folder exists or if it hasn't been loaded yet.
|
If no bundled data folder exists.
|
||||||
"""
|
|
||||||
|
|
||||||
bundled_path = cog_data_path(cog_instance) / "bundled_data"
|
"""
|
||||||
|
bundled_path = Path(inspect.getfile(cog_instance.__class__)).parent / "data"
|
||||||
|
|
||||||
if not bundled_path.is_dir():
|
if not bundled_path.is_dir():
|
||||||
raise FileNotFoundError("No such directory {}".format(bundled_path))
|
raise FileNotFoundError("No such directory {}".format(bundled_path))
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import motor.motor_asyncio
|
import re
|
||||||
from .red_base import BaseDriver
|
from typing import Match, Pattern
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
|
import motor.core
|
||||||
|
import motor.motor_asyncio
|
||||||
|
|
||||||
|
from .red_base import BaseDriver
|
||||||
|
|
||||||
__all__ = ["Mongo"]
|
__all__ = ["Mongo"]
|
||||||
|
|
||||||
|
|
||||||
@@ -9,7 +14,7 @@ _conn = None
|
|||||||
|
|
||||||
|
|
||||||
def _initialize(**kwargs):
|
def _initialize(**kwargs):
|
||||||
kwargs.get("URI", "mongodb")
|
uri = kwargs.get("URI", "mongodb")
|
||||||
host = kwargs["HOST"]
|
host = kwargs["HOST"]
|
||||||
port = kwargs["PORT"]
|
port = kwargs["PORT"]
|
||||||
admin_user = kwargs["USERNAME"]
|
admin_user = kwargs["USERNAME"]
|
||||||
@@ -80,6 +85,7 @@ class Mongo(BaseDriver):
|
|||||||
async def get(self, *identifiers: str):
|
async def get(self, *identifiers: str):
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
|
identifiers = (*map(self._escape_key, identifiers),)
|
||||||
dot_identifiers = ".".join(identifiers)
|
dot_identifiers = ".".join(identifiers)
|
||||||
|
|
||||||
partial = await mongo_collection.find_one(
|
partial = await mongo_collection.find_one(
|
||||||
@@ -91,10 +97,14 @@ class Mongo(BaseDriver):
|
|||||||
|
|
||||||
for i in identifiers:
|
for i in identifiers:
|
||||||
partial = partial[i]
|
partial = partial[i]
|
||||||
|
if isinstance(partial, dict):
|
||||||
|
return self._unescape_dict_keys(partial)
|
||||||
return partial
|
return partial
|
||||||
|
|
||||||
async def set(self, *identifiers: str, value=None):
|
async def set(self, *identifiers: str, value=None):
|
||||||
dot_identifiers = ".".join(identifiers)
|
dot_identifiers = ".".join(map(self._escape_key, identifiers))
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = self._escape_dict_keys(value)
|
||||||
|
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
@@ -105,7 +115,7 @@ class Mongo(BaseDriver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def clear(self, *identifiers: str):
|
async def clear(self, *identifiers: str):
|
||||||
dot_identifiers = ".".join(identifiers)
|
dot_identifiers = ".".join(map(self._escape_key, identifiers))
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
if len(identifiers) > 0:
|
if len(identifiers) > 0:
|
||||||
@@ -115,6 +125,62 @@ class Mongo(BaseDriver):
|
|||||||
else:
|
else:
|
||||||
await mongo_collection.delete_one({"_id": self.unique_cog_identifier})
|
await mongo_collection.delete_one({"_id": self.unique_cog_identifier})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _escape_key(key: str) -> str:
|
||||||
|
return _SPECIAL_CHAR_PATTERN.sub(_replace_with_escaped, key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _unescape_key(key: str) -> str:
|
||||||
|
return _CHAR_ESCAPE_PATTERN.sub(_replace_with_unescaped, key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _escape_dict_keys(cls, data: dict) -> dict:
|
||||||
|
"""Recursively escape all keys in a dict."""
|
||||||
|
ret = {}
|
||||||
|
for key, value in data.items():
|
||||||
|
key = cls._escape_key(key)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = cls._escape_dict_keys(value)
|
||||||
|
ret[key] = value
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _unescape_dict_keys(cls, data: dict) -> dict:
|
||||||
|
"""Recursively unescape all keys in a dict."""
|
||||||
|
ret = {}
|
||||||
|
for key, value in data.items():
|
||||||
|
key = cls._unescape_key(key)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = cls._unescape_dict_keys(value)
|
||||||
|
ret[key] = value
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
_SPECIAL_CHAR_PATTERN: Pattern[str] = re.compile(r"([.$]|\\U0000002E|\\U00000024)")
|
||||||
|
_SPECIAL_CHARS = {
|
||||||
|
".": "\\U0000002E",
|
||||||
|
"$": "\\U00000024",
|
||||||
|
"\\U0000002E": "\\U&0000002E",
|
||||||
|
"\\U00000024": "\\U&00000024",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _replace_with_escaped(match: Match[str]) -> str:
|
||||||
|
return _SPECIAL_CHARS[match[0]]
|
||||||
|
|
||||||
|
|
||||||
|
_CHAR_ESCAPE_PATTERN: Pattern[str] = re.compile(r"(\\U0000002E|\\U00000024)")
|
||||||
|
_CHAR_ESCAPES = {
|
||||||
|
"\\U0000002E": ".",
|
||||||
|
"\\U00000024": "$",
|
||||||
|
"\\U&0000002E": "\\U0000002E",
|
||||||
|
"\\U&00000024": "\\U00000024",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _replace_with_unescaped(match: Match[str]) -> str:
|
||||||
|
return _CHAR_ESCAPES[match[0]]
|
||||||
|
|
||||||
|
|
||||||
def get_config_details():
|
def get_config_details():
|
||||||
uri = None
|
uri = None
|
||||||
|
|||||||
44
redbot/core/errors.py
Normal file
44
redbot/core/errors.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import importlib.machinery
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import discord
|
||||||
|
|
||||||
|
from .i18n import Translator
|
||||||
|
|
||||||
|
_ = Translator(__name__, __file__)
|
||||||
|
|
||||||
|
|
||||||
|
class RedError(Exception):
|
||||||
|
"""Base error class for Red-related errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class PackageAlreadyLoaded(RedError):
|
||||||
|
"""Raised when trying to load an already-loaded package."""
|
||||||
|
|
||||||
|
def __init__(self, spec: importlib.machinery.ModuleSpec, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.spec: importlib.machinery.ModuleSpec = spec
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"There is already a package named {self.spec.name.split('.')[-1]} loaded"
|
||||||
|
|
||||||
|
|
||||||
|
class BankError(RedError):
|
||||||
|
"""Base error class for bank-related errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class BalanceTooHigh(BankError, OverflowError):
|
||||||
|
"""Raised when trying to set a user's balance to higher than the maximum."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, user: discord.abc.User, max_balance: int, currency_name: str, *args, **kwargs
|
||||||
|
):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.user = user
|
||||||
|
self.max_balance = max_balance
|
||||||
|
self.currency_name = currency_name
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return _("{user}'s balance cannot rise above {max:,} {currency}.").format(
|
||||||
|
user=self.user, max=self.max_balance, currency=self.currency_name
|
||||||
|
)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import contextlib
|
||||||
import sys
|
import sys
|
||||||
import codecs
|
import codecs
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from distutils.version import StrictVersion
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -13,9 +13,9 @@ import pkg_resources
|
|||||||
from colorama import Fore, Style, init
|
from colorama import Fore, Style, init
|
||||||
from pkg_resources import DistributionNotFound
|
from pkg_resources import DistributionNotFound
|
||||||
|
|
||||||
from . import __version__, commands
|
from . import __version__ as red_version, version_info as red_version_info, VersionInfo, commands
|
||||||
from .data_manager import storage_type
|
from .data_manager import storage_type
|
||||||
from .utils.chat_formatting import inline, bordered, humanize_list
|
from .utils.chat_formatting import inline, bordered, format_perms_list
|
||||||
from .utils import fuzzy_command_search, format_fuzzy_results
|
from .utils import fuzzy_command_search, format_fuzzy_results
|
||||||
|
|
||||||
log = logging.getLogger("red")
|
log = logging.getLogger("red")
|
||||||
@@ -105,7 +105,6 @@ def init_events(bot, cli_flags):
|
|||||||
|
|
||||||
prefixes = cli_flags.prefix or (await bot.db.prefix())
|
prefixes = cli_flags.prefix or (await bot.db.prefix())
|
||||||
lang = await bot.db.locale()
|
lang = await bot.db.locale()
|
||||||
red_version = __version__
|
|
||||||
red_pkg = pkg_resources.get_distribution("Red-DiscordBot")
|
red_pkg = pkg_resources.get_distribution("Red-DiscordBot")
|
||||||
dpy_version = discord.__version__
|
dpy_version = discord.__version__
|
||||||
|
|
||||||
@@ -125,24 +124,22 @@ def init_events(bot, cli_flags):
|
|||||||
|
|
||||||
INFO.append("{} cogs with {} commands".format(len(bot.cogs), len(bot.commands)))
|
INFO.append("{} cogs with {} commands".format(len(bot.cogs), len(bot.commands)))
|
||||||
|
|
||||||
try:
|
with contextlib.suppress(aiohttp.ClientError, discord.HTTPException):
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get("https://pypi.python.org/pypi/red-discordbot/json") as r:
|
async with session.get("https://pypi.python.org/pypi/red-discordbot/json") as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
if StrictVersion(data["info"]["version"]) > StrictVersion(red_version):
|
if VersionInfo.from_str(data["info"]["version"]) > red_version_info:
|
||||||
INFO.append(
|
INFO.append(
|
||||||
"Outdated version! {} is available "
|
"Outdated version! {} is available "
|
||||||
"but you're using {}".format(data["info"]["version"], red_version)
|
"but you're using {}".format(data["info"]["version"], red_version)
|
||||||
)
|
)
|
||||||
owner = discord.utils.get(bot.get_all_members(), id=bot.owner_id)
|
owner = await bot.get_user_info(bot.owner_id)
|
||||||
await owner.send(
|
await owner.send(
|
||||||
"Your Red instance is out of date! {} is the current "
|
"Your Red instance is out of date! {} is the current "
|
||||||
"version, however you are using {}!".format(
|
"version, however you are using {}!".format(
|
||||||
data["info"]["version"], red_version
|
data["info"]["version"], red_version
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
INFO2 = []
|
INFO2 = []
|
||||||
|
|
||||||
sentry = await bot.db.enable_sentry()
|
sentry = await bot.db.enable_sentry()
|
||||||
@@ -237,18 +234,13 @@ def init_events(bot, cli_flags):
|
|||||||
else:
|
else:
|
||||||
await ctx.send(await format_fuzzy_results(ctx, fuzzy_commands, embed=False))
|
await ctx.send(await format_fuzzy_results(ctx, fuzzy_commands, embed=False))
|
||||||
elif isinstance(error, commands.BotMissingPermissions):
|
elif isinstance(error, commands.BotMissingPermissions):
|
||||||
missing_perms: List[str] = []
|
if bin(error.missing.value).count("1") == 1: # Only one perm missing
|
||||||
for perm, value in error.missing:
|
|
||||||
if value is True:
|
|
||||||
perm_name = '"' + perm.replace("_", " ").title() + '"'
|
|
||||||
missing_perms.append(perm_name)
|
|
||||||
if len(missing_perms) == 1:
|
|
||||||
plural = ""
|
plural = ""
|
||||||
else:
|
else:
|
||||||
plural = "s"
|
plural = "s"
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"I require the {perms} permission{plural} to execute that command.".format(
|
"I require the {perms} permission{plural} to execute that command.".format(
|
||||||
perms=humanize_list(missing_perms), plural=plural
|
perms=format_perms_list(error.missing), plural=plural
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif isinstance(error, commands.CheckFailure):
|
elif isinstance(error, commands.CheckFailure):
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ discord.py 1.0.0a
|
|||||||
|
|
||||||
This help formatter contains work by Rapptz (Danny) and SirThane#1780.
|
This help formatter contains work by Rapptz (Danny) and SirThane#1780.
|
||||||
"""
|
"""
|
||||||
|
import contextlib
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
@@ -224,8 +225,8 @@ class Help(dpy_formatter.HelpFormatter):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def format_help_for(self, ctx, command_or_bot, reason: str = None):
|
async def format_help_for(self, ctx, command_or_bot, reason: str = ""):
|
||||||
"""Formats the help page and handles the actual heavy lifting of how ### WTF HAPPENED?
|
"""Formats the help page and handles the actual heavy lifting of how
|
||||||
the help command looks like. To change the behaviour, override the
|
the help command looks like. To change the behaviour, override the
|
||||||
:meth:`~.HelpFormatter.format` method.
|
:meth:`~.HelpFormatter.format` method.
|
||||||
|
|
||||||
@@ -244,10 +245,24 @@ class Help(dpy_formatter.HelpFormatter):
|
|||||||
"""
|
"""
|
||||||
self.context = ctx
|
self.context = ctx
|
||||||
self.command = command_or_bot
|
self.command = command_or_bot
|
||||||
|
|
||||||
|
# We want the permission state to be set as if the author had run the command he is
|
||||||
|
# requesting help for. This is so the subcommands shown in the help menu correctly reflect
|
||||||
|
# any permission rules set.
|
||||||
|
if isinstance(self.command, commands.Command):
|
||||||
|
with contextlib.suppress(commands.CommandError):
|
||||||
|
await self.command.can_run(
|
||||||
|
self.context, check_all_parents=True, change_permission_state=True
|
||||||
|
)
|
||||||
|
elif isinstance(self.command, commands.Cog):
|
||||||
|
with contextlib.suppress(commands.CommandError):
|
||||||
|
# Cog's don't have a `can_run` method, so we use the `Requires` object directly.
|
||||||
|
await self.command.requires.verify(self.context)
|
||||||
|
|
||||||
emb = await self.format()
|
emb = await self.format()
|
||||||
|
|
||||||
if reason:
|
if reason:
|
||||||
emb["embed"]["title"] = "{0}".format(reason)
|
emb["embed"]["title"] = reason
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import re
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Union
|
from typing import Callable, Union
|
||||||
|
|
||||||
from . import commands
|
|
||||||
|
|
||||||
__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"]
|
__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"]
|
||||||
|
|
||||||
_current_locale = "en_us"
|
_current_locale = "en_us"
|
||||||
@@ -219,6 +217,12 @@ class Translator(Callable[[str], str]):
|
|||||||
self.translations.update({untranslated: translated})
|
self.translations.update({untranslated: translated})
|
||||||
|
|
||||||
|
|
||||||
|
# This import to be down here to avoid circular import issues.
|
||||||
|
# This will be cleaned up at a later date
|
||||||
|
# noinspection PyPep8
|
||||||
|
from . import commands
|
||||||
|
|
||||||
|
|
||||||
def cog_i18n(translator: Translator):
|
def cog_i18n(translator: Translator):
|
||||||
"""Get a class decorator to link the translator to this cog."""
|
"""Get a class decorator to link the translator to this cog."""
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from copy import deepcopy
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
# This is basically our old DataIO and just a base for much more elaborate classes
|
# This is basically our old DataIO and just a base for much more elaborate classes
|
||||||
@@ -69,7 +70,11 @@ class JsonIO:
|
|||||||
|
|
||||||
async def _threadsafe_save_json(self, data, settings=PRETTY):
|
async def _threadsafe_save_json(self, data, settings=PRETTY):
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
func = functools.partial(self._save_json, data, settings)
|
# the deepcopy is needed here. otherwise,
|
||||||
|
# the dict can change during serialization
|
||||||
|
# and this will break the encoder.
|
||||||
|
data_copy = deepcopy(data)
|
||||||
|
func = functools.partial(self._save_json, data_copy, settings)
|
||||||
async with self._lock:
|
async with self._lock:
|
||||||
await loop.run_in_executor(None, func)
|
await loop.run_in_executor(None, func)
|
||||||
|
|
||||||
|
|||||||
@@ -666,29 +666,30 @@ async def register_casetypes(new_types: List[dict]) -> List[CaseType]:
|
|||||||
return type_list
|
return type_list
|
||||||
|
|
||||||
|
|
||||||
async def get_modlog_channel(guild: discord.Guild) -> Union[discord.TextChannel, None]:
|
async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel:
|
||||||
"""
|
"""
|
||||||
Get the current modlog channel
|
Get the current modlog channel.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
guild: `discord.Guild`
|
guild: `discord.Guild`
|
||||||
The guild to get the modlog channel for
|
The guild to get the modlog channel for.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
`discord.TextChannel` or `None`
|
`discord.TextChannel`
|
||||||
The channel object representing the modlog channel
|
The channel object representing the modlog channel.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the modlog channel is not found
|
If the modlog channel is not found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if hasattr(guild, "get_channel"):
|
if hasattr(guild, "get_channel"):
|
||||||
channel = guild.get_channel(await _conf.guild(guild).mod_log())
|
channel = guild.get_channel(await _conf.guild(guild).mod_log())
|
||||||
else:
|
else:
|
||||||
|
# For unit tests only
|
||||||
channel = await _conf.guild(guild).mod_log()
|
channel = await _conf.guild(guild).mod_log()
|
||||||
if channel is None:
|
if channel is None:
|
||||||
raise RuntimeError("Failed to get the mod log channel!")
|
raise RuntimeError("Failed to get the mod log channel!")
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import itertools
|
import itertools
|
||||||
from typing import Sequence, Iterator, List
|
from typing import Sequence, Iterator, List
|
||||||
|
|
||||||
|
import discord
|
||||||
|
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
_ = Translator("UtilsChatFormatting", __file__)
|
_ = Translator("UtilsChatFormatting", __file__)
|
||||||
@@ -329,7 +332,7 @@ def escape(text: str, *, mass_mentions: bool = False, formatting: bool = False)
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def humanize_list(items: Sequence[str]):
|
def humanize_list(items: Sequence[str]) -> str:
|
||||||
"""Get comma-separted list, with the last element joined with *and*.
|
"""Get comma-separted list, with the last element joined with *and*.
|
||||||
|
|
||||||
This uses an Oxford comma, because without one, items containing
|
This uses an Oxford comma, because without one, items containing
|
||||||
@@ -357,3 +360,29 @@ def humanize_list(items: Sequence[str]):
|
|||||||
if len(items) == 1:
|
if len(items) == 1:
|
||||||
return items[0]
|
return items[0]
|
||||||
return ", ".join(items[:-1]) + _(", and ") + items[-1]
|
return ", ".join(items[:-1]) + _(", and ") + items[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def format_perms_list(perms: discord.Permissions) -> str:
|
||||||
|
"""Format a list of permission names.
|
||||||
|
|
||||||
|
This will return a humanized list of the names of all enabled
|
||||||
|
permissions in the provided `discord.Permissions` object.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
perms : discord.Permissions
|
||||||
|
The permissions object with the requested permissions to list
|
||||||
|
enabled.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
The humanized list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
perm_names: List[str] = []
|
||||||
|
for perm, value in perms:
|
||||||
|
if value is True:
|
||||||
|
perm_name = '"' + perm.replace("_", " ").title() + '"'
|
||||||
|
perm_names.append(perm_name)
|
||||||
|
return humanize_list(perm_names).replace("Guild", "Server")
|
||||||
|
|||||||
@@ -73,10 +73,13 @@ async def menu(
|
|||||||
# noinspection PyAsyncCall
|
# noinspection PyAsyncCall
|
||||||
start_adding_reactions(message, controls.keys(), ctx.bot.loop)
|
start_adding_reactions(message, controls.keys(), ctx.bot.loop)
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
if isinstance(current_page, discord.Embed):
|
if isinstance(current_page, discord.Embed):
|
||||||
await message.edit(embed=current_page)
|
await message.edit(embed=current_page)
|
||||||
else:
|
else:
|
||||||
await message.edit(content=current_page)
|
await message.edit(content=current_page)
|
||||||
|
except discord.NotFound:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
react, user = await ctx.bot.wait_for(
|
react, user = await ctx.bot.wait_for(
|
||||||
@@ -90,9 +93,12 @@ async def menu(
|
|||||||
except discord.Forbidden: # cannot remove all reactions
|
except discord.Forbidden: # cannot remove all reactions
|
||||||
for key in controls.keys():
|
for key in controls.keys():
|
||||||
await message.remove_reaction(key, ctx.bot.user)
|
await message.remove_reaction(key, ctx.bot.user)
|
||||||
return None
|
except discord.NotFound:
|
||||||
|
return
|
||||||
return await controls[react.emoji](ctx, pages, controls, message, page, timeout, react.emoji)
|
else:
|
||||||
|
return await controls[react.emoji](
|
||||||
|
ctx, pages, controls, message, page, timeout, react.emoji
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def next_page(
|
async def next_page(
|
||||||
@@ -106,10 +112,8 @@ async def next_page(
|
|||||||
):
|
):
|
||||||
perms = message.channel.permissions_for(ctx.me)
|
perms = message.channel.permissions_for(ctx.me)
|
||||||
if perms.manage_messages: # Can manage messages, so remove react
|
if perms.manage_messages: # Can manage messages, so remove react
|
||||||
try:
|
with contextlib.suppress(discord.NotFound):
|
||||||
await message.remove_reaction(emoji, ctx.author)
|
await message.remove_reaction(emoji, ctx.author)
|
||||||
except discord.NotFound:
|
|
||||||
pass
|
|
||||||
if page == len(pages) - 1:
|
if page == len(pages) - 1:
|
||||||
page = 0 # Loop around to the first item
|
page = 0 # Loop around to the first item
|
||||||
else:
|
else:
|
||||||
@@ -128,10 +132,8 @@ async def prev_page(
|
|||||||
):
|
):
|
||||||
perms = message.channel.permissions_for(ctx.me)
|
perms = message.channel.permissions_for(ctx.me)
|
||||||
if perms.manage_messages: # Can manage messages, so remove react
|
if perms.manage_messages: # Can manage messages, so remove react
|
||||||
try:
|
with contextlib.suppress(discord.NotFound):
|
||||||
await message.remove_reaction(emoji, ctx.author)
|
await message.remove_reaction(emoji, ctx.author)
|
||||||
except discord.NotFound:
|
|
||||||
pass
|
|
||||||
if page == 0:
|
if page == 0:
|
||||||
page = len(pages) - 1 # Loop around to the last item
|
page = len(pages) - 1 # Loop around to the last item
|
||||||
else:
|
else:
|
||||||
@@ -148,9 +150,8 @@ async def close_menu(
|
|||||||
timeout: float,
|
timeout: float,
|
||||||
emoji: str,
|
emoji: str,
|
||||||
):
|
):
|
||||||
if message:
|
with contextlib.suppress(discord.NotFound):
|
||||||
await message.delete()
|
await message.delete()
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def start_adding_reactions(
|
def start_adding_reactions(
|
||||||
@@ -161,7 +162,7 @@ def start_adding_reactions(
|
|||||||
"""Start adding reactions to a message.
|
"""Start adding reactions to a message.
|
||||||
|
|
||||||
This is a non-blocking operation - calling this will schedule the
|
This is a non-blocking operation - calling this will schedule the
|
||||||
reactions being added, but will the calling code will continue to
|
reactions being added, but the calling code will continue to
|
||||||
execute asynchronously. There is no need to await this function.
|
execute asynchronously. There is no need to await this function.
|
||||||
|
|
||||||
This is particularly useful if you wish to start waiting for a
|
This is particularly useful if you wish to start waiting for a
|
||||||
@@ -169,7 +170,7 @@ def start_adding_reactions(
|
|||||||
this is exactly what `menu` uses to do that.
|
this is exactly what `menu` uses to do that.
|
||||||
|
|
||||||
This spawns a `asyncio.Task` object and schedules it on ``loop``.
|
This spawns a `asyncio.Task` object and schedules it on ``loop``.
|
||||||
If ``loop`` omitted, the loop will be retreived with
|
If ``loop`` omitted, the loop will be retrieved with
|
||||||
`asyncio.get_event_loop`.
|
`asyncio.get_event_loop`.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import discord
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from redbot.core.utils.chat_formatting import pagify
|
from redbot.core.utils.chat_formatting import pagify
|
||||||
import io
|
import io
|
||||||
import sys
|
|
||||||
import weakref
|
import weakref
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from .common_filters import filter_mass_mentions
|
from .common_filters import filter_mass_mentions
|
||||||
@@ -151,14 +150,11 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
files = []
|
files = []
|
||||||
size = 0
|
max_size = 8 * 1000 * 1000
|
||||||
max_size = 8 * 1024 * 1024
|
if m.attachments and sum(a.size for a in m.attachments) <= max_size:
|
||||||
for a in m.attachments:
|
for a in m.attachments:
|
||||||
_fp = io.BytesIO()
|
_fp = io.BytesIO()
|
||||||
await a.save(_fp)
|
await a.save(_fp)
|
||||||
size += sys.getsizeof(_fp)
|
|
||||||
if size > max_size:
|
|
||||||
return []
|
|
||||||
files.append(discord.File(_fp, filename=a.filename))
|
files.append(discord.File(_fp, filename=a.filename))
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
|||||||
@@ -8,18 +8,14 @@ import asyncio
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
from pathlib import Path
|
|
||||||
from distutils.version import StrictVersion
|
|
||||||
from redbot.setup import (
|
from redbot.setup import (
|
||||||
basic_setup,
|
basic_setup,
|
||||||
load_existing_config,
|
load_existing_config,
|
||||||
remove_instance,
|
remove_instance,
|
||||||
remove_instance_interaction,
|
remove_instance_interaction,
|
||||||
create_backup,
|
create_backup,
|
||||||
save_config,
|
|
||||||
)
|
)
|
||||||
from redbot.core import __version__
|
from redbot.core import __version__, version_info as red_version_info, VersionInfo
|
||||||
from redbot.core.utils import safe_delete
|
|
||||||
from redbot.core.cli import confirm
|
from redbot.core.cli import confirm
|
||||||
|
|
||||||
if sys.platform == "linux":
|
if sys.platform == "linux":
|
||||||
@@ -390,7 +386,7 @@ async def is_outdated():
|
|||||||
async with session.get("{}/json".format(red_pypi)) as r:
|
async with session.get("{}/json".format(red_pypi)) as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
new_version = data["info"]["version"]
|
new_version = data["info"]["version"]
|
||||||
return StrictVersion(new_version) > StrictVersion(__version__), new_version
|
return VersionInfo.from_str(new_version) > red_version_info, new_version
|
||||||
|
|
||||||
|
|
||||||
def main_menu():
|
def main_menu():
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
@@ -177,26 +176,21 @@ def basic_setup():
|
|||||||
async def json_to_mongo(current_data_dir: Path, storage_details: dict):
|
async def json_to_mongo(current_data_dir: Path, storage_details: dict):
|
||||||
from redbot.core.drivers.red_mongo import Mongo
|
from redbot.core.drivers.red_mongo import Mongo
|
||||||
|
|
||||||
core_data_file = list(current_data_dir.glob("core/settings.json"))[0]
|
core_data_file = current_data_dir / "core" / "settings.json"
|
||||||
m = Mongo("Core", "0", **storage_details)
|
driver = Mongo(cog_name="Core", identifier="0", **storage_details)
|
||||||
with core_data_file.open(mode="r") as f:
|
with core_data_file.open(mode="r") as f:
|
||||||
core_data = json.loads(f.read())
|
core_data = json.loads(f.read())
|
||||||
collection = m.get_collection()
|
data = core_data.get("0", {})
|
||||||
await collection.update_one(
|
for key, value in data.items():
|
||||||
{"_id": m.unique_cog_identifier}, update={"$set": core_data["0"]}, upsert=True
|
await driver.set(key, value=value)
|
||||||
)
|
|
||||||
for p in current_data_dir.glob("cogs/**/settings.json"):
|
for p in current_data_dir.glob("cogs/**/settings.json"):
|
||||||
|
cog_name = p.parent.stem
|
||||||
with p.open(mode="r") as f:
|
with p.open(mode="r") as f:
|
||||||
cog_data = json.loads(f.read())
|
cog_data = json.load(f)
|
||||||
cog_i = None
|
for identifier, data in cog_data.items():
|
||||||
for ident in list(cog_data.keys()):
|
driver = Mongo(cog_name, identifier, **storage_details)
|
||||||
cog_i = str(hash(ident))
|
for key, value in data.items():
|
||||||
cog_m = Mongo(p.parent.stem, cog_i, **storage_details)
|
await driver.set(key, value=value)
|
||||||
cog_c = cog_m.get_collection()
|
|
||||||
for ident in list(cog_data.keys()):
|
|
||||||
await cog_c.update_one(
|
|
||||||
{"_id": cog_m.unique_cog_identifier}, update={"$set": cog_data[cog_i]}, upsert=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def mongo_to_json(current_data_dir: Path, storage_details: dict):
|
async def mongo_to_json(current_data_dir: Path, storage_details: dict):
|
||||||
|
|||||||
50
setup.py
50
setup.py
@@ -9,59 +9,59 @@ install_requires = [
|
|||||||
"aiohttp-json-rpc==0.11.2",
|
"aiohttp-json-rpc==0.11.2",
|
||||||
"aiohttp==3.4.4",
|
"aiohttp==3.4.4",
|
||||||
"appdirs==1.4.3",
|
"appdirs==1.4.3",
|
||||||
"async-timeout==3.0.0",
|
"async-timeout==3.0.1",
|
||||||
"attrs==18.2.0",
|
"attrs==18.2.0",
|
||||||
"chardet==3.0.4",
|
"chardet==3.0.4",
|
||||||
"colorama==0.3.9",
|
"colorama==0.4.1",
|
||||||
"discord.py>=1.0.0a0",
|
"discord.py>=1.0.0a0",
|
||||||
"distro==1.3.0; sys_platform == 'linux'",
|
"distro==1.3.0; sys_platform == 'linux'",
|
||||||
"fuzzywuzzy==0.17.0",
|
"fuzzywuzzy==0.17.0",
|
||||||
"idna-ssl==1.1.0",
|
"idna-ssl==1.1.0",
|
||||||
"idna==2.7",
|
"idna==2.8",
|
||||||
"multidict==4.4.2",
|
"multidict==4.5.2",
|
||||||
"python-levenshtein==0.12.0",
|
"python-levenshtein==0.12.0",
|
||||||
"pyyaml==3.13",
|
"pyyaml==3.13",
|
||||||
"raven==6.9.0",
|
"raven==6.10.0",
|
||||||
"raven-aiohttp==0.7.0",
|
"raven-aiohttp==0.7.0",
|
||||||
"schema==0.6.8",
|
"schema==0.6.8",
|
||||||
"websockets==6.0",
|
"websockets==6.0",
|
||||||
"yarl==1.2.6",
|
"yarl==1.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
extras_require = {
|
extras_require = {
|
||||||
"test": [
|
"test": [
|
||||||
"atomicwrites==1.2.1",
|
"atomicwrites==1.2.1",
|
||||||
"more-itertools==4.3.0",
|
"more-itertools==5.0.0",
|
||||||
"pluggy==0.7.1",
|
"pluggy==0.8.1",
|
||||||
"py==1.6.0",
|
"py==1.7.0",
|
||||||
"pytest==3.8.2",
|
"pytest==4.1.0",
|
||||||
"pytest-asyncio==0.9.0",
|
"pytest-asyncio==0.10.0",
|
||||||
"six==1.11.0",
|
"six==1.12.0",
|
||||||
],
|
],
|
||||||
"mongo": ["motor==2.0.0", "pymongo==3.7.1", "dnspython==1.15.0"],
|
"mongo": ["motor==2.0.0", "pymongo==3.7.2", "dnspython==1.16.0"],
|
||||||
"docs": [
|
"docs": [
|
||||||
"alabaster==0.7.11",
|
"alabaster==0.7.12",
|
||||||
"babel==2.6.0",
|
"babel==2.6.0",
|
||||||
"certifi==2018.8.24",
|
"certifi==2018.11.29",
|
||||||
"docutils==0.14",
|
"docutils==0.14",
|
||||||
"imagesize==1.1.0",
|
"imagesize==1.1.0",
|
||||||
"Jinja2==2.10",
|
"Jinja2==2.10",
|
||||||
"MarkupSafe==1.0",
|
"MarkupSafe==1.1.0",
|
||||||
"packaging==18.0",
|
"packaging==18.0",
|
||||||
"pyparsing==2.2.2",
|
"pyparsing==2.3.0",
|
||||||
"Pygments==2.2.0",
|
"Pygments==2.3.1",
|
||||||
"pytz==2018.5",
|
"pytz==2018.9",
|
||||||
"requests==2.19.1",
|
"requests==2.21.0",
|
||||||
"urllib3==1.23",
|
"six==1.12.0",
|
||||||
"six==1.11.0",
|
|
||||||
"snowballstemmer==1.2.1",
|
"snowballstemmer==1.2.1",
|
||||||
"sphinx==1.7.9",
|
"sphinx==1.8.3",
|
||||||
"sphinx_rtd_theme==0.4.1",
|
"sphinx_rtd_theme==0.4.2",
|
||||||
"sphinxcontrib-asyncio==0.2.0",
|
"sphinxcontrib-asyncio==0.2.0",
|
||||||
"sphinxcontrib-websupport==1.1.0",
|
"sphinxcontrib-websupport==1.1.0",
|
||||||
|
"urllib3==1.24.1",
|
||||||
],
|
],
|
||||||
"voice": ["red-lavalink==0.1.2"],
|
"voice": ["red-lavalink==0.1.2"],
|
||||||
"style": ["black==18.9b0", "click==7.0", "toml==0.9.6"],
|
"style": ["black==18.9b0", "click==7.0", "toml==0.10.0"],
|
||||||
}
|
}
|
||||||
|
|
||||||
python_requires = ">=3.6.2,<3.8"
|
python_requires = ">=3.6.2,<3.8"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from redbot.cogs.permissions.permissions import Permissions, GLOBAL
|
|||||||
|
|
||||||
def test_schema_update():
|
def test_schema_update():
|
||||||
old = {
|
old = {
|
||||||
GLOBAL: {
|
str(GLOBAL): {
|
||||||
"owner_models": {
|
"owner_models": {
|
||||||
"cogs": {
|
"cogs": {
|
||||||
"Admin": {"allow": [78631113035100160], "deny": [96733288462286848]},
|
"Admin": {"allow": [78631113035100160], "deny": [96733288462286848]},
|
||||||
@@ -19,7 +19,7 @@ def test_schema_update():
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
43733288462286848: {
|
"43733288462286848": {
|
||||||
"owner_models": {
|
"owner_models": {
|
||||||
"cogs": {
|
"cogs": {
|
||||||
"Admin": {
|
"Admin": {
|
||||||
@@ -43,22 +43,22 @@ def test_schema_update():
|
|||||||
assert new == (
|
assert new == (
|
||||||
{
|
{
|
||||||
"Admin": {
|
"Admin": {
|
||||||
GLOBAL: {78631113035100160: True, 96733288462286848: False},
|
str(GLOBAL): {"78631113035100160": True, "96733288462286848": False},
|
||||||
43733288462286848: {24231113035100160: True, 35533288462286848: False},
|
"43733288462286848": {"24231113035100160": True, "35533288462286848": False},
|
||||||
},
|
},
|
||||||
"Audio": {GLOBAL: {133049272517001216: True, "default": False}},
|
"Audio": {str(GLOBAL): {"133049272517001216": True, "default": False}},
|
||||||
"General": {43733288462286848: {133049272517001216: True, "default": False}},
|
"General": {"43733288462286848": {"133049272517001216": True, "default": False}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cleanup bot": {
|
"cleanup bot": {
|
||||||
GLOBAL: {78631113035100160: True, "default": False},
|
str(GLOBAL): {"78631113035100160": True, "default": False},
|
||||||
43733288462286848: {17831113035100160: True, "default": True},
|
"43733288462286848": {"17831113035100160": True, "default": True},
|
||||||
},
|
},
|
||||||
"ping": {GLOBAL: {96733288462286848: True, "default": True}},
|
"ping": {str(GLOBAL): {"96733288462286848": True, "default": True}},
|
||||||
"set adminrole": {
|
"set adminrole": {
|
||||||
43733288462286848: {
|
"43733288462286848": {
|
||||||
87733288462286848: True,
|
"87733288462286848": True,
|
||||||
95433288462286848: False,
|
"95433288462286848": False,
|
||||||
"default": True,
|
"default": True,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ def test_trivia_lists():
|
|||||||
for l in list_names:
|
for l in list_names:
|
||||||
with l.open() as f:
|
with l.open() as f:
|
||||||
try:
|
try:
|
||||||
dict_ = yaml.load(f)
|
dict_ = yaml.safe_load(f)
|
||||||
except yaml.error.YAMLError as e:
|
except yaml.error.YAMLError as e:
|
||||||
problem_lists.append((l.stem, "YAML error:\n{!s}".format(e)))
|
problem_lists.append((l.stem, "YAML error:\n{!s}".format(e)))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -475,3 +475,18 @@ async def test_get_raw_mixes_defaults(config):
|
|||||||
|
|
||||||
subgroup = await config.get_raw("subgroup")
|
subgroup = await config.get_raw("subgroup")
|
||||||
assert subgroup == {"foo": True, "bar": False}
|
assert subgroup == {"foo": True, "bar": False}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_cast_str_raw(config):
|
||||||
|
await config.set_raw(123, 456, value=True)
|
||||||
|
assert await config.get_raw(123, 456) is True
|
||||||
|
assert await config.get_raw("123", "456") is True
|
||||||
|
await config.clear_raw("123", 456)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_cast_str_nested(config):
|
||||||
|
config.register_global(foo={})
|
||||||
|
await config.foo.set({123: True, 456: {789: False}})
|
||||||
|
assert await config.foo() == {"123": True, "456": {"789": False}}
|
||||||
|
|||||||
@@ -1,6 +1,36 @@
|
|||||||
from redbot import core
|
from redbot import core
|
||||||
|
from redbot.core import VersionInfo
|
||||||
|
|
||||||
|
|
||||||
def test_version_working():
|
def test_version_working():
|
||||||
assert hasattr(core, "__version__")
|
assert hasattr(core, "__version__")
|
||||||
assert core.__version__[0] == "3"
|
assert core.__version__[0] == "3"
|
||||||
|
|
||||||
|
|
||||||
|
# When adding more of these, ensure they are added in ascending order of precedence
|
||||||
|
version_tests = (
|
||||||
|
"3.0.0a32.post10.dev12",
|
||||||
|
"3.0.0rc1.dev1",
|
||||||
|
"3.0.0rc1",
|
||||||
|
"3.0.0",
|
||||||
|
"3.0.1",
|
||||||
|
"3.0.1.post1.dev1",
|
||||||
|
"3.0.1.post1",
|
||||||
|
"2018.10.6b21",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_info_str_parsing():
|
||||||
|
for version_str in version_tests:
|
||||||
|
assert version_str == str(VersionInfo.from_str(version_str))
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_info_lt():
|
||||||
|
for next_idx, cur in enumerate(version_tests[:-1], start=1):
|
||||||
|
cur_test = VersionInfo.from_str(cur)
|
||||||
|
next_test = VersionInfo.from_str(version_tests[next_idx])
|
||||||
|
assert cur_test < next_test
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_info_gt():
|
||||||
|
assert VersionInfo.from_str(version_tests[1]) > VersionInfo.from_str(version_tests[0])
|
||||||
|
|||||||
Reference in New Issue
Block a user