mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2026-05-13 19:48:23 -04:00
Compare commits
176 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 | |||
| 3a968d707f | |||
| b07c44c8b4 | |||
| 43b0a58649 | |||
| f258e93cf7 | |||
| 93138b04cb | |||
| 0cf54ec9c2 | |||
| ce031cf7bd | |||
| e6495bc7c0 | |||
| 1b196bf0fb | |||
| dbed24aaca | |||
| 48a7a21aca | |||
| f595afab18 | |||
| 0aca00b245 | |||
| 9af58d3abf | |||
| dd5ef3696f | |||
| 03d49bac53 | |||
| 6c082a10b1 | |||
| 77944e195a | |||
| 6ebfdef025 | |||
| bc39a6741c | |||
| bda7e08208 | |||
| aa69dd381f | |||
| 1fd8a8e0a6 | |||
| 1329fa1b09 | |||
| b550f38eed | |||
| ae7b912ac8 | |||
| af9478922e | |||
| 7acea29cdb | |||
| 6082eb21e3 | |||
| a91cda4995 | |||
| 7959654dc8 | |||
| dc9a85ca98 | |||
| 591ed50ac3 | |||
| 47350328e6 | |||
| 75ed749cb3 | |||
| f44ea8b749 | |||
| 76c0071f57 | |||
| 2a396b4438 | |||
| 51a54863c5 | |||
| 06f986b92e | |||
| 652ceba845 | |||
| 16d0f54d8f | |||
| 872cce784a | |||
| aec3ad382a | |||
| 9d4f9ef73c | |||
| cf7cafc261 | |||
| e3bff7e87c | |||
| 4b19421075 | |||
| cf371e8093 | |||
| 5eeadc6399 | |||
| f6823ea3d1 | |||
| f24290c423 | |||
| f8a36885fe | |||
| a555eff2cc | |||
| 05c389623c | |||
| bf00f5e9a2 | |||
| 7685c4d5d5 | |||
| e701ec9617 | |||
| 6c1ee096a1 | |||
| 2df282222f | |||
| 43c7bd48c7 | |||
| 86579068d9 | |||
| 8e6ab9aa35 | |||
| 77566a887a | |||
| 9d0eca1914 | |||
| 79a3164d9d | |||
| eb73e48192 | |||
| cd6af7f185 | |||
| 3d6020b9cf | |||
| 461f03aac0 | |||
| 35149f8837 | |||
| c0d01f32a6 | |||
| 83a0459b6a | |||
| 50f6dcef2f | |||
| 5c514fd663 | |||
| 1c2196f78f | |||
| 43cc3c40f3 | |||
| 7a6a4cf59d | |||
| 3bcf375204 | |||
| a175bdc1c7 | |||
| b557b437a3 | |||
| d1f0b59b5d | |||
| 3ece3a1f2b | |||
| 1f1a85de18 | |||
| e08c9dafa6 | |||
| ad27607ccc | |||
| c1bcca4432 | |||
| 9f2ed694ce | |||
| edadd8f2fd | |||
| afa08713e0 | |||
| d23620727e | |||
| b456c6ad3b | |||
| 0298b53803 | |||
| bfd6e4af3f | |||
| 31612aae4a | |||
| 219367e7c1 | |||
| 7b64f10fc7 | |||
| 1ad1744054 | |||
| 7b825f2cd7 | |||
| 3759fce090 | |||
| 470521f7c8 | |||
| a070dffb93 | |||
| 9e7bc94aab | |||
| 033d0113a5 | |||
| d0a53ed2df | |||
| 49b80e9fe3 | |||
| d5f5ddbec5 | |||
| 17c7dd658d | |||
| ca19ecaefc | |||
| c149f00f82 | |||
| b041d59fc7 | |||
| b983d5904b | |||
| 8b15053dd4 | |||
| e15815cd97 | |||
| 94a64d8fae | |||
| fd7088de1a | |||
| 7d4946560d | |||
| b7c9647e1a | |||
| 36b9f64aae | |||
| 60a72b2ba4 | |||
| f830f73ae6 | |||
| 95f51e1126 | |||
| 8916f55d52 | |||
| 4aaef9558a | |||
| 0b78664792 | |||
| db5d4d5158 | |||
| 0dfd8b6453 | |||
| 11a2fb1088 | |||
| 40feeff442 | |||
| a0a2976e0a | |||
| 741f3cbdcc | |||
| a6965c4b5a | |||
| 19b05e632c | |||
| 8610b47a68 | |||
| 2ab8890540 | |||
| 5de5a519c3 | |||
| 0d193d3e9e | |||
| 622382f425 | |||
| c1f09326cc | |||
| ddbbba4aaa | |||
| bcf7ea30c5 | |||
| 35e9fab701 | |||
| 864b6d313e | |||
| d47d12e961 | |||
| 9f0e752318 | |||
| 34bd5ead15 | |||
| 1fd5dffdc7 | |||
| 6d7a900bbb | |||
| fb4f921159 | |||
| 14cc701b25 | |||
| d8c4113d24 | |||
| 9eb6bb7738 | |||
| de96f8b9f9 |
@@ -24,6 +24,8 @@ redbot/core/utils/mod.py @palmtree5
|
||||
redbot/core/utils/data_converter.py @mikeshardmind
|
||||
redbot/core/utils/antispam.py @mikeshardmind
|
||||
redbot/core/utils/tunnel.py @mikeshardmind
|
||||
redbot/core/utils/caching.py @mikeshardmind
|
||||
redbot/core/utils/common_filters.py @mikeshardmind
|
||||
|
||||
# Cogs
|
||||
redbot/cogs/admin/* @tekulvw
|
||||
@@ -44,6 +46,7 @@ redbot/cogs/trivia/* @Tobotimus
|
||||
redbot/cogs/dataconverter/* @mikeshardmind
|
||||
redbot/cogs/reports/* @mikeshardmind
|
||||
redbot/cogs/permissions/* @mikeshardmind
|
||||
redbot/cogs/warnings/* @palmtree5
|
||||
|
||||
# Docs
|
||||
docs/* @tekulvw @palmtree5
|
||||
|
||||
@@ -31,7 +31,7 @@ We love receiving contributions from our community. Any assistance you can provi
|
||||
# 2. Ground Rules
|
||||
We've made a point to use [ZenHub](https://www.zenhub.com/) (a plugin for GitHub) as our main source of collaboration and coordination. Your experience contributing to Red will be greatly improved if you go get that plugin.
|
||||
1. Ensure cross compatibility for Windows, Mac OS and Linux.
|
||||
2. Ensure all Python features used in contributions exist and work in Python 3.5 and above.
|
||||
2. Ensure all Python features used in contributions exist and work in Python 3.6 and above.
|
||||
3. Create new tests for code you add or bugs you fix. It helps us help you by making sure we don't accidentally break anything :grinning:
|
||||
4. Create any issues for new features you'd like to implement and explain why this feature is useful to everyone and not just you personally.
|
||||
5. Don't add new cogs unless specifically given approval in an issue discussing said cog idea.
|
||||
@@ -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 both python 3.5 and 3.6 (test environments `py35` and `py36` respectively)
|
||||
- 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`)
|
||||
|
||||
@@ -94,8 +94,6 @@ Our style checker of choice, [black](https://github.com/ambv/black), actually ha
|
||||
|
||||
Use the command `black --help` to see how to use this tool. The full style guide is explained in detail on [black's GitHub repository](https://github.com/ambv/black). **There is one exception to this**, however, which is that we set the line length to 99, instead of black's default 88. When using `black` on the command line, simply use it like so: `black -l 99 <src>`.
|
||||
|
||||
Note: Python 3.6+ is required to install and run black. If you installed your development environment with Python 3.5, black will not be installed.
|
||||
|
||||
### 4.4 Make
|
||||
You may have noticed we have a `Makefile` and a `make.bat` in the top-level directory. For now, you can do two things with them:
|
||||
1. `make reformat`: Reformat all python files in the project with Black
|
||||
|
||||
+3
-27
@@ -1,40 +1,16 @@
|
||||
# Trivia list repo injection
|
||||
redbot/trivia/
|
||||
|
||||
*.json
|
||||
*.exe
|
||||
*.dll
|
||||
*.pot
|
||||
.data
|
||||
!/tests/cogs/dataconverter/data/**/*.json
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
.idea/
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
+2
-1
@@ -4,10 +4,11 @@ formats:
|
||||
build:
|
||||
image: latest
|
||||
|
||||
requirements_file: docs/requirements.txt
|
||||
requirements_file: dependency_links.txt
|
||||
|
||||
python:
|
||||
version: 3.6
|
||||
pip_install: true
|
||||
extra_requirements:
|
||||
- docs
|
||||
- mongo
|
||||
|
||||
+18
-13
@@ -1,33 +1,37 @@
|
||||
dist: trusty
|
||||
dist: xenial
|
||||
language: python
|
||||
cache: pip
|
||||
notifications:
|
||||
email: false
|
||||
sudo: true
|
||||
|
||||
python:
|
||||
- 3.6.5
|
||||
- 3.6.6
|
||||
- 3.7
|
||||
env:
|
||||
global:
|
||||
PIPENV_IGNORE_VIRTUALENVS=1
|
||||
matrix:
|
||||
- TOXENV=py
|
||||
- TOXENV=docs
|
||||
- TOXENV=style
|
||||
TOXENV=py
|
||||
|
||||
install:
|
||||
- pip install --upgrade pip pipenv
|
||||
- pipenv install --dev
|
||||
- pip install --upgrade pip tox
|
||||
|
||||
script:
|
||||
- pipenv run tox
|
||||
- tox
|
||||
|
||||
jobs:
|
||||
include:
|
||||
|
||||
- python: 3.6.6
|
||||
env: TOXENV=docs
|
||||
- python: 3.6.6
|
||||
env: TOXENV=style
|
||||
|
||||
# These jobs only occur on tag creation for V3/develop if the prior ones succeed
|
||||
- stage: PyPi Deployment
|
||||
if: tag IS present
|
||||
python: 3.6.5
|
||||
python: 3.6.6
|
||||
env:
|
||||
- DEPLOYING=true
|
||||
deploy:
|
||||
@@ -39,11 +43,11 @@ jobs:
|
||||
on:
|
||||
repo: Cog-Creators/Red-DiscordBot
|
||||
branch: V3/develop
|
||||
python: 3.6.5
|
||||
python: 3.6.6
|
||||
tags: true
|
||||
- stage: Crowdin Deployment
|
||||
if: tag IS present
|
||||
python: 3.6.5
|
||||
python: 3.6.6
|
||||
env:
|
||||
- DEPLOYING=true
|
||||
before_deploy:
|
||||
@@ -51,12 +55,13 @@ jobs:
|
||||
- echo "deb https://artifacts.crowdin.com/repo/deb/ /" | sudo tee -a /etc/apt/sources.list
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y crowdin
|
||||
- pip install redgettext==2.1
|
||||
deploy:
|
||||
- provider: script
|
||||
script: python3 ./generate_strings.py
|
||||
script: make gettext
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: Cog-Creators/Red-DiscordBot
|
||||
branch: V3/develop
|
||||
python: 3.6.5
|
||||
python: 3.6.6
|
||||
tags: true
|
||||
|
||||
+2
-4
@@ -1,5 +1,3 @@
|
||||
include README.rst
|
||||
include README.md
|
||||
include LICENSE
|
||||
include requirements.txt
|
||||
include discord/bin/*.dll
|
||||
include redbot/cogs/audio/application.yml
|
||||
include dependency_links.txt
|
||||
|
||||
@@ -2,3 +2,6 @@ reformat:
|
||||
black -l 99 `git ls-files "*.py"`
|
||||
stylecheck:
|
||||
black --check -l 99 `git ls-files "*.py"`
|
||||
gettext:
|
||||
redgettext --command-docstrings --verbose --recursive redbot --exclude-files "redbot/pytest/**/*"
|
||||
crowdin upload
|
||||
|
||||
@@ -4,17 +4,9 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
"discord.py" = { git = 'git://github.com/Rapptz/discord.py', ref = 'rewrite', editable = true}
|
||||
"e1839a8" = {path = ".", editable = true}
|
||||
"discord.py" = { git = 'git://github.com/Rapptz/discord.py', ref = 'rewrite', editable = true }
|
||||
"e1839a8" = { path = ".", editable = true, extras = ['mongo', 'voice'] }
|
||||
|
||||
[dev-packages]
|
||||
tox = "*"
|
||||
pytest = "*"
|
||||
pytest-asyncio = "*"
|
||||
sphinx = ">1.7"
|
||||
sphinxcontrib-asyncio = "*"
|
||||
sphinx-rtd-theme = "*"
|
||||
black = {version = "*", python_version = ">= '3.6'"}
|
||||
|
||||
[pipenv]
|
||||
allow_prereleases = true
|
||||
"e1839a9" = { path = ".", editable = true, extras = ['docs', 'test', 'style'] }
|
||||
|
||||
Generated
+432
-187
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "d340e4a19777736703970e45766d05d67b973db38b87382b6ef8696cb53abb60"
|
||||
"sha256": "edd35f353e1fadc20094e40de6627db77fd61303da01794214c44d748e99838b"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -16,21 +16,30 @@
|
||||
"default": {
|
||||
"aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:129d83dd067760cec3cfd4456b5c6d7ac29f2c639d856884568fd539bed5a51f",
|
||||
"sha256:33c62afd115c456b0cf1e890fe6753055effe0f31a28321efd4f787378d6f4ab",
|
||||
"sha256:666756e1d4cf161ed1486b82f65fdd386ac07dd20fb10f025abf4be54be12746",
|
||||
"sha256:9705ded5a0faa25c8f14c6afb7044002d66c9120ed7eadb4aa9ca4aad32bd00c",
|
||||
"sha256:af5bfdd164256118a0a306b3f7046e63207d1f8cba73a67dcc0bd858dcfcd3bc",
|
||||
"sha256:b80f44b99fa3c9b4530fcfa324a99b84843043c35b084e0b653566049974435d",
|
||||
"sha256:c67e105ec74b85c8cb666b6877569dee6f55b9548f982983b9bee80b3d47e6f3",
|
||||
"sha256:d15c6658de5b7783c2538407278fa062b079a46d5f814a133ae0f09bbb2cfbc4",
|
||||
"sha256:d611ebd1ef48498210b65486306e065fde031040a1f3c455ca1b6baa7bf32ad3",
|
||||
"sha256:dcc7e4dcec6b0012537b9f8a0726f8b111188894ab0f924b680d40b13d3298a0",
|
||||
"sha256:de8ef106e130b94ca143fdfc6f27cda1d8ba439462542377738af4d99d9f5dd2",
|
||||
"sha256:eb6f1405b607fff7e44168e3ceb5d3c8a8c5a2d3effe0a27f843b16ec047a6d7",
|
||||
"sha256:f0e2ac69cb709367400008cebccd5d48161dd146096a009a632a132babe5714c"
|
||||
"sha256:1a112a1fdf3802b7f2b182e22e51d71e4a8fa7387d0d38e79a268921b869e384",
|
||||
"sha256:33aa7c937ebaf063a860cbb0c263a771b33333a84965c6148eeafe64fb4e29ca",
|
||||
"sha256:550b4a0788500f6d00f41b7fdd9fcce6d78f99706a7b2f6f81d4d331c7ca468e",
|
||||
"sha256:601e8e83123b4d423a9dfddf7d6943f4f520651a78ffcd50c99d065136c7ff7b",
|
||||
"sha256:620f19ba7628b70b177f5c2e6a55a6fd6e7c8591cde38c3f8f52551733d31b66",
|
||||
"sha256:70d56c784da1239c89d39fefa166fd429306dada641178389be4184a9c04e501",
|
||||
"sha256:7de2c9e445a5d257935011268202338538abef1aaff341a4733eca56419ca6f6",
|
||||
"sha256:96bb80b659cc2bafa160f3f0c346ce7fc10de1ffec4908d7f9690797f155f658",
|
||||
"sha256:ae7501cc6a6c37b8d4774bf2218c37be47fe42019a2570e8510fc2044e59d573",
|
||||
"sha256:c833aa6f4c9ac3e3eb843e3d999bae51339ad33a937303f43ce78064e61cb4b6",
|
||||
"sha256:dd81d85a342edf3d2a388e2f24d9facebc9c04550043888f970ee2f228c93059",
|
||||
"sha256:f20deec7a3fbaec7b5eb7ad99878427ad2ee4cc16a46732b705e8121cbb3cc12",
|
||||
"sha256:f52e7287eb9286a1e91e4c67c207c2573147fbaddc68f70efb5aeee5d1992f2e",
|
||||
"sha256:fe7b2972ff7e779e812f974aa5695edc328ecf559ceeea887ac46f06f090ad4c",
|
||||
"sha256:ff1447c84a02b9cd5dd3a9332d1fb181a4386c3625765bb5caf1cfbc210ab3f9"
|
||||
],
|
||||
"version": "==2.2.5"
|
||||
"version": "==3.3.2"
|
||||
},
|
||||
"aiohttp-json-rpc": {
|
||||
"hashes": [
|
||||
"sha256:970806a3b9887c389095d2bde84e2b540fefeddd0bae0efcae03c65f092ce00e",
|
||||
"sha256:d6f365067676e6089ac043ad31bcbabbf33d0343c42b57c36751a562fbe64fb6"
|
||||
],
|
||||
"version": "==0.11.1"
|
||||
},
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
@@ -41,10 +50,18 @@
|
||||
},
|
||||
"async-timeout": {
|
||||
"hashes": [
|
||||
"sha256:00cff4d2dce744607335cba84e9929c3165632da2d27970dbc55802a0c7873d0",
|
||||
"sha256:9093db5b8ddbe4b8f6885d1a6e0ad84ae3155464cbf6877c387605244c285f3c"
|
||||
"sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c",
|
||||
"sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287"
|
||||
],
|
||||
"version": "==2.0.1"
|
||||
"markers": "python_version >= '3.5.3'",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
|
||||
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
|
||||
],
|
||||
"version": "==18.2.0"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -63,7 +80,7 @@
|
||||
"discord.py": {
|
||||
"editable": true,
|
||||
"git": "git://github.com/Rapptz/discord.py",
|
||||
"ref": "rewrite"
|
||||
"ref": "00a659c6526b2445162b52eaf970adbd22c6d35d"
|
||||
},
|
||||
"distro": {
|
||||
"hashes": [
|
||||
@@ -74,59 +91,107 @@
|
||||
},
|
||||
"e1839a8": {
|
||||
"editable": true,
|
||||
"path": "."
|
||||
},
|
||||
"funcsigs": {
|
||||
"hashes": [
|
||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
||||
"extras": [
|
||||
"mongo",
|
||||
"voice"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
"path": "."
|
||||
},
|
||||
"fuzzywuzzy": {
|
||||
"hashes": [
|
||||
"sha256:d40c22d2744dff84885b30bbfc07fab7875f641d070374331777a4d1808b8d4e",
|
||||
"sha256:ecf490216fb4d76b558a03042ff8f45a8782f17326caca1384d834cbaa2c7e6f"
|
||||
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
|
||||
"sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62"
|
||||
],
|
||||
"version": "==0.16.0"
|
||||
"version": "==0.17.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||
],
|
||||
"version": "==2.6"
|
||||
"version": "==2.7"
|
||||
},
|
||||
"jsonrpcserver": {
|
||||
"idna-ssl": {
|
||||
"hashes": [
|
||||
"sha256:ab8013cdee3f65d59c5d3f84c75be76a3492caa0b33ecaa3f0f69906cf3d9e92"
|
||||
"sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"
|
||||
],
|
||||
"version": "==3.5.4"
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jsonschema": {
|
||||
"motor": {
|
||||
"hashes": [
|
||||
"sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08",
|
||||
"sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"
|
||||
"sha256:462fbb824f4289481c158227a2579d6adaf1ec7c70cf7ebe60ed6ceb321e5869",
|
||||
"sha256:d035c09ab422bc50bf3efb134f7405694cae76268545bd21e14fb22e2638f84e"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"version": "==4.3.1"
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"pymongo": {
|
||||
"hashes": [
|
||||
"sha256:08dea6dbff33363419af7af3bf2e9a373ff71eb22833dd7063f9b953f09a0bdf",
|
||||
"sha256:0949110db76eb1b54cecfc0c0f8468a8b9a7fd42ba23fd0d4a37d97e0b4ca203",
|
||||
"sha256:0c31a39f440801cc8603547ccaacf4cb1f02b81af6ba656621c13677b27f4426",
|
||||
"sha256:1e10b3fda5677d360440ebd12a1185944dc81d9ea9acf0c6b0681013b3fb9bc2",
|
||||
"sha256:1f59440b993666a417ba1954cfb1b7fb11cb4dea1a1d2777897009f688d000ee",
|
||||
"sha256:2b5a3806d9f656c14e9d9b693a344fc5684fdd045155594be0c505c6e9410a94",
|
||||
"sha256:4a14e2d7c2c0e07b5affcfbfc5c395d767f94bb1a822934a41a3b5371cde1458",
|
||||
"sha256:4cb50541225208b37786fdb0de632e475c4f00ec4792579df551ef48d6999d69",
|
||||
"sha256:52999666ad01de885653e1f74a86c2a6520d1004afec475180bebf3d7393a8fc",
|
||||
"sha256:562c353079e8ce7e2ad611fd7436a72f5df97be72bca59ae9ebf789a724afd5c",
|
||||
"sha256:5ce2a71f473f4703daa8d6c61a00b35ce625a7f5015b4371e3af728dafca296a",
|
||||
"sha256:6613e633676168a4500e5e6bb6e3e64d3fdb96d2dc472eb4b99235fb4141adb1",
|
||||
"sha256:8330406f294df118399c721f80979f2516447bcc73e4262826687872c864751e",
|
||||
"sha256:8e939dfa7d16609b99eb4d1fd2fc74f7a90f4fd0aaf31d611822daaff456236f",
|
||||
"sha256:8fa4303e1f50d9f0c8f2f7833b5a370a94d19d41449def62b34ae072126b4dfd",
|
||||
"sha256:966d987975aa3b4cfcdf1495930ff6ecb152fafe8e544e40633e41b24ca3e1c5",
|
||||
"sha256:aec4ea43a1b8e9782246a259410f66692f2d3aa0f03c54477e506193b0781cb6",
|
||||
"sha256:b73f889f032fbef05863f5056b46468a8262ae83628898e20b10bbbb79a3617e",
|
||||
"sha256:b752088a2f819f163d11dfdbbe627b27eef9d8478c7e57d42c5e7c600fee434e",
|
||||
"sha256:c8669f96277f140797e0ff99f80bd706271674942672a38ed694e2bfa66f3900",
|
||||
"sha256:ccf00549efaf6f8d5b35b654beb9aed2b788a5b33b05606eb818ddaa4e924ea3",
|
||||
"sha256:ce7c91463ad21ac72fc795188292b01c8366cf625e2d1e5ed473ce127b844f60",
|
||||
"sha256:d776d8d47884e6ad39ff8a301f1ae6b7d2186f209218cf024f43334dbba79c64",
|
||||
"sha256:dab0f63841aebb2b421fadb31f3c7eef27898f21274a8e5b45c4f2bccb40f9ed",
|
||||
"sha256:daedcfbf3b24b2b687e35b33252a9315425c2dd06a085a36906d516135bdd60e",
|
||||
"sha256:e7ad1ec621db2c5ad47924f63561f75abfd4fff669c62c8cc99c169c90432f59",
|
||||
"sha256:f14fb6c4058772a0d74d82874d3b89d7264d89b4ed7fa0413ea0ef8112b268b9",
|
||||
"sha256:f16c7b6b98bc400d180f05e65e2236ef4ee9d71f3815280558582670e1e67536",
|
||||
"sha256:f2d9eb92b26600ae6e8092f66da4bcede1b61a647c9080d6b44c148aff3a8ea4",
|
||||
"sha256:ffe94f9d17800610dda5282d7f6facfc216d79a93dd728a03d2f21cff3af7cc6"
|
||||
],
|
||||
"version": "==3.7.1"
|
||||
},
|
||||
"python-levenshtein": {
|
||||
"hashes": [
|
||||
@@ -136,94 +201,117 @@
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8",
|
||||
"sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736",
|
||||
"sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f",
|
||||
"sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608",
|
||||
"sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8",
|
||||
"sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab",
|
||||
"sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7",
|
||||
"sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3",
|
||||
"sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1",
|
||||
"sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6",
|
||||
"sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8",
|
||||
"sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4",
|
||||
"sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca",
|
||||
"sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269"
|
||||
"sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
|
||||
"sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
|
||||
"sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
|
||||
"sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
|
||||
"sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
|
||||
"sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
|
||||
"sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
|
||||
"sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
|
||||
"sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
|
||||
"sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
|
||||
"sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
|
||||
],
|
||||
"version": "==3.12"
|
||||
"version": "==3.13"
|
||||
},
|
||||
"raven": {
|
||||
"hashes": [
|
||||
"sha256:0adae40e004dfe2181d1f2883aa3d4ca1cf16dbe449ae4b445b011c6eb220a90",
|
||||
"sha256:84da75114739191bdf2388f296ffd6177e83567a7fbaf2701e034ad6026e4f3b"
|
||||
"sha256:3fd787d19ebb49919268f06f19310e8112d619ef364f7989246fc8753d469888",
|
||||
"sha256:95f44f3ea2c1b176d5450df4becdb96c15bf2632888f9ab193e9dd22300ce46a"
|
||||
],
|
||||
"version": "==6.5.0"
|
||||
"version": "==6.9.0"
|
||||
},
|
||||
"red-trivia": {
|
||||
"raven-aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:39413b9fb3f9b9362d6de1dcf69a4bf635b0f3518243f7178299b96d26cbb6a7"
|
||||
"sha256:1444a49c93a85b8bb57c6ee649e512368dce7a26ad64ac3a01d86aa5669d77f3",
|
||||
"sha256:6a34b6a9841ad0fd827eeb158edb5826c5c5bd7babe2cde2a3f23eb85313af04"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"six": {
|
||||
"red-lavalink": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
"sha256:6a1a34471ccf4630eee537049568dd87e8e93614f1d1ce355dd74e5b10079782"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
"version": "==0.1.2"
|
||||
},
|
||||
"websockets": {
|
||||
"hashes": [
|
||||
"sha256:09dfec40e9b73e8808c39ecdbc1733e33915a2b26b90c54566afc0af546a9ec3",
|
||||
"sha256:2aa6d52264cecb08d39741e8fda49f5ac4872aef02617230c84d02e861f3cc5a",
|
||||
"sha256:2f5b7f3920f29609086fb0b63552bb1f86a04b8cbdcc0dbf3775cc90d489dfc8",
|
||||
"sha256:3d38f76f71654268e5533b45df125ff208fee242a102d4b5ca958da5cf5fb345",
|
||||
"sha256:3fcc7dfb365e81ff8206f950c86d1e73accdf3be2f9110c0cb73be32d2e7a9a5",
|
||||
"sha256:4128212ab6f91afda03a0c697add261bdf6946b47928db83f07298ea2cd8d937",
|
||||
"sha256:43e5b9f51dd0000a4c6f646e2ade0c886bd14a784ffac08b9e079bd17a63bcc5",
|
||||
"sha256:4a932c17cb11c361c286c04842dc2385cc7157019bbba8b64808acbc89a95584",
|
||||
"sha256:5ddc5fc121eb76771e990f071071d9530e27d20e8cfb804d9f5823de055837af",
|
||||
"sha256:7347af28fcc70eb45be409760c2a428f8199e7f73c04a621916c3c219ed7ad27",
|
||||
"sha256:85ae1e4b36aa2e90de56d211d2de36d7c093d00277a9afdd9b4f81e69c0214ab",
|
||||
"sha256:8a29100079f5b91a72bcd25d35a7354db985d3babae42d00b9d629f9a0aaa8ac",
|
||||
"sha256:a7e7585c8e3c0f9277ad7d6ee6ccddc69649cd216255d5e255d68f90482aeefa",
|
||||
"sha256:aa42ecef3aed807e23218c264b1e82004cdd131a6698a10b57fc3d8af8f651fc",
|
||||
"sha256:b19e7ede1ba80ee9de6f5b8ccd31beee25402e68bef7c13eeb0b8bc46bc4b7b7",
|
||||
"sha256:c4c5b5ce2d66cb0cf193c14bc9726adca095febef0f7b2c04e5e3fa3487a97a4",
|
||||
"sha256:de743ef26b002efceea7d7756e99e5d38bf5d4f27563b8d27df2a9a5cc57340a",
|
||||
"sha256:e1e568136ad5cb6768504be36d470a136b072acbf3ea882303aee6361be01941",
|
||||
"sha256:e8992f1db371f2a1c5af59e032d9dc7c1aa92f16241efcda695b7d955b4de0c2",
|
||||
"sha256:e9c1cdbb591432c59d0b5ca64fd30b6d517024767f152fc169563b26e7bcc9da"
|
||||
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
|
||||
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
|
||||
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
|
||||
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
|
||||
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
|
||||
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
|
||||
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
|
||||
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
|
||||
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
|
||||
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
|
||||
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
|
||||
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
|
||||
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
|
||||
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
|
||||
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
|
||||
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
|
||||
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
|
||||
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
|
||||
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
|
||||
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
||||
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
||||
],
|
||||
"version": "==3.4"
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==6.0"
|
||||
},
|
||||
"yarl": {
|
||||
"hashes": [
|
||||
"sha256:605480ee43eead69ec8e8c52cdfefc79cef6379cc0e87d908cf290408c1e49af",
|
||||
"sha256:7fad2530cb4ddf2b74c1e4f6f9f0e28eac482094c6542f98fd71ecf67fb4fded",
|
||||
"sha256:837d866a70f1ea03005914a740bddea89a253afabd6589db981b91738768bd25",
|
||||
"sha256:885e40812ff9fc80e6f28ef04ad6396e3ae583ab504b1a76301fdcec7fc9f30f",
|
||||
"sha256:a5457e075eab1170141774a8c69906c223ea0088eaebd6ef91b04b33527fa905",
|
||||
"sha256:baa0d3f7982fa0c03a55433109c405e79a597141f2e2d6ee7e16c03eabd74886",
|
||||
"sha256:beeefbe0edd47fc8b657bf7bf44791f7a6e5b14f3de1846daf999687cb68c156",
|
||||
"sha256:cf6a3d6fd3e79d3457d520c12d5d18b030d5ca5d0b205ca6481857804d8d944d",
|
||||
"sha256:d07d3dc6849345b7437dc58ea49ad2a1960017386d86288550728ca38e482ddc",
|
||||
"sha256:d81e45bedefccb97e4e8f7d32cfae0af1d9eadd1ae795fc420c8319c3dab2a28",
|
||||
"sha256:e1da2853a92fbc7e2d0248bbfa931cd621121e70ce6dda7c1eeef3516d51b46c",
|
||||
"sha256:f1201de3e93fb1efc3111c8928d9366875edefd65d77c0f6b847fe299e8e1122",
|
||||
"sha256:fe0390a29b5c7e90975feefe863e3d3a851be546bd797b23f338d24a15efa920"
|
||||
"sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9",
|
||||
"sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee",
|
||||
"sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308",
|
||||
"sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357",
|
||||
"sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78",
|
||||
"sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8",
|
||||
"sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1",
|
||||
"sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4",
|
||||
"sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7"
|
||||
],
|
||||
"version": "==0.18.0"
|
||||
"markers": "python_version >= '3.4.1'",
|
||||
"version": "==1.2.6"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:1a112a1fdf3802b7f2b182e22e51d71e4a8fa7387d0d38e79a268921b869e384",
|
||||
"sha256:33aa7c937ebaf063a860cbb0c263a771b33333a84965c6148eeafe64fb4e29ca",
|
||||
"sha256:550b4a0788500f6d00f41b7fdd9fcce6d78f99706a7b2f6f81d4d331c7ca468e",
|
||||
"sha256:601e8e83123b4d423a9dfddf7d6943f4f520651a78ffcd50c99d065136c7ff7b",
|
||||
"sha256:620f19ba7628b70b177f5c2e6a55a6fd6e7c8591cde38c3f8f52551733d31b66",
|
||||
"sha256:70d56c784da1239c89d39fefa166fd429306dada641178389be4184a9c04e501",
|
||||
"sha256:7de2c9e445a5d257935011268202338538abef1aaff341a4733eca56419ca6f6",
|
||||
"sha256:96bb80b659cc2bafa160f3f0c346ce7fc10de1ffec4908d7f9690797f155f658",
|
||||
"sha256:ae7501cc6a6c37b8d4774bf2218c37be47fe42019a2570e8510fc2044e59d573",
|
||||
"sha256:c833aa6f4c9ac3e3eb843e3d999bae51339ad33a937303f43ce78064e61cb4b6",
|
||||
"sha256:dd81d85a342edf3d2a388e2f24d9facebc9c04550043888f970ee2f228c93059",
|
||||
"sha256:f20deec7a3fbaec7b5eb7ad99878427ad2ee4cc16a46732b705e8121cbb3cc12",
|
||||
"sha256:f52e7287eb9286a1e91e4c67c207c2573147fbaddc68f70efb5aeee5d1992f2e",
|
||||
"sha256:fe7b2972ff7e779e812f974aa5695edc328ecf559ceeea887ac46f06f090ad4c",
|
||||
"sha256:ff1447c84a02b9cd5dd3a9332d1fb181a4386c3625765bb5caf1cfbc210ab3f9"
|
||||
],
|
||||
"version": "==3.3.2"
|
||||
},
|
||||
"aiohttp-json-rpc": {
|
||||
"hashes": [
|
||||
"sha256:970806a3b9887c389095d2bde84e2b540fefeddd0bae0efcae03c65f092ce00e",
|
||||
"sha256:d6f365067676e6089ac043ad31bcbabbf33d0343c42b57c36751a562fbe64fb6"
|
||||
],
|
||||
"version": "==0.11.1"
|
||||
},
|
||||
"alabaster": {
|
||||
"hashes": [
|
||||
"sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732",
|
||||
"sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0"
|
||||
"sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456",
|
||||
"sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7"
|
||||
],
|
||||
"version": "==0.7.10"
|
||||
"version": "==0.7.11"
|
||||
},
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
@@ -232,42 +320,49 @@
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"async-timeout": {
|
||||
"hashes": [
|
||||
"sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c",
|
||||
"sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287"
|
||||
],
|
||||
"markers": "python_version >= '3.5.3'",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"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": [
|
||||
"sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14",
|
||||
"sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80"
|
||||
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
|
||||
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
|
||||
],
|
||||
"version": "==2.5.3"
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:4fec2566f9fbbd4a58de50a168cbe3ab952713530410d227e82e4c65d1fad946",
|
||||
"sha256:5fec0f25486046b9edb97961c946412ced96021247dd1a60ecd9f0567b68b030"
|
||||
"sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44",
|
||||
"sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==18.5b0"
|
||||
"version": "==18.6b4"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
||||
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
|
||||
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
|
||||
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
|
||||
],
|
||||
"version": "==2018.4.16"
|
||||
"version": "==2018.8.24"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -283,6 +378,20 @@
|
||||
],
|
||||
"version": "==6.7"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
|
||||
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
|
||||
],
|
||||
"version": "==0.3.9"
|
||||
},
|
||||
"distro": {
|
||||
"hashes": [
|
||||
"sha256:224041cef9600e72d19ae41ba006e71c05c4dc802516da715d7fda55ba3d8742",
|
||||
"sha256:6ec8e539cf412830e5ccf521aecf879f2c7fcf60ce446e33cd16eef1ed8a0158"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||
@@ -291,19 +400,42 @@
|
||||
],
|
||||
"version": "==0.14"
|
||||
},
|
||||
"e1839a9": {
|
||||
"editable": true,
|
||||
"extras": [
|
||||
"docs",
|
||||
"test",
|
||||
"style"
|
||||
],
|
||||
"path": "."
|
||||
},
|
||||
"fuzzywuzzy": {
|
||||
"hashes": [
|
||||
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
|
||||
"sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62"
|
||||
],
|
||||
"version": "==0.17.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||
],
|
||||
"version": "==2.6"
|
||||
"version": "==2.7"
|
||||
},
|
||||
"idna-ssl": {
|
||||
"hashes": [
|
||||
"sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"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": [
|
||||
@@ -320,11 +452,45 @@
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
|
||||
"sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3",
|
||||
"sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"
|
||||
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
|
||||
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
|
||||
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
|
||||
],
|
||||
"version": "==4.2.0"
|
||||
"version": "==4.3.0"
|
||||
},
|
||||
"multidict": {
|
||||
"hashes": [
|
||||
"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"
|
||||
],
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
@@ -335,18 +501,19 @@
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
|
||||
"sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
|
||||
"sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
|
||||
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
|
||||
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881",
|
||||
"sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"
|
||||
"sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
|
||||
"sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
|
||||
],
|
||||
"version": "==1.5.3"
|
||||
"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": [
|
||||
@@ -358,44 +525,74 @@
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
|
||||
"sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
|
||||
"sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
|
||||
"sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
|
||||
"sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
|
||||
"sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58",
|
||||
"sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
|
||||
],
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:39555d023af3200d004d09e51b4dd9fdd828baa863cded3fd6ba2f29f757ae2d",
|
||||
"sha256:c76e93f3145a44812955e8d46cdd302d8a45fbfc7bf22be24fe231f9d8d8853a"
|
||||
"sha256:2d7c49e931316cc7d1638a3e5f54f5d7b4e5225972b3c9838f3584788d27f349",
|
||||
"sha256:ad0c7db7b5d4081631e0155f5c61b80ad76ce148551aaafe3a718d65a7508b18"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.6.0"
|
||||
"version": "==3.7.4"
|
||||
},
|
||||
"pytest-asyncio": {
|
||||
"hashes": [
|
||||
"sha256:286b50773e996c80d894b95afaf45df6952408a67a59979ca9839f94693ec7fd",
|
||||
"sha256:f32804bb58a66e13a3eda11f8942a71b1b6a30466b0d2ffe9214787aab0e172e"
|
||||
"sha256:a962e8e1b6ec28648c8fe214edab4e16bacdb37b52df26eb9d63050af309b2a9",
|
||||
"sha256:fbd92c067c16111174a1286bfb253660f1e564e5146b39eeed1133315cf2c2cf"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.8.0"
|
||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||
"version": "==0.9.0"
|
||||
},
|
||||
"python-levenshtein": {
|
||||
"hashes": [
|
||||
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
|
||||
],
|
||||
"version": "==0.12.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
|
||||
"sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
|
||||
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
|
||||
"sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
|
||||
],
|
||||
"version": "==2018.4"
|
||||
"version": "==2018.5"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
|
||||
"sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
|
||||
"sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
|
||||
"sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
|
||||
"sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
|
||||
"sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
|
||||
"sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
|
||||
"sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
|
||||
"sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
|
||||
"sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
|
||||
"sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
|
||||
],
|
||||
"version": "==3.13"
|
||||
},
|
||||
"raven": {
|
||||
"hashes": [
|
||||
"sha256:3fd787d19ebb49919268f06f19310e8112d619ef364f7989246fc8753d469888",
|
||||
"sha256:95f44f3ea2c1b176d5450df4becdb96c15bf2632888f9ab193e9dd22300ce46a"
|
||||
],
|
||||
"version": "==6.9.0"
|
||||
},
|
||||
"raven-aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:1444a49c93a85b8bb57c6ee649e512368dce7a26ad64ac3a01d86aa5669d77f3",
|
||||
"sha256:6a34b6a9841ad0fd827eeb158edb5826c5c5bd7babe2cde2a3f23eb85313af04"
|
||||
],
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
||||
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
||||
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||
],
|
||||
"version": "==2.18.4"
|
||||
"version": "==2.19.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
@@ -413,55 +610,103 @@
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:2e7ad92e96eff1b2006cf9f0cdb2743dacbae63755458594e9e8238b0c3dc60b",
|
||||
"sha256:e9b1a75a3eae05dded19c80eb17325be675e0698975baae976df603b6ed1eb10"
|
||||
"sha256:a07050845cc9a2f4026a6035cc8ed795a5ce7be6528bbc82032385c10807dfe7",
|
||||
"sha256:d719de667218d763e8fd144b7fcfeefd8d434a6201f76bf9f0f0c1fa6f47fcdb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.7.4"
|
||||
"version": "==1.7.8"
|
||||
},
|
||||
"sphinx-rtd-theme": {
|
||||
"hashes": [
|
||||
"sha256:32424dac2779f0840b4788fbccb032ba2496c1ca47a439ad2510c8b1e55dfd33",
|
||||
"sha256:6d0481532b5f441b075127a2d755f430f1f8410a50112b1af6b069518548381d"
|
||||
"sha256:3b49758a64f8a1ebd8a33cb6cc9093c3935a908b716edfaa5772fd86aac27ef6",
|
||||
"sha256:80e01ec0eb711abacb1fa507f3eae8b805ae8fa3e8b057abfdf497e3f644c82c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.1"
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"sphinxcontrib-asyncio": {
|
||||
"hashes": [
|
||||
"sha256:96627b1ec4eba08d09ad577ff9416c131910333ef37a2c82a2716e59646739f0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"sphinxcontrib-websupport": {
|
||||
"hashes": [
|
||||
"sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9",
|
||||
"sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2"
|
||||
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
|
||||
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:8e86bd6ce8cc11b9620cb637466453d94f5d57ad86f17e98a98d1f73e3baab2d"
|
||||
],
|
||||
"version": "==0.9.4"
|
||||
},
|
||||
"tox": {
|
||||
"hashes": [
|
||||
"sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f",
|
||||
"sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0"
|
||||
"sha256:37cf240781b662fb790710c6998527e65ca6851eace84d1595ee71f7af4e85f7",
|
||||
"sha256:eb61aa5bcce65325538686f09848f04ef679b5cd9b83cc491272099b28739600"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.0"
|
||||
"version": "==3.2.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||
],
|
||||
"version": "==1.22"
|
||||
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version < '4' and python_version >= '2.6' and python_version != '3.1.*'",
|
||||
"version": "==1.23"
|
||||
},
|
||||
"virtualenv": {
|
||||
"hashes": [
|
||||
"sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
|
||||
"sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
|
||||
],
|
||||
"markers": "python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||
"version": "==16.0.0"
|
||||
},
|
||||
"websockets": {
|
||||
"hashes": [
|
||||
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
|
||||
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
|
||||
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
|
||||
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
|
||||
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
|
||||
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
|
||||
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
|
||||
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
|
||||
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
|
||||
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
|
||||
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
|
||||
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
|
||||
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
|
||||
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
|
||||
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
|
||||
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
|
||||
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
|
||||
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
|
||||
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
|
||||
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
||||
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
||||
],
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==6.0"
|
||||
},
|
||||
"yarl": {
|
||||
"hashes": [
|
||||
"sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9",
|
||||
"sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee",
|
||||
"sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308",
|
||||
"sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357",
|
||||
"sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78",
|
||||
"sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8",
|
||||
"sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1",
|
||||
"sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4",
|
||||
"sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7"
|
||||
],
|
||||
"markers": "python_version >= '3.4.1'",
|
||||
"version": "==1.2.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
.. raw:: html
|
||||
|
||||
<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>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<h4 align="center">Music, Moderation, Trivia, Stream Alerts and fully customizable.</h4>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/red">
|
||||
<img src="https://discordapp.com/api/guilds/133049272517001216/widget.png?style=shield">
|
||||
</a>
|
||||
<a href="https://www.patreon.com/Red_Devs">
|
||||
<img src="https://img.shields.io/badge/Support-Red!-yellow.svg">
|
||||
</a>
|
||||
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/Made%20With-Python%203.6-blue.svg?style=for-the-badge">
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/red-discordbot">
|
||||
<img src="https://d322cqt584bo4o.cloudfront.net/red-discordbot/localized.svg">
|
||||
</a>
|
||||
<a href="https://github.com/Rapptz/discord.py/tree/rewrite">
|
||||
<img src="https://img.shields.io/badge/discord-py-blue.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<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"></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)
|
||||
- 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
@@ -1,5 +1,5 @@
|
||||
api_key_env: CROWDIN_API_KEY
|
||||
project_identifier_env: CROWDIN_PROJECT_ID
|
||||
files:
|
||||
- source: /**/*.pot
|
||||
- source: /redbot/**/*.pot
|
||||
translation: /%original_path%/%locale%.po
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
https://github.com/Rapptz/discord.py/tarball/00a659c6526b2445162b52eaf970adbd22c6d35d#egg=discord.py-1.0.0a0
|
||||
@@ -10,7 +10,7 @@ Creating the service file
|
||||
|
||||
Create the new service file:
|
||||
|
||||
:code:`sudo nano /etc/systemd/system/red@.service`
|
||||
:code:`sudo -e /etc/systemd/system/red@.service`
|
||||
|
||||
Paste the following and replace all instances of :code:`username` with the username your bot is running under (hopefully not root):
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -37,6 +37,7 @@ For each of those, settings have varying priorities (listed below, highest to lo
|
||||
7. Role settings (see below)
|
||||
8. Server whitelist
|
||||
9. Server blacklist
|
||||
10. Default settings
|
||||
|
||||
For the role whitelist and blacklist settings,
|
||||
roles will be checked individually in order from highest to lowest role the user has
|
||||
@@ -73,3 +74,34 @@ An example of the expected format is shown below.
|
||||
- 96733288462286848
|
||||
default: allow
|
||||
|
||||
----------------------
|
||||
Example configurations
|
||||
----------------------
|
||||
|
||||
Locking Audio cog to approved server(s) as a bot owner
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]permissions setglobaldefault Audio deny
|
||||
[p]permissions addglobalrule allow Audio [server ID or name]
|
||||
|
||||
Locking Audio to specific voice channel(s) as a serverowner or admin:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]permissions setguilddefault deny play
|
||||
[p]permissions setguilddefault deny "playlist start"
|
||||
[p]permissions addguildrule allow play [voice channel ID or name]
|
||||
[p]permissions addguildrule allow "playlist start" [voice channel ID or name]
|
||||
|
||||
Allowing extra roles to use cleanup
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]permissions addguildrule allow Cleanup [role ID]
|
||||
|
||||
Preventing cleanup from being used in channels where message history is important:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[p]permissions addguildrule deny Cleanup [channel ID or mention]
|
||||
|
||||
@@ -190,6 +190,13 @@ texinfo_documents = [
|
||||
]
|
||||
|
||||
|
||||
# -- Options for linkcheck builder ----------------------------------------
|
||||
|
||||
# A list of regular expressions that match URIs that should not be
|
||||
# checked when doing a linkcheck build.
|
||||
linkcheck_ignore = [r"https://java.com*"]
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3.6", None),
|
||||
|
||||
@@ -13,6 +13,9 @@ RedBase
|
||||
:members:
|
||||
:exclude-members: get_context
|
||||
|
||||
.. automethod:: register_rpc_handler
|
||||
.. automethod:: unregister_rpc_handler
|
||||
|
||||
Red
|
||||
^^^
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
Commands Package
|
||||
================
|
||||
|
||||
This package acts almost identically to ``discord.ext.commands``; i.e. they both have the same
|
||||
attributes. Some of these attributes, however, have been slightly modified, as outlined below.
|
||||
This package acts almost identically to :doc:`discord.ext.commands <dpy:ext/commands/api>`; i.e.
|
||||
all of the attributes from discord.py's are also in ours.
|
||||
Some of these attributes, however, have been slightly modified, while others have been added to
|
||||
extend functionlities used throughout the bot, as outlined below.
|
||||
|
||||
.. autofunction:: redbot.core.commands.command
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ Keys common to both repo and cog info.json (case sensitive)
|
||||
|
||||
- ``install_msg`` (string) - The message that gets displayed when a cog
|
||||
is installed or a repo is added
|
||||
|
||||
.. tip:: You can use the ``[p]`` key in your string to use the prefix
|
||||
used for installing.
|
||||
|
||||
- ``short`` (string) - A short description of the cog or repo. For cogs, this info
|
||||
is displayed when a user executes ``!cog list``
|
||||
@@ -29,7 +32,9 @@ Keys specific to the cog info.json (case sensitive)
|
||||
|
||||
- ``bot_version`` (list of integer) - Min version number of Red in the format ``(MAJOR, MINOR, PATCH)``
|
||||
|
||||
- ``hidden`` (bool) - Determines if a cog is available for install.
|
||||
- ``hidden`` (bool) - Determines if a cog is visible in the cog list for a repo.
|
||||
|
||||
- ``disabled`` (bool) - Determines if a cog is available for install.
|
||||
|
||||
- ``required_cogs`` (map of cogname to repo URL) - A map of required cogs that this cog depends on.
|
||||
Downloader will not deal with this functionality but it may be useful for other cogs.
|
||||
|
||||
+43
-19
@@ -4,36 +4,60 @@
|
||||
RPC
|
||||
===
|
||||
|
||||
.. currentmodule:: redbot.core.rpc
|
||||
|
||||
V3 comes default with an internal RPC server that may be used to remotely control the bot in various ways.
|
||||
Cogs must register functions to be exposed to RPC clients.
|
||||
Each of those functions must only take JSON serializable parameters and must return JSON serializable objects.
|
||||
|
||||
To begin, register all methods using individual calls to the :func:`Methods.add` method.
|
||||
To enable the internal RPC server you must start the bot with the ``--rpc`` flag.
|
||||
|
||||
********
|
||||
Examples
|
||||
********
|
||||
|
||||
Coming soon to a docs page near you!
|
||||
.. code-block:: Python
|
||||
|
||||
def setup(bot):
|
||||
c = Cog()
|
||||
bot.add_cog(c)
|
||||
bot.register_rpc_handler(c.rpc_method)
|
||||
|
||||
*******************************
|
||||
Interacting with the RPC Server
|
||||
*******************************
|
||||
|
||||
The RPC server opens a websocket bound to port ``6133`` on ``127.0.0.1``.
|
||||
This is not configurable for security reasons as broad access to this server gives anyone complete control over your bot.
|
||||
To access the server you must find a library that implements websocket based JSONRPC in the language of your choice.
|
||||
|
||||
There are a few built-in RPC methods to note:
|
||||
|
||||
* ``GET_METHODS`` - Returns a list of available RPC methods.
|
||||
* ``GET_METHOD_INFO`` - Will return the docstring for an available RPC method. Useful for finding information about the method's parameters and return values.
|
||||
* ``GET_TOPIC`` - Returns a list of available RPC message topics.
|
||||
* ``GET_SUBSCRIPTIONS`` - Returns a list of RPC subscriptions.
|
||||
* ``SUBSCRIBE`` - Subscribes to an available RPC message topic.
|
||||
* ``UNSUBSCRIBE`` - Unsubscribes from an RPC message topic.
|
||||
|
||||
All RPC methods accept a list of parameters.
|
||||
The built-in methods above expect their parameters to be in list format.
|
||||
|
||||
All cog-based methods expect their parameter list to take one argument, a JSON object, in the following format::
|
||||
|
||||
params = [
|
||||
{
|
||||
"args": [], # A list of positional arguments
|
||||
"kwargs": {}, # A dictionary of keyword arguments
|
||||
}
|
||||
]
|
||||
|
||||
# As an example, here's a call to "get_method_info"
|
||||
rpc_call("GET_METHOD_INFO", ["get_methods",])
|
||||
|
||||
# And here's a call to "core__load"
|
||||
rpc_call("CORE__LOAD", {"args": [["general", "economy", "downloader"],], "kwargs": {}})
|
||||
|
||||
*************
|
||||
API Reference
|
||||
*************
|
||||
|
||||
.. py:attribute:: redbot.core.rpc.methods
|
||||
|
||||
An instance of the :class:`Methods` class.
|
||||
All attempts to register new RPC methods **MUST** use this object.
|
||||
You should never create a new instance of the :class:`Methods` class!
|
||||
|
||||
RPC
|
||||
^^^
|
||||
.. autoclass:: redbot.core.rpc.RPC
|
||||
:members:
|
||||
|
||||
Methods
|
||||
^^^^^^^
|
||||
.. autoclass:: redbot.core.rpc.Methods
|
||||
:members:
|
||||
Please see the :class:`redbot.core.bot.RedBase` class for details on the RPC handler register and unregister methods.
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
Utility Functions
|
||||
=================
|
||||
|
||||
General Utility
|
||||
===============
|
||||
|
||||
.. automodule:: redbot.core.utils
|
||||
:members: deduplicate_iterables, bounded_gather, bounded_gather_iter
|
||||
|
||||
Chat Formatting
|
||||
===============
|
||||
|
||||
@@ -38,4 +44,10 @@ Tunnel
|
||||
======
|
||||
|
||||
.. automodule:: redbot.core.utils.tunnel
|
||||
:members: Tunnel
|
||||
:members: Tunnel
|
||||
|
||||
Common Filters
|
||||
==============
|
||||
|
||||
.. automodule:: redbot.core.utils.common_filters
|
||||
:members:
|
||||
|
||||
@@ -17,11 +17,10 @@ you in the process.
|
||||
Getting started
|
||||
---------------
|
||||
|
||||
To start off, be sure that you have installed Python 3.5 or higher (if you
|
||||
are on Windows, stick with 3.5). 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.
|
||||
This will install the latest version of V3.
|
||||
|
||||
--------------------
|
||||
Setting up a package
|
||||
@@ -45,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"""
|
||||
|
||||
+3
-7
@@ -11,13 +11,8 @@ Welcome to Red - Discord Bot's documentation!
|
||||
:caption: Installation Guides:
|
||||
|
||||
install_windows
|
||||
install_mac
|
||||
install_ubuntu_xenial
|
||||
install_ubuntu_bionic
|
||||
install_debian
|
||||
install_centos
|
||||
install_arch
|
||||
install_raspbian
|
||||
install_linux_mac
|
||||
venv_guide
|
||||
cog_dataconverter
|
||||
autostart_systemd
|
||||
|
||||
@@ -25,6 +20,7 @@ Welcome to Red - Discord Bot's documentation!
|
||||
:maxdepth: 2
|
||||
:caption: Cog Reference:
|
||||
|
||||
cog_customcom
|
||||
cog_downloader
|
||||
cog_permissions
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
.. arch install guide
|
||||
|
||||
==============================
|
||||
Installing Red on Arch Linux
|
||||
==============================
|
||||
|
||||
.. warning:: For safety reasons, DO NOT install Red with a root user. Instead, make a new one.
|
||||
|
||||
:code:`https://wiki.archlinux.org/index.php/Users_and_groups`
|
||||
|
||||
-------------------------------
|
||||
Installing the pre-requirements
|
||||
-------------------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo pacman -Syu python-pip git base-devel jre8-openjdk
|
||||
|
||||
------------------
|
||||
Installing the bot
|
||||
------------------
|
||||
|
||||
To install without audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot --user`
|
||||
|
||||
To install with audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot[voice] --user`
|
||||
|
||||
To install the development version (without audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot --user`
|
||||
|
||||
To install the development version (with audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot[voice] --user`
|
||||
|
||||
------------------------
|
||||
Setting up your instance
|
||||
------------------------
|
||||
|
||||
Run :code:`redbot-setup` and follow the prompts. It will ask first for where you want to
|
||||
store the data (the default is :code:`~/.local/share/Red-DiscordBot`) and will then ask
|
||||
for confirmation of that selection. Next, it will ask you to choose your storage backend
|
||||
(the default here is JSON). It will then ask for a name for your instance. This can be
|
||||
anything as long as it does not contain spaces; however, keep in mind that this is the
|
||||
name you will use to run your bot, and so it should be something you can remember.
|
||||
|
||||
-----------
|
||||
Running Red
|
||||
-----------
|
||||
|
||||
Run :code:`redbot <your instance name>` and run through the initial setup. This will ask for
|
||||
your token and a prefix.
|
||||
@@ -1,55 +0,0 @@
|
||||
.. centos install guide
|
||||
|
||||
==========================
|
||||
Installing Red on CentOS 7
|
||||
==========================
|
||||
|
||||
.. warning:: For safety reasons, DO NOT install Red with a root user. Instead, `make a new one <https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/Step_by_Step_Guide/s1-starting-create-account.html>`_.
|
||||
|
||||
---------------------------
|
||||
Installing pre-requirements
|
||||
---------------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
yum -y groupinstall development
|
||||
yum -y install https://centos7.iuscommunity.org/ius-release.rpm
|
||||
yum -y install yum-utils wget which python36u python36u-pip python36u-devel openssl-devel libffi-devel git java-1.8.0-openjdk
|
||||
|
||||
--------------
|
||||
Installing Red
|
||||
--------------
|
||||
|
||||
Without audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot --user`
|
||||
|
||||
With audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot[voice] --user`
|
||||
|
||||
To install the development version (without audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot --user`
|
||||
|
||||
To install the development version (with audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot[voice] --user`
|
||||
|
||||
----------------------
|
||||
Setting up an instance
|
||||
----------------------
|
||||
|
||||
Run :code:`redbot-setup` and follow the prompts. It will ask first for where you want to
|
||||
store the data (the default is :code:`~/.local/share/Red-DiscordBot`) and will then ask
|
||||
for confirmation of that selection. Next, it will ask you to choose your storage backend
|
||||
(the default here is JSON). It will then ask for a name for your instance. This can be
|
||||
anything as long as it does not contain spaces; however, keep in mind that this is the
|
||||
name you will use to run your bot, and so it should be something you can remember.
|
||||
|
||||
-----------
|
||||
Running Red
|
||||
-----------
|
||||
|
||||
Run :code:`redbot <your instance name>` and run through the initial setup. This will ask for
|
||||
your token and a prefix.
|
||||
@@ -1,70 +0,0 @@
|
||||
.. debian install guide
|
||||
|
||||
================================
|
||||
Installing Red on Debian Stretch
|
||||
================================
|
||||
|
||||
.. warning:: For safety reasons, DO NOT install Red with a root user. Instead, `make a new one <https://manpages.debian.org/stretch/adduser/adduser.8.en.html>`_.
|
||||
|
||||
---------------------------
|
||||
Installing pre-requirements
|
||||
---------------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo apt install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev git unzip default-jre
|
||||
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
|
||||
|
||||
After that last command, you may see a warning about 'pyenv' not being in the load path. Follow the instructions given to fix that, then close and reopen your shell
|
||||
|
||||
Then run the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
CONFIGURE_OPTS=--enable-optimizations pyenv install 3.6.5 -v
|
||||
|
||||
This may take a long time to complete.
|
||||
|
||||
After that is finished, run:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
pyenv global 3.6.5
|
||||
|
||||
------------------
|
||||
Installing the bot
|
||||
------------------
|
||||
|
||||
To install without audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot`
|
||||
|
||||
To install with audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot[voice]`
|
||||
|
||||
To install the development version (without audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot`
|
||||
|
||||
To install the development version (with audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot[voice]`
|
||||
|
||||
------------------------
|
||||
Setting up your instance
|
||||
------------------------
|
||||
|
||||
Run :code:`redbot-setup` and follow the prompts. It will ask first for where you want to
|
||||
store the data (the default is :code:`~/.local/share/Red-DiscordBot`) and will then ask
|
||||
for confirmation of that selection. Next, it will ask you to choose your storage backend
|
||||
(the default here is JSON). It will then ask for a name for your instance. This can be
|
||||
anything as long as it does not contain spaces; however, keep in mind that this is the
|
||||
name you will use to run your bot, and so it should be something you can remember.
|
||||
|
||||
-----------
|
||||
Running Red
|
||||
-----------
|
||||
|
||||
Run :code:`redbot <your instance name>` and run through the initial setup. This will ask for
|
||||
your token and a prefix.
|
||||
@@ -0,0 +1,203 @@
|
||||
.. _linux-mac-install-guide:
|
||||
|
||||
==============================
|
||||
Installing Red on Linux or Mac
|
||||
==============================
|
||||
|
||||
.. warning::
|
||||
|
||||
For safety reasons, DO NOT install Red with a root user. If you are unsure how to create
|
||||
a new user, see the man page for the ``useradd`` command.
|
||||
|
||||
-------------------------------
|
||||
Installing the pre-requirements
|
||||
-------------------------------
|
||||
|
||||
Please install the pre-requirements using the commands listed for your operating system.
|
||||
|
||||
The pre-requirements are:
|
||||
- Python 3.6.2 or greater
|
||||
- pip 9.0 or greater
|
||||
- git
|
||||
- Java Runtime Environment 8 or later (for audio support)
|
||||
|
||||
~~~~~~~~~~
|
||||
Arch Linux
|
||||
~~~~~~~~~~
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo pacman -Syu python-pip git base-devel jre8-openjdk
|
||||
|
||||
~~~~~~~~
|
||||
CentOS 7
|
||||
~~~~~~~~
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
yum -y groupinstall development
|
||||
yum -y install https://centos7.iuscommunity.org/ius-release.rpm
|
||||
yum -y install yum-utils wget which python36u python36u-pip python36u-devel openssl-devel libffi-devel git java-1.8.0-openjdk
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Debian and Raspbian Stretch
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. warning::
|
||||
|
||||
Audio will not work on Raspberry Pi's **below** 2B. This is a CPU problem and
|
||||
*cannot* be fixed.
|
||||
|
||||
We recommend installing pyenv as a method of installing non-native versions of python on
|
||||
Debian/Raspbian Stretch. This guide will tell you how. First, run the following commands:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo apt install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev git unzip default-jre
|
||||
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
|
||||
|
||||
After that last command, you may see a warning about 'pyenv' not being in the load path. Follow the
|
||||
instructions given to fix that, then close and reopen your shell.
|
||||
|
||||
Then run the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
CONFIGURE_OPTS=--enable-optimizations pyenv install 3.7.0 -v
|
||||
|
||||
This may take a long time to complete.
|
||||
|
||||
After that is finished, run:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
pyenv global 3.7.0
|
||||
|
||||
Pyenv is now installed and your system should be configured to run Python 3.7.
|
||||
|
||||
~~~
|
||||
Mac
|
||||
~~~
|
||||
|
||||
Install Brew: in Finder or Spotlight, search for and open *Terminal*. In the terminal, paste the
|
||||
following, then press Enter:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||
|
||||
After the installation, install the required packages by pasting the commands and pressing enter,
|
||||
one-by-one:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
brew install python3 --with-brewed-openssl
|
||||
brew install git
|
||||
brew tap caskroom/versions
|
||||
brew cask install java8
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Ubuntu 18.04 Bionic Beaver
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo apt install python3.6-dev python3-pip build-essential libssl-dev libffi-dev git unzip default-jre -y
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Ubuntu 16.04 Xenial Xerus
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We recommend adding the ``deadsnakes`` apt repository to install Python 3.6.2 or greater:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo apt install software-properties-common
|
||||
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||
sudo apt update
|
||||
|
||||
Now, install python, pip, git and java with the following commands:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo apt install python3.6-dev build-essential libssl-dev libffi-dev git unzip default-jre wget -y
|
||||
wget https://bootstrap.pypa.io/get-pip.py
|
||||
sudo python3.6 get-pip.py
|
||||
|
||||
------------------------------
|
||||
Creating a Virtual Environment
|
||||
------------------------------
|
||||
|
||||
We **strongly** recommend installing Red into a virtual environment. See the section
|
||||
`installing-in-virtual-environment`.
|
||||
|
||||
.. _installing-red-linux-mac:
|
||||
|
||||
--------------
|
||||
Installing Red
|
||||
--------------
|
||||
|
||||
Choose one of the following commands to install Red.
|
||||
|
||||
.. note::
|
||||
|
||||
If you're not inside an activated virtual environment, include the ``--user`` flag with all
|
||||
``pip3`` commands.
|
||||
|
||||
To install without audio support:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
pip3 install -U --process-dependency-links --no-cache-dir Red-DiscordBot
|
||||
|
||||
Or, to install with audio support:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
pip3 install -U --process-dependency-links --no-cache-dir Red-DiscordBot[voice]
|
||||
|
||||
Or, install with audio and MongoDB support:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
pip3 install -U --process-dependency-links --no-cache-dir Red-DiscordBot[voice,mongo]
|
||||
|
||||
.. note::
|
||||
|
||||
To install the development version, replace ``Red-DiscordBot`` in the above commands with the
|
||||
following link:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot
|
||||
|
||||
--------------------------
|
||||
Setting Up and Running Red
|
||||
--------------------------
|
||||
|
||||
After installation, set up your instance with the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
redbot-setup
|
||||
|
||||
This will set the location where data will be stored, as well as your
|
||||
storage backend and the name of the instance (which will be used for
|
||||
running the bot).
|
||||
|
||||
Once done setting up the instance, run the following command to run Red:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
redbot <your instance name>
|
||||
|
||||
It will walk through the initial setup, asking for your token and a prefix.
|
||||
|
||||
You may also run Red via the launcher, which allows you to restart the bot
|
||||
from discord, and enable auto-restart. You may also update the bot from the
|
||||
launcher menu. Use the following command to run the launcher:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
redbot-launcher
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
.. mac install guide
|
||||
|
||||
=====================
|
||||
Installing Red on Mac
|
||||
=====================
|
||||
|
||||
---------------------------
|
||||
Installing pre-requirements
|
||||
---------------------------
|
||||
|
||||
* Install Brew
|
||||
* In Finder or Spotlight, search for and open terminal. In the window that will open, paste this:
|
||||
:code:`/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
|
||||
and press enter.
|
||||
* After the installation, install the required packages by pasting the commands and pressing enter, one-by-one:
|
||||
* :code:`brew install python3 --with-brewed-openssl`
|
||||
* :code:`brew install git`
|
||||
* :code:`brew tap caskroom/versions`
|
||||
* :code:`brew cask install java8`
|
||||
|
||||
--------------
|
||||
Installing Red
|
||||
--------------
|
||||
|
||||
Without audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot`
|
||||
|
||||
With audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot[voice]`
|
||||
|
||||
To install the development version (without audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot`
|
||||
|
||||
To install the development version (with audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot[voice]`
|
||||
|
||||
----------------------
|
||||
Setting up an instance
|
||||
----------------------
|
||||
|
||||
To set up an instance, run :code:`redbot-setup` and follow the steps there, providing the requested information
|
||||
or accepting the defaults. Keep in mind that the instance name will be the one you use when running the bot, so
|
||||
make it something you can remember
|
||||
|
||||
-----------
|
||||
Running Red
|
||||
-----------
|
||||
|
||||
Run :code:`redbot <your instance name>` and go through the initial setup (it will ask for the token and a prefix).
|
||||
@@ -1,72 +0,0 @@
|
||||
.. raspbian install guide
|
||||
|
||||
==================================
|
||||
Installing Red on Raspbian Stretch
|
||||
==================================
|
||||
|
||||
.. warning:: For safety reasons, DO NOT install Red with a root user. Instead, `make a new one <https://www.raspberrypi.org/documentation/linux/usage/users.md>`_.
|
||||
|
||||
---------------------------
|
||||
Installing pre-requirements
|
||||
---------------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo apt install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev git unzip default-jre
|
||||
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
|
||||
|
||||
After that last command, you may see a warning about 'pyenv' not being in the load path. Follow the instructions given to fix that, then close and reopen your shell
|
||||
|
||||
Then run the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
CONFIGURE_OPTS=--enable-optimizations pyenv install 3.6.5 -v
|
||||
|
||||
This may take a long time to complete.
|
||||
|
||||
After that is finished, run:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
pyenv global 3.6.5
|
||||
|
||||
--------------
|
||||
Installing Red
|
||||
--------------
|
||||
|
||||
Without audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot --user`
|
||||
|
||||
With audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot[voice] --user`
|
||||
|
||||
To install the development version (without audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot --user`
|
||||
|
||||
To install the development version (with audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot[voice] --user`
|
||||
|
||||
----------------------
|
||||
Setting up an instance
|
||||
----------------------
|
||||
|
||||
Run :code:`redbot-setup` and follow the prompts. It will ask first for where you want to
|
||||
store the data (the default is :code:`~/.local/share/Red-DiscordBot`) and will then ask
|
||||
for confirmation of that selection. Next, it will ask you to choose your storage backend
|
||||
(the default here is JSON). It will then ask for a name for your instance. This can be
|
||||
anything as long as it does not contain spaces; however, keep in mind that this is the
|
||||
name you will use to run your bot, and so it should be something you can remember.
|
||||
|
||||
-----------
|
||||
Running Red
|
||||
-----------
|
||||
|
||||
Run :code:`redbot <your instance name>` and run through the initial setup. This will ask for
|
||||
your token and a prefix.
|
||||
|
||||
.. warning:: Audio will not work on Raspberry Pi's **below** 2B. This is a CPU problem and *cannot* be fixed.
|
||||
@@ -1,54 +0,0 @@
|
||||
.. ubuntu bionic install guide
|
||||
|
||||
==============================
|
||||
Installing Red on Ubuntu 18.04
|
||||
==============================
|
||||
|
||||
.. warning:: For safety reasons, DO NOT install Red with a root user. Instead, `make a new one <http://manpages.ubuntu.com/manpages/artful/man8/adduser.8.html>`_.
|
||||
|
||||
-------------------------------
|
||||
Installing the pre-requirements
|
||||
-------------------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo apt install python3.6-dev python3-pip build-essential libssl-dev libffi-dev git unzip default-jre -y
|
||||
|
||||
|
||||
------------------
|
||||
Installing the bot
|
||||
------------------
|
||||
|
||||
To install without audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot --user`
|
||||
|
||||
To install with audio:
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links red-discordbot[voice] --user`
|
||||
|
||||
To install the development version (without audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot --user`
|
||||
|
||||
To install the development version (with audio):
|
||||
|
||||
:code:`pip3 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot[voice] --user`
|
||||
|
||||
------------------------
|
||||
Setting up your instance
|
||||
------------------------
|
||||
|
||||
Run :code:`redbot-setup` and follow the prompts. It will ask first for where you want to
|
||||
store the data (the default is :code:`~/.local/share/Red-DiscordBot`) and will then ask
|
||||
for confirmation of that selection. Next, it will ask you to choose your storage backend
|
||||
(the default here is JSON). It will then ask for a name for your instance. This can be
|
||||
anything as long as it does not contain spaces; however, keep in mind that this is the
|
||||
name you will use to run your bot, and so it should be something you can remember.
|
||||
|
||||
-----------
|
||||
Running Red
|
||||
-----------
|
||||
|
||||
Run :code:`redbot <your instance name>` and run through the initial setup. This will ask for
|
||||
your token and a prefix.
|
||||
@@ -1,59 +0,0 @@
|
||||
.. ubuntu xenial install guide
|
||||
|
||||
==============================
|
||||
Installing Red on Ubuntu 16.04
|
||||
==============================
|
||||
|
||||
.. warning:: For safety reasons, DO NOT install Red with a root user. Instead, `make a new one <http://manpages.ubuntu.com/manpages/artful/man8/adduser.8.html>`_.
|
||||
|
||||
-------------------------------
|
||||
Installing the pre-requirements
|
||||
-------------------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sudo apt install software-properties-common
|
||||
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||
sudo apt update
|
||||
sudo apt install python3.6-dev build-essential libssl-dev libffi-dev git unzip default-jre wget -y
|
||||
wget https://bootstrap.pypa.io/get-pip.py
|
||||
sudo python3.6 get-pip.py
|
||||
|
||||
|
||||
------------------
|
||||
Installing the bot
|
||||
------------------
|
||||
|
||||
To install without audio:
|
||||
|
||||
:code:`pip3.6 install -U --process-dependency-links red-discordbot --user`
|
||||
|
||||
To install with audio:
|
||||
|
||||
:code:`pip3.6 install -U --process-dependency-links red-discordbot[voice] --user`
|
||||
|
||||
To install the development version (without audio):
|
||||
|
||||
:code:`pip3.6 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot --user`
|
||||
|
||||
To install the development version (with audio):
|
||||
|
||||
:code:`pip3.6 install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot[voice] --user`
|
||||
|
||||
------------------------
|
||||
Setting up your instance
|
||||
------------------------
|
||||
|
||||
Run :code:`redbot-setup` and follow the prompts. It will ask first for where you want to
|
||||
store the data (the default is :code:`~/.local/share/Red-DiscordBot`) and will then ask
|
||||
for confirmation of that selection. Next, it will ask you to choose your storage backend
|
||||
(the default here is JSON). It will then ask for a name for your instance. This can be
|
||||
anything as long as it does not contain spaces; however, keep in mind that this is the
|
||||
name you will use to run your bot, and so it should be something you can remember.
|
||||
|
||||
-----------
|
||||
Running Red
|
||||
-----------
|
||||
|
||||
Run :code:`redbot <your instance name>` and run through the initial setup. This will ask for
|
||||
your token and a prefix.
|
||||
+64
-13
@@ -1,4 +1,4 @@
|
||||
.. windows installation docs
|
||||
.. _windows-install-guide:
|
||||
|
||||
=========================
|
||||
Installing Red on Windows
|
||||
@@ -8,7 +8,7 @@ Installing Red on Windows
|
||||
Needed Software
|
||||
---------------
|
||||
|
||||
* `Python <https://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
|
||||
@@ -21,23 +21,74 @@ Needed Software
|
||||
|
||||
.. attention:: Please choose the "Windows Online" installer
|
||||
|
||||
.. _installing-red-windows:
|
||||
|
||||
--------------
|
||||
Installing Red
|
||||
--------------
|
||||
|
||||
1. Open a command prompt (open Start, search for "command prompt", then click it)
|
||||
2. Run the appropriate command, depending on if you want audio or not
|
||||
2. Create and activate a virtual environment (strongly recommended), see the section `using-venv`
|
||||
3. Run **one** of the following commands, depending on what extras you want installed
|
||||
|
||||
* No audio: :code:`python -m pip install -U --process-dependency-links Red-DiscordBot`
|
||||
* Audio: :code:`python -m pip install -U --process-dependency-links Red-DiscordBot[voice]`
|
||||
* Development version (without audio): :code:`python -m pip install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot`
|
||||
* Development version (with audio): :code:`python -m pip install -U --process-dependency-links git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=red-discordbot[voice]`
|
||||
.. note::
|
||||
|
||||
3. Once that has completed, run :code:`redbot-setup` to set up your instance
|
||||
If you're not inside an activated virtual environment, include the ``--user`` flag with all
|
||||
``pip`` commands.
|
||||
|
||||
* This will set the location where data will be stored, as well as your
|
||||
storage backend and the name of the instance (which will be used for
|
||||
running the bot)
|
||||
* No audio:
|
||||
|
||||
4. Once done setting up the instance, run :code:`redbot <your instance name>` to run Red.
|
||||
It will walk through the initial setup, asking for your token and a prefix
|
||||
.. code-block:: none
|
||||
|
||||
python -m pip install -U --process-dependency-links --no-cache-dir Red-DiscordBot
|
||||
|
||||
* With audio:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
python -m pip install -U --process-dependency-links --no-cache-dir Red-DiscordBot[voice]
|
||||
|
||||
* With audio and MongoDB support:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
python -m pip install -U --process-dependency-links --no-cache-dir Red-DiscordBot[voice,mongo]
|
||||
|
||||
.. note::
|
||||
|
||||
To install the development version, replace ``Red-DiscordBot`` in the above commands with the
|
||||
following link:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot
|
||||
|
||||
--------------------------
|
||||
Setting Up and Running Red
|
||||
--------------------------
|
||||
|
||||
After installation, set up your instance with the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
redbot-setup
|
||||
|
||||
This will set the location where data will be stored, as well as your
|
||||
storage backend and the name of the instance (which will be used for
|
||||
running the bot).
|
||||
|
||||
Once done setting up the instance, run the following command to run Red:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
redbot <your instance name>
|
||||
|
||||
It will walk through the initial setup, asking for your token and a prefix.
|
||||
|
||||
You may also run Red via the launcher, which allows you to restart the bot
|
||||
from discord, and enable auto-restart. You may also update the bot from the
|
||||
launcher menu. Use the following command to run the launcher:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
redbot-launcher
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
-i https://pypi.org/simple
|
||||
alabaster==0.7.10
|
||||
attrs==18.1.0
|
||||
babel==2.5.3
|
||||
certifi==2018.4.16
|
||||
chardet==3.0.4
|
||||
docutils==0.14
|
||||
idna==2.6
|
||||
imagesize==1.0.0
|
||||
jinja2==2.10
|
||||
markupsafe==1.0
|
||||
more-itertools==4.1.0
|
||||
packaging==17.1
|
||||
pluggy==0.6.0
|
||||
py==1.5.3
|
||||
pygments==2.2.0
|
||||
pyparsing==2.2.0
|
||||
pytest-asyncio==0.8.0
|
||||
pytest==3.5.1
|
||||
pytz==2018.4
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
snowballstemmer==1.2.1
|
||||
sphinx-rtd-theme==0.3.1
|
||||
sphinx==1.7.4
|
||||
sphinxcontrib-asyncio==0.2.0
|
||||
sphinxcontrib-websupport==1.0.1
|
||||
urllib3==1.22
|
||||
git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py-1.0
|
||||
@@ -0,0 +1,132 @@
|
||||
.. _installing-in-virtual-environment:
|
||||
|
||||
=======================================
|
||||
Installing Red in a Virtual Environment
|
||||
=======================================
|
||||
Virtual environments allow you to isolate red's library dependencies, cog dependencies and python
|
||||
binaries from the rest of your system. It is strongly recommended you use this if you use python
|
||||
for more than just Red.
|
||||
|
||||
.. _using-venv:
|
||||
|
||||
--------------
|
||||
Using ``venv``
|
||||
--------------
|
||||
This is the quickest way to get your virtual environment up and running, as `venv` is shipped with
|
||||
python.
|
||||
|
||||
First, choose a directory where you would like to create your virtual environment. It's a good idea
|
||||
to keep it in a location which is easy to type out the path to. From now, we'll call it
|
||||
``path/to/venv/`` (or ``path\to\venv\`` on Windows).
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``venv`` on Linux or Mac
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Create your virtual environment with the following command::
|
||||
|
||||
python3 -m venv path/to/venv/
|
||||
|
||||
And activate it with the following command::
|
||||
|
||||
source path/to/venv/bin/activate
|
||||
|
||||
.. important::
|
||||
|
||||
You must activate the virtual environment with the above command every time you open a new
|
||||
shell to run, install or update Red.
|
||||
|
||||
Continue reading `below <after-activating-virtual-environment>`.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
``venv`` on Windows
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
Create your virtual environment with the following command::
|
||||
|
||||
python -m venv path\to\venv\
|
||||
|
||||
And activate it with the following command::
|
||||
|
||||
path\to\venv\Scripts\activate.bat
|
||||
|
||||
.. important::
|
||||
|
||||
You must activate the virtual environment with the above command every time you open a new
|
||||
Command Prompt to run, install or update Red.
|
||||
|
||||
Continue reading `below <after-activating-virtual-environment>`.
|
||||
|
||||
.. _using-pyenv-virtualenv:
|
||||
|
||||
--------------------------
|
||||
Using ``pyenv virtualenv``
|
||||
--------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This is for non-Windows users only.
|
||||
|
||||
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.2 or greater with the following command::
|
||||
|
||||
pyenv version
|
||||
|
||||
Now, create a virtual environment with the following command::
|
||||
|
||||
pyenv virtualenv <name>
|
||||
|
||||
Replace ``<name>`` with whatever you like. If you forget what you named it, use the command ``pyenv
|
||||
versions``.
|
||||
|
||||
Now activate your virtualenv with the following command::
|
||||
|
||||
pyenv shell <name>
|
||||
|
||||
.. important::
|
||||
|
||||
You must activate the virtual environment with the above command every time you open a new
|
||||
shell to run, install or update Red.
|
||||
|
||||
Continue reading `below <after-activating-virtual-environment>`.
|
||||
|
||||
.. _pyenv-installer: https://github.com/pyenv/pyenv-installer/blob/master/README.rst
|
||||
|
||||
----
|
||||
|
||||
.. _after-activating-virtual-environment:
|
||||
|
||||
Once activated, your ``PATH`` environment variable will be modified to use the virtual
|
||||
environment's python executables, as well as other executables like ``pip``.
|
||||
|
||||
From here, install Red using the commands listed on your installation guide (`Windows
|
||||
<installing-red-windows>` or `Non-Windows <installing-red-linux-mac>`).
|
||||
|
||||
.. note::
|
||||
|
||||
The alternative to activating the virtual environment each time you open a new shell is to
|
||||
provide the full path to the executable. This will automatically use the virtual environment's
|
||||
python interpreter and installed libraries.
|
||||
|
||||
--------------------------------------------
|
||||
Virtual Environments with Multiple Instances
|
||||
--------------------------------------------
|
||||
If you are running multiple instances of Red on the same machine, you have the option of either
|
||||
using the same virtual environment for all of them, or creating separate ones.
|
||||
|
||||
.. note::
|
||||
|
||||
This only applies for multiple instances of V3. If you are running a V2 instance as well,
|
||||
You **must** use separate virtual environments.
|
||||
|
||||
The advantages of using a *single* virtual environment for all of your V3 instances are:
|
||||
|
||||
- When updating Red, you will only need to update it once for all instances (however you will still need to restart all instances for the changes to take effect)
|
||||
- It will save space on your hard drive
|
||||
|
||||
On the other hand, you may wish to update each of your instances individually.
|
||||
|
||||
.. important::
|
||||
|
||||
Windows users with multiple instances should create *separate* virtual environments, as
|
||||
updating multiple running instances at once is likely to cause errors.
|
||||
@@ -1,37 +0,0 @@
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
interpreter = sys.executable
|
||||
print(interpreter)
|
||||
root_dir = os.getcwd()
|
||||
cogs = [i for i in os.listdir("redbot/cogs") if os.path.isdir(os.path.join("redbot/cogs", i))]
|
||||
for d in cogs:
|
||||
if "locales" in os.listdir(os.path.join("redbot/cogs", d)):
|
||||
os.chdir(os.path.join("redbot/cogs", d, "locales"))
|
||||
if "regen_messages.py" not in os.listdir(os.getcwd()):
|
||||
print(
|
||||
"Directory 'locales' exists for {} but no 'regen_messages.py' is available!".format(
|
||||
d
|
||||
)
|
||||
)
|
||||
exit(1)
|
||||
else:
|
||||
print("Running 'regen_messages.py' for {}".format(d))
|
||||
retval = subprocess.run([interpreter, "regen_messages.py"])
|
||||
if retval.returncode != 0:
|
||||
exit(1)
|
||||
os.chdir(root_dir)
|
||||
os.chdir("redbot/core/locales")
|
||||
print("Running 'regen_messages.py' for core")
|
||||
retval = subprocess.run([interpreter, "regen_messages.py"])
|
||||
if retval.returncode != 0:
|
||||
exit(1)
|
||||
os.chdir(root_dir)
|
||||
subprocess.run(["crowdin", "upload"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+4
-9
@@ -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.
|
||||
@@ -13,11 +14,5 @@ if discord.version_info.major < 1:
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if sys.version_info < (3, 6, 0):
|
||||
print(Back.RED + "[DEPRECATION WARNING]")
|
||||
print(
|
||||
Back.RED + "You are currently running Python 3.5."
|
||||
" Support for Python 3.5 will end with the release of beta 16."
|
||||
" Please update your environment to Python 3.6 as soon as possible to avoid"
|
||||
" any interruptions after the beta 16 release."
|
||||
)
|
||||
# Filter fuzzywuzzy slow sequence matcher warning
|
||||
warnings.filterwarnings("ignore", module=r"fuzzywuzzy.*")
|
||||
|
||||
+39
-21
@@ -6,19 +6,31 @@ import sys
|
||||
import discord
|
||||
from redbot.core.bot import Red, ExitCodes
|
||||
from redbot.core.cog_manager import CogManagerUI
|
||||
from redbot.core.data_manager import load_basic_configuration, config_file
|
||||
from redbot.core.data_manager import create_temp_config, load_basic_configuration, config_file
|
||||
from redbot.core.json_io import JsonIO
|
||||
from redbot.core.global_checks import init_global_checks
|
||||
from redbot.core.events import init_events
|
||||
from redbot.core.cli import interactive_config, confirm, parse_cli_flags, ask_sentry
|
||||
from redbot.core.core_commands import Core
|
||||
from redbot.core.dev_commands import Dev
|
||||
from redbot.core import rpc, __version__
|
||||
from redbot.core import __version__
|
||||
import asyncio
|
||||
import logging.handlers
|
||||
import logging
|
||||
import os
|
||||
|
||||
# Let's not force this dependency, uvloop is much faster on cpython
|
||||
if sys.implementation.name == "cpython":
|
||||
try:
|
||||
import uvloop
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
|
||||
if sys.platform == "win32":
|
||||
asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
||||
|
||||
|
||||
#
|
||||
# Red - Discord Bot v3
|
||||
@@ -40,7 +52,7 @@ def init_loggers(cli_flags):
|
||||
logger = logging.getLogger("red")
|
||||
|
||||
red_format = logging.Formatter(
|
||||
"%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: " "%(message)s",
|
||||
"%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: %(message)s",
|
||||
datefmt="[%d/%m/%Y %H:%M]",
|
||||
)
|
||||
|
||||
@@ -51,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
|
||||
|
||||
@@ -106,12 +118,20 @@ def main():
|
||||
elif cli_flags.version:
|
||||
print(description)
|
||||
sys.exit(0)
|
||||
elif not cli_flags.instance_name:
|
||||
elif not cli_flags.instance_name and not cli_flags.no_instance:
|
||||
print("Error: No instance name was provided!")
|
||||
sys.exit(1)
|
||||
if cli_flags.no_instance:
|
||||
print(
|
||||
"\033[1m"
|
||||
"Warning: The data will be placed in a temporary folder and removed on next system reboot."
|
||||
"\033[0m"
|
||||
)
|
||||
cli_flags.instance_name = "temporary_red"
|
||||
create_temp_config()
|
||||
load_basic_configuration(cli_flags.instance_name)
|
||||
log, sentry_log = init_loggers(cli_flags)
|
||||
red = Red(cli_flags, description=description, pm_help=None)
|
||||
red = Red(cli_flags=cli_flags, description=description, pm_help=None)
|
||||
init_global_checks(red)
|
||||
init_events(red, cli_flags)
|
||||
red.add_cog(Core(red))
|
||||
@@ -122,8 +142,10 @@ def main():
|
||||
tmp_data = {}
|
||||
loop.run_until_complete(_get_prefix_and_token(red, tmp_data))
|
||||
token = os.environ.get("RED_TOKEN", tmp_data["token"])
|
||||
if cli_flags.token:
|
||||
token = cli_flags.token
|
||||
prefix = cli_flags.prefix or tmp_data["prefix"]
|
||||
if token is None or not prefix:
|
||||
if not (token and prefix):
|
||||
if cli_flags.no_prompt is False:
|
||||
new_token = interactive_config(red, token_set=bool(token), prefix_set=bool(prefix))
|
||||
if new_token:
|
||||
@@ -138,18 +160,11 @@ def main():
|
||||
sys.exit(0)
|
||||
if tmp_data["enable_sentry"]:
|
||||
red.enable_sentry()
|
||||
cleanup_tasks = True
|
||||
try:
|
||||
loop.run_until_complete(red.start(token, bot=not cli_flags.not_bot))
|
||||
loop.run_until_complete(red.start(token, bot=True))
|
||||
except discord.LoginFailure:
|
||||
cleanup_tasks = False # No login happened, no need for this
|
||||
log.critical(
|
||||
"This token doesn't seem to be valid. If it belongs to "
|
||||
"a user account, remember that the --not-bot flag "
|
||||
"must be used. For self-bot functionalities instead, "
|
||||
"--self-bot"
|
||||
)
|
||||
db_token = red.db.token()
|
||||
log.critical("This token doesn't seem to be valid.")
|
||||
db_token = loop.run_until_complete(red.db.token())
|
||||
if db_token and not cli_flags.no_prompt:
|
||||
print("\nDo you want to reset the token? (y/n)")
|
||||
if confirm("> "):
|
||||
@@ -164,10 +179,13 @@ def main():
|
||||
sentry_log.critical("Fatal Exception", exc_info=e)
|
||||
loop.run_until_complete(red.logout())
|
||||
finally:
|
||||
if cleanup_tasks:
|
||||
pending = asyncio.Task.all_tasks(loop=red.loop)
|
||||
gathered = asyncio.gather(*pending, loop=red.loop, return_exceptions=True)
|
||||
gathered.cancel()
|
||||
pending = asyncio.Task.all_tasks(loop=red.loop)
|
||||
gathered = asyncio.gather(*pending, loop=red.loop, return_exceptions=True)
|
||||
gathered.cancel()
|
||||
try:
|
||||
red.rpc.server.close()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
sys.exit(red._shutdown_mode.value)
|
||||
|
||||
|
||||
+24
-23
@@ -1,9 +1,8 @@
|
||||
from typing import Tuple
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core import Config, checks
|
||||
from redbot.core import Config, checks, commands
|
||||
|
||||
import logging
|
||||
|
||||
@@ -40,7 +39,6 @@ RUNNING_ANNOUNCEMENT = (
|
||||
|
||||
|
||||
class Admin:
|
||||
|
||||
def __init__(self, config=Config):
|
||||
self.conf = config.get_conf(self, 8237492837454039, force_registration=True)
|
||||
|
||||
@@ -129,8 +127,8 @@ class Admin:
|
||||
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
||||
):
|
||||
"""
|
||||
Adds a role to a user. If user is left blank it defaults to the
|
||||
author of the command.
|
||||
Adds a role to a user.
|
||||
If user is left blank it defaults to the author of the command.
|
||||
"""
|
||||
if user is None:
|
||||
user = ctx.author
|
||||
@@ -138,7 +136,7 @@ class Admin:
|
||||
# noinspection PyTypeChecker
|
||||
await self._addrole(ctx, user, rolename)
|
||||
else:
|
||||
await self.complain(ctx, USER_HIERARCHY_ISSUE, member=ctx.author)
|
||||
await self.complain(ctx, USER_HIERARCHY_ISSUE, member=ctx.author, role=rolename)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@@ -147,8 +145,8 @@ class Admin:
|
||||
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
||||
):
|
||||
"""
|
||||
Removes a role from a user. If user is left blank it defaults to the
|
||||
author of the command.
|
||||
Removes a role from a user.
|
||||
If user is left blank it defaults to the author of the command.
|
||||
"""
|
||||
if user is None:
|
||||
user = ctx.author
|
||||
@@ -163,8 +161,7 @@ class Admin:
|
||||
@checks.admin_or_permissions(manage_roles=True)
|
||||
async def editrole(self, ctx: commands.Context):
|
||||
"""Edits roles settings"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@editrole.command(name="colour", aliases=["color"])
|
||||
async def editrole_colour(
|
||||
@@ -265,20 +262,16 @@ class Admin:
|
||||
@announce.command(name="ignore")
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def announce_ignore(self, ctx, *, guild: discord.Guild = None):
|
||||
async def announce_ignore(self, ctx):
|
||||
"""
|
||||
Toggles whether the announcements will ignore the given server.
|
||||
Defaults to the current server if none is provided.
|
||||
Toggles whether the announcements will ignore the current server.
|
||||
"""
|
||||
if guild is None:
|
||||
guild = ctx.guild
|
||||
|
||||
ignored = await self.conf.guild(guild).announce_ignore()
|
||||
await self.conf.guild(guild).announce_ignore.set(not ignored)
|
||||
ignored = await self.conf.guild(ctx.guild).announce_ignore()
|
||||
await self.conf.guild(ctx.guild).announce_ignore.set(not ignored)
|
||||
|
||||
verb = "will" if ignored else "will not"
|
||||
|
||||
await ctx.send("The server {} {} receive announcements.".format(guild.name, verb))
|
||||
await ctx.send(f"The server {ctx.guild.name} {verb} receive announcements.")
|
||||
|
||||
async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]:
|
||||
"""
|
||||
@@ -298,11 +291,13 @@ class Admin:
|
||||
# noinspection PyTypeChecker
|
||||
return valid_roles
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.group(invoke_without_command=True)
|
||||
async def selfrole(self, ctx: commands.Context, *, selfrole: SelfRole):
|
||||
"""
|
||||
Add a role to yourself that server admins have configured as
|
||||
user settable.
|
||||
Add a role to yourself that server admins have configured as user settable.
|
||||
|
||||
NOTE: The role is case sensitive!
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
await self._addrole(ctx, ctx.author, selfrole)
|
||||
@@ -311,15 +306,19 @@ class Admin:
|
||||
async def selfrole_remove(self, ctx: commands.Context, *, selfrole: SelfRole):
|
||||
"""
|
||||
Removes a selfrole from yourself.
|
||||
|
||||
NOTE: The role is case sensitive!
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
await self._removerole(ctx, ctx.author, selfrole)
|
||||
|
||||
@selfrole.command(name="add")
|
||||
@commands.has_permissions(manage_roles=True)
|
||||
@checks.admin_or_permissions(manage_roles=True)
|
||||
async def selfrole_add(self, ctx: commands.Context, *, role: discord.Role):
|
||||
"""
|
||||
Add a role to the list of available selfroles.
|
||||
|
||||
NOTE: The role is case sensitive!
|
||||
"""
|
||||
async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles:
|
||||
if role.id not in curr_selfroles:
|
||||
@@ -328,10 +327,12 @@ class Admin:
|
||||
await ctx.send("The selfroles list has been successfully modified.")
|
||||
|
||||
@selfrole.command(name="delete")
|
||||
@commands.has_permissions(manage_roles=True)
|
||||
@checks.admin_or_permissions(manage_roles=True)
|
||||
async def selfrole_delete(self, ctx: commands.Context, *, role: SelfRole):
|
||||
"""
|
||||
Removes a role from the list of available selfroles.
|
||||
|
||||
NOTE: The role is case sensitive!
|
||||
"""
|
||||
async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles:
|
||||
curr_selfroles.remove(role.id)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from redbot.core import commands
|
||||
|
||||
|
||||
class Announcer:
|
||||
|
||||
def __init__(self, ctx: commands.Context, message: str, config=None):
|
||||
"""
|
||||
:param ctx:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from redbot.core import commands
|
||||
|
||||
|
||||
class MemberDefaultAuthor(commands.Converter):
|
||||
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> discord.Member:
|
||||
member_converter = commands.MemberConverter()
|
||||
try:
|
||||
@@ -17,7 +16,6 @@ class MemberDefaultAuthor(commands.Converter):
|
||||
|
||||
|
||||
class SelfRole(commands.Converter):
|
||||
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> discord.Role:
|
||||
admin = ctx.command.instance
|
||||
if admin is None:
|
||||
@@ -30,5 +28,5 @@ class SelfRole(commands.Converter):
|
||||
role = await role_converter.convert(ctx, arg)
|
||||
|
||||
if role.id not in selfroles:
|
||||
raise commands.BadArgument("The provided role is not a valid" " selfrole.")
|
||||
raise commands.BadArgument("The provided role is not a valid selfrole.")
|
||||
return role
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../admin.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -1,6 +1,6 @@
|
||||
from .alias import Alias
|
||||
from discord.ext import commands
|
||||
from redbot.core.bot import Red
|
||||
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
def setup(bot: Red):
|
||||
bot.add_cog(Alias(bot))
|
||||
|
||||
@@ -174,16 +174,14 @@ class Alias:
|
||||
@commands.guild_only()
|
||||
async def alias(self, ctx: commands.Context):
|
||||
"""Manage per-server aliases for commands"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@alias.group(name="global")
|
||||
async def global_(self, ctx: commands.Context):
|
||||
"""
|
||||
Manage global aliases.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None or isinstance(ctx.invoked_subcommand, commands.Group):
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@checks.mod_or_permissions(manage_guild=True)
|
||||
@alias.command(name="add")
|
||||
@@ -233,9 +231,7 @@ class Alias:
|
||||
|
||||
await self.add_alias(ctx, alias_name, command)
|
||||
|
||||
await ctx.send(
|
||||
_("A new alias with the trigger `{}`" " has been created.").format(alias_name)
|
||||
)
|
||||
await ctx.send(_("A new alias with the trigger `{}` has been created.").format(alias_name))
|
||||
|
||||
@checks.is_owner()
|
||||
@global_.command(name="add")
|
||||
@@ -282,14 +278,14 @@ class Alias:
|
||||
await self.add_alias(ctx, alias_name, command, global_=True)
|
||||
|
||||
await ctx.send(
|
||||
_("A new global alias with the trigger `{}`" " has been created.").format(alias_name)
|
||||
_("A new global alias with the trigger `{}` has been created.").format(alias_name)
|
||||
)
|
||||
|
||||
@alias.command(name="help")
|
||||
@commands.guild_only()
|
||||
async def _help_alias(self, ctx: commands.Context, alias_name: str):
|
||||
"""Tries to execute help for the base command of the alias"""
|
||||
is_alias, alias = self.is_alias(ctx.guild, alias_name=alias_name)
|
||||
is_alias, alias = await self.is_alias(ctx.guild, alias_name=alias_name)
|
||||
if is_alias:
|
||||
base_cmd = alias.command[0]
|
||||
|
||||
@@ -307,9 +303,7 @@ class Alias:
|
||||
|
||||
if is_alias:
|
||||
await ctx.send(
|
||||
_("The `{}` alias will execute the" " command `{}`").format(
|
||||
alias_name, alias.command
|
||||
)
|
||||
_("The `{}` alias will execute the command `{}`").format(alias_name, alias.command)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("There is no alias with the name `{}`").format(alias_name))
|
||||
@@ -330,7 +324,7 @@ class Alias:
|
||||
|
||||
if await self.delete_alias(ctx, alias_name):
|
||||
await ctx.send(
|
||||
_("Alias with the name `{}` was successfully" " deleted.").format(alias_name)
|
||||
_("Alias with the name `{}` was successfully deleted.").format(alias_name)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
||||
@@ -350,7 +344,7 @@ class Alias:
|
||||
|
||||
if await self.delete_alias(ctx, alias_name, global_=True):
|
||||
await ctx.send(
|
||||
_("Alias with the name `{}` was successfully" " deleted.").format(alias_name)
|
||||
_("Alias with the name `{}` was successfully deleted.").format(alias_name)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
||||
|
||||
@@ -5,7 +5,6 @@ from redbot.core import commands
|
||||
|
||||
|
||||
class AliasEntry:
|
||||
|
||||
def __init__(
|
||||
self, name: str, command: Tuple[str], creator: discord.Member, global_: bool = False
|
||||
):
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../alias.py:129
|
||||
msgid "No prefix found."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:198
|
||||
msgid "You attempted to create a new alias with the name {} but that name is already a command on this bot."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:205
|
||||
msgid "You attempted to create a new alias with the name {} but that alias already exists on this server."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:212
|
||||
msgid "You attempted to create a new alias with the name {} but that name is an invalid alias name. Alias names may not contain spaces."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:224
|
||||
msgid "A new alias with the trigger `{}` has been created."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:236
|
||||
msgid "You attempted to create a new global alias with the name {} but that name is already a command on this bot."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:243
|
||||
msgid "You attempted to create a new global alias with the name {} but that alias already exists on this server."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:250
|
||||
msgid "You attempted to create a new global alias with the name {} but that name is an invalid alias name. Alias names may not contain spaces."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:259
|
||||
msgid "A new global alias with the trigger `{}` has been created."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:274
|
||||
msgid "No such alias exists."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:283
|
||||
msgid "The `{}` alias will execute the command `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:286
|
||||
msgid "There is no alias with the name `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:298
|
||||
msgid "There are no aliases on this guild."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:302 ../alias.py:320
|
||||
msgid "Alias with the name `{}` was successfully deleted."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:305 ../alias.py:323
|
||||
msgid "Alias with name `{}` was not found."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:316
|
||||
msgid "There are no aliases on this bot."
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:331 ../alias.py:342
|
||||
msgid "Aliases:"
|
||||
msgstr ""
|
||||
|
||||
#: ../alias.py:333 ../alias.py:344
|
||||
msgid "There are no aliases on this server."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../alias.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -1,22 +1,25 @@
|
||||
from pathlib import Path
|
||||
from aiohttp import ClientSession
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
from .audio import Audio
|
||||
from .manager import start_lavalink_server
|
||||
from discord.ext import commands
|
||||
from redbot.core import commands
|
||||
from redbot.core.data_manager import cog_data_path
|
||||
import redbot.core
|
||||
|
||||
log = logging.getLogger("red.audio")
|
||||
|
||||
LAVALINK_DOWNLOAD_URL = (
|
||||
"https://github.com/Cog-Creators/Red-DiscordBot/" "releases/download/{}/Lavalink.jar"
|
||||
"https://github.com/Cog-Creators/Red-DiscordBot/releases/download/{}/Lavalink.jar"
|
||||
).format(redbot.core.__version__)
|
||||
|
||||
LAVALINK_DOWNLOAD_DIR = cog_data_path(raw_name="Audio")
|
||||
LAVALINK_JAR_FILE = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar"
|
||||
|
||||
APP_YML_FILE = LAVALINK_DOWNLOAD_DIR / "application.yml"
|
||||
BUNDLED_APP_YML_FILE = Path(__file__).parent / "application.yml"
|
||||
BUNDLED_APP_YML_FILE = Path(__file__).parent / "data/application.yml"
|
||||
|
||||
|
||||
async def download_lavalink(session):
|
||||
@@ -33,15 +36,13 @@ async def maybe_download_lavalink(loop, cog):
|
||||
jar_exists = LAVALINK_JAR_FILE.exists()
|
||||
current_build = redbot.core.VersionInfo(*await cog.config.current_build())
|
||||
|
||||
session = ClientSession(loop=loop)
|
||||
|
||||
if not jar_exists or current_build < redbot.core.version_info:
|
||||
log.info("Downloading Lavalink.jar")
|
||||
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||
await download_lavalink(session)
|
||||
async with ClientSession(loop=loop) as session:
|
||||
await download_lavalink(session)
|
||||
await cog.config.current_build.set(redbot.core.version_info.to_json())
|
||||
|
||||
session.close()
|
||||
|
||||
shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
|
||||
|
||||
|
||||
@@ -52,4 +53,5 @@ async def setup(bot: commands.Bot):
|
||||
await start_lavalink_server(bot.loop)
|
||||
|
||||
bot.add_cog(cog)
|
||||
bot.loop.create_task(cog.disconnect_timer())
|
||||
bot.loop.create_task(cog.init_config())
|
||||
|
||||
+802
-189
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
|
||||
@@ -1,41 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../audio.py:25 ../audio.py:45
|
||||
msgid "Join a voice channel first!"
|
||||
msgstr ""
|
||||
|
||||
#: ../audio.py:33
|
||||
msgid "Let's play a file that exists pls"
|
||||
msgstr ""
|
||||
|
||||
#: ../audio.py:38 ../audio.py:58
|
||||
msgid "{} is playing a song..."
|
||||
msgstr ""
|
||||
|
||||
#: ../audio.py:48
|
||||
msgid "Youtube links pls"
|
||||
msgstr ""
|
||||
|
||||
#: ../audio.py:67 ../audio.py:77 ../audio.py:87 ../audio.py:97
|
||||
msgid "I'm not even connected to a voice channel!"
|
||||
msgstr ""
|
||||
|
||||
#: ../audio.py:95
|
||||
msgid "Volume set."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../audio.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -1,9 +1,14 @@
|
||||
import shlex
|
||||
import shutil
|
||||
import asyncio
|
||||
from subprocess import Popen, DEVNULL, PIPE
|
||||
import asyncio.subprocess
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
from subprocess import Popen, DEVNULL
|
||||
from typing import Optional, Tuple
|
||||
|
||||
_JavaVersion = Tuple[int, int]
|
||||
|
||||
log = logging.getLogger("red.audio.manager")
|
||||
|
||||
@@ -36,29 +41,48 @@ async def monitor_lavalink_server(loop):
|
||||
)
|
||||
|
||||
|
||||
async def has_java(loop):
|
||||
async def has_java(loop) -> Tuple[bool, Optional[_JavaVersion]]:
|
||||
java_available = shutil.which("java") is not None
|
||||
if not java_available:
|
||||
return False
|
||||
return False, None
|
||||
|
||||
version = await get_java_version(loop)
|
||||
return version >= (1, 8), version
|
||||
return (2, 0) > version >= (1, 8) or version >= (8, 0), version
|
||||
|
||||
|
||||
async def get_java_version(loop):
|
||||
async def get_java_version(loop) -> _JavaVersion:
|
||||
"""
|
||||
This assumes we've already checked that java exists.
|
||||
"""
|
||||
proc = Popen(shlex.split("java -version", posix=os.name == "posix"), stdout=PIPE, stderr=PIPE)
|
||||
_, err = proc.communicate()
|
||||
_proc: asyncio.subprocess.Process = await asyncio.create_subprocess_exec(
|
||||
"java",
|
||||
"-version",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
loop=loop,
|
||||
)
|
||||
# java -version outputs to stderr
|
||||
_, err = await _proc.communicate()
|
||||
|
||||
version_info = str(err, encoding="utf-8")
|
||||
version_info: str = err.decode("utf-8")
|
||||
# We expect the output to look something like:
|
||||
# $ java -version
|
||||
# ...
|
||||
# ... version "MAJOR.MINOR.PATCH[_BUILD]" ...
|
||||
# ...
|
||||
# We only care about the major and minor parts though.
|
||||
version_line_re = re.compile(r'version "(?P<major>\d+).(?P<minor>\d+).\d+(?:_\d+)?"')
|
||||
|
||||
version_line = version_info.split("\n")[0]
|
||||
version_start = version_line.find('"')
|
||||
version_string = version_line[version_start + 1 : -1]
|
||||
major, minor = version_string.split(".")[:2]
|
||||
return int(major), int(minor)
|
||||
lines = version_info.splitlines()
|
||||
for line in lines:
|
||||
match = version_line_re.search(line)
|
||||
if match:
|
||||
return int(match["major"]), int(match["minor"])
|
||||
|
||||
raise RuntimeError(
|
||||
"The output of `java -version` was unexpected. Please report this issue on Red's "
|
||||
"issue tracker."
|
||||
)
|
||||
|
||||
|
||||
async def start_lavalink_server(loop):
|
||||
|
||||
@@ -17,13 +17,15 @@ def check_global_setting_guildowner():
|
||||
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
if await ctx.bot.is_owner(author):
|
||||
return True
|
||||
if not await bank.is_global():
|
||||
if not isinstance(ctx.channel, discord.abc.GuildChannel):
|
||||
return False
|
||||
if await ctx.bot.is_owner(author):
|
||||
return True
|
||||
permissions = ctx.channel.permissions_for(author)
|
||||
return author == ctx.guild.owner or permissions.administrator
|
||||
else:
|
||||
return await ctx.bot.is_owner(author)
|
||||
|
||||
return commands.check(pred)
|
||||
|
||||
@@ -36,15 +38,17 @@ def check_global_setting_admin():
|
||||
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
if await ctx.bot.is_owner(author):
|
||||
return True
|
||||
if not await bank.is_global():
|
||||
if not isinstance(ctx.channel, discord.abc.GuildChannel):
|
||||
return False
|
||||
if await ctx.bot.is_owner(author):
|
||||
return True
|
||||
permissions = ctx.channel.permissions_for(author)
|
||||
is_guild_owner = author == ctx.guild.owner
|
||||
admin_role = await ctx.bot.db.guild(ctx.guild).admin_role()
|
||||
return admin_role in author.roles or is_guild_owner or permissions.manage_guild
|
||||
else:
|
||||
return await ctx.bot.is_owner(author)
|
||||
|
||||
return commands.check(pred)
|
||||
|
||||
@@ -58,8 +62,9 @@ class Bank:
|
||||
|
||||
# SECTION commands
|
||||
|
||||
@commands.group()
|
||||
@check_global_setting_guildowner()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
@commands.group(autohelp=True)
|
||||
async def bankset(self, ctx: commands.Context):
|
||||
"""Base command for bank settings"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
@@ -69,17 +74,15 @@ class Bank:
|
||||
default_balance = await bank._conf.default_balance()
|
||||
else:
|
||||
if not ctx.guild:
|
||||
await ctx.send_help()
|
||||
return
|
||||
bank_name = await bank._conf.guild(ctx.guild).bank_name()
|
||||
currency_name = await bank._conf.guild(ctx.guild).currency()
|
||||
default_balance = await bank._conf.guild(ctx.guild).default_balance()
|
||||
|
||||
settings = _(
|
||||
"Bank settings:\n\n" "Bank name: {}\n" "Currency: {}\n" "Default balance: {}" ""
|
||||
"Bank settings:\n\nBank name: {}\nCurrency: {}\nDefault balance: {}"
|
||||
).format(bank_name, currency_name, default_balance)
|
||||
await ctx.send(box(settings))
|
||||
await ctx.send_help()
|
||||
|
||||
@bankset.command(name="toggleglobal")
|
||||
@checks.is_owner()
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../bank.py:68
|
||||
msgid "global"
|
||||
msgstr ""
|
||||
|
||||
#: ../bank.py:68
|
||||
msgid "per-guild"
|
||||
msgstr ""
|
||||
|
||||
#: ../bank.py:70
|
||||
msgid "The bank is now {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../bank.py:77
|
||||
msgid "Bank's name has been set to {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../bank.py:84
|
||||
msgid "Currency name has been set to {}"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../bank.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
+83
-100
@@ -1,4 +1,6 @@
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Union, List, Callable
|
||||
|
||||
import discord
|
||||
|
||||
@@ -49,59 +51,65 @@ class Cleanup:
|
||||
|
||||
@staticmethod
|
||||
async def get_messages_for_deletion(
|
||||
ctx: commands.Context,
|
||||
*,
|
||||
channel: discord.TextChannel,
|
||||
number,
|
||||
check=lambda x: True,
|
||||
limit=100,
|
||||
before=None,
|
||||
after=None,
|
||||
delete_pinned=False,
|
||||
) -> list:
|
||||
number: int = None,
|
||||
check: Callable[[discord.Message], bool] = lambda x: True,
|
||||
before: Union[discord.Message, datetime] = None,
|
||||
after: Union[discord.Message, datetime] = None,
|
||||
delete_pinned: bool = False,
|
||||
) -> List[discord.Message]:
|
||||
"""
|
||||
Gets a list of messages meeting the requirements to be deleted.
|
||||
|
||||
Generally, the requirements are:
|
||||
- We don't have the number of messages to be deleted already
|
||||
- The message passes a provided check (if no check is provided,
|
||||
this is automatically true)
|
||||
- The message is less than 14 days old
|
||||
- The message is not pinned
|
||||
"""
|
||||
to_delete = []
|
||||
too_old = False
|
||||
|
||||
while not too_old and len(to_delete) - 1 < number:
|
||||
message = None
|
||||
async for message in channel.history(limit=limit, before=before, after=after):
|
||||
if (
|
||||
(not number or len(to_delete) - 1 < number)
|
||||
and check(message)
|
||||
and (ctx.message.created_at - message.created_at).days < 14
|
||||
and (delete_pinned or not message.pinned)
|
||||
):
|
||||
to_delete.append(message)
|
||||
elif (ctx.message.created_at - message.created_at).days >= 14:
|
||||
too_old = True
|
||||
break
|
||||
elif number and len(to_delete) >= number:
|
||||
break
|
||||
if message is None:
|
||||
Warning: Due to the way the API hands messages back in chunks,
|
||||
passing after and a number together is not advisable.
|
||||
If you need to accomplish this, you should filter messages on
|
||||
the entire applicable range, rather than use this utility.
|
||||
"""
|
||||
|
||||
# This isn't actually two weeks ago to allow some wiggle room on API limits
|
||||
two_weeks_ago = datetime.utcnow() - timedelta(days=14, minutes=-5)
|
||||
|
||||
def message_filter(message):
|
||||
return (
|
||||
check(message)
|
||||
and message.created_at > two_weeks_ago
|
||||
and (delete_pinned or not message.pinned)
|
||||
)
|
||||
|
||||
if after:
|
||||
if isinstance(after, discord.Message):
|
||||
after = after.created_at
|
||||
after = max(after, two_weeks_ago)
|
||||
|
||||
collected = []
|
||||
async for message in channel.history(
|
||||
limit=None, before=before, after=after, reverse=False
|
||||
):
|
||||
if message.created_at < two_weeks_ago:
|
||||
break
|
||||
else:
|
||||
before = message
|
||||
return to_delete
|
||||
if check(message):
|
||||
collected.append(message)
|
||||
if number and number <= len(collected):
|
||||
break
|
||||
|
||||
return collected
|
||||
|
||||
@commands.group()
|
||||
@checks.mod_or_permissions(manage_messages=True)
|
||||
async def cleanup(self, ctx: commands.Context):
|
||||
"""Deletes messages."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def text(
|
||||
self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False
|
||||
):
|
||||
@@ -113,8 +121,11 @@ class Cleanup:
|
||||
Remember to use double quotes."""
|
||||
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
|
||||
author = ctx.author
|
||||
is_bot = self.bot.user.bot
|
||||
|
||||
if number > 100:
|
||||
cont = await self.check_100_plus(ctx, number)
|
||||
@@ -130,28 +141,22 @@ class Cleanup:
|
||||
return False
|
||||
|
||||
to_delete = await self.get_messages_for_deletion(
|
||||
ctx,
|
||||
channel,
|
||||
number,
|
||||
channel=channel,
|
||||
number=number,
|
||||
check=check,
|
||||
limit=1000,
|
||||
before=ctx.message,
|
||||
delete_pinned=delete_pinned,
|
||||
)
|
||||
|
||||
reason = "{}({}) deleted {} messages " " containing '{}' in channel {}.".format(
|
||||
reason = "{}({}) deleted {} messages containing '{}' in channel {}.".format(
|
||||
author.name, author.id, len(to_delete), text, channel.id
|
||||
)
|
||||
log.info(reason)
|
||||
|
||||
if is_bot:
|
||||
await mass_purge(to_delete, channel)
|
||||
else:
|
||||
await slow_deletion(to_delete)
|
||||
await mass_purge(to_delete, channel)
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def user(
|
||||
self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False
|
||||
):
|
||||
@@ -160,6 +165,10 @@ class Cleanup:
|
||||
Examples:
|
||||
cleanup user @\u200bTwentysix 2
|
||||
cleanup user Red 6"""
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
|
||||
member = None
|
||||
try:
|
||||
@@ -172,9 +181,7 @@ class Cleanup:
|
||||
else:
|
||||
_id = member.id
|
||||
|
||||
channel = ctx.channel
|
||||
author = ctx.author
|
||||
is_bot = self.bot.user.bot
|
||||
|
||||
if number > 100:
|
||||
cont = await self.check_100_plus(ctx, number)
|
||||
@@ -190,11 +197,9 @@ class Cleanup:
|
||||
return False
|
||||
|
||||
to_delete = await self.get_messages_for_deletion(
|
||||
ctx,
|
||||
channel,
|
||||
number,
|
||||
channel=channel,
|
||||
number=number,
|
||||
check=check,
|
||||
limit=1000,
|
||||
before=ctx.message,
|
||||
delete_pinned=delete_pinned,
|
||||
)
|
||||
@@ -205,15 +210,10 @@ class Cleanup:
|
||||
)
|
||||
log.info(reason)
|
||||
|
||||
if is_bot:
|
||||
# For whatever reason the purge endpoint requires manage_messages
|
||||
await mass_purge(to_delete, channel)
|
||||
else:
|
||||
await slow_deletion(to_delete)
|
||||
await mass_purge(to_delete, channel)
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool = False):
|
||||
"""Deletes all messages after specified message.
|
||||
|
||||
@@ -225,24 +225,21 @@ class Cleanup:
|
||||
"""
|
||||
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
author = ctx.author
|
||||
is_bot = self.bot.user.bot
|
||||
|
||||
if not is_bot:
|
||||
await ctx.send(_("This command can only be used on bots with " "bot accounts."))
|
||||
return
|
||||
|
||||
after = await channel.get_message(message_id)
|
||||
|
||||
if not after:
|
||||
await ctx.send(_("Message not found."))
|
||||
return
|
||||
try:
|
||||
after = await channel.get_message(message_id)
|
||||
except discord.NotFound:
|
||||
return await ctx.send(_("Message not found."))
|
||||
|
||||
to_delete = await self.get_messages_for_deletion(
|
||||
ctx, channel, 0, limit=None, after=after, delete_pinned=delete_pinned
|
||||
channel=channel, number=None, after=after, delete_pinned=delete_pinned
|
||||
)
|
||||
|
||||
reason = "{}({}) deleted {} messages in channel {}." "".format(
|
||||
reason = "{}({}) deleted {} messages in channel {}.".format(
|
||||
author.name, author.id, len(to_delete), channel.name
|
||||
)
|
||||
log.info(reason)
|
||||
@@ -251,7 +248,6 @@ class Cleanup:
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||
"""Deletes last X messages.
|
||||
|
||||
@@ -259,39 +255,38 @@ class Cleanup:
|
||||
cleanup messages 26"""
|
||||
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
author = ctx.author
|
||||
|
||||
is_bot = self.bot.user.bot
|
||||
|
||||
if number > 100:
|
||||
cont = await self.check_100_plus(ctx, number)
|
||||
if not cont:
|
||||
return
|
||||
|
||||
to_delete = await self.get_messages_for_deletion(
|
||||
ctx, channel, number, limit=1000, before=ctx.message, delete_pinned=delete_pinned
|
||||
channel=channel, number=number, before=ctx.message, delete_pinned=delete_pinned
|
||||
)
|
||||
to_delete.append(ctx.message)
|
||||
|
||||
reason = "{}({}) deleted {} messages in channel {}." "".format(
|
||||
reason = "{}({}) deleted {} messages in channel {}.".format(
|
||||
author.name, author.id, number, channel.name
|
||||
)
|
||||
log.info(reason)
|
||||
|
||||
if is_bot:
|
||||
await mass_purge(to_delete, channel)
|
||||
else:
|
||||
await slow_deletion(to_delete)
|
||||
await mass_purge(to_delete, channel)
|
||||
|
||||
@cleanup.command(name="bot")
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||
"""Cleans up command messages and messages from the bot."""
|
||||
|
||||
channel = ctx.message.channel
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
author = ctx.message.author
|
||||
is_bot = self.bot.user.bot
|
||||
|
||||
if number > 100:
|
||||
cont = await self.check_100_plus(ctx, number)
|
||||
@@ -318,11 +313,9 @@ class Cleanup:
|
||||
return False
|
||||
|
||||
to_delete = await self.get_messages_for_deletion(
|
||||
ctx,
|
||||
channel,
|
||||
number,
|
||||
channel=channel,
|
||||
number=number,
|
||||
check=check,
|
||||
limit=1000,
|
||||
before=ctx.message,
|
||||
delete_pinned=delete_pinned,
|
||||
)
|
||||
@@ -335,10 +328,7 @@ class Cleanup:
|
||||
)
|
||||
log.info(reason)
|
||||
|
||||
if is_bot:
|
||||
await mass_purge(to_delete, channel)
|
||||
else:
|
||||
await slow_deletion(to_delete)
|
||||
await mass_purge(to_delete, channel)
|
||||
|
||||
@cleanup.command(name="self")
|
||||
async def cleanup_self(
|
||||
@@ -360,7 +350,6 @@ class Cleanup:
|
||||
"""
|
||||
channel = ctx.channel
|
||||
author = ctx.message.author
|
||||
is_bot = self.bot.user.bot
|
||||
|
||||
if number > 100:
|
||||
cont = await self.check_100_plus(ctx, number)
|
||||
@@ -400,20 +389,14 @@ class Cleanup:
|
||||
return False
|
||||
|
||||
to_delete = await self.get_messages_for_deletion(
|
||||
ctx,
|
||||
channel,
|
||||
number,
|
||||
channel=channel,
|
||||
number=number,
|
||||
check=check,
|
||||
limit=1000,
|
||||
before=ctx.message,
|
||||
delete_pinned=delete_pinned,
|
||||
)
|
||||
|
||||
# Selfbot convenience, delete trigger message
|
||||
if author == self.bot.user:
|
||||
to_delete.append(ctx.message)
|
||||
|
||||
if channel.name:
|
||||
if ctx.guild:
|
||||
channel_name = "channel " + channel.name
|
||||
else:
|
||||
channel_name = str(channel)
|
||||
@@ -425,7 +408,7 @@ class Cleanup:
|
||||
)
|
||||
log.info(reason)
|
||||
|
||||
if is_bot and can_mass_purge:
|
||||
if can_mass_purge:
|
||||
await mass_purge(to_delete, channel)
|
||||
else:
|
||||
await slow_deletion(to_delete)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../cleanup.py:150
|
||||
msgid "This command can only be used on bots with bot accounts."
|
||||
msgstr ""
|
||||
|
||||
#: ../cleanup.py:157
|
||||
msgid "Message not found."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../cleanup.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -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,8 +27,11 @@ class AlreadyExists(CCError):
|
||||
pass
|
||||
|
||||
|
||||
class CommandObj:
|
||||
class ArgParseError(CCError):
|
||||
pass
|
||||
|
||||
|
||||
class CommandObj:
|
||||
def __init__(self, **kwargs):
|
||||
config = kwargs.get("config")
|
||||
self.bot = kwargs.get("bot")
|
||||
@@ -43,7 +49,7 @@ class CommandObj:
|
||||
intro = _(
|
||||
"Welcome to the interactive random {} maker!\n"
|
||||
"Every message you send will be added as one of the random "
|
||||
"response to choose from once this {} is "
|
||||
"responses to choose from once this {} is "
|
||||
"triggered. To exit this interactive menu, type `{}`"
|
||||
).format("customcommand", "customcommand", "exit()")
|
||||
await ctx.send(intro)
|
||||
@@ -52,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)
|
||||
@@ -59,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
|
||||
|
||||
@@ -70,15 +86,17 @@ 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"]
|
||||
|
||||
async def create(self, ctx: commands.Context, command: str, response):
|
||||
"""Create a customcommand"""
|
||||
"""Create a custom command"""
|
||||
# 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},
|
||||
@@ -111,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()
|
||||
|
||||
@@ -132,6 +153,7 @@ class CommandObj:
|
||||
@cog_i18n(_)
|
||||
class CustomCommands:
|
||||
"""Custom commands
|
||||
|
||||
Creates commands used to display text"""
|
||||
|
||||
def __init__(self, bot):
|
||||
@@ -141,42 +163,31 @@ class CustomCommands:
|
||||
self.config.register_guild(commands={})
|
||||
self.commandobj = CommandObj(config=self.config, bot=self.bot)
|
||||
|
||||
@commands.group(aliases=["cc"], no_pm=True)
|
||||
@commands.group(aliases=["cc"])
|
||||
@commands.guild_only()
|
||||
async def customcom(self, ctx: commands.Context):
|
||||
"""Custom commands management"""
|
||||
if not ctx.invoked_subcommand:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@customcom.group(name="add")
|
||||
@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
|
||||
"""
|
||||
if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand, commands.Group):
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@cc_add.command(name="random")
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def cc_add_random(self, ctx: commands.Context, command: str):
|
||||
"""
|
||||
Create a CC where it will randomly choose a response!
|
||||
|
||||
Note: This is interactive
|
||||
"""
|
||||
channel = ctx.channel
|
||||
responses = []
|
||||
|
||||
responses = await self.commandobj.get_responses(ctx=ctx)
|
||||
@@ -185,7 +196,7 @@ class CustomCommands:
|
||||
await ctx.send(_("Custom command successfully added."))
|
||||
except AlreadyExists:
|
||||
await ctx.send(
|
||||
_("This command already exists. Use " "`{}` to edit it.").format(
|
||||
_("This command already exists. Use `{}` to edit it.").format(
|
||||
"{}customcom edit".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
@@ -196,10 +207,10 @@ class CustomCommands:
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def cc_add_simple(self, ctx, command: str, *, text):
|
||||
"""Adds a simple custom command
|
||||
|
||||
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."))
|
||||
@@ -209,19 +220,21 @@ class CustomCommands:
|
||||
await ctx.send(_("Custom command successfully added."))
|
||||
except AlreadyExists:
|
||||
await ctx.send(
|
||||
_("This command already exists. Use " "`{}` to edit it.").format(
|
||||
_("This command already exists. Use `{}` to edit it.").format(
|
||||
"{}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)
|
||||
async def cc_edit(self, ctx, command: str, *, text=None):
|
||||
"""Edits a custom command
|
||||
|
||||
Example:
|
||||
[p]customcom edit yourcommand Text you want
|
||||
"""
|
||||
guild = ctx.message.guild
|
||||
command = command.lower()
|
||||
|
||||
try:
|
||||
@@ -229,10 +242,12 @@ class CustomCommands:
|
||||
await ctx.send(_("Custom command successfully edited."))
|
||||
except NotFound:
|
||||
await ctx.send(
|
||||
_("That command doesn't exist. Use " "`{}` to add it.").format(
|
||||
_("That command doesn't exist. Use `{}` to add it.").format(
|
||||
"{}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)
|
||||
@@ -240,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)
|
||||
@@ -285,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:
|
||||
"""
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../customcom.py:44
|
||||
msgid ""
|
||||
"Welcome to the interactive random {} maker!\n"
|
||||
"Every message you send will be added as one of the random response to choose from once this {} is triggered. To exit this interactive menu, type `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:56
|
||||
msgid "Add a random response:"
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:119
|
||||
msgid "Do you want to create a 'randomized' cc? {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:126
|
||||
msgid "What response do you want?"
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:205 ../customcom.py:235
|
||||
msgid "Custom command successfully added."
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:207 ../customcom.py:237
|
||||
msgid "This command already exists. Use `{}` to edit it."
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:229
|
||||
msgid "That command is already a standard command."
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:261
|
||||
msgid "Custom command successfully edited."
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:263
|
||||
msgid "That command doesn't exist. Use `{}` to add it."
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:282
|
||||
msgid "Custom command successfully deleted."
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:284
|
||||
msgid "That command doesn't exist."
|
||||
msgstr ""
|
||||
|
||||
#: ../customcom.py:294
|
||||
msgid "There are no custom commands in this guild. Use `{}` to start adding some."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../customcom.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -119,11 +119,10 @@ class SpecResolver(object):
|
||||
def past_nicknames_conv_spec(self, data: dict):
|
||||
flatscoped = self.apply_scope(Config.MEMBER, self.flatten_dict(data))
|
||||
ret = {}
|
||||
for k, v in flatscoped.items():
|
||||
outerkey, innerkey = (*k[:-1],), (k[-1],)
|
||||
if outerkey not in ret:
|
||||
ret[outerkey] = {}
|
||||
ret[outerkey].update({innerkey: v})
|
||||
for config_identifiers, v2data in flatscoped.items():
|
||||
if config_identifiers not in ret:
|
||||
ret[config_identifiers] = {}
|
||||
ret[config_identifiers].update({("past_nicks",): v2data})
|
||||
return ret
|
||||
|
||||
def customcom_conv_spec(self, data: dict):
|
||||
@@ -144,18 +143,28 @@ class SpecResolver(object):
|
||||
ret[outerkey].update({innerkey: ccinfo})
|
||||
return ret
|
||||
|
||||
async def convert(self, bot: Red, prettyname: str):
|
||||
if prettyname not in self.available:
|
||||
raise NotImplementedError("No Conversion Specs for this")
|
||||
|
||||
info = self.available_core_conversions[prettyname]
|
||||
filepath, converter = info["file"], info["converter"]
|
||||
(cogname, attr, _id) = info["cfg"]
|
||||
def get_config_object(self, bot, cogname, attr, _id):
|
||||
try:
|
||||
config = getattr(bot.get_cog(cogname), attr)
|
||||
except (TypeError, AttributeError):
|
||||
config = Config.get_conf(None, _id, cog_name=cogname)
|
||||
|
||||
return config
|
||||
|
||||
def get_conversion_info(self, prettyname: str):
|
||||
info = self.available_core_conversions[prettyname]
|
||||
filepath, converter = info["file"], info["converter"]
|
||||
(cogname, attr, _id) = info["cfg"]
|
||||
return filepath, converter, cogname, attr, _id
|
||||
|
||||
async def convert(self, bot: Red, prettyname: str, config=None):
|
||||
if prettyname not in self.available:
|
||||
raise NotImplementedError("No Conversion Specs for this")
|
||||
|
||||
filepath, converter, cogname, attr, _id = self.get_conversion_info(prettyname)
|
||||
if config is None:
|
||||
config = self.get_config_object(bot, cogname, attr, _id)
|
||||
|
||||
try:
|
||||
items = converter(dc.json_load(filepath))
|
||||
await dc(config).dict_import(items)
|
||||
|
||||
@@ -41,7 +41,7 @@ class DataConverter:
|
||||
)
|
||||
)
|
||||
while resolver.available:
|
||||
menu = _("Please select a set of data to import by number" ", or 'exit' to exit")
|
||||
menu = _("Please select a set of data to import by number, or 'exit' to exit")
|
||||
for index, entry in enumerate(resolver.available, 1):
|
||||
menu += "\n{}. {}".format(index, entry)
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-03-12 04:35+EDT\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../dataconverter.py:38
|
||||
msgid "There don't seem to be any data files I know how to handle here. Are you sure you gave me the base installation path?"
|
||||
msgstr ""
|
||||
|
||||
#: ../dataconverter.py:43
|
||||
msgid "Please select a set of data to import by number, or 'exit' to exit"
|
||||
msgstr ""
|
||||
|
||||
#: ../dataconverter.py:59
|
||||
msgid "Try this again when you are more ready"
|
||||
msgstr ""
|
||||
|
||||
#: ../dataconverter.py:70
|
||||
msgid "That wasn't a valid choice."
|
||||
msgstr ""
|
||||
|
||||
#: ../dataconverter.py:76
|
||||
msgid "{} converted."
|
||||
msgstr ""
|
||||
|
||||
#: ../dataconverter.py:80
|
||||
msgid ""
|
||||
"There isn't anything else I know how to convert here.\n"
|
||||
"There might be more things I can convert in the future."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../dataconverter.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -1,9 +1,9 @@
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from redbot.core import commands
|
||||
|
||||
__all__ = ["install_agreement"]
|
||||
__all__ = ["do_install_agreement"]
|
||||
|
||||
REPO_INSTALL_MSG = (
|
||||
"You're about to add a 3rd party repository. The creator of Red"
|
||||
@@ -16,33 +16,21 @@ REPO_INSTALL_MSG = (
|
||||
)
|
||||
|
||||
|
||||
def install_agreement():
|
||||
|
||||
async def pred(ctx: commands.Context):
|
||||
downloader = ctx.command.instance
|
||||
if downloader is None:
|
||||
return True
|
||||
elif downloader.already_agreed:
|
||||
return True
|
||||
elif ctx.invoked_subcommand is None or isinstance(ctx.invoked_subcommand, commands.Group):
|
||||
return True
|
||||
|
||||
def does_agree(msg: discord.Message):
|
||||
return (
|
||||
ctx.author == msg.author
|
||||
and ctx.channel == msg.channel
|
||||
and msg.content == "I agree"
|
||||
)
|
||||
|
||||
await ctx.send(REPO_INSTALL_MSG)
|
||||
|
||||
try:
|
||||
await ctx.bot.wait_for("message", check=does_agree, timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("Your response has timed out, please try again.")
|
||||
return False
|
||||
|
||||
downloader.already_agreed = True
|
||||
async def do_install_agreement(ctx: commands.Context):
|
||||
downloader = ctx.cog
|
||||
if downloader is None or downloader.already_agreed:
|
||||
return True
|
||||
|
||||
return commands.check(pred)
|
||||
def does_agree(msg: discord.Message):
|
||||
return ctx.author == msg.author and ctx.channel == msg.channel and msg.content == "I agree"
|
||||
|
||||
await ctx.send(REPO_INSTALL_MSG)
|
||||
|
||||
try:
|
||||
await ctx.bot.wait_for("message", check=does_agree, timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("Your response has timed out, please try again.")
|
||||
return False
|
||||
|
||||
downloader.already_agreed = True
|
||||
return True
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from .repo_manager import RepoManager
|
||||
from redbot.core import commands
|
||||
from .installable import Installable
|
||||
|
||||
|
||||
class InstalledCog(commands.Converter):
|
||||
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> Installable:
|
||||
downloader = ctx.bot.get_cog("Downloader")
|
||||
if downloader is None:
|
||||
|
||||
@@ -15,7 +15,7 @@ from redbot.core.utils.chat_formatting import box, pagify
|
||||
from redbot.core import commands
|
||||
|
||||
from redbot.core.bot import Red
|
||||
from .checks import install_agreement
|
||||
from .checks import do_install_agreement
|
||||
from .converters import InstalledCog
|
||||
from .errors import CloningError, ExistingGitRepo
|
||||
from .installable import Installable
|
||||
@@ -27,7 +27,6 @@ _ = Translator("Downloader", __file__)
|
||||
|
||||
@cog_i18n(_)
|
||||
class Downloader:
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
|
||||
@@ -54,7 +53,7 @@ class Downloader:
|
||||
|
||||
async def cog_install_path(self):
|
||||
"""Get the current cog install path.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
pathlib.Path
|
||||
@@ -65,7 +64,7 @@ class Downloader:
|
||||
|
||||
async def installed_cogs(self) -> Tuple[Installable]:
|
||||
"""Get info on installed cogs.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
`tuple` of `Installable`
|
||||
@@ -78,7 +77,7 @@ class Downloader:
|
||||
|
||||
async def _add_to_installed(self, cog: Installable):
|
||||
"""Mark a cog as installed.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cog : Installable
|
||||
@@ -94,7 +93,7 @@ class Downloader:
|
||||
|
||||
async def _remove_from_installed(self, cog: Installable):
|
||||
"""Remove a cog from the saved list of installed cogs.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cog : Installable
|
||||
@@ -211,11 +210,9 @@ class Downloader:
|
||||
"""
|
||||
Command group for managing Downloader repos.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@repo.command(name="add")
|
||||
@install_agreement()
|
||||
async def _repo_add(self, ctx, name: str, repo_url: str, branch: str = None):
|
||||
"""
|
||||
Add a new repo to Downloader.
|
||||
@@ -223,6 +220,9 @@ class Downloader:
|
||||
Name can only contain characters A-z, numbers and underscore
|
||||
Branch will default to master if not specified
|
||||
"""
|
||||
agreed = await do_install_agreement(ctx)
|
||||
if not agreed:
|
||||
return
|
||||
try:
|
||||
# noinspection PyTypeChecker
|
||||
repo = await self._repo_manager.add_repo(name=name, url=repo_url, branch=branch)
|
||||
@@ -234,7 +234,7 @@ class Downloader:
|
||||
else:
|
||||
await ctx.send(_("Repo `{}` successfully added.").format(name))
|
||||
if repo.install_msg is not None:
|
||||
await ctx.send(repo.install_msg)
|
||||
await ctx.send(repo.install_msg.replace("[p]", ctx.prefix))
|
||||
|
||||
@repo.command(name="delete")
|
||||
async def _repo_del(self, ctx, repo_name: Repo):
|
||||
@@ -278,8 +278,7 @@ class Downloader:
|
||||
"""
|
||||
Command group for managing installable Cogs.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@cog.command(name="install")
|
||||
async def _cog_install(self, ctx, repo_name: Repo, cog_name: str):
|
||||
@@ -289,24 +288,22 @@ class Downloader:
|
||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name) # type: Installable
|
||||
if cog is None:
|
||||
await ctx.send(
|
||||
_("Error, there is no cog by the name of" " `{}` in the `{}` repo.").format(
|
||||
_("Error, there is no cog by the name of `{}` in the `{}` repo.").format(
|
||||
cog_name, repo_name.name
|
||||
)
|
||||
)
|
||||
return
|
||||
elif cog.min_python_version > sys.version_info:
|
||||
await ctx.send(
|
||||
_(
|
||||
"This cog requires at least python version {}, aborting install.".format(
|
||||
".".join([str(n) for n in cog.min_python_version])
|
||||
)
|
||||
_("This cog requires at least python version {}, aborting install.").format(
|
||||
".".join([str(n) for n in cog.min_python_version])
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if not await repo_name.install_requirements(cog, self.LIB_PATH):
|
||||
await ctx.send(
|
||||
_("Failed to install the required libraries for" " `{}`: `{}`").format(
|
||||
_("Failed to install the required libraries for `{}`: `{}`").format(
|
||||
cog.name, cog.requirements
|
||||
)
|
||||
)
|
||||
@@ -320,7 +317,7 @@ class Downloader:
|
||||
|
||||
await ctx.send(_("`{}` cog successfully installed.").format(cog_name))
|
||||
if cog.install_msg is not None:
|
||||
await ctx.send(cog.install_msg)
|
||||
await ctx.send(cog.install_msg.replace("[p]", ctx.prefix))
|
||||
|
||||
@cog.command(name="uninstall")
|
||||
async def _cog_uninstall(self, ctx, cog_name: InstalledCog):
|
||||
@@ -381,11 +378,25 @@ class Downloader:
|
||||
"""
|
||||
Lists all available cogs from a single repo.
|
||||
"""
|
||||
installed = await self.installed_cogs()
|
||||
installed_str = ""
|
||||
if installed:
|
||||
installed_str = _("Installed Cogs:\n") + "\n".join(
|
||||
[
|
||||
"- {}{}".format(i.name, ": {}".format(i.short) if i.short else "")
|
||||
for i in installed
|
||||
if i.repo_name == repo_name.name
|
||||
]
|
||||
)
|
||||
cogs = repo_name.available_cogs
|
||||
cogs = _("Available Cogs:\n") + "\n".join(
|
||||
["+ {}: {}".format(c.name, c.short or "") for c in cogs]
|
||||
[
|
||||
"+ {}: {}".format(c.name, c.short or "")
|
||||
for c in cogs
|
||||
if not (c.hidden or c in installed)
|
||||
]
|
||||
)
|
||||
|
||||
cogs = cogs + "\n\n" + installed_str
|
||||
for page in pagify(cogs, ["\n"], shorten_by=16):
|
||||
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
||||
|
||||
@@ -401,7 +412,9 @@ class Downloader:
|
||||
)
|
||||
return
|
||||
|
||||
msg = _("Information on {}:\n{}").format(cog.name, cog.description or "")
|
||||
msg = _("Information on {}:\n{}\n\nRequirements: {}").format(
|
||||
cog.name, cog.description or "", ", ".join(cog.requirements) or "None"
|
||||
)
|
||||
await ctx.send(box(msg))
|
||||
|
||||
async def is_installed(
|
||||
@@ -437,7 +450,7 @@ class Downloader:
|
||||
Name of the command which belongs to the cog.
|
||||
cog_installable : `Installable` or `object`
|
||||
Can be an `Installable` instance or a Cog instance.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
@@ -462,12 +475,12 @@ class Downloader:
|
||||
"""Determines the cog name that Downloader knows from the cog instance.
|
||||
|
||||
Probably.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
instance : object
|
||||
The cog instance.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
|
||||
@@ -17,6 +17,7 @@ class DownloaderException(Exception):
|
||||
"""
|
||||
Base class for Downloader exceptions.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -31,6 +32,7 @@ class InvalidRepoName(DownloaderException):
|
||||
Throw when a repo name is invalid. Check
|
||||
the message for a more detailed reason.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -39,6 +41,7 @@ class ExistingGitRepo(DownloaderException):
|
||||
Thrown when trying to clone into a folder where a
|
||||
git repo already exists.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -47,6 +50,7 @@ class MissingGitRepo(DownloaderException):
|
||||
Thrown when a git repo is expected to exist but
|
||||
does not.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -54,6 +58,7 @@ class CloningError(GitException):
|
||||
"""
|
||||
Thrown when git clone returns a non zero exit code.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -62,6 +67,7 @@ class CurrentHashError(GitException):
|
||||
Thrown when git returns a non zero exit code attempting
|
||||
to determine the current commit hash.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -70,6 +76,7 @@ class HardResetError(GitException):
|
||||
Thrown when there is an issue trying to execute a hard reset
|
||||
(usually prior to a repo update).
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -77,6 +84,7 @@ class UpdateError(GitException):
|
||||
"""
|
||||
Thrown when git pull returns a non zero error code.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -84,6 +92,7 @@ class GitDiffError(GitException):
|
||||
"""
|
||||
Thrown when a git diff fails.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -91,4 +100,5 @@ class PipError(DownloaderException):
|
||||
"""
|
||||
Thrown when pip returns a non-zero return code.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@@ -3,9 +3,8 @@ import distutils.dir_util
|
||||
import shutil
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import MutableMapping, Any
|
||||
from typing import MutableMapping, Any, TYPE_CHECKING
|
||||
|
||||
from redbot.core.utils import TYPE_CHECKING
|
||||
from .log import log
|
||||
from .json_mixins import RepoJSONMixin
|
||||
|
||||
@@ -25,7 +24,7 @@ class Installable(RepoJSONMixin):
|
||||
- Modules
|
||||
- Repo Libraries
|
||||
- Other stuff?
|
||||
|
||||
|
||||
The attributes of this class will mostly come from the installation's
|
||||
info.json.
|
||||
|
||||
@@ -76,6 +75,7 @@ class Installable(RepoJSONMixin):
|
||||
self.bot_version = (3, 0, 0)
|
||||
self.min_python_version = (3, 5, 1)
|
||||
self.hidden = False
|
||||
self.disabled = False
|
||||
self.required_cogs = {} # Cog name -> repo URL
|
||||
self.requirements = ()
|
||||
self.tags = ()
|
||||
@@ -117,7 +117,7 @@ class Installable(RepoJSONMixin):
|
||||
try:
|
||||
copy_func(src=str(self._location), dst=str(target_dir / self._location.stem))
|
||||
except:
|
||||
log.exception("Error occurred when copying path:" " {}".format(self._location))
|
||||
log.exception("Error occurred when copying path: {}".format(self._location))
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -146,9 +146,7 @@ class Installable(RepoJSONMixin):
|
||||
info = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
info = {}
|
||||
log.exception(
|
||||
"Invalid JSON information file at path:" " {}".format(info_file_path)
|
||||
)
|
||||
log.exception("Invalid JSON information file at path: {}".format(info_file_path))
|
||||
else:
|
||||
self._info = info
|
||||
|
||||
@@ -176,6 +174,12 @@ class Installable(RepoJSONMixin):
|
||||
hidden = False
|
||||
self.hidden = hidden
|
||||
|
||||
try:
|
||||
disabled = bool(info.get("disabled", False))
|
||||
except ValueError:
|
||||
disabled = False
|
||||
self.disabled = disabled
|
||||
|
||||
self.required_cogs = info.get("required_cogs", {})
|
||||
|
||||
self.requirements = info.get("requirements", ())
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../downloader.py:215
|
||||
msgid "That git repo has already been added under another name."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:217 ../downloader.py:218
|
||||
msgid "Something went wrong during the cloning process."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:220
|
||||
msgid "Repo `{}` successfully added."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:229
|
||||
msgid "The repo `{}` has been deleted successfully."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:237
|
||||
msgid ""
|
||||
"Installed Repos:\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:258
|
||||
msgid "Error, there is no cog by the name of `{}` in the `{}` repo."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:263
|
||||
msgid "Failed to install the required libraries for `{}`: `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:273
|
||||
msgid "`{}` cog successfully installed."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:289
|
||||
msgid "`{}` was successfully removed."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:291
|
||||
msgid "That cog was installed but can no longer be located. You may need to remove it's files manually if it is still usable."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:315
|
||||
msgid "Cog update completed successfully."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:323
|
||||
msgid ""
|
||||
"Available Cogs:\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:335
|
||||
msgid "There is no cog `{}` in the repo `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:340
|
||||
msgid ""
|
||||
"Information on {}:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:381
|
||||
msgid "Missing from info.json"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:390
|
||||
msgid ""
|
||||
"Command: {}\n"
|
||||
"Made by: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Cog name: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:422
|
||||
msgid "That command doesn't seem to exist."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../downloader.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -2,17 +2,13 @@ import asyncio
|
||||
import functools
|
||||
import os
|
||||
import pkgutil
|
||||
import shutil
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
from subprocess import run as sp_run, PIPE
|
||||
from sys import executable
|
||||
from typing import Tuple, MutableMapping, Union
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core import Config
|
||||
from redbot.core import data_manager
|
||||
from redbot.core import data_manager, commands
|
||||
from redbot.core.utils import safe_delete
|
||||
from .errors import *
|
||||
from .installable import Installable, InstallableType
|
||||
@@ -27,10 +23,8 @@ class Repo(RepoJSONMixin):
|
||||
GIT_LATEST_COMMIT = "git -C {path} rev-parse {branch}"
|
||||
GIT_HARD_RESET = "git -C {path} reset --hard origin/{branch} -q"
|
||||
GIT_PULL = "git -C {path} pull -q --ff-only"
|
||||
GIT_DIFF_FILE_STATUS = (
|
||||
"git -C {path} diff --no-commit-id --name-status" " {old_hash} {new_hash}"
|
||||
)
|
||||
GIT_LOG = "git -C {path} log --relative-date --reverse {old_hash}.." " {relative_file_path}"
|
||||
GIT_DIFF_FILE_STATUS = "git -C {path} diff --no-commit-id --name-status {old_hash} {new_hash}"
|
||||
GIT_LOG = "git -C {path} log --relative-date --reverse {old_hash}.. {relative_file_path}"
|
||||
GIT_DISCOVER_REMOTE_URL = "git -C {path} config --get remote.origin.url"
|
||||
|
||||
PIP_INSTALL = "{python} -m pip install -U -t {target_dir} {reqs}"
|
||||
@@ -98,7 +92,7 @@ class Repo(RepoJSONMixin):
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise GitDiffError("Git diff failed for repo at path:" " {}".format(self.folder_path))
|
||||
raise GitDiffError("Git diff failed for repo at path: {}".format(self.folder_path))
|
||||
|
||||
stdout = p.stdout.strip().decode().split("\n")
|
||||
|
||||
@@ -222,7 +216,7 @@ class Repo(RepoJSONMixin):
|
||||
|
||||
if p.returncode != 0:
|
||||
raise GitException(
|
||||
"Could not determine current branch" " at path: {}".format(self.folder_path)
|
||||
"Could not determine current branch at path: {}".format(self.folder_path)
|
||||
)
|
||||
|
||||
return p.stdout.decode().strip()
|
||||
@@ -234,7 +228,7 @@ class Repo(RepoJSONMixin):
|
||||
----------
|
||||
branch : `str`, optional
|
||||
Override for repo's branch attribute.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
@@ -383,7 +377,7 @@ class Repo(RepoJSONMixin):
|
||||
Directory to install shared libraries to.
|
||||
libraries : `tuple` of `Installable`
|
||||
A subset of available libraries.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
@@ -405,7 +399,7 @@ class Repo(RepoJSONMixin):
|
||||
|
||||
async def install_requirements(self, cog: Installable, target_dir: Path) -> bool:
|
||||
"""Install a cog's requirements.
|
||||
|
||||
|
||||
Requirements will be installed via pip directly into
|
||||
:code:`target_dir`.
|
||||
|
||||
@@ -467,12 +461,12 @@ class Repo(RepoJSONMixin):
|
||||
@property
|
||||
def available_cogs(self) -> Tuple[Installable]:
|
||||
"""`tuple` of `installable` : All available cogs in this Repo.
|
||||
|
||||
|
||||
This excludes hidden or shared packages.
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(
|
||||
[m for m in self.available_modules if m.type == InstallableType.COG and not m.hidden]
|
||||
[m for m in self.available_modules if m.type == InstallableType.COG and not m.disabled]
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -495,7 +489,6 @@ class Repo(RepoJSONMixin):
|
||||
|
||||
|
||||
class RepoManager:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._repos = {}
|
||||
|
||||
@@ -9,7 +9,8 @@ import discord
|
||||
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
|
||||
from redbot.core import Config, bank, commands
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
from redbot.core.utils.chat_formatting import pagify, box
|
||||
from redbot.core.utils.chat_formatting import box
|
||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||
|
||||
from redbot.core.bot import Red
|
||||
|
||||
@@ -74,7 +75,6 @@ SLOT_PAYOUTS_MSG = _(
|
||||
|
||||
|
||||
def guild_only_check():
|
||||
|
||||
async def pred(ctx: commands.Context):
|
||||
if await bank.is_global():
|
||||
return True
|
||||
@@ -87,7 +87,6 @@ def guild_only_check():
|
||||
|
||||
|
||||
class SetParser:
|
||||
|
||||
def __init__(self, argument):
|
||||
allowed = ("+", "-")
|
||||
self.sum = int(argument)
|
||||
@@ -139,11 +138,11 @@ class Economy:
|
||||
self.config.register_role(**self.default_role_settings)
|
||||
self.slot_register = defaultdict(dict)
|
||||
|
||||
@guild_only_check()
|
||||
@commands.group(name="bank")
|
||||
async def _bank(self, ctx: commands.Context):
|
||||
"""Bank operations"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@_bank.command()
|
||||
async def balance(self, ctx: commands.Context, user: discord.Member = None):
|
||||
@@ -212,7 +211,6 @@ class Economy:
|
||||
)
|
||||
|
||||
@_bank.command()
|
||||
@guild_only_check()
|
||||
@check_global_setting_guildowner()
|
||||
async def reset(self, ctx, confirmation: bool = False):
|
||||
"""Deletes bank accounts"""
|
||||
@@ -228,13 +226,13 @@ class Economy:
|
||||
else:
|
||||
await bank.wipe_bank()
|
||||
await ctx.send(
|
||||
_("All bank accounts for {} have been " "deleted.").format(
|
||||
_("All bank accounts for {} have been deleted.").format(
|
||||
self.bot.user.name if await bank.is_global() else "this server"
|
||||
)
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
@commands.command()
|
||||
async def payday(self, ctx: commands.Context):
|
||||
"""Get some free currency"""
|
||||
author = ctx.author
|
||||
@@ -254,7 +252,7 @@ class Economy:
|
||||
_(
|
||||
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
||||
"You currently have {3} {1}.\n\n"
|
||||
"You are currently #{4} on the leaderboard!"
|
||||
"You are currently #{4} on the global leaderboard!"
|
||||
).format(
|
||||
author,
|
||||
credits_name,
|
||||
@@ -267,7 +265,7 @@ class Economy:
|
||||
else:
|
||||
dtime = self.display_time(next_payday - cur_time)
|
||||
await ctx.send(
|
||||
_("{} Too soon. For your next payday you have to" " wait {}.").format(
|
||||
_("{} Too soon. For your next payday you have to wait {}.").format(
|
||||
author.mention, dtime
|
||||
)
|
||||
)
|
||||
@@ -301,7 +299,7 @@ class Economy:
|
||||
else:
|
||||
dtime = self.display_time(next_payday - cur_time)
|
||||
await ctx.send(
|
||||
_("{} Too soon. For your next payday you have to" " wait {}.").format(
|
||||
_("{} Too soon. For your next payday you have to wait {}.").format(
|
||||
author.mention, dtime
|
||||
)
|
||||
)
|
||||
@@ -312,8 +310,8 @@ class Economy:
|
||||
"""Prints out the leaderboard
|
||||
|
||||
Defaults to top 10"""
|
||||
# Originally coded by Airenkun - edited by irdumb, rewritten by Palm__ for v3
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
if top < 1:
|
||||
top = 10
|
||||
if (
|
||||
@@ -323,25 +321,25 @@ class Economy:
|
||||
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
|
||||
if len(bank_sorted) < top:
|
||||
top = len(bank_sorted)
|
||||
highscore = ""
|
||||
for pos, acc in enumerate(bank_sorted, 1):
|
||||
pos = pos
|
||||
poswidth = 2
|
||||
name = acc[1]["name"]
|
||||
namewidth = 35
|
||||
balance = acc[1]["balance"]
|
||||
balwidth = 2
|
||||
highscore += "{pos: <{poswidth}} {name: <{namewidth}s} {balance: >{balwidth}}\n".format(
|
||||
pos=pos,
|
||||
poswidth=poswidth,
|
||||
name=name,
|
||||
namewidth=namewidth,
|
||||
balance=balance,
|
||||
balwidth=balwidth,
|
||||
header = f"{f'#':4}{f'Name':36}{f'Score':2}\n"
|
||||
highscores = [
|
||||
(
|
||||
f"{f'{pos}.': <{3 if pos < 10 else 2}} {acc[1]['name']: <{35}s} "
|
||||
f"{acc[1]['balance']: >{2 if pos < 10 else 1}}\n"
|
||||
)
|
||||
if highscore != "":
|
||||
for page in pagify(highscore, shorten_by=12):
|
||||
await ctx.send(box(page, lang="py"))
|
||||
if acc[0] != author.id
|
||||
else (
|
||||
f"{f'{pos}.': <{3 if pos < 10 else 2}} <<{acc[1]['name'] + '>>': <{33}s} "
|
||||
f"{acc[1]['balance']: >{2 if pos < 10 else 1}}\n"
|
||||
)
|
||||
for pos, acc in enumerate(bank_sorted, 1)
|
||||
]
|
||||
if highscores:
|
||||
pages = [
|
||||
f"```md\n{header}{''.join(''.join(highscores[x:x + 10]))}```"
|
||||
for x in range(0, len(highscores), 10)
|
||||
]
|
||||
await menu(ctx, pages, DEFAULT_CONTROLS)
|
||||
else:
|
||||
await ctx.send(_("There are no accounts in the bank."))
|
||||
|
||||
@@ -427,7 +425,7 @@ class Economy:
|
||||
now = then - bid + pay
|
||||
await bank.set_balance(author, now)
|
||||
await channel.send(
|
||||
_("{}\n{} {}\n\nYour bid: {}\n{} → {}!" "").format(
|
||||
_("{}\n{} {}\n\nYour bid: {}\n{} → {}!").format(
|
||||
slot, author.mention, payout["phrase"], bid, then, now
|
||||
)
|
||||
)
|
||||
@@ -436,7 +434,7 @@ class Economy:
|
||||
await bank.withdraw_credits(author, bid)
|
||||
now = then - bid
|
||||
await channel.send(
|
||||
_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!" "").format(
|
||||
_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!").format(
|
||||
slot, author.mention, bid, then, now
|
||||
)
|
||||
)
|
||||
@@ -448,7 +446,6 @@ class Economy:
|
||||
"""Changes economy module settings"""
|
||||
guild = ctx.guild
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
if await bank.is_global():
|
||||
slot_min = await self.config.SLOT_MIN()
|
||||
slot_max = await self.config.SLOT_MAX()
|
||||
@@ -497,7 +494,7 @@ class Economy:
|
||||
"""Maximum slot machine bid"""
|
||||
slot_min = await self.config.SLOT_MIN()
|
||||
if bid < 1 or bid < slot_min:
|
||||
await ctx.send(_("Invalid slotmax bid amount. Must be greater" " than slotmin."))
|
||||
await ctx.send(_("Invalid slotmax bid amount. Must be greater than slotmin."))
|
||||
return
|
||||
guild = ctx.guild
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
@@ -526,9 +523,7 @@ class Economy:
|
||||
else:
|
||||
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
||||
await ctx.send(
|
||||
_("Value modified. At least {} seconds must pass " "between each payday.").format(
|
||||
seconds
|
||||
)
|
||||
_("Value modified. At least {} seconds must pass between each payday.").format(seconds)
|
||||
)
|
||||
|
||||
@economyset.command()
|
||||
@@ -543,7 +538,7 @@ class Economy:
|
||||
await self.config.PAYDAY_CREDITS.set(creds)
|
||||
else:
|
||||
await self.config.guild(guild).PAYDAY_CREDITS.set(creds)
|
||||
await ctx.send(_("Every payday will now give {} {}." "").format(creds, credits_name))
|
||||
await ctx.send(_("Every payday will now give {} {}.").format(creds, credits_name))
|
||||
|
||||
@economyset.command()
|
||||
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
||||
@@ -555,7 +550,7 @@ class Economy:
|
||||
else:
|
||||
await self.config.role(role).PAYDAY_CREDITS.set(creds)
|
||||
await ctx.send(
|
||||
_("Every payday will now give {} {} to people with the role {}." "").format(
|
||||
_("Every payday will now give {} {} to people with the role {}.").format(
|
||||
creds, credits_name, role.name
|
||||
)
|
||||
)
|
||||
@@ -569,7 +564,7 @@ class Economy:
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
await bank.set_default_balance(creds, guild)
|
||||
await ctx.send(
|
||||
_("Registering an account will now give {} {}." "").format(creds, credits_name)
|
||||
_("Registering an account will now give {} {}.").format(creds, credits_name)
|
||||
)
|
||||
|
||||
# What would I ever do without stackoverflow?
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../economy.py:40
|
||||
msgid "JACKPOT! 226! Your bid has been multiplied * 2500!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:44
|
||||
msgid "4LC! +1000!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:48
|
||||
msgid "Three cherries! +800!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:52
|
||||
msgid "2 6! Your bid has been multiplied * 4!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:56
|
||||
msgid "Two cherries! Your bid has been multiplied * 3!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:60
|
||||
msgid "Three symbols! +500!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:64
|
||||
msgid "Two consecutive symbols! Your bid has been multiplied * 2!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:68
|
||||
msgid ""
|
||||
"Slot machine payouts:\n"
|
||||
"{two.value} {two.value} {six.value} Bet * 2500\n"
|
||||
"{flc.value} {flc.value} {flc.value} +1000\n"
|
||||
"{cherries.value} {cherries.value} {cherries.value} +800\n"
|
||||
"{two.value} {six.value} Bet * 4\n"
|
||||
"{cherries.value} {cherries.value} Bet * 3\n"
|
||||
"\n"
|
||||
"Three symbols: +500\n"
|
||||
"Two symbols: Bet * 2"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:157
|
||||
msgid "{}'s balance is {} {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:171
|
||||
msgid "{} transferred {} {} to {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:191
|
||||
msgid "{} added {} {} to {}'s account."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:196
|
||||
msgid "{} removed {} {} from {}'s account."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:201
|
||||
msgid "{} set {}'s account to {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:212
|
||||
msgid ""
|
||||
"This will delete all bank accounts for {}.\n"
|
||||
"If you're sure, type `{}bank reset yes`"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:229
|
||||
msgid "All bank accounts of this guild have been deleted."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:248 ../economy.py:268
|
||||
msgid "{} Here, take some {}. Enjoy! (+{} {}!)"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:258 ../economy.py:276
|
||||
msgid "{} Too soon. For your next payday you have to wait {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:313
|
||||
msgid "There are no accounts in the bank."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:339
|
||||
msgid "You're on cooldown, try again in a bit."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:342
|
||||
msgid "That's an invalid bid amount, sorry :/"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:345
|
||||
msgid "You ain't got enough money, friend."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:391
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"{} {}\n"
|
||||
"\n"
|
||||
"Your bid: {}\n"
|
||||
"{} → {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:398
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"{} Nothing!\n"
|
||||
"Your bid: {}\n"
|
||||
"{} → {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:423
|
||||
msgid ""
|
||||
"Minimum slot bid: {}\n"
|
||||
"Maximum slot bid: {}\n"
|
||||
"Slot cooldown: {}\n"
|
||||
"Payday amount: {}\n"
|
||||
"Payday cooldown: {}\n"
|
||||
"Amount given at account registration: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:433
|
||||
msgid "Current Economy settings:"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:441
|
||||
msgid "Invalid min bid amount."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:449
|
||||
msgid "Minimum bid is now {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:456
|
||||
msgid "Invalid slotmax bid amount. Must be greater than slotmin."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:465
|
||||
msgid "Maximum bid is now {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:475
|
||||
msgid "Cooldown is now {} seconds."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:485
|
||||
msgid "Value modified. At least {} seconds must pass between each payday."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:494
|
||||
msgid "Har har so funny."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:500
|
||||
msgid "Every payday will now give {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:511
|
||||
msgid "Registering an account will now give {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:517
|
||||
msgid "weeks"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:518
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:519
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:520
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:521
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../economy.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -49,7 +49,6 @@ class Filter:
|
||||
Using this command with no subcommands will send
|
||||
the list of the server's filtered words."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
server = ctx.guild
|
||||
author = ctx.author
|
||||
word_list = await self.settings.guild(server).filter()
|
||||
@@ -79,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."))
|
||||
@@ -109,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."))
|
||||
@@ -124,36 +123,35 @@ class Filter:
|
||||
|
||||
@_filter.command(name="names")
|
||||
async def filter_names(self, ctx: commands.Context):
|
||||
"""
|
||||
Toggles whether or not to check names and nicknames against the filter
|
||||
"""Toggles whether or not to check names and nicknames against the filter
|
||||
|
||||
This is disabled by default
|
||||
"""
|
||||
guild = ctx.guild
|
||||
current_setting = await self.settings.guild(guild).filter_names()
|
||||
await self.settings.guild(guild).filter_names.set(not current_setting)
|
||||
if current_setting:
|
||||
await ctx.send(
|
||||
_("Names and nicknames will no longer be " "checked against the filter")
|
||||
)
|
||||
await ctx.send(_("Names and nicknames will no longer be checked against the filter."))
|
||||
else:
|
||||
await ctx.send(_("Names and nicknames will now be checked against " "the filter"))
|
||||
await ctx.send(_("Names and nicknames will now be checked against the filter."))
|
||||
|
||||
@_filter.command(name="defaultname")
|
||||
async def filter_default_name(self, ctx: commands.Context, name: str):
|
||||
"""
|
||||
Sets the default name to use if filtering names is enabled
|
||||
"""Sets the default name to use if filtering names is enabled
|
||||
|
||||
Note that this has no effect if filtering names is disabled
|
||||
|
||||
The default name used is John Doe
|
||||
"""
|
||||
guild = ctx.guild
|
||||
await self.settings.guild(guild).filter_default_name.set(name)
|
||||
await ctx.send(_("The name to use on filtered names has been set"))
|
||||
await ctx.send(_("The name to use on filtered names has been set."))
|
||||
|
||||
@_filter.command(name="ban")
|
||||
async def filter_ban(self, ctx: commands.Context, count: int, timeframe: int):
|
||||
"""
|
||||
Sets up an autoban if the specified number of messages are
|
||||
filtered in the specified amount of time (in seconds)
|
||||
"""Autobans if the specified number of messages are filtered in the timeframe
|
||||
|
||||
The timeframe is represented by seconds.
|
||||
"""
|
||||
if (count <= 0) != (timeframe <= 0):
|
||||
await ctx.send(
|
||||
@@ -223,7 +221,7 @@ class Filter:
|
||||
user_count >= filter_count
|
||||
and message.created_at.timestamp() < next_reset_time
|
||||
):
|
||||
reason = "Autoban (too many filtered messages)"
|
||||
reason = "Autoban (too many filtered messages.)"
|
||||
try:
|
||||
await server.ban(author, reason=reason)
|
||||
except:
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../filter.py:62
|
||||
msgid "Filtered in this server:"
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:67
|
||||
msgid "I can't send direct messages to you."
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:96
|
||||
msgid "Words added to filter."
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:98
|
||||
msgid "Words already in the filter."
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:127
|
||||
msgid "Words removed from filter."
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:129
|
||||
msgid "Those words weren't in the filter."
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:142
|
||||
msgid "Names and nicknames will no longer be checked against the filter"
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:147
|
||||
msgid "Names and nicknames will now be checked against the filter"
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:160
|
||||
msgid "The name to use on filtered names has been set"
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:171
|
||||
msgid "Count and timeframe either both need to be 0 or both need to be greater than 0!"
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:179
|
||||
msgid "Autoban disabled."
|
||||
msgstr ""
|
||||
|
||||
#: ../filter.py:183
|
||||
msgid "Count and time have been set."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../filter.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -3,12 +3,11 @@ import time
|
||||
from enum import Enum
|
||||
from random import randint, choice
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
from redbot.core import commands
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
|
||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||
from redbot.core.utils.chat_formatting import escape, italics, pagify
|
||||
|
||||
_ = Translator("General", __file__)
|
||||
@@ -21,7 +20,6 @@ class RPS(Enum):
|
||||
|
||||
|
||||
class RPSParser:
|
||||
|
||||
def __init__(self, argument):
|
||||
argument = argument.lower()
|
||||
if argument == "rock":
|
||||
@@ -98,7 +96,7 @@ class General:
|
||||
msg = ""
|
||||
if user.id == ctx.bot.user.id:
|
||||
user = ctx.author
|
||||
msg = _("Nice try. You think this is funny?\n" "How about *this* instead:\n\n")
|
||||
msg = _("Nice try. You think this is funny?\n How about *this* instead:\n\n")
|
||||
char = "abcdefghijklmnopqrstuvwxyz"
|
||||
tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz"
|
||||
table = str.maketrans(char, tran)
|
||||
@@ -165,7 +163,9 @@ class General:
|
||||
@commands.command()
|
||||
async def lmgtfy(self, ctx, *, search_terms: str):
|
||||
"""Creates a lmgtfy link"""
|
||||
search_terms = escape(search_terms.replace(" ", "+"), mass_mentions=True)
|
||||
search_terms = escape(
|
||||
search_terms.replace("+", "%2B").replace(" ", "+"), mass_mentions=True
|
||||
)
|
||||
await ctx.send("https://lmgtfy.com/?q={}".format(search_terms))
|
||||
|
||||
@commands.command(hidden=True)
|
||||
@@ -192,25 +192,15 @@ class General:
|
||||
async def serverinfo(self, ctx):
|
||||
"""Shows server's informations"""
|
||||
guild = ctx.guild
|
||||
online = len(
|
||||
[
|
||||
m.status
|
||||
for m in guild.members
|
||||
if m.status == discord.Status.online or m.status == discord.Status.idle
|
||||
]
|
||||
)
|
||||
online = len([m.status for m in guild.members if m.status != discord.Status.offline])
|
||||
total_users = len(guild.members)
|
||||
text_channels = len(guild.text_channels)
|
||||
voice_channels = len(guild.voice_channels)
|
||||
passed = (ctx.message.created_at - guild.created_at).days
|
||||
created_at = _("Since {}. That's over {} days ago!" "").format(
|
||||
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)
|
||||
@@ -228,52 +218,93 @@ class General:
|
||||
try:
|
||||
await ctx.send(embed=data)
|
||||
except discord.HTTPException:
|
||||
await ctx.send(_("I need the `Embed links` permission " "to send this."))
|
||||
await ctx.send(_("I need the `Embed links` permission to send this."))
|
||||
|
||||
@commands.command()
|
||||
async def urban(self, ctx, *, search_terms: str, definition_number: int = 1):
|
||||
"""Urban Dictionary search
|
||||
async def urban(self, ctx, *, word):
|
||||
"""Searches urban dictionary entries using the unofficial api"""
|
||||
|
||||
Definition number must be between 1 and 10"""
|
||||
|
||||
def encode(s):
|
||||
return quote_plus(s, encoding="utf-8", errors="replace")
|
||||
|
||||
# definition_number is just there to show up in the help
|
||||
# all this mess is to avoid forcing double quotes on the user
|
||||
|
||||
search_terms = search_terms.split(" ")
|
||||
try:
|
||||
if len(search_terms) > 1:
|
||||
pos = int(search_terms[-1]) - 1
|
||||
search_terms = search_terms[:-1]
|
||||
else:
|
||||
pos = 0
|
||||
if pos not in range(0, 11): # API only provides the
|
||||
pos = 0 # top 10 definitions
|
||||
except ValueError:
|
||||
pos = 0
|
||||
url = "https://api.urbandictionary.com/v0/define?term=" + str(word).lower()
|
||||
|
||||
headers = {"content-type": "application/json"}
|
||||
|
||||
search_terms = {"term": "+".join([s for s in search_terms])}
|
||||
url = "http://api.urbandictionary.com/v0/define"
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, params=search_terms) as r:
|
||||
result = await r.json()
|
||||
item_list = result["list"]
|
||||
if item_list:
|
||||
definition = item_list[pos]["definition"]
|
||||
example = item_list[pos]["example"]
|
||||
defs = len(item_list)
|
||||
msg = "**Definition #{} out of {}:\n**{}\n\n" "**Example:\n**{}".format(
|
||||
pos + 1, defs, definition, example
|
||||
)
|
||||
msg = pagify(msg, ["\n"])
|
||||
for page in msg:
|
||||
await ctx.send(page)
|
||||
else:
|
||||
await ctx.send(_("Your search terms gave no results."))
|
||||
except IndexError:
|
||||
await ctx.send(_("There is no definition #{}").format(pos + 1))
|
||||
async with session.get(url, headers=headers) as response:
|
||||
data = await response.json()
|
||||
|
||||
except:
|
||||
await ctx.send(_("Error."))
|
||||
await ctx.send(
|
||||
_("No Urban dictionary entries were found or there was an error in the process")
|
||||
)
|
||||
|
||||
if data.get("error") != 404:
|
||||
|
||||
if await ctx.embed_requested():
|
||||
# a list of embeds
|
||||
embeds = []
|
||||
for ud in data["list"]:
|
||||
embed = discord.Embed()
|
||||
embed.title = _("{} by {}").format(ud["word"].capitalize(), ud["author"])
|
||||
embed.url = ud["permalink"]
|
||||
|
||||
description = "{} \n \n **Example : ** {}".format(
|
||||
ud["definition"], ud.get("example", "N/A")
|
||||
)
|
||||
if len(description) > 2048:
|
||||
description = "{}...".format(description[:2045])
|
||||
embed.description = description
|
||||
|
||||
embed.set_footer(
|
||||
text=_("{} Down / {} Up , Powered by urban dictionary").format(
|
||||
ud["thumbs_down"], ud["thumbs_up"]
|
||||
)
|
||||
)
|
||||
embeds.append(embed)
|
||||
|
||||
if embeds is not None and len(embeds) > 0:
|
||||
await menu(
|
||||
ctx,
|
||||
pages=embeds,
|
||||
controls=DEFAULT_CONTROLS,
|
||||
message=None,
|
||||
page=0,
|
||||
timeout=30,
|
||||
)
|
||||
else:
|
||||
messages = []
|
||||
for ud in data["list"]:
|
||||
description = _("{} \n \n **Example : ** {}").format(
|
||||
ud["definition"], ud.get("example", "N/A")
|
||||
)
|
||||
if len(description) > 2048:
|
||||
description = "{}...".format(description[:2045])
|
||||
description = description
|
||||
|
||||
message = _(
|
||||
"<{}> \n {} by {} \n \n {} \n \n {} Down / {} Up, Powered by urban "
|
||||
"dictionary"
|
||||
).format(
|
||||
ud["permalink"],
|
||||
ud["word"].capitalize(),
|
||||
ud["author"],
|
||||
description,
|
||||
ud["thumbs_down"],
|
||||
ud["thumbs_up"],
|
||||
)
|
||||
messages.append(message)
|
||||
|
||||
if messages is not None and len(messages) > 0:
|
||||
await menu(
|
||||
ctx,
|
||||
pages=messages,
|
||||
controls=DEFAULT_CONTROLS,
|
||||
message=None,
|
||||
page=0,
|
||||
timeout=30,
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
_("No Urban dictionary entries were found or there was an error in the process")
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "As I see it, yes"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "It is certain"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "It is decidedly so"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:43
|
||||
msgid "Most likely"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:43
|
||||
msgid "Outlook good"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:43
|
||||
msgid "Signs point to yes"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:44
|
||||
msgid "Without a doubt"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:44
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:44
|
||||
msgid "Yes – definitely"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:44
|
||||
msgid "You may rely on it"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:45
|
||||
msgid "Ask again later"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:45
|
||||
msgid "Reply hazy, try again"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:46
|
||||
msgid "Better not tell you now"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:46
|
||||
msgid "Cannot predict now"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:47
|
||||
msgid "Concentrate and ask again"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:47
|
||||
msgid "Don't count on it"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:47
|
||||
msgid "My reply is no"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:48
|
||||
msgid "My sources say no"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:48
|
||||
msgid "Outlook not so good"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:48
|
||||
msgid "Very doubtful"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:64
|
||||
msgid "Not enough choices to pick from."
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:78
|
||||
msgid "{} :game_die: {} :game_die:"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:81
|
||||
msgid "{} Maybe higher than 1? ;P"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:93
|
||||
msgid ""
|
||||
"Nice try. You think this is funny?How about *this* instead:\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:106
|
||||
msgid "*flips a coin and... "
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:106
|
||||
msgid "HEADS!*"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:106
|
||||
msgid "TAILS!*"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:130
|
||||
msgid "{} You win {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:134
|
||||
msgid "{} You lose {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:138
|
||||
msgid "{} We're square {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:151
|
||||
msgid "That doesn't look like a question."
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:159
|
||||
msgid " Stopwatch started!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:163
|
||||
msgid " Stopwatch stopped! Time: **"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:216 ../general.py:217
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"({} days ago)"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:219
|
||||
msgid "Chilling in {} status"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:223
|
||||
msgid "Playing {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:225
|
||||
msgid "Streaming [{}]({})"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:227
|
||||
msgid "Listening to {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:229
|
||||
msgid "Watching {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:234
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:237
|
||||
msgid "Joined Discord on"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:238
|
||||
msgid "Joined this guild on"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:239 ../general.py:286
|
||||
msgid "Roles"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:240
|
||||
msgid "Member #{} | User ID: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:257 ../general.py:299
|
||||
msgid "I need the `Embed links` permission to send this."
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:272
|
||||
msgid "Since {}. That's over {} days ago!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:282
|
||||
msgid "Region"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:283
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:284
|
||||
msgid "Text Channels"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:285
|
||||
msgid "Voice Channels"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:287
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:288
|
||||
msgid "Guild ID: "
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:343
|
||||
msgid "Your search terms gave no results."
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:345
|
||||
msgid "There is no definition #{}"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:347
|
||||
msgid "Error."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../general.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
+14
-13
@@ -13,6 +13,7 @@ GIPHY_API_KEY = "dc6zaTOxFJmzC"
|
||||
@cog_i18n(_)
|
||||
class Image:
|
||||
"""Image related commands."""
|
||||
|
||||
default_global = {"imgur_client_id": None}
|
||||
|
||||
def __init__(self, bot):
|
||||
@@ -23,7 +24,7 @@ class Image:
|
||||
self.imgur_base_url = "https://api.imgur.com/3/"
|
||||
|
||||
def __unload(self):
|
||||
self.session.close()
|
||||
self.session.detach()
|
||||
|
||||
@commands.group(name="imgur")
|
||||
async def _imgur(self, ctx):
|
||||
@@ -31,8 +32,7 @@ class Image:
|
||||
|
||||
Make sure to set the client ID using
|
||||
[p]imgurcreds"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@_imgur.command(name="search")
|
||||
async def imgur_search(self, ctx, *, term: str):
|
||||
@@ -42,7 +42,7 @@ class Image:
|
||||
imgur_client_id = await self.settings.imgur_client_id()
|
||||
if not imgur_client_id:
|
||||
await ctx.send(
|
||||
_("A client ID has not been set! Please set one with {}").format(
|
||||
_("A client ID has not been set! Please set one with {}.").format(
|
||||
"`{}imgurcreds`".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
@@ -54,7 +54,7 @@ class Image:
|
||||
if data["success"]:
|
||||
results = data["data"]
|
||||
if not results:
|
||||
await ctx.send(_("Your search returned no results"))
|
||||
await ctx.send(_("Your search returned no results."))
|
||||
return
|
||||
shuffle(results)
|
||||
msg = _("Search results...\n")
|
||||
@@ -63,7 +63,7 @@ class Image:
|
||||
msg += "\n"
|
||||
await ctx.send(msg)
|
||||
else:
|
||||
await ctx.send(_("Something went wrong. Error code is {}").format(data["status"]))
|
||||
await ctx.send(_("Something went wrong. Error code is {}.").format(data["status"]))
|
||||
|
||||
@_imgur.command(name="subreddit")
|
||||
async def imgur_subreddit(
|
||||
@@ -91,7 +91,7 @@ class Image:
|
||||
imgur_client_id = await self.settings.imgur_client_id()
|
||||
if not imgur_client_id:
|
||||
await ctx.send(
|
||||
_("A client ID has not been set! Please set one with {}").format(
|
||||
_("A client ID has not been set! Please set one with {}.").format(
|
||||
"`{}imgurcreds`".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
@@ -116,12 +116,13 @@ class Image:
|
||||
else:
|
||||
await ctx.send(_("No results found."))
|
||||
else:
|
||||
await ctx.send(_("Something went wrong. Error code is {}").format(data["status"]))
|
||||
await ctx.send(_("Something went wrong. Error code is {}.").format(data["status"]))
|
||||
|
||||
@checks.is_owner()
|
||||
@commands.command()
|
||||
async def imgurcreds(self, ctx, imgur_client_id: str):
|
||||
"""Sets the imgur client id
|
||||
|
||||
You will need an account on Imgur to get this
|
||||
|
||||
You can get these by visiting https://api.imgur.com/oauth2/addclient
|
||||
@@ -130,7 +131,7 @@ class Image:
|
||||
set the authorization callback url to 'https://localhost'
|
||||
leave the app website blank, enter a valid email address, and
|
||||
enter a description. Check the box for the captcha, then click Next.
|
||||
Your client ID will be on the page that loads"""
|
||||
Your client ID will be on the page that loads."""
|
||||
await self.settings.imgur_client_id.set(imgur_client_id)
|
||||
await ctx.send(_("Set the imgur client id!"))
|
||||
|
||||
@@ -143,7 +144,7 @@ class Image:
|
||||
await ctx.send_help()
|
||||
return
|
||||
|
||||
url = "http://api.giphy.com/v1/gifs/search?&api_key={}&q={}" "".format(
|
||||
url = "http://api.giphy.com/v1/gifs/search?&api_key={}&q={}".format(
|
||||
GIPHY_API_KEY, keywords
|
||||
)
|
||||
|
||||
@@ -155,7 +156,7 @@ class Image:
|
||||
else:
|
||||
await ctx.send(_("No results found."))
|
||||
else:
|
||||
await ctx.send(_("Error contacting the API"))
|
||||
await ctx.send(_("Error contacting the API."))
|
||||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
async def gifr(self, ctx, *keywords):
|
||||
@@ -166,7 +167,7 @@ class Image:
|
||||
await ctx.send_help()
|
||||
return
|
||||
|
||||
url = "http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}" "".format(
|
||||
url = "http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}".format(
|
||||
GIPHY_API_KEY, keywords
|
||||
)
|
||||
|
||||
@@ -178,4 +179,4 @@ class Image:
|
||||
else:
|
||||
await ctx.send(_("No results found."))
|
||||
else:
|
||||
await ctx.send(_("Error contacting the API"))
|
||||
await ctx.send(_("Error contacting the API."))
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../image.py:49
|
||||
msgid "Your search returned no results"
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:52
|
||||
msgid ""
|
||||
"Search results...\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:58 ../image.py:100
|
||||
msgid "Something went wrong. Error code is {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:70
|
||||
msgid "Only 'new' and 'top' are a valid sort type."
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:98 ../image.py:135 ../image.py:157
|
||||
msgid "No results found."
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:115
|
||||
msgid "Set the imgur client id!"
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:137 ../image.py:159
|
||||
msgid "Error contacting the API"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../image.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -1,9 +1,8 @@
|
||||
from discord.ext import commands
|
||||
from redbot.core import commands
|
||||
import discord
|
||||
|
||||
|
||||
def mod_or_voice_permissions(**perms):
|
||||
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
@@ -31,7 +30,6 @@ def mod_or_voice_permissions(**perms):
|
||||
|
||||
|
||||
def admin_or_voice_permissions(**perms):
|
||||
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
@@ -54,7 +52,6 @@ def admin_or_voice_permissions(**perms):
|
||||
|
||||
|
||||
def bot_has_voice_permissions(**perms):
|
||||
|
||||
async def pred(ctx: commands.Context):
|
||||
guild = ctx.guild
|
||||
for vc in guild.voice_channels:
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../mod.py:209
|
||||
msgid "Role hierarchy will be checked when moderation commands are issued."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:213
|
||||
msgid "Role hierarchy will be ignored when moderation commands are issued."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:228
|
||||
msgid "Autoban for mention spam enabled. Anyone mentioning {} or more different people in a single message will be autobanned."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:239
|
||||
msgid "Autoban for mention spam disabled."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:249
|
||||
msgid "Messages repeated up to 3 times will be deleted."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:253
|
||||
msgid "Repeated messages will be ignored."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:267
|
||||
msgid "Command deleting disabled."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:270
|
||||
msgid "Delete delay set to {} seconds."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:275
|
||||
msgid "Bot will delete command messages after {} seconds. Set this value to -1 to stop deleting messages"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:279
|
||||
msgid "I will not delete command messages."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:292
|
||||
msgid "Users unbanned with {} will be reinvited."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:295
|
||||
msgid "Users unbanned with {} will not be reinvited."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:309 ../mod.py:349 ../mod.py:506
|
||||
msgid "I cannot let you do that. Self-harm is bad {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:313 ../mod.py:353 ../mod.py:510
|
||||
msgid "I cannot let you do that. You are not higher than the user in the role hierarchy."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:323 ../mod.py:379 ../mod.py:570
|
||||
msgid "I'm not allowed to do that."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:327
|
||||
msgid "Done. That felt good."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:369
|
||||
msgid "Invalid days. Must be between 0 and 7."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:384
|
||||
msgid "Done. It was about time."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:413
|
||||
msgid "User is already banned."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:429
|
||||
msgid "User not found. Have you provided the correct user ID?"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:433
|
||||
msgid "I lack the permissions to do this."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:435
|
||||
msgid "Done. The user will not be able to join this guild."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:472
|
||||
msgid "You have been temporarily banned from {} until {}. Here is an invite for when your ban expires: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:481
|
||||
msgid "I can't do that for some reason."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:483
|
||||
msgid "Something went wrong while banning"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:485
|
||||
msgid "Done. Enough chaos for now"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:525
|
||||
msgid ""
|
||||
"You have been banned and then unbanned as a quick way to delete your messages.\n"
|
||||
"You can now join the guild again. {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:537
|
||||
msgid "My role is not high enough to softban that user."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:553
|
||||
msgid "Done. Enough chaos."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:587
|
||||
msgid "Couldn't find a user with that ID!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:593
|
||||
msgid "It seems that user isn't banned!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:601
|
||||
msgid "Something went wrong while attempting to unban that user"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:604
|
||||
msgid "Unbanned that user from this guild"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:619
|
||||
msgid ""
|
||||
"You've been unbanned from {}.\n"
|
||||
"Here is an invite for that guild: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:623
|
||||
msgid ""
|
||||
"I failed to send an invite to that user. Perhaps you may be able to send it for me?\n"
|
||||
"Here's the invite link: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:629
|
||||
msgid "Something went wrong when attempting to send that useran invite. Here's the link so you can try: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:673 ../mod.py:710
|
||||
msgid "No voice state for that user!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:687
|
||||
msgid "That user is already muted and deafened guild-wide!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:690
|
||||
msgid "User has been banned from speaking or listening in voice channels"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:722
|
||||
msgid "That user isn't muted or deafened by the guild!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:725
|
||||
msgid "User is now allowed to speak and listen in voice channels"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:754
|
||||
msgid "I cannot do that, I lack the '{}' permission."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:783
|
||||
msgid "Muted {}#{} in channel {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:797
|
||||
msgid "That user is already muted in {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:800 ../mod.py:932
|
||||
msgid "That user is not in a voice channel right now!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:802 ../mod.py:934
|
||||
msgid "No voice state for the target!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:822
|
||||
msgid "User has been muted in this channel."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:858
|
||||
msgid "User has been muted in this guild."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:919
|
||||
msgid "Unmuted {}#{} in channel {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:929
|
||||
msgid "That user is already unmuted in {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:949
|
||||
msgid "User unmuted in this channel."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:958
|
||||
msgid "Unmute failed. Reason: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:981
|
||||
msgid "User has been unmuted in this guild."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1045
|
||||
msgid "Channel added to ignore list."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1047
|
||||
msgid "Channel already in ignore list."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1055
|
||||
msgid "This guild has been added to the ignore list."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1057
|
||||
msgid "This guild is already being ignored."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1078
|
||||
msgid "Channel removed from ignore list."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1080
|
||||
msgid "That channel is not in the ignore list."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1088
|
||||
msgid "This guild has been removed from the ignore list."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1090
|
||||
msgid "This guild is not in the ignore list."
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1102
|
||||
msgid ""
|
||||
"Currently ignoring:\n"
|
||||
"{} channels\n"
|
||||
"{} guilds\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1133
|
||||
msgid "**Past 20 names**:"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1140
|
||||
msgid "**Past 20 nicknames**:"
|
||||
msgstr ""
|
||||
|
||||
#: ../mod.py:1146
|
||||
msgid "That user doesn't have any recorded name or nickname change."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../mod.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
+69
-40
@@ -12,6 +12,8 @@ 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, filter_various_mentions
|
||||
|
||||
_ = Translator("Mod", __file__)
|
||||
|
||||
|
||||
@@ -168,8 +170,6 @@ class Mod:
|
||||
"""Manages server administration settings."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
guild = ctx.guild
|
||||
await ctx.send_help()
|
||||
|
||||
# Display current settings
|
||||
delete_repeats = await self.settings.guild(guild).delete_repeats()
|
||||
ban_mention_spam = await self.settings.guild(guild).ban_mention_spam()
|
||||
@@ -199,12 +199,12 @@ class Mod:
|
||||
if not toggled:
|
||||
await self.settings.guild(guild).respect_hierarchy.set(True)
|
||||
await ctx.send(
|
||||
_("Role hierarchy will be checked when " "moderation commands are issued.")
|
||||
_("Role hierarchy will be checked when moderation commands are issued.")
|
||||
)
|
||||
else:
|
||||
await self.settings.guild(guild).respect_hierarchy.set(False)
|
||||
await ctx.send(
|
||||
_("Role hierarchy will be ignored when " "moderation commands are issued.")
|
||||
_("Role hierarchy will be ignored when moderation commands are issued.")
|
||||
)
|
||||
|
||||
@modset.command()
|
||||
@@ -241,7 +241,7 @@ class Mod:
|
||||
cur_setting = await self.settings.guild(guild).delete_repeats()
|
||||
if not cur_setting:
|
||||
await self.settings.guild(guild).delete_repeats.set(True)
|
||||
await ctx.send(_("Messages repeated up to 3 times will " "be deleted."))
|
||||
await ctx.send(_("Messages repeated up to 3 times will be deleted."))
|
||||
else:
|
||||
await self.settings.guild(guild).delete_repeats.set(False)
|
||||
await ctx.send(_("Repeated messages will be ignored."))
|
||||
@@ -304,7 +304,7 @@ class Mod:
|
||||
|
||||
if author == user:
|
||||
await ctx.send(
|
||||
_("I cannot let you do that. Self-harm is " "bad {}").format("\N{PENSIVE FACE}")
|
||||
_("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}")
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
@@ -316,6 +316,9 @@ class Mod:
|
||||
)
|
||||
)
|
||||
return
|
||||
elif ctx.guild.me.top_role <= user.top_role or user == ctx.guild.owner:
|
||||
await ctx.send(_("I cannot do that due to discord hierarchy rules"))
|
||||
return
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
try:
|
||||
await guild.kick(user, reason=audit_reason)
|
||||
@@ -357,7 +360,7 @@ class Mod:
|
||||
|
||||
if author == user:
|
||||
await ctx.send(
|
||||
_("I cannot let you do that. Self-harm is " "bad {}").format("\N{PENSIVE FACE}")
|
||||
_("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}")
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
@@ -369,6 +372,9 @@ class Mod:
|
||||
)
|
||||
)
|
||||
return
|
||||
elif ctx.guild.me.top_role <= user.top_role or user == ctx.guild.owner:
|
||||
await ctx.send(_("I cannot do that due to discord hierarchy rules"))
|
||||
return
|
||||
|
||||
if days:
|
||||
if days.isdigit():
|
||||
@@ -451,15 +457,15 @@ class Mod:
|
||||
self.ban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.ban(user, reason=audit_reason)
|
||||
log.info("{}({}) hackbanned {}" "".format(author.name, author.id, user_id))
|
||||
log.info("{}({}) hackbanned {}".format(author.name, author.id, user_id))
|
||||
except discord.NotFound:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
await ctx.send(_("User not found. Have you provided the " "correct user ID?"))
|
||||
await ctx.send(_("User not found. Have you provided the correct user ID?"))
|
||||
except discord.Forbidden:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
await ctx.send(_("I lack the permissions to do this."))
|
||||
else:
|
||||
await ctx.send(_("Done. The user will not be able to join this " "server."))
|
||||
await ctx.send(_("Done. The user will not be able to join this server."))
|
||||
|
||||
user_info = await self.bot.get_user_info(user_id)
|
||||
try:
|
||||
@@ -547,7 +553,7 @@ class Mod:
|
||||
|
||||
if author == user:
|
||||
await ctx.send(
|
||||
_("I cannot let you do that. Self-harm is " "bad {}").format("\N{PENSIVE FACE}")
|
||||
_("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}")
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
@@ -624,7 +630,6 @@ class Mod:
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
async def unban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
|
||||
"""Unbans the target user.
|
||||
|
||||
@@ -632,13 +637,17 @@ class Mod:
|
||||
1. Copy it from the mod log case (if one was created), or
|
||||
2. enable developer mode, go to Bans in this server's settings, right-
|
||||
click the user and select 'Copy ID'."""
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).ban_members:
|
||||
await ctx.send("I need the Ban Members permission to do this.")
|
||||
return
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
user = await self.bot.get_user_info(user_id)
|
||||
if not user:
|
||||
await ctx.send(_("Couldn't find a user with that ID!"))
|
||||
return
|
||||
reason = get_audit_reason(ctx.author, reason)
|
||||
audit_reason = get_audit_reason(ctx.author, reason)
|
||||
bans = await guild.bans()
|
||||
bans = [be.user for be in bans]
|
||||
if user not in bans:
|
||||
@@ -647,7 +656,7 @@ class Mod:
|
||||
queue_entry = (guild.id, user.id)
|
||||
self.unban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.unban(user, reason=reason)
|
||||
await guild.unban(user, reason=audit_reason)
|
||||
except discord.HTTPException:
|
||||
self.unban_queue.remove(queue_entry)
|
||||
await ctx.send(_("Something went wrong while attempting to unban that user"))
|
||||
@@ -753,7 +762,7 @@ class Mod:
|
||||
else:
|
||||
await ctx.send(_("That user is already muted and deafened server-wide!"))
|
||||
return
|
||||
await ctx.send(_("User has been banned from speaking or " "listening in voice channels"))
|
||||
await ctx.send(_("User has been banned from speaking or listening in voice channels"))
|
||||
|
||||
try:
|
||||
await modlog.create_case(
|
||||
@@ -825,7 +834,7 @@ class Mod:
|
||||
await ctx.send("Done.")
|
||||
except discord.Forbidden:
|
||||
await ctx.send(
|
||||
_("I cannot do that, I lack the " "'{}' permission.").format("Manage Nicknames")
|
||||
_("I cannot do that, I lack the '{}' permission.").format("Manage Nicknames")
|
||||
)
|
||||
|
||||
@commands.group()
|
||||
@@ -833,8 +842,7 @@ class Mod:
|
||||
@checks.mod_or_permissions(manage_channel=True)
|
||||
async def mute(self, ctx: commands.Context):
|
||||
"""Mutes user in the channel/server"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@mute.command(name="voice")
|
||||
@commands.guild_only()
|
||||
@@ -1002,8 +1010,7 @@ class Mod:
|
||||
"""Unmutes user in the channel/server
|
||||
|
||||
Defaults to channel"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@unmute.command(name="voice")
|
||||
@commands.guild_only()
|
||||
@@ -1168,7 +1175,6 @@ class Mod:
|
||||
async def ignore(self, ctx: commands.Context):
|
||||
"""Adds servers/channels to ignorelist"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
await ctx.send(await self.count_ignored())
|
||||
|
||||
@ignore.command(name="channel")
|
||||
@@ -1185,7 +1191,7 @@ class Mod:
|
||||
await ctx.send(_("Channel already in ignore list."))
|
||||
|
||||
@ignore.command(name="server", aliases=["guild"])
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
@checks.admin_or_permissions(manage_guild=True)
|
||||
async def ignore_guild(self, ctx: commands.Context):
|
||||
"""Ignores current server"""
|
||||
guild = ctx.guild
|
||||
@@ -1201,7 +1207,6 @@ class Mod:
|
||||
async def unignore(self, ctx: commands.Context):
|
||||
"""Removes servers/channels from ignorelist"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
await ctx.send(await self.count_ignored())
|
||||
|
||||
@unignore.command(name="channel")
|
||||
@@ -1219,7 +1224,7 @@ class Mod:
|
||||
await ctx.send(_("That channel is not in the ignore list."))
|
||||
|
||||
@unignore.command(name="server", aliases=["guild"])
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
@checks.admin_or_permissions(manage_guild=True)
|
||||
async def unignore_guild(self, ctx: commands.Context):
|
||||
"""Removes current guild from ignore list"""
|
||||
guild = ctx.message.guild
|
||||
@@ -1263,7 +1268,14 @@ class Mod:
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
async def userinfo(self, ctx, *, user: discord.Member = None):
|
||||
"""Shows users's informations"""
|
||||
"""Shows information for a user.
|
||||
|
||||
This includes fields for status, discord join date, server
|
||||
join date, voice state and previous names/nicknames.
|
||||
|
||||
If the user has none of roles, previous names or previous
|
||||
nicknames, these fields will be omitted.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
@@ -1303,26 +1315,32 @@ class Mod:
|
||||
if roles:
|
||||
roles = ", ".join([x.name for x in roles])
|
||||
else:
|
||||
roles = _("None")
|
||||
roles = None
|
||||
|
||||
data = discord.Embed(description=activity, colour=user.colour)
|
||||
data.add_field(name=_("Joined Discord on"), value=created_on)
|
||||
data.add_field(name=_("Joined this server on"), value=joined_on)
|
||||
data.add_field(name=_("Roles"), value=roles, inline=False)
|
||||
if roles is not None:
|
||||
data.add_field(name=_("Roles"), value=roles, inline=False)
|
||||
if names:
|
||||
data.add_field(name=_("Previous Names"), value=", ".join(names), inline=False)
|
||||
# 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:
|
||||
data.add_field(name=_("Previous Nicknames"), value=", ".join(nicks), inline=False)
|
||||
# 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:
|
||||
data.add_field(
|
||||
name=_("Current voice channel"),
|
||||
value="{0.name} (ID {0.id})".format(voice_state.channel),
|
||||
inline=False,
|
||||
)
|
||||
data.set_footer(text=_("Member #{} | User ID: {}" "").format(member_number, user.id))
|
||||
data.set_footer(text=_("Member #{} | User ID: {}").format(member_number, user.id))
|
||||
|
||||
name = str(user)
|
||||
name = " ~ ".join((name, user.nick)) if user.nick else name
|
||||
name = filter_invites(name)
|
||||
|
||||
if user.avatar:
|
||||
avatar = user.avatar_url
|
||||
@@ -1335,7 +1353,7 @@ class Mod:
|
||||
try:
|
||||
await ctx.send(embed=data)
|
||||
except discord.HTTPException:
|
||||
await ctx.send(_("I need the `Embed links` permission " "to send this."))
|
||||
await ctx.send(_("I need the `Embed links` permission to send this."))
|
||||
|
||||
@commands.command()
|
||||
async def names(self, ctx: commands.Context, user: discord.Member):
|
||||
@@ -1353,17 +1371,18 @@ 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."))
|
||||
await ctx.send(_("That user doesn't have any recorded name or nickname change."))
|
||||
|
||||
async def get_names_and_nicks(self, user):
|
||||
names = await self.settings.user(user).past_names()
|
||||
nicks = await self.settings.member(user).past_nicks()
|
||||
if names:
|
||||
names = [escape(name, mass_mentions=True) for name in names]
|
||||
names = [escape(name, mass_mentions=True) for name in names if name]
|
||||
if nicks:
|
||||
nicks = [escape(nick, mass_mentions=True) for nick in nicks]
|
||||
nicks = [escape(nick, mass_mentions=True) for nick in nicks if nick]
|
||||
return names, nicks
|
||||
|
||||
async def check_tempban_expirations(self):
|
||||
@@ -1418,7 +1437,7 @@ class Mod:
|
||||
await guild.ban(author, reason="Mention spam (Autoban)")
|
||||
except discord.HTTPException:
|
||||
log.info(
|
||||
"Failed to ban member for mention spam in " "server {}.".format(guild.id)
|
||||
"Failed to ban member for mention spam in server {}.".format(guild.id)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
@@ -1439,7 +1458,13 @@ class Mod:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def on_command(self, ctx: commands.Context):
|
||||
async def on_command_completion(self, ctx: commands.Context):
|
||||
await self._delete_delay(ctx)
|
||||
|
||||
async def on_command_error(self, ctx: commands.Context, error):
|
||||
await self._delete_delay(ctx)
|
||||
|
||||
async def _delete_delay(self, ctx: commands.Context):
|
||||
"""Currently used for:
|
||||
* delete delay"""
|
||||
guild = ctx.guild
|
||||
@@ -1595,18 +1620,22 @@ class Mod:
|
||||
if entry.target == target:
|
||||
return entry
|
||||
|
||||
async def on_member_update(self, before, after):
|
||||
async def on_member_update(self, before: discord.Member, after: discord.Member):
|
||||
if before.name != after.name:
|
||||
async with self.settings.user(before).past_names() as name_list:
|
||||
if after.nick in name_list:
|
||||
while None in name_list: # clean out null entries from a bug
|
||||
name_list.remove(None)
|
||||
if after.name in name_list:
|
||||
# Ensure order is maintained without duplicates occuring
|
||||
name_list.remove(after.nick)
|
||||
name_list.append(after.nick)
|
||||
name_list.remove(after.name)
|
||||
name_list.append(after.name)
|
||||
while len(name_list) > 20:
|
||||
name_list.pop(0)
|
||||
|
||||
if before.nick != after.nick and after.nick is not None:
|
||||
async with self.settings.member(before).past_nicks() as nick_list:
|
||||
while None in nick_list: # clean out null entries from a bug
|
||||
nick_list.remove(None)
|
||||
if after.nick in nick_list:
|
||||
nick_list.remove(after.nick)
|
||||
nick_list.append(after.nick)
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-02-18 14:42+AKST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: ../modlog.py:36
|
||||
msgid "Mod events will be sent to {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:42
|
||||
msgid "I do not have permissions to send messages in {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:52
|
||||
msgid "Mod log deactivated."
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:63
|
||||
msgid "Current settings:"
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:75
|
||||
msgid "That action is not registered"
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:82
|
||||
msgid "Case creation for {} actions is now {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:94
|
||||
msgid "Cases have been reset."
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:103
|
||||
msgid "That case does not exist for that guild"
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:122
|
||||
msgid "That case does not exist!"
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:146
|
||||
msgid "You are not authorized to modify that case!"
|
||||
msgstr ""
|
||||
|
||||
#: ../modlog.py:155
|
||||
msgid "Reason has been updated."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
TO_TRANSLATE = ["../modlog.py"]
|
||||
|
||||
|
||||
def regen_messages():
|
||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
regen_messages()
|
||||
@@ -19,8 +19,7 @@ class ModLog:
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def modlogset(self, ctx: commands.Context):
|
||||
"""Settings for the mod log"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@modlogset.command()
|
||||
@commands.guild_only()
|
||||
@@ -35,9 +34,7 @@ class ModLog:
|
||||
await ctx.send(_("Mod events will be sent to {}").format(channel.mention))
|
||||
else:
|
||||
await ctx.send(
|
||||
_("I do not have permissions to " "send messages in {}!").format(
|
||||
channel.mention
|
||||
)
|
||||
_("I do not have permissions to send messages in {}!").format(channel.mention)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
@@ -98,19 +95,29 @@ class ModLog:
|
||||
await ctx.send(_("That case does not exist for that server"))
|
||||
return
|
||||
else:
|
||||
await ctx.send(embed=await case.get_case_msg_content())
|
||||
if await ctx.embed_requested():
|
||||
await ctx.send(embed=await case.message_content(embed=True))
|
||||
else:
|
||||
await ctx.send(await case.message_content(embed=False))
|
||||
|
||||
@commands.command()
|
||||
@commands.command(usage="[case] <reason>")
|
||||
@commands.guild_only()
|
||||
async def reason(self, ctx: commands.Context, case: int, *, reason: str = ""):
|
||||
async def reason(self, ctx: commands.Context, *, reason: str):
|
||||
"""Lets you specify a reason for mod-log's cases
|
||||
|
||||
Please note that you can only edit cases you are
|
||||
the owner of unless you are a mod/admin or the server owner"""
|
||||
the owner of unless you are a mod/admin or the server owner.
|
||||
|
||||
If no number is specified, the latest case will be used."""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
if not reason:
|
||||
await ctx.send_help()
|
||||
return
|
||||
potential_case = reason.split()[0]
|
||||
if potential_case.isdigit():
|
||||
case = int(potential_case)
|
||||
reason = reason.replace(potential_case, "")
|
||||
else:
|
||||
case = str(int(await modlog.get_next_case_number(guild)) - 1)
|
||||
# latest case
|
||||
try:
|
||||
case_before = await modlog.get_case(case, guild, self.bot)
|
||||
except RuntimeError:
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import Tuple
|
||||
|
||||
|
||||
class CogOrCommand(commands.Converter):
|
||||
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> Tuple[str]:
|
||||
ret = ctx.bot.get_cog(arg)
|
||||
if ret:
|
||||
@@ -12,15 +11,34 @@ class CogOrCommand(commands.Converter):
|
||||
if ret:
|
||||
return "commands", ret.qualified_name
|
||||
|
||||
raise commands.BadArgument()
|
||||
raise commands.BadArgument(
|
||||
'Cog or command "{arg}" not found. Please note that this is case sensitive.'
|
||||
"".format(arg=arg)
|
||||
)
|
||||
|
||||
|
||||
class RuleType(commands.Converter):
|
||||
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> str:
|
||||
if arg.lower() in ("allow", "whitelist", "allowed"):
|
||||
return "allow"
|
||||
if arg.lower() in ("deny", "blacklist", "denied"):
|
||||
return "deny"
|
||||
|
||||
raise commands.BadArgument()
|
||||
raise commands.BadArgument(
|
||||
'"{arg}" is not a valid rule. Valid rules are "allow" or "deny"'.format(arg=arg)
|
||||
)
|
||||
|
||||
|
||||
class ClearableRuleType(commands.Converter):
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> str:
|
||||
if arg.lower() in ("allow", "whitelist", "allowed"):
|
||||
return "allow"
|
||||
if arg.lower() in ("deny", "blacklist", "denied"):
|
||||
return "deny"
|
||||
if arg.lower() in ("clear", "reset"):
|
||||
return "clear"
|
||||
|
||||
raise commands.BadArgument(
|
||||
'"{arg}" is not a valid rule. Valid rules are "allow" or "deny", or "clear" to remove the rule'
|
||||
"".format(arg=arg)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
from redbot.core import commands
|
||||
from redbot.core.config import Config
|
||||
from .resolvers import entries_from_ctx, resolve_lists
|
||||
|
||||
# This has optimizations in it that may not hold True if other parts of the permission
|
||||
# model are changed from the state they are in currently.
|
||||
# (commit hash ~ 3bcf375204c22271ad3ed1fc059b598b751aa03f)
|
||||
#
|
||||
# This is primarily to help with the performance of the help formatter
|
||||
|
||||
# This is less efficient if only checking one command,
|
||||
# but is much faster for checking all of them.
|
||||
|
||||
|
||||
async def mass_resolve(*, ctx: commands.Context, config: Config):
|
||||
"""
|
||||
Get's all the permission cog interactions for all loaded commands
|
||||
in the given context.
|
||||
"""
|
||||
|
||||
owner_settings = await config.owner_models()
|
||||
guild_owner_settings = await config.guild(ctx.guild).owner_models() if ctx.guild else None
|
||||
|
||||
ret = {"allowed": [], "denied": [], "default": []}
|
||||
|
||||
for cogname, cog in ctx.bot.cogs.items():
|
||||
|
||||
cog_setting = resolve_cog_or_command(
|
||||
objname=cogname, models=owner_settings, ctx=ctx, typ="cogs"
|
||||
)
|
||||
if cog_setting is None and guild_owner_settings:
|
||||
cog_setting = resolve_cog_or_command(
|
||||
objname=cogname, models=guild_owner_settings, ctx=ctx, typ="cogs"
|
||||
)
|
||||
|
||||
for command in [c for c in ctx.bot.all_commands.values() if c.instance is cog]:
|
||||
resolution = recursively_resolve(
|
||||
com_or_group=command,
|
||||
o_models=owner_settings,
|
||||
g_models=guild_owner_settings,
|
||||
ctx=ctx,
|
||||
)
|
||||
|
||||
for com, resolved in resolution:
|
||||
if resolved is None:
|
||||
resolved = cog_setting
|
||||
if resolved is True:
|
||||
ret["allowed"].append(com)
|
||||
elif resolved is False:
|
||||
ret["denied"].append(com)
|
||||
else:
|
||||
ret["default"].append(com)
|
||||
|
||||
ret = {k: set(v) for k, v in ret.items()}
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def recursively_resolve(*, com_or_group, o_models, g_models, ctx, override=False):
|
||||
ret = []
|
||||
if override:
|
||||
current = False
|
||||
else:
|
||||
current = resolve_cog_or_command(
|
||||
typ="commands", objname=com_or_group.qualified_name, ctx=ctx, models=o_models
|
||||
)
|
||||
if current is None and g_models:
|
||||
current = resolve_cog_or_command(
|
||||
typ="commands", objname=com_or_group.qualified_name, ctx=ctx, models=o_models
|
||||
)
|
||||
ret.append((com_or_group, current))
|
||||
if isinstance(com_or_group, commands.Group):
|
||||
for com in com_or_group.commands:
|
||||
ret.extend(
|
||||
recursively_resolve(
|
||||
com_or_group=com,
|
||||
o_models=o_models,
|
||||
g_models=g_models,
|
||||
ctx=ctx,
|
||||
override=(current is False),
|
||||
)
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
def resolve_cog_or_command(*, typ, ctx, objname, models: dict) -> bool:
|
||||
"""
|
||||
Resolves models in order.
|
||||
"""
|
||||
|
||||
resolved = None
|
||||
|
||||
if objname in models.get(typ, {}):
|
||||
blacklist = models[typ][objname].get("deny", [])
|
||||
whitelist = models[typ][objname].get("allow", [])
|
||||
resolved = resolve_lists(ctx=ctx, whitelist=whitelist, blacklist=blacklist)
|
||||
if resolved is not None:
|
||||
return resolved
|
||||
resolved = models[typ][objname].get("default", None)
|
||||
if resolved is not None:
|
||||
return resolved
|
||||
return None
|
||||
@@ -7,16 +7,19 @@ from redbot.core.bot import Red
|
||||
from redbot.core import checks
|
||||
from redbot.core.config import Config
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
from redbot.core.utils.caching import LRUDict
|
||||
|
||||
from .resolvers import val_if_check_is_valid, resolve_models
|
||||
from .resolvers import val_if_check_is_valid, resolve_models, entries_from_ctx
|
||||
from .yaml_handler import yamlset_acl, yamlget_acl
|
||||
from .converters import CogOrCommand, RuleType
|
||||
from .converters import CogOrCommand, RuleType, ClearableRuleType
|
||||
from .mass_resolution import mass_resolve
|
||||
|
||||
_models = ["owner", "guildowner", "admin", "mod"]
|
||||
_models = ["owner", "guildowner", "admin", "mod", "all"]
|
||||
|
||||
_ = Translator("Permissions", __file__)
|
||||
|
||||
REACTS = {"\N{WHITE HEAVY CHECK MARK}": True, "\N{NEGATIVE SQUARED CROSS MARK}": False}
|
||||
Y_OR_N = {"y": True, "yes": True, "n": False, "no": False}
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
@@ -32,61 +35,34 @@ class Permissions:
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, identifier=78631113035100160, force_registration=True)
|
||||
self._before = []
|
||||
self._after = []
|
||||
self.config.register_global(owner_models={})
|
||||
self.config.register_guild(owner_models={})
|
||||
self.cache = LRUDict(size=25000) # This can be tuned later
|
||||
|
||||
def add_check(self, check_obj: object, before_or_after: str):
|
||||
async def get_user_ctx_overrides(self, ctx: commands.Context) -> dict:
|
||||
"""
|
||||
adds a check to the check ordering
|
||||
This takes a context object, and returns a dict of
|
||||
|
||||
checks should be a function taking 2 arguments:
|
||||
ctx: commands.Context
|
||||
level: str
|
||||
allowed: list of commands
|
||||
denied: list of commands
|
||||
default: list of commands
|
||||
|
||||
and returning:
|
||||
None: do not interfere
|
||||
True: command should be allowed even if they dont
|
||||
have role or perm requirements for the check
|
||||
False: command should be blocked
|
||||
representing how permissions interacts with the
|
||||
user, channel, guild, and (possibly) voice channel
|
||||
for all commands on the bot (not just the one in the context object)
|
||||
|
||||
before_or_after:
|
||||
Should literally be a str equaling 'before' or 'after'
|
||||
This should be based on if this should take priority
|
||||
over set rules or not
|
||||
This mainly exists for use by the help formatter,
|
||||
but others may find it useful
|
||||
|
||||
3rd party cogs adding checks using this should only allow
|
||||
the owner to add checks before, and ensure only the owner
|
||||
can add checks recieving the level 'owner'
|
||||
Unlike the rest of the permission system, if other models are added later,
|
||||
due to optimizations made for this, this needs to be adjusted accordingly
|
||||
|
||||
3rd party cogs should keep a copy of of any checks they registered
|
||||
and deregister then on unload
|
||||
This does not account for before and after permission hooks,
|
||||
these need to be checked seperately
|
||||
"""
|
||||
return await mass_resolve(ctx=ctx, config=self.config)
|
||||
|
||||
if before_or_after == "before":
|
||||
self._before.append(check_obj)
|
||||
elif before_or_after == "after":
|
||||
self._after.append(check_obj)
|
||||
else:
|
||||
raise TypeError("RTFM")
|
||||
|
||||
def remove_check(self, check_obj: object, before_or_after: str):
|
||||
"""
|
||||
removes a previously registered check object
|
||||
|
||||
3rd party cogs should keep a copy of of any checks they registered
|
||||
and deregister then on unload
|
||||
"""
|
||||
|
||||
if before_or_after == "before":
|
||||
self._before.remove(check_obj)
|
||||
elif before_or_after == "after":
|
||||
self._after.remove(check_obj)
|
||||
else:
|
||||
raise TypeError("RTFM")
|
||||
|
||||
async def __global_check(self, ctx):
|
||||
async def __global_check(self, ctx: commands.Context) -> bool:
|
||||
"""
|
||||
Yes, this is needed on top of hooking into checks.py
|
||||
to ensure that unchecked commands can still be managed by permissions
|
||||
@@ -94,7 +70,7 @@ class Permissions:
|
||||
defering to check logic
|
||||
This works since all checks must be True to run
|
||||
"""
|
||||
v = await self.check_overrides(ctx, "mod")
|
||||
v = await self.check_overrides(ctx, "all")
|
||||
|
||||
if v is False:
|
||||
return False
|
||||
@@ -109,7 +85,7 @@ class Permissions:
|
||||
ctx: `redbot.core.context.commands.Context`
|
||||
The context of the command
|
||||
level: `str`
|
||||
One of 'owner', 'guildowner', 'admin', 'mod'
|
||||
One of 'owner', 'guildowner', 'admin', 'mod', 'all'
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -119,25 +95,44 @@ class Permissions:
|
||||
"""
|
||||
if await ctx.bot.is_owner(ctx.author):
|
||||
return True
|
||||
voice_channel = None
|
||||
with contextlib.suppress(Exception):
|
||||
voice_channel = ctx.author.voice.voice_channel
|
||||
entries = [x for x in (ctx.author, voice_channel, ctx.channel) if x]
|
||||
roles = sorted(ctx.author.roles, reverse=True) if ctx.guild else []
|
||||
entries.extend([x.id for x in roles])
|
||||
|
||||
for check in self._before:
|
||||
before = [
|
||||
getattr(cog, "_{0.__class__.__name__}__red_permissions_before".format(cog), None)
|
||||
for cog in ctx.bot.cogs.values()
|
||||
]
|
||||
for check in before:
|
||||
if check is None:
|
||||
continue
|
||||
override = await val_if_check_is_valid(check=check, ctx=ctx, level=level)
|
||||
if override is not None:
|
||||
return override
|
||||
|
||||
for model in self.resolution_order[level]:
|
||||
override_model = getattr(self, model + "_model", None)
|
||||
override = await override_model(ctx) if override_model else None
|
||||
# checked ids + configureable to be checked against
|
||||
cache_tup = entries_from_ctx(ctx) + (
|
||||
ctx.cog.__class__.__name__,
|
||||
ctx.command.qualified_name,
|
||||
)
|
||||
if cache_tup in self.cache:
|
||||
override = self.cache[cache_tup]
|
||||
if override is not None:
|
||||
return override
|
||||
else:
|
||||
for model in self.resolution_order[level]:
|
||||
if ctx.guild is None and model != "owner":
|
||||
break
|
||||
override_model = getattr(self, model + "_model", None)
|
||||
override = await override_model(ctx) if override_model else None
|
||||
if override is not None:
|
||||
self.cache[cache_tup] = override
|
||||
return override
|
||||
# This is intentional not being in an else block
|
||||
self.cache[cache_tup] = None
|
||||
|
||||
for check in self._after:
|
||||
after = [
|
||||
getattr(cog, "_{0.__class__.__name__}__red_permissions_after".format(cog), None)
|
||||
for cog in ctx.bot.cogs.values()
|
||||
]
|
||||
for check in after:
|
||||
override = await val_if_check_is_valid(check=check, ctx=ctx, level=level)
|
||||
if override is not None:
|
||||
return override
|
||||
@@ -156,7 +151,8 @@ class Permissions:
|
||||
"""
|
||||
Handles guild level overrides
|
||||
"""
|
||||
|
||||
if ctx.guild is None:
|
||||
return None
|
||||
async with self.config.guild(ctx.guild).owner_models() as models:
|
||||
return resolve_models(ctx=ctx, models=models)
|
||||
|
||||
@@ -171,8 +167,7 @@ class Permissions:
|
||||
"""
|
||||
Permission management tools
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
pass
|
||||
|
||||
@permissions.command()
|
||||
async def explain(self, ctx: commands.Context):
|
||||
@@ -206,10 +201,10 @@ class Permissions:
|
||||
"\n"
|
||||
"1. Rules about a user.\n"
|
||||
"2. Rules about the voice channel a user is in.\n"
|
||||
"3. Rules about the text channel a command was issued in\n"
|
||||
"3. Rules about the text channel a command was issued in.\n"
|
||||
"4. Rules about a role the user has "
|
||||
"(The highest role they have with a rule will be used)\n"
|
||||
"5. Rules about the guild a user is in (Owner level only)"
|
||||
"(The highest role they have with a rule will be used).\n"
|
||||
"5. Rules about the guild a user is in (Owner level only)."
|
||||
"\n\nFor more details, please read the official documentation."
|
||||
)
|
||||
|
||||
@@ -236,7 +231,9 @@ class Permissions:
|
||||
else:
|
||||
try:
|
||||
testcontext = await self.bot.get_context(message, cls=commands.Context)
|
||||
can = await com.can_run(testcontext)
|
||||
can = await com.can_run(testcontext) and all(
|
||||
[await p.can_run(testcontext) for p in com.parents]
|
||||
)
|
||||
except commands.CheckFailure:
|
||||
can = False
|
||||
|
||||
@@ -254,15 +251,16 @@ class Permissions:
|
||||
Take a YAML file upload to set permissions from
|
||||
"""
|
||||
if not ctx.message.attachments:
|
||||
return await ctx.send(_("You must upload a file"))
|
||||
return await ctx.send(_("You must upload a file."))
|
||||
|
||||
try:
|
||||
await yamlset_acl(ctx, config=self.config.owner_models, update=False)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return await ctx.send(_("Inalid syntax"))
|
||||
return await ctx.send(_("Invalid syntax."))
|
||||
else:
|
||||
await ctx.send(_("Rules set."))
|
||||
self.invalidate_cache()
|
||||
|
||||
@checks.is_owner()
|
||||
@permissions.command(name="getglobalacl")
|
||||
@@ -280,15 +278,16 @@ class Permissions:
|
||||
Take a YAML file upload to set permissions from
|
||||
"""
|
||||
if not ctx.message.attachments:
|
||||
return await ctx.send(_("You must upload a file"))
|
||||
return await ctx.send(_("You must upload a file."))
|
||||
|
||||
try:
|
||||
await yamlset_acl(ctx, config=self.config.guild(ctx.guild).owner_models, update=False)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return await ctx.send(_("Inalid syntax"))
|
||||
return await ctx.send(_("Invalid syntax."))
|
||||
else:
|
||||
await ctx.send(_("Rules set."))
|
||||
self.invalidate_cache(ctx.guild.id)
|
||||
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
@@ -309,15 +308,16 @@ class Permissions:
|
||||
Use this to not lose existing rules
|
||||
"""
|
||||
if not ctx.message.attachments:
|
||||
return await ctx.send(_("You must upload a file"))
|
||||
return await ctx.send(_("You must upload a file."))
|
||||
|
||||
try:
|
||||
await yamlset_acl(ctx, config=self.config.guild(ctx.guild).owner_models, update=True)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return await ctx.send(_("Inalid syntax"))
|
||||
return await ctx.send(_("Invalid syntax."))
|
||||
else:
|
||||
await ctx.send(_("Rules set."))
|
||||
self.invalidate_cache(ctx.guild.id)
|
||||
|
||||
@checks.is_owner()
|
||||
@permissions.command(name="updateglobalacl")
|
||||
@@ -328,15 +328,16 @@ class Permissions:
|
||||
Use this to not lose existing rules
|
||||
"""
|
||||
if not ctx.message.attachments:
|
||||
return await ctx.send(_("You must upload a file"))
|
||||
return await ctx.send(_("You must upload a file."))
|
||||
|
||||
try:
|
||||
await yamlset_acl(ctx, config=self.config.owner_models, update=True)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return await ctx.send(_("Inalid syntax"))
|
||||
return await ctx.send(_("Invalid syntax."))
|
||||
else:
|
||||
await ctx.send(_("Rules set."))
|
||||
self.invalidate_cache()
|
||||
|
||||
@checks.is_owner()
|
||||
@permissions.command(name="addglobalrule")
|
||||
@@ -348,7 +349,7 @@ class Permissions:
|
||||
who_or_what: str,
|
||||
):
|
||||
"""
|
||||
adds something to the rules
|
||||
Adds something to the rules
|
||||
|
||||
allow_or_deny: "allow" or "deny", depending on the rule to modify
|
||||
|
||||
@@ -363,7 +364,7 @@ class Permissions:
|
||||
"""
|
||||
obj = self.find_object_uniquely(who_or_what)
|
||||
if not obj:
|
||||
return await ctx.send(_("No unique matches. Try using an ID or mention"))
|
||||
return await ctx.send(_("No unique matches. Try using an ID or mention."))
|
||||
model_type, type_name = cog_or_command
|
||||
async with self.config.owner_models() as models:
|
||||
data = {k: v for k, v in models.items()}
|
||||
@@ -380,6 +381,7 @@ class Permissions:
|
||||
data[model_type][type_name][allow_or_deny].append(obj)
|
||||
models.update(data)
|
||||
await ctx.send(_("Rule added."))
|
||||
self.invalidate_cache(type_name, obj)
|
||||
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
@@ -392,7 +394,7 @@ class Permissions:
|
||||
who_or_what: str,
|
||||
):
|
||||
"""
|
||||
adds something to the rules
|
||||
Adds something to the rules
|
||||
|
||||
allow_or_deny: "allow" or "deny", depending on the rule to modify
|
||||
|
||||
@@ -407,7 +409,7 @@ class Permissions:
|
||||
"""
|
||||
obj = self.find_object_uniquely(who_or_what)
|
||||
if not obj:
|
||||
return await ctx.send(_("No unique matches. Try using an ID or mention"))
|
||||
return await ctx.send(_("No unique matches. Try using an ID or mention."))
|
||||
model_type, type_name = cog_or_command
|
||||
async with self.config.guild(ctx.guild).owner_models() as models:
|
||||
data = {k: v for k, v in models.items()}
|
||||
@@ -424,6 +426,7 @@ class Permissions:
|
||||
data[model_type][type_name][allow_or_deny].append(obj)
|
||||
models.update(data)
|
||||
await ctx.send(_("Rule added."))
|
||||
self.invalidate_cache(type_name, obj)
|
||||
|
||||
@checks.is_owner()
|
||||
@permissions.command(name="removeglobalrule")
|
||||
@@ -450,7 +453,7 @@ class Permissions:
|
||||
"""
|
||||
obj = self.find_object_uniquely(who_or_what)
|
||||
if not obj:
|
||||
return await ctx.send(_("No unique matches. Try using an ID or mention"))
|
||||
return await ctx.send(_("No unique matches. Try using an ID or mention."))
|
||||
model_type, type_name = cog_or_command
|
||||
async with self.config.owner_models() as models:
|
||||
data = {k: v for k, v in models.items()}
|
||||
@@ -467,6 +470,7 @@ class Permissions:
|
||||
data[model_type][type_name][allow_or_deny].remove(obj)
|
||||
models.update(data)
|
||||
await ctx.send(_("Rule removed."))
|
||||
self.invalidate_cache(obj, type_name)
|
||||
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
@@ -494,7 +498,7 @@ class Permissions:
|
||||
"""
|
||||
obj = self.find_object_uniquely(who_or_what)
|
||||
if not obj:
|
||||
return await ctx.send(_("No unique matches. Try using an ID or mention"))
|
||||
return await ctx.send(_("No unique matches. Try using an ID or mention."))
|
||||
model_type, type_name = cog_or_command
|
||||
async with self.config.guild(ctx.guild).owner_models() as models:
|
||||
data = {k: v for k, v in models.items()}
|
||||
@@ -511,23 +515,18 @@ class Permissions:
|
||||
data[model_type][type_name][allow_or_deny].remove(obj)
|
||||
models.update(data)
|
||||
await ctx.send(_("Rule removed."))
|
||||
self.invalidate_cache(obj, type_name)
|
||||
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
@permissions.command(name="setdefaultguildrule")
|
||||
async def set_default_guild_rule(
|
||||
self, ctx: commands.Context, cog_or_command: CogOrCommand, allow_or_deny: RuleType = None
|
||||
self, ctx: commands.Context, allow_or_deny: ClearableRuleType, cog_or_command: CogOrCommand
|
||||
):
|
||||
"""
|
||||
Sets the default behavior for a cog or command if no rule is set
|
||||
|
||||
Use with a cog or command and no setting to clear the default and defer to
|
||||
normal check logic
|
||||
"""
|
||||
if allow_or_deny:
|
||||
val_to_set = {"allow": True, "deny": False}.get(allow_or_deny)
|
||||
else:
|
||||
val_to_set = None
|
||||
val_to_set = {"allow": True, "deny": False, "clear": None}.get(allow_or_deny)
|
||||
|
||||
model_type, type_name = cog_or_command
|
||||
async with self.config.guild(ctx.guild).owner_models() as models:
|
||||
@@ -540,24 +539,18 @@ class Permissions:
|
||||
data[model_type][type_name]["default"] = val_to_set
|
||||
|
||||
models.update(data)
|
||||
await ctx.send(_("Defualt set."))
|
||||
await ctx.send(_("Default set."))
|
||||
self.invalidate_cache(type_name)
|
||||
|
||||
@checks.is_owner()
|
||||
@permissions.command(name="setdefaultglobalrule")
|
||||
async def set_default_global_rule(
|
||||
self, ctx: commands.Context, cog_or_command: CogOrCommand, allow_or_deny: RuleType = None
|
||||
self, ctx: commands.Context, allow_or_deny: ClearableRuleType, cog_or_command: CogOrCommand
|
||||
):
|
||||
"""
|
||||
Sets the default behavior for a cog or command if no rule is set
|
||||
|
||||
Use with a cog or command and no setting to clear the default and defer to
|
||||
normal check logic
|
||||
"""
|
||||
|
||||
if allow_or_deny:
|
||||
val_to_set = {"allow": True, "deny": False}.get(allow_or_deny)
|
||||
else:
|
||||
val_to_set = None
|
||||
val_to_set = {"allow": True, "deny": False, "clear": None}.get(allow_or_deny)
|
||||
|
||||
model_type, type_name = cog_or_command
|
||||
async with self.config.owner_models() as models:
|
||||
@@ -570,33 +563,18 @@ class Permissions:
|
||||
data[model_type][type_name]["default"] = val_to_set
|
||||
|
||||
models.update(data)
|
||||
await ctx.send(_("Defualt set."))
|
||||
await ctx.send(_("Default set."))
|
||||
self.invalidate_cache(type_name)
|
||||
|
||||
@commands.bot_has_permissions(add_reactions=True)
|
||||
@checks.is_owner()
|
||||
@permissions.command(name="clearglobalsettings")
|
||||
async def clear_globals(self, ctx: commands.Context):
|
||||
"""
|
||||
Clears all global rules.
|
||||
"""
|
||||
await self._confirm_then_clear_rules(ctx, is_guild=False)
|
||||
self.invalidate_cache()
|
||||
|
||||
m = await ctx.send("Are you sure?")
|
||||
for r in REACTS.keys():
|
||||
await m.add_reaction(r)
|
||||
try:
|
||||
reaction, user = await self.bot.wait_for(
|
||||
"reaction_add", check=lambda r, u: u == ctx.author and str(r) in REACTS, timeout=30
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
return await ctx.send(_("Ok, try responding with an emoji next time."))
|
||||
|
||||
if REACTS.get(str(reaction)):
|
||||
await self.config.owner_models.clear()
|
||||
await ctx.send(_("Global settings cleared"))
|
||||
else:
|
||||
await ctx.send(_("Okay."))
|
||||
|
||||
@commands.bot_has_permissions(add_reactions=True)
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
@permissions.command(name="clearguildsettings")
|
||||
@@ -604,23 +582,61 @@ class Permissions:
|
||||
"""
|
||||
Clears all guild rules.
|
||||
"""
|
||||
await self._confirm_then_clear_rules(ctx, is_guild=True)
|
||||
self.invalidate_cache(ctx.guild.id)
|
||||
|
||||
m = await ctx.send("Are you sure?")
|
||||
for r in REACTS.keys():
|
||||
await m.add_reaction(r)
|
||||
try:
|
||||
reaction, user = await self.bot.wait_for(
|
||||
"reaction_add", check=lambda r, u: u == ctx.author and str(r) in REACTS, timeout=30
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
return await ctx.send(_("Ok, try responding with an emoji next time."))
|
||||
async def _confirm_then_clear_rules(self, ctx: commands.Context, is_guild: bool):
|
||||
if ctx.guild.me.permissions_in(ctx.channel).add_reactions:
|
||||
m = await ctx.send(_("Are you sure?"))
|
||||
for r in REACTS.keys():
|
||||
await m.add_reaction(r)
|
||||
try:
|
||||
reaction, user = await self.bot.wait_for(
|
||||
"reaction_add",
|
||||
check=lambda r, u: u == ctx.author and str(r) in REACTS,
|
||||
timeout=30,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
return await ctx.send(_("Ok, try responding with an emoji next time."))
|
||||
|
||||
if REACTS.get(str(reaction)):
|
||||
await self.config.guild(ctx.guild).owner_models.clear()
|
||||
await ctx.send(_("Guild settings cleared"))
|
||||
agreed = REACTS.get(str(reaction))
|
||||
else:
|
||||
await ctx.send(_("Are you sure? (y/n)"))
|
||||
try:
|
||||
message = await self.bot.wait_for(
|
||||
"message",
|
||||
check=lambda m: m.author == ctx.author and m.content in Y_OR_N,
|
||||
timeout=30,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
return await ctx.send(_("Ok, try responding with yes or no next time."))
|
||||
|
||||
agreed = Y_OR_N.get(message.content.lower())
|
||||
|
||||
if agreed:
|
||||
if is_guild:
|
||||
await self.config.guild(ctx.guild).owner_models.clear()
|
||||
await ctx.send(_("Guild settings cleared."))
|
||||
else:
|
||||
await self.config.owner_models.clear()
|
||||
await ctx.send(_("Global settings cleared."))
|
||||
else:
|
||||
await ctx.send(_("Okay."))
|
||||
|
||||
def invalidate_cache(self, *to_invalidate):
|
||||
"""
|
||||
Either invalidates the entire cache (if given no objects)
|
||||
or does a partial invalidation based on passed objects
|
||||
"""
|
||||
if len(to_invalidate) == 0:
|
||||
self.cache.clear()
|
||||
return
|
||||
# LRUDict inherits from ordered dict, hence the syntax below
|
||||
stil_valid = [
|
||||
(k, v) for k, v in self.cache.items() if not any(obj in k for obj in to_invalidate)
|
||||
]
|
||||
self.cache = LRUDict(stil_valid, size=self.cache.size)
|
||||
|
||||
def find_object_uniquely(self, info: str) -> int:
|
||||
"""
|
||||
Finds an object uniquely, returns it's id or returns None
|
||||
|
||||
@@ -7,26 +7,32 @@ from redbot.core import commands
|
||||
log = logging.getLogger("redbot.cogs.permissions.resolvers")
|
||||
|
||||
|
||||
def entries_from_ctx(ctx: commands.Context) -> tuple:
|
||||
voice_channel = None
|
||||
with contextlib.suppress(Exception):
|
||||
voice_channel = ctx.author.voice.voice_channel
|
||||
entries = [x.id for x in (ctx.author, voice_channel, ctx.channel) if x]
|
||||
roles = sorted(ctx.author.roles, reverse=True) if ctx.guild else []
|
||||
entries.extend([x.id for x in roles])
|
||||
# entries now contains the following (in order) (if applicable)
|
||||
# author.id
|
||||
# author.voice.voice_channel.id
|
||||
# channel.id
|
||||
# role.id for each role (highest to lowest)
|
||||
# (implicitly) guild.id because
|
||||
# the @everyone role shares an id with the guild
|
||||
return tuple(entries)
|
||||
|
||||
|
||||
async def val_if_check_is_valid(*, ctx: commands.Context, check: object, level: str) -> bool:
|
||||
"""
|
||||
Returns the value from a check if it is valid
|
||||
"""
|
||||
|
||||
# Non staticmethods should not be run without their parent
|
||||
# class, even if the parent class did not deregister them
|
||||
if check.__module__ is None:
|
||||
pass
|
||||
elif isinstance(check, types.FunctionType):
|
||||
if (
|
||||
next(filter(lambda x: check.__module__ == x.__module__, ctx.bot.cogs.values()), None)
|
||||
is None
|
||||
):
|
||||
return None
|
||||
|
||||
val = None
|
||||
# let's not spam the console with improperly made 3rd party checks
|
||||
try:
|
||||
if asyncio.iscoroutine(check) or asyncio.iscoroutinefunction(check):
|
||||
if asyncio.iscoroutinefunction(check):
|
||||
val = await check(ctx, level=level)
|
||||
else:
|
||||
val = check(ctx, level=level)
|
||||
@@ -67,23 +73,7 @@ def resolve_lists(*, ctx: commands.Context, whitelist: list, blacklist: list) ->
|
||||
"""
|
||||
resolves specific lists
|
||||
"""
|
||||
|
||||
voice_channel = None
|
||||
with contextlib.suppress(Exception):
|
||||
voice_channel = ctx.author.voice.voice_channel
|
||||
|
||||
entries = [x.id for x in (ctx.author, voice_channel, ctx.channel) if x]
|
||||
roles = sorted(ctx.author.roles, reverse=True) if ctx.guild else []
|
||||
entries.extend([x.id for x in roles])
|
||||
# entries now contains the following (in order) (if applicable)
|
||||
# author.id
|
||||
# author.voice.voice_channel.id
|
||||
# channel.id
|
||||
# role.id for each role (highest to lowest)
|
||||
# (implicitly) guild.id because
|
||||
# the @everyone role shares an id with the guild
|
||||
|
||||
for entry in entries:
|
||||
for entry in entries_from_ctx(ctx):
|
||||
if entry in whitelist:
|
||||
return True
|
||||
if entry in blacklist:
|
||||
|
||||
@@ -59,29 +59,29 @@ class Reports:
|
||||
@commands.group(name="reportset")
|
||||
async def reportset(self, ctx: commands.Context):
|
||||
"""
|
||||
settings for reports
|
||||
Settings for the report system.
|
||||
"""
|
||||
pass
|
||||
|
||||
@checks.admin_or_permissions(manage_guild=True)
|
||||
@reportset.command(name="output")
|
||||
async def setoutput(self, ctx: commands.Context, channel: discord.TextChannel):
|
||||
"""sets the output channel"""
|
||||
"""Set the channel where reports will show up"""
|
||||
await self.config.guild(ctx.guild).output_channel.set(channel.id)
|
||||
await ctx.send(_("Report Channel Set."))
|
||||
await ctx.send(_("The report channel has been set."))
|
||||
|
||||
@checks.admin_or_permissions(manage_guild=True)
|
||||
@reportset.command(name="toggleactive")
|
||||
@reportset.command(name="toggle", aliases=["toggleactive"])
|
||||
async def report_toggle(self, ctx: commands.Context):
|
||||
"""Toggles whether the Reporting tool is enabled or not"""
|
||||
"""Enables or Disables reporting for the server"""
|
||||
|
||||
active = await self.config.guild(ctx.guild).active()
|
||||
active = not active
|
||||
await self.config.guild(ctx.guild).active.set(active)
|
||||
if active:
|
||||
await ctx.send(_("Reporting now enabled"))
|
||||
await ctx.send(_("Reporting is now enabled"))
|
||||
else:
|
||||
await ctx.send(_("Reporting disabled."))
|
||||
await ctx.send(_("Reporting is now disabled."))
|
||||
|
||||
async def internal_filter(self, m: discord.Member, mod=False, perms=None):
|
||||
ret = False
|
||||
@@ -105,7 +105,7 @@ class Reports:
|
||||
*,
|
||||
mod: bool = False,
|
||||
permissions: Union[discord.Permissions, dict] = None,
|
||||
prompt: str = ""
|
||||
prompt: str = "",
|
||||
):
|
||||
"""
|
||||
discovers which of shared guilds between the bot
|
||||
@@ -175,7 +175,10 @@ class Reports:
|
||||
if await self.bot.embed_requested(channel, author):
|
||||
em = discord.Embed(description=report)
|
||||
em.set_author(
|
||||
name=_("Report from {0.display_name}").format(author), icon_url=author.avatar_url
|
||||
name=_("Report from {author}{maybe_nick}").format(
|
||||
author=author, maybe_nick=(f" ({author.nick})" if author.nick else "")
|
||||
),
|
||||
icon_url=author.avatar_url,
|
||||
)
|
||||
em.set_footer(text=_("Report #{}").format(ticket_number))
|
||||
send_content = None
|
||||
@@ -201,10 +204,10 @@ class Reports:
|
||||
@commands.group(name="report", invoke_without_command=True)
|
||||
async def report(self, ctx: commands.Context, *, _report: str = ""):
|
||||
"""
|
||||
Follow the prompts to make a report
|
||||
Send a report.
|
||||
|
||||
optionally use with a report message
|
||||
to use it non interactively
|
||||
Use without arguments for interactive reporting, or do
|
||||
[p]report <text> to use it non-interactively.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
@@ -212,11 +215,6 @@ class Reports:
|
||||
guild = await self.discover_guild(
|
||||
author, prompt=_("Select a server to make a report in by number.")
|
||||
)
|
||||
else:
|
||||
try:
|
||||
await ctx.message.delete()
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
if guild is None:
|
||||
return
|
||||
g_active = await self.config.guild(guild).active()
|
||||
@@ -229,22 +227,18 @@ class Reports:
|
||||
if self.antispam[guild.id][author.id].spammy:
|
||||
return await author.send(
|
||||
_(
|
||||
"You've sent a few too many of these recently. "
|
||||
"Contact a server admin to resolve this, or try again "
|
||||
"later."
|
||||
"You've sent too many reports recently. "
|
||||
"Please contact a server admin if this is important matter, "
|
||||
"or please wait and try again later."
|
||||
)
|
||||
)
|
||||
|
||||
if author.id in self.user_cache:
|
||||
return await author.send(
|
||||
_("Finish making your prior report " "before making an additional one")
|
||||
_(
|
||||
"Please finish making your prior report before trying to make an "
|
||||
"additional one!"
|
||||
)
|
||||
)
|
||||
|
||||
if ctx.guild:
|
||||
try:
|
||||
await ctx.message.delete()
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
pass
|
||||
self.user_cache.append(author.id)
|
||||
|
||||
if _report:
|
||||
@@ -261,9 +255,7 @@ class Reports:
|
||||
)
|
||||
)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(_("This requires DMs enabled."))
|
||||
self.user_cache.remove(author.id)
|
||||
return
|
||||
return await ctx.send(_("This requires DMs enabled."))
|
||||
|
||||
def pred(m):
|
||||
return m.author == author and m.channel == dm.channel
|
||||
@@ -271,18 +263,32 @@ class Reports:
|
||||
try:
|
||||
message = await self.bot.wait_for("message", check=pred, timeout=180)
|
||||
except asyncio.TimeoutError:
|
||||
await author.send(_("You took too long. Try again later."))
|
||||
return await author.send(_("You took too long. Try again later."))
|
||||
else:
|
||||
val = await self.send_report(message, guild)
|
||||
|
||||
with contextlib.suppress(discord.Forbidden, discord.HTTPException):
|
||||
if val is None:
|
||||
await author.send(_("There was an error sending your report."))
|
||||
await author.send(
|
||||
_("There was an error sending your report, please contact a server admin.")
|
||||
)
|
||||
else:
|
||||
await author.send(_("Your report was submitted. (Ticket #{})").format(val))
|
||||
self.antispam[guild.id][author.id].stamp()
|
||||
self.antispam[guild.id][author.id].stamp()
|
||||
|
||||
self.user_cache.remove(author.id)
|
||||
@report.after_invoke
|
||||
async def report_cleanup(self, ctx: commands.Context):
|
||||
"""
|
||||
The logic is cleaner this way
|
||||
"""
|
||||
if ctx.author.id in self.user_cache:
|
||||
self.user_cache.remove(ctx.author.id)
|
||||
if ctx.guild and ctx.invoked_subcommand is None:
|
||||
if ctx.channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
try:
|
||||
await ctx.message.delete()
|
||||
except discord.NotFound:
|
||||
pass
|
||||
|
||||
async def on_raw_reaction_add(self, payload):
|
||||
"""
|
||||
@@ -311,17 +317,19 @@ class Reports:
|
||||
if msgs:
|
||||
self.tunnel_store[k]["msgs"] = msgs
|
||||
|
||||
@commands.guild_only()
|
||||
@checks.mod_or_permissions(manage_members=True)
|
||||
@report.command(name="interact")
|
||||
async def response(self, ctx, ticket_number: int):
|
||||
"""
|
||||
opens a message tunnel between things you say in this channel
|
||||
and the ticket opener's direct messages
|
||||
Open a message tunnel.
|
||||
|
||||
This tunnel will forward things you say in this channel
|
||||
to the ticket opener's direct messages.
|
||||
|
||||
tunnels do not persist across bot restarts
|
||||
Tunnels do not persist across bot restarts.
|
||||
"""
|
||||
|
||||
# note, mod_or_permissions is an implicit guild_only
|
||||
guild = ctx.guild
|
||||
rec = await self.config.custom("REPORT", guild.id, ticket_number).report()
|
||||
|
||||
@@ -344,14 +352,15 @@ class Reports:
|
||||
)
|
||||
|
||||
big_topic = _(
|
||||
"{who} opened a 2-way communication."
|
||||
"{who} opened a 2-way communication "
|
||||
"about ticket number {ticketnum}. Anything you say or upload here "
|
||||
"(8MB file size limitation on uploads) "
|
||||
"will be forwarded to them until the communication is closed.\n"
|
||||
"You can close a communication at any point "
|
||||
"by reacting with the X to the last message recieved. "
|
||||
"\nAny message succesfully forwarded will be marked with a check."
|
||||
"\nTunnels are not persistent across bot restarts."
|
||||
"You can close a communication at any point by reacting with "
|
||||
"the \N{NEGATIVE SQUARED CROSS MARK} to the last message recieved.\n"
|
||||
"Any message succesfully forwarded will be marked with "
|
||||
"\N{WHITE HEAVY CHECK MARK}.\n"
|
||||
"Tunnels are not persistent across bot restarts."
|
||||
)
|
||||
topic = big_topic.format(
|
||||
ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild)
|
||||
@@ -359,8 +368,7 @@ class Reports:
|
||||
try:
|
||||
m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(_("User has disabled DMs."))
|
||||
tun.close()
|
||||
await ctx.send(_("That user has DMs disabled."))
|
||||
else:
|
||||
self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m}
|
||||
await ctx.send(big_topic.format(who=_("You have"), ticketnum=ticket_number))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from .streams import Streams
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Streams(bot))
|
||||
async def setup(bot):
|
||||
cog = Streams(bot)
|
||||
await cog.initialize()
|
||||
bot.add_cog(cog)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user