Full Code of RedCokeDevelopment/Teapot.py for AI

master c621261fa44a cached
37 files
76.3 KB
19.2k tokens
137 symbols
1 requests
Download .txt
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
================================================
![banner](https://user-images.githubusercontent.com/43201383/72987537-89830a80-3e25-11ea-95ef-ecfa0afcff7e.png)

<p align="center">
    <a href="https://github.com/RedCokeDevelopment/Teapot.py/blob/master/LICENSE"><img src="https://img.shields.io/github/license/redcokedevelopment/teapot.py.svg?style=flat-square" alt="GitHub License"></a>
    <a href="https://github.com/RedCokeDevelopment/Teapot.py/issues"><img src="https://img.shields.io/github/issues/redcokedevelopment/teapot.py.svg?color=purple&style=flat-square" alt="GitHub Issues"></a>
    <a href="https://github.com/RedCokeDevelopment/Teapot.py/pulls"><img src="https://img.shields.io/github/issues-pr/redcokedevelopment/teapot.py.svg?color=purple&style=flat-square" alt="GitHub Pull Requests"></a>
    <a href="https://github.com/RedCokeDevelopment/Teapot.py/stargazers"><img src="https://img.shields.io/github/stars/redcokedevelopment/teapot.py.svg?style=flat-square" alt="GitHub Stars"></a>
   <img src="https://github.com/RedCokeDevelopment/Teapot.py/workflows/Teapot.py/badge.svg" alt="Teapot.py">
    <br><br>
    <a href="https://discord.gg/7BRGs6F"><img src="https://discordapp.com/api/guilds/667714189254459414/widget.png?style=banner3" alt="Discord Server"></a>
</p>

<h2 align="center">
    This project is currently in development!<br>
</h2>
<h4 align="center">
    If you would like to be notified when we commit, please watch this repository and join our Discord server.
</h4>


## 👋 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"
Download .txt
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
Download .txt
SYMBOL INDEX (137 symbols across 21 files)

FILE: Teapot.py
  function on_ready (line 99) | async def on_ready():

FILE: teapot/__init__.py
  function version (line 15) | def version():
  function config_version (line 19) | def config_version():
  function time (line 23) | def time():
  function year (line 27) | def year():
  function copyright (line 31) | def copyright():
  function get_platform (line 35) | def get_platform():
  function hostname (line 39) | def hostname():
  function ip (line 43) | def ip():
  function path (line 47) | def path():

FILE: teapot/cogs/cat.py
  class Cat (line 11) | class Cat(commands.Cog):
    method __init__ (line 14) | def __init__(self, bot):
    method cat (line 20) | async def cat(self, ctx):
    method dog (line 35) | async def dog(self, ctx):
  function setup (line 50) | async def setup(bot):

FILE: teapot/cogs/cmds.py
  class BasicCommands (line 8) | class BasicCommands(commands.Cog):
    method __init__ (line 11) | def __init__(self, bot):
    method help (line 15) | async def help(self, ctx, *cog):
    method info (line 59) | async def info(self, ctx):
    method ping (line 86) | async def ping(self, ctx):
    method prune (line 92) | async def prune(self, ctx, amount: int = 0):
    method kick (line 106) | async def kick(self, ctx, member: discord.Member, *, reason=None):
    method ban (line 116) | async def ban(self, ctx, member: discord.Member, *, reason=None):
    method admin (line 125) | async def admin(self, ctx):
    method owner (line 130) | async def owner(self, ctx):
    method debug (line 149) | async def debug(self, ctx):
  function setup (line 178) | async def setup(bot):

FILE: teapot/cogs/github.py
  class GitHub (line 11) | class GitHub(commands.Cog):
    method __init__ (line 14) | def __init__(self, bot):
    method github (line 18) | async def github(self, ctx, arg):
  function setup (line 65) | async def setup(bot):

FILE: teapot/cogs/music.py
  class LavalinkVoiceClient (line 13) | class LavalinkVoiceClient(discord.VoiceProtocol):
    method __init__ (line 16) | def __init__(self, client: discord.Client, channel: discord.abc.Connec...
    method connect (line 25) | async def connect(self, *, timeout: float, reconnect: bool, self_deaf:...
    method disconnect (line 31) | async def disconnect(self, *, force: bool = False):
    method on_voice_server_update (line 44) | async def on_voice_server_update(self, data):
    method on_voice_state_update (line 47) | async def on_voice_state_update(self, data):
    method _destroy (line 59) | async def _destroy(self):
  class Music (line 71) | class Music(commands.Cog): # TODO: event check to save-up resources when...
    method __init__ (line 74) | def __init__(self, bot):
    method cog_unload (line 79) | def cog_unload(self):
    method cog_before_invoke (line 82) | async def cog_before_invoke(self, ctx):
    method cog_command_error (line 88) | async def cog_command_error(self, ctx, error):
    method track_hook (line 92) | async def track_hook(self, event):
    method play (line 103) | async def play(self, ctx, *, query: str):
    method seek (line 139) | async def seek(self, ctx, *, seconds: int):
    method skip (line 149) | async def skip(self, ctx):
    method stop (line 160) | async def stop(self, ctx):
    method now (line 172) | async def now(self, ctx):
    method queue (line 191) | async def queue(self, ctx, page: int = 1):
    method pause (line 214) | async def pause(self, ctx):
    method volume (line 229) | async def volume(self, ctx, volume):
    method shuffle (line 245) | async def shuffle(self, ctx):
    method repeat (line 255) | async def repeat(self, ctx):
    method remove (line 266) | async def remove(self, ctx, index: int):
    method find (line 281) | async def find(self, ctx, *, query):
    method disconnect (line 305) | async def disconnect(self, ctx):
    method ensure_voice (line 321) | async def ensure_voice(self, ctx):
  function setup (line 355) | async def setup(bot):

FILE: teapot/cogs/neko.py
  function neko_api (line 14) | def neko_api(x):
  class Neko (line 27) | class Neko(commands.Cog):
    method __init__ (line 30) | def __init__(self, bot):
    method neko (line 35) | async def neko(self, ctx):
    method waifu (line 39) | async def waifu(self, ctx):
    method avatar (line 43) | async def avatar(self, ctx):
    method wallpaper (line 47) | async def wallpaper(self, ctx):
    method tickle (line 51) | async def tickle(self, ctx):
    method poke (line 55) | async def poke(self, ctx):
    method kiss (line 59) | async def kiss(self, ctx):
    method eightball (line 63) | async def eightball(self, ctx):
    method lizard (line 67) | async def lizard(self, ctx):
    method slap (line 71) | async def slap(self, ctx):
    method cuddle (line 75) | async def cuddle(self, ctx):
    method goose (line 79) | async def goose(self, ctx):
    method fox_girl (line 83) | async def fox_girl(self, ctx):
    method baka (line 87) | async def baka(self, ctx):
    method hentai (line 91) | async def hentai(self, ctx, api_type=""):
  function setup (line 127) | async def setup(bot):

FILE: teapot/cogs/nqn.py
  class Emoji (line 5) | class Emoji(commands.Cog):
    method __init__ (line 6) | def __init__(self, bot):
    method getemote (line 9) | async def getemote(self, arg):
    method getinstr (line 21) | async def getinstr(self, content):
  function setup (line 64) | async def setup(bot):

FILE: teapot/cogs/osu.py
  class OsuPlayer (line 10) | class OsuPlayer:
    method __init__ (line 12) | def __init__(self, player):
    method display (line 32) | def display(self, author):
  class Osu (line 49) | class Osu(commands.Cog):
    method __init__ (line 52) | def __init__(self, bot):
    method osu (line 57) | async def osu(self, ctx, *, args: str):
  function setup (line 90) | async def setup(bot):

FILE: teapot/config.py
  function bot_owner (line 4) | def bot_owner():
  function bot_token (line 7) | def bot_token():
  function osu_api_key (line 10) | def osu_api_key():
  function trn_api_key (line 13) | def trn_api_key():
  function bot_prefix (line 16) | def bot_prefix():
  function bot_status (line 19) | def bot_status():
  function storage_type (line 28) | def storage_type():
  function db_host (line 33) | def db_host():
  function db_port (line 36) | def db_port():
  function db_schema (line 39) | def db_schema():
  function db_user (line 42) | def db_user():
  function db_password (line 45) | def db_password():
  function lavalink_host (line 48) | def lavalink_host():
  function lavalink_port (line 51) | def lavalink_port():
  function lavalink_password (line 54) | def lavalink_password():

FILE: teapot/event_handler/db_handler.py
  function on_message_handler (line 3) | async def on_message_handler(bot, message):

FILE: teapot/event_handler/loader.py
  class EventHandlerLoader (line 5) | class EventHandlerLoader:
    method __init__ (line 12) | def __init__(self, bot: commands.Bot):
    method load_event_handlers (line 18) | def load_event_handlers(self):
    method get_registered_handlers (line 30) | def get_registered_handlers(self):
    method register_on_message (line 33) | def register_on_message(self):

FILE: teapot/event_handler/nqn_handler.py
  function on_message_handler (line 3) | async def on_message_handler(bot, message):

FILE: teapot/event_handler/profanity_handler.py
  function on_message_handler (line 5) | async def on_message_handler(bot, message):

FILE: teapot/event_handler/sao_handler.py
  function on_message_handler (line 4) | async def on_message_handler(bot, message):

FILE: teapot/events.py
  function __init__ (line 7) | def __init__(bot):
  function join (line 18) | def join(bot):
  function leave (line 33) | def leave(bot):
  function on_guild_join (line 48) | def on_guild_join(bot):
  function message_edit (line 54) | def message_edit(bot):
  function message_delete (line 89) | def message_delete(bot):
  function on_command_error (line 104) | def on_command_error(bot):

FILE: teapot/managers/database.py
  function __init__ (line 25) | def __init__():
  function db (line 42) | def db(database):
  function create_table (line 50) | def create_table(stmt):
  function insert (line 59) | def insert(stmt, var):
  function insert_if_not_exists (line 70) | def insert_if_not_exists(stmt):
  function create_guild_table (line 81) | def create_guild_table(guild):

FILE: teapot/messages.py
  function WIP (line 4) | def WIP():
  function permission_denied (line 11) | def permission_denied():
  function notfound (line 17) | def notfound(s):
  function downloading (line 23) | def downloading():
  function error (line 28) | def error(e="executing command"):
  function invalidargument (line 34) | def invalidargument(arg):
  function toomanyarguments (line 39) | def toomanyarguments():

FILE: teapot/setup.py
  function __init__ (line 6) | def __init__():

FILE: teapot/tools/embed.py
  function newembed (line 6) | def newembed(c=0x428DFF):

FILE: test/test_cogs.py
  function test_cog_import_and_setup (line 12) | def test_cog_import_and_setup(cog_name):
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 3351,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 960,
    "preview": "# These are supported funding model platforms\n\ngithub: RedCokeDevelopment # Replace with up to 4 GitHub Sponsors-enabled"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 886,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG] \"\nlabels: bug\nassignees: ColaIan\n\n---\n\n**De"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 619,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[FEATURE] \"\nlabels: enhancement\nassignees: Col"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 501,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/Teapot.py.yml",
    "chars": 1152,
    "preview": "# This workflow will install Python dependencies, run tests and lint with a single version of Python\n# For more informat"
  },
  {
    "path": ".gitignore",
    "chars": 1252,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "LICENSE",
    "chars": 1082,
    "preview": "MIT License\n\nCopyright (c) 2020-2023 Red Coke Development\n\nPermission is hereby granted, free of charge, to any person o"
  },
  {
    "path": "Procfile",
    "chars": 24,
    "preview": "worker: python Teapot.py"
  },
  {
    "path": "README.md",
    "chars": 3140,
    "preview": "![banner](https://user-images.githubusercontent.com/43201383/72987537-89830a80-3e25-11ea-95ef-ecfa0afcff7e.png)\n\n<p alig"
  },
  {
    "path": "Teapot.py",
    "chars": 6874,
    "preview": "import os\nimport time\nfrom os.path import join, dirname\nimport json\nimport requests\n\nimport discord\nfrom discord.ext imp"
  },
  {
    "path": "requirements.txt",
    "chars": 496,
    "preview": "aiohttp>=3.12.15\nasync-timeout>=5.0.1\nattrs>=25.3.0\nbeautifulsoup4>=4.14.0\nbs4>=0.0.2\ncertifi>=2025.8.3\nchardet>=5.2.0\nd"
  },
  {
    "path": "teapot/__init__.py",
    "chars": 743,
    "preview": "import datetime\nimport platform\nimport socket\nimport sys\n\nfrom .cogs import *\nfrom .managers import *\nfrom .tools import"
  },
  {
    "path": "teapot/cogs/__init__.py",
    "chars": 140,
    "preview": "from .cat import *\nfrom .cmds import *\nfrom .github import *\nfrom .music import *\nfrom .neko import *\nfrom .osu import *"
  },
  {
    "path": "teapot/cogs/cat.py",
    "chars": 1603,
    "preview": "\"\"\" Module for generating a random cat picture\"\"\"\nimport json\n\nimport requests\nfrom bs4 import BeautifulSoup\nfrom discor"
  },
  {
    "path": "teapot/cogs/cmds.py",
    "chars": 9749,
    "preview": "import asyncio\nimport discord\nimport time\nimport psutil\nfrom discord.ext import commands\nimport teapot\n\nclass BasicComma"
  },
  {
    "path": "teapot/cogs/github.py",
    "chars": 2728,
    "preview": "import json\n\nimport requests\nfrom bs4 import BeautifulSoup\nfrom discord.ext import commands\n\nimport teapot\nimport teapot"
  },
  {
    "path": "teapot/cogs/music.py",
    "chars": 13139,
    "preview": "import math\nimport re\n\nimport discord\nimport lavalink\nfrom lavalink.errors import ClientError\nfrom discord.ext import co"
  },
  {
    "path": "teapot/cogs/neko.py",
    "chars": 4466,
    "preview": "\"\"\" Module for generating random neko pictures\"\"\"\nimport io\nimport json\n\nimport aiohttp\nimport discord\nimport requests\nf"
  },
  {
    "path": "teapot/cogs/nqn.py",
    "chars": 1919,
    "preview": "from discord import utils\nfrom discord.ext import commands\n\n\nclass Emoji(commands.Cog):\n    def __init__(self, bot):\n   "
  },
  {
    "path": "teapot/cogs/osu.py",
    "chars": 3257,
    "preview": "import json\n\nimport requests\nfrom discord.ext import commands\n\nimport teapot.config\nimport teapot.tools.embed as dmbd\n\n\n"
  },
  {
    "path": "teapot/config.py",
    "chars": 1227,
    "preview": "import os\nimport teapot\n\ndef bot_owner():\n    return eval(os.getenv(\"BOT_owner\", \"216127021028212737\"))\n\ndef bot_token()"
  },
  {
    "path": "teapot/event_handler/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "teapot/event_handler/db_handler.py",
    "chars": 1335,
    "preview": "import teapot\n\nasync def on_message_handler(bot, message):\n    if teapot.config.storage_type() == \"mysql\":\n        try:\n"
  },
  {
    "path": "teapot/event_handler/loader.py",
    "chars": 1409,
    "preview": "from discord.ext import commands\nimport importlib\nimport os\n\nclass EventHandlerLoader:\n    \"\"\"\n    Dynamically loads eve"
  },
  {
    "path": "teapot/event_handler/nqn_handler.py",
    "chars": 1278,
    "preview": "from discord import utils\n\nasync def on_message_handler(bot, message):\n    if message.author.bot:\n        return\n    if "
  },
  {
    "path": "teapot/event_handler/profanity_handler.py",
    "chars": 523,
    "preview": "import teapot\nfrom teapot.events import predict_prob\nimport discord\n\nasync def on_message_handler(bot, message):\n    pun"
  },
  {
    "path": "teapot/event_handler/sao_handler.py",
    "chars": 1634,
    "preview": "import discord\nimport teapot\n\nasync def on_message_handler(bot, message):\n    punctuations = '!()-[]{};:\\'\"\\\\,<>./?@#$%^"
  },
  {
    "path": "teapot/events.py",
    "chars": 4635,
    "preview": "import json\nimport teapot\nimport discord\nfrom profanity_check import predict_prob\n\n\ndef __init__(bot):\n    \"\"\" Initializ"
  },
  {
    "path": "teapot/managers/__init__.py",
    "chars": 24,
    "preview": "from .database import *\n"
  },
  {
    "path": "teapot/managers/database.py",
    "chars": 2292,
    "preview": "\"\"\" Database Manager \"\"\"\nimport mysql.connector\nimport teapot\n\n\n# TABLES = {}\n# TABLES['guilds'] = (\n#     \"CREATE TABLE"
  },
  {
    "path": "teapot/messages.py",
    "chars": 1597,
    "preview": "import discord\n\n\ndef WIP():\n    \"\"\"Work In Progress\"\"\"\n    return discord.Embed(title=\"⏲ This feature is work in progres"
  },
  {
    "path": "teapot/setup.py",
    "chars": 2895,
    "preview": "import time\n\nimport teapot\n\n\ndef __init__():\n    print('\\n' * 100)\n    print(\"\"\"\n    \n      _____                      _"
  },
  {
    "path": "teapot/tools/__init__.py",
    "chars": 21,
    "preview": "from .embed import *\n"
  },
  {
    "path": "teapot/tools/embed.py",
    "chars": 279,
    "preview": "import discord\n\nimport teapot\n\n\ndef newembed(c=0x428DFF):\n    em = discord.Embed(colour=c)\n    em.set_footer(text=teapot"
  },
  {
    "path": "test/__init__.py",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "test/test_cogs.py",
    "chars": 926,
    "preview": "import importlib\nimport pytest\nimport os\n\nCOGS_DIR = os.path.join(os.path.dirname(__file__), \"..\", \"teapot\", \"cogs\")\nCOG"
  }
]

About this extraction

This page contains the full source code of the RedCokeDevelopment/Teapot.py GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (76.3 KB), approximately 19.2k tokens, and a symbol index with 137 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!