mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2026-05-12 11:08:23 -04:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8096cd803e | |||
| 27b0d606c8 | |||
| af220e497f | |||
| 892b2487f5 | |||
| 7971c02dc5 | |||
| c1d8272b49 | |||
| bce5dd26f3 | |||
| 04e97f3516 | |||
| 7eed033c9e | |||
| a2fe1a8757 | |||
| 9ee860c3f0 | |||
| 1dbe9537e9 | |||
| 775f4ce69a | |||
| e83beeef34 | |||
| e77cfff892 | |||
| 9495432b8f | |||
| d71c334a34 | |||
| aa8cc90ad0 | |||
| 589041556e | |||
| 85354f2722 | |||
| c0c5535005 | |||
| e126cf9f4e | |||
| c2d23b37a7 |
@@ -53,7 +53,7 @@ Red's repository is configured to follow a particular development workflow, usin
|
||||
|
||||
### 4.1 Setting up your development environment
|
||||
The following requirements must be installed prior to setting up:
|
||||
- Python 3.6
|
||||
- Python 3.6.2 or greater
|
||||
- git
|
||||
- pip
|
||||
- pipenv
|
||||
@@ -79,7 +79,7 @@ Note: If you haven't used `pipenv` before but are comfortable with virtualenvs,
|
||||
We've recently started using [tox](https://github.com/tox-dev/tox) to run all of our tests. It's extremely simple to use, and if you followed the previous section correctly, it is already installed to your virtual environment.
|
||||
|
||||
Currently, tox does the following, creating its own virtual environments for each stage:
|
||||
- Runs all of our unit tests with [pytest](https://github.com/pytest-dev/pytest) on python 3.6 (test environment `py36`)
|
||||
- Runs all of our unit tests with [pytest](https://github.com/pytest-dev/pytest) on python 3.6 and 3.7 (test environments `py36` and `py37`)
|
||||
- Ensures documentation builds without warnings, and all hyperlinks have a valid destination (test environment `docs`)
|
||||
- Ensures that the code meets our style guide with [black](https://github.com/ambv/black) (test environment `style`)
|
||||
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
include README.rst
|
||||
include README.md
|
||||
include LICENSE
|
||||
include dependency_links.txt
|
||||
|
||||
Generated
+100
-68
@@ -36,10 +36,10 @@
|
||||
},
|
||||
"aiohttp-json-rpc": {
|
||||
"hashes": [
|
||||
"sha256:bf1eb7e30949b60f74cb84731b5676bd7dc3f0298056ddbbe989d9219260008c",
|
||||
"sha256:e1ae47d522a7857c612be8ba447cec3cad8c8b7d628353289a0889a1135166c8"
|
||||
"sha256:970806a3b9887c389095d2bde84e2b540fefeddd0bae0efcae03c65f092ce00e",
|
||||
"sha256:d6f365067676e6089ac043ad31bcbabbf33d0343c42b57c36751a562fbe64fb6"
|
||||
],
|
||||
"version": "==0.11"
|
||||
"version": "==0.11.1"
|
||||
},
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
@@ -58,10 +58,10 @@
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
|
||||
"sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
|
||||
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
|
||||
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
|
||||
],
|
||||
"version": "==18.1.0"
|
||||
"version": "==18.2.0"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -80,7 +80,7 @@
|
||||
"discord.py": {
|
||||
"editable": true,
|
||||
"git": "git://github.com/Rapptz/discord.py",
|
||||
"ref": "8ccb98d395537b1c9acc187e1647dfdd07bb831b"
|
||||
"ref": "00a659c6526b2445162b52eaf970adbd22c6d35d"
|
||||
},
|
||||
"distro": {
|
||||
"hashes": [
|
||||
@@ -99,10 +99,10 @@
|
||||
},
|
||||
"fuzzywuzzy": {
|
||||
"hashes": [
|
||||
"sha256:d40c22d2744dff84885b30bbfc07fab7875f641d070374331777a4d1808b8d4e",
|
||||
"sha256:ecf490216fb4d76b558a03042ff8f45a8782f17326caca1384d834cbaa2c7e6f"
|
||||
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
|
||||
"sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62"
|
||||
],
|
||||
"version": "==0.16.0"
|
||||
"version": "==0.17.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
@@ -126,22 +126,37 @@
|
||||
},
|
||||
"multidict": {
|
||||
"hashes": [
|
||||
"sha256:1a1d76374a1e7fe93acef96b354a03c1d7f83e7512e225a527d283da0d7ba5e0",
|
||||
"sha256:1d6e191965505652f194bc4c40270a842922685918a4f45e6936a6b15cc5816d",
|
||||
"sha256:295961a6a88f1199e19968e15d9b42f3a191c89ec13034dbc212bf9c394c3c82",
|
||||
"sha256:2be5af084de6c3b8e20d6421cb0346378a9c867dcf7c86030d6b0b550f9888e4",
|
||||
"sha256:2eb99617c7a0e9f2b90b64bc1fb742611718618572747d6f3d6532b7b78755ab",
|
||||
"sha256:4ba654c6b5ad1ae4a4d792abeb695b29ce981bb0f157a41d0fd227b385f2bef0",
|
||||
"sha256:5ba766433c30d703f6b2c17eb0b6826c6f898e5f58d89373e235f07764952314",
|
||||
"sha256:a59d58ee85b11f337b54933e8d758b2356fcdcc493248e004c9c5e5d11eedbe4",
|
||||
"sha256:a6e35d28900cf87bcc11e6ca9e474db0099b78f0be0a41d95bef02d49101b5b2",
|
||||
"sha256:b4df7ca9c01018a51e43937eaa41f2f5dce17a6382fda0086403bcb1f5c2cf8e",
|
||||
"sha256:bbd5a6bffd3ba8bfe75b16b5e28af15265538e8be011b0b9fddc7d86a453fd4a",
|
||||
"sha256:d870f399fcd58a1889e93008762a3b9a27cf7ea512818fc6e689f59495648355",
|
||||
"sha256:e9404e2e19e901121c3c5c6cffd5a8ae0d1d67919c970e3b3262231175713068"
|
||||
"sha256:112eeeddd226af681dc82b756ed34aa7b6d98f9c4a15760050298c21d715473d",
|
||||
"sha256:13b64ecb692effcabc5e29569ba9b5eb69c35112f990a16d6833ec3a9d9f8ec0",
|
||||
"sha256:1725373fb8f18c2166f8e0e5789851ccf98453c849b403945fa4ef59a16ca44e",
|
||||
"sha256:2061a50b7cae60a1f987503a995b2fc38e47027a937a355a124306ed9c629041",
|
||||
"sha256:35b062288a9a478f627c520fd27983160fc97591017d170f966805b428d17e07",
|
||||
"sha256:467b134bcc227b91b8e2ef8d2931f28b50bf7eb7a04c0403d102ded22e66dbfc",
|
||||
"sha256:475a3ece8bb450e49385414ebfae7f8fdb33f62f1ac0c12935c1cfb1b7c1076a",
|
||||
"sha256:49b885287e227a24545a1126d9ac17ae43138610713dc6219b781cc0ad5c6dfc",
|
||||
"sha256:4c95b2725592adb5c46642be2875c1234c32af841732c5504c17726b92082021",
|
||||
"sha256:4ea7ed00f4be0f7335c9a2713a65ac3d986be789ce5ebc10821da9664cbe6b85",
|
||||
"sha256:5e2d5e1d999e941b4a626aea46bdc4206877cf727107fdaa9d46a8a773a6e49b",
|
||||
"sha256:8039c520ef7bb9ec7c3db3df14c570be6362f43c200ae9854d2422d4ffe175a4",
|
||||
"sha256:81459a0ebcca09c1fcb8fe887ed13cf267d9b60fe33718fc5fd1a2a1ab49470a",
|
||||
"sha256:847c3b7b9ca3268e883685dc1347a4d09f84de7bd7597310044d847590447492",
|
||||
"sha256:8551d1db45f0ca4e8ec99130767009a29a4e0dc6558a4a6808491bcd3472d325",
|
||||
"sha256:8fa7679ffe615e0c1c7b80946ab4194669be74848719adf2d7867b5e861eb073",
|
||||
"sha256:a42a36f09f0f907579ff0fde547f2fde8a739a69efe4a2728835979d2bb5e17b",
|
||||
"sha256:a5fcad0070685c5b2d04b468bf5f4c735f5c176432f495ad055fcc4bc0a79b23",
|
||||
"sha256:ae22195b2a7494619b73c01129ddcddc0dfaa9e42727404b1d9a77253da3f420",
|
||||
"sha256:b360e82bdbbd862e1ce2a41cc3bbd0ab614350e813ca74801b34aac0f73465aa",
|
||||
"sha256:b96417899344c5e96bef757f4963a72d02e52653a4e0f99bbea3a531cedac59f",
|
||||
"sha256:b9e921140b797093edfc13ac08dc2a4fd016dd711dc42bb0e1aaf180e48425a7",
|
||||
"sha256:c5022b94fc330e6d177f3eb38097fb52c7df96ca0e04842c068cf0d9fc38b1e6",
|
||||
"sha256:cf2b117f2a8d951638efc7592fb72d3eeb2d38cc2194c26ba7f00e7190451d92",
|
||||
"sha256:d79620b542d9d0e23ae9790ca2fe44f1af40ffad9936efa37bd14954bc3e2818",
|
||||
"sha256:e2860691c11d10dac7c91bddae44f6211b3da4122d9a2ebb509c2247674d6070",
|
||||
"sha256:e3a293553715afecf7e10ea02da40593f9d7f48fe48a74fc5dd3ce08a0c46188",
|
||||
"sha256:e465be3fe7e992e5a6e16731afa6f41cb6ca53afccb4f28ea2fa6457783edf15",
|
||||
"sha256:e6d27895ef922bc859d969452f247bfbe5345d9aba69b9c8dbe1ea7704f0c5d9"
|
||||
],
|
||||
"markers": "python_version >= '3.4.1'",
|
||||
"version": "==4.3.1"
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"pymongo": {
|
||||
"hashes": [
|
||||
@@ -286,10 +301,10 @@
|
||||
},
|
||||
"aiohttp-json-rpc": {
|
||||
"hashes": [
|
||||
"sha256:bf1eb7e30949b60f74cb84731b5676bd7dc3f0298056ddbbe989d9219260008c",
|
||||
"sha256:e1ae47d522a7857c612be8ba447cec3cad8c8b7d628353289a0889a1135166c8"
|
||||
"sha256:970806a3b9887c389095d2bde84e2b540fefeddd0bae0efcae03c65f092ce00e",
|
||||
"sha256:d6f365067676e6089ac043ad31bcbabbf33d0343c42b57c36751a562fbe64fb6"
|
||||
],
|
||||
"version": "==0.11"
|
||||
"version": "==0.11.1"
|
||||
},
|
||||
"alabaster": {
|
||||
"hashes": [
|
||||
@@ -315,17 +330,18 @@
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
|
||||
"sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6"
|
||||
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
|
||||
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
|
||||
],
|
||||
"version": "==1.1.5"
|
||||
"markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*'",
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
|
||||
"sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
|
||||
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
|
||||
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
|
||||
],
|
||||
"version": "==18.1.0"
|
||||
"version": "==18.2.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
@@ -343,10 +359,10 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
||||
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
|
||||
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
|
||||
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
|
||||
],
|
||||
"version": "==2018.4.16"
|
||||
"version": "==2018.8.24"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -395,10 +411,10 @@
|
||||
},
|
||||
"fuzzywuzzy": {
|
||||
"hashes": [
|
||||
"sha256:d40c22d2744dff84885b30bbfc07fab7875f641d070374331777a4d1808b8d4e",
|
||||
"sha256:ecf490216fb4d76b558a03042ff8f45a8782f17326caca1384d834cbaa2c7e6f"
|
||||
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
|
||||
"sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62"
|
||||
],
|
||||
"version": "==0.16.0"
|
||||
"version": "==0.17.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
@@ -415,10 +431,11 @@
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
"sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18",
|
||||
"sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315"
|
||||
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
|
||||
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
"markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*'",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
@@ -443,22 +460,37 @@
|
||||
},
|
||||
"multidict": {
|
||||
"hashes": [
|
||||
"sha256:1a1d76374a1e7fe93acef96b354a03c1d7f83e7512e225a527d283da0d7ba5e0",
|
||||
"sha256:1d6e191965505652f194bc4c40270a842922685918a4f45e6936a6b15cc5816d",
|
||||
"sha256:295961a6a88f1199e19968e15d9b42f3a191c89ec13034dbc212bf9c394c3c82",
|
||||
"sha256:2be5af084de6c3b8e20d6421cb0346378a9c867dcf7c86030d6b0b550f9888e4",
|
||||
"sha256:2eb99617c7a0e9f2b90b64bc1fb742611718618572747d6f3d6532b7b78755ab",
|
||||
"sha256:4ba654c6b5ad1ae4a4d792abeb695b29ce981bb0f157a41d0fd227b385f2bef0",
|
||||
"sha256:5ba766433c30d703f6b2c17eb0b6826c6f898e5f58d89373e235f07764952314",
|
||||
"sha256:a59d58ee85b11f337b54933e8d758b2356fcdcc493248e004c9c5e5d11eedbe4",
|
||||
"sha256:a6e35d28900cf87bcc11e6ca9e474db0099b78f0be0a41d95bef02d49101b5b2",
|
||||
"sha256:b4df7ca9c01018a51e43937eaa41f2f5dce17a6382fda0086403bcb1f5c2cf8e",
|
||||
"sha256:bbd5a6bffd3ba8bfe75b16b5e28af15265538e8be011b0b9fddc7d86a453fd4a",
|
||||
"sha256:d870f399fcd58a1889e93008762a3b9a27cf7ea512818fc6e689f59495648355",
|
||||
"sha256:e9404e2e19e901121c3c5c6cffd5a8ae0d1d67919c970e3b3262231175713068"
|
||||
"sha256:112eeeddd226af681dc82b756ed34aa7b6d98f9c4a15760050298c21d715473d",
|
||||
"sha256:13b64ecb692effcabc5e29569ba9b5eb69c35112f990a16d6833ec3a9d9f8ec0",
|
||||
"sha256:1725373fb8f18c2166f8e0e5789851ccf98453c849b403945fa4ef59a16ca44e",
|
||||
"sha256:2061a50b7cae60a1f987503a995b2fc38e47027a937a355a124306ed9c629041",
|
||||
"sha256:35b062288a9a478f627c520fd27983160fc97591017d170f966805b428d17e07",
|
||||
"sha256:467b134bcc227b91b8e2ef8d2931f28b50bf7eb7a04c0403d102ded22e66dbfc",
|
||||
"sha256:475a3ece8bb450e49385414ebfae7f8fdb33f62f1ac0c12935c1cfb1b7c1076a",
|
||||
"sha256:49b885287e227a24545a1126d9ac17ae43138610713dc6219b781cc0ad5c6dfc",
|
||||
"sha256:4c95b2725592adb5c46642be2875c1234c32af841732c5504c17726b92082021",
|
||||
"sha256:4ea7ed00f4be0f7335c9a2713a65ac3d986be789ce5ebc10821da9664cbe6b85",
|
||||
"sha256:5e2d5e1d999e941b4a626aea46bdc4206877cf727107fdaa9d46a8a773a6e49b",
|
||||
"sha256:8039c520ef7bb9ec7c3db3df14c570be6362f43c200ae9854d2422d4ffe175a4",
|
||||
"sha256:81459a0ebcca09c1fcb8fe887ed13cf267d9b60fe33718fc5fd1a2a1ab49470a",
|
||||
"sha256:847c3b7b9ca3268e883685dc1347a4d09f84de7bd7597310044d847590447492",
|
||||
"sha256:8551d1db45f0ca4e8ec99130767009a29a4e0dc6558a4a6808491bcd3472d325",
|
||||
"sha256:8fa7679ffe615e0c1c7b80946ab4194669be74848719adf2d7867b5e861eb073",
|
||||
"sha256:a42a36f09f0f907579ff0fde547f2fde8a739a69efe4a2728835979d2bb5e17b",
|
||||
"sha256:a5fcad0070685c5b2d04b468bf5f4c735f5c176432f495ad055fcc4bc0a79b23",
|
||||
"sha256:ae22195b2a7494619b73c01129ddcddc0dfaa9e42727404b1d9a77253da3f420",
|
||||
"sha256:b360e82bdbbd862e1ce2a41cc3bbd0ab614350e813ca74801b34aac0f73465aa",
|
||||
"sha256:b96417899344c5e96bef757f4963a72d02e52653a4e0f99bbea3a531cedac59f",
|
||||
"sha256:b9e921140b797093edfc13ac08dc2a4fd016dd711dc42bb0e1aaf180e48425a7",
|
||||
"sha256:c5022b94fc330e6d177f3eb38097fb52c7df96ca0e04842c068cf0d9fc38b1e6",
|
||||
"sha256:cf2b117f2a8d951638efc7592fb72d3eeb2d38cc2194c26ba7f00e7190451d92",
|
||||
"sha256:d79620b542d9d0e23ae9790ca2fe44f1af40ffad9936efa37bd14954bc3e2818",
|
||||
"sha256:e2860691c11d10dac7c91bddae44f6211b3da4122d9a2ebb509c2247674d6070",
|
||||
"sha256:e3a293553715afecf7e10ea02da40593f9d7f48fe48a74fc5dd3ce08a0c46188",
|
||||
"sha256:e465be3fe7e992e5a6e16731afa6f41cb6ca53afccb4f28ea2fa6457783edf15",
|
||||
"sha256:e6d27895ef922bc859d969452f247bfbe5345d9aba69b9c8dbe1ea7704f0c5d9"
|
||||
],
|
||||
"markers": "python_version >= '3.4.1'",
|
||||
"version": "==4.3.1"
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
@@ -477,11 +509,11 @@
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
|
||||
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
|
||||
"sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
|
||||
"sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
|
||||
],
|
||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||
"version": "==1.5.4"
|
||||
"markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*'",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
@@ -499,10 +531,10 @@
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:8214ab8446104a1d0c17fbd218ec6aac743236c6ffbe23abc038e40213c60b88",
|
||||
"sha256:e2b2c6e1560b8f9dc8dd600b0923183fbd68ba3d9bdecde04467be6dd296a384"
|
||||
"sha256:2d7c49e931316cc7d1638a3e5f54f5d7b4e5225972b3c9838f3584788d27f349",
|
||||
"sha256:ad0c7db7b5d4081631e0155f5c61b80ad76ce148551aaafe3a718d65a7508b18"
|
||||
],
|
||||
"version": "==3.7.0"
|
||||
"version": "==3.7.4"
|
||||
},
|
||||
"pytest-asyncio": {
|
||||
"hashes": [
|
||||
@@ -578,10 +610,10 @@
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:217ad9ece2156ed9f8af12b5d2c82a499ddf2c70a33c5f81864a08d8c67b9efc",
|
||||
"sha256:a765c6db1e5b62aae857697cd4402a5c1a315a7b0854bbcd0fc8cdc524da5896"
|
||||
"sha256:a07050845cc9a2f4026a6035cc8ed795a5ce7be6528bbc82032385c10807dfe7",
|
||||
"sha256:d719de667218d763e8fd144b7fcfeefd8d434a6201f76bf9f0f0c1fa6f47fcdb"
|
||||
],
|
||||
"version": "==1.7.6"
|
||||
"version": "==1.7.8"
|
||||
},
|
||||
"sphinx-rtd-theme": {
|
||||
"hashes": [
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://github.com/Cog-Creators/Red-DiscordBot/tree/V3/develop"><img src="https://imgur.com/pY1WUFX.png" alt="Red - Discord Bot"></a>
|
||||
<br>
|
||||
Red Discord Bot
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
<h4 align="center">Music, Moderation, Trivia, Stream Alerts and Fully Modular.</h4>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/red">
|
||||
<img src="https://discordapp.com/api/guilds/133049272517001216/widget.png?style=shield" alt="Discord Server">
|
||||
</a>
|
||||
<a href="https://www.patreon.com/Red_Devs">
|
||||
<img src="https://img.shields.io/badge/Support-Red!-yellow.svg" alt="Support Red on Patreon!">
|
||||
</a>
|
||||
<a href="https://www.python.org/downloads/">
|
||||
<img src="https://img.shields.io/badge/Made%20With-Python%203-blue.svg?style=for-the-badge" alt="Made with Python 3">
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/red-discordbot">
|
||||
<img src="https://d322cqt584bo4o.cloudfront.net/red-discordbot/localized.svg" alt="Localized with Crowdin">
|
||||
</a>
|
||||
<a href="https://github.com/Rapptz/discord.py/tree/rewrite">
|
||||
<img src="https://img.shields.io/badge/discord-py-blue.svg" alt="discord.py">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/Cog-Creators/Red-DiscordBot">
|
||||
<img src="https://api.travis-ci.org/Cog-Creators/Red-DiscordBot.svg?branch=V3/develop" alt="Travis CI">
|
||||
</a>
|
||||
<a href="http://red-discordbot.readthedocs.io/en/v3-develop/?badge=v3-develop">
|
||||
<img src="https://readthedocs.org/projects/red-discordbot/badge/?version=v3-develop" alt="Red on readthedocs.org">
|
||||
</a>
|
||||
<a href="https://github.com/ambv/black">
|
||||
<img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code Style: Black">
|
||||
</a>
|
||||
<a href="http://makeapullrequest.com">
|
||||
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#overview">Overview</a>
|
||||
•
|
||||
<a href="#installation">Installation</a>
|
||||
•
|
||||
<a href="http://red-discordbot.readthedocs.io/en/v3-develop/index.html">Documentation</a>
|
||||
•
|
||||
<a href="#plugins">Plugins</a>
|
||||
•
|
||||
<a href="#join-the-community">Community</a>
|
||||
•
|
||||
<a href="#license">License</a>
|
||||
</p>
|
||||
|
||||
# Overview
|
||||
|
||||
Red is a fully modular bot – meaning all features and commands can be enabled/disabled to your
|
||||
liking, making it completely customizable. This is also a *self-hosted bot* – meaning you will need
|
||||
to host and maintain your own instance. You can turn Red into an admin bot, music bot, trivia bot,
|
||||
new best friend or all of these together!
|
||||
|
||||
[Installation](#installation) is easy, and you do **NOT** need to know anything about coding! Aside
|
||||
from installation and updating, every part of the bot can be controlled from within Discord.
|
||||
|
||||
**The default set of modules includes and is not limited to:**
|
||||
|
||||
- Moderation features (kick/ban/softban/hackban, mod-log, filter, chat cleanup)
|
||||
- Trivia (lists are included and can be easily added)
|
||||
- Music features (YouTube, SoundCloud, local files, playlists, queues)
|
||||
- Stream alerts (Twitch, Youtube, Mixer, Hitbox, Picarto)
|
||||
- Bank (slot machine, user credits)
|
||||
- Custom commands
|
||||
- Imgur/gif search
|
||||
- Admin automation (self-role assignment, cross-server announcements, mod-mail reports)
|
||||
- Customisable command permissions
|
||||
|
||||
**Additionally, other [plugins](#plugins) (cogs) can be easily found and added from our growing
|
||||
community of cog repositories.**
|
||||
|
||||
# Installation
|
||||
|
||||
**The following platforms are officially supported:**
|
||||
|
||||
- [Windows](https://red-discordbot.readthedocs.io/en/v3-develop/install_windows.html)
|
||||
- [MacOS](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
|
||||
- [Ubuntu](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
|
||||
- [Debian Stretch](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
|
||||
- [CentOS 7](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
|
||||
- [Arch Linux](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
|
||||
- [Raspbian Stretch](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
|
||||
|
||||
Already using **Red** V2? Take a look at the [Data Converter](https://red-discordbot.readthedocs.io/en/v3-develop/cog_dataconverter.html)
|
||||
to import your data to V3.
|
||||
|
||||
If after reading the guide you are still experiencing issues, feel free to join the
|
||||
[Official Discord Server](https://discord.gg/red) and ask in the **#v3-support** channel for help.
|
||||
|
||||
# Plugins
|
||||
|
||||
Red is fully modular, allowing you to load and unload plugins of your choice, and install 3rd party
|
||||
plugins directly from Discord! A few examples are:
|
||||
|
||||
- Cleverbot integration (talk to Red and she talks back)
|
||||
- Ban sync
|
||||
- Welcome messages
|
||||
- Casino
|
||||
- Reaction roles
|
||||
- Slow Mode
|
||||
- Anilist
|
||||
- And much, much more!
|
||||
|
||||
Feel free to take a [peek](https://github.com/Cog-Creators/Red-DiscordBot/issues/1398) at a list of
|
||||
available 3rd party cogs!
|
||||
|
||||
# Join the community!
|
||||
|
||||
**Red** is in continuous development, and it’s supported by an active community which produces new
|
||||
content (cogs/plugins) for everyone to enjoy. New features are constantly added. If you can’t
|
||||
[find](https://github.com/Cog-Creators/Red-DiscordBot/issues/1398) the cog you’re looking for,
|
||||
consult our [guide](https://red-discordbot.readthedocs.io/en/v3-develop/guide_cog_creation.html) on
|
||||
building your own cogs!
|
||||
|
||||
Join us on our [Official Discord Server](https://discord.gg/red)!
|
||||
|
||||
# License
|
||||
|
||||
Released under the [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html) license.
|
||||
|
||||
Red is named after the main character of "Transistor", a video game by
|
||||
[Super Giant Games](https://www.supergiantgames.com/games/transistor/).
|
||||
|
||||
Artwork created by [Sinlaire](https://sinlaire.deviantart.com/) on Deviant Art for the Red Discord
|
||||
Bot Project.
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
.. class:: center
|
||||
|
||||
.. image:: https://imgur.com/pY1WUFX.png
|
||||
:target: https://github.com/Cog-Creators/Red-DiscordBot/tree/V3/develop
|
||||
:alt: Red Discord Bot
|
||||
|
||||
|
||||
.. class:: center
|
||||
|
||||
Music, Moderation, Trivia, Stream Alerts and fully customizable.
|
||||
|
||||
.. class:: center
|
||||
|
||||
.. image:: https://discordapp.com/api/guilds/133049272517001216/embed.png
|
||||
:target: https://discord.gg/red
|
||||
:alt: Discord server
|
||||
|
||||
.. image:: https://api.travis-ci.org/Cog-Creators/Red-DiscordBot.svg?branch=V3/develop
|
||||
:target: https://travis-ci.org/Cog-Creators/Red-DiscordBot
|
||||
:alt: Travis CI status
|
||||
|
||||
.. image:: https://readthedocs.org/projects/red-discordbot/badge/?version=v3-develop
|
||||
:target: http://red-discordbot.readthedocs.io/en/v3-develop/?badge=v3-develop
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://img.shields.io/badge/discord-py-blue.svg
|
||||
:target: https://github.com/Rapptz/discord.py
|
||||
:alt: discord.py
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/ambv/black
|
||||
:alt: Code style: black
|
||||
|
||||
.. image:: https://d322cqt584bo4o.cloudfront.net/red-discordbot/localized.svg
|
||||
:target: https://crowdin.com/project/red-discordbot
|
||||
:alt: Crowdin
|
||||
|
||||
.. image:: https://img.shields.io/badge/Support-Red!-orange.svg
|
||||
:target: https://www.patreon.com/Red_Devs
|
||||
:alt: Patreon
|
||||
|
||||
.. image:: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
|
||||
:target: http://makeapullrequest.com
|
||||
:alt: PRs open
|
||||
|
||||
==========
|
||||
Overview
|
||||
==========
|
||||
|
||||
Red is a fully modular bot – meaning all features and commands can be enabled/disabled to your liking, making it completely customizable.
|
||||
This is also a *self-hosted bot* – meaning you will need to host and maintain your own instance. You can turn Red into an admin bot, music bot, trivia bot, new best friend or all of these together!
|
||||
|
||||
`Installation <#installation>`_ is easy, and you do **NOT** need to know anything about coding! Aside from installation and updating, every part of the bot can be controlled from within Discord.
|
||||
|
||||
**The default set of modules includes and is not limited to:**
|
||||
|
||||
- Moderation features (kick/ban/softban/hackban, mod-log, filter, chat cleanup)
|
||||
- Trivia (lists are included and can be easily added)
|
||||
- Music features (YouTube, SoundCloud, local files, playlists, queues)
|
||||
- Stream alerts (Twitch, Youtube, Mixer, Hitbox, Picarto)
|
||||
- Slot machine
|
||||
- Custom commands
|
||||
- Imgur/gif search
|
||||
|
||||
|
||||
**Additionally, other plugins (cogs) can be easily found and added from our growing community of cog repositories.**
|
||||
|
||||
- Cleverbot integration (talk to Red and she talks back)
|
||||
- Ban sync
|
||||
- Welcome messages
|
||||
- Casino
|
||||
- Reaction roles
|
||||
- Slow Mode
|
||||
- Anilist
|
||||
- And much, much more!
|
||||
|
||||
Feel free to take a `peek <https://github.com/Cog-Creators/Red-DiscordBot/issues/1398>`_!
|
||||
|
||||
==============
|
||||
Installation
|
||||
==============
|
||||
|
||||
**The following platforms are officially supported:**
|
||||
|
||||
- `Windows <https://red-discordbot.readthedocs.io/en/v3-develop/install_windows.html>`_
|
||||
- `MacOS <https://red-discordbot.readthedocs.io/en/v3-develop/install_mac.html>`_
|
||||
- `Ubuntu <https://red-discordbot.readthedocs.io/en/v3-develop/install_ubuntu.html>`_
|
||||
- `Debian Stretch <https://red-discordbot.readthedocs.io/en/v3-develop/install_debian.html>`_
|
||||
- `CentOS 7 <https://red-discordbot.readthedocs.io/en/v3-develop/install_centos.html>`_
|
||||
- `Arch Linux <https://red-discordbot.readthedocs.io/en/v3-develop/install_arch.html>`_
|
||||
- `Raspbian Stretch <https://red-discordbot.readthedocs.io/en/v3-develop/install_raspbian.html>`_
|
||||
|
||||
Already using **Red** V2? Take a look at the `Data Converter <https://red-discordbot.readthedocs.io/en/v3-develop/cog_dataconverter.html>`_ to import your data to V3.
|
||||
|
||||
If `after reading the guides <https://red-discordbot.readthedocs.io/en/v3-develop/>`_ you are still experiencing issues, feel free to join the `Official Server <https://discord.gg/red>`_ and ask in the **#support** channel for help.
|
||||
|
||||
=====================
|
||||
Join the community!
|
||||
=====================
|
||||
|
||||
**Red** is in continuous development, and it’s supported by an active community which produces new content (cogs/plugins) for everyone to enjoy. New features are constantly added. If you can’t `find <https://github.com/Cog-Creators/Red-DiscordBot/issues/1398>`_ what you’re looking for, consult our `guide <https://red-discordbot.readthedocs.io/en/v3-develop/guide_cog_creation.html>`_ on building your own cogs!
|
||||
|
||||
Join us on our `Official Discord Server <https://discord.gg/red>`_!
|
||||
|
||||
=========
|
||||
License
|
||||
=========
|
||||
|
||||
Released under the `GNU GPL v3 <#License>`_.
|
||||
|
||||
Red is named after the main character of "Transistor", a videogame by `Super Giant Games <https://www.supergiantgames.com/games/transistor/>`_
|
||||
|
||||
Artwork created by `Sinlaire <https://sinlaire.deviantart.com/>`_ on Deviant Art for the Red Bot Project.
|
||||
@@ -1 +1 @@
|
||||
https://github.com/Rapptz/discord.py/tarball/8ccb98d395537b1c9acc187e1647dfdd07bb831b#egg=discord.py-1.0.0a0
|
||||
https://github.com/Rapptz/discord.py/tarball/00a659c6526b2445162b52eaf970adbd22c6d35d#egg=discord.py-1.0.0a0
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
.. CustomCommands Cog Reference
|
||||
|
||||
============================
|
||||
CustomCommands Cog Reference
|
||||
============================
|
||||
|
||||
------------
|
||||
How it works
|
||||
------------
|
||||
|
||||
CustomCommands allows you to create simple commands for your bot without requiring you to code your own cog for Red.
|
||||
|
||||
If the command you attempt to create shares a name with an already loaded command, you cannot overwrite it with this cog.
|
||||
|
||||
------------------
|
||||
Context Parameters
|
||||
------------------
|
||||
|
||||
You can enhance your custom command's response by leaving spaces for the bot to substitute.
|
||||
|
||||
+-----------+----------------------------------------+
|
||||
| Argument | Substitute |
|
||||
+===========+========================================+
|
||||
| {message} | The message the bot is responding to. |
|
||||
+-----------+----------------------------------------+
|
||||
| {author} | The user who called the command. |
|
||||
+-----------+----------------------------------------+
|
||||
| {channel} | The channel the command was called in. |
|
||||
+-----------+----------------------------------------+
|
||||
| {server} | The server the command was called in. |
|
||||
+-----------+----------------------------------------+
|
||||
| {guild} | Same as with {server}. |
|
||||
+-----------+----------------------------------------+
|
||||
|
||||
You can further refine the response with dot notation. For example, {author.mention} will mention the user who called the command.
|
||||
|
||||
------------------
|
||||
Command Parameters
|
||||
------------------
|
||||
|
||||
You can further enhance your custom command's response by leaving spaces for the user to substitute.
|
||||
|
||||
To do this, simply put {#} in the response, replacing # with any number starting with 0. Each number will be replaced with what the user gave the command, in order.
|
||||
|
||||
You can refine the response with colon notation. For example, {0:Member} will accept members of the server, and {0:int} will accept a number. If no colon notation is provided, the argument will be returned unchanged.
|
||||
|
||||
+-----------------+--------------------------------+
|
||||
| Argument | Substitute |
|
||||
+=================+================================+
|
||||
| {#:Member} | A member of your server. |
|
||||
+-----------------+--------------------------------+
|
||||
| {#:TextChannel} | A text channel in your server. |
|
||||
+-----------------+--------------------------------+
|
||||
| {#:Role} | A role in your server. |
|
||||
+-----------------+--------------------------------+
|
||||
| {#:int} | A whole number. |
|
||||
+-----------------+--------------------------------+
|
||||
| {#:float} | A decimal number. |
|
||||
+-----------------+--------------------------------+
|
||||
| {#:bool} | True or False. |
|
||||
+-----------------+--------------------------------+
|
||||
|
||||
You can specify more than the above with colon notation, but those are the most common.
|
||||
|
||||
As with context parameters, you can use dot notation to further refine the response. For example, {0.mention:Member} will mention the Member specified.
|
||||
|
||||
----------------
|
||||
Example commands
|
||||
----------------
|
||||
|
||||
Showing your own avatar
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]customcom add simple avatar {author.avatar_url}
|
||||
[p]avatar
|
||||
https://cdn.discordapp.com/avatars/133801473317404673/be4c4a4fe47cb3e74c31a0504e7a295e.webp?size=1024
|
||||
|
||||
Repeating the user
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]customcom add simple say {0}
|
||||
[p]say Pete and Repeat
|
||||
Pete and Repeat
|
||||
|
||||
Greeting the specified member
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]customcom add simple greet Hello, {0.mention:Member}!
|
||||
[p]greet Twentysix
|
||||
Hello, @Twentysix!
|
||||
|
||||
Comparing two text channel's categories
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]customcom add simple comparecategory {0.category:TextChannel} | {1.category:TextChannel}
|
||||
[p]comparecategory #support #general
|
||||
Red | Community
|
||||
@@ -17,7 +17,7 @@ you in the process.
|
||||
Getting started
|
||||
---------------
|
||||
|
||||
To start off, be sure that you have installed Python 3.6 or higher. Open a terminal or command prompt and type
|
||||
To start off, be sure that you have installed Python 3.6.2 or higher. Open a terminal or command prompt and type
|
||||
:code:`pip install --process-dependency-links -U git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=redbot[test]`
|
||||
(note that if you get an error with this, try again but put :code:`python -m` in front of the command
|
||||
This will install the latest version of V3.
|
||||
@@ -44,7 +44,7 @@ In that file, place the following code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from discord.ext import commands
|
||||
from redbot.core import commands
|
||||
|
||||
class Mycog:
|
||||
"""My custom cog"""
|
||||
|
||||
@@ -20,6 +20,7 @@ Welcome to Red - Discord Bot's documentation!
|
||||
:maxdepth: 2
|
||||
:caption: Cog Reference:
|
||||
|
||||
cog_customcom
|
||||
cog_downloader
|
||||
cog_permissions
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Installing the pre-requirements
|
||||
Please install the pre-requirements using the commands listed for your operating system.
|
||||
|
||||
The pre-requirements are:
|
||||
- Python 3.6 or greater
|
||||
- Python 3.6.2 or greater
|
||||
- pip 9.0 or greater
|
||||
- git
|
||||
- Java Runtime Environment 8 or later (for audio support)
|
||||
@@ -108,7 +108,7 @@ Ubuntu 18.04 Bionic Beaver
|
||||
Ubuntu 16.04 Xenial Xerus
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We recommend adding the ``deadsnakes`` apt repository to install Python 3.6 or greater:
|
||||
We recommend adding the ``deadsnakes`` apt repository to install Python 3.6.2 or greater:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Installing Red on Windows
|
||||
Needed Software
|
||||
---------------
|
||||
|
||||
* `Python <https://www.python.org/downloads/>`_ - Red needs Python 3.6
|
||||
* `Python <https://www.python.org/downloads/>`_ - Red needs Python 3.6.2 or greater
|
||||
|
||||
.. note:: Please make sure that the box to add Python to PATH is CHECKED, otherwise
|
||||
you may run into issues when trying to run Red
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ Using ``pyenv virtualenv``
|
||||
Using ``pyenv virtualenv`` saves you the headache of remembering where you installed your virtual
|
||||
environments. If you haven't already, install pyenv with `pyenv-installer`_.
|
||||
|
||||
First, ensure your pyenv interpreter is set to python 3.6 or later with the following command::
|
||||
First, ensure your pyenv interpreter is set to python 3.6.2 or greater with the following command::
|
||||
|
||||
pyenv version
|
||||
|
||||
|
||||
+5
-1
@@ -1,6 +1,7 @@
|
||||
import sys
|
||||
import warnings
|
||||
import discord
|
||||
from colorama import init, Back
|
||||
from colorama import init
|
||||
|
||||
init()
|
||||
# Let's do all the dumb version checking in one place.
|
||||
@@ -12,3 +13,6 @@ if discord.version_info.major < 1:
|
||||
" >= 1.0.0."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Filter fuzzywuzzy slow sequence matcher warning
|
||||
warnings.filterwarnings("ignore", module=r"fuzzywuzzy.*")
|
||||
|
||||
+4
-1
@@ -28,6 +28,9 @@ if sys.implementation.name == "cpython":
|
||||
else:
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
|
||||
if sys.platform == "win32":
|
||||
asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
||||
|
||||
|
||||
#
|
||||
# Red - Discord Bot v3
|
||||
@@ -60,7 +63,7 @@ def init_loggers(cli_flags):
|
||||
os.environ["PYTHONASYNCIODEBUG"] = "1"
|
||||
logger.setLevel(logging.DEBUG)
|
||||
else:
|
||||
logger.setLevel(logging.WARNING)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
from redbot.core.data_manager import core_data_path
|
||||
|
||||
|
||||
+674
-180
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ lavalink:
|
||||
vimeo: true
|
||||
mixer: true
|
||||
http: true
|
||||
local: false
|
||||
local: true
|
||||
sentryDsn: ""
|
||||
bufferDurationMs: 400
|
||||
youtubePlaylistLoadLimit: 10000
|
||||
youtubePlaylistLoadLimit: 10000
|
||||
|
||||
@@ -2,6 +2,9 @@ import os
|
||||
import re
|
||||
import random
|
||||
from datetime import datetime
|
||||
from inspect import Parameter
|
||||
from collections import OrderedDict
|
||||
from typing import Mapping
|
||||
|
||||
import discord
|
||||
|
||||
@@ -24,6 +27,10 @@ class AlreadyExists(CCError):
|
||||
pass
|
||||
|
||||
|
||||
class ArgParseError(CCError):
|
||||
pass
|
||||
|
||||
|
||||
class CommandObj:
|
||||
def __init__(self, **kwargs):
|
||||
config = kwargs.get("config")
|
||||
@@ -51,6 +58,7 @@ class CommandObj:
|
||||
return m.channel == ctx.channel and m.author == ctx.message.author
|
||||
|
||||
responses = []
|
||||
args = None
|
||||
while True:
|
||||
await ctx.send(_("Add a random response:"))
|
||||
msg = await self.bot.wait_for("message", check=check)
|
||||
@@ -58,6 +66,15 @@ class CommandObj:
|
||||
if msg.content.lower() == "exit()":
|
||||
break
|
||||
else:
|
||||
try:
|
||||
this_args = ctx.cog.prepare_args(msg.content)
|
||||
except ArgParseError as e:
|
||||
await ctx.send(e.args[0])
|
||||
continue
|
||||
if args and args != this_args:
|
||||
await ctx.send(_("Random responses must take the same arguments!"))
|
||||
continue
|
||||
args = args or this_args
|
||||
responses.append(msg.content)
|
||||
return responses
|
||||
|
||||
@@ -69,7 +86,7 @@ class CommandObj:
|
||||
async def get(self, message: discord.Message, command: str) -> str:
|
||||
ccinfo = await self.db(message.guild).commands.get_raw(command, default=None)
|
||||
if not ccinfo:
|
||||
raise NotFound
|
||||
raise NotFound()
|
||||
else:
|
||||
return ccinfo["response"]
|
||||
|
||||
@@ -78,6 +95,8 @@ class CommandObj:
|
||||
# Check if this command is already registered as a customcommand
|
||||
if await self.db(ctx.guild).commands.get_raw(command, default=None):
|
||||
raise AlreadyExists()
|
||||
# test to raise
|
||||
ctx.cog.prepare_args(response if isinstance(response, str) else response[0])
|
||||
author = ctx.message.author
|
||||
ccinfo = {
|
||||
"author": {"id": author.id, "name": author.name},
|
||||
@@ -110,6 +129,9 @@ class CommandObj:
|
||||
await ctx.send(_("What response do you want?"))
|
||||
response = (await self.bot.wait_for("message", check=check)).content
|
||||
|
||||
# test to raise
|
||||
ctx.cog.prepare_args(response if isinstance(response, str) else response[0])
|
||||
|
||||
ccinfo["response"] = response
|
||||
ccinfo["edited_at"] = self.get_now()
|
||||
|
||||
@@ -151,19 +173,10 @@ class CustomCommands:
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def cc_add(self, ctx: commands.Context):
|
||||
"""
|
||||
Adds a new custom command
|
||||
|
||||
CCs can be enhanced with arguments:
|
||||
|
||||
Argument What it will be substituted with
|
||||
|
||||
{message} message
|
||||
|
||||
{author} message.author
|
||||
|
||||
{channel} message.channel
|
||||
|
||||
{guild} message.guild
|
||||
|
||||
{server} message.guild
|
||||
https://red-discordbot.readthedocs.io/en/v3-develop/cog_customcom.html
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -175,7 +188,6 @@ class CustomCommands:
|
||||
|
||||
Note: This is interactive
|
||||
"""
|
||||
channel = ctx.channel
|
||||
responses = []
|
||||
|
||||
responses = await self.commandobj.get_responses(ctx=ctx)
|
||||
@@ -199,7 +211,6 @@ class CustomCommands:
|
||||
Example:
|
||||
[p]customcom add simple yourcommand Text you want
|
||||
"""
|
||||
guild = ctx.guild
|
||||
command = command.lower()
|
||||
if command in self.bot.all_commands:
|
||||
await ctx.send(_("That command is already a standard command."))
|
||||
@@ -213,6 +224,8 @@ class CustomCommands:
|
||||
"{}customcom edit".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
except ArgParseError as e:
|
||||
await ctx.send(e.args[0])
|
||||
|
||||
@customcom.command(name="edit")
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@@ -222,7 +235,6 @@ class CustomCommands:
|
||||
Example:
|
||||
[p]customcom edit yourcommand Text you want
|
||||
"""
|
||||
guild = ctx.message.guild
|
||||
command = command.lower()
|
||||
|
||||
try:
|
||||
@@ -234,6 +246,8 @@ class CustomCommands:
|
||||
"{}customcom add".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
except ArgParseError as e:
|
||||
await ctx.send(e.args[0])
|
||||
|
||||
@customcom.command(name="delete")
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@@ -241,7 +255,6 @@ class CustomCommands:
|
||||
"""Deletes a custom command
|
||||
Example:
|
||||
[p]customcom delete yourcommand"""
|
||||
guild = ctx.message.guild
|
||||
command = command.lower()
|
||||
try:
|
||||
await self.commandobj.delete(ctx=ctx, command=command)
|
||||
@@ -286,49 +299,145 @@ class CustomCommands:
|
||||
|
||||
async def on_message(self, message):
|
||||
is_private = isinstance(message.channel, discord.abc.PrivateChannel)
|
||||
if len(message.content) < 2 or is_private:
|
||||
return
|
||||
|
||||
guild = message.guild
|
||||
prefixes = await self.bot.db.guild(guild).get_raw("prefix", default=[])
|
||||
|
||||
if len(prefixes) < 1:
|
||||
def_prefixes = await self.bot.get_prefix(message)
|
||||
for prefix in def_prefixes:
|
||||
prefixes.append(prefix)
|
||||
|
||||
# user_allowed check, will be replaced with self.bot.user_allowed or
|
||||
# something similar once it's added
|
||||
|
||||
user_allowed = True
|
||||
|
||||
for prefix in prefixes:
|
||||
if message.content.startswith(prefix):
|
||||
break
|
||||
else:
|
||||
if len(message.content) < 2 or is_private or not user_allowed or message.author.bot:
|
||||
return
|
||||
|
||||
if user_allowed:
|
||||
cmd = message.content[len(prefix) :]
|
||||
try:
|
||||
c = await self.commandobj.get(message=message, command=cmd)
|
||||
if isinstance(c, list):
|
||||
command = random.choice(c)
|
||||
elif isinstance(c, str):
|
||||
command = c
|
||||
else:
|
||||
raise NotFound()
|
||||
except NotFound:
|
||||
return
|
||||
response = self.format_cc(command, message)
|
||||
await message.channel.send(response)
|
||||
ctx = await self.bot.get_context(message)
|
||||
|
||||
def format_cc(self, command, message) -> str:
|
||||
results = re.findall("\{([^}]+)\}", command)
|
||||
if ctx.prefix is None or ctx.valid:
|
||||
return
|
||||
|
||||
try:
|
||||
raw_response = await self.commandobj.get(message=message, command=ctx.invoked_with)
|
||||
if isinstance(raw_response, list):
|
||||
raw_response = random.choice(raw_response)
|
||||
elif isinstance(raw_response, str):
|
||||
pass
|
||||
else:
|
||||
raise NotFound()
|
||||
except NotFound:
|
||||
return
|
||||
await self.call_cc_command(ctx, raw_response, message)
|
||||
|
||||
async def call_cc_command(self, ctx, raw_response, message) -> None:
|
||||
# wrap the command here so it won't register with the bot
|
||||
fake_cc = commands.Command(ctx.invoked_with, self.cc_callback)
|
||||
fake_cc.params = self.prepare_args(raw_response)
|
||||
ctx.command = fake_cc
|
||||
await self.bot.invoke(ctx)
|
||||
if not ctx.command_failed:
|
||||
await self.cc_command(*ctx.args, **ctx.kwargs, raw_response=raw_response)
|
||||
|
||||
async def cc_callback(self, *args, **kwargs) -> None:
|
||||
"""
|
||||
Custom command.
|
||||
|
||||
Created via the CustomCom cog. See `[p]customcom` for more details.
|
||||
"""
|
||||
# fake command to take advantage of discord.py's parsing and events
|
||||
pass
|
||||
|
||||
async def cc_command(self, ctx, *cc_args, raw_response, **cc_kwargs) -> None:
|
||||
cc_args = (*cc_args, *cc_kwargs.values())
|
||||
results = re.findall(r"\{([^}]+)\}", raw_response)
|
||||
for result in results:
|
||||
param = self.transform_parameter(result, message)
|
||||
command = command.replace("{" + result + "}", param)
|
||||
return command
|
||||
param = self.transform_parameter(result, ctx.message)
|
||||
raw_response = raw_response.replace("{" + result + "}", param)
|
||||
results = re.findall(r"\{((\d+)[^\.}]*(\.[^:}]+)?[^}]*)\}", raw_response)
|
||||
if results:
|
||||
low = min(int(result[1]) for result in results)
|
||||
for result in results:
|
||||
index = int(result[1]) - low
|
||||
arg = self.transform_arg(result[0], result[2], cc_args[index])
|
||||
raw_response = raw_response.replace("{" + result[0] + "}", arg)
|
||||
await ctx.send(raw_response)
|
||||
|
||||
def prepare_args(self, raw_response) -> Mapping[str, Parameter]:
|
||||
args = re.findall(r"\{(\d+)[^:}]*(:[^\.}]*)?[^}]*\}", raw_response)
|
||||
default = [["ctx", Parameter("ctx", Parameter.POSITIONAL_OR_KEYWORD)]]
|
||||
if not args:
|
||||
return OrderedDict(default)
|
||||
allowed_builtins = {
|
||||
"bool": bool,
|
||||
"complex": complex,
|
||||
"float": float,
|
||||
"frozenset": frozenset,
|
||||
"int": int,
|
||||
"list": list,
|
||||
"set": set,
|
||||
"str": str,
|
||||
"tuple": tuple,
|
||||
}
|
||||
indices = [int(a[0]) for a in args]
|
||||
low = min(indices)
|
||||
indices = [a - low for a in indices]
|
||||
high = max(indices)
|
||||
if high > 9:
|
||||
raise ArgParseError(_("Too many arguments!"))
|
||||
gaps = set(indices).symmetric_difference(range(high + 1))
|
||||
if gaps:
|
||||
raise ArgParseError(
|
||||
_("Arguments must be sequential. Missing arguments: {}.").format(
|
||||
", ".join(str(i + low) for i in gaps)
|
||||
)
|
||||
)
|
||||
fin = [Parameter("_" + str(i), Parameter.POSITIONAL_OR_KEYWORD) for i in range(high + 1)]
|
||||
for arg in args:
|
||||
index = int(arg[0]) - low
|
||||
anno = arg[1][1:] # strip initial colon
|
||||
if anno.lower().endswith("converter"):
|
||||
anno = anno[:-9]
|
||||
if not anno or anno.startswith("_"): # public types only
|
||||
name = "{}_{}".format("text", index if index < high else "final")
|
||||
fin[index] = fin[index].replace(name=name)
|
||||
continue
|
||||
# allow type hinting only for discord.py and builtin types
|
||||
try:
|
||||
anno = getattr(discord, anno)
|
||||
# force an AttributeError if there's no discord.py converter
|
||||
getattr(commands.converter, anno.__name__ + "Converter")
|
||||
except AttributeError:
|
||||
anno = allowed_builtins.get(anno.lower(), Parameter.empty)
|
||||
if (
|
||||
anno is not Parameter.empty
|
||||
and fin[index].annotation is not Parameter.empty
|
||||
and anno != fin[index].annotation
|
||||
):
|
||||
raise ArgParseError(
|
||||
_('Conflicting colon notation for argument {}: "{}" and "{}".').format(
|
||||
index + low, fin[index].annotation.__name__, anno.__name__
|
||||
)
|
||||
)
|
||||
if anno is not Parameter.empty:
|
||||
fin[index] = fin[index].replace(annotation=anno)
|
||||
# consume rest
|
||||
fin[-1] = fin[-1].replace(kind=Parameter.KEYWORD_ONLY)
|
||||
# name the parameters for the help text
|
||||
for i, param in enumerate(fin):
|
||||
anno = param.annotation
|
||||
name = "{}_{}".format(
|
||||
"text" if anno is Parameter.empty else anno.__name__.lower(),
|
||||
i if i < high else "final",
|
||||
)
|
||||
fin[i] = fin[i].replace(name=name)
|
||||
# insert ctx parameter for discord.py parsing
|
||||
fin = default + [(p.name, p) for p in fin]
|
||||
return OrderedDict(fin)
|
||||
|
||||
def transform_arg(self, result, attr, obj) -> str:
|
||||
attr = attr[1:] # strip initial dot
|
||||
if not attr:
|
||||
return str(obj)
|
||||
raw_result = "{" + result + "}"
|
||||
# forbid private members and nested attr lookups
|
||||
if attr.startswith("_") or "." in attr:
|
||||
return raw_result
|
||||
return str(getattr(obj, attr, raw_result))
|
||||
|
||||
def transform_parameter(self, result, message) -> str:
|
||||
"""
|
||||
|
||||
@@ -78,13 +78,13 @@ class Filter:
|
||||
word_list.append(word)
|
||||
else:
|
||||
if word.startswith('"'):
|
||||
tmp += word[1:]
|
||||
tmp += word[1:] + " "
|
||||
elif word.endswith('"'):
|
||||
tmp += word[:-1]
|
||||
word_list.append(tmp)
|
||||
tmp = ""
|
||||
else:
|
||||
tmp += word
|
||||
tmp += word + " "
|
||||
added = await self.add_to_filter(server, word_list)
|
||||
if added:
|
||||
await ctx.send(_("Words added to filter."))
|
||||
@@ -108,13 +108,13 @@ class Filter:
|
||||
word_list.append(word)
|
||||
else:
|
||||
if word.startswith('"'):
|
||||
tmp += word[1:]
|
||||
tmp += word[1:] + " "
|
||||
elif word.endswith('"'):
|
||||
tmp += word[:-1]
|
||||
word_list.append(tmp)
|
||||
tmp = ""
|
||||
else:
|
||||
tmp += word
|
||||
tmp += word + " "
|
||||
removed = await self.remove_from_filter(server, word_list)
|
||||
if removed:
|
||||
await ctx.send(_("Words removed from filter."))
|
||||
|
||||
@@ -200,11 +200,7 @@ class General:
|
||||
created_at = _("Since {}. That's over {} days ago!").format(
|
||||
guild.created_at.strftime("%d %b %Y %H:%M"), passed
|
||||
)
|
||||
|
||||
colour = "".join([choice("0123456789ABCDEF") for x in range(6)])
|
||||
colour = randint(0, 0xFFFFFF)
|
||||
|
||||
data = discord.Embed(description=created_at, colour=discord.Colour(value=colour))
|
||||
data = discord.Embed(description=created_at, colour=(await ctx.embed_colour()))
|
||||
data.add_field(name=_("Region"), value=str(guild.region))
|
||||
data.add_field(name=_("Users"), value="{}/{}".format(online, total_users))
|
||||
data.add_field(name=_("Text Channels"), value=text_channels)
|
||||
|
||||
@@ -12,7 +12,7 @@ from .checks import mod_or_voice_permissions, admin_or_voice_permissions, bot_ha
|
||||
from redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, get_audit_reason
|
||||
from .log import log
|
||||
|
||||
from redbot.core.utils.common_filters import filter_invites
|
||||
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
||||
|
||||
_ = Translator("Mod", __file__)
|
||||
|
||||
@@ -1323,9 +1323,11 @@ class Mod:
|
||||
if roles is not None:
|
||||
data.add_field(name=_("Roles"), value=roles, inline=False)
|
||||
if names:
|
||||
# May need sanitizing later, but mentions do not ping in embeds currently
|
||||
val = filter_invites(", ".join(names))
|
||||
data.add_field(name=_("Previous Names"), value=val, inline=False)
|
||||
if nicks:
|
||||
# May need sanitizing later, but mentions do not ping in embeds currently
|
||||
val = filter_invites(", ".join(nicks))
|
||||
data.add_field(name=_("Previous Nicknames"), value=val, inline=False)
|
||||
if voice_state and voice_state.channel:
|
||||
@@ -1369,6 +1371,7 @@ class Mod:
|
||||
msg += "\n"
|
||||
msg += ", ".join(nicks)
|
||||
if msg:
|
||||
msg = filter_various_mentions(msg)
|
||||
await ctx.send(msg)
|
||||
else:
|
||||
await ctx.send(_("That user doesn't have any recorded name or nickname change."))
|
||||
|
||||
@@ -508,6 +508,8 @@ class Streams:
|
||||
try:
|
||||
embed = await stream.is_online()
|
||||
except OfflineStream:
|
||||
if not stream._messages_cache:
|
||||
continue
|
||||
for message in stream._messages_cache:
|
||||
try:
|
||||
autodelete = await self.db.guild(message.guild).autodelete()
|
||||
@@ -558,6 +560,8 @@ class Streams:
|
||||
print(_("The Community {} was not found!").format(community.name))
|
||||
continue
|
||||
except OfflineCommunity:
|
||||
if not community._messages_cache:
|
||||
continue
|
||||
for message in community._messages_cache:
|
||||
try:
|
||||
autodelete = await self.db.guild(message.guild).autodelete()
|
||||
|
||||
@@ -221,8 +221,8 @@ class Warnings:
|
||||
if user == ctx.author:
|
||||
await ctx.send(_("You cannot warn yourself."))
|
||||
return
|
||||
custom_allowed = await self.config.guild(ctx.guild).allow_custom_reasons()
|
||||
if reason.lower() == "custom":
|
||||
custom_allowed = await self.config.guild(ctx.guild).allow_custom_reasons()
|
||||
if not custom_allowed:
|
||||
await ctx.send(
|
||||
_(
|
||||
@@ -236,7 +236,21 @@ class Warnings:
|
||||
guild_settings = self.config.guild(ctx.guild)
|
||||
async with guild_settings.reasons() as registered_reasons:
|
||||
if reason.lower() not in registered_reasons:
|
||||
await ctx.send(_("That is not a registered reason!"))
|
||||
msg = _("That is not a registered reason!")
|
||||
if custom_allowed:
|
||||
msg += " " + _(
|
||||
"Do `{prefix}warn {user} custom` to specify a custom reason."
|
||||
).format(prefix=ctx.prefix, user=ctx.author)
|
||||
elif (
|
||||
ctx.guild.owner == ctx.author
|
||||
or ctx.channel.permissions_for(ctx.author).administrator
|
||||
or await ctx.bot.is_owner(ctx.author)
|
||||
):
|
||||
msg += " " + _(
|
||||
"Do `{prefix}warningset allowcustomreasons true` to enable custom "
|
||||
"reasons."
|
||||
).format(prefix=ctx.prefix)
|
||||
await ctx.send(msg)
|
||||
return
|
||||
else:
|
||||
reason_type = registered_reasons[reason.lower()]
|
||||
|
||||
@@ -36,5 +36,5 @@ class VersionInfo:
|
||||
return [self.major, self.minor, self.micro, self.releaselevel, self.serial]
|
||||
|
||||
|
||||
__version__ = "3.0.0b20"
|
||||
version_info = VersionInfo(3, 0, 0, "beta", 20)
|
||||
__version__ = "3.0.0b21"
|
||||
version_info = VersionInfo(3, 0, 0, "beta", 21)
|
||||
|
||||
@@ -58,6 +58,8 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
help__page_char_limit=1000,
|
||||
help__max_pages_in_guild=2,
|
||||
help__tagline="",
|
||||
disabled_commands=[],
|
||||
disabled_command_msg="That command is disabled.",
|
||||
)
|
||||
|
||||
self.db.register_guild(
|
||||
@@ -69,6 +71,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
embeds=None,
|
||||
use_bot_color=False,
|
||||
fuzzy=False,
|
||||
disabled_commands=[],
|
||||
)
|
||||
|
||||
self.db.register_user(embeds=None)
|
||||
@@ -340,6 +343,13 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
)
|
||||
super().add_cog(cog)
|
||||
|
||||
def add_command(self, command: commands.Command):
|
||||
if not isinstance(command, commands.Command):
|
||||
raise TypeError("Command objects must derive from redbot.core.commands.Command")
|
||||
|
||||
super().add_command(command)
|
||||
self.dispatch("command_add", command)
|
||||
|
||||
|
||||
class Red(RedBase, discord.AutoShardedClient):
|
||||
"""
|
||||
|
||||
@@ -4,8 +4,10 @@ This module contains extended classes and functions which are intended to
|
||||
replace those from the `discord.ext.commands` module.
|
||||
"""
|
||||
import inspect
|
||||
from typing import TYPE_CHECKING
|
||||
import weakref
|
||||
from typing import Awaitable, Callable, TYPE_CHECKING
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from .errors import ConversionFailure
|
||||
@@ -104,6 +106,49 @@ class Command(commands.Command):
|
||||
# We should expose anything which might be a bug in the converter
|
||||
raise exc
|
||||
|
||||
def disable_in(self, guild: discord.Guild) -> bool:
|
||||
"""Disable this command in the given guild.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
guild : discord.Guild
|
||||
The guild to disable the command in.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` if the command wasn't already disabled.
|
||||
|
||||
"""
|
||||
disabler = get_command_disabler(guild)
|
||||
if disabler in self.checks:
|
||||
return False
|
||||
else:
|
||||
self.checks.append(disabler)
|
||||
return True
|
||||
|
||||
def enable_in(self, guild: discord.Guild) -> bool:
|
||||
"""Enable this command in the given guild.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
guild : discord.Guild
|
||||
The guild to enable the command in.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` if the command wasn't already enabled.
|
||||
|
||||
"""
|
||||
disabler = get_command_disabler(guild)
|
||||
try:
|
||||
self.checks.remove(disabler)
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class GroupMixin(commands.GroupMixin):
|
||||
"""Mixin for `Group` and `Red` classes.
|
||||
@@ -162,6 +207,12 @@ class Group(GroupMixin, Command, commands.Group):
|
||||
if self.autohelp and not self.invoke_without_command:
|
||||
await self._verify_checks(ctx)
|
||||
await ctx.send_help()
|
||||
elif self.invoke_without_command:
|
||||
# So invoke_without_command when a subcommand of this group is invoked
|
||||
# will skip the the invokation of *this* command. However, because of
|
||||
# how our permissions system works, we don't want it to skip the checks
|
||||
# as well.
|
||||
await self._verify_checks(ctx)
|
||||
|
||||
await super().invoke(ctx)
|
||||
|
||||
@@ -184,3 +235,25 @@ def group(name=None, **attrs):
|
||||
Same interface as `discord.ext.commands.group`.
|
||||
"""
|
||||
return command(name, cls=Group, **attrs)
|
||||
|
||||
|
||||
__command_disablers = weakref.WeakValueDictionary()
|
||||
|
||||
|
||||
def get_command_disabler(guild: discord.Guild) -> Callable[["Context"], Awaitable[bool]]:
|
||||
"""Get the command disabler for a guild.
|
||||
|
||||
A command disabler is a simple check predicate which returns
|
||||
``False`` if the context is within the given guild.
|
||||
"""
|
||||
try:
|
||||
return __command_disablers[guild]
|
||||
except KeyError:
|
||||
|
||||
async def disabler(ctx: "Context") -> bool:
|
||||
if ctx.guild == guild:
|
||||
raise commands.DisabledCommand()
|
||||
return True
|
||||
|
||||
__command_disablers[guild] = disabler
|
||||
return disabler
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
import datetime
|
||||
import importlib
|
||||
import itertools
|
||||
@@ -1066,6 +1067,9 @@ class Core(CoreLogic):
|
||||
red_dist = pkg_resources.get_distribution("red-discordbot")
|
||||
red_path = Path(red_dist.location) / "redbot"
|
||||
locale_list = sorted(set([loc.stem for loc in list(red_path.glob("**/*.po"))]))
|
||||
if not locale_list:
|
||||
await ctx.send("No languages found.")
|
||||
return
|
||||
pages = pagify("\n".join(locale_list))
|
||||
|
||||
await ctx.send_interactive(pages, box_lang="Available Locales:")
|
||||
@@ -1558,6 +1562,138 @@ class Core(CoreLogic):
|
||||
await ctx.bot.db.guild(ctx.guild).blacklist.set([])
|
||||
await ctx.send(_("blacklist has been cleared."))
|
||||
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
@commands.group(name="command")
|
||||
async def command_manager(self, ctx: commands.Context):
|
||||
"""Manage the bot's commands."""
|
||||
pass
|
||||
|
||||
@command_manager.group(name="disable", invoke_without_command=True)
|
||||
async def command_disable(self, ctx: commands.Context, *, command: str):
|
||||
"""Disable a command.
|
||||
|
||||
If you're the bot owner, this will disable commands
|
||||
globally by default.
|
||||
"""
|
||||
# Select the scope based on the author's privileges
|
||||
if await ctx.bot.is_owner(ctx.author):
|
||||
await ctx.invoke(self.command_disable_global, command=command)
|
||||
else:
|
||||
await ctx.invoke(self.command_disable_guild, command=command)
|
||||
|
||||
@checks.is_owner()
|
||||
@command_disable.command(name="global")
|
||||
async def command_disable_global(self, ctx: commands.Context, *, command: str):
|
||||
"""Disable a command globally."""
|
||||
command_obj: commands.Command = ctx.bot.get_command(command)
|
||||
if command_obj is None:
|
||||
await ctx.send(
|
||||
_("I couldn't find that command. Please note that it is case sensitive.")
|
||||
)
|
||||
return
|
||||
|
||||
async with ctx.bot.db.disabled_commands() as disabled_commands:
|
||||
if command not in disabled_commands:
|
||||
disabled_commands.append(command_obj.qualified_name)
|
||||
|
||||
if not command_obj.enabled:
|
||||
await ctx.send(_("That command is already disabled globally."))
|
||||
return
|
||||
command_obj.enabled = False
|
||||
|
||||
await ctx.tick()
|
||||
|
||||
@commands.guild_only()
|
||||
@command_disable.command(name="server", aliases=["guild"])
|
||||
async def command_disable_guild(self, ctx: commands.Context, *, command: str):
|
||||
"""Disable a command in this server only."""
|
||||
command_obj: commands.Command = ctx.bot.get_command(command)
|
||||
if command_obj is None:
|
||||
await ctx.send(
|
||||
_("I couldn't find that command. Please note that it is case sensitive.")
|
||||
)
|
||||
return
|
||||
|
||||
async with ctx.bot.db.guild(ctx.guild).disabled_commands() as disabled_commands:
|
||||
if command not in disabled_commands:
|
||||
disabled_commands.append(command_obj.qualified_name)
|
||||
|
||||
done = command_obj.disable_in(ctx.guild)
|
||||
|
||||
if not done:
|
||||
await ctx.send(_("That command is already disabled in this server."))
|
||||
else:
|
||||
await ctx.tick()
|
||||
|
||||
@command_manager.group(name="enable", invoke_without_command=True)
|
||||
async def command_enable(self, ctx: commands.Context, *, command: str):
|
||||
"""Enable a command.
|
||||
|
||||
If you're a bot owner, this will try to enable a globally
|
||||
disabled command by default.
|
||||
"""
|
||||
if await ctx.bot.is_owner(ctx.author):
|
||||
await ctx.invoke(self.command_enable_global, command=command)
|
||||
else:
|
||||
await ctx.invoke(self.command_enable_guild, command=command)
|
||||
|
||||
@commands.is_owner()
|
||||
@command_enable.command(name="global")
|
||||
async def command_enable_global(self, ctx: commands.Context, *, command: str):
|
||||
"""Enable a command globally."""
|
||||
command_obj: commands.Command = ctx.bot.get_command(command)
|
||||
if command_obj is None:
|
||||
await ctx.send(
|
||||
_("I couldn't find that command. Please note that it is case sensitive.")
|
||||
)
|
||||
return
|
||||
|
||||
async with ctx.bot.db.disabled_commands() as disabled_commands:
|
||||
with contextlib.suppress(ValueError):
|
||||
disabled_commands.remove(command_obj.qualified_name)
|
||||
|
||||
if command_obj.enabled:
|
||||
await ctx.send(_("That command is already enabled globally."))
|
||||
return
|
||||
|
||||
command_obj.enabled = True
|
||||
await ctx.tick()
|
||||
|
||||
@commands.guild_only()
|
||||
@command_enable.command(name="server", aliases=["guild"])
|
||||
async def command_enable_guild(self, ctx: commands.Context, *, command: str):
|
||||
"""Enable a command in this server."""
|
||||
command_obj: commands.Command = ctx.bot.get_command(command)
|
||||
if command_obj is None:
|
||||
await ctx.send(
|
||||
_("I couldn't find that command. Please note that it is case sensitive.")
|
||||
)
|
||||
return
|
||||
|
||||
async with ctx.bot.db.guild(ctx.guild).disabled_commands() as disabled_commands:
|
||||
with contextlib.suppress(ValueError):
|
||||
disabled_commands.remove(command_obj.qualified_name)
|
||||
|
||||
done = command_obj.enable_in(ctx.guild)
|
||||
|
||||
if not done:
|
||||
await ctx.send(_("That command is already enabled in this server."))
|
||||
else:
|
||||
await ctx.tick()
|
||||
|
||||
@checks.is_owner()
|
||||
@command_manager.command(name="disabledmsg")
|
||||
async def command_disabledmsg(self, ctx: commands.Context, *, message: str = ""):
|
||||
"""Set the bot's response to disabled commands.
|
||||
|
||||
Leave blank to send nothing.
|
||||
|
||||
To include the command name in the message, include the
|
||||
`{command}` placeholder.
|
||||
"""
|
||||
await ctx.bot.db.disabled_command_msg.set(message)
|
||||
await ctx.tick()
|
||||
|
||||
# RPC handlers
|
||||
async def rpc_load(self, request):
|
||||
cog_name = request.params[0]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import motor.motor_asyncio
|
||||
from .red_base import BaseDriver
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
__all__ = ["Mongo"]
|
||||
|
||||
@@ -15,7 +16,9 @@ def _initialize(**kwargs):
|
||||
db_name = kwargs.get("DB_NAME", "default_db")
|
||||
|
||||
if admin_user is not None and admin_pass is not None:
|
||||
url = "mongodb://{}:{}@{}:{}/{}".format(admin_user, admin_pass, host, port, db_name)
|
||||
url = "mongodb://{}:{}@{}:{}/{}".format(
|
||||
quote_plus(admin_user), quote_plus(admin_pass), host, port, db_name
|
||||
)
|
||||
else:
|
||||
url = "mongodb://{}:{}/{}".format(host, port, db_name)
|
||||
|
||||
|
||||
+46
-2
@@ -170,6 +170,12 @@ def init_events(bot, cli_flags):
|
||||
print("\nInvite URL: {}\n".format(invite_url))
|
||||
|
||||
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
|
||||
async def on_error(event_method, *args, **kwargs):
|
||||
@@ -187,7 +193,9 @@ def init_events(bot, cli_flags):
|
||||
elif isinstance(error, commands.BadArgument):
|
||||
await ctx.send_help()
|
||||
elif isinstance(error, commands.DisabledCommand):
|
||||
await ctx.send("That command is disabled.")
|
||||
disabled_message = await bot.db.disabled_command_msg()
|
||||
if disabled_message:
|
||||
await ctx.send(disabled_message.replace("{command}", ctx.invoked_with))
|
||||
elif isinstance(error, commands.CommandInvokeError):
|
||||
# Need to test if the following still works
|
||||
"""
|
||||
@@ -235,7 +243,7 @@ def init_events(bot, cli_flags):
|
||||
await ctx.send("That command is not available in DMs.")
|
||||
elif isinstance(error, commands.CommandOnCooldown):
|
||||
await ctx.send(
|
||||
"This command is on cooldown. " "Try again in {:.2f}s" "".format(error.retry_after)
|
||||
"This command is on cooldown. Try again in {:.2f}s".format(error.retry_after)
|
||||
)
|
||||
else:
|
||||
log.exception(type(error).__name__, exc_info=error)
|
||||
@@ -274,6 +282,42 @@ def init_events(bot, cli_flags):
|
||||
async def on_command(command):
|
||||
bot.counter["processed_commands"] += 1
|
||||
|
||||
@bot.event
|
||||
async def on_command_add(command: commands.Command):
|
||||
disabled_commands = await bot.db.disabled_commands()
|
||||
if command.qualified_name in disabled_commands:
|
||||
command.enabled = False
|
||||
for guild in bot.guilds:
|
||||
disabled_commands = await bot.db.guild(guild).disabled_commands()
|
||||
if command.qualified_name in disabled_commands:
|
||||
command.disable_in(guild)
|
||||
|
||||
async def _guild_added(guild: discord.Guild):
|
||||
disabled_commands = await bot.db.guild(guild).disabled_commands()
|
||||
for command_name in disabled_commands:
|
||||
command_obj = bot.get_command(command_name)
|
||||
if command_obj is not None:
|
||||
command_obj.disable_in(guild)
|
||||
|
||||
@bot.event
|
||||
async def on_guild_join(guild: discord.Guild):
|
||||
await _guild_added(guild)
|
||||
|
||||
@bot.event
|
||||
async def on_guild_available(guild: discord.Guild):
|
||||
# We need to check guild-disabled commands here since some cogs
|
||||
# are loaded prior to `on_ready`.
|
||||
await _guild_added(guild)
|
||||
|
||||
@bot.event
|
||||
async def on_guild_leave(guild: discord.Guild):
|
||||
# Clean up any unneeded checks
|
||||
disabled_commands = await bot.db.guild(guild).disabled_commands()
|
||||
for command_name in disabled_commands:
|
||||
command_obj = bot.get_command(command_name)
|
||||
if command_obj is not None:
|
||||
command_obj.enable_in(guild)
|
||||
|
||||
|
||||
def _get_startup_screen_specs():
|
||||
"""Get specs for displaying the startup screen on stdout.
|
||||
|
||||
@@ -7,6 +7,7 @@ __all__ = [
|
||||
"filter_urls",
|
||||
"filter_invites",
|
||||
"filter_mass_mentions",
|
||||
"filter_various_mentions",
|
||||
]
|
||||
|
||||
# regexes
|
||||
@@ -16,6 +17,7 @@ INVITE_URL_RE = re.compile(r"(discord.gg|discordapp.com/invite|discord.me)(\S+)"
|
||||
|
||||
MASS_MENTION_RE = re.compile(r"(@)(?=everyone|here)") # This only matches the @ for sanitizing
|
||||
|
||||
OTHER_MENTION_RE = re.compile(r"(<)(@[!&]?|#)(\d+>)")
|
||||
|
||||
# convenience wrappers
|
||||
def filter_urls(to_filter: str) -> str:
|
||||
@@ -79,3 +81,23 @@ def filter_mass_mentions(to_filter: str) -> str:
|
||||
|
||||
"""
|
||||
return MASS_MENTION_RE.sub("@\u200b", to_filter)
|
||||
|
||||
|
||||
def filter_various_mentions(to_filter: str) -> str:
|
||||
"""
|
||||
Get a string with role, user, and channel mentions sanitized.
|
||||
|
||||
This is mainly for use on user display names, not message content,
|
||||
and should be applied sparingly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_filter : str
|
||||
The string to filter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The sanitized string.
|
||||
"""
|
||||
return OTHER_MENTION_RE.sub(r"\1\\\2\3", to_filter)
|
||||
|
||||
+4
-3
@@ -25,7 +25,7 @@ from redbot.core.cli import confirm
|
||||
if sys.platform == "linux":
|
||||
import distro
|
||||
|
||||
PYTHON_OK = sys.version_info >= (3, 6)
|
||||
PYTHON_OK = sys.version_info >= (3, 6, 2)
|
||||
INTERACTIVE_MODE = not len(sys.argv) > 1 # CLI flags = non-interactive
|
||||
|
||||
INTRO = "==========================\nRed Discord Bot - Launcher\n==========================\n"
|
||||
@@ -140,9 +140,10 @@ def update_red(dev=False, voice=False, mongo=False, docs=False, test=False):
|
||||
|
||||
|
||||
def run_red(selected_instance, autorestart: bool = False, cliflags=None):
|
||||
interpreter = sys.executable
|
||||
while True:
|
||||
print("Starting {}...".format(selected_instance))
|
||||
cmd_list = ["redbot", selected_instance]
|
||||
cmd_list = [interpreter, "-m", "redbot", selected_instance]
|
||||
if cliflags:
|
||||
cmd_list += cliflags
|
||||
status = subprocess.call(cmd_list)
|
||||
@@ -461,7 +462,7 @@ def main_menu():
|
||||
def main():
|
||||
if not PYTHON_OK:
|
||||
raise RuntimeError(
|
||||
"Red requires Python 3.6 or greater. Please install the correct version!"
|
||||
"Red requires Python 3.6.2 or greater. Please install the correct version!"
|
||||
)
|
||||
if args.debuginfo: # Check first since the function triggers an exit
|
||||
debug_info()
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[metadata]
|
||||
long_description = file: README.rst
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown; charset=UTF-8; variant=GFM
|
||||
|
||||
@@ -6,19 +6,19 @@ from distutils.errors import CCompilerError, DistutilsPlatformError
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
requirements = [
|
||||
"aiohttp-json-rpc==0.11",
|
||||
"aiohttp-json-rpc==0.11.1",
|
||||
"aiohttp==3.3.2",
|
||||
"appdirs==1.4.3",
|
||||
"async-timeout==3.0.0",
|
||||
"attrs==18.1.0",
|
||||
"attrs==18.2.0",
|
||||
"chardet==3.0.4",
|
||||
"colorama==0.3.9",
|
||||
"discord.py>=1.0.0a0",
|
||||
"distro==1.3.0; sys_platform == 'linux'",
|
||||
"fuzzywuzzy==0.16.0",
|
||||
"fuzzywuzzy==0.17.0",
|
||||
"idna-ssl==1.1.0",
|
||||
"idna==2.7",
|
||||
"multidict==4.3.1",
|
||||
"multidict==4.4.0",
|
||||
"python-levenshtein==0.12.0",
|
||||
"pyyaml==3.13",
|
||||
"raven==6.9.0",
|
||||
@@ -94,16 +94,16 @@ if __name__ == "__main__":
|
||||
],
|
||||
"pytest11": ["red-discordbot = redbot.pytest"],
|
||||
},
|
||||
python_requires=">=3.6,<3.8",
|
||||
python_requires=">=3.6.2,<3.8",
|
||||
install_requires=requirements,
|
||||
dependency_links=get_dependency_links(),
|
||||
extras_require={
|
||||
"test": [
|
||||
"atomicwrites==1.1.5",
|
||||
"atomicwrites==1.2.1",
|
||||
"more-itertools==4.3.0",
|
||||
"pluggy==0.7.1",
|
||||
"py==1.5.4",
|
||||
"pytest==3.7.0",
|
||||
"py==1.6.0",
|
||||
"pytest==3.7.4",
|
||||
"pytest-asyncio==0.9.0",
|
||||
"six==1.11.0",
|
||||
],
|
||||
@@ -111,9 +111,9 @@ if __name__ == "__main__":
|
||||
"docs": [
|
||||
"alabaster==0.7.11",
|
||||
"babel==2.6.0",
|
||||
"certifi==2018.4.16",
|
||||
"certifi==2018.8.24",
|
||||
"docutils==0.14",
|
||||
"imagesize==1.0.0",
|
||||
"imagesize==1.1.0",
|
||||
"Jinja2==2.10",
|
||||
"MarkupSafe==1.0",
|
||||
"packaging==17.1",
|
||||
@@ -125,7 +125,7 @@ if __name__ == "__main__":
|
||||
"urllib3==1.23",
|
||||
"six==1.11.0",
|
||||
"snowballstemmer==1.2.1",
|
||||
"sphinx==1.7.6",
|
||||
"sphinx==1.7.8",
|
||||
"sphinx_rtd_theme==0.4.1",
|
||||
"sphinxcontrib-asyncio==0.2.0",
|
||||
"sphinxcontrib-websupport==1.1.0",
|
||||
|
||||
Reference in New Issue
Block a user