Compare commits

..

23 Commits

Author SHA1 Message Date
Toby Harradine 8096cd803e Bump version to 3.0.0b21 (#2115)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2018-09-10 00:44:11 +10:00
Toby Harradine 27b0d606c8 Fix CCs with no args (#2114)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2018-09-10 00:35:53 +10:00
aikaterna af220e497f [Docs] Update sample cog (#2072)
* [V3 Docs] Update sample cog

* Remove self.bot assignment

We don't really do this any more unless we need to
2018-09-09 23:35:22 +10:00
Toby Harradine 892b2487f5 Dependency update (#2100)
aiohttp-json-rpc 0.11 -> 0.11.1
atomicwrites 1.1.5 -> 1.2.1
attrs 18.1.0 -> 18.2.0
certifi 2018.4.16 -> 2018.8.24
discord.py 8ccb98d395537b1c9acc187e1647dfdd07bb831b -> 00a659c6526b2445162b52eaf970adbd22c6d35d
fuzzywuzzy 0.16.0 -> 0.17.0
imagesize 1.0.0 -> 1.1.0
multidict 4.3.1 -> 4.4.0
py 1.5.4 -> 1.6.0
pytest 3.7.0 -> 3.7.4
sphinx 1.7.6 -> 1.7.8

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2018-09-09 23:21:48 +10:00
Kowlin 7971c02dc5 Escape user and passwords for mongo (#2111)
* Escape user and passwords for mongo.

* Quote -> Quote_Plus

* formatting
2018-09-09 23:07:40 +10:00
zephyrkul c1d8272b49 [CustomCommands] Show parameter name correctly in help (#2102) 2018-09-09 23:05:33 +10:00
aikaterna bce5dd26f3 [Audio] Local playback (#2097)
* [V3 Audio] Local playback

* Formatting

* Update application.yml

* Add catch for an empty localtracks directory

* Linebreak in help for i18n
2018-09-09 23:02:53 +10:00
zephyrkul 04e97f3516 [CustomCom] Custom Command Parameters (#2051)
* [V3 CustomCom] Custom Command Parameters

Allows specifying more parameters for CC's via {0}, {1}, etc. that will be filled by the user invoking the CC. Python-style type hinting and attribute access is also allowed for Discord and builtin types.

> [p]cc add simple greet Hi, {0.mention:Member}!
> ...
> [p]greet zephyrkul
> Hi, @zephyrkul!

The bot will reply with the standard help messages if the cc is incorrectly executed.

> [p]greet me
> Member "me" not found

* black formatting

* check command failure

Only call the custom command if the faked command succeeded.

* misc fixes

1) don't str.strip all the time, it's not family-friendly and doesn't match transform_parameter
2) transform_arg now actually returns strings in every case
3) improve prepare_args parsing security
4) help parameters will show what type they expect
5) make linter less angery

* customcom documentation

I hate rst

* don't require repeated type hinting

If a parameter was type hinted previously, don't require it again.
Ex: `{0.display_name:Member}#{0.discriminator}` is now possible.

* add cog_customcom.rts to index

I despise rst

* don't enforce order

Allow type hinting and attribute access to be in either order.
Ex. `{0:Member.mention}` is now valid.

* clean up on_message

We're building context anyway, may as well use it.

* [doc] correct cog name

Cog class is named CustomCommands, not CustomCom

* minor on_message optimization

only build context if it's needed

* update cc_add docstring

Old one wasn't user-friendly. Replaced with a link to the new docs.
Link will not function until PR is merged and docs refreshed.

* [doc] change repeat to say

repeat is an audio command, use say in the example instead

* compare ctx.prefix to None

allows for null prefixes, which is a bad idea but who am I to judge

* address review

* raise error on conflicting colon notation

bugfix I was working on but failed to actually commit
2018-09-07 00:14:02 +10:00
Toby Harradine 7eed033c9e Prettify README and translate to MarkDown (#2101)
Signed-off-by: Redjumpman <redjumpman@users.noreply.github.com>
2018-09-06 21:53:18 +10:00
Richard Guan a2fe1a8757 [Audio] Don't allow duplicate links in playlists (#2071)
* Just a test

* another test

* undoing my test

* First solution, style issue

* Works functionally, but there are is a minor style issue

* style fixed

* Revert README changes
2018-09-06 16:25:27 +10:00
Toby Harradine 9ee860c3f0 [Core] Command disable feature (#2099)
* [Core] Command disable feature

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* [Core] Allow user to set the "command disabled" message

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Reformat

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2018-09-06 12:52:19 +10:00
Toby Harradine 1dbe9537e9 Ignore fuzzywuzzy slow sequence matcher warning (#2096)
* Ignore fuzzywuzzy slow sequence matcher warning

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add log.info call instead

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2018-09-06 12:50:30 +10:00
Toby Harradine 775f4ce69a Use ProactorEventLoop on Windows (#2062)
* Use ProactorEventLoop on Windows

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Set the actual loop instead of the policy

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2018-09-06 00:40:15 +10:00
zephyrkul e83beeef34 [Audio] Add [p]sing (#2067)
* port [p]sing

* [p]sing in alphabetical order

what even is an alphabet
2018-09-04 13:23:50 +10:00
Brramble e77cfff892 [p]serverinfo respects set embed colour (#2083) 2018-09-04 13:01:50 +10:00
Michael H 9495432b8f More mention filtering (#2081)
* more filters

* note to future people touching this

* chan mentions were almost missed...

* Swap strategies as the previous escaped the mention, while still pinging the user/role
2018-09-04 12:59:59 +10:00
palmtree5 d71c334a34 [Filter] Fix issue with filtering sentences (#2065) 2018-09-04 12:55:18 +10:00
Michael H aa8cc90ad0 Update minimum Python version to 3.6.2 (#2093)
* Correct minimum version

see #2092 

While this is needed because of an import just for typing, I see no reason not to bump the minimum version since this is a minor version difference since this is several minor version behind the latest 3.6, and there have been both security and performance improvements since.

That said, we need to be testing on our lowest supported version to ensure we don't have this happen again, right now our tests run on whatever Travis grabs for 3.6, which I assume is 3.6.6, but could be wrong.

* Update other mentions of min version to 3.6.2
2018-09-04 11:38:56 +10:00
palmtree5 589041556e [Streams] Fix excessive writes to config (#2095)
Resolves #2052.
2018-09-04 11:01:48 +10:00
El Laggron 85354f2722 Support missing .po files (#2068)
* [V3 Core] Support missing .po files

* Modified if-state

* Use PEP8 recommendation for empty list check

https://www.python.org/dev/peps/pep-0008/#programming-recommendations
2018-09-03 15:44:58 +10:00
Toby Harradine c0c5535005 [Warnings] Help users understand custom reasons (#2049)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2018-09-03 14:57:14 +10:00
zephyrkul e126cf9f4e [Launcher] use "python -m" to run Red (#2080)
friendlier for virtual environments
2018-09-03 10:44:50 +10:00
aikaterna c2d23b37a7 [Audio] Fix for using [p]now with thumbnail (#2063) 2018-08-28 13:02:40 +10:00
32 changed files with 1532 additions and 459 deletions
+2 -2
View File
@@ -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
View File
@@ -1,3 +1,3 @@
include README.rst
include README.md
include LICENSE
include dependency_links.txt
Generated
+100 -68
View File
@@ -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": [
+135
View File
@@ -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 its supported by an active community which produces new
content (cogs/plugins) for everyone to enjoy. New features are constantly added. If you cant
[find](https://github.com/Cog-Creators/Red-DiscordBot/issues/1398) the cog youre 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
View File
@@ -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 its supported by an active community which produces new content (cogs/plugins) for everyone to enjoy. New features are constantly added. If you cant `find <https://github.com/Cog-Creators/Red-DiscordBot/issues/1398>`_ what youre 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
View File
@@ -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
+101
View File
@@ -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
+2 -2
View File
@@ -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"""
+1
View File
@@ -20,6 +20,7 @@ Welcome to Red - Discord Bot's documentation!
:maxdepth: 2
:caption: Cog Reference:
cog_customcom
cog_downloader
cog_permissions
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -14,7 +14,7 @@ lavalink:
vimeo: true
mixer: true
http: true
local: false
local: true
sentryDsn: ""
bufferDurationMs: 400
youtubePlaylistLoadLimit: 10000
youtubePlaylistLoadLimit: 10000
+160 -51
View File
@@ -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:
"""
+4 -4
View File
@@ -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."))
+1 -5
View File
@@ -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)
+4 -1
View File
@@ -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."))
+4
View File
@@ -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()
+16 -2
View File
@@ -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()]
+2 -2
View File
@@ -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)
+10
View File
@@ -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):
"""
+74 -1
View File
@@ -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
+136
View File
@@ -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]
+4 -1
View File
@@ -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
View File
@@ -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.
+22
View File
@@ -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
View File
@@ -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()
+2 -1
View File
@@ -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
+11 -11
View File
@@ -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",