mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-06 09:22:31 -05:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@@ -24,6 +24,8 @@ redbot/core/utils/mod.py @palmtree5
|
|||||||
redbot/core/utils/data_converter.py @mikeshardmind
|
redbot/core/utils/data_converter.py @mikeshardmind
|
||||||
redbot/core/utils/antispam.py @mikeshardmind
|
redbot/core/utils/antispam.py @mikeshardmind
|
||||||
redbot/core/utils/tunnel.py @mikeshardmind
|
redbot/core/utils/tunnel.py @mikeshardmind
|
||||||
|
redbot/core/utils/caching.py @mikeshardmind
|
||||||
|
redbot/core/utils/common_filters.py @mikeshardmind
|
||||||
|
|
||||||
# Cogs
|
# Cogs
|
||||||
redbot/cogs/admin/* @tekulvw
|
redbot/cogs/admin/* @tekulvw
|
||||||
@@ -44,6 +46,7 @@ redbot/cogs/trivia/* @Tobotimus
|
|||||||
redbot/cogs/dataconverter/* @mikeshardmind
|
redbot/cogs/dataconverter/* @mikeshardmind
|
||||||
redbot/cogs/reports/* @mikeshardmind
|
redbot/cogs/reports/* @mikeshardmind
|
||||||
redbot/cogs/permissions/* @mikeshardmind
|
redbot/cogs/permissions/* @mikeshardmind
|
||||||
|
redbot/cogs/warnings/* @palmtree5
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
docs/* @tekulvw @palmtree5
|
docs/* @tekulvw @palmtree5
|
||||||
|
|||||||
6
.github/CONTRIBUTING.md
vendored
6
.github/CONTRIBUTING.md
vendored
@@ -31,7 +31,7 @@ We love receiving contributions from our community. Any assistance you can provi
|
|||||||
# 2. Ground Rules
|
# 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.
|
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.
|
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:
|
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.
|
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.
|
5. Don't add new cogs unless specifically given approval in an issue discussing said cog idea.
|
||||||
@@ -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.
|
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:
|
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 (test environment `py36`)
|
||||||
- Ensures documentation builds without warnings, and all hyperlinks have a valid destination (test environment `docs`)
|
- 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`)
|
- 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>`.
|
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
|
### 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:
|
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
|
1. `make reformat`: Reformat all python files in the project with Black
|
||||||
|
|||||||
30
.gitignore
vendored
30
.gitignore
vendored
@@ -1,40 +1,16 @@
|
|||||||
# Trivia list repo injection
|
|
||||||
redbot/trivia/
|
|
||||||
|
|
||||||
*.json
|
*.json
|
||||||
*.exe
|
*.exe
|
||||||
*.dll
|
*.dll
|
||||||
|
*.pot
|
||||||
.data
|
.data
|
||||||
|
!/tests/cogs/dataconverter/data/**/*.json
|
||||||
|
|
||||||
### JetBrains template
|
### JetBrains template
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
# User-specific stuff:
|
# User-specific stuff:
|
||||||
.idea/**/workspace.xml
|
.idea/
|
||||||
.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:
|
|
||||||
*.iws
|
*.iws
|
||||||
|
|
||||||
## Plugin-specific files:
|
## Plugin-specific files:
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ formats:
|
|||||||
build:
|
build:
|
||||||
image: latest
|
image: latest
|
||||||
|
|
||||||
requirements_file: docs/requirements.txt
|
requirements_file: dependency_links.txt
|
||||||
|
|
||||||
python:
|
python:
|
||||||
version: 3.6
|
version: 3.6
|
||||||
pip_install: true
|
pip_install: true
|
||||||
extra_requirements:
|
extra_requirements:
|
||||||
|
- docs
|
||||||
- mongo
|
- mongo
|
||||||
|
|||||||
31
.travis.yml
31
.travis.yml
@@ -1,33 +1,37 @@
|
|||||||
dist: trusty
|
dist: xenial
|
||||||
language: python
|
language: python
|
||||||
cache: pip
|
cache: pip
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
|
sudo: true
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- 3.6.5
|
- 3.6.6
|
||||||
|
- 3.7
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
PIPENV_IGNORE_VIRTUALENVS=1
|
PIPENV_IGNORE_VIRTUALENVS=1
|
||||||
matrix:
|
matrix:
|
||||||
- TOXENV=py
|
TOXENV=py
|
||||||
- TOXENV=docs
|
|
||||||
- TOXENV=style
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install --upgrade pip pipenv
|
- pip install --upgrade pip tox
|
||||||
- pipenv install --dev
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- pipenv run tox
|
- tox
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
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
|
# These jobs only occur on tag creation for V3/develop if the prior ones succeed
|
||||||
- stage: PyPi Deployment
|
- stage: PyPi Deployment
|
||||||
if: tag IS present
|
if: tag IS present
|
||||||
python: 3.6.5
|
python: 3.6.6
|
||||||
env:
|
env:
|
||||||
- DEPLOYING=true
|
- DEPLOYING=true
|
||||||
deploy:
|
deploy:
|
||||||
@@ -39,11 +43,11 @@ jobs:
|
|||||||
on:
|
on:
|
||||||
repo: Cog-Creators/Red-DiscordBot
|
repo: Cog-Creators/Red-DiscordBot
|
||||||
branch: V3/develop
|
branch: V3/develop
|
||||||
python: 3.6.5
|
python: 3.6.6
|
||||||
tags: true
|
tags: true
|
||||||
- stage: Crowdin Deployment
|
- stage: Crowdin Deployment
|
||||||
if: tag IS present
|
if: tag IS present
|
||||||
python: 3.6.5
|
python: 3.6.6
|
||||||
env:
|
env:
|
||||||
- DEPLOYING=true
|
- DEPLOYING=true
|
||||||
before_deploy:
|
before_deploy:
|
||||||
@@ -51,12 +55,13 @@ jobs:
|
|||||||
- echo "deb https://artifacts.crowdin.com/repo/deb/ /" | sudo tee -a /etc/apt/sources.list
|
- echo "deb https://artifacts.crowdin.com/repo/deb/ /" | sudo tee -a /etc/apt/sources.list
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -y crowdin
|
- sudo apt-get install -y crowdin
|
||||||
|
- pip install redgettext==2.1
|
||||||
deploy:
|
deploy:
|
||||||
- provider: script
|
- provider: script
|
||||||
script: python3 ./generate_strings.py
|
script: make gettext
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
repo: Cog-Creators/Red-DiscordBot
|
repo: Cog-Creators/Red-DiscordBot
|
||||||
branch: V3/develop
|
branch: V3/develop
|
||||||
python: 3.6.5
|
python: 3.6.6
|
||||||
tags: true
|
tags: true
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
include README.rst
|
include README.rst
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include requirements.txt
|
include dependency_links.txt
|
||||||
include discord/bin/*.dll
|
|
||||||
include redbot/cogs/audio/application.yml
|
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -2,3 +2,6 @@ reformat:
|
|||||||
black -l 99 `git ls-files "*.py"`
|
black -l 99 `git ls-files "*.py"`
|
||||||
stylecheck:
|
stylecheck:
|
||||||
black --check -l 99 `git ls-files "*.py"`
|
black --check -l 99 `git ls-files "*.py"`
|
||||||
|
gettext:
|
||||||
|
redgettext --command-docstrings --verbose --recursive redbot --exclude-files "redbot/pytest/**/*"
|
||||||
|
crowdin upload
|
||||||
|
|||||||
14
Pipfile
14
Pipfile
@@ -4,17 +4,9 @@ verify_ssl = true
|
|||||||
name = "pypi"
|
name = "pypi"
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
"discord.py" = { git = 'git://github.com/Rapptz/discord.py', ref = 'rewrite', editable = true}
|
"discord.py" = { git = 'git://github.com/Rapptz/discord.py', ref = 'rewrite', editable = true }
|
||||||
"e1839a8" = {path = ".", editable = true}
|
"e1839a8" = { path = ".", editable = true, extras = ['mongo', 'voice'] }
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
tox = "*"
|
tox = "*"
|
||||||
pytest = "*"
|
"e1839a9" = { path = ".", editable = true, extras = ['docs', 'test', 'style'] }
|
||||||
pytest-asyncio = "*"
|
|
||||||
sphinx = ">1.7"
|
|
||||||
sphinxcontrib-asyncio = "*"
|
|
||||||
sphinx-rtd-theme = "*"
|
|
||||||
black = {version = "*", python_version = ">= '3.6'"}
|
|
||||||
|
|
||||||
[pipenv]
|
|
||||||
allow_prereleases = true
|
|
||||||
|
|||||||
529
Pipfile.lock
generated
529
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "d340e4a19777736703970e45766d05d67b973db38b87382b6ef8696cb53abb60"
|
"sha256": "edd35f353e1fadc20094e40de6627db77fd61303da01794214c44d748e99838b"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
@@ -16,21 +16,30 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"aiohttp": {
|
"aiohttp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:129d83dd067760cec3cfd4456b5c6d7ac29f2c639d856884568fd539bed5a51f",
|
"sha256:1a112a1fdf3802b7f2b182e22e51d71e4a8fa7387d0d38e79a268921b869e384",
|
||||||
"sha256:33c62afd115c456b0cf1e890fe6753055effe0f31a28321efd4f787378d6f4ab",
|
"sha256:33aa7c937ebaf063a860cbb0c263a771b33333a84965c6148eeafe64fb4e29ca",
|
||||||
"sha256:666756e1d4cf161ed1486b82f65fdd386ac07dd20fb10f025abf4be54be12746",
|
"sha256:550b4a0788500f6d00f41b7fdd9fcce6d78f99706a7b2f6f81d4d331c7ca468e",
|
||||||
"sha256:9705ded5a0faa25c8f14c6afb7044002d66c9120ed7eadb4aa9ca4aad32bd00c",
|
"sha256:601e8e83123b4d423a9dfddf7d6943f4f520651a78ffcd50c99d065136c7ff7b",
|
||||||
"sha256:af5bfdd164256118a0a306b3f7046e63207d1f8cba73a67dcc0bd858dcfcd3bc",
|
"sha256:620f19ba7628b70b177f5c2e6a55a6fd6e7c8591cde38c3f8f52551733d31b66",
|
||||||
"sha256:b80f44b99fa3c9b4530fcfa324a99b84843043c35b084e0b653566049974435d",
|
"sha256:70d56c784da1239c89d39fefa166fd429306dada641178389be4184a9c04e501",
|
||||||
"sha256:c67e105ec74b85c8cb666b6877569dee6f55b9548f982983b9bee80b3d47e6f3",
|
"sha256:7de2c9e445a5d257935011268202338538abef1aaff341a4733eca56419ca6f6",
|
||||||
"sha256:d15c6658de5b7783c2538407278fa062b079a46d5f814a133ae0f09bbb2cfbc4",
|
"sha256:96bb80b659cc2bafa160f3f0c346ce7fc10de1ffec4908d7f9690797f155f658",
|
||||||
"sha256:d611ebd1ef48498210b65486306e065fde031040a1f3c455ca1b6baa7bf32ad3",
|
"sha256:ae7501cc6a6c37b8d4774bf2218c37be47fe42019a2570e8510fc2044e59d573",
|
||||||
"sha256:dcc7e4dcec6b0012537b9f8a0726f8b111188894ab0f924b680d40b13d3298a0",
|
"sha256:c833aa6f4c9ac3e3eb843e3d999bae51339ad33a937303f43ce78064e61cb4b6",
|
||||||
"sha256:de8ef106e130b94ca143fdfc6f27cda1d8ba439462542377738af4d99d9f5dd2",
|
"sha256:dd81d85a342edf3d2a388e2f24d9facebc9c04550043888f970ee2f228c93059",
|
||||||
"sha256:eb6f1405b607fff7e44168e3ceb5d3c8a8c5a2d3effe0a27f843b16ec047a6d7",
|
"sha256:f20deec7a3fbaec7b5eb7ad99878427ad2ee4cc16a46732b705e8121cbb3cc12",
|
||||||
"sha256:f0e2ac69cb709367400008cebccd5d48161dd146096a009a632a132babe5714c"
|
"sha256:f52e7287eb9286a1e91e4c67c207c2573147fbaddc68f70efb5aeee5d1992f2e",
|
||||||
|
"sha256:fe7b2972ff7e779e812f974aa5695edc328ecf559ceeea887ac46f06f090ad4c",
|
||||||
|
"sha256:ff1447c84a02b9cd5dd3a9332d1fb181a4386c3625765bb5caf1cfbc210ab3f9"
|
||||||
],
|
],
|
||||||
"version": "==2.2.5"
|
"version": "==3.3.2"
|
||||||
|
},
|
||||||
|
"aiohttp-json-rpc": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:bf1eb7e30949b60f74cb84731b5676bd7dc3f0298056ddbbe989d9219260008c",
|
||||||
|
"sha256:e1ae47d522a7857c612be8ba447cec3cad8c8b7d628353289a0889a1135166c8"
|
||||||
|
],
|
||||||
|
"version": "==0.11"
|
||||||
},
|
},
|
||||||
"appdirs": {
|
"appdirs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -41,10 +50,18 @@
|
|||||||
},
|
},
|
||||||
"async-timeout": {
|
"async-timeout": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:00cff4d2dce744607335cba84e9929c3165632da2d27970dbc55802a0c7873d0",
|
"sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c",
|
||||||
"sha256:9093db5b8ddbe4b8f6885d1a6e0ad84ae3155464cbf6877c387605244c285f3c"
|
"sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287"
|
||||||
],
|
],
|
||||||
"version": "==2.0.1"
|
"markers": "python_version >= '3.5.3'",
|
||||||
|
"version": "==3.0.0"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
|
||||||
|
"sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
|
||||||
|
],
|
||||||
|
"version": "==18.1.0"
|
||||||
},
|
},
|
||||||
"chardet": {
|
"chardet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -63,7 +80,7 @@
|
|||||||
"discord.py": {
|
"discord.py": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"git": "git://github.com/Rapptz/discord.py",
|
"git": "git://github.com/Rapptz/discord.py",
|
||||||
"ref": "rewrite"
|
"ref": "8ccb98d395537b1c9acc187e1647dfdd07bb831b"
|
||||||
},
|
},
|
||||||
"distro": {
|
"distro": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -74,14 +91,11 @@
|
|||||||
},
|
},
|
||||||
"e1839a8": {
|
"e1839a8": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"path": "."
|
"extras": [
|
||||||
},
|
"mongo",
|
||||||
"funcsigs": {
|
"voice"
|
||||||
"hashes": [
|
|
||||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
|
||||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
|
||||||
],
|
],
|
||||||
"version": "==1.0.2"
|
"path": "."
|
||||||
},
|
},
|
||||||
"fuzzywuzzy": {
|
"fuzzywuzzy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -92,23 +106,23 @@
|
|||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||||
],
|
],
|
||||||
"version": "==2.6"
|
"version": "==2.7"
|
||||||
},
|
},
|
||||||
"jsonrpcserver": {
|
"idna-ssl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:ab8013cdee3f65d59c5d3f84c75be76a3492caa0b33ecaa3f0f69906cf3d9e92"
|
"sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"
|
||||||
],
|
],
|
||||||
"version": "==3.5.4"
|
"version": "==1.1.0"
|
||||||
},
|
},
|
||||||
"jsonschema": {
|
"motor": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08",
|
"sha256:462fbb824f4289481c158227a2579d6adaf1ec7c70cf7ebe60ed6ceb321e5869",
|
||||||
"sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"
|
"sha256:d035c09ab422bc50bf3efb134f7405694cae76268545bd21e14fb22e2638f84e"
|
||||||
],
|
],
|
||||||
"version": "==2.6.0"
|
"version": "==2.0.0"
|
||||||
},
|
},
|
||||||
"multidict": {
|
"multidict": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -126,8 +140,44 @@
|
|||||||
"sha256:d870f399fcd58a1889e93008762a3b9a27cf7ea512818fc6e689f59495648355",
|
"sha256:d870f399fcd58a1889e93008762a3b9a27cf7ea512818fc6e689f59495648355",
|
||||||
"sha256:e9404e2e19e901121c3c5c6cffd5a8ae0d1d67919c970e3b3262231175713068"
|
"sha256:e9404e2e19e901121c3c5c6cffd5a8ae0d1d67919c970e3b3262231175713068"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version >= '3.4.1'",
|
||||||
"version": "==4.3.1"
|
"version": "==4.3.1"
|
||||||
},
|
},
|
||||||
|
"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": {
|
"python-levenshtein": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
|
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
|
||||||
@@ -136,94 +186,117 @@
|
|||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8",
|
"sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
|
||||||
"sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736",
|
"sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
|
||||||
"sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f",
|
"sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
|
||||||
"sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608",
|
"sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
|
||||||
"sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8",
|
"sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
|
||||||
"sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab",
|
"sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
|
||||||
"sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7",
|
"sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
|
||||||
"sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3",
|
"sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
|
||||||
"sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1",
|
"sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
|
||||||
"sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6",
|
"sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
|
||||||
"sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8",
|
"sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
|
||||||
"sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4",
|
|
||||||
"sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca",
|
|
||||||
"sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269"
|
|
||||||
],
|
],
|
||||||
"version": "==3.12"
|
"version": "==3.13"
|
||||||
},
|
},
|
||||||
"raven": {
|
"raven": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0adae40e004dfe2181d1f2883aa3d4ca1cf16dbe449ae4b445b011c6eb220a90",
|
"sha256:3fd787d19ebb49919268f06f19310e8112d619ef364f7989246fc8753d469888",
|
||||||
"sha256:84da75114739191bdf2388f296ffd6177e83567a7fbaf2701e034ad6026e4f3b"
|
"sha256:95f44f3ea2c1b176d5450df4becdb96c15bf2632888f9ab193e9dd22300ce46a"
|
||||||
],
|
],
|
||||||
"version": "==6.5.0"
|
"version": "==6.9.0"
|
||||||
},
|
},
|
||||||
"red-trivia": {
|
"raven-aiohttp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:39413b9fb3f9b9362d6de1dcf69a4bf635b0f3518243f7178299b96d26cbb6a7"
|
"sha256:1444a49c93a85b8bb57c6ee649e512368dce7a26ad64ac3a01d86aa5669d77f3",
|
||||||
|
"sha256:6a34b6a9841ad0fd827eeb158edb5826c5c5bd7babe2cde2a3f23eb85313af04"
|
||||||
],
|
],
|
||||||
"version": "==1.1.1"
|
"version": "==0.7.0"
|
||||||
},
|
},
|
||||||
"six": {
|
"red-lavalink": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
"sha256:6a1a34471ccf4630eee537049568dd87e8e93614f1d1ce355dd74e5b10079782"
|
||||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
|
||||||
],
|
],
|
||||||
"version": "==1.11.0"
|
"version": "==0.1.2"
|
||||||
},
|
},
|
||||||
"websockets": {
|
"websockets": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:09dfec40e9b73e8808c39ecdbc1733e33915a2b26b90c54566afc0af546a9ec3",
|
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
|
||||||
"sha256:2aa6d52264cecb08d39741e8fda49f5ac4872aef02617230c84d02e861f3cc5a",
|
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
|
||||||
"sha256:2f5b7f3920f29609086fb0b63552bb1f86a04b8cbdcc0dbf3775cc90d489dfc8",
|
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
|
||||||
"sha256:3d38f76f71654268e5533b45df125ff208fee242a102d4b5ca958da5cf5fb345",
|
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
|
||||||
"sha256:3fcc7dfb365e81ff8206f950c86d1e73accdf3be2f9110c0cb73be32d2e7a9a5",
|
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
|
||||||
"sha256:4128212ab6f91afda03a0c697add261bdf6946b47928db83f07298ea2cd8d937",
|
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
|
||||||
"sha256:43e5b9f51dd0000a4c6f646e2ade0c886bd14a784ffac08b9e079bd17a63bcc5",
|
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
|
||||||
"sha256:4a932c17cb11c361c286c04842dc2385cc7157019bbba8b64808acbc89a95584",
|
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
|
||||||
"sha256:5ddc5fc121eb76771e990f071071d9530e27d20e8cfb804d9f5823de055837af",
|
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
|
||||||
"sha256:7347af28fcc70eb45be409760c2a428f8199e7f73c04a621916c3c219ed7ad27",
|
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
|
||||||
"sha256:85ae1e4b36aa2e90de56d211d2de36d7c093d00277a9afdd9b4f81e69c0214ab",
|
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
|
||||||
"sha256:8a29100079f5b91a72bcd25d35a7354db985d3babae42d00b9d629f9a0aaa8ac",
|
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
|
||||||
"sha256:a7e7585c8e3c0f9277ad7d6ee6ccddc69649cd216255d5e255d68f90482aeefa",
|
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
|
||||||
"sha256:aa42ecef3aed807e23218c264b1e82004cdd131a6698a10b57fc3d8af8f651fc",
|
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
|
||||||
"sha256:b19e7ede1ba80ee9de6f5b8ccd31beee25402e68bef7c13eeb0b8bc46bc4b7b7",
|
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
|
||||||
"sha256:c4c5b5ce2d66cb0cf193c14bc9726adca095febef0f7b2c04e5e3fa3487a97a4",
|
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
|
||||||
"sha256:de743ef26b002efceea7d7756e99e5d38bf5d4f27563b8d27df2a9a5cc57340a",
|
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
|
||||||
"sha256:e1e568136ad5cb6768504be36d470a136b072acbf3ea882303aee6361be01941",
|
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
|
||||||
"sha256:e8992f1db371f2a1c5af59e032d9dc7c1aa92f16241efcda695b7d955b4de0c2",
|
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
|
||||||
"sha256:e9c1cdbb591432c59d0b5ca64fd30b6d517024767f152fc169563b26e7bcc9da"
|
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
||||||
|
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
||||||
],
|
],
|
||||||
"version": "==3.4"
|
"markers": "python_version >= '3.4'",
|
||||||
|
"version": "==6.0"
|
||||||
},
|
},
|
||||||
"yarl": {
|
"yarl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:605480ee43eead69ec8e8c52cdfefc79cef6379cc0e87d908cf290408c1e49af",
|
"sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9",
|
||||||
"sha256:7fad2530cb4ddf2b74c1e4f6f9f0e28eac482094c6542f98fd71ecf67fb4fded",
|
"sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee",
|
||||||
"sha256:837d866a70f1ea03005914a740bddea89a253afabd6589db981b91738768bd25",
|
"sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308",
|
||||||
"sha256:885e40812ff9fc80e6f28ef04ad6396e3ae583ab504b1a76301fdcec7fc9f30f",
|
"sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357",
|
||||||
"sha256:a5457e075eab1170141774a8c69906c223ea0088eaebd6ef91b04b33527fa905",
|
"sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78",
|
||||||
"sha256:baa0d3f7982fa0c03a55433109c405e79a597141f2e2d6ee7e16c03eabd74886",
|
"sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8",
|
||||||
"sha256:beeefbe0edd47fc8b657bf7bf44791f7a6e5b14f3de1846daf999687cb68c156",
|
"sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1",
|
||||||
"sha256:cf6a3d6fd3e79d3457d520c12d5d18b030d5ca5d0b205ca6481857804d8d944d",
|
"sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4",
|
||||||
"sha256:d07d3dc6849345b7437dc58ea49ad2a1960017386d86288550728ca38e482ddc",
|
"sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7"
|
||||||
"sha256:d81e45bedefccb97e4e8f7d32cfae0af1d9eadd1ae795fc420c8319c3dab2a28",
|
|
||||||
"sha256:e1da2853a92fbc7e2d0248bbfa931cd621121e70ce6dda7c1eeef3516d51b46c",
|
|
||||||
"sha256:f1201de3e93fb1efc3111c8928d9366875edefd65d77c0f6b847fe299e8e1122",
|
|
||||||
"sha256:fe0390a29b5c7e90975feefe863e3d3a851be546bd797b23f338d24a15efa920"
|
|
||||||
],
|
],
|
||||||
"version": "==0.18.0"
|
"markers": "python_version >= '3.4.1'",
|
||||||
|
"version": "==1.2.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"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:bf1eb7e30949b60f74cb84731b5676bd7dc3f0298056ddbbe989d9219260008c",
|
||||||
|
"sha256:e1ae47d522a7857c612be8ba447cec3cad8c8b7d628353289a0889a1135166c8"
|
||||||
|
],
|
||||||
|
"version": "==0.11"
|
||||||
|
},
|
||||||
"alabaster": {
|
"alabaster": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732",
|
"sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456",
|
||||||
"sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0"
|
"sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7"
|
||||||
],
|
],
|
||||||
"version": "==0.7.10"
|
"version": "==0.7.11"
|
||||||
},
|
},
|
||||||
"appdirs": {
|
"appdirs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -232,6 +305,14 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.4.3"
|
"version": "==1.4.3"
|
||||||
},
|
},
|
||||||
|
"async-timeout": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:474d4bc64cee20603e225eb1ece15e248962958b45a3648a9f5cc29e827a610c",
|
||||||
|
"sha256:b3c0ddc416736619bd4a95ca31de8da6920c3b9a140c64dbef2b2fa7bf521287"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.5.3'",
|
||||||
|
"version": "==3.0.0"
|
||||||
|
},
|
||||||
"atomicwrites": {
|
"atomicwrites": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
|
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
|
||||||
@@ -248,19 +329,17 @@
|
|||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14",
|
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
|
||||||
"sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80"
|
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
|
||||||
],
|
],
|
||||||
"version": "==2.5.3"
|
"version": "==2.6.0"
|
||||||
},
|
},
|
||||||
"black": {
|
"black": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4fec2566f9fbbd4a58de50a168cbe3ab952713530410d227e82e4c65d1fad946",
|
"sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44",
|
||||||
"sha256:5fec0f25486046b9edb97961c946412ced96021247dd1a60ecd9f0567b68b030"
|
"sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==18.6b4"
|
||||||
"markers": "python_version >= '3.6'",
|
|
||||||
"version": "==18.5b0"
|
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -283,6 +362,20 @@
|
|||||||
],
|
],
|
||||||
"version": "==6.7"
|
"version": "==6.7"
|
||||||
},
|
},
|
||||||
|
"colorama": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
|
||||||
|
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
|
||||||
|
],
|
||||||
|
"version": "==0.3.9"
|
||||||
|
},
|
||||||
|
"distro": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:224041cef9600e72d19ae41ba006e71c05c4dc802516da715d7fda55ba3d8742",
|
||||||
|
"sha256:6ec8e539cf412830e5ccf521aecf879f2c7fcf60ce446e33cd16eef1ed8a0158"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
"docutils": {
|
"docutils": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||||
@@ -291,12 +384,34 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.14"
|
"version": "==0.14"
|
||||||
},
|
},
|
||||||
|
"e1839a9": {
|
||||||
|
"editable": true,
|
||||||
|
"extras": [
|
||||||
|
"docs",
|
||||||
|
"test",
|
||||||
|
"style"
|
||||||
|
],
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
"fuzzywuzzy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:d40c22d2744dff84885b30bbfc07fab7875f641d070374331777a4d1808b8d4e",
|
||||||
|
"sha256:ecf490216fb4d76b558a03042ff8f45a8782f17326caca1384d834cbaa2c7e6f"
|
||||||
|
],
|
||||||
|
"version": "==0.16.0"
|
||||||
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||||
],
|
],
|
||||||
"version": "==2.6"
|
"version": "==2.7"
|
||||||
|
},
|
||||||
|
"idna-ssl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"
|
||||||
|
],
|
||||||
|
"version": "==1.1.0"
|
||||||
},
|
},
|
||||||
"imagesize": {
|
"imagesize": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -320,11 +435,30 @@
|
|||||||
},
|
},
|
||||||
"more-itertools": {
|
"more-itertools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
|
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
|
||||||
"sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3",
|
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
|
||||||
"sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"
|
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
|
||||||
],
|
],
|
||||||
"version": "==4.2.0"
|
"version": "==4.3.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"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.4.1'",
|
||||||
|
"version": "==4.3.1"
|
||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -335,18 +469,19 @@
|
|||||||
},
|
},
|
||||||
"pluggy": {
|
"pluggy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
|
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
|
||||||
"sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
|
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
|
||||||
"sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
|
|
||||||
],
|
],
|
||||||
"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": {
|
"py": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881",
|
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
|
||||||
"sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"
|
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
|
||||||
],
|
],
|
||||||
"version": "==1.5.3"
|
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||||
|
"version": "==1.5.4"
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -358,44 +493,74 @@
|
|||||||
"pyparsing": {
|
"pyparsing": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
|
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
|
||||||
"sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
|
|
||||||
"sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
|
|
||||||
"sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
|
|
||||||
"sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
|
|
||||||
"sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58",
|
|
||||||
"sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
|
"sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
|
||||||
],
|
],
|
||||||
"version": "==2.2.0"
|
"version": "==2.2.0"
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:39555d023af3200d004d09e51b4dd9fdd828baa863cded3fd6ba2f29f757ae2d",
|
"sha256:8214ab8446104a1d0c17fbd218ec6aac743236c6ffbe23abc038e40213c60b88",
|
||||||
"sha256:c76e93f3145a44812955e8d46cdd302d8a45fbfc7bf22be24fe231f9d8d8853a"
|
"sha256:e2b2c6e1560b8f9dc8dd600b0923183fbd68ba3d9bdecde04467be6dd296a384"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==3.7.0"
|
||||||
"version": "==3.6.0"
|
|
||||||
},
|
},
|
||||||
"pytest-asyncio": {
|
"pytest-asyncio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:286b50773e996c80d894b95afaf45df6952408a67a59979ca9839f94693ec7fd",
|
"sha256:a962e8e1b6ec28648c8fe214edab4e16bacdb37b52df26eb9d63050af309b2a9",
|
||||||
"sha256:f32804bb58a66e13a3eda11f8942a71b1b6a30466b0d2ffe9214787aab0e172e"
|
"sha256:fbd92c067c16111174a1286bfb253660f1e564e5146b39eeed1133315cf2c2cf"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||||
"version": "==0.8.0"
|
"version": "==0.9.0"
|
||||||
|
},
|
||||||
|
"python-levenshtein": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
|
||||||
|
],
|
||||||
|
"version": "==0.12.0"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
|
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
|
||||||
"sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
|
"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": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||||
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||||
],
|
],
|
||||||
"version": "==2.18.4"
|
"version": "==2.19.1"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -413,55 +578,103 @@
|
|||||||
},
|
},
|
||||||
"sphinx": {
|
"sphinx": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2e7ad92e96eff1b2006cf9f0cdb2743dacbae63755458594e9e8238b0c3dc60b",
|
"sha256:217ad9ece2156ed9f8af12b5d2c82a499ddf2c70a33c5f81864a08d8c67b9efc",
|
||||||
"sha256:e9b1a75a3eae05dded19c80eb17325be675e0698975baae976df603b6ed1eb10"
|
"sha256:a765c6db1e5b62aae857697cd4402a5c1a315a7b0854bbcd0fc8cdc524da5896"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==1.7.6"
|
||||||
"version": "==1.7.4"
|
|
||||||
},
|
},
|
||||||
"sphinx-rtd-theme": {
|
"sphinx-rtd-theme": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:32424dac2779f0840b4788fbccb032ba2496c1ca47a439ad2510c8b1e55dfd33",
|
"sha256:3b49758a64f8a1ebd8a33cb6cc9093c3935a908b716edfaa5772fd86aac27ef6",
|
||||||
"sha256:6d0481532b5f441b075127a2d755f430f1f8410a50112b1af6b069518548381d"
|
"sha256:80e01ec0eb711abacb1fa507f3eae8b805ae8fa3e8b057abfdf497e3f644c82c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==0.4.1"
|
||||||
"version": "==0.3.1"
|
|
||||||
},
|
},
|
||||||
"sphinxcontrib-asyncio": {
|
"sphinxcontrib-asyncio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:96627b1ec4eba08d09ad577ff9416c131910333ef37a2c82a2716e59646739f0"
|
"sha256:96627b1ec4eba08d09ad577ff9416c131910333ef37a2c82a2716e59646739f0"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.2.0"
|
"version": "==0.2.0"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-websupport": {
|
"sphinxcontrib-websupport": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9",
|
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
|
||||||
"sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2"
|
"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": {
|
"tox": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f",
|
"sha256:37cf240781b662fb790710c6998527e65ca6851eace84d1595ee71f7af4e85f7",
|
||||||
"sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0"
|
"sha256:eb61aa5bcce65325538686f09848f04ef679b5cd9b83cc491272099b28739600"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.0.0"
|
"version": "==3.2.1"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
"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": {
|
"virtualenv": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
|
"sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
|
||||||
"sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
|
"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"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
73
README.rst
73
README.rst
@@ -1,46 +1,47 @@
|
|||||||
.. raw:: html
|
.. class:: center
|
||||||
|
|
||||||
<h1 align="center">
|
.. image:: https://imgur.com/pY1WUFX.png
|
||||||
<br>
|
:target: https://github.com/Cog-Creators/Red-DiscordBot/tree/V3/develop
|
||||||
<a href="https://github.com/Cog-Creators/Red-DiscordBot/tree/V3/develop"><img src="https://imgur.com/pY1WUFX.png" alt="Red Discord Bot"></a>
|
:alt: Red Discord Bot
|
||||||
<br>
|
|
||||||
Red Discord Bot
|
|
||||||
<br>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<h4 align="center">Music, Moderation, Trivia, Stream Alerts and fully customizable.</h4>
|
.. class:: center
|
||||||
|
|
||||||
.. raw:: html
|
Music, Moderation, Trivia, Stream Alerts and fully customizable.
|
||||||
|
|
||||||
<p align="center">
|
.. class:: 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
|
.. image:: https://discordapp.com/api/guilds/133049272517001216/embed.png
|
||||||
|
:target: https://discord.gg/red
|
||||||
|
:alt: Discord server
|
||||||
|
|
||||||
<p align="center">
|
.. image:: https://api.travis-ci.org/Cog-Creators/Red-DiscordBot.svg?branch=V3/develop
|
||||||
<a href="#overview">Overview</a> •
|
:target: https://travis-ci.org/Cog-Creators/Red-DiscordBot
|
||||||
<a href="#installation">Installation</a> •
|
:alt: Travis CI status
|
||||||
<a href="http://red-discordbot.readthedocs.io/en/v3-develop/index.html">Documentation</a>
|
|
||||||
<a href="#plugins"></a> •
|
.. image:: https://readthedocs.org/projects/red-discordbot/badge/?version=v3-develop
|
||||||
<a href="#join-the-community">Community</a> •
|
:target: http://red-discordbot.readthedocs.io/en/v3-develop/?badge=v3-develop
|
||||||
<a href="#license">License</a>
|
:alt: Documentation Status
|
||||||
</p>
|
|
||||||
|
.. image:: https://img.shields.io/badge/discord-py-blue.svg
|
||||||
|
:target: https://github.com/Rapptz/discord.py
|
||||||
|
:alt: discord.py
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||||
|
:target: https://github.com/ambv/black
|
||||||
|
:alt: Code style: black
|
||||||
|
|
||||||
|
.. image:: https://d322cqt584bo4o.cloudfront.net/red-discordbot/localized.svg
|
||||||
|
:target: https://crowdin.com/project/red-discordbot
|
||||||
|
:alt: Crowdin
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/Support-Red!-orange.svg
|
||||||
|
:target: https://www.patreon.com/Red_Devs
|
||||||
|
:alt: Patreon
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
|
||||||
|
:target: http://makeapullrequest.com
|
||||||
|
:alt: PRs open
|
||||||
|
|
||||||
==========
|
==========
|
||||||
Overview
|
Overview
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
api_key_env: CROWDIN_API_KEY
|
api_key_env: CROWDIN_API_KEY
|
||||||
project_identifier_env: CROWDIN_PROJECT_ID
|
project_identifier_env: CROWDIN_PROJECT_ID
|
||||||
files:
|
files:
|
||||||
- source: /**/*.pot
|
- source: /redbot/**/*.pot
|
||||||
translation: /%original_path%/%locale%.po
|
translation: /%original_path%/%locale%.po
|
||||||
|
|||||||
1
dependency_links.txt
Normal file
1
dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
https://github.com/Rapptz/discord.py/tarball/8ccb98d395537b1c9acc187e1647dfdd07bb831b#egg=discord.py-1.0.0a0
|
||||||
@@ -10,7 +10,7 @@ Creating the service file
|
|||||||
|
|
||||||
Create the new 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):
|
Paste the following and replace all instances of :code:`username` with the username your bot is running under (hopefully not root):
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ For each of those, settings have varying priorities (listed below, highest to lo
|
|||||||
7. Role settings (see below)
|
7. Role settings (see below)
|
||||||
8. Server whitelist
|
8. Server whitelist
|
||||||
9. Server blacklist
|
9. Server blacklist
|
||||||
|
10. Default settings
|
||||||
|
|
||||||
For the role whitelist and blacklist settings,
|
For the role whitelist and blacklist settings,
|
||||||
roles will be checked individually in order from highest to lowest role the user has
|
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
|
- 96733288462286848
|
||||||
default: allow
|
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.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
"python": ("https://docs.python.org/3.6", None),
|
"python": ("https://docs.python.org/3.6", None),
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ RedBase
|
|||||||
:members:
|
:members:
|
||||||
:exclude-members: get_context
|
:exclude-members: get_context
|
||||||
|
|
||||||
|
.. automethod:: register_rpc_handler
|
||||||
|
.. automethod:: unregister_rpc_handler
|
||||||
|
|
||||||
Red
|
Red
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
Commands Package
|
Commands Package
|
||||||
================
|
================
|
||||||
|
|
||||||
This package acts almost identically to ``discord.ext.commands``; i.e. they both have the same
|
This package acts almost identically to :doc:`discord.ext.commands <dpy:ext/commands/api>`; i.e.
|
||||||
attributes. Some of these attributes, however, have been slightly modified, as outlined below.
|
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
|
.. autofunction:: redbot.core.commands.command
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ Keys common to both repo and cog info.json (case sensitive)
|
|||||||
- ``install_msg`` (string) - The message that gets displayed when a cog
|
- ``install_msg`` (string) - The message that gets displayed when a cog
|
||||||
is installed or a repo is added
|
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
|
- ``short`` (string) - A short description of the cog or repo. For cogs, this info
|
||||||
is displayed when a user executes ``!cog list``
|
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)``
|
- ``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.
|
- ``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.
|
Downloader will not deal with this functionality but it may be useful for other cogs.
|
||||||
|
|||||||
@@ -4,36 +4,60 @@
|
|||||||
RPC
|
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.
|
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.
|
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.
|
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
|
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
|
API Reference
|
||||||
*************
|
*************
|
||||||
|
|
||||||
.. py:attribute:: redbot.core.rpc.methods
|
Please see the :class:`redbot.core.bot.RedBase` class for details on the RPC handler register and unregister 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:
|
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
Utility Functions
|
Utility Functions
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
General Utility
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. automodule:: redbot.core.utils
|
||||||
|
:members: deduplicate_iterables, bounded_gather, bounded_gather_iter
|
||||||
|
|
||||||
Chat Formatting
|
Chat Formatting
|
||||||
===============
|
===============
|
||||||
|
|
||||||
@@ -39,3 +45,9 @@ Tunnel
|
|||||||
|
|
||||||
.. automodule:: redbot.core.utils.tunnel
|
.. automodule:: redbot.core.utils.tunnel
|
||||||
:members: Tunnel
|
:members: Tunnel
|
||||||
|
|
||||||
|
Common Filters
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. automodule:: redbot.core.utils.common_filters
|
||||||
|
:members:
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ you in the process.
|
|||||||
Getting started
|
Getting started
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
To start off, be sure that you have installed Python 3.5 or higher (if you
|
To start off, be sure that you have installed Python 3.6 or higher. Open a terminal or command prompt and type
|
||||||
are on Windows, stick with 3.5). 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]`
|
: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
|
(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.
|
||||||
|
|||||||
@@ -11,13 +11,8 @@ Welcome to Red - Discord Bot's documentation!
|
|||||||
:caption: Installation Guides:
|
:caption: Installation Guides:
|
||||||
|
|
||||||
install_windows
|
install_windows
|
||||||
install_mac
|
install_linux_mac
|
||||||
install_ubuntu_xenial
|
venv_guide
|
||||||
install_ubuntu_bionic
|
|
||||||
install_debian
|
|
||||||
install_centos
|
|
||||||
install_arch
|
|
||||||
install_raspbian
|
|
||||||
cog_dataconverter
|
cog_dataconverter
|
||||||
autostart_systemd
|
autostart_systemd
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
203
docs/install_linux_mac.rst
Normal file
203
docs/install_linux_mac.rst
Normal file
@@ -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 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 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.
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
.. windows installation docs
|
.. _windows-install-guide:
|
||||||
|
|
||||||
=========================
|
=========================
|
||||||
Installing Red on Windows
|
Installing Red on Windows
|
||||||
@@ -8,7 +8,7 @@ Installing Red on Windows
|
|||||||
Needed Software
|
Needed Software
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
* `Python <https://python.org/downloads/>`_ - Red needs Python 3.6
|
* `Python <https://www.python.org/downloads/>`_ - Red needs Python 3.6
|
||||||
|
|
||||||
.. note:: Please make sure that the box to add Python to PATH is CHECKED, otherwise
|
.. 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
|
you may run into issues when trying to run Red
|
||||||
@@ -21,23 +21,74 @@ Needed Software
|
|||||||
|
|
||||||
.. attention:: Please choose the "Windows Online" installer
|
.. attention:: Please choose the "Windows Online" installer
|
||||||
|
|
||||||
|
.. _installing-red-windows:
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
Installing Red
|
Installing Red
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
1. Open a command prompt (open Start, search for "command prompt", then click it)
|
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`
|
.. note::
|
||||||
* 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]`
|
|
||||||
|
|
||||||
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
|
* No audio:
|
||||||
storage backend and the name of the instance (which will be used for
|
|
||||||
running the bot)
|
|
||||||
|
|
||||||
4. Once done setting up the instance, run :code:`redbot <your instance name>` to run Red.
|
.. code-block:: none
|
||||||
It will walk through the initial setup, asking for your token and a prefix
|
|
||||||
|
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
|
|
||||||
132
docs/venv_guide.rst
Normal file
132
docs/venv_guide.rst
Normal file
@@ -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 or later 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()
|
|
||||||
@@ -12,12 +12,3 @@ if discord.version_info.major < 1:
|
|||||||
" >= 1.0.0."
|
" >= 1.0.0."
|
||||||
)
|
)
|
||||||
sys.exit(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."
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -6,19 +6,28 @@ import sys
|
|||||||
import discord
|
import discord
|
||||||
from redbot.core.bot import Red, ExitCodes
|
from redbot.core.bot import Red, ExitCodes
|
||||||
from redbot.core.cog_manager import CogManagerUI
|
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.json_io import JsonIO
|
||||||
from redbot.core.global_checks import init_global_checks
|
from redbot.core.global_checks import init_global_checks
|
||||||
from redbot.core.events import init_events
|
from redbot.core.events import init_events
|
||||||
from redbot.core.cli import interactive_config, confirm, parse_cli_flags, ask_sentry
|
from redbot.core.cli import interactive_config, confirm, parse_cli_flags, ask_sentry
|
||||||
from redbot.core.core_commands import Core
|
from redbot.core.core_commands import Core
|
||||||
from redbot.core.dev_commands import Dev
|
from redbot.core.dev_commands import Dev
|
||||||
from redbot.core import rpc, __version__
|
from redbot.core import __version__
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import logging
|
import logging
|
||||||
import os
|
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())
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Red - Discord Bot v3
|
# Red - Discord Bot v3
|
||||||
@@ -40,7 +49,7 @@ def init_loggers(cli_flags):
|
|||||||
logger = logging.getLogger("red")
|
logger = logging.getLogger("red")
|
||||||
|
|
||||||
red_format = logging.Formatter(
|
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]",
|
datefmt="[%d/%m/%Y %H:%M]",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,12 +115,20 @@ def main():
|
|||||||
elif cli_flags.version:
|
elif cli_flags.version:
|
||||||
print(description)
|
print(description)
|
||||||
sys.exit(0)
|
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!")
|
print("Error: No instance name was provided!")
|
||||||
sys.exit(1)
|
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)
|
load_basic_configuration(cli_flags.instance_name)
|
||||||
log, sentry_log = init_loggers(cli_flags)
|
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_global_checks(red)
|
||||||
init_events(red, cli_flags)
|
init_events(red, cli_flags)
|
||||||
red.add_cog(Core(red))
|
red.add_cog(Core(red))
|
||||||
@@ -122,8 +139,10 @@ def main():
|
|||||||
tmp_data = {}
|
tmp_data = {}
|
||||||
loop.run_until_complete(_get_prefix_and_token(red, tmp_data))
|
loop.run_until_complete(_get_prefix_and_token(red, tmp_data))
|
||||||
token = os.environ.get("RED_TOKEN", tmp_data["token"])
|
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"]
|
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:
|
if cli_flags.no_prompt is False:
|
||||||
new_token = interactive_config(red, token_set=bool(token), prefix_set=bool(prefix))
|
new_token = interactive_config(red, token_set=bool(token), prefix_set=bool(prefix))
|
||||||
if new_token:
|
if new_token:
|
||||||
@@ -138,18 +157,11 @@ def main():
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
if tmp_data["enable_sentry"]:
|
if tmp_data["enable_sentry"]:
|
||||||
red.enable_sentry()
|
red.enable_sentry()
|
||||||
cleanup_tasks = True
|
|
||||||
try:
|
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:
|
except discord.LoginFailure:
|
||||||
cleanup_tasks = False # No login happened, no need for this
|
log.critical("This token doesn't seem to be valid.")
|
||||||
log.critical(
|
db_token = loop.run_until_complete(red.db.token())
|
||||||
"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()
|
|
||||||
if db_token and not cli_flags.no_prompt:
|
if db_token and not cli_flags.no_prompt:
|
||||||
print("\nDo you want to reset the token? (y/n)")
|
print("\nDo you want to reset the token? (y/n)")
|
||||||
if confirm("> "):
|
if confirm("> "):
|
||||||
@@ -164,10 +176,13 @@ def main():
|
|||||||
sentry_log.critical("Fatal Exception", exc_info=e)
|
sentry_log.critical("Fatal Exception", exc_info=e)
|
||||||
loop.run_until_complete(red.logout())
|
loop.run_until_complete(red.logout())
|
||||||
finally:
|
finally:
|
||||||
if cleanup_tasks:
|
|
||||||
pending = asyncio.Task.all_tasks(loop=red.loop)
|
pending = asyncio.Task.all_tasks(loop=red.loop)
|
||||||
gathered = asyncio.gather(*pending, loop=red.loop, return_exceptions=True)
|
gathered = asyncio.gather(*pending, loop=red.loop, return_exceptions=True)
|
||||||
gathered.cancel()
|
gathered.cancel()
|
||||||
|
try:
|
||||||
|
red.rpc.server.close()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
sys.exit(red._shutdown_mode.value)
|
sys.exit(red._shutdown_mode.value)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
from redbot.core import Config, checks
|
from redbot.core import Config, checks, commands
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -40,7 +39,6 @@ RUNNING_ANNOUNCEMENT = (
|
|||||||
|
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
|
|
||||||
def __init__(self, config=Config):
|
def __init__(self, config=Config):
|
||||||
self.conf = config.get_conf(self, 8237492837454039, force_registration=True)
|
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
|
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
|
Adds a role to a user.
|
||||||
author of the command.
|
If user is left blank it defaults to the author of the command.
|
||||||
"""
|
"""
|
||||||
if user is None:
|
if user is None:
|
||||||
user = ctx.author
|
user = ctx.author
|
||||||
@@ -138,7 +136,7 @@ class Admin:
|
|||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
await self._addrole(ctx, user, rolename)
|
await self._addrole(ctx, user, rolename)
|
||||||
else:
|
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.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@@ -147,8 +145,8 @@ class Admin:
|
|||||||
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
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
|
Removes a role from a user.
|
||||||
author of the command.
|
If user is left blank it defaults to the author of the command.
|
||||||
"""
|
"""
|
||||||
if user is None:
|
if user is None:
|
||||||
user = ctx.author
|
user = ctx.author
|
||||||
@@ -163,8 +161,7 @@ class Admin:
|
|||||||
@checks.admin_or_permissions(manage_roles=True)
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
async def editrole(self, ctx: commands.Context):
|
async def editrole(self, ctx: commands.Context):
|
||||||
"""Edits roles settings"""
|
"""Edits roles settings"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@editrole.command(name="colour", aliases=["color"])
|
@editrole.command(name="colour", aliases=["color"])
|
||||||
async def editrole_colour(
|
async def editrole_colour(
|
||||||
@@ -265,20 +262,16 @@ class Admin:
|
|||||||
@announce.command(name="ignore")
|
@announce.command(name="ignore")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@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.
|
Toggles whether the announcements will ignore the current server.
|
||||||
Defaults to the current server if none is provided.
|
|
||||||
"""
|
"""
|
||||||
if guild is None:
|
ignored = await self.conf.guild(ctx.guild).announce_ignore()
|
||||||
guild = ctx.guild
|
await self.conf.guild(ctx.guild).announce_ignore.set(not ignored)
|
||||||
|
|
||||||
ignored = await self.conf.guild(guild).announce_ignore()
|
|
||||||
await self.conf.guild(guild).announce_ignore.set(not ignored)
|
|
||||||
|
|
||||||
verb = "will" if ignored else "will not"
|
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]:
|
async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]:
|
||||||
"""
|
"""
|
||||||
@@ -298,11 +291,13 @@ class Admin:
|
|||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
return valid_roles
|
return valid_roles
|
||||||
|
|
||||||
|
@commands.guild_only()
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
async def selfrole(self, ctx: commands.Context, *, selfrole: SelfRole):
|
async def selfrole(self, ctx: commands.Context, *, selfrole: SelfRole):
|
||||||
"""
|
"""
|
||||||
Add a role to yourself that server admins have configured as
|
Add a role to yourself that server admins have configured as user settable.
|
||||||
user settable.
|
|
||||||
|
NOTE: The role is case sensitive!
|
||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
await self._addrole(ctx, ctx.author, selfrole)
|
await self._addrole(ctx, ctx.author, selfrole)
|
||||||
@@ -311,15 +306,19 @@ class Admin:
|
|||||||
async def selfrole_remove(self, ctx: commands.Context, *, selfrole: SelfRole):
|
async def selfrole_remove(self, ctx: commands.Context, *, selfrole: SelfRole):
|
||||||
"""
|
"""
|
||||||
Removes a selfrole from yourself.
|
Removes a selfrole from yourself.
|
||||||
|
|
||||||
|
NOTE: The role is case sensitive!
|
||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
await self._removerole(ctx, ctx.author, selfrole)
|
await self._removerole(ctx, ctx.author, selfrole)
|
||||||
|
|
||||||
@selfrole.command(name="add")
|
@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):
|
async def selfrole_add(self, ctx: commands.Context, *, role: discord.Role):
|
||||||
"""
|
"""
|
||||||
Add a role to the list of available selfroles.
|
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:
|
async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles:
|
||||||
if role.id not in 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.")
|
await ctx.send("The selfroles list has been successfully modified.")
|
||||||
|
|
||||||
@selfrole.command(name="delete")
|
@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):
|
async def selfrole_delete(self, ctx: commands.Context, *, role: SelfRole):
|
||||||
"""
|
"""
|
||||||
Removes a role from the list of available selfroles.
|
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:
|
async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles:
|
||||||
curr_selfroles.remove(role.id)
|
curr_selfroles.remove(role.id)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from redbot.core import commands
|
||||||
|
|
||||||
|
|
||||||
class Announcer:
|
class Announcer:
|
||||||
|
|
||||||
def __init__(self, ctx: commands.Context, message: str, config=None):
|
def __init__(self, ctx: commands.Context, message: str, config=None):
|
||||||
"""
|
"""
|
||||||
:param ctx:
|
:param ctx:
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from redbot.core import commands
|
||||||
|
|
||||||
|
|
||||||
class MemberDefaultAuthor(commands.Converter):
|
class MemberDefaultAuthor(commands.Converter):
|
||||||
|
|
||||||
async def convert(self, ctx: commands.Context, arg: str) -> discord.Member:
|
async def convert(self, ctx: commands.Context, arg: str) -> discord.Member:
|
||||||
member_converter = commands.MemberConverter()
|
member_converter = commands.MemberConverter()
|
||||||
try:
|
try:
|
||||||
@@ -17,7 +16,6 @@ class MemberDefaultAuthor(commands.Converter):
|
|||||||
|
|
||||||
|
|
||||||
class SelfRole(commands.Converter):
|
class SelfRole(commands.Converter):
|
||||||
|
|
||||||
async def convert(self, ctx: commands.Context, arg: str) -> discord.Role:
|
async def convert(self, ctx: commands.Context, arg: str) -> discord.Role:
|
||||||
admin = ctx.command.instance
|
admin = ctx.command.instance
|
||||||
if admin is None:
|
if admin is None:
|
||||||
@@ -30,5 +28,5 @@ class SelfRole(commands.Converter):
|
|||||||
role = await role_converter.convert(ctx, arg)
|
role = await role_converter.convert(ctx, arg)
|
||||||
|
|
||||||
if role.id not in selfroles:
|
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
|
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 .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))
|
bot.add_cog(Alias(bot))
|
||||||
|
|||||||
@@ -174,16 +174,14 @@ class Alias:
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def alias(self, ctx: commands.Context):
|
async def alias(self, ctx: commands.Context):
|
||||||
"""Manage per-server aliases for commands"""
|
"""Manage per-server aliases for commands"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@alias.group(name="global")
|
@alias.group(name="global")
|
||||||
async def global_(self, ctx: commands.Context):
|
async def global_(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Manage global aliases.
|
Manage global aliases.
|
||||||
"""
|
"""
|
||||||
if ctx.invoked_subcommand is None or isinstance(ctx.invoked_subcommand, commands.Group):
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@checks.mod_or_permissions(manage_guild=True)
|
@checks.mod_or_permissions(manage_guild=True)
|
||||||
@alias.command(name="add")
|
@alias.command(name="add")
|
||||||
@@ -233,9 +231,7 @@ class Alias:
|
|||||||
|
|
||||||
await self.add_alias(ctx, alias_name, command)
|
await self.add_alias(ctx, alias_name, command)
|
||||||
|
|
||||||
await ctx.send(
|
await ctx.send(_("A new alias with the trigger `{}` has been created.").format(alias_name))
|
||||||
_("A new alias with the trigger `{}`" " has been created.").format(alias_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@global_.command(name="add")
|
@global_.command(name="add")
|
||||||
@@ -282,14 +278,14 @@ class Alias:
|
|||||||
await self.add_alias(ctx, alias_name, command, global_=True)
|
await self.add_alias(ctx, alias_name, command, global_=True)
|
||||||
|
|
||||||
await ctx.send(
|
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")
|
@alias.command(name="help")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _help_alias(self, ctx: commands.Context, alias_name: str):
|
async def _help_alias(self, ctx: commands.Context, alias_name: str):
|
||||||
"""Tries to execute help for the base command of the alias"""
|
"""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:
|
if is_alias:
|
||||||
base_cmd = alias.command[0]
|
base_cmd = alias.command[0]
|
||||||
|
|
||||||
@@ -307,9 +303,7 @@ class Alias:
|
|||||||
|
|
||||||
if is_alias:
|
if is_alias:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("The `{}` alias will execute the" " command `{}`").format(
|
_("The `{}` alias will execute the command `{}`").format(alias_name, alias.command)
|
||||||
alias_name, alias.command
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("There is no alias with the name `{}`").format(alias_name))
|
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):
|
if await self.delete_alias(ctx, alias_name):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Alias with the name `{}` was successfully" " deleted.").format(alias_name)
|
_("Alias with the name `{}` was successfully deleted.").format(alias_name)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
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):
|
if await self.delete_alias(ctx, alias_name, global_=True):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Alias with the name `{}` was successfully" " deleted.").format(alias_name)
|
_("Alias with the name `{}` was successfully deleted.").format(alias_name)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from redbot.core import commands
|
|||||||
|
|
||||||
|
|
||||||
class AliasEntry:
|
class AliasEntry:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str, command: Tuple[str], creator: discord.Member, global_: bool = False
|
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 pathlib import Path
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
import shutil
|
import shutil
|
||||||
|
import logging
|
||||||
|
|
||||||
from .audio import Audio
|
from .audio import Audio
|
||||||
from .manager import start_lavalink_server
|
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
|
from redbot.core.data_manager import cog_data_path
|
||||||
import redbot.core
|
import redbot.core
|
||||||
|
|
||||||
|
log = logging.getLogger("red.audio")
|
||||||
|
|
||||||
LAVALINK_DOWNLOAD_URL = (
|
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__)
|
).format(redbot.core.__version__)
|
||||||
|
|
||||||
LAVALINK_DOWNLOAD_DIR = cog_data_path(raw_name="Audio")
|
LAVALINK_DOWNLOAD_DIR = cog_data_path(raw_name="Audio")
|
||||||
LAVALINK_JAR_FILE = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar"
|
LAVALINK_JAR_FILE = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar"
|
||||||
|
|
||||||
APP_YML_FILE = LAVALINK_DOWNLOAD_DIR / "application.yml"
|
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):
|
async def download_lavalink(session):
|
||||||
@@ -33,15 +36,13 @@ async def maybe_download_lavalink(loop, cog):
|
|||||||
jar_exists = LAVALINK_JAR_FILE.exists()
|
jar_exists = LAVALINK_JAR_FILE.exists()
|
||||||
current_build = redbot.core.VersionInfo(*await cog.config.current_build())
|
current_build = redbot.core.VersionInfo(*await cog.config.current_build())
|
||||||
|
|
||||||
session = ClientSession(loop=loop)
|
|
||||||
|
|
||||||
if not jar_exists or current_build < redbot.core.version_info:
|
if not jar_exists or current_build < redbot.core.version_info:
|
||||||
|
log.info("Downloading Lavalink.jar")
|
||||||
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
async with ClientSession(loop=loop) as session:
|
||||||
await download_lavalink(session)
|
await download_lavalink(session)
|
||||||
await cog.config.current_build.set(redbot.core.version_info.to_json())
|
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))
|
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)
|
await start_lavalink_server(bot.loop)
|
||||||
|
|
||||||
bot.add_cog(cog)
|
bot.add_cog(cog)
|
||||||
|
bot.loop.create_task(cog.disconnect_timer())
|
||||||
bot.loop.create_task(cog.init_config())
|
bot.loop.create_task(cog.init_config())
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import heapq
|
|||||||
import lavalink
|
import lavalink
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
import redbot.core
|
import redbot.core
|
||||||
from redbot.core import Config, commands, checks, bank
|
from redbot.core import Config, commands, checks, bank
|
||||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS, prev_page, next_page, close_menu
|
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS, prev_page, next_page, close_menu
|
||||||
@@ -14,13 +15,12 @@ from .manager import shutdown_lavalink_server
|
|||||||
|
|
||||||
_ = Translator("Audio", __file__)
|
_ = Translator("Audio", __file__)
|
||||||
|
|
||||||
__version__ = "0.0.6a"
|
__version__ = "0.0.6d"
|
||||||
__author__ = ["aikaterna", "billy/bollo/ati"]
|
__author__ = ["aikaterna", "billy/bollo/ati"]
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Audio:
|
class Audio:
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.config = Config.get_conf(self, 2711759130, force_registration=True)
|
self.config = Config.get_conf(self, 2711759130, force_registration=True)
|
||||||
@@ -38,12 +38,15 @@ class Audio:
|
|||||||
default_guild = {
|
default_guild = {
|
||||||
"dj_enabled": False,
|
"dj_enabled": False,
|
||||||
"dj_role": None,
|
"dj_role": None,
|
||||||
|
"emptydc_enabled": False,
|
||||||
|
"emptydc_timer": 0,
|
||||||
"jukebox": False,
|
"jukebox": False,
|
||||||
"jukebox_price": 0,
|
"jukebox_price": 0,
|
||||||
"playlists": {},
|
"playlists": {},
|
||||||
"notify": False,
|
"notify": False,
|
||||||
"repeat": False,
|
"repeat": False,
|
||||||
"shuffle": False,
|
"shuffle": False,
|
||||||
|
"thumbnail": False,
|
||||||
"volume": 100,
|
"volume": 100,
|
||||||
"vote_enabled": False,
|
"vote_enabled": False,
|
||||||
"vote_percent": 0,
|
"vote_percent": 0,
|
||||||
@@ -70,6 +73,13 @@ class Audio:
|
|||||||
)
|
)
|
||||||
lavalink.register_event_listener(self.event_handler)
|
lavalink.register_event_listener(self.event_handler)
|
||||||
|
|
||||||
|
async def _get_embed_colour(self, channel: discord.abc.GuildChannel):
|
||||||
|
# Unfortunately we need this for when context is unavailable.
|
||||||
|
if await self.bot.db.guild(channel.guild).use_bot_color():
|
||||||
|
return channel.guild.me.color
|
||||||
|
else:
|
||||||
|
return self.bot.color
|
||||||
|
|
||||||
async def event_handler(self, player, event_type, extra):
|
async def event_handler(self, player, event_type, extra):
|
||||||
notify = await self.config.guild(player.channel.guild).notify()
|
notify = await self.config.guild(player.channel.guild).notify()
|
||||||
status = await self.config.status()
|
status = await self.config.status()
|
||||||
@@ -99,10 +109,15 @@ class Audio:
|
|||||||
except discord.errors.NotFound:
|
except discord.errors.NotFound:
|
||||||
pass
|
pass
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=notify_channel.guild.me.top_role.colour,
|
colour=(await self._get_embed_colour(notify_channel)),
|
||||||
title="Now Playing",
|
title="Now Playing",
|
||||||
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
||||||
)
|
)
|
||||||
|
if (
|
||||||
|
await self.config.guild(player.channel.guild).thumbnail()
|
||||||
|
and player.current.thumbnail
|
||||||
|
):
|
||||||
|
embed.set_thumbnail(url=player.current.thumbnail)
|
||||||
notify_message = await notify_channel.send(embed=embed)
|
notify_message = await notify_channel.send(embed=embed)
|
||||||
player.store("notify_message", notify_message)
|
player.store("notify_message", notify_message)
|
||||||
|
|
||||||
@@ -128,7 +143,7 @@ class Audio:
|
|||||||
if notify_channel:
|
if notify_channel:
|
||||||
notify_channel = self.bot.get_channel(notify_channel)
|
notify_channel = self.bot.get_channel(notify_channel)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=notify_channel.guild.me.top_role.colour, title="Queue ended."
|
colour=(await self._get_embed_colour(notify_channel)), title="Queue ended."
|
||||||
)
|
)
|
||||||
await notify_channel.send(embed=embed)
|
await notify_channel.send(embed=embed)
|
||||||
|
|
||||||
@@ -154,7 +169,7 @@ class Audio:
|
|||||||
if message_channel:
|
if message_channel:
|
||||||
message_channel = self.bot.get_channel(message_channel)
|
message_channel = self.bot.get_channel(message_channel)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=message_channel.guild.me.top_role.colour,
|
colour=(await self._get_embed_colour(message_channel)),
|
||||||
title="Track Error",
|
title="Track Error",
|
||||||
description="{}\n**[{}]({})**".format(
|
description="{}\n**[{}]({})**".format(
|
||||||
extra, player.current.title, player.current.uri
|
extra, player.current.title, player.current.uri
|
||||||
@@ -168,8 +183,7 @@ class Audio:
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def audioset(self, ctx):
|
async def audioset(self, ctx):
|
||||||
"""Music configuration options."""
|
"""Music configuration options."""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@audioset.command()
|
@audioset.command()
|
||||||
@checks.admin_or_permissions(manage_roles=True)
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
@@ -197,6 +211,26 @@ class Audio:
|
|||||||
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
|
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
|
||||||
await self._embed_msg(ctx, "DJ role enabled: {}.".format(not dj_enabled))
|
await self._embed_msg(ctx, "DJ role enabled: {}.".format(not dj_enabled))
|
||||||
|
|
||||||
|
@audioset.command()
|
||||||
|
@checks.mod_or_permissions(administrator=True)
|
||||||
|
async def emptydisconnect(self, ctx, seconds: int):
|
||||||
|
"""Auto-disconnection after x seconds while stopped. 0 to disable."""
|
||||||
|
if seconds < 0:
|
||||||
|
return await self._embed_msg(ctx, "Can't be less than zero.")
|
||||||
|
if seconds < 10 and seconds > 0:
|
||||||
|
seconds = 10
|
||||||
|
if seconds == 0:
|
||||||
|
enabled = False
|
||||||
|
await self._embed_msg(ctx, "Empty disconnect disabled.")
|
||||||
|
else:
|
||||||
|
enabled = True
|
||||||
|
await self._embed_msg(
|
||||||
|
ctx, "Empty disconnect timer set to {}.".format(self._dynamic_time(seconds))
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.config.guild(ctx.guild).emptydc_timer.set(seconds)
|
||||||
|
await self.config.guild(ctx.guild).emptydc_enabled.set(enabled)
|
||||||
|
|
||||||
@audioset.command()
|
@audioset.command()
|
||||||
@checks.admin_or_permissions(manage_roles=True)
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
async def role(self, ctx, role_name: discord.Role):
|
async def role(self, ctx, role_name: discord.Role):
|
||||||
@@ -242,12 +276,17 @@ class Audio:
|
|||||||
global_data = await self.config.all()
|
global_data = await self.config.all()
|
||||||
dj_role_obj = discord.utils.get(ctx.guild.roles, id=data["dj_role"])
|
dj_role_obj = discord.utils.get(ctx.guild.roles, id=data["dj_role"])
|
||||||
dj_enabled = data["dj_enabled"]
|
dj_enabled = data["dj_enabled"]
|
||||||
|
emptydc_enabled = data["emptydc_enabled"]
|
||||||
|
emptydc_timer = data["emptydc_timer"]
|
||||||
jukebox = data["jukebox"]
|
jukebox = data["jukebox"]
|
||||||
jukebox_price = data["jukebox_price"]
|
jukebox_price = data["jukebox_price"]
|
||||||
|
thumbnail = data["thumbnail"]
|
||||||
jarbuild = redbot.core.__version__
|
jarbuild = redbot.core.__version__
|
||||||
|
|
||||||
vote_percent = data["vote_percent"]
|
vote_percent = data["vote_percent"]
|
||||||
msg = "```ini\n" "----Server Settings----\n"
|
msg = "```ini\n" "----Server Settings----\n"
|
||||||
|
if emptydc_enabled:
|
||||||
|
msg += "Disconnect timer: [{0}]\n".format(self._dynamic_time(emptydc_timer))
|
||||||
if dj_enabled:
|
if dj_enabled:
|
||||||
msg += "DJ Role: [{}]\n".format(dj_role_obj.name)
|
msg += "DJ Role: [{}]\n".format(dj_role_obj.name)
|
||||||
if jukebox:
|
if jukebox:
|
||||||
@@ -259,6 +298,8 @@ class Audio:
|
|||||||
"Song notify msgs: [{notify}]\n"
|
"Song notify msgs: [{notify}]\n"
|
||||||
"Songs as status: [{status}]\n".format(**global_data, **data)
|
"Songs as status: [{status}]\n".format(**global_data, **data)
|
||||||
)
|
)
|
||||||
|
if thumbnail:
|
||||||
|
msg += "Thumbnails: [{0}]\n".format(thumbnail)
|
||||||
if vote_percent > 0:
|
if vote_percent > 0:
|
||||||
msg += (
|
msg += (
|
||||||
"Vote skip: [{vote_enabled}]\n" "Skip percentage: [{vote_percent}%]\n"
|
"Vote skip: [{vote_enabled}]\n" "Skip percentage: [{vote_percent}%]\n"
|
||||||
@@ -270,9 +311,17 @@ class Audio:
|
|||||||
"External server: [{use_external_lavalink}]```"
|
"External server: [{use_external_lavalink}]```"
|
||||||
).format(__version__, jarbuild, **global_data)
|
).format(__version__, jarbuild, **global_data)
|
||||||
|
|
||||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, description=msg)
|
embed = discord.Embed(colour=(await ctx.embed_colour()), description=msg)
|
||||||
return await ctx.send(embed=embed)
|
return await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@audioset.command()
|
||||||
|
@checks.mod_or_permissions(administrator=True)
|
||||||
|
async def thumbnail(self, ctx):
|
||||||
|
"""Toggle displaying a thumbnail on audio messages."""
|
||||||
|
thumbnail = await self.config.guild(ctx.guild).thumbnail()
|
||||||
|
await self.config.guild(ctx.guild).thumbnail.set(not thumbnail)
|
||||||
|
await self._embed_msg(ctx, "Thumbnail display: {}.".format(not thumbnail))
|
||||||
|
|
||||||
@audioset.command()
|
@audioset.command()
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def vote(self, ctx, percent: int):
|
async def vote(self, ctx, percent: int):
|
||||||
@@ -330,7 +379,7 @@ class Audio:
|
|||||||
else:
|
else:
|
||||||
servers = "\n".join(server_list)
|
servers = "\n".join(server_list)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Connected in {} servers:".format(server_num),
|
title="Connected in {} servers:".format(server_num),
|
||||||
description=servers,
|
description=servers,
|
||||||
)
|
)
|
||||||
@@ -408,8 +457,10 @@ class Audio:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="Now Playing", description=song
|
colour=(await ctx.embed_colour()), title="Now Playing", description=song
|
||||||
)
|
)
|
||||||
|
if await self.config.guild(ctx.guild).thumbnail() and player.current.thumbnail:
|
||||||
|
embed.set_thumbnail(url=player.current.thumbnail)
|
||||||
message = await ctx.send(embed=embed)
|
message = await ctx.send(embed=embed)
|
||||||
player.store("np_message", message)
|
player.store("np_message", message)
|
||||||
|
|
||||||
@@ -426,7 +477,11 @@ class Audio:
|
|||||||
await message.add_reaction(expected[i])
|
await message.add_reaction(expected[i])
|
||||||
|
|
||||||
def check(r, u):
|
def check(r, u):
|
||||||
return r.message.id == message.id and u == ctx.message.author
|
return (
|
||||||
|
r.message.id == message.id
|
||||||
|
and u == ctx.message.author
|
||||||
|
and any(e in str(r.emoji) for e in expected)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(r, u) = await self.bot.wait_for("reaction_add", check=check, timeout=10.0)
|
(r, u) = await self.bot.wait_for("reaction_add", check=check, timeout=10.0)
|
||||||
@@ -471,7 +526,7 @@ class Audio:
|
|||||||
if player.current and not player.paused and command != "resume":
|
if player.current and not player.paused and command != "resume":
|
||||||
await player.pause()
|
await player.pause()
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Track Paused",
|
title="Track Paused",
|
||||||
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
||||||
)
|
)
|
||||||
@@ -480,7 +535,7 @@ class Audio:
|
|||||||
if player.paused and command != "pause":
|
if player.paused and command != "pause":
|
||||||
await player.pause(False)
|
await player.pause(False)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Track Resumed",
|
title="Track Resumed",
|
||||||
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
||||||
)
|
)
|
||||||
@@ -542,7 +597,7 @@ class Audio:
|
|||||||
queue_user = ["{}: {:g}%".format(x[0], x[1]) for x in top_queue_users]
|
queue_user = ["{}: {:g}%".format(x[0], x[1]) for x in top_queue_users]
|
||||||
queue_user_list = "\n".join(queue_user)
|
queue_user_list = "\n".join(queue_user)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Queued and playing songs:",
|
title="Queued and playing songs:",
|
||||||
description=queue_user_list,
|
description=queue_user_list,
|
||||||
)
|
)
|
||||||
@@ -557,6 +612,12 @@ class Audio:
|
|||||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
try:
|
try:
|
||||||
|
if not ctx.author.voice.channel.permissions_for(
|
||||||
|
ctx.me
|
||||||
|
).connect == True or self._userlimit(ctx.author.voice.channel):
|
||||||
|
return await self._embed_msg(
|
||||||
|
ctx, "I don't have permission to connect to your channel."
|
||||||
|
)
|
||||||
await lavalink.connect(ctx.author.voice.channel)
|
await lavalink.connect(ctx.author.voice.channel)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("connect", datetime.datetime.utcnow())
|
player.store("connect", datetime.datetime.utcnow())
|
||||||
@@ -590,20 +651,20 @@ class Audio:
|
|||||||
|
|
||||||
queue_duration = await self._queue_duration(ctx)
|
queue_duration = await self._queue_duration(ctx)
|
||||||
queue_total_duration = lavalink.utils.format_time(queue_duration)
|
queue_total_duration = lavalink.utils.format_time(queue_duration)
|
||||||
before_queue_length = len(player.queue) + 1
|
before_queue_length = len(player.queue)
|
||||||
|
|
||||||
if "list" in query and "ytsearch:" not in query:
|
if "list" in query and "ytsearch:" not in query:
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
player.add(ctx.author, track)
|
player.add(ctx.author, track)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Playlist Enqueued",
|
title="Playlist Enqueued",
|
||||||
description="Added {} tracks to the queue.".format(len(tracks)),
|
description="Added {} tracks to the queue.".format(len(tracks)),
|
||||||
)
|
)
|
||||||
if not shuffle and queue_duration > 0:
|
if not shuffle and queue_duration > 0:
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text="{} until start of playlist playback: starts at #{} in queue".format(
|
text="{} until start of playlist playback: starts at #{} in queue".format(
|
||||||
queue_total_duration, before_queue_length
|
queue_total_duration, before_queue_length + 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if not player.current:
|
if not player.current:
|
||||||
@@ -612,18 +673,18 @@ class Audio:
|
|||||||
single_track = tracks[0]
|
single_track = tracks[0]
|
||||||
player.add(ctx.author, single_track)
|
player.add(ctx.author, single_track)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Track Enqueued",
|
title="Track Enqueued",
|
||||||
description="**[{}]({})**".format(single_track.title, single_track.uri),
|
description="**[{}]({})**".format(single_track.title, single_track.uri),
|
||||||
)
|
)
|
||||||
if not shuffle and queue_duration > 0:
|
if not shuffle and queue_duration > 0:
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text="{} until track playback: #{} in queue".format(
|
text="{} until track playback: #{} in queue".format(
|
||||||
queue_total_duration, before_queue_length
|
queue_total_duration, before_queue_length + 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif queue_duration > 0:
|
elif queue_duration > 0:
|
||||||
embed.set_footer(text="#{} in queue".format(len(player.queue) + 1))
|
embed.set_footer(text="#{} in queue".format(len(player.queue)))
|
||||||
if not player.current:
|
if not player.current:
|
||||||
await player.play()
|
await player.play()
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
@@ -632,8 +693,7 @@ class Audio:
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def playlist(self, ctx):
|
async def playlist(self, ctx):
|
||||||
"""Playlist configuration options."""
|
"""Playlist configuration options."""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@playlist.command(name="append")
|
@playlist.command(name="append")
|
||||||
async def _playlist_append(self, ctx, playlist_name, *url):
|
async def _playlist_append(self, ctx, playlist_name, *url):
|
||||||
@@ -680,7 +740,9 @@ class Audio:
|
|||||||
return await self._embed_msg(
|
return await self._embed_msg(
|
||||||
ctx, "Playlist name already exists, try again with a different name."
|
ctx, "Playlist name already exists, try again with a different name."
|
||||||
)
|
)
|
||||||
|
playlist_name = playlist_name.split(" ")[0].strip('"')
|
||||||
playlist_list = self._to_json(ctx, None, None)
|
playlist_list = self._to_json(ctx, None, None)
|
||||||
|
async with self.config.guild(ctx.guild).playlists() as playlists:
|
||||||
playlists[playlist_name] = playlist_list
|
playlists[playlist_name] = playlist_list
|
||||||
await self._embed_msg(ctx, "Empty playlist {} created.".format(playlist_name))
|
await self._embed_msg(ctx, "Empty playlist {} created.".format(playlist_name))
|
||||||
|
|
||||||
@@ -717,7 +779,7 @@ class Audio:
|
|||||||
else:
|
else:
|
||||||
playlist_url = "URL: <{}>".format(playlist_url)
|
playlist_url = "URL: <{}>".format(playlist_url)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Playlist info for {}:".format(playlist_name),
|
title="Playlist info for {}:".format(playlist_name),
|
||||||
description="Author: **{}**\n{}".format(author_obj, playlist_url),
|
description="Author: **{}**\n{}".format(author_obj, playlist_url),
|
||||||
)
|
)
|
||||||
@@ -734,12 +796,13 @@ class Audio:
|
|||||||
abc_names = sorted(playlist_list, key=str.lower)
|
abc_names = sorted(playlist_list, key=str.lower)
|
||||||
all_playlists = ", ".join(abc_names)
|
all_playlists = ", ".join(abc_names)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Playlists for {}:".format(ctx.guild.name),
|
title="Playlists for {}:".format(ctx.guild.name),
|
||||||
description=all_playlists,
|
description=all_playlists,
|
||||||
)
|
)
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.cooldown(1, 15, discord.ext.commands.BucketType.guild)
|
||||||
@playlist.command(name="queue")
|
@playlist.command(name="queue")
|
||||||
async def _playlist_queue(self, ctx, playlist_name=None):
|
async def _playlist_queue(self, ctx, playlist_name=None):
|
||||||
"""Save the queue to a playlist."""
|
"""Save the queue to a playlist."""
|
||||||
@@ -766,11 +829,11 @@ class Audio:
|
|||||||
await self._embed_msg(ctx, "Please enter a name for this playlist.")
|
await self._embed_msg(ctx, "Please enter a name for this playlist.")
|
||||||
|
|
||||||
def check(m):
|
def check(m):
|
||||||
return m.author == ctx.author
|
return m.author == ctx.author and not m.content.startswith(ctx.prefix)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
playlist_name_msg = await ctx.bot.wait_for("message", timeout=15.0, check=check)
|
playlist_name_msg = await ctx.bot.wait_for("message", timeout=15.0, check=check)
|
||||||
playlist_name = str(playlist_name_msg.content)
|
playlist_name = playlist_name_msg.content.split(" ")[0].strip('"')
|
||||||
if len(playlist_name) > 20:
|
if len(playlist_name) > 20:
|
||||||
return await self._embed_msg(ctx, "Try the command again with a shorter name.")
|
return await self._embed_msg(ctx, "Try the command again with a shorter name.")
|
||||||
if playlist_name in playlists:
|
if playlist_name in playlists:
|
||||||
@@ -781,11 +844,12 @@ class Audio:
|
|||||||
return await self._embed_msg(ctx, "No playlist name entered, try again later.")
|
return await self._embed_msg(ctx, "No playlist name entered, try again later.")
|
||||||
playlist_list = self._to_json(ctx, None, tracklist)
|
playlist_list = self._to_json(ctx, None, tracklist)
|
||||||
async with self.config.guild(ctx.guild).playlists() as playlists:
|
async with self.config.guild(ctx.guild).playlists() as playlists:
|
||||||
|
playlist_name = playlist_name.split(" ")[0].strip('"')
|
||||||
playlists[playlist_name] = playlist_list
|
playlists[playlist_name] = playlist_list
|
||||||
await self._embed_msg(
|
await self._embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
"Playlist {} saved from current queue: {} tracks added.".format(
|
"Playlist {} saved from current queue: {} tracks added.".format(
|
||||||
playlist_name, len(tracklist)
|
playlist_name.split(" ")[0].strip('"'), len(tracklist)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -833,6 +897,7 @@ class Audio:
|
|||||||
playlist_list = self._to_json(ctx, playlist_url, tracklist)
|
playlist_list = self._to_json(ctx, playlist_url, tracklist)
|
||||||
if tracklist is not None:
|
if tracklist is not None:
|
||||||
async with self.config.guild(ctx.guild).playlists() as playlists:
|
async with self.config.guild(ctx.guild).playlists() as playlists:
|
||||||
|
playlist_name = playlist_name.split(" ")[0].strip('"')
|
||||||
playlists[playlist_name] = playlist_list
|
playlists[playlist_name] = playlist_list
|
||||||
return await self._embed_msg(
|
return await self._embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -853,7 +918,7 @@ class Audio:
|
|||||||
player.add(author_obj, lavalink.rest_api.Track(data=track))
|
player.add(author_obj, lavalink.rest_api.Track(data=track))
|
||||||
track_count = track_count + 1
|
track_count = track_count + 1
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Playlist Enqueued",
|
title="Playlist Enqueued",
|
||||||
description="Added {} tracks to the queue.".format(track_count),
|
description="Added {} tracks to the queue.".format(track_count),
|
||||||
)
|
)
|
||||||
@@ -891,8 +956,11 @@ class Audio:
|
|||||||
file_suffix = file_url.rsplit(".", 1)[1]
|
file_suffix = file_url.rsplit(".", 1)[1]
|
||||||
if file_suffix != "txt":
|
if file_suffix != "txt":
|
||||||
return await self._embed_msg(ctx, "Only playlist files can be uploaded.")
|
return await self._embed_msg(ctx, "Only playlist files can be uploaded.")
|
||||||
|
try:
|
||||||
async with self.session.request("GET", file_url) as r:
|
async with self.session.request("GET", file_url) as r:
|
||||||
v2_playlist = await r.json(content_type="text/plain")
|
v2_playlist = await r.json(content_type="text/plain")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return await self._embed_msg(ctx, "Not a valid playlist file.")
|
||||||
try:
|
try:
|
||||||
v2_playlist_url = v2_playlist["link"]
|
v2_playlist_url = v2_playlist["link"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -913,7 +981,7 @@ class Audio:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
embed1 = discord.Embed(
|
embed1 = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="Please wait, adding tracks..."
|
colour=(await ctx.embed_colour()), title="Please wait, adding tracks..."
|
||||||
)
|
)
|
||||||
playlist_msg = await ctx.send(embed=embed1)
|
playlist_msg = await ctx.send(embed=embed1)
|
||||||
for song_url in v2_playlist["playlist"]:
|
for song_url in v2_playlist["playlist"]:
|
||||||
@@ -926,7 +994,7 @@ class Audio:
|
|||||||
pass
|
pass
|
||||||
if track_count % 5 == 0:
|
if track_count % 5 == 0:
|
||||||
embed2 = discord.Embed(
|
embed2 = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Loading track {}/{}...".format(
|
title="Loading track {}/{}...".format(
|
||||||
track_count, len(v2_playlist["playlist"])
|
track_count, len(v2_playlist["playlist"])
|
||||||
),
|
),
|
||||||
@@ -946,7 +1014,7 @@ class Audio:
|
|||||||
else:
|
else:
|
||||||
msg = "Added {} tracks from the {} playlist.".format(track_count, v2_playlist_name)
|
msg = "Added {} tracks from the {} playlist.".format(track_count, v2_playlist_name)
|
||||||
embed3 = discord.Embed(
|
embed3 = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="Playlist Saved", description=msg
|
colour=(await ctx.embed_colour()), title="Playlist Saved", description=msg
|
||||||
)
|
)
|
||||||
await playlist_msg.edit(embed=embed3)
|
await playlist_msg.edit(embed=embed3)
|
||||||
else:
|
else:
|
||||||
@@ -961,6 +1029,12 @@ class Audio:
|
|||||||
return False
|
return False
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
try:
|
try:
|
||||||
|
if not ctx.author.voice.channel.permissions_for(
|
||||||
|
ctx.me
|
||||||
|
).connect == True or self._userlimit(ctx.author.voice.channel):
|
||||||
|
return await self._embed_msg(
|
||||||
|
ctx, "I don't have permission to connect to your channel."
|
||||||
|
)
|
||||||
await lavalink.connect(ctx.author.voice.channel)
|
await lavalink.connect(ctx.author.voice.channel)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("connect", datetime.datetime.utcnow())
|
player.store("connect", datetime.datetime.utcnow())
|
||||||
@@ -1036,7 +1110,7 @@ class Audio:
|
|||||||
player.queue.pop(queue_len)
|
player.queue.pop(queue_len)
|
||||||
await player.skip()
|
await player.skip()
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Replaying Track",
|
title="Replaying Track",
|
||||||
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
||||||
)
|
)
|
||||||
@@ -1102,10 +1176,12 @@ class Audio:
|
|||||||
)
|
)
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Queue for " + ctx.guild.name,
|
title="Queue for " + ctx.guild.name,
|
||||||
description=queue_list,
|
description=queue_list,
|
||||||
)
|
)
|
||||||
|
if await self.config.guild(ctx.guild).thumbnail() and player.current.thumbnail:
|
||||||
|
embed.set_thumbnail(url=player.current.thumbnail)
|
||||||
queue_duration = await self._queue_duration(ctx)
|
queue_duration = await self._queue_duration(ctx)
|
||||||
queue_total_duration = lavalink.utils.format_time(queue_duration)
|
queue_total_duration = lavalink.utils.format_time(queue_duration)
|
||||||
text = "Page {}/{} | {} tracks, {} remaining".format(
|
text = "Page {}/{} | {} tracks, {} remaining".format(
|
||||||
@@ -1178,6 +1254,12 @@ class Audio:
|
|||||||
"""
|
"""
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
try:
|
try:
|
||||||
|
if not ctx.author.voice.channel.permissions_for(
|
||||||
|
ctx.me
|
||||||
|
).connect == True or self._userlimit(ctx.author.voice.channel):
|
||||||
|
return await self._embed_msg(
|
||||||
|
ctx, "I don't have permission to connect to your channel."
|
||||||
|
)
|
||||||
await lavalink.connect(ctx.author.voice.channel)
|
await lavalink.connect(ctx.author.voice.channel)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("connect", datetime.datetime.utcnow())
|
player.store("connect", datetime.datetime.utcnow())
|
||||||
@@ -1195,13 +1277,12 @@ class Audio:
|
|||||||
|
|
||||||
query = query.strip("<>")
|
query = query.strip("<>")
|
||||||
if query.startswith("list "):
|
if query.startswith("list "):
|
||||||
query = "ytsearch:{}".format(query.lstrip("list "))
|
query = "ytsearch:{}".format(query.replace("list ", ""))
|
||||||
tracks = await player.get_tracks(query)
|
tracks = await player.get_tracks(query)
|
||||||
if not tracks:
|
if not tracks:
|
||||||
return await self._embed_msg(ctx, "Nothing found 👀")
|
return await self._embed_msg(ctx, "Nothing found.")
|
||||||
songembed = discord.Embed(
|
songembed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()), title="Queued {} track(s).".format(len(tracks))
|
||||||
title="Queued {} track(s).".format(len(tracks)),
|
|
||||||
)
|
)
|
||||||
queue_duration = await self._queue_duration(ctx)
|
queue_duration = await self._queue_duration(ctx)
|
||||||
queue_total_duration = lavalink.utils.format_time(queue_duration)
|
queue_total_duration = lavalink.utils.format_time(queue_duration)
|
||||||
@@ -1217,12 +1298,12 @@ class Audio:
|
|||||||
await player.play()
|
await player.play()
|
||||||
return await ctx.send(embed=songembed)
|
return await ctx.send(embed=songembed)
|
||||||
if query.startswith("sc "):
|
if query.startswith("sc "):
|
||||||
query = "scsearch:{}".format(query.lstrip("sc "))
|
query = "scsearch:{}".format(query.replace("sc ", ""))
|
||||||
elif not query.startswith("http"):
|
elif not query.startswith("http"):
|
||||||
query = "ytsearch:{}".format(query)
|
query = "ytsearch:{}".format(query)
|
||||||
tracks = await player.get_tracks(query)
|
tracks = await player.get_tracks(query)
|
||||||
if not tracks:
|
if not tracks:
|
||||||
return await self._embed_msg(ctx, "Nothing found 👀")
|
return await self._embed_msg(ctx, "Nothing found.")
|
||||||
|
|
||||||
len_search_pages = math.ceil(len(tracks) / 5)
|
len_search_pages = math.ceil(len(tracks) / 5)
|
||||||
search_page_list = []
|
search_page_list = []
|
||||||
@@ -1281,7 +1362,7 @@ class Audio:
|
|||||||
search_choice = tracks[-1]
|
search_choice = tracks[-1]
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Track Enqueued",
|
title="Track Enqueued",
|
||||||
description="**[{}]({})**".format(search_choice.title, search_choice.uri),
|
description="**[{}]({})**".format(search_choice.title, search_choice.uri),
|
||||||
)
|
)
|
||||||
@@ -1318,7 +1399,7 @@ class Audio:
|
|||||||
search_track_num, track.title, track.uri
|
search_track_num, track.title, track.uri
|
||||||
)
|
)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="Tracks Found:", description=search_list
|
colour=(await ctx.embed_colour()), title="Tracks Found:", description=search_list
|
||||||
)
|
)
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text="Page {}/{} | {} search results".format(page_num, search_num_pages, len(tracks))
|
text="Page {}/{} | {} search results".format(page_num, search_num_pages, len(tracks))
|
||||||
@@ -1485,26 +1566,28 @@ class Audio:
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
async def _skip_action(self, ctx):
|
||||||
async def _skip_action(ctx):
|
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
if not player.queue:
|
if not player.queue:
|
||||||
|
try:
|
||||||
pos, dur = player.position, player.current.length
|
pos, dur = player.position, player.current.length
|
||||||
|
except AttributeError:
|
||||||
|
return await self._embed_msg(ctx, "There's nothing in the queue.")
|
||||||
time_remain = lavalink.utils.format_time(dur - pos)
|
time_remain = lavalink.utils.format_time(dur - pos)
|
||||||
if player.current.is_stream:
|
if player.current.is_stream:
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="There's nothing in the queue."
|
colour=(await ctx.embed_colour()), title="There's nothing in the queue."
|
||||||
)
|
)
|
||||||
embed.set_footer(text="Currently livestreaming {}".format(player.current.title))
|
embed.set_footer(text="Currently livestreaming {}".format(player.current.title))
|
||||||
else:
|
else:
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="There's nothing in the queue."
|
colour=(await ctx.embed_colour()), title="There's nothing in the queue."
|
||||||
)
|
)
|
||||||
embed.set_footer(text="{} left on {}".format(time_remain, player.current.title))
|
embed.set_footer(text="{} left on {}".format(time_remain, player.current.title))
|
||||||
return await ctx.send(embed=embed)
|
return await ctx.send(embed=embed)
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Track Skipped",
|
title="Track Skipped",
|
||||||
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
description="**[{}]({})**".format(player.current.title, player.current.uri),
|
||||||
)
|
)
|
||||||
@@ -1552,7 +1635,7 @@ class Audio:
|
|||||||
if not vol:
|
if not vol:
|
||||||
vol = await self.config.guild(ctx.guild).volume()
|
vol = await self.config.guild(ctx.guild).volume()
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Current Volume:",
|
title="Current Volume:",
|
||||||
description=str(vol) + "%",
|
description=str(vol) + "%",
|
||||||
)
|
)
|
||||||
@@ -1582,7 +1665,7 @@ class Audio:
|
|||||||
if self._player_check(ctx):
|
if self._player_check(ctx):
|
||||||
await lavalink.get_player(ctx.guild.id).set_volume(vol)
|
await lavalink.get_player(ctx.guild.id).set_volume(vol)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="Volume:", description=str(vol) + "%"
|
colour=(await ctx.embed_colour()), title="Volume:", description=str(vol) + "%"
|
||||||
)
|
)
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
embed.set_footer(text="Nothing playing.")
|
embed.set_footer(text="Nothing playing.")
|
||||||
@@ -1593,8 +1676,7 @@ class Audio:
|
|||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def llsetup(self, ctx):
|
async def llsetup(self, ctx):
|
||||||
"""Lavalink server configuration options."""
|
"""Lavalink server configuration options."""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@llsetup.command()
|
@llsetup.command()
|
||||||
async def external(self, ctx):
|
async def external(self, ctx):
|
||||||
@@ -1607,7 +1689,7 @@ class Audio:
|
|||||||
await self.config.rest_port.set(2333)
|
await self.config.rest_port.set(2333)
|
||||||
await self.config.ws_port.set(2332)
|
await self.config.ws_port.set(2332)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="External lavalink server: {}.".format(not external),
|
title="External lavalink server: {}.".format(not external),
|
||||||
)
|
)
|
||||||
embed.set_footer(text="Defaults reset.")
|
embed.set_footer(text="Defaults reset.")
|
||||||
@@ -1621,7 +1703,7 @@ class Audio:
|
|||||||
await self.config.host.set(host)
|
await self.config.host.set(host)
|
||||||
if await self._check_external():
|
if await self._check_external():
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="Host set to {}.".format(host)
|
colour=(await ctx.embed_colour()), title="Host set to {}.".format(host)
|
||||||
)
|
)
|
||||||
embed.set_footer(text="External lavalink server set to True.")
|
embed.set_footer(text="External lavalink server set to True.")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
@@ -1634,7 +1716,7 @@ class Audio:
|
|||||||
await self.config.password.set(str(password))
|
await self.config.password.set(str(password))
|
||||||
if await self._check_external():
|
if await self._check_external():
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Server password set to {}.".format(password),
|
title="Server password set to {}.".format(password),
|
||||||
)
|
)
|
||||||
embed.set_footer(text="External lavalink server set to True.")
|
embed.set_footer(text="External lavalink server set to True.")
|
||||||
@@ -1648,7 +1730,7 @@ class Audio:
|
|||||||
await self.config.rest_port.set(rest_port)
|
await self.config.rest_port.set(rest_port)
|
||||||
if await self._check_external():
|
if await self._check_external():
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour, title="REST port set to {}.".format(rest_port)
|
colour=(await ctx.embed_colour()), title="REST port set to {}.".format(rest_port)
|
||||||
)
|
)
|
||||||
embed.set_footer(text="External lavalink server set to True.")
|
embed.set_footer(text="External lavalink server set to True.")
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
@@ -1661,7 +1743,7 @@ class Audio:
|
|||||||
await self.config.ws_port.set(ws_port)
|
await self.config.ws_port.set(ws_port)
|
||||||
if await self._check_external():
|
if await self._check_external():
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=ctx.guild.me.top_role.colour,
|
colour=(await ctx.embed_colour()),
|
||||||
title="Websocket port set to {}.".format(ws_port),
|
title="Websocket port set to {}.".format(ws_port),
|
||||||
)
|
)
|
||||||
embed.set_footer(text="External lavalink server set to True.")
|
embed.set_footer(text="External lavalink server set to True.")
|
||||||
@@ -1711,6 +1793,34 @@ class Audio:
|
|||||||
if player.volume != volume:
|
if player.volume != volume:
|
||||||
await player.set_volume(volume)
|
await player.set_volume(volume)
|
||||||
|
|
||||||
|
async def disconnect_timer(self):
|
||||||
|
stop_times = {}
|
||||||
|
|
||||||
|
while self == self.bot.get_cog("Audio"):
|
||||||
|
for p in lavalink.players:
|
||||||
|
server = p.channel.guild
|
||||||
|
|
||||||
|
if server.id not in stop_times:
|
||||||
|
stop_times[server.id] = None
|
||||||
|
|
||||||
|
if [self.bot.user] == p.channel.members:
|
||||||
|
if stop_times[server.id] is None:
|
||||||
|
stop_times[server.id] = int(time.time())
|
||||||
|
|
||||||
|
for sid in stop_times:
|
||||||
|
server_obj = self.bot.get_guild(sid)
|
||||||
|
emptydc_enabled = await self.config.guild(server_obj).emptydc_enabled()
|
||||||
|
if emptydc_enabled:
|
||||||
|
if stop_times[sid] is not None and [self.bot.user] == p.channel.members:
|
||||||
|
emptydc_timer = await self.config.guild(server_obj).emptydc_timer()
|
||||||
|
if stop_times[sid] and (
|
||||||
|
int(time.time()) - stop_times[sid] > emptydc_timer
|
||||||
|
):
|
||||||
|
stop_times[sid] = None
|
||||||
|
await lavalink.get_player(sid).disconnect()
|
||||||
|
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _draw_time(ctx):
|
async def _draw_time(ctx):
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
@@ -1752,7 +1862,7 @@ class Audio:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _embed_msg(ctx, title):
|
async def _embed_msg(ctx, title):
|
||||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title=title)
|
embed = discord.Embed(colour=(await ctx.embed_colour()), title=title)
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def _get_playing(self, ctx):
|
async def _get_playing(self, ctx):
|
||||||
@@ -1826,6 +1936,15 @@ class Audio:
|
|||||||
track_obj[key] = value
|
track_obj[key] = value
|
||||||
return track_obj
|
return track_obj
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _userlimit(channel):
|
||||||
|
if channel.user_limit == 0:
|
||||||
|
return False
|
||||||
|
if channel.user_limit < len(channel.members) + 1:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
async def on_voice_state_update(self, member, before, after):
|
async def on_voice_state_update(self, member, before, after):
|
||||||
if after.channel != before.channel:
|
if after.channel != before.channel:
|
||||||
try:
|
try:
|
||||||
@@ -1834,7 +1953,7 @@ class Audio:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def __unload(self):
|
def __unload(self):
|
||||||
self.session.close()
|
self.session.detach()
|
||||||
lavalink.unregister_event_listener(self.event_handler)
|
lavalink.unregister_event_listener(self.event_handler)
|
||||||
self.bot.loop.create_task(lavalink.close())
|
self.bot.loop.create_task(lavalink.close())
|
||||||
shutdown_lavalink_server()
|
shutdown_lavalink_server()
|
||||||
|
|||||||
@@ -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 shlex
|
||||||
import shutil
|
import shutil
|
||||||
import asyncio
|
import asyncio
|
||||||
from subprocess import Popen, DEVNULL, PIPE
|
import asyncio.subprocess
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
from subprocess import Popen, DEVNULL
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
_JavaVersion = Tuple[int, int]
|
||||||
|
|
||||||
log = logging.getLogger("red.audio.manager")
|
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
|
java_available = shutil.which("java") is not None
|
||||||
if not java_available:
|
if not java_available:
|
||||||
return False
|
return False, None
|
||||||
|
|
||||||
version = await get_java_version(loop)
|
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.
|
This assumes we've already checked that java exists.
|
||||||
"""
|
"""
|
||||||
proc = Popen(shlex.split("java -version", posix=os.name == "posix"), stdout=PIPE, stderr=PIPE)
|
_proc: asyncio.subprocess.Process = await asyncio.create_subprocess_exec(
|
||||||
_, err = proc.communicate()
|
"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]
|
lines = version_info.splitlines()
|
||||||
version_start = version_line.find('"')
|
for line in lines:
|
||||||
version_string = version_line[version_start + 1 : -1]
|
match = version_line_re.search(line)
|
||||||
major, minor = version_string.split(".")[:2]
|
if match:
|
||||||
return int(major), int(minor)
|
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):
|
async def start_lavalink_server(loop):
|
||||||
|
|||||||
@@ -17,13 +17,15 @@ def check_global_setting_guildowner():
|
|||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
if await ctx.bot.is_owner(author):
|
|
||||||
return True
|
|
||||||
if not await bank.is_global():
|
if not await bank.is_global():
|
||||||
if not isinstance(ctx.channel, discord.abc.GuildChannel):
|
if not isinstance(ctx.channel, discord.abc.GuildChannel):
|
||||||
return False
|
return False
|
||||||
|
if await ctx.bot.is_owner(author):
|
||||||
|
return True
|
||||||
permissions = ctx.channel.permissions_for(author)
|
permissions = ctx.channel.permissions_for(author)
|
||||||
return author == ctx.guild.owner or permissions.administrator
|
return author == ctx.guild.owner or permissions.administrator
|
||||||
|
else:
|
||||||
|
return await ctx.bot.is_owner(author)
|
||||||
|
|
||||||
return commands.check(pred)
|
return commands.check(pred)
|
||||||
|
|
||||||
@@ -36,15 +38,17 @@ def check_global_setting_admin():
|
|||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
if await ctx.bot.is_owner(author):
|
|
||||||
return True
|
|
||||||
if not await bank.is_global():
|
if not await bank.is_global():
|
||||||
if not isinstance(ctx.channel, discord.abc.GuildChannel):
|
if not isinstance(ctx.channel, discord.abc.GuildChannel):
|
||||||
return False
|
return False
|
||||||
|
if await ctx.bot.is_owner(author):
|
||||||
|
return True
|
||||||
permissions = ctx.channel.permissions_for(author)
|
permissions = ctx.channel.permissions_for(author)
|
||||||
is_guild_owner = author == ctx.guild.owner
|
is_guild_owner = author == ctx.guild.owner
|
||||||
admin_role = await ctx.bot.db.guild(ctx.guild).admin_role()
|
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
|
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)
|
return commands.check(pred)
|
||||||
|
|
||||||
@@ -58,8 +62,9 @@ class Bank:
|
|||||||
|
|
||||||
# SECTION commands
|
# SECTION commands
|
||||||
|
|
||||||
@commands.group()
|
@check_global_setting_guildowner()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
|
@commands.group(autohelp=True)
|
||||||
async def bankset(self, ctx: commands.Context):
|
async def bankset(self, ctx: commands.Context):
|
||||||
"""Base command for bank settings"""
|
"""Base command for bank settings"""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
@@ -69,17 +74,15 @@ class Bank:
|
|||||||
default_balance = await bank._conf.default_balance()
|
default_balance = await bank._conf.default_balance()
|
||||||
else:
|
else:
|
||||||
if not ctx.guild:
|
if not ctx.guild:
|
||||||
await ctx.send_help()
|
|
||||||
return
|
return
|
||||||
bank_name = await bank._conf.guild(ctx.guild).bank_name()
|
bank_name = await bank._conf.guild(ctx.guild).bank_name()
|
||||||
currency_name = await bank._conf.guild(ctx.guild).currency()
|
currency_name = await bank._conf.guild(ctx.guild).currency()
|
||||||
default_balance = await bank._conf.guild(ctx.guild).default_balance()
|
default_balance = await bank._conf.guild(ctx.guild).default_balance()
|
||||||
|
|
||||||
settings = _(
|
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)
|
).format(bank_name, currency_name, default_balance)
|
||||||
await ctx.send(box(settings))
|
await ctx.send(box(settings))
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@bankset.command(name="toggleglobal")
|
@bankset.command(name="toggleglobal")
|
||||||
@checks.is_owner()
|
@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()
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Union, List, Callable
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@@ -49,59 +51,65 @@ class Cleanup:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_messages_for_deletion(
|
async def get_messages_for_deletion(
|
||||||
ctx: commands.Context,
|
*,
|
||||||
channel: discord.TextChannel,
|
channel: discord.TextChannel,
|
||||||
number,
|
number: int = None,
|
||||||
check=lambda x: True,
|
check: Callable[[discord.Message], bool] = lambda x: True,
|
||||||
limit=100,
|
before: Union[discord.Message, datetime] = None,
|
||||||
before=None,
|
after: Union[discord.Message, datetime] = None,
|
||||||
after=None,
|
delete_pinned: bool = False,
|
||||||
delete_pinned=False,
|
) -> List[discord.Message]:
|
||||||
) -> list:
|
|
||||||
"""
|
"""
|
||||||
Gets a list of messages meeting the requirements to be deleted.
|
Gets a list of messages meeting the requirements to be deleted.
|
||||||
|
|
||||||
Generally, the requirements are:
|
Generally, the requirements are:
|
||||||
- We don't have the number of messages to be deleted already
|
- We don't have the number of messages to be deleted already
|
||||||
- The message passes a provided check (if no check is provided,
|
- The message passes a provided check (if no check is provided,
|
||||||
this is automatically true)
|
this is automatically true)
|
||||||
- The message is less than 14 days old
|
- The message is less than 14 days old
|
||||||
- The message is not pinned
|
- The message is not pinned
|
||||||
"""
|
|
||||||
to_delete = []
|
|
||||||
too_old = False
|
|
||||||
|
|
||||||
while not too_old and len(to_delete) - 1 < number:
|
Warning: Due to the way the API hands messages back in chunks,
|
||||||
message = None
|
passing after and a number together is not advisable.
|
||||||
async for message in channel.history(limit=limit, before=before, after=after):
|
If you need to accomplish this, you should filter messages on
|
||||||
if (
|
the entire applicable range, rather than use this utility.
|
||||||
(not number or len(to_delete) - 1 < number)
|
"""
|
||||||
and check(message)
|
|
||||||
and (ctx.message.created_at - message.created_at).days < 14
|
# 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)
|
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
|
||||||
):
|
):
|
||||||
to_delete.append(message)
|
if message.created_at < two_weeks_ago:
|
||||||
elif (ctx.message.created_at - message.created_at).days >= 14:
|
|
||||||
too_old = True
|
|
||||||
break
|
break
|
||||||
elif number and len(to_delete) >= number:
|
if check(message):
|
||||||
|
collected.append(message)
|
||||||
|
if number and number <= len(collected):
|
||||||
break
|
break
|
||||||
if message is None:
|
|
||||||
break
|
return collected
|
||||||
else:
|
|
||||||
before = message
|
|
||||||
return to_delete
|
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.mod_or_permissions(manage_messages=True)
|
@checks.mod_or_permissions(manage_messages=True)
|
||||||
async def cleanup(self, ctx: commands.Context):
|
async def cleanup(self, ctx: commands.Context):
|
||||||
"""Deletes messages."""
|
"""Deletes messages."""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
|
||||||
async def text(
|
async def text(
|
||||||
self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False
|
self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False
|
||||||
):
|
):
|
||||||
@@ -113,8 +121,11 @@ class Cleanup:
|
|||||||
Remember to use double quotes."""
|
Remember to use double quotes."""
|
||||||
|
|
||||||
channel = ctx.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.author
|
author = ctx.author
|
||||||
is_bot = self.bot.user.bot
|
|
||||||
|
|
||||||
if number > 100:
|
if number > 100:
|
||||||
cont = await self.check_100_plus(ctx, number)
|
cont = await self.check_100_plus(ctx, number)
|
||||||
@@ -130,28 +141,22 @@ class Cleanup:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx,
|
channel=channel,
|
||||||
channel,
|
number=number,
|
||||||
number,
|
|
||||||
check=check,
|
check=check,
|
||||||
limit=1000,
|
|
||||||
before=ctx.message,
|
before=ctx.message,
|
||||||
delete_pinned=delete_pinned,
|
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
|
author.name, author.id, len(to_delete), text, channel.id
|
||||||
)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot:
|
|
||||||
await mass_purge(to_delete, channel)
|
await mass_purge(to_delete, channel)
|
||||||
else:
|
|
||||||
await slow_deletion(to_delete)
|
|
||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
|
||||||
async def user(
|
async def user(
|
||||||
self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False
|
self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False
|
||||||
):
|
):
|
||||||
@@ -160,6 +165,10 @@ class Cleanup:
|
|||||||
Examples:
|
Examples:
|
||||||
cleanup user @\u200bTwentysix 2
|
cleanup user @\u200bTwentysix 2
|
||||||
cleanup user Red 6"""
|
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
|
member = None
|
||||||
try:
|
try:
|
||||||
@@ -172,9 +181,7 @@ class Cleanup:
|
|||||||
else:
|
else:
|
||||||
_id = member.id
|
_id = member.id
|
||||||
|
|
||||||
channel = ctx.channel
|
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
is_bot = self.bot.user.bot
|
|
||||||
|
|
||||||
if number > 100:
|
if number > 100:
|
||||||
cont = await self.check_100_plus(ctx, number)
|
cont = await self.check_100_plus(ctx, number)
|
||||||
@@ -190,11 +197,9 @@ class Cleanup:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx,
|
channel=channel,
|
||||||
channel,
|
number=number,
|
||||||
number,
|
|
||||||
check=check,
|
check=check,
|
||||||
limit=1000,
|
|
||||||
before=ctx.message,
|
before=ctx.message,
|
||||||
delete_pinned=delete_pinned,
|
delete_pinned=delete_pinned,
|
||||||
)
|
)
|
||||||
@@ -205,15 +210,10 @@ class Cleanup:
|
|||||||
)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot:
|
|
||||||
# For whatever reason the purge endpoint requires manage_messages
|
|
||||||
await mass_purge(to_delete, channel)
|
await mass_purge(to_delete, channel)
|
||||||
else:
|
|
||||||
await slow_deletion(to_delete)
|
|
||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
|
||||||
async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool = False):
|
async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool = False):
|
||||||
"""Deletes all messages after specified message.
|
"""Deletes all messages after specified message.
|
||||||
|
|
||||||
@@ -225,24 +225,21 @@ class Cleanup:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
channel = ctx.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.author
|
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
|
|
||||||
|
|
||||||
|
try:
|
||||||
after = await channel.get_message(message_id)
|
after = await channel.get_message(message_id)
|
||||||
|
except discord.NotFound:
|
||||||
if not after:
|
return await ctx.send(_("Message not found."))
|
||||||
await ctx.send(_("Message not found."))
|
|
||||||
return
|
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
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
|
author.name, author.id, len(to_delete), channel.name
|
||||||
)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
@@ -251,7 +248,6 @@ class Cleanup:
|
|||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
|
||||||
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||||
"""Deletes last X messages.
|
"""Deletes last X messages.
|
||||||
|
|
||||||
@@ -259,39 +255,38 @@ class Cleanup:
|
|||||||
cleanup messages 26"""
|
cleanup messages 26"""
|
||||||
|
|
||||||
channel = ctx.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.author
|
author = ctx.author
|
||||||
|
|
||||||
is_bot = self.bot.user.bot
|
|
||||||
|
|
||||||
if number > 100:
|
if number > 100:
|
||||||
cont = await self.check_100_plus(ctx, number)
|
cont = await self.check_100_plus(ctx, number)
|
||||||
if not cont:
|
if not cont:
|
||||||
return
|
return
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
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)
|
to_delete.append(ctx.message)
|
||||||
|
|
||||||
reason = "{}({}) deleted {} messages in channel {}." "".format(
|
reason = "{}({}) deleted {} messages in channel {}.".format(
|
||||||
author.name, author.id, number, channel.name
|
author.name, author.id, number, channel.name
|
||||||
)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot:
|
|
||||||
await mass_purge(to_delete, channel)
|
await mass_purge(to_delete, channel)
|
||||||
else:
|
|
||||||
await slow_deletion(to_delete)
|
|
||||||
|
|
||||||
@cleanup.command(name="bot")
|
@cleanup.command(name="bot")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
|
||||||
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||||
"""Cleans up command messages and messages from the bot."""
|
"""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
|
author = ctx.message.author
|
||||||
is_bot = self.bot.user.bot
|
|
||||||
|
|
||||||
if number > 100:
|
if number > 100:
|
||||||
cont = await self.check_100_plus(ctx, number)
|
cont = await self.check_100_plus(ctx, number)
|
||||||
@@ -318,11 +313,9 @@ class Cleanup:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx,
|
channel=channel,
|
||||||
channel,
|
number=number,
|
||||||
number,
|
|
||||||
check=check,
|
check=check,
|
||||||
limit=1000,
|
|
||||||
before=ctx.message,
|
before=ctx.message,
|
||||||
delete_pinned=delete_pinned,
|
delete_pinned=delete_pinned,
|
||||||
)
|
)
|
||||||
@@ -335,10 +328,7 @@ class Cleanup:
|
|||||||
)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot:
|
|
||||||
await mass_purge(to_delete, channel)
|
await mass_purge(to_delete, channel)
|
||||||
else:
|
|
||||||
await slow_deletion(to_delete)
|
|
||||||
|
|
||||||
@cleanup.command(name="self")
|
@cleanup.command(name="self")
|
||||||
async def cleanup_self(
|
async def cleanup_self(
|
||||||
@@ -360,7 +350,6 @@ class Cleanup:
|
|||||||
"""
|
"""
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
is_bot = self.bot.user.bot
|
|
||||||
|
|
||||||
if number > 100:
|
if number > 100:
|
||||||
cont = await self.check_100_plus(ctx, number)
|
cont = await self.check_100_plus(ctx, number)
|
||||||
@@ -400,20 +389,14 @@ class Cleanup:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx,
|
channel=channel,
|
||||||
channel,
|
number=number,
|
||||||
number,
|
|
||||||
check=check,
|
check=check,
|
||||||
limit=1000,
|
|
||||||
before=ctx.message,
|
before=ctx.message,
|
||||||
delete_pinned=delete_pinned,
|
delete_pinned=delete_pinned,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Selfbot convenience, delete trigger message
|
if ctx.guild:
|
||||||
if author == self.bot.user:
|
|
||||||
to_delete.append(ctx.message)
|
|
||||||
|
|
||||||
if channel.name:
|
|
||||||
channel_name = "channel " + channel.name
|
channel_name = "channel " + channel.name
|
||||||
else:
|
else:
|
||||||
channel_name = str(channel)
|
channel_name = str(channel)
|
||||||
@@ -425,7 +408,7 @@ class Cleanup:
|
|||||||
)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot and can_mass_purge:
|
if can_mass_purge:
|
||||||
await mass_purge(to_delete, channel)
|
await mass_purge(to_delete, channel)
|
||||||
else:
|
else:
|
||||||
await slow_deletion(to_delete)
|
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()
|
|
||||||
@@ -25,7 +25,6 @@ class AlreadyExists(CCError):
|
|||||||
|
|
||||||
|
|
||||||
class CommandObj:
|
class CommandObj:
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
config = kwargs.get("config")
|
config = kwargs.get("config")
|
||||||
self.bot = kwargs.get("bot")
|
self.bot = kwargs.get("bot")
|
||||||
@@ -43,7 +42,7 @@ class CommandObj:
|
|||||||
intro = _(
|
intro = _(
|
||||||
"Welcome to the interactive random {} maker!\n"
|
"Welcome to the interactive random {} maker!\n"
|
||||||
"Every message you send will be added as one of the random "
|
"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 `{}`"
|
"triggered. To exit this interactive menu, type `{}`"
|
||||||
).format("customcommand", "customcommand", "exit()")
|
).format("customcommand", "customcommand", "exit()")
|
||||||
await ctx.send(intro)
|
await ctx.send(intro)
|
||||||
@@ -75,7 +74,7 @@ class CommandObj:
|
|||||||
return ccinfo["response"]
|
return ccinfo["response"]
|
||||||
|
|
||||||
async def create(self, ctx: commands.Context, command: str, 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
|
# Check if this command is already registered as a customcommand
|
||||||
if await self.db(ctx.guild).commands.get_raw(command, default=None):
|
if await self.db(ctx.guild).commands.get_raw(command, default=None):
|
||||||
raise AlreadyExists()
|
raise AlreadyExists()
|
||||||
@@ -132,6 +131,7 @@ class CommandObj:
|
|||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class CustomCommands:
|
class CustomCommands:
|
||||||
"""Custom commands
|
"""Custom commands
|
||||||
|
|
||||||
Creates commands used to display text"""
|
Creates commands used to display text"""
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
@@ -141,12 +141,11 @@ class CustomCommands:
|
|||||||
self.config.register_guild(commands={})
|
self.config.register_guild(commands={})
|
||||||
self.commandobj = CommandObj(config=self.config, bot=self.bot)
|
self.commandobj = CommandObj(config=self.config, bot=self.bot)
|
||||||
|
|
||||||
@commands.group(aliases=["cc"], no_pm=True)
|
@commands.group(aliases=["cc"])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def customcom(self, ctx: commands.Context):
|
async def customcom(self, ctx: commands.Context):
|
||||||
"""Custom commands management"""
|
"""Custom commands management"""
|
||||||
if not ctx.invoked_subcommand:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@customcom.group(name="add")
|
@customcom.group(name="add")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
@@ -166,14 +165,14 @@ class CustomCommands:
|
|||||||
|
|
||||||
{server} message.guild
|
{server} message.guild
|
||||||
"""
|
"""
|
||||||
if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand, commands.Group):
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@cc_add.command(name="random")
|
@cc_add.command(name="random")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_add_random(self, ctx: commands.Context, command: str):
|
async def cc_add_random(self, ctx: commands.Context, command: str):
|
||||||
"""
|
"""
|
||||||
Create a CC where it will randomly choose a response!
|
Create a CC where it will randomly choose a response!
|
||||||
|
|
||||||
Note: This is interactive
|
Note: This is interactive
|
||||||
"""
|
"""
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
@@ -185,7 +184,7 @@ class CustomCommands:
|
|||||||
await ctx.send(_("Custom command successfully added."))
|
await ctx.send(_("Custom command successfully added."))
|
||||||
except AlreadyExists:
|
except AlreadyExists:
|
||||||
await ctx.send(
|
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)
|
"{}customcom edit".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -196,6 +195,7 @@ class CustomCommands:
|
|||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_add_simple(self, ctx, command: str, *, text):
|
async def cc_add_simple(self, ctx, command: str, *, text):
|
||||||
"""Adds a simple custom command
|
"""Adds a simple custom command
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
[p]customcom add simple yourcommand Text you want
|
[p]customcom add simple yourcommand Text you want
|
||||||
"""
|
"""
|
||||||
@@ -209,7 +209,7 @@ class CustomCommands:
|
|||||||
await ctx.send(_("Custom command successfully added."))
|
await ctx.send(_("Custom command successfully added."))
|
||||||
except AlreadyExists:
|
except AlreadyExists:
|
||||||
await ctx.send(
|
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)
|
"{}customcom edit".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -218,6 +218,7 @@ class CustomCommands:
|
|||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_edit(self, ctx, command: str, *, text=None):
|
async def cc_edit(self, ctx, command: str, *, text=None):
|
||||||
"""Edits a custom command
|
"""Edits a custom command
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
[p]customcom edit yourcommand Text you want
|
[p]customcom edit yourcommand Text you want
|
||||||
"""
|
"""
|
||||||
@@ -229,7 +230,7 @@ class CustomCommands:
|
|||||||
await ctx.send(_("Custom command successfully edited."))
|
await ctx.send(_("Custom command successfully edited."))
|
||||||
except NotFound:
|
except NotFound:
|
||||||
await ctx.send(
|
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)
|
"{}customcom add".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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):
|
def past_nicknames_conv_spec(self, data: dict):
|
||||||
flatscoped = self.apply_scope(Config.MEMBER, self.flatten_dict(data))
|
flatscoped = self.apply_scope(Config.MEMBER, self.flatten_dict(data))
|
||||||
ret = {}
|
ret = {}
|
||||||
for k, v in flatscoped.items():
|
for config_identifiers, v2data in flatscoped.items():
|
||||||
outerkey, innerkey = (*k[:-1],), (k[-1],)
|
if config_identifiers not in ret:
|
||||||
if outerkey not in ret:
|
ret[config_identifiers] = {}
|
||||||
ret[outerkey] = {}
|
ret[config_identifiers].update({("past_nicks",): v2data})
|
||||||
ret[outerkey].update({innerkey: v})
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def customcom_conv_spec(self, data: dict):
|
def customcom_conv_spec(self, data: dict):
|
||||||
@@ -144,18 +143,28 @@ class SpecResolver(object):
|
|||||||
ret[outerkey].update({innerkey: ccinfo})
|
ret[outerkey].update({innerkey: ccinfo})
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def convert(self, bot: Red, prettyname: str):
|
def get_config_object(self, bot, cogname, attr, _id):
|
||||||
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"]
|
|
||||||
try:
|
try:
|
||||||
config = getattr(bot.get_cog(cogname), attr)
|
config = getattr(bot.get_cog(cogname), attr)
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
config = Config.get_conf(None, _id, cog_name=cogname)
|
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:
|
try:
|
||||||
items = converter(dc.json_load(filepath))
|
items = converter(dc.json_load(filepath))
|
||||||
await dc(config).dict_import(items)
|
await dc(config).dict_import(items)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class DataConverter:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
while resolver.available:
|
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):
|
for index, entry in enumerate(resolver.available, 1):
|
||||||
menu += "\n{}. {}".format(index, entry)
|
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 asyncio
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from redbot.core import commands
|
||||||
|
|
||||||
__all__ = ["install_agreement"]
|
__all__ = ["do_install_agreement"]
|
||||||
|
|
||||||
REPO_INSTALL_MSG = (
|
REPO_INSTALL_MSG = (
|
||||||
"You're about to add a 3rd party repository. The creator of Red"
|
"You're about to add a 3rd party repository. The creator of Red"
|
||||||
@@ -16,23 +16,13 @@ REPO_INSTALL_MSG = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def install_agreement():
|
async def do_install_agreement(ctx: commands.Context):
|
||||||
|
downloader = ctx.cog
|
||||||
async def pred(ctx: commands.Context):
|
if downloader is None or downloader.already_agreed:
|
||||||
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
|
return True
|
||||||
|
|
||||||
def does_agree(msg: discord.Message):
|
def does_agree(msg: discord.Message):
|
||||||
return (
|
return ctx.author == msg.author and ctx.channel == msg.channel and msg.content == "I agree"
|
||||||
ctx.author == msg.author
|
|
||||||
and ctx.channel == msg.channel
|
|
||||||
and msg.content == "I agree"
|
|
||||||
)
|
|
||||||
|
|
||||||
await ctx.send(REPO_INSTALL_MSG)
|
await ctx.send(REPO_INSTALL_MSG)
|
||||||
|
|
||||||
@@ -44,5 +34,3 @@ def install_agreement():
|
|||||||
|
|
||||||
downloader.already_agreed = True
|
downloader.already_agreed = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return commands.check(pred)
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from redbot.core import commands
|
||||||
from .repo_manager import RepoManager
|
|
||||||
from .installable import Installable
|
from .installable import Installable
|
||||||
|
|
||||||
|
|
||||||
class InstalledCog(commands.Converter):
|
class InstalledCog(commands.Converter):
|
||||||
|
|
||||||
async def convert(self, ctx: commands.Context, arg: str) -> Installable:
|
async def convert(self, ctx: commands.Context, arg: str) -> Installable:
|
||||||
downloader = ctx.bot.get_cog("Downloader")
|
downloader = ctx.bot.get_cog("Downloader")
|
||||||
if downloader is None:
|
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 import commands
|
||||||
|
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from .checks import install_agreement
|
from .checks import do_install_agreement
|
||||||
from .converters import InstalledCog
|
from .converters import InstalledCog
|
||||||
from .errors import CloningError, ExistingGitRepo
|
from .errors import CloningError, ExistingGitRepo
|
||||||
from .installable import Installable
|
from .installable import Installable
|
||||||
@@ -27,7 +27,6 @@ _ = Translator("Downloader", __file__)
|
|||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Downloader:
|
class Downloader:
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@@ -211,11 +210,9 @@ class Downloader:
|
|||||||
"""
|
"""
|
||||||
Command group for managing Downloader repos.
|
Command group for managing Downloader repos.
|
||||||
"""
|
"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@repo.command(name="add")
|
@repo.command(name="add")
|
||||||
@install_agreement()
|
|
||||||
async def _repo_add(self, ctx, name: str, repo_url: str, branch: str = None):
|
async def _repo_add(self, ctx, name: str, repo_url: str, branch: str = None):
|
||||||
"""
|
"""
|
||||||
Add a new repo to Downloader.
|
Add a new repo to Downloader.
|
||||||
@@ -223,6 +220,9 @@ class Downloader:
|
|||||||
Name can only contain characters A-z, numbers and underscore
|
Name can only contain characters A-z, numbers and underscore
|
||||||
Branch will default to master if not specified
|
Branch will default to master if not specified
|
||||||
"""
|
"""
|
||||||
|
agreed = await do_install_agreement(ctx)
|
||||||
|
if not agreed:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
repo = await self._repo_manager.add_repo(name=name, url=repo_url, branch=branch)
|
repo = await self._repo_manager.add_repo(name=name, url=repo_url, branch=branch)
|
||||||
@@ -234,7 +234,7 @@ class Downloader:
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("Repo `{}` successfully added.").format(name))
|
await ctx.send(_("Repo `{}` successfully added.").format(name))
|
||||||
if repo.install_msg is not None:
|
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")
|
@repo.command(name="delete")
|
||||||
async def _repo_del(self, ctx, repo_name: Repo):
|
async def _repo_del(self, ctx, repo_name: Repo):
|
||||||
@@ -278,8 +278,7 @@ class Downloader:
|
|||||||
"""
|
"""
|
||||||
Command group for managing installable Cogs.
|
Command group for managing installable Cogs.
|
||||||
"""
|
"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@cog.command(name="install")
|
@cog.command(name="install")
|
||||||
async def _cog_install(self, ctx, repo_name: Repo, cog_name: str):
|
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
|
cog = discord.utils.get(repo_name.available_cogs, name=cog_name) # type: Installable
|
||||||
if cog is None:
|
if cog is None:
|
||||||
await ctx.send(
|
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
|
cog_name, repo_name.name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
elif cog.min_python_version > sys.version_info:
|
elif cog.min_python_version > sys.version_info:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_("This cog requires at least python version {}, aborting install.").format(
|
||||||
"This cog requires at least python version {}, aborting install.".format(
|
|
||||||
".".join([str(n) for n in cog.min_python_version])
|
".".join([str(n) for n in cog.min_python_version])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not await repo_name.install_requirements(cog, self.LIB_PATH):
|
if not await repo_name.install_requirements(cog, self.LIB_PATH):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Failed to install the required libraries for" " `{}`: `{}`").format(
|
_("Failed to install the required libraries for `{}`: `{}`").format(
|
||||||
cog.name, cog.requirements
|
cog.name, cog.requirements
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -320,7 +317,7 @@ class Downloader:
|
|||||||
|
|
||||||
await ctx.send(_("`{}` cog successfully installed.").format(cog_name))
|
await ctx.send(_("`{}` cog successfully installed.").format(cog_name))
|
||||||
if cog.install_msg is not None:
|
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")
|
@cog.command(name="uninstall")
|
||||||
async def _cog_uninstall(self, ctx, cog_name: InstalledCog):
|
async def _cog_uninstall(self, ctx, cog_name: InstalledCog):
|
||||||
@@ -381,11 +378,25 @@ class Downloader:
|
|||||||
"""
|
"""
|
||||||
Lists all available cogs from a single repo.
|
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 = repo_name.available_cogs
|
||||||
cogs = _("Available Cogs:\n") + "\n".join(
|
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):
|
for page in pagify(cogs, ["\n"], shorten_by=16):
|
||||||
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
||||||
|
|
||||||
@@ -401,7 +412,9 @@ class Downloader:
|
|||||||
)
|
)
|
||||||
return
|
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))
|
await ctx.send(box(msg))
|
||||||
|
|
||||||
async def is_installed(
|
async def is_installed(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class DownloaderException(Exception):
|
|||||||
"""
|
"""
|
||||||
Base class for Downloader exceptions.
|
Base class for Downloader exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ class InvalidRepoName(DownloaderException):
|
|||||||
Throw when a repo name is invalid. Check
|
Throw when a repo name is invalid. Check
|
||||||
the message for a more detailed reason.
|
the message for a more detailed reason.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ class ExistingGitRepo(DownloaderException):
|
|||||||
Thrown when trying to clone into a folder where a
|
Thrown when trying to clone into a folder where a
|
||||||
git repo already exists.
|
git repo already exists.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ class MissingGitRepo(DownloaderException):
|
|||||||
Thrown when a git repo is expected to exist but
|
Thrown when a git repo is expected to exist but
|
||||||
does not.
|
does not.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -54,6 +58,7 @@ class CloningError(GitException):
|
|||||||
"""
|
"""
|
||||||
Thrown when git clone returns a non zero exit code.
|
Thrown when git clone returns a non zero exit code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -62,6 +67,7 @@ class CurrentHashError(GitException):
|
|||||||
Thrown when git returns a non zero exit code attempting
|
Thrown when git returns a non zero exit code attempting
|
||||||
to determine the current commit hash.
|
to determine the current commit hash.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -70,6 +76,7 @@ class HardResetError(GitException):
|
|||||||
Thrown when there is an issue trying to execute a hard reset
|
Thrown when there is an issue trying to execute a hard reset
|
||||||
(usually prior to a repo update).
|
(usually prior to a repo update).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -77,6 +84,7 @@ class UpdateError(GitException):
|
|||||||
"""
|
"""
|
||||||
Thrown when git pull returns a non zero error code.
|
Thrown when git pull returns a non zero error code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -84,6 +92,7 @@ class GitDiffError(GitException):
|
|||||||
"""
|
"""
|
||||||
Thrown when a git diff fails.
|
Thrown when a git diff fails.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -91,4 +100,5 @@ class PipError(DownloaderException):
|
|||||||
"""
|
"""
|
||||||
Thrown when pip returns a non-zero return code.
|
Thrown when pip returns a non-zero return code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import distutils.dir_util
|
|||||||
import shutil
|
import shutil
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
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 .log import log
|
||||||
from .json_mixins import RepoJSONMixin
|
from .json_mixins import RepoJSONMixin
|
||||||
|
|
||||||
@@ -76,6 +75,7 @@ class Installable(RepoJSONMixin):
|
|||||||
self.bot_version = (3, 0, 0)
|
self.bot_version = (3, 0, 0)
|
||||||
self.min_python_version = (3, 5, 1)
|
self.min_python_version = (3, 5, 1)
|
||||||
self.hidden = False
|
self.hidden = False
|
||||||
|
self.disabled = False
|
||||||
self.required_cogs = {} # Cog name -> repo URL
|
self.required_cogs = {} # Cog name -> repo URL
|
||||||
self.requirements = ()
|
self.requirements = ()
|
||||||
self.tags = ()
|
self.tags = ()
|
||||||
@@ -117,7 +117,7 @@ class Installable(RepoJSONMixin):
|
|||||||
try:
|
try:
|
||||||
copy_func(src=str(self._location), dst=str(target_dir / self._location.stem))
|
copy_func(src=str(self._location), dst=str(target_dir / self._location.stem))
|
||||||
except:
|
except:
|
||||||
log.exception("Error occurred when copying path:" " {}".format(self._location))
|
log.exception("Error occurred when copying path: {}".format(self._location))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -146,9 +146,7 @@ class Installable(RepoJSONMixin):
|
|||||||
info = json.load(f)
|
info = json.load(f)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
info = {}
|
info = {}
|
||||||
log.exception(
|
log.exception("Invalid JSON information file at path: {}".format(info_file_path))
|
||||||
"Invalid JSON information file at path:" " {}".format(info_file_path)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self._info = info
|
self._info = info
|
||||||
|
|
||||||
@@ -176,6 +174,12 @@ class Installable(RepoJSONMixin):
|
|||||||
hidden = False
|
hidden = False
|
||||||
self.hidden = hidden
|
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.required_cogs = info.get("required_cogs", {})
|
||||||
|
|
||||||
self.requirements = info.get("requirements", ())
|
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 functools
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import shutil
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import run as sp_run, PIPE
|
from subprocess import run as sp_run, PIPE
|
||||||
from sys import executable
|
from sys import executable
|
||||||
from typing import Tuple, MutableMapping, Union
|
from typing import Tuple, MutableMapping, Union
|
||||||
|
|
||||||
from discord.ext import commands
|
from redbot.core import data_manager, commands
|
||||||
|
|
||||||
from redbot.core import Config
|
|
||||||
from redbot.core import data_manager
|
|
||||||
from redbot.core.utils import safe_delete
|
from redbot.core.utils import safe_delete
|
||||||
from .errors import *
|
from .errors import *
|
||||||
from .installable import Installable, InstallableType
|
from .installable import Installable, InstallableType
|
||||||
@@ -27,10 +23,8 @@ class Repo(RepoJSONMixin):
|
|||||||
GIT_LATEST_COMMIT = "git -C {path} rev-parse {branch}"
|
GIT_LATEST_COMMIT = "git -C {path} rev-parse {branch}"
|
||||||
GIT_HARD_RESET = "git -C {path} reset --hard origin/{branch} -q"
|
GIT_HARD_RESET = "git -C {path} reset --hard origin/{branch} -q"
|
||||||
GIT_PULL = "git -C {path} pull -q --ff-only"
|
GIT_PULL = "git -C {path} pull -q --ff-only"
|
||||||
GIT_DIFF_FILE_STATUS = (
|
GIT_DIFF_FILE_STATUS = "git -C {path} diff --no-commit-id --name-status {old_hash} {new_hash}"
|
||||||
"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_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"
|
GIT_DISCOVER_REMOTE_URL = "git -C {path} config --get remote.origin.url"
|
||||||
|
|
||||||
PIP_INSTALL = "{python} -m pip install -U -t {target_dir} {reqs}"
|
PIP_INSTALL = "{python} -m pip install -U -t {target_dir} {reqs}"
|
||||||
@@ -98,7 +92,7 @@ class Repo(RepoJSONMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
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")
|
stdout = p.stdout.strip().decode().split("\n")
|
||||||
|
|
||||||
@@ -222,7 +216,7 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise GitException(
|
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()
|
return p.stdout.decode().strip()
|
||||||
@@ -472,7 +466,7 @@ class Repo(RepoJSONMixin):
|
|||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
return tuple(
|
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
|
@property
|
||||||
@@ -495,7 +489,6 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
|
|
||||||
class RepoManager:
|
class RepoManager:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self._repos = {}
|
self._repos = {}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import discord
|
|||||||
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
|
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
|
||||||
from redbot.core import Config, bank, commands
|
from redbot.core import Config, bank, commands
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
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
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
@@ -74,7 +75,6 @@ SLOT_PAYOUTS_MSG = _(
|
|||||||
|
|
||||||
|
|
||||||
def guild_only_check():
|
def guild_only_check():
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
return True
|
return True
|
||||||
@@ -87,7 +87,6 @@ def guild_only_check():
|
|||||||
|
|
||||||
|
|
||||||
class SetParser:
|
class SetParser:
|
||||||
|
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
allowed = ("+", "-")
|
allowed = ("+", "-")
|
||||||
self.sum = int(argument)
|
self.sum = int(argument)
|
||||||
@@ -139,11 +138,11 @@ class Economy:
|
|||||||
self.config.register_role(**self.default_role_settings)
|
self.config.register_role(**self.default_role_settings)
|
||||||
self.slot_register = defaultdict(dict)
|
self.slot_register = defaultdict(dict)
|
||||||
|
|
||||||
|
@guild_only_check()
|
||||||
@commands.group(name="bank")
|
@commands.group(name="bank")
|
||||||
async def _bank(self, ctx: commands.Context):
|
async def _bank(self, ctx: commands.Context):
|
||||||
"""Bank operations"""
|
"""Bank operations"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@_bank.command()
|
@_bank.command()
|
||||||
async def balance(self, ctx: commands.Context, user: discord.Member = None):
|
async def balance(self, ctx: commands.Context, user: discord.Member = None):
|
||||||
@@ -212,7 +211,6 @@ class Economy:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@_bank.command()
|
@_bank.command()
|
||||||
@guild_only_check()
|
|
||||||
@check_global_setting_guildowner()
|
@check_global_setting_guildowner()
|
||||||
async def reset(self, ctx, confirmation: bool = False):
|
async def reset(self, ctx, confirmation: bool = False):
|
||||||
"""Deletes bank accounts"""
|
"""Deletes bank accounts"""
|
||||||
@@ -228,13 +226,13 @@ class Economy:
|
|||||||
else:
|
else:
|
||||||
await bank.wipe_bank()
|
await bank.wipe_bank()
|
||||||
await ctx.send(
|
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"
|
self.bot.user.name if await bank.is_global() else "this server"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
|
@commands.command()
|
||||||
async def payday(self, ctx: commands.Context):
|
async def payday(self, ctx: commands.Context):
|
||||||
"""Get some free currency"""
|
"""Get some free currency"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
@@ -254,7 +252,7 @@ class Economy:
|
|||||||
_(
|
_(
|
||||||
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
||||||
"You currently have {3} {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(
|
).format(
|
||||||
author,
|
author,
|
||||||
credits_name,
|
credits_name,
|
||||||
@@ -267,7 +265,7 @@ class Economy:
|
|||||||
else:
|
else:
|
||||||
dtime = self.display_time(next_payday - cur_time)
|
dtime = self.display_time(next_payday - cur_time)
|
||||||
await ctx.send(
|
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
|
author.mention, dtime
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -301,7 +299,7 @@ class Economy:
|
|||||||
else:
|
else:
|
||||||
dtime = self.display_time(next_payday - cur_time)
|
dtime = self.display_time(next_payday - cur_time)
|
||||||
await ctx.send(
|
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
|
author.mention, dtime
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -312,8 +310,8 @@ class Economy:
|
|||||||
"""Prints out the leaderboard
|
"""Prints out the leaderboard
|
||||||
|
|
||||||
Defaults to top 10"""
|
Defaults to top 10"""
|
||||||
# Originally coded by Airenkun - edited by irdumb, rewritten by Palm__ for v3
|
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
author = ctx.author
|
||||||
if top < 1:
|
if top < 1:
|
||||||
top = 10
|
top = 10
|
||||||
if (
|
if (
|
||||||
@@ -323,25 +321,25 @@ class Economy:
|
|||||||
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
|
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
|
||||||
if len(bank_sorted) < top:
|
if len(bank_sorted) < top:
|
||||||
top = len(bank_sorted)
|
top = len(bank_sorted)
|
||||||
highscore = ""
|
header = f"{f'#':4}{f'Name':36}{f'Score':2}\n"
|
||||||
for pos, acc in enumerate(bank_sorted, 1):
|
highscores = [
|
||||||
pos = pos
|
(
|
||||||
poswidth = 2
|
f"{f'{pos}.': <{3 if pos < 10 else 2}} {acc[1]['name']: <{35}s} "
|
||||||
name = acc[1]["name"]
|
f"{acc[1]['balance']: >{2 if pos < 10 else 1}}\n"
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
if highscore != "":
|
if acc[0] != author.id
|
||||||
for page in pagify(highscore, shorten_by=12):
|
else (
|
||||||
await ctx.send(box(page, lang="py"))
|
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:
|
else:
|
||||||
await ctx.send(_("There are no accounts in the bank."))
|
await ctx.send(_("There are no accounts in the bank."))
|
||||||
|
|
||||||
@@ -427,7 +425,7 @@ class Economy:
|
|||||||
now = then - bid + pay
|
now = then - bid + pay
|
||||||
await bank.set_balance(author, now)
|
await bank.set_balance(author, now)
|
||||||
await channel.send(
|
await channel.send(
|
||||||
_("{}\n{} {}\n\nYour bid: {}\n{} → {}!" "").format(
|
_("{}\n{} {}\n\nYour bid: {}\n{} → {}!").format(
|
||||||
slot, author.mention, payout["phrase"], bid, then, now
|
slot, author.mention, payout["phrase"], bid, then, now
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -436,7 +434,7 @@ class Economy:
|
|||||||
await bank.withdraw_credits(author, bid)
|
await bank.withdraw_credits(author, bid)
|
||||||
now = then - bid
|
now = then - bid
|
||||||
await channel.send(
|
await channel.send(
|
||||||
_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!" "").format(
|
_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!").format(
|
||||||
slot, author.mention, bid, then, now
|
slot, author.mention, bid, then, now
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -448,7 +446,6 @@ class Economy:
|
|||||||
"""Changes economy module settings"""
|
"""Changes economy module settings"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help()
|
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
slot_min = await self.config.SLOT_MIN()
|
slot_min = await self.config.SLOT_MIN()
|
||||||
slot_max = await self.config.SLOT_MAX()
|
slot_max = await self.config.SLOT_MAX()
|
||||||
@@ -497,7 +494,7 @@ class Economy:
|
|||||||
"""Maximum slot machine bid"""
|
"""Maximum slot machine bid"""
|
||||||
slot_min = await self.config.SLOT_MIN()
|
slot_min = await self.config.SLOT_MIN()
|
||||||
if bid < 1 or bid < 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
|
return
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
@@ -526,9 +523,7 @@ class Economy:
|
|||||||
else:
|
else:
|
||||||
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Value modified. At least {} seconds must pass " "between each payday.").format(
|
_("Value modified. At least {} seconds must pass between each payday.").format(seconds)
|
||||||
seconds
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@economyset.command()
|
@economyset.command()
|
||||||
@@ -543,7 +538,7 @@ class Economy:
|
|||||||
await self.config.PAYDAY_CREDITS.set(creds)
|
await self.config.PAYDAY_CREDITS.set(creds)
|
||||||
else:
|
else:
|
||||||
await self.config.guild(guild).PAYDAY_CREDITS.set(creds)
|
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()
|
@economyset.command()
|
||||||
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
||||||
@@ -555,7 +550,7 @@ class Economy:
|
|||||||
else:
|
else:
|
||||||
await self.config.role(role).PAYDAY_CREDITS.set(creds)
|
await self.config.role(role).PAYDAY_CREDITS.set(creds)
|
||||||
await ctx.send(
|
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
|
creds, credits_name, role.name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -569,7 +564,7 @@ class Economy:
|
|||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
await bank.set_default_balance(creds, guild)
|
await bank.set_default_balance(creds, guild)
|
||||||
await ctx.send(
|
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?
|
# 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
|
Using this command with no subcommands will send
|
||||||
the list of the server's filtered words."""
|
the list of the server's filtered words."""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help()
|
|
||||||
server = ctx.guild
|
server = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
word_list = await self.settings.guild(server).filter()
|
word_list = await self.settings.guild(server).filter()
|
||||||
@@ -124,36 +123,35 @@ class Filter:
|
|||||||
|
|
||||||
@_filter.command(name="names")
|
@_filter.command(name="names")
|
||||||
async def filter_names(self, ctx: commands.Context):
|
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
|
This is disabled by default
|
||||||
"""
|
"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
current_setting = await self.settings.guild(guild).filter_names()
|
current_setting = await self.settings.guild(guild).filter_names()
|
||||||
await self.settings.guild(guild).filter_names.set(not current_setting)
|
await self.settings.guild(guild).filter_names.set(not current_setting)
|
||||||
if current_setting:
|
if current_setting:
|
||||||
await ctx.send(
|
await ctx.send(_("Names and nicknames will no longer be checked against the filter."))
|
||||||
_("Names and nicknames will no longer be " "checked against the filter")
|
|
||||||
)
|
|
||||||
else:
|
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")
|
@_filter.command(name="defaultname")
|
||||||
async def filter_default_name(self, ctx: commands.Context, name: str):
|
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
|
Note that this has no effect if filtering names is disabled
|
||||||
|
|
||||||
The default name used is John Doe
|
The default name used is John Doe
|
||||||
"""
|
"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
await self.settings.guild(guild).filter_default_name.set(name)
|
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")
|
@_filter.command(name="ban")
|
||||||
async def filter_ban(self, ctx: commands.Context, count: int, timeframe: int):
|
async def filter_ban(self, ctx: commands.Context, count: int, timeframe: int):
|
||||||
"""
|
"""Autobans if the specified number of messages are filtered in the timeframe
|
||||||
Sets up an autoban if the specified number of messages are
|
|
||||||
filtered in the specified amount of time (in seconds)
|
The timeframe is represented by seconds.
|
||||||
"""
|
"""
|
||||||
if (count <= 0) != (timeframe <= 0):
|
if (count <= 0) != (timeframe <= 0):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
@@ -223,7 +221,7 @@ class Filter:
|
|||||||
user_count >= filter_count
|
user_count >= filter_count
|
||||||
and message.created_at.timestamp() < next_reset_time
|
and message.created_at.timestamp() < next_reset_time
|
||||||
):
|
):
|
||||||
reason = "Autoban (too many filtered messages)"
|
reason = "Autoban (too many filtered messages.)"
|
||||||
try:
|
try:
|
||||||
await server.ban(author, reason=reason)
|
await server.ban(author, reason=reason)
|
||||||
except:
|
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 enum import Enum
|
||||||
from random import randint, choice
|
from random import randint, choice
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
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
|
from redbot.core.utils.chat_formatting import escape, italics, pagify
|
||||||
|
|
||||||
_ = Translator("General", __file__)
|
_ = Translator("General", __file__)
|
||||||
@@ -21,7 +20,6 @@ class RPS(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class RPSParser:
|
class RPSParser:
|
||||||
|
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
argument = argument.lower()
|
argument = argument.lower()
|
||||||
if argument == "rock":
|
if argument == "rock":
|
||||||
@@ -98,7 +96,7 @@ class General:
|
|||||||
msg = ""
|
msg = ""
|
||||||
if user.id == ctx.bot.user.id:
|
if user.id == ctx.bot.user.id:
|
||||||
user = ctx.author
|
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"
|
char = "abcdefghijklmnopqrstuvwxyz"
|
||||||
tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz"
|
tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz"
|
||||||
table = str.maketrans(char, tran)
|
table = str.maketrans(char, tran)
|
||||||
@@ -165,7 +163,9 @@ class General:
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
async def lmgtfy(self, ctx, *, search_terms: str):
|
async def lmgtfy(self, ctx, *, search_terms: str):
|
||||||
"""Creates a lmgtfy link"""
|
"""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))
|
await ctx.send("https://lmgtfy.com/?q={}".format(search_terms))
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
@commands.command(hidden=True)
|
||||||
@@ -192,18 +192,12 @@ class General:
|
|||||||
async def serverinfo(self, ctx):
|
async def serverinfo(self, ctx):
|
||||||
"""Shows server's informations"""
|
"""Shows server's informations"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
online = len(
|
online = len([m.status for m in guild.members if m.status != discord.Status.offline])
|
||||||
[
|
|
||||||
m.status
|
|
||||||
for m in guild.members
|
|
||||||
if m.status == discord.Status.online or m.status == discord.Status.idle
|
|
||||||
]
|
|
||||||
)
|
|
||||||
total_users = len(guild.members)
|
total_users = len(guild.members)
|
||||||
text_channels = len(guild.text_channels)
|
text_channels = len(guild.text_channels)
|
||||||
voice_channels = len(guild.voice_channels)
|
voice_channels = len(guild.voice_channels)
|
||||||
passed = (ctx.message.created_at - guild.created_at).days
|
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
|
guild.created_at.strftime("%d %b %Y %H:%M"), passed
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -228,52 +222,93 @@ class General:
|
|||||||
try:
|
try:
|
||||||
await ctx.send(embed=data)
|
await ctx.send(embed=data)
|
||||||
except discord.HTTPException:
|
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()
|
@commands.command()
|
||||||
async def urban(self, ctx, *, search_terms: str, definition_number: int = 1):
|
async def urban(self, ctx, *, word):
|
||||||
"""Urban Dictionary search
|
"""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:
|
try:
|
||||||
if len(search_terms) > 1:
|
url = "https://api.urbandictionary.com/v0/define?term=" + str(word).lower()
|
||||||
pos = int(search_terms[-1]) - 1
|
|
||||||
search_terms = search_terms[:-1]
|
headers = {"content-type": "application/json"}
|
||||||
else:
|
|
||||||
pos = 0
|
|
||||||
if pos not in range(0, 11): # API only provides the
|
|
||||||
pos = 0 # top 10 definitions
|
|
||||||
except ValueError:
|
|
||||||
pos = 0
|
|
||||||
|
|
||||||
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 aiohttp.ClientSession() as session:
|
||||||
async with session.get(url, params=search_terms) as r:
|
async with session.get(url, headers=headers) as response:
|
||||||
result = await r.json()
|
data = await response.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))
|
|
||||||
except:
|
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()
|
|
||||||
@@ -13,6 +13,7 @@ GIPHY_API_KEY = "dc6zaTOxFJmzC"
|
|||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Image:
|
class Image:
|
||||||
"""Image related commands."""
|
"""Image related commands."""
|
||||||
|
|
||||||
default_global = {"imgur_client_id": None}
|
default_global = {"imgur_client_id": None}
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
@@ -23,7 +24,7 @@ class Image:
|
|||||||
self.imgur_base_url = "https://api.imgur.com/3/"
|
self.imgur_base_url = "https://api.imgur.com/3/"
|
||||||
|
|
||||||
def __unload(self):
|
def __unload(self):
|
||||||
self.session.close()
|
self.session.detach()
|
||||||
|
|
||||||
@commands.group(name="imgur")
|
@commands.group(name="imgur")
|
||||||
async def _imgur(self, ctx):
|
async def _imgur(self, ctx):
|
||||||
@@ -31,8 +32,7 @@ class Image:
|
|||||||
|
|
||||||
Make sure to set the client ID using
|
Make sure to set the client ID using
|
||||||
[p]imgurcreds"""
|
[p]imgurcreds"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@_imgur.command(name="search")
|
@_imgur.command(name="search")
|
||||||
async def imgur_search(self, ctx, *, term: str):
|
async def imgur_search(self, ctx, *, term: str):
|
||||||
@@ -42,7 +42,7 @@ class Image:
|
|||||||
imgur_client_id = await self.settings.imgur_client_id()
|
imgur_client_id = await self.settings.imgur_client_id()
|
||||||
if not imgur_client_id:
|
if not imgur_client_id:
|
||||||
await ctx.send(
|
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)
|
"`{}imgurcreds`".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -54,7 +54,7 @@ class Image:
|
|||||||
if data["success"]:
|
if data["success"]:
|
||||||
results = data["data"]
|
results = data["data"]
|
||||||
if not results:
|
if not results:
|
||||||
await ctx.send(_("Your search returned no results"))
|
await ctx.send(_("Your search returned no results."))
|
||||||
return
|
return
|
||||||
shuffle(results)
|
shuffle(results)
|
||||||
msg = _("Search results...\n")
|
msg = _("Search results...\n")
|
||||||
@@ -63,7 +63,7 @@ class Image:
|
|||||||
msg += "\n"
|
msg += "\n"
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
else:
|
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")
|
@_imgur.command(name="subreddit")
|
||||||
async def imgur_subreddit(
|
async def imgur_subreddit(
|
||||||
@@ -91,7 +91,7 @@ class Image:
|
|||||||
imgur_client_id = await self.settings.imgur_client_id()
|
imgur_client_id = await self.settings.imgur_client_id()
|
||||||
if not imgur_client_id:
|
if not imgur_client_id:
|
||||||
await ctx.send(
|
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)
|
"`{}imgurcreds`".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -116,12 +116,13 @@ class Image:
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("No results found."))
|
await ctx.send(_("No results found."))
|
||||||
else:
|
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()
|
@checks.is_owner()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def imgurcreds(self, ctx, imgur_client_id: str):
|
async def imgurcreds(self, ctx, imgur_client_id: str):
|
||||||
"""Sets the imgur client id
|
"""Sets the imgur client id
|
||||||
|
|
||||||
You will need an account on Imgur to get this
|
You will need an account on Imgur to get this
|
||||||
|
|
||||||
You can get these by visiting https://api.imgur.com/oauth2/addclient
|
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'
|
set the authorization callback url to 'https://localhost'
|
||||||
leave the app website blank, enter a valid email address, and
|
leave the app website blank, enter a valid email address, and
|
||||||
enter a description. Check the box for the captcha, then click Next.
|
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 self.settings.imgur_client_id.set(imgur_client_id)
|
||||||
await ctx.send(_("Set the imgur client id!"))
|
await ctx.send(_("Set the imgur client id!"))
|
||||||
|
|
||||||
@@ -143,7 +144,7 @@ class Image:
|
|||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
return
|
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
|
GIPHY_API_KEY, keywords
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ class Image:
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("No results found."))
|
await ctx.send(_("No results found."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Error contacting the API"))
|
await ctx.send(_("Error contacting the API."))
|
||||||
|
|
||||||
@commands.command(pass_context=True, no_pm=True)
|
@commands.command(pass_context=True, no_pm=True)
|
||||||
async def gifr(self, ctx, *keywords):
|
async def gifr(self, ctx, *keywords):
|
||||||
@@ -166,7 +167,7 @@ class Image:
|
|||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
return
|
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
|
GIPHY_API_KEY, keywords
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -178,4 +179,4 @@ class Image:
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("No results found."))
|
await ctx.send(_("No results found."))
|
||||||
else:
|
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
|
import discord
|
||||||
|
|
||||||
|
|
||||||
def mod_or_voice_permissions(**perms):
|
def mod_or_voice_permissions(**perms):
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@@ -31,7 +30,6 @@ def mod_or_voice_permissions(**perms):
|
|||||||
|
|
||||||
|
|
||||||
def admin_or_voice_permissions(**perms):
|
def admin_or_voice_permissions(**perms):
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@@ -54,7 +52,6 @@ def admin_or_voice_permissions(**perms):
|
|||||||
|
|
||||||
|
|
||||||
def bot_has_voice_permissions(**perms):
|
def bot_has_voice_permissions(**perms):
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
for vc in guild.voice_channels:
|
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()
|
|
||||||
@@ -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 redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, get_audit_reason
|
||||||
from .log import log
|
from .log import log
|
||||||
|
|
||||||
|
from redbot.core.utils.common_filters import filter_invites
|
||||||
|
|
||||||
_ = Translator("Mod", __file__)
|
_ = Translator("Mod", __file__)
|
||||||
|
|
||||||
|
|
||||||
@@ -168,8 +170,6 @@ class Mod:
|
|||||||
"""Manages server administration settings."""
|
"""Manages server administration settings."""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
# Display current settings
|
# Display current settings
|
||||||
delete_repeats = await self.settings.guild(guild).delete_repeats()
|
delete_repeats = await self.settings.guild(guild).delete_repeats()
|
||||||
ban_mention_spam = await self.settings.guild(guild).ban_mention_spam()
|
ban_mention_spam = await self.settings.guild(guild).ban_mention_spam()
|
||||||
@@ -199,12 +199,12 @@ class Mod:
|
|||||||
if not toggled:
|
if not toggled:
|
||||||
await self.settings.guild(guild).respect_hierarchy.set(True)
|
await self.settings.guild(guild).respect_hierarchy.set(True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Role hierarchy will be checked when " "moderation commands are issued.")
|
_("Role hierarchy will be checked when moderation commands are issued.")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self.settings.guild(guild).respect_hierarchy.set(False)
|
await self.settings.guild(guild).respect_hierarchy.set(False)
|
||||||
await ctx.send(
|
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()
|
@modset.command()
|
||||||
@@ -241,7 +241,7 @@ class Mod:
|
|||||||
cur_setting = await self.settings.guild(guild).delete_repeats()
|
cur_setting = await self.settings.guild(guild).delete_repeats()
|
||||||
if not cur_setting:
|
if not cur_setting:
|
||||||
await self.settings.guild(guild).delete_repeats.set(True)
|
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:
|
else:
|
||||||
await self.settings.guild(guild).delete_repeats.set(False)
|
await self.settings.guild(guild).delete_repeats.set(False)
|
||||||
await ctx.send(_("Repeated messages will be ignored."))
|
await ctx.send(_("Repeated messages will be ignored."))
|
||||||
@@ -304,7 +304,7 @@ class Mod:
|
|||||||
|
|
||||||
if author == user:
|
if author == user:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||||
@@ -316,6 +316,9 @@ class Mod:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
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)
|
audit_reason = get_audit_reason(author, reason)
|
||||||
try:
|
try:
|
||||||
await guild.kick(user, reason=audit_reason)
|
await guild.kick(user, reason=audit_reason)
|
||||||
@@ -357,7 +360,7 @@ class Mod:
|
|||||||
|
|
||||||
if author == user:
|
if author == user:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||||
@@ -369,6 +372,9 @@ class Mod:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
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:
|
||||||
if days.isdigit():
|
if days.isdigit():
|
||||||
@@ -451,15 +457,15 @@ class Mod:
|
|||||||
self.ban_queue.append(queue_entry)
|
self.ban_queue.append(queue_entry)
|
||||||
try:
|
try:
|
||||||
await guild.ban(user, reason=audit_reason)
|
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:
|
except discord.NotFound:
|
||||||
self.ban_queue.remove(queue_entry)
|
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:
|
except discord.Forbidden:
|
||||||
self.ban_queue.remove(queue_entry)
|
self.ban_queue.remove(queue_entry)
|
||||||
await ctx.send(_("I lack the permissions to do this."))
|
await ctx.send(_("I lack the permissions to do this."))
|
||||||
else:
|
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)
|
user_info = await self.bot.get_user_info(user_id)
|
||||||
try:
|
try:
|
||||||
@@ -547,7 +553,7 @@ class Mod:
|
|||||||
|
|
||||||
if author == user:
|
if author == user:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||||
@@ -624,7 +630,6 @@ class Mod:
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@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):
|
async def unban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
|
||||||
"""Unbans the target user.
|
"""Unbans the target user.
|
||||||
|
|
||||||
@@ -632,13 +637,17 @@ class Mod:
|
|||||||
1. Copy it from the mod log case (if one was created), or
|
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-
|
2. enable developer mode, go to Bans in this server's settings, right-
|
||||||
click the user and select 'Copy ID'."""
|
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
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
user = await self.bot.get_user_info(user_id)
|
user = await self.bot.get_user_info(user_id)
|
||||||
if not user:
|
if not user:
|
||||||
await ctx.send(_("Couldn't find a user with that ID!"))
|
await ctx.send(_("Couldn't find a user with that ID!"))
|
||||||
return
|
return
|
||||||
reason = get_audit_reason(ctx.author, reason)
|
audit_reason = get_audit_reason(ctx.author, reason)
|
||||||
bans = await guild.bans()
|
bans = await guild.bans()
|
||||||
bans = [be.user for be in bans]
|
bans = [be.user for be in bans]
|
||||||
if user not in bans:
|
if user not in bans:
|
||||||
@@ -647,7 +656,7 @@ class Mod:
|
|||||||
queue_entry = (guild.id, user.id)
|
queue_entry = (guild.id, user.id)
|
||||||
self.unban_queue.append(queue_entry)
|
self.unban_queue.append(queue_entry)
|
||||||
try:
|
try:
|
||||||
await guild.unban(user, reason=reason)
|
await guild.unban(user, reason=audit_reason)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
self.unban_queue.remove(queue_entry)
|
self.unban_queue.remove(queue_entry)
|
||||||
await ctx.send(_("Something went wrong while attempting to unban that user"))
|
await ctx.send(_("Something went wrong while attempting to unban that user"))
|
||||||
@@ -753,7 +762,7 @@ class Mod:
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("That user is already muted and deafened server-wide!"))
|
await ctx.send(_("That user is already muted and deafened server-wide!"))
|
||||||
return
|
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:
|
try:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
@@ -825,7 +834,7 @@ class Mod:
|
|||||||
await ctx.send("Done.")
|
await ctx.send("Done.")
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await ctx.send(
|
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()
|
@commands.group()
|
||||||
@@ -833,8 +842,7 @@ class Mod:
|
|||||||
@checks.mod_or_permissions(manage_channel=True)
|
@checks.mod_or_permissions(manage_channel=True)
|
||||||
async def mute(self, ctx: commands.Context):
|
async def mute(self, ctx: commands.Context):
|
||||||
"""Mutes user in the channel/server"""
|
"""Mutes user in the channel/server"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@mute.command(name="voice")
|
@mute.command(name="voice")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@@ -1002,8 +1010,7 @@ class Mod:
|
|||||||
"""Unmutes user in the channel/server
|
"""Unmutes user in the channel/server
|
||||||
|
|
||||||
Defaults to channel"""
|
Defaults to channel"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@unmute.command(name="voice")
|
@unmute.command(name="voice")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@@ -1168,7 +1175,6 @@ class Mod:
|
|||||||
async def ignore(self, ctx: commands.Context):
|
async def ignore(self, ctx: commands.Context):
|
||||||
"""Adds servers/channels to ignorelist"""
|
"""Adds servers/channels to ignorelist"""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help()
|
|
||||||
await ctx.send(await self.count_ignored())
|
await ctx.send(await self.count_ignored())
|
||||||
|
|
||||||
@ignore.command(name="channel")
|
@ignore.command(name="channel")
|
||||||
@@ -1185,7 +1191,7 @@ class Mod:
|
|||||||
await ctx.send(_("Channel already in ignore list."))
|
await ctx.send(_("Channel already in ignore list."))
|
||||||
|
|
||||||
@ignore.command(name="server", aliases=["guild"])
|
@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):
|
async def ignore_guild(self, ctx: commands.Context):
|
||||||
"""Ignores current server"""
|
"""Ignores current server"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@@ -1201,7 +1207,6 @@ class Mod:
|
|||||||
async def unignore(self, ctx: commands.Context):
|
async def unignore(self, ctx: commands.Context):
|
||||||
"""Removes servers/channels from ignorelist"""
|
"""Removes servers/channels from ignorelist"""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help()
|
|
||||||
await ctx.send(await self.count_ignored())
|
await ctx.send(await self.count_ignored())
|
||||||
|
|
||||||
@unignore.command(name="channel")
|
@unignore.command(name="channel")
|
||||||
@@ -1219,7 +1224,7 @@ class Mod:
|
|||||||
await ctx.send(_("That channel is not in the ignore list."))
|
await ctx.send(_("That channel is not in the ignore list."))
|
||||||
|
|
||||||
@unignore.command(name="server", aliases=["guild"])
|
@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):
|
async def unignore_guild(self, ctx: commands.Context):
|
||||||
"""Removes current guild from ignore list"""
|
"""Removes current guild from ignore list"""
|
||||||
guild = ctx.message.guild
|
guild = ctx.message.guild
|
||||||
@@ -1263,7 +1268,14 @@ class Mod:
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def userinfo(self, ctx, *, user: discord.Member = None):
|
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
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
@@ -1303,26 +1315,30 @@ class Mod:
|
|||||||
if roles:
|
if roles:
|
||||||
roles = ", ".join([x.name for x in roles])
|
roles = ", ".join([x.name for x in roles])
|
||||||
else:
|
else:
|
||||||
roles = _("None")
|
roles = None
|
||||||
|
|
||||||
data = discord.Embed(description=activity, colour=user.colour)
|
data = discord.Embed(description=activity, colour=user.colour)
|
||||||
data.add_field(name=_("Joined Discord on"), value=created_on)
|
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=_("Joined this server on"), value=joined_on)
|
||||||
|
if roles is not None:
|
||||||
data.add_field(name=_("Roles"), value=roles, inline=False)
|
data.add_field(name=_("Roles"), value=roles, inline=False)
|
||||||
if names:
|
if names:
|
||||||
data.add_field(name=_("Previous Names"), value=", ".join(names), inline=False)
|
val = filter_invites(", ".join(names))
|
||||||
|
data.add_field(name=_("Previous Names"), value=val, inline=False)
|
||||||
if nicks:
|
if nicks:
|
||||||
data.add_field(name=_("Previous Nicknames"), value=", ".join(nicks), inline=False)
|
val = filter_invites(", ".join(nicks))
|
||||||
|
data.add_field(name=_("Previous Nicknames"), value=val, inline=False)
|
||||||
if voice_state and voice_state.channel:
|
if voice_state and voice_state.channel:
|
||||||
data.add_field(
|
data.add_field(
|
||||||
name=_("Current voice channel"),
|
name=_("Current voice channel"),
|
||||||
value="{0.name} (ID {0.id})".format(voice_state.channel),
|
value="{0.name} (ID {0.id})".format(voice_state.channel),
|
||||||
inline=False,
|
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 = str(user)
|
||||||
name = " ~ ".join((name, user.nick)) if user.nick else name
|
name = " ~ ".join((name, user.nick)) if user.nick else name
|
||||||
|
name = filter_invites(name)
|
||||||
|
|
||||||
if user.avatar:
|
if user.avatar:
|
||||||
avatar = user.avatar_url
|
avatar = user.avatar_url
|
||||||
@@ -1335,7 +1351,7 @@ class Mod:
|
|||||||
try:
|
try:
|
||||||
await ctx.send(embed=data)
|
await ctx.send(embed=data)
|
||||||
except discord.HTTPException:
|
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()
|
@commands.command()
|
||||||
async def names(self, ctx: commands.Context, user: discord.Member):
|
async def names(self, ctx: commands.Context, user: discord.Member):
|
||||||
@@ -1355,15 +1371,15 @@ class Mod:
|
|||||||
if msg:
|
if msg:
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
else:
|
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):
|
async def get_names_and_nicks(self, user):
|
||||||
names = await self.settings.user(user).past_names()
|
names = await self.settings.user(user).past_names()
|
||||||
nicks = await self.settings.member(user).past_nicks()
|
nicks = await self.settings.member(user).past_nicks()
|
||||||
if names:
|
if names:
|
||||||
names = [escape(name, mass_mentions=True) for name in names]
|
names = [escape(name, mass_mentions=True) for name in names if name]
|
||||||
if nicks:
|
if nicks:
|
||||||
nicks = [escape(nick, mass_mentions=True) for nick in nicks]
|
nicks = [escape(nick, mass_mentions=True) for nick in nicks if nick]
|
||||||
return names, nicks
|
return names, nicks
|
||||||
|
|
||||||
async def check_tempban_expirations(self):
|
async def check_tempban_expirations(self):
|
||||||
@@ -1418,7 +1434,7 @@ class Mod:
|
|||||||
await guild.ban(author, reason="Mention spam (Autoban)")
|
await guild.ban(author, reason="Mention spam (Autoban)")
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
log.info(
|
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:
|
else:
|
||||||
try:
|
try:
|
||||||
@@ -1439,7 +1455,13 @@ class Mod:
|
|||||||
return True
|
return True
|
||||||
return False
|
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:
|
"""Currently used for:
|
||||||
* delete delay"""
|
* delete delay"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@@ -1595,18 +1617,22 @@ class Mod:
|
|||||||
if entry.target == target:
|
if entry.target == target:
|
||||||
return entry
|
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:
|
if before.name != after.name:
|
||||||
async with self.settings.user(before).past_names() as name_list:
|
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
|
# Ensure order is maintained without duplicates occuring
|
||||||
name_list.remove(after.nick)
|
name_list.remove(after.name)
|
||||||
name_list.append(after.nick)
|
name_list.append(after.name)
|
||||||
while len(name_list) > 20:
|
while len(name_list) > 20:
|
||||||
name_list.pop(0)
|
name_list.pop(0)
|
||||||
|
|
||||||
if before.nick != after.nick and after.nick is not None:
|
if before.nick != after.nick and after.nick is not None:
|
||||||
async with self.settings.member(before).past_nicks() as nick_list:
|
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:
|
if after.nick in nick_list:
|
||||||
nick_list.remove(after.nick)
|
nick_list.remove(after.nick)
|
||||||
nick_list.append(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)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def modlogset(self, ctx: commands.Context):
|
async def modlogset(self, ctx: commands.Context):
|
||||||
"""Settings for the mod log"""
|
"""Settings for the mod log"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@modlogset.command()
|
@modlogset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@@ -35,9 +34,7 @@ class ModLog:
|
|||||||
await ctx.send(_("Mod events will be sent to {}").format(channel.mention))
|
await ctx.send(_("Mod events will be sent to {}").format(channel.mention))
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I do not have permissions to " "send messages in {}!").format(
|
_("I do not have permissions to send messages in {}!").format(channel.mention)
|
||||||
channel.mention
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@@ -98,19 +95,29 @@ class ModLog:
|
|||||||
await ctx.send(_("That case does not exist for that server"))
|
await ctx.send(_("That case does not exist for that server"))
|
||||||
return
|
return
|
||||||
else:
|
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()
|
@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
|
"""Lets you specify a reason for mod-log's cases
|
||||||
|
|
||||||
Please note that you can only edit cases you are
|
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
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if not reason:
|
potential_case = reason.split()[0]
|
||||||
await ctx.send_help()
|
if potential_case.isdigit():
|
||||||
return
|
case = int(potential_case)
|
||||||
|
reason = reason.replace(potential_case, "")
|
||||||
|
else:
|
||||||
|
case = str(int(await modlog.get_next_case_number(guild)) - 1)
|
||||||
|
# latest case
|
||||||
try:
|
try:
|
||||||
case_before = await modlog.get_case(case, guild, self.bot)
|
case_before = await modlog.get_case(case, guild, self.bot)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from typing import Tuple
|
|||||||
|
|
||||||
|
|
||||||
class CogOrCommand(commands.Converter):
|
class CogOrCommand(commands.Converter):
|
||||||
|
|
||||||
async def convert(self, ctx: commands.Context, arg: str) -> Tuple[str]:
|
async def convert(self, ctx: commands.Context, arg: str) -> Tuple[str]:
|
||||||
ret = ctx.bot.get_cog(arg)
|
ret = ctx.bot.get_cog(arg)
|
||||||
if ret:
|
if ret:
|
||||||
@@ -12,15 +11,34 @@ class CogOrCommand(commands.Converter):
|
|||||||
if ret:
|
if ret:
|
||||||
return "commands", ret.qualified_name
|
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):
|
class RuleType(commands.Converter):
|
||||||
|
|
||||||
async def convert(self, ctx: commands.Context, arg: str) -> str:
|
async def convert(self, ctx: commands.Context, arg: str) -> str:
|
||||||
if arg.lower() in ("allow", "whitelist", "allowed"):
|
if arg.lower() in ("allow", "whitelist", "allowed"):
|
||||||
return "allow"
|
return "allow"
|
||||||
if arg.lower() in ("deny", "blacklist", "denied"):
|
if arg.lower() in ("deny", "blacklist", "denied"):
|
||||||
return "deny"
|
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)
|
||||||
|
)
|
||||||
|
|||||||
102
redbot/cogs/permissions/mass_resolution.py
Normal file
102
redbot/cogs/permissions/mass_resolution.py
Normal file
@@ -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 import checks
|
||||||
from redbot.core.config import Config
|
from redbot.core.config import Config
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
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 .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__)
|
_ = Translator("Permissions", __file__)
|
||||||
|
|
||||||
REACTS = {"\N{WHITE HEAVY CHECK MARK}": True, "\N{NEGATIVE SQUARED CROSS MARK}": False}
|
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(_)
|
@cog_i18n(_)
|
||||||
@@ -32,61 +35,34 @@ class Permissions:
|
|||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.config = Config.get_conf(self, identifier=78631113035100160, force_registration=True)
|
self.config = Config.get_conf(self, identifier=78631113035100160, force_registration=True)
|
||||||
self._before = []
|
|
||||||
self._after = []
|
|
||||||
self.config.register_global(owner_models={})
|
self.config.register_global(owner_models={})
|
||||||
self.config.register_guild(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:
|
allowed: list of commands
|
||||||
ctx: commands.Context
|
denied: list of commands
|
||||||
level: str
|
default: list of commands
|
||||||
|
|
||||||
and returning:
|
representing how permissions interacts with the
|
||||||
None: do not interfere
|
user, channel, guild, and (possibly) voice channel
|
||||||
True: command should be allowed even if they dont
|
for all commands on the bot (not just the one in the context object)
|
||||||
have role or perm requirements for the check
|
|
||||||
False: command should be blocked
|
|
||||||
|
|
||||||
before_or_after:
|
This mainly exists for use by the help formatter,
|
||||||
Should literally be a str equaling 'before' or 'after'
|
but others may find it useful
|
||||||
This should be based on if this should take priority
|
|
||||||
over set rules or not
|
|
||||||
|
|
||||||
3rd party cogs adding checks using this should only allow
|
Unlike the rest of the permission system, if other models are added later,
|
||||||
the owner to add checks before, and ensure only the owner
|
due to optimizations made for this, this needs to be adjusted accordingly
|
||||||
can add checks recieving the level 'owner'
|
|
||||||
|
|
||||||
3rd party cogs should keep a copy of of any checks they registered
|
This does not account for before and after permission hooks,
|
||||||
and deregister then on unload
|
these need to be checked seperately
|
||||||
"""
|
"""
|
||||||
|
return await mass_resolve(ctx=ctx, config=self.config)
|
||||||
|
|
||||||
if before_or_after == "before":
|
async def __global_check(self, ctx: commands.Context) -> bool:
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Yes, this is needed on top of hooking into checks.py
|
Yes, this is needed on top of hooking into checks.py
|
||||||
to ensure that unchecked commands can still be managed by permissions
|
to ensure that unchecked commands can still be managed by permissions
|
||||||
@@ -94,7 +70,7 @@ class Permissions:
|
|||||||
defering to check logic
|
defering to check logic
|
||||||
This works since all checks must be True to run
|
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:
|
if v is False:
|
||||||
return False
|
return False
|
||||||
@@ -109,7 +85,7 @@ class Permissions:
|
|||||||
ctx: `redbot.core.context.commands.Context`
|
ctx: `redbot.core.context.commands.Context`
|
||||||
The context of the command
|
The context of the command
|
||||||
level: `str`
|
level: `str`
|
||||||
One of 'owner', 'guildowner', 'admin', 'mod'
|
One of 'owner', 'guildowner', 'admin', 'mod', 'all'
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -119,25 +95,44 @@ class Permissions:
|
|||||||
"""
|
"""
|
||||||
if await ctx.bot.is_owner(ctx.author):
|
if await ctx.bot.is_owner(ctx.author):
|
||||||
return True
|
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)
|
override = await val_if_check_is_valid(check=check, ctx=ctx, level=level)
|
||||||
if override is not None:
|
if override is not None:
|
||||||
return override
|
return override
|
||||||
|
|
||||||
|
# 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]:
|
for model in self.resolution_order[level]:
|
||||||
|
if ctx.guild is None and model != "owner":
|
||||||
|
break
|
||||||
override_model = getattr(self, model + "_model", None)
|
override_model = getattr(self, model + "_model", None)
|
||||||
override = await override_model(ctx) if override_model else None
|
override = await override_model(ctx) if override_model else None
|
||||||
if override is not None:
|
if override is not None:
|
||||||
|
self.cache[cache_tup] = override
|
||||||
return 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)
|
override = await val_if_check_is_valid(check=check, ctx=ctx, level=level)
|
||||||
if override is not None:
|
if override is not None:
|
||||||
return override
|
return override
|
||||||
@@ -156,7 +151,8 @@ class Permissions:
|
|||||||
"""
|
"""
|
||||||
Handles guild level overrides
|
Handles guild level overrides
|
||||||
"""
|
"""
|
||||||
|
if ctx.guild is None:
|
||||||
|
return None
|
||||||
async with self.config.guild(ctx.guild).owner_models() as models:
|
async with self.config.guild(ctx.guild).owner_models() as models:
|
||||||
return resolve_models(ctx=ctx, models=models)
|
return resolve_models(ctx=ctx, models=models)
|
||||||
|
|
||||||
@@ -171,8 +167,7 @@ class Permissions:
|
|||||||
"""
|
"""
|
||||||
Permission management tools
|
Permission management tools
|
||||||
"""
|
"""
|
||||||
if ctx.invoked_subcommand is None:
|
pass
|
||||||
await ctx.send_help()
|
|
||||||
|
|
||||||
@permissions.command()
|
@permissions.command()
|
||||||
async def explain(self, ctx: commands.Context):
|
async def explain(self, ctx: commands.Context):
|
||||||
@@ -206,10 +201,10 @@ class Permissions:
|
|||||||
"\n"
|
"\n"
|
||||||
"1. Rules about a user.\n"
|
"1. Rules about a user.\n"
|
||||||
"2. Rules about the voice channel a user is in.\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 "
|
"4. Rules about a role the user has "
|
||||||
"(The highest role they have with a rule will be used)\n"
|
"(The highest role they have with a rule will be used).\n"
|
||||||
"5. Rules about the guild a user is in (Owner level only)"
|
"5. Rules about the guild a user is in (Owner level only)."
|
||||||
"\n\nFor more details, please read the official documentation."
|
"\n\nFor more details, please read the official documentation."
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -236,7 +231,9 @@ class Permissions:
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
testcontext = await self.bot.get_context(message, cls=commands.Context)
|
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:
|
except commands.CheckFailure:
|
||||||
can = False
|
can = False
|
||||||
|
|
||||||
@@ -254,15 +251,16 @@ class Permissions:
|
|||||||
Take a YAML file upload to set permissions from
|
Take a YAML file upload to set permissions from
|
||||||
"""
|
"""
|
||||||
if not ctx.message.attachments:
|
if not ctx.message.attachments:
|
||||||
return await ctx.send(_("You must upload a file"))
|
return await ctx.send(_("You must upload a file."))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await yamlset_acl(ctx, config=self.config.owner_models, update=False)
|
await yamlset_acl(ctx, config=self.config.owner_models, update=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return await ctx.send(_("Inalid syntax"))
|
return await ctx.send(_("Invalid syntax."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Rules set."))
|
await ctx.send(_("Rules set."))
|
||||||
|
self.invalidate_cache()
|
||||||
|
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@permissions.command(name="getglobalacl")
|
@permissions.command(name="getglobalacl")
|
||||||
@@ -280,15 +278,16 @@ class Permissions:
|
|||||||
Take a YAML file upload to set permissions from
|
Take a YAML file upload to set permissions from
|
||||||
"""
|
"""
|
||||||
if not ctx.message.attachments:
|
if not ctx.message.attachments:
|
||||||
return await ctx.send(_("You must upload a file"))
|
return await ctx.send(_("You must upload a file."))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await yamlset_acl(ctx, config=self.config.guild(ctx.guild).owner_models, update=False)
|
await yamlset_acl(ctx, config=self.config.guild(ctx.guild).owner_models, update=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return await ctx.send(_("Inalid syntax"))
|
return await ctx.send(_("Invalid syntax."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Rules set."))
|
await ctx.send(_("Rules set."))
|
||||||
|
self.invalidate_cache(ctx.guild.id)
|
||||||
|
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
@@ -309,15 +308,16 @@ class Permissions:
|
|||||||
Use this to not lose existing rules
|
Use this to not lose existing rules
|
||||||
"""
|
"""
|
||||||
if not ctx.message.attachments:
|
if not ctx.message.attachments:
|
||||||
return await ctx.send(_("You must upload a file"))
|
return await ctx.send(_("You must upload a file."))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await yamlset_acl(ctx, config=self.config.guild(ctx.guild).owner_models, update=True)
|
await yamlset_acl(ctx, config=self.config.guild(ctx.guild).owner_models, update=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return await ctx.send(_("Inalid syntax"))
|
return await ctx.send(_("Invalid syntax."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Rules set."))
|
await ctx.send(_("Rules set."))
|
||||||
|
self.invalidate_cache(ctx.guild.id)
|
||||||
|
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@permissions.command(name="updateglobalacl")
|
@permissions.command(name="updateglobalacl")
|
||||||
@@ -328,15 +328,16 @@ class Permissions:
|
|||||||
Use this to not lose existing rules
|
Use this to not lose existing rules
|
||||||
"""
|
"""
|
||||||
if not ctx.message.attachments:
|
if not ctx.message.attachments:
|
||||||
return await ctx.send(_("You must upload a file"))
|
return await ctx.send(_("You must upload a file."))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await yamlset_acl(ctx, config=self.config.owner_models, update=True)
|
await yamlset_acl(ctx, config=self.config.owner_models, update=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return await ctx.send(_("Inalid syntax"))
|
return await ctx.send(_("Invalid syntax."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Rules set."))
|
await ctx.send(_("Rules set."))
|
||||||
|
self.invalidate_cache()
|
||||||
|
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@permissions.command(name="addglobalrule")
|
@permissions.command(name="addglobalrule")
|
||||||
@@ -348,7 +349,7 @@ class Permissions:
|
|||||||
who_or_what: str,
|
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
|
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)
|
obj = self.find_object_uniquely(who_or_what)
|
||||||
if not obj:
|
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
|
model_type, type_name = cog_or_command
|
||||||
async with self.config.owner_models() as models:
|
async with self.config.owner_models() as models:
|
||||||
data = {k: v for k, v in models.items()}
|
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)
|
data[model_type][type_name][allow_or_deny].append(obj)
|
||||||
models.update(data)
|
models.update(data)
|
||||||
await ctx.send(_("Rule added."))
|
await ctx.send(_("Rule added."))
|
||||||
|
self.invalidate_cache(type_name, obj)
|
||||||
|
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
@@ -392,7 +394,7 @@ class Permissions:
|
|||||||
who_or_what: str,
|
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
|
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)
|
obj = self.find_object_uniquely(who_or_what)
|
||||||
if not obj:
|
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
|
model_type, type_name = cog_or_command
|
||||||
async with self.config.guild(ctx.guild).owner_models() as models:
|
async with self.config.guild(ctx.guild).owner_models() as models:
|
||||||
data = {k: v for k, v in models.items()}
|
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)
|
data[model_type][type_name][allow_or_deny].append(obj)
|
||||||
models.update(data)
|
models.update(data)
|
||||||
await ctx.send(_("Rule added."))
|
await ctx.send(_("Rule added."))
|
||||||
|
self.invalidate_cache(type_name, obj)
|
||||||
|
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@permissions.command(name="removeglobalrule")
|
@permissions.command(name="removeglobalrule")
|
||||||
@@ -450,7 +453,7 @@ class Permissions:
|
|||||||
"""
|
"""
|
||||||
obj = self.find_object_uniquely(who_or_what)
|
obj = self.find_object_uniquely(who_or_what)
|
||||||
if not obj:
|
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
|
model_type, type_name = cog_or_command
|
||||||
async with self.config.owner_models() as models:
|
async with self.config.owner_models() as models:
|
||||||
data = {k: v for k, v in models.items()}
|
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)
|
data[model_type][type_name][allow_or_deny].remove(obj)
|
||||||
models.update(data)
|
models.update(data)
|
||||||
await ctx.send(_("Rule removed."))
|
await ctx.send(_("Rule removed."))
|
||||||
|
self.invalidate_cache(obj, type_name)
|
||||||
|
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
@@ -494,7 +498,7 @@ class Permissions:
|
|||||||
"""
|
"""
|
||||||
obj = self.find_object_uniquely(who_or_what)
|
obj = self.find_object_uniquely(who_or_what)
|
||||||
if not obj:
|
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
|
model_type, type_name = cog_or_command
|
||||||
async with self.config.guild(ctx.guild).owner_models() as models:
|
async with self.config.guild(ctx.guild).owner_models() as models:
|
||||||
data = {k: v for k, v in models.items()}
|
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)
|
data[model_type][type_name][allow_or_deny].remove(obj)
|
||||||
models.update(data)
|
models.update(data)
|
||||||
await ctx.send(_("Rule removed."))
|
await ctx.send(_("Rule removed."))
|
||||||
|
self.invalidate_cache(obj, type_name)
|
||||||
|
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
@permissions.command(name="setdefaultguildrule")
|
@permissions.command(name="setdefaultguildrule")
|
||||||
async def set_default_guild_rule(
|
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
|
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, "clear": None}.get(allow_or_deny)
|
||||||
val_to_set = {"allow": True, "deny": False}.get(allow_or_deny)
|
|
||||||
else:
|
|
||||||
val_to_set = None
|
|
||||||
|
|
||||||
model_type, type_name = cog_or_command
|
model_type, type_name = cog_or_command
|
||||||
async with self.config.guild(ctx.guild).owner_models() as models:
|
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
|
data[model_type][type_name]["default"] = val_to_set
|
||||||
|
|
||||||
models.update(data)
|
models.update(data)
|
||||||
await ctx.send(_("Defualt set."))
|
await ctx.send(_("Default set."))
|
||||||
|
self.invalidate_cache(type_name)
|
||||||
|
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@permissions.command(name="setdefaultglobalrule")
|
@permissions.command(name="setdefaultglobalrule")
|
||||||
async def set_default_global_rule(
|
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
|
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
|
|
||||||
"""
|
"""
|
||||||
|
val_to_set = {"allow": True, "deny": False, "clear": None}.get(allow_or_deny)
|
||||||
if allow_or_deny:
|
|
||||||
val_to_set = {"allow": True, "deny": False}.get(allow_or_deny)
|
|
||||||
else:
|
|
||||||
val_to_set = None
|
|
||||||
|
|
||||||
model_type, type_name = cog_or_command
|
model_type, type_name = cog_or_command
|
||||||
async with self.config.owner_models() as models:
|
async with self.config.owner_models() as models:
|
||||||
@@ -570,33 +563,18 @@ class Permissions:
|
|||||||
data[model_type][type_name]["default"] = val_to_set
|
data[model_type][type_name]["default"] = val_to_set
|
||||||
|
|
||||||
models.update(data)
|
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()
|
@checks.is_owner()
|
||||||
@permissions.command(name="clearglobalsettings")
|
@permissions.command(name="clearglobalsettings")
|
||||||
async def clear_globals(self, ctx: commands.Context):
|
async def clear_globals(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Clears all global rules.
|
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()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
@permissions.command(name="clearguildsettings")
|
@permissions.command(name="clearguildsettings")
|
||||||
@@ -604,23 +582,61 @@ class Permissions:
|
|||||||
"""
|
"""
|
||||||
Clears all guild rules.
|
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?")
|
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():
|
for r in REACTS.keys():
|
||||||
await m.add_reaction(r)
|
await m.add_reaction(r)
|
||||||
try:
|
try:
|
||||||
reaction, user = await self.bot.wait_for(
|
reaction, user = await self.bot.wait_for(
|
||||||
"reaction_add", check=lambda r, u: u == ctx.author and str(r) in REACTS, timeout=30
|
"reaction_add",
|
||||||
|
check=lambda r, u: u == ctx.author and str(r) in REACTS,
|
||||||
|
timeout=30,
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return await ctx.send(_("Ok, try responding with an emoji next time."))
|
return await ctx.send(_("Ok, try responding with an emoji next time."))
|
||||||
|
|
||||||
if REACTS.get(str(reaction)):
|
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 self.config.guild(ctx.guild).owner_models.clear()
|
||||||
await ctx.send(_("Guild settings cleared"))
|
await ctx.send(_("Guild settings cleared."))
|
||||||
|
else:
|
||||||
|
await self.config.owner_models.clear()
|
||||||
|
await ctx.send(_("Global settings cleared."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Okay."))
|
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:
|
def find_object_uniquely(self, info: str) -> int:
|
||||||
"""
|
"""
|
||||||
Finds an object uniquely, returns it's id or returns None
|
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")
|
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:
|
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
|
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
|
val = None
|
||||||
# let's not spam the console with improperly made 3rd party checks
|
# let's not spam the console with improperly made 3rd party checks
|
||||||
try:
|
try:
|
||||||
if asyncio.iscoroutine(check) or asyncio.iscoroutinefunction(check):
|
if asyncio.iscoroutinefunction(check):
|
||||||
val = await check(ctx, level=level)
|
val = await check(ctx, level=level)
|
||||||
else:
|
else:
|
||||||
val = check(ctx, level=level)
|
val = check(ctx, level=level)
|
||||||
@@ -67,23 +73,7 @@ def resolve_lists(*, ctx: commands.Context, whitelist: list, blacklist: list) ->
|
|||||||
"""
|
"""
|
||||||
resolves specific lists
|
resolves specific lists
|
||||||
"""
|
"""
|
||||||
|
for entry in entries_from_ctx(ctx):
|
||||||
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:
|
|
||||||
if entry in whitelist:
|
if entry in whitelist:
|
||||||
return True
|
return True
|
||||||
if entry in blacklist:
|
if entry in blacklist:
|
||||||
|
|||||||
@@ -59,29 +59,29 @@ class Reports:
|
|||||||
@commands.group(name="reportset")
|
@commands.group(name="reportset")
|
||||||
async def reportset(self, ctx: commands.Context):
|
async def reportset(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
settings for reports
|
Settings for the report system.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@checks.admin_or_permissions(manage_guild=True)
|
@checks.admin_or_permissions(manage_guild=True)
|
||||||
@reportset.command(name="output")
|
@reportset.command(name="output")
|
||||||
async def setoutput(self, ctx: commands.Context, channel: discord.TextChannel):
|
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 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)
|
@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):
|
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 = await self.config.guild(ctx.guild).active()
|
||||||
active = not active
|
active = not active
|
||||||
await self.config.guild(ctx.guild).active.set(active)
|
await self.config.guild(ctx.guild).active.set(active)
|
||||||
if active:
|
if active:
|
||||||
await ctx.send(_("Reporting now enabled"))
|
await ctx.send(_("Reporting is now enabled"))
|
||||||
else:
|
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):
|
async def internal_filter(self, m: discord.Member, mod=False, perms=None):
|
||||||
ret = False
|
ret = False
|
||||||
@@ -105,7 +105,7 @@ class Reports:
|
|||||||
*,
|
*,
|
||||||
mod: bool = False,
|
mod: bool = False,
|
||||||
permissions: Union[discord.Permissions, dict] = None,
|
permissions: Union[discord.Permissions, dict] = None,
|
||||||
prompt: str = ""
|
prompt: str = "",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
discovers which of shared guilds between the bot
|
discovers which of shared guilds between the bot
|
||||||
@@ -175,7 +175,10 @@ class Reports:
|
|||||||
if await self.bot.embed_requested(channel, author):
|
if await self.bot.embed_requested(channel, author):
|
||||||
em = discord.Embed(description=report)
|
em = discord.Embed(description=report)
|
||||||
em.set_author(
|
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))
|
em.set_footer(text=_("Report #{}").format(ticket_number))
|
||||||
send_content = None
|
send_content = None
|
||||||
@@ -201,10 +204,10 @@ class Reports:
|
|||||||
@commands.group(name="report", invoke_without_command=True)
|
@commands.group(name="report", invoke_without_command=True)
|
||||||
async def report(self, ctx: commands.Context, *, _report: str = ""):
|
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
|
Use without arguments for interactive reporting, or do
|
||||||
to use it non interactively
|
[p]report <text> to use it non-interactively.
|
||||||
"""
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@@ -212,11 +215,6 @@ class Reports:
|
|||||||
guild = await self.discover_guild(
|
guild = await self.discover_guild(
|
||||||
author, prompt=_("Select a server to make a report in by number.")
|
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:
|
if guild is None:
|
||||||
return
|
return
|
||||||
g_active = await self.config.guild(guild).active()
|
g_active = await self.config.guild(guild).active()
|
||||||
@@ -229,22 +227,18 @@ class Reports:
|
|||||||
if self.antispam[guild.id][author.id].spammy:
|
if self.antispam[guild.id][author.id].spammy:
|
||||||
return await author.send(
|
return await author.send(
|
||||||
_(
|
_(
|
||||||
"You've sent a few too many of these recently. "
|
"You've sent too many reports recently. "
|
||||||
"Contact a server admin to resolve this, or try again "
|
"Please contact a server admin if this is important matter, "
|
||||||
"later."
|
"or please wait and try again later."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if author.id in self.user_cache:
|
if author.id in self.user_cache:
|
||||||
return await author.send(
|
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)
|
self.user_cache.append(author.id)
|
||||||
|
|
||||||
if _report:
|
if _report:
|
||||||
@@ -261,9 +255,7 @@ class Reports:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await ctx.send(_("This requires DMs enabled."))
|
return await ctx.send(_("This requires DMs enabled."))
|
||||||
self.user_cache.remove(author.id)
|
|
||||||
return
|
|
||||||
|
|
||||||
def pred(m):
|
def pred(m):
|
||||||
return m.author == author and m.channel == dm.channel
|
return m.author == author and m.channel == dm.channel
|
||||||
@@ -271,18 +263,32 @@ class Reports:
|
|||||||
try:
|
try:
|
||||||
message = await self.bot.wait_for("message", check=pred, timeout=180)
|
message = await self.bot.wait_for("message", check=pred, timeout=180)
|
||||||
except asyncio.TimeoutError:
|
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:
|
else:
|
||||||
val = await self.send_report(message, guild)
|
val = await self.send_report(message, guild)
|
||||||
|
|
||||||
with contextlib.suppress(discord.Forbidden, discord.HTTPException):
|
with contextlib.suppress(discord.Forbidden, discord.HTTPException):
|
||||||
if val is None:
|
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:
|
else:
|
||||||
await author.send(_("Your report was submitted. (Ticket #{})").format(val))
|
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):
|
async def on_raw_reaction_add(self, payload):
|
||||||
"""
|
"""
|
||||||
@@ -311,17 +317,19 @@ class Reports:
|
|||||||
if msgs:
|
if msgs:
|
||||||
self.tunnel_store[k]["msgs"] = msgs
|
self.tunnel_store[k]["msgs"] = msgs
|
||||||
|
|
||||||
|
@commands.guild_only()
|
||||||
@checks.mod_or_permissions(manage_members=True)
|
@checks.mod_or_permissions(manage_members=True)
|
||||||
@report.command(name="interact")
|
@report.command(name="interact")
|
||||||
async def response(self, ctx, ticket_number: int):
|
async def response(self, ctx, ticket_number: int):
|
||||||
"""
|
"""
|
||||||
opens a message tunnel between things you say in this channel
|
Open a message tunnel.
|
||||||
and the ticket opener's direct messages
|
|
||||||
|
|
||||||
tunnels do not persist across bot restarts
|
This tunnel will forward things you say in this channel
|
||||||
|
to the ticket opener's direct messages.
|
||||||
|
|
||||||
|
Tunnels do not persist across bot restarts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# note, mod_or_permissions is an implicit guild_only
|
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
rec = await self.config.custom("REPORT", guild.id, ticket_number).report()
|
rec = await self.config.custom("REPORT", guild.id, ticket_number).report()
|
||||||
|
|
||||||
@@ -344,14 +352,15 @@ class Reports:
|
|||||||
)
|
)
|
||||||
|
|
||||||
big_topic = _(
|
big_topic = _(
|
||||||
"{who} opened a 2-way communication."
|
"{who} opened a 2-way communication "
|
||||||
"about ticket number {ticketnum}. Anything you say or upload here "
|
"about ticket number {ticketnum}. Anything you say or upload here "
|
||||||
"(8MB file size limitation on uploads) "
|
"(8MB file size limitation on uploads) "
|
||||||
"will be forwarded to them until the communication is closed.\n"
|
"will be forwarded to them until the communication is closed.\n"
|
||||||
"You can close a communication at any point "
|
"You can close a communication at any point by reacting with "
|
||||||
"by reacting with the X to the last message recieved. "
|
"the \N{NEGATIVE SQUARED CROSS MARK} to the last message recieved.\n"
|
||||||
"\nAny message succesfully forwarded will be marked with a check."
|
"Any message succesfully forwarded will be marked with "
|
||||||
"\nTunnels are not persistent across bot restarts."
|
"\N{WHITE HEAVY CHECK MARK}.\n"
|
||||||
|
"Tunnels are not persistent across bot restarts."
|
||||||
)
|
)
|
||||||
topic = big_topic.format(
|
topic = big_topic.format(
|
||||||
ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild)
|
ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild)
|
||||||
@@ -359,8 +368,7 @@ class Reports:
|
|||||||
try:
|
try:
|
||||||
m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True)
|
m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await ctx.send(_("User has disabled DMs."))
|
await ctx.send(_("That user has DMs disabled."))
|
||||||
tun.close()
|
|
||||||
else:
|
else:
|
||||||
self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m}
|
self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m}
|
||||||
await ctx.send(big_topic.format(who=_("You have"), ticketnum=ticket_number))
|
await ctx.send(big_topic.format(who=_("You have"), ticketnum=ticket_number))
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from .streams import Streams
|
from .streams import Streams
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
async def setup(bot):
|
||||||
bot.add_cog(Streams(bot))
|
cog = Streams(bot)
|
||||||
|
await cog.initialize()
|
||||||
|
bot.add_cog(cog)
|
||||||
|
|||||||
@@ -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 = ["../mod.py"]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
|
||||||
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
regen_messages()
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user