first commit

This commit is contained in:
Romain J 2020-05-24 01:16:08 +02:00
parent 7eac932a4e
commit 04645ec639
76 changed files with 792 additions and 4532 deletions

View file

@ -1,31 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Launch bot
3. Type command '...'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Debian]
- Python Version [e.g. 3.7.4]
**Additional context**
Add any other context about the problem here.

136
.gitignore vendored
View file

@ -1,146 +1,12 @@
#Python
__pycache__/
*.pyc
.env
configs/config.cfg
configs/prefixes.cfg
configs/fallbacks.cfg
.DS_Store
private.py
#jetbrains
.idea/
# other
logs/tuxbot.log
utils/images/tmp/*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
settings.py

6
.idea/discord.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="true" />
</component>
</project>

View 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>

View 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
View 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.8 (tuxbot-bot-rewrite)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View 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-rewrite.iml" filepath="$PROJECT_DIR$/.idea/tuxbot-bot-rewrite.iml" />
</modules>
</component>
</project>

View file

@ -0,0 +1,10 @@
<?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$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

11
.idea/vcs.xml Normal file
View 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>

199
.idea/workspace.xml Normal file
View file

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="6566fca1-2e90-48bb-9e74-dd3badbaca99" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/.github/issue_template.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/LICENSE" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/bot.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cogs/API.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cogs/Admin.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cogs/Help.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cogs/Logs.py" beforeDir="false" afterPath="$PROJECT_DIR$/cogs/Logs.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cogs/Monitoring.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cogs/Poll.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cogs/Useful.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cogs/User.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/configs/blacklist.cfg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/configs/config.cfg.example" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/configs/fallbacks.cfg.example" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/configs/prefixes.cfg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/database.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/generate_locales.sh" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/requirements.txt" beforeDir="false" afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/todo" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/fonts/credit_card.ttf" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/functions/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/functions/config.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/functions/database.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/functions/emotes.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/functions/extra.py" beforeDir="false" afterPath="$PROJECT_DIR$/utils/functions/extra.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/utils/functions/lang.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/functions/paginator.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/functions/version.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/images/blank_credit_card.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/images/gnous.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/admin.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/admin_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/base.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/logs.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/poll.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/poll_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/useful.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/useful_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/user.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/user_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/en/LC_MESSAGES/utils.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/admin.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/admin_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/base.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/logs.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/logs_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/poll.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/poll_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/useful.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/useful_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/user.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/user_help.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/locales/fr/LC_MESSAGES/utils.po" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/models/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/models/alias.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/models/poll.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/utils/models/warn.py" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="package.json" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="1c8uTCADTYzyrek4IjGPAZUYsa9" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
<ConfirmationsSetting value="1" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
<option name="showMembers" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/configs/bot" />
<property name="node.js.detected.package.eslint" value="true" />
<property name="node.js.detected.package.tslint" value="true" />
<property name="node.js.path.for.package.eslint" value="project" />
<property name="node.js.path.for.package.tslint" value="project" />
<property name="node.js.selected.package.eslint" value="(autodetect)" />
<property name="node.js.selected.package.tslint" value="(autodetect)" />
<property name="settings.editor.selected.configurable" value="reference.settingsdialog.IDE.editor.colors" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/configs/bot" />
<recent name="$PROJECT_DIR$/cogs" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/cogs" />
</key>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="6566fca1-2e90-48bb-9e74-dd3badbaca99" name="Default Changelist" comment="" />
<created>1589922546510</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1589991138014</updated>
<workItem from="1589922559463" duration="2090000" />
<workItem from="1589925987600" duration="7107000" />
<workItem from="1589991138257" duration="2797000" />
<workItem from="1590020744819" duration="3432000" />
<workItem from="1590065919217" duration="8090000" />
<workItem from="1590152430112" duration="7500000" />
<workItem from="1590268542625" duration="3535000" />
<workItem from="1590273830049" duration="1288000" />
</task>
<option name="localTasksCounter" value="2" />
<option name="createBranch" value="false" />
<option name="commitChanges" value="false" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="2" />
</component>
<component name="WindowStateProjectService">
<state x="2338" y="213" key="#com.intellij.execution.impl.EditConfigurationsDialog" timestamp="1589991158766">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2338" y="213" key="#com.intellij.execution.impl.EditConfigurationsDialog/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589991158766" />
<state x="2616" y="357" width="521" height="396" key="#com.intellij.fileTypes.FileTypeChooser" timestamp="1589928148179">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2616" y="357" width="521" height="396" key="#com.intellij.fileTypes.FileTypeChooser/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589928148179" />
<state x="2613" y="304" width="528" height="502" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog" timestamp="1590160080814">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2613" y="304" width="528" height="502" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1590160080814" />
<state x="2663" y="313" width="428" height="484" key="FileChooserDialogImpl" timestamp="1590152666732">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2663" y="313" width="428" height="484" key="FileChooserDialogImpl/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1590152666732" />
<state x="2666" y="239" width="421" height="633" key="RollbackChangesDialog" timestamp="1589983513390">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2666" y="239" width="421" height="633" key="RollbackChangesDialog/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983513390" />
<state x="2669" y="310" key="SettingsEditor" timestamp="1589983575542">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2669" y="310" key="SettingsEditor/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983575542" />
<state x="2656" y="388" key="SimpleOpenTaskDialog" timestamp="1589983150588">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2656" y="388" key="SimpleOpenTaskDialog/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983150588" />
<state x="2727" y="471" width="299" height="169" key="VCS.ChangelistChooser" timestamp="1589983445998">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2727" y="471" width="299" height="169" key="VCS.ChangelistChooser/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983445998" />
<state x="2475" y="291" width="804" height="528" key="Vcs.Push.Dialog.v2" timestamp="1589983676744">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2475" y="291" width="804" height="528" key="Vcs.Push.Dialog.v2/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983676744" />
<state x="2440" y="156" width="873" height="799" key="com.intellij.openapi.editor.actions.MultiplePasteAction$ClipboardContentChooser" timestamp="1590155739870">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2440" y="156" width="873" height="799" key="com.intellij.openapi.editor.actions.MultiplePasteAction$ClipboardContentChooser/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1590155739870" />
<state x="2584" y="164" width="592" height="783" key="find.popup" timestamp="1589983672115">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2584" y="164" width="592" height="783" key="find.popup/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983672115" />
<state width="600" height="428" key="javadoc.popup" timestamp="1589983663541">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state width="600" height="428" key="javadoc.popup/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983663541" />
<state x="2527" y="274" width="700" height="530" key="recent.locations.popup" timestamp="1589983638293">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2527" y="274" width="700" height="530" key="recent.locations.popup/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983638292" />
<state x="2543" y="261" width="672" height="678" key="search.everywhere.popup" timestamp="1589983695298">
<screen x="1920" y="0" width="1920" height="1080" />
</state>
<state x="2543" y="261" width="672" height="678" key="search.everywhere.popup/1920.0.1920.1080/0.29.1920.1051@1920.0.1920.1080" timestamp="1589983695298" />
</component>
</project>

437
LICENSE
View file

@ -1,437 +0,0 @@
Attribution-NonCommercial-ShareAlike 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More_considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial-ShareAlike 4.0 International Public License
("Public License"). To the extent this Public License may be
interpreted as a contract, You are granted the Licensed Rights in
consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the
Licensor receives from making the Licensed Material available under
these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution, NonCommercial, and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. NonCommercial means not primarily intended for or directed towards
commercial advantage or monetary compensation. For purposes of
this Public License, the exchange of the Licensed Material for
other material subject to Copyright and Similar Rights by digital
file-sharing or similar means is NonCommercial provided there is
no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part, for NonCommercial purposes only; and
b. produce, reproduce, and Share Adapted Material for
NonCommercial purposes only.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties, including when
the Licensed Material is used other than for NonCommercial
purposes.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-NC-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database for NonCommercial purposes
only;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.

View file

@ -1,86 +0,0 @@
# News
- [ ] i18n for messages
- [x] Custom prefixes
- [ ] Better help command
- [ ] Alias system for commands (e.g. `.alias .ci show .cs`)
- [x] Migrate MySQL to postgresql
- [x] Prepare bot for python 3.8 and discord.py 1.3.0
- [ ] Create launcher
- [ ] Create documentation
## Launcher requirements :
- [ ] Can install the bot
- [ ] Can launch the bot
- [ ] Can propose updates
## New commands :
- [x] `.sondage --anonyme <...>` (create à sondage with the possibility of answering anonymously)
- [ ] `.sondage --edit <id>` (edit a sondage if we are the author or an admin)
## Documentation:
- [ ] How to use ?
- [ ] How to add more commands ?
## Ultimate :
- [ ] Send email or Telegram's message when something is wrong on the bot
- [ ] Create skynet (group of multiple commands about sky (planes, meteo, AI,...))
---
# Cogs.admin commands
- [x] upload `removed`, cause : `never used`
- [x] ban
- [x] kick
- [x] clear
- [x] say
- [x] sayto `removed`, now : `say to`
- [x] sayto_dm `removed`, now : `say to`
- [x] editsay `removed`, now : `say edit`
- [x] addreaction `renamed`, now `react add`
- [x] delete
- [x] deletefrom `removed`, now `delete (from|to|in)`
- [x] embed `removed`, cause : `never used`
- [x] warn `new command`
---
# Cogs.basics commands
- [x] ping
- [x] info
- [ ] help
- [x] credits `new command`
---
# Cogs.ci commands `canceled until the frontend development`
- [ ] ci (help?)
- [ ] ci show
- [ ] ci register
- [ ] ci delete
- [ ] ci update
- [ ] ci setconfig
- [ ] ci setos
- [ ] ci setcountry
- [ ] ci online_edit `renamed`, cause : `website down`
- [ ] ci list
---
# Cogs.utility commands
- [ ] clock `removed` ?
- [ ] clock * `removed` ?
- [ ] ytdiscover `removed` ?
- [x] iplocalise
- [x] getheaders
- [x] git
- [x] quote
---
# Cogs.sondage commands `(renamed as cogs.poll)` `canceled until the frontend development`
- [ ] sondage (help?)

151
app.py Normal file
View file

@ -0,0 +1,151 @@
import contextlib
import datetime
import logging
from collections import Counter
from typing import List
import aiohttp
import discord
from discord.ext import commands
from tortoise import Tortoise
from configs.bot import settings
from utils.functions.extra import ContextPlus, get_prefix, \
get_owners, get_blacklist
log = logging.getLogger(__name__)
l_extensions: List[str] = [
"jishaku",
"cogs.Logs",
"cogs.Images",
]
class TuxBot(commands.AutoShardedBot):
logs_channels: dict
session: aiohttp.ClientSession
command_stats: Counter = Counter()
socket_stats: Counter = Counter()
def __init__(self):
self.uptime = datetime.datetime.utcnow()
self.config = settings
super().__init__(
command_prefix=get_prefix,
case_insensitive=True
)
self.logs_channels = {
"dm": self.config.logs["dm"],
"mentions": self.config.logs["mentions"],
"guilds": self.config.logs["guilds"],
"errors": self.config.logs["errors"],
}
print("\n"*2)
for extension in l_extensions:
try:
self.load_extension(extension)
print(f"{extension} loaded !")
except Exception as e:
print(f"{type(e).__name__}: {e}")
print("\n"*2)
async def is_owner(self, user: discord.User):
return user.id in get_owners()
async def on_ready(self):
print(f"Connected !\n"
f"\n"
f"==> info: bot username {self.user}\n"
f" info: bot id {self.user.id}\n"
f" info: bot prefix {self.command_prefix}\n"
f"==> info: guild count {len(self.guilds)}\n"
f" info: member count {len(list(self.get_all_members()))}\n"
f" info: channel count {len(list(self.get_all_channels()))}")
print(f"\n{'='*118}\n\n")
@staticmethod
async def on_resumed():
print("resumed...")
async def get_context(self, message: discord.Message, *, cls=None):
return await super().get_context(message, cls=ContextPlus)
async def on_message(self, message: discord.Message):
if message.author.bot:
return
if message.author.id in get_blacklist()['users'] \
or message.channel.id in get_blacklist()['channels'] \
or (message.channel.guild
and message.channel.guild.id in get_blacklist()['guilds']):
return
try:
await self.process_commands(message)
except Exception as e:
print(f"{type(e).__name__}: {e}")
async def bot_logout(self):
await super().logout()
await self.session.close()
async def bot_start(self):
self.session = aiohttp.ClientSession(loop=self.loop)
await self.login(self.config.token, bot=True)
await self.connect()
def run(self):
loop = self.loop
loop.run_until_complete(Tortoise.init(
db_url=self.config.postgresql,
modules={
"models": [
"models.__init__"
]
}
))
loop.run_until_complete(Tortoise.generate_schemas())
try:
loop.run_until_complete(self.bot_start())
except KeyboardInterrupt:
loop.run_until_complete(self.bot_logout())
@contextlib.contextmanager
def setup_logging():
logging.getLogger('discord').setLevel(logging.INFO)
logging.getLogger('discord.http').setLevel(logging.WARNING)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
try:
handler = logging.FileHandler(filename='logs/tuxbot.log',
encoding='utf-8', mode='w')
fmt = logging.Formatter('[{levelname:<7}] [{asctime}]'
' {name}: {message}',
'%Y-%m-%d %H:%M:%S', style='{')
handler.setFormatter(fmt)
logger.addHandler(handler)
yield
finally:
handlers = logger.handlers[:]
for handler in handlers:
handler.close()
logger.removeHandler(handler)
if __name__ == "__main__":
tutux = TuxBot()
with setup_logging():
tutux.run()

250
bot.py
View file

@ -1,250 +0,0 @@
import contextlib
import datetime
import json
import logging
import sys
from collections import deque, Counter
from typing import List
import aiohttp
import discord
import git
import sqlalchemy
from discord.ext import commands
from utils.functions import Config
from utils.functions import Texts
from utils.functions import Version
from utils.functions import ContextPlus
from utils.models import metadata, database
description = """
Je suis TuxBot, le bot qui vit de l'OpenSource ! ;)
"""
build = git.Repo(search_parent_directories=True).head.object.hexsha
version = (2, 0, 0)
log = logging.getLogger(__name__)
l_extensions: List[str] = [
'cogs.Admin',
'cogs.API',
'cogs.Help',
'cogs.Logs',
# 'cogs.Monitoring',
'cogs.Poll',
'cogs.Useful',
'cogs.User',
'jishaku',
]
async def _prefix_callable(bot, message: discord.message) -> list:
<<<<<<< HEAD
extras = [bot.cluster.get('Name') + '.']
if message.guild is not None:
if str(message.guild.id) in bot.prefixes:
extras.extend(
bot.prefixes.get(str(message.guild.id), "prefixes").split(
bot.config.get("misc", "Separator")
)
)
=======
try:
with open(f'./configs/guilds/{message.guild.id}.json', 'r') as f:
data = json.load(f)
custom_prefix = data['prefixes']
except FileNotFoundError:
custom_prefix = ['']
extras = [bot.cluster.get('Name') + '.']
extras.extend(custom_prefix)
>>>>>>> cce7bb409303e9ad27ef4e5617d0bc9068810f13
return commands.when_mentioned_or(*extras)(bot, message)
class TuxBot(commands.AutoShardedBot):
def __init__(self, ):
super().__init__(command_prefix=_prefix_callable, pm_help=None,
help_command=None, description=description,
help_attrs=dict(hidden=True),
activity=discord.Game(
name=Texts().get('Starting...'))
)
self.socket_stats = Counter()
self.command_stats = Counter()
self.config = Config('./configs/config.cfg')
self.blacklist = Config('./configs/blacklist.cfg')
self.fallbacks = Config('./configs/fallbacks.cfg')
self.cluster = self.fallbacks.find('True', key='This', first=True)
self.uptime: datetime = datetime.datetime.utcnow()
self._prev_events = deque(maxlen=10)
self.session = aiohttp.ClientSession(loop=self.loop)
self.database, self.metadata = database, metadata
self.engine = sqlalchemy.create_engine(str(self.database.url))
self.metadata.create_all(self.engine)
self.version = Version(*version, pre_release='rc2', build=build)
self.owners_id = [int(owner_id) for owner_id in self.config.get('permissions', 'Owners').split(', ')]
self.owner_id = int(self.owners_id[0])
for extension in l_extensions:
try:
self.load_extension(extension)
print(Texts().get("Extension loaded successfully : ")
+ extension)
log.info(Texts().get("Extension loaded successfully : ")
+ extension)
except Exception as e:
print(Texts().get("Failed to load extension : ")
+ extension, file=sys.stderr)
print(e)
log.error(Texts().get("Failed to load extension : ")
+ extension, exc_info=e)
@property
def owner(self):
return self.get_user(self.owner_id)
@property
def owners(self):
return [self.get_user(owner_id) for owner_id in self.owners_id]
async def is_owner(self, user: discord.User) -> bool:
return user in self.owners
async def get_context(self, message, *, cls=None):
return await super().get_context(message, cls=cls or ContextPlus)
async def on_socket_response(self, msg):
self._prev_events.append(msg)
async def on_command_error(self, ctx: discord.ext.commands.Context, error):
if isinstance(error, commands.NoPrivateMessage):
await ctx.author.send(
Texts().get("This command cannot be used in private messages.")
)
elif isinstance(error, commands.DisabledCommand):
await ctx.author.send(
Texts().get(
"Sorry. This command is disabled and cannot be used."
)
)
elif isinstance(error, commands.CommandOnCooldown):
await ctx.send(str(error))
async def process_commands(self, message: discord.message):
ctx: commands.Context = await self.get_context(message)
if ctx.command is None:
return
await self.invoke(ctx)
async def on_message(self, message: discord.message):
if message.author.id in self.blacklist \
or (message.guild is not None
and message.guild.id in self.blacklist):
return
if message.author.bot and message.author.id != int(
self.config.get('bot', 'Tester')):
return
await self.process_commands(message)
async def on_ready(self):
if not hasattr(self, 'uptime'):
self.uptime = datetime.datetime.utcnow()
print('-' * 60)
print(Texts().get("Ready:") + f' {self.user} (ID: {self.user.id})')
print(self.version)
presence: dict = dict(status=discord.Status.dnd)
if self.config.get("bot", "Activity", fallback=None) is not None:
presence.update(
activity=discord.Game(
name=self.config.get("bot", "Activity")
)
)
print(f"Discord.py: {discord.__version__}")
print(f"Server: {self.cluster.get('Name')}")
print('-' * 60)
await self.change_presence(**presence)
@staticmethod
async def on_resumed():
print('resumed...')
@property
def logs_webhook(self) -> discord.Webhook:
webhook_config = self.config["webhook"]
webhook = discord.Webhook.partial(
id=webhook_config.get('ID'),
token=webhook_config.get('Token'),
adapter=discord.AsyncWebhookAdapter(
self.session
)
)
return webhook
async def close(self):
extensions = self.extensions.copy()
for extension in extensions:
self.unload_extension(extension)
await super().close()
await self.session.close()
def run(self):
super().run(self.config.get("bot", "Token"), reconnect=True)
@contextlib.contextmanager
def setup_logging():
logging.getLogger('discord').setLevel(logging.INFO)
logging.getLogger('discord.http').setLevel(logging.WARNING)
log = logging.getLogger()
log.setLevel(logging.INFO)
try:
handler = logging.FileHandler(filename='logs/tuxbot.log',
encoding='utf-8', mode='w')
fmt = logging.Formatter('[{levelname:<7}] [{asctime}]'
' {name}: {message}',
'%Y-%m-%d %H:%M:%S', style='{')
handler.setFormatter(fmt)
log.addHandler(handler)
yield
finally:
handlers = log.handlers[:]
for handler in handlers:
handler.close()
log.removeHandler(handler)
if __name__ == "__main__":
print(Texts().get('Starting...'))
app = TuxBot()
try:
with setup_logging():
app.run()
except KeyboardInterrupt:
app.close()

View file

@ -1,56 +0,0 @@
import logging
import discord
from aiohttp import web
from discord.ext import commands
from bot import TuxBot
log = logging.getLogger(__name__)
class API(commands.Cog):
def __init__(self, bot: TuxBot):
self.bot = bot
self.site = web.TCPSite
app = web.Application()
app.add_routes([web.get('/users/{user_id}', self.users)])
self.runner = web.AppRunner(app)
self.bot.loop.create_task(self.start_HTTPMonitoring_server())
async def start_HTTPMonitoring_server(self):
host = self.bot.config.get('API', 'Host')
port = self.bot.config.get('API', 'Port')
print(f"Starting API server on {host}:{port}")
await self.runner.setup()
self.site = web.TCPSite(self.runner, host, port)
await self.site.start()
async def users(self, request):
try:
user = await self.bot.fetch_user(request.match_info['user_id'])
except discord.NotFound:
return web.Response(status=404)
json = {
'id': user.id,
'username': user.name,
'discriminator': user.discriminator,
'avatar': user.avatar,
'default_avatar': user.default_avatar.value,
'bot': user.bot,
'system': user.system,
}
return web.json_response(
json
)
def setup(bot: TuxBot):
bot.add_cog(API(bot))

View file

@ -1,534 +0,0 @@
import asyncio
import datetime
import logging
from typing import Union
import discord
import humanize
from discord.ext import commands
from bot import TuxBot
from utils import Texts
from utils.models import WarnModel
from utils import command_extra, group_extra
log = logging.getLogger(__name__)
class Admin(commands.Cog):
def __init__(self, bot: TuxBot):
self.bot = bot
self.icon = ":shield:"
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/160/twitter/233/shield_1f6e1.png"
async def cog_check(self, ctx: commands.Context) -> bool:
permissions: discord.Permissions = ctx.channel.permissions_for(
ctx.author)
has_permission = permissions.administrator
is_owner = await self.bot.is_owner(ctx.author)
return has_permission or is_owner
@staticmethod
async def kick_ban_message(ctx: commands.Context,
**kwargs) -> discord.Embed:
member: discord.Member = kwargs.get('member')
reason = kwargs.get(
'reason',
Texts('admin', ctx).get("Please enter a reason")
)
if kwargs.get('type') == 'ban':
title = '**Ban** ' + str(len(await ctx.guild.bans()))
color = discord.Color.dark_red()
else:
title = '**Kick**'
color = discord.Color.red()
e = discord.Embed(
title=title,
description=reason,
timestamp=datetime.datetime.utcnow(),
color=color
)
e.set_author(
name=f'{member.name}#{member.discriminator} ({member.id})',
icon_url=member.avatar_url_as(format='jpg')
)
e.set_footer(
text=f'{ctx.author.name}#{ctx.author.discriminator}',
icon_url=ctx.author.avatar_url_as(format='png')
)
return e
###########################################################################
@group_extra(name='say', invoke_without_command=True, category='text')
async def _say(self, ctx: commands.Context, *, content: str):
if ctx.invoked_subcommand is None:
try:
await ctx.message.delete()
except discord.errors.Forbidden:
pass
await ctx.send(content)
@_say.command(name='edit')
async def _say_edit(self, ctx: commands.Context, message_id: int, *,
content: str):
try:
await ctx.message.delete()
except discord.errors.Forbidden:
pass
try:
message: discord.Message = await ctx.channel.fetch_message(
message_id)
await message.edit(content=content)
except (discord.errors.NotFound, discord.errors.Forbidden):
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
@_say.command(name='to')
async def _say_to(self, ctx: commands.Context,
channel: Union[discord.TextChannel, discord.User], *,
content):
try:
await ctx.message.delete()
except discord.errors.Forbidden:
pass
await channel.send(content)
###########################################################################
@command_extra(name='ban', category='administration')
async def _ban(self, ctx: commands.Context, user: discord.Member, *,
reason=""):
try:
member: discord.Member = await ctx.guild.fetch_member(user.id)
try:
await member.ban(reason=reason)
e: discord.Embed = await self.kick_ban_message(
ctx,
member=member,
type='ban',
reason=reason
)
await ctx.send(embed=e)
except discord.Forbidden:
await ctx.send(
Texts('admin', ctx).get("Unable to ban this user"),
delete_after=5)
except discord.errors.NotFound:
await ctx.send(
Texts('utils', ctx).get("Unable to find the user..."),
delete_after=5)
###########################################################################
@command_extra(name='kick', category='administration')
async def _kick(self, ctx: commands.Context, user: discord.Member, *,
reason=""):
try:
member: discord.Member = await ctx.guild.fetch_member(user.id)
try:
await member.kick(reason=reason)
e: discord.Embed = await self.kick_ban_message(
ctx,
member=member,
type='kick',
reason=reason
)
await ctx.send(embed=e)
except discord.Forbidden:
await ctx.send(
Texts('admin', ctx).get("Unable to kick this user"),
delete_after=5)
except discord.errors.NotFound:
await ctx.send(
Texts('utils', ctx).get("Unable to find the user..."),
delete_after=5)
###########################################################################
@command_extra(name='clear', category='text')
async def _clear(self, ctx: commands.Context, count: int):
try:
await ctx.message.delete()
await ctx.channel.purge(limit=count)
except discord.errors.Forbidden:
pass
###########################################################################
@group_extra(name='react', category='text')
async def _react(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
await ctx.send_help('react')
@_react.command(name='add')
async def _react_add(self, ctx: commands.Context, message_id: int, *,
emojis: str):
emojis: list = emojis.split(' ')
try:
message: discord.Message = await ctx.channel.fetch_message(
message_id)
for emoji in emojis:
await message.add_reaction(emoji)
except discord.errors.NotFound:
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
@_react.command(name='remove', aliases=['clear'])
async def _react_remove(self, ctx: commands.Context, message_id: int):
try:
message: discord.Message = await ctx.channel.fetch_message(
message_id)
await message.clear_reactions()
except discord.errors.NotFound:
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
###########################################################################
@group_extra(name='delete', invoke_without_command=True, category='text')
async def _delete(self, ctx: commands.Context, message_id: int):
try:
await ctx.message.delete()
except discord.errors.Forbidden:
pass
try:
message: discord.Message = await ctx.channel.fetch_message(
message_id)
await message.delete()
except (discord.errors.NotFound, discord.errors.Forbidden):
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
@_delete.command(name='from', aliases=['to', 'in'])
async def _delete_from(self, ctx: commands.Context,
channel: discord.TextChannel, message_id: int):
try:
await ctx.message.delete()
except discord.errors.Forbidden:
pass
try:
message: discord.Message = await channel.fetch_message(
message_id
)
await message.delete()
except (discord.errors.NotFound, discord.errors.Forbidden):
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5
)
###########################################################################
async def get_warn(self, ctx: commands.Context,
member: discord.Member = False):
await ctx.trigger_typing()
if member:
warns = WarnModel.objects.filter(
server_id=str(ctx.guild.id),
user_id=member.id
)
else:
warns = WarnModel.objects.filter(
server_id=str(ctx.guild.id)
)
warns_list = ''
for warn in await warns.all():
row_id = warn.id
user_id = warn.user_id
user = await self.bot.fetch_user(user_id)
reason = warn.reason
ago = humanize.naturaldelta(
datetime.datetime.now() - warn.created_at
)
warns_list += f"[{row_id}] **{user}**: `{reason}` *({ago} ago)*\n"
return warns_list, warns
async def add_warn(self, ctx: commands.Context, member: discord.Member,
reason):
now = datetime.datetime.now()
warn = WarnModel(server_id=ctx.guild.id, user_id=member.id,
reason=reason,
created_at=now)
self.bot.database.session.add(warn)
self.bot.database.session.commit()
@group_extra(name='warn', aliases=['warns'], category='administration')
async def _warn(self, ctx: commands.Context):
await ctx.trigger_typing()
if ctx.invoked_subcommand is None:
warns_list, warns = await self.get_warn(ctx)
e = discord.Embed(
title=f"{warns.count()} {Texts('admin', ctx).get('last warns')}: ",
description=warns_list
)
await ctx.send(embed=e)
@_warn.command(name='add', aliases=['new'])
async def _warn_add(self, ctx: commands.Context, member: discord.Member,
*, reason="N/A"):
if not member:
return await ctx.send(
Texts('utils', ctx).get("Unable to find the user...")
)
def check(pld: discord.RawReactionActionEvent):
if pld.message_id != choice.id \
or pld.user_id != ctx.author.id:
return False
return pld.emoji.name in ('1⃣', '2⃣', '3⃣')
warns_list, warns = await self.get_warn(ctx, member)
if warns.count() >= 2:
e = discord.Embed(
title=Texts('admin', ctx).get('More than 2 warns'),
description=f"{member.mention} "
+ Texts('admin', ctx).get('has more than 2 warns')
)
e.add_field(
name='__Actions__',
value=':one: kick\n'
':two: ban\n'
':three: ' + Texts('admin', ctx).get('ignore')
)
choice = await ctx.send(embed=e)
for reaction in ('1⃣', '2⃣', '3⃣'):
await choice.add_reaction(reaction)
try:
payload = await self.bot.wait_for(
'raw_reaction_add',
check=check,
timeout=50.0
)
except asyncio.TimeoutError:
return await ctx.send(
Texts('admin', ctx).get('Took too long. Aborting.')
)
finally:
await choice.delete()
if payload.emoji.name == '1⃣':
from jishaku.models import copy_context_with
alt_ctx = await copy_context_with(
ctx,
content=f"{ctx.prefix}"
f"kick "
f"{member} "
f"{Texts('admin', ctx).get('More than 2 warns')}"
)
return await alt_ctx.command.invoke(alt_ctx)
elif payload.emoji.name == '2⃣':
from jishaku.models import copy_context_with
alt_ctx = await copy_context_with(
ctx,
content=f"{ctx.prefix}"
f"ban "
f"{member} "
f"{Texts('admin', ctx).get('More than 2 warns')}"
)
return await alt_ctx.command.invoke(alt_ctx)
await self.add_warn(ctx, member, reason)
await ctx.send(
content=f"{member.mention} "
f"**{Texts('admin', ctx).get('got a warn')}**"
f"\n**{Texts('admin', ctx).get('Reason')}:** `{reason}`"
)
@_warn.command(name='remove', aliases=['revoke', 'del', 'delete'])
async def _warn_remove(self, ctx: commands.Context, warn_id: int):
warn = self.bot.database.session \
.query(WarnModel) \
.filter(WarnModel.id == warn_id) \
.one()
self.bot.database.session.delete(warn)
await ctx.send(f"{Texts('admin', ctx).get('Warn with id')} `{warn_id}`"
f" {Texts('admin', ctx).get('successfully removed')}")
@_warn.command(name='show', aliases=['list', 'all'])
async def _warn_show(self, ctx: commands.Context, member: discord.Member):
warns_list, warns = await self.get_warn(ctx, member)
e = discord.Embed(
title=f"{warns.count()} {Texts('admin', ctx).get('last warns')}: ",
description=warns_list
)
await ctx.send(embed=e)
@_warn.command(name='edit', aliases=['change', 'modify'])
async def _warn_edit(self, ctx: commands.Context, warn_id: int, *, reason):
warn = self.bot.database.session \
.query(WarnModel) \
.filter(WarnModel.id == warn_id) \
.one()
warn.reason = reason
self.bot.database.session.commit()
await ctx.send(f"{Texts('admin', ctx).get('Warn with id')} `{warn_id}`"
f" {Texts('admin', ctx).get('successfully edited')}")
###########################################################################
@command_extra(name='language', aliases=['lang', 'langue', 'langage'], category='server')
async def _language(self, ctx: commands.Context, locale: str):
available = self.bot.database.session \
.query(LangModel.value) \
.filter(LangModel.key == 'available') \
.first()[0] \
.split(',')
if locale.lower() not in available:
await ctx.send(
Texts('admin', ctx).get('Unable to find this language'))
else:
current = self.bot.database.session \
.query(LangModel) \
.filter(LangModel.key == str(ctx.guild.id))
if current.count() > 0:
current = current.one()
current.value = locale.lower()
self.bot.database.session.commit()
else:
new_row = LangModel(key=str(ctx.guild.id),
value=locale.lower())
self.bot.database.session.add(new_row)
self.bot.database.session.commit()
await ctx.send(
Texts('admin', ctx).get('Language changed successfully'))
###########################################################################
@group_extra(name='prefix', aliases=['prefixes'], category='server')
async def _prefix(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
await ctx.send_help('prefix')
@_prefix.command(name='add', aliases=['set', 'new'])
async def _prefix_add(self, ctx: commands.Context, prefix: str):
if str(ctx.guild.id) in self.bot.prefixes:
prefixes = self.bot.prefixes.get(
str(ctx.guild.id), "prefixes"
).split(
self.bot.config.get("misc", "separator")
)
if prefix in prefixes:
return await ctx.send(
Texts('admin', ctx).get('This prefix already exists')
)
else:
prefixes.append(prefix)
self.bot.prefixes.set(
str(ctx.guild.id),
"prefixes",
self.bot.config.get("misc", "separator")
.join(prefixes)
)
with open('./configs/prefixes.cfg', 'w') as configfile:
self.bot.prefixes.write(configfile)
else:
self.bot.prefixes.add_section(str(ctx.guild.id))
self.bot.prefixes.set(str(ctx.guild.id), "prefixes", prefix)
with open('./configs/prefixes.cfg', 'w') as configfile:
self.bot.prefixes.write(configfile)
await ctx.send(
Texts('admin', ctx).get('Prefix added successfully')
)
@_prefix.command(name='remove', aliases=['drop', 'del', 'delete'])
async def _prefix_remove(self, ctx: commands.Context, prefix: str):
if str(ctx.guild.id) in self.bot.prefixes:
prefixes = self.bot.prefixes.get(
str(ctx.guild.id), "prefixes"
).split(
self.bot.config.get("misc", "separator")
)
if prefix in prefixes:
prefixes.remove(prefix)
self.bot.prefixes.set(
str(ctx.guild.id),
"prefixes",
self.bot.config.get("misc", "separator")
.join(prefixes)
)
with open('./configs/prefixes.cfg', 'w') as configfile:
self.bot.prefixes.write(configfile)
return await ctx.send(
Texts('admin', ctx).get('Prefix removed successfully')
)
await ctx.send(
Texts('admin', ctx).get('This prefix does not exist')
)
@_prefix.command(name='list', aliases=['show', 'all'])
async def _prefix_list(self, ctx: commands.Context):
extras = ['.']
if ctx.message.guild is not None:
extras = []
if str(ctx.message.guild.id) in self.bot.prefixes:
extras.extend(
self.bot.prefixes.get(str(ctx.message.guild.id),
"prefixes").split(
self.bot.config.get("misc", "separator")
)
)
prefixes = [self.bot.user.mention]
prefixes.extend(extras)
if len(prefixes) <= 1:
text = Texts('admin', ctx) \
.get('The only prefix for this guild is :\n')
else:
text = Texts('admin', ctx) \
.get('Available prefixes for this guild are :\n')
await ctx.send(text + "\n".join(prefixes))
def setup(bot: TuxBot):
bot.add_cog(Admin(bot))

View file

@ -1,227 +0,0 @@
# Created by romain at 04/01/2020
import logging
import discord
from discord.ext import commands
from bot import TuxBot
from utils import Texts, GroupPlus
from utils import FieldPages
log = logging.getLogger(__name__)
class HelpCommand(commands.HelpCommand):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ignore_cogs = ["Monitoring", "Help", "Jishaku"]
self.owner_cogs = []
self.admin_cogs = ["Admin"]
def command_formatting(self, e, command):
prefix = self.context.prefix \
if str(self.context.bot.user.id) not in self.context.prefix \
else f"@{self.context.bot.user.name}"
file = Texts(command.cog.qualified_name.lower() + '_help', self.context)
if command.parent is not None:
description = file.get(f"_{command.parent}_{command.name}")
usage = file.get(f"_{command.parent}_{command.name}__usage")
else:
description = file.get(f"_{command.name}")
usage = file.get(f"_{command.name}__usage")
e.title = self.get_command_signature(command) + usage
e.description = description
e.add_field(
name=Texts(
'help', self.context
).get(
'command_help.params'
),
value=usage
)
e.add_field(
name=Texts(
'help', self.context
).get(
'command_help.usage'
),
value=f"{prefix}{command.qualified_name} " + usage
)
aliases = "`" + '`, `'.join(command.aliases) + "`"
if aliases == "``":
aliases = Texts(
'help', self.context
).get(
'command_help.no_aliases'
)
e.add_field(
name=Texts(
'help', self.context
).get(
'command_help.aliases'
),
value=aliases
)
return e
async def send_bot_help(self, mapping):
owners = self.context.bot.owners
prefix = self.context.prefix \
if str(self.context.bot.user.id) not in self.context.prefix \
else f"@{self.context.bot.user.name} "
e = discord.Embed(
color=discord.Color.blue(),
description=Texts(
'help', self.context
).get(
'main_page.description'
)
)
e.set_author(
icon_url=self.context.author.avatar_url_as(format='png'),
name=self.context.author
)
e.set_footer(
text=Texts(
'help', self.context
).get(
'main_page.footer'
).format(
prefix
)
)
for extension in self.context.bot.cogs.values():
if self.context.author not in owners \
and extension.__class__.__name__ in self.owner_cogs:
continue
if extension.__class__.__name__ in self.ignore_cogs:
continue
count = len(extension.get_commands())
text = Texts('help', self.context).get('main_page.commands')
if count <= 1:
text = text[:-1]
e.add_field(
name=f"__{extension.icon} **{extension.qualified_name}**__",
value=f"{count} {text}"
)
await self.context.send(embed=e)
async def send_cog_help(self, cog):
pages = {}
prefix = self.context.prefix \
if str(self.context.bot.user.id) not in self.context.prefix \
else f"@{self.context.bot.user.name}"
file = Texts(cog.qualified_name.lower() + '_help', self.context)
if cog.__class__.__name__ in self.owner_cogs \
and self.context.author not in self.context.bot.owners:
return self.command_not_found(cog.qualified_name)
for cmd in cog.get_commands():
if self.context.author not in self.context.bot.owners \
and (cmd.hidden or cmd.category == "Hidden"):
continue
if cmd.category not in pages:
pages[cmd.category] = "```asciidoc\n"
pages[cmd.category] \
+= f"{cmd.name}" \
+ ' ' * int(13 - len(cmd.name)) \
+ f":: {file.get(f'_{cmd.name}__short')}\n"
if isinstance(cmd, GroupPlus):
for group_command in cmd.commands:
pages[cmd.category] \
+= f"└> {group_command.name}" \
+ ' ' * int(10 - len(group_command.name)) \
+ f":: {file.get(f'_{group_command.parent}_{group_command.name}__short')}\n"
for e in pages:
pages[e] += "```"
formatted = []
for name, cont in pages.items():
formatted.append((name, cont))
footer_text = Texts('help', self.context) \
.get('main_page.footer') \
.format(prefix)
pages = FieldPages(
self.context,
embed_color=discord.Color.blue(),
entries=formatted,
title=cog.qualified_name.upper(),
thumbnail=cog.big_icon,
footericon=self.context.bot.user.avatar_url,
footertext=footer_text,
per_page=1
)
await pages.paginate()
async def send_group_help(self, group):
if group.cog_name in self.ignore_cogs:
return await self.send_error_message(
self.command_not_found(group.name)
)
file = Texts(group.qualified_name.lower() + '_help', self.context)
formatted = self.command_formatting(
discord.Embed(color=discord.Color.blue()),
group
)
sub_command_list = "" # this is braille, please don't touch unless you know what you're doing
for group_command in group.commands:
sub_command_list += f"└> **{group_command.name}** - {file.get(f'_{group_command.parent}_{group_command.name}')}\n"
subcommands = Texts(
'help', self.context
).get(
'command_help.subcommands'
)
formatted.add_field(name=subcommands, value=sub_command_list, inline=False)
await self.context.send(embed=formatted)
async def send_command_help(self, command):
if isinstance(command, commands.Group):
return await self.send_group_help(command)
if command.cog_name in self.ignore_cogs:
return await self.send_error_message(
self.command_not_found(command.name))
formatted = self.command_formatting(
discord.Embed(color=discord.Color.blue()),
command
)
await self.context.send(embed=formatted)
def command_not_found(self, command):
return Texts(
'help', self.context
).get(
'main_page.not_found'
).format(
command
)
class Help(commands.Cog):
def __init__(self, bot: TuxBot):
bot.help_command = HelpCommand()
def setup(bot: TuxBot):
bot.add_cog(Help(bot))

180
cogs/Images.py Normal file
View file

@ -0,0 +1,180 @@
import logging
from io import BytesIO
import discord
from discord.ext import commands, flags
from app import TuxBot
from utils.functions.extra import ContextPlus
log = logging.getLogger(__name__)
class Images(commands.Cog, name="Images"):
def __init__(self, bot):
self.bot = bot
self.image_api = "http://0.0.0.0:8080"
async def _send_meme(self, ctx: ContextPlus, endpoint: str, **passed_flags):
async with ctx.typing():
url = f"{self.image_api}/{endpoint}?"
for key, val in passed_flags.items():
if val:
url += f"{key}={val}&"
async with self.bot.session.get(url) as r:
if r.status != 200:
return await ctx.send("Failed...")
data = BytesIO(await r.read())
await ctx.send(
file=discord.File(data, "output.png")
)
@commands.command(name="phcomment")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _phcomment(self, ctx: ContextPlus, user: discord.User = None, *, message: commands.clean_content(fix_channel_mentions=True, escape_markdown=True)):
async with ctx.typing():
message = message.replace("&", "%26")
if user is None:
avatar = ctx.author.avatar_url_as(format='png')
username = ctx.author.name
else:
avatar = user.avatar_url_as(format='png')
username = user.name
url = f"{self.image_api}/ph/comment" \
f"?image={avatar}" \
f"&username={username}" \
f"&message={message}"
async with self.bot.session.get(url) as r:
if r.status != 200:
return await ctx.send("Failed...")
data = BytesIO(await r.read())
await ctx.send(
file=discord.File(data, "output.png")
)
@commands.command(name="phvideo")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _phvideo(self, ctx: ContextPlus, image: str, author: discord.User, *, title: commands.clean_content(fix_channel_mentions=True, escape_markdown=True)):
async with ctx.typing():
url = f"{self.image_api}/ph/video" \
f"?image={image}" \
f"&username={author.name}" \
f"&title={title}"
async with self.bot.session.get(url) as r:
if r.status != 200:
return await ctx.send("Failed...")
data = BytesIO(await r.read())
await ctx.send(
file=discord.File(data, "output.png")
)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str)
@flags.add_flag("--text3", type=str)
@flags.command(name="balloon")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _balloon(self, ctx: ContextPlus, **passed_flags):
passed_flags["text3"] = passed_flags.get("text3")
passed_flags["text4"] = passed_flags.get("text1")
passed_flags["text5"] = passed_flags.get("text2")
await self._send_meme(ctx, 'balloon', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str)
@flags.add_flag("--text3", type=str)
@flags.command(name="butterfly")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _butterfly(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'butterfly', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str)
@flags.command(name="buttons")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _buttons(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'buttons', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.command(name="cmm")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _cmm(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'change_my_mind', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str)
@flags.command(name="drake")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _drake(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'drake', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str, default=False)
@flags.command(name="fry")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _fry(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'fry', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str, default=False)
@flags.command(name="imagination")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _imagination(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'imagination', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str, default=False)
@flags.command(name="everywhere")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _everywhere(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'everywhere', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str)
@flags.add_flag("--text3", type=str)
@flags.command(name="choice")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _choice(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'choice', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.command(name="pika")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _pika(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'pika', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str)
@flags.add_flag("--text3", type=str)
@flags.command(name="pkp")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _pkp(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'pkp', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str)
@flags.command(name="puppet")
@commands.cooldown(1, 5, commands.BucketType.user)
async def _puppet(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'puppet', **passed_flags)
@flags.add_flag("--text1", type=str)
@flags.command(name="scroll_of_truth", alias=['sot'])
@commands.cooldown(1, 5, commands.BucketType.user)
async def _sot(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'scroll_of_truth', **passed_flags)
def setup(bot: TuxBot):
cog = Images(bot)
bot.add_cog(cog)

View file

@ -18,9 +18,7 @@ import humanize
import psutil
from discord.ext import commands, tasks
from bot import TuxBot
from utils import Texts
from utils import command_extra
from app import TuxBot
log = logging.getLogger(__name__)
@ -52,9 +50,6 @@ class Logs(commands.Cog):
self._resumes = []
self._identifies = defaultdict(list)
self.icon = ":newspaper:"
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/newspaper_1f4f0.png"
def _clear_gateway_data(self):
one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
to_remove = [
@ -112,8 +107,19 @@ class Logs(commands.Cog):
self.bot.socket_stats[msg.get('t')] += 1
@property
def webhook(self):
return self.bot.logs_webhook
def logs(self):
webhooks = {}
for key, value in self.bot.logs_channels.items():
webhooks[key] = discord.Webhook.partial(
id=value.get('webhook')['id'],
token=value.get('webhook')['token'],
adapter=discord.AsyncWebhookAdapter(
self.bot.session
)
)
return webhooks
async def log_error(self, *, ctx=None, extra=None):
e = discord.Embed(title='Error', colour=0xdd5f53)
@ -131,7 +137,7 @@ class Logs(commands.Cog):
e.add_field(name='Channel', value=channel)
e.add_field(name='Guild', value=guild)
await self.webhook.send(embed=e)
await self.logs.get('errors').send(embed=e)
async def send_guild_stats(self, e, guild):
e.add_field(name='Name', value=guild.name)
@ -155,7 +161,7 @@ class Logs(commands.Cog):
if guild.me:
e.timestamp = guild.me.joined_at
await self.webhook.send(embed=e)
await self.logs.get('guilds').send(embed=e)
@commands.Cog.listener()
async def on_guild_join(self, guild: discord.guild):
@ -169,17 +175,37 @@ class Logs(commands.Cog):
@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')
ctx = await self.bot.get_context(message)
if ctx.valid:
return
if isinstance(message.channel, discord.DMChannel):
if message.author is self.bot.user:
e = discord.Embed(
title=f"DM to: {message.channel.recipient}",
description=message.content,
color=0x39e326
)
e.description = message.content
if len(message.attachments) > 0:
e.set_image(url=message.attachments[0].url)
e.set_footer(text=f"User ID: {message.author.id}")
await self.webhook.send(embed=e)
else:
e = discord.Embed(
title="New DM:",
description=message.content,
color=0x0A97F5
)
e.set_author(
name=message.channel.recipient,
icon_url=message.channel.recipient.avatar_url_as(format="png")
)
if message.attachments:
attachment_url = message.attachments[0].url
e.set_image(url=attachment_url)
e.set_footer(
text=f"User ID: {message.channel.recipient.id}"
)
await self.logs["dm"].send(embed=e)
@commands.Cog.listener()
async def on_command_error(self, ctx, error):
@ -212,7 +238,7 @@ class Logs(commands.Cog):
)
e.description = f'```py\n{exc}\n```'
e.timestamp = datetime.datetime.utcnow()
await self.webhook.send(embed=e)
await self.logs.get('errors').send(embed=e)
@commands.Cog.listener()
async def on_socket_raw_send(self, data):
@ -241,9 +267,9 @@ class Logs(commands.Cog):
emoji = types.get(record.levelname, ':heavy_multiplication_x:')
dt = datetime.datetime.utcfromtimestamp(record.created)
msg = f'{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`'
await self.webhook.send(msg)
await self.logs.get('gateway').send(msg)
@command_extra(name='commandstats', hidden=True, category='misc')
@commands.command('commandstats')
@commands.is_owner()
async def _commandstats(self, ctx, limit=20):
counter = self.bot.command_stats
@ -258,7 +284,7 @@ class Logs(commands.Cog):
await ctx.send(f'```\n{output}\n```')
@command_extra(name='socketstats', hidden=True, category='misc')
@commands.command('socketstats')
@commands.is_owner()
async def _socketstats(self, ctx):
delta = datetime.datetime.utcnow() - self.bot.uptime
@ -268,7 +294,7 @@ class Logs(commands.Cog):
await ctx.send(
f'{total} socket events observed ({cpm:.2f}/minute):\n{self.bot.socket_stats}')
@command_extra(name='uptime', category='misc')
@commands.command('uptime')
async def _uptime(self, ctx):
uptime = humanize.naturaltime(
datetime.datetime.utcnow() - self.bot.uptime)
@ -287,7 +313,7 @@ async def on_error(self, event, *args):
args_str.append('```')
e.add_field(name='Args', value='\n'.join(args_str), inline=False)
hook = self.get_cog('Logs').webhook
hook = self.get_cog('Logs').logs.get('errors')
try:
await hook.send(embed=e)
except (discord.HTTPException, discord.NotFound,

View file

@ -1,110 +0,0 @@
import logging
import urllib.request
from datetime import datetime
import discord
from aiohttp import web
from discord.ext import tasks, commands
from bot import TuxBot
log = logging.getLogger(__name__)
class Monitoring(commands.Cog):
def __init__(self, bot: TuxBot):
self.bot = bot
self.site = web.TCPSite
self.ping_clusters.start()
app = web.Application()
app.add_routes([web.get('/', self.handle)])
self.runner = web.AppRunner(app)
self.bot.loop.create_task(self.start_HTTPMonitoring_server())
def cog_unload(self):
self.ping_clusters.stop()
@tasks.loop(seconds=10.0)
async def ping_clusters(self):
for cluster in self.bot.fallbacks:
if cluster == 'DEFAULT':
pass
else:
cluster = self.bot.fallbacks[cluster]
if not cluster.get('This', False):
host = cluster.get('Host')
port = cluster.get('Port')
try:
req = urllib.request.urlopen(
f"http://{host}:{port}",
timeout=2
)
except Exception:
global_channel = await self.bot.fetch_channel(
661347412463321098
)
e = discord.Embed(
title=f"Server `{cluster.get('Name')}`",
color=discord.colour.Color.red(),
description=f"Server **`{cluster.get('Name')}`** with address **`http://{host}:{port}`** is down ! ",
timestamp=datetime.now()
)
e.set_thumbnail(
url='https://upload.wikimedia.org/wikipedia/commons/7/75/Erroricon404.PNG'
)
await global_channel.send(embed=e)
else:
print(req.read().decode())
@ping_clusters.before_loop
async def before_pinging(self):
await self.bot.wait_until_ready()
cluster = self.bot.cluster
host = cluster.get('Host')
port = cluster.get('Port')
global_channel = await self.bot.fetch_channel(
661347412463321098
)
e = discord.Embed(
title=f"Server `{cluster.get('Name')}`",
color=discord.colour.Color.green(),
description=f"Server **`{cluster.get('Name')}`** with address **`http://{host}:{port}`** is started ! ",
timestamp=datetime.now()
)
e.set_thumbnail(
url='https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/MW-Icon-CheckMark.svg/1024px-MW-Icon-CheckMark.svg.png'
)
await global_channel.send(embed=e)
async def start_HTTPMonitoring_server(self):
host = self.bot.cluster.get('WebPage')
port = self.bot.cluster.get('Port')
print(f"Starting HTTP Monitoring server on {host}:{port}")
await self.runner.setup()
self.site = web.TCPSite(self.runner, host, port)
await self.site.start()
async def handle(self, _):
return web.json_response(
{
'message': "I'm alive !",
'ws': self.bot.latency * 1000
}
)
def setup(bot: TuxBot):
bot.add_cog(Monitoring(bot))

View file

@ -1,222 +0,0 @@
import json
import logging
from typing import Union
import discord
from discord.ext import commands
from yarl import URL
from bot import TuxBot
from utils import PollModel, ResponsesModel
from utils import Texts
from utils.functions import emotes as utils_emotes
from utils import group_extra
log = logging.getLogger(__name__)
class Poll(commands.Cog):
def __init__(self, bot: TuxBot):
self.bot = bot
self.icon = ":bar_chart:"
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/bar-chart_1f4ca.png:"
def get_poll(self, pld) -> Union[bool, PollModel]:
if pld.user_id != self.bot.user.id:
poll = self.bot.database.session \
.query(PollModel) \
.filter(PollModel.message_id == pld.message_id)
if poll.count() > 0:
poll = poll.one()
emotes = utils_emotes.get(poll.available_choices)
if pld.emoji.name in emotes:
return poll
return False
async def remove_reaction(self, pld):
channel: discord.TextChannel = self.bot.get_channel(pld.channel_id)
message: discord.Message = await channel.fetch_message(pld.message_id)
user: discord.User = await self.bot.fetch_user(pld.user_id)
await message.remove_reaction(pld.emoji.name, user)
@commands.Cog.listener()
async def on_raw_reaction_add(self, pld: discord.RawReactionActionEvent):
poll = self.get_poll(pld)
if poll:
if poll.is_anonymous:
try:
await self.remove_reaction(pld)
except discord.errors.Forbidden:
pass
choice = utils_emotes.get_index(pld.emoji.name)
responses = self.bot.database.session.query(ResponsesModel) \
.filter(
ResponsesModel.poll_id == poll.id,
ResponsesModel.user == pld.user_id,
ResponsesModel.choice == choice
)
if responses.count() != 0:
response = responses.first()
self.bot.database.session.delete(response)
self.bot.database.session.commit()
else:
response = ResponsesModel(
user=pld.user_id,
poll_id=poll.id,
choice=choice
)
self.bot.database.session.add(response)
self.bot.database.session.commit()
await self.update_poll(poll.id)
@commands.Cog.listener()
async def on_raw_reaction_remove(self,
pld: discord.RawReactionActionEvent):
poll = self.get_poll(pld)
if poll:
choice = utils_emotes.get_index(pld.emoji.name)
responses = self.bot.database.session.query(ResponsesModel) \
.filter(
ResponsesModel.poll_id == poll.id,
ResponsesModel.user == pld.user_id,
ResponsesModel.choice == choice
)
if responses.count() != 0:
response = responses.first()
self.bot.database.session.delete(response)
self.bot.database.session.commit()
await self.update_poll(poll.id)
###########################################################################
async def create_poll(self, ctx: commands.Context, poll: str, anonymous):
question = (poll.split('|')[0]).strip()
responses = [response.strip() for response in poll.split('|')[1:]]
emotes = utils_emotes.get(len(responses))
stmt = await ctx.send(Texts('poll', ctx).get('**Preparation...**'))
poll_row = PollModel()
self.bot.database.session.add(poll_row)
self.bot.database.session.flush()
e = discord.Embed(description=f"**{question}**")
e.set_author(
name=ctx.author,
icon_url="https://cdn.gnous.eu/tuxbot/survey1.png"
)
for i, response in enumerate(responses):
e.add_field(
name=f"__{emotes[i]}` - {response.capitalize()}`__",
value="**0** vote"
)
e.set_footer(text=f"ID: #{poll_row.id}")
poll_row.channel_id = stmt.channel.id
poll_row.message_id = stmt.id
poll_row.content = e.to_dict()
poll_row.is_anonymous = anonymous
poll_row.available_choices = len(responses)
self.bot.database.session.commit()
await stmt.edit(content='', embed=e)
for emote in range(len(responses)):
await stmt.add_reaction(emotes[emote])
async def update_poll(self, poll_id: int):
poll = self.bot.database.session \
.query(PollModel) \
.filter(PollModel.id == poll_id) \
.one()
channel: discord.TextChannel = self.bot.get_channel(poll.channel_id)
message: discord.Message = await channel.fetch_message(poll.message_id)
chart_base_url = "https://quickchart.io/chart?backgroundColor=white&c="
chart_options = {
'type': 'pie',
'data': {
'labels': [],
'datasets': [
{
'data': []
}
]
}
}
content = json.loads(poll.content) \
if isinstance(poll.content, str) \
else poll.content
raw_responses = self.bot.database.session \
.query(ResponsesModel) \
.filter(ResponsesModel.poll_id == poll_id)
responses = {}
for response in raw_responses.all():
if responses.get(response.choice):
responses[response.choice] += 1
else:
responses[response.choice] = 1
for i, field in enumerate(content.get('fields')):
responders = responses.get(i, 0)
chart_options.get('data') \
.get('labels') \
.append(field.get('name')[5:].replace('__', ''))
chart_options.get('data') \
.get('datasets')[0] \
.get('data') \
.append(responders)
if responders <= 1:
field['value'] = f"**{responders}** vote"
else:
field['value'] = f"**{responders}** votes"
e = discord.Embed(description=content.get('description'))
e.set_author(
name=content.get('author').get('name'),
icon_url=content.get('author').get('icon_url')
)
chart_url = URL(chart_base_url + json.dumps(chart_options))
e.set_thumbnail(url=str(chart_url))
for field in content.get('fields'):
e.add_field(
name=field.get('name'),
value=field.get('value'),
inline=True
)
e.set_footer(text=content.get('footer').get('text'))
await message.edit(embed=e)
poll.content = json.dumps(content)
self.bot.database.session.commit()
@group_extra(name='poll', aliases=['sondage'], category='poll')
async def _poll(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
await ctx.send_help('poll')
@_poll.group(name='create', aliases=['new', 'nouveau'])
async def _poll_create(self, ctx: commands.Context, *, poll: str):
is_anonymous = '--anonyme' in poll
poll = poll.replace('--anonyme', '')
await self.create_poll(ctx, poll, anonymous=is_anonymous)
def setup(bot: TuxBot):
bot.add_cog(Poll(bot))

View file

@ -1,410 +0,0 @@
# Created by romain at 04/01/2020
import logging
import os
import pathlib
import platform
import random
import re
import socket
import time
from socket import AF_INET6
from io import BytesIO
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from PIL import ImageOps
import aiohttp
import discord
import humanize
import psutil
from discord.ext import commands
from tcp_latency import measure_latency
from bot import TuxBot
from utils import Texts
from utils import command_extra, group_extra
log = logging.getLogger(__name__)
class Useful(commands.Cog):
def __init__(self, bot: TuxBot):
self.bot = bot
self.icon = ":toolbox:"
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/toolbox_1f9f0.png"
@staticmethod
def _latest_commits():
cmd = 'git log -n 3 -s --format="[\`%h\`](https://git.gnous.eu/gnouseu/tuxbot-bot/commits/%H) %s (%cr)"'
return os.popen(cmd).read().strip()
@staticmethod
def fetch_info():
total_lines = 0
total_python_lines = 0
file_amount = 0
python_file_amount = 0
ENV = "env"
for path, _, files in os.walk("."):
for name in files:
file_dir = str(pathlib.PurePath(path, name))
if (
not name.endswith(".py")
and not name.endswith(".po")
and not name.endswith(".json")
) or ENV in file_dir:
continue
file_amount += 1
python_file_amount += 1 if name.endswith(".py") else 0
with open(file_dir, "r", encoding="utf-8") as file:
for line in file:
if not line.strip().startswith("#") \
or not line.strip():
total_lines += 1
total_python_lines += 1 if name.endswith(".py") \
else 0
return (file_amount, total_lines), (
python_file_amount, total_python_lines)
@staticmethod
def luhn_checker(number: int):
digits = [int(x) for x in reversed(str(number))]
for index, digit in enumerate(digits, start=1):
digit = digit * 2 if index % 2 == 0 else digit
if digit >= 10:
digit = sum(int(x) for x in list(str(digit)))
digits[index - 1] = digit
return sum(digits) % 10 == 0
###########################################################################
@command_extra(name='iplocalise', category='network')
async def _iplocalise(self, ctx: commands.Context, addr, ip_type=''):
addr = re.sub(r'http(s?)://', '', addr)
addr = addr[:-1] if addr.endswith('/') else addr
await ctx.trigger_typing()
try:
if 'v6' in ip_type:
try:
ip = socket.getaddrinfo(addr, None, AF_INET6)[1][4][0]
except socket.gaierror:
return await ctx.send(
Texts('useful', ctx).get('ipv6 not available'))
else:
ip = socket.gethostbyname(addr)
async with self.bot.session.get(f"http://ip-api.com/json/{ip}") \
as s:
response: dict = await s.json()
if response.get('status') == 'success':
e = discord.Embed(
title=f"{Texts('useful', ctx).get('Information for')}"
f" ``{addr}`` *`({response.get('query')})`*",
color=0x5858d7
)
e.add_field(
name=Texts('useful', ctx).get('Belongs to :'),
value=response['org'] if response['org'] else 'N/A',
inline=False
)
e.add_field(
name=Texts('useful', ctx).get('Is located at :'),
value=response['city'] if response['city'] else 'N/A',
inline=True
)
e.add_field(
name="Region :",
value=f"{response['regionName'] if response['regionName'] else 'N/A'} "
f"({response['country'] if response['country'] else 'N/A'})",
inline=True
)
e.set_thumbnail(
url=f"https://www.countryflags.io/"
f"{response.get('countryCode')}/flat/64.png")
await ctx.send(embed=e)
else:
await ctx.send(
content=f"{Texts('useful', ctx).get('info not available')}"
f"``{response['query'] if response['query'] else 'N/A'}``")
except Exception as e:
await ctx.send(e)
await ctx.send(
f"{Texts('useful', ctx).get('Cannot connect to host')} {addr}"
)
###########################################################################
@command_extra(name='getheaders', category='network')
async def _getheaders(self, ctx: commands.Context, addr: str):
if (addr.startswith('http') or addr.startswith('ftp')) is not True:
addr = f"http://{addr}"
await ctx.trigger_typing()
try:
async with self.bot.session.get(addr) as s:
e = discord.Embed(
title=f"{Texts('useful', ctx).get('Headers of')} {addr}",
color=0xd75858
)
e.add_field(name="Status", value=s.status, inline=True)
e.set_thumbnail(url=f"https://http.cat/{s.status}")
headers = dict(s.headers.items())
headers.pop('Set-Cookie', headers)
for key, value in headers.items():
e.add_field(name=key, value=value, inline=True)
await ctx.send(embed=e)
except aiohttp.ClientError:
await ctx.send(
f"{Texts('useful', ctx).get('Cannot connect to host')} {addr}"
)
###########################################################################
@command_extra(name='git', aliases=['sources', 'source', 'github'], category='misc')
async def _git(self, ctx):
e = discord.Embed(
title=Texts('useful', ctx).get('git repo'),
description=Texts('useful', ctx).get('git text'),
colour=0xE9D460
)
e.set_author(
name='Gnous',
icon_url="https://cdn.gnous.eu/logo1.png"
)
await ctx.send(embed=e)
###########################################################################
@command_extra(name='quote', category='misc')
async def _quote(self, ctx, message_id: discord.Message):
e = discord.Embed(
colour=message_id.author.colour,
description=message_id.clean_content,
timestamp=message_id.created_at
)
e.set_author(
name=message_id.author.display_name,
icon_url=message_id.author.avatar_url_as(format="jpg")
)
if len(message_id.attachments) >= 1:
e.set_image(url=message_id.attachments[0].url)
e.add_field(name="**Original**",
value=f"[Go!]({message_id.jump_url})")
e.set_footer(text="#" + message_id.channel.name)
await ctx.send(embed=e)
###########################################################################
@command_extra(name='ping', category='network')
async def _ping(self, ctx: commands.Context):
start = time.perf_counter()
await ctx.trigger_typing()
end = time.perf_counter()
latency = round(self.bot.latency * 1000, 2)
typing = round((end - start) * 1000, 2)
discordapp = measure_latency(host='discordapp.com', wait=0)[0]
e = discord.Embed(title='Ping', color=discord.Color.teal())
e.add_field(name='Websocket', value=f'{latency}ms')
e.add_field(name='Typing', value=f'{typing}ms')
e.add_field(name='discordapp.com', value=f'{discordapp}ms')
await ctx.send(embed=e)
###########################################################################
@command_extra(name='info', aliases=['about'], category='misc')
async def _info(self, ctx: commands.Context):
proc = psutil.Process()
total, python = self.fetch_info()
with proc.oneshot():
mem = proc.memory_full_info()
e = discord.Embed(
title=Texts('useful', ctx).get('Information about TuxBot'),
color=0x89C4F9)
e.add_field(
name=f"__:busts_in_silhouette: "
f"{Texts('useful', ctx).get('Development')}__",
value=f"**Romain#5117:** [git](https://git.gnous.eu/Romain)\n"
f"**Outout#4039:** [git](https://git.gnous.eu/mael)\n",
inline=True
)
e.add_field(
name="__<:python:596577462335307777> Python__",
value=f"**python** `{platform.python_version()}`\n"
f"**discord.py** `{discord.__version__}`",
inline=True
)
e.add_field(
name="__:gear: Usage__",
value=f"**{humanize.naturalsize(mem.rss)}** "
f"{Texts('useful', ctx).get('physical memory')}\n"
f"**{humanize.naturalsize(mem.vms)}** "
f"{Texts('useful', ctx).get('virtual memory')}\n",
inline=True
)
e.add_field(
name=f"__{Texts('useful', ctx).get('Servers count')}__",
value=str(len(self.bot.guilds)),
inline=True
)
e.add_field(
name=f"__{Texts('useful', ctx).get('Channels count')}__",
value=str(len([_ for _ in self.bot.get_all_channels()])),
inline=True
)
e.add_field(
name=f"__{Texts('useful', ctx).get('Members count')}__",
value=str(len([_ for _ in self.bot.get_all_members()])),
inline=True
)
e.add_field(
name=f"__:file_folder: {Texts('useful', ctx).get('Files')}__",
value=f"{total[0]} *({python[0]} <:python:596577462335307777>)*",
inline=True
)
e.add_field(
name=f"__¶ {Texts('useful', ctx).get('Lines')}__",
value=f"{total[1]} *({python[1]} <:python:596577462335307777>)*",
inline=True
)
e.add_field(
name=f"__{Texts('useful', ctx).get('Latest changes')}__",
value=self._latest_commits(),
inline=False
)
e.add_field(
name=f"__:link: {Texts('useful', ctx).get('Links')}__",
value="[tuxbot.gnous.eu](https://tuxbot.gnous.eu/) "
"| [gnous.eu](https://gnous.eu/) "
"| [git](https://git.gnous.eu/gnouseu/tuxbot-bot) "
"| [status](https://status.gnous.eu/check/154250) "
f"| [{Texts('useful', ctx).get('Invite')}](https://discordapp.com/oauth2/authorize?client_id=301062143942590465&scope=bot&permissions=268749888)",
inline=False
)
e.set_footer(text=f'version: {self.bot.version} '
f'• prefix: {ctx.prefix}')
await ctx.send(embed=e)
###########################################################################
@command_extra(name='credits', aliases=['contributors', 'authors'], category='misc')
async def _credits(self, ctx: commands.Context):
e = discord.Embed(
title=Texts('useful', ctx).get('Contributors'),
color=0x36393f
)
e.add_field(
name="**Outout#4039** ",
value="• https://git.gnous.eu/mael \n"
"• mael@gnous.eu\n"
"• [@outoutxyz](https://twitter.com/outouxyz)",
inline=True
)
e.add_field(
name="**Romain#5117** ",
value="• https://git.gnous.eu/Romain\n"
"• romain@gnous.eu",
inline=True
)
await ctx.send(embed=e)
###########################################################################
@group_extra(name='cb', aliases=['cc'], category='misc')
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def _cb(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
await ctx.send_help('cb')
@_cb.command(name='validate', aliases=['valid', 'correct'], category='misc')
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def _cb_validate(self, ctx: commands.Context, *, number: int):
valid = self.luhn_checker(number)
await ctx.send(
Texts(
'useful', ctx
).get(
'valid_credit_card'
if valid
else 'invalid_credit_card'
)
)
@_cb.command(name='generate', aliases=['new', 'get'], category='misc')
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def _cb_generate(self, ctx: commands.Context):
await ctx.channel.trigger_typing()
number = random.randint(4000_0000_0000_0000, 5999_9999_9999_9999)
while not self.luhn_checker(number):
number = random.randint(4000_0000_0000_0000, 5999_9999_9999_9999)
number = str(number)
cvv = ''.join(random.choice("abcdefghij") for _ in range(3))
with Image.open("utils/images/blank_credit_card.png") as blank:
cc_font = ImageFont.truetype('utils/fonts/credit_card.ttf', 26)
user_font = ImageFont.truetype('utils/fonts/credit_card.ttf', 20)
draw = ImageDraw.Draw(blank)
cvv_text = Image.new('L', (500, 50))
cvv_draw = ImageDraw.Draw(cvv_text)
cvv_draw.text((0, 0), cvv, font=user_font, fill=255)
cvv_rotated = cvv_text.rotate(23, expand=1)
draw.text(
(69, 510),
' '.join([number[i:i+4] for i in range(0, len(number), 4)]),
(210, 210, 210),
font=cc_font
)
draw.text(
(69, 550),
ctx.author.name.upper(),
(210, 210, 210),
font=user_font
)
blank.paste(ImageOps.colorize(cvv_rotated, (0, 0, 0), (0, 0, 0)), (470, 0), cvv_rotated)
output = BytesIO()
blank.save(output, 'png')
output.seek(0)
await ctx.send(file=discord.File(fp=output, filename="credit_card.png"))
def setup(bot: TuxBot):
bot.add_cog(Useful(bot))

View file

@ -1,64 +0,0 @@
import logging
from discord.ext import commands
from bot import TuxBot
from utils import AliasesModel
from utils import Texts
from utils import group_extra
log = logging.getLogger(__name__)
class User(commands.Cog):
def __init__(self, bot: TuxBot):
self.bot = bot
self.icon = ":bust_in_silhouette:"
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/bust-in-silhouette_1f464.png"
###########################################################################
@group_extra(name='alias', aliases=['aliases'], category='alias')
async def _alias(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
await ctx.send_help('alias')
@_alias.command(name='add', aliases=['set', 'new'])
async def _alias_add(self, ctx: commands.Context, *, user_alias: str):
is_global = False
if '--global' in user_alias:
is_global = True
user_alias.replace('--global', '')
user_alias = user_alias.split(' -> ')
if len(user_alias) != 2:
return await ctx.send_help('alias')
command = user_alias[1]
user_alias = user_alias[0]
if self.bot.get_command(command) is None:
return await ctx.send(Texts('user').get('Command not found'))
alias = AliasesModel(
user_id=ctx.author.id,
alias=user_alias,
command=command,
guild="global" if is_global else str(ctx.guild.id)
)
self.bot.database.session.add(alias)
self.bot.database.session.commit()
@_alias.command(name='remove', aliases=['drop', 'del', 'delete'])
async def _alias_remove(self, ctx: commands.Context, prefix: str):
...
@_alias.command(name='list', aliases=['show', 'all'])
async def _alias_list(self, ctx: commands.Context):
...
def setup(bot: TuxBot):
bot.add_cog(User(bot))

View file

@ -0,0 +1,5 @@
{
"channels": [],
"guilds": [],
"users": []
}

8
configs/bot/protected.py Normal file
View file

@ -0,0 +1,8 @@
from .settings import token, postgresql, logs
protected = [
token, str(list(token)),
postgresql, str(list(postgresql)),
*[channel.get('webhook').get('token') for channel in logs.values()]
]

View file

@ -0,0 +1,44 @@
token = ""
prefix = "drw."
main_guild = int
logs = {
"gateway": {
'channel': int,
'webhook': {
'id': int,
'token': ''
}
},
"dm": {
'channel': int,
'webhook': {
'id': int,
'token': ''
}
},
"mentions": {
'channel': int,
'webhook': {
'id': int,
'token': ''
}
},
"guilds": {
'channel': int,
'webhook': {
'id': int,
'token': ''
}
},
"errors": {
'channel': int,
'webhook': {
'id': int,
'token': ''
}
},
}
postgresql = 'postgres://tuxbot:tuxbot@localhost:5432/tuxbot-rewrite'

View file

@ -0,0 +1,3 @@
{
"owners": [269156684155453451]
}

View file

@ -1,24 +0,0 @@
[bot]
Token =
Tester =
Activity =
[postgresql]
Username =
Password =
Host =
DBName =
[permissions]
Owners =
[webhook]
ID =
Token =
[misc]
Separator =
[API]
Host =
Port =

View file

@ -1,18 +0,0 @@
[fr-srv01]
Host =
Name = fr-srv01
WebPage = 0.0.0.0
Port =
[rm-dev01]
This = True
Host = 127.0.0.1
Name = rm-dev01
WebPage = 0.0.0.0
Port = 3389
[rm-srv01]
Host = 127.0.0.1
Name = rm-srv01
WebPage = 0.0.0.0
Port = 3390

View file

@ -1,18 +0,0 @@
[280805240977227776]
prefixes = b1.[301062143942590465]*
[303633056944881686]
prefixes = b1.[301062143942590465]*
[373881878471770112]
prefixes = b1.
[336642139381301249]
prefixes = ba.
[274247231534792704]
prefixes = test.
[528679953399676938]
prefixes = test.

View file

@ -1,19 +0,0 @@
import sqlalchemy
from utils.models import database, metadata
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--migrate", action="store_true")
parser.add_argument("-s", "--seed", action="store_true")
args = parser.parse_args()
if args.migrate:
print("Migrate...")
engine = sqlalchemy.create_engine(str(database.url))
metadata.create_all(engine)
print("Done!")
if args.seed:
print('Seeding...')
# todo: add seeding
print("Done!")

View file

@ -1,17 +0,0 @@
#!/bin/bash
BASEDIR=$(pwd)
cd "$BASEDIR/utils/locales/en/LC_MESSAGES"
for i in *.po ; do
[[ -f "$i" ]] || continue
/usr/lib/python3.8/Tools/i18n/msgfmt.py -o "${i%.po}.mo" "${i%.po}"
done
cd "$BASEDIR/utils/locales/fr/LC_MESSAGES"
for i in *.po ; do
[[ -f "$i" ]] || continue
/usr/lib/python3.8/Tools/i18n/msgfmt.py -o "${i%.po}.mo" "${i%.po}"
done

View file

@ -1,13 +1,27 @@
requests
humanize
git+https://github.com/Rapptz/discord.py@master
jishaku
gitpython
orm
asyncpg
psycopg2
configparser
psutil
tcp_latency
yarl
pillow
aiofiles==0.5.0
aiohttp==3.6.2
aiosqlite==0.13.0
astunparse==1.6.3
async-timeout==3.0.1
asyncpg==0.20.1
attrs==19.3.0
braceexpand==0.1.5
chardet==3.0.4
ciso8601==2.1.3
discord-flags==2.1.1
discord.py==1.3.3
humanize==2.4.0
idna==2.9
import-expression==1.1.2
jishaku==1.18.2.188
mpmath==1.1.0
multidict==4.7.6
Pillow==7.1.2
psutil==5.7.0
PyPika==0.37.6
six==1.14.0
sympy==1.5.1
tortoise-orm==0.16.11
typing-extensions==3.7.4.2
websockets==8.1
yarl==1.4.2

2
todo
View file

@ -1,2 +0,0 @@
reconnaissance d'image
commande d'archivage pour les salons vocaux avec output mp4 dans lequel on voit le pseudo de celui qui parle

View file

@ -1,6 +0,0 @@
from utils.functions.config import *
from utils.functions.lang import *
from utils.functions.version import *
from utils.functions.extra import *
from utils.functions.paginator import *

Binary file not shown.

View file

@ -1,6 +0,0 @@
from .config import Config
from .database import Database
from .extra import *
from .lang import Texts
from .paginator import *
from .version import Version

View file

@ -1,36 +0,0 @@
from typing import List, Union
import configparser
class Config(configparser.ConfigParser):
__slots__ = ('name', '_db')
def __init__(self, name):
super().__init__()
self._db = super()
self._db.read(name)
def find(self, value: str, **kwargs) \
-> Union[
List[configparser.SectionProxy], configparser.SectionProxy
]:
key = kwargs.get('key', None)
first = kwargs.get('first', False)
results = []
for name, section in self._db.items():
if key is None:
for k in section.keys():
if section.get(k) == value:
results.append(section)
if first and len(results) == 1:
return results[0]
else:
if section.get(key) == value:
results.append(section)
if first and len(results) == 1:
return results[0]
return results

View file

@ -1,16 +0,0 @@
from .config import Config
import sqlalchemy
import databases
class Database:
def __init__(self, config: Config):
conf_postgresql = config["postgresql"]
postgresql = 'postgresql://{}:{}@{}/{}'.format(
conf_postgresql.get("Username"), conf_postgresql.get("Password"),
conf_postgresql.get("Host"), conf_postgresql.get("DBName"))
self.database = databases.Database(postgresql)
self.metadata = sqlalchemy.MetaData()
self.engine = sqlalchemy.create_engine(str(self.database.url))

View file

@ -1,10 +0,0 @@
emotes = ['1⃣', '2⃣', '3⃣', '4⃣', '5⃣', '6⃣', '7⃣', '8⃣', '9⃣', '🔟', '0⃣',
'🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮']
def get(count):
return emotes[:count]
def get_index(emote):
return emotes.index(emote)

View file

@ -1,32 +1,70 @@
import ast
import json
import os
import discord
from discord.ext import commands
from utils.functions import Config
class CommandsPlus(commands.Command):
def __init__(self, func, **kwargs):
super().__init__(func, **kwargs)
self.category = kwargs.get("category", 'other')
class GroupPlus(commands.Group):
def __init__(self, func, **kwargs):
super().__init__(func, **kwargs)
self.category = kwargs.get("category", 'other')
from configs.bot.protected import protected
from configs.bot.settings import prefix
class ContextPlus(commands.Context):
async def send(self, content=None, **kwargs):
config = Config('./configs/config.cfg')
async def send(self, content=None, *args, **kwargs):
if content is not None:
for value in protected:
content = content.replace(
str(value),
'[Deleted]'
)
content = content.replace(config.get("bot", "Token"), 'Whoops! leaked token')
content = content.replace(config.get("webhook", "Token"), 'Whoops! leaked token')
if kwargs.get('content') is not None:
for value in protected:
kwargs['content'] = kwargs['content'].replace(
str(value),
'[Deleted]'
)
return await super().send(content, **kwargs)
if kwargs.get('embeds') is not None and len(kwargs.get('embeds')) > 0:
for i, embed in enumerate(kwargs.get('embeds')):
embed = str(kwargs.get('embed').to_dict())
for value in protected:
embed = embed.replace(str(value), '[Deleted]')
kwargs['embeds'][i] = discord.Embed.from_dict(
ast.literal_eval(embed)
)
if kwargs.get('embed') is not None:
embed = str(kwargs.get('embed').to_dict())
for value in protected:
embed = embed.replace(str(value), '[Deleted]')
kwargs['embed'] = discord.Embed.from_dict(
ast.literal_eval(embed)
)
return await super().send(content, *args, **kwargs)
def command_extra(*args, **kwargs):
return commands.command(*args, **kwargs, cls=CommandsPlus)
async def get_prefix(bot, message):
custom_prefix = [prefix]
if message.guild:
path = f"configs/guilds/{str(message.guild.id)}.json"
if os.path.exists(path):
with open(path) as f:
datas = json.load(f)
custom_prefix = datas["Prefix"]
return commands.when_mentioned_or(*custom_prefix)(bot, message)
def group_extra(*args, **kwargs):
return commands.group(*args, **kwargs, cls=GroupPlus)
def get_owners() -> list:
with open("configs/bot/whitelist.json") as f:
datas = json.load(f)
return datas['owners']
def get_blacklist() -> dict:
with open("configs/bot/blacklist.json") as f:
return json.load(f)

View file

@ -1,34 +0,0 @@
import gettext
import json
from discord.ext import commands
class Texts:
def __init__(self, base: str = 'base', ctx: commands.Context = None):
self.locale = self.get_locale(ctx)
self.base = base
def get(self, text: str) -> str:
texts = gettext.translation(self.base, localedir='utils/locales',
languages=[self.locale])
texts.install()
return texts.gettext(text)
def set(self, lang: str):
self.locale = lang
@staticmethod
def get_locale(ctx: commands.Context):
lang = 'fr'
if ctx is not None:
try:
with open(f'./configs/guilds/{ctx.guild.id}.json', 'r') as f:
data = json.load(f)
lang = data['lang']
except FileNotFoundError:
pass
return lang

View file

@ -1,343 +0,0 @@
"""
Based on https://github.com/Rapptz/RoboDanny/blob/3ec71c4c4031f868caff3027d71aecdebc3c5cec/cogs/utils/paginator.py
Adapted by Romain J.
"""
import asyncio
import discord
from discord.ext import commands
from discord.ext.commands import Paginator as CommandPaginator
class CannotPaginate(Exception):
pass
class Pages:
"""Implements a paginator that queries the user for the
pagination interface.
Pages are 1-index based, not 0-index based.
If the user does not reply within 2 minutes then the pagination
interface exits automatically.
Parameters
------------
ctx: Context
The context of the command.
entries: List[str]
A list of entries to paginate.
per_page: int
How many entries show up per page.
show_entry_count: bool
Whether to show an entry count in the footer.
Attributes
-----------
embed: discord.Embed
The embed object that is being used to send pagination info.
Feel free to modify this externally. Only the description,
footer fields, and colour are internally modified.
permissions: discord.Permissions
Our permissions for the channel.
"""
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True,
embed_color=discord.Color.blurple(), title=None,
thumbnail=None, footericon=None, footertext=None, author=None,
delete_after=None):
self.bot = ctx.bot
self.entries = entries
self.message = ctx.message
self.channel = ctx.channel
self.author = author if author else ctx.author
self.thumbnail = thumbnail
self.footericon = footericon
self.footertext = footertext
self.title = title
self.delete_after = delete_after
self.per_page = per_page
pages, left_over = divmod(len(self.entries), self.per_page)
if left_over:
pages += 1
self.maximum_pages = pages
self.embed = discord.Embed(colour=embed_color)
self.paginating = len(entries) > per_page
self.show_entry_count = show_entry_count
self.reaction_emojis = [
('\U000023ee\U0000fe0f', self.first_page),
('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page),
('\U000023f9', self.stop_pages),
('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page),
('\U000023ed\U0000fe0f', self.last_page)
]
if ctx.guild is not None:
self.permissions = self.channel.permissions_for(ctx.guild.me)
else:
self.permissions = self.channel.permissions_for(ctx.bot.user)
if not self.permissions.embed_links:
raise commands.BotMissingPermissions(
'I do not have permissions to : Embed links.'
)
if not self.permissions.send_messages:
raise commands.BotMissingPermissions('Bot cannot send messages.')
if self.paginating:
# verify we can actually use the pagination session
if not self.permissions.add_reactions:
raise commands.BotMissingPermissions(
'I do not have permissions to : Add Reactions.'
)
if not self.permissions.read_message_history:
raise commands.BotMissingPermissions(
'I do not have permissions to : Read Message History.'
)
def get_page(self, page):
base = (page - 1) * self.per_page
return self.entries[base:base + self.per_page]
def get_content(self, entries, page, *, first=False):
return None
def get_embed(self, entries, page, *, first=False):
self.prepare_embed(entries, page, first=first)
return self.embed
def prepare_embed(self, entries, page, *, first=False):
p = []
for index, entry in enumerate(entries,
1 + ((page - 1) * self.per_page)):
p.append(f'`{index}.` {entry}')
if self.maximum_pages > 1:
if self.show_entry_count:
text = f'Showing page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
else:
text = f'Showing page {page}/{self.maximum_pages}'
self.embed.set_footer(text=text)
if self.paginating and first:
p.append('')
self.embed.description = '\n'.join(p)
self.embed.title = self.title or discord.Embed.Empty
self.embed.set_author(icon_url=self.author.avatar_url,
name=str(self.author))
async def show_page(self, page, *, first=False):
self.current_page = page
entries = self.get_page(page)
content = self.get_content(entries, page, first=first)
embed = self.get_embed(entries, page, first=first)
if not self.paginating:
return await self.channel.send(content=content, embed=embed)
if not first:
await self.message.edit(content=content, embed=embed)
return
self.message = await self.channel.send(content=content, embed=embed)
for (reaction, _) in self.reaction_emojis:
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
# no |<< or >>| buttons if we only have two pages
# we can't forbid it if someone ends up using it but remove
# it from the default set
continue
await self.message.add_reaction(reaction)
async def checked_show_page(self, page):
if page != 0 and page <= self.maximum_pages:
await self.show_page(page)
async def first_page(self):
"""goes to the first page"""
await self.show_page(1)
async def last_page(self):
"""goes to the last page"""
await self.show_page(self.maximum_pages)
async def next_page(self):
"""goes to the next page"""
await self.checked_show_page(self.current_page + 1)
async def previous_page(self):
"""goes to the previous page"""
await self.checked_show_page(self.current_page - 1)
async def show_current_page(self):
if self.paginating:
await self.show_page(self.current_page)
async def numbered_page(self):
"""lets you type a page number to go to"""
to_delete = []
to_delete.append(
await self.channel.send('What page do you want to go to?'))
def message_check(m):
return m.author == self.author and \
self.channel == m.channel and \
m.content.isdigit()
try:
msg = await self.bot.wait_for(
'message',
check=message_check,
timeout=30.0
)
except asyncio.TimeoutError:
to_delete.append(await self.channel.send('Took too long.'))
await asyncio.sleep(5)
else:
page = int(msg.content)
to_delete.append(msg)
if page != 0 and page <= self.maximum_pages:
await self.show_page(page)
else:
to_delete.append(await self.channel.send(
f'Invalid page given. ({page}/{self.maximum_pages})'))
await asyncio.sleep(5)
try:
await self.channel.delete_messages(to_delete)
except Exception:
pass
async def show_help(self):
"""shows this message"""
messages = ['Welcome to the interactive paginator!\n']
messages.append(
'This interactively allows you to see pages of text by navigating with ' \
'reactions. They are as follows:\n')
for (emoji, func) in self.reaction_emojis:
messages.append(f'{emoji} {func.__doc__}')
embed = self.embed.copy()
embed.clear_fields()
embed.description = '\n'.join(messages)
embed.set_footer(
text=f'We were on page {self.current_page} before this message.')
await self.message.edit(content=None, embed=embed)
async def go_back_to_current_page():
await asyncio.sleep(60.0)
await self.show_current_page()
self.bot.loop.create_task(go_back_to_current_page())
async def stop_pages(self):
"""stops the interactive pagination session"""
await self.message.delete()
self.paginating = False
def react_check(self, reaction, user):
if user is None or user.id != self.author.id:
return False
if reaction.message.id != self.message.id:
return False
for (emoji, func) in self.reaction_emojis:
if reaction.emoji == emoji:
self.match = func
return True
return False
async def paginate(self):
"""Actually paginate the entries and run the interactive loop if necessary."""
first_page = self.show_page(1, first=True)
if not self.paginating:
await first_page
else:
# allow us to react to reactions right away if we're paginating
self.bot.loop.create_task(first_page)
while self.paginating:
try:
reaction, user = await self.bot.wait_for(
'reaction_add',
check=self.react_check,
timeout=self.delete_after
)
except asyncio.TimeoutError:
self.paginating = False
try:
await self.message.delete()
except:
pass
finally:
break
try:
await self.message.remove_reaction(reaction, user)
except:
pass # can't remove it so don't bother doing so
await self.match()
class FieldPages(Pages):
"""Similar to Pages except entries should be a list of
tuples having (key, value) to show as embed fields instead.
"""
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True,
title, thumbnail, footericon, footertext,
embed_color=discord.Color.blurple()):
super().__init__(ctx, entries=entries, per_page=per_page,
show_entry_count=show_entry_count, title=title,
thumbnail=thumbnail, footericon=footericon,
footertext=footertext, embed_color=embed_color)
def prepare_embed(self, entries, page, *, first=False):
self.embed.clear_fields()
for key, value in entries:
self.embed.add_field(name=key, value=value, inline=False)
self.embed.title = self.title
if self.maximum_pages > 1:
if self.show_entry_count:
text = f' [{page}/{self.maximum_pages}]'
else:
text = f' [{page}/{self.maximum_pages}]'
self.embed.title = self.title + text
self.embed.set_footer(icon_url=self.footericon, text=self.footertext)
self.embed.set_thumbnail(url=self.thumbnail)
class TextPages(Pages):
"""Uses a commands.Paginator internally to paginate some text."""
def __init__(self, ctx, text, *, prefix='```', suffix='```',
max_size=2000):
paginator = CommandPaginator(prefix=prefix, suffix=suffix,
max_size=max_size - 200)
for line in text.split('\n'):
paginator.add_line(line)
super().__init__(ctx, entries=paginator.pages, per_page=1,
show_entry_count=False)
def get_page(self, page):
return self.entries[page - 1]
def get_embed(self, entries, page, *, first=False):
return None
def get_content(self, entry, page, *, first=False):
if self.maximum_pages > 1:
return f'{entry}\nPage {page}/{self.maximum_pages}'
return entry

View file

@ -1,12 +0,0 @@
class Version:
def __init__(self, major: int, minor: int, patch: int, **kwargs):
self.major: int = major
self.minor: int = minor
self.patch: int = patch
self.pre_release = kwargs.get('pre_release', '')
self.build = kwargs.get('build', '')
def __str__(self) -> str:
build = self.build[:10]
return f'v{self.major}.{self.minor}.{self.patch}{self.pre_release}+{build}'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View file

@ -1,61 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "Please enter a reason"
msgstr ""
msgid "Unable to ban this user"
msgstr ""
msgid "Unable to kick this user"
msgstr ""
msgid "last warns"
msgstr ""
msgid "More than 2 warns"
msgstr ""
msgid "has more than 2 warns"
msgstr "has more than 2 warns, what do you want to do ?"
msgid "ignore"
msgstr ""
msgid "Took too long. Aborting."
msgstr ""
msgid "got a warn"
msgstr ""
msgid "Reason"
msgstr ""
msgid "WarnModel with id"
msgstr ""
msgid "successfully removed"
msgstr ""
msgid "successfully edited"
msgstr ""
msgid "Unable to find this language"
msgstr ""
msgid "Language changed successfully"
msgstr ""

View file

@ -1,252 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
###########################################################################
########################## SAY ########################################
###########################################################################
msgid '_say'
msgstr ''
msgid '_say_short'
msgstr '_say_short'
msgid '_say_usage'
msgstr '_say_usage'
###########################################################################
msgid '_say_edit'
msgstr ''
msgid '_say_edit__help'
msgstr ''
msgid '_say_edit__usage'
msgstr ''
###########################################################################
msgid '_say_to'
msgstr ''
msgid '_say_to__help'
msgstr ''
msgid '_say_to__usage'
msgstr ''
###########################################################################
########################## BAN ########################################
###########################################################################
msgid '_ban'
msgstr ''
msgid '_ban__help'
msgstr ''
msgid '_ban__usage'
msgstr ''
###########################################################################
########################## KICK #######################################
###########################################################################
msgid '_kick'
msgstr ''
msgid '_kick__help'
msgstr ''
msgid '_kick__usage'
msgstr ''
###########################################################################
########################## CLEAR ######################################
###########################################################################
msgid '_clear'
msgstr ''
msgid '_clear__help'
msgstr ''
msgid '_clear__usage'
msgstr ''
###########################################################################
########################## REACT ######################################
###########################################################################
msgid '_react'
msgstr ''
msgid '_react__help'
msgstr ''
msgid '_react__usage'
msgstr ''
###########################################################################
msgid '_react_remove'
msgstr ''
msgid '_react_remove__help'
msgstr ''
msgid '_react_remove__usage'
msgstr ''
###########################################################################
########################## DELETE #####################################
###########################################################################
msgid '_delete'
msgstr ''
msgid '_delete__help'
msgstr ''
msgid '_delete__usage'
msgstr ''
###########################################################################
msgid '_delete_from'
msgstr ''
msgid '_delete_from__help'
msgstr ''
msgid '_delete_from__usage'
msgstr ''
###########################################################################
########################## WARN #######################################
###########################################################################
msgid '_warn'
msgstr ''
msgid '_warn__help'
msgstr ''
msgid '_warn__usage'
msgstr ''
###########################################################################
msgid '_warn_new'
msgstr ''
msgid '_warn_new__help'
msgstr ''
msgid '_warn_new__usage'
msgstr ''
###########################################################################
msgid '_warn_remove'
msgstr ''
msgid '_warn_remove__help'
msgstr ''
msgid '_warn_remove__usage'
msgstr ''
###########################################################################
msgid '_warn_show'
msgstr ''
msgid '_warn_show__help'
msgstr ''
msgid '_warn_show__usage'
msgstr ''
###########################################################################
msgid '_warn_edit'
msgstr ''
msgid '_warn_edit__help'
msgstr ''
msgid '_warn_edit__usage'
msgstr ''
###########################################################################
########################## LANGUAGE ###################################
###########################################################################
msgid '_language'
msgstr ''
msgid '_language__help'
msgstr ''
msgid '_language__usage'
msgstr ''
###########################################################################
########################## PREFIX #####################################
###########################################################################
msgid '_prefix'
msgstr ''
msgid '_prefix__help'
msgstr ''
msgid '_prefix__usage'
msgstr ''
###########################################################################
msgid '_prefix_add'
msgstr ''
msgid '_prefix_add__help'
msgstr ''
msgid '_prefix_add__usage'
msgstr ''
###########################################################################
msgid '_prefix_remove'
msgstr ''
msgid '_prefix_remove__help'
msgstr ''
msgid '_prefix_remove__usage'
msgstr ''
###########################################################################
msgid '_prefix_list'
msgstr ''
msgid '_prefix_list__help'
msgstr ''
msgid '_prefix_list__usage'
msgstr ''

View file

@ -1,62 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "Starting..."
msgstr ""
msgid "Could not set up PostgreSQL..."
msgstr ""
msgid "Launch without loading the <TEXT> module"
msgstr ""
msgid "Search for update"
msgstr ""
msgid "Checking for update..."
msgstr ""
msgid "A new version is available !"
msgstr ""
msgid "Update ? [Y/n] "
msgstr ""
msgid "Downloading..."
msgstr ""
msgid "Tuxbot is up to date"
msgstr ""
msgid "Failed to load extension : "
msgstr ""
msgid "Extension loaded successfully : "
msgstr ""
msgid "This command cannot be used in private messages."
msgstr ""
msgid "Sorry. This command is disabled and cannot be used."
msgstr ""
msgid "In "
msgstr ""
msgid "Ready:"
msgstr ""

View file

@ -1,43 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid 'main_page.description'
msgstr "When using commands, **<>** means a **required argument** and **[]** means an **optional** argument.\n***(Don't type these symbols!)***"
msgid 'main_page.commands'
msgstr 'Commands'
msgid 'main_page.footer'
msgstr '- Send {}help <Command> to see more help about a command.'
msgid 'main_page.not_found'
msgstr 'No command called "{}" found.'
msgid 'command_help.subcommands'
msgstr 'Subcommands'
msgid 'command_help.aliases'
msgstr 'Aliases'
msgid 'command_help.no_aliases'
msgstr 'No aliases'
msgid 'command_help.params'
msgstr 'Parameters'
msgid 'command_help.usage'
msgstr 'Usage'

View file

@ -1,17 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"

View file

@ -1,20 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
#: launcher.py:51
msgid "**Preparation...**"
msgstr ""

View file

@ -1,17 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"

View file

@ -1,83 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "Information about TuxBot"
msgstr ""
msgid "Latest changes"
msgstr ""
msgid "Development"
msgstr ""
msgid "physical memory"
msgstr ""
msgid "virtual memory"
msgstr ""
msgid "Servers count"
msgstr ""
msgid "Channels count"
msgstr ""
msgid "Members count"
msgstr ""
msgid "Links"
msgstr ""
msgid "Files"
msgstr ""
msgid "Lines"
msgstr ""
msgid "Invite"
msgstr ""
msgid "Contributors"
msgstr ""
msgid "ipv6 not available"
msgstr "Error, this address is not available in IPv6."
msgid "Information for"
msgstr ""
msgid "Belongs to :"
msgstr ""
msgid "Is located at :"
msgstr ""
msgid "info not available"
msgstr ""
msgid "Headers of"
msgstr ""
msgid "Cannot connect to host"
msgstr ""
msgid "git repo"
msgstr "TuxBot-Bot's repository"
msgid "git text"
msgstr "Whoa, do you want to see my Gitea repository to dissect me? No problem ! I am a Bot, I do not feel the pain! \n https://git.gnous.eu/gnouseu/tuxbot-bot"

View file

@ -1,17 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"

View file

@ -1,17 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"

View file

@ -1,22 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "Unable to find the user..."
msgstr ""
msgid "Unable to find the message"
msgstr ""

View file

@ -1,61 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "Please enter a reason"
msgstr "Merci d'entrer une raison"
msgid "Unable to ban this user"
msgstr "Impossible de bannir cet utilisateur"
msgid "Unable to kick this user"
msgstr "Impossible d'expulser cet utilisateur"
msgid "last warns"
msgstr "derniers avertissements"
msgid "More than 2 warns"
msgstr "Plus de 2 avertissements"
msgid "has more than 2 warns"
msgstr "a plus de 2 avertissements, que voulez-vous faire?"
msgid "ignore"
msgstr "ignorer"
msgid "Took too long. Aborting."
msgstr "Temps expiré. Abandons."
msgid "got a warn"
msgstr "a recu un avertissement"
msgid "Reason"
msgstr "Raison"
msgid "WarnModel with id"
msgstr "L'avertissement avec l'id"
msgid "successfully removed"
msgstr "a été enlevé avec succes"
msgid "successfully edited"
msgstr "a été édité avec succes"
msgid "Unable to find this language"
msgstr "Impossible de trouver cette langue"
msgid "Language changed successfully"
msgstr "Langue changée avec succès"

View file

@ -1,263 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
###########################################################################
########################## SAY ########################################
###########################################################################
msgid '_say'
msgstr 'Permet de faire en sorte que le TuxBot envoie votre message'
msgid '_say__short'
msgstr 'Faire parler TuxBot'
msgid '_say_usage'
msgstr '[sous-commande] <message>'
###########################################################################
msgid '_say_edit'
msgstr "Permet de modifier le contenu d'un message envoyé par TuxBot"
msgid '_say_edit__short'
msgstr 'Editer un message envoyé'
msgid '_say_edit__usage'
msgstr '<ID/Lien du message> <message>'
###########################################################################
msgid '_say_to'
msgstr "Permet de faire en sorte que le TuxBot envoi votre message dans un autre salon ou en MP à quelqu'un"
msgid '_say_to__short'
msgstr 'Faire parler TuxBot dans un autre salon'
msgid '_say_to__usage'
msgstr "<ID/Mention du salon ou ID/Mention d'un membre> <message>"
###########################################################################
########################## BAN ########################################
###########################################################################
msgid '_ban'
msgstr 'Permet de bannir un membre'
msgid '_ban__short'
msgstr 'Bannir un membre'
msgid '_ban__usage'
msgstr '<ID/Mention du membre>'
###########################################################################
########################## KICK #######################################
###########################################################################
msgid '_kick'
msgstr "Permet d'expulser un membre"
msgid '_kick__short'
msgstr 'Expulser un membre'
msgid '_kick__usage'
msgstr '<ID/Mention du membre>'
###########################################################################
########################## CLEAR ######################################
###########################################################################
msgid '_clear'
msgstr 'Permet de supprimer un nombre donné de message'
msgid '_clear__short'
msgstr 'Supprime X messages'
msgid '_clear__usage'
msgstr '<quantité>'
###########################################################################
########################## REACT ######################################
###########################################################################
msgid '_react'
msgstr "Affiche l'aide relative a la commande `react`"
msgid '_react__short'
msgstr "Afficher l'aide pour `react`"
msgid '_react__usage'
msgstr '[sous-commande]'
###########################################################################
msgid '_react_add'
msgstr "Permet d'ajouter une réaction à un message de la part de TuxBot"
msgid '_react_add__short'
msgstr 'Ajouter une réaction'
msgid '_react_add__usage'
msgstr '<ID/Lien du message> <émoji>'
###########################################################################
msgid '_react_remove'
msgstr "Permet d'enlever toutes les réactions d'un message"
msgid '_react_remove__short'
msgstr "Enlever toutes les réactions d'un message"
msgid '_react_remove__usage'
msgstr '<ID/Lien du message>'
###########################################################################
########################## DELETE #####################################
###########################################################################
msgid '_delete'
msgstr 'Permet de supprimer un message'
msgid '_delete__short'
msgstr 'Supprimer un message'
msgid '_delete__usage'
msgstr '[sous-commande] <ID/Lien du message>'
###########################################################################
msgid '_delete_from'
msgstr 'Permet de supprimer un message dans un autre salon'
msgid '_delete_from__short'
msgstr 'Supprimer un message dans un autre salon'
msgid '_delete_from__usage'
msgstr '<ID/Mention du salon> <ID/Lien du message>'
###########################################################################
########################## WARN #######################################
###########################################################################
msgid '_warn'
msgstr "Permet d'afficher les derniers avertissements donnés sur ce serveur"
msgid '_warn__short'
msgstr 'Lister les derniers avertissements'
msgid '_warn__usage'
msgstr '[sous-commande]'
###########################################################################
msgid '_warn_add'
msgstr "Permet d'ajouter un avertissement à un membre en donnant une raison"
msgid '_warn_add__short'
msgstr 'Ajouter un avertissement a un membre'
msgid '_warn_add__usage'
msgstr '<ID/Pseudo/Mention du membre> <raison>'
###########################################################################
msgid '_warn_remove'
msgstr "Permet de supprimer un avertissement qui a été donné à un membre"
msgid '_warn_remove__short'
msgstr "Retirer l'avertissement d'un membre"
msgid '_warn_remove__usage'
msgstr "<ID de l'avertissement>"
###########################################################################
msgid '_warn_show'
msgstr "Permet d'afficher les derniers avertissements donnés à un membre"
msgid '_warn_show__short'
msgstr "Lister les derniers avertissements d'un membre"
msgid '_warn_show__usage'
msgstr '<ID/Pseudo/Mention du membre>'
###########################################################################
msgid '_warn_edit'
msgstr "Permet de modifier la raison de l'avertissement donné à un membre"
msgid '_warn_edit__short'
msgstr "Modifier la raison d'un avertissement"
msgid '_warn_edit__usage'
msgstr "<ID de l'avertissement> <raison>"
###########################################################################
########################## LANGUAGE ###################################
###########################################################################
msgid '_language'
msgstr 'Permet de définir la langue utilisée sur ce serveur'
msgid '_language__short'
msgstr 'Définir la langue de Tuxbot'
msgid '_language__usage'
msgstr '<langue>'
###########################################################################
########################## PREFIX #####################################
###########################################################################
msgid '_prefix'
msgstr "Affiche l'aide relative a la commande `prefix`"
msgid '_prefix__short'
msgstr "Afficher l'aide pour `prefix`"
msgid '_prefix__usage'
msgstr '[sous-commande]'
###########################################################################
msgid '_prefix_add'
msgstr "Permet d'ajouter un nouveau préfixe pour ce serveur"
msgid '_prefix_add__short'
msgstr 'Ajouter un préfixe pour ce serveur'
msgid '_prefix_add__usage'
msgstr '<prefixe>'
###########################################################################
msgid '_prefix_remove'
msgstr "Permet retirer un préfixe pour ce serveur"
msgid '_prefix_remove__short'
msgstr 'Retirer un préfixe pour ce serveur'
msgid '_prefix_remove__usage'
msgstr '<prefixe>'
###########################################################################
msgid '_prefix_list'
msgstr "Permet d'afficher tous les prefixes définis pour ce serveur"
msgid '_prefix_list__short'
msgstr 'Lister les prefixes pour ce serveur'
msgid '_prefix_list__usage'
msgstr ''

View file

@ -1,62 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "Starting..."
msgstr "Démarrage..."
msgid "Could not set up PostgreSQL..."
msgstr "Impossible de lancer PostgreSQL..."
msgid "Launch without loading the <TEXT> module"
msgstr "Lancer sans charger le module <TEXT>"
msgid "Search for update"
msgstr "Rechercher les mises à jour"
msgid "Checking for update..."
msgstr "Recherche de mise à jour..."
msgid "A new version is available !"
msgstr "Une nouvelle version est disponible !"
msgid "Update ? [Y/n] "
msgstr "Mettre à jour ? [O/n]"
msgid "Downloading..."
msgstr "Téléchargement..."
msgid "Tuxbot is up to date"
msgstr "Tuxbot est à jour"
msgid "Failed to load extension : "
msgstr "Impossible de charger l'extension : "
msgid "Extension loaded successfully : "
msgstr "Extension chargée avec succes : "
msgid "This command cannot be used in private messages."
msgstr "Cette commande ne peut pas être utilisée en message privé."
msgid "Sorry. This command is disabled and cannot be used."
msgstr "Désolé mais cette commande est désactivée."
msgid "In "
msgstr "Dans "
msgid "Ready:"
msgstr "Prêt :"

View file

@ -1,43 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid 'main_page.description'
msgstr "Lorsque vous utilisez les commandes, **<>** correspond à un argument **obligatoire** et **[]** à un argument **optionnel**.\n***(N'écrivez pas ces symboles!)***"
msgid 'main_page.commands'
msgstr 'Commandes'
msgid 'main_page.footer'
msgstr "- Envoyez {}help <Commande> pour avoir plus d'aide sur la commande."
msgid 'main_page.not_found'
msgstr 'Impossible de trouver la commande {}.'
msgid 'command_help.subcommands'
msgstr 'Sous-commandes'
msgid 'command_help.aliases'
msgstr 'Alias'
msgid 'command_help.no_aliases'
msgstr 'Aucun alias'
msgid 'command_help.params'
msgstr 'Parametres'
msgid 'command_help.usage'
msgstr 'Utilisation'

View file

@ -1,17 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"

View file

@ -1,19 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "_uptime__short"
msgstr "Retourne depuis quand TuxBot est en ligne."

View file

@ -1,20 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
#: launcher.py:51
msgid "**Preparation...**"
msgstr "**Préparation...**"

View file

@ -1,40 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
###########################################################################
########################## POLLS #####################################
###########################################################################
msgid '_poll'
msgstr "Affiche l'aide relative a la commande `sondage`"
msgid '_poll__short'
msgstr "Afficher l'aide pour `sondage`"
msgid '_poll__usage'
msgstr '[sous-commande]'
###########################################################################
msgid '_poll_create'
msgstr "Créez un sondage basé sur les réactions ! *(**BETA!** ajoutez `--anonyme` pour rendre les réponses anonymes)*"
msgid '_poll_create__short'
msgstr 'Créer un sondage'
msgid '_poll_create__usage'
msgstr '<question> | <réponse A> | <réponse B> | [réponse C] | [réponse D] | [...]'

View file

@ -1,82 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "Information about TuxBot"
msgstr "Informations sur TuxBot"
msgid "Latest changes"
msgstr "Derniers changements"
msgid "Development"
msgstr "Développement"
msgid "physical memory"
msgstr "mémoire physique"
msgid "virtual memory"
msgstr "mémoire virtuelle"
msgid "Servers count"
msgstr "Nombre de serveurs"
msgid "Channels count"
msgstr "Nombre de salons"
msgid "Members count"
msgstr "Nombre de membres"
msgid "Links"
msgstr "Liens"
msgid "Files"
msgstr "Fichiers"
msgid "Lines"
msgstr "Lignes"
msgid "Invite"
msgstr "Invitation"
msgid "Contributors"
msgstr "Contributeurs"
msgid "ipv6 not available"
msgstr "Erreur, cette adresse n'est pas disponible en IPv6."
msgid "Information for"
msgstr "Informations pour"
msgid "Belongs to :"
msgstr "Appartient à :"
msgid "Is located at :"
msgstr "Se situe à :"
msgid "info not available"
msgstr "Erreur, impossible d'obtenir des informations sur cette adresse IP"
msgid "Headers of"
msgstr "Entêtes de"
msgid "Cannot connect to host"
msgstr "Impossible de se connecter à l'hôte"
msgid "git repo"
msgstr "Repos TuxBot-Bot"
msgid "git text"
msgstr "Whoa tu veux voir mon repos Gitea pour me disséquer ? Pas de soucis ! Je suis un Bot, je ne ressens pas la douleur! \n https://git.gnous.eu/gnouseu/tuxbot-bot"

View file

@ -1,17 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"

View file

@ -1,17 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"

View file

@ -1,22 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid "Unable to find the user..."
msgstr "Impossibe de trouver l'utilisateur..."
msgid "Unable to find the message"
msgstr "Impossible de trouver le message"

View file

@ -1,18 +0,0 @@
import databases
import sqlalchemy
from utils.functions import Config
conf_postgresql = Config('./configs/config.cfg')["postgresql"]
postgresql = 'postgresql://{}:{}@{}/{}'.format(
conf_postgresql.get("Username"), conf_postgresql.get("Password"),
conf_postgresql.get("Host"), conf_postgresql.get("DBName"))
database = databases.Database(postgresql)
metadata = sqlalchemy.MetaData()
engine = sqlalchemy.create_engine(str(database.url))
metadata.create_all(engine)
from .warn import WarnModel
from .poll import PollModel, ResponsesModel
from .alias import AliasesModel

View file

@ -1,14 +0,0 @@
import orm
from . import database, metadata
class AliasesModel(orm.Model):
__tablename__ = 'aliases'
__database__ = database
__metadata__ = metadata
id = orm.Integer(primary_key=True)
user_id = orm.String(max_length=18)
alias = orm.String(max_length=255)
command = orm.String(max_length=255)
guild = orm.String(max_length=255)

View file

@ -1,29 +0,0 @@
import orm
from . import database, metadata
class ResponsesModel(orm.Model):
__tablename__ = 'responses'
__database__ = database
__metadata__ = metadata
id = orm.Integer(primary_key=True)
user = orm.String(max_length=18)
choice = orm.Integer()
class PollModel(orm.Model):
__tablename__ = 'polls'
__database__ = database
__metadata__ = metadata
id = orm.Integer(primary_key=True)
channel_id = orm.String(max_length=18)
message_id = orm.String(max_length=18)
content = orm.JSON()
is_anonymous = orm.Boolean()
available_choices = orm.Integer()
choice = orm.ForeignKey(ResponsesModel)

View file

@ -1,14 +0,0 @@
import orm
from . import database, metadata
class WarnModel(orm.Model):
__tablename__ = 'warns'
__database__ = database
__metadata__ = metadata
id = orm.Integer(primary_key=True)
server_id = orm.String(max_length=18)
user_id = orm.String(max_length=18)
reason = orm.String(max_length=255)
created_at = orm.DateTime()