Compare commits
160 commits
951784718b
...
834f071332
Author | SHA1 | Date | |
---|---|---|---|
Romain J | 834f071332 | ||
Romain J | cfd59def74 | ||
Romain J | b4194dcadf | ||
Romain J | 42d7cad0e5 | ||
Romain J | 5a65fe1a6c | ||
Romain J | 3587a8f8a4 | ||
Romain J | 71576f48e4 | ||
Romain J | d6e9cd6512 | ||
Romain J | 7d588b2dbc | ||
Romain J | e38823e5be | ||
Romain J | 71335de878 | ||
Romain J | bdd77d1841 | ||
Romain J | d7a2330fb6 | ||
Romain J | 969ff8c351 | ||
Romain J | 4751a1b518 | ||
Romain J | 008ae76aca | ||
Romain J | bef9060b78 | ||
Romain J | 533ca6e3e7 | ||
Romain J | 42e2d04a9e | ||
Romain J | 7d67b8d581 | ||
Romain J | 6a926d717c | ||
Romain J | bdc7afb1ef | ||
260ef9f41c | |||
Romain J | 10b7e4039c | ||
Romain J | 179c84b45a | ||
Romain J | cc5df29e71 | ||
Romain J | 421ecbf6cb | ||
Romain J | 554ec46413 | ||
Romain J | 888a7924be | ||
Romain J | 1be4af8405 | ||
Romain J | 3ca1a42cad | ||
Romain J | cebb1b0123 | ||
Romain J | e0788137ff | ||
Romain J | d68d54be44 | ||
Romain J | 331599eb38 | ||
Romain J | 9a0786af7c | ||
Romain J | c1e253689d | ||
Romain J | 1f88499d44 | ||
Romain J | 5482429cba | ||
Mael G. | 85506c8db4 | ||
Romain J | a42eb58be8 | ||
Romain J | 4efb707257 | ||
Romain J | 8e8a4b899e | ||
Romain J | 85da8a34ab | ||
Romain J | a73d408462 | ||
Romain J | 5e8868b660 | ||
Romain J | 9869312ee8 | ||
Romain J | cdb891d435 | ||
Romain J | bf6d961658 | ||
Romain J | dbf7f3ce8e | ||
Romain J | 14f995550a | ||
Romain J | fbafd03ea9 | ||
Romain J | 7c75b0efad | ||
Romain J | 815709d68b | ||
Romain J | b5b7f0c7ef | ||
Romain J | ec68280519 | ||
Romain J | 50562059f9 | ||
Romain J | 33fa6b7f1f | ||
Romain J | 335397554f | ||
Romain J | cbe250f137 | ||
Romain J | 79ca4f95d6 | ||
Romain J | 9020fe7201 | ||
Romain J | 9f8765e0a6 | ||
Romain J | 078dc075f2 | ||
Romain J | 28d1d71c5a | ||
Romain J | 2e76379c87 | ||
Romain J | 45d61fc71d | ||
Romain J | f9c31f4017 | ||
Romain J | 04645ec639 | ||
Romain J | 7eac932a4e | ||
Romain J | 833884bcb2 | ||
Romain J | cce7bb4093 | ||
Romain J | be1e6d24e4 | ||
Romain J | 96618fa502 | ||
Romain J | caa98c44f4 | ||
Romain J | f03a54ca29 | ||
Romain J | ad24e4ba86 | ||
Romain J | b7ed8ffae7 | ||
Romain J | d187177908 | ||
Romain J | de4c8c549a | ||
Romain J | a781e09bde | ||
Romain J | d9221002d2 | ||
Romain J | 1bd73601f5 | ||
Romain J | 248228408d | ||
Romain J | 7b1fd7b463 | ||
Romain J | 07e683e7cb | ||
Romain J | fdf220cdfa | ||
Romain J | 7e8f0bbf12 | ||
Romain J | c71c976111 | ||
Romain J | 6cd66ba407 | ||
Romain J | 92a0c257ca | ||
Romain J | ff878711bf | ||
Romain J | 6d078e829d | ||
Romain J | 6c18c3213e | ||
Romain J | d94775e0e6 | ||
Romain J | 64b092dff2 | ||
Romain J | c442fd55fe | ||
Romain J | 97980e96d1 | ||
Romain J | d9427d1863 | ||
Romain J | 348a78e4b3 | ||
Romain J | d5afdcc60b | ||
Romain J | e7d4a9ee43 | ||
Romain J | f42b2194cd | ||
Romain J | ba1122c07b | ||
Romain J | c6114709ee | ||
Romain J | fdc9c9196d | ||
Romain J | d00aadd82f | ||
Romain J | 25f5c5e1f6 | ||
Romain J | ad4fb2fa89 | ||
Romain J | 98b241d51b | ||
Romain J | 76e845e5be | ||
Romain J | d5f1f71a0a | ||
Romain J | 29808d41d6 | ||
Romain J | 8f17085cf7 | ||
Romain J | 46b1daeae4 | ||
Romain J | 6429dc2e01 | ||
Romain J | 3ae55bc92e | ||
Romain J | 60f68f245d | ||
Romain J | 2d175b4453 | ||
Romain J | 0849c1bdff | ||
Romain J | e38fd5417f | ||
Romain J | 4c48fdff6e | ||
Romain J | 9274895226 | ||
Romain J | 764068ef22 | ||
Romain J | 6605115941 | ||
Romain J | a3edb16528 | ||
Romain J | 68daf90938 | ||
Mael G. | 7c65c92084 | ||
Mael G. | 23ec0f7e67 | ||
Romain J | 289fedb4b7 | ||
Romain J | a9a8572aad | ||
Romain J | 582d254af5 | ||
Romain J | d0a9e658a6 | ||
Romain J | d6f155e682 | ||
Romain J | c0af425383 | ||
Romain J | 806ddf0540 | ||
Romain J | 04f2c9feae | ||
Romain J | 985f839d21 | ||
Romain J | f6de008952 | ||
Romain J | 7e5f6e6dbf | ||
Romain J | 537a905f85 | ||
Romain J | 02d279b6c4 | ||
Romain J | 712339968c | ||
Romain J | f6ba42a143 | ||
Romain J | 30e7906f3f | ||
Romain J | cb0b9a2681 | ||
Romain J | efc05f816e | ||
Romain J | b03dc30c6c | ||
Romain J | f4813702fd | ||
Romain J | 7bd7a5402e | ||
Romain J | 4102f3e723 | ||
Romain J | 63938a6bf3 | ||
Romain J | 04ce551acb | ||
Romain J | 108e2b8af3 | ||
Romain J | 0a25ced905 | ||
Romain J | 31c6df0f88 | ||
Romain J | 36e03ad961 | ||
Romain J | 59423ae8a2 | ||
Romain J | d1afd8c7ec | ||
Romain J | 2157ae2899 |
8
.deepsource.toml
Normal file
8
.deepsource.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
2
.github/issue_template.md
vendored
2
.github/issue_template.md
vendored
|
@ -25,4 +25,4 @@ If applicable, add screenshots to help explain your problem.
|
|||
- Python Version [e.g. 3.7.4]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
<-- Add any other context about the problem here. -->
|
48
.gitignore
vendored
Executable file → Normal file
48
.gitignore
vendored
Executable file → Normal file
|
@ -1,17 +1,39 @@
|
|||
#Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
config.py
|
||||
.DS_Store
|
||||
private.py
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
#jetbrains
|
||||
.idea/
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
#Ipinfo key
|
||||
ipinfoio.key
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
#Bgp Graph
|
||||
bgpgraph.png
|
||||
bgpgraph
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
|
||||
.idea/sonarlint/
|
||||
|
||||
*.log
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
venv
|
||||
dist
|
||||
build
|
||||
*.egg
|
||||
*.egg-info
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
52
.idea/dictionaries/romain.xml
Normal file
52
.idea/dictionaries/romain.xml
Normal file
|
@ -0,0 +1,52 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="romain">
|
||||
<words>
|
||||
<w>anglais</w>
|
||||
<w>anonyme</w>
|
||||
<w>appdirs</w>
|
||||
<w>apres</w>
|
||||
<w>asctime</w>
|
||||
<w>commandstats</w>
|
||||
<w>ctype</w>
|
||||
<w>debian</w>
|
||||
<w>découverte</w>
|
||||
<w>fonction</w>
|
||||
<w>francais</w>
|
||||
<w>français</w>
|
||||
<w>gnous</w>
|
||||
<w>ipinfo</w>
|
||||
<w>iplocalise</w>
|
||||
<w>jishaku</w>
|
||||
<w>langue</w>
|
||||
<w>levelname</w>
|
||||
<w>liste</w>
|
||||
<w>localiseip</w>
|
||||
<w>octobre</w>
|
||||
<w>pacman</w>
|
||||
<w>postgre</w>
|
||||
<w>postgresql</w>
|
||||
<w>pred</w>
|
||||
<w>pylint</w>
|
||||
<w>releaselevel</w>
|
||||
<w>rprint</w>
|
||||
<w>socketstats</w>
|
||||
<w>soit</w>
|
||||
<w>sondage</w>
|
||||
<w>sondages</w>
|
||||
<w>splt</w>
|
||||
<w>suivante</w>
|
||||
<w>systemd</w>
|
||||
<w>tablename</w>
|
||||
<w>tldr</w>
|
||||
<w>tutux</w>
|
||||
<w>tuxbot</w>
|
||||
<w>tuxbot's</w>
|
||||
<w>tuxvenv</w>
|
||||
<w>venv</w>
|
||||
<w>webhook</w>
|
||||
<w>webhooks</w>
|
||||
<w>youtrack</w>
|
||||
<w>écrite</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
9
.idea/discord.xml
Normal file
9
.idea/discord.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
</component>
|
||||
<component name="ProjectNotificationSettings">
|
||||
<option name="askShowProject" value="false" />
|
||||
</component>
|
||||
</project>
|
15
.idea/inspectionProfiles/Project_Default.xml
Normal file
15
.idea/inspectionProfiles/Project_Default.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredPackages">
|
||||
<value>
|
||||
<list size="2">
|
||||
<item index="0" class="java.lang.String" itemvalue="discord" />
|
||||
<item index="1" class="java.lang.String" itemvalue="tortoise" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
7
.idea/misc.xml
Normal file
7
.idea/misc.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (tuxbot-bot)" project-jdk-type="Python SDK" />
|
||||
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/tuxbot-bot.iml" filepath="$PROJECT_DIR$/.idea/tuxbot-bot.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
11
.idea/statistic.xml
Normal file
11
.idea/statistic.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Statistic">
|
||||
<option name="excludedDirectories">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/Tuxbot_bot.egg-info" />
|
||||
<option value="$PROJECT_DIR$/venv" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
16
.idea/tuxbot-bot.iml
Normal file
16
.idea/tuxbot-bot.iml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.9 (tuxbot-bot)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="NUMPY" />
|
||||
<option name="myDocStringFormat" value="NumPy" />
|
||||
</component>
|
||||
</module>
|
11
.idea/vcs.xml
Normal file
11
.idea/vcs.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CommitMessageInspectionProfile">
|
||||
<profile version="1.0">
|
||||
<inspection_tool class="GrazieCommit" enabled="true" level="TYPO" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
19
.pylintrc
Normal file
19
.pylintrc
Normal file
|
@ -0,0 +1,19 @@
|
|||
[BASIC]
|
||||
good-names=
|
||||
e, # (exception) as e
|
||||
f, # (file) as f
|
||||
k, # for k, v in
|
||||
v, # for k, v in
|
||||
dt, # datetime
|
||||
|
||||
[MASTER]
|
||||
disable=
|
||||
C0103, # invalid-name
|
||||
C0114, # missing-module-docstring
|
||||
C0115, # missing-class-docstring
|
||||
C0116, # missing-function-docstring
|
||||
W0703, # broad-except
|
||||
R0801, # duplicate-code
|
||||
R0902, # too-many-instance-attributes
|
||||
R0903, # too-few-public-methods
|
||||
E1136, # unsubscriptable-object (false positive with python 3.9)
|
35
Makefile
Normal file
35
Makefile
Normal file
|
@ -0,0 +1,35 @@
|
|||
PYTHON = python
|
||||
VENV = venv
|
||||
|
||||
XGETTEXT_FLAGS = --no-wrap --language='python' --keyword=_ --from-code='UTF-8' --msgid-bugs-address='rick@gnous.eu' --width=79 --package-name='Tuxbot-bot'
|
||||
|
||||
# Init
|
||||
main:
|
||||
$(PYTHON) -m venv --clear $(VENV)
|
||||
$(VENV)/bin/pip install -U pip setuptools
|
||||
install:
|
||||
$(VENV)/bin/pip install .
|
||||
install-dev:
|
||||
$(VENV)/bin/pip install -r dev.requirements.txt
|
||||
update:
|
||||
$(VENV)/bin/pip install -U .
|
||||
|
||||
# Blackify code
|
||||
reformat:
|
||||
$(PYTHON) -m black `git ls-files "*.py"` --line-length=79 && $(PYTHON) -m pylint tuxbot
|
||||
|
||||
# Translations
|
||||
xgettext:
|
||||
for cog in tuxbot/cogs/*/; do \
|
||||
xgettext `find $$cog -type f -name '*.py'` --output=$$cog/locales/messages.pot $(XGETTEXT_FLAGS); \
|
||||
done
|
||||
msginit:
|
||||
for cog in tuxbot/cogs/*/; do \
|
||||
msginit --input=$$cog/locales/messages.pot --output=$$cog/locales/fr-FR.po --locale=fr_FR.UTF-8 --no-translator; \
|
||||
msginit --input=$$cog/locales/messages.pot --output=$$cog/locales/en-US.po --locale=en_US.UTF-8 --no-translator; \
|
||||
done
|
||||
msgmerge:
|
||||
for cog in tuxbot/cogs/*/; do \
|
||||
msgmerge --update $$cog/locales/fr-FR.po $$cog/locales/messages.pot; \
|
||||
msgmerge --update $$cog/locales/en-US.po $$cog/locales/messages.pot; \
|
||||
done
|
32
README.md
32
README.md
|
@ -1,32 +0,0 @@
|
|||
Tuxbot is a french speaking Discord bot made for the GnousEU's discord server. He has been maintened since 2016.
|
||||
|
||||
This code is not intended to reuse "as is". If you want to configure your own instance, you will need to modify some files in the source code (see below).
|
||||
|
||||
### Requirements
|
||||
- A server with a modern GNU/Linux distribution with internet connectivity
|
||||
- Python 3.7 or later with PIP
|
||||
- Graphviz
|
||||
|
||||
### Installation
|
||||
Install Python dependencies using ``pip3 install -r requirements.txt`` (make sure the pip executable match the correct python version)
|
||||
|
||||
Rename ``config.py.example`` to ``config.py`` and edit it with the required information.
|
||||
You may want to edit the file ``cogs/filter_messages.py`` as well.
|
||||
|
||||
### Launch
|
||||
Start the program using ``python3 bot.py`` (make sure you use the right Python executable)
|
||||
|
||||
### Additional features
|
||||
#### Ipinfo.io API
|
||||
Tuxbot can use the ipinfo.io API for more precises results for the ``iplocalise`` command. If you want to use it you should create a ``ipinfoio.key`` in the top tuxbot folder.
|
||||
|
||||
### Versions
|
||||
Version list : [Click here to display](https://git.gnous.eu/gnouseu/tuxbot-bot/releases)
|
||||
|
||||
### Main contributors
|
||||
* **Maël** _alias_ [@outoutxyz](https://twitter.com/outoutxyz)
|
||||
* **Romain** _alias_ [Romain le malchanceux](https://github.com/Rom194)
|
||||
|
||||
### Licensing
|
||||
|
||||
This project is under the ``Creative Commons BY-NC-SA 4.0`` license - see [LICENSE.md](LICENSE.md) for more details
|
90
README.rst
Normal file
90
README.rst
Normal file
|
@ -0,0 +1,90 @@
|
|||
|image0| |image1| |image2|
|
||||
|
||||
.. role:: bash(code)
|
||||
:language: bash
|
||||
|
||||
Installing Tuxbot
|
||||
=================
|
||||
|
||||
It is preferable to install the bot on a dedicated user. If you don't
|
||||
know how to do it, please refer to `this guide <https://www.digitalocean.com/community/tutorials/how-to-create-a-sudo-user-on-ubuntu-quickstart>`__
|
||||
|
||||
Installing the pre-requirements
|
||||
-------------------------------
|
||||
|
||||
- The pre-requirements are:
|
||||
|
||||
- Python 3.7 or greater
|
||||
- Pip
|
||||
- Git
|
||||
|
||||
Operating systems
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Arch Linux
|
||||
^^^^^^^^^^
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo pacman -Syu python python-pip python-virtualenv git
|
||||
|
||||
Continue to `create the venv <#creating-the-virtual-environment>`__.
|
||||
|
||||
--------------
|
||||
|
||||
Debian
|
||||
^^^^^^
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo apt update
|
||||
$ sudo apt -y install python3 python3-dev python3-pip python3-venv git
|
||||
|
||||
Continue to `create the venv <#creating-the-virtual-environment>`__.
|
||||
|
||||
--------------
|
||||
|
||||
Windows
|
||||
^^^^^^^
|
||||
|
||||
*not for now and not for the future*
|
||||
|
||||
Creating the Virtual Environment
|
||||
--------------------------------
|
||||
|
||||
To set up the virtual environment and install the bot, simply run this
|
||||
two commands:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ make
|
||||
$ make install
|
||||
|
||||
Now, switch your environment to the virtual one by run this single
|
||||
command: :bash:`source ~/tuxvenv/bin/activate`
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
It's time to set up your first instance, to do this, you can simply
|
||||
execute this command:
|
||||
|
||||
:bash:`tuxbot-setup [your instance name]`
|
||||
|
||||
After following the instructions, you can run your instance by executing
|
||||
this command:
|
||||
|
||||
:bash:`tuxbot [your instance name]`
|
||||
|
||||
Update
|
||||
------
|
||||
|
||||
To update the whole bot after a :bash:`git pull`, just execute
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ make update
|
||||
|
||||
.. |image0| image:: https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10-%23007ec6
|
||||
.. |image1| image:: https://img.shields.io/badge/dynamic/json?color=%23dfb317&label=issues&query=%24.open_issues_count&suffix=%20open&url=https%3A%2F%2Fgit.gnous.eu%2Fapi%2Fv1%2Frepos%2FGnousEU%2Ftuxbot-bot%2F
|
||||
.. |image2| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
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
|
||||
|
||||
|
||||
"""
|
141
bot.py
141
bot.py
|
@ -1,141 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__author__ = "Maël / Outout | Romain"
|
||||
__licence__ = "WTFPL Licence 2.0"
|
||||
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
import cogs.utils.cli_colors as colors
|
||||
import config
|
||||
from cogs.utils import checks
|
||||
|
||||
if sys.version_info[1] < 7 or sys.version_info[0] < 3:
|
||||
print(f"{colors.text_colors.RED}[ERROR] Python 3.7 or + is required.{colors.ENDC}")
|
||||
exit()
|
||||
|
||||
l_extensions = (
|
||||
'cogs.admin',
|
||||
'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")
|
||||
|
||||
|
||||
class TuxBot(commands.Bot):
|
||||
def __init__(self):
|
||||
self.uptime = datetime.datetime.utcnow()
|
||||
self.config = config
|
||||
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
|
||||
self.session = aiohttp.ClientSession(loop=self.loop)
|
||||
self._events = []
|
||||
|
||||
self.add_command(self.do)
|
||||
|
||||
for extension in l_extensions:
|
||||
try:
|
||||
self.load_extension(extension)
|
||||
print(f"{colors.text_colors.GREEN}\"{extension}\""
|
||||
f" chargé !{colors.ENDC}")
|
||||
except Exception as e:
|
||||
print(f"{colors.text_colors.RED}"
|
||||
f"Impossible de charger l'extension {extension}\n"
|
||||
f"{type(e).__name__}: {e}{colors.ENDC}", file=sys.stderr)
|
||||
|
||||
async def is_owner(self, user: discord.User):
|
||||
return str(user.id) in config.authorized_id
|
||||
|
||||
async def on_command_error(self, ctx, error):
|
||||
if isinstance(error, commands.NoPrivateMessage):
|
||||
await ctx.author.send('Cette commande ne peut pas être utilisee '
|
||||
'en message privee.')
|
||||
elif isinstance(error, commands.DisabledCommand):
|
||||
await ctx.author.send('Desoler mais cette commande est desactive, '
|
||||
'elle ne peut donc pas être utilisée.')
|
||||
elif isinstance(error, commands.CommandInvokeError):
|
||||
print(f'In {ctx.command.qualified_name}:', file=sys.stderr)
|
||||
traceback.print_tb(error.original.__traceback__)
|
||||
print(f'{error.original.__class__.__name__}: {error.original}',
|
||||
file=sys.stderr)
|
||||
|
||||
async def on_ready(self):
|
||||
log_channel_id = await self.fetch_channel(self.config.log_channel_id)
|
||||
|
||||
print('\n\n---------------------')
|
||||
print('CONNECTÉ :')
|
||||
print(f'Nom d\'utilisateur: {self.user} {colors.text_style.DIM}'
|
||||
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,
|
||||
activity=discord.Game(
|
||||
name=self.config.game)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def on_resumed():
|
||||
print('resumed...')
|
||||
|
||||
async def on_message(self, message):
|
||||
if message.author.bot:
|
||||
return
|
||||
|
||||
try:
|
||||
await self.process_commands(message)
|
||||
except Exception as e:
|
||||
print(f'{colors.text_colors.RED}Erreur rencontré : \n'
|
||||
f' {type(e).__name__}: {e}{colors.ENDC} \n \n')
|
||||
|
||||
def run(self):
|
||||
super().run(self.config.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__':
|
||||
if os.path.exists('config.py') is not True:
|
||||
print(f"{colors.text_colors.RED}"
|
||||
f"Veuillez créer le fichier config.py{colors.ENDC}")
|
||||
exit()
|
||||
|
||||
tuxbot = TuxBot()
|
||||
tuxbot.run()
|
BIN
cogs/.DS_Store
vendored
BIN
cogs/.DS_Store
vendored
Binary file not shown.
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, delete_message_days=0)
|
||||
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))
|
65
cogs/afk.py
65
cogs/afk.py
|
@ -1,65 +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"]
|
||||
|
||||
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 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,68 +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):
|
||||
result = self.bot.latency * 1000
|
||||
|
||||
if float(result) >= 300:
|
||||
em = discord.Embed(title="Ping : " + str(result) + "ms",
|
||||
description="... c'est quoi ce ping !",
|
||||
colour=0xFF1111)
|
||||
await ctx.send(embed=em)
|
||||
elif float(result) > 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,64 +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):
|
||||
|
||||
#
|
||||
# These are hard-coded ID that you should change in production to adapt your requirements
|
||||
#
|
||||
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))
|
191
cogs/funs.py
191
cogs/funs.py
|
@ -1,191 +0,0 @@
|
|||
from discord.ext import commands
|
||||
from typing import Union
|
||||
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: Union[discord.Member, discord.User] = 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))
|
152
cogs/search.py
152
cogs/search.py
|
@ -1,152 +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/')
|
||||
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")
|
||||
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))
|
603
cogs/utility.py
603
cogs/utility.py
|
@ -1,603 +0,0 @@
|
|||
import datetime
|
||||
import json
|
||||
import pytz
|
||||
import random
|
||||
import urllib
|
||||
import aiohttp
|
||||
import ipinfo as ipinfoio
|
||||
|
||||
import pydig
|
||||
|
||||
import telnetlib
|
||||
from graphviz import Digraph
|
||||
|
||||
from ipwhois.net import Net
|
||||
from ipwhois.asn import IPASN
|
||||
import ipwhois
|
||||
|
||||
import discord
|
||||
import requests, re
|
||||
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
|
||||
"""Getting 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" or iptype=="-6"):
|
||||
try:
|
||||
ipaddress = socket.getaddrinfo(ipaddress, None, socket.AF_INET6)[1][4][0]
|
||||
except Exception as e:
|
||||
await ctx.send("Erreur, cette adresse n'est pas disponible en IPv6.")
|
||||
return
|
||||
elif(iptype=="ipv4" or iptype=="v4" or iptype=="-4"):
|
||||
try:
|
||||
ipaddress = socket.getaddrinfo(ipaddress, None, socket.AF_INET)[1][4][0]
|
||||
except Exception as e:
|
||||
await ctx.send("Erreur, cette adresse n'est pas disponible en IPv4.")
|
||||
return
|
||||
else:
|
||||
try:
|
||||
ipaddress = socket.getaddrinfo(ipaddress, None)[1][4][0]
|
||||
except Exception as e:
|
||||
await ctx.send("Erreur, cette adresse n'est pas disponible.")
|
||||
return
|
||||
|
||||
iploading = await ctx.send("_Récupération des informations..._")
|
||||
|
||||
try:
|
||||
net = Net(ipaddress)
|
||||
obj = IPASN(net)
|
||||
ipinfo = obj.lookup()
|
||||
except ipwhois.exceptions.IPDefinedError:
|
||||
await ctx.send("Cette IP est reservée à un usage local selon la RFC 1918. Impossible d'avoir des informations supplémentaires à son propos.")
|
||||
await iploading.delete()
|
||||
return
|
||||
|
||||
try:
|
||||
iphostname = socket.gethostbyaddr(ipaddress)[0]
|
||||
except:
|
||||
iphostname = "N/A"
|
||||
|
||||
# IPINFO api
|
||||
api_result = True
|
||||
try:
|
||||
with open('ipinfoio.key') as k:
|
||||
access_token = k.read().replace("\n", "")
|
||||
handler = ipinfoio.getHandler(access_token)
|
||||
details = handler.getDetails(ipaddress)
|
||||
except Exception as e:
|
||||
api_result = False
|
||||
|
||||
try:
|
||||
embed = discord.Embed(title=f"Informations pour ``{realipaddress} ({ipaddress})``", color=0x5858d7)
|
||||
|
||||
if(api_result):
|
||||
asn = details.org.split(" ")[0]
|
||||
embed.add_field(name="Appartient à :", value=f"[{details.org}](https://bgp.he.net/{asn})")
|
||||
else:
|
||||
embed.add_field(name="Appartient à :", value=f"{ipinfo['asn_description']} ([AS{ipinfo['asn']}](https://bgp.he.net/{ipinfo['asn']}))", inline = False)
|
||||
|
||||
embed.add_field(name="RIR :", value=f"{ipinfo['asn_registry']}", inline = True)
|
||||
|
||||
if(api_result):
|
||||
embed.add_field(name="Région :", value=f"{details.city} - {details.region} ({details.country})")
|
||||
else:
|
||||
embed.add_field(name="Région :", value=f"{ipinfo['asn_country_code']}")
|
||||
embed.add_field(name="Nom de l'hôte :", value=f"{iphostname}")
|
||||
|
||||
# Adding country flag
|
||||
if(api_result):
|
||||
embed.set_thumbnail(url=f"https://www.countryflags.io/{details.country}/shiny/64.png")
|
||||
else:
|
||||
embed.set_thumbnail(url=f"https://www.countryflags.io/{ipinfo['asn_country_code']}/shiny/64.png")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
except:
|
||||
await ctx.send(content=f"Erreur, impossible d'avoir des informations sur l'adresse IP ``{realipaddress}``")
|
||||
await iploading.delete()
|
||||
|
||||
"""---------------------------------------------------------------------"""
|
||||
@commands.command(name='dig', pass_context=True)
|
||||
async def _dig(self, ctx, domain, querytype="abc", dnssec="no"):
|
||||
if not querytype in ['A', 'AAAA', 'CNAME', 'NS', 'DS', 'DNSKEY', 'SOA', 'TXT', 'PTR', 'MX']:
|
||||
await ctx.send("Requêtes supportées : A, AAAA, CNAME, NS, DS, DNSKEY, SOA, TXT, PTR, MX")
|
||||
return
|
||||
|
||||
if(dnssec == "no"):
|
||||
resolver = pydig.Resolver(
|
||||
nameservers=[
|
||||
'80.67.169.40',
|
||||
'80.67.169.12',
|
||||
]
|
||||
)
|
||||
else:
|
||||
resolver = pydig.Resolver(
|
||||
nameservers=[
|
||||
'80.67.169.40',
|
||||
'80.67.169.12',
|
||||
],
|
||||
additional_args=[
|
||||
'+dnssec',
|
||||
]
|
||||
)
|
||||
|
||||
resquery = resolver.query(domain, querytype)
|
||||
embed = discord.Embed(title=f"Requête DIG sur {domain} pour une entrée {querytype}", color=0x5858d7)
|
||||
|
||||
champ_id = 1
|
||||
for champ in resquery:
|
||||
embed.add_field(name=f"Champ {champ_id} :", value=champ)
|
||||
champ_id = champ_id + 1
|
||||
|
||||
if champ_id == 1:
|
||||
embed.add_field(name="Ooops", value="Pas de résultat")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
"""---------------------------------------------------------------------"""
|
||||
@commands.command(name='getheaders')
|
||||
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"Headers : {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"Cannot connect to host {addr}"
|
||||
)
|
||||
|
||||
|
||||
"""---------------------------------------------------------------------"""
|
||||
@commands.command(name='peeringdb', pass_context=True)
|
||||
async def _peeringdb(self, ctx, *, asn):
|
||||
def notEmptyField(embed, name, value):
|
||||
if(value != ""):
|
||||
embed.add_field(name=name, value=value)
|
||||
|
||||
if asn.startswith("AS"):
|
||||
asn = asn[2:]
|
||||
loadingmsg = await ctx.send("_Récupération des informations..._")
|
||||
|
||||
"""Getting the ASN id in the peeringdb database"""
|
||||
try:
|
||||
asnid = urllib.request.urlopen("https://www.peeringdb.com/api/as_set/" + asn)
|
||||
asnid = json.loads(asnid.read().decode())
|
||||
pdbid = asnid["data"][0][asn]
|
||||
|
||||
asinfo = urllib.request.urlopen("https://www.peeringdb.com/api/net?irr_as_set=" + pdbid)
|
||||
|
||||
asinfo = json.loads(asinfo.read().decode())["data"]
|
||||
|
||||
for asndata in asinfo:
|
||||
if(asndata['asn'] == int(asn)):
|
||||
asinfo = asndata
|
||||
|
||||
asproto = ""
|
||||
if(asinfo["info_ipv6"]):
|
||||
asproto = asproto + "IPv6 "
|
||||
if(asinfo["info_unicast"]):
|
||||
asproto = asproto + "Unicast "
|
||||
if(asinfo["info_multicast"]):
|
||||
asproto = asproto + "Multicast "
|
||||
if(asinfo["info_never_via_route_servers"]):
|
||||
asproto = asproto + "Never via Route servers"
|
||||
|
||||
embed = discord.Embed(title=f"Informations pour {asinfo['name']} ``AS{asn}``", color=0x5858d7)
|
||||
notEmptyField(embed, name="Nom :", value=asinfo['name'])
|
||||
notEmptyField(embed, name="Aka :", value=asinfo['aka'])
|
||||
notEmptyField(embed, name="Site :", value=asinfo['website'])
|
||||
notEmptyField(embed, name="Looking Glass :", value=asinfo['looking_glass'])
|
||||
notEmptyField(embed, name="Traffic :", value=asinfo['info_traffic'])
|
||||
notEmptyField(embed, name="Ratio du traffic :", value=asinfo['info_ratio'])
|
||||
notEmptyField(embed, name="Prefixes IPv4 :", value=asinfo['info_prefixes4'])
|
||||
notEmptyField(embed, name="Prefixes IPv6 :", value=asinfo['info_prefixes6'])
|
||||
notEmptyField(embed, name="Politique de Peering :", value=f"[{asinfo['policy_general']}]({asinfo['policy_url']})")
|
||||
notEmptyField(embed, name="Protocoles supportés :", value=asproto)
|
||||
embed.set_footer(text=f"https://www.peeringdb.com/")
|
||||
await ctx.send(embed=embed)
|
||||
await loadingmsg.delete()
|
||||
except IndexError:
|
||||
await ctx.send(f"Impossible d'avoir des informations sur l'AS AS{asn}")
|
||||
await loadingmsg.delete()
|
||||
except urllib.error.HTTPError:
|
||||
await ctx.send(f"L'AS{asn} est introuvable dans la base de données de PeeringDB.")
|
||||
await loadingmsg.delete()
|
||||
|
||||
"""---------------------------------------------------------------------"""
|
||||
@commands.command(name='shroute', pass_context=True)
|
||||
async def _shroute(self, ctx, srv, ipaddress):
|
||||
"""Show as path graph to an IP via data from a Route Server using graphviz"""
|
||||
|
||||
if not srv in ["opentransit", 'he', 'att', "oregonuniv", "warian", 'csaholdigs', 'iamageeknz']:
|
||||
await ctx.send("Requêtes supportées : opentransit (Orange), he (Huricanne Electric), att (AT&T), oregonuniv, warian, csaholdigs, iamageeknz")
|
||||
return
|
||||
|
||||
#List of RS
|
||||
if srv == "opentransit":
|
||||
host = "route-server.opentransit.net"
|
||||
user = "rviews"
|
||||
password = "Rviews"
|
||||
lg_asn = "5511"
|
||||
cmd = "show bgp {}"
|
||||
elif srv == "oregonuniv":
|
||||
host = "route-views.routeviews.org"
|
||||
user = "rviews"
|
||||
password = "none"
|
||||
lg_asn = "3582"
|
||||
cmd = "show bgp {}"
|
||||
elif srv == "warian":
|
||||
host = "route-server.warian.net"
|
||||
user = "none"
|
||||
password = "rviews"
|
||||
lg_asn = "56911"
|
||||
cmd = "show bgp ipv4 unicast {}"
|
||||
elif srv == "csaholdigs": #Blacklist sometime
|
||||
host = "route-views.sg.routeviews.org"
|
||||
user = "none"
|
||||
password = "none"
|
||||
lg_asn = "45494"
|
||||
cmd = "show bgp ipv4 unicast {}"
|
||||
elif srv == "he": #Blacklist sometime
|
||||
host = "route-server.he.net"
|
||||
user = "none"
|
||||
password = "none"
|
||||
lg_asn = "6939"
|
||||
cmd = "show bgp ipv4 unicast {}"
|
||||
elif srv == "iamageeknz": #Blacklist sometime
|
||||
host = "rs.as45186.net"
|
||||
user = "none"
|
||||
password = "none"
|
||||
lg_asn = "45186"
|
||||
cmd = "show bgp ipv4 unicast {}"
|
||||
elif srv == "att":
|
||||
host = "route-server.ip.att.net"
|
||||
user = "rviews"
|
||||
password = "rviews"
|
||||
lg_asn = "7018"
|
||||
cmd = "show route {}"
|
||||
|
||||
ip = ipaddress
|
||||
await ctx.send("Connexion en cours au route server...")
|
||||
tn = telnetlib.Telnet(host)
|
||||
|
||||
#Login to the RS via Telnet
|
||||
if user != "none":
|
||||
if(srv == "att"):
|
||||
tn.read_until("login: ".encode())
|
||||
tn.write((user + "\n").encode())
|
||||
else:
|
||||
tn.read_until("Username: ".encode())
|
||||
tn.write((user + "\n").encode())
|
||||
|
||||
if password != "none":
|
||||
if(srv == "att"):
|
||||
tn.read_until("Password:".encode())
|
||||
tn.write((password + "\n").encode())
|
||||
else:
|
||||
tn.read_until("Password: ".encode())
|
||||
tn.write((password + "\n").encode())
|
||||
|
||||
await ctx.send("Connecté ! Récupération des données...")
|
||||
|
||||
#Sending show route via telnet to the RS
|
||||
tn.write((cmd + "\n").format(ip).encode())
|
||||
tn.write(chr(25).encode())
|
||||
tn.write(chr(25).encode())
|
||||
tn.write(chr(25).encode())
|
||||
tn.write("q\n".encode())
|
||||
tn.write("exit\n".encode())
|
||||
|
||||
await ctx.send("Données récupérées ! Traitement en cours")
|
||||
|
||||
#Parsing data
|
||||
data = tn.read_all().decode("utf-8")
|
||||
paths = {}
|
||||
|
||||
#Parsing as paths
|
||||
paths["as_list"] = re.findall(r" ([0-9][0-9 ]+),", data)
|
||||
if(paths["as_list"] == []):
|
||||
paths["as_list"] = re.findall(r" ([0-9][0-9 ]+)[^0-9.]", data)
|
||||
|
||||
#Custom parsing for AT&T
|
||||
if(srv == "att"):
|
||||
paths["as_list"] = re.findall(r"(?<=AS path: 7018 )[0-9][0-9 ]+[^ I]", data)
|
||||
|
||||
#Graphviz diagram
|
||||
g = Digraph('G', filename='bgpgraph', format='png', graph_attr={'rankdir':'LR', 'concentrate': 'true'})
|
||||
|
||||
#Diagram paths generation
|
||||
as_path_count = 0
|
||||
for as_path in paths['as_list']:
|
||||
as_path = as_path.split(" ")
|
||||
as_path.reverse()
|
||||
original_asn = as_path[0]
|
||||
border_asn = as_path[-1]
|
||||
precedent_asn = original_asn
|
||||
for asn in as_path:
|
||||
if asn != "2001": #Cause HE got a default or something weird to this asn
|
||||
if asn != original_asn:
|
||||
g.edge("AS" + asn, "AS" + precedent_asn)
|
||||
precedent_asn = asn
|
||||
if asn == border_asn:
|
||||
g.edge("AS" + lg_asn, "AS" + asn)
|
||||
as_path_count += 1
|
||||
|
||||
#If empty as_path
|
||||
if as_path_count == 0:
|
||||
await ctx.send("Pas de route trouvée vers l'IP demandée depuis le route server choisi.")
|
||||
return
|
||||
|
||||
#Render the graph
|
||||
g.render()
|
||||
|
||||
#Send it
|
||||
with open('bgpgraph.png', 'rb') as fp:
|
||||
await ctx.send(file=discord.File(fp, 'bgpgraph.png'))
|
||||
|
||||
"""---------------------------------------------------------------------"""
|
||||
|
||||
@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']
|
3
dev.requirements.txt
Normal file
3
dev.requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
youtrack
|
||||
pylint==2.6.0
|
||||
black==20.8b1
|
3
init.sh
3
init.sh
|
@ -1,3 +0,0 @@
|
|||
#pip install -U "https://github.com/Rapptz/discord.py/archive/rewrite.zip#egg=discord.py[voice]"
|
||||
python3 -m pip install -U discord.py[voice]
|
||||
python3 -m pip install -r requirements.txt
|
|
@ -1,15 +0,0 @@
|
|||
pymysql
|
||||
gtts
|
||||
beautifulsoup4
|
||||
lxml==4.2.4
|
||||
bs4
|
||||
pytz
|
||||
requests
|
||||
wikipedia
|
||||
pillow
|
||||
ipwhois
|
||||
ipinfo
|
||||
pydig
|
||||
networkx
|
||||
graphviz
|
||||
jishaku
|
45
setup.cfg
Normal file
45
setup.cfg
Normal file
|
@ -0,0 +1,45 @@
|
|||
[metadata]
|
||||
name = Tuxbot-bot
|
||||
version = attr: tuxbot.__version__
|
||||
url = https://github.com/Rom1-J/tuxbot-bot/
|
||||
author = Romain J.
|
||||
author_email = romain@gnous.eu
|
||||
maintainer = Romain J.
|
||||
maintainer_email = romain@gnous.eu
|
||||
description = A discord bot made for GnousEU's guild and OpenSource
|
||||
long_description = file: README.rst
|
||||
license = agplv3
|
||||
platforms = linux
|
||||
|
||||
[options]
|
||||
packages = find_namespace:
|
||||
python_requires = >=3.7
|
||||
install_requires =
|
||||
appdirs>=1.4.4
|
||||
asyncpg>=0.21.0
|
||||
Babel>=2.8.0
|
||||
discord.py>=1.5.1
|
||||
discord_flags>=2.1.1
|
||||
humanize>=2.6.0
|
||||
jishaku>=1.19.1.200
|
||||
psutil>=5.7.2
|
||||
rich>=6.0.0
|
||||
structured_config>=4.12
|
||||
tortoise-orm>=0.16.17
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
tuxbot=tuxbot.__main__:main
|
||||
tuxbot-setup=tuxbot.setup:setup
|
||||
|
||||
[options.packages.find]
|
||||
include =
|
||||
tuxbot
|
||||
tuxbot.*
|
||||
|
||||
[options.package_data]
|
||||
* =
|
||||
locales/*.po
|
||||
**/locales/*.po
|
||||
data/*
|
||||
data/**/*
|
5
setup.py
Normal file
5
setup.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
python_requires=">=3.7",
|
||||
)
|
117
test.py
117
test.py
|
@ -1,117 +0,0 @@
|
|||
import telnetlib
|
||||
import re
|
||||
from graphviz import Digraph
|
||||
|
||||
|
||||
srv = "he"
|
||||
|
||||
if srv == "opentransit":
|
||||
host = "route-server.opentransit.net"
|
||||
user = "rviews"
|
||||
password = "Rviews"
|
||||
lg_asn = "5511"
|
||||
cmd = "show bgp {}"
|
||||
elif srv == "oregonuniv":
|
||||
host = "route-views.routeviews.org"
|
||||
user = "rviews"
|
||||
password = "none"
|
||||
lg_asn = "3582"
|
||||
cmd = "show bgp {}"
|
||||
elif srv == "warian":
|
||||
host = "route-server.warian.net"
|
||||
user = "none"
|
||||
password = "rviews"
|
||||
lg_asn = "56911"
|
||||
cmd = "show bgp ipv4 unicast {}"
|
||||
elif srv == "csaholdigs": #Blacklist des fois
|
||||
host = "route-views.sg.routeviews.org"
|
||||
user = "none"
|
||||
password = "none"
|
||||
lg_asn = "45494"
|
||||
cmd = "show bgp ipv4 unicast {}"
|
||||
elif srv == "he": #Blacklist des fois
|
||||
host = "route-server.he.net"
|
||||
user = "none"
|
||||
password = "none"
|
||||
lg_asn = "6939"
|
||||
cmd = "show bgp ipv4 unicast {}"
|
||||
elif srv == "iamageeknz": #Blacklist des fois
|
||||
host = "rs.as45186.net"
|
||||
user = "none"
|
||||
password = "none"
|
||||
lg_asn = "45186"
|
||||
cmd = "show bgp ipv4 unicast {}"
|
||||
elif srv == "att":
|
||||
host = "route-server.ip.att.net"
|
||||
user = "rviews"
|
||||
password = "rviews"
|
||||
lg_asn = "7018"
|
||||
cmd = "show route {}"
|
||||
|
||||
|
||||
# Récupération de l'IP dont on veux connaitre plus d'informations
|
||||
ip = input("IP : ")
|
||||
|
||||
# Connexion à OpenTransit
|
||||
tn = telnetlib.Telnet(host)
|
||||
|
||||
if user != "none":
|
||||
if(srv == "att"):
|
||||
tn.read_until("login: ".encode())
|
||||
tn.write((user + "\n").encode())
|
||||
print("ok")
|
||||
else:
|
||||
tn.read_until("Username: ".encode())
|
||||
tn.write((user + "\n").encode())
|
||||
|
||||
if password != "none":
|
||||
if(srv == "att"):
|
||||
tn.read_until("Password:".encode())
|
||||
tn.write((password + "\n").encode())
|
||||
print("ok")
|
||||
else:
|
||||
tn.read_until("Password: ".encode())
|
||||
tn.write((password + "\n").encode())
|
||||
|
||||
# Execution d'une commande d'information BGP
|
||||
tn.write((cmd + "\n").format(ip).encode())
|
||||
tn.write(chr(25).encode())
|
||||
tn.write(chr(25).encode())
|
||||
tn.write(chr(25).encode())
|
||||
tn.write("q\n".encode())
|
||||
tn.write("exit\n".encode())
|
||||
|
||||
|
||||
# Decodage des données pour les adaptées à python
|
||||
data = tn.read_all().decode("utf-8")
|
||||
|
||||
print(data)
|
||||
|
||||
# Récupération des données grâce à l'utilisation d'expression régulière (module re)
|
||||
paths = {}
|
||||
|
||||
paths["as_list"] = re.findall(r" ([0-9][0-9 ]+),", data)
|
||||
if(paths["as_list"] == []):
|
||||
paths["as_list"] = re.findall(r" ([0-9][0-9 ]+)[^0-9.]", data)
|
||||
|
||||
if(srv == "att"):
|
||||
paths["as_list"] = re.findall(r"(?<=AS path: 7018 )[0-9][0-9 ]+[^ I]", data)
|
||||
|
||||
as_list = paths['as_list']
|
||||
|
||||
g = Digraph('G', filename='hello', format='png', graph_attr={'rankdir':'LR', 'concentrate': 'true'})
|
||||
|
||||
for as_path in as_list:
|
||||
as_path = as_path.split(" ")
|
||||
as_path.reverse()
|
||||
original_asn = as_path[0]
|
||||
border_asn = as_path[-1]
|
||||
precedent_asn = original_asn
|
||||
for asn in as_path:
|
||||
if asn != original_asn:
|
||||
g.edge("AS" + asn, "AS" + precedent_asn)
|
||||
precedent_asn = asn
|
||||
if asn == border_asn:
|
||||
g.edge("AS" + lg_asn, "AS" + asn)
|
||||
|
||||
g.render()
|
30
test2.py
30
test2.py
|
@ -1,30 +0,0 @@
|
|||
import networkx as nx
|
||||
import matplotlib.pyplot as plt
|
||||
from graphviz import Digraph
|
||||
|
||||
import ipinfo as ipinfoio
|
||||
|
||||
as_list = ['701 2914 395747', '3267 1299 395747', '3257 395747']
|
||||
|
||||
g = Digraph('G', filename='hello', format='png', graph_attr={'rankdir':'LR'})
|
||||
|
||||
lg_asn = "5511"
|
||||
|
||||
for as_path in as_list:
|
||||
as_path = as_path.split(" ")
|
||||
as_path.reverse()
|
||||
original_asn = as_path[0]
|
||||
border_asn = as_path[-1]
|
||||
precedent_asn = original_asn
|
||||
for asn in as_path:
|
||||
if asn != original_asn:
|
||||
g.edge("AS" + asn, "AS" + precedent_asn)
|
||||
precedent_asn = asn
|
||||
if asn == border_asn:
|
||||
g.edge("AS" + lg_asn, "AS" + asn)
|
||||
print(as_path)
|
||||
print("\n")
|
||||
|
||||
|
||||
|
||||
g.render()
|
|
@ -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,43 +0,0 @@
|
|||
**Commandes utilitaires**
|
||||
-> .afk : Signaler son absence
|
||||
-> .back : Signaler son retour
|
||||
-> .clock _ville_: Affiche l'heure et quelques infos sur la ville en question
|
||||
-> .ytdiscover : Découvrir des chaînes 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
|
||||
-> .role _nomdurole_ : Ajoute/Retire le rôle _nomdurole_
|
||||
-> .btcprice : Affiche le prix du bitcoin
|
||||
[split]
|
||||
**Commandes Réseau**
|
||||
-> .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
|
||||
-> .shroute _source_ _IPv4_ : envoie un graphique contenant la route pour aller vers _IPv4_ depuis _source_ (ex: _he_)
|
||||
-> .peeringdb _ASN_ : affiche les informations PeeringDB de _ASN_
|
||||
[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 miaou
|
||||
[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
|
||||
-> .sondage : Affiche l'aide pour la commande sondage
|
||||
-> .ping : Ping le bot
|
||||
-> .git : Affiche le repos Gitea du Bot :heart:
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
:tools: **Développement** :
|
||||
└> Outout : [jesuis.enpls.org](https://jesuis.enpls.org/)
|
||||
└> 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
|
||||
└> Courriel : [romain@gnous.eu](mailto:romain@gnous.eu)
|
||||
|
||||
|
||||
:link: **Serveur** :
|
||||
└> Serveur GnousEU : [rejoindre](https://discord.gg/URKy7yd)
|
||||
|
||||
|
|
@ -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"}
|
||||
}
|
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 ou les distributions 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"}
|
||||
}
|
||||
|
26
tuxbot/__init__.py
Normal file
26
tuxbot/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import os
|
||||
from collections import namedtuple
|
||||
|
||||
build = os.popen("/usr/bin/git rev-parse --short HEAD").read().strip()
|
||||
info = os.popen('/usr/bin/git log -n 3 -s --format="%s"').read().strip()
|
||||
|
||||
VersionInfo = namedtuple(
|
||||
"VersionInfo", "major minor micro releaselevel build, info"
|
||||
)
|
||||
version_info = VersionInfo(
|
||||
major=3, minor=0, micro=0, releaselevel="alpha", build=build, info=info
|
||||
)
|
||||
|
||||
__version__ = "v{}.{}.{}-{}.{}".format(
|
||||
version_info.major,
|
||||
version_info.minor,
|
||||
version_info.micro,
|
||||
version_info.releaselevel,
|
||||
version_info.build,
|
||||
).replace("\n", "")
|
||||
|
||||
|
||||
class ExitCodes:
|
||||
CRITICAL = 1
|
||||
SHUTDOWN = 0
|
||||
RESTART = 42
|
30
tuxbot/__main__.py
Normal file
30
tuxbot/__main__.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from rich.console import Console
|
||||
from rich.traceback import install
|
||||
from tuxbot import ExitCodes
|
||||
|
||||
console = Console()
|
||||
install(console=console, show_locals=True)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
from .__run__ import run # pylint: disable=import-outside-toplevel
|
||||
|
||||
run()
|
||||
except SystemExit as exc:
|
||||
if exc.code == ExitCodes.RESTART:
|
||||
# reimport to load changes
|
||||
from .__run__ import run # pylint: disable=import-outside-toplevel
|
||||
|
||||
run()
|
||||
else:
|
||||
raise exc
|
||||
except Exception:
|
||||
console.print_exception(show_locals=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
console.print_exception(show_locals=True)
|
308
tuxbot/__run__.py
Normal file
308
tuxbot/__run__.py
Normal file
|
@ -0,0 +1,308 @@
|
|||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import signal
|
||||
import sys
|
||||
import os
|
||||
from argparse import Namespace
|
||||
from datetime import datetime
|
||||
|
||||
import discord
|
||||
import humanize
|
||||
import pip
|
||||
from rich.columns import Columns
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.traceback import install
|
||||
from rich.table import Table, box
|
||||
from rich.text import Text
|
||||
from rich import print as rprint
|
||||
|
||||
import tuxbot.logging
|
||||
from tuxbot.core.bot import Tux
|
||||
from tuxbot.core import config
|
||||
from .core.utils import data_manager
|
||||
from . import __version__, version_info, ExitCodes
|
||||
|
||||
log = logging.getLogger("tuxbot.main")
|
||||
|
||||
console = Console()
|
||||
install(console=console, show_locals=True)
|
||||
|
||||
BORDER_STYLE = "not dim"
|
||||
|
||||
|
||||
def list_instances() -> None:
|
||||
"""List all available instances"""
|
||||
app_config = config.ConfigFile(
|
||||
data_manager.config_dir / "config.yaml", config.AppConfig
|
||||
).config
|
||||
|
||||
console.print(
|
||||
Panel("[bold green]Instances", style="green"), justify="center"
|
||||
)
|
||||
console.print()
|
||||
|
||||
columns = Columns(expand=True, padding=2, align="center")
|
||||
for instance, details in app_config.Instances.items():
|
||||
active = details["active"]
|
||||
last_run = (
|
||||
humanize.naturaltime(
|
||||
datetime.now() - datetime.fromtimestamp(details["last_run"])
|
||||
)
|
||||
or "[i]unknown"
|
||||
)
|
||||
|
||||
table = Table(
|
||||
style="dim", border_style=BORDER_STYLE, box=box.HEAVY_HEAD
|
||||
)
|
||||
table.add_column("Name")
|
||||
table.add_column(("Running since" if active else "Last run"))
|
||||
table.add_row(instance, last_run)
|
||||
table.title = Text(instance, style="green" if active else "red")
|
||||
columns.add_renderable(table)
|
||||
console.print(columns)
|
||||
console.print()
|
||||
|
||||
sys.exit(os.EX_OK)
|
||||
|
||||
|
||||
def debug_info() -> None:
|
||||
"""Show debug info relatives to the bot"""
|
||||
python_version = sys.version.replace("\n", "")
|
||||
pip_version = pip.__version__
|
||||
tuxbot_version = __version__
|
||||
dpy_version = discord.__version__
|
||||
|
||||
uptime = os.popen("/usr/bin/uptime").read().strip().split()
|
||||
|
||||
console.print(
|
||||
Panel("[bold blue]Debug Info", style="blue"), justify="center"
|
||||
)
|
||||
console.print()
|
||||
|
||||
columns = Columns(expand=True, padding=2, align="center")
|
||||
|
||||
table = Table(style="dim", border_style=BORDER_STYLE, box=box.HEAVY_HEAD)
|
||||
table.add_column(
|
||||
"Bot Info",
|
||||
)
|
||||
table.add_row(f"[u]Tuxbot version:[/u] {tuxbot_version}")
|
||||
table.add_row(f"[u]Major:[/u] {version_info.major}")
|
||||
table.add_row(f"[u]Minor:[/u] {version_info.minor}")
|
||||
table.add_row(f"[u]Micro:[/u] {version_info.micro}")
|
||||
table.add_row(f"[u]Level:[/u] {version_info.releaselevel}")
|
||||
table.add_row(f"[u]Last change:[/u] {version_info.info}")
|
||||
columns.add_renderable(table)
|
||||
|
||||
table = Table(style="dim", border_style=BORDER_STYLE, box=box.HEAVY_HEAD)
|
||||
table.add_column(
|
||||
"Python Info",
|
||||
)
|
||||
table.add_row(f"[u]Python version:[/u] {python_version}")
|
||||
table.add_row(f"[u]Python executable path:[/u] {sys.executable}")
|
||||
table.add_row(f"[u]Pip version:[/u] {pip_version}")
|
||||
table.add_row(f"[u]Discord.py version:[/u] {dpy_version}")
|
||||
columns.add_renderable(table)
|
||||
|
||||
table = Table(style="dim", border_style=BORDER_STYLE, box=box.HEAVY_HEAD)
|
||||
table.add_column(
|
||||
"Server Info",
|
||||
)
|
||||
table.add_row(f"[u]System:[/u] {os.uname().sysname}")
|
||||
table.add_row(f"[u]System arch:[/u] {os.uname().machine}")
|
||||
table.add_row(f"[u]Kernel:[/u] {os.uname().release}")
|
||||
table.add_row(f"[u]User:[/u] {os.getlogin()}")
|
||||
table.add_row(f"[u]Uptime:[/u] {uptime[2][:-1]}")
|
||||
table.add_row(
|
||||
f"[u]Load Average:[/u] {' '.join(map(str, os.getloadavg()))}"
|
||||
)
|
||||
columns.add_renderable(table)
|
||||
|
||||
console.print(columns)
|
||||
console.print()
|
||||
|
||||
sys.exit(os.EX_OK)
|
||||
|
||||
|
||||
def parse_cli_flags(args: list) -> Namespace:
|
||||
"""Parser for cli values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args:list
|
||||
Is a list of all passed values.
|
||||
Returns
|
||||
-------
|
||||
Namespace
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Tuxbot - OpenSource bot",
|
||||
usage="tuxbot <instance_name> [arguments]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
"-V",
|
||||
action="store_true",
|
||||
help="Show tuxbot's used version",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug", action="store_true", help="Show debug information."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--list-instances",
|
||||
"-L",
|
||||
action="store_true",
|
||||
help="List all instance names",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--token", "-T", type=str, help="Run Tuxbot with passed token"
|
||||
)
|
||||
parser.add_argument(
|
||||
"instance_name",
|
||||
nargs="?",
|
||||
help="Name of the bot instance created during `tuxbot-setup`.",
|
||||
)
|
||||
|
||||
args = parser.parse_args(args)
|
||||
|
||||
return args
|
||||
|
||||
|
||||
async def shutdown_handler(tux: Tux, signal_type, exit_code=None) -> None:
|
||||
"""Handler when the bot shutdown
|
||||
|
||||
It cancels all running task.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tux:Tux
|
||||
Object for the bot.
|
||||
signal_type:int, None
|
||||
Exiting signal code.
|
||||
exit_code:None|int
|
||||
Code to show when exiting.
|
||||
"""
|
||||
if signal_type:
|
||||
log.info("%s received. Quitting...", signal_type)
|
||||
elif exit_code is None:
|
||||
log.info("Shutting down from unhandled exception")
|
||||
tux.shutdown_code = ExitCodes.CRITICAL
|
||||
|
||||
if exit_code is not None:
|
||||
tux.shutdown_code = exit_code
|
||||
|
||||
await tux.shutdown()
|
||||
|
||||
|
||||
async def run_bot(tux: Tux, cli_flags: Namespace) -> None:
|
||||
"""This run the bot.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tux:Tux
|
||||
Object for the bot.
|
||||
cli_flags:Namespace
|
||||
All different flags passed in the console.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
When exiting, this function return None.
|
||||
"""
|
||||
data_path = data_manager.data_path(tux.instance_name)
|
||||
|
||||
tuxbot.logging.init_logging(10, location=data_path / "logs")
|
||||
|
||||
log.debug("====Basic Config====")
|
||||
log.debug("Data Path: %s", data_path)
|
||||
|
||||
if cli_flags.token:
|
||||
token = cli_flags.token
|
||||
else:
|
||||
token = tux.config.Core.token
|
||||
|
||||
if not token:
|
||||
log.critical("Token must be set if you want to login.")
|
||||
sys.exit(ExitCodes.CRITICAL)
|
||||
|
||||
try:
|
||||
await tux.load_packages()
|
||||
console.print()
|
||||
await tux.start(token=token, bot=True)
|
||||
except discord.LoginFailure:
|
||||
log.critical("This token appears to be valid.")
|
||||
console.print()
|
||||
console.print(
|
||||
"[prompt.invalid]This token appears to be valid. [i]exiting...[/i]"
|
||||
)
|
||||
sys.exit(ExitCodes.CRITICAL)
|
||||
except Exception as e:
|
||||
log.critical(e)
|
||||
raise e
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def run() -> None:
|
||||
"""Main function"""
|
||||
tux = None
|
||||
cli_flags = parse_cli_flags(sys.argv[1:])
|
||||
|
||||
if cli_flags.list_instances:
|
||||
list_instances()
|
||||
elif cli_flags.debug:
|
||||
debug_info()
|
||||
elif cli_flags.version:
|
||||
rprint(f"Tuxbot V{version_info.major}")
|
||||
rprint(f"Complete Version: {__version__}")
|
||||
|
||||
sys.exit(os.EX_OK)
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
try:
|
||||
if not cli_flags.instance_name:
|
||||
console.print(
|
||||
"[red]No instance provided ! "
|
||||
"You can use 'tuxbot -L' to list all available instances"
|
||||
)
|
||||
sys.exit(ExitCodes.CRITICAL)
|
||||
|
||||
tux = Tux(
|
||||
cli_flags=cli_flags,
|
||||
description="Tuxbot, made from and for OpenSource",
|
||||
dm_help=None,
|
||||
)
|
||||
|
||||
loop.run_until_complete(run_bot(tux, cli_flags))
|
||||
except KeyboardInterrupt:
|
||||
console.print(
|
||||
" [red]Please use <prefix>quit instead of Ctrl+C to Shutdown!"
|
||||
)
|
||||
log.warning("Please use <prefix>quit instead of Ctrl+C to Shutdown!")
|
||||
log.info("Received KeyboardInterrupt")
|
||||
console.print("[i]Trying to shutdown...")
|
||||
if tux is not None:
|
||||
loop.run_until_complete(shutdown_handler(tux, signal.SIGINT))
|
||||
except SystemExit as exc:
|
||||
log.info("Shutting down with exit code: %s", exc.code)
|
||||
if tux is not None:
|
||||
loop.run_until_complete(shutdown_handler(tux, None, exc.code))
|
||||
raise
|
||||
except Exception as exc:
|
||||
log.error("Unexpected exception (%s): ", type(exc))
|
||||
console.print_exception(show_locals=True)
|
||||
if tux is not None:
|
||||
loop.run_until_complete(shutdown_handler(tux, None, 1))
|
||||
finally:
|
||||
loop.run_until_complete(loop.shutdown_asyncgens())
|
||||
log.info("Please wait, cleaning up a bit more")
|
||||
loop.run_until_complete(asyncio.sleep(1))
|
||||
asyncio.set_event_loop(None)
|
||||
loop.stop()
|
||||
loop.close()
|
||||
exit_code = ExitCodes.CRITICAL if tux is None else tux.shutdown_code
|
||||
|
||||
sys.exit(exit_code)
|
19
tuxbot/cogs/Admin/__init__.py
Normal file
19
tuxbot/cogs/Admin/__init__.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from .admin import Admin
|
||||
from .config import AdminConfig, HAS_MODELS
|
||||
from ...core.bot import Tux
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro release_level")
|
||||
version_info = VersionInfo(major=2, minor=0, micro=0, release_level="alpha")
|
||||
|
||||
__version__ = "v{}.{}.{}-{}".format(
|
||||
version_info.major,
|
||||
version_info.minor,
|
||||
version_info.micro,
|
||||
version_info.release_level,
|
||||
).replace("\n", "")
|
||||
|
||||
|
||||
def setup(bot: Tux):
|
||||
bot.add_cog(Admin(bot))
|
86
tuxbot/cogs/Admin/admin.py
Normal file
86
tuxbot/cogs/Admin/admin.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
import logging
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from tuxbot.core.utils import checks
|
||||
from tuxbot.core.bot import Tux
|
||||
from tuxbot.core.config import set_for_key
|
||||
from tuxbot.core.config import Config
|
||||
from tuxbot.core.i18n import (
|
||||
Translator,
|
||||
find_locale,
|
||||
get_locale_name,
|
||||
available_locales,
|
||||
)
|
||||
from tuxbot.core.utils.functions.extra import (
|
||||
group_extra,
|
||||
command_extra,
|
||||
ContextPlus,
|
||||
)
|
||||
|
||||
log = logging.getLogger("tuxbot.cogs.Admin")
|
||||
_ = Translator("Admin", __file__)
|
||||
|
||||
|
||||
class Admin(commands.Cog, name="Admin"):
|
||||
def __init__(self, bot: Tux):
|
||||
self.bot = bot
|
||||
|
||||
async def _save_lang(self, ctx: ContextPlus, lang: str):
|
||||
set_for_key(
|
||||
self.bot.config.Servers, ctx.guild.id, Config.Server, locale=lang
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# =========================================================================
|
||||
|
||||
@group_extra(name="lang", aliases=["locale", "langue"], deletable=True)
|
||||
@commands.guild_only()
|
||||
@checks.is_admin()
|
||||
async def _lang(self, ctx: ContextPlus):
|
||||
"""Manage lang settings."""
|
||||
|
||||
@_lang.command(name="set", aliases=["define", "choice"])
|
||||
async def _lang_set(self, ctx: ContextPlus, lang: str):
|
||||
try:
|
||||
await self._save_lang(ctx, find_locale(lang.lower()))
|
||||
await ctx.send(
|
||||
_(
|
||||
"Locale changed to {lang} successfully",
|
||||
ctx,
|
||||
self.bot.config,
|
||||
).format(lang=f"`{get_locale_name(lang).lower()}`")
|
||||
)
|
||||
except NotImplementedError:
|
||||
await self._lang_list(ctx)
|
||||
|
||||
@_lang.command(name="list", aliases=["liste", "all", "view"])
|
||||
async def _lang_list(self, ctx: ContextPlus):
|
||||
description = ""
|
||||
for key, value in available_locales.items():
|
||||
description += f":flag_{key[-2:].lower()}: {value[0]}\n"
|
||||
|
||||
e = discord.Embed(
|
||||
title=_("List of available locales: ", ctx, self.bot.config),
|
||||
description=description,
|
||||
color=0x36393E,
|
||||
)
|
||||
|
||||
await ctx.send(embed=e)
|
||||
|
||||
# =========================================================================
|
||||
|
||||
@command_extra(name="quit", aliases=["shutdown"], deletable=False)
|
||||
@commands.guild_only()
|
||||
@checks.is_owner()
|
||||
async def _quit(self, ctx: ContextPlus):
|
||||
await ctx.send("*quit...*")
|
||||
await self.bot.shutdown()
|
||||
|
||||
@command_extra(name="restart", deletable=False)
|
||||
@commands.guild_only()
|
||||
@checks.is_owner()
|
||||
async def _restart(self, ctx: ContextPlus):
|
||||
await ctx.send("*restart...*")
|
||||
await self.bot.shutdown(restart=True)
|
10
tuxbot/cogs/Admin/config.py
Normal file
10
tuxbot/cogs/Admin/config.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from structured_config import Structure
|
||||
|
||||
HAS_MODELS = True
|
||||
|
||||
|
||||
class AdminConfig(Structure):
|
||||
pass
|
||||
|
||||
|
||||
extra = {}
|
27
tuxbot/cogs/Admin/locales/en-US.po
Normal file
27
tuxbot/cogs/Admin/locales/en-US.po
Normal file
|
@ -0,0 +1,27 @@
|
|||
# English translations for Tuxbot-bot package.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-11-11 02:40+0100\n"
|
||||
"PO-Revision-Date: 2020-06-10 00:38+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: en_US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: tuxbot/cogs/Admin/admin.py:50
|
||||
#, python-brace-format
|
||||
msgid "Locale changed to {lang} successfully"
|
||||
msgstr ""
|
||||
|
||||
#: tuxbot/cogs/Admin/admin.py:65
|
||||
msgid "List of available locales: "
|
||||
msgstr ""
|
27
tuxbot/cogs/Admin/locales/en-US.po~
Normal file
27
tuxbot/cogs/Admin/locales/en-US.po~
Normal file
|
@ -0,0 +1,27 @@
|
|||
# English translations for Tuxbot-bot package.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:13+0200\n"
|
||||
"PO-Revision-Date: 2020-06-10 00:38+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: en_US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: tuxbot/cogs/admin/admin.py:47
|
||||
#, python-brace-format
|
||||
msgid "Locale changed to {lang} successfully"
|
||||
msgstr ""
|
||||
|
||||
#: tuxbot/cogs/admin/admin.py:62
|
||||
msgid "List of available locales: "
|
||||
msgstr ""
|
28
tuxbot/cogs/Admin/locales/fr-FR.po
Normal file
28
tuxbot/cogs/Admin/locales/fr-FR.po
Normal file
|
@ -0,0 +1,28 @@
|
|||
# French translations for Tuxbot-bot package
|
||||
# Traductions françaises du paquet Tuxbot-bot.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-11-11 02:40+0100\n"
|
||||
"PO-Revision-Date: 2020-06-10 00:38+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: tuxbot/cogs/Admin/admin.py:50
|
||||
#, fuzzy, python-brace-format
|
||||
msgid "Locale changed to {lang} successfully"
|
||||
msgstr "Langue changée pour {lang} avec succès"
|
||||
|
||||
#: tuxbot/cogs/Admin/admin.py:65
|
||||
msgid "List of available locales: "
|
||||
msgstr "Liste des langues disponibles : "
|
28
tuxbot/cogs/Admin/locales/fr-FR.po~
Normal file
28
tuxbot/cogs/Admin/locales/fr-FR.po~
Normal file
|
@ -0,0 +1,28 @@
|
|||
# French translations for Tuxbot-bot package
|
||||
# Traductions françaises du paquet Tuxbot-bot.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:13+0200\n"
|
||||
"PO-Revision-Date: 2020-06-10 00:38+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: tuxbot/cogs/admin/admin.py:47
|
||||
#, fuzzy, python-brace-format
|
||||
msgid "Locale changed to {lang} successfully"
|
||||
msgstr "Langue changée pour {lang} avec succès"
|
||||
|
||||
#: tuxbot/cogs/admin/admin.py:62
|
||||
msgid "List of available locales: "
|
||||
msgstr "Liste des langues disponibles : "
|
27
tuxbot/cogs/Admin/locales/messages.pot
Normal file
27
tuxbot/cogs/Admin/locales/messages.pot
Normal file
|
@ -0,0 +1,27 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-11-11 16:42+0100\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"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: tuxbot/cogs/Admin/admin.py:50
|
||||
#, python-brace-format
|
||||
msgid "Locale changed to {lang} successfully"
|
||||
msgstr ""
|
||||
|
||||
#: tuxbot/cogs/Admin/admin.py:65
|
||||
msgid "List of available locales: "
|
||||
msgstr ""
|
2
tuxbot/cogs/Admin/models/__init__.py
Normal file
2
tuxbot/cogs/Admin/models/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from .alias import *
|
||||
from .warns import *
|
24
tuxbot/cogs/Admin/models/alias.py
Normal file
24
tuxbot/cogs/Admin/models/alias.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import tortoise
|
||||
from tortoise import fields
|
||||
|
||||
|
||||
class Alias(tortoise.Model):
|
||||
id = fields.BigIntField(pk=True)
|
||||
user_id = fields.BigIntField()
|
||||
alias = fields.TextField(max_length=255)
|
||||
command = fields.TextField(max_length=255)
|
||||
guild = fields.BigIntField()
|
||||
|
||||
class Meta:
|
||||
table = "aliases"
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"<Alias id={self.id} "
|
||||
f"user_id={self.user_id} "
|
||||
f"alias='{self.alias}' "
|
||||
f"command='{self.command}' "
|
||||
f"guild={self.guild}>"
|
||||
)
|
||||
|
||||
__repr__ = __str__
|
24
tuxbot/cogs/Admin/models/warns.py
Normal file
24
tuxbot/cogs/Admin/models/warns.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import tortoise
|
||||
from tortoise import fields
|
||||
|
||||
|
||||
class Warn(tortoise.Model):
|
||||
id = fields.BigIntField(pk=True)
|
||||
server_id = fields.BigIntField()
|
||||
user_id = fields.BigIntField()
|
||||
reason = fields.TextField(max_length=255)
|
||||
created_at = fields.DatetimeField()
|
||||
|
||||
class Meta:
|
||||
table = "warns"
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"<Warn id={self.id} "
|
||||
f"server_id={self.server_id} "
|
||||
f"user_id={self.user_id} "
|
||||
f"reason='{self.reason}' "
|
||||
f"created_at={self.created_at}>"
|
||||
)
|
||||
|
||||
__repr__ = __str__
|
20
tuxbot/cogs/Dev/__init__.py
Normal file
20
tuxbot/cogs/Dev/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from .dev import Dev
|
||||
from .config import DevConfig, HAS_MODELS
|
||||
from ...core.bot import Tux
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro release_level")
|
||||
version_info = VersionInfo(major=0, minor=1, micro=0, release_level="alpha")
|
||||
|
||||
__version__ = "v{}.{}.{}-{}".format(
|
||||
version_info.major,
|
||||
version_info.minor,
|
||||
version_info.micro,
|
||||
version_info.release_level,
|
||||
).replace("\n", "")
|
||||
|
||||
|
||||
def setup(bot: Tux):
|
||||
cog = Dev(bot)
|
||||
bot.add_cog(cog)
|
22
tuxbot/cogs/Dev/config.py
Normal file
22
tuxbot/cogs/Dev/config.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from structured_config import Structure, StrField
|
||||
|
||||
HAS_MODELS = False
|
||||
|
||||
|
||||
class DevConfig(Structure):
|
||||
url: str = StrField("")
|
||||
login: str = StrField("")
|
||||
password: str = StrField("")
|
||||
|
||||
|
||||
extra = {
|
||||
"url": {
|
||||
"type": str,
|
||||
"description": "URL of the YouTrack instance (without /youtrack/)",
|
||||
},
|
||||
"login": {"type": str, "description": "Login for YouTrack instance"},
|
||||
"password": {
|
||||
"type": str,
|
||||
"description": "Password for YouTrack instance",
|
||||
},
|
||||
}
|
46
tuxbot/cogs/Dev/dev.py
Normal file
46
tuxbot/cogs/Dev/dev.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import logging
|
||||
from discord.ext import commands
|
||||
from youtrack.connection import Connection as YouTrack
|
||||
from structured_config import ConfigFile
|
||||
|
||||
from tuxbot.core.bot import Tux
|
||||
from tuxbot.core.i18n import (
|
||||
Translator,
|
||||
)
|
||||
from tuxbot.core.utils.data_manager import cogs_data_path
|
||||
from .config import DevConfig
|
||||
from ...core.utils import checks
|
||||
from ...core.utils.functions.extra import group_extra, ContextPlus
|
||||
|
||||
log = logging.getLogger("tuxbot.cogs.Dev")
|
||||
_ = Translator("Dev", __file__)
|
||||
|
||||
|
||||
class Dev(commands.Cog, name="Dev"):
|
||||
yt: YouTrack # pylint: disable=invalid-name
|
||||
|
||||
def __init__(self, bot: Tux):
|
||||
self.bot = bot
|
||||
self.config: DevConfig = ConfigFile(
|
||||
str(cogs_data_path(self.bot.instance_name, "Dev") / "config.yaml"),
|
||||
DevConfig,
|
||||
).config
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
self.yt = YouTrack(
|
||||
self.config.url.rstrip("/") + "/youtrack/",
|
||||
login=self.config.login,
|
||||
password=self.config.password,
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# =========================================================================
|
||||
|
||||
@group_extra(name="issue", aliases=["issues"], deletable=True)
|
||||
@checks.is_owner()
|
||||
async def _issue(self, ctx: ContextPlus):
|
||||
"""Manage bot issues."""
|
||||
|
||||
@_issue.command(name="list", aliases=["liste", "all", "view"])
|
||||
async def _lang_list(self, ctx: ContextPlus):
|
||||
pass
|
18
tuxbot/cogs/Dev/locales/en-US.po
Normal file
18
tuxbot/cogs/Dev/locales/en-US.po
Normal file
|
@ -0,0 +1,18 @@
|
|||
# English translations for Tuxbot-bot package.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+0200\n"
|
||||
"PO-Revision-Date: 2020-10-21 01:15+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: en_US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
19
tuxbot/cogs/Dev/locales/fr-FR.po
Normal file
19
tuxbot/cogs/Dev/locales/fr-FR.po
Normal file
|
@ -0,0 +1,19 @@
|
|||
# French translations for Tuxbot-bot package
|
||||
# Traductions françaises du paquet Tuxbot-bot.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+0200\n"
|
||||
"PO-Revision-Date: 2020-10-21 01:15+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
18
tuxbot/cogs/Dev/locales/messages.pot
Normal file
18
tuxbot/cogs/Dev/locales/messages.pot
Normal file
|
@ -0,0 +1,18 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+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"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
0
cogs/utils/__init__.py → tuxbot/cogs/Dev/models/__init__.py
Executable file → Normal file
0
cogs/utils/__init__.py → tuxbot/cogs/Dev/models/__init__.py
Executable file → Normal file
27
tuxbot/cogs/Logs/__init__.py
Normal file
27
tuxbot/cogs/Logs/__init__.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
from .logs import Logs, on_error, GatewayHandler
|
||||
from .config import LogsConfig, HAS_MODELS
|
||||
from ...core.bot import Tux
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro release_level")
|
||||
version_info = VersionInfo(major=1, minor=0, micro=0, release_level="alpha")
|
||||
|
||||
__version__ = "v{}.{}.{}-{}".format(
|
||||
version_info.major,
|
||||
version_info.minor,
|
||||
version_info.micro,
|
||||
version_info.release_level,
|
||||
).replace("\n", "")
|
||||
|
||||
|
||||
def setup(bot: Tux):
|
||||
cog = Logs(bot)
|
||||
bot.add_cog(cog)
|
||||
|
||||
handler = GatewayHandler(cog)
|
||||
logging.getLogger().addHandler(handler)
|
||||
commands.AutoShardedBot.on_error = on_error
|
38
tuxbot/cogs/Logs/config.py
Normal file
38
tuxbot/cogs/Logs/config.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from structured_config import Structure, StrField
|
||||
|
||||
HAS_MODELS = False
|
||||
|
||||
|
||||
class LogsConfig(Structure):
|
||||
dm: str = StrField("")
|
||||
mentions: str = StrField("")
|
||||
guilds: str = StrField("")
|
||||
errors: str = StrField("")
|
||||
gateway: str = StrField("")
|
||||
|
||||
|
||||
extra = {
|
||||
"dm": {
|
||||
"type": str,
|
||||
"description": "URL of the webhook used for send DMs "
|
||||
"received and sent by the bot",
|
||||
},
|
||||
"mentions": {
|
||||
"type": str,
|
||||
"description": "URL of the webhook used for send Mentions "
|
||||
"received by the bot",
|
||||
},
|
||||
"guilds": {
|
||||
"type": str,
|
||||
"description": "URL of the webhook used for send guilds where the "
|
||||
"bot is added or removed",
|
||||
},
|
||||
"errors": {
|
||||
"type": str,
|
||||
"description": "URL of the webhook used for send errors in the bot",
|
||||
},
|
||||
"gateway": {
|
||||
"type": str,
|
||||
"description": "URL of the webhook used for send gateway information",
|
||||
},
|
||||
}
|
18
tuxbot/cogs/Logs/locales/en-US.po
Normal file
18
tuxbot/cogs/Logs/locales/en-US.po
Normal file
|
@ -0,0 +1,18 @@
|
|||
# English translations for Tuxbot-bot package.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+0200\n"
|
||||
"PO-Revision-Date: 2020-10-21 01:15+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: en_US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
19
tuxbot/cogs/Logs/locales/fr-FR.po
Normal file
19
tuxbot/cogs/Logs/locales/fr-FR.po
Normal file
|
@ -0,0 +1,19 @@
|
|||
# French translations for Tuxbot-bot package
|
||||
# Traductions françaises du paquet Tuxbot-bot.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+0200\n"
|
||||
"PO-Revision-Date: 2020-10-21 01:15+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
18
tuxbot/cogs/Logs/locales/messages.pot
Normal file
18
tuxbot/cogs/Logs/locales/messages.pot
Normal file
|
@ -0,0 +1,18 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+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"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
332
tuxbot/cogs/Logs/logs.py
Normal file
332
tuxbot/cogs/Logs/logs.py
Normal file
|
@ -0,0 +1,332 @@
|
|||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import textwrap
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from logging import LogRecord
|
||||
|
||||
import discord
|
||||
import humanize
|
||||
import psutil
|
||||
from discord.ext import commands, tasks
|
||||
from structured_config import ConfigFile
|
||||
|
||||
from tuxbot.core.bot import Tux
|
||||
from tuxbot.core.i18n import (
|
||||
Translator,
|
||||
)
|
||||
from tuxbot.core.utils.functions.extra import (
|
||||
command_extra,
|
||||
ContextPlus,
|
||||
)
|
||||
from tuxbot.core.utils.data_manager import cogs_data_path
|
||||
from .config import LogsConfig
|
||||
|
||||
log = logging.getLogger("tuxbot.cogs.Logs")
|
||||
_ = Translator("Logs", __file__)
|
||||
|
||||
|
||||
class GatewayHandler(logging.Handler):
|
||||
def __init__(self, cog):
|
||||
self.cog = cog
|
||||
super().__init__(logging.INFO)
|
||||
|
||||
def filter(self, record: LogRecord):
|
||||
return (
|
||||
record.name == "discord.gateway"
|
||||
or "Shard ID" in record.msg
|
||||
or "Websocket closed " in record.msg
|
||||
)
|
||||
|
||||
def emit(self, record: LogRecord):
|
||||
self.cog.add_record(record)
|
||||
|
||||
|
||||
class Logs(commands.Cog, name="Logs"):
|
||||
def __init__(self, bot: Tux):
|
||||
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() # pylint: disable=no-member
|
||||
|
||||
self.config: LogsConfig = ConfigFile(
|
||||
str(
|
||||
cogs_data_path(self.bot.instance_name, "Logs") / "config.yaml"
|
||||
),
|
||||
LogsConfig,
|
||||
).config
|
||||
|
||||
self._resumes = []
|
||||
self._identifies = defaultdict(list)
|
||||
|
||||
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 _, 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: ContextPlus):
|
||||
if ctx.command is None:
|
||||
return
|
||||
|
||||
command = ctx.command.qualified_name
|
||||
self.bot.stats["commands"][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(
|
||||
"%s: %s in %s > %s",
|
||||
message.created_at,
|
||||
message.author,
|
||||
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: ContextPlus):
|
||||
await self.register_command(ctx)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_socket_response(self, msg):
|
||||
self.bot.stats["socket"][msg.get("t")] += 1
|
||||
|
||||
def webhook(self, log_type):
|
||||
webhook = discord.Webhook.from_url(
|
||||
getattr(self.config, log_type),
|
||||
adapter=discord.AsyncWebhookAdapter(self.bot.session),
|
||||
)
|
||||
return webhook
|
||||
|
||||
async def log_error(self, *, ctx: ContextPlus = 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("errors").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("guilds").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("dm").send(embed=e)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx: ContextPlus, 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("errors").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: LogRecord):
|
||||
self._gateway_queue.put_nowait(record)
|
||||
|
||||
async def notify_gateway_status(self, record: LogRecord):
|
||||
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("gateway").send(msg)
|
||||
|
||||
@command_extra(name="commandstats", hidden=True, deletable=True)
|
||||
@commands.is_owner()
|
||||
async def _commandstats(self, ctx: ContextPlus, limit=20):
|
||||
counter = self.bot.stats["commands"]
|
||||
width = len(max(counter, key=len)) + 1
|
||||
|
||||
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, deletable=True)
|
||||
@commands.is_owner()
|
||||
async def _socketstats(self, ctx: ContextPlus):
|
||||
delta = datetime.datetime.now() - self.bot.uptime
|
||||
minutes = delta.total_seconds() / 60
|
||||
|
||||
counter = self.bot.stats["socket"]
|
||||
if None in counter:
|
||||
counter.pop(None)
|
||||
width = len(max(counter, key=len)) + 1
|
||||
common = counter.most_common()
|
||||
|
||||
total = sum(self.bot.stats["socket"].values())
|
||||
cpm = total / minutes
|
||||
|
||||
output = "\n".join(f"{k:<{width}}: {c}" for k, c in common)
|
||||
|
||||
await ctx.send(
|
||||
f"{total} socket events observed ({cpm:.2f}/minute):"
|
||||
f"```\n{output}\n```"
|
||||
)
|
||||
|
||||
@command_extra(name="uptime")
|
||||
async def _uptime(self, ctx: ContextPlus):
|
||||
uptime = humanize.naturaltime(
|
||||
datetime.datetime.now() - 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("errors")
|
||||
try:
|
||||
await hook.send(embed=e)
|
||||
except (
|
||||
discord.HTTPException,
|
||||
discord.NotFound,
|
||||
discord.Forbidden,
|
||||
discord.InvalidArgument,
|
||||
):
|
||||
pass
|
0
tuxbot/cogs/Logs/models/__init__.py
Normal file
0
tuxbot/cogs/Logs/models/__init__.py
Normal file
19
tuxbot/cogs/Polls/__init__.py
Normal file
19
tuxbot/cogs/Polls/__init__.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from .polls import Polls
|
||||
from .config import PollsConfig, HAS_MODELS
|
||||
from ...core.bot import Tux
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro release_level")
|
||||
version_info = VersionInfo(major=2, minor=0, micro=0, release_level="alpha")
|
||||
|
||||
__version__ = "v{}.{}.{}-{}".format(
|
||||
version_info.major,
|
||||
version_info.minor,
|
||||
version_info.micro,
|
||||
version_info.release_level,
|
||||
).replace("\n", "")
|
||||
|
||||
|
||||
def setup(bot: Tux):
|
||||
bot.add_cog(Polls(bot))
|
10
tuxbot/cogs/Polls/config.py
Normal file
10
tuxbot/cogs/Polls/config.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from structured_config import Structure
|
||||
|
||||
HAS_MODELS = True
|
||||
|
||||
|
||||
class PollsConfig(Structure):
|
||||
pass
|
||||
|
||||
|
||||
extra = {}
|
18
tuxbot/cogs/Polls/locales/en-US.po
Normal file
18
tuxbot/cogs/Polls/locales/en-US.po
Normal file
|
@ -0,0 +1,18 @@
|
|||
# English translations for Tuxbot-bot package.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+0200\n"
|
||||
"PO-Revision-Date: 2020-10-21 01:15+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: en_US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
19
tuxbot/cogs/Polls/locales/fr-FR.po
Normal file
19
tuxbot/cogs/Polls/locales/fr-FR.po
Normal file
|
@ -0,0 +1,19 @@
|
|||
# French translations for Tuxbot-bot package
|
||||
# Traductions françaises du paquet Tuxbot-bot.
|
||||
# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# Automatically generated, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+0200\n"
|
||||
"PO-Revision-Date: 2020-10-21 01:15+0200\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
18
tuxbot/cogs/Polls/locales/messages.pot
Normal file
18
tuxbot/cogs/Polls/locales/messages.pot
Normal file
|
@ -0,0 +1,18 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the Tuxbot-bot package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Tuxbot-bot\n"
|
||||
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
|
||||
"POT-Creation-Date: 2020-10-21 01:15+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"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
2
tuxbot/cogs/Polls/models/__init__.py
Normal file
2
tuxbot/cogs/Polls/models/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from .polls import *
|
||||
from .responses import *
|
35
tuxbot/cogs/Polls/models/polls.py
Normal file
35
tuxbot/cogs/Polls/models/polls.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import tortoise
|
||||
from tortoise import fields
|
||||
|
||||
|
||||
class Poll(tortoise.Model):
|
||||
id = fields.BigIntField(pk=True)
|
||||
channel_id = fields.BigIntField()
|
||||
message_id = fields.BigIntField()
|
||||
author_id = fields.BigIntField()
|
||||
|
||||
content = fields.JSONField()
|
||||
is_anonymous = fields.BooleanField()
|
||||
|
||||
available_choices = fields.IntField()
|
||||
|
||||
choices: fields.ManyToManyRelation["Response"] = fields.ManyToManyField(
|
||||
"models.Response", related_name="choices"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
table = "polls"
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"<Poll id={self.id} "
|
||||
f"channel_id={self.channel_id} "
|
||||
f"message_id={self.message_id} "
|
||||
f"author_id={self.author_id} "
|
||||
f"content={self.content} "
|
||||
f"is_anonymous={self.is_anonymous} "
|
||||
f"available_choices={self.available_choices} "
|
||||
f"choices={self.choices}>"
|
||||
)
|
||||
|
||||
__repr__ = __str__
|
22
tuxbot/cogs/Polls/models/responses.py
Normal file
22
tuxbot/cogs/Polls/models/responses.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import tortoise
|
||||
from tortoise import fields
|
||||
|
||||
|
||||
class Response(tortoise.Model):
|
||||
response_id = fields.BigIntField(pk=True)
|
||||
poll = fields.ForeignKeyField("models.Poll")
|
||||
user_id = fields.BigIntField()
|
||||
|
||||
choice = fields.IntField()
|
||||
|
||||
class Meta:
|
||||
table = "responses"
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"<Response poll={self.poll} "
|
||||
f"user_id={self.user_id} "
|
||||
f"choice={self.choice}>"
|
||||
)
|
||||
|
||||
__repr__ = __str__
|
51
tuxbot/cogs/Polls/polls.py
Normal file
51
tuxbot/cogs/Polls/polls.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
import logging
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
from tuxbot.core.utils.functions.extra import ContextPlus, group_extra
|
||||
from tuxbot.core.bot import Tux
|
||||
from tuxbot.core.i18n import (
|
||||
Translator,
|
||||
)
|
||||
|
||||
log = logging.getLogger("tuxbot.cogs.Polls")
|
||||
_ = Translator("Polls", __file__)
|
||||
|
||||
|
||||
class Polls(commands.Cog, name="Polls"):
|
||||
def __init__(self, bot: Tux):
|
||||
self.bot = bot
|
||||
|
||||
# =========================================================================
|
||||
# =========================================================================
|
||||
|
||||
@group_extra(name="polls", aliases=["poll", "sondages", "sondage"])
|
||||
async def _polls(self, ctx: ContextPlus, *, message):
|
||||
if ctx.invoked_subcommand is None:
|
||||
args: list = message.lower().split()
|
||||
is_anonymous = False
|
||||
|
||||
if "--anonymous" in args:
|
||||
is_anonymous = True
|
||||
args.remove("--anonymous")
|
||||
elif "--anonyme" in args:
|
||||
is_anonymous = True
|
||||
args.remove("--anonyme")
|
||||
|
||||
if args[-1] != "|":
|
||||
args.append("|")
|
||||
|
||||
delimiters = [i for i, val in enumerate(args) if val == "|"]
|
||||
|
||||
question = " ".join(args[0 : delimiters[0]]).capitalize()
|
||||
answers = []
|
||||
|
||||
for i in range(len(delimiters) - 1):
|
||||
start = delimiters[i] + 1
|
||||
end = delimiters[i + 1]
|
||||
|
||||
answers.append(" ".join(args[start:end]).capitalize())
|
||||
|
||||
await ctx.send(
|
||||
f"{message=}\n{question=}\n{answers=}\n{is_anonymous=}"
|
||||
)
|
19
tuxbot/cogs/Utils/__init__.py
Normal file
19
tuxbot/cogs/Utils/__init__.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from .utils import Utils
|
||||
from .config import UtilsConfig, HAS_MODELS
|
||||
from ...core.bot import Tux
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro release_level")
|
||||
version_info = VersionInfo(major=2, minor=0, micro=0, release_level="alpha")
|
||||
|
||||
__version__ = "v{}.{}.{}-{}".format(
|
||||
version_info.major,
|
||||
version_info.minor,
|
||||
version_info.micro,
|
||||
version_info.release_level,
|
||||
).replace("\n", "")
|
||||
|
||||
|
||||
def setup(bot: Tux):
|
||||
bot.add_cog(Utils(bot))
|
10
tuxbot/cogs/Utils/config.py
Normal file
10
tuxbot/cogs/Utils/config.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from structured_config import Structure
|
||||
|
||||
HAS_MODELS = False
|
||||
|
||||
|
||||
class UtilsConfig(Structure):
|
||||
pass
|
||||
|
||||
|
||||
extra = {}
|
50
tuxbot/cogs/Utils/functions/info.py
Normal file
50
tuxbot/cogs/Utils/functions/info.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
import os
|
||||
import pathlib
|
||||
|
||||
|
||||
def fetch_info():
|
||||
total_lines = 0
|
||||
|
||||
total_python_class = 0
|
||||
total_python_functions = 0
|
||||
total_python_coroutines = 0
|
||||
total_python_comments = 0
|
||||
|
||||
file_amount = 0
|
||||
python_file_amount = 0
|
||||
|
||||
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.readlines():
|
||||
line = line.strip()
|
||||
if line.startswith("class"):
|
||||
total_python_class += 1
|
||||
if line.startswith("def"):
|
||||
total_python_functions += 1
|
||||
if line.startswith("async def"):
|
||||
total_python_coroutines += 1
|
||||
if "#" in line:
|
||||
total_python_comments += 1
|
||||
total_lines += 1
|
||||
|
||||
return {
|
||||
"total_lines": total_lines,
|
||||
"total_python_class": total_python_class,
|
||||
"total_python_functions": total_python_functions,
|
||||
"total_python_coroutines": total_python_coroutines,
|
||||
"total_python_comments": total_python_comments,
|
||||
"file_amount": file_amount,
|
||||
"python_file_amount": python_file_amount,
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue