├── pyproject.toml ├── .flake8 ├── esolang ├── __init__.py ├── info.json ├── brainfuck.py ├── cow.py ├── esolang.py ├── whitespace.py └── befunge.py ├── authgg ├── __init__.py ├── info.json └── authgg.py ├── opensea ├── __init__.py └── info.json ├── cooldown ├── __init__.py ├── info.json └── cooldown.py ├── reacticket ├── __init__.py ├── info.json └── extensions │ ├── abc.py │ ├── views │ ├── alert.py │ ├── confirmation.py │ └── queue.py │ ├── mixin.py │ ├── usersettings.py │ └── closesettings.py ├── requirements.txt ├── Makefile ├── dashboard ├── __init__.py ├── abc │ ├── mixin.py │ ├── abc.py │ ├── webserver.py │ └── roles.py ├── rpc │ ├── webhooks.py │ ├── alias.py │ ├── utils.py │ ├── botsettings.py │ └── permissions.py ├── info.json ├── dashboard.py └── menus.py ├── simon ├── __init__.py ├── info.json └── simon.py ├── sw ├── __init__.py └── info.json ├── color ├── __init__.py ├── info.json └── color.py ├── editor ├── __init__.py ├── info.json └── editor.py ├── twenty ├── __init__.py └── info.json ├── grammar ├── __init__.py ├── info.json ├── converters.py └── grammar.py ├── scanner ├── __init__.py └── info.json ├── targeter ├── __init__.py └── info.json ├── minesweeper ├── __init__.py └── info.json ├── commandchart ├── __init__.py ├── info.json └── commandchart.py ├── deleter ├── __init__.py ├── info.json └── deleter.py ├── listpermissions ├── __init__.py └── info.json ├── updatechecker ├── __init__.py └── info.json ├── maintenance ├── __init__.py ├── info.json ├── utils.py ├── classes.py └── converters.py ├── info.json ├── .github └── workflows │ ├── matchers │ ├── check-toml.json │ ├── check-yaml.json │ ├── check-json.json │ └── flake8.json │ └── run_precommit.yml ├── evolution ├── info.json ├── __init__.py ├── tasks.py └── utils.py ├── make.bat ├── LICENSE ├── .pre-commit-config.yaml ├── .gitignore └── README.md /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 99 3 | required-version = '23' 4 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 99 3 | select = E9,F7,F82 4 | extend-exclude=.venv/,.stubs/ 5 | -------------------------------------------------------------------------------- /esolang/__init__.py: -------------------------------------------------------------------------------- 1 | from .esolang import Esolang 2 | 3 | 4 | def setup(bot): 5 | bot.add_cog(Esolang(bot)) 6 | -------------------------------------------------------------------------------- /authgg/__init__.py: -------------------------------------------------------------------------------- 1 | from .authgg import AuthGG 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(AuthGG(bot)) 6 | -------------------------------------------------------------------------------- /opensea/__init__.py: -------------------------------------------------------------------------------- 1 | from .opensea import OpenSea 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(OpenSea(bot)) 6 | -------------------------------------------------------------------------------- /cooldown/__init__.py: -------------------------------------------------------------------------------- 1 | from .cooldown import Cooldown 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(Cooldown(bot)) 6 | -------------------------------------------------------------------------------- /reacticket/__init__.py: -------------------------------------------------------------------------------- 1 | from .reacticket import ReacTicket 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(ReacTicket(bot)) 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.7.4 2 | async-timeout==3.0.0 3 | discord.py==1.0.0a0 4 | bs4 5 | matplotlib 6 | colour 7 | prettytable 8 | fuzzywuzzy 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON ?= python3.8 2 | 3 | # Python Code Style 4 | reformat: 5 | $(PYTHON) -m black . 6 | stylecheck: 7 | $(PYTHON) -m black --check . 8 | stylediff: 9 | $(PYTHON) -m black --check --diff . 10 | -------------------------------------------------------------------------------- /dashboard/__init__.py: -------------------------------------------------------------------------------- 1 | from redbot.core.bot import Red 2 | 3 | from .dashboard import Dashboard 4 | 5 | 6 | async def setup(bot: Red): 7 | cog = Dashboard(bot) 8 | bot.add_cog(cog) 9 | await cog.initialize() 10 | -------------------------------------------------------------------------------- /simon/__init__.py: -------------------------------------------------------------------------------- 1 | from .simon import Simon 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | def setup(bot): 9 | bot.add_cog(Simon(bot)) 10 | -------------------------------------------------------------------------------- /sw/__init__.py: -------------------------------------------------------------------------------- 1 | from .sw import SW 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(SW(bot)) 10 | -------------------------------------------------------------------------------- /color/__init__.py: -------------------------------------------------------------------------------- 1 | from .color import Color 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(Color(bot)) 10 | -------------------------------------------------------------------------------- /editor/__init__.py: -------------------------------------------------------------------------------- 1 | from .editor import Editor 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(Editor(bot)) 10 | -------------------------------------------------------------------------------- /twenty/__init__.py: -------------------------------------------------------------------------------- 1 | from .twenty import Twenty 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(Twenty(bot)) 10 | -------------------------------------------------------------------------------- /grammar/__init__.py: -------------------------------------------------------------------------------- 1 | from .grammar import Grammar 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(Grammar(bot)) 10 | -------------------------------------------------------------------------------- /scanner/__init__.py: -------------------------------------------------------------------------------- 1 | from .scanner import Scanner 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(Scanner(bot)) 10 | -------------------------------------------------------------------------------- /targeter/__init__.py: -------------------------------------------------------------------------------- 1 | from .targeter import Targeter 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(Targeter(bot)) 10 | -------------------------------------------------------------------------------- /minesweeper/__init__.py: -------------------------------------------------------------------------------- 1 | from .minesweeper import Minesweeper 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(Minesweeper(bot)) 10 | -------------------------------------------------------------------------------- /commandchart/__init__.py: -------------------------------------------------------------------------------- 1 | from .commandchart import CommandChart 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(CommandChart(bot)) 10 | -------------------------------------------------------------------------------- /deleter/__init__.py: -------------------------------------------------------------------------------- 1 | from .deleter import Deleter 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | cog = Deleter(bot) 10 | await bot.add_cog(cog) 11 | -------------------------------------------------------------------------------- /listpermissions/__init__.py: -------------------------------------------------------------------------------- 1 | from .listpermissions import ListPermissions 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | await bot.add_cog(ListPermissions(bot)) 10 | -------------------------------------------------------------------------------- /updatechecker/__init__.py: -------------------------------------------------------------------------------- 1 | from .updatechecker import UpdateChecker 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog does not persistently store data or metadata about users." 5 | ) 6 | 7 | 8 | async def setup(bot): 9 | cog = UpdateChecker(bot) 10 | await bot.add_cog(cog) 11 | -------------------------------------------------------------------------------- /dashboard/abc/mixin.py: -------------------------------------------------------------------------------- 1 | from redbot.core import commands 2 | 3 | 4 | @commands.group(name="dashboard") 5 | async def dashboard(self, ctx: commands.Context): 6 | """Group command for controlling the web dashboard for Red.""" 7 | pass 8 | 9 | 10 | class DBMixin: 11 | """This is mostly here to easily mess with things...""" 12 | 13 | c = dashboard 14 | -------------------------------------------------------------------------------- /grammar/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "grammer", 7 | "short": "Get words related to the specified arguments", 8 | "description": "Get words related to the specified arguments", 9 | "tags": [], 10 | "requirements": [], 11 | "hidden": false 12 | } 13 | -------------------------------------------------------------------------------- /opensea/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "opensea", 7 | "short": "Keep track of OpenSea asset events.", 8 | "description": "Keep track of specific wallets by wallet address or NFT contact address.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /sw/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "sw", 7 | "short": "Get information about a bunch of different things in Star Wars.", 8 | "description": "Get information about a bunch of different things in Star Wars.", 9 | "tags": [ 10 | "info", 11 | "fun" 12 | ], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /twenty/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "twenty", 7 | "short": "Allows you to play 2048 inside of Discord!", 8 | "description": "This cog allows a Discord User to play 2048 inside of a text channel.", 9 | "tags": [ 10 | "fun" 11 | ], 12 | "requirements": [], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /authgg/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "authgg", 7 | "short": "Control your users on auth.gg straight from Discord.", 8 | "description": "Unlock your users' HWID locks on auth.gg straight from your bot in Discord", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /maintenance/__init__.py: -------------------------------------------------------------------------------- 1 | from .maintenance import Maintenance 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog stores user's Discord IDs for operational data, in the form of " 5 | "whitelist to specify what users may interact with the cog/bot. This data " 6 | "is only deleted on Discord's user deletion requests." 7 | ) 8 | 9 | 10 | async def setup(bot): 11 | cog = Maintenance(bot) 12 | await bot.add_cog(cog) 13 | -------------------------------------------------------------------------------- /info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thanks for adding my repo. Join the support server for help:\nhttps://discord.gg/vQZTdB9", 6 | "name": "Toxic Cogs", 7 | "short": "Holds cogs made by Neuro Assassin.", 8 | "description": "This repo holds the several different cogs, for Red - Discord Bot by TwentySix.", 9 | "tags": [ 10 | "fun", 11 | "tools" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /dashboard/abc/abc.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from redbot.core import Config 4 | from redbot.core.bot import Red 5 | 6 | 7 | class MixinMeta(ABC): 8 | """Base class for well behaved type hint detection with composite class. 9 | Basically, to keep developers sane when not all attributes are defined in each mixin. 10 | """ 11 | 12 | def __init__(self, *_args): 13 | self.config: Config 14 | self.bot: Red 15 | -------------------------------------------------------------------------------- /.github/workflows/matchers/check-toml.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "check-toml", 5 | "pattern": [ 6 | { 7 | "regexp": "^(.+\\.toml):\\s(.+line\\s(\\d+)\\scolumn\\s(\\d+).+)$", 8 | "file": 1, 9 | "message": 2, 10 | "line": 3, 11 | "column": 4 12 | } 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /reacticket/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "reacticket", 7 | "short": "Create a reaction-based ticketing system.", 8 | "description": "Allow users to create ticket channels by clicking on a set reaction on a set message.", 9 | "tags": [ 10 | "tools", 11 | "utility" 12 | ], 13 | "requirements": [], 14 | "hidden": false 15 | } 16 | -------------------------------------------------------------------------------- /targeter/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "targeter", 7 | "short": "Targets users based upon the passed arguments", 8 | "description": "Returns a list of members in the current guild that meet the passed arguments. Commands can take long times based upon the amount of members in the guild.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /maintenance/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "maintenance", 7 | "short": "Allows the bot owner to put the bot on maintenance.", 8 | "description": "Allows the owner to put the bot on maintenance, and specify when the maintenance will be over (but does not have to). The bot owner can also whitelist users from the maintenance.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "hidden": false 13 | } 14 | -------------------------------------------------------------------------------- /editor/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "editor", 7 | "short": "Edit one of the bot's messages easily.", 8 | "description": "Allows an administrator to change the text of one of the bot's messages, by either specifying the new content or by taking the content (and embed, if applicable) of another message.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/matchers/check-yaml.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "check-yaml", 5 | "pattern": [ 6 | { 7 | "regexp": "^(.+)$", 8 | "message": 1 9 | }, 10 | { 11 | "regexp": "^ in \"(.+\\.ya?ml)\", line (\\d+), column (\\d+)$", 12 | "file": 1, 13 | "line": 2, 14 | "column": 3 15 | } 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /maintenance/utils.py: -------------------------------------------------------------------------------- 1 | from redbot.core.commands import BadArgument 2 | 3 | 4 | def convert_time(value): 5 | if not value: 6 | return None 7 | value[1] = value[1].lower() 8 | passing = int(value[0]) 9 | if value[1].startswith("second"): 10 | pass 11 | elif value[1].startswith("minute"): 12 | passing *= 60 13 | elif value[1].startswith("hour"): 14 | passing *= 3600 15 | elif value[1].startswith("day"): 16 | passing *= 86400 17 | else: 18 | raise BadArgument() 19 | return passing 20 | -------------------------------------------------------------------------------- /evolution/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog. This cog requires for the bank to be global in order to be used.", 6 | "name": "evolution", 7 | "short": "Buy and get animals to get more economy credits!", 8 | "description": "Buy animals using economy credits or get them every 10 minutes, and gain a certain amount of credits every minute!", 9 | "tags": [ 10 | "fun" 11 | ], 12 | "requirements": [ 13 | "tabulate" 14 | ], 15 | "hidden": false 16 | } 17 | -------------------------------------------------------------------------------- /commandchart/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "commandchart", 7 | "short": "Tells the used and usages of commands! Based off of aikaterna's chatchart cog.", 8 | "description": "This cog tells a Discord User the commands used, and the usage of them, in the last so and so messages. Based off of aikaterna's chatchart cog.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [ 13 | "matplotlib" 14 | ], 15 | "hidden": false 16 | } 17 | -------------------------------------------------------------------------------- /updatechecker/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "updatechecker", 7 | "short": "Notifies you when an update for a repo is available.", 8 | "description": "This cog will tell when there is an update available for a repository you have added for your bot, and, depending on settings, will auto update or will just notify you.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [ 13 | "feedparser" 14 | ], 15 | "hidden": false 16 | } 17 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if [%1] == [] goto help 4 | 5 | REM This allows us to expand variables at execution 6 | setlocal ENABLEDELAYEDEXPANSION 7 | 8 | goto %1 9 | 10 | :reformat 11 | black . 12 | exit /B %ERRORLEVEL% 13 | 14 | :stylecheck 15 | black --check . 16 | exit /B %ERRORLEVEL% 17 | 18 | :stylediff 19 | black --check --diff . 20 | exit /B %ERRORLEVEL% 21 | 22 | :help 23 | echo Usage: 24 | echo make ^ 25 | echo. 26 | echo Commands: 27 | echo reformat Reformat all .py files being tracked by git. 28 | echo stylecheck Check which tracked .py files need reformatting. 29 | -------------------------------------------------------------------------------- /esolang/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Due to the eval nature of this cog, this may cause issues in performance including blocking of bot. As a result, do not use in a production environment. Copyright holders and contributors hold no responsibility for damamges caused by usage of this code.", 6 | "name": "esolang", 7 | "short": "Run code in esoteric languages.", 8 | "description": "Run code in some of the more popular esoteric languages for fun.", 9 | "tags": [ 10 | "fun" 11 | ], 12 | "requirements": [], 13 | "hidden": true 14 | } 15 | -------------------------------------------------------------------------------- /scanner/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog. All settings are disabled by default. Run `[p]scanner settings`.", 6 | "name": "scanner", 7 | "short": "Detects images being sent and checks whether they are inappropriate based on the set filters.", 8 | "description": "This cog will send a report to a channel with details of an image violating the set conditions, and will auto delete the message if set to.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /dashboard/rpc/webhooks.py: -------------------------------------------------------------------------------- 1 | from redbot.core.bot import Red 2 | from redbot.core.commands import commands 3 | 4 | from .utils import rpccheck 5 | 6 | 7 | class DashboardRPC_Webhooks: 8 | def __init__(self, cog: commands.Cog): 9 | self.bot: Red = cog.bot 10 | self.cog: commands.Cog = cog 11 | 12 | self.bot.register_rpc_handler(self.webhook_receive) 13 | 14 | def unload(self): 15 | self.bot.unregister_rpc_handler(self.webhook_receive) 16 | 17 | @rpccheck() 18 | async def webhook_receive(self, payload: dict) -> dict: 19 | self.bot.dispatch("webhook_receive", payload) 20 | return {"status": 1} 21 | -------------------------------------------------------------------------------- /.github/workflows/matchers/check-json.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "Credits to: https://github.com/home-assistant/core/blob/d32c364d7f9e138e0dd9363b34b3cb39f4afcd06/.github/workflows/matchers/check-json.json", 3 | "problemMatcher": [ 4 | { 5 | "owner": "check-json", 6 | "pattern": [ 7 | { 8 | "regexp": "^(.+):\\s(Failed to json decode\\s.+\\sline\\s(\\d+)\\scolumn\\s(\\d+).+)$", 9 | "file": 1, 10 | "message": 2, 11 | "line": 3, 12 | "column": 4 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /deleter/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "deleter", 7 | "short": "Allows for the auto-deletion of messages in a channel after a specified wait time.", 8 | "description": "Allows moderators to make messages be auto-deleted after a certain amount of time in a certain channel. WARNING: This cog has potential API abuse AND SHOULD BE USED CAREFULLY! If you see any issues arise due to this, please report to Neuro Assassin or bot owner ASAP!", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /color/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog. In-message shortcuts are disabled by guild by default. An administrator can change this rule by running `[p]color msgshort true`", 6 | "name": "color", 7 | "short": "Tells you the hexadecimal values, rgb values and names of colors", 8 | "description": "This cog will tell you the hexadecimal value, rgb value and the name of the color that is supplied to it.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [ 13 | "colour", 14 | "pillow" 15 | ], 16 | "hidden": false 17 | } 18 | -------------------------------------------------------------------------------- /cooldown/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.\n\nWARNING: Some cooldowns are meant to be in place, meaning that they should not be removed. Any contributors to this cog are not at fault if it is used improperly, and is instead at the fault of the person running the command. By installing this cog, you agree to these terms. If you do not agree, uninstall this cog right now.", 6 | "name": "cooldown", 7 | "short": "Allows bot owner to set cooldowns for commands", 8 | "description": "Allows a bot owner to set cooldowns for commands", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "hidden": false 13 | } 14 | -------------------------------------------------------------------------------- /evolution/__init__.py: -------------------------------------------------------------------------------- 1 | from . import bank 2 | 3 | from .evolution import Evolution 4 | 5 | __red_end_user_data_statement__ = ( 6 | "This cog stores user's Discord IDs for the sake of storing game data. " 7 | "Users may delete their own data at the cost of losing game data through " 8 | "a data request, if the bot is configured to lose data at the cost of " 9 | "functionality. Alternatively, there is a in-cog command to delete user " 10 | "data as well." 11 | ) 12 | 13 | 14 | async def setup(bot): 15 | bank._init(bot) 16 | is_global = await bank.is_global() 17 | if not is_global: 18 | raise RuntimeError("Bank must be global for this cog to work.") 19 | cog = Evolution(bot) 20 | await bot.add_cog(cog) 21 | await cog.utils.initialize() 22 | -------------------------------------------------------------------------------- /listpermissions/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.", 6 | "name": "listpermissions", 7 | "short": "Gives you the permissions of a role/member guild wide or in a channel", 8 | "description": "This cog will allow you to see all permissions, available permissions or denied permissions or a role or member across the guild or in a channel. Do note however that the `[p](groupcommand) channel role` only provides basic permissions, and will only tell what is different for the channel compared to the guild permissions for a role.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [ 13 | "prettytable", 14 | "fuzzywuzzy" 15 | ], 16 | "hidden": false 17 | } 18 | -------------------------------------------------------------------------------- /minesweeper/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.\nSmall warning: this cog can sometimes you up rate limits (depending how fast you play), so be careful while playing.", 6 | "name": "minesweeper", 7 | "short": "Allows you to play Minesweeper inside of Discord!\nSmall warning: this cog can sometimes you up rate limits (depending how fast you play), so be careful while playing.", 8 | "description": "This cog allows a Discord User to play the game Minesweeper inside of a text channel.\nSmall warning: this cog can sometimes you up rate limits (depending how fast you play), so be careful while playing.", 9 | "tags": [ 10 | "fun" 11 | ], 12 | "requirements": [], 13 | "hidden": false 14 | } 15 | -------------------------------------------------------------------------------- /maintenance/classes.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class ScheduledMaintenance: 5 | def __init__(self, start=0, end=0, after=True, whitelist=None): 6 | if not whitelist: 7 | whitelist = [] 8 | self.start = start + time.time() 9 | if end: 10 | if after: 11 | self.end = end + self.start 12 | else: 13 | self.end = time.time() + end 14 | else: 15 | self.end = 0 16 | self.whitelist = whitelist 17 | 18 | def to_dict(self): 19 | return self.__dict__ 20 | 21 | def to_conf(self): 22 | active = (self.start == 0) or (time.time() >= self.start) 23 | return [active, self.end, self.whitelist] 24 | 25 | def to_scheduled(self): 26 | return [self.start, self.end, self.whitelist] 27 | -------------------------------------------------------------------------------- /simon/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog.\n\n***WARNING***: This cog relies heavily on sending messages, editing emojis and messages and deleting messages. It may use up the bot's rate limits while in use.", 6 | "name": "simon", 7 | "short": "Allows you to play Simon inside of Discord!\nWarning! This cog uses a major portion of the bot's rate limits.", 8 | "description": "This cog allows a Discord User to play the game Simon inside of a text channel.\nWarning! This cog uses a major portion of the bot's rate limits. This may prevent the bot from sending messages, adding emojis or deleting messages in specific peroids of time.", 9 | "tags": [ 10 | "fun" 11 | ], 12 | "requirements": [], 13 | "hidden": true 14 | } 15 | -------------------------------------------------------------------------------- /dashboard/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "Neuro Assassin" 4 | ], 5 | "install_msg": "Thank you for downloading this cog. Please follow the instructions at https://red-dashboard.readthedocs.io to get started.\nWARNING: This cog/webserver is in alpha status. Contributors to the Red Discord Bot - Dashboard code hold no responsibility for any issues or risks that may arise as a result of this program. Proceed at your own risk.", 6 | "name": "dashboard", 7 | "short": "Interact with your bot through a web dashboard.", 8 | "description": "Control your bot through a web dashboard, allowing people to run commands (with permissions checked) straight from an easy-to-use dashboard.", 9 | "tags": [ 10 | "tools" 11 | ], 12 | "requirements": [ 13 | "markdown2" 14 | ], 15 | "hidden": true 16 | } 17 | -------------------------------------------------------------------------------- /reacticket/extensions/abc.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | from redbot.core import Config 4 | from redbot.core.bot import Red 5 | 6 | 7 | class MixinMeta(ABC): 8 | """Base class for well behaved type hint detection with composite class. 9 | Basically, to keep developers sane when not all attributes are defined in each mixin. 10 | """ 11 | 12 | def __init__(self, *_args): 13 | self.config: Config 14 | self.bot: Red 15 | 16 | async def embed_requested(self, channel): 17 | # Copy of ctx.embed_requested, but with the context taken out 18 | if not channel.permissions_for(channel.guild.me).embed_links: 19 | return False 20 | 21 | channel_setting = await self.bot._config.channel(channel).embeds() 22 | if channel_setting is not None: 23 | return channel_setting 24 | 25 | guild_setting = await self.bot._config.guild(channel.guild).embeds() 26 | if guild_setting is not None: 27 | return guild_setting 28 | 29 | return await self.bot._config.embeds() 30 | -------------------------------------------------------------------------------- /.github/workflows/matchers/flake8.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "Credits to: https://github.com/home-assistant/core/blob/d32c364d7f9e138e0dd9363b34b3cb39f4afcd06/.github/workflows/matchers/flake8.json", 3 | "problemMatcher": [ 4 | { 5 | "owner": "flake8-error", 6 | "severity": "error", 7 | "pattern": [ 8 | { 9 | "regexp": "^(.+):(\\d+):(\\d+):\\s(([EF]\\d{3})\\s.*)$", 10 | "file": 1, 11 | "line": 2, 12 | "column": 3, 13 | "message": 4, 14 | "code": 5 15 | } 16 | ] 17 | }, 18 | { 19 | "owner": "flake8-warning", 20 | "severity": "warning", 21 | "pattern": [ 22 | { 23 | "regexp": "^(.+):(\\d+):(\\d+):\\s(([CDNW]\\d{3})\\s.*)$", 24 | "file": 1, 25 | "line": 2, 26 | "column": 3, 27 | "message": 4 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-Present NeuroAssassin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /reacticket/extensions/views/alert.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import discord 3 | 4 | 5 | class Alert(discord.ui.View): 6 | def __init__( 7 | self, 8 | title: Optional[str] = None, 9 | description: Optional[str] = None, 10 | color: Optional[int] = None, 11 | embed: Optional[discord.Embed] = None, 12 | ): 13 | if not embed and not ((title or description) and color): 14 | raise ValueError( 15 | "Either embed, or color with a title or description must be provided." 16 | ) 17 | 18 | self.title: Optional[str] = title 19 | self.description: Optional[str] = description 20 | self.color: Optional[int] = color 21 | 22 | self.embed: discord.Embed = embed or discord.Embed( 23 | title=self.title, description=self.description, color=self.color 24 | ) 25 | 26 | super().__init__(timeout=10.0) 27 | 28 | def send(self): 29 | return {"embed": self.embed, "view": self} 30 | 31 | @discord.ui.button(label="Acknowledge", style=discord.ButtonStyle.success) 32 | async def response_continue(self, button: discord.ui.Button, interaction: discord.Interaction): 33 | self.stop() 34 | -------------------------------------------------------------------------------- /reacticket/extensions/views/confirmation.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import discord 3 | 4 | 5 | class Confirmation(discord.ui.View): 6 | def __init__(self, title: str, description: str, color: int): 7 | self.title: str = title 8 | self.description: str = description 9 | self.color: int = color 10 | 11 | self._result: Optional[bool] = None 12 | 13 | super().__init__(timeout=60.0) 14 | 15 | def send(self): 16 | embed: discord.Embed = discord.Embed( 17 | title=self.title, description=self.description, color=self.color 18 | ) 19 | return {"embed": embed, "view": self} 20 | 21 | async def result(self): 22 | await self.wait() 23 | return self._result 24 | 25 | @discord.ui.button(label="Yes, continue", style=discord.ButtonStyle.success) 26 | async def response_continue(self, button: discord.ui.Button, interaction: discord.Interaction): 27 | self._result = True 28 | self.stop() 29 | 30 | @discord.ui.button(label="No, cancel", style=discord.ButtonStyle.danger) 31 | async def response_cancel(self, button: discord.ui.Button, interaction: discord.Interaction): 32 | self._result = False 33 | self.stop() 34 | -------------------------------------------------------------------------------- /.github/workflows/run_precommit.yml: -------------------------------------------------------------------------------- 1 | name: Run pre-commit 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | run_precommit: 7 | name: Run pre-commit 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | # Checkout repository 12 | - uses: actions/checkout@v3 13 | 14 | # Setup Python and install pre-commit 15 | - uses: actions/setup-python@v4 16 | with: 17 | python-version: "3.8" 18 | - name: Install pre-commit 19 | run: | 20 | pip install -U pre-commit 21 | 22 | # Load cached pre-commit environment 23 | - name: set PY 24 | run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV 25 | - uses: actions/cache@v3 26 | with: 27 | path: ~/.cache/pre-commit 28 | key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} 29 | 30 | # Register problem matchers 31 | - name: Register problem matchers 32 | run: | 33 | echo "::add-matcher::.github/workflows/matchers/check-json.json" 34 | echo "::add-matcher::.github/workflows/matchers/check-toml.json" 35 | echo "::add-matcher::.github/workflows/matchers/check-yaml.json" 36 | echo "::add-matcher::.github/workflows/matchers/flake8.json" 37 | 38 | # Run pre-commit 39 | - name: Run pre-commit 40 | run: | 41 | pre-commit run --show-diff-on-failure --color=never --all-files --verbose 42 | -------------------------------------------------------------------------------- /dashboard/rpc/alias.py: -------------------------------------------------------------------------------- 1 | from html import escape 2 | 3 | import discord 4 | from redbot.core.bot import Red 5 | from redbot.core.commands import commands 6 | from redbot.core.utils.chat_formatting import humanize_list 7 | 8 | from .utils import permcheck, rpccheck 9 | 10 | 11 | class DashboardRPC_AliasCC: 12 | def __init__(self, cog: commands.Cog): 13 | self.bot: Red = cog.bot 14 | self.cog: commands.Cog = cog 15 | 16 | # Initialize RPC handlers 17 | self.bot.register_rpc_handler(self.fetch_aliases) 18 | 19 | def unload(self): 20 | self.bot.unregister_rpc_handler(self.fetch_aliases) 21 | 22 | @rpccheck() 23 | @permcheck("Alias", ["aliascc"]) 24 | async def fetch_aliases(self, guild: discord.Guild, member: discord.Member): 25 | aliascog = self.bot.get_cog("Alias") 26 | aliases = await aliascog._aliases.get_guild_aliases(guild) 27 | 28 | ida = {} 29 | for alias in aliases: 30 | if len(alias.command) > 50: 31 | command = alias.command[:47] + "..." 32 | else: 33 | command = alias.command 34 | if alias.command not in ida: 35 | ida[alias.command] = {"aliases": [], "shortened": escape(command)} 36 | ida[alias.command]["aliases"].append(f"{escape(alias.name)}") 37 | 38 | data = {} 39 | for command, aliases in ida.items(): 40 | data[command] = { 41 | "humanized": humanize_list( 42 | list(map(lambda x: f"{x}", aliases["aliases"])) 43 | ), 44 | "raw": aliases["aliases"], 45 | "shortened": aliases["shortened"], 46 | } 47 | return data 48 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3.8 3 | exclude: ^.stubs/ 4 | repos: 5 | - repo: https://github.com/psf/black 6 | rev: 'refs/tags/23.7.0:refs/tags/23.7.0' 7 | hooks: 8 | - id: black 9 | - repo: https://github.com/Pierre-Sassoulas/black-disable-checker 10 | rev: 'v1.1.3' 11 | hooks: 12 | - id: black-disable-checker 13 | - repo: https://github.com/pycqa/flake8 14 | rev: '6.1.0' 15 | hooks: 16 | - id: flake8 17 | - repo: https://github.com/pre-commit/pre-commit-hooks 18 | rev: v4.4.0 19 | hooks: 20 | # `.gitattributes` should technically already handle this 21 | # but autocrlf can result in local files keeping the CRLF 22 | # which is problematic for codespell 23 | - id: end-of-file-fixer 24 | # normally you would want this but Neuro is not very consistent :P 25 | # - id: mixed-line-ending 26 | # args: 27 | # - "--fix=lf" 28 | 29 | # Trailing whitespace is evil 30 | - id: trailing-whitespace 31 | 32 | # Require literal syntax when initializing builtin types 33 | - id: check-builtin-literals 34 | 35 | # Ensure that links to code on GitHub use the permalinks 36 | - id: check-vcs-permalinks 37 | 38 | # Syntax validation 39 | - id: check-ast 40 | - id: check-json 41 | - id: check-toml 42 | # can be switched to yamllint when this issue gets resolved: 43 | # https://github.com/adrienverge/yamllint/issues/238 44 | - id: check-yaml 45 | 46 | # JSON auto-formatter 47 | - id: pretty-format-json 48 | args: 49 | - "--autofix" 50 | - "--indent=4" 51 | - "--no-sort-keys" 52 | 53 | # Checks for git-related issues 54 | - id: check-case-conflict 55 | - id: check-merge-conflict 56 | -------------------------------------------------------------------------------- /.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 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /reacticket/extensions/mixin.py: -------------------------------------------------------------------------------- 1 | from redbot.core import commands, checks 2 | 3 | 4 | class RTMixin: 5 | """This is mostly here to easily mess with things...""" 6 | 7 | @checks.bot_has_permissions(add_reactions=True) 8 | @commands.guild_only() 9 | @commands.group(name="reacticket") 10 | async def reacticket(self, ctx: commands.Context): 11 | """Create a reaction ticket system in your server""" 12 | pass 13 | 14 | @checks.admin() 15 | @reacticket.group(invoke_without_command=True, aliases=["set"]) 16 | async def settings(self, ctx): 17 | """Manage settings for ReacTicket""" 18 | await ctx.send_help() 19 | guild_settings = await self.config.guild(ctx.guild).all() 20 | channel_id, message_id = list(map(int, guild_settings["msg"].split("-"))) 21 | 22 | ticket_channel = getattr(self.bot.get_channel(channel_id), "name", "Not set") 23 | ticket_category = getattr( 24 | self.bot.get_channel(guild_settings["category"]), "name", "Not set" 25 | ) 26 | archive_category = getattr( 27 | self.bot.get_channel(guild_settings["archive"]["category"]), "name", "Not set" 28 | ) 29 | report_channel = getattr(self.bot.get_channel(guild_settings["report"]), "name", "Not set") 30 | 31 | await ctx.send( 32 | "```ini\n" 33 | f"[Ticket Channel]: {ticket_channel}\n" 34 | f"[Ticket MessageID]: {message_id}\n" 35 | f"[Ticket Reaction]: {guild_settings['reaction']}\n" 36 | f"[User-closable]: {guild_settings['usercanclose']}\n" 37 | f"[User-modifiable]: {guild_settings['usercanmodify']}\n" 38 | f"[User-nameable]: {guild_settings['usercanclose']}\n" 39 | f"[Ticket Category]: {ticket_category}\n" 40 | f"[Report Channel]: {report_channel}\n" 41 | f"[Ticket Close DM]: {guild_settings['dm']}\n" 42 | f"[Archive Category]: {archive_category}\n" 43 | f"[Archive Enabled]: {guild_settings['archive']['enabled']}\n" 44 | f"[System Enabled]: {guild_settings['enabled']}\n" 45 | "```" 46 | ) 47 | -------------------------------------------------------------------------------- /reacticket/extensions/usersettings.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from reacticket.extensions.abc import MixinMeta 4 | from reacticket.extensions.mixin import RTMixin 5 | 6 | 7 | class ReacTicketUserSettingsMixin(MixinMeta): 8 | @RTMixin.settings.group() 9 | async def userpermissions(self, ctx): 10 | """Control the permissions that users have with their own tickets""" 11 | pass 12 | 13 | @userpermissions.command() 14 | async def usercanclose(self, ctx, yes_or_no: Optional[bool] = None): 15 | """Set whether users can close their own tickets or not.""" 16 | if yes_or_no is None: 17 | yes_or_no = not await self.config.guild(ctx.guild).usercanclose() 18 | 19 | await self.config.guild(ctx.guild).usercanclose.set(yes_or_no) 20 | if yes_or_no: 21 | await ctx.send("Users can now close their own tickets.") 22 | else: 23 | await ctx.send("Only administrators can now close tickets.") 24 | 25 | @userpermissions.command() 26 | async def usercanmodify(self, ctx, yes_or_no: Optional[bool] = None): 27 | """Set whether users can add or remove additional users to their ticket.""" 28 | if yes_or_no is None: 29 | yes_or_no = not await self.config.guild(ctx.guild).usercanmodify() 30 | 31 | await self.config.guild(ctx.guild).usercanmodify.set(yes_or_no) 32 | if yes_or_no: 33 | await ctx.send("Users can now add/remove other users to their own tickets.") 34 | else: 35 | await ctx.send("Only administrators can now add/remove users to tickets.") 36 | 37 | @userpermissions.command() 38 | async def usercanname(self, ctx, yes_or_no: Optional[bool] = None): 39 | """Set whether users can rename their tickets and associated channels.""" 40 | if yes_or_no is None: 41 | yes_or_no = not await self.config.guild(ctx.guild).usercanname() 42 | 43 | await self.config.guild(ctx.guild).usercanname.set(yes_or_no) 44 | if yes_or_no: 45 | await ctx.send("Users can now rename their tickets and associated channels.") 46 | else: 47 | await ctx.send("Only administrators can now rename tickets and associated channels.") 48 | -------------------------------------------------------------------------------- /dashboard/rpc/utils.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from inspect import signature 3 | from typing import List 4 | 5 | 6 | def rpccheck(): 7 | def conditional(func): 8 | @functools.wraps(func) 9 | async def rpccheckwrapped(self, *args, **kwargs): 10 | if self.bot.get_cog("Dashboard") and self.bot.is_ready(): 11 | return await func(self, *args, **kwargs) 12 | else: 13 | return {"disconnected": True} 14 | 15 | rpccheckwrapped.__signature__ = signature( 16 | func 17 | ) # Because aiohttp json rpc doesn't accept *args, **kwargs 18 | return rpccheckwrapped 19 | 20 | return conditional 21 | 22 | 23 | def permcheck(cog: str = None, permissions: List[str] = ["view"]): 24 | def conditional(func): 25 | @functools.wraps(func) 26 | async def permcheckwrapped(self, guild: int, member: int, *args, **kwargs): 27 | if cog: 28 | if not (self.bot.get_cog(cog)): 29 | return {"status": 0, "message": f"The {cog} cog is not loaded"} 30 | if not (guildobj := self.bot.get_guild(int(guild))): 31 | return {"status": 0, "message": "Unknown guild"} 32 | 33 | m = guildobj.get_member(int(member)) 34 | if not m: 35 | return {"status": 0, "message": "Unknown guild"} 36 | 37 | perms = self.cog.rpc.get_perms(guild, m) 38 | if perms is None: 39 | return {"status": 0, "message": "Unknown guild"} 40 | if int(member) != guildobj.owner_id: 41 | for perm in permissions: 42 | if perm not in perms: 43 | return {"status": 0, "message": "Unknown guild"} 44 | 45 | return await func(self, guildobj, m, *args, **kwargs) 46 | 47 | permcheckwrapped.__signature__ = signature(func) 48 | return permcheckwrapped 49 | 50 | return conditional 51 | 52 | 53 | class FakePermissionsContext: 54 | """A fake context class so that the CogOrCommand class can be used""" 55 | 56 | def __init__(self, bot, guild): 57 | self.bot = bot 58 | self.guild = guild 59 | -------------------------------------------------------------------------------- /dashboard/dashboard.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | from redbot.core import Config, commands 4 | from redbot.core.bot import Red 5 | from abc import ABC 6 | 7 | # ABC Mixins 8 | from dashboard.abc.mixin import DBMixin 9 | 10 | # Command Mixins 11 | from dashboard.abc.roles import DashboardRolesMixin 12 | from dashboard.abc.webserver import DashboardWebserverMixin 13 | from dashboard.abc.settings import DashboardSettingsMixin 14 | 15 | # RPC Mixins 16 | from dashboard.baserpc import DashboardRPC 17 | 18 | THEME_COLORS = ["red", "primary", "blue", "green", "greener", "yellow"] 19 | 20 | 21 | class CompositeMetaClass(type(commands.Cog), type(ABC)): 22 | """This allows the metaclass used for proper type detection to coexist with discord.py's 23 | metaclass.""" 24 | 25 | 26 | # Thanks to Flare for showing how to use group commands across multiple files. 27 | # If this breaks, its his fault 28 | class Dashboard( 29 | DashboardRolesMixin, 30 | DashboardWebserverMixin, 31 | DashboardSettingsMixin, 32 | DBMixin, 33 | commands.Cog, 34 | metaclass=CompositeMetaClass, 35 | ): 36 | __version__ = "0.1.8a" 37 | 38 | def __init__(self, bot: Red, *args, **kwargs): 39 | super().__init__(*args, **kwargs) 40 | self.bot = bot 41 | 42 | self.config = Config.get_conf(self, identifier=473541068378341376) 43 | self.config.register_global( 44 | secret="[Not set]", 45 | redirect="http://127.0.0.1:42356/callback", 46 | clientid=0, 47 | blacklisted=[], 48 | disallowedperms=[], 49 | support="", 50 | defaultcolor="red", 51 | meta={"title": "", "icon": "", "description": "", "color": ""}, 52 | ) 53 | self.config.register_guild(roles=[]) 54 | self.configcache = defaultdict(self.cache_defaults) 55 | 56 | self.rpc = DashboardRPC(self) 57 | 58 | def cog_unload(self): 59 | self.configcache.clear() 60 | self.rpc.unload() 61 | 62 | def cache_defaults(self): 63 | return {"roles": []} 64 | 65 | async def initialize(self): 66 | config = await self.config.all_guilds() 67 | for k, v in config.items(): 68 | self.configcache[k] = v 69 | -------------------------------------------------------------------------------- /maintenance/converters.py: -------------------------------------------------------------------------------- 1 | # Large pieces of the argument parser is taken from Sinbad's cogs. I based mine off of https://github.com/mikeshardmind/SinbadCogs/blob/d59fd7bc69833dc24f9e74ec59e635ffe593d43f/scheduler/converters.py#L23 2 | 3 | import argparse 4 | import time 5 | 6 | from redbot.core.commands import BadArgument, Converter 7 | 8 | from .classes import ScheduledMaintenance 9 | from .utils import convert_time 10 | 11 | 12 | class NoExitParser(argparse.ArgumentParser): 13 | def error(self, message): 14 | raise BadArgument() 15 | 16 | 17 | class Margs(Converter): 18 | async def convert(self, ctx, argument): 19 | argument = argument.replace("—", "--") 20 | parser = NoExitParser(description="Maintenance Scheduler", add_help=False) 21 | parser.add_argument("--start-in", nargs="*", dest="start", default=[]) 22 | parser.add_argument("--whitelist", nargs="*", dest="whitelist", default=[]) 23 | _end = parser.add_mutually_exclusive_group() 24 | _end.add_argument("--end-after", nargs="*", dest="end", default=[]) 25 | _end.add_argument("--end-in", nargs="*", dest="endin", default=[]) 26 | try: 27 | vals = vars(parser.parse_args(argument.split(" "))) 28 | except Exception as exc: 29 | raise BadArgument() from exc 30 | start_seconds = convert_time(vals.get("start", None)) 31 | end_seconds = convert_time(vals.get("end", None)) 32 | whitelist = vals.get("whitelist", []) 33 | whitelist = list(map(int, whitelist)) 34 | after = True 35 | if end_seconds == None: 36 | end_seconds = convert_time(vals.get("endin", None)) 37 | after = False 38 | if start_seconds: 39 | if end_seconds: 40 | scheduled = ScheduledMaintenance( 41 | start=start_seconds, end=end_seconds, after=after, whitelist=whitelist 42 | ) 43 | else: 44 | scheduled = ScheduledMaintenance(start=start_seconds, whitelist=whitelist) 45 | else: 46 | if end_seconds: 47 | scheduled = ScheduledMaintenance(end=end_seconds, after=after, whitelist=whitelist) 48 | else: 49 | scheduled = ScheduledMaintenance(whitelist=whitelist) 50 | return scheduled 51 | -------------------------------------------------------------------------------- /esolang/brainfuck.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | 4 | class Brainfuck: 5 | @staticmethod 6 | def cleanup(code): 7 | return "".join(filter(lambda x: x in [".", "[", "]", "<", ">", "+", "-"], code)) 8 | 9 | @staticmethod 10 | def getlines(code): 11 | return [code[i : i + 50] for i in range(0, len(code), 50)] 12 | 13 | @staticmethod 14 | def buildbracemap(code): 15 | temp_bracestack, bracemap = [], {} 16 | for position, command in enumerate(code): 17 | if command == "[": 18 | temp_bracestack.append(position) 19 | elif command == "]": 20 | start = temp_bracestack.pop() 21 | bracemap[start] = position 22 | bracemap[position] = start 23 | return bracemap 24 | 25 | @staticmethod 26 | def evaluate(code): 27 | code = Brainfuck.cleanup(list(code)) 28 | bracemap = Brainfuck.buildbracemap(code) 29 | cells, codeptr, cellptr, prev = [0], 0, 0, -1 30 | 31 | output = io.StringIO("") 32 | 33 | while codeptr < len(code): 34 | command = code[codeptr] 35 | if command == ">": 36 | cellptr += 1 37 | if cellptr == len(cells): 38 | cells.append(0) 39 | elif command == "<": 40 | cellptr = 0 if cellptr <= 0 else cellptr - 1 41 | elif command == "+": 42 | cells[cellptr] = cells[cellptr] + 1 if cells[cellptr] < 255 else 0 43 | elif command == "-": 44 | cells[cellptr] = cells[cellptr] - 1 if cells[cellptr] > 0 else 255 45 | elif command == "[": 46 | if cells[cellptr] == 0: 47 | codeptr = bracemap[codeptr] 48 | else: 49 | prev = cells[cellptr] 50 | elif command == "]": 51 | if cells[cellptr] == 0: 52 | prev = 0 53 | else: 54 | if cells[cellptr] == prev: 55 | lines = Brainfuck.getlines("".join(code)) 56 | errorptr = codeptr % 50 57 | raise SyntaxError( 58 | f"Infinite loop: []", ("program.bf", len(lines), errorptr, lines[-1]) 59 | ) 60 | else: 61 | codeptr = bracemap[codeptr] 62 | elif command == ".": 63 | output.write(chr(cells[cellptr])) 64 | 65 | codeptr += 1 66 | return output, cells 67 | -------------------------------------------------------------------------------- /dashboard/rpc/botsettings.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from redbot.core.bot import Red 3 | from redbot.core.commands import commands 4 | 5 | from .utils import permcheck, rpccheck 6 | 7 | 8 | class DashboardRPC_BotSettings: 9 | def __init__(self, cog: commands.Cog): 10 | self.bot: Red = cog.bot 11 | self.cog: commands.Cog = cog 12 | 13 | # Initialize RPC handlers 14 | self.bot.register_rpc_handler(self.serverprefix) 15 | self.bot.register_rpc_handler(self.adminroles) 16 | self.bot.register_rpc_handler(self.modroles) 17 | 18 | def unload(self): 19 | self.bot.unregister_rpc_handler(self.serverprefix) 20 | self.bot.unregister_rpc_handler(self.adminroles) 21 | self.bot.unregister_rpc_handler(self.modroles) 22 | 23 | @rpccheck() 24 | @permcheck(permissions=["botsettings"]) 25 | async def serverprefix( 26 | self, guild: discord.Guild, member: discord.Member, method: str = "get", prefixes=None 27 | ): 28 | if prefixes is None: 29 | prefixes = [] 30 | method = method.lower() 31 | if method == "get": 32 | return {"prefixes": await self.bot.get_valid_prefixes(guild)} 33 | elif method == "set": 34 | method = getattr(self.bot, "set_prefixes", self.bot._prefix_cache.set_prefixes) 35 | await method(guild=guild, prefixes=prefixes) 36 | return {"status": 1} 37 | 38 | @rpccheck() 39 | @permcheck(permissions=["botsettings"]) 40 | async def adminroles( 41 | self, guild: discord.Guild, member: discord.Member, method: str = "get", roles=None 42 | ): 43 | if roles is None: 44 | roles = [] 45 | roles = list(map(int, roles)) 46 | 47 | method = method.lower() 48 | if method == "get": 49 | return {"roles": await self.bot._config.guild(guild).admin_role()} 50 | elif method == "set": 51 | for r in roles: 52 | rl = guild.get_role(r) 53 | if not rl: 54 | return {"status": 0, "message": f"Role ID {r} not found"} 55 | await self.bot._config.guild(guild).admin_role.set(roles) 56 | return {"status": 1} 57 | 58 | @rpccheck() 59 | @permcheck(permissions=["botsettings"]) 60 | async def modroles( 61 | self, guild: discord.Guild, member: discord.Member, method: str = "get", roles=None 62 | ): 63 | if roles is None: 64 | roles = [] 65 | roles = list(map(int, roles)) 66 | 67 | method = method.lower() 68 | if method == "get": 69 | return {"roles": await self.bot._config.guild(guild).mod_role()} 70 | elif method == "set": 71 | for r in roles: 72 | rl = guild.get_role(r) 73 | if not rl: 74 | return {"status": 0, "message": f"Role ID {r} not found"} 75 | await self.bot._config.guild(guild).mod_role.set(roles) 76 | return {"status": 1} 77 | -------------------------------------------------------------------------------- /grammar/converters.py: -------------------------------------------------------------------------------- 1 | # Large pieces of the argument parser is taken from Sinbad's cogs. I based mine off of https://github.com/mikeshardmind/SinbadCogs/blob/d59fd7bc69833dc24f9e74ec59e635ffe593d43f/scheduler/converters.py#L23 2 | 3 | import argparse 4 | 5 | from redbot.core.commands import BadArgument, Converter 6 | 7 | 8 | class NoExitParser(argparse.ArgumentParser): 9 | def error(self, message): 10 | raise BadArgument() 11 | 12 | 13 | class Gargs(Converter): 14 | async def convert(self, ctx, argument): 15 | argument = argument.replace("—", "--") 16 | parser = NoExitParser(description="Grammar argument parser", add_help=False) 17 | 18 | parser.add_argument("--meaning-like", "--ml", nargs="*", dest="ml", default=[]) 19 | parser.add_argument("--spelled-like", "--sp", nargs="?", dest="sp", default=[]) 20 | parser.add_argument("--sounds-like", "--sl", nargs="?", dest="sl", default=[]) 21 | parser.add_argument("--rhymes-with", "--rw", nargs="?", dest="rw", default=[]) 22 | parser.add_argument("--adjectives-for", "--af", nargs="?", dest="af", default=[]) 23 | parser.add_argument("--nouns-for", "--nf", nargs="?", dest="nf", default=[]) 24 | parser.add_argument("--comes-before", "--cb", nargs="*", dest="ca", default=[]) 25 | parser.add_argument("--comes-after", "--ca", nargs="*", dest="cb", default=[]) 26 | parser.add_argument("--topics", "--t", nargs="*", dest="t", default=[]) 27 | parser.add_argument("--synonyms-for", "--sf", nargs="*", dest="sf", default=[]) 28 | parser.add_argument("--antonyms-for", "--anf", nargs="*", dest="anf", default=[]) 29 | parser.add_argument("--kind-of", "--ko", nargs="?", dest="ko", default=[]) 30 | parser.add_argument("--more-specific-than", "--mst", nargs="?", dest="mso", default=[]) 31 | parser.add_argument("--homophones", "--h", nargs="?", dest="h", default=[]) 32 | 33 | try: 34 | vals = vars(parser.parse_args(argument.split(" "))) 35 | except Exception as error: 36 | raise BadArgument() from error 37 | 38 | data = {} 39 | if vals["ml"]: 40 | data["ml"] = " ".join(vals["ml"]) 41 | if vals["sp"]: 42 | data["sp"] = vals["sp"] 43 | if vals["sl"]: 44 | data["sl"] = vals["sl"] 45 | if vals["rw"]: 46 | data["rel_rhy"] = vals["rw"] 47 | if vals["af"]: 48 | data["rel_jjb"] = vals["af"] 49 | if vals["nf"]: 50 | data["rel_jja"] = vals["nf"] 51 | if vals["ca"]: 52 | data["lc"] = " ".join(vals["ca"]) 53 | if vals["cb"]: 54 | data["rc"] = " ".join(vals["cb"]) 55 | if vals["t"]: 56 | if len(vals["t"]) > 5: 57 | raise BadArgument("Topic can only be five words") 58 | data["topics"] = " ".join(vals["t"]) 59 | if vals["sf"]: 60 | data["rel_syn"] = " ".join(vals["sf"]) 61 | if vals["anf"]: 62 | data["rel_ant"] = " ".join(vals["anf"]) 63 | if vals["ko"]: 64 | data["rel_spc"] = vals["ko"] 65 | if vals["mso"]: 66 | data["rel_gen"] = vals["mso"] 67 | if vals["h"]: 68 | data["rel_hom"] = vals["h"] 69 | 70 | data["max"] = 10 71 | 72 | return data 73 | -------------------------------------------------------------------------------- /grammar/grammar.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2018-Present NeuroAssassin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | import aiohttp 26 | from redbot.core import commands 27 | 28 | from .converters import Gargs 29 | 30 | URL = "http://api.datamuse.com/words" 31 | 32 | 33 | class Grammar(commands.Cog): 34 | """Get words related to the specified arguments""" 35 | 36 | def __init__(self, bot): 37 | self.bot = bot 38 | self.session = aiohttp.ClientSession() 39 | 40 | async def red_delete_data_for_user(self, **kwargs): 41 | """This cog does not store user data""" 42 | return 43 | 44 | @commands.command() 45 | async def grammar(self, ctx, *, args: Gargs): 46 | """Get words related to the passed arguments. 47 | 48 | Arguments must have `--` before them. 49 |    `meaning-like`/`ml`: Get words that mean close to what the passed word means. 50 |    `spelled-like`/`sp`: Get words that are spelled like the passed word. 51 |    `sounds-like`/`sl`: Get words that sound like the passed word. 52 |    `rhymes-with`/`rw`: Get words that rhyme with the passed word. 53 |    `adjectives-for`/`af`: Get adjectives for the passed noun. 54 |    `nouns-for`/`nf`: Get nouns for the passed adjective. 55 |    `comes-before`/`cb`: Get words that usually come before the passed word. 56 |    `comes-after`/`ca`: Get words that usually come after the passed word. 57 |    `topics`: Get words that are related to the passed topic. Max 5 words. 58 |    `synonyms-for`/`sf`: Get synonyms for the passed word. 59 |    `antonyms-for`/`anf`: Get antonyms for the passed word. 60 |    `kind-of`/`ko`: Get the kind of what the passed word is (Computer -> Machine). 61 |    `more-specific-than`/`mst`: Get more specific nouns for the passed word (Ex: Machine -> Computer). 62 |    `homophones`/`h`: Get homophones of the passed word.""" 63 | data = args 64 | async with self.session.get(URL, params=data) as r: 65 | if r.status != 200: 66 | return await ctx.send(f"Invalid status code: {r.status}") 67 | text = await r.json() 68 | sending = "Here are the top 10 words that came close to your filters:\n```\n" 69 | for x in text: 70 | sending += x["word"] + "\n" 71 | sending += "```" 72 | await ctx.send(sending) 73 | -------------------------------------------------------------------------------- /dashboard/abc/webserver.py: -------------------------------------------------------------------------------- 1 | from redbot.core import commands, checks 2 | from redbot.core.utils.chat_formatting import box, humanize_list, inline 3 | from redbot.core.utils.predicates import MessagePredicate 4 | import discord 5 | 6 | from dashboard.abc.abc import MixinMeta 7 | from dashboard.abc.mixin import dashboard 8 | 9 | from dashboard.baserpc import HUMANIZED_PERMISSIONS 10 | from dashboard.menus import ClientList, ClientMenu 11 | 12 | 13 | class DashboardWebserverMixin(MixinMeta): 14 | @checks.is_owner() 15 | @dashboard.group() 16 | async def webserver(self, ctx): 17 | """Group command for controlling settings related to webserver""" 18 | pass 19 | 20 | @webserver.command(enabled=False) 21 | async def clients(self, ctx: commands.Context): 22 | """View connected RPC clients. These could be dashboard or other processes. 23 | 24 | Only terminate them if they are looking suspicious.""" 25 | return await ctx.send("This command is disabled.") 26 | clients = self.bot.rpc._rpc.clients 27 | if clients: 28 | await ClientMenu( 29 | source=ClientList(clients), clear_reactions_after=True, timeout=180 30 | ).start(ctx, wait=False) 31 | else: 32 | e = discord.Embed(title="No RPC Clients connected", color=await ctx.embed_color()) 33 | await ctx.send(embed=e) 34 | 35 | @webserver.group() 36 | async def blacklist(self, ctx: commands.Context): 37 | """Manage dashboard blacklist""" 38 | pass 39 | 40 | @blacklist.command(name="view") 41 | async def blacklist_view(self, ctx: commands.Context): 42 | """See blacklisted IP addresses""" 43 | blacklisted = await self.config.blacklisted() or ["None"] 44 | await ctx.author.send( 45 | f"The following IP addresses are blocked: {humanize_list(blacklisted)}" 46 | ) 47 | 48 | @blacklist.command(name="remove") 49 | async def blacklist_remove(self, ctx: commands.Context, *, ip): 50 | """Remove an IP address from blacklist""" 51 | try: 52 | async with self.config.blacklisted() as data: 53 | data.remove(ip) 54 | await ctx.tick() 55 | except ValueError: 56 | await ctx.send("Couldn't find that IP in blacklist.") 57 | 58 | @blacklist.command(name="add") 59 | async def blacklist_add(self, ctx: commands.Context, *, ip): 60 | """Add an IP address to blacklist""" 61 | async with self.config.blacklisted() as data: 62 | data.append(ip) 63 | await ctx.tick() 64 | 65 | @webserver.command() 66 | async def secret(self, ctx: commands.Context, *, secret: str): 67 | """Set the client secret needed for Discord Oauth.""" 68 | await self.config.secret.set(secret) 69 | await ctx.tick() 70 | 71 | @webserver.command() 72 | async def redirect(self, ctx: commands.Context, redirect: str): 73 | """Set the redirect for after logging in via Discord OAuth.""" 74 | if not redirect.endswith("/callback"): 75 | await ctx.send("Redirect must end with `/callback`") 76 | return 77 | await self.config.redirect.set(redirect) 78 | await ctx.tick() 79 | 80 | @webserver.command(hidden=True) 81 | async def clientid(self, ctx: commands.Context, cid: int): 82 | """Set the Client ID for after logging in via Discord OAuth. 83 | 84 | Note that this should almost never be used. This is only here 85 | for special cases where the Client ID is not the same as the bot 86 | ID. 87 | 88 | Pass 0 if you wish to revert to Bot ID.""" 89 | await ctx.send( 90 | "**Warning**\n\nThis command only exists for special cases. It is most likely that your client ID is your bot ID, which is the default. **Changing this will break Discord OAuth until reverted.** Are you sure you want to do this?" 91 | ) 92 | 93 | pred = MessagePredicate.yes_or_no(ctx) 94 | await self.bot.wait_for("message", check=pred) 95 | 96 | if pred.result is True: 97 | await self.config.clientid.set(cid) 98 | if cid == 0: 99 | await ctx.send("Client ID restored to bot ID.") 100 | else: 101 | await ctx.send(f"Client ID set to {cid}.") 102 | else: 103 | await ctx.send("Cancelled.") 104 | -------------------------------------------------------------------------------- /esolang/cow.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | 4 | class COW: 5 | instruction_mapping = { 6 | 0: "moo", 7 | 1: "mOo", 8 | 2: "moO", 9 | 3: "mOO", 10 | 4: "MOo", 11 | 5: "MoO", 12 | 6: "MOO", 13 | 7: "MOO", 14 | 8: "OOO", 15 | 9: "MMM", 16 | 10: "OOM", 17 | } 18 | 19 | @staticmethod 20 | def cleanup(code): 21 | return "".join(filter(lambda x: x in ["m", "o", "M", "O"], code)) 22 | 23 | @staticmethod 24 | def getlines(code): 25 | return [code[i : i + 50] for i in range(0, len(code), 50)] 26 | 27 | @staticmethod 28 | def buildbracemap(code): 29 | temp_bracestack, bracemap = [], {} 30 | for position, command in enumerate(code): 31 | if command == "MOO": 32 | temp_bracestack.append(position) 33 | elif command == "moo": 34 | start = temp_bracestack.pop() 35 | bracemap[start] = position 36 | bracemap[position] = start 37 | if temp_bracestack: 38 | lines = COW.getlines("".join(code)) 39 | raise SyntaxError( 40 | "Trailing MOO", ("program.moo", len(lines), len(lines[-1]), lines[-1]) 41 | ) 42 | return bracemap 43 | 44 | @staticmethod 45 | def evaluate(code): 46 | code = COW.cleanup(code) 47 | 48 | if len(code) % 3 != 0: 49 | lines = COW.getlines(code) 50 | raise SyntaxError( 51 | "Trailing command", ("program.moo", len(lines), len(lines[-1]), lines[-1]) 52 | ) 53 | code = [code[i : i + 3] for i in range(0, len(code), 3)] 54 | 55 | bracemap = COW.buildbracemap(code) 56 | cells, codeptr, cellptr, prev, registry = [0], 0, 0, -1, -1 57 | output = io.StringIO("") 58 | 59 | while codeptr < len(code): 60 | command = code[codeptr] 61 | if command == "moO": 62 | cellptr += 1 63 | if cellptr == len(cells): 64 | cells.append(0) 65 | elif command == "mOo": 66 | cellptr = 0 if cellptr <= 0 else cellptr - 1 67 | elif command == "MoO": 68 | cells[cellptr] = cells[cellptr] + 1 if cells[cellptr] < 255 else 0 69 | elif command == "MOo": 70 | cells[cellptr] = cells[cellptr] - 1 if cells[cellptr] > 0 else 255 71 | elif command == "MOO": 72 | if cells[cellptr] == 0: 73 | codeptr = bracemap[codeptr] 74 | else: 75 | prev = cells[cellptr] 76 | elif command == "moo": 77 | if cells[cellptr] == 0: 78 | prev = 0 79 | else: 80 | if cells[cellptr] == prev: 81 | lines = COW.getlines("".join(code)) 82 | errorptr = ((codeptr * 3) % 50) + 3 83 | raise SyntaxError( 84 | "Infinite loop: MOO/moo", 85 | ("program.moo", len(lines), errorptr, lines[-1]), 86 | ) 87 | else: 88 | codeptr = bracemap[codeptr] 89 | elif command == "Moo": 90 | output.write(chr(cells[cellptr])) 91 | elif command == "mOO": 92 | try: 93 | code[codeptr] = COW.instruction_mapping[cells[cellptr]] 94 | except KeyError: 95 | lines = COW.getlines("".join(code)) 96 | errorptr = ((codeptr * 3) % 50) + 3 97 | raise SyntaxError( 98 | f"Invalid mOO execution in memory address {cellptr}: {cells[cellptr]}", 99 | ("program.moo", len(lines), errorptr, lines[-1]), 100 | ) 101 | if code[codeptr] == "mOO": 102 | lines = COW.getlines("".join(code)) 103 | errorptr = ((codeptr * 3) % 50) + 3 104 | raise SyntaxError( 105 | "Infinite loop: mOO", ("program.moo", len(lines), errorptr, lines[-1]) 106 | ) 107 | continue 108 | elif command == "MMM": 109 | if registry == -1: 110 | registry = cells[cellptr] 111 | else: 112 | cells[cellptr] = registry 113 | registry = -1 114 | elif command == "OOO": 115 | cells[cellptr] = 0 116 | elif command == "OOM": 117 | output.write(str(cells[cellptr])) 118 | else: 119 | lines = COW.getlines("".join(code)) 120 | errorptr = ((codeptr * 3) % 50) + 3 121 | raise SyntaxError( 122 | "Invalid COW command", ("program.moo", len(lines), errorptr, lines[-1]) 123 | ) 124 | 125 | codeptr += 1 126 | return output, cells 127 | -------------------------------------------------------------------------------- /evolution/tasks.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import contextlib 5 | import random 6 | import time 7 | from typing import TYPE_CHECKING, Dict 8 | 9 | from redbot.core import Config 10 | from redbot.core.bot import Red 11 | from redbot.core.utils import AsyncIter 12 | 13 | if TYPE_CHECKING: 14 | from .evolution import Evolution 15 | 16 | from . import bank 17 | 18 | 19 | class EvolutionTaskManager: 20 | def __init__(self, cog): 21 | self.bot: Red = cog.bot 22 | self.conf: Config = cog.conf 23 | self.cog: Evolution = cog 24 | 25 | self.tasks: Dict[str, asyncio.Task] = {} 26 | 27 | async def process_credits(self, data, ct, timedata): 28 | all_gaining = 0 29 | async for key, value in AsyncIter(data.items()): 30 | last_processed = timedata[str(key)] 31 | if ct > last_processed + self.cog.utils.delays[int(key)]: 32 | for x in range(0, value): 33 | chance = random.randint(1, 100) 34 | chances = list(self.cog.utils.levels[int(key)].keys()) 35 | chosen = min([c for c in chances if chance <= c]) 36 | gaining = self.cog.utils.levels[int(key)][chosen] 37 | all_gaining += gaining 38 | return all_gaining 39 | 40 | async def process_times(self, ct, timedata): 41 | async for key in AsyncIter(range(1, 26)): 42 | last_processed = timedata[str(key)] 43 | if ct > last_processed + self.cog.utils.delays[int(key)]: 44 | timedata[str(key)] = ct 45 | return timedata 46 | 47 | async def income_task(self): 48 | await self.bot.wait_until_ready() 49 | while True: 50 | # First, process the credits being added 51 | bulk_edit = {} 52 | ct = time.time() 53 | lastcredited = await self.cog.conf.lastcredited() 54 | async for userid, data in AsyncIter(self.cog.cache.copy().items()): 55 | animal = data["animal"] 56 | if animal == "": 57 | continue 58 | multiplier = data["multiplier"] 59 | animals = data["animals"] 60 | gaining = await self.process_credits(animals, ct, lastcredited) * multiplier 61 | bulk_edit[str(userid)] = gaining 62 | 63 | # Credit to aikaterna's seen cog for this bulk write 64 | config = bank._get_config() 65 | users = config._get_base_group(config.USER) 66 | max_credits = await bank.get_max_balance() 67 | async with users.all() as new_data: 68 | for user_id, userdata in bulk_edit.items(): 69 | if str(user_id) not in new_data: 70 | new_data[str(user_id)] = {"balance": userdata} 71 | continue 72 | if new_data[str(user_id)]["balance"] + userdata > max_credits: 73 | new_data[str(user_id)]["balance"] = int(max_credits) 74 | else: 75 | new_data[str(user_id)]["balance"] = int( 76 | new_data[str(user_id)]["balance"] + userdata 77 | ) 78 | 79 | await self.cog.conf.lastcredited.set(await self.process_times(ct, lastcredited)) 80 | await asyncio.sleep(60) 81 | 82 | async def daily_task(self): 83 | await self.bot.wait_until_ready() 84 | while True: 85 | lastdailyupdate = await self.cog.conf.lastdailyupdate() 86 | if lastdailyupdate + 86400 <= time.time(): 87 | deals = {} 88 | levels = random.sample( 89 | self.cog.utils.randlvl_chances, len(self.cog.utils.randlvl_chances) 90 | ) 91 | amounts = random.sample( 92 | self.cog.utils.randamt_chances, len(self.cog.utils.randamt_chances) 93 | ) 94 | for x in range(1, 7): 95 | level = random.choice(levels) 96 | amount = random.choice(amounts) 97 | deals[str(x)] = {"details": {"level": level, "amount": amount}, "bought": []} 98 | await self.cog.conf.daily.set(deals) 99 | await self.cog.conf.lastdailyupdate.set(time.time()) 100 | await asyncio.sleep(300) 101 | 102 | def get_statuses(self): 103 | returning = {} 104 | for task, obj in self.tasks.items(): 105 | exc = None 106 | with contextlib.suppress(asyncio.exceptions.InvalidStateError): 107 | exc = obj.exception() 108 | returning[task] = {"state": obj._state, "exc": exc} 109 | return returning 110 | 111 | def init_tasks(self): 112 | self.tasks["income"] = self.bot.loop.create_task(self.income_task()) 113 | self.tasks["daily"] = self.bot.loop.create_task(self.daily_task()) 114 | 115 | def shutdown(self): 116 | for task in self.tasks.values(): 117 | task.cancel() 118 | -------------------------------------------------------------------------------- /dashboard/menus.py: -------------------------------------------------------------------------------- 1 | # Pieces of this are taken from flare's pokecord cog 2 | 3 | import asyncio 4 | import contextlib 5 | from typing import Any, Dict, Iterable, Optional 6 | 7 | import discord 8 | from aiohttp.web_request import Request 9 | from redbot.core import commands 10 | from redbot.core.utils.chat_formatting import box 11 | from redbot.core.utils.menus import start_adding_reactions 12 | from redbot.core.utils.predicates import ReactionPredicate 13 | from redbot.vendored.discord.ext import menus 14 | 15 | 16 | class ClientMenu(menus.MenuPages, inherit_buttons=False): 17 | def __init__( 18 | self, 19 | source: menus.PageSource, 20 | cog: Optional[commands.Cog] = None, 21 | ctx=None, 22 | user=None, 23 | clear_reactions_after: bool = True, 24 | delete_message_after: bool = True, 25 | add_reactions: bool = True, 26 | using_custom_emoji: bool = False, 27 | using_embeds: bool = False, 28 | keyword_to_reaction_mapping: Dict[str, str] = None, 29 | timeout: int = 180, 30 | message: discord.Message = None, 31 | **kwargs: Any, 32 | ) -> None: 33 | super().__init__( 34 | source, 35 | clear_reactions_after=clear_reactions_after, 36 | delete_message_after=delete_message_after, 37 | check_embeds=using_embeds, 38 | timeout=timeout, 39 | message=message, 40 | **kwargs, 41 | ) 42 | 43 | def reaction_check(self, payload): 44 | """The function that is used to check whether the payload should be processed. 45 | This is passed to :meth:`discord.ext.commands.Bot.wait_for `. 46 | 47 | There should be no reason to override this function for most users. 48 | 49 | Parameters 50 | ------------ 51 | payload: :class:`discord.RawReactionActionEvent` 52 | The payload to check. 53 | 54 | Returns 55 | --------- 56 | :class:`bool` 57 | Whether the payload should be processed. 58 | """ 59 | if payload.message_id != self.message.id: 60 | return False 61 | if payload.user_id not in (*self.bot.owner_ids, self._author_id): 62 | return False 63 | 64 | return payload.emoji in self.buttons 65 | 66 | @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}", position=menus.First(0)) 67 | async def prev(self, payload: discord.RawReactionActionEvent): 68 | if self.current_page == 0: 69 | await self.show_page(self._source.get_max_pages() - 1) 70 | else: 71 | await self.show_checked_page(self.current_page - 1) 72 | 73 | @menus.button("\N{CROSS MARK}", position=menus.First(1)) 74 | async def stop_pages_default(self, payload: discord.RawReactionActionEvent) -> None: 75 | self.stop() 76 | 77 | @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}", position=menus.First(2)) 78 | async def next(self, payload: discord.RawReactionActionEvent): 79 | if self.current_page == self._source.get_max_pages() - 1: 80 | await self.show_page(0) 81 | else: 82 | await self.show_checked_page(self.current_page + 1) 83 | 84 | @menus.button("\N{WARNING SIGN}\N{VARIATION SELECTOR-16}", position=menus.First(3)) 85 | async def close_ws(self, payload: discord.RawReactionActionEvent): 86 | number = self.current_page 87 | msg = await self.ctx.send( 88 | ( 89 | f"Are you sure you want to close RPC Client {number + 1}? " 90 | "This will prevent them from communicating and may raise errors if not handled properly." 91 | ) 92 | ) 93 | emojis = ReactionPredicate.YES_OR_NO_EMOJIS 94 | start_adding_reactions(msg, emojis) 95 | pred = ReactionPredicate.yes_or_no(msg, self.ctx.author) 96 | with contextlib.suppress(asyncio.TimeoutError): 97 | await self.ctx.bot.wait_for("reaction_add", check=pred, timeout=30) 98 | 99 | if pred.result: 100 | # Definitely do NOT do this at home 101 | self.ctx.bot.rpc._rpc.clients[number].ws._reader.set_exception(Exception) 102 | await self.ctx.send(f"Successfully closed RPC Client {number + 1}") 103 | else: 104 | await self.ctx.send("Cancelled.") 105 | 106 | 107 | class ClientList(menus.ListPageSource): 108 | def __init__(self, entries: Iterable[str]): 109 | super().__init__(entries, per_page=1) 110 | 111 | async def format_page(self, menu: ClientMenu, client: Request): 112 | description = ( 113 | f"Connected to [{client.url}]\n" 114 | f"Connected from [{client.remote}]\n" 115 | f"Connected since [{client.ws._headers['Date']}]" 116 | ) 117 | e = discord.Embed( 118 | title=f"RPC Client {menu.current_page + 1}/{self._max_pages}", 119 | description=box(description, lang="ini"), 120 | color=await menu.ctx.embed_color(), 121 | ) 122 | return e 123 | 124 | def is_paginating(self): 125 | return True # So it always adds reactions 126 | -------------------------------------------------------------------------------- /reacticket/extensions/views/queue.py: -------------------------------------------------------------------------------- 1 | from redbot.core.commands.context import Context 2 | from typing import List, Optional 3 | from datetime import datetime 4 | import discord 5 | 6 | from reacticket.extensions.views.ticket import Ticket 7 | 8 | SPACE = " \N{ZERO WIDTH SPACE}" 9 | 10 | 11 | class QueueTicketButton(discord.ui.Button["Queue"]): 12 | def __init__(self, counter): 13 | self.counter = counter 14 | 15 | super().__init__( 16 | style=discord.ButtonStyle.secondary, 17 | label=f"#{counter}", 18 | emoji="\N{ADMISSION TICKETS}", 19 | row=0, 20 | ) 21 | 22 | async def callback(self, interaction: discord.Interaction): 23 | await interaction.response.defer() 24 | view = self.view 25 | view.processing = self 26 | 27 | ticket = view.tickets[view.page][self.counter - 1] 28 | tview = Ticket(view.ctx, ticket, view) 29 | 30 | await tview.build_embed() 31 | await view.original.edit(embed=tview.embed, view=tview) 32 | 33 | await tview.wait() 34 | if not tview.timed_out and not tview.repeat: 35 | updated_tickets = await view.ctx.cog.config.guild(view.ctx.guild).created() 36 | try: 37 | tickets = view.ctx.cog.sort_tickets(updated_tickets) 38 | except ValueError: 39 | embed = discord.Embed( 40 | title="Open tickets", 41 | description="No tickets are currently open.", 42 | color=await view.ctx.embed_color(), 43 | ) 44 | await view.original.edit(embed=embed, view=None) 45 | view.stop() # Just to make sure 46 | return 47 | 48 | new_queue = Queue(view.ctx, tickets) 49 | new_queue.page = view.page 50 | new_queue.refresh_ticket_row() 51 | await new_queue.build_embed() 52 | 53 | new_queue.set_message(view.original) 54 | await view.original.edit(embed=new_queue.embed, view=new_queue) 55 | elif not tview.timed_out: 56 | await self.callback(interaction) 57 | 58 | view.processing = None 59 | 60 | 61 | class Queue(discord.ui.View): 62 | def __init__(self, ctx: Context, tickets: List): 63 | self.ctx: Context = ctx 64 | self.tickets: List = tickets 65 | 66 | self.page: int = 0 67 | self.embed: Optional[discord.Embed] = None 68 | self.original: Optional[discord.Message] = None 69 | self.processing: Optional[discord.ui.Button] = None 70 | 71 | super().__init__() 72 | 73 | self.refresh_ticket_row() 74 | 75 | def set_message(self, message: discord.Message): 76 | self.original = message 77 | 78 | def refresh_ticket_row(self): 79 | if len(self.tickets) == 1: 80 | self.clear_items() 81 | else: 82 | for child in self.children.copy(): 83 | if child.row == 0: 84 | self.remove_item(child) 85 | 86 | for ticket_num in range(1, len(self.tickets[self.page]) + 1): 87 | self.add_item(QueueTicketButton(ticket_num)) 88 | 89 | async def build_embed(self): 90 | self.embed = discord.Embed( 91 | title="Open tickets", description="", color=await self.ctx.embed_color() 92 | ) 93 | 94 | counter = 1 95 | for ticket in self.tickets[self.page]: 96 | user = getattr(self.ctx.guild.get_member(ticket["user"]), "mention", "Removed user") 97 | timestamp = datetime.fromtimestamp(ticket["opened"]).strftime("%B %d, %Y at %H:%M UTC") 98 | self.embed.description += f"**{counter}.** Ticket created by {user} on {timestamp}\n" 99 | counter += 1 100 | 101 | @discord.ui.button( 102 | label=SPACE * 6, 103 | style=discord.ButtonStyle.secondary, 104 | emoji="\N{LEFTWARDS BLACK ARROW}", 105 | row=1, 106 | ) 107 | async def page_left(self, button: discord.ui.Button, interaction: discord.Interaction): 108 | if self.page == 0: 109 | self.page = len(self.tickets) - 1 110 | else: 111 | self.page -= 1 112 | 113 | self.refresh_ticket_row() 114 | await self.build_embed() 115 | await interaction.response.edit_message(embed=self.embed, view=self) 116 | 117 | @discord.ui.button(label=SPACE * 14, style=discord.ButtonStyle.secondary, disabled=True, row=1) 118 | async def first_blank(self, *args, **kwargs): 119 | pass 120 | 121 | @discord.ui.button(label=SPACE * 14, style=discord.ButtonStyle.secondary, disabled=True, row=1) 122 | async def second_blank(self, *args, **kwargs): 123 | pass 124 | 125 | @discord.ui.button(label=SPACE * 14, style=discord.ButtonStyle.secondary, disabled=True, row=1) 126 | async def third_blank(self, *args, **kwargs): 127 | pass 128 | 129 | @discord.ui.button( 130 | label=SPACE * 6, 131 | style=discord.ButtonStyle.secondary, 132 | emoji="\N{BLACK RIGHTWARDS ARROW}", 133 | row=1, 134 | ) 135 | async def page_right(self, button: discord.ui.Button, interaction: discord.Interaction): 136 | if self.page == len(self.tickets) - 1: 137 | self.page = 0 138 | else: 139 | self.page += 1 140 | 141 | self.refresh_ticket_row() 142 | await self.build_embed() 143 | await interaction.response.edit_message(embed=self.embed, view=self) 144 | 145 | async def on_timeout(self): 146 | if not self.processing: 147 | await self.original.edit(content="This message has expired.", view=None) 148 | -------------------------------------------------------------------------------- /esolang/esolang.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2018-Present NeuroAssassin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from redbot.core import commands, checks 25 | from redbot.core.utils.chat_formatting import box 26 | import traceback 27 | import functools 28 | import asyncio 29 | import inspect 30 | 31 | from .brainfuck import Brainfuck 32 | from .cow import COW 33 | from .befunge import Befunge 34 | from .whitespace import Whitespace 35 | 36 | 37 | class Esolang(commands.Cog): 38 | """Do not ever look at the source for this""" 39 | 40 | def __init__(self, bot): 41 | self.bot = bot 42 | 43 | @checks.is_owner() 44 | @commands.command() 45 | async def brainfuck(self, ctx, *, code): 46 | """Run brainfuck code""" 47 | try: 48 | output, cells = Brainfuck.evaluate(code) 49 | except Exception as error: 50 | await ctx.send( 51 | box("".join(traceback.format_exception_only(type(error), error)), lang="py") 52 | ) 53 | else: 54 | output.seek(0) 55 | output = output.read() 56 | await ctx.send( 57 | box( 58 | f"[Memory]: {'[' + ']['.join(list(map(str, cells))) + ']'}\n" 59 | f"[Output]: {output}", 60 | lang="ini", 61 | ) 62 | ) 63 | 64 | @checks.is_owner() 65 | @commands.command() 66 | async def cow(self, ctx, *, code): 67 | """Run COW code""" 68 | try: 69 | output, cells = COW.evaluate(code) 70 | except Exception as error: 71 | await ctx.send( 72 | box("".join(traceback.format_exception_only(type(error), error)), lang="py") 73 | ) 74 | else: 75 | output.seek(0) 76 | output = output.read() 77 | await ctx.send( 78 | box( 79 | f"[Memory]: {'[' + ']['.join(list(map(str, cells))) + ']'}\n" 80 | f"[Output]: {output}", 81 | lang="ini", 82 | ) 83 | ) 84 | 85 | @checks.is_owner() 86 | @commands.command() 87 | async def befunge(self, ctx, *, code): 88 | """Run Befunge code""" 89 | if code.startswith("```") and code.endswith("```"): 90 | code = code[3:-3] 91 | 92 | try: 93 | task = self.bot.loop.create_task(Befunge.evaluate(code)) 94 | await asyncio.wait_for(task, timeout=5.0) 95 | output, cells = task.result() 96 | except asyncio.TimeoutError: 97 | await ctx.send("Your befunge program took too long to run.") 98 | except Exception as error: 99 | frame_vars = inspect.trace()[-1][0].f_locals 100 | stack = frame_vars.get("stack", frame_vars.get("self")) 101 | if stack: 102 | stack = "[" + "][".join(list(map(str, stack._internal[:10]))) + "]" 103 | else: 104 | stack = "Not initialized" 105 | await ctx.send( 106 | box( 107 | f"[Stack]: {stack}\n" 108 | f"[Exception]:\n{''.join(traceback.format_exception_only(type(error), error))}", 109 | lang="ini", 110 | ) 111 | ) 112 | else: 113 | output.seek(0) 114 | output = output.read() 115 | await ctx.send( 116 | box( 117 | f"[Stack]: {'[' + ']['.join(list(map(str, cells))) + ']'}\n" 118 | f"[Output]: {output}", 119 | lang="ini", 120 | ) 121 | ) 122 | 123 | @checks.is_owner() 124 | @commands.command() 125 | async def whitespace(self, ctx, *, code): 126 | """Run whitepsace code. 127 | 128 | Since Discord auto-converts tabs to spaces, use EM QUAD instead. 129 | 130 | If you need to copy it, here: `\u2001` 131 | """ 132 | try: 133 | wrapped = functools.partial(Whitespace.evaluate, code=code) 134 | future = self.bot.loop.run_in_executor(None, wrapped) 135 | for x in range(500): 136 | await asyncio.sleep(0.01) 137 | try: 138 | output = future.result() 139 | except asyncio.InvalidStateError: 140 | continue 141 | else: 142 | break 143 | except Exception as error: 144 | await ctx.send( 145 | box("".join(traceback.format_exception_only(type(error), error)), lang="py") 146 | ) 147 | else: 148 | output.seek(0) 149 | output = output.read() 150 | await ctx.send( 151 | box( 152 | f"[Output]: {output}", 153 | lang="ini", 154 | ) 155 | ) 156 | -------------------------------------------------------------------------------- /editor/editor.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2018-Present NeuroAssassin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from typing import Union 26 | 27 | import discord 28 | from redbot.core import commands 29 | 30 | 31 | class Editor(commands.Cog): 32 | """Allows for Administrators to edit a bot's messages by providing the new content or by copying another message""" 33 | 34 | def __init__(self, bot): 35 | self.bot = bot 36 | 37 | async def red_delete_data_for_user(self, **kwargs): 38 | """This cog does not store user data""" 39 | return 40 | 41 | @commands.command() 42 | @commands.admin() 43 | async def editmessage( 44 | self, ctx, ecid: int, editid: int, ccid: int, *, content: Union[int, str] 45 | ): 46 | """Edits a message with the content of another message or the specified content. 47 | 48 | Arguments: 49 | - ecid: The ID of the channel of the message you are editing (Required) 50 | 51 | - editid: The ID of the message you are editing (Required) 52 | 53 | - ccid: The ID of the channel of the message you are copying from. If you are giving the raw content yourself, pass 0 as the channel ID. (Optional) 54 | 55 | - content: The ID of the message that contains the contents of what you want the other message to become, or the new content of the message. (Required, integer (for message id) or text (for new content) 56 | 57 | Examples: 58 | `[p]editmessage ` 59 | `[p]editmessage 0 New content here` 60 | 61 | Real Examples: 62 | `[p]editmessage 133251234164375552 578969593708806144 133251234164375552 578968157520134161` 63 | `[p]editmessage 133251234164375552 578969593708806144 0 ah bruh` 64 | """ 65 | if isinstance(content, int) and ccid == 0: 66 | return await ctx.send( 67 | "You provided an ID of a message to copy from, but didn't provide a channel ID to get the message from." 68 | ) 69 | 70 | # Make sure channels and IDs are all good 71 | editchannel = self.bot.get_channel(ecid) 72 | if not editchannel or not type(editchannel) == discord.TextChannel: 73 | return await ctx.send("Invalid channel for the message you are editing.") 74 | if not editchannel.permissions_for(ctx.author).manage_messages and not ( 75 | await self.bot.is_owner(ctx.author) 76 | ): 77 | return await ctx.send("You do not have permission to edit messages in that channel.") 78 | try: 79 | editmessage = await editchannel.fetch_message(editid) 80 | except discord.NotFound: 81 | return await ctx.send( 82 | "Invalid editing message ID, or you passed the wrong channel ID for the message." 83 | ) 84 | except discord.Forbidden: 85 | return await ctx.send( 86 | "I'm not allowed to view the channel which contains the message I am editing." 87 | ) 88 | if ccid != 0 and type(content) == int: 89 | copychannel = self.bot.get_channel(ccid) 90 | if not copychannel or not type(editchannel) == discord.TextChannel: 91 | return await ctx.send("Invalid ID for channel of the message to copy from.") 92 | try: 93 | copymessage = await copychannel.fetch_message(content) 94 | except discord.NotFound: 95 | return await ctx.send( 96 | "Invalid copying message ID, or you passed the wrong channel ID for the message." 97 | ) 98 | except discord.Forbidden: 99 | return await ctx.send( 100 | "I'm not allowed to view the channel of the message from which I am copying." 101 | ) 102 | 103 | # All checks passed 104 | content = copymessage.content 105 | try: 106 | embed = copymessage.embeds[0] 107 | except IndexError: 108 | embed = None 109 | try: 110 | await editmessage.edit(content=content, embed=embed) 111 | except discord.errors.Forbidden: 112 | return await ctx.send("I can only edit my own messages.") 113 | await ctx.send(f"Message successfully edited. Jump URL: {editmessage.jump_url}") 114 | else: 115 | try: 116 | await editmessage.edit(content=content, embed=None) 117 | await ctx.send(f"Message successfully edited. Jump URL: {editmessage.jump_url}") 118 | except discord.errors.Forbidden: 119 | await ctx.send("I can only edit my own messages.") 120 | -------------------------------------------------------------------------------- /reacticket/extensions/closesettings.py: -------------------------------------------------------------------------------- 1 | from redbot.core.utils.predicates import ReactionPredicate 2 | from redbot.core.utils.menus import start_adding_reactions 3 | from typing import Optional 4 | import discord 5 | import contextlib 6 | 7 | from reacticket.extensions.abc import MixinMeta 8 | from reacticket.extensions.mixin import RTMixin 9 | 10 | 11 | class ReacTicketCloseSettingsMixin(MixinMeta): 12 | @RTMixin.settings.group() 13 | async def closesettings(self, ctx): 14 | """Control what actions occur when a ticket is closed""" 15 | pass 16 | 17 | @closesettings.group() 18 | async def archive(self, ctx): 19 | """Customize settings for archiving ticket channels""" 20 | pass 21 | 22 | @archive.command(name="category") 23 | async def archive_category(self, ctx, category: discord.CategoryChannel): 24 | """Set the category to move closed ticket channels to.""" 25 | if not category.permissions_for(ctx.guild.me).manage_channels: 26 | await ctx.send( 27 | 'I require "Manage Channels" permissions in that category to execute that command.' 28 | ) 29 | return 30 | 31 | async with self.config.guild(ctx.guild).archive() as data: 32 | data["category"] = category.id 33 | await ctx.send( 34 | f"Closed ticket channels will now be moved to the {category.name} category, " 35 | "if Archive mode is enabled." 36 | ) 37 | 38 | @archive.command(name="enable") 39 | async def archive_enable(self, ctx, yes_or_no: bool = None): 40 | """Enable Archiving mode, to move the Ticket Channels to the set category once closed.""" 41 | async with self.config.guild(ctx.guild).archive() as data: 42 | if yes_or_no is None: 43 | data["enabled"] = not data["enabled"] 44 | yes_or_no = data["enabled"] 45 | else: 46 | data["enabled"] = yes_or_no 47 | 48 | if yes_or_no: 49 | await ctx.send("Archiving mode is now enabled.") 50 | else: 51 | await ctx.send("Archiving mode is now disabled.") 52 | 53 | @closesettings.command() 54 | async def reports(self, ctx, channel: discord.TextChannel = None): 55 | """Set a channel to make a mini report in when a ticket is closed or opened. 56 | 57 | If left blank, this will disable reports.""" 58 | saving = getattr(channel, "id", 0) 59 | await self.config.guild(ctx.guild).report.set(saving) 60 | 61 | if not channel: 62 | await ctx.send("Reporting has been disabled.") 63 | else: 64 | await ctx.send(f"Reporting channel has been set to {channel.mention}") 65 | 66 | @closesettings.command() 67 | async def dm(self, ctx, yes_or_no: bool = None): 68 | """Set whether or not to send a DM to the ticket author on ticket close.""" 69 | if yes_or_no is None: 70 | yes_or_no = not await self.config.guild(ctx.guild).dm() 71 | 72 | await self.config.guild(ctx.guild).dm.set(yes_or_no) 73 | if yes_or_no: 74 | await ctx.send("Users will now be DMed a message when their ticket is closed.") 75 | else: 76 | await ctx.send("Users will no longer be DMed a message when their ticket is closed.") 77 | 78 | @closesettings.command(name="closeonleave") 79 | async def close_ticket_on_leave(self, ctx, toggle: Optional[bool] = None): 80 | """Set whether to automatically close tickets if the ticket author leaves.""" 81 | if toggle is None: 82 | toggle = not await self.config.guild(ctx.guild).closeonleave() 83 | 84 | await self.config.guild(ctx.guild).closeonleave.set(toggle) 85 | if toggle: 86 | await ctx.send( 87 | "Tickets will now be automatically closed if the author leaves the server." 88 | ) 89 | else: 90 | await ctx.send("Tickets will be kept open even if the author leaves the server") 91 | 92 | @closesettings.command(name="prune", aliases=["cleanup", "purge"]) 93 | async def ticket_channel_prune(self, ctx, skip_confirmation: bool = False): 94 | """Clean out channels under the archive category. 95 | 96 | Pass a boolean value of True to skip confirmation message.""" 97 | category = self.bot.get_channel((await self.config.guild(ctx.guild).archive())["category"]) 98 | if not category: 99 | await ctx.send("Your archive category no longer exists!") 100 | return 101 | 102 | channels = category.text_channels 103 | 104 | if not skip_confirmation: 105 | message = await ctx.send( 106 | "Are you sure you want to remove all archived ticket channels? " 107 | f"This will delete {len(channels)} Text Channels." 108 | ) 109 | 110 | start_adding_reactions(message, ReactionPredicate.YES_OR_NO_EMOJIS) 111 | pred = ReactionPredicate.yes_or_no(message, ctx.author) 112 | await self.bot.wait_for("reaction_add", check=pred) 113 | 114 | if skip_confirmation or pred.result is True: 115 | progress = await ctx.send("Purging text channels...") 116 | for channel in channels: 117 | try: 118 | await channel.delete() 119 | except discord.Forbidden: 120 | await ctx.send( 121 | "I do not have permission to delete those text channels. " 122 | 'Make sure I have both "Manage Channels" and "View Channels".' 123 | ) 124 | return 125 | except discord.HTTPException: 126 | continue 127 | 128 | with contextlib.suppress(discord.HTTPException): 129 | await progress.edit(content=f"Successfully pruned {len(channels)} channels.") 130 | else: 131 | await ctx.send("Channel purge cancelled.") 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toxic-Cogs 2 | [![Discord server](https://discordapp.com/api/guilds/540613833237069836/embed.png)](https://discord.gg/vQZTdB9) 3 | [![Black coding style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) 4 | [![Red cogs](https://img.shields.io/badge/Red--DiscordBot-cogs-red.svg)](https://github.com/Cog-Creators/Red-DiscordBot/tree/V3/develop) 5 | [![discord.py](https://img.shields.io/badge/discord-py-blue.svg)](https://github.com/Rapptz/discord.py) 6 | 7 | Cogs for Red - Discord Bot by TwentySix. 8 | **** 9 | ## Table of Contents 10 | * [Information](#information) 11 | * [How to Install](#how-to-install) 12 | * [Descriptions](#descriptions) 13 | * [Credits](#credits) 14 | * [Bugs and Help](#bugs-and-help) 15 | * [Helpful Libraries](#helpful-libraries) 16 | * [Required Libraries](#required-libraries) 17 | **** 18 | ### Information 19 | 20 | The Toxic-Cogs repo holds a bunch of different cogs, some being tools (listpermissions, maintenance), some being for fun (like simon, twenty (2048)) and some others giving information (sw (Star Wars)). You can find descriptions of them below in the table. 21 | 22 | [Back to Table of Contents](#table-of-contents) 23 | **** 24 | ### How to install 25 | 26 | These cogs are made for [Red V3](https://github.com/Cog-Creators/Red-DiscordBot) by TwentySix. Using Red V3, you can add my repo by doing `[p]repo add Toxic-Cogs https://github.com/NeuroAssassin/Toxic-Cogs master`, then using `[p]cog install Toxic-Cogs ` (with `[p]` being your prefix) 27 | 28 | [Back to Table of Contents](#table-of-contents) 29 | **** 30 | ### Descriptions 31 | 32 | | Cog | Description | 33 | | --- | ----------- | 34 | | Color |
Get information about colorsProvide either the name, rgb, hexadecimal or hsl value of a color and get the rgb, hexadecimal and hsl value about it
| 35 | | Commandchart |
Get the latest commandsGet the latest commands from the last certain amount of messages in a certain channel
| 36 | | Cooldown |
Set cooldowns for commandsOverride (not recommended) or set cooldowns for commands to make sure your users don't commands too much
| 37 | | ListPermissions |
List the permissions of a role or userGet the permissions of a user or role in a certain channel or guild-wide.
| 38 | | Minesweeper |
Play minesweeper in DiscordPlay minesweeper interactively on the bot or with spoilers
| 39 | | Simon |
Play Simon in DiscordPlay Simon in Discord and guess the correct sequences
| 40 | | Twenty |
Play 2048 in DiscordPlay 2048 in Discord with reactions
| 41 | | UpdateChecker |
Get notifications when there is an update for one of your repositories added to your botHave your bot tell you in DM or a channel when there is an update for one of the repos added to your bot
| 42 | | SW |
Get Star Wars information through DiscordGet info about something in Star Wars using this cog
| 43 | | Maintenance |
Put your bot on maintenancePut your bot on maintenance, telling people who are not in the whitelist for the maintenance that the bot is on maintenance, and will not respond to commands
| 44 | | Evolution |
Buy animals with economy credits and get more credits!A cog that I made after a mobile app, kinda cheesy but fun.
| 45 | | Deleter |
Auto-delete messages after a certain amount of timeDelete messages after a certain amount of specified time, after the message was sent
| 46 | | Editor |
Edit messages sent by the botAllows an administrator to edit one of the bot's messages, by either copying the content and/or embed from a previously sent message from the bot, or by the specified content
| 47 | | Targeter |
Target users in the guild based upon the passed argumentsAllows arguments for dates, roles, names, activities, statuses or devices.
| 48 | | Scanner |
Auto-delete and report messages that are considered inappropriateBot Owner must set credentials first before use
| 49 | 50 | [Back to Table of Contents](#table-of-contents) 51 | **** 52 | ### Credits 53 | 54 | Credit to Aikaterna's chatchart cog, which I based my commandchart cog off of (which was also a requested cog on cogboard.red). You can find that cog here: https://github.com/aikaterna/aikaterna-cogs/tree/v3/chatchart. 55 | 56 | Thanks to: 57 | + Yukirin for suggesting/asking for the commandchart cog. 58 | + Olaroll for suggesting/asking for the editor cog 59 | + kennnyshiwa for suggestin/asking for the deleter cog 60 | + Other people in Red for helping me with coming up with ideas and helping me find shortcuts to some things 61 | 62 | [Back to Table of Contents](#table-of-contents) 63 | **** 64 | ### Bugs and Help 65 | For bugs, contact me at Neuro Assassin#4779 <@473541068378341376>. It would be best though to join my support server, where other people could help you too. You can find the invite button at the top of this file. 66 | 67 | [Back to Table of Contents](#table-of-contents) 68 | **** 69 | ### Helpful Libraries 70 | [PrettyTable](https://pypi.org/project/PrettyTable/) is helpful for the SQL cog. It makes the showing of the data much nicer, neater and more comprehendible. 71 | 72 | [Back to Table of Contents](#table-of-contents) 73 | **** 74 | ### Required Libraries 75 | [Matplotlib](https://pypi.org/project/matplotlib/) and [pytz](https://pypi.org/project/pytz/) are required for the commandchart cog. 76 | 77 | [Colour](https://pypi.org/project/colour/) is required for the color cog. 78 | 79 | [PrettyTable](https://pypi.org/project/PrettyTable/) and [FuzzyWuzzy](https://pypi.org/project/fuzzywuzzy/) are required for the listpermissions cog. 80 | 81 | [Back to Table of Contents](#table-of-contents) 82 | -------------------------------------------------------------------------------- /evolution/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import traceback 4 | from typing import TYPE_CHECKING 5 | 6 | from redbot.core import Config 7 | from redbot.core.bot import Red 8 | from redbot.core.utils.menus import menu 9 | 10 | if TYPE_CHECKING: 11 | from .evolution import Evolution 12 | 13 | 14 | class EvolutionUtils: 15 | def __init__(self, cog): 16 | self.bot: Red = cog.bot 17 | self.conf: Config = cog.conf 18 | self.cog: Evolution = cog 19 | 20 | @staticmethod 21 | def get_total_price(level, bought, amount, bt=True): 22 | total = 0 23 | for x in range(amount): 24 | normal = level * 800 25 | level_tax = ((2**level) * 10) - 200 26 | if bt: 27 | tax = bought * 300 28 | extra = x * 300 29 | else: 30 | tax = 0 31 | extra = 0 32 | total += normal + level_tax + tax + extra 33 | return total 34 | 35 | @property 36 | def levels(self): 37 | return { 38 | 1: {100: 10}, 39 | 2: {90: 10, 100: 100}, 40 | 3: {80: 10, 100: 100}, 41 | 4: {70: 10, 100: 100}, 42 | 5: {60: 10, 100: 100}, 43 | 6: {50: 10, 90: 100, 100: 1000}, 44 | 7: {40: 10, 80: 100, 100: 1000}, 45 | 8: {30: 10, 70: 100, 100: 1000}, 46 | 9: {20: 10, 60: 100, 100: 1000}, 47 | 10: {10: 10, 50: 100, 100: 1000}, 48 | 11: {40: 100, 90: 1000, 100: 1500}, 49 | 12: {30: 100, 80: 1000, 100: 1500}, 50 | 13: {20: 100, 70: 1000, 100: 1500}, 51 | 14: {10: 100, 60: 1000, 100: 1500}, 52 | 15: {50: 1000, 100: 1500}, 53 | 16: {40: 1000, 100: 1500}, 54 | 17: {30: 1000, 100: 1500}, 55 | 18: {20: 1000, 100: 1500}, 56 | 19: {10: 1000, 100: 1500}, 57 | 20: {90: 1500, 100: 2000}, 58 | 21: {80: 1500, 100: 2000}, 59 | 22: {70: 1500, 100: 2000}, 60 | 23: {60: 1500, 100: 2000}, 61 | 24: {50: 1500, 100: 2000}, 62 | 25: {100: 2000}, 63 | } 64 | 65 | @property 66 | def delays(self): 67 | return { 68 | 1: 86400, # 24 hours 69 | 2: 64800, # 18 hours 70 | 3: 43200, # 12 hours 71 | 4: 39600, # 11 hours 72 | 5: 36000, # 10 hours 73 | 6: 32400, # 9 hours 74 | 7: 28800, # 8 hours 75 | 8: 25200, # 7 hours 76 | 9: 21600, # 6 hours 77 | 10: 18000, # 5 hours 78 | 11: 14400, # 4 hours 79 | 12: 10800, # 3 hours 80 | 13: 7200, # 2 hours 81 | 14: 3600, # 1 hour 82 | 15: 3000, # 50 minutes 83 | 16: 2400, # 40 minutes 84 | 17: 1800, # 30 minutes 85 | 18: 1200, # 20 minutes 86 | 19: 600, # 10 minutes 87 | 20: 420, # 7 minutes 88 | 21: 300, # 5 minutes 89 | 22: 240, # 4 minutes 90 | 23: 180, # 3 minutes 91 | 24: 120, # 2 minutes 92 | 25: 60, # 1 minute 93 | 26: 60, # 1 minute (Just in case) 94 | } 95 | 96 | @property 97 | def randlvl_chances(self): 98 | return [ 99 | 1, 100 | 2, 101 | 3, 102 | 4, 103 | 4, 104 | 5, 105 | 5, 106 | 5, 107 | 6, 108 | 6, 109 | 6, 110 | 6, 111 | 7, 112 | 7, 113 | 7, 114 | 7, 115 | 8, 116 | 8, 117 | 8, 118 | 8, 119 | 9, 120 | 9, 121 | 9, 122 | 9, 123 | 10, 124 | 10, 125 | 10, 126 | 10, 127 | 10, 128 | 10, 129 | 11, 130 | 11, 131 | 11, 132 | 11, 133 | 11, 134 | 12, 135 | 12, 136 | 12, 137 | 12, 138 | 12, 139 | 12, 140 | 13, 141 | 13, 142 | 13, 143 | 13, 144 | 13, 145 | 14, 146 | 14, 147 | 14, 148 | 14, 149 | 14, 150 | 15, 151 | 15, 152 | 15, 153 | 15, 154 | 16, 155 | 16, 156 | 16, 157 | 17, 158 | 17, 159 | 18, 160 | 19, 161 | 20, 162 | ] 163 | 164 | @property 165 | def randamt_chances(self): 166 | return [1, 1, 2, 2, 2, 3, 3, 3, 4, 5] 167 | 168 | async def shop_control_callback(self, ctx, pages, controls, message, page, timeout, emoji): 169 | description = message.embeds[0].description 170 | level = int(description.split(" ")[1]) 171 | self.bot.loop.create_task(ctx.invoke(self.cog.store, level=level)) 172 | return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) 173 | 174 | def format_task(self, task): 175 | state = task["state"].lower() 176 | if task["exc"]: 177 | e = task["exc"] 178 | exc = traceback.format_exception(type(e), e, e.__traceback__) 179 | exc_output = ( 180 | f"Please report the following error to Neuro Assassin: ```py\n{''.join(exc)}```" 181 | ) 182 | else: 183 | exc_output = "No error has been encountered." 184 | return f"Task is currently {state}. {exc_output}" 185 | 186 | def init_config(self): 187 | default_user = { 188 | "animal": "", 189 | "animals": {}, 190 | "multiplier": 1.0, 191 | "bought": {}, 192 | "stash": {"animals": {}, "perks": {}}, 193 | } 194 | default_guild = {"cartchannel": 0, "last": 0} 195 | default_global = { 196 | "travelercooldown": "2h", 197 | "lastcredited": {}, 198 | "lastdailyupdate": 0, 199 | "daily": {}, 200 | } 201 | for x in range(1, 27): 202 | default_global["lastcredited"][str(x)] = 0 203 | 204 | self.conf.register_user(**default_user) 205 | self.conf.register_guild(**default_guild) 206 | self.conf.register_global(**default_global) 207 | 208 | async def initialize(self): 209 | config = await self.cog.conf.all_users() 210 | for k, v in config.items(): 211 | self.cog.cache[k] = v 212 | -------------------------------------------------------------------------------- /esolang/whitespace.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import io 3 | 4 | 5 | # Exceptions 6 | class EmptyStack(SyntaxError): 7 | """Raised when the the stack is accessed in an empty state""" 8 | 9 | def __init__(self, message: str, code: str, pointer: int): 10 | if pointer > 50: 11 | line = pointer // 50 12 | start = line * 50 13 | code = code[start : start + 50] 14 | pointer %= 50 15 | else: 16 | line = 0 17 | super().__init__( 18 | message, 19 | ("program.ws", line + 1, pointer + 1, code), 20 | ) 21 | 22 | 23 | class InvalidNumber(SyntaxError): 24 | """Raised when a number fails to be parsed""" 25 | 26 | def __init__(self, message: str, code: str, pointer: int): 27 | if pointer > 50: 28 | line = pointer // 50 29 | start = line * 50 30 | code = code[start : start + 50] 31 | pointer %= 50 32 | else: 33 | line = 0 34 | super().__init__( 35 | message, 36 | ("program.ws", line + 1, pointer + 1, code), 37 | ) 38 | 39 | 40 | # Core classes 41 | 42 | 43 | class Stack: 44 | def __init__(self, code: str): 45 | self._internal: List[int] = [] 46 | self.code = code 47 | self.pointer: int = 0 48 | 49 | def push(self, value: int) -> None: 50 | self._internal.append(value) 51 | 52 | def __pop(self) -> int: 53 | return self._internal.pop() 54 | 55 | def pop(self) -> int: 56 | try: 57 | return self.__pop() 58 | except IndexError: 59 | raise EmptyStack("Empty stack in pop call", self.code, self.pointer) 60 | 61 | def duplicate(self) -> None: 62 | try: 63 | a = self.__pop() 64 | except IndexError: 65 | a = 0 66 | self.push(a) 67 | self.push(a) 68 | 69 | def swap(self) -> None: 70 | try: 71 | a = self.__pop() 72 | except IndexError: 73 | raise EmptyStack("Empty stack in swap call", self.code, self.pointer) 74 | 75 | try: 76 | b = self.__pop() 77 | except IndexError: 78 | b = 0 79 | self.push(a) 80 | self.push(b) 81 | 82 | def addition(self) -> None: 83 | try: 84 | a, b = self.__pop(), self.__pop() 85 | except IndexError: 86 | raise EmptyStack("Empty stack in addition call", self.codemap, self.pointer) 87 | self.push(a + b) 88 | 89 | def subtraction(self) -> None: 90 | try: 91 | a, b = self.__pop(), self.__pop() 92 | except IndexError: 93 | raise EmptyStack("Empty stack in subtraction call", self.codemap, self.pointer) 94 | self.push(a - b) 95 | 96 | def multiplication(self) -> None: 97 | try: 98 | a, b = self.__pop(), self.__pop() 99 | except IndexError: 100 | raise EmptyStack("Empty stack in multiplication call", self.codemap, self.pointer) 101 | self.push(a * b) 102 | 103 | def division(self) -> None: 104 | try: 105 | a, b = self.__pop(), self.__pop() 106 | except IndexError: 107 | raise EmptyStack("Empty stack in division call", self.codemap, self.pointer) 108 | self.push(a // b) 109 | 110 | def modulo(self) -> None: 111 | try: 112 | a, b = self.__pop(), self.__pop() 113 | except IndexError: 114 | raise EmptyStack("Empty stack in modulo call", self.codemap, self.pointer) 115 | self.push(a % b) 116 | 117 | 118 | class Whitespace: 119 | @staticmethod 120 | def clean_syntax(code: str) -> str: 121 | if code.startswith("```\n"): 122 | code = code[4:] 123 | if code.endswith("\n```"): 124 | code = code[:-4] 125 | code = "".join(filter(lambda x: x in ["\u2001", " ", "\n"], code)) 126 | code = code.replace("\u2001", "t").replace(" ", "s").replace("\n", "l") 127 | return code 128 | 129 | @staticmethod 130 | def parse_to_number(stack: Stack, number: str) -> int: 131 | try: 132 | sign = 1 if number[0] == "s" else -1 133 | 134 | number = number[1:].replace("s", "0").replace("t", "1") 135 | number = int(number, 2) 136 | return number * sign 137 | except IndexError: 138 | raise InvalidNumber( 139 | "Incorrect number: must be at least two chars", stack.code, stack.pointer 140 | ) 141 | 142 | @staticmethod 143 | def evaluate(code: str) -> io.StringIO: 144 | code: str = Whitespace.clean_syntax(code) 145 | stack: Stack = Stack(code) 146 | command: str = "" 147 | param: str = "" 148 | 149 | output: io.StringIO = io.StringIO("") 150 | 151 | while stack.pointer <= len(code): 152 | print(command) 153 | print(stack._internal) 154 | try: 155 | target = code[stack.pointer] 156 | except IndexError: 157 | target = "" 158 | if command == "ss": 159 | if target == "l": 160 | number = Whitespace.parse_to_number(stack, param) 161 | stack.push(number) 162 | command, param = "", "" 163 | elif target: 164 | param += target 165 | else: 166 | raise InvalidNumber( 167 | "Incorrect stack push: No argument supplied", stack.code, stack.pointer 168 | ) 169 | stack.pointer += 1 170 | continue 171 | elif command == "sls": 172 | stack.duplicate() 173 | command, param = "", "" 174 | elif command == "slt": 175 | stack.swap() 176 | command, param = "", "" 177 | elif command == "sll": 178 | stack.pop() 179 | command, param = "", "" 180 | elif command == "tsss": 181 | stack.addition() 182 | command, param = "", "" 183 | elif command == "tsst": 184 | stack.subtraction() 185 | command, param = "", "" 186 | elif command == "tssl": 187 | stack.multiplication() 188 | command, param = "", "" 189 | elif command == "tsts": 190 | stack.division() 191 | command, param = "", "" 192 | elif command == "tstt": 193 | stack.modulo() 194 | command, param = "", "" 195 | elif command == "tlss": 196 | output.write(chr(stack.pop())) 197 | command, param = "", "" 198 | elif command == "tlst": 199 | output.write(str(stack.pop())) 200 | command, param = "", "" 201 | command += target 202 | stack.pointer += 1 203 | return output 204 | -------------------------------------------------------------------------------- /commandchart/commandchart.py: -------------------------------------------------------------------------------- 1 | """This cog is mostly derived from Aikaterna's cog "chatchart" 2 | You can find the cog here: https://github.com/aikaterna/aikaterna-cogs/tree/v3/chatchart 3 | 4 | This cog was also a cog requested by Yukirin on the cogboard (cogboard.red).""" 5 | 6 | """ 7 | MIT License 8 | 9 | Copyright (c) 2018-Present NeuroAssassin 10 | Copyright (c) 2016-present aikaterna 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | """ 30 | 31 | 32 | import heapq 33 | import typing 34 | from io import BytesIO 35 | 36 | import discord 37 | from redbot.core import commands 38 | 39 | import matplotlib 40 | import matplotlib.pyplot as plt 41 | 42 | matplotlib.use("agg") 43 | 44 | 45 | plt.switch_backend("agg") 46 | 47 | 48 | class CommandChart(commands.Cog): 49 | """Shows the commands most used in a certain channel within the last so-and-so messages""" 50 | 51 | def __init__(self, bot): 52 | self.bot = bot 53 | 54 | __author__ = "Neuro Assassin#4779 <@473541068378341376>" 55 | 56 | async def red_delete_data_for_user(self, **kwargs): 57 | """This cog does not store user data""" 58 | return 59 | 60 | async def command_from_message(self, m: discord.Message): 61 | message_context = await self.bot.get_context(m) 62 | if not message_context.valid: 63 | return None 64 | 65 | maybe_command = message_context.command 66 | command = maybe_command 67 | while isinstance(maybe_command, commands.Group): 68 | message_context.view.skip_ws() 69 | possible = message_context.view.get_word() 70 | maybe_command = maybe_command.all_commands.get(possible, None) 71 | if maybe_command: 72 | command = maybe_command 73 | 74 | return command 75 | 76 | def create_chart(self, top, others, channel): 77 | plt.clf() 78 | sizes = [x[1] for x in top] 79 | labels = ["{} {:g}%".format(x[0], x[1]) for x in top] 80 | if len(top) >= 20: 81 | sizes = sizes + [others] 82 | labels = labels + ["Others {:g}%".format(others)] 83 | if len(channel.name) >= 19: 84 | channel_name = "{}...".format(channel.name[:19]) 85 | else: 86 | channel_name = channel.name 87 | title = plt.title("Stats in #{}".format(channel_name), color="white") 88 | title.set_va("top") 89 | title.set_ha("center") 90 | plt.gca().axis("equal") 91 | colors = [ 92 | "r", 93 | "darkorange", 94 | "gold", 95 | "y", 96 | "olivedrab", 97 | "green", 98 | "darkcyan", 99 | "mediumblue", 100 | "darkblue", 101 | "blueviolet", 102 | "indigo", 103 | "orchid", 104 | "mediumvioletred", 105 | "crimson", 106 | "chocolate", 107 | "yellow", 108 | "limegreen", 109 | "forestgreen", 110 | "dodgerblue", 111 | "slateblue", 112 | "gray", 113 | ] 114 | pie = plt.pie(sizes, colors=colors, startangle=0) 115 | plt.legend( 116 | pie[0], 117 | labels, 118 | bbox_to_anchor=(0.7, 0.5), 119 | loc="center", 120 | fontsize=10, 121 | bbox_transform=plt.gcf().transFigure, 122 | facecolor="#ffffff", 123 | ) 124 | plt.subplots_adjust(left=0.0, bottom=0.1, right=0.45) 125 | image_object = BytesIO() 126 | plt.savefig(image_object, format="PNG", facecolor="#36393E") 127 | image_object.seek(0) 128 | return image_object 129 | 130 | @commands.bot_has_permissions(embed_links=True) 131 | @commands.guild_only() 132 | @commands.command() 133 | async def commandchart( 134 | self, ctx, channel: typing.Optional[discord.TextChannel] = None, number: int = 5000 135 | ): 136 | """See the used commands in a certain channel within a certain amount of messages.""" 137 | e = discord.Embed(description="Loading...", color=0x000099) 138 | e.set_thumbnail(url="https://cdn.discordapp.com/emojis/544517783224975387.gif?v=1") 139 | em = await ctx.send(embed=e) 140 | 141 | if not channel: 142 | channel = ctx.channel 143 | if not channel.permissions_for(ctx.message.author).read_messages == True: 144 | await em.delete() 145 | return await ctx.send("You do not have the proper permissions to access that channel.") 146 | 147 | message_list = [] 148 | try: 149 | async for msg in channel.history(limit=number): 150 | # Thanks Sinbad 151 | com = await self.command_from_message(msg) 152 | if com != None: 153 | message_list.append(com.qualified_name) 154 | except discord.errors.Forbidden: 155 | await em.delete() 156 | return await ctx.send("I do not have permission to look at that channel.") 157 | msg_data = {"total count": 0, "commands": {}} 158 | 159 | for msg in message_list: 160 | if len(msg) >= 20: 161 | short_name = "{}...".format(msg[:20]) 162 | else: 163 | short_name = msg 164 | if short_name in msg_data["commands"]: 165 | msg_data["commands"][short_name]["count"] += 1 166 | msg_data["total count"] += 1 167 | else: 168 | msg_data["commands"][short_name] = {} 169 | msg_data["commands"][short_name]["count"] = 1 170 | msg_data["total count"] += 1 171 | 172 | if msg_data["commands"] == {}: 173 | await em.delete() 174 | return await ctx.send("No commands have been run in that channel.") 175 | for command in msg_data["commands"]: 176 | pd = float(msg_data["commands"][command]["count"]) / float(msg_data["total count"]) 177 | msg_data["commands"][command]["percent"] = round(pd * 100, 1) 178 | 179 | top_ten = heapq.nlargest( 180 | 20, 181 | [ 182 | (x, msg_data["commands"][x][y]) 183 | for x in msg_data["commands"] 184 | for y in msg_data["commands"][x] 185 | if y == "percent" 186 | ], 187 | key=lambda x: x[1], 188 | ) 189 | others = 100 - sum(x[1] for x in top_ten) 190 | img = self.create_chart(top_ten, others, channel) 191 | await em.delete() 192 | await ctx.channel.send(file=discord.File(img, "chart.png")) 193 | -------------------------------------------------------------------------------- /simon/simon.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2018-Present NeuroAssassin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | import asyncio 26 | import random 27 | 28 | import discord 29 | from redbot.core import commands 30 | 31 | 32 | class Simon(commands.Cog): 33 | """Play Simon, and guess the write number sequence! 34 | 35 | WARNING: 36 | This cog sends a lot of messages, edits emojis and edits messages. It may use up rate limits heavily. 37 | """ 38 | 39 | def __init__(self, bot): 40 | self.bot = bot 41 | 42 | __author__ = "Neuro Assassin#4779 <@473541068378341376>" 43 | 44 | async def red_delete_data_for_user(self, **kwargs): 45 | """This cog does not store user data""" 46 | return 47 | 48 | @commands.bot_has_permissions(add_reactions=True) 49 | @commands.command() 50 | async def simon(self, ctx): 51 | """Start a game of Simon.""" 52 | await ctx.send( 53 | "Starting game...\n**RULES:**\n```1. When you are ready for the sequence, click the green checkmark.\n2. Watch the sequence carefully, then repeat it back into chat. For example, if the 1 then the 2 changed, I would type 12.\n3. You are given 10 seconds to repeat the sequence.\n4. When waiting for confirmation for next sequence, click the green check within 5 minutes of the bot being ready.\n5. Answer as soon as you can once the bot adds the stop watch emoji.```" 54 | ) 55 | board = [[1, 2], [3, 4]] 56 | level = [1, 4] 57 | points = 0 58 | message = await ctx.send("```" + self.print_board(board) + "```") 59 | await message.add_reaction("\u2705") 60 | await message.add_reaction("\u274C") 61 | await ctx.send("Click the Green Check Reaction when you are ready for the sequence.") 62 | 63 | def check(reaction, user): 64 | return ( 65 | (user.id == ctx.author.id) 66 | and (str(reaction.emoji) in ["\u2705", "\u274C"]) 67 | and (reaction.message.id == message.id) 68 | ) 69 | 70 | randoms = [] 71 | for x in range(4): 72 | randoms.append(random.randint(1, 4)) 73 | 74 | while True: 75 | try: 76 | reaction, user = await self.bot.wait_for( 77 | "reaction_add", check=check, timeout=300.0 78 | ) 79 | except asyncio.TimeoutError: 80 | await message.delete() 81 | await ctx.send( 82 | f"Game has ended due to no response for starting the next sequence. You got {points} sequence{'s' if points != 1 else ''} correct!" 83 | ) 84 | return 85 | else: 86 | if str(reaction.emoji) == "\u274C": 87 | await message.delete() 88 | await ctx.send( 89 | f"Game has ended due to no response. You got {points} sequence{'s' if points != 1 else ''} correct!" 90 | ) 91 | return 92 | await message.remove_reaction("\u2705", self.bot.user) 93 | await message.remove_reaction("\u274C", self.bot.user) 94 | try: 95 | await message.remove_reaction("\u2705", ctx.author) 96 | except discord.errors.Forbidden: 97 | pass 98 | await message.add_reaction("\u26A0") 99 | for x in randoms: 100 | await asyncio.sleep(1) 101 | if x == 1: 102 | board[0][0] = "-" 103 | await message.edit(content="```" + self.print_board(board) + "```") 104 | await asyncio.sleep(level[0]) 105 | board[0][0] = 1 106 | elif x == 2: 107 | board[0][1] = "-" 108 | await message.edit(content="```" + self.print_board(board) + "```") 109 | await asyncio.sleep(level[0]) 110 | board[0][1] = 2 111 | elif x == 3: 112 | board[1][0] = "-" 113 | await message.edit(content="```" + self.print_board(board) + "```") 114 | await asyncio.sleep(level[0]) 115 | board[1][0] = 3 116 | elif x == 4: 117 | board[1][1] = "-" 118 | await message.edit(content="```" + self.print_board(board) + "```") 119 | await asyncio.sleep(level[0]) 120 | board[1][1] = 4 121 | await message.edit(content="```" + self.print_board(board) + "```") 122 | await message.remove_reaction("\u26A0", self.bot.user) 123 | answer = "".join(list(map(str, randoms))) 124 | await message.add_reaction("\u23F1") 125 | 126 | def check_t(m): 127 | return (m.author.id == ctx.author.id) and (m.content.isdigit()) 128 | 129 | try: 130 | user_answer = await self.bot.wait_for("message", check=check_t, timeout=10.0) 131 | except asyncio.TimeoutError: 132 | await ctx.send( 133 | f"Sorry {ctx.author.mention}! You took too long to answer. You got {points} sequence{'s' if points != 1 else ''} correct!" 134 | ) 135 | await message.remove_reaction("\u23F1", self.bot.user) 136 | return 137 | else: 138 | try: 139 | await user_answer.delete() 140 | except discord.errors.Forbidden: 141 | pass 142 | await message.remove_reaction("\u23F1", self.bot.user) 143 | if str(user_answer.content) == str(answer): 144 | await message.add_reaction("\U0001F44D") 145 | else: 146 | await message.add_reaction("\U0001F6AB") 147 | await ctx.send( 148 | f"Sorry, but that was the incorrect pattern. The pattern was {answer}. You got {points} sequence{'s' if points != 1 else ''} correct!" 149 | ) 150 | return 151 | another_message = await ctx.send("Sequence was correct.") 152 | points += 1 153 | await asyncio.sleep(3) 154 | await message.remove_reaction("\U0001F44D", self.bot.user) 155 | await message.add_reaction("\u2705") 156 | await message.add_reaction("\u274C") 157 | await another_message.delete() 158 | level[0] *= 0.90 159 | randoms.append(random.randint(1, 4)) 160 | 161 | def print_board(self, board): 162 | col_width = max(len(str(word)) for row in board for word in row) + 2 # padding 163 | whole_thing = "" 164 | for row in board: 165 | whole_thing += "".join(str(word).ljust(col_width) for word in row) + "\n" 166 | return whole_thing 167 | -------------------------------------------------------------------------------- /dashboard/abc/roles.py: -------------------------------------------------------------------------------- 1 | from redbot.core import commands, checks 2 | from redbot.core.utils.chat_formatting import box, humanize_list, inline 3 | import discord 4 | 5 | from dashboard.abc.abc import MixinMeta 6 | from dashboard.abc.mixin import dashboard 7 | 8 | from dashboard.baserpc import HUMANIZED_PERMISSIONS 9 | 10 | 11 | class DashboardRolesMixin(MixinMeta): 12 | @checks.guildowner() 13 | @dashboard.group() 14 | async def roles(self, ctx: commands.Context): 15 | """Customize the roles that have permission to certain parts of the dashboard.""" 16 | 17 | @roles.command() 18 | async def create(self, ctx: commands.Context, role: discord.Role, *permissions): 19 | """Register a new discord role to access certain parts of the dashboard.""" 20 | roles = await self.config.guild(ctx.guild).roles() 21 | if role.id in [r["roleid"] for r in roles]: 22 | await ctx.send( 23 | f"That role is already registered. Please edit with `{ctx.prefix}dashboard roles edit`." 24 | ) 25 | return 26 | assigning = [] 27 | missing = [] 28 | disallowed = await self.config.disallowedperms() 29 | for p in permissions: 30 | if p.lower() in HUMANIZED_PERMISSIONS and not p.lower() in disallowed: 31 | assigning.append(p.lower()) 32 | else: 33 | missing.append(p) 34 | if assigning: 35 | async with self.config.guild(ctx.guild).roles() as data: 36 | data.append({"roleid": role.id, "perms": assigning}) 37 | self.configcache[ctx.guild.id]["roles"].append({"roleid": role.id, "perms": assigning}) 38 | else: 39 | await ctx.send("Failed to identify any permissions in list. Please try again.") 40 | return 41 | if await ctx.embed_requested(): 42 | e = discord.Embed( 43 | title="Role Registered", 44 | description=( 45 | f"**Permissions assigned**: {humanize_list(list(map(inline, assigning)))}\n" 46 | f"**Permissions unidentified**: {humanize_list(list(map(inline, missing or ['None'])))}" 47 | ), 48 | color=(await ctx.embed_color()), 49 | ) 50 | await ctx.send(embed=e) 51 | else: 52 | await ctx.send( 53 | f"**Role registered**```css\n" 54 | f"[Permissions assigned]: {humanize_list(assigning)}\n" 55 | f"[Permissions unidentified]: {humanize_list(missing or ['None'])}" 56 | "```" 57 | ) 58 | 59 | @roles.command() 60 | async def edit(self, ctx: commands.Context, role: discord.Role, *permissions): 61 | """Edit the permissions registered with a registered role.""" 62 | changing = [] 63 | missing = [] 64 | disallowed = await self.config.disallowedperms() 65 | for p in permissions: 66 | if p.lower() in HUMANIZED_PERMISSIONS and not p.lower() in disallowed: 67 | changing.append(p.lower()) 68 | else: 69 | missing.append(p.lower()) 70 | 71 | if not changing: 72 | await ctx.send("Failed to identify any permissions in list. Please try again.") 73 | return 74 | 75 | roles = await self.config.guild(ctx.guild).roles() 76 | try: 77 | ro = [ro for ro in roles if ro["roleid"] == role.id][0] 78 | except IndexError: 79 | return await ctx.send("That role is not registered.") 80 | 81 | del roles[roles.index(ro)] 82 | 83 | added = [] 84 | removed = [] 85 | for p in changing: 86 | if p in ro["perms"]: 87 | ro["perms"].remove(p) 88 | removed.append(p) 89 | else: 90 | ro["perms"].append(p) 91 | added.append(p) 92 | 93 | if not ro["perms"]: 94 | await ctx.send( 95 | f"Failed to edit role. If you wish to remove all permissions from the role, please use `{ctx.clean_prefix}dashboard roles delete`." 96 | ) 97 | return 98 | 99 | roles.append(ro) 100 | await self.config.guild(ctx.guild).roles.set(roles) 101 | self.configcache[ctx.guild.id]["roles"] = roles 102 | 103 | if await ctx.embed_requested(): 104 | e = discord.Embed( 105 | title="Successfully edited role", 106 | description=( 107 | f"**Permissions added**: {humanize_list(list(map(inline, added or ['None'])))}\n" 108 | f"**Permissions removed**: {humanize_list(list(map(inline, removed or ['None'])))}\n" 109 | f"**Permissions unidentified**: {humanize_list(list(map(inline, missing or ['None'])))}\n" 110 | ), 111 | color=(await ctx.embed_color()), 112 | ) 113 | await ctx.send(embed=e) 114 | else: 115 | await ctx.send( 116 | "**Successfully edited role**```css\n" 117 | f"[Permissions added]: {humanize_list(added or ['None'])}\n" 118 | f"[Permissions removed]: {humanize_list(removed or ['None'])}\n" 119 | f"[Permissions unidentified]: {humanize_list(missing or ['None'])}" 120 | "```" 121 | ) 122 | 123 | @roles.command() 124 | async def delete(self, ctx: commands.Context, *, role: discord.Role): 125 | """Unregister a role from the dashboard.""" 126 | roles = await self.config.guild(ctx.guild).roles() 127 | try: 128 | ro = [ro for ro in roles if ro["roleid"] == role.id][0] 129 | except IndexError: 130 | return await ctx.send("That role is not registered.") 131 | 132 | del roles[roles.index(ro)] 133 | await self.config.guild(ctx.guild).roles.set(roles) 134 | self.configcache[ctx.guild.id]["roles"] = roles 135 | 136 | await ctx.send("Successfully deleted role.") 137 | 138 | @roles.command(name="list") 139 | async def roles_list(self, ctx: commands.Context): 140 | """List roles registered with dashboard.""" 141 | data = await self.config.guild(ctx.guild).roles() 142 | roles = [ 143 | ctx.guild.get_role(role["roleid"]).mention 144 | for role in data 145 | if ctx.guild.get_role(role["roleid"]) 146 | ] 147 | if not roles: 148 | return await ctx.send("No roles set.") 149 | e = discord.Embed(title="Registered roles", description="\n".join(roles), color=0x0000FF) 150 | await ctx.send(embed=e) 151 | 152 | @roles.command() 153 | async def info(self, ctx: commands.Context, *, role: discord.Role): 154 | """List permissions for a registered role.""" 155 | roles = await self.config.guild(ctx.guild).roles() 156 | try: 157 | r = [ro for ro in roles if ro["roleid"] == role.id][0] 158 | except IndexError: 159 | return await ctx.send("That role is not registered.") 160 | 161 | description = "" 162 | disallowed = await self.config.disallowedperms() 163 | 164 | if await ctx.embed_requested(): 165 | for perm in r["perms"]: 166 | if perm in disallowed: 167 | continue 168 | description += f"{inline(perm.title())}: {HUMANIZED_PERMISSIONS[perm]}\n" 169 | e = discord.Embed(description=f"**Role {role.mention} permissions**\n", color=0x0000FF) 170 | e.description += description 171 | await ctx.send(embed=e) 172 | else: 173 | for perm in r["perms"]: 174 | if perm in disallowed: 175 | continue 176 | description += f"[{perm.title()}]: {HUMANIZED_PERMISSIONS[perm]}\n" 177 | await ctx.send(f"**Role {role.name} permissions**```css\n{description}```") 178 | 179 | @roles.command() 180 | async def perms(self, ctx: commands.Context): 181 | """Displays permission keywords matched with humanized descriptions.""" 182 | disallowed = await self.config.disallowedperms() 183 | if await ctx.embed_requested(): 184 | e = discord.Embed( 185 | title="Dashboard permissions", description="", color=(await ctx.embed_color()) 186 | ) 187 | for key, value in HUMANIZED_PERMISSIONS.items(): 188 | if key in disallowed: 189 | continue 190 | e.description += f"{inline(key.title())}: {value}\n" 191 | await ctx.send(embed=e) 192 | else: 193 | description = "" 194 | for key, value in HUMANIZED_PERMISSIONS.items(): 195 | if key in disallowed: 196 | continue 197 | description += f"[{key.title()}]: {value}\n" 198 | await ctx.send(f"**Dashboard permissions**```css\n{description}```") 199 | -------------------------------------------------------------------------------- /deleter/deleter.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2018-Present NeuroAssassin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | import asyncio 26 | import time 27 | from collections import defaultdict 28 | from copy import deepcopy as dc 29 | 30 | import discord 31 | from redbot.core import Config, commands 32 | from redbot.core.utils import AsyncIter 33 | from redbot.core.utils.chat_formatting import humanize_list 34 | 35 | 36 | class Deleter(commands.Cog): 37 | """Set channels for their messages to be auto-deleted after a specified amount of time. 38 | 39 | WARNING: This cog has potential API abuse AND SHOULD BE USED CAREFULLY! If you see any issues arise due to this, please report to Neuro Assassin or bot owner ASAP! 40 | """ 41 | 42 | def __init__(self, bot): 43 | self.bot = bot 44 | self.lock = asyncio.Lock() 45 | self.conf = Config.get_conf(self, identifier=473541068378341376) 46 | default_channel = {"wait": 0, "messages": {}} 47 | self.conf.register_channel(**default_channel) 48 | self.task = self.bot.loop.create_task(self.background_task()) 49 | 50 | def cog_unload(self): 51 | self.task.cancel() 52 | 53 | async def red_delete_data_for_user(self, **kwargs): 54 | """This cog does not store user data""" 55 | return 56 | 57 | async def background_task(self): 58 | await self.bot.wait_until_ready() 59 | while True: 60 | async with self.lock: 61 | cs = await self.conf.all_channels() 62 | async for channel, data in AsyncIter(cs.items()): 63 | if int(data["wait"]) == 0: 64 | continue 65 | c = self.bot.get_channel(int(channel)) 66 | if not c: 67 | continue 68 | old = dc(data) 69 | ms = dc(data["messages"]) 70 | async for message, wait in AsyncIter(ms.items()): 71 | if int(wait) <= time.time(): 72 | dontdelete = False 73 | try: 74 | m = await c.fetch_message(int(message)) 75 | if not m.pinned: 76 | await m.delete() 77 | except (discord.NotFound, discord.Forbidden): 78 | pass 79 | except discord.HTTPException: 80 | dontdelete = True 81 | if not dontdelete: 82 | del data["messages"][str(message)] 83 | if old != data: 84 | await self.conf.channel(c).messages.set(data["messages"]) 85 | await asyncio.sleep(10) 86 | 87 | @commands.Cog.listener() 88 | async def on_message(self, message): 89 | async with self.lock: 90 | c = await self.conf.channel(message.channel).all() 91 | if int(c["wait"]) == 0: 92 | return 93 | c["messages"][str(message.id)] = time.time() + int(c["wait"]) 94 | await self.conf.channel(message.channel).messages.set(c["messages"]) 95 | 96 | @commands.group(invoke_without_command=True) 97 | @commands.guild_only() 98 | @commands.mod_or_permissions(manage_messages=True) 99 | async def deleter(self, ctx): 100 | """Group command for commands dealing with auto-timed deletion. 101 | 102 | To see what channels are currently being tracked, use this command with no subcommands passed. 103 | """ 104 | async with self.lock: 105 | channels = await self.conf.all_channels() 106 | sending = "" 107 | for c, data in channels.items(): 108 | c = self.bot.get_channel(int(c)) 109 | if c is None: 110 | continue 111 | if c.guild.id == ctx.guild.id and int(data["wait"]) != 0: 112 | sending += f"{c.mention}: {data['wait']} seconds\n" 113 | if sending: 114 | await ctx.send(sending) 115 | else: 116 | await ctx.send( 117 | f"No channels are currently being tracked. Add one by using `{ctx.prefix}deleter channel`." 118 | ) 119 | 120 | @deleter.command() 121 | async def channel(self, ctx, channel: discord.TextChannel, wait): 122 | """Set the amount of time after a message sent in the specified channel is supposed to be deleted. 123 | 124 | There may be about an approximate 10 second difference between the wait and the actual time the message is deleted, due to rate limiting and cooldowns. 125 | 126 | Wait times must be greater than or equal to 5 seconds, or 0 to disable auto-timed deletion. If you would like to use time specifications other than seconds, suffix the wait argument with one below: 127 | 128 | s => seconds (ex. 5s => 5 seconds) 129 | m => minutes (ex. 5m => 5 minutes) 130 | h => hours (ex. 5h => 5 hours) 131 | d => days (ex. 5d => 5 days) 132 | w => weeks (ex. 5w => 5 weeks""" 133 | if wait != "0": 134 | ttype = None 135 | wait = wait.lower() 136 | wt = wait[:-1] 137 | og = wait[:-1] 138 | if not wt.isdigit(): 139 | return await ctx.send( 140 | "Invalid amount of time. There is a non-number in your `wait` argument, not including the time type." 141 | ) 142 | wt = int(wt) 143 | if wait.endswith("s"): 144 | ttype = "second" 145 | elif wait.endswith("m"): 146 | ttype = "minute" 147 | wt *= 60 148 | elif wait.endswith("h"): 149 | ttype = "hour" 150 | wt *= 3600 151 | elif wait.endswith("d"): 152 | ttype = "day" 153 | wt *= 86400 154 | elif wait.endswith("w"): 155 | ttype = "week" 156 | wt *= 604800 157 | if not ttype: 158 | return await ctx.send("Invalid time unit. Please use S, M, H, D or W.") 159 | else: 160 | wt = 0 161 | if wt < 5 and wt != 0: 162 | return await ctx.send("Wait times must be greater than or equal to 5 seconds.") 163 | if not channel.permissions_for(ctx.guild.me).manage_messages: 164 | return await ctx.send("I do not have permission to delete messages in that channel.") 165 | if not channel.permissions_for(ctx.author).manage_messages: 166 | return await ctx.send("You do not have permission to delete messages in that channel.") 167 | await self.conf.channel(channel).wait.set(str(wt)) 168 | if wt: 169 | await ctx.send( 170 | f"Messages in {channel.mention} will now be deleted after {og} {ttype}{'s' if og != '1' else ''}." 171 | ) 172 | else: 173 | await ctx.send("Messages will not be auto-deleted after a specific amount of time.") 174 | 175 | @deleter.command() 176 | async def remove(self, ctx, channel: discord.TextChannel, *messages: str): 177 | """Remove messages in the specified channel from the auto-timed deletion. 178 | 179 | Helpful for announcements that you want to stay in the channel. 180 | The messages to be removed must be the IDs of the messages you wish to remove.""" 181 | failed = [] 182 | success = [] 183 | msgs = await self.conf.channel(channel).messages() 184 | for m in messages: 185 | if not m in msgs: 186 | failed.append(m) 187 | continue 188 | del msgs[m] 189 | success.append(m) 190 | if not failed: 191 | failed = [None] 192 | if not success: 193 | success = [None] 194 | await self.conf.channel(channel).messages.set(msgs) 195 | await ctx.send( 196 | f"Messages successfully removed: {humanize_list(success)}\nMessages that failed to be removed: {humanize_list(failed)}" 197 | ) 198 | 199 | @deleter.command() 200 | async def wipe(self, ctx, channel: discord.TextChannel = None): 201 | """Removes all messages in the specified channel from the auto-timed deleter. 202 | 203 | Leave blank to do it for the current channel.""" 204 | if not channel: 205 | channel = ctx.channel 206 | await self.conf.channel(channel).messages.set({}) 207 | await ctx.tick() 208 | -------------------------------------------------------------------------------- /dashboard/rpc/permissions.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | from html import escape 3 | 4 | import discord 5 | from redbot.cogs.permissions.converters import CogOrCommand, GuildUniqueObjectFinder, RuleType 6 | from redbot.core.bot import Red 7 | from redbot.core.commands import commands 8 | from redbot.core.utils import AsyncIter 9 | 10 | from .utils import FakePermissionsContext, permcheck, rpccheck 11 | 12 | 13 | class DashboardRPC_Permissions: 14 | def __init__(self, cog: commands.Cog): 15 | self.bot: Red = cog.bot 16 | self.cog: commands.Cog = cog 17 | 18 | # Initialize RPC handlers 19 | self.bot.register_rpc_handler(self.fetch_guild_rules) 20 | self.bot.register_rpc_handler(self.fetch_guild_targets) 21 | self.bot.register_rpc_handler(self.fetch_cog_commands) 22 | self.bot.register_rpc_handler(self.add_rule) 23 | self.bot.register_rpc_handler(self.add_default_rule) 24 | self.bot.register_rpc_handler(self.remove_rule) 25 | self.bot.register_rpc_handler(self.remove_default_rule) 26 | 27 | def unload(self): 28 | self.bot.unregister_rpc_handler(self.fetch_guild_rules) 29 | self.bot.unregister_rpc_handler(self.fetch_guild_targets) 30 | self.bot.unregister_rpc_handler(self.fetch_cog_commands) 31 | self.bot.unregister_rpc_handler(self.add_rule) 32 | self.bot.unregister_rpc_handler(self.add_default_rule) 33 | self.bot.unregister_rpc_handler(self.remove_rule) 34 | self.bot.unregister_rpc_handler(self.remove_default_rule) 35 | 36 | @rpccheck() 37 | @permcheck("Permissions", ["permissions"]) 38 | async def fetch_guild_rules(self, guild: discord.Guild, member: discord.Member): 39 | permcog = self.bot.get_cog("Permissions") 40 | 41 | # Basically a copy and paste of perms._yaml_get_guild, except doesn't save to a yaml file 42 | # and instead returns JSON data, and tinkers with the data 43 | guild_rules = {} 44 | for category in ("COG", "COMMAND"): 45 | guild_rules.setdefault(category, {}) 46 | rules_dict = await permcog.config.custom(category).all() 47 | for cmd_name, cmd_rules in rules_dict.items(): 48 | model_rules = cmd_rules.get(str(guild.id)) 49 | if model_rules is not None: 50 | for target, rule in model_rules.items(): 51 | if cmd_name not in guild_rules[category]: 52 | guild_rules[category][cmd_name] = [] 53 | 54 | if target != "default": 55 | if rule is None: 56 | continue 57 | target = int(target) 58 | obj = None 59 | name = "" 60 | objtype = "" 61 | if obj := guild.get_channel(target): 62 | objtype = "Channel" 63 | name = obj.name 64 | elif obj := guild.get_role(target): 65 | objtype = "Role" 66 | name = obj.name 67 | elif obj := guild.get_member(target): 68 | objtype = "User" 69 | name = f"{obj.display_name}#{obj.discriminator}" 70 | else: 71 | continue 72 | 73 | saving = { 74 | "type": objtype, 75 | "name": escape(name), 76 | "id": str(target), 77 | "permission": "allowed" if rule else "denied", 78 | } 79 | else: 80 | if rule is None: 81 | continue 82 | saving = { 83 | "type": "Default", 84 | "permission": "allowed" if rule else "denied", 85 | } 86 | 87 | guild_rules[category][cmd_name].append(saving) 88 | if not guild_rules[category].get(cmd_name, True): 89 | del guild_rules[category][cmd_name] 90 | 91 | return guild_rules 92 | 93 | @rpccheck() 94 | @permcheck("Permissions", ["permissions"]) 95 | async def fetch_guild_targets(self, guild: discord.Guild, member: discord.Member): 96 | data = {"USERS": [], "ROLES": [], "CHANNELS": []} 97 | 98 | async for user in AsyncIter(guild.members, steps=1300): 99 | data["USERS"].append( 100 | (str(user.id), escape(f"{user.display_name}#{user.discriminator}")) 101 | ) 102 | 103 | async for role in AsyncIter(guild.roles, steps=1300): 104 | data["ROLES"].append((str(role.id), escape(role.name))) 105 | 106 | async for channel in AsyncIter(guild.channels, steps=1300): 107 | data["CHANNELS"].append((str(channel.id), escape(channel.name))) 108 | 109 | return data 110 | 111 | @rpccheck() 112 | @permcheck("Permissions", ["permissions"]) 113 | async def fetch_cog_commands(self, guild: discord.Guild, member: discord.Member): 114 | data = { 115 | "COGS": list(self.bot.cogs.keys()), 116 | "COMMANDS": await self.cog.rpc.build_cmd_list(self.bot.commands, details=False), 117 | } 118 | 119 | return data 120 | 121 | @rpccheck() 122 | @permcheck("Permissions", ["permissions"]) 123 | async def add_rule( 124 | self, guild: discord.Guild, member: discord.Member, t: str, target: int, command: str 125 | ): 126 | ctx = FakePermissionsContext(self.bot, guild) 127 | cog = self.bot.get_cog("Permissions") 128 | try: 129 | cog_or_command = await CogOrCommand.convert(ctx, command) 130 | except commands.BadArgument: 131 | return {"status": 0, "message": "Invalid command"} 132 | 133 | try: 134 | allow_or_deny = RuleType(t) 135 | except commands.BadArgument: 136 | return {"status": 0, "message": "Invalid action"} 137 | 138 | try: 139 | who_or_what = await GuildUniqueObjectFinder.convert("", ctx, str(target)) 140 | except commands.BadArgument: 141 | return {"status": 0, "message": "Invalid target"} 142 | 143 | if isinstance(cog_or_command.obj, commands._AlwaysAvailableCommand): 144 | return {"status": 0, "message": "That command can not be restricted"} 145 | 146 | await cog._add_rule( 147 | rule=cast(bool, allow_or_deny), 148 | cog_or_cmd=cog_or_command, 149 | model_id=who_or_what.id, 150 | guild_id=guild.id, 151 | ) 152 | 153 | return {"status": 1} 154 | 155 | @rpccheck() 156 | @permcheck("Permissions", ["permissions"]) 157 | async def add_default_rule( 158 | self, guild: discord.Guild, member: discord.Member, t: str, command: str 159 | ): 160 | ctx = FakePermissionsContext(self.bot, guild) 161 | cog = self.bot.get_cog("Permissions") 162 | try: 163 | cog_or_command = await CogOrCommand.convert(ctx, command) 164 | except commands.BadArgument: 165 | return {"status": 0, "message": "Invalid command"} 166 | 167 | try: 168 | allow_or_deny = RuleType(t) 169 | except commands.BadArgument: 170 | return {"status": 0, "message": "Invalid action"} 171 | 172 | if isinstance(cog_or_command.obj, commands._AlwaysAvailableCommand): 173 | return {"status": 0, "message": "That command can not be restricted"} 174 | 175 | await cog._set_default_rule( 176 | rule=cast(bool, allow_or_deny), cog_or_cmd=cog_or_command, guild_id=guild.id 177 | ) 178 | 179 | return {"status": 1} 180 | 181 | @rpccheck() 182 | @permcheck("Permissions", ["permissions"]) 183 | async def remove_rule( 184 | self, guild: discord.Guild, member: discord.Member, target: int, command: str 185 | ): 186 | ctx = FakePermissionsContext(self.bot, guild) 187 | cog = self.bot.get_cog("Permissions") 188 | try: 189 | cog_or_command = await CogOrCommand.convert(ctx, command) 190 | except commands.BadArgument: 191 | return {"status": 0, "message": "Invalid command"} 192 | 193 | try: 194 | who_or_what = await GuildUniqueObjectFinder.convert("", ctx, str(target)) 195 | except commands.BadArgument: 196 | return {"status": 0, "message": "Invalid target"} 197 | 198 | await cog._remove_rule( 199 | cog_or_cmd=cog_or_command, model_id=who_or_what.id, guild_id=guild.id 200 | ) 201 | 202 | return {"status": 1} 203 | 204 | @rpccheck() 205 | @permcheck("Permissions", ["permissions"]) 206 | async def remove_default_rule( 207 | self, guild: discord.Guild, member: discord.Member, command: str 208 | ): 209 | ctx = FakePermissionsContext(self.bot, guild) 210 | cog = self.bot.get_cog("Permissions") 211 | try: 212 | cog_or_command = await CogOrCommand.convert(ctx, command) 213 | except commands.BadArgument: 214 | return {"status": 0, "message": "Invalid command"} 215 | 216 | if isinstance(cog_or_command.obj, commands._AlwaysAvailableCommand): 217 | return {"status": 0, "message": "That command can not be restricted"} 218 | 219 | await cog._set_default_rule(rule=None, cog_or_cmd=cog_or_command, guild_id=guild.id) 220 | 221 | return {"status": 1} 222 | -------------------------------------------------------------------------------- /authgg/authgg.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2018-Present NeuroAssassin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from redbot.core.utils.predicates import MessagePredicate 26 | from redbot.core.utils.chat_formatting import humanize_list, inline 27 | from redbot.core import commands, Config 28 | from typing import Optional 29 | import discord 30 | import asyncio 31 | import aiohttp 32 | 33 | HWID_URL = "https://developers.auth.gg/HWID/" 34 | USERS_URL = "https://developers.auth.gg/USERS/" 35 | 36 | 37 | class AuthGG(commands.Cog): 38 | def __init__(self, bot): 39 | self.bot = bot 40 | self.conf = Config.get_conf( 41 | "authgg", identifier=473541068378341376, force_registration=True 42 | ) 43 | 44 | self.conf.register_global(roles=[], keys={}) 45 | 46 | @commands.group() 47 | async def authgg(self, ctx): 48 | """Control your users from auth.gg straight from Discord""" 49 | 50 | @authgg.command() 51 | async def resethwid(self, ctx, apikey: str, *, name: Optional[str] = None): 52 | """Reset a user's HWID lock on auth.gg for the specified API key name. 53 | 54 | The API key name must be the friendly name provided by `[p]authgg keys add`.""" 55 | if not await self.bot.is_owner(ctx.author): 56 | roles = await self.conf.roles() 57 | has_role = False 58 | for r in ctx.author.roles: 59 | if r.id in roles: 60 | has_role = True 61 | if not has_role: 62 | return 63 | 64 | key = (await self.conf.keys()).get(apikey) 65 | if key is None: 66 | return await ctx.send( 67 | f"That API key is not registered. Please run `{ctx.prefix}authgg keys add {apikey} your_api_key_here` before running this command" 68 | ) 69 | 70 | if name is None: 71 | pred = MessagePredicate.same_context(ctx) 72 | await ctx.send("Please enter the username of the user you wish to unlock") 73 | try: 74 | message = await self.bot.wait_for("message", check=pred, timeout=30.0) 75 | except asyncio.TimeoutError: 76 | return await ctx.send("Command timed out.") 77 | name = message.content 78 | 79 | async with aiohttp.ClientSession() as session: 80 | data = {"type": "reset", "authorization": key, "user": name} 81 | response = await session.get(HWID_URL, params=data) 82 | if response.status != 200: 83 | return await ctx.send( 84 | f"Something went wrong while contacting the API. Status code: {response.status}" 85 | ) 86 | text = await response.json(content_type="text/html") 87 | if text["status"] == "success": 88 | return await ctx.send(f"Successfully reset {name}'s HWID") 89 | else: 90 | return await ctx.send(f"Failed to reset {name}'s HWID: {text['info']}") 91 | 92 | @authgg.command() 93 | async def changepw(self, ctx, apikey: str, username: str, password: str): 94 | """Set a user's password to the specified input for the specified API key name. 95 | 96 | The API key name must be the friendly name provided by `[p]authgg keys add`.""" 97 | if not await self.bot.is_owner(ctx.author): 98 | roles = await self.conf.roles() 99 | has_role = False 100 | for r in ctx.author.roles: 101 | if r.id in roles: 102 | has_role = True 103 | if not has_role: 104 | return 105 | 106 | key = (await self.conf.keys()).get(apikey) 107 | if key is None: 108 | return await ctx.send( 109 | f"That API key is not registered. Please run `{ctx.prefix}authgg keys add {apikey} your_api_key_here` before running this command" 110 | ) 111 | 112 | try: 113 | async with aiohttp.ClientSession() as session: 114 | data = { 115 | "type": "changepw", 116 | "authorization": key, 117 | "user": username, 118 | "password": password, 119 | } 120 | response = await session.get(USERS_URL, params=data) 121 | if response.status != 200: 122 | return await ctx.send( 123 | f"Something went wrong while contacting the API. Status code: {response.status}" 124 | ) 125 | text = await response.json(content_type="text/html") 126 | if text["status"] == "success": 127 | return await ctx.send(f"Successfully set {username}'s password") 128 | else: 129 | return await ctx.send(f"Failed to reset {username}'s password: {text['info']}") 130 | finally: 131 | try: 132 | await ctx.message.delete() 133 | except: 134 | await ctx.send( 135 | "I was unable to delete your command message due to lack of perms. It is recommended to due so to prevent your user's password from getting leaked." 136 | ) 137 | 138 | @commands.is_owner() 139 | @authgg.group() 140 | async def keys(self, ctx): 141 | """Manage API keys for auth.gg""" 142 | 143 | @keys.command(name="add") 144 | async def _keys_add(self, ctx, friendly: str, key: str): 145 | """Register an auth.gg API key under a friendly name""" 146 | keys = await self.conf.keys() 147 | try: 148 | if friendly in keys: 149 | return await ctx.send("That friendly name is already registered.") 150 | keys[friendly] = key 151 | await self.conf.keys.set(keys) 152 | finally: 153 | try: 154 | await ctx.message.delete() 155 | except: 156 | await ctx.send( 157 | "I was unable to delete your command message due to lack of perms. It is recommended to due so to prevent your API key from getting leaked." 158 | ) 159 | await ctx.send(f"Successfully registered API key under `{friendly}`") 160 | 161 | @keys.command(name="remove") 162 | async def _keys_remove(self, ctx, friendly: str): 163 | """Remove an auth.gg API key via its friendly name""" 164 | keys = await self.conf.keys() 165 | if friendly not in keys: 166 | return await ctx.send("That friendly name is not registered.") 167 | del keys[friendly] 168 | await self.conf.keys.set(keys) 169 | await ctx.tick() 170 | 171 | @keys.command(name="clear") 172 | async def _keys_clear(self, ctx): 173 | """Clears all auth.gg API keys""" 174 | await self.conf.keys.set({}) 175 | await ctx.tick() 176 | 177 | @keys.command(name="list") 178 | async def _keys_list(self, ctx): 179 | """Lists registered auth.gg API keys by their friendly name""" 180 | keys = await self.conf.keys() 181 | if not keys: 182 | return await ctx.send("No API keys are currently registered") 183 | message = f"The following keys are currently registered: {humanize_list(list(map(inline, keys.keys())))}" 184 | await ctx.send(message) 185 | 186 | @commands.is_owner() 187 | @authgg.group() 188 | async def roles(self, ctx): 189 | """Control what roles have access to reseting a user's HWID""" 190 | 191 | @roles.command() 192 | async def add(self, ctx, *, role: discord.Role): 193 | """Add a role to the whitelist for resetting user's HWID""" 194 | roles = await self.conf.roles() 195 | if role.id in roles: 196 | return await ctx.send("That role is already registered") 197 | 198 | roles.append(role.id) 199 | await self.conf.roles.set(roles) 200 | await ctx.tick() 201 | 202 | @roles.command() 203 | async def remove(self, ctx, *, role: discord.Role): 204 | """Remove a role from the whitelist for resetting user's HWID""" 205 | roles = await self.conf.roles() 206 | if role.id not in roles: 207 | return await ctx.send("That role is not registered") 208 | 209 | roles.remove(role.id) 210 | await self.conf.roles.set(roles) 211 | await ctx.tick() 212 | 213 | @roles.command() 214 | async def clear(self, ctx): 215 | """Remove all currently whitelisted roles""" 216 | await self.conf.roles.set([]) 217 | await ctx.tick() 218 | 219 | @roles.command(name="list") 220 | async def _list(self, ctx): 221 | """View all currently whitelisted roles""" 222 | roles = await self.conf.roles() 223 | if not roles: 224 | return await ctx.send("No roles are currently registered") 225 | message = "The following roles are currently registered:" 226 | for r in roles: 227 | role = ctx.guild.get_role(r) 228 | if role: 229 | message += f"\n{role.name}: {r}" 230 | await ctx.send(message) 231 | -------------------------------------------------------------------------------- /color/color.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2018-Present NeuroAssassin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | import io 26 | import re 27 | 28 | import discord 29 | from redbot.core import Config, commands 30 | 31 | from colour import Color as col 32 | from colour import rgb2hex 33 | from PIL import Image 34 | 35 | 36 | class Color(commands.Cog): 37 | """View embeds showcasing the supplied color and information about it""" 38 | 39 | def __init__(self, bot): 40 | self.bot = bot 41 | self.conf = Config.get_conf(self, identifier=473541068378341376) 42 | 43 | self.conf.register_guild(enabled=False) 44 | 45 | self.r = re.compile( 46 | r"(?i)^(?:(?:(?:0x|#|)((?:[a-fA-F0-9]{3}){1,2}$))|(?:([+-]?(?:[0-9]*[.])?[0-9]+,[+-]?(?:[0-9]*[.])?[0-9]+,[+-]?(?:[0-9]*[.])?[0-9]+))|(?:(\S+)))" 47 | ) # The Regex gods are going to kill me 48 | 49 | __author__ = "Neuro Assassin#4779 <@473541068378341376>" 50 | 51 | async def red_delete_data_for_user(self, **kwargs): 52 | """This cog does not store user data""" 53 | return 54 | 55 | def have_fun_with_pillow(self, rgb): 56 | im = Image.new("RGB", (200, 200), rgb) 57 | f = io.BytesIO() 58 | im.save(f, format="png") 59 | f.seek(0) 60 | file = discord.File(f, filename="picture.png") 61 | return file 62 | 63 | def rgb_to_decimal(self, rgb): 64 | return (rgb[0] << 16) + (rgb[1] << 8) + rgb[2] 65 | 66 | def decimal_to_rgb(self, d): 67 | return ((d >> 16) & 255, (d >> 8) & 255, d & 255) 68 | 69 | async def build_embed(self, co): 70 | if isinstance(co, int): 71 | rgb = self.decimal_to_rgb(co) 72 | r, g, b = rgb[0] / 255, rgb[1] / 255, rgb[2] / 255 73 | co = col(rgb=(r, g, b)) 74 | else: 75 | rgb = tuple([int(c * 255) for c in co.rgb]) 76 | file = await self.bot.loop.run_in_executor(None, self.have_fun_with_pillow, rgb) 77 | hexa = rgb2hex(co.rgb, force_long=True) 78 | decimal = self.rgb_to_decimal(rgb) 79 | embed = discord.Embed( 80 | title=f"Color Embed for: {hexa}", color=int(hexa.replace("#", "0x"), 0) 81 | ) 82 | embed.add_field(name="Hexadecimal Value:", value=hexa) 83 | embed.add_field(name="Decimal Value:", value=decimal) 84 | normal = ", ".join([f"{part:.2f}" for part in co.rgb]) 85 | extended = ", ".join([f"{(part*255):.2f}" for part in co.rgb]) 86 | embed.add_field(name="Red, Green, Blue (RGB) Value: ", value=f"{normal}\n{extended}") 87 | embed.add_field(name="Hue, Saturation, Luminance (HSL) Value:", value=str(co.hsl)) 88 | embed.set_thumbnail(url="attachment://picture.png") 89 | return embed, file 90 | 91 | @commands.Cog.listener() 92 | async def on_message(self, message): 93 | if message.author.bot: 94 | return 95 | if message.guild and not (await self.conf.guild(message.guild).enabled()): 96 | return 97 | ctx = await self.bot.get_context(message) 98 | if ctx.valid: 99 | return 100 | words = message.content.split(" ") 101 | counter = 0 102 | for word in words: 103 | if counter == 3: 104 | return 105 | if word.startswith("#"): 106 | word = word[1:] 107 | m = self.r.match(word) 108 | if not m: 109 | continue 110 | if m.group(1): # Hex 111 | hexa = m.group(1) 112 | try: 113 | c = col(f"#{hexa}") 114 | embed, file = await self.build_embed(c) 115 | await message.channel.send(file=file, embed=embed) 116 | counter += 1 117 | except (ValueError, AttributeError): 118 | pass 119 | elif m.group(2): # RGB 120 | try: 121 | stuff = m.group(2) 122 | tup = tuple(stuff.split(",")) 123 | if any([float(item) > 1 for item in tup]): 124 | tup = tuple([float(item) / 255 for item in tup]) 125 | tup = tuple(map(float, tup)) 126 | try: 127 | c = col(rgb=tup) 128 | embed, file = await self.build_embed(c) 129 | await message.channel.send(file=file, embed=embed) 130 | counter += 1 131 | except (ValueError, AttributeError) as e: 132 | await message.channel.send(f"Not recognized: {tup}; {e}") 133 | except Exception as e: 134 | await message.channel.send(e) 135 | elif m.group(3): # Named 136 | name = m.group(3) 137 | try: 138 | c = col(name) 139 | embed, file = await self.build_embed(c) 140 | await message.channel.send(file=file, embed=embed) 141 | counter += 1 142 | except (ValueError, AttributeError): 143 | pass 144 | 145 | @commands.group(aliases=["colour"]) 146 | async def color(self, ctx): 147 | """Group command for color commands""" 148 | pass 149 | 150 | @commands.bot_has_permissions(embed_links=True) 151 | @color.command() 152 | async def name(self, ctx, name): 153 | """Provides the hexadecimal value, RGB value and HSL value of a passed color. For example, pass `red` or `blue` as the name argument.""" 154 | name = name.lower() 155 | try: 156 | c = col(name) 157 | embed, file = await self.build_embed(c) 158 | await ctx.send(file=file, embed=embed) 159 | except (ValueError, AttributeError): 160 | await ctx.send("That color is not recognized.") 161 | 162 | @commands.bot_has_permissions(embed_links=True) 163 | @color.command() 164 | async def hex(self, ctx, hexa: str): 165 | """Provides the RGB value and HSL value of a passed hexadecimal value. Hexadecimal value must in the format of something like `#ffffff` or `0xffffff` to be used.""" 166 | try: 167 | match = re.match(r"(?i)^(?:0x|#|)((?:[a-fA-F0-9]{3}){1,2})$", hexa) 168 | c = col("#" + match.group(1)) 169 | embed, file = await self.build_embed(c) 170 | await ctx.send(file=file, embed=embed) 171 | except (ValueError, AttributeError): 172 | await ctx.send("That hexadecimal value is not recognized.") 173 | except IndexError: 174 | await ctx.send( 175 | "Invalid formatting for the hexadecimal. Must be the hexadecimal value, with an optional `0x` or `#` in the beginning." 176 | ) 177 | 178 | @commands.bot_has_permissions(embed_links=True) 179 | @color.command() 180 | async def rgb(self, ctx, highest: int, r: float, g: float, b: float): 181 | """Provides the hexadecimal value and HSL value of the rgb value given. Each value must have a space between them. Highest argument must be 1 or 255, indicating the highest value of a single value (r, g, or b).""" 182 | if not (highest in [1, 255]): 183 | return await ctx.send("Invalid `highest` argument.") 184 | if highest == 255: 185 | r = r / 255 186 | g = g / 255 187 | b = b / 255 188 | try: 189 | c = col(rgb=(r, g, b)) 190 | embed, file = await self.build_embed(c) 191 | await ctx.send(file=file, embed=embed) 192 | except (ValueError, AttributeError): 193 | await ctx.send("That rgb number is not recognized.") 194 | 195 | @commands.bot_has_permissions(embed_links=True) 196 | @color.command() 197 | async def hsl(self, ctx, h: float, s: float, l: float): 198 | """Provides the hexadecimal value and the RGB value of the hsl value given. Each value must have a space between them.""" 199 | try: 200 | c = col(hsl=(h, s, l)) 201 | embed, file = await self.build_embed(c) 202 | await ctx.send(file=file, embed=embed) 203 | except (ValueError, AttributeError): 204 | await ctx.send("That hsl number is not recognized.") 205 | 206 | @commands.bot_has_permissions(embed_links=True) 207 | @color.command() 208 | async def decimal(self, ctx, decimal: int): 209 | """Provides the RGB value of the decimal value given.""" 210 | try: 211 | embed, file = await self.build_embed(decimal) 212 | await ctx.send(file=file, embed=embed) 213 | except (ValueError, AttributeError): 214 | await ctx.send("That decimal value is not recognized.") 215 | 216 | @commands.admin() 217 | @color.command() 218 | async def msgshort(self, ctx, enable: bool): 219 | """Enable or disable the in-message shortcut. 220 | 221 | In-message shortcuts can be used by using the hex, rgb or name after a `#` in the middle of a message, as follows: 222 | 223 | `#000000` (hex) 224 | `#1,1,1` (rgb) 225 | `#black` (named)""" 226 | await self.conf.guild(ctx.guild).enabled.set(enable) 227 | if enable: 228 | await ctx.send("The in-message shortcut is now enabled.") 229 | else: 230 | await ctx.send("The in-message shortcut is now disabled.") 231 | -------------------------------------------------------------------------------- /esolang/befunge.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from enum import Enum 3 | import random 4 | import io 5 | 6 | 7 | # Utility 8 | class Mode(Enum): 9 | LITERAL = 1 10 | STRING = 2 11 | 12 | 13 | class Point: 14 | def __init__(self): 15 | self.x = 0 16 | self.y = 0 17 | 18 | def move(self, direction: List[int]) -> None: 19 | self.x += direction[0] 20 | self.y += direction[1] 21 | 22 | 23 | DIRECTIONS = [[1, 0], [0, 1], [-1, 0], [0, -1]] 24 | 25 | 26 | # Exceptions 27 | class EmptyStack(SyntaxError): 28 | """Raised when the the stack is accessed in an empty state""" 29 | 30 | def __init__(self, message: str, codemap: List[List[str]], position: Point): 31 | super().__init__( 32 | message, 33 | ("program.befunge", position.y + 1, position.x + 1, "".join(codemap[position.y])), 34 | ) 35 | 36 | 37 | class UnknownSymbol(SyntaxError): 38 | def __init__(self, message: str, codemap: List[List[str]], position: Point): 39 | super().__init__( 40 | message, 41 | ("program.befunge", position.y + 1, position.x + 1, "".join(codemap[position.y])), 42 | ) 43 | 44 | 45 | class NoTermination(SyntaxError): 46 | def __init__(self, message: str, code: str): 47 | if len(code) > 50: 48 | code = code[-50:] 49 | ptr = 50 50 | else: 51 | ptr = len(code) 52 | super().__init__( 53 | message, 54 | ("program.befunge", 1, ptr, code), 55 | ) 56 | 57 | 58 | # Core classes 59 | 60 | 61 | class Stack: 62 | def __init__(self, codemap: List[List[str]], pointer: Point): 63 | self._internal: List[int] = [] 64 | self.codemap: List[List[str]] = codemap 65 | self.pointer = pointer 66 | 67 | def push(self, value: int) -> None: 68 | self._internal.append(value) 69 | 70 | def __pop(self) -> int: 71 | return self._internal.pop() 72 | 73 | def pop(self) -> int: 74 | try: 75 | return self.__pop() 76 | except IndexError: 77 | raise EmptyStack("Empty stack in pop call", self.codemap, self.pointer) 78 | 79 | def addition(self) -> None: 80 | try: 81 | a, b = self.__pop(), self.__pop() 82 | except IndexError: 83 | raise EmptyStack("Empty stack in addition call", self.codemap, self.pointer) 84 | self.push(a + b) 85 | 86 | def subtraction(self) -> None: 87 | try: 88 | a, b = self.__pop(), self.__pop() 89 | except IndexError: 90 | raise EmptyStack("Empty stack in subtraction call", self.codemap, self.pointer) 91 | self.push(a - b) 92 | 93 | def multiplication(self) -> None: 94 | try: 95 | a, b = self.__pop(), self.__pop() 96 | except IndexError: 97 | raise EmptyStack("Empty stack in multiplication call", self.codemap, self.pointer) 98 | self.push(a * b) 99 | 100 | def division(self) -> None: 101 | try: 102 | a, b = self.__pop(), self.__pop() 103 | except IndexError: 104 | raise EmptyStack("Empty stack in division call", self.codemap, self.pointer) 105 | self.push(a // b) 106 | 107 | def modulo(self) -> None: 108 | try: 109 | a, b = self.__pop(), self.__pop() 110 | except IndexError: 111 | raise EmptyStack("Empty stack in modulo call", self.codemap, self.pointer) 112 | self.push(a % b) 113 | 114 | def lnot(self) -> None: 115 | try: 116 | a = self.__pop() 117 | except IndexError: 118 | raise EmptyStack("Empty stack in logical not call", self.codemap, self.pointer) 119 | self.push(1 if a == 0 else 0) 120 | 121 | def greater(self) -> None: 122 | try: 123 | a, b = self.__pop(), self.__pop() 124 | except IndexError: 125 | raise EmptyStack("Empty stack in logical greater call", self.codemap, self.pointer) 126 | self.push(1 if b > a else 0) 127 | 128 | def underscore(self) -> List[int]: 129 | try: 130 | a = self.__pop() 131 | except IndexError: 132 | a = 0 133 | if a == 0: 134 | return [1, 0] 135 | else: 136 | return [-1, 0] 137 | 138 | def pipe(self) -> List[int]: 139 | try: 140 | a = self.__pop() 141 | except IndexError: 142 | a = 0 143 | if a == 0: 144 | return [0, 1] 145 | else: 146 | return [0, -1] 147 | 148 | def duplicate(self) -> None: 149 | try: 150 | a = self.__pop() 151 | except IndexError: 152 | a = 0 153 | self.push(a) 154 | self.push(a) 155 | 156 | def swap(self) -> None: 157 | try: 158 | a = self.__pop() 159 | except IndexError: 160 | raise EmptyStack("Empty stack in swap call", self.codemap, self.pointer) 161 | 162 | try: 163 | b = self.__pop() 164 | except IndexError: 165 | b = 0 166 | self.push(a) 167 | self.push(b) 168 | 169 | 170 | class Befunge: 171 | @staticmethod 172 | def check_syntax(code: str) -> None: 173 | if "@" not in code: 174 | raise NoTermination("Program does not terminate", code) 175 | if code.count('"') % 2 != 0: 176 | raise NoTermination("Program has an un-ending stringmode segment", code) 177 | 178 | @staticmethod 179 | def buildcodemap(code: str) -> List[List[str]]: 180 | codemap = code.split("\n") 181 | max_segment = 0 182 | for index, segment in enumerate(codemap): 183 | codemap[index] = [x for x in segment] 184 | max_segment = max((max_segment, len(codemap[index]))) 185 | 186 | for i in range(len(codemap)): 187 | try: 188 | if len(codemap[i]) == 0: 189 | del codemap[i] 190 | except IndexError: 191 | # We have to catch this here because we are modifying the list 192 | # and so the indexes will become invalid 193 | break 194 | 195 | for index, segment in enumerate(codemap): 196 | if len(codemap[index]) < max_segment: 197 | codemap[index] = codemap[index] + ([" "] * (max_segment - len(codemap[index]))) 198 | return codemap 199 | 200 | @staticmethod 201 | async def evaluate(code: str) -> io.StringIO: 202 | Befunge.check_syntax(code) 203 | 204 | codemap: List[List[str]] = Befunge.buildcodemap(code) 205 | pointer: Point = Point() 206 | stack: Stack = Stack(codemap, pointer) 207 | 208 | mode: int = Mode.LITERAL 209 | direction: List[int] = [1, 0] 210 | output = io.StringIO("") 211 | skip_next = False 212 | counter: int = 0 213 | 214 | while counter < 100000: 215 | try: 216 | assert pointer.y > -1 217 | row: List[str] = codemap[pointer.y] 218 | except (IndexError, AssertionError): 219 | if pointer.y == -1 and direction[1] == 1: 220 | pointer.y += 1 221 | continue 222 | elif pointer.y == -1 and direction[1] == -1: 223 | pointer.y = len(codemap) - 1 224 | continue 225 | elif pointer.y == len(codemap) and direction[1] == 1: 226 | pointer.y = 0 227 | continue 228 | elif pointer.y == len(codemap) and direction[1] == -1: 229 | pointer.y = len(codemap) - 1 230 | continue 231 | 232 | try: 233 | assert pointer.x > -1 234 | char: str = row[pointer.x] 235 | except (IndexError, AssertionError): 236 | if pointer.x == -1 and direction[0] == 1: 237 | pointer.x += 1 238 | continue 239 | elif pointer.x == -1 and direction[0] == -1: 240 | pointer.x = len(row) - 1 241 | continue 242 | elif pointer.x == len(row) and direction[0] == 1: 243 | pointer.x = 0 244 | continue 245 | elif pointer.x == len(row) and direction[0] == -1: 246 | pointer.x = len(row) - 1 247 | continue 248 | 249 | if skip_next: 250 | pointer.move(direction) 251 | skip_next = False 252 | continue 253 | 254 | if mode == Mode.LITERAL: 255 | if char == ">": 256 | direction = [1, 0] 257 | elif char == "<": 258 | direction = [-1, 0] 259 | elif char == "^": 260 | direction = [0, -1] 261 | elif char == "v": 262 | direction = [0, 1] 263 | elif char == "?": 264 | direction = random.choice(DIRECTIONS) 265 | elif char == "+": 266 | stack.addition() 267 | elif char == "-": 268 | stack.subtraction() 269 | elif char == "*": 270 | stack.multiplication() 271 | elif char == "/": 272 | stack.division() 273 | elif char == "%": 274 | stack.modulo() 275 | elif char == "!": 276 | stack.lnot() 277 | elif char == "`": 278 | stack.greater() 279 | elif char == "_": 280 | direction = stack.underscore() 281 | elif char == "|": 282 | direction = stack.pipe() 283 | elif char == ":": 284 | stack.duplicate() 285 | elif char == "\\": 286 | stack.swap() 287 | elif char == "$": 288 | stack.pop() 289 | elif char == ".": 290 | output.write(str(stack.pop())) 291 | output.write(" ") 292 | elif char == ",": 293 | output.write(chr(stack.pop())) 294 | elif char == "#": 295 | skip_next = True 296 | elif char == "@": 297 | return output, stack._internal 298 | elif char == '"': 299 | mode = Mode.STRING 300 | elif char == " ": 301 | pass 302 | elif char.isdigit(): 303 | stack.push(int(char)) 304 | else: 305 | raise UnknownSymbol(char, codemap, pointer) 306 | elif mode == Mode.STRING: 307 | if char == '"': 308 | mode = Mode.LITERAL 309 | else: 310 | stack.push(ord(char)) 311 | pointer.move(direction) 312 | counter += 1 313 | return output, stack._internal 314 | -------------------------------------------------------------------------------- /cooldown/cooldown.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2018-Present NeuroAssassin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from redbot.core import commands, Config 26 | from discord.ext import commands as dc 27 | import asyncio 28 | import time 29 | import traceback 30 | 31 | 32 | class Cooldown(commands.Cog): 33 | """Add or remove cooldowns from/to commands 34 | 35 | WARNING: Some cooldowns are meant to be in place, meaning that they should not be removed. 36 | Any contributors to this cog are not at fault if it is used improperly, and is instead at 37 | the fault of the person running the command. By installing this cog, you agree to these terms. 38 | """ 39 | 40 | def __init__(self, bot): 41 | self.bot = bot 42 | self.conf = Config.get_conf(self, identifier=473541068378341376) 43 | default_global = {"data": []} 44 | self.conf.register_global(**default_global) 45 | self.task = self.bot.loop.create_task(self.initialize()) 46 | 47 | def cog_unload(self): 48 | self.__unload() 49 | 50 | def __unload(self): 51 | self.task.cancel() 52 | 53 | async def initialize(self): 54 | await self.bot.wait_until_ready() 55 | data = await self.conf.data() 56 | for entry in data: 57 | cmd = self.bot.get_command(entry[0]) 58 | if cmd: 59 | switch = { 60 | "user": dc.BucketType.user, 61 | "channel": dc.BucketType.channel, 62 | "guild": dc.BucketType.guild, 63 | "global": dc.BucketType.default, 64 | } 65 | commands.cooldown(entry[1], entry[2], switch[entry[3]])(cmd) 66 | 67 | @commands.is_owner() 68 | @commands.group() 69 | async def cooldown(self, ctx): 70 | """Group command for working with cooldowns for commands.""" 71 | pass 72 | 73 | @cooldown.command(alises=["update", "change", "edit"]) 74 | async def add(self, ctx, rate: int, per, btype, *, command): 75 | """Sets a cooldown for a command, allowing a certain amount of times in a certain amount of time for a certain type. If a cooldown already exists for the specified command, then it will be overwritten and edited. 76 | 77 | The command argument does not require quotes, as it consumes the rest in order to make cooldowns for subcommands easier. 78 | 79 | Example: `[p]cooldown add 1 5s user ping` 80 | 81 | The above example will limit a user to using the `ping` command every 5 seconds. 82 | 83 | Example 2: `[p]cooldown add 5 10m guild alias add` 84 | 85 | The above example (number 2) will limit people in a guild to using the `alias add` command to 5 times every 10 minutes. 86 | 87 | Time Types: 88 | - S => Seconds 89 | - M => Minutes 90 | - H => Hours 91 | - D => Days 92 | 93 | Bucket Types: 94 | - User 95 | - Channel 96 | - Guild 97 | - Global 98 | 99 | Arguments: 100 | - Rate: how many times 101 | - Per: during how long 102 | - Type: for what type 103 | - Command: for what command. Do not use a prefix, and does not work with aliases. Please pass the actual command for the alias if you wish. 104 | """ 105 | ttype = None 106 | per = per.lower() 107 | np = per[:-1] 108 | if not np.isdigit(): 109 | return await ctx.send( 110 | "Invalid amount of time. There is a non-number in your `per` argument, not including the time type." 111 | ) 112 | if rate < 1: 113 | return await ctx.send("The rate argument must be at least 1 or higher.") 114 | np = int(np) 115 | if per.endswith("s"): 116 | ttype = "seconds" 117 | elif per.endswith("m"): 118 | ttype = "minutes" 119 | np *= 60 120 | elif per.endswith("h"): 121 | ttype = "hours" 122 | np *= 3600 123 | elif per.endswith("d"): 124 | ttype = "days" 125 | np *= 86400 126 | if not ttype: 127 | return await ctx.send("Invalid time unit. Please use S, M, H or D.") 128 | btype = btype.lower() 129 | if not btype in ["user", "channel", "guild", "global"]: 130 | return await ctx.send("Invalid bucket type.") 131 | cmd = self.bot.get_command(command) 132 | if cmd == None or not str(cmd) == command: 133 | return await ctx.send("Invalid command argument.") 134 | 135 | def check(m): 136 | return ( 137 | (m.author.id == ctx.author.id) 138 | and (m.channel.id == ctx.channel.id) 139 | and (m.content[0].lower() in ["y", "n"]) 140 | ) 141 | 142 | cooldowns = cmd._buckets._cooldown 143 | all_data = await self.conf.data() 144 | if cooldowns: 145 | if not command in [item[0] for item in all_data]: 146 | extra = "\nThis command also had an original cooldown. Cooldowns are typically on commands for certain reasons, and so editing it is not recommended. Proceed at your own risk." 147 | else: 148 | extra = "\nThis command already had a cooldown from this cog, so its current cooldown will be edited to the new one." 149 | else: 150 | extra = "" 151 | 152 | await ctx.send( 153 | ( 154 | "You are about to add a cooldown for a command using this cog. " 155 | "Are you sure you wish to set this cooldown? Respond with 'y' or 'n' to this message." 156 | f"{extra}" 157 | ) 158 | ) 159 | try: 160 | m = await self.bot.wait_for("message", check=check, timeout=30.0) 161 | except asyncio.TimeoutError: 162 | return await ctx.send("You took too long to respond.") 163 | if m.content.lower().startswith("y"): 164 | switch = { 165 | "user": dc.BucketType.user, 166 | "channel": dc.BucketType.channel, 167 | "guild": dc.BucketType.guild, 168 | "global": dc.BucketType.default, 169 | } 170 | commands.cooldown(rate, np, switch[btype])(cmd) 171 | else: 172 | return await ctx.send("Not establishing command cooldown.") 173 | data = [command, rate, np, btype] 174 | changed = False 175 | for position, entry in enumerate(all_data): 176 | if entry[0] == data[0]: 177 | all_data[position] = data 178 | changed = True 179 | break 180 | if not changed: 181 | all_data.append(data) 182 | await self.conf.data.set(all_data) 183 | 184 | await ctx.send("Your cooldown has been established") 185 | 186 | @cooldown.command() 187 | async def remove(self, ctx, *, command): 188 | """Removes the cooldown from a command. 189 | 190 | The cooldown can be one set from this cog or from inside the cog's code. 191 | 192 | The command argument does not require quotes, as it consumes the rest in order to make cooldowns for subcommands easier. 193 | 194 | Please do note however: some commands are meant to have cooldowns. They may prevent something malicious from happening, or maybe your device from breaking or from being used too much. I (Neuro Assassin <@473541068378341376>) or any other contributor to this cog take no responsibility for any complications that may result because of this. Use at your own risk. 195 | 196 | Note: Does not actually remove the command cooldown (undocumented), so instead it allows for the command to be run 100000 times every 1 second until the next boot up, where it will not be added (unless you are removing a cooldown from outside of this cog, then it will be kept after restart). 197 | """ 198 | cmd = self.bot.get_command(command) 199 | if cmd == None or not str(cmd) == command: 200 | return await ctx.send("Invalid command argument.") 201 | 202 | cooldowns = cmd._buckets._cooldown 203 | if not cooldowns: 204 | return await ctx.send("This command does not have any cooldown.") 205 | 206 | data = await self.conf.data() 207 | if not command in [item[0] for item in data]: 208 | fromcog = False 209 | extra = "\nThis command also had an original cooldown. Cooldowns are typically on commands for certain reasons, and so removing it is not recommended. Proceed at your own risk." 210 | else: 211 | fromcog = True 212 | extra = "" 213 | 214 | def check(m): 215 | return ( 216 | (m.author.id == ctx.author.id) 217 | and (m.channel.id == ctx.channel.id) 218 | and (m.content[0].lower() in ["y", "n"]) 219 | ) 220 | 221 | await ctx.send( 222 | ( 223 | "You are about to remove a cooldown for a command. " 224 | "Are you sure you wish to remove it? Respond with 'y' or 'n' to this message." 225 | f"{extra}" 226 | ) 227 | ) 228 | try: 229 | m = await self.bot.wait_for("message", check=check, timeout=30.0) 230 | except asyncio.TimeoutError: 231 | return await ctx.send("You took too long to respond.") 232 | if m.content.lower().startswith("y"): 233 | commands.cooldown(10000, 1, dc.BucketType.user)(cmd) 234 | else: 235 | return await ctx.send("Not removing command cooldown.") 236 | if fromcog: 237 | for entry in data: 238 | if entry[0] == command: 239 | data.remove(entry) 240 | break 241 | else: 242 | data.append([command, 10000, 1, "global"]) 243 | await self.conf.data.set(data) 244 | 245 | await ctx.send( 246 | "Your cooldown has been removed. If this cog originally had a cooldown, then you removed/edited it, and you just removed it, a bot restart is required for the original cooldown to be instated." 247 | ) 248 | --------------------------------------------------------------------------------