├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── codeql-analysis.yml │ ├── greetings.yml │ ├── label.yml │ ├── python-app.yml │ └── stale.yml ├── .gitignore ├── README.md ├── cogs ├── Paginator.py ├── database.py ├── rtfm.py └── tags.py ├── ext ├── helpers.py ├── paginator.py └── rtfm_utils.py ├── exten.py ├── main.py ├── requirements.txt └── storage ├── bot_data.py ├── config.json └── morse.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '35 23 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: 'Message that will be displayed on users first issue' 16 | pr-message: 'Message that will be displayed on users first pull request' 17 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | # This workflow will triage pull requests and apply a label based on the 2 | # paths that are modified in the pull request. 3 | # 4 | # To use this workflow, you will need to set up a .github/labeler.yml 5 | # file with configuration. For more information, see: 6 | # https://github.com/actions/labeler 7 | 8 | name: Labeler 9 | on: [pull_request] 10 | 11 | jobs: 12 | label: 13 | 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | pull-requests: write 18 | 19 | steps: 20 | - uses: actions/labeler@v2 21 | with: 22 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 23 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.9 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.9 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install flake8 pytest 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | - name: Lint with flake8 29 | run: | 30 | # stop the build if there are Python syntax errors or undefined names 31 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 32 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 33 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 34 | - name: Test with pytest 35 | run: | 36 | pytest 37 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '17 6 * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v3 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | stale-issue-message: 'Stale issue message' 25 | stale-pr-message: 'Stale pull request message' 26 | stale-issue-label: 'no-issue-activity' 27 | stale-pr-label: 'no-pr-activity' 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # local batch files 132 | *.bat 133 | 134 | # testings 135 | tests/ 136 | 137 | # vscode nub 138 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robocord 2 | 3 | 4 | Just a bot created for [Pycord Server](https://discord.gg/pycord). 5 | 6 | ## Contribution 7 | 8 | 22 | 23 | Robocord has been moved and this repo is no longer supporting contributions. Please Open Pull Requests in [the new repo](https://github.com/Pycord-Development/Robocord), managed by BobDotCom instead. 24 | 25 | The reason Robocord was moved is because the Pycord Server Owner wanted to become its owner. The new Robocord is now a public bot and the contributers to this repo no longer manage it. 26 | 27 | ## Self Hosting 28 | 29 | I recommend you do not try to install the whole code and copy it, since certain things here and there 30 | are not really defined, for example, token. Some variables are stored in env files, so you might get a lot of 31 | undefined errors. You can copy the code of specific sections you want, but copying, say, the error handler will 32 | probably give you a lot of errors and not help you learn anything. 33 | 34 | 35 | ## Credits 36 | 37 | - [BruceDev#0001](https://discord.com/users/571638000661037056) 38 | - [Oliiiiii#0007](https://discord.com/users/761932885565374474) 39 | - [Marcus | Bot Dev#4438](https://discord.com/users/754557382708822137) 40 | 41 | ![Robocord](https://user-images.githubusercontent.com/86602706/132475732-a2e0cb82-ff20-4c2e-ad38-50f33a5c8ac9.png) 42 | -------------------------------------------------------------------------------- /cogs/Paginator.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from discord.ext import commands 4 | 5 | 6 | class Paginator(commands.Cog): 7 | def __init__(self, bot): 8 | self.bot = bot 9 | 10 | async def paginate(self, ctx, p): 11 | m = await ctx.send(embed=p[0]) 12 | c = p[0] 13 | a = ["⏪", "⬅️", "➡️", "⏩"] 14 | if not len(p) == 1: 15 | for i in a: 16 | await m.add_reaction(i) 17 | while True: 18 | try: 19 | reaction, user = await self.bot.wait_for( 20 | "reaction_add", 21 | check=lambda reaction, user: user == ctx.author 22 | and reaction.emoji in a, 23 | timeout=30.0, 24 | ) 25 | except asyncio.TimeoutError: 26 | for i in a[::-1]: 27 | await m.remove_reaction(i, member=self.bot.user) 28 | break 29 | else: 30 | if reaction.emoji == a[0]: 31 | l = p.index(c) 32 | if not l == 0: 33 | c = p[l - 1] 34 | await m.remove_reaction(a[0], member=ctx.author) 35 | await m.edit(embed=c) 36 | else: 37 | await m.remove_reaction(a[0], member=ctx.author) 38 | elif reaction.emoji == a[1]: 39 | l = p.index(c) 40 | if not l == len(p) - 1: 41 | c = p[l + 1] 42 | await m.remove_reaction(a[1], member=ctx.author) 43 | await m.edit(embed=c) 44 | else: 45 | await m.remove_reaction(a[1], member=ctx.author) 46 | elif reaction.emoji == a[2]: 47 | c = p[0] 48 | await m.remove_reaction(a[2], member=ctx.author) 49 | await m.edit(embed=c) 50 | elif reaction.emoji == a[3]: 51 | c = p[-1] 52 | await m.remove_reaction(a[3], member=ctx.author) 53 | await m.edit(embed=c) 54 | continue 55 | else: 56 | pass 57 | 58 | 59 | def setup(bot): 60 | bot.add_cog(Paginator(bot)) 61 | -------------------------------------------------------------------------------- /cogs/database.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | 3 | 4 | class Database(commands.Cog): 5 | def __init__(self, bot): 6 | self.bot = bot 7 | 8 | async def create_db(self): 9 | await self.bot.con.execute( 10 | "CREATE TABLE IF NOT EXISTS Tags (name TEXT,content TEXT ,author BIGINT ,aliases TEXT[])" 11 | ) 12 | 13 | async def new(self, n, v, i): 14 | exists = await self.bot.con.fetchrow("SELECT name FROM Tags WHERE name=$1", n) 15 | if exists: 16 | return "A tag with that name already exists" 17 | else: 18 | await self.bot.con.execute( 19 | "INSERT INTO Tags (name,content,author) VALUES($1,$2,$3)", 20 | n, 21 | v, 22 | i, 23 | ) 24 | return "Tag succesfully created" 25 | 26 | async def show(self, n): 27 | e = await self.bot.con.fetchrow("SELECT content FROM Tags WHERE name=$1", n) 28 | if not e: 29 | return "No such tag found" 30 | else: 31 | return e[0] 32 | 33 | async def remove(self, n, auth, allowed=False): 34 | e = await self.bot.con.fetchrow("SELECT content FROM Tags WHERE name=$1", n) 35 | if not e: 36 | return "No such tag found" 37 | else: 38 | authcheck = await self.bot.con.fetchrow( 39 | "SELECT author FROM Tags WHERE name=$1", n 40 | ) 41 | if authcheck[0] == auth or allowed: 42 | a = e[0] 43 | await self.bot.con.execute("DELETE FROM Tags WHERE name=$1", n) 44 | return "Succsfully Deleted" 45 | else: 46 | return "You dont own the tag" 47 | 48 | async def update(self, n, c, auth): 49 | e = await self.bot.con.fetchrow("SELECT content FROM Tags WHERE name=$1", n) 50 | if not e: 51 | return "No such tag found" 52 | else: 53 | authcheck = await self.bot.con.fetchrow( 54 | "SELECT author FROM Tags WHERE name=$1", n 55 | ) 56 | if authcheck[0] == auth: 57 | a = e[0] 58 | await self.bot.con.execute( 59 | "UPDATE Tags SET content=$1 WHERE name=$2", c, n 60 | ) 61 | return "Succesfully Edited" 62 | else: 63 | return "You dont own the tag" 64 | 65 | async def data(self, n): 66 | e = await self.bot.con.fetchrow("SELECT author FROM Tags WHERE name=$1", n) 67 | if e: 68 | return e[0] 69 | else: 70 | return "nothing found" 71 | 72 | async def transfer(self, n, auth, auth2): 73 | e = await self.bot.con.fetchrow("SELECT author FROM Tags WHERE name=$1", n) 74 | if not e: 75 | return "No such tags found" 76 | else: 77 | if e[0] == auth: 78 | await self.bot.con.execute( 79 | "UPDATE Tags SET author=$1 WHERE name=$2", auth2, n 80 | ) 81 | return "Succesfully transfered ownership of tag" 82 | else: 83 | return "You dont own the tag" 84 | 85 | async def mine(self, auth): 86 | e = await self.bot.con.fetch("SELECT name FROM Tags WHERE author=$1", auth) 87 | if not e: 88 | return "You have no tags" 89 | else: 90 | return e 91 | 92 | async def view_all(self): 93 | e = await self.bot.con.fetch("SELECT name FROM Tags") 94 | if not e: 95 | return "You have no tags" 96 | else: 97 | return e 98 | 99 | async def set_aliases(self, n, a, auth): 100 | e = await self.bot.con.fetchrow("SELECT content FROM Tags WHERE name=$1", n) 101 | if not e: 102 | return "Tag not found" 103 | else: 104 | e = e[0] 105 | await self.bot.con.execute( 106 | "INSERT INTO Tags (name,content,author) VALUES($1,$2,$3)", 107 | a, 108 | e, 109 | auth, 110 | ) 111 | return "SUCCESS" 112 | 113 | async def see_if_not(self, n): 114 | e = await self.bot.con.fetchrow("SELECT author FROM Tags WHERE name=$1", n) 115 | if not e: 116 | return "Tag not found" 117 | else: 118 | return e[0] 119 | 120 | async def transfer_not(self, n, auth): 121 | await self.bot.con.execute("UPDATE Tags SET author=$1 WHERE name=$2", auth, n) 122 | return "Succesfully Claimed" 123 | 124 | @commands.Cog.listener() 125 | async def on_ready(self): 126 | await self.create_db() 127 | print("DB is gud") 128 | 129 | 130 | def setup(bot): 131 | bot.add_cog(Database(bot)) 132 | -------------------------------------------------------------------------------- /cogs/rtfm.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | import discord 4 | import ext.rtfm_utils as rtfm 5 | from discord.ext import commands 6 | 7 | 8 | class RTFM(commands.Cog): 9 | """Commands related to searching documentations""" 10 | 11 | def __init__(self, bot): 12 | self.bot = bot 13 | self.targets = { 14 | "python": "https://docs.python.org/3", 15 | "pycord": "https://pycord.readthedocs.io/en/latest", 16 | "master": "https://pycord.readthedocs.io/en/master", 17 | } 18 | self.aliases = { 19 | ("py", "py3", "python3", "python"): "python", 20 | ("pycord", "pyc", "py-cord"): "pycord", 21 | ( 22 | "master", 23 | "pycord-master", 24 | "pyc-master", 25 | "py-cord-master", 26 | ): "master", 27 | # too many aliases? idk pls change this before using 28 | } 29 | self.cache = {} 30 | 31 | @property 32 | def session(self): 33 | return self.bot.session 34 | 35 | async def build(self, target) -> None: 36 | url = self.targets[target] 37 | req = await self.session.get(url + "/objects.inv") 38 | if req.status != 200: 39 | warnings.warn( 40 | Warning( 41 | f"Received response with status code {req.status} when trying to build RTFM cache for {target} through {url}/objects.inv" 42 | ) 43 | ) 44 | raise commands.CommandError("Failed to build RTFM cache") 45 | self.cache[target] = rtfm.SphinxObjectFileReader( 46 | await req.read() 47 | ).parse_object_inv(url) 48 | 49 | @commands.command() 50 | async def rtfm(self, ctx, docs: str, *, term: str = None): 51 | """ 52 | Returns the top 10 best matches of documentation links for searching the given term in the given docs 53 | Returns the entire documentation link if no term given 54 | """ 55 | docs = docs.lower() 56 | target = None 57 | for aliases, target_name in self.aliases.items(): 58 | if docs in aliases: 59 | target = target_name 60 | 61 | if not target: 62 | lis = "\n".join( 63 | [f"{index}. {value}" for index, value in list(self.targets.keys())] 64 | ) 65 | return await ctx.reply( 66 | embed=ctx.error( 67 | title="Invalid Documentation", 68 | description=f"Documentation {docs} is invalid. Must be one of \n{lis}", 69 | ) 70 | ) 71 | if not term: 72 | return await ctx.reply(self.targets[target]) 73 | 74 | cache = self.cache.get(target) 75 | if not cache: 76 | await ctx.trigger_typing() 77 | await self.build(target) 78 | cache = self.cache.get(target) 79 | 80 | results = rtfm.finder( 81 | term, list(cache.items()), key=lambda x: x[0], lazy=False 82 | )[:10] 83 | 84 | if not results: 85 | return await ctx.reply( 86 | f"No results found when searching for {term} in {docs}" 87 | ) 88 | 89 | await ctx.reply( 90 | embed=discord.Embed( 91 | title=f"Best matches for {term} in {docs}", 92 | description="\n".join([f"[`{key}`]({url})" for key, url in results]), 93 | color=discord.Color.dark_theme(), 94 | ) 95 | ) 96 | 97 | 98 | def setup(bot): 99 | bot.add_cog(RTFM(bot)) 100 | -------------------------------------------------------------------------------- /cogs/tags.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from cogs.database import Database 4 | from random import randint 5 | from cogs.paginator import Paginator 6 | 7 | 8 | class Tags(commands.Cog): 9 | def __init__(self, bot): 10 | self.bot = bot 11 | self.db = Database(self.bot) 12 | self.paginate = Paginator(self.bot) 13 | 14 | @commands.group(invoke_without_command=True) 15 | async def tag(self, ctx, name=None): 16 | if not name: 17 | return await ctx.reply("Name is a required argument") 18 | a = await self.db.show(name) 19 | await ctx.reply(a) 20 | 21 | @tag.command() 22 | async def create(self, ctx, name=None, *, value=None): 23 | if not name: 24 | return await ctx.reply("Name is a required argument") 25 | if not value: 26 | return await ctx.reply("Value is a required argument") 27 | if not name.lower() in [ 28 | "create", 29 | "edit", 30 | "delete", 31 | "make", 32 | "add", 33 | "remove", 34 | "info", 35 | "transfer", 36 | "claim", 37 | "all", 38 | ]: 39 | b = await self.db.new(name, value, ctx.author.id) 40 | await ctx.reply(b) 41 | else: 42 | await ctx.reply("That name is reserved") 43 | 44 | @tag.command() 45 | async def delete(self, ctx, name=None): 46 | if not name: 47 | return await ctx.reply("Name is a required argument") 48 | f = ctx.author.guild_permissions.manage_messages 49 | b = await self.db.remove(name, ctx.author.id, f) 50 | await ctx.reply(b) 51 | 52 | @tag.command() 53 | async def edit(self, ctx, name=None, *, value=None): 54 | if not name: 55 | return await ctx.reply("Name is a required argument") 56 | if not value: 57 | return await ctx.reply("Value is a required argument") 58 | b = await self.db.update(name, value, ctx.author.id) 59 | await ctx.reply(b) 60 | 61 | @tag.command() 62 | async def info(self, ctx, name=None): 63 | if not name: 64 | return await ctx.reply("Name is a required argument") 65 | b = await self.db.data(name) 66 | embed = discord.Embed(title=name, color=randint(0, 0xFFFFFF)) 67 | if not b == "nothing found": 68 | embed.add_field(name="Author", value=f"<@{b}>") 69 | await ctx.reply(embed=embed) 70 | return 71 | else: 72 | await ctx.send("Nothing Found") 73 | 74 | @tag.command() 75 | async def transfer(self, ctx, name=None, member: discord.Member = None): 76 | if not name: 77 | return await ctx.reply("Name is a required argument") 78 | if not member: 79 | return await ctx.reply("Member is a required argument") 80 | b = await self.db.transfer(name, ctx.author.id, member.id) 81 | await ctx.reply(b) 82 | 83 | @tag.command() 84 | async def alias(self, ctx, n, a): 85 | a = await self.db.set_aliases(n, a, ctx.author.id) 86 | await ctx.send(a) 87 | 88 | @tag.command() 89 | async def claim(self, ctx, n): 90 | listx = ctx.guild.members 91 | m = [] 92 | for i in range(len(listx)): 93 | m.append(listx[i]) 94 | s = await self.db.see_if_not(n) 95 | s = ctx.guild.get_member(s) 96 | if s in m: 97 | await ctx.reply("The tag owner is still in the server") 98 | else: 99 | b = await self.db.transfer_not(n, ctx.author.id) 100 | await ctx.reply(b) 101 | 102 | @tag.command() 103 | async def all(self, ctx): 104 | b = await self.db.view_all() 105 | if b == "Server has no tags": 106 | return 107 | c = [i[0] for i in b] 108 | embeds = [] 109 | a = 1 110 | d = "" 111 | while c: 112 | try: 113 | g = c[0:10] 114 | for i in g: 115 | d += f"{a}) {i} \n" 116 | a += 1 117 | embed = discord.Embed( 118 | title=f"{ctx.guild.name}'s tags", 119 | description=d, 120 | color=randint(0, 0xFFFFFF), 121 | ) 122 | embeds.append(embed) 123 | d = "" 124 | del c[0:10] 125 | except IndexError: 126 | g = c[0:-1] 127 | for i in g: 128 | d += f"{a}) {i} \n" 129 | embed = discord.Embed( 130 | title=f"{ctx.guild.name}'s tags", 131 | description=d, 132 | color=randint(0, 0xFFFFFF), 133 | ) 134 | embeds.append(embed) 135 | d = "" 136 | del c[0:-1] 137 | await self.paginate.paginate(ctx, embeds) 138 | 139 | @commands.command() 140 | async def tags(self, ctx, m: discord.Member = None): 141 | if m == None: 142 | m = ctx.author 143 | b = await self.db.mine(m.id) 144 | if b == "You have no tags": 145 | return 146 | c = [i[0] for i in b] 147 | embeds = [] 148 | a = 1 149 | d = "" 150 | while c: 151 | try: 152 | g = c[0:10] 153 | for i in g: 154 | d += f"{a}) {i} \n" 155 | a += 1 156 | embed = discord.Embed( 157 | title=f"{m.name}'s tags", 158 | description=d, 159 | color=randint(0, 0xFFFFFF), 160 | ) 161 | embeds.append(embed) 162 | d = "" 163 | del c[0:10] 164 | except IndexError: 165 | g = c[0:-1] 166 | for i in g: 167 | d += f"{a}) {i} \n" 168 | embed = discord.Embed( 169 | title=f"{m.name}'s tags", 170 | description=d, 171 | color=randint(0, 0xFFFFFF), 172 | ) 173 | embeds.append(embed) 174 | d = "" 175 | del c[0:-1] 176 | await self.paginate.paginate(ctx, embeds) 177 | 178 | 179 | def setup(bot): 180 | bot.add_cog(Tags(bot)) 181 | -------------------------------------------------------------------------------- /ext/helpers.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import datetime 3 | import json 4 | import random 5 | import sys 6 | import traceback 7 | 8 | import discord 9 | from discord.ext import commands 10 | 11 | 12 | class Bot(commands.Bot): 13 | async def logout(self, *args, **kwargs): 14 | if hasattr(self.pools, "config"): 15 | try: 16 | await asyncio.wait_for(self.pools.config.close(), timeout=5) 17 | except Exception as e: 18 | print(e) 19 | if hasattr(self, "sr_api"): 20 | try: 21 | await asyncio.wait_for(self.sr_api.close(), timeout=5) 22 | except Exception as e: 23 | print(e) 24 | if hasattr(self, "wavelink"): 25 | if not self.wavelink.session.closed: 26 | await asyncio.wait_for(self.wavelink.session.close(), timeout=5) 27 | if hasattr(self, "session"): 28 | if not self.session.closed: 29 | await self.session.close() 30 | await super().logout(*args, **kwargs) 31 | 32 | 33 | class Embed(discord.Embed): 34 | def __repr__(self): 35 | return self.description or "" 36 | 37 | 38 | class Context(commands.Context): 39 | 40 | Embed = Embed 41 | 42 | def embed(self, description=None, *args, **kwargs): 43 | discord_colors = { 44 | "Blurple": 0x5865F2, 45 | "Green": 0x57F287, 46 | "Yellow": 0xFEE75C, 47 | "Fuchsia": 0xEB459E, 48 | "Red": 0xED4245, 49 | "White": 0xFFFFFE, 50 | "Black": 0x23272A, 51 | } 52 | default = { 53 | "timestamp": self.message.created_at, 54 | "description": description, 55 | "color": random.choice([discord_colors[color] for color in discord_colors]), 56 | } 57 | default.update(kwargs) 58 | return_embed = self.Embed(*args, **default) 59 | return_embed.set_footer( 60 | icon_url=self.author.avatar_url, text=f"Requested by {self.author}" 61 | ) 62 | return return_embed 63 | 64 | def error(self, description=None, *args, **kwargs): 65 | default = { 66 | "title": "Error", 67 | "color": discord.Color.red(), 68 | "timestamp": self.message.created_at, 69 | "description": description, 70 | } 71 | default.update(kwargs) 72 | return_embed = self.Embed(*args, **default) 73 | return_embed.set_author(name=self.author, icon_url=self.author.avatar_url) 74 | return_embed.set_footer( 75 | icon_url=self.bot.user.avatar_url, 76 | text=( 77 | "If you think this is a mistake please contact " 78 | f"{self.bot.get_user(self.bot.owner_ids[0])}" 79 | ), 80 | ) 81 | return return_embed 82 | 83 | def success(self, description=None, *args, **kwargs): 84 | default = { 85 | "title": "Success", 86 | "color": discord.Color.green(), 87 | "timestamp": self.message.created_at, 88 | "description": description, 89 | } 90 | default.update(kwargs) 91 | return_embed = self.Embed(*args, **default) 92 | return_embed.set_author(name=self.author, icon_url=self.author.avatar_url) 93 | return_embed.set_footer( 94 | icon_url=self.bot.user.avatar_url, text="Action successful" 95 | ) 96 | return return_embed 97 | 98 | async def send_embed(self, *args, **kwargs): 99 | await self.send(embed=self.embed(*args, **kwargs)) 100 | 101 | async def send_error(self, *args, **kwargs): 102 | await self.send(embed=self.error(*args, **kwargs)) 103 | 104 | async def send_success(self, *args, **kwargs): 105 | await self.send(embed=self.success(*args, **kwargs)) 106 | 107 | 108 | async def init_connection(connection): 109 | await connection.set_type_codec( 110 | "json", encoder=json.dumps, decoder=json.loads, schema="pg_catalog" 111 | ) 112 | 113 | 114 | def storage(bot, key=None, value=None, method=None, override=False): 115 | try: 116 | with open("./storage/config.json", "r") as f: 117 | data = json.load(f) 118 | except OSError: 119 | with open("./storage/config.json", "w+") as f: 120 | f.write("{}") 121 | with open("./storage/config.json", "r") as f: 122 | data = json.load(f) 123 | data["cogs"] = data.get("cogs", []) 124 | data["blacklisted"] = data.get("blacklisted", []) 125 | data["disabled"] = data.get("disabled", False) 126 | if bot.default_owner: 127 | temp_owner = int(bot.default_owner) 128 | else: 129 | temp_owner = 571638000661037056 130 | data["owners"] = data.get("owners", [temp_owner]) 131 | bot.restart_channel = data.get("restart_channel", None) 132 | data["restart_channel"] = None 133 | if key and value is not None: 134 | if method == "append": 135 | if value not in data[key] or override: 136 | data[key].append(value) 137 | elif method == "remove": 138 | if value in data[key] or override: 139 | data[key].remove(value) 140 | else: 141 | data[key] = value 142 | with open("./storage/config.json", "w") as f: 143 | json.dump(data, f, indent=4) 144 | return data 145 | 146 | 147 | async def log_command_error(ctx, error, handled): 148 | if not handled: 149 | channel = ctx.bot.get_channel(875972719143956500) 150 | else: 151 | channel = ctx.bot.get_channel(875973025424621598) 152 | title = "Ignoring exception in command {}:".format(ctx.command) 153 | err = "".join(traceback.format_exception(type(error), error, error.__traceback__)) 154 | try: 155 | embed = discord.Embed( 156 | title=title, 157 | description=f"```py\n{err}```", 158 | timestamp=ctx.message.created_at, 159 | color=discord.Color.red(), 160 | ) 161 | embed.set_author(name=ctx.author, icon_url=ctx.author.avatar_url) 162 | await channel.send(embed=embed) 163 | except discord.errors.Forbidden: 164 | try: 165 | await channel.send( 166 | ( 167 | f"**<@{ctx.bot.owner_ids[0]}> An error " 168 | "occurred but I couldn't log it here**" 169 | ) 170 | ) 171 | except discord.errors.Forbidden: 172 | pass 173 | print( 174 | "Ignoring exception in command {}:".format(ctx.command), 175 | file=sys.stderr, 176 | ) 177 | traceback.print_exception( 178 | type(error), error, error.__traceback__, file=sys.stderr 179 | ) 180 | finally: 181 | return 182 | 183 | 184 | async def log_error(bot, event_method, *args, **kwargs): 185 | channel = bot.get_channel(875972719143956500) 186 | try: 187 | title = "Ignoring exception in {}".format(event_method) 188 | err = "".join(traceback.format_exc()) 189 | embed = discord.Embed( 190 | title=title, 191 | description=f"```py\n{err}```", 192 | timestamp=datetime.datetime.utcnow(), 193 | color=discord.Color.red(), 194 | ) 195 | await channel.send(embed=embed) 196 | except discord.errors.Forbidden: 197 | print("Ignoring exception in {}".format(event_method), file=sys.stderr) 198 | traceback.print_exc() 199 | -------------------------------------------------------------------------------- /ext/paginator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Directly copied and modified from https://github.com/FrostiiWeeb/discord-ext-paginator/ 3 | """ 4 | 5 | from contextlib import suppress 6 | from typing import List, Union 7 | 8 | import discord 9 | 10 | 11 | class Paginator: 12 | __slots__ = ( 13 | "_pages", 14 | "index", 15 | "current", 16 | "timeout", 17 | "ctx", 18 | "message", 19 | "compact", 20 | "_buttons", 21 | ) 22 | 23 | def __init__( 24 | self, 25 | *, 26 | entries: Union[List[discord.Embed], discord.Embed] = None, 27 | timeout: float = 90.0, 28 | ): 29 | 30 | self._pages = entries 31 | self.index = 0 32 | self.current = 1 33 | self.timeout = timeout 34 | self.ctx = None 35 | self.compact: bool = False 36 | self.message = None 37 | if len(self._pages) == 2: 38 | self.compact = True 39 | 40 | self._buttons = { 41 | "⏪": "stop", 42 | "◀️": "plus", 43 | "▶️": "last", 44 | "⏩": "first", 45 | "⏹️": "minus", 46 | } 47 | 48 | if self.compact is True: 49 | keys = ("⏩", "⏪") 50 | for key in keys: 51 | del self._buttons[key] 52 | 53 | async def start(self, ctx): 54 | """ 55 | Start the paginator. 56 | """ 57 | self.ctx = ctx 58 | 59 | await self._paginate() 60 | 61 | async def _paginate(self): 62 | """ 63 | Start the pagination session. 64 | """ 65 | with suppress(discord.HTTPException, discord.Forbidden, IndexError): 66 | self.message = await self.ctx.send(embed=self._pages[0]) 67 | for b in self._buttons: 68 | await self.message.add_reaction(b) 69 | 70 | def check(reaction, user): 71 | return str(reaction.emoji) in self._buttons and user == self.ctx.author 72 | 73 | while True: 74 | try: 75 | reaction, user = await self.ctx.bot.wait_for( 76 | "reaction_add", check=check, timeout=self.timeout 77 | ) 78 | if str(reaction.emoji) == "⏹️": 79 | await self.message.delete() 80 | break 81 | if str(reaction.emoji) == "▶️" and self.current != len(self._pages): 82 | self.current += 1 83 | await self.message.edit(embed=self._pages[self.current - 1]) 84 | if str(reaction.emoji) == "◀️" and self.current > 1: 85 | self.current -= 1 86 | await self.message.edit(embed=self._pages[self.current - 1]) 87 | if str(reaction.emoji) == "⏩": 88 | self.current = len(self._pages) 89 | await self.message.edit(embed=self._pages[self.current - 1]) 90 | if str(reaction.emoji) == "⏪": 91 | self.current = 1 92 | await self.message.edit(embed=self._pages[self.current - 1]) 93 | 94 | except Exception as e: 95 | print(e) # just for additional info, nothing else 96 | with suppress(discord.Forbidden, discord.HTTPException): 97 | for b in self._buttons: 98 | await self.message.remove_reaction(b, self.ctx.bot.user) 99 | -------------------------------------------------------------------------------- /ext/rtfm_utils.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | import zlib 5 | 6 | # Directly taken from Techstruck/Techstruck-Bot 7 | # https://github.com/TechStruck/TechStruck-Bot/blob/main/bot/utils/rtfm.py 8 | 9 | 10 | class SphinxObjectFileReader: 11 | # Inspired by Sphinx's InventoryFileReader 12 | BUFSIZE = 16 * 1024 13 | 14 | def __init__(self, buffer): 15 | self.stream = io.BytesIO(buffer) 16 | 17 | def readline(self): 18 | return self.stream.readline().decode("utf-8") 19 | 20 | def skipline(self): 21 | self.stream.readline() 22 | 23 | def read_compressed_chunks(self): 24 | decompressor = zlib.decompressobj() 25 | while True: 26 | chunk = self.stream.read(self.BUFSIZE) 27 | if len(chunk) == 0: 28 | break 29 | yield decompressor.decompress(chunk) 30 | yield decompressor.flush() 31 | 32 | def read_compressed_lines(self): 33 | buf = b"" 34 | for chunk in self.read_compressed_chunks(): 35 | buf += chunk 36 | pos = buf.find(b"\n") 37 | while pos != -1: 38 | yield buf[:pos].decode("utf-8") 39 | buf = buf[pos + 1 :] 40 | pos = buf.find(b"\n") 41 | 42 | def parse_object_inv(self, url): 43 | # key: URL 44 | # n.b.: key doesn't have `discord` or `discord.ext.commands` namespaces 45 | result = {} 46 | 47 | # first line is version info 48 | inv_version = self.readline().rstrip() 49 | 50 | if inv_version != "# Sphinx inventory version 2": 51 | raise RuntimeError("Invalid objects.inv file version.") 52 | 53 | # next line is "# Project: " 54 | # then after that is "# Version: " 55 | projname = self.readline().rstrip()[11:] 56 | version = self.readline().rstrip()[11:] 57 | 58 | # next line says if it's a zlib header 59 | line = self.readline() 60 | if "zlib" not in line: 61 | raise RuntimeError("Invalid objects.inv file, not z-lib compatible.") 62 | 63 | # This code mostly comes from the Sphinx repository. 64 | entry_regex = re.compile(r"(?x)(.+?)\s+(\S*:\S*)\s+(-?\d+)\s+(\S+)\s+(.*)") 65 | for line in self.read_compressed_lines(): 66 | match = entry_regex.match(line.rstrip()) 67 | if not match: 68 | continue 69 | 70 | name, directive, prio, location, dispname = match.groups() 71 | domain, _, subdirective = directive.partition(":") 72 | if directive == "py:module" and name in result: 73 | # From the Sphinx Repository: 74 | # due to a bug in 1.1 and below, 75 | # two inventory entries are created 76 | # for Python modules, and the first 77 | # one is correct 78 | continue 79 | 80 | # Most documentation pages have a label 81 | if directive == "std:doc": 82 | subdirective = "label" 83 | 84 | if location.endswith("$"): 85 | location = location[:-1] + name 86 | 87 | key = name if dispname == "-" else dispname 88 | prefix = f"{subdirective}:" if domain == "std" else "" 89 | 90 | if projname in [ 91 | "discord.py", 92 | "pycord", 93 | ]: # this changes to pycord in the master branch docs 94 | key = key.replace("discord.ext.commands.", "").replace("discord.", "") 95 | 96 | result[f"{prefix}{key}"] = os.path.join(url, location) 97 | 98 | return result 99 | 100 | 101 | def finder(text: str, collection: list, *, key=None, lazy=True): 102 | suggestions = [] 103 | text = str(text) 104 | pat = ".*?".join(map(re.escape, text)) 105 | regex = re.compile(pat, flags=re.IGNORECASE) 106 | for item in collection: 107 | to_search = key(item) if key else item 108 | r = regex.search(to_search) 109 | if r: 110 | suggestions.append((len(r.group()), r.start(), item)) 111 | 112 | def sort_key(tup): 113 | if key: 114 | return tup[0], tup[1], key(tup[2]) 115 | return tup 116 | 117 | if lazy: 118 | return (z for _, _, z in sorted(suggestions, key=sort_key)) 119 | else: 120 | return [z for _, _, z in sorted(suggestions, key=sort_key)] 121 | -------------------------------------------------------------------------------- /exten.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def get_extensions(): 5 | extensions = [] 6 | extensions.append("jishaku") 7 | for file in Path("cogs").glob("**/*.py"): 8 | if "!" in file.name or "DEV" in file.name: 9 | continue 10 | extensions.append(str(file).replace("/", ".").replace(".py", "")) 11 | return extensions 12 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord.app import Option 4 | import datetime 5 | import asyncio, aiohttp 6 | from dotenv import load_dotenv 7 | import requests, humanize 8 | import math, os, time 9 | import ext.helpers as helpers 10 | from storage.bot_data import data 11 | 12 | # from discord.ui import Button # just commented it cos idk 13 | # import re # regex 14 | import sqlite3 15 | 16 | 17 | owner = "BruceDev#0001" 18 | guild_ids = [ 19 | 881207955029110855, 20 | 869782707226439720, 21 | ] # a list of guild ids for guild-specific slash commands. TCA, Robocord Testing respectively 22 | 23 | 24 | class HelpCommand(commands.HelpCommand): 25 | def get_ending_note(self): 26 | return "Use p!{0} [command] for more info on a command.".format( 27 | self.invoked_with 28 | ) 29 | 30 | def get_command_signature(self, command): 31 | parent = command.full_parent_name 32 | if len(command.aliases) > 0: 33 | aliases = "|".join(command.aliases) 34 | fmt = f"[{command.name}|{aliases}]" 35 | if parent: 36 | fmt = f"{parent}, {fmt}" 37 | alias = fmt 38 | else: 39 | alias = command.name if not parent else f"{parent} {command.name}" 40 | return f"{alias} {command.signature}" 41 | 42 | async def send_bot_help(self, mapping): 43 | embed = discord.Embed(title="Robocord", color=discord.Color.blurple()) 44 | description = self.context.bot.description 45 | if description: 46 | embed.description = description 47 | 48 | for cog_, cmds in mapping.items(): 49 | name = "Other Commands" if cog_ is None else cog_.qualified_name 50 | filtered = await self.filter_commands(cmds, sort=True) 51 | if filtered: 52 | value = "\u002C ".join(f"`{c.name}`" for c in cmds) 53 | if cog_ and cog_.description: 54 | value = "{0}\n{1}".format(cog_.description, value) 55 | 56 | embed.add_field(name=name, value=value, inline=False) 57 | 58 | embed.set_footer(text=self.get_ending_note()) 59 | await self.get_destination().send(embed=embed) 60 | 61 | async def send_cog_help(self, cog_): 62 | embed = discord.Embed(title="{0.qualified_name} Commands".format(cog_)) 63 | if cog_.description: 64 | embed.description = cog_.description 65 | 66 | filtered = await self.filter_commands(cog_.get_commands(), sort=True) 67 | for command in filtered: 68 | embed.add_field( 69 | name=self.get_command_signature(command), 70 | value=command.short_doc or "...", 71 | inline=False, 72 | ) 73 | 74 | embed.set_footer(text=self.get_ending_note()) 75 | await self.get_destination().send(embed=embed) 76 | 77 | async def send_group_help(self, group): 78 | embed = discord.Embed(title=group.qualified_name) 79 | if group.help: 80 | embed.description = group.help 81 | 82 | if isinstance(group, commands.Group): 83 | filtered = await self.filter_commands(group.commands, sort=True) 84 | for command in filtered: 85 | embed.add_field( 86 | name=self.get_command_signature(command), 87 | value=command.short_doc or "...", 88 | inline=False, 89 | ) 90 | 91 | embed.set_footer(text=self.get_ending_note()) 92 | await self.get_destination().send(embed=embed) 93 | 94 | # This makes it so it uses the function above 95 | # Less work for us to do since they're both similar. 96 | # If you want to make regular command help look different then override it 97 | send_command_help = send_group_help 98 | 99 | 100 | bot = commands.Bot( 101 | command_prefix="p!", 102 | description="The bot build with and for pycord.", 103 | case_insensitive=True, 104 | embed_color=discord.Color.blurple(), 105 | help_command=HelpCommand(), 106 | activity=discord.Activity( 107 | type=discord.ActivityType.competing, name="What's dpy's Best Fork?" 108 | ), 109 | intents=discord.Intents.all(), 110 | status=discord.Status.online, 111 | ) 112 | 113 | bot.owner_ids = [ 114 | 571638000661037056, # BruceDev 115 | 761932885565374474, # Oliiiii 116 | # 685082846993317953, # Geno no 117 | 754557382708822137, # Marcus 118 | ] 119 | 120 | connection = sqlite3.connect("db.db") 121 | crsr = connection.cursor() 122 | os.environ["JISHAKU_NO_UNDERSCORE"] = "True" 123 | os.environ["JISHAKU_RETAIN"] = "True" 124 | 125 | 126 | bot.server_cache = {} 127 | bot.session = aiohttp.ClientSession() 128 | bot.default_prefixes = ["p!"] 129 | 130 | # async def prefix(bot_, message): 131 | # return commands.when_mentioned_or(*(await helpers.prefix(bot_, message)))( 132 | # bot_, message 133 | # ) 134 | # yall set the prefix manually to "p!" :bruh: 135 | 136 | 137 | @bot.event 138 | async def on_ready(): 139 | print("{} is Ready and Online!".format(bot.user)) 140 | print(f"Default Prefixes: {', '.join(bot.default_prefixes)}") 141 | 142 | 143 | @bot.event 144 | async def on_command_error(ctx, error: commands.CommandError): 145 | exception = error 146 | if hasattr(ctx.command, "on_error"): 147 | pass 148 | error = getattr(error, "original", error) 149 | 150 | if ctx.author.id in ctx.bot.owner_ids: 151 | if isinstance( 152 | error, 153 | ( 154 | commands.MissingAnyRole, 155 | commands.CheckFailure, 156 | commands.DisabledCommand, 157 | commands.CommandOnCooldown, 158 | commands.MissingPermissions, 159 | commands.MaxConcurrencyReached, 160 | ), 161 | ): 162 | try: 163 | await ctx.reinvoke() 164 | except discord.ext.commands.CommandError as e: 165 | pass 166 | else: 167 | return 168 | 169 | if isinstance( 170 | error, 171 | ( 172 | commands.BadArgument, 173 | commands.MissingRequiredArgument, 174 | commands.NoPrivateMessage, 175 | commands.CheckFailure, 176 | commands.DisabledCommand, 177 | commands.CommandInvokeError, 178 | commands.TooManyArguments, 179 | commands.UserInputError, 180 | commands.NotOwner, 181 | commands.MissingPermissions, 182 | commands.BotMissingPermissions, 183 | commands.MaxConcurrencyReached, 184 | commands.CommandNotFound, 185 | ), 186 | ): 187 | await helpers.log_command_error(ctx, exception, True) 188 | if not isinstance(error, commands.CommandNotFound): 189 | embed = discord.Embed( 190 | title="Oops! Something went wrong...", 191 | description=f"Reason: {str(error)}", 192 | color=discord.Color.red(), 193 | ) 194 | embed.set_footer( 195 | icon_url="https://i.imgur.com/0K0awOi.png", 196 | text=f"If this keeps happening, please contact {owner}", 197 | ) 198 | await ctx.send(embed=embed) 199 | 200 | elif isinstance(error, commands.CommandOnCooldown): 201 | await helpers.log_command_error(ctx, exception, True) 202 | time2 = datetime.timedelta(seconds=math.ceil(error.retry_after)) 203 | error = f"You are on cooldown. Try again after {humanize.precisedelta(time2)}" 204 | embed = discord.Embed( 205 | title="Too soon!", description=error, color=discord.Color.red() 206 | ) 207 | embed.set_author(name=ctx.author, icon_url=ctx.author.avatar.url) 208 | embed.set_footer( 209 | icon_url="https://i.imgur.com/0K0awOi.png", 210 | text=f"If you think this is a mistake, please contact {owner}", 211 | ) 212 | await ctx.send(embed=embed) 213 | 214 | else: 215 | 216 | raise error 217 | # un reachable code below, not sure what to do 218 | embed = discord.Embed( 219 | title="Oh no!", 220 | description=( 221 | f"An error occurred. My developer has been notified of it, but if it continues to occur please DM {owner}" 222 | ), 223 | color=discord.Color.red(), 224 | ) 225 | await ctx.send(embed=embed) 226 | await helpers.log_command_error(ctx, exception, False) 227 | 228 | 229 | bot.launch_time = datetime.datetime.utcnow() 230 | 231 | 232 | @bot.command() 233 | async def ping(ctx): 234 | loading = "<:thinkCat:882473068881145899>" 235 | ws_ping = ( 236 | f"{(bot.latency * 1000):.2f}ms " 237 | f"({humanize.precisedelta(datetime.timedelta(seconds=bot.latency))})" 238 | ) 239 | embed = discord.Embed( 240 | title="PONG! :ping_pong:", 241 | description=( 242 | f"**{loading} Websocket:** {ws_ping}\n** :repeat: Round-Trip:** Calculating..." 243 | ), 244 | color=discord.Color.blurple(), 245 | ) 246 | start = time.perf_counter() 247 | message = await ctx.send(embed=embed) 248 | end = time.perf_counter() 249 | await asyncio.sleep(0.5) 250 | trip = end - start 251 | rt_ping = f"{(trip * 1000):.2f}ms ({humanize.precisedelta(datetime.timedelta(seconds=trip))})" 252 | embed.description = ( 253 | f"**{loading} Websocket:** {ws_ping}\n**" f":repeat: Round-Trip:** {rt_ping}." 254 | ) 255 | await message.edit(embed=embed) 256 | await asyncio.sleep(0.5) 257 | start = time.perf_counter() 258 | await message.edit(embed=embed) 259 | 260 | 261 | @bot.slash_command( 262 | guild_ids=guild_ids, 263 | description="Frequently Asked Questions about pycord", 264 | ) 265 | async def faq( 266 | ctx, 267 | question: Option( 268 | str, 269 | "Choose your question", 270 | choices=[ 271 | "How to create Slash Commands", 272 | "How to create Context Menu Commands", 273 | "How to create buttons", 274 | ], 275 | ), 276 | display: Option( 277 | str, 278 | "Should this message be private or displayed to everyone?", 279 | choices=["Ephemeral", "Displayed"], 280 | default="Ephemeral", 281 | required=False, 282 | ), 283 | ): 284 | isprivate = display == "Ephemeral" 285 | if question == "How to create Slash Commands": 286 | await ctx.send(f"{data['slash-commands']}", ephemeral=isprivate) 287 | elif question == "How to create Context Menu Commands": 288 | await ctx.send(f"{data['context-menu-commands']}", ephemeral=isprivate) 289 | elif question == "How to create buttons": 290 | await ctx.send(f"{data['buttons']}", ephemeral=isprivate) 291 | 292 | 293 | class Faq(discord.ui.View): 294 | @discord.ui.select( 295 | placeholder="What is your question?", 296 | min_values=1, 297 | max_values=1, 298 | options=[ 299 | discord.SelectOption(label="How to create Slash Commands"), 300 | discord.SelectOption(label="How to create Context Menu Commands"), 301 | discord.SelectOption(label="How to create Buttons"), 302 | discord.SelectOption(label="How to create Dropdowns"), 303 | ], 304 | ) 305 | async def select_callback(self, select, interaction): 306 | if select.values[0] == "How to create Buttons": 307 | await interaction.response.send_message( 308 | f"{data['buttons']}\nRequested by: {interaction.user}" 309 | ) 310 | elif select.values[0] == "How to create Slash Commands": 311 | await interaction.response.send_message( 312 | f"{data['slash-commands']}\nRequested by: {interaction.user}" 313 | ) 314 | elif select.values[0] == "How to create Context Menu Commands": 315 | await interaction.response.send_message( 316 | f"{data['context-menu-commands']}\nRequested by: {interaction.user}" 317 | ) 318 | elif select.values[0] == "How to create Dropdowns": 319 | await interaction.response.send_message( 320 | f"{data['dropdowns']}\nRequested by: {interaction.user}" 321 | ) 322 | 323 | 324 | @bot.command(name="faq") 325 | async def _faq(ctx): 326 | whitelisted_channels = [ 327 | 881309496385884180, 328 | 881309521610412082, 329 | 881405655704039504, 330 | ] # help-1, help-2, bot-commands 331 | if ctx.channel.id in whitelisted_channels: 332 | await ctx.send( 333 | content="Frequently Asked Questions - Select your question", 334 | view=Faq(timeout=30), 335 | ) 336 | else: 337 | await ctx.send( 338 | f"This command can only be used in help channels and the bot usage channels to prevent flooding." 339 | ) 340 | 341 | 342 | @bot.slash_command() 343 | async def issue(ctx, number: Option(int, "The issue number")): 344 | link = f"https://github.com/Pycord-Development/pycord/issues/{number}" 345 | response = requests.get(link) 346 | if response.status_code == 200: 347 | await ctx.send(f"{link}") 348 | else: 349 | await ctx.send( 350 | f"That issue doesn't seem to exist. If you think this is a mistake, contact {owner}." 351 | ) 352 | 353 | 354 | @bot.slash_command() 355 | async def pr(ctx, number: Option(int, "The pr number")): 356 | link = f"https://github.com/Pycord-Development/pycord/pull/{number}" 357 | response = requests.get(link) 358 | if response.status_code == 200: 359 | await ctx.send(f"{link}") 360 | else: 361 | await ctx.send( 362 | f"That pull request doesn't seem to exist in the repo. If you think this is a mistake, contact {owner}." 363 | ) 364 | 365 | 366 | @bot.user_command(name="Join Position") 367 | async def _joinpos(ctx, member: discord.Member): 368 | all_members = list(ctx.guild.members) 369 | all_members.sort(key=lambda m: m.joined_at) 370 | 371 | def ord(n): 372 | return str(n) + ( 373 | "th" 374 | if 4 <= n % 100 <= 20 375 | else {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th") 376 | ) 377 | 378 | embed = discord.Embed( 379 | title="Member info", 380 | description=f"{member.mention} was the {ord(all_members.index(member) + 1)} person to join", 381 | ) 382 | await ctx.send(embed=embed) 383 | 384 | 385 | MORSE_CODE_DICT = { 386 | "A": ".-", 387 | "B": "-...", 388 | "C": "-.-.", 389 | "D": "-..", 390 | "E": ".", 391 | "F": "..-.", 392 | "G": "--.", 393 | "H": "....", 394 | "I": "..", 395 | "J": ".---", 396 | "K": "-.-", 397 | "L": ".-..", 398 | "M": "--", 399 | "N": "-.", 400 | "O": "---", 401 | "P": ".--.", 402 | "Q": "--.-", 403 | "R": ".-.", 404 | "S": "...", 405 | "T": "-", 406 | "U": "..-", 407 | "V": "...-", 408 | "W": ".--", 409 | "X": "-..-", 410 | "Y": "-.--", 411 | "Z": "--..", 412 | "1": ".----", 413 | "2": "..---", 414 | "3": "...--", 415 | "4": "....-", 416 | "5": ".....", 417 | "6": "-....", 418 | "7": "--...", 419 | "8": "---..", 420 | "9": "----.", 421 | "0": "-----", 422 | ", ": "--..--", 423 | ".": ".-.-.-", 424 | "?": "..--..", 425 | "/": "-..-.", 426 | "-": "-....-", 427 | "(": "-.--.", 428 | ")": "-.--.-", 429 | "!": "-.-.--", 430 | ",": "--..--", 431 | } 432 | 433 | # we make a list of what to replace with what 434 | 435 | # Function to encrypt the string 436 | # according to the morse code chart 437 | def encrypt(message): 438 | cipher = "" 439 | for letter in message: 440 | if letter != " ": 441 | 442 | # Looks up the dictionary and adds the 443 | # correspponding morse code 444 | # along with a space to separate 445 | # morse codes for different characters 446 | cipher += MORSE_CODE_DICT[letter] + " " 447 | else: 448 | # 1 space indicates different characters 449 | # and 2 indicates different words 450 | cipher += " " 451 | 452 | return cipher 453 | 454 | 455 | # Function to decrypt the string 456 | # from morse to english 457 | def decrypt(message): 458 | 459 | # extra space added at the end to access the 460 | # last morse code 461 | message += " " 462 | 463 | decipher = "" 464 | citext = "" 465 | for letter in message: 466 | 467 | # checks for space 468 | if letter != " ": 469 | 470 | # counter to keep track of space 471 | i = 0 472 | 473 | # storing morse code of a single character 474 | citext += letter 475 | 476 | # in case of space 477 | else: 478 | # if i = 1 that indicates a new character 479 | i += 1 480 | 481 | # if i = 2 that indicates a new word 482 | if i == 2: 483 | 484 | # adding space to separate words 485 | decipher += " " 486 | else: 487 | 488 | # accessing the keys using their values (reverse of encryption) 489 | decipher += list(MORSE_CODE_DICT.keys())[ 490 | list(MORSE_CODE_DICT.values()).index(citext) 491 | ] 492 | citext = "" 493 | 494 | return decipher 495 | 496 | 497 | @bot.message_command(name="Encrypt to Morse") 498 | async def _tomorse(ctx, message: discord.message): 499 | result = encrypt(message.content.upper()) 500 | await ctx.send(result) 501 | 502 | 503 | @bot.message_command(name="Decrypt Morse") 504 | async def _frommorse(ctx, message: discord.message): 505 | result = decrypt(message.content) 506 | await ctx.send(result) 507 | 508 | 509 | @bot.message_command(name="Decrypt binary") 510 | async def _frombinary(ctx, message: discord.message): 511 | a_binary_string = message.content 512 | binary_values = a_binary_string.split() 513 | 514 | ascii_string = "" 515 | for binary_value in binary_values: 516 | an_integer = int(binary_value, 2) 517 | 518 | ascii_character = chr(an_integer) 519 | 520 | ascii_string += ascii_character 521 | 522 | await ctx.send(ascii_string, allowed_mentions=discord.AllowedMentions.none()) 523 | 524 | 525 | @bot.message_command(name="Encrypt to binary") 526 | async def _tobinary(ctx, message: discord.message): 527 | a_string = message.content 528 | a_byte_array = bytearray(a_string, "utf8") 529 | byte_list = [] 530 | 531 | for byte in a_byte_array: 532 | binary_representation = bin(byte) 533 | byte_list.append(binary_representation) 534 | 535 | await ctx.send(" ".join(byte_list)) 536 | 537 | 538 | # ------ 539 | # Commented because max commands reached 540 | # ------ 541 | 542 | # @bot.slash_command(name="Decrypt from hex", guild_ids=[869782707226439720, 881207955029110855]) 543 | # async def _fromhex(ctx, message:discord.message): 544 | # hex_string = message.content[2:] 545 | 546 | # bytes_object = bytes.fromhex(hex_string) 547 | 548 | 549 | # ascii_string = bytes_object.decode("ASCII") 550 | 551 | # await ctx.send(ascii_string) 552 | 553 | # @bot.message_command(name="Encrypt to hex", guild_ids=[869782707226439720, 881207955029110855]) 554 | # async def _tohex(ctx, message:discord.message): 555 | # hex_string = message.content 556 | # an_integer = int(hex_string, 16) 557 | # hex_value = hex(an_integer) 558 | # await ctx.send(hex_value) 559 | 560 | 561 | @bot.user_command(name="Avatar") 562 | async def _avatar(ctx, member: discord.Member): 563 | embed = discord.Embed( 564 | title=f"{member}'s avatar!", 565 | description=f"[Link]({member.avatar.url})", 566 | color=member.color, 567 | ) 568 | try: 569 | embed.set_image(url=member.avatar.url) 570 | except AttributeError: 571 | embed.set_image(url=member.display_avatar.url) 572 | await ctx.send(embed=embed) 573 | 574 | 575 | binary = bot.command_group("binary", "Set of tools for converting binary") 576 | 577 | 578 | @binary.command(name="encrypt") 579 | async def binary_encrypt( 580 | ctx, text: Option(str, "The string you want to convert to binary") 581 | ): 582 | a_string = text 583 | a_byte_array = bytearray(a_string, "utf8") 584 | byte_list = [] 585 | 586 | for byte in a_byte_array: 587 | binary_representation = bin(byte) 588 | byte_list.append(binary_representation[2:]) 589 | 590 | await ctx.send(" ".join(byte_list)) 591 | 592 | 593 | @binary.command(name="decrypt") 594 | async def binary_decrypt( 595 | ctx, text: Option(str, "The binary string you want to decrypt") 596 | ): 597 | a_binary_string = text 598 | binary_values = a_binary_string.split() 599 | 600 | ascii_string = "" 601 | for binary_value in binary_values: 602 | an_integer = int(binary_value, 2) 603 | 604 | ascii_character = chr(an_integer) 605 | 606 | ascii_string += ascii_character 607 | 608 | await ctx.send(ascii_string, allowed_mentions=discord.AllowedMentions.none()) 609 | 610 | 611 | for i in ["jishaku", "cogs.rtfm"]: 612 | bot.load_extension(i) 613 | load_dotenv() 614 | bot.run(os.getenv("TOKEN")) 615 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/Pycord-Development/pycord 2 | python-dotenv 3 | asyncpg 4 | asyncio 5 | aiohttp 6 | humanize 7 | requests 8 | jishaku 9 | -------------------------------------------------------------------------------- /storage/bot_data.py: -------------------------------------------------------------------------------- 1 | # this file contains a bunch of data to make the code cleaner and less spammy. Many variables, lists and dicts are dfined here. 2 | 3 | data = { 4 | "slash-commands": """**What are Slash Commands?** 5 | When you type `/`, you can see a list of commands a bot can use. These are called Slash Commands, and can be invoked with a Slash. 6 | 7 | **Can I use them with pycord?** 8 | Slash commands are not out in the stable version yet, but with the alpha and slash branch version of Py-cord, you can make user and message commands. Warning: Alpha and the slash branch can be unstable. 9 | To update to Slash Branch, use, 10 | ```bash 11 | pip install git+https://github.com/Pycord-Development/pycord@slash 12 | ``` 13 | :warning: Make sure you have git installed. Use `!git` to find out how to install it, or type `git` in the terminal to check if you already have it. 14 | 15 | **Examples:** 16 | ```py 17 | import discord 18 | from discord.app import Option 19 | 20 | bot = discord.Bot() # If you use commands.Bot, @bot.slash_command should be used for slash commands. You can use @bot.slash_command with discord.Bot as well 21 | 22 | @bot.command(guild_ids=[...]) 23 | async def hello( 24 | ctx, 25 | name: Option(str, "Enter your name"), 26 | gender: Option(str, "Choose your gender", choices=["Male", "Female", "Other"]), 27 | age: Option(int, "Enter your age", required=False, default=18), 28 | ): 29 | await ctx.send(f"Hello {name}") 30 | 31 | bot.run("TOKEN") 32 | ```""", 33 | "context-menu-commands": """**What are user commands and message commands?** 34 | When you right click a message, you may see a option called "Apps". Hover over it and you can see commands a bot can run with that message. These are called message commands. 35 | When you right click a message in the user list, you can once again see an option called "Apps". Hover over it and you can see commands a bot can run with that message. These are called user commands. 36 | 37 | **Can I use them with pycord?** 38 | With the alpha version of Py-cord, you can make user and message commands. Warning: Alpha can be unstable. 39 | To update to Alpha, use 40 | ```bash 41 | pip install -U git+https://github.com/Pycord-Development/pycord 42 | ``` 43 | :warning: Make sure you have git installed. Use `!git` to find out how to install it, or type `git` in the terminal to check if you already have it installed. 44 | 45 | **Examples**: 46 | ```py 47 | import discord 48 | 49 | bot = discord.Bot() # you can also use commands.Bot() 50 | 51 | @bot.user_command(guild_ids=[...]) # create a user command for the supplied guilds 52 | async def mention(ctx, member: discord.Member): # user commands return the member 53 | await ctx.respond(f"{ctx.author.name} just mentioned {member.mention}!") 54 | 55 | # user commands and message commands can have spaces in their names 56 | @bot.message_command(name="Show ID") # creates a global message command. use guild_ids=[] to create guild-specific commands. 57 | async def show_id(ctx, message: discord.Message): # message commands return the message 58 | await ctx.respond(f"{ctx.author.name}, here's the message id: {message.id}!") 59 | 60 | bot.run("TOKEN") 61 | ```""", 62 | } 63 | -------------------------------------------------------------------------------- /storage/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "cogs": [], 3 | "blacklisted": [], 4 | "disabled": false, 5 | "owners": [ 6 | 571638000661037056 7 | ], 8 | "restart_channel": null 9 | } 10 | -------------------------------------------------------------------------------- /storage/morse.py: -------------------------------------------------------------------------------- 1 | MORSE_CODE_DICT = { 2 | "A": ".-", 3 | "B": "-...", 4 | "C": "-.-.", 5 | "D": "-..", 6 | "E": ".", 7 | "F": "..-.", 8 | "G": "--.", 9 | "H": "....", 10 | "I": "..", 11 | "J": ".---", 12 | "K": "-.-", 13 | "L": ".-..", 14 | "M": "--", 15 | "N": "-.", 16 | "O": "---", 17 | "P": ".--.", 18 | "Q": "--.-", 19 | "R": ".-.", 20 | "S": "...", 21 | "T": "-", 22 | "U": "..-", 23 | "V": "...-", 24 | "W": ".--", 25 | "X": "-..-", 26 | "Y": "-.--", 27 | "Z": "--..", 28 | "1": ".----", 29 | "2": "..---", 30 | "3": "...--", 31 | "4": "....-", 32 | "5": ".....", 33 | "6": "-....", 34 | "7": "--...", 35 | "8": "---..", 36 | "9": "----.", 37 | "0": "-----", 38 | ", ": "--..--", 39 | ".": ".-.-.-", 40 | "?": "..--..", 41 | "/": "-..-.", 42 | "-": "-....-", 43 | "(": "-.--.", 44 | ")": "-.--.-", 45 | "!": "-.-.--", 46 | ",": "--..--", 47 | } 48 | --------------------------------------------------------------------------------