Compare commits
95 commits
Author | SHA1 | Date | |
---|---|---|---|
|
2989b6ed90 | ||
|
7d062daeb9 | ||
|
683db10f00 | ||
ceeb020855 | |||
7eac932a4e | |||
833884bcb2 | |||
cce7bb4093 | |||
be1e6d24e4 | |||
96618fa502 | |||
caa98c44f4 | |||
f03a54ca29 | |||
ad24e4ba86 | |||
b7ed8ffae7 | |||
d187177908 | |||
de4c8c549a | |||
a781e09bde | |||
d9221002d2 | |||
1bd73601f5 | |||
248228408d | |||
7b1fd7b463 | |||
07e683e7cb | |||
fdf220cdfa | |||
7e8f0bbf12 | |||
c71c976111 | |||
6cd66ba407 | |||
92a0c257ca | |||
ff878711bf | |||
6d078e829d | |||
6c18c3213e | |||
d94775e0e6 | |||
64b092dff2 | |||
c442fd55fe | |||
97980e96d1 | |||
d9427d1863 | |||
348a78e4b3 | |||
d5afdcc60b | |||
e7d4a9ee43 | |||
f42b2194cd | |||
ba1122c07b | |||
c6114709ee | |||
fdc9c9196d | |||
d00aadd82f | |||
25f5c5e1f6 | |||
ad4fb2fa89 | |||
98b241d51b | |||
76e845e5be | |||
d5f1f71a0a | |||
29808d41d6 | |||
8f17085cf7 | |||
46b1daeae4 | |||
6429dc2e01 | |||
3ae55bc92e | |||
60f68f245d | |||
2d175b4453 | |||
0849c1bdff | |||
e38fd5417f | |||
4c48fdff6e | |||
9274895226 | |||
764068ef22 | |||
6605115941 | |||
a3edb16528 | |||
68daf90938 | |||
7c65c92084 | |||
23ec0f7e67 | |||
289fedb4b7 | |||
a9a8572aad | |||
582d254af5 | |||
d0a9e658a6 | |||
d6f155e682 | |||
c0af425383 | |||
806ddf0540 | |||
04f2c9feae | |||
985f839d21 | |||
f6de008952 | |||
7e5f6e6dbf | |||
537a905f85 | |||
02d279b6c4 | |||
712339968c | |||
f6ba42a143 | |||
30e7906f3f | |||
cb0b9a2681 | |||
efc05f816e | |||
b03dc30c6c | |||
f4813702fd | |||
7bd7a5402e | |||
4102f3e723 | |||
63938a6bf3 | |||
04ce551acb | |||
108e2b8af3 | |||
0a25ced905 | |||
31c6df0f88 | |||
36e03ad961 | |||
59423ae8a2 | |||
d1afd8c7ec | |||
2157ae2899 |
97 changed files with 4303 additions and 3789 deletions
5
.github/issue_template.md
vendored
5
.github/issue_template.md
vendored
|
@ -1,6 +1,9 @@
|
||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
140
.gitignore
vendored
140
.gitignore
vendored
|
@ -2,9 +2,145 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
.env
|
.env
|
||||||
config.py
|
configs/config.cfg
|
||||||
|
configs/prefixes.cfg
|
||||||
|
configs/fallbacks.cfg
|
||||||
.DS_Store
|
.DS_Store
|
||||||
private.py
|
private.py
|
||||||
|
|
||||||
#jetbrains
|
#jetbrains
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# other
|
||||||
|
logs/tuxbot.log
|
||||||
|
utils/images/tmp/*
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
107
README.md
Executable file → Normal file
107
README.md
Executable file → Normal file
|
@ -1,33 +1,86 @@
|
||||||
TuxBot, un bot discord écrit en Python.
|
# News
|
||||||
Ici ce trouve le code source du bot provenant du serveur Discord [Aide GNU/Linux-Fr"](https://gnous.eu/discord "Rejoindre le serveur"), il à été créé spécialement pour ce discord, si vous souhaitez l'utiliser il vous faudra modifier ``params.json`` et ``cogs/utils/checks.py`` ;)
|
|
||||||
|
|
||||||
### Pré-requis
|
- [ ] i18n for messages
|
||||||
|
- [x] Custom prefixes
|
||||||
|
- [ ] Better help command
|
||||||
|
- [ ] Alias system for commands (e.g. `.alias .ci show .cs`)
|
||||||
|
- [x] Migrate MySQL to postgresql
|
||||||
|
- [x] Prepare bot for python 3.8 and discord.py 1.3.0
|
||||||
|
- [ ] Create launcher
|
||||||
|
- [ ] Create documentation
|
||||||
|
|
||||||
Il vous faut :
|
## Launcher requirements :
|
||||||
|
|
||||||
- Un ordinateur sous **GNU/Linux** avec une connexion à internet;
|
- [ ] Can install the bot
|
||||||
- Python3.7 ou + ;
|
- [ ] Can launch the bot
|
||||||
- Installer ``requirements.txt`` (avec ``pip install -r requirements.txt`` par ex)
|
- [ ] Can propose updates
|
||||||
|
|
||||||
|
## New commands :
|
||||||
|
|
||||||
### Installation
|
- [x] `.sondage --anonyme <...>` (create à sondage with the possibility of answering anonymously)
|
||||||
|
- [ ] `.sondage --edit <id>` (edit a sondage if we are the author or an admin)
|
||||||
|
|
||||||
|
## Documentation:
|
||||||
|
- [ ] How to use ?
|
||||||
|
- [ ] How to add more commands ?
|
||||||
|
|
||||||
|
## Ultimate :
|
||||||
|
|
||||||
Une fois les pré-requis installés et ``config.py.example`` renommé en ``config.py`` éditez le. Ensuite placez vous dans le repertoire de tuxbot et lancez ``bot.py`` avec python3 (ex: ``python3 bot.py``)
|
- [ ] Send email or Telegram's message when something is wrong on the bot
|
||||||
|
- [ ] Create skynet (group of multiple commands about sky (planes, meteo, AI,...))
|
||||||
|
|
||||||
## Démarrage
|
---
|
||||||
|
|
||||||
Placez vous dans le repertoire de tuxbot et exécutez ``bot.py`` avec python3 (ex: ``python3 bot.py``)
|
# Cogs.admin commands
|
||||||
|
|
||||||
## Fabriqué avec
|
- [x] upload `removed`, cause : `never used`
|
||||||
* [PyCharm](https://www.jetbrains.com/pycharm/) - Editeur de texte payant :3
|
- [x] ban
|
||||||
* [discord.py](https://github.com/Rapptz/discord.py) - API Python pour discord
|
- [x] kick
|
||||||
|
- [x] clear
|
||||||
## Versions
|
- [x] say
|
||||||
Liste des versions : [Cliquer pour afficher](https://github.com/outout14/tuxbot-bot/tags)
|
- [x] sayto `removed`, now : `say to`
|
||||||
|
- [x] sayto_dm `removed`, now : `say to`
|
||||||
## Auteurs
|
- [x] editsay `removed`, now : `say edit`
|
||||||
* **Maël** _alias_ [@outout14](https://github.com/outout14)
|
- [x] addreaction `renamed`, now `react add`
|
||||||
* **Romain** _alias_ [Romain le malchanceux](https://github.com/Rom194)
|
- [x] delete
|
||||||
|
- [x] deletefrom `removed`, now `delete (from|to|in)`
|
||||||
## License
|
- [x] embed `removed`, cause : `never used`
|
||||||
|
- [x] warn `new command`
|
||||||
Ce projet est sous licence ``Creative Commons BY-NC-SA 4.0`` - voir le fichier [LICENSE.md](LICENSE.md) pour plus d'informations
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cogs.basics commands
|
||||||
|
- [x] ping
|
||||||
|
- [x] info
|
||||||
|
- [ ] help
|
||||||
|
- [x] credits `new command`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cogs.ci commands `canceled until the frontend development`
|
||||||
|
- [ ] ci (help?)
|
||||||
|
- [ ] ci show
|
||||||
|
- [ ] ci register
|
||||||
|
- [ ] ci delete
|
||||||
|
- [ ] ci update
|
||||||
|
- [ ] ci setconfig
|
||||||
|
- [ ] ci setos
|
||||||
|
- [ ] ci setcountry
|
||||||
|
- [ ] ci online_edit `renamed`, cause : `website down`
|
||||||
|
- [ ] ci list
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cogs.utility commands
|
||||||
|
- [ ] clock `removed` ?
|
||||||
|
- [ ] clock * `removed` ?
|
||||||
|
- [ ] ytdiscover `removed` ?
|
||||||
|
- [x] iplocalise
|
||||||
|
- [x] getheaders
|
||||||
|
- [x] git
|
||||||
|
- [x] quote
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cogs.sondage commands `(renamed as cogs.poll)` `canceled until the frontend development`
|
||||||
|
- [ ] sondage (help?)
|
||||||
|
|
146
autoinstall.sh
146
autoinstall.sh
|
@ -1,146 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# This script is meant for easy install Tuxbot using curl/wget
|
|
||||||
printf "Welcome to Tuxbot's installation guide.\n"
|
|
||||||
printf "\nLog file is in ~/.tuxinstall.log\n"
|
|
||||||
# Command checking
|
|
||||||
if (( $EUID != 0 )); then
|
|
||||||
printf "\n\nError : Please run this script as ROOT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [ -x "$(command -v git)" ]; then
|
|
||||||
printf "\n\nError : Git is not installed"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [ -x "$(command -v pip3.7)" ]; then
|
|
||||||
printf "\n\nError : pip3.7 is not installed (using pip3.7 command)\nPlease install it to continue"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Tuxbot directory answer
|
|
||||||
read -p "In which directory Tuxbot should be installed ? : [/srv/]" na
|
|
||||||
na=${na:-"/srv/"}
|
|
||||||
|
|
||||||
# Cloning tuxbot USING GNOUS'S GIT MIRROR
|
|
||||||
printf "Cloning git repository, please wait... \n" &
|
|
||||||
git clone https://git.gnous.eu/gnouseu/tuxbot-bot $na/tuxbot-bot &> ~/.tuxinstall.log
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
printf "Tuxbot has been cloned to $na.\n" 5 50
|
|
||||||
sleep 1
|
|
||||||
printf "Installing pip modules, please wait...\n" 5 50 &
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# Downloading PIP modules using pip3.7 cmd
|
|
||||||
pip3.7 install -U discord.py[voice] &> ~/.tuxinstall.log
|
|
||||||
cd $na/tuxbot-bot
|
|
||||||
pip3.7 install -r requirements.txt &> ~/.tuxinstall.log
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
printf "Tuxbot's python dependencies have been downloaded\n"
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# Answers to generate config
|
|
||||||
function generateConfig {
|
|
||||||
DATE=`date +%Y-%m-%d`
|
|
||||||
read -p "Enter your Discord API Token : " cToken
|
|
||||||
read -p "Enter the bot client ID : " cID
|
|
||||||
read -p "Enter the log channel ID : " cLogID
|
|
||||||
read -p "Enter the main channel of your server : " cSrvID
|
|
||||||
read -p "What game tuxbot should display as playing (eg : 'Eat potatoes') : " cGame
|
|
||||||
read -p "What is you're discord user ID (for admin cmd) : " cAdmin
|
|
||||||
echo "------------"
|
|
||||||
read -p "MySQL's tuxbot user : " mSQLuser
|
|
||||||
read -p "MySQL's tuxbot password : " mSQLpass
|
|
||||||
read -p "MySQL's tuxbot database name : " mSQLdb
|
|
||||||
echo """
|
|
||||||
#Generated by Tuxbot install script
|
|
||||||
#$DATE
|
|
||||||
token = \"$cToken\"
|
|
||||||
client_id = \"$cID\"
|
|
||||||
log_channel_id = \"$cLogID\"
|
|
||||||
main_server_id = \"$cSrvID\"
|
|
||||||
game = \"$cGame\"
|
|
||||||
authorized_id = [\"$cAdmin\"]
|
|
||||||
prefix = [\".\"]
|
|
||||||
description = '.'
|
|
||||||
mysql = {
|
|
||||||
\"host\": \"localhost\",
|
|
||||||
\"username\": \"$mSQLuser\",
|
|
||||||
\"password\": \"$mSQLpass\",
|
|
||||||
\"dbname\": \"$mSQLdb\"
|
|
||||||
}
|
|
||||||
fonts = {
|
|
||||||
\"normal\": \"NotoSansCJK-Regular.ttc\",
|
|
||||||
\"bold\": \"NotoSansCJK-Bold.ttc\"
|
|
||||||
}
|
|
||||||
""" &> $na/tuxbot-bot/config.py
|
|
||||||
}
|
|
||||||
|
|
||||||
printf "Do you want to generate config file ?\n1 - Yes (selected)\n2 - No\n"
|
|
||||||
read -p "(1-2) : " initConf
|
|
||||||
initConf=${initConf:-"1"}
|
|
||||||
case $initConf in
|
|
||||||
1) generateConfig;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
#Non login user
|
|
||||||
echo "Adding tuxbot non-login user..."
|
|
||||||
useradd -M tuxbot
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
#Chown all perms to the non login user
|
|
||||||
echo "Fixing permissions..."
|
|
||||||
chown tuxbot:tuxbot -R $na/tuxbot-bot/
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
#Create the service file
|
|
||||||
echo "Adding Tuxbot service & start it..."
|
|
||||||
echo """[Unit]
|
|
||||||
Description=Tuxbot, a discord bot
|
|
||||||
#After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=tuxbot
|
|
||||||
|
|
||||||
Restart=on-failure
|
|
||||||
Restart=always
|
|
||||||
RestartSec=1
|
|
||||||
|
|
||||||
WorkingDirectory=$na/tuxbot-bot/
|
|
||||||
ExecStart=/usr/bin/env python3.7 $na/tuxbot-bot/bot.py
|
|
||||||
|
|
||||||
StandardOutput=file:/var/log/tuxbot.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
""" &> /lib/systemd/system/tuxbot.service
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl start tuxbot
|
|
||||||
sleep 1
|
|
||||||
echo "Activation of tuxbot at startup..."
|
|
||||||
sleep 1
|
|
||||||
systemctl enable tuxbot
|
|
||||||
|
|
||||||
#End message
|
|
||||||
echo """
|
|
||||||
|
|
||||||
|
|
||||||
Tuxbot should be correctly installed.
|
|
||||||
Please check if all is good by execute :
|
|
||||||
systemctl status tuxbot
|
|
||||||
And .ping command in discord.
|
|
||||||
|
|
||||||
Configuration file is $na/tuxbot-bot/config.py
|
|
||||||
Main tuxbot directory is $na/tuxbot-bot/
|
|
||||||
|
|
||||||
Any question ? => Make an issue on github
|
|
||||||
|
|
||||||
https://git.gnous.eu/gnouseu/tuxbot-bot
|
|
||||||
https://github.com/outout14/tuxbot-bot
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
302
bot.py
302
bot.py
|
@ -1,141 +1,239 @@
|
||||||
#!/usr/bin/env python
|
import contextlib
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
__author__ = "Maël / Outout | Romain"
|
|
||||||
__licence__ = "WTFPL Licence 2.0"
|
|
||||||
|
|
||||||
|
|
||||||
import copy
|
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import json
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
from collections import deque, Counter
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
|
import git
|
||||||
|
import sqlalchemy
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
import cogs.utils.cli_colors as colors
|
from utils.functions import Config
|
||||||
import config
|
from utils.functions import Texts
|
||||||
from cogs.utils import checks
|
from utils.functions import Version
|
||||||
|
from utils.functions import ContextPlus
|
||||||
|
|
||||||
if sys.version_info[1] < 7 or sys.version_info[0] < 3:
|
from utils.models import metadata, database
|
||||||
print(f"{colors.text_colors.RED}[ERROR] Python 3.7 or + is required.{colors.ENDC}")
|
|
||||||
exit()
|
|
||||||
|
|
||||||
l_extensions = (
|
description = """
|
||||||
'cogs.admin',
|
Je suis TuxBot, le bot qui vit de l'OpenSource ! ;)
|
||||||
'cogs.afk',
|
"""
|
||||||
'cogs.atc',
|
|
||||||
'cogs.basics',
|
|
||||||
'cogs.ci',
|
|
||||||
'cogs.filter_messages',
|
|
||||||
'cogs.funs',
|
|
||||||
'cogs.role',
|
|
||||||
'cogs.search',
|
|
||||||
'cogs.send_logs',
|
|
||||||
'cogs.sondage',
|
|
||||||
'cogs.utility',
|
|
||||||
'cogs.vocal',
|
|
||||||
'cogs.private',
|
|
||||||
'cogs.monitoring',
|
|
||||||
'jishaku'
|
|
||||||
)
|
|
||||||
|
|
||||||
help_attrs = dict(hidden=True, in_help=True, name="DONOTUSE")
|
build = git.Repo(search_parent_directories=True).head.object.hexsha
|
||||||
|
version = (2, 0, 0)
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
l_extensions: List[str] = [
|
||||||
|
'cogs.Admin',
|
||||||
|
'cogs.API',
|
||||||
|
'cogs.Help',
|
||||||
|
'cogs.Logs',
|
||||||
|
# 'cogs.Monitoring',
|
||||||
|
'cogs.Poll',
|
||||||
|
'cogs.Useful',
|
||||||
|
'cogs.User',
|
||||||
|
'jishaku',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TuxBot(commands.Bot):
|
async def _prefix_callable(bot, message: discord.message) -> list:
|
||||||
def __init__(self):
|
try:
|
||||||
self.uptime = datetime.datetime.utcnow()
|
with open(f'./configs/guilds/{message.guild.id}.json', 'r') as f:
|
||||||
self.config = config
|
data = json.load(f)
|
||||||
super().__init__(command_prefix=self.config.prefix[0],
|
|
||||||
description=self.config.description,
|
|
||||||
pm_help=None,
|
|
||||||
help_command=None)
|
|
||||||
|
|
||||||
self.client_id = self.config.client_id
|
custom_prefix = data['prefixes']
|
||||||
|
except FileNotFoundError:
|
||||||
|
custom_prefix = ['']
|
||||||
|
|
||||||
|
extras = [bot.cluster.get('Name') + '.']
|
||||||
|
extras.extend(custom_prefix)
|
||||||
|
|
||||||
|
return commands.when_mentioned_or(*extras)(bot, message)
|
||||||
|
|
||||||
|
|
||||||
|
class TuxBot(commands.AutoShardedBot):
|
||||||
|
|
||||||
|
def __init__(self, ):
|
||||||
|
super().__init__(command_prefix=_prefix_callable, pm_help=None,
|
||||||
|
help_command=None, description=description,
|
||||||
|
help_attrs=dict(hidden=True),
|
||||||
|
activity=discord.Game(
|
||||||
|
name=Texts().get('Starting...'))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.socket_stats = Counter()
|
||||||
|
self.command_stats = Counter()
|
||||||
|
|
||||||
|
self.config = Config('./configs/config.cfg')
|
||||||
|
self.blacklist = Config('./configs/blacklist.cfg')
|
||||||
|
self.fallbacks = Config('./configs/fallbacks.cfg')
|
||||||
|
self.cluster = self.fallbacks.find('True', key='This', first=True)
|
||||||
|
|
||||||
|
self.uptime: datetime = datetime.datetime.utcnow()
|
||||||
|
self._prev_events = deque(maxlen=10)
|
||||||
self.session = aiohttp.ClientSession(loop=self.loop)
|
self.session = aiohttp.ClientSession(loop=self.loop)
|
||||||
self._events = []
|
|
||||||
|
|
||||||
self.add_command(self.do)
|
self.database, self.metadata = database, metadata
|
||||||
|
self.engine = sqlalchemy.create_engine(str(self.database.url))
|
||||||
|
self.metadata.create_all(self.engine)
|
||||||
|
|
||||||
|
self.version = Version(*version, pre_release='rc2', build=build)
|
||||||
|
self.owners_id = [int(owner_id) for owner_id in self.config.get('permissions', 'Owners').split(', ')]
|
||||||
|
self.owner_id = int(self.owners_id[0])
|
||||||
|
|
||||||
for extension in l_extensions:
|
for extension in l_extensions:
|
||||||
try:
|
try:
|
||||||
self.load_extension(extension)
|
self.load_extension(extension)
|
||||||
print(f"{colors.text_colors.GREEN}\"{extension}\""
|
print(Texts().get("Extension loaded successfully : ")
|
||||||
f" chargé !{colors.ENDC}")
|
+ extension)
|
||||||
|
log.info(Texts().get("Extension loaded successfully : ")
|
||||||
|
+ extension)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{colors.text_colors.RED}"
|
print(Texts().get("Failed to load extension : ")
|
||||||
f"Impossible de charger l'extension {extension}\n"
|
+ extension, file=sys.stderr)
|
||||||
f"{type(e).__name__}: {e}{colors.ENDC}", file=sys.stderr)
|
print(e)
|
||||||
|
log.error(Texts().get("Failed to load extension : ")
|
||||||
|
+ extension, exc_info=e)
|
||||||
|
|
||||||
async def is_owner(self, user: discord.User):
|
@property
|
||||||
return str(user.id) in config.authorized_id
|
def owner(self):
|
||||||
|
return self.get_user(self.owner_id)
|
||||||
|
|
||||||
async def on_command_error(self, ctx, error):
|
@property
|
||||||
|
def owners(self):
|
||||||
|
return [self.get_user(owner_id) for owner_id in self.owners_id]
|
||||||
|
|
||||||
|
async def is_owner(self, user: discord.User) -> bool:
|
||||||
|
return user in self.owners
|
||||||
|
|
||||||
|
async def get_context(self, message, *, cls=None):
|
||||||
|
return await super().get_context(message, cls=cls or ContextPlus)
|
||||||
|
|
||||||
|
async def on_socket_response(self, msg):
|
||||||
|
self._prev_events.append(msg)
|
||||||
|
|
||||||
|
async def on_command_error(self, ctx: discord.ext.commands.Context, error):
|
||||||
if isinstance(error, commands.NoPrivateMessage):
|
if isinstance(error, commands.NoPrivateMessage):
|
||||||
await ctx.author.send('Cette commande ne peut pas être utilisee '
|
await ctx.author.send(
|
||||||
'en message privee.')
|
Texts().get("This command cannot be used in private messages.")
|
||||||
|
)
|
||||||
|
|
||||||
elif isinstance(error, commands.DisabledCommand):
|
elif isinstance(error, commands.DisabledCommand):
|
||||||
await ctx.author.send('Desoler mais cette commande est desactive, '
|
await ctx.author.send(
|
||||||
'elle ne peut donc pas être utilisée.')
|
Texts().get(
|
||||||
elif isinstance(error, commands.CommandInvokeError):
|
"Sorry. This command is disabled and cannot be used."
|
||||||
print(f'In {ctx.command.qualified_name}:', file=sys.stderr)
|
)
|
||||||
traceback.print_tb(error.original.__traceback__)
|
)
|
||||||
print(f'{error.original.__class__.__name__}: {error.original}',
|
elif isinstance(error, commands.CommandOnCooldown):
|
||||||
file=sys.stderr)
|
await ctx.send(str(error))
|
||||||
|
|
||||||
|
async def process_commands(self, message: discord.message):
|
||||||
|
ctx: commands.Context = await self.get_context(message)
|
||||||
|
|
||||||
|
if ctx.command is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.invoke(ctx)
|
||||||
|
|
||||||
|
async def on_message(self, message: discord.message):
|
||||||
|
if message.author.id in self.blacklist \
|
||||||
|
or (message.guild is not None
|
||||||
|
and message.guild.id in self.blacklist):
|
||||||
|
return
|
||||||
|
|
||||||
|
if message.author.bot and message.author.id != int(
|
||||||
|
self.config.get('bot', 'Tester')):
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.process_commands(message)
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
log_channel_id = await self.fetch_channel(self.config.log_channel_id)
|
if not hasattr(self, 'uptime'):
|
||||||
|
self.uptime = datetime.datetime.utcnow()
|
||||||
|
|
||||||
print('\n\n---------------------')
|
print('-' * 60)
|
||||||
print('CONNECTÉ :')
|
print(Texts().get("Ready:") + f' {self.user} (ID: {self.user.id})')
|
||||||
print(f'Nom d\'utilisateur: {self.user} {colors.text_style.DIM}'
|
print(self.version)
|
||||||
f'(ID: {self.user.id}){colors.ENDC}')
|
|
||||||
print(f'Channel de log: {log_channel_id} {colors.text_style.DIM}'
|
|
||||||
f'(ID: {log_channel_id.id}){colors.ENDC}')
|
|
||||||
print(f'Prefix: {self.config.prefix[0]}')
|
|
||||||
print('Merci d\'utiliser TuxBot')
|
|
||||||
print('---------------------\n\n')
|
|
||||||
|
|
||||||
await self.change_presence(status=discord.Status.dnd,
|
presence: dict = dict(status=discord.Status.dnd)
|
||||||
activity=discord.Game(
|
if self.config.get("bot", "Activity", fallback=None) is not None:
|
||||||
name=self.config.game)
|
presence.update(
|
||||||
)
|
activity=discord.Game(
|
||||||
|
name=self.config.get("bot", "Activity")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print(f"Discord.py: {discord.__version__}")
|
||||||
|
print(f"Server: {self.cluster.get('Name')}")
|
||||||
|
print('-' * 60)
|
||||||
|
|
||||||
|
await self.change_presence(**presence)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def on_resumed():
|
async def on_resumed():
|
||||||
print('resumed...')
|
print('resumed...')
|
||||||
|
|
||||||
async def on_message(self, message):
|
@property
|
||||||
if message.author.bot:
|
def logs_webhook(self) -> discord.Webhook:
|
||||||
return
|
webhook_config = self.config["webhook"]
|
||||||
|
webhook = discord.Webhook.partial(
|
||||||
|
id=webhook_config.get('ID'),
|
||||||
|
token=webhook_config.get('Token'),
|
||||||
|
adapter=discord.AsyncWebhookAdapter(
|
||||||
|
self.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
return webhook
|
||||||
await self.process_commands(message)
|
|
||||||
except Exception as e:
|
async def close(self):
|
||||||
print(f'{colors.text_colors.RED}Erreur rencontré : \n'
|
extensions = self.extensions.copy()
|
||||||
f' {type(e).__name__}: {e}{colors.ENDC} \n \n')
|
for extension in extensions:
|
||||||
|
self.unload_extension(extension)
|
||||||
|
await super().close()
|
||||||
|
await self.session.close()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
super().run(self.config.token, reconnect=True)
|
super().run(self.config.get("bot", "Token"), reconnect=True)
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(pass_context=True, hidden=True)
|
|
||||||
async def do(self, ctx, times: int, *, command):
|
|
||||||
"""Repeats a command a specified number of times."""
|
|
||||||
msg = copy.copy(ctx.message)
|
|
||||||
msg.content = command
|
|
||||||
for i in range(times):
|
|
||||||
await self.process_commands(msg)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
@contextlib.contextmanager
|
||||||
if os.path.exists('config.py') is not True:
|
def setup_logging():
|
||||||
print(f"{colors.text_colors.RED}"
|
logging.getLogger('discord').setLevel(logging.INFO)
|
||||||
f"Veuillez créer le fichier config.py{colors.ENDC}")
|
logging.getLogger('discord.http').setLevel(logging.WARNING)
|
||||||
exit()
|
|
||||||
|
|
||||||
tuxbot = TuxBot()
|
log = logging.getLogger()
|
||||||
tuxbot.run()
|
log.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
try:
|
||||||
|
handler = logging.FileHandler(filename='logs/tuxbot.log',
|
||||||
|
encoding='utf-8', mode='w')
|
||||||
|
fmt = logging.Formatter('[{levelname:<7}] [{asctime}]'
|
||||||
|
' {name}: {message}',
|
||||||
|
'%Y-%m-%d %H:%M:%S', style='{')
|
||||||
|
|
||||||
|
handler.setFormatter(fmt)
|
||||||
|
log.addHandler(handler)
|
||||||
|
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
handlers = log.handlers[:]
|
||||||
|
for handler in handlers:
|
||||||
|
handler.close()
|
||||||
|
log.removeHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(Texts().get('Starting...'))
|
||||||
|
|
||||||
|
app = TuxBot()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with setup_logging():
|
||||||
|
app.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
app.close()
|
||||||
|
|
BIN
cogs/.DS_Store
vendored
BIN
cogs/.DS_Store
vendored
Binary file not shown.
56
cogs/API.py
Normal file
56
cogs/API.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from aiohttp import web
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from bot import TuxBot
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class API(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot: TuxBot):
|
||||||
|
self.bot = bot
|
||||||
|
self.site = web.TCPSite
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
app.add_routes([web.get('/users/{user_id}', self.users)])
|
||||||
|
|
||||||
|
self.runner = web.AppRunner(app)
|
||||||
|
self.bot.loop.create_task(self.start_HTTPMonitoring_server())
|
||||||
|
|
||||||
|
async def start_HTTPMonitoring_server(self):
|
||||||
|
host = self.bot.config.get('API', 'Host')
|
||||||
|
port = self.bot.config.get('API', 'Port')
|
||||||
|
|
||||||
|
print(f"Starting API server on {host}:{port}")
|
||||||
|
|
||||||
|
await self.runner.setup()
|
||||||
|
self.site = web.TCPSite(self.runner, host, port)
|
||||||
|
await self.site.start()
|
||||||
|
|
||||||
|
async def users(self, request):
|
||||||
|
try:
|
||||||
|
user = await self.bot.fetch_user(request.match_info['user_id'])
|
||||||
|
except discord.NotFound:
|
||||||
|
return web.Response(status=404)
|
||||||
|
|
||||||
|
json = {
|
||||||
|
'id': user.id,
|
||||||
|
'username': user.name,
|
||||||
|
'discriminator': user.discriminator,
|
||||||
|
'avatar': user.avatar,
|
||||||
|
'default_avatar': user.default_avatar.value,
|
||||||
|
'bot': user.bot,
|
||||||
|
'system': user.system,
|
||||||
|
}
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
json
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: TuxBot):
|
||||||
|
bot.add_cog(API(bot))
|
534
cogs/Admin.py
Normal file
534
cogs/Admin.py
Normal file
|
@ -0,0 +1,534 @@
|
||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import discord
|
||||||
|
import humanize
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from bot import TuxBot
|
||||||
|
from utils import Texts
|
||||||
|
from utils.models import WarnModel
|
||||||
|
from utils import command_extra, group_extra
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Admin(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot: TuxBot):
|
||||||
|
self.bot = bot
|
||||||
|
self.icon = ":shield:"
|
||||||
|
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/160/twitter/233/shield_1f6e1.png"
|
||||||
|
|
||||||
|
async def cog_check(self, ctx: commands.Context) -> bool:
|
||||||
|
permissions: discord.Permissions = ctx.channel.permissions_for(
|
||||||
|
ctx.author)
|
||||||
|
|
||||||
|
has_permission = permissions.administrator
|
||||||
|
is_owner = await self.bot.is_owner(ctx.author)
|
||||||
|
|
||||||
|
return has_permission or is_owner
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def kick_ban_message(ctx: commands.Context,
|
||||||
|
**kwargs) -> discord.Embed:
|
||||||
|
member: discord.Member = kwargs.get('member')
|
||||||
|
reason = kwargs.get(
|
||||||
|
'reason',
|
||||||
|
Texts('admin', ctx).get("Please enter a reason")
|
||||||
|
)
|
||||||
|
|
||||||
|
if kwargs.get('type') == 'ban':
|
||||||
|
title = '**Ban** ' + str(len(await ctx.guild.bans()))
|
||||||
|
color = discord.Color.dark_red()
|
||||||
|
else:
|
||||||
|
title = '**Kick**'
|
||||||
|
color = discord.Color.red()
|
||||||
|
e = discord.Embed(
|
||||||
|
title=title,
|
||||||
|
description=reason,
|
||||||
|
timestamp=datetime.datetime.utcnow(),
|
||||||
|
color=color
|
||||||
|
)
|
||||||
|
e.set_author(
|
||||||
|
name=f'{member.name}#{member.discriminator} ({member.id})',
|
||||||
|
icon_url=member.avatar_url_as(format='jpg')
|
||||||
|
)
|
||||||
|
e.set_footer(
|
||||||
|
text=f'{ctx.author.name}#{ctx.author.discriminator}',
|
||||||
|
icon_url=ctx.author.avatar_url_as(format='png')
|
||||||
|
)
|
||||||
|
|
||||||
|
return e
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@group_extra(name='say', invoke_without_command=True, category='text')
|
||||||
|
async def _say(self, ctx: commands.Context, *, content: str):
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
try:
|
||||||
|
await ctx.message.delete()
|
||||||
|
except discord.errors.Forbidden:
|
||||||
|
pass
|
||||||
|
|
||||||
|
await ctx.send(content)
|
||||||
|
|
||||||
|
@_say.command(name='edit')
|
||||||
|
async def _say_edit(self, ctx: commands.Context, message_id: int, *,
|
||||||
|
content: str):
|
||||||
|
try:
|
||||||
|
await ctx.message.delete()
|
||||||
|
except discord.errors.Forbidden:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
message: discord.Message = await ctx.channel.fetch_message(
|
||||||
|
message_id)
|
||||||
|
await message.edit(content=content)
|
||||||
|
except (discord.errors.NotFound, discord.errors.Forbidden):
|
||||||
|
await ctx.send(
|
||||||
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
|
delete_after=5)
|
||||||
|
|
||||||
|
@_say.command(name='to')
|
||||||
|
async def _say_to(self, ctx: commands.Context,
|
||||||
|
channel: Union[discord.TextChannel, discord.User], *,
|
||||||
|
content):
|
||||||
|
try:
|
||||||
|
await ctx.message.delete()
|
||||||
|
except discord.errors.Forbidden:
|
||||||
|
pass
|
||||||
|
|
||||||
|
await channel.send(content)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='ban', category='administration')
|
||||||
|
async def _ban(self, ctx: commands.Context, user: discord.Member, *,
|
||||||
|
reason=""):
|
||||||
|
try:
|
||||||
|
member: discord.Member = await ctx.guild.fetch_member(user.id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await member.ban(reason=reason)
|
||||||
|
e: discord.Embed = await self.kick_ban_message(
|
||||||
|
ctx,
|
||||||
|
member=member,
|
||||||
|
type='ban',
|
||||||
|
reason=reason
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
except discord.Forbidden:
|
||||||
|
await ctx.send(
|
||||||
|
Texts('admin', ctx).get("Unable to ban this user"),
|
||||||
|
delete_after=5)
|
||||||
|
except discord.errors.NotFound:
|
||||||
|
await ctx.send(
|
||||||
|
Texts('utils', ctx).get("Unable to find the user..."),
|
||||||
|
delete_after=5)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='kick', category='administration')
|
||||||
|
async def _kick(self, ctx: commands.Context, user: discord.Member, *,
|
||||||
|
reason=""):
|
||||||
|
try:
|
||||||
|
member: discord.Member = await ctx.guild.fetch_member(user.id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await member.kick(reason=reason)
|
||||||
|
e: discord.Embed = await self.kick_ban_message(
|
||||||
|
ctx,
|
||||||
|
member=member,
|
||||||
|
type='kick',
|
||||||
|
reason=reason
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
except discord.Forbidden:
|
||||||
|
await ctx.send(
|
||||||
|
Texts('admin', ctx).get("Unable to kick this user"),
|
||||||
|
delete_after=5)
|
||||||
|
except discord.errors.NotFound:
|
||||||
|
await ctx.send(
|
||||||
|
Texts('utils', ctx).get("Unable to find the user..."),
|
||||||
|
delete_after=5)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='clear', category='text')
|
||||||
|
async def _clear(self, ctx: commands.Context, count: int):
|
||||||
|
try:
|
||||||
|
await ctx.message.delete()
|
||||||
|
await ctx.channel.purge(limit=count)
|
||||||
|
except discord.errors.Forbidden:
|
||||||
|
pass
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@group_extra(name='react', category='text')
|
||||||
|
async def _react(self, ctx: commands.Context):
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
await ctx.send_help('react')
|
||||||
|
|
||||||
|
@_react.command(name='add')
|
||||||
|
async def _react_add(self, ctx: commands.Context, message_id: int, *,
|
||||||
|
emojis: str):
|
||||||
|
emojis: list = emojis.split(' ')
|
||||||
|
|
||||||
|
try:
|
||||||
|
message: discord.Message = await ctx.channel.fetch_message(
|
||||||
|
message_id)
|
||||||
|
|
||||||
|
for emoji in emojis:
|
||||||
|
await message.add_reaction(emoji)
|
||||||
|
except discord.errors.NotFound:
|
||||||
|
await ctx.send(
|
||||||
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
|
delete_after=5)
|
||||||
|
|
||||||
|
@_react.command(name='remove', aliases=['clear'])
|
||||||
|
async def _react_remove(self, ctx: commands.Context, message_id: int):
|
||||||
|
try:
|
||||||
|
message: discord.Message = await ctx.channel.fetch_message(
|
||||||
|
message_id)
|
||||||
|
await message.clear_reactions()
|
||||||
|
except discord.errors.NotFound:
|
||||||
|
await ctx.send(
|
||||||
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
|
delete_after=5)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@group_extra(name='delete', invoke_without_command=True, category='text')
|
||||||
|
async def _delete(self, ctx: commands.Context, message_id: int):
|
||||||
|
try:
|
||||||
|
await ctx.message.delete()
|
||||||
|
except discord.errors.Forbidden:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
message: discord.Message = await ctx.channel.fetch_message(
|
||||||
|
message_id)
|
||||||
|
await message.delete()
|
||||||
|
except (discord.errors.NotFound, discord.errors.Forbidden):
|
||||||
|
await ctx.send(
|
||||||
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
|
delete_after=5)
|
||||||
|
|
||||||
|
@_delete.command(name='from', aliases=['to', 'in'])
|
||||||
|
async def _delete_from(self, ctx: commands.Context,
|
||||||
|
channel: discord.TextChannel, message_id: int):
|
||||||
|
try:
|
||||||
|
await ctx.message.delete()
|
||||||
|
except discord.errors.Forbidden:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
message: discord.Message = await channel.fetch_message(
|
||||||
|
message_id
|
||||||
|
)
|
||||||
|
await message.delete()
|
||||||
|
except (discord.errors.NotFound, discord.errors.Forbidden):
|
||||||
|
await ctx.send(
|
||||||
|
Texts('utils', ctx).get("Unable to find the message"),
|
||||||
|
delete_after=5
|
||||||
|
)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
async def get_warn(self, ctx: commands.Context,
|
||||||
|
member: discord.Member = False):
|
||||||
|
await ctx.trigger_typing()
|
||||||
|
|
||||||
|
if member:
|
||||||
|
warns = WarnModel.objects.filter(
|
||||||
|
server_id=str(ctx.guild.id),
|
||||||
|
user_id=member.id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
warns = WarnModel.objects.filter(
|
||||||
|
server_id=str(ctx.guild.id)
|
||||||
|
)
|
||||||
|
warns_list = ''
|
||||||
|
|
||||||
|
for warn in await warns.all():
|
||||||
|
row_id = warn.id
|
||||||
|
user_id = warn.user_id
|
||||||
|
user = await self.bot.fetch_user(user_id)
|
||||||
|
reason = warn.reason
|
||||||
|
ago = humanize.naturaldelta(
|
||||||
|
datetime.datetime.now() - warn.created_at
|
||||||
|
)
|
||||||
|
|
||||||
|
warns_list += f"[{row_id}] **{user}**: `{reason}` *({ago} ago)*\n"
|
||||||
|
|
||||||
|
return warns_list, warns
|
||||||
|
|
||||||
|
async def add_warn(self, ctx: commands.Context, member: discord.Member,
|
||||||
|
reason):
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
warn = WarnModel(server_id=ctx.guild.id, user_id=member.id,
|
||||||
|
reason=reason,
|
||||||
|
created_at=now)
|
||||||
|
|
||||||
|
self.bot.database.session.add(warn)
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
|
||||||
|
@group_extra(name='warn', aliases=['warns'], category='administration')
|
||||||
|
async def _warn(self, ctx: commands.Context):
|
||||||
|
await ctx.trigger_typing()
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
warns_list, warns = await self.get_warn(ctx)
|
||||||
|
e = discord.Embed(
|
||||||
|
title=f"{warns.count()} {Texts('admin', ctx).get('last warns')}: ",
|
||||||
|
description=warns_list
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
@_warn.command(name='add', aliases=['new'])
|
||||||
|
async def _warn_add(self, ctx: commands.Context, member: discord.Member,
|
||||||
|
*, reason="N/A"):
|
||||||
|
if not member:
|
||||||
|
return await ctx.send(
|
||||||
|
Texts('utils', ctx).get("Unable to find the user...")
|
||||||
|
)
|
||||||
|
|
||||||
|
def check(pld: discord.RawReactionActionEvent):
|
||||||
|
if pld.message_id != choice.id \
|
||||||
|
or pld.user_id != ctx.author.id:
|
||||||
|
return False
|
||||||
|
return pld.emoji.name in ('1⃣', '2⃣', '3⃣')
|
||||||
|
|
||||||
|
warns_list, warns = await self.get_warn(ctx, member)
|
||||||
|
|
||||||
|
if warns.count() >= 2:
|
||||||
|
e = discord.Embed(
|
||||||
|
title=Texts('admin', ctx).get('More than 2 warns'),
|
||||||
|
description=f"{member.mention} "
|
||||||
|
+ Texts('admin', ctx).get('has more than 2 warns')
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name='__Actions__',
|
||||||
|
value=':one: kick\n'
|
||||||
|
':two: ban\n'
|
||||||
|
':three: ' + Texts('admin', ctx).get('ignore')
|
||||||
|
)
|
||||||
|
|
||||||
|
choice = await ctx.send(embed=e)
|
||||||
|
|
||||||
|
for reaction in ('1⃣', '2⃣', '3⃣'):
|
||||||
|
await choice.add_reaction(reaction)
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = await self.bot.wait_for(
|
||||||
|
'raw_reaction_add',
|
||||||
|
check=check,
|
||||||
|
timeout=50.0
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
return await ctx.send(
|
||||||
|
Texts('admin', ctx).get('Took too long. Aborting.')
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
await choice.delete()
|
||||||
|
|
||||||
|
if payload.emoji.name == '1⃣':
|
||||||
|
from jishaku.models import copy_context_with
|
||||||
|
|
||||||
|
alt_ctx = await copy_context_with(
|
||||||
|
ctx,
|
||||||
|
content=f"{ctx.prefix}"
|
||||||
|
f"kick "
|
||||||
|
f"{member} "
|
||||||
|
f"{Texts('admin', ctx).get('More than 2 warns')}"
|
||||||
|
)
|
||||||
|
return await alt_ctx.command.invoke(alt_ctx)
|
||||||
|
|
||||||
|
elif payload.emoji.name == '2⃣':
|
||||||
|
from jishaku.models import copy_context_with
|
||||||
|
|
||||||
|
alt_ctx = await copy_context_with(
|
||||||
|
ctx,
|
||||||
|
content=f"{ctx.prefix}"
|
||||||
|
f"ban "
|
||||||
|
f"{member} "
|
||||||
|
f"{Texts('admin', ctx).get('More than 2 warns')}"
|
||||||
|
)
|
||||||
|
return await alt_ctx.command.invoke(alt_ctx)
|
||||||
|
|
||||||
|
await self.add_warn(ctx, member, reason)
|
||||||
|
await ctx.send(
|
||||||
|
content=f"{member.mention} "
|
||||||
|
f"**{Texts('admin', ctx).get('got a warn')}**"
|
||||||
|
f"\n**{Texts('admin', ctx).get('Reason')}:** `{reason}`"
|
||||||
|
)
|
||||||
|
|
||||||
|
@_warn.command(name='remove', aliases=['revoke', 'del', 'delete'])
|
||||||
|
async def _warn_remove(self, ctx: commands.Context, warn_id: int):
|
||||||
|
warn = self.bot.database.session \
|
||||||
|
.query(WarnModel) \
|
||||||
|
.filter(WarnModel.id == warn_id) \
|
||||||
|
.one()
|
||||||
|
|
||||||
|
self.bot.database.session.delete(warn)
|
||||||
|
|
||||||
|
await ctx.send(f"{Texts('admin', ctx).get('Warn with id')} `{warn_id}`"
|
||||||
|
f" {Texts('admin', ctx).get('successfully removed')}")
|
||||||
|
|
||||||
|
@_warn.command(name='show', aliases=['list', 'all'])
|
||||||
|
async def _warn_show(self, ctx: commands.Context, member: discord.Member):
|
||||||
|
warns_list, warns = await self.get_warn(ctx, member)
|
||||||
|
|
||||||
|
e = discord.Embed(
|
||||||
|
title=f"{warns.count()} {Texts('admin', ctx).get('last warns')}: ",
|
||||||
|
description=warns_list
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
@_warn.command(name='edit', aliases=['change', 'modify'])
|
||||||
|
async def _warn_edit(self, ctx: commands.Context, warn_id: int, *, reason):
|
||||||
|
warn = self.bot.database.session \
|
||||||
|
.query(WarnModel) \
|
||||||
|
.filter(WarnModel.id == warn_id) \
|
||||||
|
.one()
|
||||||
|
warn.reason = reason
|
||||||
|
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
|
||||||
|
await ctx.send(f"{Texts('admin', ctx).get('Warn with id')} `{warn_id}`"
|
||||||
|
f" {Texts('admin', ctx).get('successfully edited')}")
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='language', aliases=['lang', 'langue', 'langage'], category='server')
|
||||||
|
async def _language(self, ctx: commands.Context, locale: str):
|
||||||
|
available = self.bot.database.session \
|
||||||
|
.query(LangModel.value) \
|
||||||
|
.filter(LangModel.key == 'available') \
|
||||||
|
.first()[0] \
|
||||||
|
.split(',')
|
||||||
|
|
||||||
|
if locale.lower() not in available:
|
||||||
|
await ctx.send(
|
||||||
|
Texts('admin', ctx).get('Unable to find this language'))
|
||||||
|
else:
|
||||||
|
current = self.bot.database.session \
|
||||||
|
.query(LangModel) \
|
||||||
|
.filter(LangModel.key == str(ctx.guild.id))
|
||||||
|
|
||||||
|
if current.count() > 0:
|
||||||
|
current = current.one()
|
||||||
|
current.value = locale.lower()
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
else:
|
||||||
|
new_row = LangModel(key=str(ctx.guild.id),
|
||||||
|
value=locale.lower())
|
||||||
|
self.bot.database.session.add(new_row)
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
Texts('admin', ctx).get('Language changed successfully'))
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@group_extra(name='prefix', aliases=['prefixes'], category='server')
|
||||||
|
async def _prefix(self, ctx: commands.Context):
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
await ctx.send_help('prefix')
|
||||||
|
|
||||||
|
@_prefix.command(name='add', aliases=['set', 'new'])
|
||||||
|
async def _prefix_add(self, ctx: commands.Context, prefix: str):
|
||||||
|
if str(ctx.guild.id) in self.bot.prefixes:
|
||||||
|
prefixes = self.bot.prefixes.get(
|
||||||
|
str(ctx.guild.id), "prefixes"
|
||||||
|
).split(
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
)
|
||||||
|
|
||||||
|
if prefix in prefixes:
|
||||||
|
return await ctx.send(
|
||||||
|
Texts('admin', ctx).get('This prefix already exists')
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
prefixes.append(prefix)
|
||||||
|
self.bot.prefixes.set(
|
||||||
|
str(ctx.guild.id),
|
||||||
|
"prefixes",
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
.join(prefixes)
|
||||||
|
)
|
||||||
|
with open('./configs/prefixes.cfg', 'w') as configfile:
|
||||||
|
self.bot.prefixes.write(configfile)
|
||||||
|
else:
|
||||||
|
self.bot.prefixes.add_section(str(ctx.guild.id))
|
||||||
|
self.bot.prefixes.set(str(ctx.guild.id), "prefixes", prefix)
|
||||||
|
with open('./configs/prefixes.cfg', 'w') as configfile:
|
||||||
|
self.bot.prefixes.write(configfile)
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
Texts('admin', ctx).get('Prefix added successfully')
|
||||||
|
)
|
||||||
|
|
||||||
|
@_prefix.command(name='remove', aliases=['drop', 'del', 'delete'])
|
||||||
|
async def _prefix_remove(self, ctx: commands.Context, prefix: str):
|
||||||
|
if str(ctx.guild.id) in self.bot.prefixes:
|
||||||
|
prefixes = self.bot.prefixes.get(
|
||||||
|
str(ctx.guild.id), "prefixes"
|
||||||
|
).split(
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
)
|
||||||
|
|
||||||
|
if prefix in prefixes:
|
||||||
|
prefixes.remove(prefix)
|
||||||
|
self.bot.prefixes.set(
|
||||||
|
str(ctx.guild.id),
|
||||||
|
"prefixes",
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
.join(prefixes)
|
||||||
|
)
|
||||||
|
with open('./configs/prefixes.cfg', 'w') as configfile:
|
||||||
|
self.bot.prefixes.write(configfile)
|
||||||
|
|
||||||
|
return await ctx.send(
|
||||||
|
Texts('admin', ctx).get('Prefix removed successfully')
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
Texts('admin', ctx).get('This prefix does not exist')
|
||||||
|
)
|
||||||
|
|
||||||
|
@_prefix.command(name='list', aliases=['show', 'all'])
|
||||||
|
async def _prefix_list(self, ctx: commands.Context):
|
||||||
|
extras = ['.']
|
||||||
|
if ctx.message.guild is not None:
|
||||||
|
extras = []
|
||||||
|
if str(ctx.message.guild.id) in self.bot.prefixes:
|
||||||
|
extras.extend(
|
||||||
|
self.bot.prefixes.get(str(ctx.message.guild.id),
|
||||||
|
"prefixes").split(
|
||||||
|
self.bot.config.get("misc", "separator")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
prefixes = [self.bot.user.mention]
|
||||||
|
prefixes.extend(extras)
|
||||||
|
|
||||||
|
if len(prefixes) <= 1:
|
||||||
|
text = Texts('admin', ctx) \
|
||||||
|
.get('The only prefix for this guild is :\n')
|
||||||
|
else:
|
||||||
|
text = Texts('admin', ctx) \
|
||||||
|
.get('Available prefixes for this guild are :\n')
|
||||||
|
|
||||||
|
await ctx.send(text + "\n • ".join(prefixes))
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: TuxBot):
|
||||||
|
bot.add_cog(Admin(bot))
|
227
cogs/Help.py
Normal file
227
cogs/Help.py
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
# Created by romain at 04/01/2020
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from bot import TuxBot
|
||||||
|
from utils import Texts, GroupPlus
|
||||||
|
from utils import FieldPages
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HelpCommand(commands.HelpCommand):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.ignore_cogs = ["Monitoring", "Help", "Jishaku"]
|
||||||
|
self.owner_cogs = []
|
||||||
|
self.admin_cogs = ["Admin"]
|
||||||
|
|
||||||
|
def command_formatting(self, e, command):
|
||||||
|
prefix = self.context.prefix \
|
||||||
|
if str(self.context.bot.user.id) not in self.context.prefix \
|
||||||
|
else f"@{self.context.bot.user.name}"
|
||||||
|
file = Texts(command.cog.qualified_name.lower() + '_help', self.context)
|
||||||
|
|
||||||
|
if command.parent is not None:
|
||||||
|
description = file.get(f"_{command.parent}_{command.name}")
|
||||||
|
usage = file.get(f"_{command.parent}_{command.name}__usage")
|
||||||
|
else:
|
||||||
|
description = file.get(f"_{command.name}")
|
||||||
|
usage = file.get(f"_{command.name}__usage")
|
||||||
|
|
||||||
|
e.title = self.get_command_signature(command) + usage
|
||||||
|
e.description = description
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=Texts(
|
||||||
|
'help', self.context
|
||||||
|
).get(
|
||||||
|
'command_help.params'
|
||||||
|
),
|
||||||
|
value=usage
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name=Texts(
|
||||||
|
'help', self.context
|
||||||
|
).get(
|
||||||
|
'command_help.usage'
|
||||||
|
),
|
||||||
|
value=f"{prefix}{command.qualified_name} " + usage
|
||||||
|
)
|
||||||
|
|
||||||
|
aliases = "`" + '`, `'.join(command.aliases) + "`"
|
||||||
|
if aliases == "``":
|
||||||
|
aliases = Texts(
|
||||||
|
'help', self.context
|
||||||
|
).get(
|
||||||
|
'command_help.no_aliases'
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name=Texts(
|
||||||
|
'help', self.context
|
||||||
|
).get(
|
||||||
|
'command_help.aliases'
|
||||||
|
),
|
||||||
|
value=aliases
|
||||||
|
)
|
||||||
|
|
||||||
|
return e
|
||||||
|
|
||||||
|
async def send_bot_help(self, mapping):
|
||||||
|
owners = self.context.bot.owners
|
||||||
|
prefix = self.context.prefix \
|
||||||
|
if str(self.context.bot.user.id) not in self.context.prefix \
|
||||||
|
else f"@{self.context.bot.user.name} "
|
||||||
|
|
||||||
|
e = discord.Embed(
|
||||||
|
color=discord.Color.blue(),
|
||||||
|
description=Texts(
|
||||||
|
'help', self.context
|
||||||
|
).get(
|
||||||
|
'main_page.description'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
e.set_author(
|
||||||
|
icon_url=self.context.author.avatar_url_as(format='png'),
|
||||||
|
name=self.context.author
|
||||||
|
)
|
||||||
|
e.set_footer(
|
||||||
|
text=Texts(
|
||||||
|
'help', self.context
|
||||||
|
).get(
|
||||||
|
'main_page.footer'
|
||||||
|
).format(
|
||||||
|
prefix
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for extension in self.context.bot.cogs.values():
|
||||||
|
if self.context.author not in owners \
|
||||||
|
and extension.__class__.__name__ in self.owner_cogs:
|
||||||
|
continue
|
||||||
|
if extension.__class__.__name__ in self.ignore_cogs:
|
||||||
|
continue
|
||||||
|
|
||||||
|
count = len(extension.get_commands())
|
||||||
|
text = Texts('help', self.context).get('main_page.commands')
|
||||||
|
|
||||||
|
if count <= 1:
|
||||||
|
text = text[:-1]
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=f"__{extension.icon} **{extension.qualified_name}**__",
|
||||||
|
value=f"{count} {text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.context.send(embed=e)
|
||||||
|
|
||||||
|
async def send_cog_help(self, cog):
|
||||||
|
pages = {}
|
||||||
|
prefix = self.context.prefix \
|
||||||
|
if str(self.context.bot.user.id) not in self.context.prefix \
|
||||||
|
else f"@{self.context.bot.user.name}"
|
||||||
|
file = Texts(cog.qualified_name.lower() + '_help', self.context)
|
||||||
|
|
||||||
|
if cog.__class__.__name__ in self.owner_cogs \
|
||||||
|
and self.context.author not in self.context.bot.owners:
|
||||||
|
return self.command_not_found(cog.qualified_name)
|
||||||
|
|
||||||
|
for cmd in cog.get_commands():
|
||||||
|
if self.context.author not in self.context.bot.owners \
|
||||||
|
and (cmd.hidden or cmd.category == "Hidden"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cmd.category not in pages:
|
||||||
|
pages[cmd.category] = "```asciidoc\n"
|
||||||
|
|
||||||
|
pages[cmd.category] \
|
||||||
|
+= f"{cmd.name}" \
|
||||||
|
+ ' ' * int(13 - len(cmd.name)) \
|
||||||
|
+ f":: {file.get(f'_{cmd.name}__short')}\n"
|
||||||
|
|
||||||
|
if isinstance(cmd, GroupPlus):
|
||||||
|
for group_command in cmd.commands:
|
||||||
|
pages[cmd.category] \
|
||||||
|
+= f"└> {group_command.name}" \
|
||||||
|
+ ' ' * int(10 - len(group_command.name)) \
|
||||||
|
+ f":: {file.get(f'_{group_command.parent}_{group_command.name}__short')}\n"
|
||||||
|
for e in pages:
|
||||||
|
pages[e] += "```"
|
||||||
|
formatted = []
|
||||||
|
for name, cont in pages.items():
|
||||||
|
formatted.append((name, cont))
|
||||||
|
footer_text = Texts('help', self.context) \
|
||||||
|
.get('main_page.footer') \
|
||||||
|
.format(prefix)
|
||||||
|
|
||||||
|
pages = FieldPages(
|
||||||
|
self.context,
|
||||||
|
embed_color=discord.Color.blue(),
|
||||||
|
entries=formatted,
|
||||||
|
title=cog.qualified_name.upper(),
|
||||||
|
thumbnail=cog.big_icon,
|
||||||
|
footericon=self.context.bot.user.avatar_url,
|
||||||
|
footertext=footer_text,
|
||||||
|
per_page=1
|
||||||
|
)
|
||||||
|
await pages.paginate()
|
||||||
|
|
||||||
|
async def send_group_help(self, group):
|
||||||
|
if group.cog_name in self.ignore_cogs:
|
||||||
|
return await self.send_error_message(
|
||||||
|
self.command_not_found(group.name)
|
||||||
|
)
|
||||||
|
file = Texts(group.qualified_name.lower() + '_help', self.context)
|
||||||
|
|
||||||
|
formatted = self.command_formatting(
|
||||||
|
discord.Embed(color=discord.Color.blue()),
|
||||||
|
group
|
||||||
|
)
|
||||||
|
sub_command_list = "⠀" # this is braille, please don't touch unless you know what you're doing
|
||||||
|
for group_command in group.commands:
|
||||||
|
sub_command_list += f"└> **{group_command.name}** - {file.get(f'_{group_command.parent}_{group_command.name}')}\n"
|
||||||
|
|
||||||
|
subcommands = Texts(
|
||||||
|
'help', self.context
|
||||||
|
).get(
|
||||||
|
'command_help.subcommands'
|
||||||
|
)
|
||||||
|
|
||||||
|
formatted.add_field(name=subcommands, value=sub_command_list, inline=False)
|
||||||
|
await self.context.send(embed=formatted)
|
||||||
|
|
||||||
|
async def send_command_help(self, command):
|
||||||
|
if isinstance(command, commands.Group):
|
||||||
|
return await self.send_group_help(command)
|
||||||
|
|
||||||
|
if command.cog_name in self.ignore_cogs:
|
||||||
|
return await self.send_error_message(
|
||||||
|
self.command_not_found(command.name))
|
||||||
|
|
||||||
|
formatted = self.command_formatting(
|
||||||
|
discord.Embed(color=discord.Color.blue()),
|
||||||
|
command
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.context.send(embed=formatted)
|
||||||
|
|
||||||
|
def command_not_found(self, command):
|
||||||
|
return Texts(
|
||||||
|
'help', self.context
|
||||||
|
).get(
|
||||||
|
'main_page.not_found'
|
||||||
|
).format(
|
||||||
|
command
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Help(commands.Cog):
|
||||||
|
def __init__(self, bot: TuxBot):
|
||||||
|
bot.help_command = HelpCommand()
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: TuxBot):
|
||||||
|
bot.add_cog(Help(bot))
|
304
cogs/Logs.py
Normal file
304
cogs/Logs.py
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
"""
|
||||||
|
|
||||||
|
Based on https://github.com/Rapptz/RoboDanny/blob/3d94e89ef27f702a5f57f432a9131bdfb60bb3ec/cogs/stats.py
|
||||||
|
Adapted by Romain J.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import textwrap
|
||||||
|
import traceback
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import discord
|
||||||
|
import humanize
|
||||||
|
import psutil
|
||||||
|
from discord.ext import commands, tasks
|
||||||
|
|
||||||
|
from bot import TuxBot
|
||||||
|
from utils import Texts
|
||||||
|
from utils import command_extra
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GatewayHandler(logging.Handler):
|
||||||
|
def __init__(self, cog):
|
||||||
|
self.cog = cog
|
||||||
|
super().__init__(logging.INFO)
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
return record.name == 'discord.gateway' \
|
||||||
|
or 'Shard ID' in record.msg \
|
||||||
|
or 'Websocket closed ' in record.msg
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
self.cog.add_record(record)
|
||||||
|
|
||||||
|
|
||||||
|
class Logs(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot: TuxBot):
|
||||||
|
self.bot = bot
|
||||||
|
self.process = psutil.Process()
|
||||||
|
self._batch_lock = asyncio.Lock(loop=bot.loop)
|
||||||
|
self._data_batch = []
|
||||||
|
self._gateway_queue = asyncio.Queue(loop=bot.loop)
|
||||||
|
self.gateway_worker.start()
|
||||||
|
|
||||||
|
self._resumes = []
|
||||||
|
self._identifies = defaultdict(list)
|
||||||
|
|
||||||
|
self.icon = ":newspaper:"
|
||||||
|
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/newspaper_1f4f0.png"
|
||||||
|
|
||||||
|
def _clear_gateway_data(self):
|
||||||
|
one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
|
||||||
|
to_remove = [
|
||||||
|
index for index, dt in enumerate(self._resumes)
|
||||||
|
if dt < one_week_ago
|
||||||
|
]
|
||||||
|
for index in reversed(to_remove):
|
||||||
|
del self._resumes[index]
|
||||||
|
|
||||||
|
for shard_id, dates in self._identifies.items():
|
||||||
|
to_remove = [index for index, dt in enumerate(dates) if
|
||||||
|
dt < one_week_ago]
|
||||||
|
for index in reversed(to_remove):
|
||||||
|
del dates[index]
|
||||||
|
|
||||||
|
@tasks.loop(seconds=0.0)
|
||||||
|
async def gateway_worker(self):
|
||||||
|
record = await self._gateway_queue.get()
|
||||||
|
await self.notify_gateway_status(record)
|
||||||
|
|
||||||
|
async def register_command(self, ctx):
|
||||||
|
if ctx.command is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
command = ctx.command.qualified_name
|
||||||
|
self.bot.command_stats[command] += 1
|
||||||
|
message = ctx.message
|
||||||
|
if ctx.guild is None:
|
||||||
|
destination = 'Private Message'
|
||||||
|
guild_id = None
|
||||||
|
else:
|
||||||
|
destination = f'#{message.channel} ({message.guild})'
|
||||||
|
guild_id = ctx.guild.id
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
f'{message.created_at}: {message.author} '
|
||||||
|
f'in {destination}: {message.content}')
|
||||||
|
async with self._batch_lock:
|
||||||
|
self._data_batch.append({
|
||||||
|
'guild': guild_id,
|
||||||
|
'channel': ctx.channel.id,
|
||||||
|
'author': ctx.author.id,
|
||||||
|
'used': message.created_at.isoformat(),
|
||||||
|
'prefix': ctx.prefix,
|
||||||
|
'command': command,
|
||||||
|
'failed': ctx.command_failed,
|
||||||
|
})
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_command_completion(self, ctx):
|
||||||
|
await self.register_command(ctx)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_socket_response(self, msg):
|
||||||
|
self.bot.socket_stats[msg.get('t')] += 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def webhook(self):
|
||||||
|
return self.bot.logs_webhook
|
||||||
|
|
||||||
|
async def log_error(self, *, ctx=None, extra=None):
|
||||||
|
e = discord.Embed(title='Error', colour=0xdd5f53)
|
||||||
|
e.description = f'```py\n{traceback.format_exc()}\n```'
|
||||||
|
e.add_field(name='Extra', value=extra, inline=False)
|
||||||
|
e.timestamp = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
if ctx is not None:
|
||||||
|
fmt = '{0} (ID: {0.id})'
|
||||||
|
author = fmt.format(ctx.author)
|
||||||
|
channel = fmt.format(ctx.channel)
|
||||||
|
guild = 'None' if ctx.guild is None else fmt.format(ctx.guild)
|
||||||
|
|
||||||
|
e.add_field(name='Author', value=author)
|
||||||
|
e.add_field(name='Channel', value=channel)
|
||||||
|
e.add_field(name='Guild', value=guild)
|
||||||
|
|
||||||
|
await self.webhook.send(embed=e)
|
||||||
|
|
||||||
|
async def send_guild_stats(self, e, guild):
|
||||||
|
e.add_field(name='Name', value=guild.name)
|
||||||
|
e.add_field(name='ID', value=guild.id)
|
||||||
|
e.add_field(name='Shard ID', value=guild.shard_id or 'N/A')
|
||||||
|
e.add_field(name='Owner',
|
||||||
|
value=f'{guild.owner} (ID: {guild.owner.id})')
|
||||||
|
|
||||||
|
bots = sum(member.bot for member in guild.members)
|
||||||
|
total = guild.member_count
|
||||||
|
online = sum(member.status is discord.Status.online
|
||||||
|
for member in guild.members)
|
||||||
|
|
||||||
|
e.add_field(name='Members', value=str(total))
|
||||||
|
e.add_field(name='Bots', value=f'{bots} ({bots / total:.2%})')
|
||||||
|
e.add_field(name='Online', value=f'{online} ({online / total:.2%})')
|
||||||
|
|
||||||
|
if guild.icon:
|
||||||
|
e.set_thumbnail(url=guild.icon_url)
|
||||||
|
|
||||||
|
if guild.me:
|
||||||
|
e.timestamp = guild.me.joined_at
|
||||||
|
|
||||||
|
await self.webhook.send(embed=e)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_guild_join(self, guild: discord.guild):
|
||||||
|
e = discord.Embed(colour=0x53dda4, title='New Guild') # green colour
|
||||||
|
await self.send_guild_stats(e, guild)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_guild_remove(self, guild: discord.guild):
|
||||||
|
e = discord.Embed(colour=0xdd5f53, title='Left Guild') # red colour
|
||||||
|
await self.send_guild_stats(e, guild)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_message(self, message: discord.message):
|
||||||
|
if message.guild is None:
|
||||||
|
e = discord.Embed(colour=0x0a97f5, title='New DM') # blue colour
|
||||||
|
e.set_author(
|
||||||
|
name=message.author,
|
||||||
|
icon_url=message.author.avatar_url_as(format='png')
|
||||||
|
)
|
||||||
|
e.description = message.content
|
||||||
|
if len(message.attachments) > 0:
|
||||||
|
e.set_image(url=message.attachments[0].url)
|
||||||
|
e.set_footer(text=f"User ID: {message.author.id}")
|
||||||
|
await self.webhook.send(embed=e)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_command_error(self, ctx, error):
|
||||||
|
await self.register_command(ctx)
|
||||||
|
if not isinstance(error, (
|
||||||
|
commands.CommandInvokeError, commands.ConversionError)):
|
||||||
|
return
|
||||||
|
|
||||||
|
error = error.original
|
||||||
|
if isinstance(error, (discord.Forbidden, discord.NotFound)):
|
||||||
|
return
|
||||||
|
|
||||||
|
e = discord.Embed(title='Command Error', colour=0xcc3366)
|
||||||
|
e.add_field(name='Name', value=ctx.command.qualified_name)
|
||||||
|
e.add_field(name='Author', value=f'{ctx.author} (ID: {ctx.author.id})')
|
||||||
|
|
||||||
|
fmt = f'Channel: {ctx.channel} (ID: {ctx.channel.id})'
|
||||||
|
if ctx.guild:
|
||||||
|
fmt = f'{fmt}\nGuild: {ctx.guild} (ID: {ctx.guild.id})'
|
||||||
|
|
||||||
|
e.add_field(name='Location', value=fmt, inline=False)
|
||||||
|
e.add_field(name='Content', value=textwrap.shorten(
|
||||||
|
ctx.message.content,
|
||||||
|
width=512
|
||||||
|
))
|
||||||
|
|
||||||
|
exc = ''.join(traceback.format_exception(
|
||||||
|
type(error), error, error.__traceback__,
|
||||||
|
chain=False)
|
||||||
|
)
|
||||||
|
e.description = f'```py\n{exc}\n```'
|
||||||
|
e.timestamp = datetime.datetime.utcnow()
|
||||||
|
await self.webhook.send(embed=e)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_socket_raw_send(self, data):
|
||||||
|
if '"op":2' not in data and '"op":6' not in data:
|
||||||
|
return
|
||||||
|
|
||||||
|
back_to_json = json.loads(data)
|
||||||
|
if back_to_json['op'] == 2:
|
||||||
|
payload = back_to_json['d']
|
||||||
|
inner_shard = payload.get('shard', [0])
|
||||||
|
self._identifies[inner_shard[0]].append(datetime.datetime.utcnow())
|
||||||
|
else:
|
||||||
|
self._resumes.append(datetime.datetime.utcnow())
|
||||||
|
|
||||||
|
self._clear_gateway_data()
|
||||||
|
|
||||||
|
def add_record(self, record):
|
||||||
|
self._gateway_queue.put_nowait(record)
|
||||||
|
|
||||||
|
async def notify_gateway_status(self, record):
|
||||||
|
types = {
|
||||||
|
'INFO': ':information_source:',
|
||||||
|
'WARNING': ':warning:'
|
||||||
|
}
|
||||||
|
|
||||||
|
emoji = types.get(record.levelname, ':heavy_multiplication_x:')
|
||||||
|
dt = datetime.datetime.utcfromtimestamp(record.created)
|
||||||
|
msg = f'{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`'
|
||||||
|
await self.webhook.send(msg)
|
||||||
|
|
||||||
|
@command_extra(name='commandstats', hidden=True, category='misc')
|
||||||
|
@commands.is_owner()
|
||||||
|
async def _commandstats(self, ctx, limit=20):
|
||||||
|
counter = self.bot.command_stats
|
||||||
|
width = len(max(counter, key=len))
|
||||||
|
|
||||||
|
if limit > 0:
|
||||||
|
common = counter.most_common(limit)
|
||||||
|
else:
|
||||||
|
common = counter.most_common()[limit:]
|
||||||
|
|
||||||
|
output = '\n'.join(f'{k:<{width}}: {c}' for k, c in common)
|
||||||
|
|
||||||
|
await ctx.send(f'```\n{output}\n```')
|
||||||
|
|
||||||
|
@command_extra(name='socketstats', hidden=True, category='misc')
|
||||||
|
@commands.is_owner()
|
||||||
|
async def _socketstats(self, ctx):
|
||||||
|
delta = datetime.datetime.utcnow() - self.bot.uptime
|
||||||
|
minutes = delta.total_seconds() / 60
|
||||||
|
total = sum(self.bot.socket_stats.values())
|
||||||
|
cpm = total / minutes
|
||||||
|
await ctx.send(
|
||||||
|
f'{total} socket events observed ({cpm:.2f}/minute):\n{self.bot.socket_stats}')
|
||||||
|
|
||||||
|
@command_extra(name='uptime', category='misc')
|
||||||
|
async def _uptime(self, ctx):
|
||||||
|
uptime = humanize.naturaltime(
|
||||||
|
datetime.datetime.utcnow() - self.bot.uptime)
|
||||||
|
await ctx.send(f'Uptime: **{uptime}**')
|
||||||
|
|
||||||
|
|
||||||
|
async def on_error(self, event, *args):
|
||||||
|
e = discord.Embed(title='Event Error', colour=0xa32952)
|
||||||
|
e.add_field(name='Event', value=event)
|
||||||
|
e.description = f'```py\n{traceback.format_exc()}\n```'
|
||||||
|
e.timestamp = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
args_str = ['```py']
|
||||||
|
for index, arg in enumerate(args):
|
||||||
|
args_str.append(f'[{index}]: {arg!r}')
|
||||||
|
args_str.append('```')
|
||||||
|
e.add_field(name='Args', value='\n'.join(args_str), inline=False)
|
||||||
|
|
||||||
|
hook = self.get_cog('Logs').webhook
|
||||||
|
try:
|
||||||
|
await hook.send(embed=e)
|
||||||
|
except (discord.HTTPException, discord.NotFound,
|
||||||
|
discord.Forbidden, discord.InvalidArgument):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: TuxBot):
|
||||||
|
cog = Logs(bot)
|
||||||
|
bot.add_cog(cog)
|
||||||
|
|
||||||
|
handler = GatewayHandler(cog)
|
||||||
|
logging.getLogger().addHandler(handler)
|
||||||
|
commands.AutoShardedBot.on_error = on_error
|
110
cogs/Monitoring.py
Normal file
110
cogs/Monitoring.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import logging
|
||||||
|
import urllib.request
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from aiohttp import web
|
||||||
|
from discord.ext import tasks, commands
|
||||||
|
|
||||||
|
from bot import TuxBot
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Monitoring(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot: TuxBot):
|
||||||
|
self.bot = bot
|
||||||
|
self.site = web.TCPSite
|
||||||
|
|
||||||
|
self.ping_clusters.start()
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
app.add_routes([web.get('/', self.handle)])
|
||||||
|
|
||||||
|
self.runner = web.AppRunner(app)
|
||||||
|
self.bot.loop.create_task(self.start_HTTPMonitoring_server())
|
||||||
|
|
||||||
|
def cog_unload(self):
|
||||||
|
self.ping_clusters.stop()
|
||||||
|
|
||||||
|
@tasks.loop(seconds=10.0)
|
||||||
|
async def ping_clusters(self):
|
||||||
|
for cluster in self.bot.fallbacks:
|
||||||
|
if cluster == 'DEFAULT':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
cluster = self.bot.fallbacks[cluster]
|
||||||
|
if not cluster.get('This', False):
|
||||||
|
host = cluster.get('Host')
|
||||||
|
port = cluster.get('Port')
|
||||||
|
|
||||||
|
try:
|
||||||
|
req = urllib.request.urlopen(
|
||||||
|
f"http://{host}:{port}",
|
||||||
|
timeout=2
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
global_channel = await self.bot.fetch_channel(
|
||||||
|
661347412463321098
|
||||||
|
)
|
||||||
|
|
||||||
|
e = discord.Embed(
|
||||||
|
title=f"Server `{cluster.get('Name')}`",
|
||||||
|
color=discord.colour.Color.red(),
|
||||||
|
description=f"Server **`{cluster.get('Name')}`** with address **`http://{host}:{port}`** is down ! ",
|
||||||
|
timestamp=datetime.now()
|
||||||
|
)
|
||||||
|
e.set_thumbnail(
|
||||||
|
url='https://upload.wikimedia.org/wikipedia/commons/7/75/Erroricon404.PNG'
|
||||||
|
)
|
||||||
|
|
||||||
|
await global_channel.send(embed=e)
|
||||||
|
else:
|
||||||
|
print(req.read().decode())
|
||||||
|
|
||||||
|
@ping_clusters.before_loop
|
||||||
|
async def before_pinging(self):
|
||||||
|
await self.bot.wait_until_ready()
|
||||||
|
|
||||||
|
cluster = self.bot.cluster
|
||||||
|
host = cluster.get('Host')
|
||||||
|
port = cluster.get('Port')
|
||||||
|
|
||||||
|
global_channel = await self.bot.fetch_channel(
|
||||||
|
661347412463321098
|
||||||
|
)
|
||||||
|
|
||||||
|
e = discord.Embed(
|
||||||
|
title=f"Server `{cluster.get('Name')}`",
|
||||||
|
color=discord.colour.Color.green(),
|
||||||
|
description=f"Server **`{cluster.get('Name')}`** with address **`http://{host}:{port}`** is started ! ",
|
||||||
|
timestamp=datetime.now()
|
||||||
|
)
|
||||||
|
e.set_thumbnail(
|
||||||
|
url='https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/MW-Icon-CheckMark.svg/1024px-MW-Icon-CheckMark.svg.png'
|
||||||
|
)
|
||||||
|
|
||||||
|
await global_channel.send(embed=e)
|
||||||
|
|
||||||
|
async def start_HTTPMonitoring_server(self):
|
||||||
|
host = self.bot.cluster.get('WebPage')
|
||||||
|
port = self.bot.cluster.get('Port')
|
||||||
|
|
||||||
|
print(f"Starting HTTP Monitoring server on {host}:{port}")
|
||||||
|
|
||||||
|
await self.runner.setup()
|
||||||
|
self.site = web.TCPSite(self.runner, host, port)
|
||||||
|
await self.site.start()
|
||||||
|
|
||||||
|
async def handle(self, _):
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
'message': "I'm alive !",
|
||||||
|
'ws': self.bot.latency * 1000
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: TuxBot):
|
||||||
|
bot.add_cog(Monitoring(bot))
|
222
cogs/Poll.py
Normal file
222
cogs/Poll.py
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
|
from bot import TuxBot
|
||||||
|
from utils import PollModel, ResponsesModel
|
||||||
|
from utils import Texts
|
||||||
|
from utils.functions import emotes as utils_emotes
|
||||||
|
from utils import group_extra
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Poll(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot: TuxBot):
|
||||||
|
self.bot = bot
|
||||||
|
self.icon = ":bar_chart:"
|
||||||
|
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/bar-chart_1f4ca.png:"
|
||||||
|
|
||||||
|
def get_poll(self, pld) -> Union[bool, PollModel]:
|
||||||
|
if pld.user_id != self.bot.user.id:
|
||||||
|
poll = self.bot.database.session \
|
||||||
|
.query(PollModel) \
|
||||||
|
.filter(PollModel.message_id == pld.message_id)
|
||||||
|
|
||||||
|
if poll.count() > 0:
|
||||||
|
poll = poll.one()
|
||||||
|
emotes = utils_emotes.get(poll.available_choices)
|
||||||
|
if pld.emoji.name in emotes:
|
||||||
|
return poll
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def remove_reaction(self, pld):
|
||||||
|
channel: discord.TextChannel = self.bot.get_channel(pld.channel_id)
|
||||||
|
message: discord.Message = await channel.fetch_message(pld.message_id)
|
||||||
|
user: discord.User = await self.bot.fetch_user(pld.user_id)
|
||||||
|
|
||||||
|
await message.remove_reaction(pld.emoji.name, user)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_raw_reaction_add(self, pld: discord.RawReactionActionEvent):
|
||||||
|
poll = self.get_poll(pld)
|
||||||
|
|
||||||
|
if poll:
|
||||||
|
if poll.is_anonymous:
|
||||||
|
try:
|
||||||
|
await self.remove_reaction(pld)
|
||||||
|
except discord.errors.Forbidden:
|
||||||
|
pass
|
||||||
|
choice = utils_emotes.get_index(pld.emoji.name)
|
||||||
|
|
||||||
|
responses = self.bot.database.session.query(ResponsesModel) \
|
||||||
|
.filter(
|
||||||
|
ResponsesModel.poll_id == poll.id,
|
||||||
|
ResponsesModel.user == pld.user_id,
|
||||||
|
ResponsesModel.choice == choice
|
||||||
|
)
|
||||||
|
|
||||||
|
if responses.count() != 0:
|
||||||
|
response = responses.first()
|
||||||
|
self.bot.database.session.delete(response)
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
else:
|
||||||
|
response = ResponsesModel(
|
||||||
|
user=pld.user_id,
|
||||||
|
poll_id=poll.id,
|
||||||
|
choice=choice
|
||||||
|
)
|
||||||
|
self.bot.database.session.add(response)
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
|
||||||
|
await self.update_poll(poll.id)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_raw_reaction_remove(self,
|
||||||
|
pld: discord.RawReactionActionEvent):
|
||||||
|
poll = self.get_poll(pld)
|
||||||
|
|
||||||
|
if poll:
|
||||||
|
choice = utils_emotes.get_index(pld.emoji.name)
|
||||||
|
|
||||||
|
responses = self.bot.database.session.query(ResponsesModel) \
|
||||||
|
.filter(
|
||||||
|
ResponsesModel.poll_id == poll.id,
|
||||||
|
ResponsesModel.user == pld.user_id,
|
||||||
|
ResponsesModel.choice == choice
|
||||||
|
)
|
||||||
|
|
||||||
|
if responses.count() != 0:
|
||||||
|
response = responses.first()
|
||||||
|
self.bot.database.session.delete(response)
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
await self.update_poll(poll.id)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
async def create_poll(self, ctx: commands.Context, poll: str, anonymous):
|
||||||
|
question = (poll.split('|')[0]).strip()
|
||||||
|
responses = [response.strip() for response in poll.split('|')[1:]]
|
||||||
|
emotes = utils_emotes.get(len(responses))
|
||||||
|
|
||||||
|
stmt = await ctx.send(Texts('poll', ctx).get('**Preparation...**'))
|
||||||
|
|
||||||
|
poll_row = PollModel()
|
||||||
|
self.bot.database.session.add(poll_row)
|
||||||
|
self.bot.database.session.flush()
|
||||||
|
|
||||||
|
e = discord.Embed(description=f"**{question}**")
|
||||||
|
e.set_author(
|
||||||
|
name=ctx.author,
|
||||||
|
icon_url="https://cdn.gnous.eu/tuxbot/survey1.png"
|
||||||
|
)
|
||||||
|
for i, response in enumerate(responses):
|
||||||
|
e.add_field(
|
||||||
|
name=f"__{emotes[i]}` - {response.capitalize()}`__",
|
||||||
|
value="**0** vote"
|
||||||
|
)
|
||||||
|
e.set_footer(text=f"ID: #{poll_row.id}")
|
||||||
|
|
||||||
|
poll_row.channel_id = stmt.channel.id
|
||||||
|
poll_row.message_id = stmt.id
|
||||||
|
poll_row.content = e.to_dict()
|
||||||
|
poll_row.is_anonymous = anonymous
|
||||||
|
poll_row.available_choices = len(responses)
|
||||||
|
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
|
||||||
|
await stmt.edit(content='', embed=e)
|
||||||
|
for emote in range(len(responses)):
|
||||||
|
await stmt.add_reaction(emotes[emote])
|
||||||
|
|
||||||
|
async def update_poll(self, poll_id: int):
|
||||||
|
poll = self.bot.database.session \
|
||||||
|
.query(PollModel) \
|
||||||
|
.filter(PollModel.id == poll_id) \
|
||||||
|
.one()
|
||||||
|
channel: discord.TextChannel = self.bot.get_channel(poll.channel_id)
|
||||||
|
message: discord.Message = await channel.fetch_message(poll.message_id)
|
||||||
|
|
||||||
|
chart_base_url = "https://quickchart.io/chart?backgroundColor=white&c="
|
||||||
|
chart_options = {
|
||||||
|
'type': 'pie',
|
||||||
|
'data': {
|
||||||
|
'labels': [],
|
||||||
|
'datasets': [
|
||||||
|
{
|
||||||
|
'data': []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content = json.loads(poll.content) \
|
||||||
|
if isinstance(poll.content, str) \
|
||||||
|
else poll.content
|
||||||
|
raw_responses = self.bot.database.session \
|
||||||
|
.query(ResponsesModel) \
|
||||||
|
.filter(ResponsesModel.poll_id == poll_id)
|
||||||
|
responses = {}
|
||||||
|
|
||||||
|
for response in raw_responses.all():
|
||||||
|
if responses.get(response.choice):
|
||||||
|
responses[response.choice] += 1
|
||||||
|
else:
|
||||||
|
responses[response.choice] = 1
|
||||||
|
|
||||||
|
for i, field in enumerate(content.get('fields')):
|
||||||
|
responders = responses.get(i, 0)
|
||||||
|
chart_options.get('data') \
|
||||||
|
.get('labels') \
|
||||||
|
.append(field.get('name')[5:].replace('__', ''))
|
||||||
|
chart_options.get('data') \
|
||||||
|
.get('datasets')[0] \
|
||||||
|
.get('data') \
|
||||||
|
.append(responders)
|
||||||
|
|
||||||
|
if responders <= 1:
|
||||||
|
field['value'] = f"**{responders}** vote"
|
||||||
|
else:
|
||||||
|
field['value'] = f"**{responders}** votes"
|
||||||
|
|
||||||
|
e = discord.Embed(description=content.get('description'))
|
||||||
|
e.set_author(
|
||||||
|
name=content.get('author').get('name'),
|
||||||
|
icon_url=content.get('author').get('icon_url')
|
||||||
|
)
|
||||||
|
chart_url = URL(chart_base_url + json.dumps(chart_options))
|
||||||
|
e.set_thumbnail(url=str(chart_url))
|
||||||
|
for field in content.get('fields'):
|
||||||
|
e.add_field(
|
||||||
|
name=field.get('name'),
|
||||||
|
value=field.get('value'),
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
e.set_footer(text=content.get('footer').get('text'))
|
||||||
|
|
||||||
|
await message.edit(embed=e)
|
||||||
|
|
||||||
|
poll.content = json.dumps(content)
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
|
||||||
|
@group_extra(name='poll', aliases=['sondage'], category='poll')
|
||||||
|
async def _poll(self, ctx: commands.Context):
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
await ctx.send_help('poll')
|
||||||
|
|
||||||
|
@_poll.group(name='create', aliases=['new', 'nouveau'])
|
||||||
|
async def _poll_create(self, ctx: commands.Context, *, poll: str):
|
||||||
|
is_anonymous = '--anonyme' in poll
|
||||||
|
poll = poll.replace('--anonyme', '')
|
||||||
|
|
||||||
|
await self.create_poll(ctx, poll, anonymous=is_anonymous)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: TuxBot):
|
||||||
|
bot.add_cog(Poll(bot))
|
410
cogs/Useful.py
Normal file
410
cogs/Useful.py
Normal file
|
@ -0,0 +1,410 @@
|
||||||
|
# Created by romain at 04/01/2020
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import platform
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
from socket import AF_INET6
|
||||||
|
from io import BytesIO
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageFont
|
||||||
|
from PIL import ImageDraw
|
||||||
|
from PIL import ImageOps
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import discord
|
||||||
|
import humanize
|
||||||
|
import psutil
|
||||||
|
from discord.ext import commands
|
||||||
|
from tcp_latency import measure_latency
|
||||||
|
|
||||||
|
from bot import TuxBot
|
||||||
|
from utils import Texts
|
||||||
|
from utils import command_extra, group_extra
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Useful(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot: TuxBot):
|
||||||
|
self.bot = bot
|
||||||
|
self.icon = ":toolbox:"
|
||||||
|
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/toolbox_1f9f0.png"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _latest_commits():
|
||||||
|
cmd = 'git log -n 3 -s --format="[\`%h\`](https://git.gnous.eu/gnouseu/tuxbot-bot/commits/%H) %s (%cr)"'
|
||||||
|
|
||||||
|
return os.popen(cmd).read().strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fetch_info():
|
||||||
|
total_lines = 0
|
||||||
|
total_python_lines = 0
|
||||||
|
file_amount = 0
|
||||||
|
python_file_amount = 0
|
||||||
|
ENV = "env"
|
||||||
|
|
||||||
|
for path, _, files in os.walk("."):
|
||||||
|
for name in files:
|
||||||
|
file_dir = str(pathlib.PurePath(path, name))
|
||||||
|
if (
|
||||||
|
not name.endswith(".py")
|
||||||
|
and not name.endswith(".po")
|
||||||
|
and not name.endswith(".json")
|
||||||
|
) or ENV in file_dir:
|
||||||
|
continue
|
||||||
|
file_amount += 1
|
||||||
|
python_file_amount += 1 if name.endswith(".py") else 0
|
||||||
|
with open(file_dir, "r", encoding="utf-8") as file:
|
||||||
|
for line in file:
|
||||||
|
if not line.strip().startswith("#") \
|
||||||
|
or not line.strip():
|
||||||
|
total_lines += 1
|
||||||
|
total_python_lines += 1 if name.endswith(".py") \
|
||||||
|
else 0
|
||||||
|
|
||||||
|
return (file_amount, total_lines), (
|
||||||
|
python_file_amount, total_python_lines)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def luhn_checker(number: int):
|
||||||
|
digits = [int(x) for x in reversed(str(number))]
|
||||||
|
|
||||||
|
for index, digit in enumerate(digits, start=1):
|
||||||
|
digit = digit * 2 if index % 2 == 0 else digit
|
||||||
|
if digit >= 10:
|
||||||
|
digit = sum(int(x) for x in list(str(digit)))
|
||||||
|
|
||||||
|
digits[index - 1] = digit
|
||||||
|
|
||||||
|
return sum(digits) % 10 == 0
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='iplocalise', category='network')
|
||||||
|
async def _iplocalise(self, ctx: commands.Context, addr, ip_type=''):
|
||||||
|
addr = re.sub(r'http(s?)://', '', addr)
|
||||||
|
addr = addr[:-1] if addr.endswith('/') else addr
|
||||||
|
|
||||||
|
await ctx.trigger_typing()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if 'v6' in ip_type:
|
||||||
|
try:
|
||||||
|
ip = socket.getaddrinfo(addr, None, AF_INET6)[1][4][0]
|
||||||
|
except socket.gaierror:
|
||||||
|
return await ctx.send(
|
||||||
|
Texts('useful', ctx).get('ipv6 not available'))
|
||||||
|
else:
|
||||||
|
ip = socket.gethostbyname(addr)
|
||||||
|
|
||||||
|
async with self.bot.session.get(f"http://ip-api.com/json/{ip}") \
|
||||||
|
as s:
|
||||||
|
response: dict = await s.json()
|
||||||
|
if response.get('status') == 'success':
|
||||||
|
e = discord.Embed(
|
||||||
|
title=f"{Texts('useful', ctx).get('Information for')}"
|
||||||
|
f" ``{addr}`` *`({response.get('query')})`*",
|
||||||
|
color=0x5858d7
|
||||||
|
)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=Texts('useful', ctx).get('Belongs to :'),
|
||||||
|
value=response['org'] if response['org'] else 'N/A',
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=Texts('useful', ctx).get('Is located at :'),
|
||||||
|
value=response['city'] if response['city'] else 'N/A',
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name="Region :",
|
||||||
|
value=f"{response['regionName'] if response['regionName'] else 'N/A'} "
|
||||||
|
f"({response['country'] if response['country'] else 'N/A'})",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
e.set_thumbnail(
|
||||||
|
url=f"https://www.countryflags.io/"
|
||||||
|
f"{response.get('countryCode')}/flat/64.png")
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
else:
|
||||||
|
await ctx.send(
|
||||||
|
content=f"{Texts('useful', ctx).get('info not available')}"
|
||||||
|
f"``{response['query'] if response['query'] else 'N/A'}``")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(e)
|
||||||
|
await ctx.send(
|
||||||
|
f"{Texts('useful', ctx).get('Cannot connect to host')} {addr}"
|
||||||
|
)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='getheaders', category='network')
|
||||||
|
async def _getheaders(self, ctx: commands.Context, addr: str):
|
||||||
|
if (addr.startswith('http') or addr.startswith('ftp')) is not True:
|
||||||
|
addr = f"http://{addr}"
|
||||||
|
|
||||||
|
await ctx.trigger_typing()
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with self.bot.session.get(addr) as s:
|
||||||
|
e = discord.Embed(
|
||||||
|
title=f"{Texts('useful', ctx).get('Headers of')} {addr}",
|
||||||
|
color=0xd75858
|
||||||
|
)
|
||||||
|
e.add_field(name="Status", value=s.status, inline=True)
|
||||||
|
e.set_thumbnail(url=f"https://http.cat/{s.status}")
|
||||||
|
|
||||||
|
headers = dict(s.headers.items())
|
||||||
|
headers.pop('Set-Cookie', headers)
|
||||||
|
|
||||||
|
for key, value in headers.items():
|
||||||
|
e.add_field(name=key, value=value, inline=True)
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
except aiohttp.ClientError:
|
||||||
|
await ctx.send(
|
||||||
|
f"{Texts('useful', ctx).get('Cannot connect to host')} {addr}"
|
||||||
|
)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='git', aliases=['sources', 'source', 'github'], category='misc')
|
||||||
|
async def _git(self, ctx):
|
||||||
|
e = discord.Embed(
|
||||||
|
title=Texts('useful', ctx).get('git repo'),
|
||||||
|
description=Texts('useful', ctx).get('git text'),
|
||||||
|
colour=0xE9D460
|
||||||
|
)
|
||||||
|
e.set_author(
|
||||||
|
name='Gnous',
|
||||||
|
icon_url="https://cdn.gnous.eu/logo1.png"
|
||||||
|
)
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='quote', category='misc')
|
||||||
|
async def _quote(self, ctx, message_id: discord.Message):
|
||||||
|
e = discord.Embed(
|
||||||
|
colour=message_id.author.colour,
|
||||||
|
description=message_id.clean_content,
|
||||||
|
timestamp=message_id.created_at
|
||||||
|
)
|
||||||
|
e.set_author(
|
||||||
|
name=message_id.author.display_name,
|
||||||
|
icon_url=message_id.author.avatar_url_as(format="jpg")
|
||||||
|
)
|
||||||
|
if len(message_id.attachments) >= 1:
|
||||||
|
e.set_image(url=message_id.attachments[0].url)
|
||||||
|
|
||||||
|
e.add_field(name="**Original**",
|
||||||
|
value=f"[Go!]({message_id.jump_url})")
|
||||||
|
e.set_footer(text="#" + message_id.channel.name)
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='ping', category='network')
|
||||||
|
async def _ping(self, ctx: commands.Context):
|
||||||
|
start = time.perf_counter()
|
||||||
|
await ctx.trigger_typing()
|
||||||
|
end = time.perf_counter()
|
||||||
|
|
||||||
|
latency = round(self.bot.latency * 1000, 2)
|
||||||
|
typing = round((end - start) * 1000, 2)
|
||||||
|
discordapp = measure_latency(host='discordapp.com', wait=0)[0]
|
||||||
|
|
||||||
|
e = discord.Embed(title='Ping', color=discord.Color.teal())
|
||||||
|
e.add_field(name='Websocket', value=f'{latency}ms')
|
||||||
|
e.add_field(name='Typing', value=f'{typing}ms')
|
||||||
|
e.add_field(name='discordapp.com', value=f'{discordapp}ms')
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='info', aliases=['about'], category='misc')
|
||||||
|
async def _info(self, ctx: commands.Context):
|
||||||
|
proc = psutil.Process()
|
||||||
|
total, python = self.fetch_info()
|
||||||
|
|
||||||
|
with proc.oneshot():
|
||||||
|
mem = proc.memory_full_info()
|
||||||
|
e = discord.Embed(
|
||||||
|
title=Texts('useful', ctx).get('Information about TuxBot'),
|
||||||
|
color=0x89C4F9)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=f"__:busts_in_silhouette: "
|
||||||
|
f"{Texts('useful', ctx).get('Development')}__",
|
||||||
|
value=f"**Romain#5117:** [git](https://git.gnous.eu/Romain)\n"
|
||||||
|
f"**Outout#4039:** [git](https://git.gnous.eu/mael)\n",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name="__<:python:596577462335307777> Python__",
|
||||||
|
value=f"**python** `{platform.python_version()}`\n"
|
||||||
|
f"**discord.py** `{discord.__version__}`",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name="__:gear: Usage__",
|
||||||
|
value=f"**{humanize.naturalsize(mem.rss)}** "
|
||||||
|
f"{Texts('useful', ctx).get('physical memory')}\n"
|
||||||
|
f"**{humanize.naturalsize(mem.vms)}** "
|
||||||
|
f"{Texts('useful', ctx).get('virtual memory')}\n",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=f"__{Texts('useful', ctx).get('Servers count')}__",
|
||||||
|
value=str(len(self.bot.guilds)),
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name=f"__{Texts('useful', ctx).get('Channels count')}__",
|
||||||
|
value=str(len([_ for _ in self.bot.get_all_channels()])),
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name=f"__{Texts('useful', ctx).get('Members count')}__",
|
||||||
|
value=str(len([_ for _ in self.bot.get_all_members()])),
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=f"__:file_folder: {Texts('useful', ctx).get('Files')}__",
|
||||||
|
value=f"{total[0]} *({python[0]} <:python:596577462335307777>)*",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name=f"__¶ {Texts('useful', ctx).get('Lines')}__",
|
||||||
|
value=f"{total[1]} *({python[1]} <:python:596577462335307777>)*",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=f"__{Texts('useful', ctx).get('Latest changes')}__",
|
||||||
|
value=self._latest_commits(),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name=f"__:link: {Texts('useful', ctx).get('Links')}__",
|
||||||
|
value="[tuxbot.gnous.eu](https://tuxbot.gnous.eu/) "
|
||||||
|
"| [gnous.eu](https://gnous.eu/) "
|
||||||
|
"| [git](https://git.gnous.eu/gnouseu/tuxbot-bot) "
|
||||||
|
"| [status](https://status.gnous.eu/check/154250) "
|
||||||
|
f"| [{Texts('useful', ctx).get('Invite')}](https://discordapp.com/oauth2/authorize?client_id=301062143942590465&scope=bot&permissions=268749888)",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
e.set_footer(text=f'version: {self.bot.version} '
|
||||||
|
f'• prefix: {ctx.prefix}')
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@command_extra(name='credits', aliases=['contributors', 'authors'], category='misc')
|
||||||
|
async def _credits(self, ctx: commands.Context):
|
||||||
|
e = discord.Embed(
|
||||||
|
title=Texts('useful', ctx).get('Contributors'),
|
||||||
|
color=0x36393f
|
||||||
|
)
|
||||||
|
|
||||||
|
e.add_field(
|
||||||
|
name="**Outout#4039** ",
|
||||||
|
value="• https://git.gnous.eu/mael ⠀\n"
|
||||||
|
"• mael@gnous.eu\n"
|
||||||
|
"• [@outoutxyz](https://twitter.com/outouxyz)",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
e.add_field(
|
||||||
|
name="**Romain#5117** ",
|
||||||
|
value="• https://git.gnous.eu/Romain\n"
|
||||||
|
"• romain@gnous.eu",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
@group_extra(name='cb', aliases=['cc'], category='misc')
|
||||||
|
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
||||||
|
async def _cb(self, ctx: commands.Context):
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
await ctx.send_help('cb')
|
||||||
|
|
||||||
|
@_cb.command(name='validate', aliases=['valid', 'correct'], category='misc')
|
||||||
|
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
||||||
|
async def _cb_validate(self, ctx: commands.Context, *, number: int):
|
||||||
|
valid = self.luhn_checker(number)
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
Texts(
|
||||||
|
'useful', ctx
|
||||||
|
).get(
|
||||||
|
'valid_credit_card'
|
||||||
|
if valid
|
||||||
|
else 'invalid_credit_card'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@_cb.command(name='generate', aliases=['new', 'get'], category='misc')
|
||||||
|
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
||||||
|
async def _cb_generate(self, ctx: commands.Context):
|
||||||
|
await ctx.channel.trigger_typing()
|
||||||
|
|
||||||
|
number = random.randint(4000_0000_0000_0000, 5999_9999_9999_9999)
|
||||||
|
while not self.luhn_checker(number):
|
||||||
|
number = random.randint(4000_0000_0000_0000, 5999_9999_9999_9999)
|
||||||
|
number = str(number)
|
||||||
|
cvv = ''.join(random.choice("abcdefghij") for _ in range(3))
|
||||||
|
|
||||||
|
with Image.open("utils/images/blank_credit_card.png") as blank:
|
||||||
|
cc_font = ImageFont.truetype('utils/fonts/credit_card.ttf', 26)
|
||||||
|
user_font = ImageFont.truetype('utils/fonts/credit_card.ttf', 20)
|
||||||
|
draw = ImageDraw.Draw(blank)
|
||||||
|
|
||||||
|
cvv_text = Image.new('L', (500, 50))
|
||||||
|
cvv_draw = ImageDraw.Draw(cvv_text)
|
||||||
|
cvv_draw.text((0, 0), cvv, font=user_font, fill=255)
|
||||||
|
cvv_rotated = cvv_text.rotate(23, expand=1)
|
||||||
|
|
||||||
|
draw.text(
|
||||||
|
(69, 510),
|
||||||
|
' '.join([number[i:i+4] for i in range(0, len(number), 4)]),
|
||||||
|
(210, 210, 210),
|
||||||
|
font=cc_font
|
||||||
|
)
|
||||||
|
|
||||||
|
draw.text(
|
||||||
|
(69, 550),
|
||||||
|
ctx.author.name.upper(),
|
||||||
|
(210, 210, 210),
|
||||||
|
font=user_font
|
||||||
|
)
|
||||||
|
blank.paste(ImageOps.colorize(cvv_rotated, (0, 0, 0), (0, 0, 0)), (470, 0), cvv_rotated)
|
||||||
|
|
||||||
|
output = BytesIO()
|
||||||
|
blank.save(output, 'png')
|
||||||
|
output.seek(0)
|
||||||
|
|
||||||
|
await ctx.send(file=discord.File(fp=output, filename="credit_card.png"))
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: TuxBot):
|
||||||
|
bot.add_cog(Useful(bot))
|
64
cogs/User.py
Normal file
64
cogs/User.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from bot import TuxBot
|
||||||
|
from utils import AliasesModel
|
||||||
|
from utils import Texts
|
||||||
|
from utils import group_extra
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class User(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot: TuxBot):
|
||||||
|
self.bot = bot
|
||||||
|
self.icon = ":bust_in_silhouette:"
|
||||||
|
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/bust-in-silhouette_1f464.png"
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
@group_extra(name='alias', aliases=['aliases'], category='alias')
|
||||||
|
async def _alias(self, ctx: commands.Context):
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
await ctx.send_help('alias')
|
||||||
|
|
||||||
|
@_alias.command(name='add', aliases=['set', 'new'])
|
||||||
|
async def _alias_add(self, ctx: commands.Context, *, user_alias: str):
|
||||||
|
is_global = False
|
||||||
|
if '--global' in user_alias:
|
||||||
|
is_global = True
|
||||||
|
user_alias.replace('--global', '')
|
||||||
|
|
||||||
|
user_alias = user_alias.split(' -> ')
|
||||||
|
if len(user_alias) != 2:
|
||||||
|
return await ctx.send_help('alias')
|
||||||
|
|
||||||
|
command = user_alias[1]
|
||||||
|
user_alias = user_alias[0]
|
||||||
|
|
||||||
|
if self.bot.get_command(command) is None:
|
||||||
|
return await ctx.send(Texts('user').get('Command not found'))
|
||||||
|
|
||||||
|
alias = AliasesModel(
|
||||||
|
user_id=ctx.author.id,
|
||||||
|
alias=user_alias,
|
||||||
|
command=command,
|
||||||
|
guild="global" if is_global else str(ctx.guild.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.bot.database.session.add(alias)
|
||||||
|
self.bot.database.session.commit()
|
||||||
|
|
||||||
|
@_alias.command(name='remove', aliases=['drop', 'del', 'delete'])
|
||||||
|
async def _alias_remove(self, ctx: commands.Context, prefix: str):
|
||||||
|
...
|
||||||
|
|
||||||
|
@_alias.command(name='list', aliases=['show', 'all'])
|
||||||
|
async def _alias_list(self, ctx: commands.Context):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: TuxBot):
|
||||||
|
bot.add_cog(User(bot))
|
407
cogs/admin.py
407
cogs/admin.py
|
@ -1,407 +0,0 @@
|
||||||
import aiohttp
|
|
||||||
from discord.ext import commands
|
|
||||||
import discord
|
|
||||||
from .utils import checks
|
|
||||||
|
|
||||||
from .utils.checks import get_user
|
|
||||||
|
|
||||||
|
|
||||||
class Admin(commands.Cog):
|
|
||||||
"""Commandes secrètes d'administration."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
self._last_result = None
|
|
||||||
self.sessions = set()
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name="upload", pass_context=True)
|
|
||||||
async def _upload(self, ctx, *, url=""):
|
|
||||||
if len(ctx.message.attachments) >= 1:
|
|
||||||
file = ctx.message.attachments[0].url
|
|
||||||
elif url != "":
|
|
||||||
file = url
|
|
||||||
else:
|
|
||||||
em = discord.Embed(title='Une erreur est survenue',
|
|
||||||
description="Fichier introuvable.",
|
|
||||||
colour=0xDC3546)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
return
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(file) as r:
|
|
||||||
image = await r.content.read()
|
|
||||||
|
|
||||||
with open(f"data/tmp/{str(ctx.author.id)}.png", 'wb') as f:
|
|
||||||
f.write(image)
|
|
||||||
f.close()
|
|
||||||
await ctx.send(file=discord.File(f"data/tmp/{str(ctx.author.id)}.png"))
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name="ban", pass_context=True)
|
|
||||||
async def _ban(self, ctx, user, *, reason=""):
|
|
||||||
"""Ban user"""
|
|
||||||
user = get_user(ctx.message, user)
|
|
||||||
if user and str(user.id) not in self.bot.config.unkickable_id:
|
|
||||||
try:
|
|
||||||
await user.ban(reason=reason)
|
|
||||||
return_msg = f"`{user.mention}` a été banni\n"
|
|
||||||
if reason:
|
|
||||||
return_msg += f"raison : `{reason}`"
|
|
||||||
return_msg += "."
|
|
||||||
await ctx.send(return_msg)
|
|
||||||
except discord.Forbidden:
|
|
||||||
await ctx.send('Impossible de bannir cet user,'
|
|
||||||
' probleme de permission.')
|
|
||||||
else:
|
|
||||||
return await ctx.send('Impossible de trouver l\'user.')
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name="kick", pass_context=True)
|
|
||||||
async def _kick(self, ctx, user, *, reason=""):
|
|
||||||
"""Kick a user"""
|
|
||||||
user = get_user(ctx.message, user)
|
|
||||||
if user and str(user.id) not in self.bot.config.unkickable_id:
|
|
||||||
try:
|
|
||||||
await user.kick(reason=reason)
|
|
||||||
return_msg = f"`{user.mention}` a été kické\n"
|
|
||||||
if reason:
|
|
||||||
return_msg += f"raison : `{reason}`"
|
|
||||||
return_msg += "."
|
|
||||||
await ctx.send(return_msg)
|
|
||||||
except discord.Forbidden:
|
|
||||||
await ctx.send('Impossible de kicker cet user,'
|
|
||||||
' probleme de permission.')
|
|
||||||
else:
|
|
||||||
return await ctx.send('Impossible de trouver l\'user.')
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='clear', pass_context=True)
|
|
||||||
async def _clear(self, ctx, number: int, silent: str = True):
|
|
||||||
"""Clear <number> of message(s)"""
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible de supprimer le message "
|
|
||||||
f"\"{str(ctx.message.content)}\"")
|
|
||||||
if number < 1000:
|
|
||||||
try:
|
|
||||||
await ctx.message.channel.purge(limit=number)
|
|
||||||
except Exception as e: # TODO : A virer dans l'event on_error
|
|
||||||
if silent is not True:
|
|
||||||
await ctx.send(f':sob: Une erreur est survenue : \n'
|
|
||||||
f' {type(e).__name__}: {e}')
|
|
||||||
if silent is not True:
|
|
||||||
await ctx.send("Hop voila j'ai viré des messages! Hello World")
|
|
||||||
print(f"{str(number)} messages ont été supprimés")
|
|
||||||
else:
|
|
||||||
await ctx.send('Trop de messages, entre un nombre < 1000')
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='say', pass_context=True)
|
|
||||||
async def _say(self, ctx, *, tosay: str):
|
|
||||||
"""Say a message in the current channel"""
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible de supprimer le message "
|
|
||||||
f"\"{str(ctx.message.content)}\"")
|
|
||||||
await ctx.send(tosay)
|
|
||||||
except Exception as e: # TODO : A virer dans l'event on_error
|
|
||||||
await ctx.send(f':sob: Une erreur est survenue : \n'
|
|
||||||
f' {type(e).__name__}: {e}')
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='sayto', pass_context=True)
|
|
||||||
async def _sayto(self, ctx, channel_id: int, *, tosay: str):
|
|
||||||
"""Say a message in the <channel_id> channel"""
|
|
||||||
try:
|
|
||||||
chan = self.bot.get_channel(channel_id)
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible de supprimer le message "
|
|
||||||
f"\"{str(ctx.message.content)}\"")
|
|
||||||
try:
|
|
||||||
await chan.send(tosay)
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible d'envoyer le message dans {str(channel_id)}")
|
|
||||||
except Exception as e: # TODO : A virer dans l'event on_error
|
|
||||||
await ctx.send(f':sob: Une erreur est survenue : \n'
|
|
||||||
f' {type(e).__name__}: {e}')
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='sayto_dm', pass_context=True)
|
|
||||||
async def _sayto_dm(self, ctx, user_id: int, *, tosay: str):
|
|
||||||
"""Say a message to the <user_id> user"""
|
|
||||||
try:
|
|
||||||
user = self.bot.get_user(user_id)
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible de supprimer le message "
|
|
||||||
f"\"{str(ctx.message.content)}\"")
|
|
||||||
try:
|
|
||||||
await user.send(tosay)
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible d'envoyer le message dans {str(user_id)}")
|
|
||||||
except Exception as e: # TODO : A virer dans l'event on_error
|
|
||||||
await ctx.send(f':sob: Une erreur est survenue : \n'
|
|
||||||
f' {type(e).__name__}: {e}')
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='editsay', pass_context=True)
|
|
||||||
async def _editsay(self, ctx, message_id: int, *, new_content: str):
|
|
||||||
"""Edit a bot's message"""
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible de supprimer le message "
|
|
||||||
f"\"{str(ctx.message.content)}\"")
|
|
||||||
toedit = await ctx.channel.fetch_message(message_id)
|
|
||||||
except discord.errors.NotFound:
|
|
||||||
await ctx.send(f"Impossible de trouver le message avec l'id "
|
|
||||||
f"`{message_id}` sur ce salon")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await toedit.edit(content=str(new_content))
|
|
||||||
except discord.errors.Forbidden:
|
|
||||||
await ctx.send("J'ai po les perms pour editer mes messages :(")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='addreaction', pass_context=True)
|
|
||||||
async def _addreaction(self, ctx, message_id: int, reaction: str):
|
|
||||||
"""Add reactions to a message"""
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible de supprimer le message "
|
|
||||||
f"\"{str(ctx.message.content)}\"")
|
|
||||||
toadd = await ctx.channel.fetch_message(message_id)
|
|
||||||
except discord.errors.NotFound:
|
|
||||||
await ctx.send(f"Impossible de trouver le message avec l'id "
|
|
||||||
f"`{message_id}` sur ce salon")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await toadd.add_reaction(reaction)
|
|
||||||
except discord.errors.Forbidden:
|
|
||||||
await ctx.send("J'ai po les perms pour ajouter des réactions :(")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='delete', pass_context=True)
|
|
||||||
async def _delete(self, ctx, message_id: int):
|
|
||||||
"""Delete message in current channel"""
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible de supprimer le message "
|
|
||||||
f"\"{str(ctx.message.content)}\"")
|
|
||||||
todelete = await ctx.channel.fetch_message(message_id)
|
|
||||||
except discord.errors.NotFound:
|
|
||||||
await ctx.send(f"Impossible de trouver le message avec l'id "
|
|
||||||
f"`{message_id}` sur ce salon")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await todelete.delete()
|
|
||||||
except discord.errors.Forbidden:
|
|
||||||
await ctx.send("J'ai po les perms pour supprimer des messages :(")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='deletefrom', pass_context=True)
|
|
||||||
async def _deletefrom(self, ctx, chan_id: int, *, message_id: int):
|
|
||||||
"""Delete message in <chan_id> channel"""
|
|
||||||
try:
|
|
||||||
chan = self.bot.get_channel(chan_id)
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print(f"Impossible de supprimer le message "
|
|
||||||
f"\"{str(ctx.message.content)}\"")
|
|
||||||
todelete = await chan.fetch_message(message_id)
|
|
||||||
except discord.errors.NotFound:
|
|
||||||
await ctx.send(f"Impossible de trouver le message avec l'id "
|
|
||||||
f"`{id}` sur le salon")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await todelete.delete()
|
|
||||||
except discord.errors.Forbidden:
|
|
||||||
await ctx.send("J'ai po les perms pour supprimer le message :(")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@commands.command(name='embed', pass_context=True)
|
|
||||||
async def _embed(self, ctx, *, msg: str = "help"):
|
|
||||||
"""Send an embed"""
|
|
||||||
if msg != "help":
|
|
||||||
ptext = title \
|
|
||||||
= description \
|
|
||||||
= image \
|
|
||||||
= thumbnail \
|
|
||||||
= color \
|
|
||||||
= footer \
|
|
||||||
= author \
|
|
||||||
= None
|
|
||||||
timestamp = discord.Embed.Empty
|
|
||||||
embed_values = msg.split('|')
|
|
||||||
for i in embed_values:
|
|
||||||
if i.strip().lower().startswith('ptext='):
|
|
||||||
ptext = i.strip()[6:].strip()
|
|
||||||
elif i.strip().lower().startswith('title='):
|
|
||||||
title = i.strip()[6:].strip()
|
|
||||||
elif i.strip().lower().startswith('description='):
|
|
||||||
description = i.strip()[12:].strip()
|
|
||||||
elif i.strip().lower().startswith('desc='):
|
|
||||||
description = i.strip()[5:].strip()
|
|
||||||
elif i.strip().lower().startswith('image='):
|
|
||||||
image = i.strip()[6:].strip()
|
|
||||||
elif i.strip().lower().startswith('thumbnail='):
|
|
||||||
thumbnail = i.strip()[10:].strip()
|
|
||||||
elif i.strip().lower().startswith('colour='):
|
|
||||||
color = i.strip()[7:].strip()
|
|
||||||
elif i.strip().lower().startswith('color='):
|
|
||||||
color = i.strip()[6:].strip()
|
|
||||||
elif i.strip().lower().startswith('footer='):
|
|
||||||
footer = i.strip()[7:].strip()
|
|
||||||
elif i.strip().lower().startswith('author='):
|
|
||||||
author = i.strip()[7:].strip()
|
|
||||||
elif i.strip().lower().startswith('timestamp'):
|
|
||||||
timestamp = ctx.message.created_at
|
|
||||||
else:
|
|
||||||
if description is None and not i.strip()\
|
|
||||||
.lower().startswith('field='):
|
|
||||||
description = i.strip()
|
|
||||||
|
|
||||||
if color:
|
|
||||||
if color.startswith('#'):
|
|
||||||
color = color[1:]
|
|
||||||
if not color.startswith('0x'):
|
|
||||||
color = '0x' + color
|
|
||||||
|
|
||||||
if ptext \
|
|
||||||
is title \
|
|
||||||
is description \
|
|
||||||
is image \
|
|
||||||
is thumbnail \
|
|
||||||
is color \
|
|
||||||
is footer \
|
|
||||||
is author \
|
|
||||||
is None \
|
|
||||||
and 'field=' not in msg:
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print("Impossible de supprimer le message \"" + str(
|
|
||||||
ctx.message.content) + "\"")
|
|
||||||
return await ctx.send(content=None,
|
|
||||||
embed=discord.Embed(description=msg))
|
|
||||||
|
|
||||||
if color:
|
|
||||||
em = discord.Embed(timestamp=timestamp, title=title,
|
|
||||||
description=description,
|
|
||||||
color=int(color, 16))
|
|
||||||
else:
|
|
||||||
em = discord.Embed(timestamp=timestamp, title=title,
|
|
||||||
description=description)
|
|
||||||
for i in embed_values:
|
|
||||||
if i.strip().lower().startswith('field='):
|
|
||||||
field_inline = True
|
|
||||||
field = i.strip().lstrip('field=')
|
|
||||||
field_name, field_value = field.split('value=')
|
|
||||||
if 'inline=' in field_value:
|
|
||||||
field_value, field_inline = field_value.split(
|
|
||||||
'inline=')
|
|
||||||
if 'false' in field_inline.lower() \
|
|
||||||
or 'no' in field_inline.lower():
|
|
||||||
field_inline = False
|
|
||||||
field_name = field_name.strip().lstrip('name=')
|
|
||||||
em.add_field(name=field_name, value=field_value.strip(),
|
|
||||||
inline=field_inline)
|
|
||||||
if author:
|
|
||||||
if 'icon=' in author:
|
|
||||||
text, icon = author.split('icon=')
|
|
||||||
if 'url=' in icon:
|
|
||||||
em.set_author(name=text.strip()[5:],
|
|
||||||
icon_url=icon.split('url=')[0].strip(),
|
|
||||||
url=icon.split('url=')[1].strip())
|
|
||||||
else:
|
|
||||||
em.set_author(name=text.strip()[5:], icon_url=icon)
|
|
||||||
else:
|
|
||||||
if 'url=' in author:
|
|
||||||
em.set_author(name=author.split('url=')[0].strip()[5:],
|
|
||||||
url=author.split('url=')[1].strip())
|
|
||||||
else:
|
|
||||||
em.set_author(name=author)
|
|
||||||
|
|
||||||
if image:
|
|
||||||
em.set_image(url=image)
|
|
||||||
if thumbnail:
|
|
||||||
em.set_thumbnail(url=thumbnail)
|
|
||||||
if footer:
|
|
||||||
if 'icon=' in footer:
|
|
||||||
text, icon = footer.split('icon=')
|
|
||||||
em.set_footer(text=text.strip()[5:], icon_url=icon)
|
|
||||||
else:
|
|
||||||
em.set_footer(text=footer)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await ctx.message.delete()
|
|
||||||
except Exception:
|
|
||||||
print("Impossible de supprimer le message \"" + str(
|
|
||||||
ctx.message.content) + "\"")
|
|
||||||
await ctx.send(content=ptext, embed=em)
|
|
||||||
|
|
||||||
else:
|
|
||||||
embed = discord.Embed(
|
|
||||||
title="Aide sur l'utilisation de la commande .embed:")
|
|
||||||
embed.add_field(name="Titre:", value="title=<le titre>",
|
|
||||||
inline=True)
|
|
||||||
embed.add_field(name="Description:",
|
|
||||||
value="description=<la description>", inline=True)
|
|
||||||
embed.add_field(name="Couleur:", value="color=<couleur en hexa>",
|
|
||||||
inline=True)
|
|
||||||
embed.add_field(name="Image:",
|
|
||||||
value="image=<url de l'image (en https)>",
|
|
||||||
inline=True)
|
|
||||||
embed.add_field(name="Thumbnail:",
|
|
||||||
value="thumbnail=<url de l'image>", inline=True)
|
|
||||||
embed.add_field(name="Auteur:", value="author=<nom de l'auteur>",
|
|
||||||
inline=True)
|
|
||||||
embed.add_field(name="Icon", value="icon=<url de l'image>",
|
|
||||||
inline=True)
|
|
||||||
embed.add_field(name="Footer", value="footer=<le footer>",
|
|
||||||
inline=True)
|
|
||||||
embed.set_footer(text="Exemple: .embed title=Un titre |"
|
|
||||||
" description=Une description |"
|
|
||||||
" color=3AB35E |"
|
|
||||||
" field=name=test value=test")
|
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Admin(bot))
|
|
68
cogs/afk.py
68
cogs/afk.py
|
@ -1,68 +0,0 @@
|
||||||
from discord.ext import commands
|
|
||||||
import discord
|
|
||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
class AFK(commands.Cog):
|
|
||||||
"""Commandes utilitaires."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
self.afk_users = []
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
|
||||||
async def afk(self, ctx, action: str = ""):
|
|
||||||
|
|
||||||
if action.lower() == "list":
|
|
||||||
try:
|
|
||||||
await ctx.send(*self.afk_users)
|
|
||||||
except discord.HTTPException:
|
|
||||||
await ctx.send("Il n'y a personne d'afk...")
|
|
||||||
else:
|
|
||||||
user = ctx.author
|
|
||||||
self.afk_users.append(user)
|
|
||||||
msgs = ["s'absente de discord quelques instants",
|
|
||||||
"se casse de son pc",
|
|
||||||
"va sortir son chien",
|
|
||||||
"reviens bientôt",
|
|
||||||
"va nourrir son cochon",
|
|
||||||
"va manger des cookies",
|
|
||||||
"va manger de la poutine",
|
|
||||||
"va faire caca",
|
|
||||||
"va faire pipi"]
|
|
||||||
|
|
||||||
await ctx.send(f"**{user.mention}** {random.choice(msgs)}...")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_message(self, message):
|
|
||||||
if message.author.bot \
|
|
||||||
or message.guild.id != int(self.bot.config.main_server_id):
|
|
||||||
return
|
|
||||||
|
|
||||||
user = message.author
|
|
||||||
|
|
||||||
if user in self.afk_users \
|
|
||||||
and message.content != self.bot.config.prefix[0] + "afk":
|
|
||||||
self.afk_users.remove(user)
|
|
||||||
|
|
||||||
msgs = ["a réssuscité",
|
|
||||||
"est de nouveau parmi nous",
|
|
||||||
"a fini de faire caca",
|
|
||||||
"a fini d'uriner",
|
|
||||||
"n'est plus mort",
|
|
||||||
"est de nouveau sur son PC",
|
|
||||||
"a fini de manger sa poutine",
|
|
||||||
"a fini de danser",
|
|
||||||
"s'est réveillé",
|
|
||||||
"est de retour dans ce monde cruel"]
|
|
||||||
|
|
||||||
await message.channel.send(f"**{user.mention}**"
|
|
||||||
f" {random.choice(msgs)}...")
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(AFK(bot))
|
|
124
cogs/atc.py
124
cogs/atc.py
|
@ -1,124 +0,0 @@
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
import requests
|
|
||||||
import re
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
|
|
||||||
class ATC(commands.Cog):
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
self.playing = False
|
|
||||||
self.author = None
|
|
||||||
self.voice = None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def extra(self, ctx, icao):
|
|
||||||
if icao == "stop_playing":
|
|
||||||
if self.playing and (
|
|
||||||
ctx.author.id == self.author.id
|
|
||||||
or ctx.message.channel.permissions_for(ctx.message.author).administrator is True
|
|
||||||
):
|
|
||||||
await self.voice.disconnect()
|
|
||||||
self.playing = False
|
|
||||||
await ctx.send("Écoute terminée !")
|
|
||||||
return "quit"
|
|
||||||
else:
|
|
||||||
await ctx.send("Veuillez specifier un icao")
|
|
||||||
return "quit"
|
|
||||||
if icao == "info":
|
|
||||||
em = discord.Embed(title=f"Infos sur les services utilisés par {self.bot.config.prefix[0]}atc")
|
|
||||||
em.add_field(name="Service pour les communications:",
|
|
||||||
value="[liveatc.net](https://www.liveatc.net/)",
|
|
||||||
inline=False)
|
|
||||||
em.add_field(name="Service pour les plans des aéroports:",
|
|
||||||
value="[universalweather.com](http://www.universalweather.com/)",
|
|
||||||
inline=False)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
return "quit"
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command(name="atc", no_pm=True, pass_context=True)
|
|
||||||
async def _atc(self, ctx, icao="stop_playing"):
|
|
||||||
user = ctx.author
|
|
||||||
if not user.voice:
|
|
||||||
await ctx.send('Veuillez aller dans un channel vocal.')
|
|
||||||
return
|
|
||||||
|
|
||||||
if await self.extra(self, ctx, icao) == "quit":
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.playing:
|
|
||||||
await ctx.send(f"Une écoute est déja en court, "
|
|
||||||
f"demandez à {self.author.mention} de faire "
|
|
||||||
f"`.atc stop_playing` pour l'arreter")
|
|
||||||
return
|
|
||||||
self.author = user
|
|
||||||
headers = {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.35 Safari/537.36',
|
|
||||||
}
|
|
||||||
req = requests.post("https://www.liveatc.net/search/",
|
|
||||||
data={'icao': icao},
|
|
||||||
headers=headers)
|
|
||||||
html = BeautifulSoup(req.text, features="lxml")
|
|
||||||
regex = r"(javascript: pageTracker\._trackPageview\('\/listen\/)(.*)(\'\)\;)"
|
|
||||||
|
|
||||||
possibilities = {}
|
|
||||||
emojis = ['1⃣', '2⃣', '3⃣', '4⃣', '5⃣', '6⃣', '7⃣', '8⃣', '9⃣', '🔟',
|
|
||||||
'0⃣', '🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮']
|
|
||||||
to_react = []
|
|
||||||
|
|
||||||
idx = 0
|
|
||||||
for a in html.findAll("a", onclick=True):
|
|
||||||
val = a.get('onclick')
|
|
||||||
for match in re.finditer(regex, val):
|
|
||||||
possibilities[idx] = f"{emojis[idx]} - {match.groups()[1]}\n"
|
|
||||||
to_react.append(emojis[idx])
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
em = discord.Embed(title='Résultats pour : ' + icao,
|
|
||||||
description=str(''.join(possibilities.values())),
|
|
||||||
colour=0x4ECDC4)
|
|
||||||
em.set_image(
|
|
||||||
url=f"http://www.universalweather.com/regusers/mod-bin/uvtp_airport_image?icao={icao}")
|
|
||||||
|
|
||||||
poll_msg = await ctx.send(embed=em)
|
|
||||||
for emote in to_react:
|
|
||||||
await poll_msg.add_reaction(emote)
|
|
||||||
|
|
||||||
def check(reaction, user):
|
|
||||||
return user == ctx.author and reaction.emoji in to_react and \
|
|
||||||
reaction.message.id == poll_msg.id
|
|
||||||
|
|
||||||
async def waiter(future: asyncio.Future):
|
|
||||||
reaction, user = await self.bot.wait_for('reaction_add',
|
|
||||||
check=check)
|
|
||||||
|
|
||||||
future.set_result(emojis.index(reaction.emoji))
|
|
||||||
|
|
||||||
added_emoji = asyncio.Future()
|
|
||||||
self.bot.loop.create_task(waiter(added_emoji))
|
|
||||||
|
|
||||||
while not added_emoji.done():
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
|
|
||||||
freq = possibilities[added_emoji.result()].split('- ')[1]
|
|
||||||
|
|
||||||
if possibilities:
|
|
||||||
self.playing = True
|
|
||||||
self.voice = await user.voice.channel.connect()
|
|
||||||
self.voice.play(
|
|
||||||
discord.FFmpegPCMAudio(f"http://yyz.liveatc.net/{freq}"))
|
|
||||||
await poll_msg.delete()
|
|
||||||
await ctx.send(f"Écoute de {freq}")
|
|
||||||
else:
|
|
||||||
await ctx.send(f"Aucun résultat trouvé pour {icao} {freq}")
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(ATC(bot))
|
|
|
@ -1,71 +0,0 @@
|
||||||
import platform
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
from discord.http import Route
|
|
||||||
|
|
||||||
|
|
||||||
class Basics(commands.Cog):
|
|
||||||
"""Commandes générales."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def ping(self, ctx):
|
|
||||||
ping_res = str(subprocess.Popen(["/bin/ping", "-c1", "discordapp.com"],
|
|
||||||
stdout=subprocess.PIPE).stdout.read())
|
|
||||||
formated_res = [item for item in ping_res.split() if 'time=' in item]
|
|
||||||
result = self.bot.latency * 1000 # str(formated_res[0])[5:]
|
|
||||||
|
|
||||||
if float(result) >= 200:
|
|
||||||
em = discord.Embed(title="Ping : " + str(result) + "ms",
|
|
||||||
description="... c'est quoi ce ping !",
|
|
||||||
colour=0xFF1111)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
elif float(result) > 100 < 200:
|
|
||||||
em = discord.Embed(title="Ping : " + str(result) + "ms",
|
|
||||||
description="Ca va, ça peut aller, mais j'ai "
|
|
||||||
"l'impression d'avoir 40 ans !",
|
|
||||||
colour=0xFFA500)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
else:
|
|
||||||
em = discord.Embed(title="Ping : " + str(result) + "ms",
|
|
||||||
description="Wow c'te vitesse de réaction, "
|
|
||||||
"je m'épate moi-même !",
|
|
||||||
colour=0x11FF11)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def info(self, ctx):
|
|
||||||
"""Affiches des informations sur le bot"""
|
|
||||||
text = open('texts/info.md').read()
|
|
||||||
os_info = str(platform.system()) + " / " + str(platform.release())
|
|
||||||
em = discord.Embed(title='Informations sur TuxBot',
|
|
||||||
description=text.format(os_info,
|
|
||||||
platform.python_version(),
|
|
||||||
socket.gethostname(),
|
|
||||||
discord.__version__,
|
|
||||||
Route.BASE),
|
|
||||||
colour=0x89C4F9)
|
|
||||||
em.set_footer(text="/home/****/bot.py")
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def help(self, ctx, page: int = 1):
|
|
||||||
"""Affiches l'aide du bot"""
|
|
||||||
page = int(page) if 0 < int(page) < 5 else 1
|
|
||||||
text = open('texts/help.md').read().split("[split]")
|
|
||||||
em = discord.Embed(title='Commandes de TuxBot', description=text[page - 1],
|
|
||||||
colour=0x89C4F9)
|
|
||||||
await ctx.send(content=f"page {page}/{len(text)}", embed=em)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Basics(bot))
|
|
311
cogs/ci.py
311
cogs/ci.py
|
@ -1,311 +0,0 @@
|
||||||
import datetime
|
|
||||||
import random
|
|
||||||
|
|
||||||
import discord
|
|
||||||
import requests
|
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
from .utils import checks
|
|
||||||
from .utils import db
|
|
||||||
from .utils.checks import get_user, check_date
|
|
||||||
|
|
||||||
|
|
||||||
class Identity(commands.Cog):
|
|
||||||
"""Commandes des cartes d'identité ."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
self.cursor.execute("""SHOW TABLES LIKE 'users'""")
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
# Creation table Utilisateur si premiere fois
|
|
||||||
sql = "CREATE TABLE users ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, userid TEXT null, username TEXT null, os TEXT null, config TEXT null, useravatar TEXT null, userbirth TEXT null, pays TEXT null, cidate TEXT null, cibureau TEXT null);"
|
|
||||||
self.cursor.execute(sql)
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.group(name="ci", no_pm=True, pass_context=True)
|
|
||||||
async def _ci(self, ctx):
|
|
||||||
"""Cartes d'identité"""
|
|
||||||
|
|
||||||
if ctx.invoked_subcommand is None:
|
|
||||||
text = open('texts/ci-info.md').read()
|
|
||||||
em = discord.Embed(title='Commandes de carte d\'identité de TuxBot', description=text, colour=0x89C4F9)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_ci.command(pass_context=True, name="show")
|
|
||||||
async def ci_show(self, ctx, args: str = None):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
if args == None:
|
|
||||||
user = get_user(ctx.message, ctx.author.name)
|
|
||||||
else:
|
|
||||||
user = get_user(ctx.message, args)
|
|
||||||
|
|
||||||
if user:
|
|
||||||
self.cursor.execute("""SELECT userid, username, useravatar, userbirth, cidate, cibureau, os, config, pays, id FROM users WHERE userid=%s""",(str(user.id)))
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
def isexist(var):
|
|
||||||
if not var:
|
|
||||||
return "Non renseigné."
|
|
||||||
else:
|
|
||||||
return var
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Désolé mais {user.mention} est sans papier !")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
user_birth = datetime.datetime.fromisoformat(result[3])
|
|
||||||
user_birth_day = check_date(str(user_birth.day))
|
|
||||||
user_birth_month = check_date(str(user_birth.month))
|
|
||||||
|
|
||||||
formated_user_birth = str(user_birth_day) + "/" + str(user_birth_month) + "/" + str(user_birth.year)
|
|
||||||
|
|
||||||
try: ## a virer une fois le patch appliqué pour tout le monde
|
|
||||||
cidate = datetime.datetime.fromisoformat(result[4])
|
|
||||||
cidate_day = check_date(str(cidate.day)) ## a garder
|
|
||||||
cidate_month = check_date(str(cidate.month)) ## a garder
|
|
||||||
|
|
||||||
formated_cidate = str(cidate_day) + "/" + str(cidate_month) + "/" + str(cidate.year) ## a garder
|
|
||||||
except ValueError: ## a virer une fois le patch appliqué pour tout le monde
|
|
||||||
formated_cidate = str(result[4]).replace('-', '/') ## a virer une fois le patch appliqué pour tout le monde
|
|
||||||
await ctx.send(f"{user.mention} vous êtes prié(e) de faire la commande `.ci update` afin de regler un probleme de date coté bdd") ## a virer une fois le patch appliqué pour tout le monde
|
|
||||||
|
|
||||||
embed=discord.Embed(title="Carte d'identité | Communisme Linuxien")
|
|
||||||
embed.set_author(name=result[1], icon_url=result[2])
|
|
||||||
embed.set_thumbnail(url = result[2])
|
|
||||||
embed.add_field(name="Nom :", value=result[1], inline=True)
|
|
||||||
embed.add_field(name="Système d'exploitation :", value=isexist(result[6]), inline=True)
|
|
||||||
embed.add_field(name="Configuration Système : ", value=isexist(result[7]), inline=False)
|
|
||||||
embed.add_field(name="Date de naissance sur discord : ", value=formated_user_birth, inline=True)
|
|
||||||
embed.add_field(name="Pays : ", value=isexist(result[8]), inline=True)
|
|
||||||
embed.add_field(name="Profil sur le web : ", value="*indisponible*") # value=f"https://tuxbot.gnous.eu/users/{result[9]}", inline=True)
|
|
||||||
embed.set_footer(text=f"Enregistré dans le bureau {result[5]} le {formated_cidate}.")
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
except Exception as e:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Désolé mais la carte d'identité de {user.mention} est trop longue de ce fait je ne peux te l'envoyer, essaye de l'aléger, {user.mention} :wink: !")
|
|
||||||
await ctx.send(f':sob: Une erreur est survenue : \n {type(e).__name__}: {e}')
|
|
||||||
else:
|
|
||||||
return await ctx.send('Impossible de trouver l\'user.')
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_ci.command(pass_context=True, name="register")
|
|
||||||
async def ci_register(self, ctx):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
self.cursor.execute("""SELECT id, userid FROM users WHERE userid=%s""", (str(ctx.author.id)))
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
await ctx.send("Mais tu as déja une carte d'identité ! u_u")
|
|
||||||
else:
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
|
|
||||||
self.cursor.execute("""INSERT INTO users(userid, username, useravatar, userbirth, cidate, cibureau) VALUES(%s, %s, %s, %s, %s, %s)""", (str(ctx.author.id), str(ctx.author), str(ctx.author.avatar_url_as(format="jpg", size=512)), str(ctx.author.created_at), now, str(ctx.message.guild.name)))
|
|
||||||
self.conn.commit()
|
|
||||||
await ctx.send(f":clap: Bievenue à toi {ctx.author.name} dans le communisme {ctx.message.guild.name} ! Fait ``.ci`` pour plus d'informations !")
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_ci.command(pass_context=True, name="delete")
|
|
||||||
async def ci_delete(self, ctx):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
self.cursor.execute("""SELECT id, userid FROM users WHERE userid=%s""", (str(ctx.author.id)))
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
self.cursor.execute("""DELETE FROM users WHERE userid =%s""", (str(ctx.author.id)))
|
|
||||||
self.conn.commit()
|
|
||||||
await ctx.send("Tu es maintenant sans papiers !")
|
|
||||||
else:
|
|
||||||
await ctx.send("Déja enregistre ta carte d'identité avant de la supprimer u_u (après c'est pas logique...)")
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_ci.command(pass_context=True, name="update")
|
|
||||||
async def ci_update(self, ctx):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.cursor.execute("""SELECT id, userid FROM users WHERE userid=%s""", (str(ctx.author.id)))
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
self.cursor.execute("""SELECT cidate FROM users WHERE userid=%s""",(str(ctx.author.id)))
|
|
||||||
old_ci_date = self.cursor.fetchone()
|
|
||||||
|
|
||||||
try:
|
|
||||||
new_ci_date = datetime.datetime.fromisoformat(old_ci_date[0])
|
|
||||||
except ValueError:
|
|
||||||
old_ci_date = datetime.datetime.strptime(old_ci_date[0].replace('/', '-'), '%d-%m-%Y')
|
|
||||||
|
|
||||||
old_ci_date_day = check_date(str(old_ci_date.day))
|
|
||||||
old_ci_date_month = check_date(str(old_ci_date.month))
|
|
||||||
|
|
||||||
new_ci_date = f"{str(old_ci_date.year)}-{str(old_ci_date_month)}-{str(old_ci_date_day)} 00:00:00.000000"
|
|
||||||
|
|
||||||
await ctx.send("succes update")
|
|
||||||
|
|
||||||
self.cursor.execute("""UPDATE users SET cidate = %s WHERE userid = %s""", (str(new_ci_date), str(ctx.author.id)))
|
|
||||||
self.conn.commit()
|
|
||||||
|
|
||||||
self.cursor.execute("""UPDATE users SET useravatar = %s, username = %s, cibureau = %s WHERE userid = %s""", (str(ctx.author.avatar_url_as(format="jpg", size=512)), str(ctx.author), str(ctx.message.guild), str(ctx.author.id)))
|
|
||||||
self.conn.commit()
|
|
||||||
await ctx.send(f"{ctx.author.mention}> Tu viens, en quelques sortes, de renaitre !")
|
|
||||||
else:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Veuillez enregistrer votre carte d'identité pour commencer !")
|
|
||||||
|
|
||||||
except Exception as e: #TODO : A virer dans l'event on_error
|
|
||||||
await ctx.send(':( Erreur veuillez contacter votre administrateur :')
|
|
||||||
await ctx.send(f'{type(e).__name__}: {e}')
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_ci.command(pass_context=True, name="setconfig")
|
|
||||||
async def ci_setconfig(self, ctx, *, conf: str = None):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
if conf:
|
|
||||||
self.cursor.execute("""SELECT id, userid FROM users WHERE userid=%s""", (str(ctx.author.id)))
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
self.cursor.execute("""UPDATE users SET config = %s WHERE userid = %s""", (str(conf), str(ctx.author.id)))
|
|
||||||
self.conn.commit()
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :ok_hand: Carte d'identité mise à jour !")
|
|
||||||
else:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Veuillez enregistrer votre carte d'identité pour commencer !")
|
|
||||||
else:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Il manque un paramètre !")
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_ci.command(pass_context=True, name="setos")
|
|
||||||
async def ci_setos(self, ctx, *, conf: str = None):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
if conf:
|
|
||||||
self.cursor.execute("""SELECT id, userid FROM users WHERE userid=%s""", (str(ctx.author.id)))
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
self.cursor.execute("""UPDATE users SET os = %s WHERE userid = %s""", (str(conf), str(ctx.author.id)))
|
|
||||||
self.conn.commit()
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :ok_hand: Carte d'identité mise à jour !")
|
|
||||||
else:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Veuillez enregistrer votre carte d'identité pour commencer !")
|
|
||||||
else:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Il manque un paramètre !")
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_ci.command(pass_context=True, name="setcountry")
|
|
||||||
async def ci_setcountry(self, ctx, *, country: str = None):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
if country:
|
|
||||||
self.cursor.execute("""SELECT id, userid FROM users WHERE userid=%s""", (str(ctx.author.id)))
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
self.cursor.execute("""UPDATE users SET pays = %s WHERE userid = %s""", (str(country), str(ctx.author.id)))
|
|
||||||
self.conn.commit()
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :ok_hand: Carte d'identité mise à jour !")
|
|
||||||
else:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Veuillez enregistrer votre carte d'identité pour commencer !")
|
|
||||||
else:
|
|
||||||
await ctx.send(f"{ctx.author.mention}> :x: Il manque un paramètre !")
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_ci.command(pass_context=True, name="online_edit")
|
|
||||||
async def ci_online_edit(self, ctx):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
self.cursor.execute("""SELECT id FROM users WHERE userid=%s""",(str(ctx.author.id)))
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
return await ctx.send(f"Déja enregistre ta carte d'identité avant de l'éditer u_u (après c'est pas logique...)")
|
|
||||||
|
|
||||||
dm = await ctx.author.create_dm()
|
|
||||||
|
|
||||||
try:
|
|
||||||
def is_exist(key, value):
|
|
||||||
self.cursor.execute("""SELECT * FROM sessions WHERE {}=%s""".format(str(key)), (str(value)))
|
|
||||||
return self.cursor.fetchone()
|
|
||||||
|
|
||||||
user_id = result[0]
|
|
||||||
is_admin = '1' if str(ctx.author.id) in self.bot.config.authorized_id else '0'
|
|
||||||
token = ''.join(random.sample('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'*25, 25))
|
|
||||||
created_at = datetime.datetime.utcnow()
|
|
||||||
|
|
||||||
while is_exist('token', token):
|
|
||||||
token = ''.join(random.sample('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'*25, 25))
|
|
||||||
|
|
||||||
if is_exist('user_id', user_id):
|
|
||||||
self.cursor.execute("""UPDATE sessions SET is_admin = %s, token = %s, updated_at = %s WHERE user_id = %s""", (str(is_admin), str(token), str(created_at), str(user_id)))
|
|
||||||
self.conn.commit()
|
|
||||||
else:
|
|
||||||
self.cursor.execute("""INSERT INTO sessions(user_id, is_admin, token, created_at, updated_at) VALUES(%s, %s, %s, %s, %s)""", (str(user_id), str(is_admin), str(token), str(created_at), str(created_at)))
|
|
||||||
self.conn.commit()
|
|
||||||
|
|
||||||
embed=discord.Embed(title="Clé d'édition pour tuxweb", description=f"Voici ta clé d'édition, vas sur [https://tuxbot.gnous.eu/fr/users/{user_id}](https://tuxbot.gnous.eu/fr/users/{user_id}) puis cliques sur `editer` et entre la clé afin de pouvoir modifier ta ci", colour=0x89C4F9)
|
|
||||||
embed.set_footer(text=f"Cette clé sera valide durant les 10 prochaines minutes, ne la communiques à personne !")
|
|
||||||
await dm.send(embed=embed)
|
|
||||||
await dm.send(token)
|
|
||||||
|
|
||||||
await ctx.send(f"{ctx.author.mention} ta clé d'édition t'a été envoyée en message privé")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
await ctx.send(f"{ctx.author.mention}, je ne peux pas t'envoyer de message privé :(. Penses à autoriser les messages privés provenant des membres du serveur pour que je puisse te donner ta clef d'édition")
|
|
||||||
|
|
||||||
"""--------------------------------------------------------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@checks.has_permissions(administrator=True)
|
|
||||||
@_ci.command(pass_context=True, name="list")
|
|
||||||
async def ci_list(self, ctx):
|
|
||||||
self.conn = db.connect_to_db(self)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
self.cursor.execute("""SELECT id, username FROM users""")
|
|
||||||
rows = self.cursor.fetchall()
|
|
||||||
msg = ""
|
|
||||||
try:
|
|
||||||
for row in rows:
|
|
||||||
row_id = row[0]
|
|
||||||
row_name = row[1].encode('utf-8')
|
|
||||||
msg += f"{str(row_id)} : {str(row_name)} \n"
|
|
||||||
post = requests.post("https://hastebin.com/documents", data=msg)
|
|
||||||
await ctx.send(f"{ctx.author.mention} liste posté avec succès sur :\nhttps://hastebin.com/{post.json()['key']}.txt")
|
|
||||||
|
|
||||||
with open('ci_list.txt', 'w', encoding='utf-8') as fp:
|
|
||||||
for row in rows:
|
|
||||||
row_id = row[0]
|
|
||||||
row_name = row[1]
|
|
||||||
|
|
||||||
fp.write(f"{str(row_id)} : {str(row_name)} \n")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
await ctx.send(f':sob: Une erreur est survenue : \n {type(e).__name__}: {e}')
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Identity(bot))
|
|
|
@ -1,60 +0,0 @@
|
||||||
from discord.ext import commands
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class FilterMessages(commands.Cog):
|
|
||||||
"""Flitre des messages"""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_message(self, message):
|
|
||||||
no_pub_guild = [280805240977227776, 303633056944881686,
|
|
||||||
274247231534792704]
|
|
||||||
lien_channel = [280805783795662848, 508794201509593088,
|
|
||||||
516017286948061204]
|
|
||||||
sondage_channel = [394146769107419146, 477147964393914388]
|
|
||||||
|
|
||||||
if message.author.bot \
|
|
||||||
or str(message.author.id) in self.bot.config.authorized_id \
|
|
||||||
or message.channel.permissions_for(message.author).administrator is True:
|
|
||||||
return
|
|
||||||
|
|
||||||
discord_invite_regex = re.compile(r"(discord\.(gg|io|me|li)|discordapp\.com\/invite)\/[0-9A-Za-z]*", re.IGNORECASE)
|
|
||||||
invalid_link_regex = re.compile(r"^(\[[^\]]+\]|<\:[a-z0-9]+\:[0-9]+>) .+ https?:\/\/\S*$", re.IGNORECASE)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if message.guild.id in no_pub_guild:
|
|
||||||
if isinstance(discord_invite_regex.search(message.content), re.Match):
|
|
||||||
author = self.bot.get_user(message.author.id)
|
|
||||||
await message.delete()
|
|
||||||
await author.send("La pub pour les serveurs discord n'est pas autorisée ici")
|
|
||||||
|
|
||||||
if message.channel.id in lien_channel \
|
|
||||||
and not isinstance(invalid_link_regex.search(message.content), re.Match):
|
|
||||||
author = self.bot.get_user(message.author.id)
|
|
||||||
await message.delete()
|
|
||||||
await author.send(f"Votre message `{message.content}` a été "
|
|
||||||
f"supprimé du channel `liens` ou `projets` "
|
|
||||||
f"car il ne respecte pas la structure "
|
|
||||||
f"définie. Pour partager un lien veuillez "
|
|
||||||
f"suivre la structure suivante :"
|
|
||||||
f" ` [Sujet] Descirption http(s)://....`")
|
|
||||||
await author.send("Si vous voulez commenter ou discuter à "
|
|
||||||
"propos d'un lien ou d'un projet, veuillez "
|
|
||||||
"le faire dans le channel"
|
|
||||||
" `#discussion-des-liens` ou"
|
|
||||||
" `#discussion-projets`.")
|
|
||||||
|
|
||||||
if message.channel.id in sondage_channel:
|
|
||||||
prefix_lenght = len(await self.bot.get_prefix(message))
|
|
||||||
command = (message.content.split()[0])[prefix_lenght:]
|
|
||||||
if command != "sondage":
|
|
||||||
await message.delete()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(FilterMessages(bot))
|
|
190
cogs/funs.py
190
cogs/funs.py
|
@ -1,190 +0,0 @@
|
||||||
from discord.ext import commands
|
|
||||||
import asyncio
|
|
||||||
import discord
|
|
||||||
import urllib.request
|
|
||||||
import json
|
|
||||||
import random
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
class Funs(commands.Cog):
|
|
||||||
"""Commandes funs."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def avatar(self, ctx, user: discord.Member = None):
|
|
||||||
"""Récuperer l'avatar de ..."""
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
user = ctx.message.author
|
|
||||||
|
|
||||||
embed = discord.Embed(title="Avatar de " + user.name,
|
|
||||||
description=f"[Ouvrir dans mon navigateur]"
|
|
||||||
f"({user.avatar_url_as(format='png')})")
|
|
||||||
embed.set_image(url=user.avatar_url_as(format='png'))
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
|
||||||
async def poke(self, ctx, user: discord.Member):
|
|
||||||
"""Poke quelqu'un"""
|
|
||||||
await ctx.send(f":clap: Hey {user.mention} tu t'es fait poker par"
|
|
||||||
f" {ctx.message.author} !")
|
|
||||||
await ctx.message.delete()
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def btcprice(self, ctx):
|
|
||||||
"""Le prix du BTC"""
|
|
||||||
loading = await ctx.send("_réfléchis..._")
|
|
||||||
try:
|
|
||||||
url = urllib.request.urlopen("https://blockchain.info/fr/ticker")
|
|
||||||
btc = json.loads(url.read().decode())
|
|
||||||
except KeyError:
|
|
||||||
btc = 1
|
|
||||||
|
|
||||||
if btc == 1:
|
|
||||||
await loading.edit(content="Impossible d'accèder à l'API"
|
|
||||||
" blockchain.info, veuillez réessayer"
|
|
||||||
" ultérieurment ! :c")
|
|
||||||
else:
|
|
||||||
frbtc = str(btc["EUR"]["last"]).replace(".", ",")
|
|
||||||
usbtc = str(btc["USD"]["last"]).replace(".", ",")
|
|
||||||
await loading.edit(content=f"Un bitcoin est égal à :"
|
|
||||||
f" {usbtc}$US soit {frbtc}€.")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def joke(self, ctx, number: str = 0):
|
|
||||||
"""Print a random joke in a json file"""
|
|
||||||
with open('texts/jokes.json') as js:
|
|
||||||
jk = json.load(js)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if 15 >= int(number) > 0:
|
|
||||||
clef = str(number)
|
|
||||||
else:
|
|
||||||
clef = str(random.randint(1, 15))
|
|
||||||
except Exception:
|
|
||||||
clef = str(random.randint(1, 15))
|
|
||||||
|
|
||||||
joke = jk["{}".format(clef)]
|
|
||||||
|
|
||||||
embed = discord.Embed(title="Blague _{}_ : ".format(clef),
|
|
||||||
description=joke['content'], colour=0x03C9A9)
|
|
||||||
embed.set_footer(text="Par " + joke['author'])
|
|
||||||
embed.set_thumbnail(url='https://outout.tech/tuxbot/blobjoy.png')
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def ethylotest(self, ctx):
|
|
||||||
"""Ethylotest simulator 2018"""
|
|
||||||
results_poulet = ["Désolé mais mon ethylotest est sous Windows Vista, "
|
|
||||||
"merci de patienter...",
|
|
||||||
"_(ethylotest)_ : Une erreur est survenue. Windows "
|
|
||||||
"cherche une solution à se problème.",
|
|
||||||
"Mais j'l'ai foutu où ce p\\*\\*\\* d'ethylotest de m\\*\\*\\* "
|
|
||||||
"bordel fait ch\\*\\*\\*",
|
|
||||||
"C'est pas possible z'avez cassé l'ethylotest !"]
|
|
||||||
results_client = ["D'accord, il n'y a pas de problème à cela je suis "
|
|
||||||
"complètement clean",
|
|
||||||
"Bien sur si c'est votre devoir !", "Suce bi\\*e !",
|
|
||||||
"J'ai l'air d'être bourré ?",
|
|
||||||
"_laissez moi prendre un bonbon à la menthe..._"]
|
|
||||||
|
|
||||||
result_p = random.choice(results_poulet)
|
|
||||||
result_c = random.choice(results_client)
|
|
||||||
|
|
||||||
await ctx.send(":oncoming_police_car: Bonjour bonjour, contrôle "
|
|
||||||
"d'alcoolémie !")
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await ctx.send(":man: " + result_c)
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
await ctx.send(":police_car: " + result_p)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def coin(self, ctx):
|
|
||||||
"""Coin flip simulator 2025"""
|
|
||||||
starts_msg = ["Je lance la pièce !", "C'est parti !", "C'est une pièce"
|
|
||||||
" d'un cent faut"
|
|
||||||
" pas la perdre",
|
|
||||||
"C'est une pièce d'un euro faut pas la perdre",
|
|
||||||
"Je lance !"]
|
|
||||||
results_coin = ["{0} pile", "{0} face", "{1} Heu c'est quoi pile c'est"
|
|
||||||
" quoi face enfaite ?",
|
|
||||||
"{1} Oh shit, je crois que je l'ai perdue",
|
|
||||||
"{1} Et bim je te vol ta pièce !",
|
|
||||||
"{0} Oh une erreur d'impression il n'y a ni pile ni"
|
|
||||||
" face !"]
|
|
||||||
|
|
||||||
start = random.choice(starts_msg)
|
|
||||||
result = random.choice(results_coin)
|
|
||||||
|
|
||||||
await ctx.send(start)
|
|
||||||
await asyncio.sleep(0.6)
|
|
||||||
await ctx.send(result.format(":moneybag: Et la pièce retombe sur ...",
|
|
||||||
":robot:"))
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def pokemon(self, ctx):
|
|
||||||
"""Random pokemon fight"""
|
|
||||||
with open('texts/pokemons.json') as js:
|
|
||||||
jk = json.load(js)
|
|
||||||
|
|
||||||
poke1 = jk[random.randint(1, 150)]
|
|
||||||
poke2 = jk[random.randint(1, 150)]
|
|
||||||
|
|
||||||
try:
|
|
||||||
if poke1['MaxHP'] > poke2['MaxHP']:
|
|
||||||
winer = poke1
|
|
||||||
else:
|
|
||||||
winer = poke2
|
|
||||||
except KeyError:
|
|
||||||
winer = poke1
|
|
||||||
|
|
||||||
await ctx.send(":flag_white: **Le combat commence !**")
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
await ctx.send(":loudspeaker: Les concurants sont {} contre {} ! Bonne"
|
|
||||||
" chance à eux !".format(poke1["Name"], poke2["Name"]))
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await ctx.send(":boom: {} commence et utilise {}".format(
|
|
||||||
poke1["Name"], poke1["Fast Attack(s)"][0]["Name"]))
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
await ctx.send(":dash: {} réplique avec {}".format(
|
|
||||||
poke2["Name"], poke2["Fast Attack(s)"][0]["Name"]))
|
|
||||||
await asyncio.sleep(1.2)
|
|
||||||
await ctx.send("_le combat continue de se dérouler..._")
|
|
||||||
await asyncio.sleep(1.5)
|
|
||||||
await ctx.send(":trophy: Le gagnant est **{}** !".format(
|
|
||||||
winer["Name"]))
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def randomcat(self, ctx):
|
|
||||||
"""Display a random cat"""
|
|
||||||
r = requests.get('http://aws.random.cat/meow')
|
|
||||||
cat = str(r.json()['file'])
|
|
||||||
embed = discord.Embed(title="Meow",
|
|
||||||
description="[Voir le chat plus grand]({})".
|
|
||||||
format(cat), colour=0x03C9A9)
|
|
||||||
embed.set_thumbnail(url=cat)
|
|
||||||
embed.set_author(name="Random.cat", url='https://random.cat/')
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Funs(bot))
|
|
|
@ -1,41 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import threading
|
|
||||||
from aiohttp import web
|
|
||||||
|
|
||||||
from discord.ext import commands
|
|
||||||
from bot import TuxBot
|
|
||||||
|
|
||||||
|
|
||||||
class Monitoring(commands.Cog):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.app = web.Application()
|
|
||||||
|
|
||||||
t = threading.Thread(
|
|
||||||
target=self.run_server,
|
|
||||||
args=(self.aiohttp_server(),)
|
|
||||||
)
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
def aiohttp_server(self):
|
|
||||||
async def hi(request):
|
|
||||||
return web.Response(text="I'm alive !")
|
|
||||||
|
|
||||||
self.app.add_routes([web.get('/', hi)])
|
|
||||||
runner = web.AppRunner(self.app)
|
|
||||||
|
|
||||||
return runner
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def run_server(runner):
|
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
loop.run_until_complete(runner.setup())
|
|
||||||
site = web.TCPSite(runner, '0.0.0.0', 3389)
|
|
||||||
loop.run_until_complete(site.start())
|
|
||||||
loop.run_forever()
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot: TuxBot):
|
|
||||||
bot.add_cog(Monitoring())
|
|
||||||
|
|
152
cogs/role.py
152
cogs/role.py
|
@ -1,152 +0,0 @@
|
||||||
from discord.ext import commands
|
|
||||||
import discord
|
|
||||||
|
|
||||||
|
|
||||||
class Role(commands.Cog):
|
|
||||||
"""Commandes role."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
self.ARCH_ROLE = 393077257826205706
|
|
||||||
self.DEBIAN_ROLE = 393077933209550859
|
|
||||||
self.RHEL_ROLE = 393078333245751296
|
|
||||||
self.ANDROID_ROLE = 393087862972612627
|
|
||||||
self.BSD_ROLE = 401791543708745738
|
|
||||||
|
|
||||||
@commands.group(name="role", no_pm=True, pass_context=True,
|
|
||||||
case_insensitive=True)
|
|
||||||
async def _role(self, ctx):
|
|
||||||
"""Affiche l'aide sur la commande role"""
|
|
||||||
if ctx.message.guild.id != 280805240977227776:
|
|
||||||
return
|
|
||||||
|
|
||||||
if ctx.invoked_subcommand is None:
|
|
||||||
text = open('texts/roles.md').read()
|
|
||||||
em = discord.Embed(title='Gestionnaires de rôles',
|
|
||||||
description=text, colour=0x89C4F9)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_role.command(name="arch", aliases=["archlinux", "arch_linux"],
|
|
||||||
pass_context=True)
|
|
||||||
async def role_arch(self, ctx):
|
|
||||||
"""Ajoute/retire le role 'Arch user'"""
|
|
||||||
roles = ctx.message.author.roles
|
|
||||||
role_id = []
|
|
||||||
for role in roles:
|
|
||||||
role_id.append(role.id)
|
|
||||||
|
|
||||||
user = ctx.message.author
|
|
||||||
if self.ARCH_ROLE in role_id:
|
|
||||||
await user.remove_roles(discord.Object(id=self.ARCH_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Pourquoi tu viens "
|
|
||||||
f"de supprimer Arch Linux, c'était trop compliqué "
|
|
||||||
f"pour toi ? <:sad:343723037331292170>")
|
|
||||||
else:
|
|
||||||
await user.add_roles(discord.Object(id=self.ARCH_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > How un "
|
|
||||||
f"ArchLinuxien, c'est bon les ``yaourt`` ? "
|
|
||||||
f"<:hap:354275645574086656>")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_role.command(name="debian", pass_context=True)
|
|
||||||
async def role_debian(self, ctx):
|
|
||||||
"""Ajoute/retire le role 'debian user'"""
|
|
||||||
roles = ctx.message.author.roles
|
|
||||||
role_id = []
|
|
||||||
for role in roles:
|
|
||||||
role_id.append(role.id)
|
|
||||||
|
|
||||||
user = ctx.message.author
|
|
||||||
if self.DEBIAN_ROLE in role_id:
|
|
||||||
await user.remove_roles(discord.Object(id=self.DEBIAN_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Adieu ! Tu verras, "
|
|
||||||
f"APT te manquera ! ")
|
|
||||||
else:
|
|
||||||
await user.add_roles(discord.Object(id=self.DEBIAN_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Un utilisateur de "
|
|
||||||
f"Debian, encore et encore ! "
|
|
||||||
f"<:stuck_out_tongue:343723077412323339>")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_role.command(name="rhel", pass_context=True)
|
|
||||||
async def role_rhel(self, ctx):
|
|
||||||
"""Ajoute/retire le role 'rhel user'"""
|
|
||||||
roles = ctx.message.author.roles
|
|
||||||
role_id = []
|
|
||||||
for role in roles:
|
|
||||||
role_id.append(role.id)
|
|
||||||
|
|
||||||
user = ctx.message.author
|
|
||||||
if self.RHEL_ROLE in role_id:
|
|
||||||
await user.remove_roles(discord.Object(id=self.RHEL_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Pourquoi tu t'en "
|
|
||||||
f"vas, il sont déjà assez seul là-bas "
|
|
||||||
f"<:sad:343723037331292170>")
|
|
||||||
else:
|
|
||||||
await user.add_roles(discord.Object(id=self.RHEL_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Mais, voila "
|
|
||||||
f"quelqu'un qui porte des chapeaux ! "
|
|
||||||
f"<:hap:354275645574086656>")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_role.command(name="android", pass_context=True)
|
|
||||||
async def role_android(self, ctx):
|
|
||||||
"""Ajoute/retire le role 'android user'"""
|
|
||||||
roles = ctx.message.author.roles
|
|
||||||
role_id = []
|
|
||||||
for role in roles:
|
|
||||||
role_id.append(role.id)
|
|
||||||
|
|
||||||
user = ctx.message.author
|
|
||||||
if self.ANDROID_ROLE in role_id:
|
|
||||||
await user.remove_roles(discord.Object(id=self.ANDROID_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} >How, me dit pas "
|
|
||||||
f"que tu as compris que les Android's allaient "
|
|
||||||
f"exterminer le monde ? "
|
|
||||||
f"<:trollface:375327667160875008>")
|
|
||||||
else:
|
|
||||||
await user.add_roles(discord.Object(id=self.ANDROID_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Hey, un utilisateur"
|
|
||||||
f" d'Android, prêt à continuer l'extermination de "
|
|
||||||
f"WP et iOS ? "
|
|
||||||
f"<:stuck_out_tongue:343723077412323339>")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_role.command(name="bsd", pass_context=True)
|
|
||||||
async def role_bsd(self, ctx):
|
|
||||||
"""Ajoute/retire le role 'BSD user'"""
|
|
||||||
roles = ctx.message.author.roles
|
|
||||||
role_id = []
|
|
||||||
for role in roles:
|
|
||||||
role_id.append(role.id)
|
|
||||||
|
|
||||||
user = ctx.message.author
|
|
||||||
if self.BSD_ROLE in role_id:
|
|
||||||
await user.remove_roles(discord.Object(id=self.BSD_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Ohhhh fait gaffe "
|
|
||||||
f"ou le démon va te piquer")
|
|
||||||
else:
|
|
||||||
await user.add_roles(discord.Object(id=self.BSD_ROLE))
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Quelqu'un sous "
|
|
||||||
f"BSD ! Au moins il a pas besoin de mettre GNU "
|
|
||||||
f"devant son OS à chaque fois :d")
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@_role.command(name="staff", pass_context=True, hidden=True)
|
|
||||||
async def role_staff(self, ctx):
|
|
||||||
"""Easter egg"""
|
|
||||||
|
|
||||||
await ctx.send(f"{ctx.message.author.mention} > Vous n'avez pas "
|
|
||||||
f"le rôle staff, tu crois quoi :joy:")
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Role(bot))
|
|
157
cogs/search.py
157
cogs/search.py
|
@ -1,157 +0,0 @@
|
||||||
from discord.ext import commands
|
|
||||||
import discord
|
|
||||||
import asyncio
|
|
||||||
import urllib.request
|
|
||||||
import wikipedia
|
|
||||||
|
|
||||||
wikipedia.set_lang("fr")
|
|
||||||
|
|
||||||
|
|
||||||
class Search(commands.Cog):
|
|
||||||
"""Commandes de WWW."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.group(name="search", no_pm=True, pass_context=True)
|
|
||||||
async def _search(self, ctx):
|
|
||||||
"""Rechercher sur le world wide web"""
|
|
||||||
if ctx.invoked_subcommand is None:
|
|
||||||
text = open('texts/search.md').read()
|
|
||||||
em = discord.Embed(title='Commandes de search TuxBot',
|
|
||||||
description=text,
|
|
||||||
colour=0x89C4F9)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@_search.command(pass_context=True, name="docubuntu")
|
|
||||||
async def search_docubuntu(self, ctx, args):
|
|
||||||
attends = await ctx.send("_Je te cherche ça {} !_".format(
|
|
||||||
ctx.message.author.mention))
|
|
||||||
html = urllib.request.urlopen("https://doc.ubuntu-fr.org/" +
|
|
||||||
args).read()
|
|
||||||
if "avez suivi un lien" in str(html):
|
|
||||||
await attends.edit(content=":sob: Nooooon ! Cette page n'existe "
|
|
||||||
"pas, mais tu peux toujours la créer : "
|
|
||||||
"https://doc.ubuntu-fr.org/" + args)
|
|
||||||
else:
|
|
||||||
await attends.delete()
|
|
||||||
embed = discord.Embed(description="Voila j'ai trouvé ! Voici la "
|
|
||||||
"page ramenant à votre recherche,"
|
|
||||||
" toujours aussi bien rédigée "
|
|
||||||
":wink: : https://doc.ubuntu-fr."
|
|
||||||
"org/" + args,
|
|
||||||
url='http://doc.ubuntu-fr.org/')
|
|
||||||
embed.set_author(name="DocUbuntu-Fr",
|
|
||||||
url='http://doc.ubuntu-fr.org/',
|
|
||||||
icon_url='https://tuxbot.outout.xyz/data/ubuntu.png')
|
|
||||||
embed.set_thumbnail(url='https://tuxbot.outout.xyz/data/ubuntu.png')
|
|
||||||
embed.set_footer(text="Merci à ceux qui ont pris le temps d'écrire "
|
|
||||||
"cette documentation")
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
@_search.command(pass_context=True, name="docarch")
|
|
||||||
async def search_docarch(self, ctx, args):
|
|
||||||
attends = await ctx.send("_Je te cherche ça {} !_".format(
|
|
||||||
ctx.message.author.mention))
|
|
||||||
html = urllib.request.urlopen("https://wiki.archlinux.org/index.php/" +
|
|
||||||
args).read()
|
|
||||||
if "There is currently no text in this page" in str(html):
|
|
||||||
await attends.edit(content=":sob: Nooooon ! Cette page n'existe "
|
|
||||||
"pas.")
|
|
||||||
else:
|
|
||||||
await attends.delete()
|
|
||||||
embed = discord.Embed(description="Voila j'ai trouvé ! Voici la "
|
|
||||||
"page ramenant à votre recherche,"
|
|
||||||
" toujours aussi bien rédigée "
|
|
||||||
":wink: : https://wiki.archlinux."
|
|
||||||
"org/index.php/" + args,
|
|
||||||
url='https://wiki.archlinux.org/index.php/')
|
|
||||||
embed.set_author(name="Doc ArchLinux",
|
|
||||||
url='https://wiki.archlinux.org/index.php/',
|
|
||||||
icon_url='https://tuxbot.outout.xyz/data/arch.png')
|
|
||||||
embed.set_thumbnail(url='https://tuxbot.outout.xyz/data/arch.png')
|
|
||||||
embed.set_footer(text="Merci à ceux qui ont pris le temps d'écrire "
|
|
||||||
"cette documentation")
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
@_search.command(pass_context=True, name="wikipedia")
|
|
||||||
async def search_wikipedia(self, ctx: commands.Context, args):
|
|
||||||
"""Fait une recherche sur wikipd"""
|
|
||||||
|
|
||||||
wait = await ctx.send("_Je cherche..._")
|
|
||||||
results = wikipedia.search(args)
|
|
||||||
nbmr = 0
|
|
||||||
mmssgg = ""
|
|
||||||
|
|
||||||
for value in results:
|
|
||||||
nbmr = nbmr + 1
|
|
||||||
mmssgg = mmssgg + "**{}**: {} \n".format(str(nbmr), value)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Résultats de : ' + args,
|
|
||||||
description = mmssgg,
|
|
||||||
colour=0x4ECDC4)
|
|
||||||
em.set_thumbnail(url="https://upload.wikimedia.org/wikipedia/commons/"
|
|
||||||
"2/26/Paullusmagnus-logo_%28large%29.png")
|
|
||||||
await wait.delete()
|
|
||||||
|
|
||||||
sending = ["1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣", "🔟"]
|
|
||||||
|
|
||||||
def check(reaction, user):
|
|
||||||
return user == ctx.author and reaction.emoji in sending and \
|
|
||||||
reaction.message.id == msg.id
|
|
||||||
|
|
||||||
async def waiter(future: asyncio.Future):
|
|
||||||
reaction, user = await self.bot.wait_for('reaction_add',
|
|
||||||
check=check)
|
|
||||||
future.set_result(reaction.emoji)
|
|
||||||
|
|
||||||
emoji = asyncio.Future()
|
|
||||||
self.bot.loop.create_task(waiter(emoji))
|
|
||||||
|
|
||||||
msg = await ctx.send(embed=em)
|
|
||||||
for e in sending:
|
|
||||||
await msg.add_reaction(e)
|
|
||||||
if emoji.done():
|
|
||||||
break
|
|
||||||
|
|
||||||
while not emoji.done():
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
|
|
||||||
page = int(sending.index(emoji.result()))
|
|
||||||
|
|
||||||
args_ = results[page]
|
|
||||||
|
|
||||||
try:
|
|
||||||
await msg.delete()
|
|
||||||
await ctx.trigger_typing()
|
|
||||||
wait = await ctx.send(ctx.message.author.mention +
|
|
||||||
" ah ok sympa cette recherche, je l'effectue de suite !")
|
|
||||||
wp = wikipedia.page(args_)
|
|
||||||
wp_contenu = wp.summary[:200] + "..."
|
|
||||||
em = discord.Embed(title='Wikipedia : ' + wp.title,
|
|
||||||
description = "{} \n_Lien_ : {} ".format(
|
|
||||||
wp_contenu, wp.url),
|
|
||||||
colour=0x9B59B6)
|
|
||||||
em.set_author(name="Wikipedia",
|
|
||||||
url='http://wikipedia.org',
|
|
||||||
icon_url='https://upload.wikimedia.org/wikipedia/'
|
|
||||||
'commons/2/26/Paullusmagnus-logo_%28large'
|
|
||||||
'%29.png')
|
|
||||||
em.set_thumbnail(url = "https://upload.wikimedia.org/wikipedia/"
|
|
||||||
"commons/2/26/Paullusmagnus-logo_%28large"
|
|
||||||
"%29.png")
|
|
||||||
em.set_footer(text="Merci à eux de nous fournir une encyclopédie "
|
|
||||||
"libre !")
|
|
||||||
await wait.delete()
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
except wikipedia.exceptions.PageError:
|
|
||||||
# TODO : A virer dans l'event on_error
|
|
||||||
await ctx.send(":open_mouth: Une **erreur interne** est survenue,"
|
|
||||||
" si cela ce reproduit contactez votre"
|
|
||||||
" administrateur ou faites une Issue sur"
|
|
||||||
" ``gitea`` !")
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Search(bot))
|
|
|
@ -1,105 +0,0 @@
|
||||||
import datetime
|
|
||||||
import socket
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
|
|
||||||
class SendLogs(commands.Cog):
|
|
||||||
"""Send logs to a specific channel"""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
|
|
||||||
self.bot = bot
|
|
||||||
self.log_channel = None
|
|
||||||
self.main_server_id = int(self.bot.config.main_server_id)
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_resumed(self):
|
|
||||||
em = discord.Embed(title="Et hop je me reconnecte à l'api 😃",
|
|
||||||
colour=0x5cb85c)
|
|
||||||
em.timestamp = datetime.datetime.utcnow()
|
|
||||||
await self.log_channel.send(embed=em)
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_ready(self):
|
|
||||||
self.log_channel = await self.bot.fetch_channel(int(self.bot.config.log_channel_id))
|
|
||||||
em = discord.Embed(title="Je suis opérationnel 😃",
|
|
||||||
description=f"*Instance lancée sur "
|
|
||||||
f"{socket.gethostname()}*", colour=0x5cb85c)
|
|
||||||
em.timestamp = datetime.datetime.utcnow()
|
|
||||||
await self.log_channel.send(embed=em)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_guild_join(self, guild: discord.Guild):
|
|
||||||
em = discord.Embed(title=f"On m'a ajouté à : {str(guild.name)} 😃",
|
|
||||||
colour=0x51A351)
|
|
||||||
em.timestamp = datetime.datetime.utcnow()
|
|
||||||
await self.log_channel.send(embed=em)
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_guild_remove(self, guild: discord.Guild):
|
|
||||||
em = discord.Embed(title=f"On m'a viré de : {str(guild.name)} 😦",
|
|
||||||
colour=0xBD362F)
|
|
||||||
em.timestamp = datetime.datetime.utcnow()
|
|
||||||
await self.log_channel.send(embed=em)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_member_join(self, member):
|
|
||||||
if member.guild.id == self.main_server_id:
|
|
||||||
em = discord.Embed(title=f"{str(member)} *`({str(member.id)})`* "
|
|
||||||
f"nous a rejoint 😃", colour=0x51A351)
|
|
||||||
em.set_footer(text=f"Compte crée le {member.created_at}")
|
|
||||||
em.timestamp = datetime.datetime.utcnow()
|
|
||||||
await self.log_channel.send(embed=em)
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_member_remove(self, member):
|
|
||||||
if member.guild.id == self.main_server_id:
|
|
||||||
em = discord.Embed(title=f"{str(member)} *`({str(member.id)})`* "
|
|
||||||
f"nous a quitté 😦", colour=0xBD362F)
|
|
||||||
em.timestamp = datetime.datetime.utcnow()
|
|
||||||
await self.log_channel.send(embed=em)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_message_delete(self, message):
|
|
||||||
if message.guild.id == self.main_server_id and not message.author.bot:
|
|
||||||
async def is_a_command(message):
|
|
||||||
prefix_lenght = len(await self.bot.get_prefix(message))
|
|
||||||
command = (message.content.split()[0])[prefix_lenght:]
|
|
||||||
if command == '':
|
|
||||||
command = "not_a_command"
|
|
||||||
|
|
||||||
return self.bot.get_command(str(command))
|
|
||||||
|
|
||||||
if await is_a_command(message) is None:
|
|
||||||
em = discord.Embed(title=f"Message supprimé dans :"
|
|
||||||
f" {str(message.channel.name)}",
|
|
||||||
colour=0xBD362F)
|
|
||||||
em.add_field(name=f"{str(message.author)} "
|
|
||||||
f"*`({str(message.author.id)})`* "
|
|
||||||
f"a supprimé :", value=str(message.content))
|
|
||||||
em.timestamp = datetime.datetime.utcnow()
|
|
||||||
await self.log_channel.send(embed=em)
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_message_edit(self, before, after):
|
|
||||||
if before.guild.id == self.main_server_id and not before.author.bot:
|
|
||||||
em = discord.Embed(title=f"Message edité dans : "
|
|
||||||
f"{before.channel.name}", colour=0x0088CC)
|
|
||||||
em.add_field(name=f"{str(before.author)} "
|
|
||||||
f"*`({str(before.author.id)})`* a"
|
|
||||||
f" edité :", value=str(before.content))
|
|
||||||
em.add_field(name="Pour remplacer par :", value=str(after.content))
|
|
||||||
em.timestamp = datetime.datetime.utcnow()
|
|
||||||
await self.log_channel.send(embed=em)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(SendLogs(bot))
|
|
|
@ -1,99 +0,0 @@
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
|
|
||||||
class Sondage(commands.Cog):
|
|
||||||
"""Commandes sondage."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.command(pass_context=True)
|
|
||||||
async def sondage(self, ctx, *, msg="help"):
|
|
||||||
if msg != "help":
|
|
||||||
await ctx.message.delete()
|
|
||||||
options = msg.split(" | ")
|
|
||||||
|
|
||||||
times = [x for x in options if x.startswith("time=")]
|
|
||||||
|
|
||||||
if times:
|
|
||||||
time = int(times[0].strip("time="))
|
|
||||||
options.remove(times[0])
|
|
||||||
else:
|
|
||||||
time = 0
|
|
||||||
|
|
||||||
if len(options) <= 1:
|
|
||||||
raise commands.errors.MissingRequiredArgument
|
|
||||||
if len(options) >= 22:
|
|
||||||
return await ctx.send(f"{ctx.message.author.mention}> "
|
|
||||||
f":octagonal_sign: Vous ne pouvez pas "
|
|
||||||
f"mettre plus de 20 réponses !")
|
|
||||||
|
|
||||||
emoji = ['1⃣', '2⃣', '3⃣', '4⃣', '5⃣', '6⃣', '7⃣', '8⃣', '9⃣', '🔟', '0⃣',
|
|
||||||
'🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮']
|
|
||||||
to_react = []
|
|
||||||
confirmation_msg = f"**{options[0].rstrip('?')}?**:\n\n"
|
|
||||||
|
|
||||||
for idx, option in enumerate(options[1:]):
|
|
||||||
confirmation_msg += f"{emoji[idx]} - {option}\n"
|
|
||||||
to_react.append(emoji[idx])
|
|
||||||
|
|
||||||
confirmation_msg += "*Sondage proposé par* " + \
|
|
||||||
str(ctx.message.author.mention)
|
|
||||||
if time == 0:
|
|
||||||
confirmation_msg += ""
|
|
||||||
else:
|
|
||||||
confirmation_msg += f"\n\nVous avez {time} secondes pour voter!"
|
|
||||||
|
|
||||||
poll_msg = await ctx.send(confirmation_msg)
|
|
||||||
for emote in to_react:
|
|
||||||
await poll_msg.add_reaction(emote)
|
|
||||||
|
|
||||||
if time != 0:
|
|
||||||
await asyncio.sleep(time)
|
|
||||||
async for message in ctx.message.channel.history():
|
|
||||||
if message.id == poll_msg.id:
|
|
||||||
poll_msg = message
|
|
||||||
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
for reaction in poll_msg.reactions:
|
|
||||||
if reaction.emoji in to_react:
|
|
||||||
results[reaction.emoji] = reaction.count - 1
|
|
||||||
end_msg = "Le sondage est términé. Les résultats sont:\n\n"
|
|
||||||
|
|
||||||
for result in results:
|
|
||||||
end_msg += "{} {} - {} votes\n". \
|
|
||||||
format(result,
|
|
||||||
options[emoji.index(result)+1],
|
|
||||||
results[result])
|
|
||||||
|
|
||||||
top_result = max(results, key=lambda key: results[key])
|
|
||||||
|
|
||||||
if len([x for x in results
|
|
||||||
if results[x] == results[top_result]]) > 1:
|
|
||||||
top_results = []
|
|
||||||
for key, value in results.items():
|
|
||||||
if value == results[top_result]:
|
|
||||||
top_results.append(options[emoji.index(key)+1])
|
|
||||||
end_msg += "\nLes gagnants sont : {}". \
|
|
||||||
format(", ".join(top_results))
|
|
||||||
else:
|
|
||||||
top_result = options[emoji.index(top_result)+1]
|
|
||||||
end_msg += "\n\"{}\" est le gagnant!".format(top_result)
|
|
||||||
await ctx.send(end_msg)
|
|
||||||
else:
|
|
||||||
await ctx.send("please use `@tuxbot poll` (this is rewrite version in beta")
|
|
||||||
await ctx.message.delete()
|
|
||||||
|
|
||||||
text = open('texts/rpoll.md').read()
|
|
||||||
em = discord.Embed(title='Aide sur le sondage',
|
|
||||||
description=text,
|
|
||||||
colour=0xEEEEEE)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Sondage(bot))
|
|
363
cogs/utility.py
363
cogs/utility.py
|
@ -1,363 +0,0 @@
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import pytz
|
|
||||||
import random
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
import discord
|
|
||||||
import requests
|
|
||||||
from discord.ext import commands
|
|
||||||
import socket
|
|
||||||
|
|
||||||
class Utility(commands.Cog):
|
|
||||||
"""Commandes utilitaires."""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.group(name="clock", pass_context=True, case_insensitive=True)
|
|
||||||
async def clock(self, ctx):
|
|
||||||
"""Display hour in a country"""
|
|
||||||
|
|
||||||
if ctx.invoked_subcommand is None:
|
|
||||||
text = open('texts/clocks.md').read()
|
|
||||||
em = discord.Embed(title='Liste des Horloges', description=text, colour=0xEEEEEE)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="montréal", aliases=["mtl", "montreal"], pass_context=True)
|
|
||||||
async def clock_montreal(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('America/Montreal'))
|
|
||||||
site = "http://ville.montreal.qc.ca/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/e/e0/Rentier_fws_1.jpg"
|
|
||||||
country = "au Canada, Québec"
|
|
||||||
description = "Montréal est la deuxième ville la plus peuplée du Canada. Elle se situe dans la région du Québec"
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à Montréal', description=f"A [Montréal]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="vancouver", pass_context=True)
|
|
||||||
async def clock_vancouver(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('America/Vancouver'))
|
|
||||||
site = "http://vancouver.ca/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/f/fe/Dock_Vancouver.JPG"
|
|
||||||
country = "au Canada"
|
|
||||||
description = "Vancouver, officiellement City of Vancouver, est une cité portuaire au Canada"
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à Vancouver', description=f"A [Vancouver]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="new-york",aliases=["ny", "n-y", "new york"], pass_context=True)
|
|
||||||
async def clock_new_york(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('America/New_York'))
|
|
||||||
site = "http://www1.nyc.gov/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/e/e3/NewYork_LibertyStatue.jpg"
|
|
||||||
country = "aux U.S.A."
|
|
||||||
description = "New York, est la plus grande ville des États-Unis en termes d'habitants et l'une des plus importantes du continent américain. "
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à New York', description=f"A [str(New York]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="la", aliases=["los-angeles", "losangeles", "l-a", "los angeles"], pass_context=True)
|
|
||||||
async def clock_la(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('America/Los_Angeles'))
|
|
||||||
site = "https://www.lacity.org/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/57/LA_Skyline_Mountains2.jpg/800px-LA_Skyline_Mountains2.jpg"
|
|
||||||
country = "aux U.S.A."
|
|
||||||
description = "Los Angeles est la deuxième ville la plus peuplée des États-Unis après New York. Elle est située dans le sud de l'État de Californie, sur la côte pacifique."
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à Los Angeles', description=f"A [Los Angeles]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="paris", aliases=["baguette"],pass_context=True)
|
|
||||||
async def clock_paris(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('Europe/Paris'))
|
|
||||||
site = "http://www.paris.fr/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/a/af/Tour_eiffel_at_sunrise_from_the_trocadero.jpg"
|
|
||||||
country = "en France"
|
|
||||||
description = "Paris est la capitale de la France. Elle se situe au cœur d'un vaste bassin sédimentaire aux sols fertiles et au climat tempéré, le bassin parisien."
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à Paris', description=f"A [Paris]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="berlin", pass_context=True)
|
|
||||||
async def clock_berlin(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('Europe/Berlin'))
|
|
||||||
site = "http://www.berlin.de/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/9/91/Eduard_Gaertner_Schlossfreiheit.jpg"
|
|
||||||
country = "en Allemagne"
|
|
||||||
description = "Berlin est la capitale et la plus grande ville d'Allemagne. Située dans le nord-est du pays, elle compte environ 3,5 millions d'habitants. "
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à Berlin', description=f"A [Berlin]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="berne", aliases=["zurich", "bern"], pass_context=True)
|
|
||||||
async def clock_berne(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('Europe/Zurich'))
|
|
||||||
site = "http://www.berne.ch/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/d/db/Justitia_Statue_02.jpg"
|
|
||||||
country = "en Suisse"
|
|
||||||
description = "Berne est la cinquième plus grande ville de Suisse et la capitale du canton homonyme. Depuis 1848, Berne est la « ville fédérale »."
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à Berne', description=f"A [Berne]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="tokyo", pass_context=True)
|
|
||||||
async def clock_tokyo(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('Asia/Tokyo'))
|
|
||||||
site = "http://www.gotokyo.org/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/3/37/TaroTokyo20110213-TokyoTower-01.jpg"
|
|
||||||
country = "au Japon"
|
|
||||||
description = "Tokyo, anciennement Edo, officiellement la préfecture métropolitaine de Tokyo, est la capitale du Japon."
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à Tokyo', description=f"A [Tokyo]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@clock.command(name="moscou", aliases=["moscow", "moskova"], pass_context=True)
|
|
||||||
async def clock_moscou(self, ctx):
|
|
||||||
then = datetime.datetime.now(pytz.utc)
|
|
||||||
|
|
||||||
utc = then.astimezone(pytz.timezone('Europe/Moscow'))
|
|
||||||
site = "https://www.mos.ru/"
|
|
||||||
img = "https://upload.wikimedia.org/wikipedia/commons/f/f7/Andreyevsky_Zal.jpg"
|
|
||||||
country = "en Russie"
|
|
||||||
description = "Moscou est la capitale de la Fédération de Russie et la plus grande ville d'Europe. Moscou est situé sur la rivière Moskova. "
|
|
||||||
|
|
||||||
form = '%H heures %M'
|
|
||||||
tt = utc.strftime(form)
|
|
||||||
|
|
||||||
em = discord.Embed(title='Heure à Moscou', description=f"A [Moscou]({site}) {country}, Il est **{str(tt)}** ! \n {description} \n _source des images et du texte : [Wikimedia foundation](http://commons.wikimedia.org/)_", colour=0xEEEEEE)
|
|
||||||
em.set_thumbnail(url = img)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def ytdiscover(self, ctx):
|
|
||||||
"""Random youtube channel"""
|
|
||||||
with open('texts/ytb.json') as js:
|
|
||||||
ytb = json.load(js)
|
|
||||||
|
|
||||||
clef = str(random.randint(0,12))
|
|
||||||
chaine = ytb["{}".format(clef)]
|
|
||||||
|
|
||||||
embed = discord.Embed(title=chaine['name'],
|
|
||||||
url=chaine['url'],
|
|
||||||
description=f"**{chaine['name']}**, {chaine['desc']} \n[Je veux voir ça]({chaine['url']})")
|
|
||||||
embed.set_thumbnail(url='https://outout.tech/tuxbot/yt.png')
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command(name='iplocalise', pass_context=True)
|
|
||||||
async def _iplocalise(self, ctx, ipaddress, iptype=""):
|
|
||||||
realipaddress = ipaddress
|
|
||||||
"""Recup headers."""
|
|
||||||
if ipaddress.startswith("http://"):
|
|
||||||
if ipaddress[-1:] == '/':
|
|
||||||
ipaddress = ipaddress[:-1]
|
|
||||||
ipaddress = ipaddress.split("http://")[1]
|
|
||||||
if ipaddress.startswith("https://"):
|
|
||||||
if ipaddress[-1:] == '/':
|
|
||||||
ipaddress = ipaddress[:-1]
|
|
||||||
ipaddress = ipaddress.split("https://")[1]
|
|
||||||
|
|
||||||
if(iptype=="ipv6" or iptype=="v6"):
|
|
||||||
try:
|
|
||||||
ipaddress = socket.getaddrinfo(ipaddress, None, socket.AF_INET6)[1][4][0]
|
|
||||||
|
|
||||||
if (ipaddress == "2602:fe53:b:1::87") and not ("gnous" in realipaddress):
|
|
||||||
await ctx.send("Erreur, cette adresse n'est pas disponible en IPv6.")
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
await ctx.send("Erreur, cette adresse n'est pas disponible en IPv6.")
|
|
||||||
print(e)
|
|
||||||
return
|
|
||||||
|
|
||||||
iploading = await ctx.send("_réfléchis..._")
|
|
||||||
ipapi = urllib.request.urlopen("http://ip-api.com/json/" + ipaddress)
|
|
||||||
ipinfo = json.loads(ipapi.read().decode())
|
|
||||||
|
|
||||||
if ipinfo["status"] != "fail":
|
|
||||||
if ipinfo['query']:
|
|
||||||
embed = discord.Embed(title=f"Informations pour ``{realipaddress}`` *`({ipinfo['query']})`*", color=0x5858d7)
|
|
||||||
|
|
||||||
if ipinfo['org']:
|
|
||||||
embed.add_field(name="Appartient à :", value=ipinfo['org'], inline = False)
|
|
||||||
|
|
||||||
if ipinfo['city']:
|
|
||||||
embed.add_field(name="Se situe à :", value=ipinfo['city'], inline = True)
|
|
||||||
|
|
||||||
|
|
||||||
if ipinfo['country']:
|
|
||||||
if ipinfo['regionName']:
|
|
||||||
regionName = ipinfo['regionName']
|
|
||||||
else:
|
|
||||||
regionName = "N/A"
|
|
||||||
embed.add_field(name="Region :", value=f"{regionName} ({ipinfo['country']})", inline = True)
|
|
||||||
|
|
||||||
embed.set_thumbnail(url=f"https://www.countryflags.io/{ipinfo['countryCode']}/flat/64.png")
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
else:
|
|
||||||
await ctx.send(content=f"Erreur, impossible d'avoir des informations sur l'adresse IP ``{ipinfo['query']}``")
|
|
||||||
await iploading.delete()
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
@commands.command(name='getheaders', pass_context=True)
|
|
||||||
async def _getheaders(self, ctx, *, adresse):
|
|
||||||
"""Recuperer les HEADERS :d"""
|
|
||||||
if adresse.startswith("http://") != True and adresse.startswith("https://") != True:
|
|
||||||
adresse = "http://" + adresse
|
|
||||||
if len(adresse) > 200:
|
|
||||||
await ctx.send("{0} Essaye d'entrer une adresse de moins de 200 caractères plutôt.".format(ctx.author.mention))
|
|
||||||
|
|
||||||
elif adresse.startswith("http://") or adresse.startswith("https://") or adresse.startswith("ftp://"):
|
|
||||||
try:
|
|
||||||
get = urllib.request.urlopen(adresse, timeout = 1)
|
|
||||||
embed = discord.Embed(title="Entêtes de {0}".format(adresse), color=0xd75858)
|
|
||||||
embed.add_field(name="Code Réponse", value=get.getcode(), inline = True)
|
|
||||||
embed.set_thumbnail(url="https://http.cat/{}".format(str(get.getcode())))
|
|
||||||
if get.getheader('location'):
|
|
||||||
embed.add_field(name="Redirection vers", value=get.getheader('location'), inline=True)
|
|
||||||
if get.getheader('server'):
|
|
||||||
embed.add_field(name="Serveur", value=get.getheader('server'), inline=True)
|
|
||||||
if get.getheader('content-type'):
|
|
||||||
embed.add_field(name="Type de contenu", value = get.getheader('content-type'), inline = True)
|
|
||||||
if get.getheader('x-content-type-options'):
|
|
||||||
embed.add_field(name="x-content-type", value= get.getheader('x-content-type-options'), inline=True)
|
|
||||||
if get.getheader('x-frame-options'):
|
|
||||||
embed.add_field(name="x-frame-options", value= get.getheader('x-frame-options'), inline=True)
|
|
||||||
if get.getheader('cache-control'):
|
|
||||||
embed.add_field(name="Controle du cache", value = get.getheader('cache-control'), inline = True)
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
embed = discord.Embed(title="Entêtes de {0}".format(adresse), color=0xd75858)
|
|
||||||
embed.add_field(name="Code Réponse", value=e.getcode(), inline = True)
|
|
||||||
embed.set_thumbnail(url="https://http.cat/{}".format(str(e.getcode())))
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
print('''An error occurred: {} The response code was {}'''.format(e, e.getcode()))
|
|
||||||
except urllib.error.URLError as e:
|
|
||||||
if "No address associated" in str(e):
|
|
||||||
await ctx.send("Erreur, aucune adresse n'est associé à ce nom d'hôte.")
|
|
||||||
return
|
|
||||||
if "timed out" in str(e):
|
|
||||||
await ctx.send("Erreur, l'adresse en question dépasse le délais d'attente :(")
|
|
||||||
return
|
|
||||||
if "SSL" in str(e):
|
|
||||||
await ctx.send("Erreur avec le certificat SSL, essayez sans ``https://`` !")
|
|
||||||
return
|
|
||||||
return
|
|
||||||
if "no host":
|
|
||||||
await ctx.send("Erreur, aucun nom d'hôte n'a été donné.")
|
|
||||||
return
|
|
||||||
if "not known":
|
|
||||||
await ctx.send("Erreur, nom de l'hôte inconnu.")
|
|
||||||
return
|
|
||||||
print("ERROR @ getheaders @ urlerror : {} - adress {}".format(e, adresse))
|
|
||||||
await ctx.send('[CONTACTER ADMIN] URLError: {}'.format(e.reason))
|
|
||||||
except Exception as e:
|
|
||||||
print("ERROR @ getheaders @ Exception : {} - adress {}".format(e, adresse))
|
|
||||||
await ctx.send("{0} Impossible d'accèder à {1}, es-tu sur que l'adresse {1} est correcte et que le serveur est allumé ?".format(ctx.author.mention, adresse))
|
|
||||||
else:
|
|
||||||
await ctx.send("{0} Merci de faire commencer {1} par ``https://``, ``http://`` ou ``ftp://``.".format(ctx.author.mention, adresse))
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command(name='git', pass_context=True)
|
|
||||||
async def _git(self, ctx):
|
|
||||||
"""Pour voir mon code"""
|
|
||||||
text = "How tu veux voir mon repos Gitea pour me disséquer ? " \
|
|
||||||
"Pas de soucis ! Je suis un Bot, je ne ressens pas la " \
|
|
||||||
"douleur !\n https://git.gnous.eu/gnouseu/tuxbot-bot"
|
|
||||||
em = discord.Embed(title='Repos TuxBot-Bot', description=text, colour=0xE9D460)
|
|
||||||
em.set_author(name='Gnous', icon_url="https://cdn.discordapp.com/"
|
|
||||||
"icons/280805240977227776/"
|
|
||||||
"9ba1f756c9d9bfcf27989d0d0abb3862"
|
|
||||||
".png")
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@commands.command(name='quote', pass_context=True)
|
|
||||||
async def _quote(self, ctx, quote_id):
|
|
||||||
global quoted_message
|
|
||||||
|
|
||||||
async def get_message(message_id: int):
|
|
||||||
for channel in ctx.message.guild.channels:
|
|
||||||
if isinstance(channel, discord.TextChannel):
|
|
||||||
test_chan = await self.bot.fetch_channel(channel.id)
|
|
||||||
try:
|
|
||||||
return await test_chan.fetch_message(message_id)
|
|
||||||
except (discord.NotFound, discord.Forbidden):
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
quoted_message = await get_message(int(quote_id))
|
|
||||||
|
|
||||||
if quoted_message is not None:
|
|
||||||
embed = discord.Embed(colour=quoted_message.author.colour,
|
|
||||||
description=quoted_message.clean_content,
|
|
||||||
timestamp=quoted_message.created_at)
|
|
||||||
embed.set_author(name=quoted_message.author.display_name,
|
|
||||||
icon_url=quoted_message.author.avatar_url_as(
|
|
||||||
format="jpg"))
|
|
||||||
if len(quoted_message.attachments) >= 1:
|
|
||||||
embed.set_image(url=quoted_message.attachments[0].url)
|
|
||||||
embed.add_field(name="**Original**",
|
|
||||||
value=f"[Go!]({quoted_message.jump_url})")
|
|
||||||
embed.set_footer(text="#" + quoted_message.channel.name)
|
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
else:
|
|
||||||
await ctx.send("Impossible de trouver le message.")
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Utility(bot))
|
|
|
@ -1,124 +0,0 @@
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
|
|
||||||
def is_owner_check(message):
|
|
||||||
return str(message.author.id) in ['171685542553976832',
|
|
||||||
'269156684155453451']
|
|
||||||
|
|
||||||
|
|
||||||
def is_owner(warn=True):
|
|
||||||
def check(ctx, log):
|
|
||||||
owner = is_owner_check(ctx.message)
|
|
||||||
if not owner and log:
|
|
||||||
print(ctx.message.author.name + " à essayer d'executer " + ctx.message.content + " sur le serveur " + ctx.message.guild.name)
|
|
||||||
return owner
|
|
||||||
|
|
||||||
owner = commands.check(lambda ctx: check(ctx, warn))
|
|
||||||
return owner
|
|
||||||
|
|
||||||
|
|
||||||
"""-------------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
|
|
||||||
async def check_permissions(ctx, perms, *, check=all):
|
|
||||||
is_owner = await ctx.bot.is_owner(ctx.author)
|
|
||||||
if is_owner or is_owner_check(ctx.message) is True:
|
|
||||||
return True
|
|
||||||
|
|
||||||
resolved = ctx.channel.permissions_for(ctx.author)
|
|
||||||
return check(getattr(resolved, name, None) == value for name, value in
|
|
||||||
perms.items())
|
|
||||||
|
|
||||||
|
|
||||||
def has_permissions(*, check=all, **perms):
|
|
||||||
async def pred(ctx):
|
|
||||||
return await check_permissions(ctx, perms, check=check)
|
|
||||||
|
|
||||||
return commands.check(pred)
|
|
||||||
|
|
||||||
|
|
||||||
async def check_guild_permissions(ctx, perms, *, check=all):
|
|
||||||
is_owner = await ctx.bot.is_owner(ctx.author)
|
|
||||||
if is_owner:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if ctx.guild is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
resolved = ctx.author.guild_permissions
|
|
||||||
return check(getattr(resolved, name, None) == value for name, value in
|
|
||||||
perms.items())
|
|
||||||
|
|
||||||
|
|
||||||
def has_guild_permissions(*, check=all, **perms):
|
|
||||||
async def pred(ctx):
|
|
||||||
return await check_guild_permissions(ctx, perms, check=check)
|
|
||||||
|
|
||||||
return commands.check(pred)
|
|
||||||
|
|
||||||
|
|
||||||
# These do not take channel overrides into account
|
|
||||||
|
|
||||||
|
|
||||||
def is_mod():
|
|
||||||
async def pred(ctx):
|
|
||||||
return await check_guild_permissions(ctx, {'manage_guild': True})
|
|
||||||
|
|
||||||
return commands.check(pred)
|
|
||||||
|
|
||||||
|
|
||||||
def is_admin():
|
|
||||||
async def pred(ctx):
|
|
||||||
return await check_guild_permissions(ctx, {'administrator': True})
|
|
||||||
|
|
||||||
return commands.check(pred)
|
|
||||||
|
|
||||||
|
|
||||||
def mod_or_permissions(**perms):
|
|
||||||
perms['manage_guild'] = True
|
|
||||||
|
|
||||||
async def predicate(ctx):
|
|
||||||
return await check_guild_permissions(ctx, perms, check=any)
|
|
||||||
|
|
||||||
return commands.check(predicate)
|
|
||||||
|
|
||||||
|
|
||||||
def admin_or_permissions(**perms):
|
|
||||||
perms['administrator'] = True
|
|
||||||
|
|
||||||
async def predicate(ctx):
|
|
||||||
return await check_guild_permissions(ctx, perms, check=any)
|
|
||||||
|
|
||||||
return commands.check(predicate)
|
|
||||||
|
|
||||||
|
|
||||||
def is_in_guilds(*guild_ids):
|
|
||||||
def predicate(ctx):
|
|
||||||
guild = ctx.guild
|
|
||||||
if guild is None:
|
|
||||||
return False
|
|
||||||
return guild.id in guild_ids
|
|
||||||
|
|
||||||
return commands.check(predicate)
|
|
||||||
|
|
||||||
|
|
||||||
def get_user(message, user):
|
|
||||||
try:
|
|
||||||
member = message.mentions[0]
|
|
||||||
except:
|
|
||||||
member = message.guild.get_member_named(user)
|
|
||||||
if not member:
|
|
||||||
try:
|
|
||||||
member = message.guild.get_member(int(user))
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if not member:
|
|
||||||
return None
|
|
||||||
return member
|
|
||||||
|
|
||||||
|
|
||||||
def check_date(date: str):
|
|
||||||
if len(date) == 1:
|
|
||||||
return f"0{date}"
|
|
||||||
else:
|
|
||||||
return date
|
|
|
@ -1,46 +0,0 @@
|
||||||
class text_colors:
|
|
||||||
BLACK = '\033[30m'
|
|
||||||
RED = '\033[31m'
|
|
||||||
GREEN = '\033[32m'
|
|
||||||
YELLOW = '\033[33m'
|
|
||||||
BLUE = '\033[34m'
|
|
||||||
MAGENTA = '\033[35m'
|
|
||||||
CYAN = '\033[36m'
|
|
||||||
LIGHT_GRAY = '\033[37m'
|
|
||||||
DARK_GRAY = '\033[90m'
|
|
||||||
LIGHT_RED = '\033[91m'
|
|
||||||
LIGHT_GREEN = '\033[92m'
|
|
||||||
LIGHT_YELLOW = '\033[93m'
|
|
||||||
LIGHT_BLUE = '\033[94m'
|
|
||||||
LIGHT_MAGENTA = '\033[95m'
|
|
||||||
LIGHT_CYAN = '\033[96m'
|
|
||||||
WHITE = '\033[97m'
|
|
||||||
|
|
||||||
|
|
||||||
class bg_colors:
|
|
||||||
BLACK = '\033[40m'
|
|
||||||
RED = '\033[41m'
|
|
||||||
GREEN = '\033[42m'
|
|
||||||
YELLOW = '\033[43m'
|
|
||||||
BLUE = '\033[44m'
|
|
||||||
MAGENTA = '\033[45m'
|
|
||||||
CYAN = '\033[46m'
|
|
||||||
LIGHT_GRAY = '\033[47m'
|
|
||||||
DARK_GRAY = '\033[100m'
|
|
||||||
LIGHT_RED = '\033[101m'
|
|
||||||
LIGHT_GREEN = '\033[102m'
|
|
||||||
LIGHT_YELLOW = '\033[103m'
|
|
||||||
LIGHT_BLUE = '\033[104m'
|
|
||||||
LIGHT_MAGENTA = '\033[105m'
|
|
||||||
LIGHT_CYAN = '\033[106m'
|
|
||||||
WHITE = '\033[107m'
|
|
||||||
|
|
||||||
|
|
||||||
class text_style:
|
|
||||||
BOLD = '\033[1m'
|
|
||||||
DIM = '\033[2m'
|
|
||||||
UNDERLINE = '\033[4m'
|
|
||||||
BLINK = '\033[5m'
|
|
||||||
|
|
||||||
|
|
||||||
ENDC = '\033[0m'
|
|
|
@ -1,29 +0,0 @@
|
||||||
import pymysql
|
|
||||||
|
|
||||||
|
|
||||||
def connect_to_db(self):
|
|
||||||
mysqlHost = self.bot.config.mysql["host"]
|
|
||||||
mysqlUser = self.bot.config.mysql["username"]
|
|
||||||
mysqlPass = self.bot.config.mysql["password"]
|
|
||||||
mysqlDB = self.bot.config.mysql["dbname"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
return pymysql.connect(host=mysqlHost, user=mysqlUser,
|
|
||||||
passwd=mysqlPass, db=mysqlDB, charset='utf8')
|
|
||||||
except KeyError:
|
|
||||||
print(
|
|
||||||
"Rest in peperoni, Impossible de se connecter a la base de données.")
|
|
||||||
print(str(KeyError))
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def reconnect_to_db(self):
|
|
||||||
if not self.conn:
|
|
||||||
mysqlHost = self.bot.config.mysql["host"]
|
|
||||||
mysqlUser = self.bot.config.mysql["username"]
|
|
||||||
mysqlPass = self.bot.config.mysql["password"]
|
|
||||||
mysqlDB = self.bot.config.mysql["dbname"]
|
|
||||||
|
|
||||||
return pymysql.connect(host=mysqlHost, user=mysqlUser,
|
|
||||||
passwd=mysqlPass, db=mysqlDB, charset='utf8')
|
|
||||||
return self.conn
|
|
|
@ -1,75 +0,0 @@
|
||||||
async def entry_to_code(bot, entries):
|
|
||||||
width = max(map(lambda t: len(t[0]), entries))
|
|
||||||
output = ['```']
|
|
||||||
fmt = '{0:<{width}}: {1}'
|
|
||||||
for name, entry in entries:
|
|
||||||
output.append(fmt.format(name, entry, width=width))
|
|
||||||
output.append('```')
|
|
||||||
await bot.say('\n'.join(output))
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
async def indented_entry_to_code(bot, entries):
|
|
||||||
width = max(map(lambda t: len(t[0]), entries))
|
|
||||||
output = ['```']
|
|
||||||
fmt = '\u200b{0:>{width}}: {1}'
|
|
||||||
for name, entry in entries:
|
|
||||||
output.append(fmt.format(name, entry, width=width))
|
|
||||||
output.append('```')
|
|
||||||
await bot.say('\n'.join(output))
|
|
||||||
|
|
||||||
async def too_many_matches(bot, msg, matches, entry):
|
|
||||||
check = lambda m: m.content.isdigit()
|
|
||||||
await bot.say('There are too many matches... Which one did you mean? **Only say the number**.')
|
|
||||||
await bot.say('\n'.join(map(entry, enumerate(matches, 1))))
|
|
||||||
|
|
||||||
# only give them 3 tries.
|
|
||||||
for i in range(3):
|
|
||||||
message = await bot.wait_for_message(author=msg.author, channel=msg.channel, check=check)
|
|
||||||
index = int(message.content)
|
|
||||||
try:
|
|
||||||
return matches[index - 1]
|
|
||||||
except:
|
|
||||||
await bot.say('Please give me a valid number. {} tries remaining...'.format(2 - i))
|
|
||||||
|
|
||||||
raise ValueError('Too many tries. Goodbye.')
|
|
||||||
|
|
||||||
class Plural:
|
|
||||||
def __init__(self, **attr):
|
|
||||||
iterator = attr.items()
|
|
||||||
self.name, self.value = next(iter(iterator))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
v = self.value
|
|
||||||
if v > 1:
|
|
||||||
return '%s %ss' % (v, self.name)
|
|
||||||
return '%s %s' % (v, self.name)
|
|
||||||
|
|
||||||
def human_timedelta(dt):
|
|
||||||
now = datetime.datetime.utcnow()
|
|
||||||
delta = now - dt
|
|
||||||
hours, remainder = divmod(int(delta.total_seconds()), 3600)
|
|
||||||
minutes, seconds = divmod(remainder, 60)
|
|
||||||
days, hours = divmod(hours, 24)
|
|
||||||
years, days = divmod(days, 365)
|
|
||||||
|
|
||||||
if years:
|
|
||||||
if days:
|
|
||||||
return '%s and %s ago' % (Plural(year=years), Plural(day=days))
|
|
||||||
return '%s ago' % Plural(year=years)
|
|
||||||
|
|
||||||
if days:
|
|
||||||
if hours:
|
|
||||||
return '%s and %s ago' % (Plural(day=days), Plural(hour=hours))
|
|
||||||
return '%s ago' % Plural(day=days)
|
|
||||||
|
|
||||||
if hours:
|
|
||||||
if minutes:
|
|
||||||
return '%s and %s ago' % (Plural(hour=hours), Plural(minute=minutes))
|
|
||||||
return '%s ago' % Plural(hour=hours)
|
|
||||||
|
|
||||||
if minutes:
|
|
||||||
if seconds:
|
|
||||||
return '%s and %s ago' % (Plural(minute=minutes), Plural(second=seconds))
|
|
||||||
return '%s ago' % Plural(minute=minutes)
|
|
||||||
return '%s ago' % Plural(second=seconds)
|
|
|
@ -1,147 +0,0 @@
|
||||||
#!/bin/env python
|
|
||||||
|
|
||||||
# With credit to DanielKO
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
import datetime, re
|
|
||||||
import asyncio, aiohttp
|
|
||||||
|
|
||||||
NINTENDO_LOGIN_PAGE = "https://id.nintendo.net/oauth/authorize"
|
|
||||||
SPLATNET_CALLBACK_URL = "https://splatoon.nintendo.net/users/auth/nintendo/callback"
|
|
||||||
SPLATNET_CLIENT_ID = "12af3d0a3a1f441eb900411bb50a835a"
|
|
||||||
SPLATNET_SCHEDULE_URL = "https://splatoon.nintendo.net/schedule"
|
|
||||||
|
|
||||||
class Rotation(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.start = None
|
|
||||||
self.end = None
|
|
||||||
self.turf_maps = []
|
|
||||||
self.ranked_mode = None
|
|
||||||
self.ranked_maps = []
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_over(self):
|
|
||||||
return self.end < datetime.datetime.utcnow()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
now = datetime.datetime.utcnow()
|
|
||||||
prefix = ''
|
|
||||||
if self.start > now:
|
|
||||||
minutes_delta = int((self.start - now) / datetime.timedelta(minutes=1))
|
|
||||||
hours = int(minutes_delta / 60)
|
|
||||||
minutes = minutes_delta % 60
|
|
||||||
prefix = '**In {0} hours and {1} minutes**:\n'.format(hours, minutes)
|
|
||||||
else:
|
|
||||||
prefix = '**Current Rotation**:\n'
|
|
||||||
|
|
||||||
fmt = 'Turf War is {0[0]} and {0[1]}\n{1} is {2[0]} and {2[1]}'
|
|
||||||
return prefix + fmt.format(self.turf_maps, self.ranked_mode, self.ranked_maps)
|
|
||||||
|
|
||||||
# based on https://github.com/Wiwiweb/SakuraiBot/blob/master/src/sakuraibot.py
|
|
||||||
async def get_new_splatnet_cookie(username, password):
|
|
||||||
parameters = {'client_id': SPLATNET_CLIENT_ID,
|
|
||||||
'response_type': 'code',
|
|
||||||
'redirect_uri': SPLATNET_CALLBACK_URL,
|
|
||||||
'username': username,
|
|
||||||
'password': password}
|
|
||||||
|
|
||||||
async with aiohttp.post(NINTENDO_LOGIN_PAGE, data=parameters) as response:
|
|
||||||
cookie = response.history[-1].cookies.get('_wag_session')
|
|
||||||
if cookie is None:
|
|
||||||
print(req)
|
|
||||||
raise Exception("Couldn't retrieve cookie")
|
|
||||||
return cookie
|
|
||||||
|
|
||||||
def parse_splatnet_time(timestr):
|
|
||||||
# time is given as "MM/DD at H:MM [p|a].m. (PDT|PST)"
|
|
||||||
# there is a case where it goes over the year, e.g. 12/31 at ... and then 1/1 at ...
|
|
||||||
# this case is kind of weird though and is currently unexpected
|
|
||||||
# it could even end up being e.g. 12/31/2015 ... and then 1/1/2016 ...
|
|
||||||
# we'll never know
|
|
||||||
|
|
||||||
regex = r'(?P<month>\d+)\/(?P<day>\d+)\s*at\s*(?P<hour>\d+)\:(?P<minutes>\d+)\s*(?P<p>a\.m\.|p\.m\.)\s*\((?P<tz>.+)\)'
|
|
||||||
m = re.match(regex, timestr.strip())
|
|
||||||
|
|
||||||
if m is None:
|
|
||||||
raise RuntimeError('Apparently the timestamp "{}" does not match the regex.'.format(timestr))
|
|
||||||
|
|
||||||
matches = m.groupdict()
|
|
||||||
tz = matches['tz'].strip().upper()
|
|
||||||
offset = None
|
|
||||||
if tz == 'PDT':
|
|
||||||
# EDT is UTC - 4, PDT is UTC - 7, so you need +7 to make it UTC
|
|
||||||
offset = +7
|
|
||||||
elif tz == 'PST':
|
|
||||||
# EST is UTC - 5, PST is UTC - 8, so you need +8 to make it UTC
|
|
||||||
offset = +8
|
|
||||||
else:
|
|
||||||
raise RuntimeError('Unknown timezone found: {}'.format(tz))
|
|
||||||
|
|
||||||
pm = matches['p'].replace('.', '') # a.m. -> am
|
|
||||||
|
|
||||||
current_time = datetime.datetime.utcnow()
|
|
||||||
|
|
||||||
# Kind of hacky.
|
|
||||||
fmt = "{2}/{0[month]}/{0[day]} {0[hour]}:{0[minutes]} {1}".format(matches, pm, current_time.year)
|
|
||||||
splatoon_time = datetime.datetime.strptime(fmt, '%Y/%m/%d %I:%M %p') + datetime.timedelta(hours=offset)
|
|
||||||
|
|
||||||
# check for new year
|
|
||||||
if current_time.month == 12 and splatoon_time.month == 1:
|
|
||||||
splatoon_time.replace(current_time.year + 1)
|
|
||||||
|
|
||||||
return splatoon_time
|
|
||||||
|
|
||||||
|
|
||||||
async def get_splatnet_schedule(splatnet_cookie):
|
|
||||||
cookies = {'_wag_session': splatnet_cookie}
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
This is repeated 3 times:
|
|
||||||
|
|
||||||
<span class"stage-schedule"> ... </span> <--- figure out how to parse this
|
|
||||||
<div class="stage-list">
|
|
||||||
<div class="match-type">
|
|
||||||
<span class="icon-regular-match"></span> <--- turf war
|
|
||||||
</div>
|
|
||||||
... <span class="map-name"> ... </span>
|
|
||||||
... <span class="map-name"> ... </span>
|
|
||||||
</div>
|
|
||||||
<div class="stage-list">
|
|
||||||
<div class="match-type">
|
|
||||||
<span class="icon-earnest-match"></span> <--- ranked
|
|
||||||
</div>
|
|
||||||
... <span class="rule-description"> ... </span> <--- Splat Zones, Rainmaker, Tower Control
|
|
||||||
... <span class="map-name"> ... </span>
|
|
||||||
... <span class="map-name"> ... </span>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
schedule = []
|
|
||||||
async with aiohttp.get(SPLATNET_SCHEDULE_URL, cookies=cookies, data={'locale':"en"}) as response:
|
|
||||||
text = await response.text()
|
|
||||||
root = etree.fromstring(text, etree.HTMLParser())
|
|
||||||
stage_schedule_nodes = root.xpath("//*[@class='stage-schedule']")
|
|
||||||
stage_list_nodes = root.xpath("//*[@class='stage-list']")
|
|
||||||
|
|
||||||
if len(stage_schedule_nodes)*2 != len(stage_list_nodes):
|
|
||||||
raise RuntimeError("SplatNet changed, need to update the parsing!")
|
|
||||||
|
|
||||||
for sched_node in stage_schedule_nodes:
|
|
||||||
r = Rotation()
|
|
||||||
|
|
||||||
start_time, end_time = sched_node.text.split("~")
|
|
||||||
r.start = parse_splatnet_time(start_time)
|
|
||||||
r.end = parse_splatnet_time(end_time)
|
|
||||||
|
|
||||||
tw_list_node = stage_list_nodes.pop(0)
|
|
||||||
r.turf_maps = tw_list_node.xpath(".//*[@class='map-name']/text()")
|
|
||||||
|
|
||||||
ranked_list_node = stage_list_nodes.pop(0)
|
|
||||||
r.ranked_maps = ranked_list_node.xpath(".//*[@class='map-name']/text()")
|
|
||||||
r.ranked_mode = ranked_list_node.xpath(".//*[@class='rule-description']/text()")[0]
|
|
||||||
|
|
||||||
schedule.append(r)
|
|
||||||
|
|
||||||
return schedule
|
|
|
@ -1,140 +0,0 @@
|
||||||
import asyncio
|
|
||||||
|
|
||||||
class Menu:
|
|
||||||
"""An interactive menu class for Discord."""
|
|
||||||
|
|
||||||
|
|
||||||
class Submenu:
|
|
||||||
"""A metaclass of the Menu class."""
|
|
||||||
def __init__(self, name, content):
|
|
||||||
self.content = content
|
|
||||||
self.leads_to = []
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def get_text(self):
|
|
||||||
text = ""
|
|
||||||
for idx, menu in enumerate(self.leads_to):
|
|
||||||
text += "[{}] {}\n".format(idx+1, menu.name)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def get_child(self, child_idx):
|
|
||||||
try:
|
|
||||||
return self.leads_to[child_idx]
|
|
||||||
except IndexError:
|
|
||||||
raise IndexError("child index out of range")
|
|
||||||
|
|
||||||
def add_child(self, child):
|
|
||||||
self.leads_to.append(child)
|
|
||||||
|
|
||||||
class InputSubmenu:
|
|
||||||
"""A metaclass of the Menu class for submenu options that take input, instead of prompting the user to pick an option."""
|
|
||||||
def __init__(self, name, content, input_function, leads_to):
|
|
||||||
self.content = content
|
|
||||||
self.name = name
|
|
||||||
self.input_function = input_function
|
|
||||||
self.leads_to = leads_to
|
|
||||||
|
|
||||||
def next_child(self):
|
|
||||||
return self.leads_to
|
|
||||||
|
|
||||||
class ChoiceSubmenu:
|
|
||||||
"""A metaclass of the Menu class for submenu options for choosing an option from a list."""
|
|
||||||
def __init__(self, name, content, options, input_function, leads_to):
|
|
||||||
self.content = content
|
|
||||||
self.name = name
|
|
||||||
self.options = options
|
|
||||||
self.input_function = input_function
|
|
||||||
self.leads_to = leads_to
|
|
||||||
|
|
||||||
def next_child(self):
|
|
||||||
return self.leads_to
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, main_page):
|
|
||||||
self.children = []
|
|
||||||
self.main = self.Submenu("main", main_page)
|
|
||||||
|
|
||||||
def add_child(self, child):
|
|
||||||
self.main.add_child(child)
|
|
||||||
|
|
||||||
async def start(self, ctx):
|
|
||||||
current = self.main
|
|
||||||
menu_msg = None
|
|
||||||
while True:
|
|
||||||
output = ""
|
|
||||||
|
|
||||||
if type(current) == self.Submenu:
|
|
||||||
if type(current.content) == str:
|
|
||||||
output += current.content + "\n"
|
|
||||||
elif callable(current.content):
|
|
||||||
current.content()
|
|
||||||
else:
|
|
||||||
raise TypeError("submenu body is not a str or function")
|
|
||||||
|
|
||||||
if not current.leads_to:
|
|
||||||
if not menu_msg:
|
|
||||||
menu_msg = await ctx.send("```" + output + "```")
|
|
||||||
else:
|
|
||||||
await menu_msg.edit(content="```" + output + "```")
|
|
||||||
break
|
|
||||||
|
|
||||||
output += "\n" + current.get_text() + "\n"
|
|
||||||
output += "Enter a number."
|
|
||||||
|
|
||||||
if not menu_msg:
|
|
||||||
menu_msg = await ctx.send("```" + output + "```")
|
|
||||||
else:
|
|
||||||
await menu_msg.edit(content="```" + output + "```")
|
|
||||||
|
|
||||||
reply = await ctx.bot.wait_for("message", check=lambda m: m.author == ctx.bot.user and m.content.isdigit() and m.channel == ctx.message.channel)
|
|
||||||
await reply.delete()
|
|
||||||
|
|
||||||
try:
|
|
||||||
current = current.get_child(int(reply.content) - 1)
|
|
||||||
except IndexError:
|
|
||||||
print("Invalid number.")
|
|
||||||
break
|
|
||||||
|
|
||||||
elif type(current) == self.InputSubmenu:
|
|
||||||
if type(current.content) == list:
|
|
||||||
answers = []
|
|
||||||
for question in current.content:
|
|
||||||
await menu_msg.edit(content="```" + question + "\n\nEnter a value." + "```")
|
|
||||||
reply = await ctx.bot.wait_for("message", check=lambda m: m.author == ctx.bot.user and m.channel == ctx.message.channel)
|
|
||||||
await reply.delete()
|
|
||||||
answers.append(reply)
|
|
||||||
current.input_function(*answers)
|
|
||||||
else:
|
|
||||||
await menu_msg.edit(content="```" + current.content + "\n\nEnter a value." + "```")
|
|
||||||
reply = await ctx.bot.wait_for("message", check=lambda m: m.author == ctx.bot.user and m.channel == ctx.message.channel)
|
|
||||||
await reply.delete()
|
|
||||||
current.input_function(reply)
|
|
||||||
|
|
||||||
if not current.leads_to:
|
|
||||||
break
|
|
||||||
|
|
||||||
current = current.leads_to
|
|
||||||
|
|
||||||
elif type(current) == self.ChoiceSubmenu:
|
|
||||||
result = "```" + current.content + "\n\n"
|
|
||||||
if type(current.options) == dict:
|
|
||||||
indexes = {}
|
|
||||||
for idx, option in enumerate(current.options):
|
|
||||||
result += "[{}] {}: {}\n".format(idx+1, option, current.options[option])
|
|
||||||
indexes[idx] = option
|
|
||||||
else:
|
|
||||||
for idx, option in current.options:
|
|
||||||
result += "[{}] {}\n".format(idx+1, option)
|
|
||||||
await menu_msg.edit(content=result + "\nPick an option.```")
|
|
||||||
reply = await ctx.bot.wait_for("message", check=lambda m: m.author == ctx.bot.user and m.content.isdigit() and m.channel == ctx.message.channel)
|
|
||||||
await reply.delete()
|
|
||||||
if type(current.options) == dict:
|
|
||||||
current.input_function(reply, indexes[int(reply.content)-1])
|
|
||||||
else:
|
|
||||||
current.input_function(reply, current.options[reply-1])
|
|
||||||
|
|
||||||
if not current.leads_to:
|
|
||||||
break
|
|
||||||
|
|
||||||
current = current.leads_to
|
|
||||||
|
|
|
@ -1,503 +0,0 @@
|
||||||
# Help paginator by Rapptz
|
|
||||||
# Edited by F4stZ4p
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import discord
|
|
||||||
|
|
||||||
class CannotPaginate(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Pages:
|
|
||||||
"""Implements a paginator that queries the user for the
|
|
||||||
pagination interface.
|
|
||||||
Pages are 1-index based, not 0-index based.
|
|
||||||
If the user does not reply within 2 minutes then the pagination
|
|
||||||
interface exits automatically.
|
|
||||||
Parameters
|
|
||||||
------------
|
|
||||||
ctx: Context
|
|
||||||
The context of the command.
|
|
||||||
entries: List[str]
|
|
||||||
A list of entries to paginate.
|
|
||||||
per_page: int
|
|
||||||
How many entries show up per page.
|
|
||||||
show_entry_count: bool
|
|
||||||
Whether to show an entry count in the footer.
|
|
||||||
Attributes
|
|
||||||
-----------
|
|
||||||
embed: discord.Embed
|
|
||||||
The embed object that is being used to send pagination info.
|
|
||||||
Feel free to modify this externally. Only the description,
|
|
||||||
footer fields, and colour are internally modified.
|
|
||||||
permissions: discord.Permissions
|
|
||||||
Our permissions for the channel.
|
|
||||||
"""
|
|
||||||
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True):
|
|
||||||
self.bot = ctx.bot
|
|
||||||
self.entries = entries
|
|
||||||
self.message = ctx.message
|
|
||||||
self.channel = ctx.channel
|
|
||||||
self.author = ctx.author
|
|
||||||
self.per_page = per_page
|
|
||||||
pages, left_over = divmod(len(self.entries), self.per_page)
|
|
||||||
if left_over:
|
|
||||||
pages += 1
|
|
||||||
self.maximum_pages = pages
|
|
||||||
self.embed = discord.Embed(colour=discord.Color.green())
|
|
||||||
self.paginating = len(entries) > per_page
|
|
||||||
self.show_entry_count = show_entry_count
|
|
||||||
self.reaction_emojis = [
|
|
||||||
('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.first_page),
|
|
||||||
('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page),
|
|
||||||
('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page),
|
|
||||||
('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.last_page),
|
|
||||||
('\N{INPUT SYMBOL FOR NUMBERS}', self.numbered_page ),
|
|
||||||
('\N{BLACK SQUARE FOR STOP}', self.stop_pages),
|
|
||||||
('\N{INFORMATION SOURCE}', self.show_help),
|
|
||||||
]
|
|
||||||
|
|
||||||
if ctx.guild is not None:
|
|
||||||
self.permissions = self.channel.permissions_for(ctx.guild.me)
|
|
||||||
else:
|
|
||||||
self.permissions = self.channel.permissions_for(ctx.bot.user)
|
|
||||||
|
|
||||||
if not self.permissions.embed_links:
|
|
||||||
raise CannotPaginate('Bot does not have embed links permission.')
|
|
||||||
|
|
||||||
if not self.permissions.send_messages:
|
|
||||||
raise CannotPaginate('Bot cannot send messages.')
|
|
||||||
|
|
||||||
if self.paginating:
|
|
||||||
# verify we can actually use the pagination session
|
|
||||||
if not self.permissions.add_reactions:
|
|
||||||
raise CannotPaginate('Bot does not have add reactions permission.')
|
|
||||||
|
|
||||||
if not self.permissions.read_message_history:
|
|
||||||
raise CannotPaginate('Bot does not have Read Message History permission.')
|
|
||||||
|
|
||||||
def get_page(self, page):
|
|
||||||
base = (page - 1) * self.per_page
|
|
||||||
return self.entries[base:base + self.per_page]
|
|
||||||
|
|
||||||
async def show_page(self, page, *, first=False):
|
|
||||||
self.current_page = page
|
|
||||||
entries = self.get_page(page)
|
|
||||||
p = []
|
|
||||||
for index, entry in enumerate(entries, 1 + ((page - 1) * self.per_page)):
|
|
||||||
p.append(f'{index}. {entry}')
|
|
||||||
|
|
||||||
if self.maximum_pages > 1:
|
|
||||||
if self.show_entry_count:
|
|
||||||
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
|
|
||||||
else:
|
|
||||||
text = f'Page {page}/{self.maximum_pages}'
|
|
||||||
|
|
||||||
self.embed.set_footer(text=text)
|
|
||||||
|
|
||||||
if not self.paginating:
|
|
||||||
self.embed.description = '\n'.join(p)
|
|
||||||
return await self.channel.send(embed=self.embed)
|
|
||||||
|
|
||||||
if not first:
|
|
||||||
self.embed.description = '\n'.join(p)
|
|
||||||
await self.message.edit(embed=self.embed)
|
|
||||||
return
|
|
||||||
|
|
||||||
p.append('')
|
|
||||||
p.append('Confused? React with \N{INFORMATION SOURCE} for more info.')
|
|
||||||
self.embed.description = '\n'.join(p)
|
|
||||||
self.message = await self.channel.send(embed=self.embed)
|
|
||||||
for (reaction, _) in self.reaction_emojis:
|
|
||||||
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
|
|
||||||
# no |<< or >>| buttons if we only have two pages
|
|
||||||
# we can't forbid it if someone ends up using it but remove
|
|
||||||
# it from the default set
|
|
||||||
continue
|
|
||||||
|
|
||||||
await self.message.add_reaction(reaction)
|
|
||||||
|
|
||||||
async def checked_show_page(self, page):
|
|
||||||
if page != 0 and page <= self.maximum_pages:
|
|
||||||
await self.show_page(page)
|
|
||||||
|
|
||||||
async def first_page(self):
|
|
||||||
"""goes to the first page"""
|
|
||||||
await self.show_page(1)
|
|
||||||
|
|
||||||
async def last_page(self):
|
|
||||||
"""goes to the last page"""
|
|
||||||
await self.show_page(self.maximum_pages)
|
|
||||||
|
|
||||||
async def next_page(self):
|
|
||||||
"""goes to the next page"""
|
|
||||||
await self.checked_show_page(self.current_page + 1)
|
|
||||||
|
|
||||||
async def previous_page(self):
|
|
||||||
"""goes to the previous page"""
|
|
||||||
await self.checked_show_page(self.current_page - 1)
|
|
||||||
|
|
||||||
async def show_current_page(self):
|
|
||||||
if self.paginating:
|
|
||||||
await self.show_page(self.current_page)
|
|
||||||
|
|
||||||
async def numbered_page(self):
|
|
||||||
"""lets you type a page number to go to"""
|
|
||||||
to_delete = []
|
|
||||||
to_delete.append(await self.channel.send('What page do you want to go to?'))
|
|
||||||
|
|
||||||
def message_check(m):
|
|
||||||
return m.author == self.author and \
|
|
||||||
self.channel == m.channel and \
|
|
||||||
m.content.isdigit()
|
|
||||||
|
|
||||||
try:
|
|
||||||
msg = await self.bot.wait_for('message', check=message_check, timeout=30.0)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
to_delete.append(await self.channel.send('Took too long.'))
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
else:
|
|
||||||
page = int(msg.content)
|
|
||||||
to_delete.append(msg)
|
|
||||||
if page != 0 and page <= self.maximum_pages:
|
|
||||||
await self.show_page(page)
|
|
||||||
else:
|
|
||||||
to_delete.append(await self.channel.send(f'Invalid page given. ({page}/{self.maximum_pages})'))
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self.channel.delete_messages(to_delete)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def show_help(self):
|
|
||||||
"""shows this message"""
|
|
||||||
messages = ['Welcome to the interactive paginator!\n']
|
|
||||||
messages.append('This interactively allows you to see pages of text by navigating with ' \
|
|
||||||
'reactions. They are as follows:\n')
|
|
||||||
|
|
||||||
for (emoji, func) in self.reaction_emojis:
|
|
||||||
messages.append(f'{emoji} {func.__doc__}')
|
|
||||||
|
|
||||||
self.embed.description = '\n'.join(messages)
|
|
||||||
self.embed.clear_fields()
|
|
||||||
self.embed.set_footer(text=f'We were on page {self.current_page} before this message.')
|
|
||||||
await self.message.edit(embed=self.embed)
|
|
||||||
|
|
||||||
async def go_back_to_current_page():
|
|
||||||
await asyncio.sleep(60.0)
|
|
||||||
await self.show_current_page()
|
|
||||||
|
|
||||||
self.bot.loop.create_task(go_back_to_current_page())
|
|
||||||
|
|
||||||
async def stop_pages(self):
|
|
||||||
"""stops the interactive pagination session"""
|
|
||||||
await self.message.delete()
|
|
||||||
self.paginating = False
|
|
||||||
|
|
||||||
def react_check(self, reaction, user):
|
|
||||||
if user is None or user.id != self.author.id:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if reaction.message.id != self.message.id:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for (emoji, func) in self.reaction_emojis:
|
|
||||||
if reaction.emoji == emoji:
|
|
||||||
self.match = func
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def paginate(self):
|
|
||||||
"""Actually paginate the entries and run the interactive loop if necessary."""
|
|
||||||
first_page = self.show_page(1, first=True)
|
|
||||||
if not self.paginating:
|
|
||||||
await first_page
|
|
||||||
else:
|
|
||||||
# allow us to react to reactions right away if we're paginating
|
|
||||||
self.bot.loop.create_task(first_page)
|
|
||||||
|
|
||||||
while self.paginating:
|
|
||||||
try:
|
|
||||||
reaction, user = await self.bot.wait_for('reaction_add', check=self.react_check, timeout=120.0)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
self.paginating = False
|
|
||||||
try:
|
|
||||||
await self.message.clear_reactions()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self.message.remove_reaction(reaction, user)
|
|
||||||
except:
|
|
||||||
pass # can't remove it so don't bother doing so
|
|
||||||
|
|
||||||
await self.match()
|
|
||||||
|
|
||||||
class FieldPages(Pages):
|
|
||||||
"""Similar to Pages except entries should be a list of
|
|
||||||
tuples having (key, value) to show as embed fields instead.
|
|
||||||
"""
|
|
||||||
async def show_page(self, page, *, first=False):
|
|
||||||
self.current_page = page
|
|
||||||
entries = self.get_page(page)
|
|
||||||
|
|
||||||
self.embed.clear_fields()
|
|
||||||
self.embed.description = discord.Embed.Empty
|
|
||||||
|
|
||||||
for key, value in entries:
|
|
||||||
self.embed.add_field(name=key, value=value, inline=False)
|
|
||||||
|
|
||||||
if self.maximum_pages > 1:
|
|
||||||
if self.show_entry_count:
|
|
||||||
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
|
|
||||||
else:
|
|
||||||
text = f'Page {page}/{self.maximum_pages}'
|
|
||||||
|
|
||||||
self.embed.set_footer(text=text)
|
|
||||||
|
|
||||||
if not self.paginating:
|
|
||||||
return await self.channel.send(embed=self.embed)
|
|
||||||
|
|
||||||
if not first:
|
|
||||||
await self.message.edit(embed=self.embed)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.message = await self.channel.send(embed=self.embed)
|
|
||||||
for (reaction, _) in self.reaction_emojis:
|
|
||||||
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
|
|
||||||
# no |<< or >>| buttons if we only have two pages
|
|
||||||
# we can't forbid it if someone ends up using it but remove
|
|
||||||
# it from the default set
|
|
||||||
continue
|
|
||||||
|
|
||||||
await self.message.add_reaction(reaction)
|
|
||||||
|
|
||||||
import itertools
|
|
||||||
import inspect
|
|
||||||
import re
|
|
||||||
|
|
||||||
# ?help
|
|
||||||
# ?help Cog
|
|
||||||
# ?help command
|
|
||||||
# -> could be a subcommand
|
|
||||||
|
|
||||||
_mention = re.compile(r'<@\!?([0-9]{1,19})>')
|
|
||||||
|
|
||||||
def cleanup_prefix(bot, prefix):
|
|
||||||
m = _mention.match(prefix)
|
|
||||||
if m:
|
|
||||||
user = bot.get_user(int(m.group(1)))
|
|
||||||
if user:
|
|
||||||
return f'@{user.name} '
|
|
||||||
return prefix
|
|
||||||
|
|
||||||
async def _can_run(cmd, ctx):
|
|
||||||
try:
|
|
||||||
return await cmd.can_run(ctx)
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _command_signature(cmd):
|
|
||||||
# this is modified from discord.py source
|
|
||||||
# which I wrote myself lmao
|
|
||||||
|
|
||||||
result = [cmd.qualified_name]
|
|
||||||
if cmd.usage:
|
|
||||||
result.append(cmd.usage)
|
|
||||||
return ' '.join(result)
|
|
||||||
|
|
||||||
params = cmd.clean_params
|
|
||||||
if not params:
|
|
||||||
return ' '.join(result)
|
|
||||||
|
|
||||||
for name, param in params.items():
|
|
||||||
if param.default is not param.empty:
|
|
||||||
# We don't want None or '' to trigger the [name=value] case and instead it should
|
|
||||||
# do [name] since [name=None] or [name=] are not exactly useful for the user.
|
|
||||||
should_print = param.default if isinstance(param.default, str) else param.default is not None
|
|
||||||
if should_print:
|
|
||||||
result.append(f'[{name}={param.default!r}]')
|
|
||||||
else:
|
|
||||||
result.append(f'[{name}]')
|
|
||||||
elif param.kind == param.VAR_POSITIONAL:
|
|
||||||
result.append(f'[{name}...]')
|
|
||||||
else:
|
|
||||||
result.append(f'<{name}>')
|
|
||||||
|
|
||||||
return ' '.join(result)
|
|
||||||
|
|
||||||
class HelpPaginator(Pages):
|
|
||||||
def __init__(self, ctx, entries, *, per_page=4):
|
|
||||||
super().__init__(ctx, entries=entries, per_page=per_page)
|
|
||||||
self.reaction_emojis.append(('\N{WHITE QUESTION MARK ORNAMENT}', self.show_bot_help))
|
|
||||||
self.total = len(entries)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def from_cog(cls, ctx, cog):
|
|
||||||
cog_name = cog.__class__.__name__
|
|
||||||
|
|
||||||
# get the commands
|
|
||||||
entries = sorted(ctx.bot.get_cog(cog_name).get_commands(), key=lambda c: c.name)
|
|
||||||
|
|
||||||
# remove the ones we can't run
|
|
||||||
entries = [cmd for cmd in entries if (await _can_run(cmd, ctx)) and not cmd.hidden]
|
|
||||||
|
|
||||||
self = cls(ctx, entries)
|
|
||||||
self.title = f'{cog_name} Commands'
|
|
||||||
self.description = inspect.getdoc(cog)
|
|
||||||
self.prefix = cleanup_prefix(ctx.bot, ctx.prefix)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def from_command(cls, ctx, command):
|
|
||||||
try:
|
|
||||||
entries = sorted(command.commands, key=lambda c: c.name)
|
|
||||||
except AttributeError:
|
|
||||||
entries = []
|
|
||||||
else:
|
|
||||||
entries = [cmd for cmd in entries if (await _can_run(cmd, ctx)) and not cmd.hidden]
|
|
||||||
|
|
||||||
self = cls(ctx, entries)
|
|
||||||
self.title = command.signature
|
|
||||||
|
|
||||||
if command.description:
|
|
||||||
self.description = f'{command.description}\n\n{command.help}'
|
|
||||||
else:
|
|
||||||
self.description = command.help or 'No help given.'
|
|
||||||
|
|
||||||
self.prefix = cleanup_prefix(ctx.bot, ctx.prefix)
|
|
||||||
return self
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def from_bot(cls, ctx):
|
|
||||||
def key(c):
|
|
||||||
return c.cog_name or '\u200bMisc'
|
|
||||||
|
|
||||||
entries = sorted(ctx.bot.commands, key=key)
|
|
||||||
nested_pages = []
|
|
||||||
per_page = 9
|
|
||||||
|
|
||||||
# 0: (cog, desc, commands) (max len == 9)
|
|
||||||
# 1: (cog, desc, commands) (max len == 9)
|
|
||||||
# ...
|
|
||||||
|
|
||||||
for cog, commands in itertools.groupby(entries, key=key):
|
|
||||||
plausible = [cmd for cmd in commands if (await _can_run(cmd, ctx)) and not cmd.hidden]
|
|
||||||
if len(plausible) == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
description = ctx.bot.get_cog(cog)
|
|
||||||
if description is None:
|
|
||||||
description = discord.Embed.Empty
|
|
||||||
else:
|
|
||||||
description = inspect.getdoc(description) or discord.Embed.Empty
|
|
||||||
|
|
||||||
nested_pages.extend((cog, description, plausible[i:i + per_page]) for i in range(0, len(plausible), per_page))
|
|
||||||
|
|
||||||
self = cls(ctx, nested_pages, per_page=1) # this forces the pagination session
|
|
||||||
self.prefix = cleanup_prefix(ctx.bot, ctx.prefix)
|
|
||||||
|
|
||||||
# swap the get_page implementation with one that supports our style of pagination
|
|
||||||
self.get_page = self.get_bot_page
|
|
||||||
self._is_bot = True
|
|
||||||
|
|
||||||
# replace the actual total
|
|
||||||
self.total = sum(len(o) for _, _, o in nested_pages)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get_bot_page(self, page):
|
|
||||||
cog, description, commands = self.entries[page - 1]
|
|
||||||
self.title = f'{cog} Commands'
|
|
||||||
self.description = description
|
|
||||||
return commands
|
|
||||||
|
|
||||||
async def show_page(self, page, *, first=False):
|
|
||||||
self.current_page = page
|
|
||||||
entries = self.get_page(page)
|
|
||||||
|
|
||||||
self.embed.clear_fields()
|
|
||||||
self.embed.description = self.description
|
|
||||||
self.embed.title = self.title
|
|
||||||
|
|
||||||
if hasattr(self, '_is_bot'):
|
|
||||||
value ='Check the bot source: **[GitHub Link](https://github.com/F4stZ4p/DJ5n4k3/)**'
|
|
||||||
self.embed.add_field(name='**GitHub**', value=value, inline=False)
|
|
||||||
|
|
||||||
self.embed.set_footer(text=f'Use "{self.prefix}help command" for more info on a command.')
|
|
||||||
|
|
||||||
signature = _command_signature
|
|
||||||
|
|
||||||
for entry in entries:
|
|
||||||
self.embed.add_field(name=signature(entry), value=entry.short_doc or "No help given", inline=False)
|
|
||||||
|
|
||||||
if self.maximum_pages:
|
|
||||||
self.embed.set_author(name=f'Page {page}/{self.maximum_pages} ({self.total} commands)')
|
|
||||||
|
|
||||||
if not self.paginating:
|
|
||||||
return await self.channel.send(embed=self.embed)
|
|
||||||
|
|
||||||
if not first:
|
|
||||||
await self.message.edit(embed=self.embed)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.message = await self.channel.send(embed=self.embed)
|
|
||||||
for (reaction, _) in self.reaction_emojis:
|
|
||||||
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
|
|
||||||
# no |<< or >>| buttons if we only have two pages
|
|
||||||
# we can't forbid it if someone ends up using it but remove
|
|
||||||
# it from the default set
|
|
||||||
continue
|
|
||||||
|
|
||||||
await self.message.add_reaction(reaction)
|
|
||||||
|
|
||||||
async def show_help(self):
|
|
||||||
"""shows this message"""
|
|
||||||
|
|
||||||
self.embed.title = 'Paginator help'
|
|
||||||
self.embed.description = 'Hello! Welcome to the help page.'
|
|
||||||
|
|
||||||
messages = [f'{emoji} {func.__doc__}' for emoji, func in self.reaction_emojis]
|
|
||||||
self.embed.clear_fields()
|
|
||||||
self.embed.add_field(name='What are these reactions for?', value='\n'.join(messages), inline=False)
|
|
||||||
|
|
||||||
self.embed.set_footer(text=f'We were on page {self.current_page} before this message.')
|
|
||||||
await self.message.edit(embed=self.embed)
|
|
||||||
|
|
||||||
async def go_back_to_current_page():
|
|
||||||
await asyncio.sleep(30.0)
|
|
||||||
await self.show_current_page()
|
|
||||||
|
|
||||||
self.bot.loop.create_task(go_back_to_current_page())
|
|
||||||
|
|
||||||
async def show_bot_help(self):
|
|
||||||
"""shows how to use the bot"""
|
|
||||||
|
|
||||||
self.embed.title = 'Using the bot'
|
|
||||||
self.embed.description = 'Hello! Welcome to the help page.'
|
|
||||||
self.embed.clear_fields()
|
|
||||||
|
|
||||||
entries = (
|
|
||||||
('<argument>', 'This means the argument is __**required**__.'),
|
|
||||||
('[argument]', 'This means the argument is __**optional**__.'),
|
|
||||||
('[A|B]', 'This means the it can be __**either A or B**__.'),
|
|
||||||
('[argument...]', 'This means you can have multiple arguments.\n' \
|
|
||||||
'Now that you know the basics, it should be noted that...\n' \
|
|
||||||
'__**You do not type in the brackets!**__')
|
|
||||||
)
|
|
||||||
|
|
||||||
self.embed.add_field(name='How do I use this bot?', value='Reading the bot signature is pretty simple.')
|
|
||||||
|
|
||||||
for name, value in entries:
|
|
||||||
self.embed.add_field(name=name, value=value, inline=False)
|
|
||||||
|
|
||||||
self.embed.set_footer(text=f'We were on page {self.current_page} before this message.')
|
|
||||||
await self.message.edit(embed=self.embed)
|
|
||||||
|
|
||||||
async def go_back_to_current_page():
|
|
||||||
await asyncio.sleep(30.0)
|
|
||||||
await self.show_current_page()
|
|
||||||
|
|
||||||
self.bot.loop.create_task(go_back_to_current_page())
|
|
113
cogs/vocal.py
113
cogs/vocal.py
|
@ -1,113 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
from gtts import gTTS
|
|
||||||
|
|
||||||
|
|
||||||
class Vocal(commands.Cog):
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
self.playing = False
|
|
||||||
self.author = None
|
|
||||||
self.voice = None
|
|
||||||
|
|
||||||
"""---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_duration(file):
|
|
||||||
popen = subprocess.Popen(("ffprobe",
|
|
||||||
"-show_entries",
|
|
||||||
"format=duration",
|
|
||||||
"-i", file),
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE)
|
|
||||||
output, err = popen.communicate()
|
|
||||||
match = re.search(r"[-+]?\d*\.\d+|\d+", str(output))
|
|
||||||
return float(match.group())
|
|
||||||
|
|
||||||
@commands.command(name="voc", no_pm=True, pass_context=True)
|
|
||||||
async def _voc(self, ctx, *, message=""):
|
|
||||||
if message == "":
|
|
||||||
await ctx.send("Veuillez écrire un message...")
|
|
||||||
return
|
|
||||||
if message == "stop_playing" \
|
|
||||||
and (
|
|
||||||
ctx.author.id == self.author.id
|
|
||||||
or ctx.message.channel.permissions_for(
|
|
||||||
ctx.message.author
|
|
||||||
).administrator is True
|
|
||||||
) \
|
|
||||||
and self.playing is True:
|
|
||||||
|
|
||||||
await ctx.send('stop')
|
|
||||||
await self.voice.disconnect()
|
|
||||||
self.playing = False
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.playing is True:
|
|
||||||
await ctx.send("Je suis déja en train de parler,"
|
|
||||||
" merci de réenvoyer ton message"
|
|
||||||
" quand j'aurais fini.")
|
|
||||||
return
|
|
||||||
|
|
||||||
user = ctx.author
|
|
||||||
self.author = user
|
|
||||||
|
|
||||||
if user.voice:
|
|
||||||
self.playing = True
|
|
||||||
filename = f"data/tmp/voc/{uuid.uuid1()}.mp3"
|
|
||||||
lang = [x for x in message.split(" ") if x.startswith("lang=")]
|
|
||||||
|
|
||||||
loading = await ctx.send("*Chargement du message en cours...*")
|
|
||||||
|
|
||||||
if lang:
|
|
||||||
choice_lang = (lang[0])[5:]
|
|
||||||
message = f"{user.display_name} à dit: {message.strip(lang[0])}" if len(ctx.author.voice.channel.members) >= 4 else message.strip(lang[0])
|
|
||||||
|
|
||||||
try:
|
|
||||||
tts = gTTS(
|
|
||||||
text=message,
|
|
||||||
lang=str(choice_lang))
|
|
||||||
except ValueError:
|
|
||||||
tts = gTTS(
|
|
||||||
text=message,
|
|
||||||
lang="fr")
|
|
||||||
await ctx.send("La langue n'est pas supportée,"
|
|
||||||
" le francais a donc été choisi")
|
|
||||||
else:
|
|
||||||
message = f"{user.display_name} à dit: {message}" if len(ctx.author.voice.channel.members) >= 4 else message
|
|
||||||
tts = gTTS(text=message,
|
|
||||||
lang="fr")
|
|
||||||
|
|
||||||
tts.save(filename)
|
|
||||||
|
|
||||||
self.voice = await user.voice.channel.connect()
|
|
||||||
self.voice.play(discord.FFmpegPCMAudio(filename))
|
|
||||||
counter = 0
|
|
||||||
duration = self.get_duration(filename)
|
|
||||||
while not counter >= duration:
|
|
||||||
if self.playing:
|
|
||||||
await loading.edit(
|
|
||||||
content=f"Lecture du message de {self.author.display_name} en cours : {counter}sec/{duration}sec")
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
counter += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
await self.voice.disconnect()
|
|
||||||
|
|
||||||
await loading.edit(content="Lecture terminée")
|
|
||||||
self.voice = None
|
|
||||||
os.remove(filename)
|
|
||||||
self.playing = False
|
|
||||||
else:
|
|
||||||
await ctx.send('Veuillez aller dans un channel vocal.')
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Vocal(bot))
|
|
|
@ -1,20 +0,0 @@
|
||||||
token = "INSERT TOKEN HERE"
|
|
||||||
client_id = <INSERT_CLIENT_ID_HERE (in int)>
|
|
||||||
log_channel_id = <INSERT_LOG_CHANNEL_HERE (in int)>
|
|
||||||
main_server_id = <INSERT_MAIN_CHANNEL_ID_HERE (in int)>
|
|
||||||
|
|
||||||
game = "PLAYING_GAME_HERE"
|
|
||||||
prefix = ["."]
|
|
||||||
description = """
|
|
||||||
Je suis TuxBot, le bot qui vit de l'OpenSource ! ;)
|
|
||||||
"""
|
|
||||||
|
|
||||||
mysql = {
|
|
||||||
"host": "localhost",
|
|
||||||
"username": "msqlusername",
|
|
||||||
"password": "msqlpasswd",
|
|
||||||
"dbname": "mysqldb"
|
|
||||||
}
|
|
||||||
|
|
||||||
authorized_id = ['admin ids here']
|
|
||||||
unkickable_id = ['unkickable ids here']
|
|
0
cogs/utils/__init__.py → configs/blacklist.cfg
Executable file → Normal file
0
cogs/utils/__init__.py → configs/blacklist.cfg
Executable file → Normal file
24
configs/config.cfg.example
Normal file
24
configs/config.cfg.example
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[bot]
|
||||||
|
Token =
|
||||||
|
Tester =
|
||||||
|
Activity =
|
||||||
|
|
||||||
|
[postgresql]
|
||||||
|
Username =
|
||||||
|
Password =
|
||||||
|
Host =
|
||||||
|
DBName =
|
||||||
|
|
||||||
|
[permissions]
|
||||||
|
Owners =
|
||||||
|
|
||||||
|
[webhook]
|
||||||
|
ID =
|
||||||
|
Token =
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
Separator =
|
||||||
|
|
||||||
|
[API]
|
||||||
|
Host =
|
||||||
|
Port =
|
18
configs/fallbacks.cfg.example
Normal file
18
configs/fallbacks.cfg.example
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[fr-srv01]
|
||||||
|
Host =
|
||||||
|
Name = fr-srv01
|
||||||
|
WebPage = 0.0.0.0
|
||||||
|
Port =
|
||||||
|
|
||||||
|
[rm-dev01]
|
||||||
|
This = True
|
||||||
|
Host = 127.0.0.1
|
||||||
|
Name = rm-dev01
|
||||||
|
WebPage = 0.0.0.0
|
||||||
|
Port = 3389
|
||||||
|
|
||||||
|
[rm-srv01]
|
||||||
|
Host = 127.0.0.1
|
||||||
|
Name = rm-srv01
|
||||||
|
WebPage = 0.0.0.0
|
||||||
|
Port = 3390
|
18
configs/prefixes.cfg
Normal file
18
configs/prefixes.cfg
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[280805240977227776]
|
||||||
|
prefixes = b1.[301062143942590465]*
|
||||||
|
|
||||||
|
[303633056944881686]
|
||||||
|
prefixes = b1.[301062143942590465]*
|
||||||
|
|
||||||
|
[373881878471770112]
|
||||||
|
prefixes = b1.
|
||||||
|
|
||||||
|
[336642139381301249]
|
||||||
|
prefixes = ba.
|
||||||
|
|
||||||
|
[274247231534792704]
|
||||||
|
prefixes = test.
|
||||||
|
|
||||||
|
[528679953399676938]
|
||||||
|
prefixes = test.
|
||||||
|
|
19
database.py
Normal file
19
database.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import sqlalchemy
|
||||||
|
from utils.models import database, metadata
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("-m", "--migrate", action="store_true")
|
||||||
|
parser.add_argument("-s", "--seed", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.migrate:
|
||||||
|
print("Migrate...")
|
||||||
|
engine = sqlalchemy.create_engine(str(database.url))
|
||||||
|
metadata.create_all(engine)
|
||||||
|
print("Done!")
|
||||||
|
|
||||||
|
if args.seed:
|
||||||
|
print('Seeding...')
|
||||||
|
# todo: add seeding
|
||||||
|
print("Done!")
|
17
generate_locales.sh
Executable file
17
generate_locales.sh
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BASEDIR=$(pwd)
|
||||||
|
|
||||||
|
cd "$BASEDIR/utils/locales/en/LC_MESSAGES"
|
||||||
|
|
||||||
|
for i in *.po ; do
|
||||||
|
[[ -f "$i" ]] || continue
|
||||||
|
/usr/lib/python3.8/Tools/i18n/msgfmt.py -o "${i%.po}.mo" "${i%.po}"
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$BASEDIR/utils/locales/fr/LC_MESSAGES"
|
||||||
|
|
||||||
|
for i in *.po ; do
|
||||||
|
[[ -f "$i" ]] || continue
|
||||||
|
/usr/lib/python3.8/Tools/i18n/msgfmt.py -o "${i%.po}.mo" "${i%.po}"
|
||||||
|
done
|
2
init.sh
Executable file → Normal file
2
init.sh
Executable file → Normal file
|
@ -1,3 +1,3 @@
|
||||||
#pip install -U "https://github.com/Rapptz/discord.py/archive/rewrite.zip#egg=discord.py[voice]"
|
#! /bin/bash
|
||||||
python3 -m pip install -U discord.py[voice]
|
python3 -m pip install -U discord.py[voice]
|
||||||
python3 -m pip install -r requirements.txt
|
python3 -m pip install -r requirements.txt
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
pymysql
|
|
||||||
gtts
|
|
||||||
beautifulsoup4
|
|
||||||
lxml==4.2.4
|
|
||||||
bs4
|
|
||||||
pytz
|
|
||||||
requests
|
requests
|
||||||
wikipedia
|
humanize
|
||||||
|
git+https://github.com/Rapptz/discord.py@master
|
||||||
|
jishaku
|
||||||
|
gitpython
|
||||||
|
orm
|
||||||
|
asyncpg
|
||||||
|
psycopg2
|
||||||
|
configparser
|
||||||
|
psutil
|
||||||
|
tcp_latency
|
||||||
|
yarl
|
||||||
pillow
|
pillow
|
|
@ -1,11 +0,0 @@
|
||||||
La carte d'identité est un petit système dans tuxbot permetant de vous démarquer de vos amis en ayant la possibilité d'y renseigner plusieurs informations !
|
|
||||||
|
|
||||||
**Liste des commandes : **
|
|
||||||
-> .ci : Affiche l'aide sur les cartes d'identité
|
|
||||||
-> .ci show _pseudo_ : Affiche la carte d'identité de _pseudo_
|
|
||||||
-> .ci register : Vous enregistre dans la base de donnée des cartes d'identité
|
|
||||||
-> .ci setos _nom de l'os_ : Défini votre système d'exploitation
|
|
||||||
-> .ci setconfig _votre configuration pc_ : Défini la configuration de votre ordinateur
|
|
||||||
-> .ci setcountry : Défini votre pays
|
|
||||||
-> .ci update : Met à jour votre image si vous l'avez changé
|
|
||||||
-> .ci delete : Supprime votre carte d'identité
|
|
|
@ -1,11 +0,0 @@
|
||||||
|
|
||||||
_Pour utiliser les horloges utilisez la commande : **clock ** ville_
|
|
||||||
-> Montreal (Canada, QC)
|
|
||||||
-> Vancouver (Canada, BC)
|
|
||||||
-> New-York / N-Y (U.S.A.)
|
|
||||||
-> LosAngeles / L-A (U.S.A.)
|
|
||||||
-> Berlin (Allemagne)
|
|
||||||
-> Bern / Zurich (Suisse)
|
|
||||||
-> Paris (France)
|
|
||||||
-> Tokyo (Japon)
|
|
||||||
-> Moscou (Russie)
|
|
|
@ -1,39 +0,0 @@
|
||||||
**Commandes utilitaires**
|
|
||||||
-> .afk : Signaler son absence *(commande désactivée)*
|
|
||||||
-> .back : Signaler son retour *(commande désactivée)*
|
|
||||||
-> .clock _ville_: Affiche l'heure et quelques infos sur la ville en question
|
|
||||||
-> .ytdiscover : Découvrir des chaines youtube
|
|
||||||
-> .search _site_ _contenu_ : Fait une recherche sur un site (.search pour plus d'infos)
|
|
||||||
-> .avatar _@pseudo_ : Récupère l'avatar de _@pseudo_
|
|
||||||
-> .poke _@pseudo_ : Poke _@pseudo_
|
|
||||||
-> .sondage _question_ | _reponse_ | _reponse_ | _option_ : Créer un sondage avec des réactions
|
|
||||||
-> .sondage : Affiche l'aide pour la commande sondage
|
|
||||||
-> .role _nomdurole_ : Ajoute/Retire le rôle en question
|
|
||||||
-> .iplocalise _IP ou NDD_ : affiche la localisation et le propriétaire de l'ip (ou de l'IP lié a un nom de domaine)
|
|
||||||
-> .iplocalise _IP ou NDD_ **ipv6**: affiche la localisation et le propriétaire de l'ip en forçant sur l'IPv6 (ou de l'IP lié a un nom de domaine)
|
|
||||||
-> .getheaders _IP ou NDD_ : affiche les en-têtes (headers) d'une IP/Nom de domaine via HTTP/HTTPS/FTP
|
|
||||||
-> .btcprice : Affiche le prix du bitcoin
|
|
||||||
[split]
|
|
||||||
**Commandes Funs**
|
|
||||||
-> .joke : Affiche une blague aléatoire
|
|
||||||
-> .ethylotest : Simule un ethylotest détraqué
|
|
||||||
-> .pokemon : Lance un combat de pokémons
|
|
||||||
-> .coin : Simule un pile ou face
|
|
||||||
-> .randomcat : Affiche des image de chats :3
|
|
||||||
[split]
|
|
||||||
**Commandes Carte d'Identité**
|
|
||||||
-> .ci : Affiche l'aide sur les cartes d'identité
|
|
||||||
-> .ci show _pseudo_ : Affiche la carte d'identité de _pseudo_
|
|
||||||
-> .ci register : Vous enregistre dans la base de donnée des cartes d'identité
|
|
||||||
-> .ci setos _nom de l'os_ : Défini le système d'exploitation
|
|
||||||
-> .ci setconfig _votre configuration pc_ : Défini la configuration de votre ordinateur
|
|
||||||
-> .ci setcountry : Défini votre pays
|
|
||||||
-> .ci update : Met à jour votre image si vous l'avez changé :wink:
|
|
||||||
-> .ci delete : Supprime votre carte d'identité **a tous jamais**
|
|
||||||
[split]
|
|
||||||
**Commandes diverses** :
|
|
||||||
-> .info : Affiche des informations sur le bot
|
|
||||||
-> .help : Affiche ce message
|
|
||||||
-> .clock : Affiche la liste des horloges des villes
|
|
||||||
-> .ping : Ping le bot
|
|
||||||
-> .git : Affiche le repos Gitea du Bot :heart:
|
|
|
@ -1,24 +0,0 @@
|
||||||
|
|
||||||
:tools: **Développement** :
|
|
||||||
└> Outout : [outout.xyz](https://outout.xyz/)
|
|
||||||
└> Romain : [son github](http://git.gnous.eu/Romain)
|
|
||||||
└> Langage : [Python3](http://www.python.org/)
|
|
||||||
└> Api : [discord.py {3}](https://github.com/Rapptz/discord.py)
|
|
||||||
└> Discord Api : [{4}]({4})
|
|
||||||
└> En se basant sur : [RobotDanny](https://github.com/Rapptz/RoboDanny)
|
|
||||||
|
|
||||||
:desktop: **Hébergé sur "{2}"**:
|
|
||||||
└> OS : {0}
|
|
||||||
└> Version : {1}
|
|
||||||
|
|
||||||
:telephone: **Contact** :
|
|
||||||
└> Discord : Outout#8406
|
|
||||||
└> Twitter : [@outoutxyz](https://twitter.com/outouxyz)
|
|
||||||
└> Courriel : [mael@gnous.eu](mailto:mael@gnous.eu)
|
|
||||||
└> Discord : Romain#5117
|
|
||||||
|
|
||||||
|
|
||||||
:link: **Serveur** :
|
|
||||||
└> Serveur GnousEU : [rejoindre](https://discord.gg/NFW3EeS)
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"1": {"content": "Les hyperboles sa sert à manger des hyper-soupes :3 (Lawl!)", "author": "Crumble14 (bukkit.fr)"},
|
|
||||||
"2": {"content": "Le comble de Windows, c’est que pour l’arrêter, il faut cliquer sur démarrer.", "author": "Keke142 (bukkit.fr)"},
|
|
||||||
"3": {"content": "Chrome: On est le 8 avril 2016 13h02 \n Safari: On est le 8 avril 2016 13h02 \n Internet Explorer: On est le... **[Internet Explorer a cessé de fonctionner, veuillez redémarrer votre machine]**", "author": "NyoSan"},
|
|
||||||
"4": {"content": "Il y a 10 types de personnes dans le monde, ceux qui comprennent le binaire et les autres.", "author": "Dartasen (bukkit.fr)"},
|
|
||||||
"5": {"content": "C'est une requête SQL qui rentre dans un bar et qui s'adresse à deux tables \"Puis-je vous joindre ?\".\"", "author": "Dartasen (bukkit.fr)"},
|
|
||||||
"6": {"content": "Combien de développeurs faut-il pour remplacer une ampoule grillée ? Aucun, c'est un problème Hardware.", "author": "Dartasen (bukkit.fr)"},
|
|
||||||
"7": {"content": "Tu sais que tu as affaire à un développeur quand ça ne le gêne pas d'avoir un String dans l'Array.", "author": "Dartasen (bukkit.fr)"},
|
|
||||||
"8": {"content": "Pourquoi y'a pas d'adresse windows ou linux ? Si y'a l'addresse mac !", "author": "Antho"},
|
|
||||||
"9": {"content": "Les appareils apple ont ils une adresse personnalisée ?", "author": "Outout"},
|
|
||||||
"10": {"content": "Le 1er janvier 1970 c'est le jour où il y a eu le plus de plantages. (cf : http://bit.ly/2rArLVe)", "author": "NyoSan"},
|
|
||||||
"11": {"content": "Pourquoi est-ce que les girafes aiment magasiner à bas prix? Tout est une question de cou.", "author": "Maxx_Qc (bukkit.fr)"},
|
|
||||||
"12": {"content": "``Même éteint le hackeur peut pirater l'ordi`` \"Le SuperGeek tournant sous Ubuntu (ou Windows)\"", "author": "Outout"},
|
|
||||||
"13": {"content": "Trois ingénieurs (1 chimiste, 1 électronicien, 1 Microsoft) dans un bus roulant dans un désert. \n\n Le bus « tombe en panne » sans raison apparente, et voila les 3 gars à discuter. \n L’électronicien : je pourrais regarder les circuits et voir si quelque chose cloche. \n Le chimiste : on devrait vérifier l'essence avant. \n L’ingé Microsoft : non, on remonte dans le bus, on ferme toutes les fenêtres, et logiquement ça devrait redémarrer.", "author": "Internet"}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
Le passeport est un petit système dans tuxbot permetant de vous démarquer de vos amis en ayant la possibilité d'y renseigner plusieurs informations !
|
|
||||||
|
|
||||||
**Liste des commandes : **
|
|
||||||
-> .passeport : Affiche l'aide sur les cartes d'identité
|
|
||||||
-> .passeport show _pseudo_ : Affiche le passeport de _pseudo_
|
|
||||||
-> .passeport config : Vous envois un message privé afin de configurer votre passeport
|
|
||||||
-> .passeport background _url_ : Défini _url_ comme étant le fond d'écran de votre passeport
|
|
||||||
-> .passeport thème <dark/light/preview>: Definie le theme de votre passeport (`.passeport preview` envoi une image avec les 2 thèmes pour comparer)
|
|
||||||
-> .passeport delete : Supprime les informations de votre carte passeport
|
|
File diff suppressed because one or more lines are too long
|
@ -1,13 +0,0 @@
|
||||||
|
|
||||||
Pour améliorer l'expérience utilisateur de tout le monde, vous pouvez spécifier la·les distribution·s que vous utilisez via les rôles Discord.
|
|
||||||
|
|
||||||
**Liste des rôles**
|
|
||||||
└> Arch : pour les utilisateurs de Arch, Manjaro et TuxNVape
|
|
||||||
└> Debian : pour Debian et ses dérivés (Ubuntu, Kali, etc.)
|
|
||||||
└> Rhel : pour Red Hat Entreprise Linux et ses dérivés (Fedora, CentOS, etc.)
|
|
||||||
└> Android : pour Android
|
|
||||||
└> BSD : pour les systèmes basés sur BSD
|
|
||||||
|
|
||||||
**Commandes**
|
|
||||||
└> Pour ajouter un rôle : ``.role Nomdurole``
|
|
||||||
└> Pour retirer un rôle : ``.role Nomdurole``
|
|
|
@ -1,14 +0,0 @@
|
||||||
**Créez un sondage avec les réactions !**
|
|
||||||
|
|
||||||
**Usage** :
|
|
||||||
``.sondage <question> | <reponse> | <reponse> | <reponse>`` Vous pouvez utiliser autant de réponses que vous le souhaitez, en plaçant un symbole | entre chaque choix.
|
|
||||||
|
|
||||||
**Exemple**:
|
|
||||||
``.sondage Quelle est votre couleur préférée ? | Rouge | Vert | Bleu | Autre``
|
|
||||||
|
|
||||||
|
|
||||||
**Definir un temps limite** :
|
|
||||||
Vous pouvez également utiliser l'option "time" pour définir le temps en secondes pendant lequel le sondage durera.
|
|
||||||
|
|
||||||
**Exemple**:
|
|
||||||
``.sondage Utilisez vous twitteur ? | Oui | Non | Pas souvent | time=10``.
|
|
|
@ -1,6 +0,0 @@
|
||||||
|
|
||||||
_Attention ! entrez vos termes de recherche sans espaces !_
|
|
||||||
Pour effectuer une recherche utilisez la commande ``.search {site_de_recherche} {termes_recherche}``
|
|
||||||
-> [**docubuntu**](https://doc.ubuntu-fr.org) : Effectuer une recherche sur un paquet dans la Documentation du site ubuntu-fr.org.
|
|
||||||
-> [**wikipedia**](https://fr.wikipedia.org) : Effectuer une recherche sur l'encyclopédie libre Wikipedia en Français !
|
|
||||||
-> [**docaur**](https://doc.archlinux.org) : Effectuer une recherche sur la doc ArchLinux !
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"1": {"name": "KickSama", "desc": "Des dessins annimés sympatiques par un jeune !", "url": "https://www.youtube.com/user/TheKickGuy"},
|
|
||||||
"2": {"name": "U=RI", "desc": "Des vidéos interessantes sur l'électricité dont des tutoriels !", "url": "https://www.youtube.com/channel/UCVqx3vXNghSqUcVg2nmegYA"},
|
|
||||||
"3": {"name": "Outout", "desc": "Outout, chaine vraiment nul et peu alimenté par mon créateur...", "url": "https://www.youtube.com/channel/UC2XpYyT5X5tq9UQpXdc1JaQ"},
|
|
||||||
"4": {"name": "SuperJDay64", "desc": "Des LetsPlay sur Nintendo64 avec beaucoup de plombiers moustachus !", "url": "https://www.youtube.com/channel/UCjkQgODdmhR9I2TatJZtGSQ/"},
|
|
||||||
"5": {"name": "Monsieur Plouf", "desc": "Vidéos comiques de critiques de jeux AAA avec un décors assez spécial !", "url": "https://www.youtube.com/channel/UCrt_PUTF9LdJyuDfXweHwuQ"},
|
|
||||||
"6": {"name": "MaxEstLa", "desc": "Petite chaîne bien _sympatique_ sur la réaction de vidéos malsaine ! Très éducative x)", "url": "https://www.youtube.com/channel/UCsk9XguwTfgbenCZ4AlIcYQ"},
|
|
||||||
"7": {"name": "Met-Hardware", "desc": "Chaine youtube sur l'hardware et des let's play bien sypatique !", "url": "https://www.youtube.com/channel/UC7rse81OttysA1m1yn_f-OA"},
|
|
||||||
"8": {"name": "ElectronikHeart", "desc": "~~Test de produits de merde ~~ L'informatique sous un angle différent et agréable", "url": "https://www.youtube.com/user/ElectronikHeart"},
|
|
||||||
"9": {"name": "Caljbeut", "desc": "Cartoon Trash ! Dessins annimés par un ancien de l'armée sur la politique et d'autre sujets ! **On est pas la pour rigoler**", "url": "https://www.youtube.com/channel/UCNM-UkIP1BL5jv9ZrN5JMCA"},
|
|
||||||
"10": {"name": "Autodisciple", "desc": "Defis, Bitcoins, Geek, la vie quoi ! Sans oublier des défis de 30 Jours !", "url": "https://www.youtube.com/channel/UCDMxcev7u9Nf7KMJuyIm-BA"},
|
|
||||||
"11": {"name": "CineAstuces", "desc": "Techniques, metiers du cinema, reportages et autres en rapport avec la cinématographie !", "url": "https://www.youtube.com/channel/UC--84qgkrqqqYivuuXuQIQg"},
|
|
||||||
"12": {"name": "Epic Teaching of the History", "desc": "L'Histoire c'est hyper méga giga ultra _(j'ai pas été payé)_ drôle avec RaAak le renard ! ", "url": "https://www.youtube.com/channel/UCHwd4qMCzN4A2r6piZxTl4A"}
|
|
||||||
}
|
|
||||||
|
|
2
todo
Normal file
2
todo
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
reconnaissance d'image
|
||||||
|
commande d'archivage pour les salons vocaux avec output mp4 dans lequel on voit le pseudo de celui qui parle
|
6
utils/__init__.py
Executable file
6
utils/__init__.py
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
from utils.functions.config import *
|
||||||
|
from utils.functions.lang import *
|
||||||
|
from utils.functions.version import *
|
||||||
|
|
||||||
|
from utils.functions.extra import *
|
||||||
|
from utils.functions.paginator import *
|
BIN
utils/fonts/credit_card.ttf
Normal file
BIN
utils/fonts/credit_card.ttf
Normal file
Binary file not shown.
6
utils/functions/__init__.py
Normal file
6
utils/functions/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from .config import Config
|
||||||
|
from .database import Database
|
||||||
|
from .extra import *
|
||||||
|
from .lang import Texts
|
||||||
|
from .paginator import *
|
||||||
|
from .version import Version
|
36
utils/functions/config.py
Normal file
36
utils/functions/config.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from typing import List, Union
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
|
class Config(configparser.ConfigParser):
|
||||||
|
__slots__ = ('name', '_db')
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._db = super()
|
||||||
|
self._db.read(name)
|
||||||
|
|
||||||
|
def find(self, value: str, **kwargs) \
|
||||||
|
-> Union[
|
||||||
|
List[configparser.SectionProxy], configparser.SectionProxy
|
||||||
|
]:
|
||||||
|
key = kwargs.get('key', None)
|
||||||
|
first = kwargs.get('first', False)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for name, section in self._db.items():
|
||||||
|
if key is None:
|
||||||
|
for k in section.keys():
|
||||||
|
if section.get(k) == value:
|
||||||
|
results.append(section)
|
||||||
|
if first and len(results) == 1:
|
||||||
|
return results[0]
|
||||||
|
else:
|
||||||
|
if section.get(key) == value:
|
||||||
|
results.append(section)
|
||||||
|
if first and len(results) == 1:
|
||||||
|
return results[0]
|
||||||
|
return results
|
16
utils/functions/database.py
Normal file
16
utils/functions/database.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from .config import Config
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
import databases
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self, config: Config):
|
||||||
|
conf_postgresql = config["postgresql"]
|
||||||
|
postgresql = 'postgresql://{}:{}@{}/{}'.format(
|
||||||
|
conf_postgresql.get("Username"), conf_postgresql.get("Password"),
|
||||||
|
conf_postgresql.get("Host"), conf_postgresql.get("DBName"))
|
||||||
|
|
||||||
|
self.database = databases.Database(postgresql)
|
||||||
|
self.metadata = sqlalchemy.MetaData()
|
||||||
|
self.engine = sqlalchemy.create_engine(str(self.database.url))
|
10
utils/functions/emotes.py
Normal file
10
utils/functions/emotes.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
emotes = ['1⃣', '2⃣', '3⃣', '4⃣', '5⃣', '6⃣', '7⃣', '8⃣', '9⃣', '🔟', '0⃣',
|
||||||
|
'🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮']
|
||||||
|
|
||||||
|
|
||||||
|
def get(count):
|
||||||
|
return emotes[:count]
|
||||||
|
|
||||||
|
|
||||||
|
def get_index(emote):
|
||||||
|
return emotes.index(emote)
|
32
utils/functions/extra.py
Normal file
32
utils/functions/extra.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from discord.ext import commands
|
||||||
|
from utils.functions import Config
|
||||||
|
|
||||||
|
|
||||||
|
class CommandsPlus(commands.Command):
|
||||||
|
def __init__(self, func, **kwargs):
|
||||||
|
super().__init__(func, **kwargs)
|
||||||
|
self.category = kwargs.get("category", 'other')
|
||||||
|
|
||||||
|
|
||||||
|
class GroupPlus(commands.Group):
|
||||||
|
def __init__(self, func, **kwargs):
|
||||||
|
super().__init__(func, **kwargs)
|
||||||
|
self.category = kwargs.get("category", 'other')
|
||||||
|
|
||||||
|
|
||||||
|
class ContextPlus(commands.Context):
|
||||||
|
async def send(self, content=None, **kwargs):
|
||||||
|
config = Config('./configs/config.cfg')
|
||||||
|
|
||||||
|
content = content.replace(config.get("bot", "Token"), 'Whoops! leaked token')
|
||||||
|
content = content.replace(config.get("webhook", "Token"), 'Whoops! leaked token')
|
||||||
|
|
||||||
|
return await super().send(content, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def command_extra(*args, **kwargs):
|
||||||
|
return commands.command(*args, **kwargs, cls=CommandsPlus)
|
||||||
|
|
||||||
|
|
||||||
|
def group_extra(*args, **kwargs):
|
||||||
|
return commands.group(*args, **kwargs, cls=GroupPlus)
|
34
utils/functions/lang.py
Normal file
34
utils/functions/lang.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import gettext
|
||||||
|
import json
|
||||||
|
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
|
class Texts:
|
||||||
|
def __init__(self, base: str = 'base', ctx: commands.Context = None):
|
||||||
|
self.locale = self.get_locale(ctx)
|
||||||
|
self.base = base
|
||||||
|
|
||||||
|
def get(self, text: str) -> str:
|
||||||
|
texts = gettext.translation(self.base, localedir='utils/locales',
|
||||||
|
languages=[self.locale])
|
||||||
|
texts.install()
|
||||||
|
return texts.gettext(text)
|
||||||
|
|
||||||
|
def set(self, lang: str):
|
||||||
|
self.locale = lang
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_locale(ctx: commands.Context):
|
||||||
|
lang = 'fr'
|
||||||
|
if ctx is not None:
|
||||||
|
try:
|
||||||
|
with open(f'./configs/guilds/{ctx.guild.id}.json', 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
lang = data['lang']
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return lang
|
343
utils/functions/paginator.py
Normal file
343
utils/functions/paginator.py
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
"""
|
||||||
|
|
||||||
|
Based on https://github.com/Rapptz/RoboDanny/blob/3ec71c4c4031f868caff3027d71aecdebc3c5cec/cogs/utils/paginator.py
|
||||||
|
Adapted by Romain J.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
from discord.ext.commands import Paginator as CommandPaginator
|
||||||
|
|
||||||
|
|
||||||
|
class CannotPaginate(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Pages:
|
||||||
|
"""Implements a paginator that queries the user for the
|
||||||
|
pagination interface.
|
||||||
|
Pages are 1-index based, not 0-index based.
|
||||||
|
If the user does not reply within 2 minutes then the pagination
|
||||||
|
interface exits automatically.
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
ctx: Context
|
||||||
|
The context of the command.
|
||||||
|
entries: List[str]
|
||||||
|
A list of entries to paginate.
|
||||||
|
per_page: int
|
||||||
|
How many entries show up per page.
|
||||||
|
show_entry_count: bool
|
||||||
|
Whether to show an entry count in the footer.
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
embed: discord.Embed
|
||||||
|
The embed object that is being used to send pagination info.
|
||||||
|
Feel free to modify this externally. Only the description,
|
||||||
|
footer fields, and colour are internally modified.
|
||||||
|
permissions: discord.Permissions
|
||||||
|
Our permissions for the channel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True,
|
||||||
|
embed_color=discord.Color.blurple(), title=None,
|
||||||
|
thumbnail=None, footericon=None, footertext=None, author=None,
|
||||||
|
delete_after=None):
|
||||||
|
self.bot = ctx.bot
|
||||||
|
self.entries = entries
|
||||||
|
self.message = ctx.message
|
||||||
|
self.channel = ctx.channel
|
||||||
|
self.author = author if author else ctx.author
|
||||||
|
self.thumbnail = thumbnail
|
||||||
|
self.footericon = footericon
|
||||||
|
self.footertext = footertext
|
||||||
|
self.title = title
|
||||||
|
self.delete_after = delete_after
|
||||||
|
self.per_page = per_page
|
||||||
|
pages, left_over = divmod(len(self.entries), self.per_page)
|
||||||
|
if left_over:
|
||||||
|
pages += 1
|
||||||
|
self.maximum_pages = pages
|
||||||
|
self.embed = discord.Embed(colour=embed_color)
|
||||||
|
self.paginating = len(entries) > per_page
|
||||||
|
self.show_entry_count = show_entry_count
|
||||||
|
self.reaction_emojis = [
|
||||||
|
('\U000023ee\U0000fe0f', self.first_page),
|
||||||
|
('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page),
|
||||||
|
('\U000023f9', self.stop_pages),
|
||||||
|
('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page),
|
||||||
|
('\U000023ed\U0000fe0f', self.last_page)
|
||||||
|
]
|
||||||
|
|
||||||
|
if ctx.guild is not None:
|
||||||
|
self.permissions = self.channel.permissions_for(ctx.guild.me)
|
||||||
|
else:
|
||||||
|
self.permissions = self.channel.permissions_for(ctx.bot.user)
|
||||||
|
|
||||||
|
if not self.permissions.embed_links:
|
||||||
|
raise commands.BotMissingPermissions(
|
||||||
|
'I do not have permissions to : Embed links.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.permissions.send_messages:
|
||||||
|
raise commands.BotMissingPermissions('Bot cannot send messages.')
|
||||||
|
|
||||||
|
if self.paginating:
|
||||||
|
# verify we can actually use the pagination session
|
||||||
|
if not self.permissions.add_reactions:
|
||||||
|
raise commands.BotMissingPermissions(
|
||||||
|
'I do not have permissions to : Add Reactions.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.permissions.read_message_history:
|
||||||
|
raise commands.BotMissingPermissions(
|
||||||
|
'I do not have permissions to : Read Message History.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_page(self, page):
|
||||||
|
base = (page - 1) * self.per_page
|
||||||
|
return self.entries[base:base + self.per_page]
|
||||||
|
|
||||||
|
def get_content(self, entries, page, *, first=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_embed(self, entries, page, *, first=False):
|
||||||
|
self.prepare_embed(entries, page, first=first)
|
||||||
|
return self.embed
|
||||||
|
|
||||||
|
def prepare_embed(self, entries, page, *, first=False):
|
||||||
|
p = []
|
||||||
|
for index, entry in enumerate(entries,
|
||||||
|
1 + ((page - 1) * self.per_page)):
|
||||||
|
p.append(f'`{index}.` {entry}')
|
||||||
|
|
||||||
|
if self.maximum_pages > 1:
|
||||||
|
if self.show_entry_count:
|
||||||
|
text = f'Showing page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
|
||||||
|
else:
|
||||||
|
text = f'Showing page {page}/{self.maximum_pages}'
|
||||||
|
|
||||||
|
self.embed.set_footer(text=text)
|
||||||
|
|
||||||
|
if self.paginating and first:
|
||||||
|
p.append('')
|
||||||
|
|
||||||
|
self.embed.description = '\n'.join(p)
|
||||||
|
self.embed.title = self.title or discord.Embed.Empty
|
||||||
|
self.embed.set_author(icon_url=self.author.avatar_url,
|
||||||
|
name=str(self.author))
|
||||||
|
|
||||||
|
async def show_page(self, page, *, first=False):
|
||||||
|
self.current_page = page
|
||||||
|
entries = self.get_page(page)
|
||||||
|
content = self.get_content(entries, page, first=first)
|
||||||
|
embed = self.get_embed(entries, page, first=first)
|
||||||
|
|
||||||
|
if not self.paginating:
|
||||||
|
return await self.channel.send(content=content, embed=embed)
|
||||||
|
|
||||||
|
if not first:
|
||||||
|
await self.message.edit(content=content, embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.message = await self.channel.send(content=content, embed=embed)
|
||||||
|
for (reaction, _) in self.reaction_emojis:
|
||||||
|
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
|
||||||
|
# no |<< or >>| buttons if we only have two pages
|
||||||
|
# we can't forbid it if someone ends up using it but remove
|
||||||
|
# it from the default set
|
||||||
|
continue
|
||||||
|
|
||||||
|
await self.message.add_reaction(reaction)
|
||||||
|
|
||||||
|
async def checked_show_page(self, page):
|
||||||
|
if page != 0 and page <= self.maximum_pages:
|
||||||
|
await self.show_page(page)
|
||||||
|
|
||||||
|
async def first_page(self):
|
||||||
|
"""goes to the first page"""
|
||||||
|
await self.show_page(1)
|
||||||
|
|
||||||
|
async def last_page(self):
|
||||||
|
"""goes to the last page"""
|
||||||
|
await self.show_page(self.maximum_pages)
|
||||||
|
|
||||||
|
async def next_page(self):
|
||||||
|
"""goes to the next page"""
|
||||||
|
await self.checked_show_page(self.current_page + 1)
|
||||||
|
|
||||||
|
async def previous_page(self):
|
||||||
|
"""goes to the previous page"""
|
||||||
|
await self.checked_show_page(self.current_page - 1)
|
||||||
|
|
||||||
|
async def show_current_page(self):
|
||||||
|
if self.paginating:
|
||||||
|
await self.show_page(self.current_page)
|
||||||
|
|
||||||
|
async def numbered_page(self):
|
||||||
|
"""lets you type a page number to go to"""
|
||||||
|
to_delete = []
|
||||||
|
to_delete.append(
|
||||||
|
await self.channel.send('What page do you want to go to?'))
|
||||||
|
|
||||||
|
def message_check(m):
|
||||||
|
return m.author == self.author and \
|
||||||
|
self.channel == m.channel and \
|
||||||
|
m.content.isdigit()
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = await self.bot.wait_for(
|
||||||
|
'message',
|
||||||
|
check=message_check,
|
||||||
|
timeout=30.0
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
to_delete.append(await self.channel.send('Took too long.'))
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
else:
|
||||||
|
page = int(msg.content)
|
||||||
|
to_delete.append(msg)
|
||||||
|
if page != 0 and page <= self.maximum_pages:
|
||||||
|
await self.show_page(page)
|
||||||
|
else:
|
||||||
|
to_delete.append(await self.channel.send(
|
||||||
|
f'Invalid page given. ({page}/{self.maximum_pages})'))
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.channel.delete_messages(to_delete)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def show_help(self):
|
||||||
|
"""shows this message"""
|
||||||
|
messages = ['Welcome to the interactive paginator!\n']
|
||||||
|
messages.append(
|
||||||
|
'This interactively allows you to see pages of text by navigating with ' \
|
||||||
|
'reactions. They are as follows:\n')
|
||||||
|
|
||||||
|
for (emoji, func) in self.reaction_emojis:
|
||||||
|
messages.append(f'{emoji} {func.__doc__}')
|
||||||
|
|
||||||
|
embed = self.embed.copy()
|
||||||
|
embed.clear_fields()
|
||||||
|
embed.description = '\n'.join(messages)
|
||||||
|
embed.set_footer(
|
||||||
|
text=f'We were on page {self.current_page} before this message.')
|
||||||
|
await self.message.edit(content=None, embed=embed)
|
||||||
|
|
||||||
|
async def go_back_to_current_page():
|
||||||
|
await asyncio.sleep(60.0)
|
||||||
|
await self.show_current_page()
|
||||||
|
|
||||||
|
self.bot.loop.create_task(go_back_to_current_page())
|
||||||
|
|
||||||
|
async def stop_pages(self):
|
||||||
|
"""stops the interactive pagination session"""
|
||||||
|
await self.message.delete()
|
||||||
|
self.paginating = False
|
||||||
|
|
||||||
|
def react_check(self, reaction, user):
|
||||||
|
if user is None or user.id != self.author.id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if reaction.message.id != self.message.id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for (emoji, func) in self.reaction_emojis:
|
||||||
|
if reaction.emoji == emoji:
|
||||||
|
self.match = func
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def paginate(self):
|
||||||
|
"""Actually paginate the entries and run the interactive loop if necessary."""
|
||||||
|
first_page = self.show_page(1, first=True)
|
||||||
|
if not self.paginating:
|
||||||
|
await first_page
|
||||||
|
else:
|
||||||
|
# allow us to react to reactions right away if we're paginating
|
||||||
|
self.bot.loop.create_task(first_page)
|
||||||
|
|
||||||
|
while self.paginating:
|
||||||
|
try:
|
||||||
|
reaction, user = await self.bot.wait_for(
|
||||||
|
'reaction_add',
|
||||||
|
check=self.react_check,
|
||||||
|
timeout=self.delete_after
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
self.paginating = False
|
||||||
|
try:
|
||||||
|
await self.message.delete()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.message.remove_reaction(reaction, user)
|
||||||
|
except:
|
||||||
|
pass # can't remove it so don't bother doing so
|
||||||
|
|
||||||
|
await self.match()
|
||||||
|
|
||||||
|
|
||||||
|
class FieldPages(Pages):
|
||||||
|
"""Similar to Pages except entries should be a list of
|
||||||
|
tuples having (key, value) to show as embed fields instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True,
|
||||||
|
title, thumbnail, footericon, footertext,
|
||||||
|
embed_color=discord.Color.blurple()):
|
||||||
|
super().__init__(ctx, entries=entries, per_page=per_page,
|
||||||
|
show_entry_count=show_entry_count, title=title,
|
||||||
|
thumbnail=thumbnail, footericon=footericon,
|
||||||
|
footertext=footertext, embed_color=embed_color)
|
||||||
|
|
||||||
|
def prepare_embed(self, entries, page, *, first=False):
|
||||||
|
self.embed.clear_fields()
|
||||||
|
|
||||||
|
for key, value in entries:
|
||||||
|
self.embed.add_field(name=key, value=value, inline=False)
|
||||||
|
|
||||||
|
self.embed.title = self.title
|
||||||
|
|
||||||
|
if self.maximum_pages > 1:
|
||||||
|
if self.show_entry_count:
|
||||||
|
text = f' [{page}/{self.maximum_pages}]'
|
||||||
|
else:
|
||||||
|
text = f' [{page}/{self.maximum_pages}]'
|
||||||
|
self.embed.title = self.title + text
|
||||||
|
|
||||||
|
self.embed.set_footer(icon_url=self.footericon, text=self.footertext)
|
||||||
|
self.embed.set_thumbnail(url=self.thumbnail)
|
||||||
|
|
||||||
|
|
||||||
|
class TextPages(Pages):
|
||||||
|
"""Uses a commands.Paginator internally to paginate some text."""
|
||||||
|
|
||||||
|
def __init__(self, ctx, text, *, prefix='```', suffix='```',
|
||||||
|
max_size=2000):
|
||||||
|
paginator = CommandPaginator(prefix=prefix, suffix=suffix,
|
||||||
|
max_size=max_size - 200)
|
||||||
|
for line in text.split('\n'):
|
||||||
|
paginator.add_line(line)
|
||||||
|
|
||||||
|
super().__init__(ctx, entries=paginator.pages, per_page=1,
|
||||||
|
show_entry_count=False)
|
||||||
|
|
||||||
|
def get_page(self, page):
|
||||||
|
return self.entries[page - 1]
|
||||||
|
|
||||||
|
def get_embed(self, entries, page, *, first=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_content(self, entry, page, *, first=False):
|
||||||
|
if self.maximum_pages > 1:
|
||||||
|
return f'{entry}\nPage {page}/{self.maximum_pages}'
|
||||||
|
return entry
|
12
utils/functions/version.py
Normal file
12
utils/functions/version.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class Version:
|
||||||
|
def __init__(self, major: int, minor: int, patch: int, **kwargs):
|
||||||
|
self.major: int = major
|
||||||
|
self.minor: int = minor
|
||||||
|
self.patch: int = patch
|
||||||
|
|
||||||
|
self.pre_release = kwargs.get('pre_release', '')
|
||||||
|
self.build = kwargs.get('build', '')
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
build = self.build[:10]
|
||||||
|
return f'v{self.major}.{self.minor}.{self.patch}{self.pre_release}+{build}'
|
BIN
utils/images/blank_credit_card.png
Normal file
BIN
utils/images/blank_credit_card.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
BIN
utils/images/gnous.png
Normal file
BIN
utils/images/gnous.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
61
utils/locales/en/LC_MESSAGES/admin.po
Normal file
61
utils/locales/en/LC_MESSAGES/admin.po
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "Please enter a reason"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to ban this user"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to kick this user"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "last warns"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "More than 2 warns"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "has more than 2 warns"
|
||||||
|
msgstr "has more than 2 warns, what do you want to do ?"
|
||||||
|
|
||||||
|
msgid "ignore"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Took too long. Aborting."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "got a warn"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Reason"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "WarnModel with id"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "successfully removed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "successfully edited"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to find this language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Language changed successfully"
|
||||||
|
msgstr ""
|
252
utils/locales/en/LC_MESSAGES/admin_help.po
Normal file
252
utils/locales/en/LC_MESSAGES/admin_help.po
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## SAY ########################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_say'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_say_short'
|
||||||
|
msgstr '_say_short'
|
||||||
|
|
||||||
|
msgid '_say_usage'
|
||||||
|
msgstr '_say_usage'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_say_edit'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_say_edit__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_say_edit__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_say_to'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_say_to__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_say_to__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## BAN ########################################
|
||||||
|
###########################################################################
|
||||||
|
msgid '_ban'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_ban__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_ban__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## KICK #######################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_kick'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_kick__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_kick__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## CLEAR ######################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_clear'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_clear__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_clear__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## REACT ######################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_react'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_react__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_react__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_react_remove'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_react_remove__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_react_remove__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## DELETE #####################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_delete'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_delete__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_delete__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_delete_from'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_delete_from__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_delete_from__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## WARN #######################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn_new'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn_new__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn_new__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn_remove'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn_remove__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn_remove__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn_show'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn_show__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn_show__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn_edit'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn_edit__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_warn_edit__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## LANGUAGE ###################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_language'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_language__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_language__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## PREFIX #####################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_prefix'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_prefix__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_prefix__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_prefix_add'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_prefix_add__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_prefix_add__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_prefix_remove'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_prefix_remove__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_prefix_remove__usage'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_prefix_list'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_prefix_list__help'
|
||||||
|
msgstr ''
|
||||||
|
|
||||||
|
msgid '_prefix_list__usage'
|
||||||
|
msgstr ''
|
62
utils/locales/en/LC_MESSAGES/base.po
Normal file
62
utils/locales/en/LC_MESSAGES/base.po
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "Starting..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Could not set up PostgreSQL..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Launch without loading the <TEXT> module"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Search for update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Checking for update..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "A new version is available !"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update ? [Y/n] "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Downloading..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Tuxbot is up to date"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Failed to load extension : "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Extension loaded successfully : "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "This command cannot be used in private messages."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Sorry. This command is disabled and cannot be used."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "In "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Ready:"
|
||||||
|
msgstr ""
|
||||||
|
|
43
utils/locales/en/LC_MESSAGES/help.po
Normal file
43
utils/locales/en/LC_MESSAGES/help.po
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid 'main_page.description'
|
||||||
|
msgstr "When using commands, **<>** means a **required argument** and **[]** means an **optional** argument.\n***(Don't type these symbols!)***"
|
||||||
|
|
||||||
|
msgid 'main_page.commands'
|
||||||
|
msgstr 'Commands'
|
||||||
|
|
||||||
|
msgid 'main_page.footer'
|
||||||
|
msgstr '- Send {}help <Command> to see more help about a command.'
|
||||||
|
|
||||||
|
msgid 'main_page.not_found'
|
||||||
|
msgstr 'No command called "{}" found.'
|
||||||
|
|
||||||
|
msgid 'command_help.subcommands'
|
||||||
|
msgstr 'Subcommands'
|
||||||
|
|
||||||
|
msgid 'command_help.aliases'
|
||||||
|
msgstr 'Aliases'
|
||||||
|
|
||||||
|
msgid 'command_help.no_aliases'
|
||||||
|
msgstr 'No aliases'
|
||||||
|
|
||||||
|
msgid 'command_help.params'
|
||||||
|
msgstr 'Parameters'
|
||||||
|
|
||||||
|
msgid 'command_help.usage'
|
||||||
|
msgstr 'Usage'
|
17
utils/locales/en/LC_MESSAGES/logs.po
Normal file
17
utils/locales/en/LC_MESSAGES/logs.po
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
20
utils/locales/en/LC_MESSAGES/poll.po
Normal file
20
utils/locales/en/LC_MESSAGES/poll.po
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
#: launcher.py:51
|
||||||
|
msgid "**Preparation...**"
|
||||||
|
msgstr ""
|
17
utils/locales/en/LC_MESSAGES/poll_help.po
Normal file
17
utils/locales/en/LC_MESSAGES/poll_help.po
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
83
utils/locales/en/LC_MESSAGES/useful.po
Normal file
83
utils/locales/en/LC_MESSAGES/useful.po
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "Information about TuxBot"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Latest changes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Development"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "physical memory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "virtual memory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Servers count"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Channels count"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Members count"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Links"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Files"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Lines"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Invite"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Contributors"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ipv6 not available"
|
||||||
|
msgstr "Error, this address is not available in IPv6."
|
||||||
|
|
||||||
|
msgid "Information for"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Belongs to :"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Is located at :"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "info not available"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Headers of"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cannot connect to host"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "git repo"
|
||||||
|
msgstr "TuxBot-Bot's repository"
|
||||||
|
|
||||||
|
msgid "git text"
|
||||||
|
msgstr "Whoa, do you want to see my Gitea repository to dissect me? No problem ! I am a Bot, I do not feel the pain! \n https://git.gnous.eu/gnouseu/tuxbot-bot"
|
||||||
|
|
17
utils/locales/en/LC_MESSAGES/useful_help.po
Normal file
17
utils/locales/en/LC_MESSAGES/useful_help.po
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
0
utils/locales/en/LC_MESSAGES/user.po
Normal file
0
utils/locales/en/LC_MESSAGES/user.po
Normal file
17
utils/locales/en/LC_MESSAGES/user_help.po
Normal file
17
utils/locales/en/LC_MESSAGES/user_help.po
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
22
utils/locales/en/LC_MESSAGES/utils.po
Normal file
22
utils/locales/en/LC_MESSAGES/utils.po
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "Unable to find the user..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to find the message"
|
||||||
|
msgstr ""
|
61
utils/locales/fr/LC_MESSAGES/admin.po
Normal file
61
utils/locales/fr/LC_MESSAGES/admin.po
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "Please enter a reason"
|
||||||
|
msgstr "Merci d'entrer une raison"
|
||||||
|
|
||||||
|
msgid "Unable to ban this user"
|
||||||
|
msgstr "Impossible de bannir cet utilisateur"
|
||||||
|
|
||||||
|
msgid "Unable to kick this user"
|
||||||
|
msgstr "Impossible d'expulser cet utilisateur"
|
||||||
|
|
||||||
|
msgid "last warns"
|
||||||
|
msgstr "derniers avertissements"
|
||||||
|
|
||||||
|
msgid "More than 2 warns"
|
||||||
|
msgstr "Plus de 2 avertissements"
|
||||||
|
|
||||||
|
msgid "has more than 2 warns"
|
||||||
|
msgstr "a plus de 2 avertissements, que voulez-vous faire?"
|
||||||
|
|
||||||
|
msgid "ignore"
|
||||||
|
msgstr "ignorer"
|
||||||
|
|
||||||
|
msgid "Took too long. Aborting."
|
||||||
|
msgstr "Temps expiré. Abandons."
|
||||||
|
|
||||||
|
msgid "got a warn"
|
||||||
|
msgstr "a recu un avertissement"
|
||||||
|
|
||||||
|
msgid "Reason"
|
||||||
|
msgstr "Raison"
|
||||||
|
|
||||||
|
msgid "WarnModel with id"
|
||||||
|
msgstr "L'avertissement avec l'id"
|
||||||
|
|
||||||
|
msgid "successfully removed"
|
||||||
|
msgstr "a été enlevé avec succes"
|
||||||
|
|
||||||
|
msgid "successfully edited"
|
||||||
|
msgstr "a été édité avec succes"
|
||||||
|
|
||||||
|
msgid "Unable to find this language"
|
||||||
|
msgstr "Impossible de trouver cette langue"
|
||||||
|
|
||||||
|
msgid "Language changed successfully"
|
||||||
|
msgstr "Langue changée avec succès"
|
263
utils/locales/fr/LC_MESSAGES/admin_help.po
Normal file
263
utils/locales/fr/LC_MESSAGES/admin_help.po
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## SAY ########################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_say'
|
||||||
|
msgstr 'Permet de faire en sorte que le TuxBot envoie votre message'
|
||||||
|
|
||||||
|
msgid '_say__short'
|
||||||
|
msgstr 'Faire parler TuxBot'
|
||||||
|
|
||||||
|
msgid '_say_usage'
|
||||||
|
msgstr '[sous-commande] <message>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_say_edit'
|
||||||
|
msgstr "Permet de modifier le contenu d'un message envoyé par TuxBot"
|
||||||
|
|
||||||
|
msgid '_say_edit__short'
|
||||||
|
msgstr 'Editer un message envoyé'
|
||||||
|
|
||||||
|
msgid '_say_edit__usage'
|
||||||
|
msgstr '<ID/Lien du message> <message>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_say_to'
|
||||||
|
msgstr "Permet de faire en sorte que le TuxBot envoi votre message dans un autre salon ou en MP à quelqu'un"
|
||||||
|
|
||||||
|
msgid '_say_to__short'
|
||||||
|
msgstr 'Faire parler TuxBot dans un autre salon'
|
||||||
|
|
||||||
|
msgid '_say_to__usage'
|
||||||
|
msgstr "<ID/Mention du salon ou ID/Mention d'un membre> <message>"
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## BAN ########################################
|
||||||
|
###########################################################################
|
||||||
|
msgid '_ban'
|
||||||
|
msgstr 'Permet de bannir un membre'
|
||||||
|
|
||||||
|
msgid '_ban__short'
|
||||||
|
msgstr 'Bannir un membre'
|
||||||
|
|
||||||
|
msgid '_ban__usage'
|
||||||
|
msgstr '<ID/Mention du membre>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## KICK #######################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_kick'
|
||||||
|
msgstr "Permet d'expulser un membre"
|
||||||
|
|
||||||
|
msgid '_kick__short'
|
||||||
|
msgstr 'Expulser un membre'
|
||||||
|
|
||||||
|
msgid '_kick__usage'
|
||||||
|
msgstr '<ID/Mention du membre>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## CLEAR ######################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_clear'
|
||||||
|
msgstr 'Permet de supprimer un nombre donné de message'
|
||||||
|
|
||||||
|
msgid '_clear__short'
|
||||||
|
msgstr 'Supprime X messages'
|
||||||
|
|
||||||
|
msgid '_clear__usage'
|
||||||
|
msgstr '<quantité>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## REACT ######################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_react'
|
||||||
|
msgstr "Affiche l'aide relative a la commande `react`"
|
||||||
|
|
||||||
|
msgid '_react__short'
|
||||||
|
msgstr "Afficher l'aide pour `react`"
|
||||||
|
|
||||||
|
msgid '_react__usage'
|
||||||
|
msgstr '[sous-commande]'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_react_add'
|
||||||
|
msgstr "Permet d'ajouter une réaction à un message de la part de TuxBot"
|
||||||
|
|
||||||
|
msgid '_react_add__short'
|
||||||
|
msgstr 'Ajouter une réaction'
|
||||||
|
|
||||||
|
msgid '_react_add__usage'
|
||||||
|
msgstr '<ID/Lien du message> <émoji>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_react_remove'
|
||||||
|
msgstr "Permet d'enlever toutes les réactions d'un message"
|
||||||
|
|
||||||
|
msgid '_react_remove__short'
|
||||||
|
msgstr "Enlever toutes les réactions d'un message"
|
||||||
|
|
||||||
|
msgid '_react_remove__usage'
|
||||||
|
msgstr '<ID/Lien du message>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## DELETE #####################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_delete'
|
||||||
|
msgstr 'Permet de supprimer un message'
|
||||||
|
|
||||||
|
msgid '_delete__short'
|
||||||
|
msgstr 'Supprimer un message'
|
||||||
|
|
||||||
|
msgid '_delete__usage'
|
||||||
|
msgstr '[sous-commande] <ID/Lien du message>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_delete_from'
|
||||||
|
msgstr 'Permet de supprimer un message dans un autre salon'
|
||||||
|
|
||||||
|
msgid '_delete_from__short'
|
||||||
|
msgstr 'Supprimer un message dans un autre salon'
|
||||||
|
|
||||||
|
msgid '_delete_from__usage'
|
||||||
|
msgstr '<ID/Mention du salon> <ID/Lien du message>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## WARN #######################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn'
|
||||||
|
msgstr "Permet d'afficher les derniers avertissements donnés sur ce serveur"
|
||||||
|
|
||||||
|
msgid '_warn__short'
|
||||||
|
msgstr 'Lister les derniers avertissements'
|
||||||
|
|
||||||
|
msgid '_warn__usage'
|
||||||
|
msgstr '[sous-commande]'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn_add'
|
||||||
|
msgstr "Permet d'ajouter un avertissement à un membre en donnant une raison"
|
||||||
|
|
||||||
|
msgid '_warn_add__short'
|
||||||
|
msgstr 'Ajouter un avertissement a un membre'
|
||||||
|
|
||||||
|
msgid '_warn_add__usage'
|
||||||
|
msgstr '<ID/Pseudo/Mention du membre> <raison>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn_remove'
|
||||||
|
msgstr "Permet de supprimer un avertissement qui a été donné à un membre"
|
||||||
|
|
||||||
|
msgid '_warn_remove__short'
|
||||||
|
msgstr "Retirer l'avertissement d'un membre"
|
||||||
|
|
||||||
|
msgid '_warn_remove__usage'
|
||||||
|
msgstr "<ID de l'avertissement>"
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn_show'
|
||||||
|
msgstr "Permet d'afficher les derniers avertissements donnés à un membre"
|
||||||
|
|
||||||
|
msgid '_warn_show__short'
|
||||||
|
msgstr "Lister les derniers avertissements d'un membre"
|
||||||
|
|
||||||
|
msgid '_warn_show__usage'
|
||||||
|
msgstr '<ID/Pseudo/Mention du membre>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_warn_edit'
|
||||||
|
msgstr "Permet de modifier la raison de l'avertissement donné à un membre"
|
||||||
|
|
||||||
|
msgid '_warn_edit__short'
|
||||||
|
msgstr "Modifier la raison d'un avertissement"
|
||||||
|
|
||||||
|
msgid '_warn_edit__usage'
|
||||||
|
msgstr "<ID de l'avertissement> <raison>"
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## LANGUAGE ###################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_language'
|
||||||
|
msgstr 'Permet de définir la langue utilisée sur ce serveur'
|
||||||
|
|
||||||
|
msgid '_language__short'
|
||||||
|
msgstr 'Définir la langue de Tuxbot'
|
||||||
|
|
||||||
|
msgid '_language__usage'
|
||||||
|
msgstr '<langue>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## PREFIX #####################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_prefix'
|
||||||
|
msgstr "Affiche l'aide relative a la commande `prefix`"
|
||||||
|
|
||||||
|
msgid '_prefix__short'
|
||||||
|
msgstr "Afficher l'aide pour `prefix`"
|
||||||
|
|
||||||
|
msgid '_prefix__usage'
|
||||||
|
msgstr '[sous-commande]'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_prefix_add'
|
||||||
|
msgstr "Permet d'ajouter un nouveau préfixe pour ce serveur"
|
||||||
|
|
||||||
|
msgid '_prefix_add__short'
|
||||||
|
msgstr 'Ajouter un préfixe pour ce serveur'
|
||||||
|
|
||||||
|
msgid '_prefix_add__usage'
|
||||||
|
msgstr '<prefixe>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_prefix_remove'
|
||||||
|
msgstr "Permet retirer un préfixe pour ce serveur"
|
||||||
|
|
||||||
|
msgid '_prefix_remove__short'
|
||||||
|
msgstr 'Retirer un préfixe pour ce serveur'
|
||||||
|
|
||||||
|
msgid '_prefix_remove__usage'
|
||||||
|
msgstr '<prefixe>'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_prefix_list'
|
||||||
|
msgstr "Permet d'afficher tous les prefixes définis pour ce serveur"
|
||||||
|
|
||||||
|
msgid '_prefix_list__short'
|
||||||
|
msgstr 'Lister les prefixes pour ce serveur'
|
||||||
|
|
||||||
|
msgid '_prefix_list__usage'
|
||||||
|
msgstr '⠀'
|
62
utils/locales/fr/LC_MESSAGES/base.po
Normal file
62
utils/locales/fr/LC_MESSAGES/base.po
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "Starting..."
|
||||||
|
msgstr "Démarrage..."
|
||||||
|
|
||||||
|
msgid "Could not set up PostgreSQL..."
|
||||||
|
msgstr "Impossible de lancer PostgreSQL..."
|
||||||
|
|
||||||
|
msgid "Launch without loading the <TEXT> module"
|
||||||
|
msgstr "Lancer sans charger le module <TEXT>"
|
||||||
|
|
||||||
|
msgid "Search for update"
|
||||||
|
msgstr "Rechercher les mises à jour"
|
||||||
|
|
||||||
|
msgid "Checking for update..."
|
||||||
|
msgstr "Recherche de mise à jour..."
|
||||||
|
|
||||||
|
msgid "A new version is available !"
|
||||||
|
msgstr "Une nouvelle version est disponible !"
|
||||||
|
|
||||||
|
msgid "Update ? [Y/n] "
|
||||||
|
msgstr "Mettre à jour ? [O/n]"
|
||||||
|
|
||||||
|
msgid "Downloading..."
|
||||||
|
msgstr "Téléchargement..."
|
||||||
|
|
||||||
|
msgid "Tuxbot is up to date"
|
||||||
|
msgstr "Tuxbot est à jour"
|
||||||
|
|
||||||
|
msgid "Failed to load extension : "
|
||||||
|
msgstr "Impossible de charger l'extension : "
|
||||||
|
|
||||||
|
msgid "Extension loaded successfully : "
|
||||||
|
msgstr "Extension chargée avec succes : "
|
||||||
|
|
||||||
|
msgid "This command cannot be used in private messages."
|
||||||
|
msgstr "Cette commande ne peut pas être utilisée en message privé."
|
||||||
|
|
||||||
|
msgid "Sorry. This command is disabled and cannot be used."
|
||||||
|
msgstr "Désolé mais cette commande est désactivée."
|
||||||
|
|
||||||
|
msgid "In "
|
||||||
|
msgstr "Dans "
|
||||||
|
|
||||||
|
msgid "Ready:"
|
||||||
|
msgstr "Prêt :"
|
||||||
|
|
43
utils/locales/fr/LC_MESSAGES/help.po
Normal file
43
utils/locales/fr/LC_MESSAGES/help.po
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid 'main_page.description'
|
||||||
|
msgstr "Lorsque vous utilisez les commandes, **<>** correspond à un argument **obligatoire** et **[]** à un argument **optionnel**.\n***(N'écrivez pas ces symboles!)***"
|
||||||
|
|
||||||
|
msgid 'main_page.commands'
|
||||||
|
msgstr 'Commandes'
|
||||||
|
|
||||||
|
msgid 'main_page.footer'
|
||||||
|
msgstr "- Envoyez {}help <Commande> pour avoir plus d'aide sur la commande."
|
||||||
|
|
||||||
|
msgid 'main_page.not_found'
|
||||||
|
msgstr 'Impossible de trouver la commande {}.'
|
||||||
|
|
||||||
|
msgid 'command_help.subcommands'
|
||||||
|
msgstr 'Sous-commandes'
|
||||||
|
|
||||||
|
msgid 'command_help.aliases'
|
||||||
|
msgstr 'Alias'
|
||||||
|
|
||||||
|
msgid 'command_help.no_aliases'
|
||||||
|
msgstr 'Aucun alias'
|
||||||
|
|
||||||
|
msgid 'command_help.params'
|
||||||
|
msgstr 'Parametres'
|
||||||
|
|
||||||
|
msgid 'command_help.usage'
|
||||||
|
msgstr 'Utilisation'
|
17
utils/locales/fr/LC_MESSAGES/logs.po
Normal file
17
utils/locales/fr/LC_MESSAGES/logs.po
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
19
utils/locales/fr/LC_MESSAGES/logs_help.po
Normal file
19
utils/locales/fr/LC_MESSAGES/logs_help.po
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "_uptime__short"
|
||||||
|
msgstr "Retourne depuis quand TuxBot est en ligne."
|
20
utils/locales/fr/LC_MESSAGES/poll.po
Normal file
20
utils/locales/fr/LC_MESSAGES/poll.po
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
#: launcher.py:51
|
||||||
|
msgid "**Preparation...**"
|
||||||
|
msgstr "**Préparation...**"
|
40
utils/locales/fr/LC_MESSAGES/poll_help.po
Normal file
40
utils/locales/fr/LC_MESSAGES/poll_help.po
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
########################## POLLS #####################################
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_poll'
|
||||||
|
msgstr "Affiche l'aide relative a la commande `sondage`"
|
||||||
|
|
||||||
|
msgid '_poll__short'
|
||||||
|
msgstr "Afficher l'aide pour `sondage`"
|
||||||
|
|
||||||
|
msgid '_poll__usage'
|
||||||
|
msgstr '[sous-commande]'
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
msgid '_poll_create'
|
||||||
|
msgstr "Créez un sondage basé sur les réactions ! *(**BETA!** ajoutez `--anonyme` pour rendre les réponses anonymes)*"
|
||||||
|
|
||||||
|
msgid '_poll_create__short'
|
||||||
|
msgstr 'Créer un sondage'
|
||||||
|
|
||||||
|
msgid '_poll_create__usage'
|
||||||
|
msgstr '<question> | <réponse A> | <réponse B> | [réponse C] | [réponse D] | [...]'
|
82
utils/locales/fr/LC_MESSAGES/useful.po
Normal file
82
utils/locales/fr/LC_MESSAGES/useful.po
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "Information about TuxBot"
|
||||||
|
msgstr "Informations sur TuxBot"
|
||||||
|
|
||||||
|
msgid "Latest changes"
|
||||||
|
msgstr "Derniers changements"
|
||||||
|
|
||||||
|
msgid "Development"
|
||||||
|
msgstr "Développement"
|
||||||
|
|
||||||
|
msgid "physical memory"
|
||||||
|
msgstr "mémoire physique"
|
||||||
|
|
||||||
|
msgid "virtual memory"
|
||||||
|
msgstr "mémoire virtuelle"
|
||||||
|
|
||||||
|
msgid "Servers count"
|
||||||
|
msgstr "Nombre de serveurs"
|
||||||
|
|
||||||
|
msgid "Channels count"
|
||||||
|
msgstr "Nombre de salons"
|
||||||
|
|
||||||
|
msgid "Members count"
|
||||||
|
msgstr "Nombre de membres"
|
||||||
|
|
||||||
|
msgid "Links"
|
||||||
|
msgstr "Liens"
|
||||||
|
|
||||||
|
msgid "Files"
|
||||||
|
msgstr "Fichiers"
|
||||||
|
|
||||||
|
msgid "Lines"
|
||||||
|
msgstr "Lignes"
|
||||||
|
|
||||||
|
msgid "Invite"
|
||||||
|
msgstr "Invitation"
|
||||||
|
|
||||||
|
msgid "Contributors"
|
||||||
|
msgstr "Contributeurs"
|
||||||
|
|
||||||
|
msgid "ipv6 not available"
|
||||||
|
msgstr "Erreur, cette adresse n'est pas disponible en IPv6."
|
||||||
|
|
||||||
|
msgid "Information for"
|
||||||
|
msgstr "Informations pour"
|
||||||
|
|
||||||
|
msgid "Belongs to :"
|
||||||
|
msgstr "Appartient à :"
|
||||||
|
|
||||||
|
msgid "Is located at :"
|
||||||
|
msgstr "Se situe à :"
|
||||||
|
|
||||||
|
msgid "info not available"
|
||||||
|
msgstr "Erreur, impossible d'obtenir des informations sur cette adresse IP"
|
||||||
|
|
||||||
|
msgid "Headers of"
|
||||||
|
msgstr "Entêtes de"
|
||||||
|
|
||||||
|
msgid "Cannot connect to host"
|
||||||
|
msgstr "Impossible de se connecter à l'hôte"
|
||||||
|
|
||||||
|
msgid "git repo"
|
||||||
|
msgstr "Repos TuxBot-Bot"
|
||||||
|
|
||||||
|
msgid "git text"
|
||||||
|
msgstr "Whoa tu veux voir mon repos Gitea pour me disséquer ? Pas de soucis ! Je suis un Bot, je ne ressens pas la douleur! \n https://git.gnous.eu/gnouseu/tuxbot-bot"
|
17
utils/locales/fr/LC_MESSAGES/useful_help.po
Normal file
17
utils/locales/fr/LC_MESSAGES/useful_help.po
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
0
utils/locales/fr/LC_MESSAGES/user.po
Normal file
0
utils/locales/fr/LC_MESSAGES/user.po
Normal file
17
utils/locales/fr/LC_MESSAGES/user_help.po
Normal file
17
utils/locales/fr/LC_MESSAGES/user_help.po
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
22
utils/locales/fr/LC_MESSAGES/utils.po
Normal file
22
utils/locales/fr/LC_MESSAGES/utils.po
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2019-09-08 19:04+0200\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=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
msgid "Unable to find the user..."
|
||||||
|
msgstr "Impossibe de trouver l'utilisateur..."
|
||||||
|
|
||||||
|
msgid "Unable to find the message"
|
||||||
|
msgstr "Impossible de trouver le message"
|
18
utils/models/__init__.py
Normal file
18
utils/models/__init__.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
from utils.functions import Config
|
||||||
|
|
||||||
|
conf_postgresql = Config('./configs/config.cfg')["postgresql"]
|
||||||
|
postgresql = 'postgresql://{}:{}@{}/{}'.format(
|
||||||
|
conf_postgresql.get("Username"), conf_postgresql.get("Password"),
|
||||||
|
conf_postgresql.get("Host"), conf_postgresql.get("DBName"))
|
||||||
|
|
||||||
|
database = databases.Database(postgresql)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
engine = sqlalchemy.create_engine(str(database.url))
|
||||||
|
metadata.create_all(engine)
|
||||||
|
|
||||||
|
from .warn import WarnModel
|
||||||
|
from .poll import PollModel, ResponsesModel
|
||||||
|
from .alias import AliasesModel
|
14
utils/models/alias.py
Normal file
14
utils/models/alias.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import orm
|
||||||
|
from . import database, metadata
|
||||||
|
|
||||||
|
|
||||||
|
class AliasesModel(orm.Model):
|
||||||
|
__tablename__ = 'aliases'
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
user_id = orm.String(max_length=18)
|
||||||
|
alias = orm.String(max_length=255)
|
||||||
|
command = orm.String(max_length=255)
|
||||||
|
guild = orm.String(max_length=255)
|
29
utils/models/poll.py
Normal file
29
utils/models/poll.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import orm
|
||||||
|
from . import database, metadata
|
||||||
|
|
||||||
|
|
||||||
|
class ResponsesModel(orm.Model):
|
||||||
|
__tablename__ = 'responses'
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
user = orm.String(max_length=18)
|
||||||
|
|
||||||
|
choice = orm.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class PollModel(orm.Model):
|
||||||
|
__tablename__ = 'polls'
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
channel_id = orm.String(max_length=18)
|
||||||
|
message_id = orm.String(max_length=18)
|
||||||
|
|
||||||
|
content = orm.JSON()
|
||||||
|
is_anonymous = orm.Boolean()
|
||||||
|
|
||||||
|
available_choices = orm.Integer()
|
||||||
|
choice = orm.ForeignKey(ResponsesModel)
|
14
utils/models/warn.py
Normal file
14
utils/models/warn.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import orm
|
||||||
|
from . import database, metadata
|
||||||
|
|
||||||
|
|
||||||
|
class WarnModel(orm.Model):
|
||||||
|
__tablename__ = 'warns'
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
server_id = orm.String(max_length=18)
|
||||||
|
user_id = orm.String(max_length=18)
|
||||||
|
reason = orm.String(max_length=255)
|
||||||
|
created_at = orm.DateTime()
|
Loading…
Reference in a new issue