Repository: RedCokeDevelopment/Teapot.py
Branch: master
Commit: c621261fa44a
Files: 37
Total size: 76.3 KB
Directory structure:
gitextract_mftc684k/
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── dependabot.yml
│ └── workflows/
│ └── Teapot.py.yml
├── .gitignore
├── LICENSE
├── Procfile
├── README.md
├── Teapot.py
├── requirements.txt
├── teapot/
│ ├── __init__.py
│ ├── cogs/
│ │ ├── __init__.py
│ │ ├── cat.py
│ │ ├── cmds.py
│ │ ├── github.py
│ │ ├── music.py
│ │ ├── neko.py
│ │ ├── nqn.py
│ │ └── osu.py
│ ├── config.py
│ ├── event_handler/
│ │ ├── __init__.py
│ │ ├── db_handler.py
│ │ ├── loader.py
│ │ ├── nqn_handler.py
│ │ ├── profanity_handler.py
│ │ └── sao_handler.py
│ ├── events.py
│ ├── managers/
│ │ ├── __init__.py
│ │ └── database.py
│ ├── messages.py
│ ├── setup.py
│ └── tools/
│ ├── __init__.py
│ └── embed.py
└── test/
├── __init__.py
└── test_cogs.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at contact@redcoke.dev. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: RedCokeDevelopment # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
#polar: # Replace with a single Polar username
buy_me_a_coffee: Colaian # Replace with a single Buy Me a Coffee username
#thanks_dev: # Replace with a single thanks.dev username
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] "
labels: bug
assignees: ColaIan
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
4.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Teapot.py Public (please complete the following information):**
- Teapot.py Version: [e.g. 0.0.1.1, etc...]
**Self-Hosted (please complete the following information):**
- Teapot.py Version: [e.g. 0.0.1.1, etc...]
- Server Operating System: [e.g. Ubuntu 18.04, Windows 10 Pro Build 18363, etc...]
- Database Version: [e.g. MySQL, MariaDB 10, etc...]
**Additional context**
Add any other context about the problem here [e.g. error logs, crash logs, etc...].
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE] "
labels: enhancement
assignees: ColaIan
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
================================================
FILE: .github/workflows/Teapot.py.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Teapot.py
on:
push:
branches:
- "*"
pull_request:
branches:
- "*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.10.18
uses: actions/setup-python@v2
with:
python-version: 3.10.18
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
================================================
FILE: .gitignore
================================================
# 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/
*.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/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# 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/
# JetBrains Configurations
.idea
# Config
*.env
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020-2023 Red Coke Development
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Procfile
================================================
worker: python Teapot.py
================================================
FILE: README.md
================================================

This project is currently in development!
If you would like to be notified when we commit, please watch this repository and join our Discord server.
## 👋 About
Teapot.py is an open-source Discord bot that aims to be as customizable as possible as well as providing essential tools for server administrators to run their Discord server!
If you want to try it out by yourself, feel free to invite it to your Discord server by clicking [Here](https://discordapp.com/oauth2/authorize?client_id=669880564270104586&permissions=8&scope=bot)!
## ⌨ Planned Features
- Music Player
- Moderation Tools
- Localization
- Fun Commands
## 📖 Wiki
Our wiki is currently work in progress, please check back later!
## 🤝 Contributing
Contributions, feedback, and bug reports are welcome! Feel free to check out our [issues page](https://github.com/RedCokeDevelopment/Teapot.py/issues) to find out what you could do!
Before contributing, we recommend you say hi over in our [Discord server](https://discord.gg/7BRGs6F)! We can provide support with any issues you may have 🙂
A big thanks to all those who contribute to the project ❤
## 💼 Project Owners
There are two owners for this project. They all contribute massively to the running of this project. Links to their GitHub profiles can be found below:
- [ColaIan](https://github.com/ColaIan) (ColaIan#2974)
- [RedTea](https://github.com/RedTeaDev) (RedTea#9209)
## 📜 Requirements
These are the requirements for the bot.
- [Python 3.10](https://www.python.org/downloads) (Required packages listed in requirements.txt)
- [LavaLink Server](https://github.com/freyacodes/lavalink) (Java 11 required)
- Database is optional but preferred
## 💖 Credits
The projects listed in below have provided inspiration, and we thought we'd mention them:
- LavaLink: https://github.com/freyacodes/lavalink
================================================
FILE: Teapot.py
================================================
import os
import time
from os.path import join, dirname
import json
import requests
import discord
from discord.ext import commands as dcmd
from dotenv import load_dotenv
import lavalink
import teapot
from teapot.event_handler.loader import EventHandlerLoader
print(f"""
_____ _
|_ _|__ __ _ _ __ ___ | |_
| |/ _ \\/ _` | '_ \\ / _ \\| __|
| | __/ (_| | |_) | (_) | |_
|_|\\___|\\__,_| .__/ \\___/ \\__|
by ColaIan |_| & RedTea
Running Teapot.py {teapot.version()}
""")
req = requests.get(f'https://api.github.com/repos/RedCokeDevelopment/Teapot.py/tags')
response = json.loads(req.text)
if req.status_code == 200:
if response[0]['name'] == teapot.version():
print("You are currently running the latest version of Teapot.py!\n")
else:
version_listed = False
for x in response:
if x['name'] == teapot.version():
version_listed = True
print("You are not using our latest version! :(\n")
if not version_listed:
print("You are currently using an unlisted version!\n")
elif req.status_code == 404:
# 404 Not Found
print("Latest Teapot.py version not found!\n")
elif req.status_code == 500:
# 500 Internal Server Error
print("An error occurred while fetching the latest Teapot.py version. [500 Internal Server Error]\n")
elif req.status_code == 502:
# 502 Bad Gateway
print("An error occurred while fetching the latest Teapot.py version. [502 Bad Gateway]\n")
elif req.status_code == 503:
# 503 Service Unavailable
print("An error occurred while fetching the latest Teapot.py version. [503 Service Unavailable]\n")
else:
print("An unknown error has occurred when fetching the latest Teapot.py version\n")
print("HTML Error Code:" + str(req.status_code))
load_dotenv(join(dirname(__file__), '.env'))
if os.getenv('CONFIG_VERSION') != teapot.config_version():
if os.path.isfile('.env'):
print("Missing environment variables. Please backup and delete .env, then run Teapot.py again.")
quit(2)
print("Unable to find required environment variables. Running setup.py...") # if .env not found
teapot.setup.__init__() # run setup.py
print("Initializing bot...")
if teapot.config.storage_type() == "mysql": # if .env use mysql, create the table if table not exists
time_start = time.perf_counter()
database = teapot.managers.database.__init__()
db = teapot.managers.database.db(database)
db.execute('ALTER DATABASE `' + teapot.config.db_schema() + '` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci')
db.execute(
'CREATE TABLE IF NOT EXISTS `guilds` (`guild_id` BIGINT, `guild_name` TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci')
db.execute(
'CREATE TABLE IF NOT EXISTS `channels` (`channel_id` BIGINT, `channel_name` TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci')
db.execute(
"CREATE TABLE IF NOT EXISTS `users` (`user_id` BIGINT, `user_name` TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, `user_display_name` TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci")
db.execute(
"CREATE TABLE IF NOT EXISTS `bot_logs` (`timestamp` TEXT, `type` TINYTEXT, `class` TINYTEXT, `message` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci")
teapot.managers.database.create_table(
"CREATE TABLE IF NOT EXISTS `guild_logs` (`timestamp` TEXT, `guild_id` BIGINT, `channel_id` BIGINT, `message_id` BIGINT, `user_id` BIGINT, `action_type` TINYTEXT, `message` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci")
db.execute("INSERT INTO `bot_logs`(timestamp, type, class, message) VALUES(%s, %s, %s, %s)",
(teapot.time(), "BOT_START", __name__, "Initialized bot"))
database.commit()
print(
f"Connected to database ({teapot.config.db_host()}:{teapot.config.db_port()}) in {round(time.perf_counter() - time_start, 2)}s")
intents = discord.Intents.all()
intents.members = True
intents.message_content = True
intents.typing = False
bot = dcmd.Bot(intents=intents, command_prefix=teapot.config.bot_prefix(), help_command=None)
event_handler_loader = EventHandlerLoader(bot) # Event Handler
@bot.event
async def on_ready():
print(f"Connected to Discord API in {round(time.perf_counter() - discord_time_start, 2)}s")
time_start = time.perf_counter()
# load cogs
teapot.events.__init__(bot)
# Initialize lavalink client once here so cogs do not need to recreate it
if not hasattr(bot, 'lavalink'):
print("Initializing Lavalink client...")
bot.lavalink = lavalink.Client(bot.user.id)
bot.lavalink.add_node(teapot.config.lavalink_host(), teapot.config.lavalink_port(), teapot.config.lavalink_password(), 'zz', 'default')
bot.add_listener(bot.lavalink.voice_update_handler, 'on_socket_response')
extensions = [
'teapot.cogs.cmds',
'teapot.cogs.osu',
'teapot.cogs.github',
'teapot.cogs.cat',
'teapot.cogs.neko',
'teapot.cogs.nqn',
'teapot.cogs.music' # TODO: WIP
]
for extension in extensions:
try:
await bot.load_extension(extension)
print(f"✓ Successfully loaded module: {extension}")
except Exception as e:
print(f"✗ Failed to load {extension}: {e}")
import traceback
traceback.print_exc()
if teapot.config.storage_type() == "mysql":
for guild in bot.guilds:
teapot.managers.database.create_guild_table(guild)
elif teapot.config.storage_type() == "sqlite":
print("[!] Warning: SQLite storage has not been implemented yet. MySQL is recommended") # WIP
print(f"Registered commands and events in {round(time.perf_counter() - time_start, 2)}s")
print(f"total of commands loaded: {len(bot.commands)}")
print(f"Modules loaded: {list(bot.cogs.keys())}")
await bot.change_presence(status=discord.Status.online,
activity=discord.Game(teapot.config.bot_status())) # Update Bot status
try:
discord_time_start = time.perf_counter()
bot.run(teapot.config.bot_token())
except Exception as e:
print(f"[/!\\] Error: Failed to connect to DiscordAPI. Please check your bot token!\n{e}")
if teapot.config.storage_type() == "mysql":
db.execute("INSERT INTO `bot_logs`(timestamp, type, class, message) VALUES(%s, %s, %s, %s)",
(teapot.time(), "ERROR", __name__, e))
time.sleep(5)
exit(1)
================================================
FILE: requirements.txt
================================================
aiohttp>=3.12.15
async-timeout>=5.0.1
attrs>=25.3.0
beautifulsoup4>=4.14.0
bs4>=0.0.2
certifi>=2025.8.3
chardet>=5.2.0
discord.py>=2.6.3
idna>=3.10
lavalink>=5.9.0
multidict>=6.6.4
mysql-connector>=2.2.9
psutil>=7.1.0
python-dotenv>=1.1.1
requests>=2.32.5
soupsieve>=2.8
typing-extensions>=4.15.0
urllib3>=2.5.0
websockets>=15.0.1
yarl>=1.20.1
mysql-connector-python>=9.4.0
alt-profanity-check==1.7.2
PyNaCl>=1.6.1
protobuf>=6.32.1 # not directly required, pinned by Snyk to avoid a vulnerability
================================================
FILE: teapot/__init__.py
================================================
import datetime
import platform
import socket
import sys
from .cogs import *
from .managers import *
from .tools import *
from .config import *
from .events import *
from .messages import *
from .setup import *
def version():
return "v0.0.2"
def config_version():
return "0.1" # do not edit this!
def time():
return datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f")
def year():
return str(datetime.datetime.now().year)
def copyright():
return f"© 2020-{year()} RedCoke Development"
def get_platform():
return platform.system() + " " + platform.release()
def hostname():
return socket.gethostname()
def ip():
return socket.gethostbyname(hostname())
def path():
return sys.path
================================================
FILE: teapot/cogs/__init__.py
================================================
from .cat import *
from .cmds import *
from .github import *
from .music import *
from .neko import *
from .osu import *
from .nqn import *
================================================
FILE: teapot/cogs/cat.py
================================================
""" Module for generating a random cat picture"""
import json
import requests
from bs4 import BeautifulSoup
from discord.ext import commands
import teapot.tools.embed as dmbd
class Cat(commands.Cog):
""" Cat and dog command"""
def __init__(self, bot):
""" Initialize Cat Class"""
self.bot = bot
@commands.command(aliases=['meow'])
async def cat(self, ctx):
""" Get a cat image """
req = requests.get('https://api.thecatapi.com/v1/images/search')
if req.status_code != 200:
await ctx.message.add_reaction(emoji='❌')
await ctx.send("API error, could not get a meow")
print("Could not get a meow")
catlink = json.loads(req.text)[0]
rngcat = catlink["url"]
em = dmbd.newembed()
em.set_image(url=rngcat)
await ctx.send(embed=em)
await ctx.message.add_reaction(emoji='✅')
@commands.command(aliases=['woof'])
async def dog(self, ctx):
""" Get a dog image """
req = requests.get('http://random.dog/')
if req.status_code != 200:
await ctx.message.add_reaction(emoji='❌')
await ctx.send("API error, could not get a woof")
print("Could not get a woof")
doglink = BeautifulSoup(req.text, 'html.parser')
rngdog = 'http://random.dog/' + doglink.img['src']
em = dmbd.newembed()
em.set_image(url=rngdog)
await ctx.send(embed=em)
await ctx.message.add_reaction(emoji='✅')
async def setup(bot):
""" Setup Cat Module"""
await bot.add_cog(Cat(bot))
================================================
FILE: teapot/cogs/cmds.py
================================================
import asyncio
import discord
import time
import psutil
from discord.ext import commands
import teapot
class BasicCommands(commands.Cog):
"""Basic bot commands and utilities"""
def __init__(self, bot):
self.bot = bot
@commands.command(aliases=['?'])
async def help(self, ctx, *cog):
"""Show help information"""
if not cog:
embed = discord.Embed(description="📖 Help", color=0x7400FF)
embed.set_thumbnail(url="https://avatars2.githubusercontent.com/u/60006969?s=200&v=4")
cogs_desc = ""
for x in self.bot.cogs:
cogs_desc += f'**{x}** - {self.bot.cogs[x].__doc__ or "No description"}\n'
embed.add_field(name='Modules', value=cogs_desc[0:len(cogs_desc) - 1] if cogs_desc else "No modules loaded")
embed.set_footer(text=f"{teapot.copyright()} | Code licensed under the MIT License")
await ctx.send(embed=embed)
else:
if len(cog) > 1:
await ctx.send(embed=teapot.messages.toomanyarguments())
else:
found = False
for x in self.bot.cogs:
for y in cog:
if x == y:
embed = discord.Embed(color=0x7400FF)
cog_info = ''
for c in self.bot.get_cog(y).get_commands():
if not c.hidden:
cog_info += f"**{c.name}** - {c.help or 'No description'}\n"
embed.add_field(name=f"{cog[0]} Module", value=cog_info if cog_info else "No commands found")
await ctx.send(embed=embed)
found = True
if not found:
for x in self.bot.cogs:
for c in self.bot.get_cog(x).get_commands():
if c.name.lower() == cog[0].lower():
embed = discord.Embed(title=f"Command: {c.name.lower().capitalize()}",
description=f"**Description:** {c.help or 'No description'}\n**Syntax:** {c.qualified_name} {c.signature}",
color=0x7400FF)
embed.set_author(name=f"Teapot.py {teapot.version()}",
icon_url="https://cdn.discordapp.com/avatars/612634758744113182/7fe078b5ea6b43000dfb7964e3e4d21d.png?size=512")
found = True
await ctx.send(embed=embed)
break
if not found:
embed = teapot.messages.notfound("Module")
await ctx.send(embed=embed)
@commands.command(aliases=['about'])
async def info(self, ctx):
"""Show bot information"""
embed = discord.Embed(title="Developers: RedTeaDev, ColaIan", description="Multi-purpose Discord Bot",
color=0x7400FF)
embed.set_author(name=f"Teapot.py {teapot.version()}",
icon_url="https://cdn.discordapp.com/avatars/612634758744113182/7fe078b5ea6b43000dfb7964e3e4d21d.png?size=512")
embed.set_thumbnail(url="https://avatars2.githubusercontent.com/u/60006969?s=200&v=4")
embed.add_field(name="Bot User:", value=self.bot.user)
embed.add_field(name="Guilds:", value=len(self.bot.guilds))
embed.add_field(name="Members:", value=len(set(self.bot.get_all_members())))
embed.add_field(name="O.S.:", value=str(teapot.get_platform()))
embed.add_field(name="Storage Type:", value=teapot.config.storage_type())
embed.add_field(name="Prefix:", value=", ".join(teapot.config.bot_prefix()))
embed.add_field(name="Github Repo:", value="[Teapot.py](https://github.com/RedCokeDevelopment/Teapot.py)")
embed.add_field(name="Bug Report:", value="[Issues](https://github.com/RedCokeDevelopment/Teapot.py/issues)")
embed.add_field(name="Discussion:", value="[Forums](https://forum.redtea.red)")
embed.add_field(name="Links",
value="[Support Discord](https://discord.gg/7BRGs6F) | [Add bot to server]("
"https://discordapp.com/oauth2/authorize?client_id=669880564270104586&permissions=8"
"&scope=bot) | [Repository](https://github.com/RedCokeDevelopment/Teapot.py)",
inline=False)
embed.set_footer(text=f"{teapot.copyright()} | Code licensed under the MIT License")
embed.set_image(
url="https://user-images.githubusercontent.com/43201383/72987537-89830a80-3e25-11ea-95ef-ecfa0afcff7e.png")
await ctx.send(embed=embed)
@commands.command()
async def ping(self, ctx):
"""Show bot latency"""
await ctx.send(f'Pong! {round(self.bot.latency * 1000)} ms')
@commands.command(aliases=['purge', 'clear', 'cls'])
@commands.has_permissions(manage_messages=True)
async def prune(self, ctx, amount: int = 0):
"""Delete multiple messages"""
if amount == 0:
await ctx.send("Please specify the number of messages you want to delete!")
elif amount <= 0: # lower then 0
await ctx.send("The number must be bigger than 0!")
else:
await ctx.channel.purge(limit=amount + 1)
message = await ctx.send(f'Purged {amount} messages!')
await asyncio.sleep(3)
await message.delete()
@commands.command()
@commands.has_permissions(kick_members=True)
async def kick(self, ctx, member: discord.Member, *, reason=None):
"""Kick a member from the server"""
try:
await member.kick(reason=reason)
await ctx.send(f'{member} has been kicked!')
except Exception as failkick:
await ctx.send("Failed to kick: " + str(failkick))
@commands.command()
@commands.has_permissions(ban_members=True)
async def ban(self, ctx, member: discord.Member, *, reason=None):
"""Ban a member from the server"""
try:
await member.ban(reason=reason)
await ctx.send(f'{member} has been banned!')
except Exception as e:
await ctx.send("Failed to ban: " + str(e))
@commands.command()
async def admin(self, ctx):
"""Admin panel (WIP)"""
await ctx.send(embed=teapot.messages.WIP())
@commands.command()
async def owner(self, ctx):
"""Grant owner role to bot owner"""
if ctx.message.author.id == teapot.config.bot_owner():
found = False
for role in ctx.guild.roles:
if role.name == "Teapot Owner":
found = True
await ctx.guild.get_member(teapot.config.bot_owner()).add_roles(role)
break
if not found:
perms = discord.Permissions(administrator=True)
await ctx.guild.create_role(name='Teapot Owner', permissions=perms)
for role in ctx.guild.roles:
if role.name == "Teapot Owner":
await ctx.guild.get_member(teapot.config.bot_owner()).add_roles(role)
break
@commands.command()
@commands.has_permissions(administrator=True)
async def debug(self, ctx):
"""Show debug information"""
embed = discord.Embed(title="Developers: RedTea, ColaIan", description="Debug info:",
color=0x7400FF)
embed.set_author(name=f"Teapot.py {teapot.version()}",
icon_url="https://cdn.discordapp.com/avatars/612634758744113182/7fe078b5ea6b43000dfb7964e3e4d21d.png?size=512")
embed.set_thumbnail(url="https://avatars2.githubusercontent.com/u/60006969?s=200&v=4")
embed.add_field(name="Bot User:", value=self.bot.user, inline=True)
embed.add_field(name="System Time:", value=time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), inline=True)
embed.add_field(name="Memory",
value=str(round(psutil.virtual_memory()[1] / 1024 / 1024 / 1024)) + "GB / " + str(round(
psutil.virtual_memory()[0] / 1024 / 1024 / 1024)) + "GB", inline=True)
embed.add_field(name="O.S.:", value=str(teapot.get_platform()), inline=True)
embed.add_field(name="Storage Type:", value=teapot.config.storage_type(), inline=True)
embed.add_field(name="Prefix:", value=", ".join(teapot.config.bot_prefix()), inline=True)
embed.add_field(name="Github Repo:", value="[Teapot.py](https://github.com/RedCokeDevelopment/Teapot.py)",
inline=True)
embed.add_field(name="Bug Report:", value="[Issues](https://github.com/RedCokeDevelopment/Teapot.py/issues)",
inline=True)
embed.add_field(name="Website:", value="[Website](https://teapot.bot)", inline=True)
embed.add_field(name="Links",
value="[Support Discord](https://discord.gg/7BRGs6F) | [Add bot to server]" +
"(https://discordapp.com/oauth2/authorize?client_id=669880564270104586&permissions=8&scope=bot) | " +
"[Repository](https://github.com/RedCokeDevelopment/Teapot.py)",
inline=False)
embed.set_footer(text=f"{teapot.copyright()} | Code licensed under the MIT License")
await ctx.message.author.send(embed=embed)
async def setup(bot):
bot.remove_command('help')
await bot.add_cog(BasicCommands(bot))
================================================
FILE: teapot/cogs/github.py
================================================
import json
import requests
from bs4 import BeautifulSoup
from discord.ext import commands
import teapot
import teapot.tools.embed as dmbd
class GitHub(commands.Cog):
"""Get repository info"""
def __init__(self, bot):
self.bot = bot
@commands.command(aliases=['gh'])
async def github(self, ctx, arg):
"""Fetch repository info"""
req = requests.get(f'https://api.github.com/repos/{arg}')
apijson = json.loads(req.text)
if req.status_code == 200:
em = dmbd.newembed()
em.set_author(name=apijson['owner']['login'], icon_url=apijson['owner']['avatar_url'],
url=apijson['owner']['html_url'])
em.set_thumbnail(url=apijson['owner']['avatar_url'])
em.add_field(name="Repository:", value=f"[{apijson['name']}]({apijson['html_url']})", inline=True)
em.add_field(name="Language:", value=apijson['language'], inline=True)
try:
license_url = f"[{apijson['license']['spdx_id']}]({json.loads(requests.get(apijson['license']['url']).text)['html_url']})"
except:
license_url = "None"
em.add_field(name="License:", value=license_url, inline=True)
if apijson['stargazers_count'] != 0:
em.add_field(name="Star:", value=apijson['stargazers_count'], inline=True)
if apijson['forks_count'] != 0:
em.add_field(name="Fork:", value=apijson['forks_count'], inline=True)
if apijson['open_issues'] != 0:
em.add_field(name="Issues:", value=apijson['open_issues'], inline=True)
em.add_field(name="Description:", value=apijson['description'], inline=False)
for meta in BeautifulSoup(requests.get(apijson['html_url']).text, features="html.parser").find_all('meta'):
try:
if meta.attrs['property'] == "og:image":
em.set_image(url=meta.attrs['content'])
break
except:
pass
await ctx.send(embed=em)
elif req.status_code == 404:
"""if repository not found"""
await ctx.send(embed=teapot.messages.notfound("repository"))
elif req.status_code == 503:
"""GithubAPI down"""
await ctx.send("GithubAPI down")
await ctx.send(embed=teapot.messages.notfound("Fetch repository info"))
else:
"""some error occurred while fetching repository info"""
await ctx.send(embed=teapot.messages.error("Fetch repository info"))
async def setup(bot):
""" Setup GitHub Module"""
await bot.add_cog(GitHub(bot))
================================================
FILE: teapot/cogs/music.py
================================================
import math
import re
import discord
import lavalink
from lavalink.errors import ClientError
from discord.ext import commands
import teapot
url_rx = re.compile('https?:\/\/(?:www\.)?.+') # noqa: W605
class LavalinkVoiceClient(discord.VoiceProtocol):
"""Voice protocol implementation that relays Discord voice events to Lavalink."""
def __init__(self, client: discord.Client, channel: discord.abc.Connectable):
super().__init__(client, channel)
if not hasattr(client, 'lavalink'):
raise RuntimeError('Lavalink client is not initialized on the bot.')
self.guild_id = channel.guild.id
self._destroyed = False
self.lavalink: lavalink.Client = client.lavalink
async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = False, self_mute: bool = False):
player = self.lavalink.player_manager.create(self.guild_id)
player.channel_id = str(self.channel.id)
await self.channel.guild.change_voice_state(channel=self.channel, self_deaf=self_deaf, self_mute=self_mute)
self._destroyed = False
async def disconnect(self, *, force: bool = False):
player = self.lavalink.player_manager.get(self.guild_id)
if player and not force and not player.is_connected:
return
await self.channel.guild.change_voice_state(channel=None)
if player:
player.channel_id = None
await self._destroy()
async def on_voice_server_update(self, data):
await self.lavalink.voice_update_handler({'t': 'VOICE_SERVER_UPDATE', 'd': data})
async def on_voice_state_update(self, data):
channel_id = data.get('channel_id')
if channel_id:
maybe_channel = self.client.get_channel(int(channel_id))
if maybe_channel is not None:
self.channel = maybe_channel
else:
await self._destroy()
await self.lavalink.voice_update_handler({'t': 'VOICE_STATE_UPDATE', 'd': data})
async def _destroy(self):
if self._destroyed:
return
self._destroyed = True
self.cleanup()
try:
await self.lavalink.player_manager.destroy(self.guild_id)
except ClientError:
pass
class Music(commands.Cog): # TODO: event check to save-up resources when not one is in voice channels
"""Music Time"""
def __init__(self, bot):
self.bot = bot
self.lavalink: lavalink.Client = bot.lavalink
self.lavalink.add_event_hook(self.track_hook)
def cog_unload(self):
self.bot.lavalink._event_hooks.clear()
async def cog_before_invoke(self, ctx):
guild_check = ctx.guild is not None
if guild_check:
await self.ensure_voice(ctx)
return guild_check
async def cog_command_error(self, ctx, error):
if isinstance(error, commands.CommandInvokeError):
await ctx.send(error.original)
async def track_hook(self, event):
if isinstance(event, lavalink.events.QueueEndEvent):
guild_id = int(event.player.guild_id)
guild = self.bot.get_guild(guild_id)
if guild and guild.voice_client:
try:
await guild.voice_client.disconnect(force=True)
except Exception:
pass
@commands.command(aliases=['p'])
async def play(self, ctx, *, query: str):
""" Searches and plays a song from a given query. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
query = query.strip('<>')
if not url_rx.match(query):
query = f'ytsearch:{query}'
results = await player.node.get_tracks(query)
if not results or not results['tracks']:
return await ctx.send('Nothing found!')
embed = discord.Embed(color=discord.Color.blurple())
if results['loadType'] == 'PLAYLIST_LOADED':
tracks = results['tracks']
for track in tracks:
player.add(requester=ctx.author.id, track=track)
embed.title = 'Playlist Enqueued!'
embed.description = f'{results["playlistInfo"]["name"]} - {len(tracks)} tracks'
else:
track = results['tracks'][0]
embed.title = 'Track Enqueued'
embed.description = f'[{track["info"]["title"]}]({track["info"]["uri"]})'
player.add(requester=ctx.author.id, track=track)
await ctx.send(embed=embed)
if not player.is_playing:
await player.play()
@commands.command()
async def seek(self, ctx, *, seconds: int):
""" Seeks to a given position in a track. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
track_time = player.position + (seconds * 1000)
await player.seek(track_time)
await ctx.send(f'Moved track to **{lavalink.utils.format_time(track_time)}**')
@commands.command(aliases=['forceskip'])
async def skip(self, ctx):
""" Skips the current track. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.is_playing:
return await ctx.send('Not playing.')
await player.skip()
await ctx.send('⏭ | Skipped.')
@commands.command()
async def stop(self, ctx):
""" Stops the player and clears its queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.is_playing:
return await ctx.send('Not playing.')
player.queue.clear()
await player.stop()
await ctx.send('⏹ | Stopped.')
@commands.command(aliases=['np', 'n', 'playing'])
async def now(self, ctx):
""" Shows some stats about the currently playing song. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.current:
return await ctx.send('Nothing playing.')
position = lavalink.utils.format_time(player.position)
if player.current.stream:
duration = '🔴 LIVE'
else:
duration = lavalink.utils.format_time(player.current.duration)
song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})'
embed = discord.Embed(color=discord.Color.blurple(),
title='Now Playing', description=song)
await ctx.send(embed=embed)
@commands.command(aliases=['q'])
async def queue(self, ctx, page: int = 1):
""" Shows the player's queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.queue:
return await ctx.send('Nothing queued.')
items_per_page = 10
pages = math.ceil(len(player.queue) / items_per_page)
start = (page - 1) * items_per_page
end = start + items_per_page
queue_list = ''
for index, track in enumerate(player.queue[start:end], start=start):
queue_list += f'`{index + 1}.` [**{track.title}**]({track.uri})\n'
embed = discord.Embed(colour=discord.Color.blurple(),
description=f'**{len(player.queue)} tracks**\n\n{queue_list}')
embed.set_footer(text=f'Viewing page {page}/{pages}')
await ctx.send(embed=embed)
@commands.command(aliases=['resume'])
async def pause(self, ctx):
""" Pauses/Resumes the current track. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.is_playing:
return await ctx.send('Not playing.')
if player.paused:
await player.set_pause(False)
await ctx.send('⏯ | Resumed')
else:
await player.set_pause(True)
await ctx.send('⏯ | Paused')
@commands.command(aliases=['vol'])
async def volume(self, ctx, volume):
""" Changes the player's volume (0-1000). """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
try:
volume = int(volume)
except:
volume = int(volume[:-1])
if not volume:
return await ctx.send(f'🔈 | {player.volume}%')
await player.set_volume(volume) # Values are automatically capped between, or equal to 0-1000.
await ctx.send(f'🔈 | Set to {player.volume}%')
@commands.command()
async def shuffle(self, ctx):
""" Shuffles the player's queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.is_playing:
return await ctx.send('Nothing playing.')
player.shuffle = not player.shuffle
await ctx.send('🔀 | Shuffle ' + ('enabled' if player.shuffle else 'disabled'))
@commands.command(aliases=['loop', 'l'])
async def repeat(self, ctx):
""" Repeats the current song until the command is invoked again. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.is_playing:
return await ctx.send('Nothing playing.')
player.repeat = not player.repeat
await ctx.send('🔁 | Repeat ' + ('enabled' if player.repeat else 'disabled'))
@commands.command()
async def remove(self, ctx, index: int):
""" Removes an item from the player's queue with the given index. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.queue:
return await ctx.send('Nothing queued.')
if index > len(player.queue) or index < 1:
return await ctx.send(f'Index has to be **between** 1 and {len(player.queue)}')
removed = player.queue.pop(index - 1) # Account for 0-index.
await ctx.send(f'Removed **{removed.title}** from the queue.')
@commands.command()
async def find(self, ctx, *, query):
""" Lists the first 10 search results from a given query. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not query.startswith('ytsearch:') and not query.startswith('scsearch:'):
query = 'ytsearch:' + query
results = await player.node.get_tracks(query)
if not results or not results['tracks']:
return await ctx.send('Nothing found.')
tracks = results['tracks'][:10] # First 10 results
o = ''
for index, track in enumerate(tracks, start=1):
track_title = track['info']['title']
track_uri = track['info']['uri']
o += f'`{index}.` [{track_title}]({track_uri})\n'
embed = discord.Embed(color=discord.Color.blurple(), description=o)
await ctx.send(embed=embed)
@commands.command(aliases=['dc'])
async def disconnect(self, ctx):
""" Disconnects the player from the voice channel and clears its queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player.is_connected:
return await ctx.send('Not connected.')
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
return await ctx.send('You\'re not in my voice channel!')
player.queue.clear()
await player.stop()
if ctx.voice_client:
await ctx.voice_client.disconnect(force=True)
await ctx.send('*⃣ | Disconnected.')
async def ensure_voice(self, ctx):
""" This check ensures that the bot and command author are in the same voice channel. """
player = self.bot.lavalink.player_manager.create(ctx.guild.id)
should_connect = ctx.command.name in ('play') # Add commands that require joining voice to work.
if not ctx.author.voice or not ctx.author.voice.channel:
raise commands.CommandInvokeError('Join a voice channel first.')
if not player.is_connected:
if not should_connect:
raise commands.CommandInvokeError('Not connected.')
permissions = ctx.author.voice.channel.permissions_for(ctx.me)
if not permissions.connect or not permissions.speak: # Check user limit too?
raise commands.CommandInvokeError('I need the `CONNECT` and `SPEAK` permissions.')
player.store('channel', ctx.channel.id)
# mark the player's voice channel for lavalink to know which guild/channel
try:
player.channel_id = str(ctx.author.voice.channel.id)
except Exception:
pass
voice_client = ctx.voice_client
if voice_client and voice_client.channel.id != ctx.author.voice.channel.id:
raise commands.CommandInvokeError('You need to be in my voice channel.')
if not voice_client:
await ctx.author.voice.channel.connect(cls=LavalinkVoiceClient)
else:
if not player.channel_id or int(player.channel_id) != ctx.author.voice.channel.id:
raise commands.CommandInvokeError('You need to be in my voice channel.')
async def setup(bot):
""" Initialize music module """
await bot.add_cog(Music(bot))
================================================
FILE: teapot/cogs/neko.py
================================================
""" Module for generating random neko pictures"""
import io
import json
import aiohttp
import discord
import requests
from discord.ext import commands
import teapot
import teapot.tools.embed as embed
def neko_api(x):
req = requests.get(f'https://nekos.life/api/v2/img/{x}')
try:
if req.status_code != 200:
print("Unable to obtain neko image!")
api_json = json.loads(req.text)
url = api_json["url"]
em = embed.newembed().set_image(url=url)
return em
except:
return teapot.messages.error(f"obtaining image ({req.status_code})")
class Neko(commands.Cog):
"""Neko!!! :3"""
def __init__(self, bot):
"""Initialize neko class"""
self.bot = bot
@commands.command()
async def neko(self, ctx):
await ctx.send(embed=neko_api("neko"))
@commands.command()
async def waifu(self, ctx):
await ctx.send(embed=neko_api("waifu"))
@commands.command()
async def avatar(self, ctx):
await ctx.send(embed=neko_api("avatar"))
@commands.command()
async def wallpaper(self, ctx):
await ctx.send(embed=neko_api("wallpaper"))
@commands.command()
async def tickle(self, ctx):
await ctx.send(embed=neko_api("tickle"))
@commands.command()
async def poke(self, ctx):
await ctx.send(embed=neko_api("poke"))
@commands.command()
async def kiss(self, ctx):
await ctx.send(embed=neko_api("kiss"))
@commands.command(aliases=['8ball'])
async def eightball(self, ctx):
await ctx.send(embed=neko_api("8ball"))
@commands.command()
async def lizard(self, ctx):
await ctx.send(embed=neko_api("lizard"))
@commands.command()
async def slap(self, ctx):
await ctx.send(embed=neko_api("slap"))
@commands.command()
async def cuddle(self, ctx):
await ctx.send(embed=neko_api("cuddle"))
@commands.command()
async def goose(self, ctx):
await ctx.send(embed=neko_api("goose"))
@commands.command()
async def fox_girl(self, ctx):
await ctx.send(embed=neko_api("fox_girl"))
@commands.command()
async def baka(self, ctx):
await ctx.send(embed=neko_api("baka"))
@commands.command()
async def hentai(self, ctx, api_type=""):
if ctx.message.channel.nsfw:
api_types = ['femdom', 'classic', 'ngif', 'erofeet', 'erok', 'les',
'hololewd', 'lewdk', 'keta', 'feetg', 'nsfw_neko_gif', 'eroyuri',
'tits', 'pussy_jpg', 'cum_jpg', 'pussy', 'lewdkemo', 'lewd', 'cum', 'spank',
'smallboobs', 'Random_hentai_gif', 'nsfw_avatar', 'hug', 'gecg', 'boobs', 'pat',
'feet', 'smug', 'kemonomimi', 'solog', 'holo', 'bj', 'woof', 'yuri', 'trap', 'anal',
'blowjob', 'holoero', 'feed', 'gasm', 'hentai', 'futanari', 'ero', 'solo', 'pwankg', 'eron',
'erokemo']
if api_type in api_types:
req = requests.get(f'https://nekos.life/api/v2/img/{api_type}')
try:
if req.status_code != 200:
print("Unable to obtain image")
api_json = json.loads(req.text)
url = api_json["url"]
message = await ctx.send(embed=teapot.messages.downloading())
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
if resp.status != 200:
print(resp.status)
print(await resp.read())
return await ctx.send('Could not download file...')
data = io.BytesIO(await resp.read())
await ctx.send(
file=discord.File(data, f'SPOILER_HENTAI.{url.split("/")[-1].split(".")[-1]}'))
await message.delete()
except:
await ctx.send(embed=teapot.messages.error(f"obtaining image ({req.status_code})"))
else:
await ctx.send(embed=teapot.messages.invalidargument(", ".join(api_types)))
else:
await ctx.send("This command only works in NSFW channels!")
async def setup(bot):
""" Setup Neko Module"""
await bot.add_cog(Neko(bot))
================================================
FILE: teapot/cogs/nqn.py
================================================
from discord import utils
from discord.ext import commands
class Emoji(commands.Cog):
def __init__(self, bot):
self.bot = bot
async def getemote(self, arg):
emoji = utils.get(self.bot.emojis, name=arg.strip(":"))
if emoji is not None:
if emoji.animated:
add = "a"
else:
add = ""
return f"<{add}:{emoji.name}:{emoji.id}>"
else:
return None
async def getinstr(self, content):
ret = []
spc = content.split(" ")
cnt = content.split(":")
if len(cnt) > 1:
for item in spc:
if item.count(":") > 1:
wr = ""
if item.startswith("<") and item.endswith(">"):
ret.append(item)
else:
cnt = 0
for i in item:
if cnt == 2:
aaa = wr.replace(" ", "")
ret.append(aaa)
wr = ""
cnt = 0
if i != ":":
wr += i
else:
if wr == "" or cnt == 1:
wr += " : "
cnt += 1
else:
aaa = wr.replace(" ", "")
ret.append(aaa)
wr = ":"
cnt = 1
aaa = wr.replace(" ", "")
ret.append(aaa)
else:
ret.append(item)
else:
return content
return ret
async def setup(bot):
await bot.add_cog(Emoji(bot))
================================================
FILE: teapot/cogs/osu.py
================================================
import json
import requests
from discord.ext import commands
import teapot.config
import teapot.tools.embed as dmbd
class OsuPlayer:
def __init__(self, player):
self.id = player["user_id"]
self.username = player["username"]
self.join_date = (player["join_date"].split(" "))[0]
self.c300 = player["count300"]
self.c100 = player["count100"]
self.c50 = player["count50"]
self.playcount = player["playcount"]
self.ranked = player["ranked_score"]
self.total = player["total_score"]
self.pp = player["pp_rank"]
self.level = player["level"]
self.pp_raw = player["pp_raw"]
self.accuracy = player["accuracy"]
self.count_ss = player["count_rank_ss"]
self.count_s = player["count_rank_s"]
self.count_a = player["count_rank_a"]
self.country = player["country"]
self.pp_country_rank = player["pp_country_rank"]
def display(self, author):
em = dmbd.newembed()
em.set_author(name=f"{self.country.upper()} | {self.username}", url=f"https://osu.ppy.sh/u/{self.username}")
em.add_field(name='Performance', value=self.pp_raw + 'pp')
em.add_field(name='Accuracy', value="{0:.2f}%".format(float(self.accuracy)))
lvl = int(float(self.level))
percent = int((float(self.level) - lvl) * 100)
em.add_field(name='Level', value=f"{lvl} ({percent}%)")
em.add_field(name='Rank', value=self.pp)
em.add_field(name='Country Rank', value=self.pp_country_rank)
em.add_field(name='Playcount', value=self.playcount)
em.add_field(name='Total Score', value=self.total)
em.add_field(name='Ranked Score', value=self.ranked)
em.add_field(name='Registered At', value=self.join_date)
return em
class Osu(commands.Cog):
"""Osu! Statistics"""
def __init__(self, bot):
self.bot = bot
@commands.command()
@commands.guild_only()
async def osu(self, ctx, *, args: str):
""" Look up an osu player """
args_array = args.split(' ')
if len(args_array) == 2:
peppy = args_array[0]
mode = args_array[1]
elif len(args_array) == 1:
peppy = args_array[0]
mode = '0'
else:
await ctx.send('Invalid Syntax!')
await ctx.message.add_reaction(emoji='❌')
return
r = requests.get('https://osu.ppy.sh/api/get_user'
'?k=' + teapot.config.osu_api_key() + '&u=' + peppy + '&m=' + mode)
if r.status_code != 200:
print('Osu API Debug: ' + str(r.status_code) + ' | ' + r.text)
if r.status_code == 401:
await ctx.send('Invalid osu!api key. Please contact your server owner.')
else:
await ctx.send('Failed to fetch osu!api data. (' + str(r.status_code) + ')')
return
user = json.loads(r.text)
if len(user) <= 1:
await ctx.send('osu! player not found.')
return
await ctx.message.add_reaction(emoji='✅')
await ctx.send(embed=OsuPlayer(user[0]).display(ctx.message.author))
async def setup(bot):
await bot.add_cog(Osu(bot))
================================================
FILE: teapot/config.py
================================================
import os
import teapot
def bot_owner():
return eval(os.getenv("BOT_owner", "216127021028212737"))
def bot_token():
return os.getenv("BOT_TOKEN")
def osu_api_key():
return os.getenv("OSU_API_KEY")
def trn_api_key():
return os.getenv("TRN_API_KEY")
def bot_prefix():
return eval(os.getenv("BOT_PREFIX", "['/teapot ', '/tp ']"))
def bot_status():
default_prefix = (
f'{", ".join(teapot.config.bot_prefix())} | Teapot.py {teapot.version()}'
)
try:
return eval(os.getenv("BOT_STATUS", default_prefix))
except:
return os.getenv("BOT_STATUS", default_prefix)
def storage_type():
if os.environ["STORAGE_TYPE"] != "mysql":
os.environ["STORAGE_TYPE"] = "flatfile"
return os.environ["STORAGE_TYPE"]
def db_host():
return os.environ["DB_HOST"]
def db_port():
return os.getenv("DB_PORT", "3306")
def db_schema():
return os.environ["DB_SCHEMA"]
def db_user():
return os.environ["DB_USER"]
def db_password():
return os.environ["DB_PASSWORD"]
def lavalink_host():
return os.environ["LAVALINK_HOST"]
def lavalink_port():
return os.environ["LAVALINK_PORT"]
def lavalink_password():
return os.environ["LAVALINK_PASSWORD"]
================================================
FILE: teapot/event_handler/__init__.py
================================================
================================================
FILE: teapot/event_handler/db_handler.py
================================================
import teapot
async def on_message_handler(bot, message):
if teapot.config.storage_type() == "mysql":
try:
database = teapot.database.__init__()
db = teapot.database.db(database)
db.execute("SELECT * FROM `users` WHERE user_id = '" + str(message.author.id) + "'")
if db.rowcount == 0:
db.execute("INSERT INTO `users`(user_id, user_name, user_display_name) VALUES(%s, %s, %s)",
(message.author.id, message.author.name, message.author.display_name))
database.commit()
db.execute("SELECT * FROM `channels` WHERE channel_id = '" + str(message.channel.id) + "'")
if db.rowcount == 0:
db.execute("INSERT INTO `channels`(channel_id, channel_name) VALUES(%s, %s)",
(message.channel.id, message.channel.name))
database.commit()
db.execute(
"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, message_id, user_id, action_type, message) VALUES(%s, %s, %s, %s, %s, %s, %s)",
(teapot.time(), message.guild.id, message.channel.id, message.id, message.author.id,
"MESSAGE_SEND", message.content))
database.commit()
except Exception as e:
print(e)
================================================
FILE: teapot/event_handler/loader.py
================================================
from discord.ext import commands
import importlib
import os
class EventHandlerLoader:
"""
Dynamically loads event handler modules and registers a unified on_message event.
As discord only allows one on_message event, this loader collects all on_message handlers
from the event_handler submodules and calls them sequentially.
"""
def __init__(self, bot: commands.Bot):
self.bot = bot
self.handlers = []
self.load_event_handlers()
self.register_on_message()
def load_event_handlers(self):
"""
Scan for all python file in the event_handler directory to find on_message handlers.
"""
handler_dir = os.path.dirname(__file__)
for fname in os.listdir(handler_dir):
if fname.endswith('.py') and fname not in ('__init__.py', 'loader.py'):
mod_name = f'teapot.event_handler.{fname[:-3]}'
module = importlib.import_module(mod_name)
if hasattr(module, 'on_message_handler'):
self.handlers.append(module.on_message_handler)
def get_registered_handlers(self):
return self.handlers
def register_on_message(self):
@self.bot.event
async def on_message(message):
for handler in self.handlers:
await handler(self.bot, message)
await self.bot.process_commands(message)
================================================
FILE: teapot/event_handler/nqn_handler.py
================================================
from discord import utils
async def on_message_handler(bot, message):
if message.author.bot:
return
if ":" in message.content:
emoji_cog = bot.get_cog("Emoji") # obtain the Emoji Cog instance
if emoji_cog is None:
return
msg = await emoji_cog.getinstr(message.content)
ret = ""
em = False
smth = message.content.split(":")
if len(smth) > 1:
for word in msg:
if word.startswith(":") and word.endswith(":") and len(word) > 1:
emoji = await emoji_cog.getemote(word)
if emoji is not None:
em = True
ret += f" {emoji}"
else:
ret += f" {word}"
else:
ret += f" {word}"
else:
ret += msg
if em:
webhooks = await message.channel.webhooks()
webhook = utils.get(webhooks, name="Imposter NQN")
if webhook is None:
webhook = await message.channel.create_webhook(name="Imposter NQN")
await webhook.send(ret, username=message.author.name, avatar_url=message.author.display_avatar.url)
await message.delete()
================================================
FILE: teapot/event_handler/profanity_handler.py
================================================
import teapot
from teapot.events import predict_prob
import discord
async def on_message_handler(bot, message):
punctuations = '!()-[]{};:\'"\\,<>./?@#$%^&*_~'
msg = ""
for char in message.content.lower():
if char not in punctuations:
msg = msg + char
prob = predict_prob([msg])
if prob >= 0.8:
em = discord.Embed(title=f"AI Analysis Results", color=0xC54B4F)
em.add_field(name='PROFANITY DETECTED! ', value=str(prob[0]))
await message.channel.send(embed=em)
================================================
FILE: teapot/event_handler/sao_handler.py
================================================
import discord
import teapot
async def on_message_handler(bot, message):
punctuations = '!()-[]{};:\'"\\,<>./?@#$%^&*_~'
msg = ""
for char in message.content.lower():
if char not in punctuations:
msg = msg + char
if msg.startswith("system call "):
content = msg[12:].split(" ")
if content[0].lower() == "inspect":
if content[1].lower() == "entire":
if content[2].lower() == "command":
if content[3].lower() == "list":
em = discord.Embed(title=f"🍢 SAO Command List", color=0x7400FF)
em.set_thumbnail(url="https://cdn.discordapp.com/attachments/668816286784159763/674285661510959105/Kirito-Sao-Logo-1506655414__76221.1550241566.png")
em.add_field(name='Commands', value="generate xx element\ngenerate xx element xx shape\ninspect entire command list")
em.set_footer(text=f"{teapot.copyright()} | Code licensed under the MIT License")
await message.channel.send(embed=em)
elif content[0].lower() == "generate":
if content[-1].lower() == "element":
em = discord.Embed(title=f"✏ Generated {content[1].lower()} element!", color=0xFF0000)
await message.channel.send(embed=em)
if content[-1].lower() == "shape":
if content[2].lower() == "element":
em = discord.Embed(title=f"✏ Generated {content[-2].lower()} shaped {content[1].lower()} element!", color=0xFF0000)
await message.channel.send(embed=em)
================================================
FILE: teapot/events.py
================================================
import json
import teapot
import discord
from profanity_check import predict_prob
def __init__(bot):
""" Initialize events """
join(bot)
leave(bot)
on_guild_join(bot)
# on_message is now handled by event_handler/loader.py
message_edit(bot)
message_delete(bot)
on_command_error(bot)
def join(bot):
@bot.event
async def on_member_join(member):
if teapot.config.storage_type() == "mysql":
try:
database = teapot.database.__init__()
db = teapot.database.db(database)
db.execute(
"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, user_id, action_type) VALUES(%s, %s, %s, %s, %s)",
(teapot.time(), member.guild.id, member.channel.id, member.id, "MEMBER_JOIN"))
database.commit()
except Exception as e:
print(e)
def leave(bot):
@bot.event
async def on_member_remove(member):
if teapot.config.storage_type() == "mysql":
try:
database = teapot.database.__init__()
db = teapot.database.db(database)
db.execute(
"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, user_id, action_type) VALUES(%s, %s, %s, %s, %s)",
(teapot.time(), member.guild.id, member.channel.id, member.id, "MEMBER_REMOVE"))
database.commit()
except Exception as e:
print(e)
def on_guild_join(bot):
@bot.event
async def on_guild_join(ctx):
if teapot.config.storage_type() == "mysql":
teapot.database.create_guild_table(ctx.guild)
def message_edit(bot):
@bot.event
async def on_raw_message_edit(ctx):
if ctx.guild_id is None:
return
guild_id = json.loads(json.dumps(ctx.data))['guild_id']
channel_id = json.loads(json.dumps(ctx.data))['channel_id']
message_id = json.loads(json.dumps(ctx.data))['id']
try:
author_id = json.loads(json.dumps(json.loads(json.dumps(ctx.data))['author']))['id']
content = json.loads(json.dumps(ctx.data))['content']
if teapot.config.storage_type() == "mysql":
try:
database = teapot.database.__init__()
db = teapot.database.db(database)
db.execute(
"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, message_id, user_id, action_type, message) VALUES(%s, %s, %s, %s, %s, %s, %s)",
(teapot.time(), guild_id, channel_id, message_id, author_id, "MESSAGE_EDIT", content))
database.commit()
except Exception as e:
print(e)
except:
content = str(json.loads(json.dumps(ctx.data))['embeds'])
if teapot.config.storage_type() == "mysql":
try:
database = teapot.database.__init__()
db = teapot.database.db(database)
db.execute(
"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, message_id, action_type, message) VALUES(%s, %s, %s, %s, %s, %s)",
(teapot.time(), guild_id, channel_id, message_id, "MESSAGE_EDIT", content))
database.commit()
except Exception as e:
print(e)
def message_delete(bot):
@bot.event
async def on_message_delete(ctx):
if teapot.config.storage_type() == "mysql":
try:
database = teapot.database.__init__()
db = teapot.database.db(database)
db.execute(
"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, message_id, user_id, action_type) VALUES(%s, %s, %s, %s, %s, %s)",
(teapot.time(), ctx.guild.id, ctx.channel.id, ctx.id, ctx.author.id, "MESSAGE_DELETE"))
database.commit()
except Exception as e:
print(e)
def on_command_error(bot):
@bot.event
async def on_command_error(ctx, e):
if teapot.config.storage_type() == "mysql":
try:
database = teapot.database.__init__()
db = teapot.database.db(database)
db.execute(
"INSERT INTO `bot_logs`(timestamp, type, class, message) VALUES(%s, %s, %s, %s)",
(teapot.time(), "CMD_ERROR", __name__, str(e)))
database.commit()
except Exception as e:
print(e)
================================================
FILE: teapot/managers/__init__.py
================================================
from .database import *
================================================
FILE: teapot/managers/database.py
================================================
""" Database Manager """
import mysql.connector
import teapot
# TABLES = {}
# TABLES['guilds'] = (
# "CREATE TABLE IF NOT EXISTS `guilds` ("
# " `guild_id` int(18) NOT NULL,"
# " `guild_name` TINYTEXT NOT NULL,"
# ") ENGINE=InnoDB")
# TABLES['channels'] = (
# "CREATE TABLE IF NOT EXISTS `channels` ("
# " `channel_id` int(18) NOT NULL,"
# " `channel_name` TINYTEXT NOT NULL,"
# ") ENGINE=InnoDB")
# TABLES['users'] = (
# "CREATE TABLE IF NOT EXISTS `users` ("
# " `user_id` int(18) NOT NULL,"
# " `user_name` TINYTEXT NOT NULL,"
# " `user_discriminator` int(4) NOT NULL,"
# ") ENGINE=InnoDB")
def __init__():
try:
database = mysql.connector.connect(
host=teapot.config.db_host(),
port=teapot.config.db_port(),
db=teapot.config.db_schema(),
user=teapot.config.db_user(),
passwd=teapot.config.db_password(),
charset='utf8mb4',
use_unicode=True
)
return (database)
except Exception as error:
print("\nUnable to connect to database. Please check your credentials!\n" + str(error) + "\n")
quit()
def db(database):
try:
return database.cursor(buffered=True)
except Exception as e:
print(f"\nAn error occurred while executing SQL statement\n{e}\n")
quit()
def create_table(stmt):
database = teapot.managers.database.__init__()
db = teapot.managers.database.db(database)
db.execute(stmt)
db.close()
del db
def insert(stmt, var):
database = teapot.managers.database.__init__()
db = teapot.managers.database.db(database)
db.execute(stmt, var)
database.commit()
db.close()
del db
def insert_if_not_exists(stmt):
database = teapot.managers.database.__init__()
db = teapot.managers.database.db(database)
db.execute(stmt)
database.commit()
db.close()
del db
def create_guild_table(guild):
database = teapot.managers.database.__init__()
db = teapot.managers.database.db(database)
db.execute("SELECT * FROM `guilds` WHERE guild_id = '" + str(guild.id) + "'")
if db.rowcount == 0:
insert("INSERT INTO `guilds`(guild_id, guild_name) VALUES(%s, %s)", (guild.id, guild.name))
================================================
FILE: teapot/messages.py
================================================
import discord
def WIP():
"""Work In Progress"""
return discord.Embed(title="⏲ This feature is work in progress!",
description="Please stay tuned to our latest updates [here]("
"https://github.com/RedCokeDevelopment/Teapot.py)!", color=0x89CFF0)
def permission_denied():
"""user don't have permission"""
return discord.Embed(title="🛑 Permission Denied!", description="You do not have permission to do this!",
color=0xFF0000)
def notfound(s):
return discord.Embed(title=f"😮 Oops! {s.capitalize()} not found!",
description=f"Unable to find the specified {s.lower()}!",
color=0xFF0000)
def downloading():
return discord.Embed(title="⏱ Downloading File...", description="Please wait for up to 3 seconds!",
color=0xFF0000)
def error(e="executing command"):
return discord.Embed(title=f"⚠ Unknown error occurred while {e}!",
description="Please report to [Teapot.py](https://github.com/RedCokeDevelopment/Teapot.py) developers [here](https://github.com/RedCokeDevelopment/Teapot.py/issues)!",
color=0xFF0000)
def invalidargument(arg):
return discord.Embed(title="🟥 Invalid argument!", description=f"Valid argument(s): ``{arg}``",
color=0xFF0000)
def toomanyarguments():
return discord.Embed(title="🛑 Too many arguments!", description=f"You have entered too many arguments!",
color=0xFF0000)
================================================
FILE: teapot/setup.py
================================================
import time
import teapot
def __init__():
print('\n' * 100)
print("""
_____ _ ____ __ _ _
|_ _|__ __ _ _ __ ___ | |_ / ___|___ _ __ / _(_) __ _ _ _ _ __ __ _| |_ ___ _ __
| |/ _ \\/ _` | '_ \\ / _ \\| __| | | / _ \\| '_ \\| |_| |/ _` | | | | '__/ _` | __/ _ \\| '__|
| | __/ (_| | |_) | (_) | |_ | |__| (_) | | | | _| | (_| | |_| | | | (_| | || (_) | |
|_|\\___|\\__,_| .__/ \\___/ \\__| \\____\\___/|_| |_|_| |_|\\__, |\\__,_|_| \\__,_|\\__\\___/|_|
|_| © 2020-2021 Red Coke Development |___/
NOTE: You can change the settings later in .env
""")
# Declare default configuration
input_mysql_host = "127.0.0.1"
input_mysql_port = "3306"
input_mysql_schema = "teapot"
input_mysql_user = "root"
input_mysql_password = ""
input_bot_token = input("Discord bot token: ")
input_bot_prefix = input("Command Prefix: ")
input_bot_status = input("Bot status: (Playing xxx) ")
input_storage_type = input("Use MySQL? [Y/n] ")
if input_storage_type.lower() == "y" or input_storage_type.lower() == "yes":
input_storage_type = "mysql"
input_mysql_host = input("Database Host: ")
input_mysql_port = input("Database Port: ")
input_mysql_schema = input("Database Schema: ")
input_mysql_user = input("Database User: ")
input_mysql_password = input("Database Password: ")
elif input_storage_type.lower() == "n" or input_storage_type.lower() == "no":
input_storage_type = "flatfile"
else:
print("[!] Your input was invalid, and has been automagically set to flatfile storage.")
input_storage_type = "flatfile"
input_lavalink_host = input("Lavalink Host: ")
input_lavalink_port = input("Lavalink Port: ")
input_lavalink_password = input("Lavalink Password: ")
input_osu_api_key = input("osu!api Key")
try:
config = f"""CONFIG_VERSION={teapot.config_version()}
BOT_TOKEN={input_bot_token}
BOT_PREFIX={input_bot_prefix}
BOT_STATUS={input_bot_status}
STORAGE_TYPE={input_storage_type}
DB_HOST={input_mysql_host}
DB_PORT={input_mysql_port}
DB_SCHEMA={input_mysql_schema}
DB_USER={input_mysql_user}
DB_PASSWORD={input_mysql_password}
LAVALINK_HOST={input_lavalink_host}
LAVALINK_PORT={input_lavalink_port}
LAVALINK_PASSWORD={input_lavalink_password}
OSU_API_KEY={input_osu_api_key}
"""
open('./.env', 'w').write(config)
print("\n[*] Successfully created .env file!")
print("Teapot.py setup complete! Starting bot in 5 seconds...")
time.sleep(5)
print('\n' * 100)
except Exception as e:
print("\n[!] An error occurred when creating config file.\n" + str(e))
quit()
================================================
FILE: teapot/tools/__init__.py
================================================
from .embed import *
================================================
FILE: teapot/tools/embed.py
================================================
import discord
import teapot
def newembed(c=0x428DFF):
em = discord.Embed(colour=c)
em.set_footer(text=teapot.copyright(),
icon_url="https://cdn.discordapp.com/avatars/612634758744113182/7fe078b5ea6b43000dfb7964e3e4d21d.png?size=512")
return em
================================================
FILE: test/__init__.py
================================================
================================================
FILE: test/test_cogs.py
================================================
import importlib
import pytest
import os
COGS_DIR = os.path.join(os.path.dirname(__file__), "..", "teapot", "cogs")
COGS = [
fname[:-3] for fname in os.listdir(COGS_DIR)
if fname.endswith(".py") and fname != "__init__.py"
]
@pytest.mark.parametrize("cog_name", COGS)
def test_cog_import_and_setup(cog_name):
mod_path = f"teapot.cogs.{cog_name}"
module = importlib.import_module(mod_path)
# check for cog class
cog_class = None
for attr in dir(module):
obj = getattr(module, attr)
if hasattr(obj, "__bases__") and any(b.__name__ == "Cog" for b in getattr(obj, "__bases__", [])):
cog_class = obj
break
assert cog_class is not None, f"{mod_path} Does not have a valid Cog class"
assert hasattr(module, "setup"), f"{mod_path} is missing setup(bot) function"
assert callable(getattr(module, "setup")), f"{mod_path} setup is not a callable function"