[
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at contact@redcoke.dev. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: RedCokeDevelopment # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\n#patreon: # Replace with a single Patreon username\n#open_collective: # Replace with a single Open Collective username\n#ko_fi: # Replace with a single Ko-fi username\n#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\n#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\n#liberapay: # Replace with a single Liberapay username\n#issuehunt: # Replace with a single IssueHunt username\n#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\n#polar: # Replace with a single Polar username\nbuy_me_a_coffee: Colaian # Replace with a single Buy Me a Coffee username \n#thanks_dev: # Replace with a single thanks.dev username\n#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG] \"\nlabels: bug\nassignees: ColaIan\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. \n2. \n3. \n4. \n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Teapot.py Public (please complete the following information):**\n - Teapot.py Version: [e.g. 0.0.1.1, etc...]\n\n**Self-Hosted (please complete the following information):**\n - Teapot.py Version: [e.g. 0.0.1.1, etc...]\n - Server Operating System: [e.g. Ubuntu 18.04, Windows 10 Pro Build 18363, etc...]\n - Database Version: [e.g. MySQL, MariaDB 10, etc...]\n\n**Additional context**\nAdd any other context about the problem here [e.g. error logs, crash logs, etc...].\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[FEATURE] \"\nlabels: enhancement\nassignees: ColaIan\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"pip\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/Teapot.py.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a single version of Python\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: Teapot.py\non:\n  push:\n    branches:\n      - \"*\"\n  pull_request:\n    branches:\n      - \"*\"\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python 3.10.18\n      uses: actions/setup-python@v2\n      with:\n        python-version: 3.10.18\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install flake8 pytest\n        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi\n    - name: Lint with flake8\n      run: |\n        # stop the build if there are Python syntax errors or undefined names\n        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics\n        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide\n        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics\n    - name: Test with pytest\n      run: |\n        pytest\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n# JetBrains Configurations\n.idea\n\n# Config\n*.env"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020-2023 Red Coke Development\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Procfile",
    "content": "worker: python Teapot.py"
  },
  {
    "path": "README.md",
    "content": "![banner](https://user-images.githubusercontent.com/43201383/72987537-89830a80-3e25-11ea-95ef-ecfa0afcff7e.png)\n\n<p align=\"center\">\n    <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>\n    <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>\n    <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>\n    <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>\n   <img src=\"https://github.com/RedCokeDevelopment/Teapot.py/workflows/Teapot.py/badge.svg\" alt=\"Teapot.py\">\n    <br><br>\n    <a href=\"https://discord.gg/7BRGs6F\"><img src=\"https://discordapp.com/api/guilds/667714189254459414/widget.png?style=banner3\" alt=\"Discord Server\"></a>\n</p>\n\n<h2 align=\"center\">\n    This project is currently in development!<br>\n</h2>\n<h4 align=\"center\">\n    If you would like to be notified when we commit, please watch this repository and join our Discord server.\n</h4>\n\n\n## 👋 About\n\nTeapot.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!\n\nIf 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)!\n\n## ⌨ Planned Features\n- Music Player\n- Moderation Tools\n- Localization\n- Fun Commands\n\n\n## 📖 Wiki\n\nOur wiki is currently work in progress, please check back later!\n\n## 🤝 Contributing\nContributions, 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!\n\nBefore 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 🙂\n\nA big thanks to all those who contribute to the project ❤\n\n## 💼 Project Owners \nThere 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:\n\n- [ColaIan](https://github.com/ColaIan) (ColaIan#2974)\n- [RedTea](https://github.com/RedTeaDev) (RedTea#9209)\n\n## 📜 Requirements\nThese are the requirements for the bot.\n\n- [Python 3.10](https://www.python.org/downloads) (Required packages listed in requirements.txt)\n- [LavaLink Server](https://github.com/freyacodes/lavalink) (Java 11 required)\n- Database is optional but preferred\n\n## 💖 Credits\nThe projects listed in below have provided inspiration, and we thought we'd mention them:\n\n- LavaLink: https://github.com/freyacodes/lavalink\n"
  },
  {
    "path": "Teapot.py",
    "content": "import os\nimport time\nfrom os.path import join, dirname\nimport json\nimport requests\n\nimport discord\nfrom discord.ext import commands as dcmd\nfrom dotenv import load_dotenv\nimport lavalink\n\nimport teapot\nfrom teapot.event_handler.loader import EventHandlerLoader\n\nprint(f\"\"\"\n  _____                      _   \n |_   _|__  __ _ _ __   ___ | |_ \n   | |/ _ \\\\/ _` | '_ \\\\ / _ \\\\| __|\n   | |  __/ (_| | |_) | (_) | |_ \n   |_|\\\\___|\\\\__,_| .__/ \\\\___/ \\\\__|\n    by ColaIan |_| & RedTea\n\nRunning Teapot.py {teapot.version()}\n\"\"\")\n\nreq = requests.get(f'https://api.github.com/repos/RedCokeDevelopment/Teapot.py/tags')\nresponse = json.loads(req.text)\nif req.status_code == 200:\n    if response[0]['name'] == teapot.version():\n        print(\"You are currently running the latest version of Teapot.py!\\n\")\n    else:\n        version_listed = False\n        for x in response:\n            if x['name'] == teapot.version():\n                version_listed = True\n                print(\"You are not using our latest version! :(\\n\")\n        if not version_listed:\n            print(\"You are currently using an unlisted version!\\n\")\nelif req.status_code == 404:\n    # 404 Not Found\n    print(\"Latest Teapot.py version not found!\\n\")\nelif req.status_code == 500:\n    # 500 Internal Server Error\n    print(\"An error occurred while fetching the latest Teapot.py version. [500 Internal Server Error]\\n\")\nelif req.status_code == 502:\n    # 502 Bad Gateway\n    print(\"An error occurred while fetching the latest Teapot.py version. [502 Bad Gateway]\\n\")\nelif req.status_code == 503:\n    # 503 Service Unavailable\n    print(\"An error occurred while fetching the latest Teapot.py version. [503 Service Unavailable]\\n\")\nelse:\n    print(\"An unknown error has occurred when fetching the latest Teapot.py version\\n\")\n    print(\"HTML Error Code:\" + str(req.status_code))\n\nload_dotenv(join(dirname(__file__), '.env'))\n\nif os.getenv('CONFIG_VERSION') != teapot.config_version():\n    if os.path.isfile('.env'):\n        print(\"Missing environment variables. Please backup and delete .env, then run Teapot.py again.\")\n        quit(2)\n    print(\"Unable to find required environment variables. Running setup.py...\")  # if .env not found\n    teapot.setup.__init__() # run setup.py\n\nprint(\"Initializing bot...\")\nif teapot.config.storage_type() == \"mysql\": # if .env use mysql, create the table if table not exists\n    time_start = time.perf_counter()\n    database = teapot.managers.database.__init__()\n    db = teapot.managers.database.db(database)\n    db.execute('ALTER DATABASE `' + teapot.config.db_schema() + '` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci')\n    db.execute(\n        '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')\n    db.execute(\n        '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')\n    db.execute(\n        \"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\")\n    db.execute(\n        \"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\")\n    teapot.managers.database.create_table(\n        \"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\")\n\n    db.execute(\"INSERT INTO `bot_logs`(timestamp, type, class, message) VALUES(%s, %s, %s, %s)\",\n               (teapot.time(), \"BOT_START\", __name__, \"Initialized bot\"))\n    database.commit()\n\n    print(\n        f\"Connected to database ({teapot.config.db_host()}:{teapot.config.db_port()}) in {round(time.perf_counter() - time_start, 2)}s\")\n\n\nintents = discord.Intents.all()\nintents.members = True\nintents.message_content = True\nintents.typing = False\nbot = dcmd.Bot(intents=intents, command_prefix=teapot.config.bot_prefix(), help_command=None)\n\nevent_handler_loader = EventHandlerLoader(bot) # Event Handler\n\n\n@bot.event\nasync def on_ready():\n    print(f\"Connected to Discord API in {round(time.perf_counter() - discord_time_start, 2)}s\")\n    time_start = time.perf_counter()\n\n    # load cogs\n    teapot.events.__init__(bot)\n    # Initialize lavalink client once here so cogs do not need to recreate it\n    if not hasattr(bot, 'lavalink'):\n        print(\"Initializing Lavalink client...\")\n        bot.lavalink = lavalink.Client(bot.user.id)\n        bot.lavalink.add_node(teapot.config.lavalink_host(), teapot.config.lavalink_port(), teapot.config.lavalink_password(), 'zz', 'default')\n        bot.add_listener(bot.lavalink.voice_update_handler, 'on_socket_response')\n    extensions = [\n        'teapot.cogs.cmds',\n        'teapot.cogs.osu', \n        'teapot.cogs.github',\n        'teapot.cogs.cat',\n        'teapot.cogs.neko',\n        'teapot.cogs.nqn',\n        'teapot.cogs.music' # TODO: WIP\n    ]\n    \n    for extension in extensions:\n        try:\n            await bot.load_extension(extension)\n            print(f\"✓ Successfully loaded module: {extension}\")\n        except Exception as e:\n            print(f\"✗ Failed to load {extension}: {e}\")\n            import traceback\n            traceback.print_exc()\n    if teapot.config.storage_type() == \"mysql\":\n        for guild in bot.guilds:\n            teapot.managers.database.create_guild_table(guild)\n    elif teapot.config.storage_type() == \"sqlite\":\n        print(\"[!] Warning: SQLite storage has not been implemented yet. MySQL is recommended\")  # WIP\n    \n    print(f\"Registered commands and events in {round(time.perf_counter() - time_start, 2)}s\")\n    print(f\"total of commands loaded: {len(bot.commands)}\")\n    print(f\"Modules loaded: {list(bot.cogs.keys())}\")\n    \n    await bot.change_presence(status=discord.Status.online,\n                              activity=discord.Game(teapot.config.bot_status()))  # Update Bot status\n\n\ntry:\n    discord_time_start = time.perf_counter()\n    bot.run(teapot.config.bot_token())\nexcept Exception as e:\n    print(f\"[/!\\\\] Error: Failed to connect to DiscordAPI. Please check your bot token!\\n{e}\")\n    if teapot.config.storage_type() == \"mysql\":\n        db.execute(\"INSERT INTO `bot_logs`(timestamp, type, class, message) VALUES(%s, %s, %s, %s)\",\n                   (teapot.time(), \"ERROR\", __name__, e))\n    time.sleep(5)\n    exit(1)\n"
  },
  {
    "path": "requirements.txt",
    "content": "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\ndiscord.py>=2.6.3\nidna>=3.10\nlavalink>=5.9.0\nmultidict>=6.6.4\nmysql-connector>=2.2.9\npsutil>=7.1.0\npython-dotenv>=1.1.1\nrequests>=2.32.5\nsoupsieve>=2.8\ntyping-extensions>=4.15.0\nurllib3>=2.5.0\nwebsockets>=15.0.1\nyarl>=1.20.1\nmysql-connector-python>=9.4.0\nalt-profanity-check==1.7.2\nPyNaCl>=1.6.1\nprotobuf>=6.32.1 # not directly required, pinned by Snyk to avoid a vulnerability"
  },
  {
    "path": "teapot/__init__.py",
    "content": "import datetime\nimport platform\nimport socket\nimport sys\n\nfrom .cogs import *\nfrom .managers import *\nfrom .tools import *\nfrom .config import *\nfrom .events import *\nfrom .messages import *\nfrom .setup import *\n\n\ndef version():\n    return \"v0.0.2\"\n\n\ndef config_version():\n    return \"0.1\"  # do not edit this!\n\n\ndef time():\n    return datetime.datetime.now().strftime(\"%Y-%m-%dT%H:%M:%S.%f\")\n\n\ndef year():\n    return str(datetime.datetime.now().year)\n\n\ndef copyright():\n        return f\"© 2020-{year()} RedCoke Development\"\n\n\ndef get_platform():\n    return platform.system() + \" \" + platform.release()\n\n\ndef hostname():\n    return socket.gethostname()\n\n\ndef ip():\n    return socket.gethostbyname(hostname())\n\n\ndef path():\n    return sys.path\n"
  },
  {
    "path": "teapot/cogs/__init__.py",
    "content": "from .cat import *\nfrom .cmds import *\nfrom .github import *\nfrom .music import *\nfrom .neko import *\nfrom .osu import *\nfrom .nqn import *\n"
  },
  {
    "path": "teapot/cogs/cat.py",
    "content": "\"\"\" Module for generating a random cat picture\"\"\"\nimport json\n\nimport requests\nfrom bs4 import BeautifulSoup\nfrom discord.ext import commands\n\nimport teapot.tools.embed as dmbd\n\n\nclass Cat(commands.Cog):\n    \"\"\" Cat and dog command\"\"\"\n\n    def __init__(self, bot):\n        \"\"\" Initialize Cat Class\"\"\"\n\n        self.bot = bot\n\n    @commands.command(aliases=['meow'])\n    async def cat(self, ctx):\n        \"\"\" Get a cat image \"\"\"\n        req = requests.get('https://api.thecatapi.com/v1/images/search')\n        if req.status_code != 200:\n            await ctx.message.add_reaction(emoji='❌')\n            await ctx.send(\"API error, could not get a meow\")\n            print(\"Could not get a meow\")\n        catlink = json.loads(req.text)[0]\n        rngcat = catlink[\"url\"]\n        em = dmbd.newembed()\n        em.set_image(url=rngcat)\n        await ctx.send(embed=em)\n        await ctx.message.add_reaction(emoji='✅')\n\n    @commands.command(aliases=['woof'])\n    async def dog(self, ctx):\n        \"\"\" Get a dog image \"\"\"\n        req = requests.get('http://random.dog/')\n        if req.status_code != 200:\n            await ctx.message.add_reaction(emoji='❌')\n            await ctx.send(\"API error, could not get a woof\")\n            print(\"Could not get a woof\")\n        doglink = BeautifulSoup(req.text, 'html.parser')\n        rngdog = 'http://random.dog/' + doglink.img['src']\n        em = dmbd.newembed()\n        em.set_image(url=rngdog)\n        await ctx.send(embed=em)\n        await ctx.message.add_reaction(emoji='✅')\n\n\nasync def setup(bot):\n    \"\"\" Setup Cat Module\"\"\"\n    await bot.add_cog(Cat(bot))\n"
  },
  {
    "path": "teapot/cogs/cmds.py",
    "content": "import asyncio\nimport discord\nimport time\nimport psutil\nfrom discord.ext import commands\nimport teapot\n\nclass BasicCommands(commands.Cog):\n    \"\"\"Basic bot commands and utilities\"\"\"\n\n    def __init__(self, bot):\n        self.bot = bot\n\n    @commands.command(aliases=['?'])\n    async def help(self, ctx, *cog):\n        \"\"\"Show help information\"\"\"\n        if not cog:\n            embed = discord.Embed(description=\"📖 Help\", color=0x7400FF)\n            embed.set_thumbnail(url=\"https://avatars2.githubusercontent.com/u/60006969?s=200&v=4\")\n            cogs_desc = \"\"\n            for x in self.bot.cogs:\n                cogs_desc += f'**{x}** - {self.bot.cogs[x].__doc__ or \"No description\"}\\n'\n            embed.add_field(name='Modules', value=cogs_desc[0:len(cogs_desc) - 1] if cogs_desc else \"No modules loaded\")\n            embed.set_footer(text=f\"{teapot.copyright()} | Code licensed under the MIT License\")\n            await ctx.send(embed=embed)\n        else:\n            if len(cog) > 1:\n                await ctx.send(embed=teapot.messages.toomanyarguments())\n            else:\n                found = False\n                for x in self.bot.cogs:\n                    for y in cog:\n                        if x == y:\n                            embed = discord.Embed(color=0x7400FF)\n                            cog_info = ''\n                            for c in self.bot.get_cog(y).get_commands():\n                                if not c.hidden:\n                                    cog_info += f\"**{c.name}** - {c.help or 'No description'}\\n\"\n                            embed.add_field(name=f\"{cog[0]} Module\", value=cog_info if cog_info else \"No commands found\")\n                            await ctx.send(embed=embed)\n                            found = True\n                if not found:\n                    for x in self.bot.cogs:\n                        for c in self.bot.get_cog(x).get_commands():\n                            if c.name.lower() == cog[0].lower():\n                                embed = discord.Embed(title=f\"Command: {c.name.lower().capitalize()}\",\n                                                      description=f\"**Description:** {c.help or 'No description'}\\n**Syntax:** {c.qualified_name} {c.signature}\",\n                                                      color=0x7400FF)\n                                embed.set_author(name=f\"Teapot.py {teapot.version()}\",\n                                                 icon_url=\"https://cdn.discordapp.com/avatars/612634758744113182/7fe078b5ea6b43000dfb7964e3e4d21d.png?size=512\")\n                                found = True\n                                await ctx.send(embed=embed)\n                                break\n                    if not found:\n                        embed = teapot.messages.notfound(\"Module\")\n                        await ctx.send(embed=embed)\n\n    @commands.command(aliases=['about'])\n    async def info(self, ctx):\n        \"\"\"Show bot information\"\"\"\n        embed = discord.Embed(title=\"Developers: RedTeaDev, ColaIan\", description=\"Multi-purpose Discord Bot\",\n                              color=0x7400FF)\n        embed.set_author(name=f\"Teapot.py {teapot.version()}\",\n                         icon_url=\"https://cdn.discordapp.com/avatars/612634758744113182/7fe078b5ea6b43000dfb7964e3e4d21d.png?size=512\")\n        embed.set_thumbnail(url=\"https://avatars2.githubusercontent.com/u/60006969?s=200&v=4\")\n        embed.add_field(name=\"Bot User:\", value=self.bot.user)\n        embed.add_field(name=\"Guilds:\", value=len(self.bot.guilds))\n        embed.add_field(name=\"Members:\", value=len(set(self.bot.get_all_members())))\n        embed.add_field(name=\"O.S.:\", value=str(teapot.get_platform()))\n        embed.add_field(name=\"Storage Type:\", value=teapot.config.storage_type())\n        embed.add_field(name=\"Prefix:\", value=\", \".join(teapot.config.bot_prefix()))\n        embed.add_field(name=\"Github Repo:\", value=\"[Teapot.py](https://github.com/RedCokeDevelopment/Teapot.py)\")\n        embed.add_field(name=\"Bug Report:\", value=\"[Issues](https://github.com/RedCokeDevelopment/Teapot.py/issues)\")\n        embed.add_field(name=\"Discussion:\", value=\"[Forums](https://forum.redtea.red)\")\n        embed.add_field(name=\"Links\",\n                        value=\"[Support Discord](https://discord.gg/7BRGs6F) | [Add bot to server](\"\n                              \"https://discordapp.com/oauth2/authorize?client_id=669880564270104586&permissions=8\"\n                              \"&scope=bot) | [Repository](https://github.com/RedCokeDevelopment/Teapot.py)\",\n                        inline=False)\n        embed.set_footer(text=f\"{teapot.copyright()} | Code licensed under the MIT License\")\n        embed.set_image(\n            url=\"https://user-images.githubusercontent.com/43201383/72987537-89830a80-3e25-11ea-95ef-ecfa0afcff7e.png\")\n        await ctx.send(embed=embed)\n\n    @commands.command()\n    async def ping(self, ctx):\n        \"\"\"Show bot latency\"\"\"\n        await ctx.send(f'Pong! {round(self.bot.latency * 1000)} ms')\n\n    @commands.command(aliases=['purge', 'clear', 'cls'])\n    @commands.has_permissions(manage_messages=True)\n    async def prune(self, ctx, amount: int = 0):\n        \"\"\"Delete multiple messages\"\"\"\n        if amount == 0:\n            await ctx.send(\"Please specify the number of messages you want to delete!\")\n        elif amount <= 0:  # lower then 0\n            await ctx.send(\"The number must be bigger than 0!\")\n        else:\n            await ctx.channel.purge(limit=amount + 1)\n            message = await ctx.send(f'Purged {amount} messages!')\n            await asyncio.sleep(3)\n            await message.delete()\n\n    @commands.command()\n    @commands.has_permissions(kick_members=True)\n    async def kick(self, ctx, member: discord.Member, *, reason=None):\n        \"\"\"Kick a member from the server\"\"\"\n        try:\n            await member.kick(reason=reason)\n            await ctx.send(f'{member} has been kicked!')\n        except Exception as failkick:\n            await ctx.send(\"Failed to kick: \" + str(failkick))\n\n    @commands.command()\n    @commands.has_permissions(ban_members=True)\n    async def ban(self, ctx, member: discord.Member, *, reason=None):\n        \"\"\"Ban a member from the server\"\"\"\n        try:\n            await member.ban(reason=reason)\n            await ctx.send(f'{member} has been banned!')\n        except Exception as e:\n            await ctx.send(\"Failed to ban: \" + str(e))\n\n    @commands.command()\n    async def admin(self, ctx):\n        \"\"\"Admin panel (WIP)\"\"\"\n        await ctx.send(embed=teapot.messages.WIP())\n\n    @commands.command()\n    async def owner(self, ctx):\n        \"\"\"Grant owner role to bot owner\"\"\"\n        if ctx.message.author.id == teapot.config.bot_owner():\n            found = False\n            for role in ctx.guild.roles:\n                if role.name == \"Teapot Owner\":\n                    found = True\n                    await ctx.guild.get_member(teapot.config.bot_owner()).add_roles(role)\n                    break\n            if not found:\n                perms = discord.Permissions(administrator=True)\n                await ctx.guild.create_role(name='Teapot Owner', permissions=perms)\n                for role in ctx.guild.roles:\n                    if role.name == \"Teapot Owner\":\n                        await ctx.guild.get_member(teapot.config.bot_owner()).add_roles(role)\n                        break\n\n    @commands.command()\n    @commands.has_permissions(administrator=True)\n    async def debug(self, ctx):\n        \"\"\"Show debug information\"\"\"\n        embed = discord.Embed(title=\"Developers: RedTea, ColaIan\", description=\"Debug info:\",\n                              color=0x7400FF)\n        embed.set_author(name=f\"Teapot.py {teapot.version()}\",\n                         icon_url=\"https://cdn.discordapp.com/avatars/612634758744113182/7fe078b5ea6b43000dfb7964e3e4d21d.png?size=512\")\n        embed.set_thumbnail(url=\"https://avatars2.githubusercontent.com/u/60006969?s=200&v=4\")\n        embed.add_field(name=\"Bot User:\", value=self.bot.user, inline=True)\n        embed.add_field(name=\"System Time:\", value=time.strftime(\"%a %b %d %H:%M:%S %Y\", time.localtime()), inline=True)\n        embed.add_field(name=\"Memory\",\n                        value=str(round(psutil.virtual_memory()[1] / 1024 / 1024 / 1024)) + \"GB / \" + str(round(\n                            psutil.virtual_memory()[0] / 1024 / 1024 / 1024)) + \"GB\", inline=True)\n        embed.add_field(name=\"O.S.:\", value=str(teapot.get_platform()), inline=True)\n        embed.add_field(name=\"Storage Type:\", value=teapot.config.storage_type(), inline=True)\n        embed.add_field(name=\"Prefix:\", value=\", \".join(teapot.config.bot_prefix()), inline=True)\n        embed.add_field(name=\"Github Repo:\", value=\"[Teapot.py](https://github.com/RedCokeDevelopment/Teapot.py)\",\n                        inline=True)\n        embed.add_field(name=\"Bug Report:\", value=\"[Issues](https://github.com/RedCokeDevelopment/Teapot.py/issues)\",\n                        inline=True)\n        embed.add_field(name=\"Website:\", value=\"[Website](https://teapot.bot)\", inline=True)\n        embed.add_field(name=\"Links\",\n                        value=\"[Support Discord](https://discord.gg/7BRGs6F) | [Add bot to server]\" +\n                              \"(https://discordapp.com/oauth2/authorize?client_id=669880564270104586&permissions=8&scope=bot) | \" +\n                              \"[Repository](https://github.com/RedCokeDevelopment/Teapot.py)\",\n                        inline=False)\n        embed.set_footer(text=f\"{teapot.copyright()} | Code licensed under the MIT License\")\n        await ctx.message.author.send(embed=embed)\n\n\nasync def setup(bot):\n    bot.remove_command('help')\n    await bot.add_cog(BasicCommands(bot))\n"
  },
  {
    "path": "teapot/cogs/github.py",
    "content": "import json\n\nimport requests\nfrom bs4 import BeautifulSoup\nfrom discord.ext import commands\n\nimport teapot\nimport teapot.tools.embed as dmbd\n\n\nclass GitHub(commands.Cog):\n    \"\"\"Get repository info\"\"\"\n\n    def __init__(self, bot):\n        self.bot = bot\n\n    @commands.command(aliases=['gh'])\n    async def github(self, ctx, arg):\n        \"\"\"Fetch repository info\"\"\"\n\n        req = requests.get(f'https://api.github.com/repos/{arg}')\n        apijson = json.loads(req.text)\n        if req.status_code == 200:\n            em = dmbd.newembed()\n            em.set_author(name=apijson['owner']['login'], icon_url=apijson['owner']['avatar_url'],\n                          url=apijson['owner']['html_url'])\n            em.set_thumbnail(url=apijson['owner']['avatar_url'])\n            em.add_field(name=\"Repository:\", value=f\"[{apijson['name']}]({apijson['html_url']})\", inline=True)\n            em.add_field(name=\"Language:\", value=apijson['language'], inline=True)\n\n            try:\n                license_url = f\"[{apijson['license']['spdx_id']}]({json.loads(requests.get(apijson['license']['url']).text)['html_url']})\"\n            except:\n                license_url = \"None\"\n            em.add_field(name=\"License:\", value=license_url, inline=True)\n            if apijson['stargazers_count'] != 0:\n                em.add_field(name=\"Star:\", value=apijson['stargazers_count'], inline=True)\n            if apijson['forks_count'] != 0:\n                em.add_field(name=\"Fork:\", value=apijson['forks_count'], inline=True)\n            if apijson['open_issues'] != 0:\n                em.add_field(name=\"Issues:\", value=apijson['open_issues'], inline=True)\n            em.add_field(name=\"Description:\", value=apijson['description'], inline=False)\n\n            for meta in BeautifulSoup(requests.get(apijson['html_url']).text, features=\"html.parser\").find_all('meta'):\n                try:\n                    if meta.attrs['property'] == \"og:image\":\n                        em.set_image(url=meta.attrs['content'])\n                        break\n                except:\n                    pass\n\n            await ctx.send(embed=em)\n        elif req.status_code == 404:\n            \"\"\"if repository not found\"\"\"\n            await ctx.send(embed=teapot.messages.notfound(\"repository\"))\n        elif req.status_code == 503:\n            \"\"\"GithubAPI down\"\"\"\n            await ctx.send(\"GithubAPI down\")\n            await ctx.send(embed=teapot.messages.notfound(\"Fetch repository info\"))\n        else:\n            \"\"\"some error occurred while fetching repository info\"\"\"\n            await ctx.send(embed=teapot.messages.error(\"Fetch repository info\"))\n\n\nasync def setup(bot):\n    \"\"\" Setup GitHub Module\"\"\"\n    await bot.add_cog(GitHub(bot))\n"
  },
  {
    "path": "teapot/cogs/music.py",
    "content": "import math\nimport re\n\nimport discord\nimport lavalink\nfrom lavalink.errors import ClientError\nfrom discord.ext import commands\n\nimport teapot\n\nurl_rx = re.compile('https?:\\/\\/(?:www\\.)?.+')  # noqa: W605\n\nclass LavalinkVoiceClient(discord.VoiceProtocol):\n    \"\"\"Voice protocol implementation that relays Discord voice events to Lavalink.\"\"\"\n\n    def __init__(self, client: discord.Client, channel: discord.abc.Connectable):\n        super().__init__(client, channel)\n        if not hasattr(client, 'lavalink'):\n            raise RuntimeError('Lavalink client is not initialized on the bot.')\n\n        self.guild_id = channel.guild.id\n        self._destroyed = False\n        self.lavalink: lavalink.Client = client.lavalink\n\n    async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = False, self_mute: bool = False):\n        player = self.lavalink.player_manager.create(self.guild_id)\n        player.channel_id = str(self.channel.id)\n        await self.channel.guild.change_voice_state(channel=self.channel, self_deaf=self_deaf, self_mute=self_mute)\n        self._destroyed = False\n\n    async def disconnect(self, *, force: bool = False):\n        player = self.lavalink.player_manager.get(self.guild_id)\n\n        if player and not force and not player.is_connected:\n            return\n\n        await self.channel.guild.change_voice_state(channel=None)\n\n        if player:\n            player.channel_id = None\n\n        await self._destroy()\n\n    async def on_voice_server_update(self, data):\n        await self.lavalink.voice_update_handler({'t': 'VOICE_SERVER_UPDATE', 'd': data})\n\n    async def on_voice_state_update(self, data):\n        channel_id = data.get('channel_id')\n\n        if channel_id:\n            maybe_channel = self.client.get_channel(int(channel_id))\n            if maybe_channel is not None:\n                self.channel = maybe_channel\n        else:\n            await self._destroy()\n\n        await self.lavalink.voice_update_handler({'t': 'VOICE_STATE_UPDATE', 'd': data})\n\n    async def _destroy(self):\n        if self._destroyed:\n            return\n\n        self._destroyed = True\n        self.cleanup()\n\n        try:\n            await self.lavalink.player_manager.destroy(self.guild_id)\n        except ClientError:\n            pass\n\nclass Music(commands.Cog): # TODO: event check to save-up resources when not one is in voice channels\n    \"\"\"Music Time\"\"\"\n\n    def __init__(self, bot):\n        self.bot = bot\n        self.lavalink: lavalink.Client = bot.lavalink\n        self.lavalink.add_event_hook(self.track_hook)\n\n    def cog_unload(self):\n        self.bot.lavalink._event_hooks.clear()\n\n    async def cog_before_invoke(self, ctx):\n        guild_check = ctx.guild is not None\n        if guild_check:\n            await self.ensure_voice(ctx)\n        return guild_check\n\n    async def cog_command_error(self, ctx, error):\n        if isinstance(error, commands.CommandInvokeError):\n            await ctx.send(error.original)\n\n    async def track_hook(self, event):\n        if isinstance(event, lavalink.events.QueueEndEvent):\n            guild_id = int(event.player.guild_id)\n            guild = self.bot.get_guild(guild_id)\n            if guild and guild.voice_client:\n                try:\n                    await guild.voice_client.disconnect(force=True)\n                except Exception:\n                    pass\n\n    @commands.command(aliases=['p'])\n    async def play(self, ctx, *, query: str):\n        \"\"\" Searches and plays a song from a given query. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        query = query.strip('<>')\n\n        if not url_rx.match(query):\n            query = f'ytsearch:{query}'\n\n        results = await player.node.get_tracks(query)\n\n        if not results or not results['tracks']:\n            return await ctx.send('Nothing found!')\n\n        embed = discord.Embed(color=discord.Color.blurple())\n\n        if results['loadType'] == 'PLAYLIST_LOADED':\n            tracks = results['tracks']\n\n            for track in tracks:\n                player.add(requester=ctx.author.id, track=track)\n\n            embed.title = 'Playlist Enqueued!'\n            embed.description = f'{results[\"playlistInfo\"][\"name\"]} - {len(tracks)} tracks'\n        else:\n            track = results['tracks'][0]\n            embed.title = 'Track Enqueued'\n            embed.description = f'[{track[\"info\"][\"title\"]}]({track[\"info\"][\"uri\"]})'\n            player.add(requester=ctx.author.id, track=track)\n\n        await ctx.send(embed=embed)\n\n        if not player.is_playing:\n            await player.play()\n\n    @commands.command()\n    async def seek(self, ctx, *, seconds: int):\n        \"\"\" Seeks to a given position in a track. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        track_time = player.position + (seconds * 1000)\n        await player.seek(track_time)\n\n        await ctx.send(f'Moved track to **{lavalink.utils.format_time(track_time)}**')\n\n    @commands.command(aliases=['forceskip'])\n    async def skip(self, ctx):\n        \"\"\" Skips the current track. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not player.is_playing:\n            return await ctx.send('Not playing.')\n\n        await player.skip()\n        await ctx.send('⏭ | Skipped.')\n\n    @commands.command()\n    async def stop(self, ctx):\n        \"\"\" Stops the player and clears its queue. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not player.is_playing:\n            return await ctx.send('Not playing.')\n\n        player.queue.clear()\n        await player.stop()\n        await ctx.send('⏹ | Stopped.')\n\n    @commands.command(aliases=['np', 'n', 'playing'])\n    async def now(self, ctx):\n        \"\"\" Shows some stats about the currently playing song. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not player.current:\n            return await ctx.send('Nothing playing.')\n\n        position = lavalink.utils.format_time(player.position)\n        if player.current.stream:\n            duration = '🔴 LIVE'\n        else:\n            duration = lavalink.utils.format_time(player.current.duration)\n        song = f'**[{player.current.title}]({player.current.uri})**\\n({position}/{duration})'\n\n        embed = discord.Embed(color=discord.Color.blurple(),\n                              title='Now Playing', description=song)\n        await ctx.send(embed=embed)\n\n    @commands.command(aliases=['q'])\n    async def queue(self, ctx, page: int = 1):\n        \"\"\" Shows the player's queue. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not player.queue:\n            return await ctx.send('Nothing queued.')\n\n        items_per_page = 10\n        pages = math.ceil(len(player.queue) / items_per_page)\n\n        start = (page - 1) * items_per_page\n        end = start + items_per_page\n\n        queue_list = ''\n        for index, track in enumerate(player.queue[start:end], start=start):\n            queue_list += f'`{index + 1}.` [**{track.title}**]({track.uri})\\n'\n\n        embed = discord.Embed(colour=discord.Color.blurple(),\n                              description=f'**{len(player.queue)} tracks**\\n\\n{queue_list}')\n        embed.set_footer(text=f'Viewing page {page}/{pages}')\n        await ctx.send(embed=embed)\n\n    @commands.command(aliases=['resume'])\n    async def pause(self, ctx):\n        \"\"\" Pauses/Resumes the current track. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not player.is_playing:\n            return await ctx.send('Not playing.')\n\n        if player.paused:\n            await player.set_pause(False)\n            await ctx.send('⏯ | Resumed')\n        else:\n            await player.set_pause(True)\n            await ctx.send('⏯ | Paused')\n\n    @commands.command(aliases=['vol'])\n    async def volume(self, ctx, volume):\n        \"\"\" Changes the player's volume (0-1000). \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        try:\n            volume = int(volume)\n        except:\n            volume = int(volume[:-1])\n\n        if not volume:\n            return await ctx.send(f'🔈 | {player.volume}%')\n\n        await player.set_volume(volume)  # Values are automatically capped between, or equal to 0-1000.\n        await ctx.send(f'🔈 | Set to {player.volume}%')\n\n    @commands.command()\n    async def shuffle(self, ctx):\n        \"\"\" Shuffles the player's queue. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n        if not player.is_playing:\n            return await ctx.send('Nothing playing.')\n\n        player.shuffle = not player.shuffle\n        await ctx.send('🔀 | Shuffle ' + ('enabled' if player.shuffle else 'disabled'))\n\n    @commands.command(aliases=['loop', 'l'])\n    async def repeat(self, ctx):\n        \"\"\" Repeats the current song until the command is invoked again. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not player.is_playing:\n            return await ctx.send('Nothing playing.')\n\n        player.repeat = not player.repeat\n        await ctx.send('🔁 | Repeat ' + ('enabled' if player.repeat else 'disabled'))\n\n    @commands.command()\n    async def remove(self, ctx, index: int):\n        \"\"\" Removes an item from the player's queue with the given index. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not player.queue:\n            return await ctx.send('Nothing queued.')\n\n        if index > len(player.queue) or index < 1:\n            return await ctx.send(f'Index has to be **between** 1 and {len(player.queue)}')\n\n        removed = player.queue.pop(index - 1)  # Account for 0-index.\n\n        await ctx.send(f'Removed **{removed.title}** from the queue.')\n\n    @commands.command()\n    async def find(self, ctx, *, query):\n        \"\"\" Lists the first 10 search results from a given query. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not query.startswith('ytsearch:') and not query.startswith('scsearch:'):\n            query = 'ytsearch:' + query\n\n        results = await player.node.get_tracks(query)\n\n        if not results or not results['tracks']:\n            return await ctx.send('Nothing found.')\n\n        tracks = results['tracks'][:10]  # First 10 results\n\n        o = ''\n        for index, track in enumerate(tracks, start=1):\n            track_title = track['info']['title']\n            track_uri = track['info']['uri']\n            o += f'`{index}.` [{track_title}]({track_uri})\\n'\n\n        embed = discord.Embed(color=discord.Color.blurple(), description=o)\n        await ctx.send(embed=embed)\n\n    @commands.command(aliases=['dc'])\n    async def disconnect(self, ctx):\n        \"\"\" Disconnects the player from the voice channel and clears its queue. \"\"\"\n        player = self.bot.lavalink.player_manager.get(ctx.guild.id)\n\n        if not player.is_connected:\n            return await ctx.send('Not connected.')\n\n        if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):\n            return await ctx.send('You\\'re not in my voice channel!')\n\n        player.queue.clear()\n        await player.stop()\n        if ctx.voice_client:\n            await ctx.voice_client.disconnect(force=True)\n        await ctx.send('*⃣ | Disconnected.')\n\n    async def ensure_voice(self, ctx):\n        \"\"\" This check ensures that the bot and command author are in the same voice channel. \"\"\"\n        player = self.bot.lavalink.player_manager.create(ctx.guild.id)\n        should_connect = ctx.command.name in ('play')  # Add commands that require joining voice to work.\n\n        if not ctx.author.voice or not ctx.author.voice.channel:\n            raise commands.CommandInvokeError('Join a voice channel first.')\n\n        if not player.is_connected:\n            if not should_connect:\n                raise commands.CommandInvokeError('Not connected.')\n\n            permissions = ctx.author.voice.channel.permissions_for(ctx.me)\n\n            if not permissions.connect or not permissions.speak:  # Check user limit too?\n                raise commands.CommandInvokeError('I need the `CONNECT` and `SPEAK` permissions.')\n\n            player.store('channel', ctx.channel.id)\n            # mark the player's voice channel for lavalink to know which guild/channel\n            try:\n                player.channel_id = str(ctx.author.voice.channel.id)\n            except Exception:\n                pass\n\n            voice_client = ctx.voice_client\n            if voice_client and voice_client.channel.id != ctx.author.voice.channel.id:\n                raise commands.CommandInvokeError('You need to be in my voice channel.')\n            if not voice_client:\n                await ctx.author.voice.channel.connect(cls=LavalinkVoiceClient)\n        else:\n            if not player.channel_id or int(player.channel_id) != ctx.author.voice.channel.id:\n                raise commands.CommandInvokeError('You need to be in my voice channel.')\n\n\nasync def setup(bot):\n    \"\"\" Initialize music module \"\"\"\n    await bot.add_cog(Music(bot))"
  },
  {
    "path": "teapot/cogs/neko.py",
    "content": "\"\"\" Module for generating random neko pictures\"\"\"\nimport io\nimport json\n\nimport aiohttp\nimport discord\nimport requests\nfrom discord.ext import commands\n\nimport teapot\nimport teapot.tools.embed as embed\n\n\ndef neko_api(x):\n    req = requests.get(f'https://nekos.life/api/v2/img/{x}')\n    try:\n        if req.status_code != 200:\n            print(\"Unable to obtain neko image!\")\n        api_json = json.loads(req.text)\n        url = api_json[\"url\"]\n        em = embed.newembed().set_image(url=url)\n        return em\n    except:\n        return teapot.messages.error(f\"obtaining image ({req.status_code})\")\n\n\nclass Neko(commands.Cog):\n    \"\"\"Neko!!! :3\"\"\"\n\n    def __init__(self, bot):\n        \"\"\"Initialize neko class\"\"\"\n        self.bot = bot\n\n    @commands.command()\n    async def neko(self, ctx):\n        await ctx.send(embed=neko_api(\"neko\"))\n\n    @commands.command()\n    async def waifu(self, ctx):\n        await ctx.send(embed=neko_api(\"waifu\"))\n\n    @commands.command()\n    async def avatar(self, ctx):\n        await ctx.send(embed=neko_api(\"avatar\"))\n\n    @commands.command()\n    async def wallpaper(self, ctx):\n        await ctx.send(embed=neko_api(\"wallpaper\"))\n\n    @commands.command()\n    async def tickle(self, ctx):\n        await ctx.send(embed=neko_api(\"tickle\"))\n\n    @commands.command()\n    async def poke(self, ctx):\n        await ctx.send(embed=neko_api(\"poke\"))\n\n    @commands.command()\n    async def kiss(self, ctx):\n        await ctx.send(embed=neko_api(\"kiss\"))\n\n    @commands.command(aliases=['8ball'])\n    async def eightball(self, ctx):\n        await ctx.send(embed=neko_api(\"8ball\"))\n\n    @commands.command()\n    async def lizard(self, ctx):\n        await ctx.send(embed=neko_api(\"lizard\"))\n\n    @commands.command()\n    async def slap(self, ctx):\n        await ctx.send(embed=neko_api(\"slap\"))\n\n    @commands.command()\n    async def cuddle(self, ctx):\n        await ctx.send(embed=neko_api(\"cuddle\"))\n\n    @commands.command()\n    async def goose(self, ctx):\n        await ctx.send(embed=neko_api(\"goose\"))\n\n    @commands.command()\n    async def fox_girl(self, ctx):\n        await ctx.send(embed=neko_api(\"fox_girl\"))\n\n    @commands.command()\n    async def baka(self, ctx):\n        await ctx.send(embed=neko_api(\"baka\"))\n\n    @commands.command()\n    async def hentai(self, ctx, api_type=\"\"):\n        if ctx.message.channel.nsfw:\n            api_types = ['femdom', 'classic', 'ngif', 'erofeet', 'erok', 'les',\n                         'hololewd', 'lewdk', 'keta', 'feetg', 'nsfw_neko_gif', 'eroyuri',\n                         'tits', 'pussy_jpg', 'cum_jpg', 'pussy', 'lewdkemo', 'lewd', 'cum', 'spank',\n                         'smallboobs', 'Random_hentai_gif', 'nsfw_avatar', 'hug', 'gecg', 'boobs', 'pat',\n                         'feet', 'smug', 'kemonomimi', 'solog', 'holo', 'bj', 'woof', 'yuri', 'trap', 'anal',\n                         'blowjob', 'holoero', 'feed', 'gasm', 'hentai', 'futanari', 'ero', 'solo', 'pwankg', 'eron',\n                         'erokemo']\n            if api_type in api_types:\n                req = requests.get(f'https://nekos.life/api/v2/img/{api_type}')\n                try:\n                    if req.status_code != 200:\n                        print(\"Unable to obtain image\")\n                    api_json = json.loads(req.text)\n                    url = api_json[\"url\"]\n\n                    message = await ctx.send(embed=teapot.messages.downloading())\n                    async with aiohttp.ClientSession() as session:\n                        async with session.get(url) as resp:\n                            if resp.status != 200:\n                                print(resp.status)\n                                print(await resp.read())\n                                return await ctx.send('Could not download file...')\n                            data = io.BytesIO(await resp.read())\n                            await ctx.send(\n                                file=discord.File(data, f'SPOILER_HENTAI.{url.split(\"/\")[-1].split(\".\")[-1]}'))\n                            await message.delete()\n                except:\n                    await ctx.send(embed=teapot.messages.error(f\"obtaining image ({req.status_code})\"))\n            else:\n                await ctx.send(embed=teapot.messages.invalidargument(\", \".join(api_types)))\n        else:\n            await ctx.send(\"This command only works in NSFW channels!\")\n\n\nasync def setup(bot):\n    \"\"\" Setup Neko Module\"\"\"\n    await bot.add_cog(Neko(bot))\n"
  },
  {
    "path": "teapot/cogs/nqn.py",
    "content": "from discord import utils\nfrom discord.ext import commands\n\n\nclass Emoji(commands.Cog):\n    def __init__(self, bot):\n        self.bot = bot\n\n    async def getemote(self, arg):\n        emoji = utils.get(self.bot.emojis, name=arg.strip(\":\"))\n\n        if emoji is not None:\n            if emoji.animated:\n                add = \"a\"\n            else:\n                add = \"\"\n            return f\"<{add}:{emoji.name}:{emoji.id}>\"\n        else:\n            return None\n\n    async def getinstr(self, content):\n        ret = []\n\n        spc = content.split(\" \")\n        cnt = content.split(\":\")\n\n        if len(cnt) > 1:\n            for item in spc:\n                if item.count(\":\") > 1:\n                    wr = \"\"\n                    if item.startswith(\"<\") and item.endswith(\">\"):\n                        ret.append(item)\n                    else:\n                        cnt = 0\n                        for i in item:\n                            if cnt == 2:\n                                aaa = wr.replace(\" \", \"\")\n                                ret.append(aaa)\n                                wr = \"\"\n                                cnt = 0\n\n                            if i != \":\":\n                                wr += i\n                            else:\n                                if wr == \"\" or cnt == 1:\n                                    wr += \" : \"\n                                    cnt += 1\n                                else:\n                                    aaa = wr.replace(\" \", \"\")\n                                    ret.append(aaa)\n                                    wr = \":\"\n                                    cnt = 1\n\n                        aaa = wr.replace(\" \", \"\")\n                        ret.append(aaa)\n                else:\n                    ret.append(item)\n        else:\n            return content\n\n        return ret\n\n\nasync def setup(bot):\n    await bot.add_cog(Emoji(bot))\n"
  },
  {
    "path": "teapot/cogs/osu.py",
    "content": "import json\n\nimport requests\nfrom discord.ext import commands\n\nimport teapot.config\nimport teapot.tools.embed as dmbd\n\n\nclass OsuPlayer:\n\n    def __init__(self, player):\n        self.id = player[\"user_id\"]\n        self.username = player[\"username\"]\n        self.join_date = (player[\"join_date\"].split(\" \"))[0]\n        self.c300 = player[\"count300\"]\n        self.c100 = player[\"count100\"]\n        self.c50 = player[\"count50\"]\n        self.playcount = player[\"playcount\"]\n        self.ranked = player[\"ranked_score\"]\n        self.total = player[\"total_score\"]\n        self.pp = player[\"pp_rank\"]\n        self.level = player[\"level\"]\n        self.pp_raw = player[\"pp_raw\"]\n        self.accuracy = player[\"accuracy\"]\n        self.count_ss = player[\"count_rank_ss\"]\n        self.count_s = player[\"count_rank_s\"]\n        self.count_a = player[\"count_rank_a\"]\n        self.country = player[\"country\"]\n        self.pp_country_rank = player[\"pp_country_rank\"]\n\n    def display(self, author):\n        em = dmbd.newembed()\n        em.set_author(name=f\"{self.country.upper()} | {self.username}\", url=f\"https://osu.ppy.sh/u/{self.username}\")\n        em.add_field(name='Performance', value=self.pp_raw + 'pp')\n        em.add_field(name='Accuracy', value=\"{0:.2f}%\".format(float(self.accuracy)))\n        lvl = int(float(self.level))\n        percent = int((float(self.level) - lvl) * 100)\n        em.add_field(name='Level', value=f\"{lvl} ({percent}%)\")\n        em.add_field(name='Rank', value=self.pp)\n        em.add_field(name='Country Rank', value=self.pp_country_rank)\n        em.add_field(name='Playcount', value=self.playcount)\n        em.add_field(name='Total Score', value=self.total)\n        em.add_field(name='Ranked Score', value=self.ranked)\n        em.add_field(name='Registered At', value=self.join_date)\n        return em\n\n\nclass Osu(commands.Cog):\n    \"\"\"Osu! Statistics\"\"\"\n\n    def __init__(self, bot):\n        self.bot = bot\n\n    @commands.command()\n    @commands.guild_only()\n    async def osu(self, ctx, *, args: str):\n        \"\"\" Look up an osu player \"\"\"\n\n        args_array = args.split(' ')\n        if len(args_array) == 2:\n            peppy = args_array[0]\n            mode = args_array[1]\n        elif len(args_array) == 1:\n            peppy = args_array[0]\n            mode = '0'\n        else:\n            await ctx.send('Invalid Syntax!')\n            await ctx.message.add_reaction(emoji='❌')\n            return\n        r = requests.get('https://osu.ppy.sh/api/get_user'\n                         '?k=' + teapot.config.osu_api_key() + '&u=' + peppy + '&m=' + mode)\n\n        if r.status_code != 200:\n            print('Osu API Debug: ' + str(r.status_code) + ' | ' + r.text)\n            if r.status_code == 401:\n                await ctx.send('Invalid osu!api key. Please contact your server owner.')\n            else:\n                await ctx.send('Failed to fetch osu!api data. (' + str(r.status_code) + ')')\n            return\n\n        user = json.loads(r.text)\n        if len(user) <= 1:\n            await ctx.send('osu! player not found.')\n            return\n        await ctx.message.add_reaction(emoji='✅')\n        await ctx.send(embed=OsuPlayer(user[0]).display(ctx.message.author))\n\n\nasync def setup(bot):\n    await bot.add_cog(Osu(bot))\n"
  },
  {
    "path": "teapot/config.py",
    "content": "import os\nimport teapot\n\ndef bot_owner():\n    return eval(os.getenv(\"BOT_owner\", \"216127021028212737\"))\n\ndef bot_token():\n    return os.getenv(\"BOT_TOKEN\")\n\ndef osu_api_key():\n    return os.getenv(\"OSU_API_KEY\")\n\ndef trn_api_key():\n    return os.getenv(\"TRN_API_KEY\")\n\ndef bot_prefix():\n    return eval(os.getenv(\"BOT_PREFIX\", \"['/teapot ', '/tp ']\"))\n\ndef bot_status():\n    default_prefix = (\n        f'{\", \".join(teapot.config.bot_prefix())} | Teapot.py {teapot.version()}'\n    )\n    try:\n        return eval(os.getenv(\"BOT_STATUS\", default_prefix))\n    except:\n        return os.getenv(\"BOT_STATUS\", default_prefix)\n\ndef storage_type():\n    if os.environ[\"STORAGE_TYPE\"] != \"mysql\":\n        os.environ[\"STORAGE_TYPE\"] = \"flatfile\"\n    return os.environ[\"STORAGE_TYPE\"]\n\ndef db_host():\n    return os.environ[\"DB_HOST\"]\n\ndef db_port():\n    return os.getenv(\"DB_PORT\", \"3306\")\n\ndef db_schema():\n    return os.environ[\"DB_SCHEMA\"]\n\ndef db_user():\n    return os.environ[\"DB_USER\"]\n\ndef db_password():\n    return os.environ[\"DB_PASSWORD\"]\n\ndef lavalink_host():\n    return os.environ[\"LAVALINK_HOST\"]\n\ndef lavalink_port():\n    return os.environ[\"LAVALINK_PORT\"]\n\ndef lavalink_password():\n    return os.environ[\"LAVALINK_PASSWORD\"]\n"
  },
  {
    "path": "teapot/event_handler/__init__.py",
    "content": ""
  },
  {
    "path": "teapot/event_handler/db_handler.py",
    "content": "import teapot\n\nasync def on_message_handler(bot, message):\n    if teapot.config.storage_type() == \"mysql\":\n        try:\n            database = teapot.database.__init__()\n            db = teapot.database.db(database)\n            db.execute(\"SELECT * FROM `users` WHERE user_id = '\" + str(message.author.id) + \"'\")\n            if db.rowcount == 0:\n                db.execute(\"INSERT INTO `users`(user_id, user_name, user_display_name) VALUES(%s, %s, %s)\",\n                           (message.author.id, message.author.name, message.author.display_name))\n                database.commit()\n            db.execute(\"SELECT * FROM `channels` WHERE channel_id = '\" + str(message.channel.id) + \"'\")\n            if db.rowcount == 0:\n                db.execute(\"INSERT INTO `channels`(channel_id, channel_name) VALUES(%s, %s)\",\n                           (message.channel.id, message.channel.name))\n                database.commit()\n            db.execute(\n                \"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, message_id, user_id, action_type, message) VALUES(%s, %s, %s, %s, %s, %s, %s)\",\n                (teapot.time(), message.guild.id, message.channel.id, message.id, message.author.id,\n                 \"MESSAGE_SEND\", message.content))\n            database.commit()\n        except Exception as e:\n            print(e)\n"
  },
  {
    "path": "teapot/event_handler/loader.py",
    "content": "from discord.ext import commands\nimport importlib\nimport os\n\nclass EventHandlerLoader:\n    \"\"\"\n    Dynamically loads event handler modules and registers a unified on_message event.\n\n    As discord only allows one on_message event, this loader collects all on_message handlers\n    from the event_handler submodules and calls them sequentially.\n    \"\"\"\n    def __init__(self, bot: commands.Bot):\n        self.bot = bot\n        self.handlers = []\n        self.load_event_handlers()\n        self.register_on_message()\n\n    def load_event_handlers(self):\n        \"\"\"\n        Scan for all python file in the event_handler directory to find on_message handlers.\n        \"\"\"\n        handler_dir = os.path.dirname(__file__)\n        for fname in os.listdir(handler_dir):\n            if fname.endswith('.py') and fname not in ('__init__.py', 'loader.py'):\n                mod_name = f'teapot.event_handler.{fname[:-3]}'\n                module = importlib.import_module(mod_name)\n                if hasattr(module, 'on_message_handler'):\n                    self.handlers.append(module.on_message_handler)\n    \n    def get_registered_handlers(self):\n        return self.handlers\n\n    def register_on_message(self):\n        @self.bot.event\n        async def on_message(message):\n            for handler in self.handlers:\n                await handler(self.bot, message)\n            await self.bot.process_commands(message)"
  },
  {
    "path": "teapot/event_handler/nqn_handler.py",
    "content": "from discord import utils\n\nasync def on_message_handler(bot, message):\n    if message.author.bot:\n        return\n    if \":\" in message.content:\n        emoji_cog = bot.get_cog(\"Emoji\")  # obtain the Emoji Cog instance\n        if emoji_cog is None:\n            return\n        msg = await emoji_cog.getinstr(message.content)\n        ret = \"\"\n        em = False\n        smth = message.content.split(\":\")\n        if len(smth) > 1:\n            for word in msg:\n                if word.startswith(\":\") and word.endswith(\":\") and len(word) > 1:\n                    emoji = await emoji_cog.getemote(word)\n                    if emoji is not None:\n                        em = True\n                        ret += f\" {emoji}\"\n                    else:\n                        ret += f\" {word}\"\n                else:\n                    ret += f\" {word}\"\n        else:\n            ret += msg\n        if em:\n            webhooks = await message.channel.webhooks()\n            webhook = utils.get(webhooks, name=\"Imposter NQN\")\n            if webhook is None:\n                webhook = await message.channel.create_webhook(name=\"Imposter NQN\")\n            await webhook.send(ret, username=message.author.name, avatar_url=message.author.display_avatar.url)\n            await message.delete()\n"
  },
  {
    "path": "teapot/event_handler/profanity_handler.py",
    "content": "import teapot\nfrom teapot.events import predict_prob\nimport discord\n\nasync def on_message_handler(bot, message):\n    punctuations = '!()-[]{};:\\'\"\\\\,<>./?@#$%^&*_~'\n    msg = \"\"\n    for char in message.content.lower():\n        if char not in punctuations:\n            msg = msg + char\n    prob = predict_prob([msg])\n    if prob >= 0.8:\n        em = discord.Embed(title=f\"AI Analysis Results\", color=0xC54B4F)\n        em.add_field(name='PROFANITY DETECTED! ', value=str(prob[0]))\n        await message.channel.send(embed=em)"
  },
  {
    "path": "teapot/event_handler/sao_handler.py",
    "content": "import discord\nimport teapot\n\nasync def on_message_handler(bot, message):\n    punctuations = '!()-[]{};:\\'\"\\\\,<>./?@#$%^&*_~'\n    msg = \"\"\n    for char in message.content.lower():\n        if char not in punctuations:\n            msg = msg + char\n    if msg.startswith(\"system call \"):\n        content = msg[12:].split(\" \")\n        if content[0].lower() == \"inspect\":\n            if content[1].lower() == \"entire\":\n                if content[2].lower() == \"command\":\n                    if content[3].lower() == \"list\":\n                        em = discord.Embed(title=f\"🍢 SAO Command List\", color=0x7400FF)\n                        em.set_thumbnail(url=\"https://cdn.discordapp.com/attachments/668816286784159763/674285661510959105/Kirito-Sao-Logo-1506655414__76221.1550241566.png\")\n                        em.add_field(name='Commands', value=\"generate xx element\\ngenerate xx element xx shape\\ninspect entire command list\")\n                        em.set_footer(text=f\"{teapot.copyright()} | Code licensed under the MIT License\")\n                        await message.channel.send(embed=em)\n        elif content[0].lower() == \"generate\":\n            if content[-1].lower() == \"element\":\n                em = discord.Embed(title=f\"✏ Generated {content[1].lower()} element!\", color=0xFF0000)\n                await message.channel.send(embed=em)\n            if content[-1].lower() == \"shape\":\n                if content[2].lower() == \"element\":\n                    em = discord.Embed(title=f\"✏ Generated {content[-2].lower()} shaped {content[1].lower()} element!\", color=0xFF0000)\n                    await message.channel.send(embed=em)\n"
  },
  {
    "path": "teapot/events.py",
    "content": "import json\nimport teapot\nimport discord\nfrom profanity_check import predict_prob\n\n\ndef __init__(bot):\n    \"\"\" Initialize events \"\"\"\n    join(bot)\n    leave(bot)\n    on_guild_join(bot)\n    # on_message is now handled by event_handler/loader.py\n    message_edit(bot)\n    message_delete(bot)\n    on_command_error(bot)\n\n\ndef join(bot):\n    @bot.event\n    async def on_member_join(member):\n        if teapot.config.storage_type() == \"mysql\":\n            try:\n                database = teapot.database.__init__()\n                db = teapot.database.db(database)\n                db.execute(\n                    \"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, user_id, action_type) VALUES(%s, %s, %s, %s, %s)\",\n                    (teapot.time(), member.guild.id, member.channel.id, member.id, \"MEMBER_JOIN\"))\n                database.commit()\n            except Exception as e:\n                print(e)\n\n\ndef leave(bot):\n    @bot.event\n    async def on_member_remove(member):\n        if teapot.config.storage_type() == \"mysql\":\n            try:\n                database = teapot.database.__init__()\n                db = teapot.database.db(database)\n                db.execute(\n                    \"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, user_id, action_type) VALUES(%s, %s, %s, %s, %s)\",\n                    (teapot.time(), member.guild.id, member.channel.id, member.id, \"MEMBER_REMOVE\"))\n                database.commit()\n            except Exception as e:\n                print(e)\n\n\ndef on_guild_join(bot):\n    @bot.event\n    async def on_guild_join(ctx):\n        if teapot.config.storage_type() == \"mysql\":\n            teapot.database.create_guild_table(ctx.guild)\n\ndef message_edit(bot):\n    @bot.event\n    async def on_raw_message_edit(ctx):\n        if ctx.guild_id is None:\n            return\n        guild_id = json.loads(json.dumps(ctx.data))['guild_id']\n        channel_id = json.loads(json.dumps(ctx.data))['channel_id']\n        message_id = json.loads(json.dumps(ctx.data))['id']\n        try:\n            author_id = json.loads(json.dumps(json.loads(json.dumps(ctx.data))['author']))['id']\n            content = json.loads(json.dumps(ctx.data))['content']\n            if teapot.config.storage_type() == \"mysql\":\n                try:\n                    database = teapot.database.__init__()\n                    db = teapot.database.db(database)\n                    db.execute(\n                        \"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, message_id, user_id, action_type, message) VALUES(%s, %s, %s, %s, %s, %s, %s)\",\n                        (teapot.time(), guild_id, channel_id, message_id, author_id, \"MESSAGE_EDIT\", content))\n                    database.commit()\n                except Exception as e:\n                    print(e)\n        except:\n            content = str(json.loads(json.dumps(ctx.data))['embeds'])\n            if teapot.config.storage_type() == \"mysql\":\n                try:\n                    database = teapot.database.__init__()\n                    db = teapot.database.db(database)\n                    db.execute(\n                        \"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, message_id, action_type, message) VALUES(%s, %s, %s, %s, %s, %s)\",\n                        (teapot.time(), guild_id, channel_id, message_id, \"MESSAGE_EDIT\", content))\n                    database.commit()\n                except Exception as e:\n                    print(e)\n\n\ndef message_delete(bot):\n    @bot.event\n    async def on_message_delete(ctx):\n        if teapot.config.storage_type() == \"mysql\":\n            try:\n                database = teapot.database.__init__()\n                db = teapot.database.db(database)\n                db.execute(\n                    \"INSERT INTO `guild_logs`(timestamp, guild_id, channel_id, message_id, user_id, action_type) VALUES(%s, %s, %s, %s, %s, %s)\",\n                    (teapot.time(), ctx.guild.id, ctx.channel.id, ctx.id, ctx.author.id, \"MESSAGE_DELETE\"))\n                database.commit()\n            except Exception as e:\n                print(e)\n\n\ndef on_command_error(bot):\n    @bot.event\n    async def on_command_error(ctx, e):\n        if teapot.config.storage_type() == \"mysql\":\n            try:\n                database = teapot.database.__init__()\n                db = teapot.database.db(database)\n                db.execute(\n                    \"INSERT INTO `bot_logs`(timestamp, type, class, message) VALUES(%s, %s, %s, %s)\",\n                    (teapot.time(), \"CMD_ERROR\", __name__, str(e)))\n                database.commit()\n            except Exception as e:\n                print(e)\n"
  },
  {
    "path": "teapot/managers/__init__.py",
    "content": "from .database import *\n"
  },
  {
    "path": "teapot/managers/database.py",
    "content": "\"\"\" Database Manager \"\"\"\nimport mysql.connector\nimport teapot\n\n\n# TABLES = {}\n# TABLES['guilds'] = (\n#     \"CREATE TABLE IF NOT EXISTS `guilds` (\"\n#     \"  `guild_id` int(18) NOT NULL,\"\n#     \"  `guild_name` TINYTEXT NOT NULL,\"\n#     \") ENGINE=InnoDB\")\n# TABLES['channels'] = (\n#     \"CREATE TABLE IF NOT EXISTS `channels` (\"\n#     \"  `channel_id` int(18) NOT NULL,\"\n#     \"  `channel_name` TINYTEXT NOT NULL,\"\n#     \") ENGINE=InnoDB\")\n# TABLES['users'] = (\n#     \"CREATE TABLE IF NOT EXISTS `users` (\"\n#     \"  `user_id` int(18) NOT NULL,\"\n#     \"  `user_name` TINYTEXT NOT NULL,\"\n#     \"  `user_discriminator` int(4) NOT NULL,\"\n#     \") ENGINE=InnoDB\")\n\n\ndef __init__():\n    try:\n        database = mysql.connector.connect(\n            host=teapot.config.db_host(),\n            port=teapot.config.db_port(),\n            db=teapot.config.db_schema(),\n            user=teapot.config.db_user(),\n            passwd=teapot.config.db_password(),\n            charset='utf8mb4',\n            use_unicode=True\n        )\n        return (database)\n    except Exception as error:\n        print(\"\\nUnable to connect to database. Please check your credentials!\\n\" + str(error) + \"\\n\")\n        quit()\n\n\ndef db(database):\n    try:\n        return database.cursor(buffered=True)\n    except Exception as e:\n        print(f\"\\nAn error occurred while executing SQL statement\\n{e}\\n\")\n        quit()\n\n\ndef create_table(stmt):\n    database = teapot.managers.database.__init__()\n    db = teapot.managers.database.db(database)\n\n    db.execute(stmt)\n    db.close()\n    del db\n\n\ndef insert(stmt, var):\n    database = teapot.managers.database.__init__()\n    db = teapot.managers.database.db(database)\n\n    db.execute(stmt, var)\n    database.commit()\n\n    db.close()\n    del db\n\n\ndef insert_if_not_exists(stmt):\n    database = teapot.managers.database.__init__()\n    db = teapot.managers.database.db(database)\n\n    db.execute(stmt)\n    database.commit()\n\n    db.close()\n    del db\n\n\ndef create_guild_table(guild):\n    database = teapot.managers.database.__init__()\n    db = teapot.managers.database.db(database)\n\n    db.execute(\"SELECT * FROM `guilds` WHERE guild_id = '\" + str(guild.id) + \"'\")\n    if db.rowcount == 0:\n        insert(\"INSERT INTO `guilds`(guild_id, guild_name) VALUES(%s, %s)\", (guild.id, guild.name))\n"
  },
  {
    "path": "teapot/messages.py",
    "content": "import discord\n\n\ndef WIP():\n    \"\"\"Work In Progress\"\"\"\n    return discord.Embed(title=\"⏲ This feature is work in progress!\",\n                         description=\"Please stay tuned to our latest updates [here](\"\n                                     \"https://github.com/RedCokeDevelopment/Teapot.py)!\", color=0x89CFF0)\n\n\ndef permission_denied():\n    \"\"\"user don't have permission\"\"\"\n    return discord.Embed(title=\"🛑 Permission Denied!\", description=\"You do not have permission to do this!\",\n                         color=0xFF0000)\n\n\ndef notfound(s):\n    return discord.Embed(title=f\"😮 Oops! {s.capitalize()} not found!\",\n                         description=f\"Unable to find the specified {s.lower()}!\",\n                         color=0xFF0000)\n\n\ndef downloading():\n    return discord.Embed(title=\"⏱ Downloading File...\", description=\"Please wait for up to 3 seconds!\",\n                         color=0xFF0000)\n\n\ndef error(e=\"executing command\"):\n    return discord.Embed(title=f\"⚠ Unknown error occurred while {e}!\",\n                         description=\"Please report to [Teapot.py](https://github.com/RedCokeDevelopment/Teapot.py) developers [here](https://github.com/RedCokeDevelopment/Teapot.py/issues)!\",\n                         color=0xFF0000)\n\n\ndef invalidargument(arg):\n    return discord.Embed(title=\"🟥 Invalid argument!\", description=f\"Valid argument(s): ``{arg}``\",\n                         color=0xFF0000)\n\n\ndef toomanyarguments():\n    return discord.Embed(title=\"🛑 Too many arguments!\", description=f\"You have entered too many arguments!\",\n                         color=0xFF0000)\n"
  },
  {
    "path": "teapot/setup.py",
    "content": "import time\n\nimport teapot\n\n\ndef __init__():\n    print('\\n' * 100)\n    print(\"\"\"\n    \n      _____                      _      ____             __ _                       _             \n     |_   _|__  __ _ _ __   ___ | |_   / ___|___  _ __  / _(_) __ _ _   _ _ __ __ _| |_ ___  _ __ \n       | |/ _ \\\\/ _` | '_ \\\\ / _ \\\\| __| | |   / _ \\\\| '_ \\\\| |_| |/ _` | | | | '__/ _` | __/ _ \\\\| '__|\n       | |  __/ (_| | |_) | (_) | |_  | |__| (_) | | | |  _| | (_| | |_| | | | (_| | || (_) | |   \n       |_|\\\\___|\\\\__,_| .__/ \\\\___/ \\\\__|  \\\\____\\\\___/|_| |_|_| |_|\\\\__, |\\\\__,_|_|  \\\\__,_|\\\\__\\\\___/|_|   \n                    |_|    © 2020-2021 Red Coke Development    |___/                               \n    \n                      NOTE: You can change the settings later in .env\n    \n    \"\"\")\n\n    # Declare default configuration\n    input_mysql_host = \"127.0.0.1\"\n    input_mysql_port = \"3306\"\n    input_mysql_schema = \"teapot\"\n    input_mysql_user = \"root\"\n    input_mysql_password = \"\"\n\n    input_bot_token = input(\"Discord bot token: \")\n    input_bot_prefix = input(\"Command Prefix: \")\n    input_bot_status = input(\"Bot status: (Playing xxx) \")\n    input_storage_type = input(\"Use MySQL? [Y/n] \")\n    if input_storage_type.lower() == \"y\" or input_storage_type.lower() == \"yes\":\n        input_storage_type = \"mysql\"\n        input_mysql_host = input(\"Database Host: \")\n        input_mysql_port = input(\"Database Port: \")\n        input_mysql_schema = input(\"Database Schema: \")\n        input_mysql_user = input(\"Database User: \")\n        input_mysql_password = input(\"Database Password: \")\n    elif input_storage_type.lower() == \"n\" or input_storage_type.lower() == \"no\":\n        input_storage_type = \"flatfile\"\n    else:\n        print(\"[!] Your input was invalid, and has been automagically set to flatfile storage.\")\n        input_storage_type = \"flatfile\"\n    input_lavalink_host = input(\"Lavalink Host: \")\n    input_lavalink_port = input(\"Lavalink Port: \")\n    input_lavalink_password = input(\"Lavalink Password: \")\n\n    input_osu_api_key = input(\"osu!api Key\")\n\n    try:\n        config = f\"\"\"CONFIG_VERSION={teapot.config_version()}\nBOT_TOKEN={input_bot_token}\nBOT_PREFIX={input_bot_prefix}\nBOT_STATUS={input_bot_status}\nSTORAGE_TYPE={input_storage_type}\n\nDB_HOST={input_mysql_host}\nDB_PORT={input_mysql_port}\nDB_SCHEMA={input_mysql_schema}\nDB_USER={input_mysql_user}\nDB_PASSWORD={input_mysql_password}\n\nLAVALINK_HOST={input_lavalink_host}\nLAVALINK_PORT={input_lavalink_port}\nLAVALINK_PASSWORD={input_lavalink_password}\n\nOSU_API_KEY={input_osu_api_key}\n\"\"\"\n        open('./.env', 'w').write(config)\n        print(\"\\n[*] Successfully created .env file!\")\n        print(\"Teapot.py setup complete! Starting bot in 5 seconds...\")\n        time.sleep(5)\n        print('\\n' * 100)\n    except Exception as e:\n        print(\"\\n[!] An error occurred when creating config file.\\n\" + str(e))\n        quit()\n"
  },
  {
    "path": "teapot/tools/__init__.py",
    "content": "from .embed import *\n"
  },
  {
    "path": "teapot/tools/embed.py",
    "content": "import discord\n\nimport teapot\n\n\ndef newembed(c=0x428DFF):\n    em = discord.Embed(colour=c)\n    em.set_footer(text=teapot.copyright(),\n                  icon_url=\"https://cdn.discordapp.com/avatars/612634758744113182/7fe078b5ea6b43000dfb7964e3e4d21d.png?size=512\")\n\n    return em\n"
  },
  {
    "path": "test/__init__.py",
    "content": "\n"
  },
  {
    "path": "test/test_cogs.py",
    "content": "import importlib\nimport pytest\nimport os\n\nCOGS_DIR = os.path.join(os.path.dirname(__file__), \"..\", \"teapot\", \"cogs\")\nCOGS = [\n    fname[:-3] for fname in os.listdir(COGS_DIR)\n    if fname.endswith(\".py\") and fname != \"__init__.py\"\n]\n\n@pytest.mark.parametrize(\"cog_name\", COGS)\ndef test_cog_import_and_setup(cog_name):\n    mod_path = f\"teapot.cogs.{cog_name}\"\n    module = importlib.import_module(mod_path)\n    # check for cog class\n    cog_class = None\n    for attr in dir(module):\n        obj = getattr(module, attr)\n        if hasattr(obj, \"__bases__\") and any(b.__name__ == \"Cog\" for b in getattr(obj, \"__bases__\", [])):\n            cog_class = obj\n            break\n    assert cog_class is not None, f\"{mod_path} Does not have a valid Cog class\"\n    assert hasattr(module, \"setup\"), f\"{mod_path} is missing setup(bot) function\"\n    assert callable(getattr(module, \"setup\")), f\"{mod_path} setup is not a callable function\"\n"
  }
]