diff --git a/cogs.csv b/cogs.csv index e7b7406bc..273259d11 100644 --- a/cogs.csv +++ b/cogs.csv @@ -6,11 +6,13 @@ Colour,View information about a colour.,"Tools, Informative",Kreusada ConsoleClearer,Completely clear your Red console,Tools,Kreusada EmbedCreator,"Create embeds using buttons, modals and dropdowns!",Tools,Kreusada Flags,Get the flag for a country,Tools,Kreusada +MessageDeleter,"Delete messages from users and bots, inclusively or exclusively, in text channels.",Tools,Kreusada Minifier,Minify your code with python minifier,Tools,Kreusada OnThisDay,"Find out what happened today, in multiple different years in history.",Fun,Kreusada PyPi,Get information about a package available on PyPi.,Tools,"Kreusada, OofChair" QR,Create QR codes.,Tools,Kreusada Quotes,Get a random quote,Fun,Kreusada +Riddles,Get a random riddle.,Fun,Kreusada RoleBoards,"Get 'leaderboards' about guild roles, such as the users with the most roles,the roles with the most users, and a full list of all the roles.",Tools,Kreusada Termino,"Custom shutdown and restart messages, and announcements",Tools,Kreusada TextEditor,Edit and manipulate with text,"Fun, Tools",Kreusada diff --git a/docs/cog_riddles.rst b/docs/cog_riddles.rst new file mode 100644 index 000000000..1c19a6f8d --- /dev/null +++ b/docs/cog_riddles.rst @@ -0,0 +1,68 @@ +.. _riddles: + +======= +Riddles +======= + +This is the cog guide for the 'Riddles' cog. This guide +contains the collection of commands which you can use in the cog. + +Through this guide, ``[p]`` will always represent your prefix. Replace +``[p]`` with your own prefix when you use these commands in Discord. + +.. note:: + + This guide was last updated for version 1.0.0. Ensure + that you are up to date by running ``[p]cog update riddles``. + + If there is something missing, or something that needs improving + in this documentation, feel free to create an issue `here `_. + + This documentation is auto-generated everytime this cog receives an update. + +-------------- +About this cog +-------------- + +Get a random riddle. + +-------- +Commands +-------- + +Here are all the commands included in this cog (1): + +* ``[p]riddle`` + Get a random riddle. + +------------ +Installation +------------ + +If you haven't added my repo before, lets add it first. We'll call it +"kreusada-cogs" here. + +.. code-block:: ini + + [p]repo add kreusada-cogs https://github.com/Kreusada/Kreusada-Cogs + +Now, we can install Riddles. + +.. code-block:: ini + + [p]cog install kreusada-cogs riddles + +Once it's installed, it is not loaded by default. Load it by running the following +command: + +.. code-block:: ini + + [p]load riddles + +--------------- +Further Support +--------------- + +For more support, head over to the `cog support server `_, +I have my own channel over there at #support_kreusada-cogs. Feel free to join my +`personal server `_ whilst you're here. diff --git a/messagedeleter/__init__.py b/messagedeleter/__init__.py new file mode 100644 index 000000000..207eb177a --- /dev/null +++ b/messagedeleter/__init__.py @@ -0,0 +1,10 @@ +from .messagedeleter import MessageDeleter + +from redbot.core.bot import Red +from redbot.core.utils import get_end_user_data_statement + +__red_end_user_data_statement__ = get_end_user_data_statement(__file__) + + +async def setup(bot: Red): + await bot.add_cog(MessageDeleter(bot)) diff --git a/messagedeleter/info.json b/messagedeleter/info.json new file mode 100644 index 000000000..89370232e --- /dev/null +++ b/messagedeleter/info.json @@ -0,0 +1,23 @@ +{ + "author": [ + "Kreusada" + ], + "description": "Delete messages from users and bots, inclusively or exclusively, in text channels.", + "disabled": false, + "end_user_data_statement": "This cog does not persistently store data or metadata about users.", + "hidden": false, + "install_msg": "Thanks for installing, have fun. Please refer to my docs if you need any help: https://kreusadacogs.readthedocs.io/en/latest/cog_messagedeleter.html", + "name": "MessageDeleter", + "required_cogs": {}, + "requirements": [], + "short": "Delete messages from users and bots, inclusively or exclusively, in text channels.", + "tags": [ + "Messages", + "Delete", + "Deletion", + "Bots", + "Humans", + "Users" + ], + "type": "COG" +} diff --git a/messagedeleter/messagedeleter.py b/messagedeleter/messagedeleter.py new file mode 100644 index 000000000..a2954af9f --- /dev/null +++ b/messagedeleter/messagedeleter.py @@ -0,0 +1,189 @@ +import discord + +from redbot.core import Config, commands +from redbot.core.bot import Red +from typing import Literal, Optional + +ENABLE_CONFIRMATION_MESSAGE = ( + "Successfully *enabled* deletion of {type} messages in {channel.mention}. " +) +ENABLE_CONFIRMATION_WITH_PERMS = "Messages sent by {type}s will be deleted with a delay of {delay} seconds. Ensure that I retain sufficient permissions to delete messages." +ENABLE_CONFIRMATION_WITHOUT_PERMS = "However, I do not currently have sufficient permissions to delete messages in {channel.mention}." + +DISABLE_CONFIRMATION_MESSAGE = "Successfully *disabled* deletion of {type} messages in {channel.mention}. Messages sent by {type}s will no longer be deleted." + + +class MessageDeleter(commands.Cog): + """Delete messages from users and bots, inclusively or exclusively, in text channels.""" + + __version__ = "1.0.0" + __author__ = "Kreusada" + + def __init__(self, bot: Red): + self.bot = bot + self.config = Config.get_conf(self, 719988449867989142, force_registration=True) + self.config.register_guild(channels={}) + + def format_help_for_context(self, ctx: commands.Context) -> str: + context = super().format_help_for_context(ctx) + return f"{context}\n\nAuthor: {self.__author__}\nVersion: {self.__version__}" + + async def red_delete_data_for_user(self, **kwargs): + """Nothing to delete.""" + return + + async def enable_for( + self, *, type: Literal["bots", "humans"], channel: discord.TextChannel, delay: int + ): + cid = str(channel.id) + async with self.config.guild(channel.guild).channels() as channels: + if cid not in channels: + channels[cid] = {"bots": False, "humans": False} + channels[cid][type] = delay + if not channel.permissions_for(channel.guild.me).manage_messages: + return False + return True + + async def enable_for( + self, *, type: Literal["bots", "humans"], channel: discord.TextChannel, delay: int + ): + cid = str(channel.id) + channels = await self.config.guild(channel.guild).channels() + if cid not in channels: + channels[cid] = {"bots": False, "humans": False} + channels[cid][type] = delay + await self.config.guild(channel.guild).channels.set(channels) + if not channel.permissions_for(channel.guild.me).manage_messages: + return False + return True + + async def disable_for(self, *, type: Literal["bots", "humans"], channel: discord.TextChannel): + cid = str(channel.id) + channels = await self.config.guild(channel.guild).channels() + if cid in channels: + channels[cid][type] = False + if all(mode is False for mode in channels[cid].values()): # config cleanup + del channels[cid] + await self.config.guild(channel.guild).channels.set(channels) + + @staticmethod + def get_confirmation_message(can_delete_messages: bool): + message = ENABLE_CONFIRMATION_MESSAGE + if not can_delete_messages: + message += ENABLE_CONFIRMATION_WITHOUT_PERMS + else: + message += ENABLE_CONFIRMATION_WITH_PERMS + return message + + @commands.group() + @commands.guild_only() + @commands.mod_or_permissions(manage_messages=True) + async def msgdeleter(self, ctx: commands.Context): + """Commands to configure MessageDeleter.""" + + @msgdeleter.command(name="settings") + async def msgdeleter_settings(self, ctx: commands.Context): + """Shows the current settings for MessageDeleter in this guild.""" + channels = await self.config.guild(ctx.guild).channels() + message = "## Settings for MessageDeleter in this guild\n" + has_settings = False + for cid, settings in channels.items(): + has_settings = True + line = f"- {ctx.guild.get_channel(int(cid)).mention} -" + bot_settings = settings["bots"] + if bot_settings is not False: + if bot_settings == 0: + line += " Messages sent by bots are **instantly** deleted." + else: + line += f" Messages sent by bots are deleted after **{bot_settings}** seconds." + human_settings = settings["humans"] + if human_settings is not False: + if human_settings == 0: + line += " Messages sent by humans are **instantly** deleted." + else: + line += ( + f" Messages sent by humans are deleted after **{human_settings}** seconds." + ) + message += line + "\n" + await ctx.send(message if has_settings else "No settings to show.") + + @msgdeleter.command(name="reset") + async def msgdeleter_reset(self, ctx: commands.Context): + """Reset MessageDeleter in this guild.""" + await self.config.guild(ctx.guild).channels.clear() + await ctx.send("MessageDeleter successfully reset.") + + @msgdeleter.group(name="bots", aliases=["bot"]) + async def msgdeleter_bots(self, ctx: commands.Context): + """Enable or disable deletion of bot messages.""" + + @msgdeleter_bots.command(name="enable") + async def msgdeleter_bots_enable( + self, + ctx: commands.Context, + channel: discord.TextChannel, + delay: Optional[commands.Range[int, None, 10]] = 0, + ): + """Enable deletion of bot messages in a text channel.""" + can_delete_messages = await self.enable_for(type="bots", channel=channel, delay=delay) + await ctx.send( + self.get_confirmation_message(can_delete_messages).format( + type="bot", + channel=channel, + delay=str(delay), + ) + ) + + @msgdeleter_bots.command(name="disable") + async def msgdeleter_bots_disable(self, ctx: commands.Context, channel: discord.TextChannel): + """Disable deletion of bot messages in a text channel.""" + await self.disable_for(type="bots", channel=channel) + await ctx.send(DISABLE_CONFIRMATION_MESSAGE.format(type="bot", channel=channel)) + + @msgdeleter.group(name="humans", aliases=["human", "users", "user", "members", "member"]) + async def msgdeleter_humans(self, ctx: commands.Context): + """Enable or disable deletion of human messages.""" + + @msgdeleter_humans.command(name="enable") + async def msgdeleter_humans_enable( + self, + ctx: commands.Context, + channel: discord.TextChannel, + delay: Optional[commands.Range[int, None, 10]] = 0, + ): + """Enable deletion of human messages in a text channel.""" + can_delete_messages = await self.enable_for(type="humans", channel=channel, delay=delay) + await ctx.send( + self.get_confirmation_message(can_delete_messages).format( + type="human", + channel=channel, + delay=str(delay), + ) + ) + + @msgdeleter_humans.command(name="disable") + async def msgdeleter_humans_disable(self, ctx: commands.Context, channel: discord.TextChannel): + """Disable deletion of human messages in a text channel.""" + await self.disable_for(type="humans", channel=channel) + await ctx.send(DISABLE_CONFIRMATION_MESSAGE.format(type="human", channel=channel)) + + @commands.Cog.listener("on_message") + async def message_deleter_listener(self, message: discord.Message): + if message.guild is None: + return + if not message.channel.permissions_for(message.guild.me).manage_messages: + return + if await self.bot.cog_disabled_in_guild(self, message.guild): + return + if not await self.bot.ignored_channel_or_guild(message): + return + if not await self.bot.allowed_by_whitelist_blacklist(message.author): + return + + config = await self.config.guild(message.guild).channels() + if (mcid := str(message.channel.id)) not in config: + return + setting = config[mcid]["bots" if message.author.bot else "humans"] + if setting is False: + return + await message.delete(delay=setting) diff --git a/pyproject.toml b/pyproject.toml index 5f21ac9db..175c25dde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,11 +32,13 @@ 'consoleclearer', 'embedcreator', 'flags', + 'messagedeleter', 'minifier', 'onthisday', 'pypi', 'qr', 'quotes', + 'riddles', 'roleboards', 'termino', 'texteditor', diff --git a/riddles/README.rst b/riddles/README.rst new file mode 100644 index 000000000..1c19a6f8d --- /dev/null +++ b/riddles/README.rst @@ -0,0 +1,68 @@ +.. _riddles: + +======= +Riddles +======= + +This is the cog guide for the 'Riddles' cog. This guide +contains the collection of commands which you can use in the cog. + +Through this guide, ``[p]`` will always represent your prefix. Replace +``[p]`` with your own prefix when you use these commands in Discord. + +.. note:: + + This guide was last updated for version 1.0.0. Ensure + that you are up to date by running ``[p]cog update riddles``. + + If there is something missing, or something that needs improving + in this documentation, feel free to create an issue `here `_. + + This documentation is auto-generated everytime this cog receives an update. + +-------------- +About this cog +-------------- + +Get a random riddle. + +-------- +Commands +-------- + +Here are all the commands included in this cog (1): + +* ``[p]riddle`` + Get a random riddle. + +------------ +Installation +------------ + +If you haven't added my repo before, lets add it first. We'll call it +"kreusada-cogs" here. + +.. code-block:: ini + + [p]repo add kreusada-cogs https://github.com/Kreusada/Kreusada-Cogs + +Now, we can install Riddles. + +.. code-block:: ini + + [p]cog install kreusada-cogs riddles + +Once it's installed, it is not loaded by default. Load it by running the following +command: + +.. code-block:: ini + + [p]load riddles + +--------------- +Further Support +--------------- + +For more support, head over to the `cog support server `_, +I have my own channel over there at #support_kreusada-cogs. Feel free to join my +`personal server `_ whilst you're here. diff --git a/riddles/__init__.py b/riddles/__init__.py new file mode 100644 index 000000000..553fe18d9 --- /dev/null +++ b/riddles/__init__.py @@ -0,0 +1,10 @@ +from .riddles import Riddles + +from redbot.core.bot import Red +from redbot.core.utils import get_end_user_data_statement + +__red_end_user_data_statement__ = get_end_user_data_statement(__file__) + + +async def setup(bot: Red): + await bot.add_cog(Riddles()) diff --git a/riddles/info.json b/riddles/info.json new file mode 100644 index 000000000..6a3b5df58 --- /dev/null +++ b/riddles/info.json @@ -0,0 +1,18 @@ +{ + "author": [ + "Kreusada" + ], + "description": "Get a random riddle.", + "disabled": false, + "end_user_data_statement": "This cog does not persistently store data or metadata about users.", + "hidden": false, + "install_msg": "Thanks for installing, have fun. Please refer to my docs if you need any help: https://kreusadacogs.readthedocs.io/en/latest/cog_riddles.html", + "name": "Riddles", + "required_cogs": {}, + "requirements": [], + "short": "Get a random riddle.", + "tags": [ + "Riddles" + ], + "type": "COG" +} diff --git a/riddles/riddles.py b/riddles/riddles.py new file mode 100644 index 000000000..9a5d8393a --- /dev/null +++ b/riddles/riddles.py @@ -0,0 +1,196 @@ +import random + +from redbot.core import commands +from redbot.core.utils.chat_formatting import spoiler + + +RIDDLES = [ + {"riddle": "What has keys but can't open locks?", "answer": "A piano"}, + {"riddle": "What runs but never walks, has a mouth but never talks?", "answer": "A river"}, + {"riddle": "What can travel around the world while staying in a corner?", "answer": "A stamp"}, + {"riddle": "What has a head, a tail, is brown, and has no legs?", "answer": "A penny"}, + { + "riddle": "What comes once in a minute, twice in a moment, but never in a thousand years?", + "answer": "The letter M", + }, + {"riddle": "What is full of holes but still holds water?", "answer": "A sponge"}, + {"riddle": "What is always in front of you but can’t be seen?", "answer": "The future"}, + {"riddle": "What has a heart that doesn’t beat?", "answer": "An artichoke"}, + {"riddle": "What has to be broken before you can use it?", "answer": "An egg"}, + { + "riddle": "I speak without a mouth and hear without ears. I have no body, but I come alive with wind. What am I?", + "answer": "An echo", + }, + {"riddle": "The more you take, the more you leave behind. What am I?", "answer": "Footsteps"}, + { + "riddle": "I’m tall when I’m young, and I’m short when I’m old. What am I?", + "answer": "A candle", + }, + { + "riddle": "What has cities, but no houses; forests, but no trees; and rivers, but no water?", + "answer": "A map", + }, + { + "riddle": "What is seen in the middle of March and April that can’t be seen at the beginning or end of either month?", + "answer": "The letter R", + }, + { + "riddle": "You see me once in June, twice in November, but not at all in May. What am I?", + "answer": "The letter E", + }, + {"riddle": "I have branches, but no fruit, trunk or leaves. What am I?", "answer": "A bank"}, + {"riddle": "What can you catch, but not throw?", "answer": "A cold"}, + { + "riddle": "If you drop me, I’m sure to crack, but give me a smile and I’ll always smile back. What am I?", + "answer": "A mirror", + }, + {"riddle": "What has legs but doesn’t walk?", "answer": "A table"}, + {"riddle": "What has one eye, but can’t see?", "answer": "A needle"}, + {"riddle": "What gets wetter as it dries?", "answer": "A towel"}, + { + "riddle": "What has a bed but never sleeps, can run but never walks, and has a bank but no money?", + "answer": "A river", + }, + {"riddle": "What word is spelled incorrectly in every dictionary?", "answer": "Incorrectly"}, + {"riddle": "What begins with T, ends with T, and has T in it?", "answer": "A teapot"}, + {"riddle": "Forward I am heavy, but backward I am not. What am I?", "answer": "A ton"}, + {"riddle": "What has hands, but can’t clap?", "answer": "A clock"}, + {"riddle": "What goes up but never comes down?", "answer": "Your age"}, + {"riddle": "What has four wheels and flies?", "answer": "A garbage truck"}, + {"riddle": "What comes down but never goes up?", "answer": "Rain"}, + {"riddle": "What is so fragile that saying its name breaks it?", "answer": "Silence"}, + {"riddle": "What can fill a room but takes up no space?", "answer": "Light"}, + {"riddle": "What begins with an E but only has one letter?", "answer": "An envelope"}, + {"riddle": "What has a neck but no head?", "answer": "A bottle"}, + {"riddle": "What belongs to you but is used more by others?", "answer": "Your name"}, + { + "riddle": "I’m light as a feather, yet the strongest man can’t hold me for more than 5 minutes. What am I?", + "answer": "Your breath", + }, + { + "riddle": "What is black when it’s clean and white when it’s dirty?", + "answer": "A chalkboard", + }, + { + "riddle": "I’m not alive, but I can grow; I don’t have lungs, but I need air; I don’t have a mouth, and I can drown. What am I?", + "answer": "A fire", + }, + {"riddle": "The more of this there is, the less you see. What is it?", "answer": "Darkness"}, + {"riddle": "What has a ring but no finger?", "answer": "A telephone"}, + {"riddle": "What has one head, one foot, and four legs?", "answer": "A bed"}, + {"riddle": "What can’t be put in a saucepan?", "answer": "Its lid"}, + {"riddle": "What has 13 hearts, but no other organs?", "answer": "A deck of cards"}, + {"riddle": "What has words, but never speaks?", "answer": "A book"}, + {"riddle": "What has a bottom at the top?", "answer": "Your legs"}, + {"riddle": "What kind of room has no doors or windows?", "answer": "A mushroom"}, + {"riddle": "What kind of tree can you carry in your hand?", "answer": "A palm"}, + {"riddle": "What gets bigger the more you take away?", "answer": "A hole"}, + {"riddle": "I shave every day, but my beard stays the same. What am I?", "answer": "A barber"}, + {"riddle": "What tastes better than it smells?", "answer": "Your tongue"}, + {"riddle": "What has a thumb and four fingers but is not alive?", "answer": "A glove"}, + {"riddle": "What has no beginning, end, or middle?", "answer": "A doughnut"}, + { + "riddle": "What is always hungry, must be fed, but dies when given a drink?", + "answer": "Fire", + }, + { + "riddle": "What can you hold in your left hand but not in your right?", + "answer": "Your right elbow", + }, + {"riddle": "What can be cracked, made, told, and played?", "answer": "A joke"}, + { + "riddle": "What starts with a P, ends with an E, and has thousands of letters?", + "answer": "The post office", + }, + { + "riddle": "What word of five letters has only one left when two letters are removed?", + "answer": "Stone", + }, + {"riddle": "What is harder to catch the faster you run?", "answer": "Your breath"}, + { + "riddle": "I am an odd number. Take away one letter and I become even. What number am I?", + "answer": "Seven", + }, + {"riddle": "What has a bark but no bite?", "answer": "A tree"}, + {"riddle": "What invention lets you look right through a wall?", "answer": "A window"}, + { + "riddle": "What has a lock but no key, can be put in a hole but not dug out?", + "answer": "A padlock", + }, + { + "riddle": "What five-letter word becomes shorter when you add two letters to it?", + "answer": "Short", + }, + {"riddle": "What can you keep after giving it to someone?", "answer": "Your word"}, + { + "riddle": "What word is pronounced the same if you take away four of its five letters?", + "answer": "Queue", + }, + {"riddle": "What has many teeth but can’t bite?", "answer": "A comb"}, + {"riddle": "What has an eye but cannot see?", "answer": "A needle"}, + {"riddle": "What is always coming but never arrives?", "answer": "Tomorrow"}, + {"riddle": "What gets sharper the more you use it?", "answer": "Your brain"}, + { + "riddle": "What has roots as nobody sees, is taller than trees. Up, up it goes, and yet never grows?", + "answer": "A mountain", + }, + { + "riddle": "What is as light as a feather, yet the world’s strongest man couldn’t hold it for much longer than a minute?", + "answer": "His breath", + }, + { + "riddle": "What comes once in a year, twice in a month, four times in a week, and six times in a day?", + "answer": "The letter E", + }, + {"riddle": "What goes through cities and fields, but never moves?", "answer": "A road"}, + {"riddle": "What can fill a room but is invisible?", "answer": "Air"}, + {"riddle": "What is so light that a feather can’t hold it down?", "answer": "A soap bubble"}, + {"riddle": "What comes down but never goes up?", "answer": "Rain"}, + {"riddle": "What has a head, a tail, is brown, and has no legs?", "answer": "A penny"}, + {"riddle": "What is always in front of you but can’t be seen?", "answer": "The future"}, + { + "riddle": "What word is spelled the same forwards, backwards, and upside down?", + "answer": "NOON", + }, + { + "riddle": "What is greater than God, more evil than the devil, the poor have it, the rich need it, and if you eat it, you die?", + "answer": "Nothing", + }, + { + "riddle": "What can you hold without ever touching or using your hands?", + "answer": "Your breath", + }, + { + "riddle": "What begins with the letter 'I', and by adding a letter, you become silent?", + "answer": "Island", + }, + {"riddle": "What has a bed but never sleeps?", "answer": "A river"}, + { + "riddle": "I am taken from a mine, and shut up in a wooden case, from which I am never released, and yet I am used by almost every person. What am I?", + "answer": "A pencil", + }, + {"riddle": "What has a face and two hands, but no arms or legs?", "answer": "A clock"}, +] + + +class Riddles(commands.Cog): + """Get a random riddle.""" + + __version__ = "1.0.0" + __author__ = "Kreusada" + + def format_help_for_context(self, ctx: commands.Context) -> str: + context = super().format_help_for_context(ctx) + return f"{context}\n\nAuthor: {self.__author__}\nVersion: {self.__version__}" + + async def red_delete_data_for_user(self, **kwargs): + """Nothing to delete.""" + return + + @commands.command() + async def riddle(self, ctx: commands.Context): + """Get a random riddle.""" + choice = random.choice(RIDDLES) + await ctx.send( + f"\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16} {choice['riddle']}\n\N{BRAIN} {spoiler(choice['answer'] + '.')}" + )