mirror of
https://github.com/neoarz/Syntrel.git
synced 2025-12-25 03:40:11 +01:00
Initial commit
This commit is contained in:
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
database/
|
||||||
|
venv/
|
||||||
|
.env*
|
||||||
|
CODE_OF_CONDUCT.md
|
||||||
|
CONTRIBUTING.md
|
||||||
|
UPDATES.md
|
||||||
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
TOKEN=YOUR_BOT_TOKEN_HERE
|
||||||
|
PREFIX=YOUR_BOT_PREFIX_HERE
|
||||||
|
INVITE_LINK=YOUR_BOT_INVITE_LINK_HERE
|
||||||
147
.gitignore
vendored
Normal file
147
.gitignore
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# 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/
|
||||||
|
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/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# 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
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .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/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm IDEA
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# SQLITE database
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
discord.log
|
||||||
8
Dockerfile
Normal file
8
Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
FROM python:3.12.9-slim-bookworm
|
||||||
|
|
||||||
|
WORKDIR /bot
|
||||||
|
COPY . /bot
|
||||||
|
|
||||||
|
RUN python -m pip install -r requirements.txt
|
||||||
|
|
||||||
|
ENTRYPOINT [ "python", "bot.py" ]
|
||||||
144
LICENSE.md
Normal file
144
LICENSE.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1
|
||||||
|
through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or
|
||||||
|
are under common control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by
|
||||||
|
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii)
|
||||||
|
beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, including but not limited to software source
|
||||||
|
code, documentation source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including
|
||||||
|
but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as
|
||||||
|
indicated by a copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work
|
||||||
|
and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an
|
||||||
|
original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or
|
||||||
|
additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the
|
||||||
|
Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright
|
||||||
|
owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including
|
||||||
|
but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems
|
||||||
|
that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "
|
||||||
|
Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been
|
||||||
|
received by Licensor and subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to
|
||||||
|
You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce,
|
||||||
|
prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||||
|
Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a
|
||||||
|
perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise
|
||||||
|
transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are
|
||||||
|
necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (
|
||||||
|
including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within
|
||||||
|
the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this
|
||||||
|
License for that Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with
|
||||||
|
or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent,
|
||||||
|
trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to
|
||||||
|
any part of the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You
|
||||||
|
distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those
|
||||||
|
notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a
|
||||||
|
NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided
|
||||||
|
along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such
|
||||||
|
third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not
|
||||||
|
modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be
|
||||||
|
construed as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and may provide additional or different license terms
|
||||||
|
and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a
|
||||||
|
whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in
|
||||||
|
this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for
|
||||||
|
inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any
|
||||||
|
additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any
|
||||||
|
separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product
|
||||||
|
names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and
|
||||||
|
reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and
|
||||||
|
each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||||
|
either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT,
|
||||||
|
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
|
||||||
|
of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this
|
||||||
|
License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or
|
||||||
|
otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing,
|
||||||
|
shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or
|
||||||
|
consequential damages of any character arising as a result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||||
|
any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such
|
||||||
|
damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose
|
||||||
|
to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or
|
||||||
|
rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and
|
||||||
|
on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and
|
||||||
|
hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2021 Krypton
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||||
|
License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
|
||||||
|
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||||
|
language governing permissions and limitations under the License.
|
||||||
141
README.md
Normal file
141
README.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# Python Discord Bot Template
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://discord.gg/xj6y5ZaTMr"><img src="https://img.shields.io/discord/1358456011316396295?logo=discord"></a>
|
||||||
|
<a href="https://github.com/kkrypt0nn/Python-Discord-Bot-Template/releases"><img src="https://img.shields.io/github/v/release/kkrypt0nn/Python-Discord-Bot-Template"></a>
|
||||||
|
<a href="https://github.com/kkrypt0nn/Python-Discord-Bot-Template/commits/main"><img src="https://img.shields.io/github/last-commit/kkrypt0nn/Python-Discord-Bot-Template"></a>
|
||||||
|
<a href="https://github.com/kkrypt0nn/Python-Discord-Bot-Template/blob/main/LICENSE.md"><img src="https://img.shields.io/github/license/kkrypt0nn/Python-Discord-Bot-Template"></a>
|
||||||
|
<a href="https://github.com/kkrypt0nn/Python-Discord-Bot-Template"><img src="https://img.shields.io/github/languages/code-size/kkrypt0nn/Python-Discord-Bot-Template"></a>
|
||||||
|
<a href="https://conventionalcommits.org/en/v1.0.0/"><img src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white"></a>
|
||||||
|
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> This project is in a **feature-freeze mode**, please read more about it [here](https://github.com/kkrypt0nn/Python-Discord-Bot-Template/issues/112). It can be summed up in a few bullet points:
|
||||||
|
>
|
||||||
|
> * The project **will** receive bug fixes
|
||||||
|
> * The project **will** be updated to make sure it works with the **latest** discord.py version
|
||||||
|
> * The project **will not** receive any new features, **unless one of the following applies**:
|
||||||
|
> * A new feature is added to Discord and it would be beneficial to have it in the template
|
||||||
|
> * A feature got a breaking change, this fits with the same point that the project will **always** support the latest discord.py version
|
||||||
|
|
||||||
|
This repository is a template that everyone can use for the start of their Discord bot.
|
||||||
|
|
||||||
|
When I first started creating my Discord bot it took me a while to get everything setup and working with cogs and more.
|
||||||
|
I would've been happy if there were any template existing. However, there wasn't any existing template. That's why I
|
||||||
|
decided to create my own template to let **you** guys create your Discord bot easily.
|
||||||
|
|
||||||
|
Please note that this template is not supposed to be the best template, but a good template to start learning how
|
||||||
|
discord.py works and to make your own bot easily.
|
||||||
|
|
||||||
|
If you plan to use this template to make your own template or bot, you **have to**:
|
||||||
|
|
||||||
|
- Keep the credits, and a link to this repository in all the files that contains my code
|
||||||
|
- Keep the same license for unchanged code
|
||||||
|
|
||||||
|
See [the license file](https://github.com/kkrypt0nn/Python-Discord-Bot-Template/blob/master/LICENSE.md) for more
|
||||||
|
information, I reserve the right to take down any repository that does not meet these requirements.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Before requesting support, you should know that this template requires you to have at least a **basic knowledge** of
|
||||||
|
Python and the library is made for **advanced users**. Do not use this template if you don't know the
|
||||||
|
basics or some advanced topics such as OOP or async. [Here's](https://pythondiscord.com/pages/resources) a link for resources to learn python.
|
||||||
|
|
||||||
|
If you need some help for something, do not hesitate to create an issue over [here](https://github.com/kkrypt0nn/Python-Discord-Bot-Template/issues), but don't forget the read the [frequently asked questions](https://github.com/kkrypt0nn/Python-Discord-Bot-Template/wiki/Frequently-Asked-Questions) before.
|
||||||
|
|
||||||
|
All the updates of the template are available [here](UPDATES.md).
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
Slash commands can take some time to get registered globally, so if you want to test a command you should use
|
||||||
|
the `@app_commands.guilds()` decorator so that it gets registered instantly. Example:
|
||||||
|
|
||||||
|
```py
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="command",
|
||||||
|
description="Command description",
|
||||||
|
)
|
||||||
|
@app_commands.guilds(discord.Object(id=GUILD_ID)) # Place your guild ID here
|
||||||
|
```
|
||||||
|
|
||||||
|
When using the template you confirm that you have read the [license](LICENSE.md) and comprehend that I can take down
|
||||||
|
your repository if you do not meet these requirements.
|
||||||
|
|
||||||
|
## How to download it
|
||||||
|
|
||||||
|
This repository is now a template, on the top left you can simply click on "**Use this template**" to create a GitHub
|
||||||
|
repository based on this template.
|
||||||
|
|
||||||
|
Alternatively you can do the following:
|
||||||
|
|
||||||
|
- Clone/Download the repository
|
||||||
|
- To clone it and get the updates you can definitely use the command
|
||||||
|
`git clone`
|
||||||
|
- Create a Discord bot [here](https://discord.com/developers/applications)
|
||||||
|
- Get your bot token
|
||||||
|
- Invite your bot on servers using the following invite:
|
||||||
|
https://discord.com/oauth2/authorize?&client_id=YOUR_APPLICATION_ID_HERE&scope=bot+applications.commands&permissions=PERMISSIONS (
|
||||||
|
Replace `YOUR_APPLICATION_ID_HERE` with the application ID and replace `PERMISSIONS` with the required permissions
|
||||||
|
your bot needs that it can be get at the bottom of a this
|
||||||
|
page https://discord.com/developers/applications/YOUR_APPLICATION_ID_HERE/bot)
|
||||||
|
|
||||||
|
## How to set up
|
||||||
|
|
||||||
|
To set up the token you will have to make use of the [`.env.example`](.env.example) file; you should rename it to `.env` and replace the `YOUR_BOT...` content with your actual values that match for your bot.
|
||||||
|
|
||||||
|
Alternatively you can simply create a system environment variable with the same names and their respective value.
|
||||||
|
|
||||||
|
## How to start
|
||||||
|
|
||||||
|
### The _"usual"_ way
|
||||||
|
|
||||||
|
To start the bot you simply need to launch, either your terminal (Linux, Mac & Windows), or your Command Prompt (
|
||||||
|
Windows)
|
||||||
|
.
|
||||||
|
|
||||||
|
Before running the bot you will need to install all the requirements with this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
After that you can start it with
|
||||||
|
|
||||||
|
```
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: You may need to replace `python` with `py`, `python3`, `python3.11`, etc. depending on what Python versions you have installed on the machine.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
Support to start the bot in a Docker container has been added. After having [Docker](https://docker.com) installed on your machine, you can simply execute:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: `-d` will make the container run in detached mode, so in the background.
|
||||||
|
|
||||||
|
## Issues or Questions
|
||||||
|
|
||||||
|
If you have any issues or questions of how to code a specific command, you can:
|
||||||
|
|
||||||
|
- Join my Discord server [here](https://discord.gg/xj6y5ZaTMr)
|
||||||
|
- Post them [here](https://github.com/kkrypt0nn/Python-Discord-Bot-Template/issues)
|
||||||
|
|
||||||
|
Me or other people will take their time to answer and help you.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
We use [SemVer](http://semver.org) for versioning. For the versions available, see
|
||||||
|
the [tags on this repository](https://github.com/kkrypt0nn/Python-Discord-Bot-Template/tags).
|
||||||
|
|
||||||
|
## Built With
|
||||||
|
|
||||||
|
- [Python 3.12.9](https://www.python.org/)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the Apache License 2.0 - see the [LICENSE.md](LICENSE.md) file for details
|
||||||
289
bot.py
Normal file
289
bot.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
"""
|
||||||
|
Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn (https://krypton.ninja)
|
||||||
|
Description:
|
||||||
|
🐍 A simple template to start to code your own and personalized Discord bot in Python
|
||||||
|
|
||||||
|
Version: 6.4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import aiosqlite
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands, tasks
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from database import DatabaseManager
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Setup bot intents (events restrictions)
|
||||||
|
For more information about intents, please go to the following websites:
|
||||||
|
https://discordpy.readthedocs.io/en/latest/intents.html
|
||||||
|
https://discordpy.readthedocs.io/en/latest/intents.html#privileged-intents
|
||||||
|
|
||||||
|
|
||||||
|
Default Intents:
|
||||||
|
intents.bans = True
|
||||||
|
intents.dm_messages = True
|
||||||
|
intents.dm_reactions = True
|
||||||
|
intents.dm_typing = True
|
||||||
|
intents.emojis = True
|
||||||
|
intents.emojis_and_stickers = True
|
||||||
|
intents.guild_messages = True
|
||||||
|
intents.guild_reactions = True
|
||||||
|
intents.guild_scheduled_events = True
|
||||||
|
intents.guild_typing = True
|
||||||
|
intents.guilds = True
|
||||||
|
intents.integrations = True
|
||||||
|
intents.invites = True
|
||||||
|
intents.messages = True # `message_content` is required to get the content of the messages
|
||||||
|
intents.reactions = True
|
||||||
|
intents.typing = True
|
||||||
|
intents.voice_states = True
|
||||||
|
intents.webhooks = True
|
||||||
|
|
||||||
|
Privileged Intents (Needs to be enabled on developer portal of Discord), please use them only if you need them:
|
||||||
|
intents.members = True
|
||||||
|
intents.message_content = True
|
||||||
|
intents.presences = True
|
||||||
|
"""
|
||||||
|
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Uncomment this if you want to use prefix (normal) commands.
|
||||||
|
It is recommended to use slash commands and therefore not use prefix commands.
|
||||||
|
|
||||||
|
If you want to use prefix commands, make sure to also enable the intent below in the Discord developer portal.
|
||||||
|
"""
|
||||||
|
# intents.message_content = True
|
||||||
|
|
||||||
|
# Setup both of the loggers
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingFormatter(logging.Formatter):
|
||||||
|
# Colors
|
||||||
|
black = "\x1b[30m"
|
||||||
|
red = "\x1b[31m"
|
||||||
|
green = "\x1b[32m"
|
||||||
|
yellow = "\x1b[33m"
|
||||||
|
blue = "\x1b[34m"
|
||||||
|
gray = "\x1b[38m"
|
||||||
|
# Styles
|
||||||
|
reset = "\x1b[0m"
|
||||||
|
bold = "\x1b[1m"
|
||||||
|
|
||||||
|
COLORS = {
|
||||||
|
logging.DEBUG: gray + bold,
|
||||||
|
logging.INFO: blue + bold,
|
||||||
|
logging.WARNING: yellow + bold,
|
||||||
|
logging.ERROR: red,
|
||||||
|
logging.CRITICAL: red + bold,
|
||||||
|
}
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
log_color = self.COLORS[record.levelno]
|
||||||
|
format = "(black){asctime}(reset) (levelcolor){levelname:<8}(reset) (green){name}(reset) {message}"
|
||||||
|
format = format.replace("(black)", self.black + self.bold)
|
||||||
|
format = format.replace("(reset)", self.reset)
|
||||||
|
format = format.replace("(levelcolor)", log_color)
|
||||||
|
format = format.replace("(green)", self.green + self.bold)
|
||||||
|
formatter = logging.Formatter(format, "%Y-%m-%d %H:%M:%S", style="{")
|
||||||
|
return formatter.format(record)
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger("discord_bot")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Console handler
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(LoggingFormatter())
|
||||||
|
# File handler
|
||||||
|
file_handler = logging.FileHandler(filename="discord.log", encoding="utf-8", mode="w")
|
||||||
|
file_handler_formatter = logging.Formatter(
|
||||||
|
"[{asctime}] [{levelname:<8}] {name}: {message}", "%Y-%m-%d %H:%M:%S", style="{"
|
||||||
|
)
|
||||||
|
file_handler.setFormatter(file_handler_formatter)
|
||||||
|
|
||||||
|
# Add the handlers
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordBot(commands.Bot):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__(
|
||||||
|
command_prefix=commands.when_mentioned_or(os.getenv("PREFIX")),
|
||||||
|
intents=intents,
|
||||||
|
help_command=None,
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
This creates custom bot variables so that we can access these variables in cogs more easily.
|
||||||
|
|
||||||
|
For example, The logger is available using the following code:
|
||||||
|
- self.logger # In this class
|
||||||
|
- bot.logger # In this file
|
||||||
|
- self.bot.logger # In cogs
|
||||||
|
"""
|
||||||
|
self.logger = logger
|
||||||
|
self.database = None
|
||||||
|
self.bot_prefix = os.getenv("PREFIX")
|
||||||
|
self.invite_link = os.getenv("INVITE_LINK")
|
||||||
|
|
||||||
|
async def init_db(self) -> None:
|
||||||
|
async with aiosqlite.connect(
|
||||||
|
f"{os.path.realpath(os.path.dirname(__file__))}/database/database.db"
|
||||||
|
) as db:
|
||||||
|
with open(
|
||||||
|
f"{os.path.realpath(os.path.dirname(__file__))}/database/schema.sql",
|
||||||
|
encoding = "utf-8"
|
||||||
|
) as file:
|
||||||
|
await db.executescript(file.read())
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
async def load_cogs(self) -> None:
|
||||||
|
"""
|
||||||
|
The code in this function is executed whenever the bot will start.
|
||||||
|
"""
|
||||||
|
for file in os.listdir(f"{os.path.realpath(os.path.dirname(__file__))}/cogs"):
|
||||||
|
if file.endswith(".py"):
|
||||||
|
extension = file[:-3]
|
||||||
|
try:
|
||||||
|
await self.load_extension(f"cogs.{extension}")
|
||||||
|
self.logger.info(f"Loaded extension '{extension}'")
|
||||||
|
except Exception as e:
|
||||||
|
exception = f"{type(e).__name__}: {e}"
|
||||||
|
self.logger.error(
|
||||||
|
f"Failed to load extension {extension}\n{exception}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@tasks.loop(minutes=1.0)
|
||||||
|
async def status_task(self) -> None:
|
||||||
|
"""
|
||||||
|
Setup the game status task of the bot.
|
||||||
|
"""
|
||||||
|
statuses = ["with you!", "with Krypton!", "with humans!"]
|
||||||
|
await self.change_presence(activity=discord.Game(random.choice(statuses)))
|
||||||
|
|
||||||
|
@status_task.before_loop
|
||||||
|
async def before_status_task(self) -> None:
|
||||||
|
"""
|
||||||
|
Before starting the status changing task, we make sure the bot is ready
|
||||||
|
"""
|
||||||
|
await self.wait_until_ready()
|
||||||
|
|
||||||
|
async def setup_hook(self) -> None:
|
||||||
|
"""
|
||||||
|
This will just be executed when the bot starts the first time.
|
||||||
|
"""
|
||||||
|
self.logger.info(f"Logged in as {self.user.name}")
|
||||||
|
self.logger.info(f"discord.py API version: {discord.__version__}")
|
||||||
|
self.logger.info(f"Python version: {platform.python_version()}")
|
||||||
|
self.logger.info(
|
||||||
|
f"Running on: {platform.system()} {platform.release()} ({os.name})"
|
||||||
|
)
|
||||||
|
self.logger.info("-------------------")
|
||||||
|
await self.init_db()
|
||||||
|
await self.load_cogs()
|
||||||
|
self.status_task.start()
|
||||||
|
self.database = DatabaseManager(
|
||||||
|
connection=await aiosqlite.connect(
|
||||||
|
f"{os.path.realpath(os.path.dirname(__file__))}/database/database.db"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def on_message(self, message: discord.Message) -> None:
|
||||||
|
"""
|
||||||
|
The code in this event is executed every time someone sends a message, with or without the prefix
|
||||||
|
|
||||||
|
:param message: The message that was sent.
|
||||||
|
"""
|
||||||
|
if message.author == self.user or message.author.bot:
|
||||||
|
return
|
||||||
|
await self.process_commands(message)
|
||||||
|
|
||||||
|
async def on_command_completion(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
The code in this event is executed every time a normal command has been *successfully* executed.
|
||||||
|
|
||||||
|
:param context: The context of the command that has been executed.
|
||||||
|
"""
|
||||||
|
full_command_name = context.command.qualified_name
|
||||||
|
split = full_command_name.split(" ")
|
||||||
|
executed_command = str(split[0])
|
||||||
|
if context.guild is not None:
|
||||||
|
self.logger.info(
|
||||||
|
f"Executed {executed_command} command in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id})"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.logger.info(
|
||||||
|
f"Executed {executed_command} command by {context.author} (ID: {context.author.id}) in DMs"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def on_command_error(self, context: Context, error) -> None:
|
||||||
|
"""
|
||||||
|
The code in this event is executed every time a normal valid command catches an error.
|
||||||
|
|
||||||
|
:param context: The context of the normal command that failed executing.
|
||||||
|
:param error: The error that has been faced.
|
||||||
|
"""
|
||||||
|
if isinstance(error, commands.CommandOnCooldown):
|
||||||
|
minutes, seconds = divmod(error.retry_after, 60)
|
||||||
|
hours, minutes = divmod(minutes, 60)
|
||||||
|
hours = hours % 24
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"**Please slow down** - You can use this command again in {f'{round(hours)} hours' if round(hours) > 0 else ''} {f'{round(minutes)} minutes' if round(minutes) > 0 else ''} {f'{round(seconds)} seconds' if round(seconds) > 0 else ''}.",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
elif isinstance(error, commands.NotOwner):
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="You are not the owner of the bot!", color=0xE02B2B
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
if context.guild:
|
||||||
|
self.logger.warning(
|
||||||
|
f"{context.author} (ID: {context.author.id}) tried to execute an owner only command in the guild {context.guild.name} (ID: {context.guild.id}), but the user is not an owner of the bot."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.logger.warning(
|
||||||
|
f"{context.author} (ID: {context.author.id}) tried to execute an owner only command in the bot's DMs, but the user is not an owner of the bot."
|
||||||
|
)
|
||||||
|
elif isinstance(error, commands.MissingPermissions):
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="You are missing the permission(s) `"
|
||||||
|
+ ", ".join(error.missing_permissions)
|
||||||
|
+ "` to execute this command!",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
elif isinstance(error, commands.BotMissingPermissions):
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="I am missing the permission(s) `"
|
||||||
|
+ ", ".join(error.missing_permissions)
|
||||||
|
+ "` to fully perform this command!",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
elif isinstance(error, commands.MissingRequiredArgument):
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Error!",
|
||||||
|
# We need to capitalize because the command arguments have no capital letter in the code and they are the first word in the error message.
|
||||||
|
description=str(error).capitalize(),
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
else:
|
||||||
|
raise error
|
||||||
|
|
||||||
|
|
||||||
|
bot = DiscordBot()
|
||||||
|
bot.run(os.getenv("TOKEN"))
|
||||||
163
cogs/fun.py
Normal file
163
cogs/fun.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
"""
|
||||||
|
Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn (https://krypton.ninja)
|
||||||
|
Description:
|
||||||
|
🐍 A simple template to start to code your own and personalized Discord bot in Python
|
||||||
|
|
||||||
|
Version: 6.4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
|
||||||
|
|
||||||
|
class Choice(discord.ui.View):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.value = None
|
||||||
|
|
||||||
|
@discord.ui.button(label="Heads", style=discord.ButtonStyle.blurple)
|
||||||
|
async def confirm(
|
||||||
|
self, interaction: discord.Interaction, button: discord.ui.Button
|
||||||
|
) -> None:
|
||||||
|
self.value = "heads"
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
@discord.ui.button(label="Tails", style=discord.ButtonStyle.blurple)
|
||||||
|
async def cancel(
|
||||||
|
self, interaction: discord.Interaction, button: discord.ui.Button
|
||||||
|
) -> None:
|
||||||
|
self.value = "tails"
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
|
||||||
|
class RockPaperScissors(discord.ui.Select):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
options = [
|
||||||
|
discord.SelectOption(
|
||||||
|
label="Scissors", description="You choose scissors.", emoji="✂"
|
||||||
|
),
|
||||||
|
discord.SelectOption(
|
||||||
|
label="Rock", description="You choose rock.", emoji="🪨"
|
||||||
|
),
|
||||||
|
discord.SelectOption(
|
||||||
|
label="Paper", description="You choose paper.", emoji="🧻"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
super().__init__(
|
||||||
|
placeholder="Choose...",
|
||||||
|
min_values=1,
|
||||||
|
max_values=1,
|
||||||
|
options=options,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction) -> None:
|
||||||
|
choices = {
|
||||||
|
"rock": 0,
|
||||||
|
"paper": 1,
|
||||||
|
"scissors": 2,
|
||||||
|
}
|
||||||
|
user_choice = self.values[0].lower()
|
||||||
|
user_choice_index = choices[user_choice]
|
||||||
|
|
||||||
|
bot_choice = random.choice(list(choices.keys()))
|
||||||
|
bot_choice_index = choices[bot_choice]
|
||||||
|
|
||||||
|
result_embed = discord.Embed(color=0xBEBEFE)
|
||||||
|
result_embed.set_author(
|
||||||
|
name=interaction.user.name, icon_url=interaction.user.display_avatar.url
|
||||||
|
)
|
||||||
|
|
||||||
|
winner = (3 + user_choice_index - bot_choice_index) % 3
|
||||||
|
if winner == 0:
|
||||||
|
result_embed.description = f"**That's a draw!**\nYou've chosen {user_choice} and I've chosen {bot_choice}."
|
||||||
|
result_embed.colour = 0xF59E42
|
||||||
|
elif winner == 1:
|
||||||
|
result_embed.description = f"**You won!**\nYou've chosen {user_choice} and I've chosen {bot_choice}."
|
||||||
|
result_embed.colour = 0x57F287
|
||||||
|
else:
|
||||||
|
result_embed.description = f"**You lost!**\nYou've chosen {user_choice} and I've chosen {bot_choice}."
|
||||||
|
result_embed.colour = 0xE02B2B
|
||||||
|
|
||||||
|
await interaction.response.edit_message(
|
||||||
|
embed=result_embed, content=None, view=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RockPaperScissorsView(discord.ui.View):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.add_item(RockPaperScissors())
|
||||||
|
|
||||||
|
|
||||||
|
class Fun(commands.Cog, name="fun"):
|
||||||
|
def __init__(self, bot) -> None:
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.hybrid_command(name="randomfact", description="Get a random fact.")
|
||||||
|
async def randomfact(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Get a random fact.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
# This will prevent your bot from stopping everything when doing a web request - see: https://discordpy.readthedocs.io/en/stable/faq.html#how-do-i-make-a-web-request
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
"https://uselessfacts.jsph.pl/random.json?language=en"
|
||||||
|
) as request:
|
||||||
|
if request.status == 200:
|
||||||
|
data = await request.json()
|
||||||
|
embed = discord.Embed(description=data["text"], color=0xD75BF4)
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Error!",
|
||||||
|
description="There is something wrong with the API, please try again later",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="coinflip", description="Make a coin flip, but give your bet before."
|
||||||
|
)
|
||||||
|
async def coinflip(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Make a coin flip, but give your bet before.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
buttons = Choice()
|
||||||
|
embed = discord.Embed(description="What is your bet?", color=0xBEBEFE)
|
||||||
|
message = await context.send(embed=embed, view=buttons)
|
||||||
|
await buttons.wait() # We wait for the user to click a button.
|
||||||
|
result = random.choice(["heads", "tails"])
|
||||||
|
if buttons.value == result:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Correct! You guessed `{buttons.value}` and I flipped the coin to `{result}`.",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Woops! You guessed `{buttons.value}` and I flipped the coin to `{result}`, better luck next time!",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await message.edit(embed=embed, view=None, content=None)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="rps", description="Play the rock paper scissors game against the bot."
|
||||||
|
)
|
||||||
|
async def rock_paper_scissors(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Play the rock paper scissors game against the bot.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
view = RockPaperScissorsView()
|
||||||
|
await context.send("Please make your choice", view=view)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot) -> None:
|
||||||
|
await bot.add_cog(Fun(bot))
|
||||||
327
cogs/general.py
Normal file
327
cogs/general.py
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
"""
|
||||||
|
Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn (https://krypton.ninja)
|
||||||
|
Description:
|
||||||
|
🐍 A simple template to start to code your own and personalized Discord bot in Python
|
||||||
|
|
||||||
|
Version: 6.4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import random
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from discord.ext import commands
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackForm(discord.ui.Modal, title="Feeedback"):
|
||||||
|
feedback = discord.ui.TextInput(
|
||||||
|
label="What do you think about this bot?",
|
||||||
|
style=discord.TextStyle.long,
|
||||||
|
placeholder="Type your answer here...",
|
||||||
|
required=True,
|
||||||
|
max_length=256,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: discord.Interaction):
|
||||||
|
self.interaction = interaction
|
||||||
|
self.answer = str(self.feedback)
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
|
||||||
|
class General(commands.Cog, name="general"):
|
||||||
|
def __init__(self, bot) -> None:
|
||||||
|
self.bot = bot
|
||||||
|
self.context_menu_user = app_commands.ContextMenu(
|
||||||
|
name="Grab ID", callback=self.grab_id
|
||||||
|
)
|
||||||
|
self.bot.tree.add_command(self.context_menu_user)
|
||||||
|
self.context_menu_message = app_commands.ContextMenu(
|
||||||
|
name="Remove spoilers", callback=self.remove_spoilers
|
||||||
|
)
|
||||||
|
self.bot.tree.add_command(self.context_menu_message)
|
||||||
|
|
||||||
|
# Message context menu command
|
||||||
|
async def remove_spoilers(
|
||||||
|
self, interaction: discord.Interaction, message: discord.Message
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Removes the spoilers from the message. This command requires the MESSAGE_CONTENT intent to work properly.
|
||||||
|
|
||||||
|
:param interaction: The application command interaction.
|
||||||
|
:param message: The message that is being interacted with.
|
||||||
|
"""
|
||||||
|
spoiler_attachment = None
|
||||||
|
for attachment in message.attachments:
|
||||||
|
if attachment.is_spoiler():
|
||||||
|
spoiler_attachment = attachment
|
||||||
|
break
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Message without spoilers",
|
||||||
|
description=message.content.replace("||", ""),
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
if spoiler_attachment is not None:
|
||||||
|
embed.set_image(url=attachment.url)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
# User context menu command
|
||||||
|
async def grab_id(
|
||||||
|
self, interaction: discord.Interaction, user: discord.User
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Grabs the ID of the user.
|
||||||
|
|
||||||
|
:param interaction: The application command interaction.
|
||||||
|
:param user: The user that is being interacted with.
|
||||||
|
"""
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"The ID of {user.mention} is `{user.id}`.",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="help", description="List all commands the bot has loaded."
|
||||||
|
)
|
||||||
|
async def help(self, context: Context) -> None:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Help", description="List of available commands:", color=0xBEBEFE
|
||||||
|
)
|
||||||
|
for i in self.bot.cogs:
|
||||||
|
if i == "owner" and not (await self.bot.is_owner(context.author)):
|
||||||
|
continue
|
||||||
|
cog = self.bot.get_cog(i.lower())
|
||||||
|
commands = cog.get_commands()
|
||||||
|
data = []
|
||||||
|
for command in commands:
|
||||||
|
description = command.description.partition("\n")[0]
|
||||||
|
data.append(f"{command.name} - {description}")
|
||||||
|
help_text = "\n".join(data)
|
||||||
|
embed.add_field(
|
||||||
|
name=i.capitalize(), value=f"```{help_text}```", inline=False
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="botinfo",
|
||||||
|
description="Get some useful (or not) information about the bot.",
|
||||||
|
)
|
||||||
|
async def botinfo(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Get some useful (or not) information about the bot.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="Used [Krypton's](https://krypton.ninja) template",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
embed.set_author(name="Bot Information")
|
||||||
|
embed.add_field(name="Owner:", value="Krypton#7331", inline=True)
|
||||||
|
embed.add_field(
|
||||||
|
name="Python Version:", value=f"{platform.python_version()}", inline=True
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name="Prefix:",
|
||||||
|
value=f"/ (Slash Commands) or {self.bot.bot_prefix} for normal commands",
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
|
embed.set_footer(text=f"Requested by {context.author}")
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="serverinfo",
|
||||||
|
description="Get some useful (or not) information about the server.",
|
||||||
|
)
|
||||||
|
async def serverinfo(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Get some useful (or not) information about the server.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
roles = [role.name for role in context.guild.roles]
|
||||||
|
num_roles = len(roles)
|
||||||
|
if num_roles > 50:
|
||||||
|
roles = roles[:50]
|
||||||
|
roles.append(f">>>> Displaying [50/{num_roles}] Roles")
|
||||||
|
roles = ", ".join(roles)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="**Server Name:**", description=f"{context.guild}", color=0xBEBEFE
|
||||||
|
)
|
||||||
|
if context.guild.icon is not None:
|
||||||
|
embed.set_thumbnail(url=context.guild.icon.url)
|
||||||
|
embed.add_field(name="Server ID", value=context.guild.id)
|
||||||
|
embed.add_field(name="Member Count", value=context.guild.member_count)
|
||||||
|
embed.add_field(
|
||||||
|
name="Text/Voice Channels", value=f"{len(context.guild.channels)}"
|
||||||
|
)
|
||||||
|
embed.add_field(name=f"Roles ({len(context.guild.roles)})", value=roles)
|
||||||
|
embed.set_footer(text=f"Created at: {context.guild.created_at}")
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="ping",
|
||||||
|
description="Check if the bot is alive.",
|
||||||
|
)
|
||||||
|
async def ping(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Check if the bot is alive.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🏓 Pong!",
|
||||||
|
description=f"The bot latency is {round(self.bot.latency * 1000)}ms.",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="invite",
|
||||||
|
description="Get the invite link of the bot to be able to invite it.",
|
||||||
|
)
|
||||||
|
async def invite(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Get the invite link of the bot to be able to invite it.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Invite me by clicking [here]({self.bot.invite_link}).",
|
||||||
|
color=0xD75BF4,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await context.author.send(embed=embed)
|
||||||
|
await context.send("I sent you a private message!")
|
||||||
|
except discord.Forbidden:
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="server",
|
||||||
|
description="Get the invite link of the discord server of the bot for some support.",
|
||||||
|
)
|
||||||
|
async def server(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Get the invite link of the discord server of the bot for some support.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Join the support server for the bot by clicking [here](https://discord.gg/mTBrXyWxAF).",
|
||||||
|
color=0xD75BF4,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await context.author.send(embed=embed)
|
||||||
|
await context.send("I sent you a private message!")
|
||||||
|
except discord.Forbidden:
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="8ball",
|
||||||
|
description="Ask any question to the bot.",
|
||||||
|
)
|
||||||
|
@app_commands.describe(question="The question you want to ask.")
|
||||||
|
async def eight_ball(self, context: Context, *, question: str) -> None:
|
||||||
|
"""
|
||||||
|
Ask any question to the bot.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param question: The question that should be asked by the user.
|
||||||
|
"""
|
||||||
|
answers = [
|
||||||
|
"It is certain.",
|
||||||
|
"It is decidedly so.",
|
||||||
|
"You may rely on it.",
|
||||||
|
"Without a doubt.",
|
||||||
|
"Yes - definitely.",
|
||||||
|
"As I see, yes.",
|
||||||
|
"Most likely.",
|
||||||
|
"Outlook good.",
|
||||||
|
"Yes.",
|
||||||
|
"Signs point to yes.",
|
||||||
|
"Reply hazy, try again.",
|
||||||
|
"Ask again later.",
|
||||||
|
"Better not tell you now.",
|
||||||
|
"Cannot predict now.",
|
||||||
|
"Concentrate and ask again later.",
|
||||||
|
"Don't count on it.",
|
||||||
|
"My reply is no.",
|
||||||
|
"My sources say no.",
|
||||||
|
"Outlook not so good.",
|
||||||
|
"Very doubtful.",
|
||||||
|
]
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="**My Answer:**",
|
||||||
|
description=f"{random.choice(answers)}",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
embed.set_footer(text=f"The question was: {question}")
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="bitcoin",
|
||||||
|
description="Get the current price of bitcoin.",
|
||||||
|
)
|
||||||
|
async def bitcoin(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Get the current price of bitcoin.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
# This will prevent your bot from stopping everything when doing a web request - see: https://discordpy.readthedocs.io/en/stable/faq.html#how-do-i-make-a-web-request
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
"https://api.coindesk.com/v1/bpi/currentprice/BTC.json"
|
||||||
|
) as request:
|
||||||
|
if request.status == 200:
|
||||||
|
data = await request.json()
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Bitcoin price",
|
||||||
|
description=f"The current price is {data['bpi']['USD']['rate']} :dollar:",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Error!",
|
||||||
|
description="There is something wrong with the API, please try again later",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@app_commands.command(
|
||||||
|
name="feedback", description="Submit a feedback for the owners of the bot"
|
||||||
|
)
|
||||||
|
async def feedback(self, interaction: discord.Interaction) -> None:
|
||||||
|
"""
|
||||||
|
Submit a feedback for the owners of the bot.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
feedback_form = FeedbackForm()
|
||||||
|
await interaction.response.send_modal(feedback_form)
|
||||||
|
|
||||||
|
await feedback_form.wait()
|
||||||
|
interaction = feedback_form.interaction
|
||||||
|
await interaction.response.send_message(
|
||||||
|
embed=discord.Embed(
|
||||||
|
description="Thank you for your feedback, the owners have been notified about it.",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
app_owner = (await self.bot.application_info()).owner
|
||||||
|
await app_owner.send(
|
||||||
|
embed=discord.Embed(
|
||||||
|
title="New Feedback",
|
||||||
|
description=f"{interaction.user} (<@{interaction.user.id}>) has submitted a new feedback:\n```\n{feedback_form.answer}\n```",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot) -> None:
|
||||||
|
await bot.add_cog(General(bot))
|
||||||
375
cogs/moderation.py
Normal file
375
cogs/moderation.py
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
"""
|
||||||
|
Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn (https://krypton.ninja)
|
||||||
|
Description:
|
||||||
|
🐍 A simple template to start to code your own and personalized Discord bot in Python
|
||||||
|
|
||||||
|
Version: 6.4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from discord.ext import commands
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
|
||||||
|
|
||||||
|
class Moderation(commands.Cog, name="moderation"):
|
||||||
|
def __init__(self, bot) -> None:
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="kick",
|
||||||
|
description="Kick a user out of the server.",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(kick_members=True)
|
||||||
|
@commands.bot_has_permissions(kick_members=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
user="The user that should be kicked.",
|
||||||
|
reason="The reason why the user should be kicked.",
|
||||||
|
)
|
||||||
|
async def kick(
|
||||||
|
self, context: Context, user: discord.User, *, reason: str = "Not specified"
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Kick a user out of the server.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param user: The user that should be kicked from the server.
|
||||||
|
:param reason: The reason for the kick. Default is "Not specified".
|
||||||
|
"""
|
||||||
|
member = context.guild.get_member(user.id) or await context.guild.fetch_member(
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
if member.guild_permissions.administrator:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="User has administrator permissions.", color=0xE02B2B
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"**{member}** was kicked by **{context.author}**!",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
embed.add_field(name="Reason:", value=reason)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
try:
|
||||||
|
await member.send(
|
||||||
|
f"You were kicked by **{context.author}** from **{context.guild.name}**!\nReason: {reason}"
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
# Couldn't send a message in the private messages of the user
|
||||||
|
pass
|
||||||
|
await member.kick(reason=reason)
|
||||||
|
except:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="An error occurred while trying to kick the user. Make sure my role is above the role of the user you want to kick.",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="nick",
|
||||||
|
description="Change the nickname of a user on a server.",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(manage_nicknames=True)
|
||||||
|
@commands.bot_has_permissions(manage_nicknames=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
user="The user that should have a new nickname.",
|
||||||
|
nickname="The new nickname that should be set.",
|
||||||
|
)
|
||||||
|
async def nick(
|
||||||
|
self, context: Context, user: discord.User, *, nickname: str = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Change the nickname of a user on a server.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param user: The user that should have its nickname changed.
|
||||||
|
:param nickname: The new nickname of the user. Default is None, which will reset the nickname.
|
||||||
|
"""
|
||||||
|
member = context.guild.get_member(user.id) or await context.guild.fetch_member(
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await member.edit(nick=nickname)
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"**{member}'s** new nickname is **{nickname}**!",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
except:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="An error occurred while trying to change the nickname of the user. Make sure my role is above the role of the user you want to change the nickname.",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="ban",
|
||||||
|
description="Bans a user from the server.",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(ban_members=True)
|
||||||
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
user="The user that should be banned.",
|
||||||
|
reason="The reason why the user should be banned.",
|
||||||
|
)
|
||||||
|
async def ban(
|
||||||
|
self, context: Context, user: discord.User, *, reason: str = "Not specified"
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Bans a user from the server.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param user: The user that should be banned from the server.
|
||||||
|
:param reason: The reason for the ban. Default is "Not specified".
|
||||||
|
"""
|
||||||
|
member = context.guild.get_member(user.id) or await context.guild.fetch_member(
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
if member.guild_permissions.administrator:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="User has administrator permissions.", color=0xE02B2B
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"**{member}** was banned by **{context.author}**!",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
embed.add_field(name="Reason:", value=reason)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
try:
|
||||||
|
await member.send(
|
||||||
|
f"You were banned by **{context.author}** from **{context.guild.name}**!\nReason: {reason}"
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
# Couldn't send a message in the private messages of the user
|
||||||
|
pass
|
||||||
|
await member.ban(reason=reason)
|
||||||
|
except:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Error!",
|
||||||
|
description="An error occurred while trying to ban the user. Make sure my role is above the role of the user you want to ban.",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_group(
|
||||||
|
name="warning",
|
||||||
|
description="Manage warnings of a user on a server.",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(manage_messages=True)
|
||||||
|
async def warning(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Manage warnings of a user on a server.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
if context.invoked_subcommand is None:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="Please specify a subcommand.\n\n**Subcommands:**\n`add` - Add a warning to a user.\n`remove` - Remove a warning from a user.\n`list` - List all warnings of a user.",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@warning.command(
|
||||||
|
name="add",
|
||||||
|
description="Adds a warning to a user in the server.",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(manage_messages=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
user="The user that should be warned.",
|
||||||
|
reason="The reason why the user should be warned.",
|
||||||
|
)
|
||||||
|
async def warning_add(
|
||||||
|
self, context: Context, user: discord.User, *, reason: str = "Not specified"
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Warns a user in his private messages.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param user: The user that should be warned.
|
||||||
|
:param reason: The reason for the warn. Default is "Not specified".
|
||||||
|
"""
|
||||||
|
member = context.guild.get_member(user.id) or await context.guild.fetch_member(
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
total = await self.bot.database.add_warn(
|
||||||
|
user.id, context.guild.id, context.author.id, reason
|
||||||
|
)
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"**{member}** was warned by **{context.author}**!\nTotal warns for this user: {total}",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
embed.add_field(name="Reason:", value=reason)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
try:
|
||||||
|
await member.send(
|
||||||
|
f"You were warned by **{context.author}** in **{context.guild.name}**!\nReason: {reason}"
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
# Couldn't send a message in the private messages of the user
|
||||||
|
await context.send(
|
||||||
|
f"{member.mention}, you were warned by **{context.author}**!\nReason: {reason}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@warning.command(
|
||||||
|
name="remove",
|
||||||
|
description="Removes a warning from a user in the server.",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(manage_messages=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
user="The user that should get their warning removed.",
|
||||||
|
warn_id="The ID of the warning that should be removed.",
|
||||||
|
)
|
||||||
|
async def warning_remove(
|
||||||
|
self, context: Context, user: discord.User, warn_id: int
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Warns a user in his private messages.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param user: The user that should get their warning removed.
|
||||||
|
:param warn_id: The ID of the warning that should be removed.
|
||||||
|
"""
|
||||||
|
member = context.guild.get_member(user.id) or await context.guild.fetch_member(
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
total = await self.bot.database.remove_warn(warn_id, user.id, context.guild.id)
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"I've removed the warning **#{warn_id}** from **{member}**!\nTotal warns for this user: {total}",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@warning.command(
|
||||||
|
name="list",
|
||||||
|
description="Shows the warnings of a user in the server.",
|
||||||
|
)
|
||||||
|
@commands.has_guild_permissions(manage_messages=True)
|
||||||
|
@app_commands.describe(user="The user you want to get the warnings of.")
|
||||||
|
async def warning_list(self, context: Context, user: discord.User) -> None:
|
||||||
|
"""
|
||||||
|
Shows the warnings of a user in the server.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param user: The user you want to get the warnings of.
|
||||||
|
"""
|
||||||
|
warnings_list = await self.bot.database.get_warnings(user.id, context.guild.id)
|
||||||
|
embed = discord.Embed(title=f"Warnings of {user}", color=0xBEBEFE)
|
||||||
|
description = ""
|
||||||
|
if len(warnings_list) == 0:
|
||||||
|
description = "This user has no warnings."
|
||||||
|
else:
|
||||||
|
for warning in warnings_list:
|
||||||
|
description += f"• Warned by <@{warning[2]}>: **{warning[3]}** (<t:{warning[4]}>) - Warn ID #{warning[5]}\n"
|
||||||
|
embed.description = description
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="purge",
|
||||||
|
description="Delete a number of messages.",
|
||||||
|
)
|
||||||
|
@commands.has_guild_permissions(manage_messages=True)
|
||||||
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
|
@app_commands.describe(amount="The amount of messages that should be deleted.")
|
||||||
|
async def purge(self, context: Context, amount: int) -> None:
|
||||||
|
"""
|
||||||
|
Delete a number of messages.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param amount: The number of messages that should be deleted.
|
||||||
|
"""
|
||||||
|
await context.send(
|
||||||
|
"Deleting messages..."
|
||||||
|
) # Bit of a hacky way to make sure the bot responds to the interaction and doens't get a "Unknown Interaction" response
|
||||||
|
purged_messages = await context.channel.purge(limit=amount + 1)
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"**{context.author}** cleared **{len(purged_messages)-1}** messages!",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await context.channel.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="hackban",
|
||||||
|
description="Bans a user without the user having to be in the server.",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(ban_members=True)
|
||||||
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
user_id="The user ID that should be banned.",
|
||||||
|
reason="The reason why the user should be banned.",
|
||||||
|
)
|
||||||
|
async def hackban(
|
||||||
|
self, context: Context, user_id: str, *, reason: str = "Not specified"
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Bans a user without the user having to be in the server.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param user_id: The ID of the user that should be banned.
|
||||||
|
:param reason: The reason for the ban. Default is "Not specified".
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self.bot.http.ban(user_id, context.guild.id, reason=reason)
|
||||||
|
user = self.bot.get_user(int(user_id)) or await self.bot.fetch_user(
|
||||||
|
int(user_id)
|
||||||
|
)
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"**{user}** (ID: {user_id}) was banned by **{context.author}**!",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
embed.add_field(name="Reason:", value=reason)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
except Exception:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="An error occurred while trying to ban the user. Make sure ID is an existing ID that belongs to a user.",
|
||||||
|
color=0xE02B2B,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="archive",
|
||||||
|
description="Archives in a text file the last messages with a chosen limit of messages.",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(manage_messages=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
limit="The limit of messages that should be archived.",
|
||||||
|
)
|
||||||
|
async def archive(self, context: Context, limit: int = 10) -> None:
|
||||||
|
"""
|
||||||
|
Archives in a text file the last messages with a chosen limit of messages. This command requires the MESSAGE_CONTENT intent to work properly.
|
||||||
|
|
||||||
|
:param limit: The limit of messages that should be archived. Default is 10.
|
||||||
|
"""
|
||||||
|
log_file = f"{context.channel.id}.log"
|
||||||
|
with open(log_file, "w", encoding="UTF-8") as f:
|
||||||
|
f.write(
|
||||||
|
f'Archived messages from: #{context.channel} ({context.channel.id}) in the guild "{context.guild}" ({context.guild.id}) at {datetime.now().strftime("%d.%m.%Y %H:%M:%S")}\n'
|
||||||
|
)
|
||||||
|
async for message in context.channel.history(
|
||||||
|
limit=limit, before=context.message
|
||||||
|
):
|
||||||
|
attachments = []
|
||||||
|
for attachment in message.attachments:
|
||||||
|
attachments.append(attachment.url)
|
||||||
|
attachments_text = (
|
||||||
|
f"[Attached File{'s' if len(attachments) >= 2 else ''}: {', '.join(attachments)}]"
|
||||||
|
if len(attachments) >= 1
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
f.write(
|
||||||
|
f"{message.created_at.strftime('%d.%m.%Y %H:%M:%S')} {message.author} {message.id}: {message.clean_content} {attachments_text}\n"
|
||||||
|
)
|
||||||
|
f = discord.File(log_file)
|
||||||
|
await context.send(file=f)
|
||||||
|
os.remove(log_file)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot) -> None:
|
||||||
|
await bot.add_cog(Moderation(bot))
|
||||||
220
cogs/owner.py
Normal file
220
cogs/owner.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
"""
|
||||||
|
Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn (https://krypton.ninja)
|
||||||
|
Description:
|
||||||
|
🐍 A simple template to start to code your own and personalized Discord bot in Python
|
||||||
|
|
||||||
|
Version: 6.4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from discord.ext import commands
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
|
||||||
|
|
||||||
|
class Owner(commands.Cog, name="owner"):
|
||||||
|
def __init__(self, bot) -> None:
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command(
|
||||||
|
name="sync",
|
||||||
|
description="Synchonizes the slash commands.",
|
||||||
|
)
|
||||||
|
@app_commands.describe(scope="The scope of the sync. Can be `global` or `guild`")
|
||||||
|
@commands.is_owner()
|
||||||
|
async def sync(self, context: Context, scope: str) -> None:
|
||||||
|
"""
|
||||||
|
Synchonizes the slash commands.
|
||||||
|
|
||||||
|
:param context: The command context.
|
||||||
|
:param scope: The scope of the sync. Can be `global` or `guild`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if scope == "global":
|
||||||
|
await context.bot.tree.sync()
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="Slash commands have been globally synchronized.",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
return
|
||||||
|
elif scope == "guild":
|
||||||
|
context.bot.tree.copy_global_to(guild=context.guild)
|
||||||
|
await context.bot.tree.sync(guild=context.guild)
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="Slash commands have been synchronized in this guild.",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
return
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="The scope must be `global` or `guild`.", color=0xE02B2B
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.command(
|
||||||
|
name="unsync",
|
||||||
|
description="Unsynchonizes the slash commands.",
|
||||||
|
)
|
||||||
|
@app_commands.describe(
|
||||||
|
scope="The scope of the sync. Can be `global`, `current_guild` or `guild`"
|
||||||
|
)
|
||||||
|
@commands.is_owner()
|
||||||
|
async def unsync(self, context: Context, scope: str) -> None:
|
||||||
|
"""
|
||||||
|
Unsynchonizes the slash commands.
|
||||||
|
|
||||||
|
:param context: The command context.
|
||||||
|
:param scope: The scope of the sync. Can be `global`, `current_guild` or `guild`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if scope == "global":
|
||||||
|
context.bot.tree.clear_commands(guild=None)
|
||||||
|
await context.bot.tree.sync()
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="Slash commands have been globally unsynchronized.",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
return
|
||||||
|
elif scope == "guild":
|
||||||
|
context.bot.tree.clear_commands(guild=context.guild)
|
||||||
|
await context.bot.tree.sync(guild=context.guild)
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="Slash commands have been unsynchronized in this guild.",
|
||||||
|
color=0xBEBEFE,
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
return
|
||||||
|
embed = discord.Embed(
|
||||||
|
description="The scope must be `global` or `guild`.", color=0xE02B2B
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="load",
|
||||||
|
description="Load a cog",
|
||||||
|
)
|
||||||
|
@app_commands.describe(cog="The name of the cog to load")
|
||||||
|
@commands.is_owner()
|
||||||
|
async def load(self, context: Context, cog: str) -> None:
|
||||||
|
"""
|
||||||
|
The bot will load the given cog.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param cog: The name of the cog to load.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self.bot.load_extension(f"cogs.{cog}")
|
||||||
|
except Exception:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Could not load the `{cog}` cog.", color=0xE02B2B
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
return
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Successfully loaded the `{cog}` cog.", color=0xBEBEFE
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="unload",
|
||||||
|
description="Unloads a cog.",
|
||||||
|
)
|
||||||
|
@app_commands.describe(cog="The name of the cog to unload")
|
||||||
|
@commands.is_owner()
|
||||||
|
async def unload(self, context: Context, cog: str) -> None:
|
||||||
|
"""
|
||||||
|
The bot will unload the given cog.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param cog: The name of the cog to unload.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self.bot.unload_extension(f"cogs.{cog}")
|
||||||
|
except Exception:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Could not unload the `{cog}` cog.", color=0xE02B2B
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
return
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Successfully unloaded the `{cog}` cog.", color=0xBEBEFE
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="reload",
|
||||||
|
description="Reloads a cog.",
|
||||||
|
)
|
||||||
|
@app_commands.describe(cog="The name of the cog to reload")
|
||||||
|
@commands.is_owner()
|
||||||
|
async def reload(self, context: Context, cog: str) -> None:
|
||||||
|
"""
|
||||||
|
The bot will reload the given cog.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param cog: The name of the cog to reload.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self.bot.reload_extension(f"cogs.{cog}")
|
||||||
|
except Exception:
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Could not reload the `{cog}` cog.", color=0xE02B2B
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
return
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=f"Successfully reloaded the `{cog}` cog.", color=0xBEBEFE
|
||||||
|
)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="shutdown",
|
||||||
|
description="Make the bot shutdown.",
|
||||||
|
)
|
||||||
|
@commands.is_owner()
|
||||||
|
async def shutdown(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
Shuts down the bot.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
"""
|
||||||
|
embed = discord.Embed(description="Shutting down. Bye! :wave:", color=0xBEBEFE)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
await self.bot.close()
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="say",
|
||||||
|
description="The bot will say anything you want.",
|
||||||
|
)
|
||||||
|
@app_commands.describe(message="The message that should be repeated by the bot")
|
||||||
|
@commands.is_owner()
|
||||||
|
async def say(self, context: Context, *, message: str) -> None:
|
||||||
|
"""
|
||||||
|
The bot will say anything you want.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param message: The message that should be repeated by the bot.
|
||||||
|
"""
|
||||||
|
await context.send(message)
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="embed",
|
||||||
|
description="The bot will say anything you want, but within embeds.",
|
||||||
|
)
|
||||||
|
@app_commands.describe(message="The message that should be repeated by the bot")
|
||||||
|
@commands.is_owner()
|
||||||
|
async def embed(self, context: Context, *, message: str) -> None:
|
||||||
|
"""
|
||||||
|
The bot will say anything you want, but using embeds.
|
||||||
|
|
||||||
|
:param context: The hybrid command context.
|
||||||
|
:param message: The message that should be repeated by the bot.
|
||||||
|
"""
|
||||||
|
embed = discord.Embed(description=message, color=0xBEBEFE)
|
||||||
|
await context.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot) -> None:
|
||||||
|
await bot.add_cog(Owner(bot))
|
||||||
38
cogs/template.py
Normal file
38
cogs/template.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"""
|
||||||
|
Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn (https://krypton.ninja)
|
||||||
|
Description:
|
||||||
|
🐍 A simple template to start to code your own and personalized Discord bot in Python
|
||||||
|
|
||||||
|
Version: 6.4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
from discord.ext import commands
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
|
||||||
|
|
||||||
|
# Here we name the cog and create a new class for the cog.
|
||||||
|
class Template(commands.Cog, name="template"):
|
||||||
|
def __init__(self, bot) -> None:
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
# Here you can just add your own commands, you'll always need to provide "self" as first parameter.
|
||||||
|
|
||||||
|
@commands.hybrid_command(
|
||||||
|
name="testcommand",
|
||||||
|
description="This is a testing command that does nothing.",
|
||||||
|
)
|
||||||
|
async def testcommand(self, context: Context) -> None:
|
||||||
|
"""
|
||||||
|
This is a testing command that does nothing.
|
||||||
|
|
||||||
|
:param context: The application command context.
|
||||||
|
"""
|
||||||
|
# Do your stuff here
|
||||||
|
|
||||||
|
# Don't forget to remove "pass", I added this just because there's no content in the method.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# And then we finally add the cog to the bot so that it can load, unload, reload and use it's content.
|
||||||
|
async def setup(bot) -> None:
|
||||||
|
await bot.add_cog(Template(bot))
|
||||||
96
database/__init__.py
Normal file
96
database/__init__.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn (https://krypton.ninja)
|
||||||
|
Description:
|
||||||
|
🐍 A simple template to start to code your own and personalized Discord bot in Python
|
||||||
|
|
||||||
|
Version: 6.4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
import aiosqlite
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManager:
|
||||||
|
def __init__(self, *, connection: aiosqlite.Connection) -> None:
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
async def add_warn(
|
||||||
|
self, user_id: int, server_id: int, moderator_id: int, reason: str
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
This function will add a warn to the database.
|
||||||
|
|
||||||
|
:param user_id: The ID of the user that should be warned.
|
||||||
|
:param reason: The reason why the user should be warned.
|
||||||
|
"""
|
||||||
|
rows = await self.connection.execute(
|
||||||
|
"SELECT id FROM warns WHERE user_id=? AND server_id=? ORDER BY id DESC LIMIT 1",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
server_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async with rows as cursor:
|
||||||
|
result = await cursor.fetchone()
|
||||||
|
warn_id = result[0] + 1 if result is not None else 1
|
||||||
|
await self.connection.execute(
|
||||||
|
"INSERT INTO warns(id, user_id, server_id, moderator_id, reason) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
(
|
||||||
|
warn_id,
|
||||||
|
user_id,
|
||||||
|
server_id,
|
||||||
|
moderator_id,
|
||||||
|
reason,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
await self.connection.commit()
|
||||||
|
return warn_id
|
||||||
|
|
||||||
|
async def remove_warn(self, warn_id: int, user_id: int, server_id: int) -> int:
|
||||||
|
"""
|
||||||
|
This function will remove a warn from the database.
|
||||||
|
|
||||||
|
:param warn_id: The ID of the warn.
|
||||||
|
:param user_id: The ID of the user that was warned.
|
||||||
|
:param server_id: The ID of the server where the user has been warned
|
||||||
|
"""
|
||||||
|
await self.connection.execute(
|
||||||
|
"DELETE FROM warns WHERE id=? AND user_id=? AND server_id=?",
|
||||||
|
(
|
||||||
|
warn_id,
|
||||||
|
user_id,
|
||||||
|
server_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
await self.connection.commit()
|
||||||
|
rows = await self.connection.execute(
|
||||||
|
"SELECT COUNT(*) FROM warns WHERE user_id=? AND server_id=?",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
server_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async with rows as cursor:
|
||||||
|
result = await cursor.fetchone()
|
||||||
|
return result[0] if result is not None else 0
|
||||||
|
|
||||||
|
async def get_warnings(self, user_id: int, server_id: int) -> list:
|
||||||
|
"""
|
||||||
|
This function will get all the warnings of a user.
|
||||||
|
|
||||||
|
:param user_id: The ID of the user that should be checked.
|
||||||
|
:param server_id: The ID of the server that should be checked.
|
||||||
|
:return: A list of all the warnings of the user.
|
||||||
|
"""
|
||||||
|
rows = await self.connection.execute(
|
||||||
|
"SELECT user_id, server_id, moderator_id, reason, strftime('%s', created_at), id FROM warns WHERE user_id=? AND server_id=?",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
server_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async with rows as cursor:
|
||||||
|
result = await cursor.fetchall()
|
||||||
|
result_list = []
|
||||||
|
for row in result:
|
||||||
|
result_list.append(row)
|
||||||
|
return result_list
|
||||||
8
database/schema.sql
Normal file
8
database/schema.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `warns` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`user_id` varchar(20) NOT NULL,
|
||||||
|
`server_id` varchar(20) NOT NULL,
|
||||||
|
`moderator_id` varchar(20) NOT NULL,
|
||||||
|
`reason` varchar(255) NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
discord-bot:
|
||||||
|
build: .
|
||||||
|
image: python-discord-bot-template
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- ./database:/bot/database
|
||||||
|
- ./discord.log:/bot/discord.log
|
||||||
|
|
||||||
|
# Alternatively you can set the environment variables as such:
|
||||||
|
# /!\ The token shouldn't be written here, as this file is not ignored from Git /!\
|
||||||
|
# environment:
|
||||||
|
# - PREFIX=YOUR_BOT_PREFIX_HERE
|
||||||
|
# - INVITE_LINK=YOUR_BOT_INVITE_LINK_HERE
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
aiohttp
|
||||||
|
aiosqlite
|
||||||
|
discord.py==2.6.3
|
||||||
|
python-dotenv
|
||||||
Reference in New Issue
Block a user