mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-05 17:02:32 -05:00
Compare commits
25 Commits
6603cd1a86
...
3.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc87f6c251 | ||
|
|
431cdf1ad4 | ||
|
|
c2bfcfdc64 | ||
|
|
343132a371 | ||
|
|
e97240e568 | ||
|
|
6383e9f738 | ||
|
|
d96062226d | ||
|
|
eebed27fe7 | ||
|
|
278abecdef | ||
|
|
21ac81679f | ||
|
|
5cf0c98bca | ||
|
|
7c8ac9cd54 | ||
|
|
cfd8ef6025 | ||
|
|
174754f800 | ||
|
|
53aa84bb94 | ||
|
|
a2c0bddd6b | ||
|
|
7dd3310377 | ||
|
|
0fa2e36629 | ||
|
|
3b38a5f9b9 | ||
|
|
f61e8e0907 | ||
|
|
5fc8f9fee1 | ||
|
|
644bb0a560 | ||
|
|
93ea773b7c | ||
|
|
655b5a96ba | ||
|
|
bbe88293ab |
4
LICENSE
4
LICENSE
@@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
|
|||||||
the "copyright" line and a pointer to where the full notice is found.
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
Red - A fully customizable Discord bot
|
Red - A fully customizable Discord bot
|
||||||
Copyright (C) 2015-2018 Twentysix
|
Copyright (C) 2015-2019 Twentysix
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
|||||||
If the program does terminal interaction, make it output a short
|
If the program does terminal interaction, make it output a short
|
||||||
notice like this when it starts in an interactive mode:
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
Red-DiscordBot Copyright (C) 2015-2018 Twentysix
|
Red-DiscordBot Copyright (C) 2015-2019 Twentysix
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
This is free software, and you are welcome to redistribute it
|
This is free software, and you are welcome to redistribute it
|
||||||
under certain conditions; type `show c' for details.
|
under certain conditions; type `show c' for details.
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -9,5 +9,5 @@ gettext:
|
|||||||
REF?=rewrite
|
REF?=rewrite
|
||||||
update_vendor:
|
update_vendor:
|
||||||
pip install --upgrade --no-deps -t . https://github.com/Rapptz/discord.py/archive/$(REF).tar.gz#egg=discord.py
|
pip install --upgrade --no-deps -t . https://github.com/Rapptz/discord.py/archive/$(REF).tar.gz#egg=discord.py
|
||||||
rm -r discord.py*.egg-info
|
rm -r discord.py*-info
|
||||||
$(MAKE) reformat
|
$(MAKE) reformat
|
||||||
|
|||||||
4
Pipfile
4
Pipfile
@@ -4,8 +4,8 @@ verify_ssl = true
|
|||||||
name = "pypi"
|
name = "pypi"
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
"e1839a8" = { path = ".", editable = true, extras = ['mongo', 'voice'] }
|
red-discordbot = {path = ".",editable = true,extras = ['mongo', 'voice']}
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
tox = "*"
|
tox = "*"
|
||||||
"e1839a9" = { path = ".", editable = true, extras = ['docs', 'test', 'style'] }
|
red-discordbot = {path = ".",editable = true,extras = ['docs', 'test', 'style']}
|
||||||
|
|||||||
376
Pipfile.lock
generated
376
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "57184ef83392116db24a1966022ad358f54048bb43d428d47a6e31f1a88fc434"
|
"sha256": "b9f385e4c53c659dd76e8722d1fb69c244d3a76e4b0dfc40956ff2493277c1f6"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
@@ -16,37 +16,37 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"aiohttp": {
|
"aiohttp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0419705a36b43c0ac6f15469f9c2a08cad5c939d78bd12a5c23ea167c8253b2b",
|
"sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55",
|
||||||
"sha256:1812fc4bc6ac1bde007daa05d2d0f61199324e0cc893b11523e646595047ca08",
|
"sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed",
|
||||||
"sha256:2214b5c0153f45256d5d52d1e0cafe53f9905ed035a142191727a5fb620c03dd",
|
"sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10",
|
||||||
"sha256:275909137f0c92c61ba6bb1af856a522d5546f1de8ea01e4e726321c697754ac",
|
"sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5",
|
||||||
"sha256:3983611922b561868428ea1e7269e757803713f55b53502423decc509fef1650",
|
"sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1",
|
||||||
"sha256:51afec6ffa50a9da4cdef188971a802beb1ca8e8edb40fa429e5e529db3475fa",
|
"sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939",
|
||||||
"sha256:589f2ec8a101a0f340453ee6945bdfea8e1cd84c8d88e5be08716c34c0799d95",
|
"sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390",
|
||||||
"sha256:789820ddc65e1f5e71516adaca2e9022498fa5a837c79ba9c692a9f8f916c330",
|
"sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa",
|
||||||
"sha256:7a968a0bdaaf9abacc260911775611c9a602214a23aeb846f2eb2eeaa350c4dc",
|
"sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc",
|
||||||
"sha256:7aeefbed253f59ea39e70c5848de42ed85cb941165357fc7e87ab5d8f1f9592b",
|
"sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5",
|
||||||
"sha256:7b2eb55c66512405103485bd7d285a839d53e7fdc261ab20e5bcc51d7aaff5de",
|
"sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d",
|
||||||
"sha256:87bc95d3d333bb689c8d755b4a9d7095a2356108002149523dfc8e607d5d32a4",
|
"sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf",
|
||||||
"sha256:9d80e40db208e29168d3723d1440ecbb06054d349c5ece6a2c5a611490830dd7",
|
"sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6",
|
||||||
"sha256:a1b442195c2a77d33e4dbee67c9877ccbdd3a1f686f91eb479a9577ed8cc326b",
|
"sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72",
|
||||||
"sha256:ab3d769413b322d6092f169f316f7b21cd261a7589f7e31db779d5731b0480d8",
|
"sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12",
|
||||||
"sha256:b066d3dec5d0f5aee6e34e5765095dc3d6d78ef9839640141a2b20816a0642bd",
|
"sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366",
|
||||||
"sha256:b24e7845ae8de3e388ef4bcfcf7f96b05f52c8e633b33cf8003a6b1d726fc7c2",
|
"sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4",
|
||||||
"sha256:c59a953c3f8524a7c86eaeaef5bf702555be12f5668f6384149fe4bb75c52698",
|
"sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300",
|
||||||
"sha256:cf2cc6c2c10d242790412bea7ccf73726a9a44b4c4b073d2699ef3b48971fd95",
|
"sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d",
|
||||||
"sha256:e0c9c8d4150ae904f308ff27b35446990d2b1dfc944702a21925937e937394c6",
|
"sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303",
|
||||||
"sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0",
|
"sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6",
|
||||||
"sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07"
|
"sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889"
|
||||||
],
|
],
|
||||||
"version": "==3.4.4"
|
"version": "==3.5.4"
|
||||||
},
|
},
|
||||||
"aiohttp-json-rpc": {
|
"aiohttp-json-rpc": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:00d72f40edfc7271578d545a8c47874c0e23cc5d3201ed8128481f6a4af47e32",
|
"sha256:1d040b7b10ff414f9174398ff6e9c647eb0434a00939450b33aa539177c51dcf",
|
||||||
"sha256:02d83b6998f8a0b7e59b46f0cb8a96b475bbf82600b1f9527df47135353f1ca8"
|
"sha256:5f5fb141c6263d2ea52a4173babe9449eef4029620dc49936dca45cdc17ac9dd"
|
||||||
],
|
],
|
||||||
"version": "==0.11.2"
|
"version": "==0.12"
|
||||||
},
|
},
|
||||||
"appdirs": {
|
"appdirs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -85,10 +85,10 @@
|
|||||||
},
|
},
|
||||||
"distro": {
|
"distro": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:224041cef9600e72d19ae41ba006e71c05c4dc802516da715d7fda55ba3d8742",
|
"sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57",
|
||||||
"sha256:6ec8e539cf412830e5ccf521aecf879f2c7fcf60ce446e33cd16eef1ed8a0158"
|
"sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4"
|
||||||
],
|
],
|
||||||
"version": "==1.3.0"
|
"version": "==1.4.0"
|
||||||
},
|
},
|
||||||
"dnspython": {
|
"dnspython": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -97,14 +97,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.16.0"
|
"version": "==1.16.0"
|
||||||
},
|
},
|
||||||
"e1839a8": {
|
|
||||||
"editable": true,
|
|
||||||
"extras": [
|
|
||||||
"mongo",
|
|
||||||
"voice"
|
|
||||||
],
|
|
||||||
"path": "."
|
|
||||||
},
|
|
||||||
"fuzzywuzzy": {
|
"fuzzywuzzy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
|
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
|
||||||
@@ -205,11 +197,43 @@
|
|||||||
],
|
],
|
||||||
"version": "==3.7.2"
|
"version": "==3.7.2"
|
||||||
},
|
},
|
||||||
"python-levenshtein": {
|
"python-levenshtein-wheels": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
|
"sha256:0065529c8aec4c044468286177761857d36981ba6f7fdb62d7d5f7ffd143de5d",
|
||||||
|
"sha256:016924a59d689f9f47d5f7b26b70f31e309255e8dd72602c91e93ceb752b9f92",
|
||||||
|
"sha256:089d046ea7727e583233c71fef1046663ed67b96967063ae8ddc9f551e86a4fc",
|
||||||
|
"sha256:0aea217eab612acd45dcc3424a2e8dbd977cc309f80359d0c01971f1e65b9a9b",
|
||||||
|
"sha256:0beb91ad80b1573829066e5af36b80190c367be6e0a65292f073353b0388c7fc",
|
||||||
|
"sha256:0fa2ca69ef803bc6037a8c919e2e8a17b55e94c9c9ffcb4c21befbb15a1d0f40",
|
||||||
|
"sha256:11c77d0d74ab7f46f89a58ae9c2d67349ebc1ae3e18636627f9939d810167c31",
|
||||||
|
"sha256:19a68716a322486ddffc8bf7e5cf44a82f7700b05a10658e6e7fc5c7ae92b13d",
|
||||||
|
"sha256:19a95a01d28d63b042438ba860c4ace90362906a038fa77962ba33325d377d10",
|
||||||
|
"sha256:1a61f3a51e00a3608659bbaabb3f27af37c9dbe84d843369061a3e45cf0d5103",
|
||||||
|
"sha256:1c50aebebab403fb2dd415d70355446ac364dece502b0e2737a1a085bb9a4aa4",
|
||||||
|
"sha256:1e51cdc123625a28709662d24ea0cb4cf6f991845e6054d9f803c78da1d6b08f",
|
||||||
|
"sha256:1f0056d3216b0fe38f25c6f8ebc84bd9f6d34c55a7a9414341b674fb98961399",
|
||||||
|
"sha256:228b59460e9a786e498bdfc8011838b89c6054650b115c86c9c819a055a793b0",
|
||||||
|
"sha256:23020f9ff2cb3457a926dcc470b84f9bd5b7646bd8b8e06b915bdbbc905cb23f",
|
||||||
|
"sha256:3e6bcca97a7ff4e720352b57ddc26380c0583dcdd4b791acef7b574ad58468a7",
|
||||||
|
"sha256:3ed88f9e638da57647149115c34e0e120cae6f3d35eee7d77e22cc9c1d8eced3",
|
||||||
|
"sha256:445bf7941cb1fa05d6c2a4a502ad4868a5cacd92e8eb77b2bd008cdda9d37c55",
|
||||||
|
"sha256:4ba5e147d76d7ee884fd6eae461438b080bcc9f2c6eb9b576811e1bcfe8f808e",
|
||||||
|
"sha256:4bb128b719c30f3b9feacfe71a338ae07d39dbffc077139416f3535c89f12362",
|
||||||
|
"sha256:53c0c9964390368fd64460b690f168221c669766b193b7e80ae3950c2b9551f8",
|
||||||
|
"sha256:57c4edef81611098d37176278f2b6a3712bf864eed313496d7d80504805896d1",
|
||||||
|
"sha256:7f7283dfe50eac8a8cd9b777de9eb50b1edf7dbb46fc7cc9d9b0050d0c135021",
|
||||||
|
"sha256:7f9759095b3fc825464a72b1cae95125e610eba3c70f91557754c32a0bf32ea2",
|
||||||
|
"sha256:98727050ba70eb8d318ec8a8203531c20119347fc8f281102b097326812742ab",
|
||||||
|
"sha256:ac9cdf044dcb9481c7da782db01b50c1f0e7cdd78c8507b963b6d072829c0263",
|
||||||
|
"sha256:b679f951f842c38665aa54bea4d7403099131f71fac6d8584f893a731fe1266d",
|
||||||
|
"sha256:b8c183dc4aa4e95dc5c373eedc3d205c176805835611fcfec5d9050736c695c4",
|
||||||
|
"sha256:c2c76f483d05eddec60a5cd89e92385adef565a4f243b1d9a6abe2f6bd2a7c0a",
|
||||||
|
"sha256:c388baa3c04272a7c585d3da24030c142353eb26eb531dd2681502e6be7d7a26",
|
||||||
|
"sha256:cb0f2a711db665b5bf8697b5af3b9884bb1139385c5c12c2e472e4bbee62da99",
|
||||||
|
"sha256:cbac984d7b36e75b440d1c8ff9d3425d778364a0cbc23f8943383d4decd35d5e",
|
||||||
|
"sha256:f9084ed3b8997ad4353d124b903f2860a9695b9e080663276d9e58c32e293244"
|
||||||
],
|
],
|
||||||
"version": "==0.12.0"
|
"version": "==0.13.1"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -241,11 +265,20 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.7.0"
|
"version": "==0.7.0"
|
||||||
},
|
},
|
||||||
|
"red-discordbot": {
|
||||||
|
"editable": true,
|
||||||
|
"extras": [
|
||||||
|
"mongo",
|
||||||
|
"voice"
|
||||||
|
],
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
"red-lavalink": {
|
"red-lavalink": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6a1a34471ccf4630eee537049568dd87e8e93614f1d1ce355dd74e5b10079782"
|
"sha256:13e1a3f91b990be9582cba039d9a32ec4cef760da1e7e6952143116ec83d4302",
|
||||||
|
"sha256:3dd0d73b4a908bbe9cfb703d2563dad1d1a58f8eea5896a0dacdf37d54a39d9c"
|
||||||
],
|
],
|
||||||
"version": "==0.1.2"
|
"version": "==0.2.3"
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -256,29 +289,29 @@
|
|||||||
},
|
},
|
||||||
"websockets": {
|
"websockets": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
|
"sha256:04b42a1b57096ffa5627d6a78ea1ff7fad3bc2c0331ffc17bc32a4024da7fea0",
|
||||||
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
|
"sha256:08e3c3e0535befa4f0c4443824496c03ecc25062debbcf895874f8a0b4c97c9f",
|
||||||
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
|
"sha256:10d89d4326045bf5e15e83e9867c85d686b612822e4d8f149cf4840aab5f46e0",
|
||||||
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
|
"sha256:232fac8a1978fc1dead4b1c2fa27c7756750fb393eb4ac52f6bc87ba7242b2fa",
|
||||||
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
|
"sha256:4bf4c8097440eff22bc78ec76fe2a865a6e658b6977a504679aaf08f02c121da",
|
||||||
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
|
"sha256:51642ea3a00772d1e48fb0c492f0d3ae3b6474f34d20eca005a83f8c9c06c561",
|
||||||
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
|
"sha256:55d86102282a636e195dad68aaaf85b81d0bef449d7e2ef2ff79ac450bb25d53",
|
||||||
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
|
"sha256:564d2675682bd497b59907d2205031acbf7d3fadf8c763b689b9ede20300b215",
|
||||||
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
|
"sha256:5d13bf5197a92149dc0badcc2b699267ff65a867029f465accfca8abab95f412",
|
||||||
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
|
"sha256:5eda665f6789edb9b57b57a159b9c55482cbe5b046d7db458948370554b16439",
|
||||||
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
|
"sha256:5edb2524d4032be4564c65dc4f9d01e79fe8fad5f966e5b552f4e5164fef0885",
|
||||||
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
|
"sha256:79691794288bc51e2a3b8de2bc0272ca8355d0b8503077ea57c0716e840ebaef",
|
||||||
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
|
"sha256:7fcc8681e9981b9b511cdee7c580d5b005f3bb86b65bde2188e04a29f1d63317",
|
||||||
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
|
"sha256:8e447e05ec88b1b408a4c9cde85aa6f4b04f06aa874b9f0b8e8319faf51b1fee",
|
||||||
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
|
"sha256:90ea6b3e7787620bb295a4ae050d2811c807d65b1486749414f78cfd6fb61489",
|
||||||
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
|
"sha256:9e13239952694b8b831088431d15f771beace10edfcf9ef230cefea14f18508f",
|
||||||
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
|
"sha256:d40f081187f7b54d7a99d8a5c782eaa4edc335a057aa54c85059272ed826dc09",
|
||||||
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
|
"sha256:e1df1a58ed2468c7b7ce9a2f9752a32ad08eac2bcd56318625c3647c2cd2da6f",
|
||||||
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
|
"sha256:e98d0cec437097f09c7834a11c69d79fe6241729b23f656cfc227e93294fc242",
|
||||||
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
"sha256:f8d59627702d2ff27cb495ca1abdea8bd8d581de425c56e93bff6517134e0a9b",
|
||||||
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
"sha256:fc30cdf2e949a2225b012a7911d1d031df3d23e99b7eda7dfc982dc4a860dae9"
|
||||||
],
|
],
|
||||||
"version": "==6.0"
|
"version": "==7.0"
|
||||||
},
|
},
|
||||||
"yarl": {
|
"yarl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -300,37 +333,37 @@
|
|||||||
"develop": {
|
"develop": {
|
||||||
"aiohttp": {
|
"aiohttp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0419705a36b43c0ac6f15469f9c2a08cad5c939d78bd12a5c23ea167c8253b2b",
|
"sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55",
|
||||||
"sha256:1812fc4bc6ac1bde007daa05d2d0f61199324e0cc893b11523e646595047ca08",
|
"sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed",
|
||||||
"sha256:2214b5c0153f45256d5d52d1e0cafe53f9905ed035a142191727a5fb620c03dd",
|
"sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10",
|
||||||
"sha256:275909137f0c92c61ba6bb1af856a522d5546f1de8ea01e4e726321c697754ac",
|
"sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5",
|
||||||
"sha256:3983611922b561868428ea1e7269e757803713f55b53502423decc509fef1650",
|
"sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1",
|
||||||
"sha256:51afec6ffa50a9da4cdef188971a802beb1ca8e8edb40fa429e5e529db3475fa",
|
"sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939",
|
||||||
"sha256:589f2ec8a101a0f340453ee6945bdfea8e1cd84c8d88e5be08716c34c0799d95",
|
"sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390",
|
||||||
"sha256:789820ddc65e1f5e71516adaca2e9022498fa5a837c79ba9c692a9f8f916c330",
|
"sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa",
|
||||||
"sha256:7a968a0bdaaf9abacc260911775611c9a602214a23aeb846f2eb2eeaa350c4dc",
|
"sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc",
|
||||||
"sha256:7aeefbed253f59ea39e70c5848de42ed85cb941165357fc7e87ab5d8f1f9592b",
|
"sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5",
|
||||||
"sha256:7b2eb55c66512405103485bd7d285a839d53e7fdc261ab20e5bcc51d7aaff5de",
|
"sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d",
|
||||||
"sha256:87bc95d3d333bb689c8d755b4a9d7095a2356108002149523dfc8e607d5d32a4",
|
"sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf",
|
||||||
"sha256:9d80e40db208e29168d3723d1440ecbb06054d349c5ece6a2c5a611490830dd7",
|
"sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6",
|
||||||
"sha256:a1b442195c2a77d33e4dbee67c9877ccbdd3a1f686f91eb479a9577ed8cc326b",
|
"sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72",
|
||||||
"sha256:ab3d769413b322d6092f169f316f7b21cd261a7589f7e31db779d5731b0480d8",
|
"sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12",
|
||||||
"sha256:b066d3dec5d0f5aee6e34e5765095dc3d6d78ef9839640141a2b20816a0642bd",
|
"sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366",
|
||||||
"sha256:b24e7845ae8de3e388ef4bcfcf7f96b05f52c8e633b33cf8003a6b1d726fc7c2",
|
"sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4",
|
||||||
"sha256:c59a953c3f8524a7c86eaeaef5bf702555be12f5668f6384149fe4bb75c52698",
|
"sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300",
|
||||||
"sha256:cf2cc6c2c10d242790412bea7ccf73726a9a44b4c4b073d2699ef3b48971fd95",
|
"sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d",
|
||||||
"sha256:e0c9c8d4150ae904f308ff27b35446990d2b1dfc944702a21925937e937394c6",
|
"sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303",
|
||||||
"sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0",
|
"sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6",
|
||||||
"sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07"
|
"sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889"
|
||||||
],
|
],
|
||||||
"version": "==3.4.4"
|
"version": "==3.5.4"
|
||||||
},
|
},
|
||||||
"aiohttp-json-rpc": {
|
"aiohttp-json-rpc": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:00d72f40edfc7271578d545a8c47874c0e23cc5d3201ed8128481f6a4af47e32",
|
"sha256:1d040b7b10ff414f9174398ff6e9c647eb0434a00939450b33aa539177c51dcf",
|
||||||
"sha256:02d83b6998f8a0b7e59b46f0cb8a96b475bbf82600b1f9527df47135353f1ca8"
|
"sha256:5f5fb141c6263d2ea52a4173babe9449eef4029620dc49936dca45cdc17ac9dd"
|
||||||
],
|
],
|
||||||
"version": "==0.11.2"
|
"version": "==0.12"
|
||||||
},
|
},
|
||||||
"alabaster": {
|
"alabaster": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -355,10 +388,10 @@
|
|||||||
},
|
},
|
||||||
"atomicwrites": {
|
"atomicwrites": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
|
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
|
||||||
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
|
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
|
||||||
],
|
],
|
||||||
"version": "==1.2.1"
|
"version": "==1.3.0"
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -411,10 +444,10 @@
|
|||||||
},
|
},
|
||||||
"distro": {
|
"distro": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:224041cef9600e72d19ae41ba006e71c05c4dc802516da715d7fda55ba3d8742",
|
"sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57",
|
||||||
"sha256:6ec8e539cf412830e5ccf521aecf879f2c7fcf60ce446e33cd16eef1ed8a0158"
|
"sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4"
|
||||||
],
|
],
|
||||||
"version": "==1.3.0"
|
"version": "==1.4.0"
|
||||||
},
|
},
|
||||||
"docutils": {
|
"docutils": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -424,15 +457,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.14"
|
"version": "==0.14"
|
||||||
},
|
},
|
||||||
"e1839a9": {
|
|
||||||
"editable": true,
|
|
||||||
"extras": [
|
|
||||||
"docs",
|
|
||||||
"test",
|
|
||||||
"style"
|
|
||||||
],
|
|
||||||
"path": "."
|
|
||||||
},
|
|
||||||
"filelock": {
|
"filelock": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:b8d5ca5ca1c815e1574aee746650ea7301de63d87935b3463d26368b76e31633",
|
"sha256:b8d5ca5ca1c815e1574aee746650ea7301de63d87935b3463d26368b76e31633",
|
||||||
@@ -509,11 +533,10 @@
|
|||||||
},
|
},
|
||||||
"more-itertools": {
|
"more-itertools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4",
|
"sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40",
|
||||||
"sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc",
|
"sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1"
|
||||||
"sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"
|
|
||||||
],
|
],
|
||||||
"version": "==5.0.0"
|
"version": "==6.0.0"
|
||||||
},
|
},
|
||||||
"multidict": {
|
"multidict": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -551,10 +574,10 @@
|
|||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807",
|
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
|
||||||
"sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9"
|
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
|
||||||
],
|
],
|
||||||
"version": "==18.0"
|
"version": "==19.0"
|
||||||
},
|
},
|
||||||
"pluggy": {
|
"pluggy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -579,17 +602,17 @@
|
|||||||
},
|
},
|
||||||
"pyparsing": {
|
"pyparsing": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b",
|
"sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a",
|
||||||
"sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592"
|
"sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3"
|
||||||
],
|
],
|
||||||
"version": "==2.3.0"
|
"version": "==2.3.1"
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3e65a22eb0d4f1bdbc1eacccf4a3198bf8d4049dea5112d70a0c61b00e748d02",
|
"sha256:65aeaa77ae87c7fc95de56285282546cfa9c886dc8e5dc78313db1c25e21bc07",
|
||||||
"sha256:5924060b374f62608a078494b909d341720a050b5224ff87e17e12377486a71d"
|
"sha256:6ac6d467d9f053e95aaacd79f831dbecfe730f419c6c7022cb316b365cd9199d"
|
||||||
],
|
],
|
||||||
"version": "==4.1.0"
|
"version": "==4.2.0"
|
||||||
},
|
},
|
||||||
"pytest-asyncio": {
|
"pytest-asyncio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -598,11 +621,43 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.10.0"
|
"version": "==0.10.0"
|
||||||
},
|
},
|
||||||
"python-levenshtein": {
|
"python-levenshtein-wheels": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
|
"sha256:0065529c8aec4c044468286177761857d36981ba6f7fdb62d7d5f7ffd143de5d",
|
||||||
|
"sha256:016924a59d689f9f47d5f7b26b70f31e309255e8dd72602c91e93ceb752b9f92",
|
||||||
|
"sha256:089d046ea7727e583233c71fef1046663ed67b96967063ae8ddc9f551e86a4fc",
|
||||||
|
"sha256:0aea217eab612acd45dcc3424a2e8dbd977cc309f80359d0c01971f1e65b9a9b",
|
||||||
|
"sha256:0beb91ad80b1573829066e5af36b80190c367be6e0a65292f073353b0388c7fc",
|
||||||
|
"sha256:0fa2ca69ef803bc6037a8c919e2e8a17b55e94c9c9ffcb4c21befbb15a1d0f40",
|
||||||
|
"sha256:11c77d0d74ab7f46f89a58ae9c2d67349ebc1ae3e18636627f9939d810167c31",
|
||||||
|
"sha256:19a68716a322486ddffc8bf7e5cf44a82f7700b05a10658e6e7fc5c7ae92b13d",
|
||||||
|
"sha256:19a95a01d28d63b042438ba860c4ace90362906a038fa77962ba33325d377d10",
|
||||||
|
"sha256:1a61f3a51e00a3608659bbaabb3f27af37c9dbe84d843369061a3e45cf0d5103",
|
||||||
|
"sha256:1c50aebebab403fb2dd415d70355446ac364dece502b0e2737a1a085bb9a4aa4",
|
||||||
|
"sha256:1e51cdc123625a28709662d24ea0cb4cf6f991845e6054d9f803c78da1d6b08f",
|
||||||
|
"sha256:1f0056d3216b0fe38f25c6f8ebc84bd9f6d34c55a7a9414341b674fb98961399",
|
||||||
|
"sha256:228b59460e9a786e498bdfc8011838b89c6054650b115c86c9c819a055a793b0",
|
||||||
|
"sha256:23020f9ff2cb3457a926dcc470b84f9bd5b7646bd8b8e06b915bdbbc905cb23f",
|
||||||
|
"sha256:3e6bcca97a7ff4e720352b57ddc26380c0583dcdd4b791acef7b574ad58468a7",
|
||||||
|
"sha256:3ed88f9e638da57647149115c34e0e120cae6f3d35eee7d77e22cc9c1d8eced3",
|
||||||
|
"sha256:445bf7941cb1fa05d6c2a4a502ad4868a5cacd92e8eb77b2bd008cdda9d37c55",
|
||||||
|
"sha256:4ba5e147d76d7ee884fd6eae461438b080bcc9f2c6eb9b576811e1bcfe8f808e",
|
||||||
|
"sha256:4bb128b719c30f3b9feacfe71a338ae07d39dbffc077139416f3535c89f12362",
|
||||||
|
"sha256:53c0c9964390368fd64460b690f168221c669766b193b7e80ae3950c2b9551f8",
|
||||||
|
"sha256:57c4edef81611098d37176278f2b6a3712bf864eed313496d7d80504805896d1",
|
||||||
|
"sha256:7f7283dfe50eac8a8cd9b777de9eb50b1edf7dbb46fc7cc9d9b0050d0c135021",
|
||||||
|
"sha256:7f9759095b3fc825464a72b1cae95125e610eba3c70f91557754c32a0bf32ea2",
|
||||||
|
"sha256:98727050ba70eb8d318ec8a8203531c20119347fc8f281102b097326812742ab",
|
||||||
|
"sha256:ac9cdf044dcb9481c7da782db01b50c1f0e7cdd78c8507b963b6d072829c0263",
|
||||||
|
"sha256:b679f951f842c38665aa54bea4d7403099131f71fac6d8584f893a731fe1266d",
|
||||||
|
"sha256:b8c183dc4aa4e95dc5c373eedc3d205c176805835611fcfec5d9050736c695c4",
|
||||||
|
"sha256:c2c76f483d05eddec60a5cd89e92385adef565a4f243b1d9a6abe2f6bd2a7c0a",
|
||||||
|
"sha256:c388baa3c04272a7c585d3da24030c142353eb26eb531dd2681502e6be7d7a26",
|
||||||
|
"sha256:cb0f2a711db665b5bf8697b5af3b9884bb1139385c5c12c2e472e4bbee62da99",
|
||||||
|
"sha256:cbac984d7b36e75b440d1c8ff9d3425d778364a0cbc23f8943383d4decd35d5e",
|
||||||
|
"sha256:f9084ed3b8997ad4353d124b903f2860a9695b9e080663276d9e58c32e293244"
|
||||||
],
|
],
|
||||||
"version": "==0.12.0"
|
"version": "==0.13.1"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -641,6 +696,21 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.7.0"
|
"version": "==0.7.0"
|
||||||
},
|
},
|
||||||
|
"red-discordbot": {
|
||||||
|
"editable": true,
|
||||||
|
"extras": [
|
||||||
|
"mongo",
|
||||||
|
"voice"
|
||||||
|
],
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
"red-lavalink": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:13e1a3f91b990be9582cba039d9a32ec4cef760da1e7e6952143116ec83d4302",
|
||||||
|
"sha256:3dd0d73b4a908bbe9cfb703d2563dad1d1a58f8eea5896a0dacdf37d54a39d9c"
|
||||||
|
],
|
||||||
|
"version": "==0.2.3"
|
||||||
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||||
@@ -671,17 +741,17 @@
|
|||||||
},
|
},
|
||||||
"sphinx": {
|
"sphinx": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:429e3172466df289f0f742471d7e30ba3ee11f3b5aecd9a840480d03f14bcfe5",
|
"sha256:b53904fa7cb4b06a39409a492b949193a1b68cc7241a1a8ce9974f86f0d24287",
|
||||||
"sha256:c4cb17ba44acffae3d3209646b6baec1e215cad3065e852c68cc569d4df1b9f8"
|
"sha256:c1c00fc4f6e8b101a0d037065043460dffc2d507257f2f11acaed71fd2b0c83c"
|
||||||
],
|
],
|
||||||
"version": "==1.8.3"
|
"version": "==1.8.4"
|
||||||
},
|
},
|
||||||
"sphinx-rtd-theme": {
|
"sphinx-rtd-theme": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:02f02a676d6baabb758a20c7a479d58648e0f64f13e07d1b388e9bb2afe86a09",
|
"sha256:00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4",
|
||||||
"sha256:d0f6bc70f98961145c5b0e26a992829363a197321ba571b31b24ea91879e0c96"
|
"sha256:728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a"
|
||||||
],
|
],
|
||||||
"version": "==0.4.2"
|
"version": "==0.4.3"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-asyncio": {
|
"sphinxcontrib-asyncio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -720,36 +790,36 @@
|
|||||||
},
|
},
|
||||||
"virtualenv": {
|
"virtualenv": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:34b9ae3742abed2f95d3970acf4d80533261d6061b51160b197f84e5b4c98b4c",
|
"sha256:8b9abfc51c38b70f61634bf265e5beacf6fae11fc25d355d1871f49b8e45f0db",
|
||||||
"sha256:fa736831a7b18bd2bfeef746beb622a92509e9733d645952da136b0639cd40cd"
|
"sha256:cceab52aa7d4df1e1871a70236eb2b89fcfe29b6b43510d9738689787c513261"
|
||||||
],
|
],
|
||||||
"version": "==16.2.0"
|
"version": "==16.4.0"
|
||||||
},
|
},
|
||||||
"websockets": {
|
"websockets": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
|
"sha256:04b42a1b57096ffa5627d6a78ea1ff7fad3bc2c0331ffc17bc32a4024da7fea0",
|
||||||
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
|
"sha256:08e3c3e0535befa4f0c4443824496c03ecc25062debbcf895874f8a0b4c97c9f",
|
||||||
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
|
"sha256:10d89d4326045bf5e15e83e9867c85d686b612822e4d8f149cf4840aab5f46e0",
|
||||||
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
|
"sha256:232fac8a1978fc1dead4b1c2fa27c7756750fb393eb4ac52f6bc87ba7242b2fa",
|
||||||
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
|
"sha256:4bf4c8097440eff22bc78ec76fe2a865a6e658b6977a504679aaf08f02c121da",
|
||||||
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
|
"sha256:51642ea3a00772d1e48fb0c492f0d3ae3b6474f34d20eca005a83f8c9c06c561",
|
||||||
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
|
"sha256:55d86102282a636e195dad68aaaf85b81d0bef449d7e2ef2ff79ac450bb25d53",
|
||||||
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
|
"sha256:564d2675682bd497b59907d2205031acbf7d3fadf8c763b689b9ede20300b215",
|
||||||
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
|
"sha256:5d13bf5197a92149dc0badcc2b699267ff65a867029f465accfca8abab95f412",
|
||||||
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
|
"sha256:5eda665f6789edb9b57b57a159b9c55482cbe5b046d7db458948370554b16439",
|
||||||
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
|
"sha256:5edb2524d4032be4564c65dc4f9d01e79fe8fad5f966e5b552f4e5164fef0885",
|
||||||
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
|
"sha256:79691794288bc51e2a3b8de2bc0272ca8355d0b8503077ea57c0716e840ebaef",
|
||||||
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
|
"sha256:7fcc8681e9981b9b511cdee7c580d5b005f3bb86b65bde2188e04a29f1d63317",
|
||||||
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
|
"sha256:8e447e05ec88b1b408a4c9cde85aa6f4b04f06aa874b9f0b8e8319faf51b1fee",
|
||||||
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
|
"sha256:90ea6b3e7787620bb295a4ae050d2811c807d65b1486749414f78cfd6fb61489",
|
||||||
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
|
"sha256:9e13239952694b8b831088431d15f771beace10edfcf9ef230cefea14f18508f",
|
||||||
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
|
"sha256:d40f081187f7b54d7a99d8a5c782eaa4edc335a057aa54c85059272ed826dc09",
|
||||||
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
|
"sha256:e1df1a58ed2468c7b7ce9a2f9752a32ad08eac2bcd56318625c3647c2cd2da6f",
|
||||||
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
|
"sha256:e98d0cec437097f09c7834a11c69d79fe6241729b23f656cfc227e93294fc242",
|
||||||
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
"sha256:f8d59627702d2ff27cb495ca1abdea8bd8d581de425c56e93bff6517134e0a9b",
|
||||||
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
"sha256:fc30cdf2e949a2225b012a7911d1d031df3d23e99b7eda7dfc982dc4a860dae9"
|
||||||
],
|
],
|
||||||
"version": "==6.0"
|
"version": "==7.0"
|
||||||
},
|
},
|
||||||
"yarl": {
|
"yarl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Discord API Wrapper
|
|||||||
|
|
||||||
A basic wrapper for the Discord API.
|
A basic wrapper for the Discord API.
|
||||||
|
|
||||||
:copyright: (c) 2015-2017 Rapptz
|
:copyright: (c) 2015-2019 Rapptz
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -14,7 +14,7 @@ A basic wrapper for the Discord API.
|
|||||||
__title__ = "discord"
|
__title__ = "discord"
|
||||||
__author__ = "Rapptz"
|
__author__ = "Rapptz"
|
||||||
__license__ = "MIT"
|
__license__ = "MIT"
|
||||||
__copyright__ = "Copyright 2015-2017 Rapptz"
|
__copyright__ = "Copyright 2015-2019 Rapptz"
|
||||||
__version__ = "1.0.0a"
|
__version__ = "1.0.0a"
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -667,6 +667,28 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
|||||||
ret.sort(key=comparator)
|
ret.sort(key=comparator)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text_channels(self):
|
||||||
|
"""List[:class:`TextChannel`]: Returns the text channels that are under this category."""
|
||||||
|
ret = [
|
||||||
|
c
|
||||||
|
for c in self.guild.channels
|
||||||
|
if c.category_id == self.id and isinstance(c, TextChannel)
|
||||||
|
]
|
||||||
|
ret.sort(key=lambda c: (c.position, c.id))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def voice_channels(self):
|
||||||
|
"""List[:class:`VoiceChannel`]: Returns the text channels that are under this category."""
|
||||||
|
ret = [
|
||||||
|
c
|
||||||
|
for c in self.guild.channels
|
||||||
|
if c.category_id == self.id and isinstance(c, VoiceChannel)
|
||||||
|
]
|
||||||
|
ret.sort(key=lambda c: (c.position, c.id))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class DMChannel(discord.abc.Messageable, Hashable):
|
class DMChannel(discord.abc.Messageable, Hashable):
|
||||||
"""Represents a Discord direct message channel.
|
"""Represents a Discord direct message channel.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -76,6 +76,13 @@ class PartialEmoji(namedtuple("PartialEmoji", "animated name id")):
|
|||||||
return "<a:%s:%s>" % (self.name, self.id)
|
return "<a:%s:%s>" % (self.name, self.id)
|
||||||
return "<:%s:%s>" % (self.name, self.id)
|
return "<:%s:%s>" % (self.name, self.id)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if self.is_unicode_emoji():
|
||||||
|
return isinstance(other, PartialEmoji) and self.name == other.name
|
||||||
|
|
||||||
|
if isinstance(other, (PartialEmoji, Emoji)):
|
||||||
|
return self.id == other.id
|
||||||
|
|
||||||
def is_custom_emoji(self):
|
def is_custom_emoji(self):
|
||||||
"""Checks if this is a custom non-Unicode emoji."""
|
"""Checks if this is a custom non-Unicode emoji."""
|
||||||
return self.id is not None
|
return self.id is not None
|
||||||
@@ -186,6 +193,9 @@ class Emoji(Hashable):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Emoji id={0.id} name={0.name!r}>".format(self)
|
return "<Emoji id={0.id} name={0.name!r}>".format(self)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return isinstance(other, (PartialEmoji, Emoji)) and self.id == other.id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def created_at(self):
|
def created_at(self):
|
||||||
"""Returns the emoji's creation time in UTC."""
|
"""Returns the emoji's creation time in UTC."""
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -30,6 +30,7 @@ __all__ = [
|
|||||||
"ChannelType",
|
"ChannelType",
|
||||||
"MessageType",
|
"MessageType",
|
||||||
"VoiceRegion",
|
"VoiceRegion",
|
||||||
|
"SpeakingState",
|
||||||
"VerificationLevel",
|
"VerificationLevel",
|
||||||
"ContentFilter",
|
"ContentFilter",
|
||||||
"Status",
|
"Status",
|
||||||
@@ -91,6 +92,16 @@ class VoiceRegion(Enum):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
class SpeakingState(IntEnum):
|
||||||
|
none = 0
|
||||||
|
voice = 1
|
||||||
|
soundshare = 2
|
||||||
|
priority = 4
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class VerificationLevel(IntEnum):
|
class VerificationLevel(IntEnum):
|
||||||
none = 0
|
none = 0
|
||||||
low = 1
|
low = 1
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ discord.ext.commands
|
|||||||
|
|
||||||
An extension module to facilitate creation of bot commands.
|
An extension module to facilitate creation of bot commands.
|
||||||
|
|
||||||
:copyright: (c) 2017 Rapptz
|
:copyright: (c) 2019 Rapptz
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -443,9 +443,9 @@ class BotBase(GroupMixin):
|
|||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
func : :ref:`coroutine <coroutine>`
|
func : :ref:`coroutine <coroutine>`
|
||||||
The extra event to listen to.
|
The function to call.
|
||||||
name : Optional[str]
|
name : Optional[str]
|
||||||
The name of the command to use. Defaults to ``func.__name__``.
|
The name of the event to listen for. Defaults to ``func.__name__``.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
--------
|
--------
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -523,7 +523,7 @@ class clean_content(Converter):
|
|||||||
result = pattern.sub(repl, argument)
|
result = pattern.sub(repl, argument)
|
||||||
|
|
||||||
if self.escape_markdown:
|
if self.escape_markdown:
|
||||||
transformations = {re.escape(c): "\\" + c for c in ("*", "`", "_", "~", "\\")}
|
transformations = {re.escape(c): "\\" + c for c in ("*", "`", "_", "~", "\\", "||")}
|
||||||
|
|
||||||
def replace(obj):
|
def replace(obj):
|
||||||
return transformations.get(re.escape(obj.group(0)), "")
|
return transformations.get(re.escape(obj.group(0)), "")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -385,22 +385,18 @@ class Command:
|
|||||||
# for use with a manual undo
|
# for use with a manual undo
|
||||||
previous = view.index
|
previous = view.index
|
||||||
|
|
||||||
# parsing errors get propagated
|
|
||||||
view.skip_ws()
|
view.skip_ws()
|
||||||
argument = quoted_word(view)
|
argument = quoted_word(view)
|
||||||
try:
|
try:
|
||||||
value = await self.do_conversion(ctx, converter, argument, param)
|
value = await self.do_conversion(ctx, converter, argument, param)
|
||||||
except CommandError:
|
except CommandError:
|
||||||
if not result:
|
|
||||||
if required:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
view.index = previous
|
|
||||||
return param.default
|
|
||||||
view.index = previous
|
view.index = previous
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
result.append(value)
|
result.append(value)
|
||||||
|
|
||||||
|
if not result and not required:
|
||||||
|
return param.default
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def _transform_greedy_var_pos(self, ctx, param, converter):
|
async def _transform_greedy_var_pos(self, ctx, param, converter):
|
||||||
@@ -750,9 +746,9 @@ class Command:
|
|||||||
If that lookup leads to an empty string then the first line of the
|
If that lookup leads to an empty string then the first line of the
|
||||||
:attr:`help` attribute is used instead.
|
:attr:`help` attribute is used instead.
|
||||||
"""
|
"""
|
||||||
if self.brief:
|
if self.brief is not None:
|
||||||
return self.brief
|
return self.brief
|
||||||
if self.help:
|
if self.help is not None:
|
||||||
return self.help.split("\n", 1)[0]
|
return self.help.split("\n", 1)[0]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -771,7 +767,7 @@ class Command:
|
|||||||
name = self.name if not parent else parent + " " + self.name
|
name = self.name if not parent else parent + " " + self.name
|
||||||
result.append(name)
|
result.append(name)
|
||||||
|
|
||||||
if self.usage:
|
if self.usage is not None:
|
||||||
result.append(self.usage)
|
result.append(self.usage)
|
||||||
return " ".join(result)
|
return " ".join(result)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -26,6 +26,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import inspect
|
import inspect
|
||||||
|
import discord.utils
|
||||||
|
|
||||||
from .core import GroupMixin, Command
|
from .core import GroupMixin, Command
|
||||||
from .errors import CommandError
|
from .errors import CommandError
|
||||||
@@ -183,7 +184,9 @@ class HelpFormatter:
|
|||||||
if commands:
|
if commands:
|
||||||
return max(
|
return max(
|
||||||
map(
|
map(
|
||||||
lambda c: len(c.name) if self.show_hidden or not c.hidden else 0,
|
lambda c: discord.utils._string_width(c.name)
|
||||||
|
if self.show_hidden or not c.hidden
|
||||||
|
else 0,
|
||||||
commands.values(),
|
commands.values(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -272,8 +275,10 @@ class HelpFormatter:
|
|||||||
if name in command.aliases:
|
if name in command.aliases:
|
||||||
# skip aliases
|
# skip aliases
|
||||||
continue
|
continue
|
||||||
|
width_gap = discord.utils._string_width(name) - len(name)
|
||||||
entry = " {0:<{width}} {1}".format(name, command.short_doc, width=max_width)
|
entry = " {0:<{width}} {1}".format(
|
||||||
|
name, command.short_doc, width=max_width - width_gap
|
||||||
|
)
|
||||||
shortened = self.shorten(entry)
|
shortened = self.shorten(entry)
|
||||||
self._paginator.add_line(shortened)
|
self._paginator.add_line(shortened)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -26,6 +26,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
import concurrent.futures
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
@@ -38,6 +39,7 @@ import websockets
|
|||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .activity import _ActivityTag
|
from .activity import _ActivityTag
|
||||||
|
from .enums import SpeakingState
|
||||||
from .errors import ConnectionClosed, InvalidArgument
|
from .errors import ConnectionClosed, InvalidArgument
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -72,6 +74,8 @@ class KeepAliveHandler(threading.Thread):
|
|||||||
self.daemon = True
|
self.daemon = True
|
||||||
self.shard_id = shard_id
|
self.shard_id = shard_id
|
||||||
self.msg = "Keeping websocket alive with sequence %s."
|
self.msg = "Keeping websocket alive with sequence %s."
|
||||||
|
self.block_msg = "Heartbeat blocked for more than %s seconds."
|
||||||
|
self.behind_msg = "Can't keep up, websocket is %.1fs behind."
|
||||||
self._stop_ev = threading.Event()
|
self._stop_ev = threading.Event()
|
||||||
self._last_ack = time.perf_counter()
|
self._last_ack = time.perf_counter()
|
||||||
self._last_send = time.perf_counter()
|
self._last_send = time.perf_counter()
|
||||||
@@ -102,7 +106,15 @@ class KeepAliveHandler(threading.Thread):
|
|||||||
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
|
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
|
||||||
try:
|
try:
|
||||||
# block until sending is complete
|
# block until sending is complete
|
||||||
f.result()
|
total = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
f.result(5)
|
||||||
|
break
|
||||||
|
except concurrent.futures.TimeoutError:
|
||||||
|
total += 5
|
||||||
|
log.warning(self.block_msg, total)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.stop()
|
self.stop()
|
||||||
else:
|
else:
|
||||||
@@ -118,12 +130,16 @@ class KeepAliveHandler(threading.Thread):
|
|||||||
ack_time = time.perf_counter()
|
ack_time = time.perf_counter()
|
||||||
self._last_ack = ack_time
|
self._last_ack = ack_time
|
||||||
self.latency = ack_time - self._last_send
|
self.latency = ack_time - self._last_send
|
||||||
|
if self.latency > 10:
|
||||||
|
log.warning(self.behind_msg, self.latency)
|
||||||
|
|
||||||
|
|
||||||
class VoiceKeepAliveHandler(KeepAliveHandler):
|
class VoiceKeepAliveHandler(KeepAliveHandler):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.msg = "Keeping voice websocket alive with timestamp %s."
|
self.msg = "Keeping voice websocket alive with timestamp %s."
|
||||||
|
self.block_msg = "Voice heartbeat blocked for more than %s seconds"
|
||||||
|
self.behind_msg = "Can't keep up, voice websocket is %.1fs behind"
|
||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self):
|
||||||
return {"op": self.ws.HEARTBEAT, "d": int(time.time() * 1000)}
|
return {"op": self.ws.HEARTBEAT, "d": int(time.time() * 1000)}
|
||||||
@@ -482,7 +498,7 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
|
|||||||
|
|
||||||
async def send_as_json(self, data):
|
async def send_as_json(self, data):
|
||||||
try:
|
try:
|
||||||
await super().send(utils.to_json(data))
|
await self.send(utils.to_json(data))
|
||||||
except websockets.exceptions.ConnectionClosed as exc:
|
except websockets.exceptions.ConnectionClosed as exc:
|
||||||
if not self._can_handle_close(exc.code):
|
if not self._can_handle_close(exc.code):
|
||||||
raise ConnectionClosed(exc, shard_id=self.shard_id) from exc
|
raise ConnectionClosed(exc, shard_id=self.shard_id) from exc
|
||||||
@@ -561,6 +577,10 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
|||||||
Receive only. Tells you that your websocket connection was acknowledged.
|
Receive only. Tells you that your websocket connection was acknowledged.
|
||||||
INVALIDATE_SESSION
|
INVALIDATE_SESSION
|
||||||
Sent only. Tells you that your RESUME request has failed and to re-IDENTIFY.
|
Sent only. Tells you that your RESUME request has failed and to re-IDENTIFY.
|
||||||
|
CLIENT_CONNECT
|
||||||
|
Indicates a user has connected to voice.
|
||||||
|
CLIENT_DISCONNECT
|
||||||
|
Receive only. Indicates a user has disconnected from voice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
IDENTIFY = 0
|
IDENTIFY = 0
|
||||||
@@ -573,6 +593,8 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
|||||||
RESUME = 7
|
RESUME = 7
|
||||||
HELLO = 8
|
HELLO = 8
|
||||||
INVALIDATE_SESSION = 9
|
INVALIDATE_SESSION = 9
|
||||||
|
CLIENT_CONNECT = 12
|
||||||
|
CLIENT_DISCONNECT = 13
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -611,7 +633,7 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
|||||||
@classmethod
|
@classmethod
|
||||||
async def from_client(cls, client, *, resume=False):
|
async def from_client(cls, client, *, resume=False):
|
||||||
"""Creates a voice websocket for the :class:`VoiceClient`."""
|
"""Creates a voice websocket for the :class:`VoiceClient`."""
|
||||||
gateway = "wss://" + client.endpoint + "/?v=3"
|
gateway = "wss://" + client.endpoint + "/?v=4"
|
||||||
ws = await websockets.connect(gateway, loop=client.loop, klass=cls, compression=None)
|
ws = await websockets.connect(gateway, loop=client.loop, klass=cls, compression=None)
|
||||||
ws.gateway = gateway
|
ws.gateway = gateway
|
||||||
ws._connection = client
|
ws._connection = client
|
||||||
@@ -624,19 +646,21 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
|||||||
|
|
||||||
return ws
|
return ws
|
||||||
|
|
||||||
async def select_protocol(self, ip, port):
|
async def select_protocol(self, ip, port, mode):
|
||||||
payload = {
|
payload = {
|
||||||
"op": self.SELECT_PROTOCOL,
|
"op": self.SELECT_PROTOCOL,
|
||||||
"d": {
|
"d": {"protocol": "udp", "data": {"address": ip, "port": port, "mode": mode}},
|
||||||
"protocol": "udp",
|
|
||||||
"data": {"address": ip, "port": port, "mode": "xsalsa20_poly1305"},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await self.send_as_json(payload)
|
await self.send_as_json(payload)
|
||||||
|
|
||||||
async def speak(self, is_speaking=True):
|
async def client_connect(self):
|
||||||
payload = {"op": self.SPEAKING, "d": {"speaking": is_speaking, "delay": 0}}
|
payload = {"op": self.CLIENT_CONNECT, "d": {"audio_ssrc": self._connection.ssrc}}
|
||||||
|
|
||||||
|
await self.send_as_json(payload)
|
||||||
|
|
||||||
|
async def speak(self, state=SpeakingState.voice):
|
||||||
|
payload = {"op": self.SPEAKING, "d": {"speaking": int(state), "delay": 0}}
|
||||||
|
|
||||||
await self.send_as_json(payload)
|
await self.send_as_json(payload)
|
||||||
|
|
||||||
@@ -646,9 +670,6 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
|||||||
data = msg.get("d")
|
data = msg.get("d")
|
||||||
|
|
||||||
if op == self.READY:
|
if op == self.READY:
|
||||||
interval = data["heartbeat_interval"] / 1000.0
|
|
||||||
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval)
|
|
||||||
self._keep_alive.start()
|
|
||||||
await self.initial_connection(data)
|
await self.initial_connection(data)
|
||||||
elif op == self.HEARTBEAT_ACK:
|
elif op == self.HEARTBEAT_ACK:
|
||||||
self._keep_alive.ack()
|
self._keep_alive.ack()
|
||||||
@@ -656,7 +677,12 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
|||||||
log.info("Voice RESUME failed.")
|
log.info("Voice RESUME failed.")
|
||||||
await self.identify()
|
await self.identify()
|
||||||
elif op == self.SESSION_DESCRIPTION:
|
elif op == self.SESSION_DESCRIPTION:
|
||||||
|
self._connection.mode = data["mode"]
|
||||||
await self.load_secret_key(data)
|
await self.load_secret_key(data)
|
||||||
|
elif op == self.HELLO:
|
||||||
|
interval = data["heartbeat_interval"] / 1000.0
|
||||||
|
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval)
|
||||||
|
self._keep_alive.start()
|
||||||
|
|
||||||
async def initial_connection(self, data):
|
async def initial_connection(self, data):
|
||||||
state = self._connection
|
state = self._connection
|
||||||
@@ -677,15 +703,23 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
|
|||||||
# the port is a little endian unsigned short in the last two bytes
|
# the port is a little endian unsigned short in the last two bytes
|
||||||
# yes, this is different endianness from everything else
|
# yes, this is different endianness from everything else
|
||||||
state.port = struct.unpack_from("<H", recv, len(recv) - 2)[0]
|
state.port = struct.unpack_from("<H", recv, len(recv) - 2)[0]
|
||||||
|
|
||||||
log.debug("detected ip: %s port: %s", state.ip, state.port)
|
log.debug("detected ip: %s port: %s", state.ip, state.port)
|
||||||
await self.select_protocol(state.ip, state.port)
|
|
||||||
log.info("selected the voice protocol for use")
|
# there *should* always be at least one supported mode (xsalsa20_poly1305)
|
||||||
|
modes = [mode for mode in data["modes"] if mode in self._connection.supported_modes]
|
||||||
|
log.debug("received supported encryption modes: %s", ", ".join(modes))
|
||||||
|
|
||||||
|
mode = modes[0]
|
||||||
|
await self.select_protocol(state.ip, state.port, mode)
|
||||||
|
log.info("selected the voice protocol for use (%s)", mode)
|
||||||
|
|
||||||
|
await self.client_connect()
|
||||||
|
|
||||||
async def load_secret_key(self, data):
|
async def load_secret_key(self, data):
|
||||||
log.info("received secret key for voice connection")
|
log.info("received secret key for voice connection")
|
||||||
self._connection.secret_key = data.get("secret_key")
|
self._connection.secret_key = data.get("secret_key")
|
||||||
await self.speak()
|
await self.speak()
|
||||||
|
await self.speak(False)
|
||||||
|
|
||||||
async def poll_event(self):
|
async def poll_event(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -592,7 +592,7 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
return utils.find(pred, members)
|
return utils.find(pred, members)
|
||||||
|
|
||||||
def _create_channel(self, name, overwrites, channel_type, category=None, reason=None):
|
def _create_channel(self, name, overwrites, channel_type, category=None, **options):
|
||||||
if overwrites is None:
|
if overwrites is None:
|
||||||
overwrites = {}
|
overwrites = {}
|
||||||
elif not isinstance(overwrites, dict):
|
elif not isinstance(overwrites, dict):
|
||||||
@@ -615,17 +615,24 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
perms.append(payload)
|
perms.append(payload)
|
||||||
|
|
||||||
|
try:
|
||||||
|
options["rate_limit_per_user"] = options.pop("slowmode_delay")
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
parent_id = category.id if category else None
|
parent_id = category.id if category else None
|
||||||
return self._state.http.create_channel(
|
return self._state.http.create_channel(
|
||||||
self.id,
|
self.id,
|
||||||
name,
|
|
||||||
channel_type.value,
|
channel_type.value,
|
||||||
|
name=name,
|
||||||
parent_id=parent_id,
|
parent_id=parent_id,
|
||||||
permission_overwrites=perms,
|
permission_overwrites=perms,
|
||||||
reason=reason,
|
**options
|
||||||
)
|
)
|
||||||
|
|
||||||
async def create_text_channel(self, name, *, overwrites=None, category=None, reason=None):
|
async def create_text_channel(
|
||||||
|
self, name, *, overwrites=None, category=None, reason=None, **options
|
||||||
|
):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Creates a :class:`TextChannel` for the guild.
|
Creates a :class:`TextChannel` for the guild.
|
||||||
@@ -637,6 +644,12 @@ class Guild(Hashable):
|
|||||||
channel upon creation. This parameter expects a :class:`dict` of
|
channel upon creation. This parameter expects a :class:`dict` of
|
||||||
overwrites with the target (either a :class:`Member` or a :class:`Role`)
|
overwrites with the target (either a :class:`Member` or a :class:`Role`)
|
||||||
as the key and a :class:`PermissionOverwrite` as the value.
|
as the key and a :class:`PermissionOverwrite` as the value.
|
||||||
|
|
||||||
|
Note
|
||||||
|
--------
|
||||||
|
Creating a channel of a specified position will not update the position of
|
||||||
|
other channels to follow suit. A follow-up call to :meth:`~TextChannel.edit`
|
||||||
|
will be required to update the position of the channel in the channel list.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
----------
|
----------
|
||||||
@@ -660,7 +673,7 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
name: str
|
name: :class:`str`
|
||||||
The channel's name.
|
The channel's name.
|
||||||
overwrites
|
overwrites
|
||||||
A :class:`dict` of target (either a role or a member) to
|
A :class:`dict` of target (either a role or a member) to
|
||||||
@@ -670,7 +683,17 @@ class Guild(Hashable):
|
|||||||
The category to place the newly created channel under.
|
The category to place the newly created channel under.
|
||||||
The permissions will be automatically synced to category if no
|
The permissions will be automatically synced to category if no
|
||||||
overwrites are provided.
|
overwrites are provided.
|
||||||
reason: Optional[str]
|
position: :class:`int`
|
||||||
|
The position in the channel list. This is a number that starts
|
||||||
|
at 0. e.g. the top channel is position 0.
|
||||||
|
topic: Optional[:class:`str`]
|
||||||
|
The new channel's topic.
|
||||||
|
slowmode_delay: :class:`int`
|
||||||
|
Specifies the slowmode rate limit for user in this channel.
|
||||||
|
The maximum value possible is `120`.
|
||||||
|
nsfw: :class:`bool`
|
||||||
|
To mark the channel as NSFW or not.
|
||||||
|
reason: Optional[:class:`str`]
|
||||||
The reason for creating this channel. Shows up on the audit log.
|
The reason for creating this channel. Shows up on the audit log.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
@@ -688,7 +711,7 @@ class Guild(Hashable):
|
|||||||
The channel that was just created.
|
The channel that was just created.
|
||||||
"""
|
"""
|
||||||
data = await self._create_channel(
|
data = await self._create_channel(
|
||||||
name, overwrites, ChannelType.text, category, reason=reason
|
name, overwrites, ChannelType.text, category, reason=reason, **options
|
||||||
)
|
)
|
||||||
channel = TextChannel(state=self._state, guild=self, data=data)
|
channel = TextChannel(state=self._state, guild=self, data=data)
|
||||||
|
|
||||||
@@ -696,13 +719,23 @@ class Guild(Hashable):
|
|||||||
self._channels[channel.id] = channel
|
self._channels[channel.id] = channel
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
async def create_voice_channel(self, name, *, overwrites=None, category=None, reason=None):
|
async def create_voice_channel(
|
||||||
|
self, name, *, overwrites=None, category=None, reason=None, **options
|
||||||
|
):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Same as :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead.
|
This is similar to :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead, in addition
|
||||||
|
to having the following new parameters.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
bitrate: :class:`int`
|
||||||
|
The channel's preferred audio bitrate in bits per second.
|
||||||
|
user_limit: :class:`int`
|
||||||
|
The channel's limit for number of members that can be in a voice channel.
|
||||||
"""
|
"""
|
||||||
data = await self._create_channel(
|
data = await self._create_channel(
|
||||||
name, overwrites, ChannelType.voice, category, reason=reason
|
name, overwrites, ChannelType.voice, category, reason=reason, **options
|
||||||
)
|
)
|
||||||
channel = VoiceChannel(state=self._state, guild=self, data=data)
|
channel = VoiceChannel(state=self._state, guild=self, data=data)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -152,7 +152,7 @@ class HTTPClient:
|
|||||||
# wait until the global lock is complete
|
# wait until the global lock is complete
|
||||||
await self._global_over.wait()
|
await self._global_over.wait()
|
||||||
|
|
||||||
await lock
|
await lock.acquire()
|
||||||
with MaybeUnlock(lock) as maybe_lock:
|
with MaybeUnlock(lock) as maybe_lock:
|
||||||
for tries in range(5):
|
for tries in range(5):
|
||||||
async with self._session.request(method, url, **kwargs) as r:
|
async with self._session.request(method, url, **kwargs) as r:
|
||||||
@@ -596,23 +596,21 @@ class HTTPClient:
|
|||||||
r = Route("PATCH", "/guilds/{guild_id}/channels", guild_id=guild_id)
|
r = Route("PATCH", "/guilds/{guild_id}/channels", guild_id=guild_id)
|
||||||
return self.request(r, json=data, reason=reason)
|
return self.request(r, json=data, reason=reason)
|
||||||
|
|
||||||
def create_channel(
|
def create_channel(self, guild_id, channel_type, *, reason=None, **options):
|
||||||
self,
|
payload = {"type": channel_type}
|
||||||
guild_id,
|
|
||||||
name,
|
|
||||||
channel_type,
|
|
||||||
parent_id=None,
|
|
||||||
permission_overwrites=None,
|
|
||||||
*,
|
|
||||||
reason=None
|
|
||||||
):
|
|
||||||
payload = {"name": name, "type": channel_type}
|
|
||||||
|
|
||||||
if permission_overwrites is not None:
|
valid_keys = (
|
||||||
payload["permission_overwrites"] = permission_overwrites
|
"name",
|
||||||
|
"parent_id",
|
||||||
if parent_id is not None:
|
"topic",
|
||||||
payload["parent_id"] = parent_id
|
"bitrate",
|
||||||
|
"nsfw",
|
||||||
|
"user_limit",
|
||||||
|
"position",
|
||||||
|
"permission_overwrites",
|
||||||
|
"rate_limit_per_user",
|
||||||
|
)
|
||||||
|
payload.update({k: v for k, v in options.items() if k in valid_keys and v is not None})
|
||||||
|
|
||||||
return self.request(
|
return self.request(
|
||||||
Route("POST", "/guilds/{guild_id}/channels", guild_id=guild_id),
|
Route("POST", "/guilds/{guild_id}/channels", guild_id=guild_id),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -160,7 +160,7 @@ class ReactionIterator(_AsyncIterator):
|
|||||||
|
|
||||||
if data:
|
if data:
|
||||||
self.limit -= retrieve
|
self.limit -= retrieve
|
||||||
self.after = Object(id=int(data[0]["id"]))
|
self.after = Object(id=int(data[-1]["id"]))
|
||||||
|
|
||||||
if self.guild is None:
|
if self.guild is None:
|
||||||
for element in reversed(data):
|
for element in reversed(data):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -157,7 +157,7 @@ class Permissions:
|
|||||||
- kick_members
|
- kick_members
|
||||||
- ban_members
|
- ban_members
|
||||||
- administrator
|
- administrator
|
||||||
- change_nicknames
|
- change_nickname
|
||||||
- manage_nicknames
|
- manage_nicknames
|
||||||
"""
|
"""
|
||||||
return cls(0b00110011111101111111110001010001)
|
return cls(0b00110011111101111111110001010001)
|
||||||
@@ -543,6 +543,10 @@ class PermissionOverwrite:
|
|||||||
+-----------+------------------------------------------+
|
+-----------+------------------------------------------+
|
||||||
| Operation | Description |
|
| Operation | Description |
|
||||||
+===========+==========================================+
|
+===========+==========================================+
|
||||||
|
| x == y | Checks if two overwrites are equal. |
|
||||||
|
+-----------+------------------------------------------+
|
||||||
|
| x != y | Checks if two overwrites are not equal. |
|
||||||
|
+-----------+------------------------------------------+
|
||||||
| iter(x) | Returns an iterator of (perm, value) |
|
| iter(x) | Returns an iterator of (perm, value) |
|
||||||
| | pairs. This allows this class to be used |
|
| | pairs. This allows this class to be used |
|
||||||
| | as an iterable in e.g. set/list/dict |
|
| | as an iterable in e.g. set/list/dict |
|
||||||
@@ -566,6 +570,9 @@ class PermissionOverwrite:
|
|||||||
|
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self._values == other._values
|
||||||
|
|
||||||
def _set(self, key, value):
|
def _set(self, key, value):
|
||||||
if value not in (True, None, False):
|
if value not in (True, None, False):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -27,6 +27,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
import threading
|
import threading
|
||||||
import subprocess
|
import subprocess
|
||||||
import audioop
|
import audioop
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import shlex
|
import shlex
|
||||||
import time
|
import time
|
||||||
@@ -286,6 +287,7 @@ class AudioPlayer(threading.Thread):
|
|||||||
|
|
||||||
# getattr lookup speed ups
|
# getattr lookup speed ups
|
||||||
play_audio = self.client.send_audio_packet
|
play_audio = self.client.send_audio_packet
|
||||||
|
self._speak(True)
|
||||||
|
|
||||||
while not self._end.is_set():
|
while not self._end.is_set():
|
||||||
# are we paused?
|
# are we paused?
|
||||||
@@ -334,14 +336,19 @@ class AudioPlayer(threading.Thread):
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
self._end.set()
|
self._end.set()
|
||||||
self._resumed.set()
|
self._resumed.set()
|
||||||
|
self._speak(False)
|
||||||
|
|
||||||
def pause(self):
|
def pause(self, *, update_speaking=True):
|
||||||
self._resumed.clear()
|
self._resumed.clear()
|
||||||
|
if update_speaking:
|
||||||
|
self._speak(False)
|
||||||
|
|
||||||
def resume(self):
|
def resume(self, *, update_speaking=True):
|
||||||
self.loops = 0
|
self.loops = 0
|
||||||
self._start = time.time()
|
self._start = time.time()
|
||||||
self._resumed.set()
|
self._resumed.set()
|
||||||
|
if update_speaking:
|
||||||
|
self._speak(True)
|
||||||
|
|
||||||
def is_playing(self):
|
def is_playing(self):
|
||||||
return self._resumed.is_set() and not self._end.is_set()
|
return self._resumed.is_set() and not self._end.is_set()
|
||||||
@@ -351,6 +358,12 @@ class AudioPlayer(threading.Thread):
|
|||||||
|
|
||||||
def _set_source(self, source):
|
def _set_source(self, source):
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self.pause()
|
self.pause(update_speaking=False)
|
||||||
self.source = source
|
self.source = source
|
||||||
self.resume()
|
self.resume(update_speaking=False)
|
||||||
|
|
||||||
|
def _speak(self, speaking):
|
||||||
|
try:
|
||||||
|
asyncio.run_coroutine_threadsafe(self.client.ws.speak(speaking), self.client.loop)
|
||||||
|
except Exception as e:
|
||||||
|
log.info("Speaking call in player failed: %s", e)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2018 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -103,7 +103,7 @@ class RawReactionActionEvent:
|
|||||||
message_id: :class:`int`
|
message_id: :class:`int`
|
||||||
The message ID that got or lost a reaction.
|
The message ID that got or lost a reaction.
|
||||||
user_id: :class:`int`
|
user_id: :class:`int`
|
||||||
The user ID who added or removed the reaction.
|
The user ID who added the reaction or whose reaction was removed.
|
||||||
channel_id: :class:`int`
|
channel_id: :class:`int`
|
||||||
The channel ID where the reaction got added or removed.
|
The channel ID where the reaction got added or removed.
|
||||||
guild_id: Optional[:class:`int`]
|
guild_id: Optional[:class:`int`]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -26,6 +26,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
import array
|
import array
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import unicodedata
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from bisect import bisect_left
|
from bisect import bisect_left
|
||||||
import datetime
|
import datetime
|
||||||
@@ -33,7 +34,7 @@ from email.utils import parsedate_to_datetime
|
|||||||
import functools
|
import functools
|
||||||
from inspect import isawaitable as _isawaitable
|
from inspect import isawaitable as _isawaitable
|
||||||
import json
|
import json
|
||||||
from re import split as re_split
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from .errors import InvalidArgument
|
from .errors import InvalidArgument
|
||||||
@@ -83,7 +84,7 @@ def cached_slot_property(name):
|
|||||||
|
|
||||||
def parse_time(timestamp):
|
def parse_time(timestamp):
|
||||||
if timestamp:
|
if timestamp:
|
||||||
return datetime.datetime(*map(int, re_split(r"[^\d]", timestamp.replace("+00:00", ""))))
|
return datetime.datetime(*map(int, re.split(r"[^\d]", timestamp.replace("+00:00", ""))))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -265,9 +266,7 @@ def _get_mime_type_for_image(data):
|
|||||||
return "image/png"
|
return "image/png"
|
||||||
elif data.startswith(b"\xFF\xD8") and data.rstrip(b"\0").endswith(b"\xFF\xD9"):
|
elif data.startswith(b"\xFF\xD8") and data.rstrip(b"\0").endswith(b"\xFF\xD9"):
|
||||||
return "image/jpeg"
|
return "image/jpeg"
|
||||||
elif data.startswith(b"\x47\x49\x46\x38\x37\x61") or data.startswith(
|
elif data.startswith((b"\x47\x49\x46\x38\x37\x61", b"\x47\x49\x46\x38\x39\x61")):
|
||||||
b"\x47\x49\x46\x38\x39\x61"
|
|
||||||
):
|
|
||||||
return "image/gif"
|
return "image/gif"
|
||||||
elif data.startswith(b"RIFF") and data[8:12] == b"WEBP":
|
elif data.startswith(b"RIFF") and data[8:12] == b"WEBP":
|
||||||
return "image/webp"
|
return "image/webp"
|
||||||
@@ -351,3 +350,20 @@ class SnowflakeList(array.array):
|
|||||||
def has(self, element):
|
def has(self, element):
|
||||||
i = bisect_left(self, element)
|
i = bisect_left(self, element)
|
||||||
return i != len(self) and self[i] == element
|
return i != len(self) and self[i] == element
|
||||||
|
|
||||||
|
|
||||||
|
_IS_ASCII = re.compile(r"^[\x00-\x7f]+$")
|
||||||
|
|
||||||
|
|
||||||
|
def _string_width(string, *, _IS_ASCII=_IS_ASCII):
|
||||||
|
"""Returns string's width."""
|
||||||
|
match = _IS_ASCII.match(string)
|
||||||
|
if match:
|
||||||
|
return match.endpos
|
||||||
|
|
||||||
|
UNICODE_WIDE_CHAR_TYPE = "WFA"
|
||||||
|
width = 0
|
||||||
|
func = unicodedata.east_asian_width
|
||||||
|
for char in string:
|
||||||
|
width += 2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1
|
||||||
|
return width
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -105,6 +105,7 @@ class VoiceClient:
|
|||||||
self._connected = threading.Event()
|
self._connected = threading.Event()
|
||||||
self._handshake_complete = asyncio.Event(loop=self.loop)
|
self._handshake_complete = asyncio.Event(loop=self.loop)
|
||||||
|
|
||||||
|
self.mode = None
|
||||||
self._connections = 0
|
self._connections = 0
|
||||||
self.sequence = 0
|
self.sequence = 0
|
||||||
self.timestamp = 0
|
self.timestamp = 0
|
||||||
@@ -113,6 +114,7 @@ class VoiceClient:
|
|||||||
self.encoder = opus.Encoder()
|
self.encoder = opus.Encoder()
|
||||||
|
|
||||||
warn_nacl = not has_nacl
|
warn_nacl = not has_nacl
|
||||||
|
supported_modes = ("xsalsa20_poly1305_suffix", "xsalsa20_poly1305")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def guild(self):
|
def guild(self):
|
||||||
@@ -305,22 +307,30 @@ class VoiceClient:
|
|||||||
|
|
||||||
def _get_voice_packet(self, data):
|
def _get_voice_packet(self, data):
|
||||||
header = bytearray(12)
|
header = bytearray(12)
|
||||||
nonce = bytearray(24)
|
|
||||||
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
|
||||||
|
|
||||||
# Formulate header
|
# Formulate rtp header
|
||||||
header[0] = 0x80
|
header[0] = 0x80
|
||||||
header[1] = 0x78
|
header[1] = 0x78
|
||||||
struct.pack_into(">H", header, 2, self.sequence)
|
struct.pack_into(">H", header, 2, self.sequence)
|
||||||
struct.pack_into(">I", header, 4, self.timestamp)
|
struct.pack_into(">I", header, 4, self.timestamp)
|
||||||
struct.pack_into(">I", header, 8, self.ssrc)
|
struct.pack_into(">I", header, 8, self.ssrc)
|
||||||
|
|
||||||
# Copy header to nonce's first 12 bytes
|
encrypt_packet = getattr(self, "_encrypt_" + self.mode)
|
||||||
|
return encrypt_packet(header, data)
|
||||||
|
|
||||||
|
def _encrypt_xsalsa20_poly1305(self, header, data):
|
||||||
|
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
||||||
|
nonce = bytearray(24)
|
||||||
nonce[:12] = header
|
nonce[:12] = header
|
||||||
|
|
||||||
# Encrypt and return the data
|
|
||||||
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext
|
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext
|
||||||
|
|
||||||
|
def _encrypt_xsalsa20_poly1305_suffix(self, header, data):
|
||||||
|
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
||||||
|
nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE)
|
||||||
|
|
||||||
|
return header + box.encrypt(bytes(data), nonce).ciphertext + nonce
|
||||||
|
|
||||||
def play(self, source, *, after=None):
|
def play(self, source, *, after=None):
|
||||||
"""Plays an :class:`AudioSource`.
|
"""Plays an :class:`AudioSource`.
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Rapptz
|
Copyright (c) 2015-2019 Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@@ -103,7 +103,7 @@ class WebhookAdapter:
|
|||||||
|
|
||||||
def store_user(self, data):
|
def store_user(self, data):
|
||||||
# mocks a ConnectionState for appropriate use for Message
|
# mocks a ConnectionState for appropriate use for Message
|
||||||
return BaseUser(state=self, data=data)
|
return BaseUser(state=self.webhook._state, data=data)
|
||||||
|
|
||||||
def execute_webhook(self, *, payload, wait=False, file=None, files=None):
|
def execute_webhook(self, *, payload, wait=False, file=None, files=None):
|
||||||
if file is not None:
|
if file is not None:
|
||||||
@@ -195,7 +195,7 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
|||||||
# transform into Message object
|
# transform into Message object
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
|
||||||
return Message(data=data, state=self, channel=self.webhook.channel)
|
return Message(data=data, state=self.webhook._state, channel=self.webhook.channel)
|
||||||
|
|
||||||
|
|
||||||
class RequestsWebhookAdapter(WebhookAdapter):
|
class RequestsWebhookAdapter(WebhookAdapter):
|
||||||
@@ -279,7 +279,7 @@ class RequestsWebhookAdapter(WebhookAdapter):
|
|||||||
# transform into Message object
|
# transform into Message object
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
|
||||||
return Message(data=response, state=self, channel=self.webhook.channel)
|
return Message(data=response, state=self.webhook._state, channel=self.webhook.channel)
|
||||||
|
|
||||||
|
|
||||||
class Webhook:
|
class Webhook:
|
||||||
|
|||||||
2
make.bat
2
make.bat
@@ -28,7 +28,7 @@ if [%REF%] == [] (
|
|||||||
set REF2=%REF%
|
set REF2=%REF%
|
||||||
)
|
)
|
||||||
pip install --upgrade --no-deps -t . https://github.com/Rapptz/discord.py/archive/!REF2!.tar.gz#egg=discord.py
|
pip install --upgrade --no-deps -t . https://github.com/Rapptz/discord.py/archive/!REF2!.tar.gz#egg=discord.py
|
||||||
del /S /Q "discord.py*.egg-info"
|
del /S /Q "discord.py*-info"
|
||||||
for /F %%i in ('dir /S /B discord.py*.egg-info') do rmdir /S /Q %%i
|
for /F %%i in ('dir /S /B discord.py*.egg-info') do rmdir /S /Q %%i
|
||||||
goto reformat
|
goto reformat
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ def main():
|
|||||||
gathered = asyncio.gather(*pending, loop=red.loop, return_exceptions=True)
|
gathered = asyncio.gather(*pending, loop=red.loop, return_exceptions=True)
|
||||||
gathered.cancel()
|
gathered.cancel()
|
||||||
try:
|
try:
|
||||||
red.rpc.server.close()
|
loop.run_until_complete(red.rpc.close())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from aiohttp import ClientSession
|
|
||||||
import shutil
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .audio import Audio
|
from .audio import Audio
|
||||||
from .manager import start_lavalink_server
|
from .manager import start_lavalink_server, maybe_download_lavalink
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.data_manager import cog_data_path
|
from redbot.core.data_manager import cog_data_path
|
||||||
import redbot.core
|
import redbot.core
|
||||||
@@ -22,30 +20,6 @@ APP_YML_FILE = LAVALINK_DOWNLOAD_DIR / "application.yml"
|
|||||||
BUNDLED_APP_YML_FILE = Path(__file__).parent / "data/application.yml"
|
BUNDLED_APP_YML_FILE = Path(__file__).parent / "data/application.yml"
|
||||||
|
|
||||||
|
|
||||||
async def download_lavalink(session):
|
|
||||||
with LAVALINK_JAR_FILE.open(mode="wb") as f:
|
|
||||||
async with session.get(LAVALINK_DOWNLOAD_URL) as resp:
|
|
||||||
while True:
|
|
||||||
chunk = await resp.content.read(512)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
f.write(chunk)
|
|
||||||
|
|
||||||
|
|
||||||
async def maybe_download_lavalink(loop, cog):
|
|
||||||
jar_exists = LAVALINK_JAR_FILE.exists()
|
|
||||||
current_build = redbot.core.VersionInfo.from_json(await cog.config.current_version())
|
|
||||||
|
|
||||||
if not jar_exists or current_build < redbot.core.version_info:
|
|
||||||
log.info("Downloading Lavalink.jar")
|
|
||||||
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
async with ClientSession(loop=loop) as session:
|
|
||||||
await download_lavalink(session)
|
|
||||||
await cog.config.current_version.set(redbot.core.version_info.to_json())
|
|
||||||
|
|
||||||
shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot: commands.Bot):
|
async def setup(bot: commands.Bot):
|
||||||
cog = Audio(bot)
|
cog = Audio(bot)
|
||||||
if not await cog.config.use_external_lavalink():
|
if not await cog.config.use_external_lavalink():
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from redbot.core.utils.menus import (
|
|||||||
)
|
)
|
||||||
from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
|
from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from .manager import shutdown_lavalink_server
|
from .manager import shutdown_lavalink_server, start_lavalink_server, maybe_download_lavalink
|
||||||
|
|
||||||
_ = Translator("Audio", __file__)
|
_ = Translator("Audio", __file__)
|
||||||
|
|
||||||
@@ -73,26 +73,47 @@ class Audio(commands.Cog):
|
|||||||
self.config.register_global(**default_global)
|
self.config.register_global(**default_global)
|
||||||
self.skip_votes = {}
|
self.skip_votes = {}
|
||||||
self.session = aiohttp.ClientSession()
|
self.session = aiohttp.ClientSession()
|
||||||
|
self._connect_task = None
|
||||||
self._disconnect_task = None
|
self._disconnect_task = None
|
||||||
self._cleaned_up = False
|
self._cleaned_up = False
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
host = await self.config.host()
|
self._restart_connect()
|
||||||
password = await self.config.password()
|
self._disconnect_task = self.bot.loop.create_task(self.disconnect_timer())
|
||||||
rest_port = await self.config.rest_port()
|
|
||||||
ws_port = await self.config.ws_port()
|
|
||||||
|
|
||||||
await lavalink.initialize(
|
|
||||||
bot=self.bot,
|
|
||||||
host=host,
|
|
||||||
password=password,
|
|
||||||
rest_port=rest_port,
|
|
||||||
ws_port=ws_port,
|
|
||||||
timeout=60,
|
|
||||||
)
|
|
||||||
lavalink.register_event_listener(self.event_handler)
|
lavalink.register_event_listener(self.event_handler)
|
||||||
|
|
||||||
self._disconnect_task = self.bot.loop.create_task(self.disconnect_timer())
|
def _restart_connect(self):
|
||||||
|
if self._connect_task:
|
||||||
|
self._connect_task.cancel()
|
||||||
|
|
||||||
|
self._connect_task = self.bot.loop.create_task(self.attempt_connect())
|
||||||
|
|
||||||
|
async def attempt_connect(self, timeout: int = 30):
|
||||||
|
while True: # run until success
|
||||||
|
external = await self.config.use_external_lavalink()
|
||||||
|
if not external:
|
||||||
|
shutdown_lavalink_server()
|
||||||
|
await maybe_download_lavalink(self.bot.loop, self)
|
||||||
|
await start_lavalink_server(self.bot.loop)
|
||||||
|
try:
|
||||||
|
host = await self.config.host()
|
||||||
|
password = await self.config.password()
|
||||||
|
rest_port = await self.config.rest_port()
|
||||||
|
ws_port = await self.config.ws_port()
|
||||||
|
|
||||||
|
await lavalink.initialize(
|
||||||
|
bot=self.bot,
|
||||||
|
host=host,
|
||||||
|
password=password,
|
||||||
|
rest_port=rest_port,
|
||||||
|
ws_port=ws_port,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
return # break infinite loop
|
||||||
|
except Exception:
|
||||||
|
if not external:
|
||||||
|
shutdown_lavalink_server()
|
||||||
|
await asyncio.sleep(1) # prevent busylooping
|
||||||
|
|
||||||
async def event_handler(self, player, event_type, extra):
|
async def event_handler(self, player, event_type, extra):
|
||||||
notify = await self.config.guild(player.channel.guild).notify()
|
notify = await self.config.guild(player.channel.guild).notify()
|
||||||
@@ -883,6 +904,10 @@ class Audio(commands.Cog):
|
|||||||
player.store("connect", datetime.datetime.utcnow())
|
player.store("connect", datetime.datetime.utcnow())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
|
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
|
||||||
|
except IndexError:
|
||||||
|
return await self._embed_msg(
|
||||||
|
ctx, _("Connection to Lavalink has not yet been established.")
|
||||||
|
)
|
||||||
if dj_enabled:
|
if dj_enabled:
|
||||||
if not await self._can_instaskip(ctx, ctx.author):
|
if not await self._can_instaskip(ctx, ctx.author):
|
||||||
return await self._embed_msg(ctx, _("You need the DJ role to queue tracks."))
|
return await self._embed_msg(ctx, _("You need the DJ role to queue tracks."))
|
||||||
@@ -1388,9 +1413,15 @@ class Audio(commands.Cog):
|
|||||||
await lavalink.connect(ctx.author.voice.channel)
|
await lavalink.connect(ctx.author.voice.channel)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("connect", datetime.datetime.utcnow())
|
player.store("connect", datetime.datetime.utcnow())
|
||||||
|
except IndexError:
|
||||||
|
await self._embed_msg(
|
||||||
|
ctx, _("Connection to Lavalink has not yet been established.")
|
||||||
|
)
|
||||||
|
return False
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
await self._embed_msg(ctx, _("Connect to a voice channel first."))
|
await self._embed_msg(ctx, _("Connect to a voice channel first."))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("channel", ctx.channel.id)
|
player.store("channel", ctx.channel.id)
|
||||||
player.store("guild", ctx.guild.id)
|
player.store("guild", ctx.guild.id)
|
||||||
@@ -1768,6 +1799,10 @@ class Audio(commands.Cog):
|
|||||||
player.store("connect", datetime.datetime.utcnow())
|
player.store("connect", datetime.datetime.utcnow())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
|
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
|
||||||
|
except IndexError:
|
||||||
|
return await self._embed_msg(
|
||||||
|
ctx, _("Connection to Lavalink has not yet been established.")
|
||||||
|
)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||||
player.store("channel", ctx.channel.id)
|
player.store("channel", ctx.channel.id)
|
||||||
@@ -1852,6 +1887,10 @@ class Audio(commands.Cog):
|
|||||||
player.store("connect", datetime.datetime.utcnow())
|
player.store("connect", datetime.datetime.utcnow())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
|
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
|
||||||
|
except IndexError:
|
||||||
|
return await self._embed_msg(
|
||||||
|
ctx, _("Connection to Lavalink has not yet been established.")
|
||||||
|
)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
jukebox_price = await self.config.guild(ctx.guild).jukebox_price()
|
jukebox_price = await self.config.guild(ctx.guild).jukebox_price()
|
||||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||||
@@ -1872,7 +1911,6 @@ class Audio(commands.Cog):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
search_choice = tracks[-1]
|
search_choice = tracks[-1]
|
||||||
try:
|
try:
|
||||||
search_check = search_choice.uri
|
|
||||||
if "localtracks" in search_choice.uri:
|
if "localtracks" in search_choice.uri:
|
||||||
if search_choice.title == "Unknown title":
|
if search_choice.title == "Unknown title":
|
||||||
description = "**{} - {}**\n{}".format(
|
description = "**{} - {}**\n{}".format(
|
||||||
@@ -2308,6 +2346,7 @@ class Audio(commands.Cog):
|
|||||||
"""Toggle using external lavalink servers."""
|
"""Toggle using external lavalink servers."""
|
||||||
external = await self.config.use_external_lavalink()
|
external = await self.config.use_external_lavalink()
|
||||||
await self.config.use_external_lavalink.set(not external)
|
await self.config.use_external_lavalink.set(not external)
|
||||||
|
|
||||||
if external:
|
if external:
|
||||||
await self.config.host.set("localhost")
|
await self.config.host.set("localhost")
|
||||||
await self.config.password.set("youshallnotpass")
|
await self.config.password.set("youshallnotpass")
|
||||||
@@ -2320,13 +2359,15 @@ class Audio(commands.Cog):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
embed.set_footer(text=_("Defaults reset."))
|
embed.set_footer(text=_("Defaults reset."))
|
||||||
return await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
else:
|
else:
|
||||||
await self._embed_msg(
|
await self._embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
_("External lavalink server: {true_or_false}.").format(true_or_false=not external),
|
_("External lavalink server: {true_or_false}.").format(true_or_false=not external),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._restart_connect()
|
||||||
|
|
||||||
@llsetup.command()
|
@llsetup.command()
|
||||||
async def host(self, ctx, host):
|
async def host(self, ctx, host):
|
||||||
"""Set the lavalink server host."""
|
"""Set the lavalink server host."""
|
||||||
@@ -2340,6 +2381,8 @@ class Audio(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await self._embed_msg(ctx, _("Host set to {host}.").format(host=host))
|
await self._embed_msg(ctx, _("Host set to {host}.").format(host=host))
|
||||||
|
|
||||||
|
self._restart_connect()
|
||||||
|
|
||||||
@llsetup.command()
|
@llsetup.command()
|
||||||
async def password(self, ctx, password):
|
async def password(self, ctx, password):
|
||||||
"""Set the lavalink server password."""
|
"""Set the lavalink server password."""
|
||||||
@@ -2356,6 +2399,8 @@ class Audio(commands.Cog):
|
|||||||
ctx, _("Server password set to {password}.").format(password=password)
|
ctx, _("Server password set to {password}.").format(password=password)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._restart_connect()
|
||||||
|
|
||||||
@llsetup.command()
|
@llsetup.command()
|
||||||
async def restport(self, ctx, rest_port: int):
|
async def restport(self, ctx, rest_port: int):
|
||||||
"""Set the lavalink REST server port."""
|
"""Set the lavalink REST server port."""
|
||||||
@@ -2370,6 +2415,8 @@ class Audio(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await self._embed_msg(ctx, _("REST port set to {port}.").format(port=rest_port))
|
await self._embed_msg(ctx, _("REST port set to {port}.").format(port=rest_port))
|
||||||
|
|
||||||
|
self._restart_connect()
|
||||||
|
|
||||||
@llsetup.command()
|
@llsetup.command()
|
||||||
async def wsport(self, ctx, ws_port: int):
|
async def wsport(self, ctx, ws_port: int):
|
||||||
"""Set the lavalink websocket server port."""
|
"""Set the lavalink websocket server port."""
|
||||||
@@ -2384,6 +2431,8 @@ class Audio(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await self._embed_msg(ctx, _("Websocket port set to {port}.").format(port=ws_port))
|
await self._embed_msg(ctx, _("Websocket port set to {port}.").format(port=ws_port))
|
||||||
|
|
||||||
|
self._restart_connect()
|
||||||
|
|
||||||
async def _check_external(self):
|
async def _check_external(self):
|
||||||
external = await self.config.use_external_lavalink()
|
external = await self.config.use_external_lavalink()
|
||||||
if not external:
|
if not external:
|
||||||
@@ -2530,7 +2579,7 @@ class Audio(commands.Cog):
|
|||||||
try:
|
try:
|
||||||
query_url = urlparse(url)
|
query_url = urlparse(url)
|
||||||
return all([query_url.scheme, query_url.netloc, query_url.path])
|
return all([query_url.scheme, query_url.netloc, query_url.path])
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2616,11 +2665,33 @@ class Audio(commands.Cog):
|
|||||||
def __unload(self):
|
def __unload(self):
|
||||||
if not self._cleaned_up:
|
if not self._cleaned_up:
|
||||||
self.session.detach()
|
self.session.detach()
|
||||||
|
|
||||||
if self._disconnect_task:
|
if self._disconnect_task:
|
||||||
self._disconnect_task.cancel()
|
self._disconnect_task.cancel()
|
||||||
|
|
||||||
|
if self._connect_task:
|
||||||
|
self._connect_task.cancel()
|
||||||
|
|
||||||
lavalink.unregister_event_listener(self.event_handler)
|
lavalink.unregister_event_listener(self.event_handler)
|
||||||
self.bot.loop.create_task(lavalink.close())
|
self.bot.loop.create_task(lavalink.close())
|
||||||
shutdown_lavalink_server()
|
shutdown_lavalink_server()
|
||||||
self._cleaned_up = True
|
self._cleaned_up = True
|
||||||
|
|
||||||
__del__ = __unload
|
__del__ = __unload
|
||||||
|
|
||||||
|
async def on_guild_remove(self, guild: discord.Guild):
|
||||||
|
"""
|
||||||
|
This is to clean up players when
|
||||||
|
the bot either leaves or is removed from a guild
|
||||||
|
"""
|
||||||
|
channels = {
|
||||||
|
x # x is a voice_channel
|
||||||
|
for y in [g.voice_channels for g in self.bot.guilds]
|
||||||
|
for x in y # y is a list of voice channels
|
||||||
|
} # Yes, this is ugly. It's also the most performant and commented.
|
||||||
|
|
||||||
|
zombie_players = {p for p in lavalink.player_manager.players if p.channel not in channels}
|
||||||
|
# Do not unroll to combine with next line.
|
||||||
|
# Can result in iterator changing size during context switching.
|
||||||
|
for zombie in zombie_players:
|
||||||
|
await zombie.destroy()
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import re
|
|||||||
from subprocess import Popen, DEVNULL
|
from subprocess import Popen, DEVNULL
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
|
||||||
|
import redbot.core
|
||||||
|
|
||||||
_JavaVersion = Tuple[int, int]
|
_JavaVersion = Tuple[int, int]
|
||||||
|
|
||||||
log = logging.getLogger("red.audio.manager")
|
log = logging.getLogger("red.audio.manager")
|
||||||
@@ -108,6 +112,10 @@ async def start_lavalink_server(loop):
|
|||||||
start_cmd = "java {} -jar {}".format(extra_flags, LAVALINK_JAR_FILE.resolve())
|
start_cmd = "java {} -jar {}".format(extra_flags, LAVALINK_JAR_FILE.resolve())
|
||||||
|
|
||||||
global proc
|
global proc
|
||||||
|
|
||||||
|
if proc and proc.poll() is None:
|
||||||
|
return # already running
|
||||||
|
|
||||||
proc = Popen(
|
proc = Popen(
|
||||||
shlex.split(start_cmd, posix=os.name == "posix"),
|
shlex.split(start_cmd, posix=os.name == "posix"),
|
||||||
cwd=str(LAVALINK_DOWNLOAD_DIR),
|
cwd=str(LAVALINK_DOWNLOAD_DIR),
|
||||||
@@ -121,10 +129,39 @@ async def start_lavalink_server(loop):
|
|||||||
|
|
||||||
|
|
||||||
def shutdown_lavalink_server():
|
def shutdown_lavalink_server():
|
||||||
log.info("Shutting down lavalink server.")
|
global shutdown
|
||||||
SHUTDOWN.set()
|
shutdown = True
|
||||||
global proc
|
global proc
|
||||||
if proc is not None:
|
if proc is not None:
|
||||||
|
log.info("Shutting down lavalink server.")
|
||||||
proc.terminate()
|
proc.terminate()
|
||||||
proc.wait()
|
proc.wait()
|
||||||
proc = None
|
proc = None
|
||||||
|
|
||||||
|
|
||||||
|
async def download_lavalink(session):
|
||||||
|
from . import LAVALINK_DOWNLOAD_URL, LAVALINK_JAR_FILE
|
||||||
|
|
||||||
|
with LAVALINK_JAR_FILE.open(mode="wb") as f:
|
||||||
|
async with session.get(LAVALINK_DOWNLOAD_URL) as resp:
|
||||||
|
while True:
|
||||||
|
chunk = await resp.content.read(512)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
async def maybe_download_lavalink(loop, cog):
|
||||||
|
from . import LAVALINK_DOWNLOAD_DIR, LAVALINK_JAR_FILE, BUNDLED_APP_YML_FILE, APP_YML_FILE
|
||||||
|
|
||||||
|
jar_exists = LAVALINK_JAR_FILE.exists()
|
||||||
|
current_build = redbot.core.VersionInfo.from_json(await cog.config.current_version())
|
||||||
|
|
||||||
|
if not jar_exists or current_build < redbot.core.version_info:
|
||||||
|
log.info("Downloading Lavalink.jar")
|
||||||
|
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
async with ClientSession(loop=loop) as session:
|
||||||
|
await download_lavalink(session)
|
||||||
|
await cog.config.current_version.set(redbot.core.version_info.to_json())
|
||||||
|
|
||||||
|
shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ class Downloader(commands.Cog):
|
|||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def pipinstall(self, ctx, *deps: str):
|
async def pipinstall(self, ctx, *deps: str):
|
||||||
"""Install a group of dependencies using pip."""
|
"""Install a group of dependencies using pip."""
|
||||||
|
if not deps:
|
||||||
|
return await ctx.send_help()
|
||||||
repo = Repo("", "", "", Path.cwd(), loop=ctx.bot.loop)
|
repo = Repo("", "", "", Path.cwd(), loop=ctx.bot.loop)
|
||||||
success = await repo.install_raw_requirements(deps, self.LIB_PATH)
|
success = await repo.install_raw_requirements(deps, self.LIB_PATH)
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import asyncio
|
|||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import re
|
import re
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import run as sp_run, PIPE
|
from subprocess import run as sp_run, PIPE
|
||||||
|
from string import Formatter
|
||||||
from sys import executable
|
from sys import executable
|
||||||
from typing import Tuple, MutableMapping, Union, Optional
|
from typing import List, Tuple, Iterable, MutableMapping, Union, Optional
|
||||||
|
|
||||||
from redbot.core import data_manager, commands
|
from redbot.core import data_manager, commands
|
||||||
from redbot.core.utils import safe_delete
|
from redbot.core.utils import safe_delete
|
||||||
@@ -22,6 +24,17 @@ from .log import log
|
|||||||
_ = Translator("RepoManager", __file__)
|
_ = Translator("RepoManager", __file__)
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessFormatter(Formatter):
|
||||||
|
def vformat(self, format_string, args, kwargs):
|
||||||
|
return shlex.split(super().vformat(format_string, args, kwargs))
|
||||||
|
|
||||||
|
def get_value(self, key, args, kwargs):
|
||||||
|
obj = super().get_value(key, args, kwargs)
|
||||||
|
if isinstance(obj, str) or not isinstance(obj, Iterable):
|
||||||
|
return shlex.quote(str(obj))
|
||||||
|
return " ".join(shlex.quote(str(o)) for o in obj)
|
||||||
|
|
||||||
|
|
||||||
class Repo(RepoJSONMixin):
|
class Repo(RepoJSONMixin):
|
||||||
GIT_CLONE = "git clone --recurse-submodules -b {branch} {url} {folder}"
|
GIT_CLONE = "git clone --recurse-submodules -b {branch} {url} {folder}"
|
||||||
GIT_CLONE_NO_BRANCH = "git clone --recurse-submodules {url} {folder}"
|
GIT_CLONE_NO_BRANCH = "git clone --recurse-submodules {url} {folder}"
|
||||||
@@ -94,8 +107,11 @@ class Repo(RepoJSONMixin):
|
|||||||
:return: Mapping of filename -> status_letter
|
:return: Mapping of filename -> status_letter
|
||||||
"""
|
"""
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_DIFF_FILE_STATUS.format(
|
ProcessFormatter().format(
|
||||||
path=self.folder_path, old_hash=old_hash, new_hash=new_hash
|
self.GIT_DIFF_FILE_STATUS,
|
||||||
|
path=self.folder_path,
|
||||||
|
old_hash=old_hash,
|
||||||
|
new_hash=new_hash,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,7 +140,8 @@ class Repo(RepoJSONMixin):
|
|||||||
:return: Git commit note log
|
:return: Git commit note log
|
||||||
"""
|
"""
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_LOG.format(
|
ProcessFormatter().format(
|
||||||
|
self.GIT_LOG,
|
||||||
path=self.folder_path,
|
path=self.folder_path,
|
||||||
old_hash=old_commit_hash,
|
old_hash=old_commit_hash,
|
||||||
relative_file_path=relative_file_path,
|
relative_file_path=relative_file_path,
|
||||||
@@ -190,13 +207,15 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
if self.branch is not None:
|
if self.branch is not None:
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_CLONE.format(
|
ProcessFormatter().format(
|
||||||
branch=self.branch, url=self.url, folder=self.folder_path
|
self.GIT_CLONE, branch=self.branch, url=self.url, folder=self.folder_path
|
||||||
).split()
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_CLONE_NO_BRANCH.format(url=self.url, folder=self.folder_path).split()
|
ProcessFormatter().format(
|
||||||
|
self.GIT_CLONE_NO_BRANCH, url=self.url, folder=self.folder_path
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode:
|
if p.returncode:
|
||||||
@@ -226,7 +245,9 @@ class Repo(RepoJSONMixin):
|
|||||||
"A git repo does not exist at path: {}".format(self.folder_path)
|
"A git repo does not exist at path: {}".format(self.folder_path)
|
||||||
)
|
)
|
||||||
|
|
||||||
p = await self._run(self.GIT_CURRENT_BRANCH.format(path=self.folder_path).split())
|
p = await self._run(
|
||||||
|
ProcessFormatter().format(self.GIT_CURRENT_BRANCH, path=self.folder_path)
|
||||||
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise errors.GitException(
|
raise errors.GitException(
|
||||||
@@ -259,7 +280,7 @@ class Repo(RepoJSONMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_LATEST_COMMIT.format(path=self.folder_path, branch=branch).split()
|
ProcessFormatter().format(self.GIT_LATEST_COMMIT, path=self.folder_path, branch=branch)
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
@@ -290,7 +311,7 @@ class Repo(RepoJSONMixin):
|
|||||||
if folder is None:
|
if folder is None:
|
||||||
folder = self.folder_path
|
folder = self.folder_path
|
||||||
|
|
||||||
p = await self._run(Repo.GIT_DISCOVER_REMOTE_URL.format(path=folder).split())
|
p = await self._run(ProcessFormatter().format(Repo.GIT_DISCOVER_REMOTE_URL, path=folder))
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise errors.NoRemoteURL("Unable to discover a repo URL.")
|
raise errors.NoRemoteURL("Unable to discover a repo URL.")
|
||||||
@@ -316,7 +337,7 @@ class Repo(RepoJSONMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_HARD_RESET.format(path=self.folder_path, branch=branch).split()
|
ProcessFormatter().format(self.GIT_HARD_RESET, path=self.folder_path, branch=branch)
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
@@ -340,7 +361,7 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
await self.hard_reset(branch=curr_branch)
|
await self.hard_reset(branch=curr_branch)
|
||||||
|
|
||||||
p = await self._run(self.GIT_PULL.format(path=self.folder_path).split())
|
p = await self._run(ProcessFormatter().format(self.GIT_PULL, path=self.folder_path))
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise errors.UpdateError(
|
raise errors.UpdateError(
|
||||||
@@ -463,9 +484,9 @@ class Repo(RepoJSONMixin):
|
|||||||
# TODO: Check and see if any of these modules are already available
|
# TODO: Check and see if any of these modules are already available
|
||||||
|
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.PIP_INSTALL.format(
|
ProcessFormatter().format(
|
||||||
python=executable, target_dir=target_dir, reqs=" ".join(requirements)
|
self.PIP_INSTALL, python=executable, target_dir=target_dir, reqs=requirements
|
||||||
).split()
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
@@ -509,7 +530,7 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
class RepoManager:
|
class RepoManager:
|
||||||
|
|
||||||
GITHUB_OR_GITLAB_RE = re.compile("https?://git(?:hub)|(?:lab)\.com/")
|
GITHUB_OR_GITLAB_RE = re.compile(r"https?://git(?:hub)|(?:lab)\.com/")
|
||||||
TREE_URL_RE = re.compile(r"(?P<tree>/tree)/(?P<branch>\S+)$")
|
TREE_URL_RE = re.compile(r"(?P<tree>/tree)/(?P<branch>\S+)$")
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ 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, format_perms_list
|
from redbot.core.utils.chat_formatting import box, escape, format_perms_list
|
||||||
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
from redbot.core.utils.common_filters import (
|
||||||
|
filter_invites,
|
||||||
|
filter_various_mentions,
|
||||||
|
escape_spoilers,
|
||||||
|
)
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
_ = T_ = Translator("Mod", __file__)
|
_ = T_ = Translator("Mod", __file__)
|
||||||
|
|
||||||
|
|
||||||
@@ -1356,10 +1359,10 @@ class Mod(commands.Cog):
|
|||||||
@commands.bot_has_permissions(embed_links=True)
|
@commands.bot_has_permissions(embed_links=True)
|
||||||
async def userinfo(self, ctx, *, user: discord.Member = None):
|
async def userinfo(self, ctx, *, user: discord.Member = None):
|
||||||
"""Show information about a user.
|
"""Show information about a user.
|
||||||
|
|
||||||
This includes fields for status, discord join date, server
|
This includes fields for status, discord join date, server
|
||||||
join date, voice state and previous names/nicknames.
|
join date, voice state and previous names/nicknames.
|
||||||
|
|
||||||
If the user has no roles, previous names or previous nicknames,
|
If the user has no roles, previous names or previous nicknames,
|
||||||
these fields will be omitted.
|
these fields will be omitted.
|
||||||
"""
|
"""
|
||||||
@@ -1378,11 +1381,18 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
joined_at = user.joined_at if not is_special else special_date
|
joined_at = user.joined_at if not is_special else special_date
|
||||||
since_created = (ctx.message.created_at - user.created_at).days
|
since_created = (ctx.message.created_at - user.created_at).days
|
||||||
since_joined = (ctx.message.created_at - joined_at).days
|
if joined_at is not None:
|
||||||
user_joined = joined_at.strftime("%d %b %Y %H:%M")
|
since_joined = (ctx.message.created_at - joined_at).days
|
||||||
|
user_joined = joined_at.strftime("%d %b %Y %H:%M")
|
||||||
|
else:
|
||||||
|
since_joined = "?"
|
||||||
|
user_joined = "Unknown"
|
||||||
user_created = user.created_at.strftime("%d %b %Y %H:%M")
|
user_created = user.created_at.strftime("%d %b %Y %H:%M")
|
||||||
voice_state = user.voice
|
voice_state = user.voice
|
||||||
member_number = sorted(guild.members, key=lambda m: m.joined_at).index(user) + 1
|
member_number = (
|
||||||
|
sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user)
|
||||||
|
+ 1
|
||||||
|
)
|
||||||
|
|
||||||
created_on = _("{}\n({} days ago)").format(user_created, since_created)
|
created_on = _("{}\n({} days ago)").format(user_created, since_created)
|
||||||
joined_on = _("{}\n({} days ago)").format(user_joined, since_joined)
|
joined_on = _("{}\n({} days ago)").format(user_joined, since_joined)
|
||||||
@@ -1464,9 +1474,9 @@ class Mod(commands.Cog):
|
|||||||
names = await self.settings.user(user).past_names()
|
names = await self.settings.user(user).past_names()
|
||||||
nicks = await self.settings.member(user).past_nicks()
|
nicks = await self.settings.member(user).past_nicks()
|
||||||
if names:
|
if names:
|
||||||
names = [escape(name, mass_mentions=True) for name in names if name]
|
names = [escape_spoilers(escape(name, mass_mentions=True)) for name in names if name]
|
||||||
if nicks:
|
if nicks:
|
||||||
nicks = [escape(nick, mass_mentions=True) for nick in nicks if nick]
|
nicks = [escape_spoilers(escape(nick, mass_mentions=True)) for nick in nicks if nick]
|
||||||
return names, nicks
|
return names, nicks
|
||||||
|
|
||||||
async def check_tempban_expirations(self):
|
async def check_tempban_expirations(self):
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ class Permissions(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
self._load_rules_for(
|
self._load_rules_for(
|
||||||
cog_or_command=cog,
|
cog_or_command=cog,
|
||||||
rule_dict=await self.config.custom(COMMAND, cog.__class__.__name__).all(),
|
rule_dict=await self.config.custom(COG, cog.__class__.__name__).all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def command_added(self, command: commands.Command) -> None:
|
async def command_added(self, command: commands.Command) -> None:
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ What car is this? http://i.imgur.com/KCd50kw.jpg:
|
|||||||
- Wraith
|
- Wraith
|
||||||
What car is this? http://i.imgur.com/KGCsWGo.jpg:
|
What car is this? http://i.imgur.com/KGCsWGo.jpg:
|
||||||
- Lamborghini Aventador
|
- Lamborghini Aventador
|
||||||
- Lambo Avendator
|
- Lambo Aventador
|
||||||
- Avendator
|
- Aventador
|
||||||
What car is this? http://i.imgur.com/Khv7UOz.jpg:
|
What car is this? http://i.imgur.com/Khv7UOz.jpg:
|
||||||
- Peugeot RCZ
|
- Peugeot RCZ
|
||||||
- RCZ
|
- RCZ
|
||||||
@@ -162,7 +162,8 @@ What car is this? http://i.imgur.com/bCq4ePD.jpg:
|
|||||||
- Mclaren P1
|
- Mclaren P1
|
||||||
- P1
|
- P1
|
||||||
What car is this? http://i.imgur.com/cOXNsmp.jpg:
|
What car is this? http://i.imgur.com/cOXNsmp.jpg:
|
||||||
- Mitsubishi 3000GT, 3000GT
|
- Mitsubishi 3000GT
|
||||||
|
- 3000GT
|
||||||
What car is this? http://i.imgur.com/cyLaOzo.jpg:
|
What car is this? http://i.imgur.com/cyLaOzo.jpg:
|
||||||
- Ford GT40
|
- Ford GT40
|
||||||
- GT40
|
- GT40
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from typing import Union, Optional
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -306,26 +307,31 @@ class Warnings(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def warnings(self, ctx: commands.Context, userid: int = None):
|
async def warnings(
|
||||||
|
self, ctx: commands.Context, user: Optional[Union[discord.Member, int]] = None
|
||||||
|
):
|
||||||
"""List the warnings for the specified user.
|
"""List the warnings for the specified user.
|
||||||
|
|
||||||
Emit `<userid>` to see your own warnings.
|
Omit `<user>` to see your own warnings.
|
||||||
|
|
||||||
Note that showing warnings for users other than yourself requires
|
Note that showing warnings for users other than yourself requires
|
||||||
appropriate permissions.
|
appropriate permissions.
|
||||||
"""
|
"""
|
||||||
if userid is None:
|
if user is None:
|
||||||
user = ctx.author
|
user = ctx.author
|
||||||
else:
|
else:
|
||||||
if not await is_admin_or_superior(self.bot, ctx.author):
|
if not await is_admin_or_superior(self.bot, ctx.author):
|
||||||
await ctx.send(
|
return await ctx.send(
|
||||||
warning(_("You are not allowed to check warnings for other users!"))
|
warning(_("You are not allowed to check warnings for other users!"))
|
||||||
)
|
)
|
||||||
return
|
|
||||||
else:
|
try:
|
||||||
|
userid: int = user.id
|
||||||
|
except AttributeError:
|
||||||
|
userid: int = user
|
||||||
user = ctx.guild.get_member(userid)
|
user = ctx.guild.get_member(userid)
|
||||||
if user is None: # user not in guild
|
user = user or namedtuple("Member", "id guild")(userid, ctx.guild)
|
||||||
user = namedtuple("Member", "id guild")(userid, ctx.guild)
|
|
||||||
msg = ""
|
msg = ""
|
||||||
member_settings = self.config.member(user)
|
member_settings = self.config.member(user)
|
||||||
async with member_settings.warnings() as user_warnings:
|
async with member_settings.warnings() as user_warnings:
|
||||||
@@ -356,23 +362,28 @@ class Warnings(commands.Cog):
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def unwarn(self, ctx: commands.Context, user_id: int, warn_id: str):
|
async def unwarn(self, ctx: commands.Context, user: Union[discord.Member, int], warn_id: str):
|
||||||
"""Remove a warning from a user."""
|
"""Remove a warning from a user."""
|
||||||
if user_id == ctx.author.id:
|
|
||||||
await ctx.send(_("You cannot remove warnings from yourself."))
|
|
||||||
return
|
|
||||||
guild = ctx.guild
|
|
||||||
member = guild.get_member(user_id)
|
|
||||||
if member is None: # no longer in guild, but need a "member" object
|
|
||||||
member = namedtuple("Member", "guild id")(guild, user_id)
|
|
||||||
member_settings = self.config.member(member)
|
|
||||||
|
|
||||||
|
guild = ctx.guild
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_id = user.id
|
||||||
|
member = user
|
||||||
|
except AttributeError:
|
||||||
|
user_id = user
|
||||||
|
member = guild.get_member(user_id)
|
||||||
|
member = member or namedtuple("Member", "guild id")(guild, user_id)
|
||||||
|
|
||||||
|
if user_id == ctx.author.id:
|
||||||
|
return await ctx.send(_("You cannot remove warnings from yourself."))
|
||||||
|
|
||||||
|
member_settings = self.config.member(member)
|
||||||
current_point_count = await member_settings.total_points()
|
current_point_count = await member_settings.total_points()
|
||||||
await warning_points_remove_check(self.config, ctx, member, current_point_count)
|
await warning_points_remove_check(self.config, ctx, member, current_point_count)
|
||||||
async with member_settings.warnings() as user_warnings:
|
async with member_settings.warnings() as user_warnings:
|
||||||
if warn_id not in user_warnings.keys():
|
if warn_id not in user_warnings.keys():
|
||||||
await ctx.send(_("That warning doesn't exist!"))
|
return await ctx.send(_("That warning doesn't exist!"))
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
current_point_count -= user_warnings[warn_id]["points"]
|
current_point_count -= user_warnings[warn_id]["points"]
|
||||||
await member_settings.total_points.set(current_point_count)
|
await member_settings.total_points.set(current_point_count)
|
||||||
|
|||||||
@@ -148,5 +148,5 @@ class VersionInfo:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__version__ = "3.0.0"
|
__version__ = "3.0.2"
|
||||||
version_info = VersionInfo.from_str(__version__)
|
version_info = VersionInfo.from_str(__version__)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ replace those from the `discord.ext.commands` module.
|
|||||||
"""
|
"""
|
||||||
import inspect
|
import inspect
|
||||||
import weakref
|
import weakref
|
||||||
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, TYPE_CHECKING
|
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
@@ -50,20 +50,54 @@ class CogCommandMixin:
|
|||||||
checks=getattr(decorated, "__requires_checks__", []),
|
checks=getattr(decorated, "__requires_checks__", []),
|
||||||
)
|
)
|
||||||
|
|
||||||
def allow_for(self, model_id: int, guild_id: int) -> None:
|
def allow_for(self, model_id: Union[int, str], guild_id: int) -> None:
|
||||||
"""Actively allow this command for the given model."""
|
"""Actively allow this command for the given model.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
model_id : Union[int, str]
|
||||||
|
Must be an `int` if supplying an ID. `str` is only valid
|
||||||
|
for "default".
|
||||||
|
guild_id : int
|
||||||
|
The guild ID to allow this cog or command in. For global
|
||||||
|
rules, use ``0``.
|
||||||
|
|
||||||
|
"""
|
||||||
self.requires.set_rule(model_id, PermState.ACTIVE_ALLOW, guild_id=guild_id)
|
self.requires.set_rule(model_id, PermState.ACTIVE_ALLOW, guild_id=guild_id)
|
||||||
|
|
||||||
def deny_to(self, model_id: int, guild_id: int) -> None:
|
def deny_to(self, model_id: Union[int, str], guild_id: int) -> None:
|
||||||
"""Actively deny this command to the given model."""
|
"""Actively deny this command to the given model.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
model_id : Union[int, str]
|
||||||
|
Must be an `int` if supplying an ID. `str` is only valid
|
||||||
|
for "default".
|
||||||
|
guild_id : int
|
||||||
|
The guild ID to deny this cog or command in. For global
|
||||||
|
rules, use ``0``.
|
||||||
|
|
||||||
|
"""
|
||||||
cur_rule = self.requires.get_rule(model_id, guild_id=guild_id)
|
cur_rule = self.requires.get_rule(model_id, guild_id=guild_id)
|
||||||
if cur_rule is PermState.PASSIVE_ALLOW:
|
if cur_rule is PermState.PASSIVE_ALLOW:
|
||||||
self.requires.set_rule(model_id, PermState.CAUTIOUS_ALLOW, guild_id=guild_id)
|
self.requires.set_rule(model_id, PermState.CAUTIOUS_ALLOW, guild_id=guild_id)
|
||||||
else:
|
else:
|
||||||
self.requires.set_rule(model_id, PermState.ACTIVE_DENY, guild_id=guild_id)
|
self.requires.set_rule(model_id, PermState.ACTIVE_DENY, guild_id=guild_id)
|
||||||
|
|
||||||
def clear_rule_for(self, model_id: int, guild_id: int) -> Tuple[PermState, PermState]:
|
def clear_rule_for(
|
||||||
"""Clear the rule which is currently set for this model."""
|
self, model_id: Union[int, str], guild_id: int
|
||||||
|
) -> Tuple[PermState, PermState]:
|
||||||
|
"""Clear the rule which is currently set for this model.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
model_id : Union[int, str]
|
||||||
|
Must be an `int` if supplying an ID. `str` is only valid
|
||||||
|
for "default".
|
||||||
|
guild_id : int
|
||||||
|
The guild ID. For global rules, use ``0``.
|
||||||
|
|
||||||
|
"""
|
||||||
cur_rule = self.requires.get_rule(model_id, guild_id=guild_id)
|
cur_rule = self.requires.get_rule(model_id, guild_id=guild_id)
|
||||||
if cur_rule is PermState.ACTIVE_ALLOW:
|
if cur_rule is PermState.ACTIVE_ALLOW:
|
||||||
new_rule = PermState.NORMAL
|
new_rule = PermState.NORMAL
|
||||||
@@ -84,15 +118,17 @@ class CogCommandMixin:
|
|||||||
rule : Optional[bool]
|
rule : Optional[bool]
|
||||||
The rule to set as default. If ``True`` for allow,
|
The rule to set as default. If ``True`` for allow,
|
||||||
``False`` for deny and ``None`` for normal.
|
``False`` for deny and ``None`` for normal.
|
||||||
guild_id : Optional[int]
|
guild_id : int
|
||||||
Specify to set the default rule for a specific guild.
|
The guild to set the default rule in. When ``0``, this will
|
||||||
When ``None``, this will set the global default rule.
|
set the global default rule.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if guild_id:
|
if rule is None:
|
||||||
self.requires.set_default_guild_rule(guild_id, PermState.from_bool(rule))
|
self.clear_rule_for(Requires.DEFAULT, guild_id=guild_id)
|
||||||
else:
|
elif rule is True:
|
||||||
self.requires.default_global_rule = PermState.from_bool(rule)
|
self.allow_for(Requires.DEFAULT, guild_id=guild_id)
|
||||||
|
elif rule is False:
|
||||||
|
self.deny_to(Requires.DEFAULT, guild_id=guild_id)
|
||||||
|
|
||||||
|
|
||||||
class Command(CogCommandMixin, commands.Command):
|
class Command(CogCommandMixin, commands.Command):
|
||||||
@@ -335,7 +371,7 @@ class Command(CogCommandMixin, commands.Command):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def allow_for(self, model_id: int, guild_id: int) -> None:
|
def allow_for(self, model_id: Union[int, str], guild_id: int) -> None:
|
||||||
super().allow_for(model_id, guild_id=guild_id)
|
super().allow_for(model_id, guild_id=guild_id)
|
||||||
parents = self.parents
|
parents = self.parents
|
||||||
if self.instance is not None:
|
if self.instance is not None:
|
||||||
@@ -347,7 +383,9 @@ class Command(CogCommandMixin, commands.Command):
|
|||||||
elif cur_rule is PermState.ACTIVE_DENY:
|
elif cur_rule is PermState.ACTIVE_DENY:
|
||||||
parent.requires.set_rule(model_id, PermState.CAUTIOUS_ALLOW, guild_id=guild_id)
|
parent.requires.set_rule(model_id, PermState.CAUTIOUS_ALLOW, guild_id=guild_id)
|
||||||
|
|
||||||
def clear_rule_for(self, model_id: int, guild_id: int) -> Tuple[PermState, PermState]:
|
def clear_rule_for(
|
||||||
|
self, model_id: Union[int, str], guild_id: int
|
||||||
|
) -> Tuple[PermState, PermState]:
|
||||||
old_rule, new_rule = super().clear_rule_for(model_id, guild_id=guild_id)
|
old_rule, new_rule = super().clear_rule_for(model_id, guild_id=guild_id)
|
||||||
if old_rule is PermState.ACTIVE_ALLOW:
|
if old_rule is PermState.ACTIVE_ALLOW:
|
||||||
parents = self.parents
|
parents = self.parents
|
||||||
@@ -396,8 +434,28 @@ class CogGroupMixin:
|
|||||||
all_commands: Dict[str, Command]
|
all_commands: Dict[str, Command]
|
||||||
|
|
||||||
def reevaluate_rules_for(
|
def reevaluate_rules_for(
|
||||||
self, model_id: int, guild_id: Optional[int]
|
self, model_id: Union[str, int], guild_id: Optional[int]
|
||||||
) -> Tuple[PermState, bool]:
|
) -> Tuple[PermState, bool]:
|
||||||
|
"""Re-evaluate a rule by checking subcommand rules.
|
||||||
|
|
||||||
|
This is called when a subcommand is no longer actively allowed.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
model_id : Union[int, str]
|
||||||
|
Must be an `int` if supplying an ID. `str` is only valid
|
||||||
|
for "default".
|
||||||
|
guild_id : int
|
||||||
|
The guild ID. For global rules, use ``0``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[PermState, bool]
|
||||||
|
A 2-tuple containing the new rule and a bool indicating
|
||||||
|
whether or not the rule was changed as a result of this
|
||||||
|
call.
|
||||||
|
|
||||||
|
"""
|
||||||
cur_rule = self.requires.get_rule(model_id, guild_id=guild_id)
|
cur_rule = self.requires.get_rule(model_id, guild_id=guild_id)
|
||||||
if cur_rule in (PermState.NORMAL, PermState.ACTIVE_ALLOW, PermState.ACTIVE_DENY):
|
if cur_rule in (PermState.NORMAL, PermState.ACTIVE_ALLOW, PermState.ACTIVE_DENY):
|
||||||
# These three states are unaffected by subcommand rules
|
# These three states are unaffected by subcommand rules
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import contextlib
|
||||||
from typing import Iterable, List
|
from typing import Iterable, List
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
@@ -167,7 +168,8 @@ class Context(commands.Context):
|
|||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await query.delete()
|
with contextlib.suppress(discord.HTTPException):
|
||||||
|
await query.delete()
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@@ -176,7 +178,8 @@ class Context(commands.Context):
|
|||||||
# In case the bot can't delete other users' messages,
|
# In case the bot can't delete other users' messages,
|
||||||
# or is not a bot account
|
# or is not a bot account
|
||||||
# or channel is a DM
|
# or channel is a DM
|
||||||
await query.delete()
|
with contextlib.suppress(discord.HTTPException):
|
||||||
|
await query.delete()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def embed_colour(self):
|
async def embed_colour(self):
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from typing import (
|
|||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
ClassVar,
|
||||||
)
|
)
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@@ -284,6 +285,14 @@ class Requires:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
DEFAULT: ClassVar[str] = "default"
|
||||||
|
"""The key for the default rule in a rules dict."""
|
||||||
|
|
||||||
|
GLOBAL: ClassVar[int] = 0
|
||||||
|
"""Should be used in place of a guild ID when setting/getting
|
||||||
|
global rules.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
privilege_level: Optional[PrivilegeLevel],
|
privilege_level: Optional[PrivilegeLevel],
|
||||||
@@ -307,10 +316,8 @@ class Requires:
|
|||||||
self.bot_perms.update(**bot_perms)
|
self.bot_perms.update(**bot_perms)
|
||||||
else:
|
else:
|
||||||
self.bot_perms = bot_perms
|
self.bot_perms = bot_perms
|
||||||
self.default_global_rule: PermState = PermState.NORMAL
|
self._global_rules: _RulesDict = _RulesDict()
|
||||||
self._global_rules: _IntKeyDict[PermState] = _IntKeyDict()
|
self._guild_rules: _IntKeyDict[_RulesDict] = _IntKeyDict[_RulesDict]()
|
||||||
self._default_guild_rules: _IntKeyDict[PermState] = _IntKeyDict()
|
|
||||||
self._guild_rules: _IntKeyDict[_IntKeyDict[PermState]] = _IntKeyDict()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_decorator(
|
def get_decorator(
|
||||||
@@ -334,16 +341,17 @@ class Requires:
|
|||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def get_rule(self, model: Union[int, PermissionModel], guild_id: int) -> PermState:
|
def get_rule(self, model: Union[int, str, PermissionModel], guild_id: int) -> PermState:
|
||||||
"""Get the rule for a particular model.
|
"""Get the rule for a particular model.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
model : PermissionModel
|
model : Union[int, str, PermissionModel]
|
||||||
The model to get the rule for.
|
The model to get the rule for. `str` is only valid for
|
||||||
|
`Requires.DEFAULT`.
|
||||||
guild_id : int
|
guild_id : int
|
||||||
The ID of the guild for the rule's scope. Set to ``0``
|
The ID of the guild for the rule's scope. Set to
|
||||||
for a global rule.
|
`Requires.GLOBAL` for a global rule.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -352,31 +360,32 @@ class Requires:
|
|||||||
for an explanation.
|
for an explanation.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(model, int):
|
if not isinstance(model, (str, int)):
|
||||||
model = model.id
|
model = model.id
|
||||||
if guild_id:
|
if guild_id:
|
||||||
rules = self._guild_rules.get(guild_id, _IntKeyDict())
|
rules = self._guild_rules.get(guild_id, _RulesDict())
|
||||||
else:
|
else:
|
||||||
rules = self._global_rules
|
rules = self._global_rules
|
||||||
return rules.get(model, PermState.NORMAL)
|
return rules.get(model, PermState.NORMAL)
|
||||||
|
|
||||||
def set_rule(self, model_id: int, rule: PermState, guild_id: int) -> None:
|
def set_rule(self, model_id: Union[str, int], rule: PermState, guild_id: int) -> None:
|
||||||
"""Set the rule for a particular model.
|
"""Set the rule for a particular model.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
model_id : PermissionModel
|
model_id : Union[str, int]
|
||||||
The model to add a rule for.
|
The model to add a rule for. `str` is only valid for
|
||||||
|
`Requires.DEFAULT`.
|
||||||
rule : PermState
|
rule : PermState
|
||||||
Which state this rule should be set as. See the `PermState`
|
Which state this rule should be set as. See the `PermState`
|
||||||
class for an explanation.
|
class for an explanation.
|
||||||
guild_id : int
|
guild_id : int
|
||||||
The ID of the guild for the rule's scope. Set to ``0``
|
The ID of the guild for the rule's scope. Set to
|
||||||
for a global rule.
|
`Requires.GLOBAL` for a global rule.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if guild_id:
|
if guild_id:
|
||||||
rules = self._guild_rules.setdefault(guild_id, _IntKeyDict())
|
rules = self._guild_rules.setdefault(guild_id, _RulesDict())
|
||||||
else:
|
else:
|
||||||
rules = self._global_rules
|
rules = self._global_rules
|
||||||
if rule is PermState.NORMAL:
|
if rule is PermState.NORMAL:
|
||||||
@@ -387,27 +396,24 @@ class Requires:
|
|||||||
def clear_all_rules(self, guild_id: int) -> None:
|
def clear_all_rules(self, guild_id: int) -> None:
|
||||||
"""Clear all rules of a particular scope.
|
"""Clear all rules of a particular scope.
|
||||||
|
|
||||||
|
This will preserve the default rule, if set.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
guild_id : int
|
guild_id : int
|
||||||
The guild ID to clear rules for. If ``0``, this will
|
The guild ID to clear rules for. If set to
|
||||||
clear all global rules and leave all guild rules
|
`Requires.GLOBAL`, this will clear all global rules and
|
||||||
untouched.
|
leave all guild rules untouched.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if guild_id:
|
if guild_id:
|
||||||
rules = self._guild_rules.setdefault(guild_id, _IntKeyDict())
|
rules = self._guild_rules.setdefault(guild_id, _RulesDict())
|
||||||
else:
|
else:
|
||||||
rules = self._global_rules
|
rules = self._global_rules
|
||||||
|
default = rules.get(self.DEFAULT, None)
|
||||||
rules.clear()
|
rules.clear()
|
||||||
|
if default is not None:
|
||||||
def get_default_guild_rule(self, guild_id: int) -> PermState:
|
rules[self.DEFAULT] = default
|
||||||
"""Get the default rule for a guild."""
|
|
||||||
return self._default_guild_rules.get(guild_id, PermState.NORMAL)
|
|
||||||
|
|
||||||
def set_default_guild_rule(self, guild_id: int, rule: PermState) -> None:
|
|
||||||
"""Set the default rule for a guild."""
|
|
||||||
self._default_guild_rules[guild_id] = rule
|
|
||||||
|
|
||||||
async def verify(self, ctx: "Context") -> bool:
|
async def verify(self, ctx: "Context") -> bool:
|
||||||
"""Check if the given context passes the requirements.
|
"""Check if the given context passes the requirements.
|
||||||
@@ -470,9 +476,9 @@ class Requires:
|
|||||||
# We must check what would happen normally, if no explicit rules were set.
|
# We must check what would happen normally, if no explicit rules were set.
|
||||||
default_rule = PermState.NORMAL
|
default_rule = PermState.NORMAL
|
||||||
if ctx.guild is not None:
|
if ctx.guild is not None:
|
||||||
default_rule = self.get_default_guild_rule(guild_id=ctx.guild.id)
|
default_rule = self.get_rule(self.DEFAULT, guild_id=ctx.guild.id)
|
||||||
if default_rule is PermState.NORMAL:
|
if default_rule is PermState.NORMAL:
|
||||||
default_rule = self.default_global_rule
|
default_rule = self.get_rule(self.DEFAULT, self.GLOBAL)
|
||||||
|
|
||||||
if default_rule == PermState.ACTIVE_DENY:
|
if default_rule == PermState.ACTIVE_DENY:
|
||||||
would_invoke = False
|
would_invoke = False
|
||||||
@@ -510,7 +516,7 @@ class Requires:
|
|||||||
rule = self._global_rules.get(author.id)
|
rule = self._global_rules.get(author.id)
|
||||||
if rule is not None:
|
if rule is not None:
|
||||||
return rule
|
return rule
|
||||||
return self.default_global_rule
|
return self.get_rule(self.DEFAULT, self.GLOBAL)
|
||||||
|
|
||||||
rules_chain = [self._global_rules]
|
rules_chain = [self._global_rules]
|
||||||
guild_rules = self._guild_rules.get(ctx.guild.id)
|
guild_rules = self._guild_rules.get(ctx.guild.id)
|
||||||
@@ -534,9 +540,9 @@ class Requires:
|
|||||||
return rule
|
return rule
|
||||||
del model_chain[-1] # We don't check for the guild in guild rules
|
del model_chain[-1] # We don't check for the guild in guild rules
|
||||||
|
|
||||||
default_rule = self.get_default_guild_rule(guild.id)
|
default_rule = self.get_rule(self.DEFAULT, guild.id)
|
||||||
if default_rule is PermState.NORMAL:
|
if default_rule is PermState.NORMAL:
|
||||||
default_rule = self.default_global_rule
|
default_rule = self.get_rule(self.DEFAULT, self.GLOBAL)
|
||||||
return default_rule
|
return default_rule
|
||||||
|
|
||||||
async def _verify_checks(self, ctx: "Context") -> bool:
|
async def _verify_checks(self, ctx: "Context") -> bool:
|
||||||
@@ -706,6 +712,20 @@ class _IntKeyDict(Dict[int, _T]):
|
|||||||
return super().__setitem__(key, value)
|
return super().__setitem__(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
class _RulesDict(Dict[Union[int, str], PermState]):
|
||||||
|
"""Dict subclass which throws a KeyError when an invalid key is used."""
|
||||||
|
|
||||||
|
def __getitem__(self, key: Any) -> PermState:
|
||||||
|
if key != Requires.DEFAULT and not isinstance(key, int):
|
||||||
|
raise TypeError(f'Expected "{Requires.DEFAULT}" or int key, not "{key}"')
|
||||||
|
return super().__getitem__(key)
|
||||||
|
|
||||||
|
def __setitem__(self, key: Any, value: PermState) -> None:
|
||||||
|
if key != Requires.DEFAULT and not isinstance(key, int):
|
||||||
|
raise TypeError(f'Expected "{Requires.DEFAULT}" or int key, not "{key}"')
|
||||||
|
return super().__setitem__(key, value)
|
||||||
|
|
||||||
|
|
||||||
def _validate_perms_dict(perms: Dict[str, bool]) -> None:
|
def _validate_perms_dict(perms: Dict[str, bool]) -> None:
|
||||||
for perm, value in perms.items():
|
for perm, value in perms.items():
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -489,7 +489,10 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
try:
|
try:
|
||||||
await self.bot.wait_for("message", check=pred, timeout=15)
|
await self.bot.wait_for("message", check=pred, timeout=15)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await query.delete()
|
try:
|
||||||
|
await query.delete()
|
||||||
|
except discord.errors.NotFound:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
await self.leave_confirmation(guilds[pred.result], ctx)
|
await self.leave_confirmation(guilds[pred.result], ctx)
|
||||||
|
|
||||||
@@ -515,6 +518,8 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def load(self, ctx: commands.Context, *cogs: str):
|
async def load(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Loads packages"""
|
"""Loads packages"""
|
||||||
|
if not cogs:
|
||||||
|
return await ctx.send_help()
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
loaded, failed, not_found, already_loaded = await self._load(cogs)
|
loaded, failed, not_found, already_loaded = await self._load(cogs)
|
||||||
|
|
||||||
@@ -545,6 +550,8 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def unload(self, ctx: commands.Context, *cogs: str):
|
async def unload(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Unloads packages"""
|
"""Unloads packages"""
|
||||||
|
if not cogs:
|
||||||
|
return await ctx.send_help()
|
||||||
unloaded, failed = await self._unload(cogs)
|
unloaded, failed = await self._unload(cogs)
|
||||||
|
|
||||||
if unloaded:
|
if unloaded:
|
||||||
@@ -561,6 +568,8 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def reload(self, ctx: commands.Context, *cogs: str):
|
async def reload(self, ctx: commands.Context, *cogs: str):
|
||||||
"""Reloads packages"""
|
"""Reloads packages"""
|
||||||
|
if not cogs:
|
||||||
|
return await ctx.send_help()
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
loaded, failed, not_found, already_loaded = await self._reload(cogs)
|
loaded, failed, not_found, already_loaded = await self._reload(cogs)
|
||||||
|
|
||||||
@@ -1078,7 +1087,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def backup(self, ctx: commands.Context, 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
|
||||||
@@ -1321,7 +1330,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: commands.Context, user: discord.User):
|
async def whitelist_remove(self, ctx: commands.Context, *, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Removes user from whitelist.
|
Removes user from whitelist.
|
||||||
"""
|
"""
|
||||||
@@ -1354,7 +1363,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@blacklist.command(name="add")
|
@blacklist.command(name="add")
|
||||||
async def blacklist_add(self, ctx: commands.Context, 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.
|
||||||
"""
|
"""
|
||||||
@@ -1383,7 +1392,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: commands.Context, user: discord.User):
|
async def blacklist_remove(self, ctx: commands.Context, *, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Removes user from blacklist.
|
Removes user from blacklist.
|
||||||
"""
|
"""
|
||||||
@@ -1417,17 +1426,13 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@localwhitelist.command(name="add")
|
@localwhitelist.command(name="add")
|
||||||
async def localwhitelist_add(self, ctx: commands.Context, *, user_or_role: str):
|
async def localwhitelist_add(
|
||||||
|
self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role]
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Adds a user or role to the whitelist.
|
Adds a user or role to the whitelist.
|
||||||
"""
|
"""
|
||||||
try:
|
user = isinstance(user_or_role, discord.Member)
|
||||||
obj = await commands.MemberConverter().convert(ctx, user_or_role)
|
|
||||||
except commands.BadArgument:
|
|
||||||
obj = await commands.RoleConverter().convert(ctx, user_or_role)
|
|
||||||
user = False
|
|
||||||
else:
|
|
||||||
user = True
|
|
||||||
async with ctx.bot.db.guild(ctx.guild).whitelist() as curr_list:
|
async with ctx.bot.db.guild(ctx.guild).whitelist() as curr_list:
|
||||||
if obj.id not in curr_list:
|
if obj.id not in curr_list:
|
||||||
curr_list.append(obj.id)
|
curr_list.append(obj.id)
|
||||||
@@ -1452,17 +1457,13 @@ 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: commands.Context, *, user_or_role: str):
|
async def localwhitelist_remove(
|
||||||
|
self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role]
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Removes user or role from whitelist.
|
Removes user or role from whitelist.
|
||||||
"""
|
"""
|
||||||
try:
|
user = isinstance(user_or_role, discord.Member)
|
||||||
obj = await commands.MemberConverter().convert(ctx, user_or_role)
|
|
||||||
except commands.BadArgument:
|
|
||||||
obj = await commands.RoleConverter().convert(ctx, user_or_role)
|
|
||||||
user = False
|
|
||||||
else:
|
|
||||||
user = True
|
|
||||||
|
|
||||||
removed = False
|
removed = False
|
||||||
async with ctx.bot.db.guild(ctx.guild).whitelist() as curr_list:
|
async with ctx.bot.db.guild(ctx.guild).whitelist() as curr_list:
|
||||||
@@ -1499,17 +1500,13 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@localblacklist.command(name="add")
|
@localblacklist.command(name="add")
|
||||||
async def localblacklist_add(self, ctx: commands.Context, *, user_or_role: str):
|
async def localblacklist_add(
|
||||||
|
self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role]
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Adds a user or role to the blacklist.
|
Adds a user or role to the blacklist.
|
||||||
"""
|
"""
|
||||||
try:
|
user = isinstance(user_or_role, discord.Member)
|
||||||
obj = await commands.MemberConverter().convert(ctx, user_or_role)
|
|
||||||
except commands.BadArgument:
|
|
||||||
obj = await commands.RoleConverter().convert(ctx, user_or_role)
|
|
||||||
user = False
|
|
||||||
else:
|
|
||||||
user = True
|
|
||||||
|
|
||||||
if user and await ctx.bot.is_owner(obj):
|
if user and await ctx.bot.is_owner(obj):
|
||||||
await ctx.send(_("You cannot blacklist an owner!"))
|
await ctx.send(_("You cannot blacklist an owner!"))
|
||||||
@@ -1539,18 +1536,14 @@ 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: commands.Context, *, user_or_role: str):
|
async def localblacklist_remove(
|
||||||
|
self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role]
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Removes user or role from blacklist.
|
Removes user or role from blacklist.
|
||||||
"""
|
"""
|
||||||
removed = False
|
removed = False
|
||||||
try:
|
user = isinstance(user_or_role, discord.Member)
|
||||||
obj = await commands.MemberConverter().convert(ctx, user_or_role)
|
|
||||||
except commands.BadArgument:
|
|
||||||
obj = await commands.RoleConverter().convert(ctx, user_or_role)
|
|
||||||
user = False
|
|
||||||
else:
|
|
||||||
user = True
|
|
||||||
|
|
||||||
async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list:
|
async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list:
|
||||||
if obj.id in curr_list:
|
if obj.id in curr_list:
|
||||||
@@ -1747,7 +1740,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@autoimmune_group.command(name="add")
|
@autoimmune_group.command(name="add")
|
||||||
async def autoimmune_add(
|
async def autoimmune_add(
|
||||||
self, ctx: commands.Context, user_or_role: Union[discord.Member, discord.Role]
|
self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role]
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Makes a user or roles immune from automated moderation actions
|
Makes a user or roles immune from automated moderation actions
|
||||||
@@ -1760,7 +1753,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@autoimmune_group.command(name="remove")
|
@autoimmune_group.command(name="remove")
|
||||||
async def autoimmune_remove(
|
async def autoimmune_remove(
|
||||||
self, ctx: commands.Context, user_or_role: Union[discord.Member, discord.Role]
|
self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role]
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Makes a user or roles immune from automated moderation actions
|
Makes a user or roles immune from automated moderation actions
|
||||||
@@ -1773,7 +1766,7 @@ class Core(commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@autoimmune_group.command(name="isimmune")
|
@autoimmune_group.command(name="isimmune")
|
||||||
async def autoimmune_checkimmune(
|
async def autoimmune_checkimmune(
|
||||||
self, ctx: commands.Context, user_or_role: Union[discord.Member, discord.Role]
|
self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role]
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Checks if a user or role would be considered immune from automated actions
|
Checks if a user or role would be considered immune from automated actions
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ def init_events(bot, cli_flags):
|
|||||||
|
|
||||||
sentry = await bot.db.enable_sentry()
|
sentry = await bot.db.enable_sentry()
|
||||||
mongo_enabled = storage_type() != "JSON"
|
mongo_enabled = storage_type() != "JSON"
|
||||||
reqs_installed = {"voice": None, "docs": None, "test": None}
|
reqs_installed = {"docs": None, "test": None}
|
||||||
for key in reqs_installed.keys():
|
for key in reqs_installed.keys():
|
||||||
reqs = [x.name for x in red_pkg._dep_map[key]]
|
reqs = [x.name for x in red_pkg._dep_map[key]]
|
||||||
try:
|
try:
|
||||||
@@ -157,7 +157,7 @@ def init_events(bot, cli_flags):
|
|||||||
options = (
|
options = (
|
||||||
("Error Reporting", sentry),
|
("Error Reporting", sentry),
|
||||||
("MongoDB", mongo_enabled),
|
("MongoDB", mongo_enabled),
|
||||||
("Voice", reqs_installed["voice"]),
|
("Voice", True),
|
||||||
("Docs", reqs_installed["docs"]),
|
("Docs", reqs_installed["docs"]),
|
||||||
("Tests", reqs_installed["test"]),
|
("Tests", reqs_installed["test"]),
|
||||||
)
|
)
|
||||||
@@ -176,12 +176,6 @@ def init_events(bot, cli_flags):
|
|||||||
print("\nInvite URL: {}\n".format(invite_url))
|
print("\nInvite URL: {}\n".format(invite_url))
|
||||||
|
|
||||||
bot.color = discord.Colour(await bot.db.color())
|
bot.color = discord.Colour(await bot.db.color())
|
||||||
try:
|
|
||||||
import Levenshtein
|
|
||||||
except ImportError:
|
|
||||||
log.info(
|
|
||||||
"python-Levenshtein is not installed, fuzzy string matching will be a bit slower."
|
|
||||||
)
|
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_error(event_method, *args, **kwargs):
|
async def on_error(event_method, *args, **kwargs):
|
||||||
@@ -204,19 +198,17 @@ def init_events(bot, cli_flags):
|
|||||||
await ctx.send(disabled_message.replace("{command}", ctx.invoked_with))
|
await ctx.send(disabled_message.replace("{command}", ctx.invoked_with))
|
||||||
elif isinstance(error, commands.CommandInvokeError):
|
elif isinstance(error, commands.CommandInvokeError):
|
||||||
log.exception(
|
log.exception(
|
||||||
"Exception in command '{}'" "".format(ctx.command.qualified_name),
|
"Exception in command '{}'".format(ctx.command.qualified_name),
|
||||||
exc_info=error.original,
|
exc_info=error.original,
|
||||||
)
|
)
|
||||||
if should_log_sentry(error):
|
if should_log_sentry(error):
|
||||||
sentry_log.exception(
|
sentry_log.exception(
|
||||||
"Exception in command '{}'" "".format(ctx.command.qualified_name),
|
"Exception in command '{}'".format(ctx.command.qualified_name),
|
||||||
exc_info=error.original,
|
exc_info=error.original,
|
||||||
)
|
)
|
||||||
|
|
||||||
message = (
|
message = "Error in command '{}'. Check your console or logs for details.".format(
|
||||||
"Error in command '{}'. Check your console or "
|
ctx.command.qualified_name
|
||||||
"logs for details."
|
|
||||||
"".format(ctx.command.qualified_name)
|
|
||||||
)
|
)
|
||||||
exception_log = "Exception in command '{}'\n" "".format(ctx.command.qualified_name)
|
exception_log = "Exception in command '{}'\n" "".format(ctx.command.qualified_name)
|
||||||
exception_log += "".join(
|
exception_log += "".join(
|
||||||
@@ -273,9 +265,9 @@ def init_events(bot, cli_flags):
|
|||||||
system_now = datetime.datetime.utcnow()
|
system_now = datetime.datetime.utcnow()
|
||||||
diff = abs((discord_now - system_now).total_seconds())
|
diff = abs((discord_now - system_now).total_seconds())
|
||||||
if diff > 60:
|
if diff > 60:
|
||||||
log.warn(
|
log.warning(
|
||||||
"Detected significant difference (%d seconds) in system clock to discord's clock."
|
"Detected significant difference (%d seconds) in system clock to discord's "
|
||||||
" Any time sensitive code may fail.",
|
"clock. Any time sensitive code may fail.",
|
||||||
diff,
|
diff,
|
||||||
)
|
)
|
||||||
bot.checked_time_accuracy = discord_now
|
bot.checked_time_accuracy = discord_now
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class Help(dpy_formatter.HelpFormatter):
|
|||||||
def get_ending_note(self):
|
def get_ending_note(self):
|
||||||
# command_name = self.context.invoked_with
|
# command_name = self.context.invoked_with
|
||||||
return (
|
return (
|
||||||
"Type {0}help <command> for more info on a command.\n"
|
"Type {0}help <command> for more info on a command. "
|
||||||
"You can also type {0}help <category> for more info on a category.".format(
|
"You can also type {0}help <category> for more info on a category.".format(
|
||||||
self.context.clean_prefix
|
self.context.clean_prefix
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import discord
|
|||||||
from redbot.core import Config
|
from redbot.core import Config
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
from .utils.common_filters import filter_invites, filter_mass_mentions, filter_urls
|
from .utils.common_filters import (
|
||||||
|
filter_invites,
|
||||||
|
filter_mass_mentions,
|
||||||
|
filter_urls,
|
||||||
|
escape_spoilers,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Case",
|
"Case",
|
||||||
@@ -115,8 +120,10 @@ class Case:
|
|||||||
reason = "**Reason:** Use `[p]reason {} <reason>` to add it".format(self.case_number)
|
reason = "**Reason:** Use `[p]reason {} <reason>` to add it".format(self.case_number)
|
||||||
|
|
||||||
if self.moderator is not None:
|
if self.moderator is not None:
|
||||||
moderator = "{}#{} ({})\n".format(
|
moderator = escape_spoilers(
|
||||||
self.moderator.name, self.moderator.discriminator, self.moderator.id
|
"{}#{} ({})\n".format(
|
||||||
|
self.moderator.name, self.moderator.discriminator, self.moderator.id
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
moderator = "Unknown"
|
moderator = "Unknown"
|
||||||
@@ -133,8 +140,10 @@ class Case:
|
|||||||
|
|
||||||
amended_by = None
|
amended_by = None
|
||||||
if self.amended_by:
|
if self.amended_by:
|
||||||
amended_by = "{}#{} ({})".format(
|
amended_by = escape_spoilers(
|
||||||
self.amended_by.name, self.amended_by.discriminator, self.amended_by.id
|
"{}#{} ({})".format(
|
||||||
|
self.amended_by.name, self.amended_by.discriminator, self.amended_by.id
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
last_modified = None
|
last_modified = None
|
||||||
@@ -143,9 +152,11 @@ class Case:
|
|||||||
datetime.fromtimestamp(self.modified_at).strftime("%Y-%m-%d %H:%M:%S")
|
datetime.fromtimestamp(self.modified_at).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
)
|
)
|
||||||
|
|
||||||
user = filter_invites(
|
user = escape_spoilers(
|
||||||
"{}#{} ({})\n".format(self.user.name, self.user.discriminator, self.user.id)
|
filter_invites(
|
||||||
) # Invites get rendered even in embeds.
|
"{}#{} ({})\n".format(self.user.name, self.user.discriminator, self.user.id)
|
||||||
|
)
|
||||||
|
) # Invites and spoilers get rendered even in embeds.
|
||||||
if embed:
|
if embed:
|
||||||
emb = discord.Embed(title=title, description=reason)
|
emb = discord.Embed(title=title, description=reason)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp_json_rpc import JsonRpc
|
from aiohttp_json_rpc import JsonRpc
|
||||||
@@ -63,25 +64,26 @@ class RPC:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.app = web.Application()
|
self.app = web.Application()
|
||||||
self._rpc = RedRpc()
|
self._rpc = RedRpc()
|
||||||
self.app.router.add_route("*", "/", self._rpc)
|
self.app.router.add_route("*", "/", self._rpc.handle_request)
|
||||||
|
|
||||||
self.app_handler = self.app.make_handler()
|
self._runner = web.AppRunner(self.app)
|
||||||
|
self._site: Optional[web.TCPSite] = None
|
||||||
self.server = None
|
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
"""
|
"""
|
||||||
Finalizes the initialization of the RPC server and allows it to begin
|
Finalizes the initialization of the RPC server and allows it to begin
|
||||||
accepting queries.
|
accepting queries.
|
||||||
"""
|
"""
|
||||||
self.server = await self.app.loop.create_server(self.app_handler, "127.0.0.1", 6133)
|
await self._runner.setup()
|
||||||
|
self._site = web.TCPSite(self._runner, host="127.0.0.1", port=6133)
|
||||||
|
await self._site.start()
|
||||||
log.debug("Created RPC server listener.")
|
log.debug("Created RPC server listener.")
|
||||||
|
|
||||||
def close(self):
|
async def close(self):
|
||||||
"""
|
"""
|
||||||
Closes the RPC server.
|
Closes the RPC server.
|
||||||
"""
|
"""
|
||||||
self.server.close()
|
await self._runner.cleanup()
|
||||||
|
|
||||||
def add_method(self, method, prefix: str = None):
|
def add_method(self, method, prefix: str = None):
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
@@ -117,7 +119,8 @@ class RPCMixin:
|
|||||||
"""
|
"""
|
||||||
Registers a method to act as an RPC handler if the internal RPC server is active.
|
Registers a method to act as an RPC handler if the internal RPC server is active.
|
||||||
|
|
||||||
When calling this method through the RPC server, use the naming scheme "cogname__methodname".
|
When calling this method through the RPC server, use the naming scheme
|
||||||
|
"cogname__methodname".
|
||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ __all__ = [
|
|||||||
"filter_mass_mentions",
|
"filter_mass_mentions",
|
||||||
"filter_various_mentions",
|
"filter_various_mentions",
|
||||||
"normalize_smartquotes",
|
"normalize_smartquotes",
|
||||||
|
"escape_spoilers",
|
||||||
|
"escape_spoilers_and_mass_mentions",
|
||||||
]
|
]
|
||||||
|
|
||||||
# regexes
|
# regexes
|
||||||
@@ -29,6 +31,10 @@ SMART_QUOTE_REPLACEMENT_DICT = {
|
|||||||
|
|
||||||
SMART_QUOTE_REPLACE_RE = re.compile("|".join(SMART_QUOTE_REPLACEMENT_DICT.keys()))
|
SMART_QUOTE_REPLACE_RE = re.compile("|".join(SMART_QUOTE_REPLACEMENT_DICT.keys()))
|
||||||
|
|
||||||
|
SPOILER_CONTENT_RE = re.compile(
|
||||||
|
r"(?s)(?<!\\)(?P<OPEN>\|{2})(?P<SPOILERED>.*?)(?<!\\)(?P<CLOSE>\|{2})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# convenience wrappers
|
# convenience wrappers
|
||||||
def filter_urls(to_filter: str) -> str:
|
def filter_urls(to_filter: str) -> str:
|
||||||
@@ -133,3 +139,37 @@ def normalize_smartquotes(to_normalize: str) -> str:
|
|||||||
return SMART_QUOTE_REPLACEMENT_DICT.get(obj.group(0), "")
|
return SMART_QUOTE_REPLACEMENT_DICT.get(obj.group(0), "")
|
||||||
|
|
||||||
return SMART_QUOTE_REPLACE_RE.sub(replacement_for, to_normalize)
|
return SMART_QUOTE_REPLACE_RE.sub(replacement_for, to_normalize)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_spoilers(content: str) -> str:
|
||||||
|
"""
|
||||||
|
Get a string with spoiler syntax escaped.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
content : str
|
||||||
|
The string to escape.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
The escaped string.
|
||||||
|
"""
|
||||||
|
return SPOILER_CONTENT_RE.sub(r"\\\g<OPEN>\g<SPOILERED>\\\g<CLOSE>", content)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_spoilers_and_mass_mentions(content: str) -> str:
|
||||||
|
"""
|
||||||
|
Get a string with spoiler syntax and mass mentions escaped
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
content : str
|
||||||
|
The string to escape.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
The escaped string.
|
||||||
|
"""
|
||||||
|
return escape_spoilers(filter_mass_mentions(content))
|
||||||
|
|||||||
@@ -744,7 +744,7 @@ class MessagePredicate(Callable[[discord.Message], bool]):
|
|||||||
if not same_context(m):
|
if not same_context(m):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
self.result = collection.index(m.content)
|
self.result = collection.index(m.content.lower())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -452,6 +452,7 @@ def main_menu():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
args, flags_to_pass = parse_cli_args()
|
||||||
if not PYTHON_OK:
|
if not PYTHON_OK:
|
||||||
print(
|
print(
|
||||||
f"Python {'.'.join(map(str, MIN_PYTHON_VERSION))} is required to run Red, but you "
|
f"Python {'.'.join(map(str, MIN_PYTHON_VERSION))} is required to run Red, but you "
|
||||||
@@ -478,8 +479,6 @@ def main():
|
|||||||
run_red(args.instancename, autorestart=args.auto_restart, cliflags=flags_to_pass)
|
run_red(args.instancename, autorestart=args.auto_restart, cliflags=flags_to_pass)
|
||||||
|
|
||||||
|
|
||||||
args, flags_to_pass = parse_cli_args()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ async def remove_instance_interaction():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
args, _ = parse_cli_args()
|
||||||
if args.delete:
|
if args.delete:
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
loop.run_until_complete(remove_instance_interaction())
|
loop.run_until_complete(remove_instance_interaction())
|
||||||
@@ -381,8 +382,6 @@ def main():
|
|||||||
basic_setup()
|
basic_setup()
|
||||||
|
|
||||||
|
|
||||||
args, _ = parse_cli_args()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
|
|||||||
47
setup.py
47
setup.py
@@ -1,40 +1,37 @@
|
|||||||
import distutils.ccompiler as ccompiler
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import tempfile
|
|
||||||
from distutils.errors import CCompilerError, DistutilsPlatformError
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
"aiohttp-json-rpc==0.11.2",
|
"aiohttp-json-rpc==0.12",
|
||||||
"aiohttp==3.4.4",
|
"aiohttp==3.5.4",
|
||||||
"appdirs==1.4.3",
|
"appdirs==1.4.3",
|
||||||
"async-timeout==3.0.1",
|
"async-timeout==3.0.1",
|
||||||
"attrs==18.2.0",
|
"attrs==18.2.0",
|
||||||
"chardet==3.0.4",
|
"chardet==3.0.4",
|
||||||
"colorama==0.4.1",
|
"colorama==0.4.1",
|
||||||
"distro==1.3.0; sys_platform == 'linux'",
|
"distro==1.4.0; sys_platform == 'linux'",
|
||||||
"fuzzywuzzy==0.17.0",
|
"fuzzywuzzy==0.17.0",
|
||||||
"idna-ssl==1.1.0",
|
"idna-ssl==1.1.0",
|
||||||
"idna==2.8",
|
"idna==2.8",
|
||||||
"multidict==4.5.2",
|
"multidict==4.5.2",
|
||||||
"python-levenshtein==0.12.0",
|
"python-levenshtein-wheels==0.13.1",
|
||||||
"pyyaml==3.13",
|
"pyyaml==3.13",
|
||||||
"raven==6.10.0",
|
"raven==6.10.0",
|
||||||
"raven-aiohttp==0.7.0",
|
"raven-aiohttp==0.7.0",
|
||||||
"red-lavalink==0.2.0",
|
"red-lavalink==0.2.3",
|
||||||
"schema==0.6.8",
|
"schema==0.6.8",
|
||||||
"websockets==6.0",
|
"websockets==7.0",
|
||||||
"yarl==1.3.0",
|
"yarl==1.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
extras_require = {
|
extras_require = {
|
||||||
"test": [
|
"test": [
|
||||||
"atomicwrites==1.2.1",
|
"atomicwrites==1.3.0",
|
||||||
"more-itertools==5.0.0",
|
"more-itertools==6.0.0",
|
||||||
"pluggy==0.8.1",
|
"pluggy==0.8.1",
|
||||||
"py==1.7.0",
|
"py==1.7.0",
|
||||||
"pytest==4.1.0",
|
"pytest==4.2.0",
|
||||||
"pytest-asyncio==0.10.0",
|
"pytest-asyncio==0.10.0",
|
||||||
"six==1.12.0",
|
"six==1.12.0",
|
||||||
],
|
],
|
||||||
@@ -47,15 +44,15 @@ extras_require = {
|
|||||||
"imagesize==1.1.0",
|
"imagesize==1.1.0",
|
||||||
"Jinja2==2.10",
|
"Jinja2==2.10",
|
||||||
"MarkupSafe==1.1.0",
|
"MarkupSafe==1.1.0",
|
||||||
"packaging==18.0",
|
"packaging==19.0",
|
||||||
"pyparsing==2.3.0",
|
"pyparsing==2.3.1",
|
||||||
"Pygments==2.3.1",
|
"Pygments==2.3.1",
|
||||||
"pytz==2018.9",
|
"pytz==2018.9",
|
||||||
"requests==2.21.0",
|
"requests==2.21.0",
|
||||||
"six==1.12.0",
|
"six==1.12.0",
|
||||||
"snowballstemmer==1.2.1",
|
"snowballstemmer==1.2.1",
|
||||||
"sphinx==1.8.3",
|
"sphinx==1.8.4",
|
||||||
"sphinx_rtd_theme==0.4.2",
|
"sphinx_rtd_theme==0.4.3",
|
||||||
"sphinxcontrib-asyncio==0.2.0",
|
"sphinxcontrib-asyncio==0.2.0",
|
||||||
"sphinxcontrib-websupport==1.1.0",
|
"sphinxcontrib-websupport==1.1.0",
|
||||||
"urllib3==1.24.1",
|
"urllib3==1.24.1",
|
||||||
@@ -70,19 +67,6 @@ if os.name == "nt":
|
|||||||
python_requires = ">=3.6.6,<3.8"
|
python_requires = ">=3.6.6,<3.8"
|
||||||
|
|
||||||
|
|
||||||
def check_compiler_available():
|
|
||||||
m = ccompiler.new_compiler()
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
|
||||||
with open(os.path.join(tdir, "dummy.c"), "w") as tfile:
|
|
||||||
tfile.write("int main(int argc, char** argv) {return 0;}")
|
|
||||||
try:
|
|
||||||
m.compile([tfile.name], output_dir=tdir)
|
|
||||||
except (CCompilerError, DistutilsPlatformError):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
with open("redbot/core/__init__.py") as f:
|
with open("redbot/core/__init__.py") as f:
|
||||||
version = re.search(
|
version = re.search(
|
||||||
@@ -92,11 +76,6 @@ def get_version():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if not check_compiler_available():
|
|
||||||
install_requires.remove(
|
|
||||||
next(r for r in install_requires if r.lower().startswith("python-levenshtein"))
|
|
||||||
)
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="Red-DiscordBot",
|
name="Red-DiscordBot",
|
||||||
version=get_version(),
|
version=get_version(),
|
||||||
|
|||||||
Reference in New Issue
Block a user