├── .github └── workflows │ └── black.yml ├── .gitignore ├── Dockerfile ├── README.md ├── requirements.txt ├── src ├── background_cogs │ ├── errorhandler.py │ ├── ipc.py │ ├── keep_connected.py │ └── logging.py ├── cogs │ ├── automod.py │ ├── bot_commands.py │ ├── fun.py │ ├── help.py │ └── ticket.py ├── main.py ├── slash_cogs │ ├── button_roles.py │ ├── config.py │ ├── fun_slash.py │ ├── moderation.py │ ├── reputation.py │ ├── slash_bot_commands.py │ └── slash_ticket.py ├── sql │ ├── automod_queries.py │ ├── buttonrole_queries.py │ ├── defaults.json │ ├── logging_queries.py │ ├── mod_queries.py │ ├── rep_queries.py │ └── ticket_queries.py ├── utility │ ├── constants │ │ └── logging_const.py │ ├── db │ │ └── database.py │ ├── disc │ │ ├── command_hinter.py │ │ ├── embed_data.py │ │ └── get_prefix.py │ ├── text │ │ ├── britishify.py │ │ ├── censor_words.py │ │ ├── decancer.py │ │ ├── safe_message.py │ │ ├── subscript_superscript.py │ │ ├── uwufy.py │ │ └── zalgoify.py │ └── web │ │ └── safe_aiohttp.py └── views │ ├── automod_views.py │ ├── button_roles_views.py │ ├── config_views.py │ ├── error_views.py │ ├── fun_views.py │ ├── help_views.py │ └── ticket_views.py └── test.py /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: black-format 2 | on: [push, pull_request] 3 | jobs: 4 | linter_name: 5 | name: runner / black formatter 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: rickstaa/action-black@v1 10 | with: 11 | black_args: ". --check" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *__pycache__/ 3 | .envf 4 | env/ 5 | venv/ 6 | node_modules/ 7 | package-lock.json 8 | venv/ 9 | .vscode/ 10 | .env.production 11 | .env -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim 2 | RUN apt-get update && apt-get install -y git 3 | 4 | COPY ./requirements.txt . 5 | RUN pip install -r requirements.txt 6 | 7 | RUN pip uninstall -y discord 8 | RUN pip uninstall -y py-cord 9 | RUN pip install git+https://github.com/pycord-development/pycord 10 | 11 | COPY ./src ./src 12 | COPY .env .env 13 | CMD ["python", "src/main.py"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpaceBot Archive. Use this code as a reference 🚀 2 | 3 |

4 | 5 |

6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # TODO: ALL DEPENDENCIES SHOULD BE LOCKED IN A VERSION MANIFEST 2 | aiohttp==3.8.1 3 | git+https://github.com/pycord-development/pycord 4 | jishaku 5 | topggpy 6 | asyncmy 7 | python-dotenv 8 | pycord-ext-ipc 9 | quart 10 | aiodns 11 | brotlipy 12 | cchardet 13 | orjson 14 | ciso8601 15 | beautifulsoup4 -------------------------------------------------------------------------------- /src/background_cogs/errorhandler.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from typing import Union 4 | 5 | from views.error_views import ErrorView 6 | 7 | 8 | class ErrorHandler(commands.Cog): 9 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 10 | super().__init__() 11 | self.bot = bot 12 | 13 | @commands.Cog.listener() 14 | async def on_command_error( 15 | self, 16 | ctx: Union[commands.Context, discord.ApplicationContext], 17 | error: Union[commands.CommandError, discord.ApplicationCommandError], 18 | ) -> None: 19 | if hasattr(ctx.command, "on_error"): 20 | return 21 | 22 | cog = ctx.cog 23 | if cog: 24 | if cog._get_overridden_method(cog.cog_command_error) is not None: 25 | return 26 | 27 | error = getattr(error, "original", error) 28 | 29 | self.bot.log.error( 30 | f"{ctx.author} ran command {ctx.command} with error: {error}" 31 | ) 32 | 33 | # Check if the error is a command error 34 | if isinstance(error, commands.CommandNotFound): 35 | return 36 | 37 | if isinstance(error, commands.MissingRequiredArgument): 38 | embed = discord.Embed( 39 | title="Missing Arguments", 40 | description=f"{ctx.command} requires the following arguments: `{[error.param.name]}`", 41 | color=0xED4245, 42 | ) 43 | embed.set_author( 44 | name=ctx.author.display_name, icon_url=ctx.author.avatar.url 45 | ) 46 | embed.set_thumbnail(url="https://emoji.discord.st/emojis/Error.png") 47 | embed.set_footer( 48 | text=f"💡 Tip: Use the {await self.bot.get_prefix(ctx.message)}help {ctx.command.name} command to see the usage of this command!" 49 | ) 50 | view = ErrorView(ctx, str(ctx.command)) 51 | return await ctx.send(embed=embed, view=view) 52 | 53 | if isinstance(error, commands.BotMissingPermissions): 54 | permissions = "\n".join( 55 | [i.replace("_", " ").upper() for i in error.missing_permissions] 56 | ) 57 | embed = discord.Embed( 58 | name="I'm missing some permissions!", 59 | description=f"I need the following permissions to run this command:\n**{permissions}**", 60 | color=0xED4245, 61 | ) 62 | await ctx.send(embed=embed) 63 | return 64 | 65 | try: 66 | if isinstance(error.original, commands.errors.MissingPermissions): 67 | permissions = "\n".join( 68 | [i.replace("_", " ").upper() for i in error.missing_permissions] 69 | ) 70 | embed = discord.Embed( 71 | name="You're missing some permissions!", 72 | description=f"You need the following permissions to run this command:\n**{permissions}**", 73 | color=0xED4245, 74 | ) 75 | await ctx.send(embed=embed) 76 | return 77 | 78 | if isinstance(error.original, commands.errors.BotMissingPermissions): 79 | permissions = "\n".join( 80 | [i.replace("_", " ").upper() for i in error.missing_permissions] 81 | ) 82 | embed = discord.Embed( 83 | name="I'm missing some permissions!", 84 | description=f"I need the following permissions to run this command:\n`{permissions}`", 85 | color=0xED4245, 86 | ) 87 | await ctx.send(embed=embed) 88 | return 89 | except AttributeError: 90 | pass 91 | 92 | if isinstance(error, commands.MissingPermissions): 93 | permissions = "\n".join( 94 | [i.replace("_", " ").upper() for i in error.missing_permissions] 95 | ) 96 | embed = discord.Embed( 97 | name="You're missing some permissions!", 98 | description=f"You need the following permissions to run this command:\n**{permissions}**", 99 | color=0xED4245, 100 | ) 101 | await ctx.send(embed=embed) 102 | return 103 | 104 | 105 | def setup(bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 106 | bot.add_cog(ErrorHandler(bot)) 107 | -------------------------------------------------------------------------------- /src/background_cogs/ipc.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from pycord.ext import ipc 3 | from typing import Union 4 | 5 | 6 | class IpcRoutes(commands.Cog): 7 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 8 | self.bot = bot 9 | 10 | @ipc.server.route() 11 | async def get_member_count(self, data: dict): 12 | guild = self.bot.get_guild( 13 | data.guild_id 14 | ) # get the guild object using parsed guild_id 15 | 16 | return guild.member_count # return the member count to the client 17 | 18 | 19 | def setup(bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 20 | bot.add_cog(IpcRoutes(bot)) 21 | -------------------------------------------------------------------------------- /src/background_cogs/keep_connected.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from discord.ext import commands 3 | from discord.ext import tasks 4 | import asyncio 5 | 6 | 7 | class Background(commands.Cog): 8 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 9 | self.bot = bot 10 | self.cursor = self.bot.cursor 11 | self.background_task.start() 12 | 13 | @tasks.loop(minutes=1) 14 | async def background_task(self): 15 | try: 16 | await self.bot.conn.ping(reconnect=True) 17 | 18 | except: 19 | await self.bot.connect_to_db(self.bot) 20 | 21 | @background_task.before_loop 22 | async def before_background_task(self): 23 | await self.bot.wait_until_ready() 24 | await asyncio.sleep(10) 25 | 26 | 27 | def setup(bot): 28 | bot.add_cog(Background(bot)) 29 | -------------------------------------------------------------------------------- /src/background_cogs/logging.py: -------------------------------------------------------------------------------- 1 | from typing import Union, List 2 | import asyncio 3 | import datetime 4 | 5 | import discord 6 | from discord.ext import commands, tasks 7 | from utility.db.database import Database 8 | 9 | # TODO: ADD WAYS TO CONFIGURE LOGGING 10 | # FIXME: Many logging not working and not tested at all 11 | class LoggingCog(commands.Cog): 12 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 13 | self.bot = bot 14 | 15 | @commands.Cog.listener() 16 | async def on_ready(self) -> None: 17 | await asyncio.sleep(10) 18 | self.database = Database(self.bot) 19 | self.force_update_logging_cache.start() 20 | 21 | async def check_and_get_channel( 22 | self, guildID: int, setting: str 23 | ) -> Union[discord.TextChannel, None]: 24 | await self.bot.wait_until_ready() 25 | try: 26 | channel = await self.database.get_log_settings(guildID, setting) 27 | except AttributeError: 28 | return 29 | if channel is None: 30 | return None 31 | return self.bot.get_channel(int(channel)) 32 | 33 | @tasks.loop(seconds=120) 34 | async def force_update_logging_cache(self): 35 | await asyncio.sleep(10) 36 | await self.database._logging_cache_set() 37 | 38 | @force_update_logging_cache.before_loop 39 | async def before_force_update_logging_cache(self): 40 | await self.bot.wait_until_ready() 41 | 42 | # * TESTED 43 | @commands.Cog.listener() 44 | async def on_message_edit(self, before: discord.Message, after: discord.Message): 45 | if not before.guild: 46 | return 47 | channel = await self.check_and_get_channel(before.guild.id, "message_edit") 48 | if channel is None: 49 | return 50 | embed = discord.Embed( 51 | description=f""" 52 | [Message]({before.jump_url}) edited in {before.channel.mention} 53 | """, 54 | timestamp=datetime.datetime.utcnow(), 55 | color=discord.Color.blue(), 56 | ) 57 | if before.content == after.content or not any([before.content, after.content]): 58 | return 59 | embed.add_field(name="Before", value=before.content, inline=False) 60 | embed.add_field(name="After", value=after.content, inline=False) 61 | embed.set_author(name=before.author, icon_url=before.author.avatar.url) 62 | embed.set_footer( 63 | text="Message ID: " 64 | + str(before.id) 65 | + " | User ID: " 66 | + str(before.author.id) 67 | ) 68 | try: 69 | await channel.send(embed=embed) 70 | except discord.HTTPException: 71 | pass 72 | 73 | @commands.Cog.listener() 74 | async def on_bulk_message_delete(self, messages: List[discord.Message]): 75 | if not messages[0].guild: 76 | return 77 | channel = await self.check_and_get_channel(messages[0].guild.id, "bulk_delete") 78 | if channel is None: 79 | return 80 | 81 | embed = discord.Embed( 82 | description=f""" 83 | Bulk messages deleted | {len(messages)} messages in {messages[0].channel.mention} 84 | """, 85 | timestamp=datetime.datetime.utcnow(), 86 | color=discord.Color.blue(), 87 | ) 88 | embed.set_author( 89 | name=f"{messages[0].author}", 90 | icon_url=messages[0].author.avatar.url, 91 | ) 92 | embed.set_footer( 93 | text="Message ID: " 94 | + str(messages[0].id) 95 | + " | User ID: " 96 | + str(messages[0].author.id) 97 | ) 98 | await channel.send(embed=embed) 99 | 100 | @commands.Cog.listener() 101 | async def on_guild_channel_create(self, channel: discord.abc.GuildChannel): 102 | channel_ = await self.check_and_get_channel(channel.guild.id, "channel_create") 103 | if channel_ is None: 104 | return 105 | embed = discord.Embed( 106 | description=f"{channel.mention} was created", 107 | timestamp=datetime.datetime.utcnow(), 108 | color=discord.Color.green(), 109 | ) 110 | embed.set_author(name=channel.guild.name, icon_url=channel.guild.icon.url) 111 | embed.set_footer(text=f" Channel ID: {channel.id}") 112 | await channel_.send(embed=embed) 113 | 114 | @commands.Cog.listener() 115 | async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): 116 | channel_ = await self.check_and_get_channel(channel.guild.id, "channel_delete") 117 | if channel_ is None: 118 | return 119 | embed = discord.Embed( 120 | description=f"{channel.mention} was deleted", 121 | timestamp=datetime.datetime.utcnow(), 122 | color=discord.Color.green(), 123 | ) 124 | embed.set_author(name=channel.guild.name, icon_url=channel.guild.icon.url) 125 | await channel_.send(embed=embed) 126 | 127 | @commands.Cog.listener() 128 | async def on_guild_channel_update( 129 | self, before: discord.abc.GuildChannel, after: discord.abc.GuildChannel 130 | ): 131 | channel = await self.check_and_get_channel(after.guild.id, "channel_update") 132 | if channel is None: 133 | return 134 | embed = discord.Embed( 135 | description=f""" 136 | {before.mention}({before.name}) was updated 137 | """, 138 | colour=discord.Color.blue(), 139 | ) 140 | old, new = ( 141 | ("Its a voice channel", "Its a voice channel") 142 | if not before.type == discord.ChannelType.text 143 | else (before.topic, after.topic) 144 | ) 145 | 146 | embed.add_field( 147 | name="Before", 148 | value=f""" 149 | **Name**: {before.name} 150 | **Old Topic**: {old} 151 | **Position**: {before.position}""", 152 | inline=False, 153 | ) 154 | embed.add_field( 155 | name="After", 156 | value=f""" 157 | **Name**: {after.name} 158 | **New Topic**: {new} 159 | **Position**: {after.position}""", 160 | inline=False, 161 | ) 162 | await channel.send(embed=embed) 163 | 164 | embed.set_author(name=before.guild.name, icon_url=before.guild.icon.url) 165 | await channel.send(embed=embed) 166 | 167 | @commands.Cog.listener() 168 | async def on_guild_role_create(self, role: discord.Role): 169 | channel = await self.check_and_get_channel(role.guild.id, "role_create") 170 | if channel is None: 171 | return 172 | embed = discord.Embed( 173 | description=f"{role.mention} was created", 174 | timestamp=datetime.datetime.utcnow(), 175 | color=discord.Color.green(), 176 | ) 177 | embed.set_author(name=role.guild.name, icon_url=role.guild.icon.url) 178 | embed.set_footer(text=f"Role ID: {role.id}") 179 | await channel.send(embed=embed) 180 | 181 | @commands.Cog.listener() 182 | async def on_guild_role_delete(self, role: discord.Role): 183 | channel = await self.check_and_get_channel(role.guild.id, "role_delete") 184 | if channel is None: 185 | return 186 | embed = discord.Embed( 187 | description=f"{role.mention} was deleted", 188 | timestamp=datetime.datetime.utcnow(), 189 | color=discord.Color.red(), 190 | ) 191 | embed.set_author(name=role.guild.name, icon_url=role.guild.icon.url) 192 | await channel.send(embed=embed) 193 | 194 | @commands.Cog.listener() 195 | async def on_guild_role_update(self, before: discord.Role, after: discord.Role): 196 | channel = await self.check_and_get_channel(after.guild.id, "role_update") 197 | if channel is None: 198 | return 199 | if ( 200 | before.name == after.name 201 | and before.permissions == after.permissions 202 | and before.colour == after.colour 203 | ): 204 | return 205 | 206 | embed = discord.Embed( 207 | title="Role update", 208 | description=f""" 209 | Before update: 210 | {before.mention}({before.name}) 211 | Position : {before.position} 212 | Permissions : {before.permissions} 213 | Color : {before.colour} 214 | Hoist : {before.hoist} 215 | Mentionable : {before.mentionable} 216 | 217 | After update: 218 | {after.mention}({after.name}) 219 | Position : {after.position} 220 | Permissions : {after.permissions} 221 | Color : {after.colour} 222 | Hoist : {after.hoist} 223 | Mentionable : {after.mentionable} 224 | """, 225 | colour=discord.Color.blue(), 226 | ) 227 | embed.set_author(name=before.guild.name, icon_url=before.guild.icon.url) 228 | await channel.send(embed=embed) 229 | 230 | #! ____________________________ 231 | # * Tested uptil here 232 | #! ____________________________ 233 | 234 | @commands.Cog.listener() 235 | async def on_guild_emojis_update( 236 | self, 237 | guild: discord.Guild, 238 | before: List[discord.Emoji], 239 | after: List[discord.Emoji], 240 | ): 241 | channel = await self.check_and_get_channel(guild.id, "emoji_update") 242 | if channel is None: 243 | return 244 | 245 | embed = discord.Embed( 246 | title="Emoji update", 247 | description=f""" 248 | Before update: 249 | {before} 250 | After update: 251 | {after} 252 | """, 253 | colour=discord.Color.blue(), 254 | ) 255 | embed.set_author(name=self.bot.user.name, icon_url=self.bot.user.avatar.url) 256 | await channel.send(embed=embed) 257 | 258 | @commands.Cog.listener() 259 | async def on_guild_integrations_update(self, guild: discord.Guild): 260 | pass 261 | 262 | @commands.Cog.listener() 263 | async def on_invite_create(self, invite: discord.Invite): 264 | channel = await self.check_and_get_channel(invite.guild.id, "invite_info") 265 | if channel is None: 266 | return 267 | embed = discord.Embed( 268 | title="Invite create", 269 | description=f""" 270 | {invite.inviter.mention}({invite.inviter.name}) 271 | in : {invite.guild.name} 272 | channel : {invite.channel.mention} ({invite.channel.name}) 273 | Max age: {invite.max_age} 274 | Max users: {invite.max_uses} 275 | is temperory? {invite.temporary} 276 | expires at: {invite.expires_at} 277 | """, 278 | ) 279 | await channel.send(embed=embed) 280 | 281 | @commands.Cog.listener() 282 | async def on_member_ban(self, guild: discord.Guild, user: discord.User): 283 | channel = await self.check_and_get_channel(guild.id, "member_ban") 284 | if channel is None: 285 | return 286 | embed = discord.Embed( 287 | title="Member ban", 288 | description=f""" 289 | {user.mention}({user.name}) was banned 290 | """, 291 | ) 292 | await channel.send(embed=embed) 293 | 294 | @commands.Cog.listener() 295 | async def on_member_join(self, member: discord.Member): 296 | channel = await self.check_and_get_channel(member.guild.id, "member_join") 297 | if channel is None: 298 | return 299 | created_at = discord.utils.format_dt(member.created_at, style="R") 300 | joined_at = discord.utils.format_dt(member.joined_at, style="R") 301 | embed = discord.Embed( 302 | title="Member joined", 303 | description=f"{member.mention} {member.id} joined the server.\nAccount created: {created_at}\nJoined: {joined_at}", 304 | timestamp=datetime.datetime.utcnow(), 305 | ) 306 | await channel.send(embed=embed) 307 | 308 | @commands.Cog.listener() 309 | async def on_member_remove(self, member: discord.Member): 310 | channel = await self.check_and_get_channel(member.guild.id, "member_leave") 311 | if channel is None: 312 | return 313 | created_at = discord.utils.format_dt(member.created_at, style="R") 314 | joined_at = discord.utils.format_dt(member.joined_at, style="R") 315 | embed = discord.Embed( 316 | title="Member left", 317 | description=f"{member.mention} {member.id} left the server.\nAccount created: {created_at}\nJoined: {joined_at}", 318 | timestamp=datetime.datetime.utcnow(), 319 | ) 320 | await channel.send(embed=embed) 321 | 322 | @commands.Cog.listener() 323 | async def on_member_unban(self, guild: discord.Guild, user: discord.User): 324 | channel = await self.check_and_get_channel(guild.id, "member_unban") 325 | if channel is None: 326 | return 327 | embed = discord.Embed( 328 | title="Member unban", 329 | description=f""" 330 | {user.mention}({user.name}) was unbanned 331 | """, 332 | colour=discord.Color.green(), 333 | ) 334 | await channel.send(embed=embed) 335 | 336 | @commands.Cog.listener() 337 | async def on_member_update(self, before: discord.Member, after: discord.Member): 338 | """This is the on_member_role_add and on_member_role_delete""" 339 | channel = await self.check_and_get_channel(after.guild.id, "member_role_add") 340 | if channel is None: 341 | return 342 | if before.roles != after.roles: 343 | embed = discord.Embed( 344 | title="Member role update", 345 | description=f""" 346 | Before update: 347 | {before.mention}({before.name}) 348 | Roles : {", ".join([role.name for role in before.roles])} 349 | After update: 350 | {after.mention}({after.name}) 351 | Roles : {", ".join([role.name for role in after.roles])} 352 | """, 353 | colour=discord.Color.blue(), 354 | ) 355 | await channel.send(embed=embed) 356 | 357 | @commands.Cog.listener() 358 | async def on_message_delete(self, message: discord.Message): 359 | channel = await self.check_and_get_channel(message.guild.id, "message_delete") 360 | if channel is None: 361 | return 362 | 363 | embed = discord.Embed( 364 | title="Message deleted", 365 | description=f""" 366 | {message.author.mention}({message.author.name}) 367 | {message.content} 368 | """, 369 | timestamp=datetime.datetime.utcnow(), 370 | ) 371 | await channel.send(embed=embed) 372 | 373 | @commands.Cog.listener() 374 | async def on_voice_state_update( 375 | self, 376 | member: discord.Member, 377 | before: discord.VoiceState, 378 | after: discord.VoiceState, 379 | ): 380 | channel = await self.check_and_get_channel( 381 | member.guild.id, "voice_channel_update" 382 | ) 383 | if channel is None: 384 | return 385 | if before.channel != after.channel: 386 | embed = discord.Embed( 387 | title="Voice channel update", 388 | description=f""" 389 | Before update: 390 | {before.channel.mention}({before.channel.name}) 391 | After update: 392 | {after.channel.mention}({after.channel.name}) 393 | """, 394 | timestamp=datetime.datetime.utcnow(), 395 | ) 396 | await channel.send(embed=embed) 397 | 398 | 399 | def setup(bot): 400 | bot.add_cog(LoggingCog(bot)) 401 | -------------------------------------------------------------------------------- /src/cogs/bot_commands.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from typing import Union 4 | 5 | from views.help_views import * 6 | from .help import MyHelp 7 | 8 | 9 | class BotCommands(commands.Cog): 10 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 11 | super().__init__() 12 | self.bot = bot 13 | self.bot.help_command = MyHelp() 14 | self.help_doc = "Some bot-commands that don't fit in other categories" 15 | 16 | @commands.command(name="ping") 17 | async def ping(self, ctx: commands.Context) -> None: 18 | """Check the ping of the bot""" 19 | await ctx.send(f"Pong! {round(self.bot.latency * 1000)}ms") 20 | 21 | 22 | def setup(bot): 23 | bot.add_cog(BotCommands(bot)) 24 | -------------------------------------------------------------------------------- /src/cogs/fun.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | import random 3 | import logging 4 | import asyncio 5 | 6 | import discord 7 | from discord.ext import commands 8 | 9 | from utility.text.britishify import strong_british_accent 10 | from utility.text.uwufy import owofy 11 | from utility.text.zalgoify import zalgo 12 | from utility.text.safe_message import safe_message 13 | from utility.web.safe_aiohttp import aiohttp_get 14 | 15 | from views.fun_views import * 16 | from utility.text.subscript_superscript import * 17 | 18 | 19 | class Fun(commands.Cog): 20 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 21 | self.bot = bot 22 | self.bot.log.debug(f"Loaded {self.__cog_name__} cog") 23 | self.help_doc = "Fun commands to have some fun!" 24 | self.beer_emojis = [] 25 | 26 | @commands.command("owofy", aliases=["uwufy", "owo"]) 27 | async def owofy(self, ctx: commands.Context, *, text: str) -> None: 28 | """ 29 | Oh... UwU Hai thewe ·///· Owofies the pwovided text uwu 30 | """ 31 | msg = safe_message(text) 32 | 33 | owofied = owofy(msg) 34 | # If the channel isnt a guild 35 | is_guild = True 36 | # if ctx.channel isnt in a guild, 37 | # then it isnt a guild channel 38 | if not isinstance(ctx.channel, discord.TextChannel): 39 | is_guild = False 40 | if not ctx.channel.permissions_for(ctx.me).manage_webhooks or not is_guild: 41 | embed = discord.Embed( 42 | title="OwO", description=owofied, color=self.bot.theme_colour["default"] 43 | ) 44 | embed.set_author( 45 | name=ctx.author.display_name, icon_url=ctx.author.avatar.url 46 | ) 47 | embed.set_thumbnail( 48 | url="https://www.kindpng.com/picc/m/236-2362818_anime-sempai-animegirl-heart-kawaii-cute-anime-girl.png" 49 | ) 50 | embed.set_footer( 51 | text="💡 Tip: Use the `owofy` command in a server where I have the Manage Webhooks permission and see the magic!" 52 | ) 53 | return await ctx.send(embed=embed) 54 | 55 | if ctx.channel.permissions_for(ctx.me).manage_messages: 56 | await ctx.message.delete() 57 | 58 | webhook = None 59 | for hook in await ctx.channel.webhooks(): 60 | if hook.name == ctx.guild.me.name: 61 | webhook = hook 62 | if webhook is None: 63 | webhook = await ctx.channel.create_webhook(name=ctx.guild.me.name) 64 | 65 | avatar = ctx.author.avatar.url.format(format="png") 66 | 67 | await webhook.send(owofied, username=ctx.author.display_name, avatar_url=avatar) 68 | 69 | @commands.command("britishify", aliases=["britainify", "brit", "british"]) 70 | async def britishify(self, ctx: commands.Context, *, text: str) -> None: 71 | """ 72 | cahn you pahss me ah bottle of waht-ah 73 | """ 74 | msg = safe_message(text) 75 | 76 | britishified = strong_british_accent(msg) 77 | 78 | is_guild = True 79 | 80 | if not isinstance(ctx.channel, discord.TextChannel): 81 | is_guild = False 82 | if not ctx.channel.permissions_for(ctx.me).manage_webhooks or not is_guild: 83 | embed = discord.Embed( 84 | title="Hey there Mate!", 85 | description=britishified, 86 | color=self.bot.theme_colour["default"], 87 | ) 88 | embed.set_author( 89 | name=ctx.author.display_name, icon_url=ctx.author.avatar.url 90 | ) 91 | embed.set_thumbnail( 92 | url="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f2/Flag_of_Great_Britain_%281707–1800%29.svg/188px-Flag_of_Great_Britain_%281707–1800%29.svg.png" 93 | ) 94 | embed.set_footer( 95 | text="💡 Tip: Use the `brit` command in a server where I have the Manage Webhooks permission and see the magic!" 96 | ) 97 | return await ctx.send(embed=embed) 98 | 99 | if ctx.channel.permissions_for(ctx.me).manage_messages: 100 | await ctx.message.delete() 101 | 102 | webhook = None 103 | for hook in await ctx.channel.webhooks(): 104 | if hook.name == ctx.guild.me.name: 105 | webhook = hook 106 | if webhook is None: 107 | webhook = await ctx.channel.create_webhook(name=ctx.guild.me.name) 108 | 109 | avatar = ctx.author.avatar.url.format(format="png") 110 | 111 | await webhook.send( 112 | britishified, username=ctx.author.display_name, avatar_url=avatar 113 | ) 114 | 115 | @commands.command("zalgoify", aliases=["corrupt"]) 116 | async def zalgoify(self, ctx: commands.Context, *, text: str) -> None: 117 | """ 118 | Zalgo-ify your message 119 | """ 120 | msg = safe_message(text) 121 | z = zalgo() 122 | # mazimum zalgo 123 | 124 | zalgoified = z.zalgofy(text=msg) 125 | 126 | is_guild = True 127 | # if ctx.channel isnt in a guild, 128 | # then it isnt a guild channel 129 | if not isinstance(ctx.channel, discord.TextChannel): 130 | is_guild = False 131 | if not ctx.channel.permissions_for(ctx.me).manage_webhooks or not is_guild: 132 | embed = discord.Embed( 133 | title="CORRUPT", 134 | description=zalgoified, 135 | color=self.bot.theme_colour["default"], 136 | ) 137 | embed.set_author( 138 | name=ctx.author.display_name, icon_url=ctx.author.avatar.url 139 | ) 140 | embed.set_thumbnail( 141 | url="https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Zalgo_text_filter.png/300px-Zalgo_text_filter.png" 142 | ) 143 | embed.set_footer( 144 | text="💡 Tip: Use the `corrupt` command in a server where I have the Manage Webhooks permission and see the magic!" 145 | ) 146 | return await ctx.send(embed=embed) 147 | 148 | if ctx.channel.permissions_for(ctx.me).manage_messages: 149 | await ctx.message.delete() 150 | 151 | webhook = None 152 | for hook in await ctx.channel.webhooks(): 153 | if hook.name == ctx.guild.me.name: 154 | webhook = hook 155 | if webhook is None: 156 | webhook = await ctx.channel.create_webhook(name=ctx.guild.me.name) 157 | 158 | avatar = ctx.author.avatar.url.format(format="png") 159 | 160 | await webhook.send( 161 | zalgoified, username=ctx.author.display_name, avatar_url=avatar 162 | ) 163 | 164 | @commands.command("decancer", aliases=["deconfuse"]) 165 | async def decancer(self, ctx: commands.Context, *, text: str) -> None: 166 | pass 167 | 168 | @commands.command(name="roast", aliases=["roastme"]) 169 | async def roast(self, ctx: commands.Context, user: discord.Member = None) -> None: 170 | """ 171 | Roast yourself or a friend! 172 | """ 173 | user = user or ctx.author 174 | response = await aiohttp_get( 175 | "https://evilinsult.com/generate_insult.php?lang=en", _type="text" 176 | ) 177 | if response is None: 178 | return await ctx.send("Failed to get insult") 179 | embed = discord.Embed( 180 | title=response, 181 | description="OOOOOOOOOOOOOOO", 182 | color=self.bot.theme_colour["default"], 183 | ) 184 | embed.set_author( 185 | name=f"{ctx.author.display_name} Roasted {user.display_name}!", 186 | icon_url=ctx.author.avatar.url, 187 | ) 188 | embed.set_thumbnail(url="https://c.tenor.com/f8YmpuCCXJcAAAAC/roasted-oh.gif") 189 | embed.set_footer( 190 | text="Roast is sourced through evilinsult.com.\nWe are not responsible for the content." 191 | ) 192 | view = RoastAgainButton(user, ctx.author, ctx, self.bot) 193 | await ctx.send( 194 | user.mention if not user == ctx.author else None, embed=embed, view=view 195 | ) 196 | 197 | @commands.command(name="darkjoke", aliases=["dark_joke"]) 198 | async def darkjoke(self, ctx: commands.Context) -> None: 199 | """Returns a random dark joke""" 200 | response = await aiohttp_get( 201 | "https://v2.jokeapi.dev/joke/Dark?blacklistFlags=nsfw,religious&format=txt", 202 | _type="text", 203 | ) 204 | if response is None: 205 | return await ctx.send("Failed to get joke") 206 | embed = discord.Embed( 207 | title="Dark Joke", 208 | description=response, 209 | color=self.bot.theme_colour["default"], 210 | ) 211 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 212 | embed.set_thumbnail( 213 | url="https://purepng.com/public/uploads/large/purepng.com-skullskullvertebratesfacehuman-skull-1421526968219gpljr.png" 214 | ) 215 | embed.set_footer( 216 | text="NOTE: Dark Joke is sourced from jokeapi.dev. We are not responsible for the content." 217 | ) 218 | view = DarkJokeAgainButton(ctx) 219 | await ctx.send(embed=embed, view=view) 220 | 221 | @commands.command(name="dadjoke", aliases=["dad_joke"]) 222 | async def dadjoke(self, ctx: commands.Context) -> None: 223 | """Gets a Dad joke""" 224 | joke = await aiohttp_get( 225 | "https://icanhazdadjoke.com/", 226 | _type="text", 227 | headers={"Accept": "text/plain"}, 228 | ) 229 | if joke is None: 230 | return await ctx.send("Failed to get joke") 231 | embed = discord.Embed( 232 | title="Dad Joke", description=joke, color=self.bot.theme_colour["default"] 233 | ) 234 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 235 | embed.set_thumbnail( 236 | url="https://us-east-1.tixte.net/uploads/i.dhravya.me/dad_joke_spacebot.png" 237 | ) 238 | embed.set_footer( 239 | text="Dad Joke is sourced from icanhazdadjoke.com. We are not responsible for the content." 240 | ) 241 | view = DadJokeAgainButton(ctx) 242 | await ctx.send(embed=embed, view=view) 243 | 244 | @commands.command(name="fact", aliases=["factoid"]) 245 | async def fact(self, ctx: commands.Context) -> None: 246 | """Gets a random fact""" 247 | await ctx.send("This endpoint isnt ready yet!") 248 | 249 | @commands.command(name="8ball", aliases=["eightball"]) 250 | async def eightball(self, ctx: commands.Context, *, question: str) -> None: 251 | """Ask the magic 🎱""" 252 | responses = { 253 | "It is certain.": 0x2ECC71, 254 | "It is decidedly so.": 0x2ECC71, 255 | "Without a doubt.": 0x2ECC71, 256 | "Yes - definitely.": 0x2ECC71, 257 | "You may rely on it.": 0x2ECC71, 258 | "As I see it, yes.": 0x2ECC71, 259 | "Most likely.": 0x2ECC71, 260 | "Outlook good.": 0x2ECC71, 261 | "Yes.": 0x2ECC71, 262 | "Signs point to yes.": 0x2ECC71, 263 | "send hazy, try again.": 0xE67E22, 264 | "Ask again later.": 0xE74C3C, 265 | "Better not tell you now.": 0xE74C3C, 266 | "Cannot predict now.": 0xE74C3C, 267 | "Concentrate and ask again.": 0xE74C3C, 268 | "Don't count on it.": 0xE74C3C, 269 | "My send is no.": 0xE74C3C, 270 | "My sources say no.": 0xE74C3C, 271 | "No": 0xE74C3C, 272 | "Clearly no": 0xE74C3C, 273 | "Outlook not so good.": 0xE74C3C, 274 | "Very doubtful.": 0xE67E22, 275 | "Maybe.": 0xE67E22, 276 | } 277 | 278 | answer = random.choice(list(responses.keys())) 279 | color = responses[answer] 280 | 281 | embed = discord.Embed( 282 | title=f"8 ball", 283 | description=f"**```Question: {question}```\n```Answer: {answer}```**", 284 | color=color, 285 | ) 286 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 287 | await ctx.send(embed=embed) 288 | 289 | @commands.command(name="truth") 290 | async def truth(self, ctx: commands.Context) -> None: 291 | """Gets a random truth""" 292 | truth = await aiohttp_get( 293 | "https://api.dhravya.me/truth?simple=true", _type="text" 294 | ) 295 | if truth is None: 296 | return await ctx.send("Failed to get truth") 297 | embed = discord.Embed( 298 | title="Say the Truth!!", 299 | description=truth, 300 | color=self.bot.theme_colour["default"], 301 | ) 302 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 303 | await ctx.send(embed=embed) 304 | 305 | @commands.command(name="dare") 306 | async def dare(self, ctx: commands.Context) -> None: 307 | """Gets a random dare""" 308 | dare = await aiohttp_get( 309 | "https://api.dhravya.me/dare?simple=true", _type="text" 310 | ) 311 | if dare is None: 312 | return await ctx.send("Failed to get dare") 313 | embed = discord.Embed( 314 | title="Say the dare!!", 315 | description=dare, 316 | color=self.bot.theme_colour["default"], 317 | ) 318 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 319 | await ctx.send(embed=embed) 320 | 321 | @commands.command(name="comic", aliases=["xkcd"]) 322 | async def comic( 323 | self, ctx: commands.Context, number: Union[int, str] = None 324 | ) -> None: 325 | """Gets a random comic from xkcd.com""" 326 | mes = await ctx.send("Fetching comic...") 327 | if number is None: 328 | json_ = await aiohttp_get("https://xkcd.com/info.0.json", _type="json") 329 | current_comic = json_["num"] 330 | comic_number = random.randint(1, current_comic) 331 | else: 332 | comic_number = number 333 | comic_json = await aiohttp_get( 334 | f"https://xkcd.com/{comic_number}/info.0.json", _type="json" 335 | ) 336 | if comic_json is None: 337 | logging.warning(f"Failed to get comic {comic_number}") 338 | return await ctx.send("Failed to get comic") 339 | 340 | embed = discord.Embed( 341 | title=f"xkcd #{comic_number}", 342 | description=comic_json["alt"], 343 | color=self.bot.theme_colour["default"], 344 | ) 345 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 346 | embed.set_image(url=comic_json["img"]) 347 | embed.set_footer(text=f"xkcd.com - {comic_json['year']}") 348 | view = GetComicAgainButton(ctx, comic_json["img"]) 349 | await mes.edit("", embed=embed, view=view) 350 | 351 | @commands.command(name="topic") 352 | async def topic(self, ctx: commands.Context) -> None: 353 | """Gets a random topic""" 354 | topic = await aiohttp_get( 355 | "https://api.dhravya.me/topic?simple=true", _type="text" 356 | ) 357 | is_guild = True 358 | if topic is None: 359 | return await ctx.send("Failed to get topic") 360 | 361 | if isinstance(ctx.channel, discord.DMChannel): 362 | is_guild = False 363 | 364 | if not ctx.channel.permissions_for(ctx.me).manage_webhooks or not is_guild: 365 | 366 | embed = discord.Embed( 367 | title="Topic for chat", 368 | description=topic, 369 | color=self.bot.theme_colour["default"], 370 | ) 371 | embed.set_author( 372 | name=ctx.author.display_name, icon_url=ctx.author.avatar.url 373 | ) 374 | await ctx.send(embed=embed) 375 | 376 | webhook = None 377 | for hook in await ctx.channel.webhooks(): 378 | if hook.name == ctx.guild.me.name: 379 | webhook = hook 380 | if webhook is None: 381 | webhook = await ctx.channel.create_webhook(name=ctx.guild.me.name) 382 | 383 | avatar = ctx.author.avatar.url.format(format="png") 384 | embed = discord.Embed( 385 | description=safe_message(topic), colour=self.bot.theme_colour["default"] 386 | ) 387 | embed.set_footer(text="Use the topic command to get more") 388 | await webhook.send( 389 | embed=embed, username=ctx.author.display_name, avatar_url=avatar 390 | ) 391 | if ctx.channel.permissions_for(ctx.me).manage_messages: 392 | await ctx.message.delete() 393 | 394 | @commands.command(name="numberfact") 395 | async def numberfact( 396 | self, ctx: commands.Context, number: Union[int, None] = None 397 | ) -> None: 398 | """Did you know the meaning of 69?""" 399 | number = number or random.randint(1, 200) 400 | fact = await aiohttp_get(f"http://numbersapi.com/{number}", _type="text") 401 | if fact is None: 402 | return await ctx.send("Failed to get fact") 403 | embed = discord.Embed( 404 | title=f"Did you know?", 405 | description=fact, 406 | color=self.bot.theme_colour["default"], 407 | ) 408 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 409 | await ctx.send(embed=embed) 410 | 411 | @commands.command(name="asktrump", aliases=["trump", "ask_trump"]) 412 | async def ask_trump(self, ctx: commands.Context, question: str) -> None: 413 | """What does trump think? 🤔""" 414 | message = await ctx.send("Asking Trump...") 415 | trump = await aiohttp_get( 416 | f"https://api.whatdoestrumpthink.com/api/v1/quotes/personalized?q={question}" 417 | ) 418 | quote = trump["message"] 419 | embed = discord.Embed( 420 | title="Trump says...", 421 | description=quote, 422 | color=self.bot.theme_colour["default"], 423 | ) 424 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 425 | await message.edit("", embed=embed) 426 | 427 | @commands.command(name="beer", aliases=["cheers"]) 428 | async def beer( 429 | self, ctx: commands.Context, user: discord.Member, *, reason: str = None 430 | ) -> None: 431 | """Cheers to your friend!""" 432 | if user.id == self.bot.user.id: 433 | return await ctx.send("*drinks beer with you* 🍻") 434 | 435 | if user.bot: 436 | return await ctx.send( 437 | f"I would love to give beer to the bot **{ctx.author.name}**, but I don't think it will respond to you :/" 438 | ) 439 | 440 | if user == ctx.author: 441 | return await ctx.send( 442 | "You can't give beer to yourself! ;-; \n (*PS* if you feel lonely, you can have a party with me or SpaceDoggo :wink:)" 443 | ) 444 | 445 | beer_offer = f"**{user.name}**, you got a 🍺 offer from **{ctx.author.name}**" 446 | beer_offer = beer_offer + f"\n\n**Reason:** {reason}" if reason else beer_offer 447 | 448 | view = BeerOfferView(ctx=ctx, user=user) 449 | 450 | embed = discord.Embed( 451 | title="Cheers! 🎉", 452 | description=beer_offer, 453 | color=self.bot.theme_colour["default"], 454 | ) 455 | await ctx.send(user.mention, embed=embed, view=view) 456 | 457 | @commands.command(name="coinflip") 458 | async def coinflip( 459 | self, ctx: commands.Context, message: discord.Message = None 460 | ) -> None: 461 | """Flip a coin to end that debate!""" 462 | if message: 463 | if not message.author.id == self.bot.user.id: 464 | return 465 | coin = random.choice(["heads", "tails"]) 466 | url = f"https://us-east-1.tixte.net/uploads/i.dhravya.me/coin_{coin}.gif" 467 | embed = discord.Embed( 468 | title="Coin Flip!", 469 | description=f"Flipping the coin...", 470 | colour=self.bot.theme_colour["default"], 471 | ) 472 | embed.set_image(url=url) 473 | message_ = ( 474 | await ctx.send(embed=embed) 475 | if not message 476 | else await message.edit(embed=embed) 477 | ) 478 | await asyncio.sleep(3) 479 | embed.description = "The coin landed on **{}**".format(coin) 480 | view = CoinFlipAgainView(ctx=ctx) 481 | await message_.edit(embed=embed, view=view) 482 | 483 | @commands.command(name="beerparty") 484 | async def beerparty_(self, ctx: commands.Context): 485 | """Have a beerparty in the server. Invite your friends!""" 486 | embed = discord.Embed( 487 | title="Beer Party 🍻", 488 | description=f"{ctx.author.mention} had invited everyone to join up this beer party :beers:!", 489 | color=discord.Color.green(), 490 | ) 491 | message = await ctx.send(embed=embed) 492 | await message.edit(embed=embed, view=BeerPartyView(message, ctx)) 493 | 494 | 495 | def setup(bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 496 | bot.add_cog(Fun(bot)) 497 | -------------------------------------------------------------------------------- /src/cogs/help.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from discord import utils, Embed, Color, Interaction, SelectOption 4 | from discord.ui import View, Select 5 | from discord.ext.commands import Command, Cog, HelpCommand, Context, Group 6 | 7 | ignored_cogs = [ 8 | "jishaku", 9 | "loggingcog", 10 | "errorhandler", 11 | "ipcroutes", 12 | "background", 13 | "errorhandler", 14 | "funslash", 15 | ] 16 | 17 | 18 | class MyHelp(HelpCommand): 19 | def __init__(self): 20 | super().__init__() 21 | 22 | async def command_callback(self, ctx, *, cmd=None): 23 | """ 24 | To make the help for Cog case-insensitive 25 | """ 26 | await self.prepare_help_command(ctx, cmd) 27 | bot = ctx.bot 28 | 29 | if cmd is None: 30 | mapping = self.get_bot_mapping() 31 | return await self.send_bot_help(mapping) 32 | 33 | # Check if it's a cog 34 | cog = None 35 | _cog = [cog for cog in ctx.bot.cogs if cog.lower() == cmd.lower()] 36 | if _cog: 37 | cog = _cog[0] 38 | if cog is not None: 39 | return await self.send_cog_help(ctx.bot.get_cog(cog)) 40 | 41 | maybe_coro = utils.maybe_coroutine 42 | 43 | # If it's not a cog then it's a command. 44 | # Since we want to have detailed errors when someone 45 | # passes an invalid subcommand, we need to walk through 46 | # the command group chain ourselves. 47 | keys = cmd.split(" ") 48 | cmd = bot.all_commands.get(keys[0]) 49 | if cmd is None: 50 | string = await maybe_coro( 51 | self.command_not_found, self.remove_mentions(keys[0]) 52 | ) 53 | return await self.send_error_message(string) 54 | 55 | for key in keys[1:]: 56 | try: 57 | found = cmd.all_commands.get(key) 58 | except AttributeError: 59 | string = await maybe_coro( 60 | self.subcommand_not_found, cmd, self.remove_mentions(key) 61 | ) 62 | return await self.send_error_message(string) 63 | else: 64 | if found is None: 65 | string = await maybe_coro( 66 | self.subcommand_not_found, cmd, self.remove_mentions(key) 67 | ) 68 | return await self.send_error_message(string) 69 | cmd = found 70 | 71 | if isinstance(cmd, Group): 72 | return await self.send_group_help(cmd) 73 | else: 74 | return await self.send_command_help(cmd) 75 | 76 | async def send_bot_help(self, mapping): 77 | """ 78 | Bot's main help command 79 | """ 80 | _bot = self.context.bot 81 | 82 | embed = Embed( 83 | color=Color.og_blurple(), 84 | description=_bot.description 85 | + f"Prefix: `{await _bot.get_prefix(self.context.message)}`", 86 | timestamp=self.context.message.created_at, 87 | ) 88 | for cog_name in _bot.cogs: 89 | if cog_name.lower() in ignored_cogs: 90 | continue 91 | cog: Cog = _bot.get_cog(cog_name) 92 | try: 93 | cog_help = cog.help_doc 94 | except AttributeError: 95 | cog_help = None 96 | embed.add_field( 97 | name=f"{cog.qualified_name.capitalize()} category ({len(cog.get_commands())} commands)", 98 | value=f"> {cog_help or 'No help text'}", # FIXED #14 99 | inline=False, 100 | ) 101 | 102 | embed.set_author(name=f"Bot help", icon_url=_bot.user.display_avatar.url) 103 | embed.set_footer( 104 | text=f"Requested by {self.context.author}", 105 | icon_url=self.context.author.display_avatar.url, 106 | ) 107 | embed.set_thumbnail(url=_bot.user.display_avatar.url) 108 | view = HelpView() 109 | view.add_item(NavigatorMenu(self.context)) 110 | view.message = await self.context.reply( 111 | embed=embed, mention_author=False, view=view 112 | ) 113 | await asyncio.sleep(180) 114 | for views in view.children: 115 | views.disabled = True 116 | await view.message.edit(embed=embed, view=view) 117 | 118 | async def send_command_help(self, cmd: Command): 119 | """ 120 | Called when a command arg is given 121 | """ 122 | command_help_dict = { 123 | "aliases": " ,".join(cmd.aliases) or "No aliases for this command", 124 | "description": cmd.description 125 | or cmd.brief 126 | or cmd.short_doc 127 | or "No command help", 128 | } 129 | command_signature = "" 130 | for arg in cmd.signature.split(" ")[: len(cmd.params) - 2]: 131 | if "=" in arg: 132 | parsed_arg = "{" + arg.split("=")[0].strip("[]<>]") + "}" 133 | else: 134 | parsed_arg = "[" + arg.strip("[]<>") + "]" 135 | if parsed_arg == "[]": 136 | parsed_arg = "" 137 | command_signature += parsed_arg + " " 138 | usage = f"```py\n{await self.context.bot.get_prefix(self.context.message)}{cmd.name} {command_signature}\n```" 139 | embed = Embed( 140 | color=Color.og_blurple(), 141 | description="\n".join( 142 | f"`{key}` : {command_help_dict[key]}" 143 | for key in command_help_dict.keys() 144 | ), 145 | ) 146 | embed.set_author( 147 | name=f"`{cmd.name}` command", 148 | icon_url=self.context.bot.user.display_avatar.url, 149 | ) 150 | embed.add_field(name="Usage:", value=usage) 151 | embed.set_footer( 152 | text="[] means required arguments | {} means optional arguments" 153 | ) 154 | await self.context.reply(embed=embed, mention_author=False) 155 | 156 | async def send_group_help(self, group: Group): 157 | """ 158 | Group command help 159 | """ 160 | desc = "\n".join( 161 | f"`{cmd.name}` : {cmd.short_doc or 'No help text'}" 162 | for cmd in group.commands 163 | ) 164 | embed = Embed( 165 | description=group.description 166 | or f"`{group.qualified_name.capitalize()}` group" + desc, 167 | color=Color.og_blurple(), 168 | ) 169 | await self.context.reply(embed=embed, mention_author=False) 170 | 171 | async def send_cog_help(self, cog: Cog): 172 | """ 173 | Help for a specific cog, gets embed with 'cog_embed' 174 | """ 175 | embed = await cog_embed(cog, self.context) 176 | await self.context.reply(embed=embed, mention_author=False) 177 | 178 | 179 | class HelpView(View): 180 | def __init__(self): 181 | super().__init__(timeout=180) 182 | 183 | 184 | class NavigatorMenu(Select): 185 | def __init__(self, ctx: Context) -> None: 186 | self.context: Context = ctx 187 | options = [] 188 | for cog_name in ctx.bot.cogs: 189 | if cog_name.lower() in ignored_cogs: 190 | continue 191 | cog: Cog = ctx.bot.get_cog(cog_name) 192 | options.append( 193 | SelectOption( 194 | label=f"{cog.qualified_name.capitalize()} commands", 195 | description=cog.description.replace("cog", "module"), 196 | ) 197 | ) 198 | super().__init__(placeholder="Navigate to a category", options=options) 199 | 200 | async def callback(self, interaction: Interaction): 201 | if interaction.user.id != self.context.author.id: 202 | return await interaction.response.send_message( 203 | f"This is not your help command.", 204 | ephemeral=True, 205 | ) 206 | cog_s = [ 207 | self.context.bot.get_cog(cog) 208 | for cog in self.context.bot.cogs 209 | if self.values[0].lower().replace(" commands", "") 210 | == self.context.bot.get_cog(cog).qualified_name.lower() 211 | ] 212 | embed = await cog_embed(cog_s[0], self.context) 213 | await interaction.response.defer() 214 | await interaction.message.edit(embed=embed) 215 | 216 | 217 | async def cog_embed(cog: Cog, ctx: Context): 218 | desc = "\n".join( 219 | f"`{cmd.name}` - {cmd.description or cmd.short_doc or 'No command help'}" 220 | for cmd in cog.get_commands() 221 | ) 222 | embed = ( 223 | Embed( 224 | color=Color.og_blurple(), 225 | description=f"Use `{await ctx.bot.get_prefix(ctx.message)}help ` for more info about commands\n\n" 226 | + desc, 227 | ) 228 | .set_author( 229 | name=f"{cog.qualified_name.capitalize()} category", 230 | icon_url=ctx.bot.user.display_avatar.url, 231 | ) 232 | .set_footer( 233 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 234 | ) 235 | ) 236 | return embed 237 | 238 | 239 | def setup(bot): 240 | pass 241 | -------------------------------------------------------------------------------- /src/cogs/ticket.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from typing import Union 4 | from views.ticket_views import * 5 | 6 | 7 | class Tickets(commands.Cog): 8 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 9 | self.bot = bot 10 | 11 | self.help_doc = "Ticket commands" 12 | 13 | @commands.Cog.listener() 14 | async def on_message(self, message: discord.Message): 15 | try: 16 | if ( 17 | message.author.id == self.bot.user.id 18 | and len(message.embeds) > 0 19 | and message.embeds[0].description.startswith("**Ticket closed by") 20 | ): 21 | await self.bot.cursor.execute( 22 | "SELECT * FROM tickets WHERE guild_id=%s AND channel_id=%s", 23 | (str(message.guild.id), str(message.channel.id)), 24 | ) 25 | data = await self.bot.cursor.fetchone() 26 | embed = discord.Embed( 27 | description="```py\n[Support team ticket controls]```", 28 | color=discord.Color.embed_background(theme="dark"), 29 | ) 30 | await message.channel.send( 31 | embed=embed, view=TicketControlsView(self.bot) 32 | ) 33 | except AttributeError: 34 | pass 35 | 36 | @commands.group(name="panel") 37 | async def panel_(self, ctx: commands.Context): 38 | """Ticket Panel related commands""" 39 | if ctx.invoked_subcommand is None: 40 | embed = discord.Embed( 41 | title="Panel", 42 | description="**--> `panel create`: Creates a panel\nUsage: `panel create [name]`\nExample: `panel create #ticket Get a ticket`\n\n--> `panel delete`: Deletes a panel\nUsage: `panel delete [panel_id]`\nExample: `panel delete #ticket 987654321123456789`\n\n--> `panel edit`: Edits the name of a panel\nUsage: `panel edit [panel_id] (name)`\nExample: `panel edit #ticket 987654321123456789 I just changed the name of the panel!`**", 43 | color=discord.Color.green(), 44 | ) 45 | await ctx.send(embed=embed) 46 | 47 | @commands.group(name="ticket") 48 | async def ticket_(self, ctx: commands.Context): 49 | if ctx.invoked_subcommand is None: 50 | """Ticket related commands""" 51 | embed = discord.Embed( 52 | title="Ticket", 53 | description="**--> `ticket role add` Adds a role to ticket channel. By doing this the role you add can view tickets! By default it is available for only admins\nUsage: `ticket role add `\nExample: `ticket role add @MODS`\n\n--> `ticket role remove` Just the vice versa of the one stated above. Removes a role from viewing ticket\nUsage: `ticket role remove `\nExample: `ticket role remove @MODS`\n\n--> `ticket reset` Resets the ticket count!\nUsage: `ticket reset`\n\n--> `ticket clean` Delete all tickets in the server\nUsage: `ticket clean`\n\n--> `ticket category` Get tickets inside a category. If you want to keep ticket view permissions, make sure to change the category permissions.\nUsage: `ticket category `\nExample: `ticket category 98765432123456789`\n\n--> `ticket close` Closes the ticket. Use the command inside a ticket only\nUsage: `ticket close`\n\n--> `ticket add` Adds a user in the ticket. Use the command inside a ticket only\nUsage: `ticket add `\nExample: `ticket add @SpaceDoggo#0007`\n\n--> `ticket remove` Removes a user from the ticket. Use the command inside a ticket only\nUsage: `ticket remove `\nExample: `ticket remove @SpaceDoggo#0007`**", 54 | color=discord.Color.green(), 55 | ) 56 | await ctx.send(embed=embed) 57 | 58 | @panel_.command(name="create", aliases=["c", "make", "add"]) 59 | @commands.has_permissions(manage_channels=True) 60 | async def create_( 61 | self, ctx: commands.Context, channel: discord.TextChannel, *, name=None 62 | ): 63 | """Creates a panel in a channel through which users can interact and open tickets""" 64 | if not channel: 65 | embed = discord.Embed( 66 | description="**:x: Please enter a channel to make the panel in!**", 67 | color=discord.Color.red(), 68 | ) 69 | return await ctx.send(embed=embed) 70 | 71 | if not name: 72 | embed = discord.Embed( 73 | description="**:x: Please enter a name!**", color=discord.Color.red() 74 | ) 75 | return await ctx.send(embed=embed) 76 | 77 | if channel == ctx.channel: 78 | panel = discord.Embed( 79 | title=name, 80 | description="To create a ticket react with 📩", 81 | color=discord.Color.green(), 82 | ) 83 | panel.set_footer( 84 | text=f"{self.bot.user.name} - Ticket Panel", 85 | icon_url=self.bot.user.avatar.url, 86 | ) 87 | 88 | message = await channel.send(embed=panel, view=TicketPanelView(self.bot)) 89 | try: 90 | await ctx.author.send( 91 | embed=discord.Embed( 92 | description=f"**Panel id** of the panel you just created in <#{channel.id}>: `{message.id}`", 93 | color=discord.Color.green(), 94 | ) 95 | ) 96 | except discord.Forbidden: 97 | pass 98 | if channel != ctx.channel: 99 | panel1 = discord.Embed( 100 | title=name, 101 | description="To create a ticket react with 📩", 102 | color=discord.Color.green(), 103 | ) 104 | panel1.set_footer( 105 | text=f"{self.bot.user.name} - Ticket Panel", 106 | icon_url=self.bot.user.avatar.url, 107 | ) 108 | 109 | message = await channel.send(embed=panel1, view=TicketPanelView(self.bot)) 110 | embed2 = discord.Embed( 111 | description=f"**:white_check_mark: Successfully posted the panel in {channel.mention}\n\nPanel ID: `{message.id}`**", 112 | color=discord.Color.green(), 113 | ) 114 | await ctx.send(embed=embed2) 115 | 116 | @panel_.command(name="delete", aliases=["del"]) 117 | @commands.has_permissions(manage_channels=True) 118 | async def delete_( 119 | self, ctx: commands.Context, channel: discord.TextChannel, panel_id: int 120 | ): 121 | """Deletes a previously built panel in the server. Requires the `panel_id` which is provided at the time of the creation of the panel""" 122 | message = await channel.fetch_message(panel_id) 123 | try: 124 | await message.delete() 125 | embed = discord.Embed( 126 | description="**:white_check_mark: Successfully deleted the panel!**", 127 | color=discord.Color.green(), 128 | ) 129 | await ctx.send(embed=embed) 130 | except discord.Forbidden: 131 | embed = discord.Embed( 132 | description="**:x: I couldn't do that!**", color=discord.Color.green() 133 | ) 134 | await ctx.send(embed=embed) 135 | except discord.NotFound: 136 | embed = discord.Embed( 137 | description=f"**:x: I couldn't find a panel with id `{panel_id}`! Please try again after checking the id!**" 138 | ) 139 | await ctx.send(embed=embed) 140 | 141 | @panel_.command(name="edit", aliases=["e"]) 142 | async def edit_( 143 | self, 144 | ctx: commands.Context, 145 | channel: discord.TextChannel, 146 | panel_id: int, 147 | *, 148 | name: str, 149 | ): 150 | """Edits a previously built panel in the server. Requires the `panel_id` which is provided at the time of the creation of the panel""" 151 | message = await channel.fetch_message(panel_id) 152 | try: 153 | embed1 = discord.Embed( 154 | title=name, 155 | description="To create a ticket react with 📩", 156 | color=discord.Color.green(), 157 | ) 158 | await message.edit(embed=embed1) 159 | embed = discord.Embed( 160 | description="**:white_check_mark: Successfully edited the panel!**", 161 | color=discord.Color.green(), 162 | ) 163 | await ctx.send(embed=embed) 164 | except discord.Forbidden: 165 | embed = discord.Embed( 166 | description="**:x: I couldn't do that!**", color=discord.Color.green() 167 | ) 168 | await ctx.send(embed=embed) 169 | except discord.NotFound: 170 | embed = discord.Embed( 171 | description=f"**:x: I couldn't find a panel with id `{panel_id}`! Please try again after checking the id!**" 172 | ) 173 | await ctx.send(embed=embed) 174 | 175 | @ticket_.command(name="reset") 176 | @commands.has_permissions(manage_channels=True) 177 | async def reset_(self, ctx: commands.Context): 178 | """Resets the ticket count set of the server""" 179 | embed = discord.Embed( 180 | description=f"Are you sure you want to reset the **Ticket Count**?\n------------------------------------------------\nRespond Within **15** seconds!", 181 | color=discord.Color.orange(), 182 | ) 183 | message = await ctx.send(embed=embed) 184 | await message.edit(embed=embed, view=TicketResetView(ctx, message, self.bot)) 185 | 186 | @ticket_.command(name="category") 187 | @commands.has_permissions(manage_channels=True) 188 | async def category_(self, ctx: commands.Context, categoryID: int = None): 189 | """Sets the category for tickets. Highly reccomended.""" 190 | try: 191 | if categoryID is None: 192 | await self.bot.cursor.execute( 193 | "SELECT category FROM ticket WHERE guild_id=%s", 194 | (str(ctx.guild.id),), 195 | ) 196 | dataCheck = await self.bot.cursor.fetchone() 197 | if not dataCheck: 198 | return await ctx.send( 199 | embed=discord.Embed( 200 | description="**:x: You have not assigned a category to tickets yet**", 201 | color=discord.Color.red(), 202 | ) 203 | ) 204 | 205 | await self.bot.cursor.execute( 206 | "SELECT * FROM ticket WHERE guild_id=%s", (str(ctx.guild.id),) 207 | ) 208 | categoryFind = await self.bot.cursor.fetchone() 209 | cat = categoryFind[2] 210 | return await ctx.send( 211 | embed=discord.Embed( 212 | description=f"**The category_id set for this server is {cat}**", 213 | color=discord.Color.green(), 214 | ) 215 | ) 216 | 217 | await self.bot.cursor.execute( 218 | "SELECT category FROM ticket WHERE guild_id=%s", (str(ctx.guild.id),) 219 | ) 220 | data = await self.bot.cursor.fetchone() 221 | if not data: 222 | await self.bot.cursor.execute( 223 | "SELECT * FROM ticket WHERE guild_id=%s", (str(ctx.guild.id),) 224 | ) 225 | dataCheck2 = await self.bot.cursor.fetchone() 226 | if not dataCheck2[0]: 227 | await self.bot.cursor.execute( 228 | "INSERT INTO ticket (guild_id, category) VALUES(%s,%s)", 229 | (str(ctx.guild.id), str(categoryID)), 230 | ) 231 | else: 232 | await self.bot.cursor.execute( 233 | "INSERT INTO ticket (category) VALUES(%s) WHERE guild_id=%s", 234 | (str(categoryID), str(ctx.guild.id)), 235 | ) 236 | if data: 237 | await self.bot.cursor.execute( 238 | "UPDATE ticket SET category = %s WHERE guild_id=%s", 239 | (str(categoryID), str(ctx.guild.id)), 240 | ) 241 | await self.bot.conn.commit() 242 | try: 243 | category = discord.utils.get(ctx.guild.categories, id=categoryID) 244 | overwrite = { 245 | ctx.guild.default_role: discord.PermissionOverwrite( 246 | send_messages=False, view_channel=False 247 | ), 248 | ctx.guild.me: discord.PermissionOverwrite( 249 | send_messages=True, manage_channels=True 250 | ), 251 | } 252 | await category.edit(overwrites=overwrite) 253 | except discord.Forbidden: 254 | await ctx.send( 255 | embed=discord.Embed( 256 | description="**Permissions missing**\n I need the `manage_channels` permission to function properly", 257 | color=discord.Color.green(), 258 | ) 259 | ) 260 | embed = discord.Embed( 261 | description=f"**:white_check_mark: Successfully added `{category}` as the ticket category!\n\nIf you want to keep ticket view permissions, make sure to change the category permissions.**", 262 | color=discord.Color.green(), 263 | ) 264 | await ctx.send(embed=embed) 265 | except Exception as e: 266 | self.bot.log.error(e) 267 | 268 | @ticket_.command() 269 | @commands.has_permissions(manage_channels=True) 270 | async def close(self, ctx: commands.Context): 271 | """Closes the ticket""" 272 | await self.bot.cursor.execute( 273 | "SELECT * FROM tickets WHERE guild_id=%s AND channel_id=%s", 274 | (str(ctx.guild.id), str(ctx.channel.id)), 275 | ) 276 | data = await self.bot.cursor.fetchone() 277 | if data[3] == "close": 278 | return await ctx.send( 279 | embed=discord.Embed( 280 | description="**:x: The ticket is already closed**", 281 | color=discord.Color.red(), 282 | ) 283 | ) 284 | if ctx.channel.id != data[1]: 285 | await ctx.send( 286 | embed=discord.Embed( 287 | description="**:x: Looks like either this channel is not a ticket channel or you aren't in the same channel**", 288 | color=discord.Color.red(), 289 | ) 290 | ) 291 | embed = discord.Embed( 292 | description="**Are you sure you want to close the ticket%s**", 293 | color=discord.Color.orange(), 294 | ) 295 | message = await ctx.send(embed=embed) 296 | await message.edit(view=TicketCloseTop2(ctx.author, message, self.bot)) 297 | 298 | @ticket_.command() 299 | async def add(self, ctx: commands.Context, user: discord.Member): 300 | """Adds a user in the ticket""" 301 | await self.bot.cursor.execute( 302 | "SELECT * FROM tickets WHERE guild_id=%s AND channel_id=%s", 303 | (str(ctx.guild.id), str(ctx.channel.id)), 304 | ) 305 | data = await self.bot.cursor.fetchone() 306 | 307 | if ctx.channel.id != data[1]: 308 | await ctx.send( 309 | embed=discord.Embed( 310 | description="**:x: Looks like either this channel is not a ticket channel or you aren't in the same channel**", 311 | color=discord.Color.red(), 312 | ) 313 | ) 314 | 315 | if user in ctx.channel.members: 316 | return await ctx.send( 317 | embed=discord.Embed( 318 | description="**:x: That user is already in the ticket**", 319 | color=discord.Color.red, 320 | ) 321 | ) 322 | 323 | channel: discord.TextChannel = ctx.channel 324 | perms = channel.overwrites_for(user) 325 | perms.view_channel = True 326 | perms.send_messages = True 327 | perms.read_message_history = True 328 | await channel.set_permissions(user, overwrite=perms) 329 | embed = discord.Embed( 330 | description=f"**:white_check_mark: Successfully added {user.mention} in the ticket!**", 331 | color=discord.Color.green(), 332 | ) 333 | await ctx.send(embed=embed) 334 | 335 | @ticket_.command(aliases=["rm"]) 336 | async def remove(self, ctx: commands.Context, user: discord.Member): 337 | """Removes a user from a ticket. Note: It can't be the user who created the ticket or a person with admin""" 338 | await self.bot.cursor.execute( 339 | "SELECT * FROM tickets WHERE guild_id=%s AND channel_id=%s", 340 | (str(ctx.guild.id), str(ctx.channel.id)), 341 | ) 342 | data = await self.bot.cursor.fetchone() 343 | 344 | if ctx.channel.id != data[1]: 345 | await ctx.send( 346 | embed=discord.Embed( 347 | description="**:x: Looks like either this channel is not a ticket channel or you aren't in the same channel**", 348 | color=discord.Color.red(), 349 | ) 350 | ) 351 | 352 | if user.id == data[2]: 353 | embed2 = discord.Embed( 354 | description=f"**:x: {user.mention} is the one who opened a ticket\nYou can't remove them from the ticket!**", 355 | color=discord.Color.red(), 356 | ) 357 | await ctx.send(embed=embed2) 358 | 359 | if ( 360 | user.guild_permissions.administrator 361 | or user.guild_permissions.manage_channels 362 | ): 363 | return await ctx.send( 364 | embed=discord.Embed( 365 | description="**:x: That user is a *MOD/ADMIN*.**", 366 | color=discord.Color.red(), 367 | ) 368 | ) 369 | 370 | if not user in ctx.channel.members: 371 | return await ctx.send( 372 | embed=discord.Embed( 373 | description="**:x: That user is already not in the ticket**", 374 | color=discord.Color.red, 375 | ) 376 | ) 377 | 378 | channel: discord.TextChannel = ctx.channel 379 | perms = channel.overwrites_for(user) 380 | perms.view_channel = False 381 | perms.send_messages = False 382 | perms.read_message_history = False 383 | await channel.set_permissions(user, overwrite=perms) 384 | embed = discord.Embed( 385 | description=f"**:white_check_mark: Successfully removed {user.mention} from the ticket!**", 386 | color=discord.Color.green(), 387 | ) 388 | await ctx.send(embed=embed) 389 | 390 | @ticket_.command(hidden=True) 391 | @commands.is_owner() 392 | async def set(self, ctx: commands.Context, *, num: int): 393 | await self.bot.cursor.execute( 394 | "UPDATE ticket SET count=%s WHERE guild_id=%s", (num, str(ctx.guild.id)) 395 | ) 396 | await self.bot.conn.commit() 397 | await ctx.send( 398 | embed=discord.Embed( 399 | description=f"**:white_check_mark: Set the Ticket Count to -> `{num}`**", 400 | color=discord.Color.green(), 401 | ) 402 | ) 403 | 404 | @ticket_.command(aliases=["how", "guide"]) 405 | @commands.has_permissions(manage_channels=True) 406 | async def setup(self, ctx: commands.Context): 407 | """Complete guide that shows us how to setup the perfect ticket system in the server""" 408 | embed = discord.Embed( 409 | title="__Ticket Setup__", 410 | description="""**How to setup a ticket system :-** 411 | 412 | --> Create a panel by using `panel create [name]` 413 | --> Use command `ticket category ` to setup a category (I highly reccomend using ticket categories. They are way better and you can personalize their permissions and stuff 414 | --> You are good to go. Use `ticket` or `panel` for more info!)""", 415 | color=discord.Color.green(), 416 | ).set_footer( 417 | text=f"{self.bot.user.name} - Ticket System", 418 | icon_url=self.bot.user.avatar.url, 419 | ) 420 | await ctx.send(embed=embed) 421 | 422 | @ticket_.command() 423 | async def role(self, ctx: commands.Context, switch: str, *, role: discord.Role): 424 | """Adds a role or removes the role from a server.\nExample: `ticket role add @SOMEROLE` `ticket role remove remove @SOMEROLE`""" 425 | await self.bot.cursor.execute( 426 | "SELECT * FROM tickets WHERE guild_id=%s AND channel_id=%s", 427 | (str(ctx.guild.id), str(ctx.channel.id)), 428 | ) 429 | data = await self.bot.cursor.fetchone() 430 | 431 | if ctx.channel.id != data[1]: 432 | await ctx.send( 433 | embed=discord.Embed( 434 | description="**:x: Looks like either this channel is not a ticket channel or you aren't in the same channel**", 435 | color=discord.Color.red(), 436 | ) 437 | ) 438 | 439 | if switch.lower() == "add": 440 | channel: discord.Channel = ctx.channel 441 | perms = channel.overwrites_for(role) 442 | perms.view_channel = True 443 | perms.send_messages = True 444 | perms.read_message_history = True 445 | await channel.set_permissions(role, overwrite=perms) 446 | embed = discord.Embed( 447 | description=f"**:white_check_mark: Successfully added {role.mention} in the ticket!**", 448 | color=discord.Color.green(), 449 | ) 450 | await ctx.send(embed=embed) 451 | 452 | if switch.lower() == "remove": 453 | channel: discord.Channel = ctx.channel 454 | perms = channel.overwrites_for(role) 455 | perms.view_channel = False 456 | perms.send_messages = False 457 | perms.read_message_history = False 458 | await channel.set_permissions(role, overwrite=perms) 459 | embed = discord.Embed( 460 | description=f"**:white_check_mark: Successfully added {role.mention} in the ticket!**", 461 | color=discord.Color.green(), 462 | ) 463 | await ctx.send(embed=embed) 464 | 465 | 466 | def setup(bot): 467 | bot.add_cog(Tickets(bot)) 468 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | # CONTRIBUTORS: 2 | # Dhravya Shah https://github.com/dhravya - Fun, Logging, Mod, and a lot more 3 | # 27Saumya https://github.com/27Saumya - Ticket system 4 | # nexxeln https://github.com/nexxeln - Button roles, modals 5 | # Infernum1 https://github.com/Infernum1 - Logging 6 | 7 | import os 8 | from os import environ as env 9 | import dotenv 10 | import logging 11 | import asyncio 12 | import sys 13 | 14 | from discord.ext import commands 15 | import discord 16 | from pycord.ext import ipc 17 | import asyncmy 18 | import topgg 19 | 20 | from utility.disc.command_hinter import CommandHinter, CommandGenerator 21 | from utility.disc.get_prefix import get_prefix 22 | from utility.db.database import Database 23 | 24 | from views.button_roles_views import RoleButton 25 | from views.ticket_views import * 26 | 27 | 28 | try: 29 | logging.basicConfig( 30 | level=logging.INFO, 31 | format="%(levelname)s - %(message)s\t\t %(asctime)s", 32 | filename=os.path.join(os.path.dirname(__file__), "logs", "main.log"), 33 | ) 34 | except Exception as e: 35 | print(e) 36 | 37 | dotenv.load_dotenv() 38 | logging.info("Starting bot, loaded env variables") 39 | 40 | 41 | # Getting the cog file 42 | cog_files = [ 43 | os.path.join(os.path.dirname(__file__), "cogs"), 44 | os.path.join(os.path.dirname(__file__), "slash_cogs"), 45 | os.path.join(os.path.dirname(__file__), "background_cogs"), 46 | ] 47 | logging.info(f"Using cog file: {cog_files}") 48 | 49 | # Specifying the intents 50 | intents = discord.Intents.default() 51 | intents.messages = True 52 | intents.members = True 53 | 54 | 55 | class SpaceBot(commands.Bot): 56 | def __init__(self, *args, **kwargs): 57 | super().__init__(*args, **kwargs) 58 | self.persistent_views_added = False 59 | self.ipc = ipc.Server( 60 | self, secret_key=env.get("IPC_SECRET_KEY"), host="0.0.0.0" 61 | ) 62 | 63 | async def is_owner(self, user: discord.User) -> bool: 64 | if user.id in [881861601756577832, 512885190251642891]: 65 | return True 66 | return await super().is_owner(user) 67 | 68 | async def on_ready(self): 69 | print(f"{bot.user.name} has connected to Discord!") 70 | bot.log.info(f"Logged in as {bot.user}") 71 | if not self.persistent_views_added: 72 | self.add_view(TicketPanelView(self)) 73 | self.add_view(TicketControlsView(self)) 74 | self.add_view(TicketCloseTop(self)) 75 | self.persistent_views_added = True 76 | await bot.change_presence( 77 | activity=discord.Game(name="Spacebot rewrite in progress") 78 | ) 79 | bot.log.info("Bot ready, waiting for threads to load") 80 | await asyncio.sleep(15) 81 | db = Database(bot) 82 | 83 | bot.log.info("Setting prefix cache") 84 | await db._prefix_cache_set() 85 | bot.prefix_cache = db.prefix_cache 86 | 87 | bot.log.info("Setting automod cache") 88 | await db._automod_cache_set() 89 | 90 | print("Loading persistent buttons") 91 | view = discord.ui.View(timeout=None) 92 | # Sleeping for a second to allow the bot to connect to the database 93 | await self.cursor.execute("SELECT * FROM roles") 94 | # make a list of guilds and roles of that guild 95 | roles = await self.cursor.fetchall() 96 | for guild_id, role_id in roles: 97 | guild = self.get_guild(guild_id) 98 | role = guild.get_role(role_id) 99 | if role: 100 | self.log.info(f"Adding role button for {role}") 101 | view.add_item(RoleButton(role)) 102 | # add the view to the bot so it will watch for button interactions 103 | self.add_view(view) 104 | print("Persistent buttons loaded. Bot is ready!") 105 | 106 | async def on_ipc_error(self, endpoint, error): 107 | """Called upon an error being raised within an IPC route""" 108 | print(endpoint, "raised", error) 109 | 110 | 111 | bot = SpaceBot( 112 | command_prefix=get_prefix, 113 | intents=intents, 114 | case_insensitive=True, 115 | strip_after_prefix=True, 116 | ) 117 | bot.connected = False 118 | 119 | bot.log = logging 120 | 121 | CommandHinter(bot, CommandGenerator()) 122 | 123 | 124 | async def connect_to_db(bot) -> None: 125 | bot.conn = await asyncmy.connect( 126 | user=env.get("DB_USER"), 127 | password=env.get("DB_PASS"), 128 | host=env.get("DB_HOST"), 129 | database=env.get("DB_NAME"), 130 | port=int(env.get("DB_PORT")), 131 | ) 132 | bot.cursor = bot.conn.cursor() 133 | bot.log.info("Connected to DB") 134 | print("Connected to DB") 135 | bot.connected = True 136 | 137 | 138 | loop = asyncio.get_event_loop() 139 | loop.run_until_complete(connect_to_db(bot)) 140 | bot.connect_to_db = connect_to_db 141 | 142 | bot.dev_mode = True 143 | 144 | if not bot.dev_mode: 145 | bot.topggpy = topgg.DBLClient( 146 | bot, 147 | os.getenv("TOPGG_TOKEN"), 148 | autopost=True, 149 | post_shard_count=True, 150 | ) 151 | bot.log.info("Loaded top.gg module") 152 | 153 | bot.theme_colour = {"default": 0x2F3136, "error": 0x57F287, "success": 0x57F287} 154 | 155 | bot.load_extension("jishaku") 156 | for cog_file in cog_files: 157 | filename = os.path.basename(cog_file) 158 | for cog in os.listdir(cog_file): 159 | if cog.endswith(".py"): 160 | try: 161 | bot.load_extension(f"{filename}.{cog[:-3]}") 162 | bot.log.info(f"Loaded cog {cog}") 163 | except Exception as e: 164 | bot.log.error(f"Error loading cog {cog}: {e}", exc_info=True) 165 | 166 | 167 | async def mobile(self): 168 | payload = { 169 | "op": self.IDENTIFY, 170 | "d": { 171 | "token": self.token, 172 | "properties": { 173 | "$os": sys.platform, 174 | "$browser": "Discord iOS", 175 | "$device": "pycord", 176 | "$referrer": "", 177 | "$referring_domain": "", 178 | }, 179 | "compress": True, 180 | "large_threshold": 250, 181 | "v": 3, 182 | }, 183 | } 184 | if self.shard_id is not None and self.shard_count is not None: 185 | payload["d"]["shard"] = [self.shard_id, self.shard_count] 186 | state = self._connection 187 | if state._activity is not None or state._status is not None: 188 | payload["d"]["presence"] = { 189 | "status": state._status, 190 | "game": state._activity, 191 | "since": 0, 192 | "afk": False, 193 | } 194 | if state._intents is not None: 195 | payload["d"]["intents"] = state._intents.value 196 | await self.call_hooks( 197 | "before_identify", self.shard_id, initial=self._initial_identify 198 | ) 199 | await self.send_as_json(payload) 200 | 201 | 202 | discord.gateway.DiscordWebSocket.identify = mobile 203 | 204 | if __name__ == "__main__": 205 | bot.ipc.start() 206 | if bot.dev_mode == True: 207 | bot.run(env.get("DEV_TOKEN")) 208 | else: 209 | bot.run(env.get("BOT_TOKEN")) 210 | -------------------------------------------------------------------------------- /src/slash_cogs/button_roles.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.commands import slash_command 3 | from discord.ext import commands 4 | from views.button_roles_views import ModalButton 5 | from utility.db.database import Database 6 | 7 | 8 | class ButtonRoles(commands.Cog): 9 | def __init__(self, bot: commands.Bot) -> None: 10 | self.bot = bot 11 | self.help_doc = "Auto-roles for your server." 12 | self.databaseInstance = Database(self.bot) 13 | 14 | @slash_command() 15 | async def button_roles(self, ctx: discord.ApplicationContext): 16 | view = discord.ui.View() 17 | view.add_item(ModalButton(databaseInstance=self.databaseInstance)) 18 | await ctx.respond("Click the button below to set up button roles", view=view) 19 | 20 | 21 | def setup(bot): 22 | bot.add_cog(ButtonRoles(bot)) 23 | -------------------------------------------------------------------------------- /src/slash_cogs/config.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord.commands import slash_command, Option, SlashCommandGroup 4 | from typing import Union 5 | 6 | from utility.db.database import Database 7 | from views.config_views import LoggingConfigModal 8 | import asyncio 9 | 10 | 11 | async def sleep(seconds: int) -> None: 12 | await asyncio.sleep(seconds) 13 | 14 | 15 | class Config(commands.Cog): 16 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 17 | self.bot = bot 18 | self.help_doc = "Config commands to customize SpaceBot for your server" 19 | 20 | self.database = Database(self.bot) 21 | 22 | @commands.Cog.listener() 23 | async def on_ready(self) -> None: 24 | await asyncio.sleep(5) 25 | self.database = Database(self.bot) 26 | 27 | self.bot.add_application_command(self.config) 28 | 29 | config = SlashCommandGroup( 30 | "config", 31 | "Commands to configure the bot to your needs", 32 | ) 33 | 34 | # Logging commands 35 | logging = config.create_subgroup("logging", "Commands to configure logging") 36 | 37 | @logging.command() 38 | async def setup(self, ctx: discord.ApplicationContext) -> None: 39 | """ 40 | Setup the logging for the bot 41 | """ 42 | await ctx.interaction.response.send_modal(LoggingConfigModal(self.bot)) 43 | 44 | @config.command() 45 | async def prefix(self, ctx: commands.Context, *, prefix: str) -> None: 46 | """ 47 | Set the bot's prefix 48 | """ 49 | await ctx.defer(ephemeral=True) 50 | if len(prefix) > 5: 51 | await ctx.send("Prefix must be less than 5 characters") 52 | return 53 | await self.database.set_prefix(str(ctx.guild.id), prefix) 54 | 55 | await ctx.respond("Prefix set to `{}`".format(prefix), ephemeral=True) 56 | 57 | @config.command() 58 | async def toggle_rep(self, ctx: discord.ApplicationContext): 59 | """ 60 | Toggle the rep system on or off 61 | """ 62 | await ctx.defer(ephemeral=True) 63 | 64 | await self.bot.cursor.execute( 65 | "SELECT guild_id, rep_toggle FROM Guilds WHERE guild_id = %s", 66 | (str(ctx.guild.id),), 67 | ) 68 | guild_info = await self.bot.cursor.fetchone() 69 | if not guild_info: 70 | await self.bot.cursor.execute( 71 | "INSERT INTO Guilds (guild_id, rep_toggle) VALUES (%s, %s)", 72 | (str(ctx.guild.id), "1"), 73 | ) 74 | return await ctx.respond("Reputation system turned on") 75 | toggle = guild_info[1] 76 | 77 | await self.bot.cursor.execute( 78 | "UPDATE Guilds SET rep_toggle = NOT rep_toggle WHERE guild_id = %s", 79 | (str(ctx.guild.id),), 80 | ) 81 | 82 | await ctx.respond( 83 | f"Rep system turned {'off' if toggle else 'on'}", ephemeral=True 84 | ) 85 | 86 | 87 | def setup(bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 88 | bot.add_cog(Config(bot)) 89 | -------------------------------------------------------------------------------- /src/slash_cogs/fun_slash.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord.commands import slash_command, SlashCommandGroup, Option 4 | from typing import Union 5 | 6 | from utility.text.safe_message import safe_message 7 | from utility.text.britishify import strong_british_accent 8 | from utility.text.uwufy import owofy 9 | from utility.web.safe_aiohttp import aiohttp_get 10 | from views.fun_views import * 11 | 12 | 13 | class FunSlash(commands.Cog): 14 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 15 | self.bot = bot 16 | self.bot.log.debug(f"Loaded {self.__cog_name__} cog") 17 | 18 | @slash_command(name="owo") 19 | async def owofy(self, ctx: commands.Context, *, text: str) -> None: 20 | """ 21 | Haiiii owoowoowoo 22 | """ 23 | msg = safe_message(text) 24 | 25 | owofied = owofy(msg) 26 | is_guild = True 27 | if not isinstance(ctx.channel, discord.TextChannel): 28 | is_guild = False 29 | if not ctx.channel.permissions_for(ctx.me).manage_webhooks or not is_guild: 30 | embed = discord.Embed( 31 | title="OwO", description=owofied, color=self.bot.theme_colour["default"] 32 | ) 33 | embed.set_author( 34 | name=ctx.author.display_name, icon_url=ctx.author.avatar.url 35 | ) 36 | embed.set_thumbnail( 37 | url="https://www.kindpng.com/picc/m/236-2362818_anime-sempai-animegirl-heart-kawaii-cute-anime-girl.png" 38 | ) 39 | embed.set_footer( 40 | text="💡 Tip: Use the `owofy` command in a server where I have the Manage Webhooks permission and see the magic!" 41 | ) 42 | await ctx.send(embed=embed) 43 | await ctx.respond("Successfully sent the Owofied text!", ephemeral=True) 44 | 45 | webhook = None 46 | for hook in await ctx.channel.webhooks(): 47 | if hook.name == ctx.guild.me.name: 48 | webhook = hook 49 | if webhook is None: 50 | webhook = await ctx.channel.create_webhook(name=ctx.guild.me.name) 51 | 52 | avatar = ctx.author.avatar.url.format(format="png") 53 | 54 | await webhook.send(owofied, username=ctx.author.display_name, avatar_url=avatar) 55 | await ctx.respond("Successfully sent the Owofied text!", ephemeral=True) 56 | 57 | @slash_command(name="britishify") 58 | async def britishify(self, ctx: commands.Context, *, text: str) -> None: 59 | """ 60 | Britishify the provided text. Are you bri'ish enough? 61 | """ 62 | msg = safe_message(text) 63 | 64 | britishified = strong_british_accent(msg) 65 | 66 | is_guild = True 67 | 68 | if not isinstance(ctx.channel, discord.TextChannel): 69 | is_guild = False 70 | 71 | if not ctx.channel.permissions_for(ctx.me).manage_webhooks or not is_guild: 72 | embed = discord.Embed( 73 | title="Hey there Mate!", 74 | description=britishified, 75 | color=self.bot.theme_colour["default"], 76 | ) 77 | embed.set_author( 78 | name=ctx.author.display_name, icon_url=ctx.author.avatar.url 79 | ) 80 | embed.set_thumbnail( 81 | url="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f2/Flag_of_Great_Britain_%281707–1800%29.svg/188px-Flag_of_Great_Britain_%281707–1800%29.svg.png" 82 | ) 83 | embed.set_footer( 84 | text="💡 Tip: Use the `brit` command in a server where I have the Manage Webhooks permission and see the magic!" 85 | ) 86 | await ctx.send(embed=embed) 87 | return await ctx.respond( 88 | "Successfully sent the britishified text!", ephemeral=True 89 | ) 90 | 91 | webhook = None 92 | for hook in await ctx.channel.webhooks(): 93 | if hook.name == ctx.guild.me.name: 94 | webhook = hook 95 | if webhook is None: 96 | webhook = await ctx.channel.create_webhook(name=ctx.guild.me.name) 97 | 98 | avatar = ctx.author.avatar.url.format(format="png") 99 | 100 | await webhook.send( 101 | britishified, username=ctx.author.display_name, avatar_url=avatar 102 | ) 103 | return await ctx.respond( 104 | "Successfully sent the britishified text!", ephemeral=True 105 | ) 106 | 107 | @slash_command(name="roast") 108 | async def roast(self, ctx: commands.Context, user: discord.Member = None) -> None: 109 | """ 110 | Roast yourself or a friend! 111 | """ 112 | user = user or ctx.author 113 | response = await aiohttp_get( 114 | "https://evilinsult.com/generate_insult.php?lang=en", _type="text" 115 | ) 116 | if response is None: 117 | return await ctx.send("Failed to get insult") 118 | embed = discord.Embed( 119 | title=response, 120 | description="OOOOOOOOOOOOOOO", 121 | color=self.bot.theme_colour["default"], 122 | ) 123 | embed.set_author( 124 | name=f"{ctx.author.display_name} Roasted {user.display_name}!", 125 | icon_url=ctx.author.avatar.url, 126 | ) 127 | embed.set_thumbnail(url="https://c.tenor.com/f8YmpuCCXJcAAAAC/roasted-oh.gif") 128 | embed.set_footer( 129 | text="Roast is sourced through evilinsult.com.\nWe are not responsible for the content." 130 | ) 131 | view = RoastAgainButton(user, ctx.author, ctx, self.bot) 132 | await ctx.respond( 133 | user.mention if not user == ctx.author else None, embed=embed, view=view 134 | ) 135 | 136 | joke = SlashCommandGroup("joke", "Joke commands") 137 | 138 | @joke.command(name="dark") 139 | async def dark(self, ctx: commands.Context) -> None: 140 | """Returns a random dark joke""" 141 | response = await aiohttp_get( 142 | "https://v2.jokeapi.dev/joke/Dark?blacklistFlags=nsfw,religious&format=txt", 143 | _type="text", 144 | ) 145 | if response is None: 146 | return await ctx.send("Failed to get joke") 147 | embed = discord.Embed( 148 | title="Dark Joke", 149 | description=response, 150 | color=self.bot.theme_colour["default"], 151 | ) 152 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 153 | embed.set_thumbnail( 154 | url="https://purepng.com/public/uploads/large/purepng.com-skullskullvertebratesfacehuman-skull-1421526968219gpljr.png" 155 | ) 156 | embed.set_footer( 157 | text="NOTE: Dark Joke is sourced from jokeapi.dev. We are not responsible for the content." 158 | ) 159 | view = DarkJokeAgainButton(ctx) 160 | await ctx.respond(embed=embed, view=view) 161 | 162 | @joke.command(name="dad") 163 | async def dadjoke(self, ctx: commands.Context) -> None: 164 | """Gets a Dad joke""" 165 | joke = await aiohttp_get( 166 | "https://icanhazdadjoke.com/", 167 | _type="text", 168 | headers={"Accept": "text/plain"}, 169 | ) 170 | if joke is None: 171 | return await ctx.send("Failed to get joke") 172 | embed = discord.Embed( 173 | title="Dad Joke", description=joke, color=self.bot.theme_colour["default"] 174 | ) 175 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 176 | embed.set_thumbnail( 177 | url="https://us-east-1.tixte.net/uploads/i.dhravya.me/dad_joke_spacebot.png" 178 | ) 179 | embed.set_footer( 180 | text="Dad Joke is sourced from icanhazdadjoke.com. We are not responsible for the content." 181 | ) 182 | view = DadJokeAgainButton(ctx) 183 | await ctx.respond(embed=embed, view=view) 184 | 185 | @slash_command(name="8ball") 186 | async def eightball(self, ctx: commands.Context, *, question: str) -> None: 187 | """Ask the magic 8 ✨""" 188 | responses = { 189 | "It is certain.": 0x2ECC71, 190 | "It is decidedly so.": 0x2ECC71, 191 | "Without a doubt.": 0x2ECC71, 192 | "Yes - definitely.": 0x2ECC71, 193 | "You may rely on it.": 0x2ECC71, 194 | "As I see it, yes.": 0x2ECC71, 195 | "Most likely.": 0x2ECC71, 196 | "Outlook good.": 0x2ECC71, 197 | "Yes.": 0x2ECC71, 198 | "Signs point to yes.": 0x2ECC71, 199 | "send hazy, try again.": 0xE67E22, 200 | "Ask again later.": 0xE74C3C, 201 | "Better not tell you now.": 0xE74C3C, 202 | "Cannot predict now.": 0xE74C3C, 203 | "Concentrate and ask again.": 0xE74C3C, 204 | "Don't count on it.": 0xE74C3C, 205 | "My send is no.": 0xE74C3C, 206 | "My sources say no.": 0xE74C3C, 207 | "Outlook not so good.": 0xE74C3C, 208 | "Very doubtful.": 0xE67E22, 209 | "Maybe.": 0xE67E22, 210 | } 211 | 212 | answer = random.choice(list(responses.keys())) 213 | color = responses[answer] 214 | 215 | embed = discord.Embed( 216 | title=f"8 ball", 217 | description=f"**```Question: {question}```\n```Answer: {answer}```**", 218 | color=color, 219 | ) 220 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 221 | await ctx.respond(embed=embed) 222 | 223 | @slash_command(name="truth") 224 | async def truth(self, ctx: commands.Context) -> None: 225 | """Gets a random truth""" 226 | truth = await aiohttp_get( 227 | "https://api.dhravya.me/truth?simple=true", _type="text" 228 | ) 229 | if truth is None: 230 | return await ctx.send("Failed to get truth") 231 | embed = discord.Embed( 232 | title="Say the Truth!!", 233 | description=truth, 234 | color=self.bot.theme_colour["default"], 235 | ) 236 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 237 | await ctx.respond(embed=embed) 238 | 239 | @slash_command(name="dare") 240 | async def dare(self, ctx: commands.Context) -> None: 241 | """Gets a random dare""" 242 | dare = await aiohttp_get( 243 | "https://api.dhravya.me/dare?simple=true", _type="text" 244 | ) 245 | if dare is None: 246 | return await ctx.send("Failed to get dare") 247 | embed = discord.Embed( 248 | title="Say the dare!!", 249 | description=dare, 250 | color=self.bot.theme_colour["default"], 251 | ) 252 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 253 | await ctx.respond(embed=embed) 254 | 255 | @slash_command(name="comic") 256 | async def comic(self, ctx: commands.Context, number) -> None: 257 | """Gets a random comic from xkcd.com""" 258 | mes = await ctx.send("Fetching comic...") 259 | if number is None: 260 | json_ = await aiohttp_get("https://xkcd.com/info.0.json", _type="json") 261 | current_comic = json_["num"] 262 | comic_number = random.randint(1, current_comic) 263 | else: 264 | comic_number = number 265 | comic_json = await aiohttp_get( 266 | f"https://xkcd.com/{comic_number}/info.0.json", _type="json" 267 | ) 268 | if comic_json is None: 269 | logging.warning(f"Failed to get comic {comic_number}") 270 | return await ctx.send("Failed to get comic") 271 | 272 | embed = discord.Embed( 273 | title=f"xkcd #{comic_number}", 274 | description=comic_json["alt"], 275 | color=self.bot.theme_colour["default"], 276 | ) 277 | embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar.url) 278 | embed.set_image(url=comic_json["img"]) 279 | embed.set_footer(text=f"xkcd.com - {comic_json['year']}") 280 | view = GetComicAgainButton(ctx, comic_json["img"]) 281 | await mes.edit("", embed=embed, view=view) 282 | 283 | @slash_command(name="topic") 284 | async def topic(self, ctx: commands.Context) -> None: 285 | """Gets a random topic""" 286 | topic = await aiohttp_get( 287 | "https://api.dhravya.me/topic?simple=true", _type="text" 288 | ) 289 | is_guild = True 290 | if topic is None: 291 | return await ctx.send("Failed to get topic") 292 | 293 | if isinstance(ctx.channel, discord.DMChannel): 294 | is_guild = False 295 | 296 | if not ctx.channel.permissions_for(ctx.me).manage_webhooks or not is_guild: 297 | 298 | embed = discord.Embed( 299 | title="Topic for chat", 300 | description=topic, 301 | color=self.bot.theme_colour["default"], 302 | ) 303 | embed.set_author( 304 | name=ctx.author.display_name, icon_url=ctx.author.avatar.url 305 | ) 306 | await ctx.send(embed=embed) 307 | 308 | await ctx.respond("trying to send a webhook...", ephemeral=True, delete_after=5) 309 | 310 | webhook = None 311 | for hook in await ctx.channel.webhooks(): 312 | if hook.name == ctx.guild.me.name: 313 | webhook = hook 314 | if webhook is None: 315 | webhook = await ctx.channel.create_webhook(name=ctx.guild.me.name) 316 | 317 | avatar = ctx.author.avatar.url.format(format="png") 318 | embed = discord.Embed( 319 | description=safe_message(topic), colour=self.bot.theme_colour["default"] 320 | ) 321 | embed.set_footer(text="Use the topic command to get more") 322 | await webhook.send( 323 | embed=embed, username=ctx.author.display_name, avatar_url=avatar 324 | ) 325 | 326 | @slash_command(name="beer") 327 | async def beer( 328 | self, 329 | ctx: commands.Context, 330 | user: Option(discord.Member, "Whom would you like to have a beer with?"), 331 | reason: Option(str, "Reason for beer"), 332 | ) -> None: 333 | """Give a beer to your friend!!""" 334 | if user.id == self.bot.user.id: 335 | return await ctx.respond("*drinks beer with you* 🍻") 336 | 337 | if user.bot: 338 | return await ctx.respond( 339 | f"I would love to give beer to the bot **{user.name}**, but I don't think it will respond to you :/" 340 | ) 341 | 342 | if user == ctx.author: 343 | return await ctx.respond( 344 | "You can't give beer to yourself! ;-; \n (*PS* if you feel lonely, you can have a party with me or SpaceDoggo :wink:)" 345 | ) 346 | 347 | beer_offer = f"**{user.name}**, you got a 🍺 offer from **{ctx.author.name}**" 348 | beer_offer = beer_offer + f"\n\n**Reason:** {reason}" if reason else beer_offer 349 | 350 | view = BeerOfferView(ctx=ctx, user=user) 351 | 352 | embed = discord.Embed( 353 | title="Cheers! 🎉", 354 | description=beer_offer, 355 | color=self.bot.theme_colour["default"], 356 | ) 357 | await ctx.respond(user.mention, embed=embed, view=view) 358 | 359 | @slash_command(name="coinflip") 360 | async def coinflip(self, ctx: commands.Context) -> None: 361 | """Flip a coin to end that debate!""" 362 | coin = random.choice(["heads", "tails"]) 363 | url = f"https://us-east-1.tixte.net/uploads/i.dhravya.me/coin_{coin}.gif" 364 | embed = discord.Embed( 365 | title="Coin Flip!", 366 | description=f"Flipping the coin...", 367 | colour=self.bot.theme_colour["default"], 368 | ) 369 | embed.set_image(url=url) 370 | interaction = await ctx.respond(embed=embed) 371 | await asyncio.sleep(3) 372 | embed.description = "The coin landed on **{}**".format(coin) 373 | view = CoinFlipAgainView(ctx=ctx) 374 | await interaction.edit_original_message(embed=embed, view=view) 375 | 376 | @slash_command( 377 | name="beerparty", 378 | description="Have a beer party with some friends 🍻!", 379 | guild_ids=[905904010760966164], 380 | ) 381 | async def beerparty_(self, ctx: commands.Context): 382 | interaction: discord.Interaction = ctx.interaction 383 | embed = discord.Embed( 384 | title="Beer Party 🍻", 385 | description=f"{ctx.author.mention} had invited everyone to join up this beer party :beers:!", 386 | color=discord.Color.green(), 387 | ) 388 | await interaction.response.send_message(embed=embed) 389 | message = await interaction.original_message() 390 | await message.edit(embed=embed, view=BeerPartyView(message, ctx)) 391 | 392 | 393 | def setup(bot: commands.Bot) -> None: 394 | bot.add_cog(FunSlash(bot)) 395 | -------------------------------------------------------------------------------- /src/slash_cogs/moderation.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import datetime 3 | from discord.ext import commands 4 | from discord.commands import slash_command, Option 5 | from random import choice 6 | 7 | reasons = ["being a pain in already painful world", "not knowing how to be civil"] 8 | 9 | 10 | class Moderation(commands.Cog): 11 | def __init__(self, bot: commands.Bot): 12 | self.bot = bot 13 | 14 | self.help_doc = "Moderation commands" 15 | 16 | mod = discord.SlashCommandGroup("mod", "commands for moderation") 17 | 18 | @mod.command() 19 | @commands.has_guild_permissions(kick_members=True) 20 | async def reason(self, ctx: commands.Context, case_id: int, reason: str): 21 | """ 22 | Edit the reason for a case 23 | """ 24 | await self.bot.cursor.execute( 25 | "SELECT * FROM cases WHERE case_number = %s", (case_id,) 26 | ) 27 | case = await self.bot.cursor.fetchone() 28 | if not case: 29 | await ctx.respond("Case not found") 30 | return 31 | if not str(case[0]) == str(ctx.guild.id): 32 | await ctx.respond("You can only edit cases in this guild") 33 | return 34 | await self.bot.cursor.execute( 35 | "UPDATE cases SET reason = %s WHERE case_number = %s", (reason, case_id) 36 | ) 37 | await ctx.respond("Case updated") 38 | await self.bot.conn.commit() 39 | message_id = case[7] 40 | if message_id: 41 | await self.bot.cursor.execute( 42 | "SELECT moderator_actions FROM logging WHERE guild_id = %s", 43 | (str(ctx.guild.id),), 44 | ) 45 | channel_ = await self.bot.cursor.fetchone() 46 | channel_ = self.bot.get_channel(channel_[0]) 47 | if channel_: 48 | message = await channel_.fetch_message(message_id) 49 | if message: 50 | embed = message.embeds[0] 51 | embed.add_field( 52 | name=f"Reason [Edited by {ctx.author.mention}]", value=reason 53 | ) 54 | await message.edit(embed=embed) 55 | 56 | @slash_command(description="Kick a user from the server.") 57 | @commands.has_guild_permissions(kick_members=True) 58 | @commands.bot_has_guild_permissions(kick_members=True) 59 | async def kick( 60 | self, 61 | ctx: discord.ApplicationContext, 62 | user: Option(discord.Member, "Id or mention", required=True), 63 | reason: Option(str, "Reason for the kick", required=False, default=None), 64 | ): 65 | """Kick a user from the server.""" 66 | if user.id == ctx.author.id: 67 | await ctx.respond("You can't kick yourself bozo.") 68 | return 69 | if user.id == self.bot.user.id: 70 | await ctx.respond("I can't kick myself bozo.") 71 | return 72 | if user.top_role.position >= ctx.author.top_role.position: 73 | await ctx.respond("Imagine kicking someone higher than you.") 74 | return 75 | if reason is None: 76 | reason = ( 77 | choice(reasons) 78 | + " (Change reason by using `/mod reason ` command)" 79 | ) 80 | await user.kick(reason=reason) 81 | await self.bot.cursor.execute( 82 | "INSERT INTO cases (guild_id, offence_type, time, reason, moderator, offender) VALUES (%s, %s, %s, %s, %s, %s)", 83 | ( 84 | str(ctx.guild.id), 85 | "kick", 86 | datetime.datetime.utcnow(), 87 | reason if reason else "Not provided", 88 | str(ctx.author.id), 89 | str(user.id), 90 | ), 91 | ) 92 | await self.bot.cursor.execute( 93 | "SELECT moderator_actions FROM logging WHERE guild_id = %s", 94 | (str(ctx.guild.id),), 95 | ) 96 | f = await self.bot.cursor.fetchone() 97 | if not f: 98 | await ctx.send( 99 | "A log channel hasn't been set for moderator actions yet. Use `/config moderation channel` to set one " 100 | ) 101 | return 102 | channel = self.bot.get_channel(int(f[0])) 103 | if channel is None: 104 | await ctx.send( 105 | "I couldn't find the channel for the log channel. Please check if I have the right permissions and the channel is set correctly." 106 | ) 107 | return 108 | await self.bot.cursor.execute( 109 | "SELECT case_number, time FROM cases WHERE guild_id = %s AND offender = %s", 110 | (str(ctx.guild.id), str(user.id)), 111 | ) 112 | case_number = await self.bot.cursor.fetchone() 113 | embed = discord.Embed( 114 | "Kicked", 115 | f"{user.mention} has been kicked. Reason: {reason}", 116 | color=0xFF0000, 117 | ) 118 | embed.set_author(name=f"Case #{case_number[0]}", icon_url=user.avatar_url) 119 | embed.set_footer( 120 | text=f"Edit the reason by using `/mod reason `", 121 | icon_url=user.avatar_url, 122 | ) 123 | await ctx.respond(embed=embed) 124 | 125 | embed = discord.Embed( 126 | title="User kicked", 127 | description=f"{user.mention} has been kicked from the server.", 128 | color=discord.Color.red(), 129 | ) 130 | embed.add_field(name="Reason", value=reason, inline=False) 131 | embed.add_field(name="Moderator", value=ctx.author.mention, inline=False) 132 | embed.set_footer(text=f"Case ID: {case_number[0]}") 133 | embed.timestamp = case_number[1] 134 | msg = await channel.send(embed=embed) 135 | 136 | await self.bot.cursor.execute( 137 | "UPDATE cases SET message_id = %s WHERE guild_id = %s AND case_number = %s", 138 | (str(msg.id), str(ctx.guild.id), str(case_number[0])), 139 | ) 140 | await self.bot.conn.commit() 141 | 142 | # TODO: BAN SHOULD ALSO SEND A MESSAGE TO THE CHANNEL like kick 143 | @slash_command(description="Ban a user from the server.") 144 | @commands.has_guild_permissions(ban_members=True) 145 | async def ban( 146 | self, 147 | ctx: discord.ApplicationContext, 148 | user: Option(discord.Member, "Id or mention", required=True), 149 | *, 150 | reason: Option(str, "Reason for the ban", required=False, default=None), 151 | ): 152 | """Ban a user from the server.""" 153 | if user.id == ctx.author.id: 154 | await ctx.send("You can't ban yourself bozo.") 155 | return 156 | if user.id == self.bot.user.id: 157 | await ctx.send("I can't ban myself bozo.") 158 | return 159 | if user.top_role.position >= ctx.author.top_role.position: 160 | await ctx.send("Imagine banning someone higher than you.") 161 | return 162 | if reason is None: 163 | reason = choice(reasons) 164 | await user.ban(reason=reason) 165 | await ctx.send(f"{user.mention} has been banned from the server for, {reason}.") 166 | 167 | # TODO: SEND A MESSAGE TO THE CHANNEL LIKE KICK 168 | @slash_command(description="Timeout a user from the server.") 169 | @commands.has_guild_permissions(moderate_members=True) 170 | async def timeout( 171 | self, 172 | ctx: discord.ApplicationContext, 173 | user: Option(discord.Member, "Id or mention", required=True), 174 | duration: Option( 175 | str, 176 | "Duration of the timeout.", 177 | choices=[ 178 | "1 min", 179 | "5 mins", 180 | "15 mins", 181 | "30 mins", 182 | "1 hour", 183 | "3 hours", 184 | "12 hours", 185 | "1 day", 186 | "1 week", 187 | ], 188 | required=True, 189 | ), 190 | *, 191 | reason: Option(str, "Reason for the timeout", required=False, default=None), 192 | ): 193 | """Timeout a member""" 194 | 195 | def duration_time(duration: str): 196 | switch = { 197 | "1 min": 1, 198 | "5 mins": 5, 199 | "15 mins": 15, 200 | "30 mins": 30, 201 | "1 hour": 60, 202 | "3 hours": 180, 203 | "12 hours": 720, 204 | "1 day": 1440, 205 | "1 week": 10080, 206 | } 207 | minutes = switch.get(duration) 208 | return datetime.timedelta(minutes=minutes) 209 | 210 | if user.id == ctx.author.id: 211 | await ctx.send("You can't timeout yourself bozo.") 212 | return 213 | if user.id == self.bot.user.id: 214 | await ctx.send("I can't timeout myself bozo.") 215 | return 216 | if user.top_role.position >= ctx.author.top_role.position: 217 | await ctx.send("Imagine timeouting someone higher than you.") 218 | return 219 | if reason is None: 220 | reason = choice(reasons) 221 | 222 | await user.timeout_for(duration_time(duration)) 223 | await ctx.send( 224 | f"{user.mention} has been timed out for, {duration} for, {reason}." 225 | ) 226 | 227 | 228 | # TODO: unban command 229 | # TODO: softban command 230 | 231 | 232 | def setup(bot): 233 | bot.add_cog(Moderation(bot)) 234 | -------------------------------------------------------------------------------- /src/slash_cogs/reputation.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from typing import Union 3 | from discord.ext import commands 4 | from discord.ext.commands import slash_command 5 | import re 6 | from utility.db.database import Database 7 | 8 | 9 | class Reputation(commands.Cog): 10 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]) -> None: 11 | self.help_docs = ( 12 | "Reputation system. Collect reputation from your server members!" 13 | ) 14 | self.bot = bot 15 | self.database = Database(bot) 16 | 17 | @commands.Cog.listener() 18 | async def on_message(self, message: discord.Message) -> None: 19 | 20 | # Check for thank words in the message 21 | if message.author.bot: 22 | return 23 | 24 | has_thank = await self.look_for_word(message.content) 25 | if has_thank: 26 | 27 | await self.bot.cursor.execute( 28 | "SELECT guild_id, rep_toggle FROM Guilds WHERE guild_id = %s", 29 | (str(message.guild.id),), 30 | ) 31 | guild_info = await self.bot.cursor.fetchone() 32 | if not guild_info: 33 | await self.bot.cursor.execute( 34 | "INSERT INTO Guilds (guild_id, rep_toggle) VALUES (%s, %s)", 35 | (str(message.guild.id), "0"), 36 | ) 37 | return 38 | if not guild_info[1]: 39 | return 40 | try: 41 | thank_whom = message.mentions[0] 42 | except IndexError: 43 | return 44 | if thank_whom == message.author: 45 | return 46 | 47 | up = await self.database.add_reputation(thank_whom.id) 48 | if not up == 0: 49 | await message.channel.send( 50 | f"{thank_whom.name} has been thanked! They now have {up} reputation!" 51 | ) 52 | 53 | @staticmethod 54 | async def look_for_word(string: str): 55 | """Returns true if string has a thank word uring regex""" 56 | pattern = r"\b(thank|thanks|thx|thnx|thnks|thnk|thnkz|thnksz|tysm|ty)\b" 57 | 58 | return re.compile(pattern, flags=re.IGNORECASE).search(string) 59 | 60 | @slash_command() 61 | async def rep(self, ctx: commands.Context, user: discord.User = None): 62 | """Get the reputation of a user""" 63 | if user is None: 64 | user = ctx.author 65 | rep = await self.bot.cursor.execute( 66 | "SELECT reputation FROM reputation WHERE user_id = %s", (str(user.id),) 67 | ) 68 | # rep = rep.fetchone() 69 | if rep is None: 70 | rep = 0 71 | await ctx.respond(f"{user.name} has **{rep}** reputation!") 72 | 73 | @slash_command() 74 | async def rep_leaderboard(self, ctx: discord.ApplicationContext): 75 | """Get the reputation leaderboard""" 76 | await ctx.defer() 77 | member_ids = [str(member.id) for member in ctx.guild.members] 78 | 79 | # Get the reputation of each member 80 | await self.bot.cursor.execute( 81 | "SELECT user_id, reputation FROM reputation WHERE user_id IN %s", 82 | (tuple(member_ids),), 83 | ) 84 | rep = await self.bot.cursor.fetchall() 85 | rep = {str(r[0]): r[1] for r in rep} 86 | 87 | # Sort the reputation 88 | rep = sorted(rep.items(), key=lambda x: x[1], reverse=True) 89 | 90 | # Get the top 10 91 | rep = rep[:10] 92 | 93 | # Get the member objects 94 | members = [ctx.guild.get_member(int(r[0])) for r in rep] 95 | 96 | # Make the embed 97 | embed = discord.Embed( 98 | title="Reputation Leaderboard", 99 | description="Top 10 members with the most reputation", 100 | color=0x2F3136, 101 | ) 102 | for i, member in enumerate(members): 103 | embed.add_field( 104 | name=f"#{i + 1} {member.name}{member.discriminator}", 105 | value=f"`{rep[i][1]}` **rep**", 106 | inline=False, 107 | ) 108 | await ctx.respond(embed=embed) 109 | 110 | 111 | def setup(bot): 112 | bot.add_cog(Reputation(bot)) 113 | -------------------------------------------------------------------------------- /src/slash_cogs/slash_bot_commands.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from typing import Union, Optional 4 | from discord.commands import slash_command, Option 5 | import datetime 6 | 7 | 8 | class BugReportModal(discord.ui.Modal): 9 | def __init__( 10 | self, 11 | bot: Union[commands.Bot, commands.AutoShardedBot], 12 | attachment: Optional[discord.Attachment], 13 | *args, 14 | **kwargs, 15 | ) -> None: 16 | super().__init__(*args, **kwargs) 17 | self.bot = bot 18 | self.screenshot = attachment 19 | 20 | self.add_item( 21 | discord.ui.InputText( 22 | label="Title", 23 | placeholder="Title of the bug", 24 | ) 25 | ) 26 | 27 | self.add_item( 28 | discord.ui.InputText( 29 | label="Description", 30 | placeholder="Describe the bug. Give a brief explanation of the bug.", 31 | ) 32 | ) 33 | 34 | async def callback(self, interaction: discord.Interaction): 35 | try: 36 | title = self.children[0].value 37 | description = self.children[1].value 38 | embed = discord.Embed( 39 | title=f"New Bug: {title}", 40 | description=description, 41 | color=discord.Color.red(), 42 | ).set_footer( 43 | text=f"Reported by {interaction.user} ({interaction.user.id})\n in {interaction.guild.name} ({interaction.guild.id})", 44 | icon_url=interaction.user.avatar.url, 45 | ) 46 | embed.timestamp = datetime.datetime.now() 47 | embed.set_image(url=self.screenshot.url) if self.screenshot else None 48 | SPACEDOGGO = await self.bot.fetch_user(881861601756577832) 49 | await SPACEDOGGO.send(embed=embed) 50 | await interaction.response.send_message( 51 | embed=discord.Embed( 52 | description=":white_check_mark: Bug reported successfully!", 53 | color=discord.Color.green(), 54 | ) 55 | ) 56 | except Exception as e: 57 | await interaction.response.send_message( 58 | embed=discord.Embed( 59 | description=f":x: Error: {str(e).capitalize()}", 60 | color=discord.Color.red(), 61 | ) 62 | ) 63 | 64 | 65 | class SlashCommands(commands.Cog): 66 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]): 67 | self.bot = bot 68 | 69 | self.help_doc = "Slash commands" 70 | 71 | @slash_command() 72 | async def bug_report( 73 | self, 74 | ctx: discord.ApplicationContext, 75 | screenshot: Option( 76 | discord.Attachment, 77 | "Provide a screenshot of the bug", 78 | required=False, 79 | default=None, 80 | ), 81 | ): 82 | """Report a bug""" 83 | file_formats = ["png", "jpg", "jpeg", "gif", "webp"] 84 | if not screenshot.filename.split(".")[-1].lower() in file_formats: 85 | return await ctx.send( 86 | embed=discord.Embed( 87 | description=f":x: Error: Invalid file format. Please provide a valid screenshot.", 88 | color=discord.Color.red(), 89 | ) 90 | ) 91 | screenshot = screenshot or None 92 | await ctx.interaction.response.send_modal( 93 | BugReportModal(self.bot, screenshot, title="Report a bug") 94 | ) 95 | 96 | 97 | def setup(bot): 98 | bot.add_cog(SlashCommands(bot)) 99 | -------------------------------------------------------------------------------- /src/sql/automod_queries.py: -------------------------------------------------------------------------------- 1 | async def create_automod_tables(bot): 2 | async with bot.cursor as cursor: 3 | await cursor.execute( 4 | """ 5 | CREATE TABLE IF NOT EXISTS 6 | Guilds ( 7 | guild_id bigint PRIMARY KEY NOT NULL, 8 | prefix VARCHAR(5) NOT NULL DEFAULT '.', 9 | rep_toggle BOOLEAN NOT NULL DEFAULT FALSE) 10 | """ 11 | ) 12 | 13 | await cursor.execute( 14 | """ 15 | CREATE TABLE IF NOT EXISTS automod ( 16 | guild_id bigint PRIMARY KEY, 17 | enabled boolean DEFAULT true, 18 | spam_threshold integer DEFAULT 4, 19 | spam_interval integer DEFAULT 2, 20 | spam_message text, 21 | capital_threshold integer DEFAULT 100, 22 | capital_message text, 23 | discord_invites boolean DEFAULT false, 24 | links boolean DEFAULT false, 25 | links_message text, 26 | mass_mentions boolean DEFAULT false, 27 | mass_mentions_message text, 28 | image_spam boolean DEFAULT true, 29 | image_spam_message text, 30 | emoji_spam boolean DEFAULT true, 31 | emoji_spam_message text, 32 | punishment_timeout_minutes integer DEFAULT 5 33 | ) 34 | """ 35 | ) 36 | await cursor.execute( 37 | """ 38 | CREATE TABLE IF NOT EXISTS automod_ignored_users ( 39 | guild_id bigint, 40 | user_id bigint, 41 | PRIMARY KEY (guild_id, user_id) 42 | ) 43 | """ 44 | ) 45 | await cursor.execute( 46 | """ 47 | CREATE TABLE IF NOT EXISTS automod_ignored_channels ( 48 | guild_id bigint, 49 | channel_id bigint, 50 | PRIMARY KEY (guild_id, channel_id) 51 | ) 52 | """ 53 | ) 54 | await cursor.execute( 55 | """ 56 | CREATE TABLE IF NOT EXISTS automod_ignored_roles ( 57 | guild_id bigint, 58 | role_id bigint, 59 | PRIMARY KEY (guild_id, role_id) 60 | ) 61 | """ 62 | ) 63 | await cursor.execute( 64 | """ 65 | CREATE TABLE IF NOT EXISTS automod_ignored_words ( 66 | guild_id bigint, 67 | banned_word VARCHAR(30), 68 | PRIMARY KEY (guild_id, banned_word) 69 | ) 70 | """ 71 | ) 72 | -------------------------------------------------------------------------------- /src/sql/buttonrole_queries.py: -------------------------------------------------------------------------------- 1 | async def create_role_tables(bot): 2 | async with bot.cursor as cursor: 3 | await cursor.execute( 4 | """ 5 | CREATE TABLE IF NOT EXISTS 6 | roles( 7 | guild_id bigint NOT NULL, 8 | role_id bigint NOT NULL, 9 | PRIMARY KEY (guild_id, role_id) 10 | ) 11 | """ 12 | ) 13 | -------------------------------------------------------------------------------- /src/sql/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "spam_message": "Please do not spam.", 3 | "capital_message": "Please do not type in excessively capital letters.", 4 | "links_message": "Please do not send links.", 5 | "mass_mentions_message": "Please do not mass mention.", 6 | "emoji_spam_message": "Please do not use emojis." 7 | } -------------------------------------------------------------------------------- /src/sql/logging_queries.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from typing import Union 3 | 4 | 5 | async def create_logging_tables(bot: Union[commands.Bot, commands.AutoShardedBot]): 6 | async with bot.cursor as cursor: 7 | await cursor.execute( 8 | """ 9 | CREATE TABLE IF NOT EXISTS logging ( 10 | guild_id bigint PRIMARY KEY, 11 | enabled boolean DEFAULT true, 12 | message_delete bigint, 13 | message_edit bigint, 14 | image_delete bigint, 15 | bulk_delete bigint, 16 | invite_info bigint, 17 | moderator_actions bigint, 18 | 19 | member_join bigint, 20 | member_leave bigint, 21 | member_role_add bigint, 22 | member_role_remove bigint, 23 | nickname_change bigint, 24 | member_ban bigint, 25 | member_unban bigint, 26 | 27 | role_create bigint, 28 | role_delete bigint, 29 | role_update bigint, 30 | 31 | channel_create bigint, 32 | channel_delete bigint, 33 | channel_update bigint, 34 | 35 | voice_channel_join bigint, 36 | voice_channel_leave bigint, 37 | voice_channel_move bigint) 38 | """ 39 | ) 40 | -------------------------------------------------------------------------------- /src/sql/mod_queries.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from typing import Union 3 | 4 | 5 | async def create_mod_tables(bot: Union[commands.Bot, commands.AutoShardedBot]): 6 | 7 | async with bot.cursor as cursor: 8 | 9 | await cursor.execute( 10 | """ 11 | CREATE TABLE IF NOT EXISTS 12 | cases ( 13 | guild_id BIGINT NOT NULL, 14 | case_number INT PRIMARY KEY AUTO_INCREMENT NOT NULL, 15 | offence_type ENUM('kick', 'ban', 'warn', 'mute', 'softban') NOT NULL, 16 | time DATETIME NOT NULL, 17 | reason VARCHAR(300) DEFAULT "NA", 18 | moderator BIGINT NOT NULL, 19 | offender BIGINT NOT NULL, 20 | message_id BIGINT 21 | ) 22 | """ 23 | ) 24 | -------------------------------------------------------------------------------- /src/sql/rep_queries.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from typing import Union 3 | 4 | 5 | async def create_rep_tables(bot: Union[commands.Bot, commands.AutoShardedBot]): 6 | async with bot.cursor as cursor: 7 | await cursor.execute( 8 | """ 9 | CREATE TABLE IF NOT EXISTS 10 | reputation ( 11 | user_id BIGINT UNSIGNED NOT NULL, 12 | reputation INT NOT NULL DEFAULT 0, 13 | last_rep_time TIME, 14 | PRIMARY KEY (user_id) 15 | ) 16 | """ 17 | ) 18 | -------------------------------------------------------------------------------- /src/sql/ticket_queries.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from typing import Union 3 | 4 | 5 | async def create_ticket_tables(bot: Union[commands.Bot, commands.AutoShardedBot]): 6 | async with bot.cursor as cursor: 7 | await cursor.execute( 8 | """ 9 | CREATE TABLE IF NOT EXISTS 10 | ticket 11 | (guild_id BIGINT , 12 | count BIGINT, 13 | category BIGINT , 14 | PRIMARY KEY (guild_id)) 15 | """ 16 | ) 17 | 18 | await cursor.execute( 19 | """ 20 | CREATE TABLE IF NOT EXISTS 21 | tickets 22 | (guild_id BIGINT, 23 | channel_id BIGINT, 24 | opener BIGINT, 25 | switch BOOL DEFAULT FALSE, 26 | PRIMARY KEY (guild_id, channel_id))""" 27 | ) 28 | -------------------------------------------------------------------------------- /src/utility/constants/logging_const.py: -------------------------------------------------------------------------------- 1 | channel_map = { 2 | "message_updates": [ 3 | "message_delete", 4 | "message_edit", 5 | "bulk_delete", 6 | "image_delete", 7 | ], 8 | "member_updates": [ 9 | "member_join", 10 | "member_leave", 11 | "invite_info", 12 | "member_role_add", 13 | "member_role_remove", 14 | "nickname_change", 15 | ], 16 | "role_channel_updates": [ 17 | "role_create", 18 | "role_delete", 19 | "role_update", 20 | "channel_create", 21 | "channel_delete", 22 | "channel_update", 23 | "voice_channel_join", 24 | "voice_channel_leave", 25 | "voice_channel_move", 26 | ], 27 | "guild_updates": ["guild_create", "guild_delete", "guild_update"], 28 | "moderation_updates": ["member_ban", "member_unban", "moderator_actions"], 29 | } 30 | -------------------------------------------------------------------------------- /src/utility/disc/command_hinter.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from difflib import SequenceMatcher 3 | from typing import List, Union, Optional, Any 4 | import inspect 5 | import discord 6 | from discord.ext import commands 7 | 8 | 9 | class InvalidGenerator(Exception): 10 | """ 11 | Raises an exception when the user passes an invalid generator. 12 | """ 13 | 14 | __slots__ = ("generator",) 15 | 16 | def __init__(self, generator): 17 | self.generator = generator 18 | super().__init__( 19 | f"Generator of type {type(self.generator)!r} is not supported." 20 | ) 21 | 22 | 23 | def get_generator_response(generator: Any, generator_type: Any, *args, **kwargs) -> Any: 24 | """ 25 | Returns the generator response with the arguments. 26 | :param generator: The generator to get the response from. 27 | :type generator: Any 28 | :param generator_type: The generator type. (Should be same as the generator type. 29 | :type generator_type: Any 30 | :param args: The arguments of the generator. 31 | :param kwargs: The key arguments of the generator 32 | :return: The generator response. 33 | :rtype: Any 34 | """ 35 | 36 | if inspect.isclass(generator) and issubclass(generator, generator_type): 37 | if inspect.ismethod(generator.generate): 38 | return generator.generate(*args, **kwargs) 39 | 40 | return generator().generate(*args, **kwargs) 41 | 42 | if isinstance(generator, generator_type): 43 | return generator.generate(*args, **kwargs) 44 | 45 | raise InvalidGenerator(generator) 46 | 47 | 48 | __all__ = ("CommandResponseGenerator", "DefaultResponseGenerator", "CommandHinter") 49 | 50 | 51 | class CommandResponseGenerator(ABC): 52 | """ 53 | Represents the default abstract CommandResponseGenerator. 54 | """ 55 | 56 | __slots__ = () 57 | 58 | @abstractmethod 59 | def generate( 60 | self, invalid_command: str, suggestions: List[str] 61 | ) -> Optional[Union[str, discord.Embed]]: 62 | """ 63 | The generate method of the generator. 64 | :param str invalid_command: The invalid command. 65 | :param List[str] suggestions: The list of suggestions. 66 | :return: The generator response. 67 | :rtype: Optional[Union[str, discord.Embed]] 68 | """ 69 | 70 | 71 | class DefaultResponseGenerator(CommandResponseGenerator): 72 | __slots__ = () 73 | 74 | def generate(self, invalid_command: str, suggestions: List[str]) -> discord.Embed: 75 | """ 76 | The default generate method of the generator. 77 | :param str invalid_command: The invalid command. 78 | :param List[str] suggestions: The list of suggestions. 79 | :return: The generator response. 80 | :rtype: discord.Embed 81 | """ 82 | 83 | embed = discord.Embed( 84 | title="Invalid command!", 85 | description=f"**`{invalid_command}`** is invalid. Did you mean:", 86 | color=0x00FF00, 87 | ) 88 | 89 | for index, suggestion in enumerate(suggestions[:3]): 90 | embed.add_field( 91 | name=f"**{index + 1}.**", value=f"**`{suggestion}`**", inline=False 92 | ) 93 | 94 | return embed 95 | 96 | 97 | class CommandHinter: 98 | """ 99 | Represents a command hinter. 100 | """ 101 | 102 | __slots__ = ("bot", "generator") 103 | 104 | def __init__( 105 | self, bot: commands.Bot, generator: Optional[CommandResponseGenerator] = None 106 | ): 107 | """ 108 | :param commands.Bot bot: The bot. 109 | :param Optional[CommandResponseGenerator] generator: The command response generator. 110 | """ 111 | 112 | self.bot = bot 113 | self.generator = DefaultResponseGenerator if generator is None else generator 114 | 115 | self.bot.add_listener(self.__handle_hinter, "on_command_error") 116 | 117 | @property 118 | def command_names(self) -> List[str]: 119 | """ 120 | Returns the command names of all commands of the bot. 121 | :return: The command names. 122 | :rtype: List[str] 123 | """ 124 | 125 | names = [] 126 | 127 | for command in self.bot.commands: 128 | if isinstance(command, commands.Group): 129 | names += [command.name] + list(command.aliases) 130 | 131 | else: 132 | names += [command.name] + list(command.aliases) 133 | 134 | return names 135 | 136 | async def __handle_hinter(self, ctx: commands.Context, error) -> None: 137 | if isinstance(error, commands.CommandNotFound): 138 | command_similarity = {} 139 | command_used = ctx.message.content.lstrip(ctx.prefix)[ 140 | : max([len(c) for c in self.command_names]) 141 | ] 142 | 143 | for command in self.command_names: 144 | command_similarity[ 145 | SequenceMatcher(None, command, command_used).ratio() 146 | ] = command 147 | 148 | # Filter it so that the match ratio is at least 0.5 149 | command_similarity = { 150 | k: v for k, v in command_similarity.items() if k >= 0.7 151 | } 152 | 153 | generated_message = get_generator_response( 154 | self.generator, 155 | CommandResponseGenerator, 156 | command_used, 157 | [x[1] for x in sorted(command_similarity.items(), reverse=True)], 158 | ) 159 | 160 | if not generated_message: 161 | return 162 | 163 | if isinstance(generated_message, discord.Embed): 164 | await ctx.send(embed=generated_message) 165 | elif isinstance(generated_message, str): 166 | await ctx.send(generated_message) 167 | else: 168 | raise TypeError( 169 | "The generated message must be of type 'discord.Embed' or 'str'." 170 | ) 171 | 172 | 173 | class CommandGenerator(CommandResponseGenerator): 174 | def generate( 175 | self, invalid_command: str, suggestions: List[str] 176 | ) -> Optional[Union[str, discord.Embed]]: 177 | if suggestions: 178 | return discord.Embed( 179 | title=f"`{invalid_command}` not found.", 180 | description=f"Did you mean `{suggestions[0]}`?", 181 | colour=0x2F3136, 182 | ) 183 | -------------------------------------------------------------------------------- /src/utility/disc/embed_data.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from utility.text.censor_words import censor_letters 3 | 4 | 5 | def description_generator(settings: dict): 6 | embed = discord.Embed(title="Automod Settings", color=0x2F3136) 7 | embed.description = f""" 8 | **Enabled?** {"<:checkmark:938814978142642196>" if settings["enabled"] else "<:crossno:938061321696575508>"} 9 | **Discord Invite Detection** {"<:checkmark:938814978142642196>" if settings["discord_invites"] else "<:crossno:938061321696575508>"} 10 | **Link detection** {"<:checkmark:938814978142642196>" if settings["links"] else "<:crossno:938061321696575508>"} 11 | **Mass mentions detection** {"<:checkmark:938814978142642196>" if settings["mass_mentions"] else "<:crossno:938061321696575508>"} 12 | **Image Spam detection** {"<:checkmark:938814978142642196>" if settings["image_spam"] else "<:crossno:938061321696575508>"} 13 | **Emoji spam detection** {"<:checkmark:938814978142642196>" if settings["emoji_spam"] else "<:crossno:938061321696575508>"} 14 | 15 | **Spam threshold** : `{settings["spam_threshold"]}` 16 | **Spam Interval**: `{settings["spam_interval"]}` 17 | This means that `{settings["spam_threshold"]}` messages every `{settings["spam_interval"]}` seconds are allowed 18 | **Capital Threshold**: `{settings["capital_threshold"]}` 19 | **Punishment**: Timeout for `{str(settings["punishment_timeout_minutes"])}` minutes 20 | 21 | **Ignored channels**: {" ".join(f"<#{channel}>" for channel in settings["ignored_channels"])} 22 | **Ignored users**: {" ".join(f"<@{user}>" for user in settings["ignored_users"])} 23 | **Ignored roles**: {" ".join(f"<@&{roleID}>" for roleID in settings["ignored_roles"])} 24 | **Blacklisted words**: `{censor_letters(" ".join(settings["ignored_words"]))}` 25 | """ 26 | embed.set_thumbnail( 27 | url="https://cdn.discordapp.com/emojis/907486308006510673.webp?size=160&quality=lossless" 28 | ) 29 | return embed 30 | -------------------------------------------------------------------------------- /src/utility/disc/get_prefix.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from typing import Union 4 | 5 | 6 | def get_prefix( 7 | bot: Union[commands.Bot, commands.AutoShardedBot], message: discord.Message 8 | ): 9 | if not message.guild: 10 | return "." 11 | try: 12 | if not str(message.guild.id) in bot.prefix_cache.keys(): 13 | return "." 14 | except AttributeError: 15 | return "." 16 | return bot.prefix_cache[str(message.guild.id)] 17 | -------------------------------------------------------------------------------- /src/utility/text/britishify.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence 2 | 3 | 4 | def strong_british_accent(text: Sequence): 5 | """Converts your given string/array to a kind-of strong british accent (if you're nonsensical about it...)""" 6 | 7 | def brit(brsentence): 8 | 9 | brsentence = brsentence.replace("it was ", "it was quite ") 10 | 11 | # Words relating to ppl 12 | brsentence = ( 13 | brsentence.replace("friend", "mate") 14 | .replace("pal", "mate") 15 | .replace("buddy", "mate") 16 | .replace("person", "mate") 17 | .replace("man", "mate") 18 | .replace("people", "mates") 19 | ) 20 | 21 | # Some weird past tense stuff i don't even know 22 | brsentence = brsentence.replace("standing", "stood") 23 | brsentence = brsentence.replace("sitting", "sat") 24 | 25 | # Pronunciations of syllables 26 | brsentence = brsentence.replace("o ", "oh ") 27 | brsentence = brsentence.replace("ee", "ea") 28 | brsentence = ( 29 | brsentence.replace("er ", "-a ") 30 | .replace("er", "-a") 31 | .replace("or ", "-a ") 32 | .replace("or", "-a") 33 | .replace("ar ", "-a ") 34 | .replace("ar", "-a") 35 | ) 36 | 37 | brsentence = brsentence.replace("a", "ah") 38 | 39 | return brsentence 40 | 41 | if not isinstance(text, str): 42 | britished_msgs = map(brit, text) 43 | 44 | return britished_msgs 45 | 46 | msg = brit(text) 47 | return msg 48 | -------------------------------------------------------------------------------- /src/utility/text/censor_words.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def censor_letters(word): 5 | """ 6 | This function censors the letters in the word. 7 | """ 8 | censored_word = "" 9 | for letter in word: 10 | if random.randint(0, 1): 11 | censored_word += "*" 12 | else: 13 | censored_word += letter 14 | return censored_word 15 | -------------------------------------------------------------------------------- /src/utility/text/decancer.py: -------------------------------------------------------------------------------- 1 | import unicodedata, sys 2 | import unicodedata 3 | 4 | 5 | def strip_accents(s): 6 | return "".join( 7 | c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn" 8 | ) 9 | 10 | 11 | print(strip_accents("ΛGΞИT ム ~ 𝘿𝙚𝙧𝙚𝙆")) 12 | -------------------------------------------------------------------------------- /src/utility/text/safe_message.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import re 3 | 4 | 5 | def safe_message(text: str) -> str: 6 | msg = text.replace("@everyone", "everyone").replace("@here", "here") 7 | 8 | # Remove mentions 9 | for mention in re.findall(r"<@!?(\d+)>", msg): 10 | msg = msg.replace(f"<@!{mention}>", "") 11 | 12 | return msg 13 | -------------------------------------------------------------------------------- /src/utility/text/subscript_superscript.py: -------------------------------------------------------------------------------- 1 | superscript = "ᵃᵇᶜᵈᵉᶠᵍʰᶦʲᵏˡᵐⁿᵒᵖᵠʳˢᵗᵘᵛʷˣʸᶻᴬᴮᶜᴰᴱᶠᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᵠᴿˢᵀᵁⱽᵂˣʸᶻ" 2 | normal_text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 3 | subscript = "₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎" 4 | 5 | 6 | def convert_to_superscript(text: str): 7 | """Converts normal text to superscript""" 8 | result = "" 9 | for char in text: 10 | if char in normal_text: 11 | result += superscript[normal_text.index(char)] 12 | else: 13 | result += char 14 | return result 15 | -------------------------------------------------------------------------------- /src/utility/text/uwufy.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence 2 | import random 3 | 4 | 5 | def owofy(text: Sequence, *, wanky: bool = False): 6 | """translates your given text to owo!""" 7 | 8 | def last_replace(s, old, new): 9 | li = s.rsplit(old, 1) 10 | return new.join(li) 11 | 12 | def text_to_owo(textstr): 13 | 14 | exclamations = ("?", "!", ".", "*") 15 | 16 | prefixes = [ 17 | "Haii UwU ", 18 | "Hiiiiii 0w0 ", 19 | "Hewoooooo >w< ", 20 | "*W* ", 21 | "mmm~ uwu ", 22 | "Oh... Hi there {} ".format(random.choice(["·///·", "(。O⁄ ⁄ω⁄ ⁄ O。)"])), 23 | ] # I need a life, help me 24 | 25 | subs = { 26 | "why": "wai", 27 | "Why": "Wai", 28 | "Hey": "Hai", 29 | "hey": "hai", 30 | "ahw": "ao", 31 | "Hi": "Hai", 32 | "hi": "hai", 33 | "you": "u", 34 | "L": "W", 35 | "l": "w", 36 | "R": "W", 37 | "r": "w", 38 | } 39 | 40 | textstr = random.choice(prefixes) + textstr 41 | if not textstr.endswith(exclamations): 42 | textstr += " uwu" 43 | 44 | smileys = [";;w;;", "^w^", ">w<", "UwU", r"(・`ω\´・)"] 45 | 46 | if not wanky: # to prevent wanking * w * 47 | textstr = textstr.replace("Rank", "Ⓡank").replace("rank", "Ⓡank") 48 | textstr = textstr.replace("Lank", "⒧ank").replace("lank", "⒧ank") 49 | 50 | textstr = last_replace(textstr, "there!", "there! *pounces on u*") 51 | 52 | for key, val in subs.items(): 53 | textstr = textstr.replace(key, val) 54 | 55 | textstr = last_replace(textstr, "!", "! {}".format(random.choice(smileys))) 56 | textstr = last_replace( 57 | textstr, "?", "? {}".format(random.choice(["owo", "O·w·O"])) 58 | ) 59 | textstr = last_replace(textstr, ".", ". {}".format(random.choice(smileys))) 60 | 61 | vowels = ["a", "e", "i", "o", "u", "A", "E", "I", "O", "U"] 62 | 63 | if not wanky: 64 | textstr = textstr.replace("Ⓡank", "rank").replace("⒧ank", "lank") 65 | 66 | for v in vowels: 67 | if "n{}".format(v) in textstr: 68 | textstr = textstr.replace("n{}".format(v), "ny{}".format(v)) 69 | if "N{}".format(v) in textstr: 70 | textstr = textstr.replace( 71 | "N{}".format(v), "N{}{}".format("Y" if v.isupper() else "y", v) 72 | ) 73 | 74 | return textstr 75 | 76 | if not isinstance(text, str): 77 | owoed_msgs = map(text_to_owo, text) 78 | 79 | return owoed_msgs 80 | 81 | return text_to_owo(text) 82 | -------------------------------------------------------------------------------- /src/utility/text/zalgoify.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | class zalgo: 5 | def __init__(self): 6 | self.numAccentsUp = (1, 3) 7 | self.numAccentsDown = (1, 3) 8 | self.numAccentsMiddle = (1, 2) 9 | self.maxAccentsPerLetter = 3 10 | # downward going diacritics 11 | self.dd = [ 12 | "̖", 13 | " ̗", 14 | " ̘", 15 | " ̙", 16 | " ̜", 17 | " ̝", 18 | " ̞", 19 | " ̟", 20 | " ̠", 21 | " ̤", 22 | " ̥", 23 | " ̦", 24 | " ̩", 25 | " ̪", 26 | " ̫", 27 | " ̬", 28 | " ̭", 29 | " ̮", 30 | " ̯", 31 | " ̰", 32 | " ̱", 33 | " ̲", 34 | " ̳", 35 | " ̹", 36 | " ̺", 37 | " ̻", 38 | " ̼", 39 | " ͅ", 40 | " ͇", 41 | " ͈", 42 | " ͉", 43 | " ͍", 44 | " ͎", 45 | " ͓", 46 | " ͔", 47 | " ͕", 48 | " ͖", 49 | " ͙", 50 | " ͚", 51 | " ", 52 | ] 53 | # upward diacritics 54 | self.du = [ 55 | " ̍", 56 | " ̎", 57 | " ̄", 58 | " ̅", 59 | " ̿", 60 | " ̑", 61 | " ̆", 62 | " ̐", 63 | " ͒", 64 | " ͗", 65 | " ͑", 66 | " ̇", 67 | " ̈", 68 | " ̊", 69 | " ͂", 70 | " ̓", 71 | " ̈́", 72 | " ͊", 73 | " ͋", 74 | " ͌", 75 | " ̃", 76 | " ̂", 77 | " ̌", 78 | " ͐", 79 | " ́", 80 | " ̋", 81 | " ̏", 82 | " ̽", 83 | " ̉", 84 | " ͣ", 85 | " ͤ", 86 | " ͥ", 87 | " ͦ", 88 | " ͧ", 89 | " ͨ", 90 | " ͩ", 91 | " ͪ", 92 | " ͫ", 93 | " ͬ", 94 | " ͭ", 95 | " ͮ", 96 | " ͯ", 97 | " ̾", 98 | " ͛", 99 | " ͆", 100 | " ̚", 101 | ] 102 | # middle diacritics 103 | self.dm = [ 104 | " ̕", 105 | " ̛", 106 | " ̀", 107 | " ́", 108 | " ͘", 109 | " ̡", 110 | " ̢", 111 | " ̧", 112 | " ̨", 113 | " ̴", 114 | " ̵", 115 | " ̶", 116 | " ͜", 117 | " ͝", 118 | " ͞", 119 | " ͟", 120 | " ͠", 121 | " ͢", 122 | " ̸", 123 | " ̷", 124 | " ͡", 125 | ] 126 | 127 | def zalgofy(self, text): 128 | """ 129 | Zalgofy a string 130 | """ 131 | # get the letters list 132 | letters = list(text) # ['t','e','s','t',' ','t',...] 133 | 134 | newWord = "" 135 | newLetters = [] 136 | 137 | # for each letter, add some diacritics of all varieties 138 | for letter in letters: #'p', etc... 139 | a = letter # create a dummy letter 140 | 141 | # skip this letter we can't add a diacritic to it 142 | if not a.isalpha(): 143 | newLetters.append(a) 144 | continue 145 | 146 | numAccents = 0 147 | numU = random.randint(self.numAccentsUp[0], self.numAccentsUp[1]) 148 | numD = random.randint(self.numAccentsDown[0], self.numAccentsDown[1]) 149 | numM = random.randint(self.numAccentsMiddle[0], self.numAccentsMiddle[1]) 150 | # Try to add accents to the letter, will add an upper, lower, or middle accent randomly until 151 | # either numAccents == self.maxAccentsPerLetter or we have added the maximum upper, middle and lower accents. Denoted 152 | # by numU, numD, and numM 153 | while numAccents < self.maxAccentsPerLetter and numU + numM + numD != 0: 154 | randint = random.randint( 155 | 0, 2 156 | ) # randomly choose what accent type to add 157 | if randint == 0: 158 | if numU > 0: 159 | a = self.combineWithDiacritic(a, self.du) 160 | numAccents += 1 161 | numU -= 1 162 | elif randint == 1: 163 | if numD > 0: 164 | a = self.combineWithDiacritic(a, self.dd) 165 | numD -= 1 166 | numAccents += 1 167 | else: 168 | if numM > 0: 169 | a = self.combineWithDiacritic(a, self.dm) 170 | numM -= 1 171 | numAccents += 1 172 | 173 | # a = a.replace(" ","") #remove any spaces, this also gives it the zalgo text look 174 | # print('accented a letter: ' + a) 175 | newLetters.append(a) 176 | 177 | newWord = "".join(newLetters) 178 | return newWord 179 | 180 | def combineWithDiacritic(self, letter, diacriticList): 181 | """ 182 | Combines letter and a random character from diacriticList 183 | """ 184 | return ( 185 | letter.strip() 186 | + diacriticList[random.randrange(0, len(diacriticList))].strip() 187 | ) 188 | -------------------------------------------------------------------------------- /src/utility/web/safe_aiohttp.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | from typing import Union 3 | import logging 4 | import html 5 | import json 6 | 7 | 8 | async def aiohttp_get( 9 | url: str, _type: Union[str, None] = "json", headers=None 10 | ) -> Union[str, bytes, dict]: 11 | async with aiohttp.ClientSession() as session: 12 | 13 | async with session.get(url, headers=headers) as response: 14 | if response.status != 200: 15 | logging.info(f"aiohttp_get: {url} returned {response.status}") 16 | return None 17 | if _type == "json": 18 | json = await response.json() 19 | return json 20 | elif _type == "text": 21 | text = await response.text() 22 | return html.unescape(text) 23 | -------------------------------------------------------------------------------- /src/views/automod_views.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from utility.db.database import Database 3 | from utility.disc.embed_data import description_generator 4 | 5 | 6 | class AutoModSettingsView(discord.ui.View): 7 | def __init__(self, ctx: discord.ApplicationContext, settings, database: Database): 8 | super().__init__(timeout=180) 9 | self.ctx = ctx 10 | self.settings = settings 11 | self.database = database 12 | toggle_button = ToggleButton(self.settings, self.database, self.ctx) 13 | invite_detector = InviteDetectionButton(self.settings, self.database, self.ctx) 14 | link_detector = LinkDetectionButton(self.settings, self.database, self.ctx) 15 | mass_mention_detector = MassMentionDetectionButton( 16 | self.settings, self.database, self.ctx 17 | ) 18 | image_spam_detector = ImageSpamDetectionButton( 19 | self.settings, self.database, self.ctx 20 | ) 21 | emoji_spam_detector = EmojiSpamDetectionButton( 22 | self.settings, self.database, self.ctx 23 | ) 24 | spam_detector = SpamDetectionButton(self.settings, self.database, self.ctx) 25 | self.add_item(spam_detector) 26 | self.add_item(invite_detector) 27 | self.add_item(link_detector) 28 | self.add_item(mass_mention_detector) 29 | self.add_item(image_spam_detector) 30 | self.add_item(emoji_spam_detector) 31 | self.add_item(toggle_button) 32 | 33 | 34 | class SpamDetectionButton(discord.ui.Button): 35 | def __init__(self, settings, database: Database, ctx): 36 | super().__init__(label="Message Spam detection", row=1) 37 | self.settings = settings 38 | self.database = database 39 | self.ctx = ctx 40 | 41 | async def callback(self, interaction: discord.Interaction): 42 | if not interaction.user.guild_permissions.manage_guild == True: 43 | await interaction.response.send_message( 44 | "You do not have permission to use this command.", ephemeral=True 45 | ) 46 | 47 | await self.database.set_particular_automod_settings( 48 | interaction.guild.id, 49 | ["spam_threshold", 0 if self.settings["spam_threshold"] else 4], 50 | ) 51 | await interaction.response.send_message( 52 | f"Message Spam detection set to {self.settings['spam_threshold']} every {self.settings['spam_interval']} seconds.", 53 | ephemeral=True, 54 | ) 55 | 56 | self.settings = await self.database.get_automod_settings( 57 | str(interaction.guild.id) 58 | ) 59 | embed = description_generator(self.settings) 60 | await interaction.message.edit( 61 | embed=embed, 62 | view=AutoModSettingsView(self.ctx, self.settings, self.database), 63 | ) 64 | 65 | 66 | class InviteDetectionButton(discord.ui.Button): 67 | def __init__(self, settings, database: Database, ctx): 68 | super().__init__(label="Toggle Invite Detection", row=1) 69 | self.settings = settings 70 | self.database = database 71 | self.ctx = ctx 72 | 73 | async def callback(self, interaction: discord.Interaction): 74 | if not interaction.user.guild_permissions.manage_guild == True: 75 | await interaction.response.send_message( 76 | "You do not have permission to use this command.", ephemeral=True 77 | ) 78 | 79 | await self.database.set_particular_automod_settings( 80 | interaction.guild.id, 81 | ["discord_invites", not self.settings["discord_invites"]], 82 | ) 83 | await interaction.response.send_message( 84 | f"Invite detection set to {not self.settings['discord_invites']}", 85 | ephemeral=True, 86 | ) 87 | 88 | embed = discord.Embed(title="Automod Settings", color=0x2F3136) 89 | self.settings = await self.database.get_automod_settings( 90 | str(interaction.guild.id) 91 | ) 92 | embed = description_generator(self.settings) 93 | await interaction.message.edit( 94 | embed=embed, 95 | view=AutoModSettingsView(self.ctx, self.settings, self.database), 96 | ) 97 | 98 | 99 | class LinkDetectionButton(discord.ui.Button): 100 | def __init__(self, settings, database: Database, ctx): 101 | super().__init__(label="Toggle Link detection", row=2) 102 | self.settings = settings 103 | self.database = database 104 | self.ctx = ctx 105 | 106 | async def callback(self, interaction: discord.Interaction): 107 | if not interaction.user.guild_permissions.manage_guild == True: 108 | await interaction.response.send_message( 109 | "You do not have permission to use this command.", ephemeral=True 110 | ) 111 | 112 | await self.database.set_particular_automod_settings( 113 | interaction.guild.id, ["links", not self.settings["links"]] 114 | ) 115 | await interaction.response.send_message( 116 | f"Link detection set to {not self.settings['links']}", ephemeral=True 117 | ) 118 | 119 | self.settings = await self.database.get_automod_settings( 120 | str(interaction.guild.id) 121 | ) 122 | embed = description_generator(self.settings) 123 | await interaction.message.edit( 124 | embed=embed, 125 | view=AutoModSettingsView(self.ctx, self.settings, self.database), 126 | ) 127 | 128 | 129 | class MassMentionDetectionButton(discord.ui.Button): 130 | def __init__(self, settings, database: Database, ctx): 131 | super().__init__(label="Mass mention detection", row=2) 132 | self.settings = settings 133 | self.database = database 134 | self.ctx = ctx 135 | 136 | async def callback(self, interaction: discord.Interaction): 137 | if not interaction.user.guild_permissions.manage_guild == True: 138 | await interaction.response.send_message( 139 | "You do not have permission to use this command.", ephemeral=True 140 | ) 141 | 142 | await self.database.set_particular_automod_settings( 143 | interaction.guild.id, ["mass_mentions", not self.settings["mass_mentions"]] 144 | ) 145 | await interaction.response.send_message( 146 | f"Mass mention detection set to {not self.settings['mass_mentions']}", 147 | ephemeral=True, 148 | ) 149 | 150 | self.settings = await self.database.get_automod_settings( 151 | str(interaction.guild.id) 152 | ) 153 | embed = description_generator(self.settings) 154 | await interaction.message.edit( 155 | embed=embed, 156 | view=AutoModSettingsView(self.ctx, self.settings, self.database), 157 | ) 158 | 159 | 160 | class ImageSpamDetectionButton(discord.ui.Button): 161 | def __init__(self, settings, database: Database, ctx): 162 | super().__init__(label="Image Spam detection", row=3) 163 | self.settings = settings 164 | self.database = database 165 | self.ctx = ctx 166 | 167 | async def callback(self, interaction: discord.Interaction): 168 | if not interaction.user.guild_permissions.manage_guild == True: 169 | await interaction.response.send_message( 170 | "You do not have permission to use this command.", ephemeral=True 171 | ) 172 | 173 | await self.database.set_particular_automod_settings( 174 | interaction.guild.id, ["image_spam", not self.settings["image_spam"]] 175 | ) 176 | await interaction.response.send_message( 177 | f"Image Spam detection set to {not self.settings['image_spam']}", 178 | ephemeral=True, 179 | ) 180 | 181 | self.settings = await self.database.get_automod_settings( 182 | str(interaction.guild.id) 183 | ) 184 | embed = description_generator(self.settings) 185 | await interaction.message.edit( 186 | embed=embed, 187 | view=AutoModSettingsView(self.ctx, self.settings, self.database), 188 | ) 189 | 190 | 191 | class EmojiSpamDetectionButton(discord.ui.Button): 192 | def __init__(self, settings, database: Database, ctx): 193 | super().__init__(label="Emoji Spam detection", row=3) 194 | self.settings = settings 195 | self.database = database 196 | self.ctx = ctx 197 | 198 | async def callback(self, interaction: discord.Interaction): 199 | if not interaction.user.guild_permissions.manage_guild == True: 200 | await interaction.response.send_message( 201 | "You do not have permission to use this command.", ephemeral=True 202 | ) 203 | 204 | await self.database.set_particular_automod_settings( 205 | interaction.guild.id, ["emoji_spam", not self.settings["emoji_spam"]] 206 | ) 207 | await interaction.response.send_message( 208 | f"Emoji Spam detection set to {not self.settings['emoji_spam']}", 209 | ephemeral=True, 210 | ) 211 | 212 | self.settings = await self.database.get_automod_settings( 213 | str(interaction.guild.id) 214 | ) 215 | embed = description_generator(self.settings) 216 | await interaction.message.edit( 217 | embed=embed, 218 | view=AutoModSettingsView(self.ctx, self.settings, self.database), 219 | ) 220 | 221 | 222 | class ToggleButton(discord.ui.Button): 223 | def __init__(self, settings, database: Database, ctx): 224 | if settings["enabled"]: 225 | super().__init__(label="Disable", style=discord.ButtonStyle.red, row=4) 226 | else: 227 | super().__init__(label="Enable", style=discord.ButtonStyle.green, row=4) 228 | self.settings = settings 229 | self.database = database 230 | self.ctx = ctx 231 | 232 | async def callback(self, interaction: discord.Interaction): 233 | if not interaction.user.guild_permissions.manage_guild == True: 234 | await interaction.response.send_message( 235 | "You do not have permission to use this command.", ephemeral=True 236 | ) 237 | 238 | await self.database.toggle_automod(str(interaction.guild.id)) 239 | b = self.settings["enabled"] 240 | 241 | self.settings = await self.database.get_automod_settings( 242 | str(interaction.guild.id) 243 | ) 244 | 245 | embed = description_generator(self.settings) 246 | 247 | if b: 248 | await interaction.response.send_message( 249 | "AutoMod has been disabled.", ephemeral=True 250 | ) 251 | else: 252 | await interaction.response.send_message( 253 | "AutoMod has been enabled.", ephemeral=True 254 | ) 255 | 256 | await interaction.message.edit( 257 | embed=embed, 258 | view=AutoModSettingsView(self.ctx, self.settings, self.database), 259 | ) 260 | -------------------------------------------------------------------------------- /src/views/button_roles_views.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ui import Modal, InputText 3 | from utility.db.database import Database 4 | 5 | 6 | class RoleButton(discord.ui.Button): 7 | def __init__(self, role: discord.Role): 8 | """ 9 | A button for one role. `custom_id` is needed for persistent views. 10 | """ 11 | super().__init__( 12 | label=role.name, 13 | style=discord.enums.ButtonStyle.primary, 14 | custom_id=str(role.id), 15 | ) 16 | 17 | async def callback(self, interaction: discord.Interaction): 18 | """This function will be called any time a user clicks on this button. 19 | Parameters 20 | ---------- 21 | interaction : discord.Interaction 22 | The interaction object that was created when a user clicks on a button. 23 | """ 24 | # Figure out who clicked the button. 25 | user = interaction.user 26 | # Get the role this button is for (stored in the custom ID). 27 | role = interaction.guild.get_role(int(self.custom_id)) 28 | 29 | if role is None: 30 | # If the specified role does not exist, return nothing. 31 | # Error handling could be done here. 32 | return 33 | 34 | # Add the role and send a response to the uesr ephemerally (hidden to other users). 35 | if role not in user.roles: 36 | # Give the user the role if they don't already have it. 37 | await user.add_roles(role) 38 | await interaction.response.send_message( 39 | f"🎉 You have been given the role {role.mention}", ephemeral=True 40 | ) 41 | else: 42 | # Else, Take the role from the user 43 | await user.remove_roles(role) 44 | await interaction.response.send_message( 45 | f"❌ The {role.mention} role has been taken from you", ephemeral=True 46 | ) 47 | 48 | 49 | class ButtonRolesEmbedModal(Modal): 50 | def __init__(self, database: Database) -> None: 51 | super().__init__(title="Button Roles Embed") 52 | self.add_item( 53 | InputText(label="Embed Title", placeholder="Type the title of the embed") 54 | ) 55 | self.database = database 56 | self.add_item( 57 | InputText( 58 | label="Embed Colour", 59 | placeholder="Hex code for the colour of the embed", 60 | style=discord.InputTextStyle.singleline, 61 | required=False, 62 | ) 63 | ) 64 | 65 | self.add_item( 66 | InputText( 67 | label="Embed Message", 68 | placeholder="Type the message you want in the embed.", 69 | style=discord.InputTextStyle.long, 70 | ) 71 | ) 72 | 73 | self.add_item( 74 | InputText( 75 | label="Role IDs", 76 | placeholder="Type the Role IDs separated by a space, eg: 930370255333752832 942483406476963900 etc.", 77 | style=discord.InputTextStyle.paragraph, 78 | ) 79 | ) 80 | 81 | async def callback(self, interaction: discord.Interaction): 82 | role_ids = [] 83 | for role_id in self.children[3].value.split(" "): 84 | try: 85 | role_ids.append(int(role_id)) 86 | except: 87 | embed = discord.Embed( 88 | description="Invalid Role ID", colour=discord.Colour.red() 89 | ) 90 | await interaction.response.send_message(embed=embed) 91 | return 92 | 93 | if len(role_ids) == 0: 94 | embed = discord.Embed( 95 | description="No Role IDs were given", colour=discord.Colour.red() 96 | ) 97 | await interaction.response.send_message(embed=embed) 98 | return 99 | 100 | elif len(role_ids) > 10: 101 | embed = discord.Embed( 102 | description="Max 10 roles can be given", colour=discord.Colour.red() 103 | ) 104 | await interaction.response.send_message(embed=embed) 105 | return 106 | view = discord.ui.View(timeout=None) 107 | for role_id in role_ids: 108 | # Get the role from the guild by ID. 109 | try: 110 | role = interaction.guild.get_role(role_id) 111 | await self.database.add_role(role_id, interaction.guild.id) 112 | view.add_item(RoleButton(role)) 113 | 114 | except discord.errors.HTTPException: 115 | embed = discord.Embed( 116 | description="@dhravya put error message here my head hurts", 117 | colour=discord.Colour.red(), 118 | ) 119 | await interaction.response.send_message(embed=embed) 120 | return 121 | 122 | try: 123 | colour = self.children[1].value[1:] 124 | except KeyError: 125 | colour = None 126 | if colour != None: 127 | try: 128 | colour = discord.Colour(int(colour, 16)) 129 | except: 130 | embed = discord.Embed( 131 | description="Invalid Colour", colour=discord.Colour.red() 132 | ) 133 | await interaction.response.send_message(embed=embed) 134 | return 135 | else: 136 | colour = discord.Colour.default() 137 | embed = discord.Embed( 138 | title=self.children[0].value, 139 | description=self.children[2].value, 140 | colour=colour, 141 | ) 142 | await interaction.response.send_message(embed=embed, view=view) 143 | 144 | 145 | class ModalButton(discord.ui.Button): 146 | def __init__(self, databaseInstance: Database) -> None: 147 | super().__init__(label="Modal Button", style=discord.ButtonStyle.primary) 148 | self.databaseInstance = databaseInstance 149 | 150 | async def callback(self, interaction: discord.Interaction): 151 | modal = ButtonRolesEmbedModal(database=self.databaseInstance) 152 | await interaction.response.send_modal(modal) 153 | -------------------------------------------------------------------------------- /src/views/config_views.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from utility.constants.logging_const import channel_map 4 | 5 | 6 | class LoggingConfigModal(discord.ui.Modal): 7 | def __init__(self, bot: commands.Bot) -> None: 8 | super().__init__(title="Logging configuration") 9 | self.bot = bot 10 | 11 | self.add_item( 12 | discord.ui.InputText( 13 | style=discord.InputTextStyle.short, 14 | label="Channel ID for Message updates", 15 | placeholder="Type the channel ID, EX: 905904027058984960", 16 | custom_id="message_updates", 17 | ) 18 | ) 19 | 20 | self.add_item( 21 | discord.ui.InputText( 22 | style=discord.InputTextStyle.short, 23 | label="Channel ID for Member updates", 24 | placeholder="Type the channel ID, EX: 905904027058984960", 25 | custom_id="member_updates", 26 | ) 27 | ) 28 | 29 | self.add_item( 30 | discord.ui.InputText( 31 | style=discord.InputTextStyle.short, 32 | label="Channel ID for Role and channel updates", 33 | placeholder="Type the channel ID, EX: 905904027058984960", 34 | custom_id="role_channel_updates", 35 | ) 36 | ) 37 | 38 | self.add_item( 39 | discord.ui.InputText( 40 | style=discord.InputTextStyle.short, 41 | label="Channel ID for Guild updates", 42 | placeholder="Type the channel ID, EX: 905904027058984960", 43 | custom_id="guild_updates", 44 | ) 45 | ) 46 | 47 | self.add_item( 48 | discord.ui.InputText( 49 | style=discord.InputTextStyle.short, 50 | label="Channel ID for Moderation updates", 51 | placeholder="Type the channel ID, EX: 905904027058984960", 52 | custom_id="moderation_updates", 53 | ) 54 | ) 55 | 56 | async def callback(self, interaction: discord.Interaction): 57 | 58 | channels = {} 59 | for child in self.children: 60 | if child.custom_id is not None: 61 | channels[child.custom_id] = child.value 62 | 63 | for log_type in channels: 64 | channel = int(channels[log_type]) 65 | 66 | if channel == "": 67 | continue 68 | try: 69 | channel_id = int(channel) 70 | except: 71 | embed = discord.Embed( 72 | description="Invalid Channel ID", colour=discord.Colour.red() 73 | ) 74 | await interaction.response.send_message(embed=embed) 75 | return 76 | try: 77 | channel = interaction.guild.get_channel(channel_id) 78 | except: 79 | embed = discord.Embed( 80 | description="Channel not found", colour=discord.Colour.red() 81 | ) 82 | await interaction.response.send_message(embed=embed) 83 | return 84 | if channel.type != discord.ChannelType.text: 85 | embed = discord.Embed( 86 | description="Channel is not a text channel", 87 | colour=discord.Colour.red(), 88 | ) 89 | await interaction.response.send_message(embed=embed) 90 | return 91 | 92 | # Check if guild is in the database 93 | 94 | await self.bot.cursor.execute( 95 | "SELECT * FROM logging WHERE guild_id = %s", 96 | (str(interaction.guild.id),), 97 | ) 98 | result = await self.bot.cursor.fetchone() 99 | 100 | if result is None: 101 | # Insert 102 | await self.bot.cursor.execute( 103 | "INSERT INTO logging (guild_id) VALUES (%s)", 104 | (str(interaction.guild.id),), 105 | ) 106 | 107 | for column_name in channel_map[log_type]: 108 | try: 109 | await self.bot.cursor.execute( 110 | "UPDATE logging SET {} = {} WHERE guild_id = {}".format( 111 | column_name, str(channel.id), str(interaction.guild.id) 112 | ) 113 | ) 114 | except: 115 | pass 116 | await self.bot.conn.commit() 117 | 118 | await interaction.response.send_message("Logging configuration updated") 119 | -------------------------------------------------------------------------------- /src/views/error_views.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | 4 | 5 | class ErrorView(discord.ui.View): 6 | def __init__(self, ctx: commands.Context, command_name: str) -> None: 7 | super().__init__() 8 | self.ctx = ctx 9 | self.command_name = command_name 10 | 11 | @discord.ui.button(label="Get help", style=discord.ButtonStyle.gray, emoji="💡") 12 | async def get_help( 13 | self, button: discord.ui.Button, interaction: discord.Interaction 14 | ): 15 | if not interaction.user == self.ctx.author: 16 | return await interaction.response.send_message( 17 | "You can't do that!", ephemeral=True 18 | ) 19 | command = self.ctx.bot.get_command(self.command_name) 20 | if command is None: 21 | return await interaction.response.send_message( 22 | f"I don't know how to {self.command_name}!" 23 | ) 24 | # Get command help 25 | help = command.help 26 | if help is None: 27 | return await interaction.response.send_message("Couldnt get help") 28 | alias_string = ", ".join([f"`{i}`" for i in command.aliases]) 29 | embed = discord.Embed( 30 | title=f"Help for {self.command_name}", 31 | description=f"{help}\n\nAliases: {alias_string}", 32 | color=0x57F287, 33 | ) 34 | 35 | embed.add_field( 36 | name="Usage", 37 | value=f"{self.ctx.prefix}{self.command_name} `{command.usage}`", 38 | inline=True, 39 | ) 40 | if command.params: 41 | for param in command.params: 42 | if not param in ["self", "ctx"]: 43 | embed.add_field( 44 | name=f"{param}", value=f"{command.params[param]}", inline=True 45 | ) 46 | 47 | await interaction.response.send_message(embed=embed) 48 | for button in self.children: 49 | button.disabled = True 50 | await interaction.message.edit(view=self) 51 | -------------------------------------------------------------------------------- /src/views/fun_views.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from typing import Union 3 | from discord.ext import commands 4 | import logging 5 | import random 6 | import asyncio 7 | 8 | 9 | class RoastAgainButton(discord.ui.View): 10 | def __init__( 11 | self, 12 | user: discord.Member, 13 | target: discord.Member, 14 | context: commands.Context, 15 | bot: Union[commands.Bot, commands.AutoShardedBot], 16 | ) -> None: 17 | super().__init__() 18 | self.user = user 19 | self.ctx = context 20 | self.target = target 21 | self.bot = bot 22 | 23 | async def on_timeout(self) -> None: 24 | 25 | for button in self.children: 26 | button.disabled = True 27 | return await super().on_timeout() 28 | 29 | @discord.ui.button(label="Roast Back", style=discord.ButtonStyle.green, emoji="😂") 30 | async def roast_again( 31 | self, button: discord.ui.Button, interaction: discord.Interaction 32 | ): 33 | if not interaction.user == self.user: 34 | return await interaction.response.send_message( 35 | "That roast wasn't against you!", ephemeral=True 36 | ) 37 | 38 | await self.ctx.invoke(self.bot.get_command("roast"), user=self.target) 39 | 40 | for button in self.children: 41 | button.disabled = True 42 | await interaction.message.edit(view=self) 43 | 44 | 45 | class DarkJokeAgainButton(discord.ui.View): 46 | def __init__(self, ctx: commands.Context) -> None: 47 | super().__init__() 48 | self.ctx = ctx 49 | 50 | async def on_timeout(self) -> None: 51 | 52 | for button in self.children: 53 | button.disabled = True 54 | return await super().on_timeout() 55 | 56 | @discord.ui.button(label="Get one more", style=discord.ButtonStyle.green, emoji="💀") 57 | async def dark_joke_again( 58 | self, button: discord.ui.Button, interaction: discord.Interaction 59 | ): 60 | if not interaction.user == self.ctx.author: 61 | return await interaction.response.send_message( 62 | "You can't do that!", ephemeral=True 63 | ) 64 | 65 | await self.ctx.invoke(self.ctx.bot.get_command("darkjoke")) 66 | 67 | for button in self.children: 68 | button.disabled = True 69 | await interaction.message.edit(view=self) 70 | 71 | 72 | class DadJokeAgainButton(discord.ui.View): 73 | def __init__(self, ctx: commands.Context) -> None: 74 | super().__init__() 75 | self.ctx = ctx 76 | 77 | async def on_timeout(self) -> None: 78 | 79 | for button in self.children: 80 | button.disabled = True 81 | return await super().on_timeout() 82 | 83 | @discord.ui.button( 84 | label="Get one more", 85 | style=discord.ButtonStyle.green, 86 | emoji="<:harold:906787943841140766>", 87 | ) 88 | async def dad_joke_again( 89 | self, button: discord.ui.Button, interaction: discord.Interaction 90 | ): 91 | if not interaction.user == self.ctx.author: 92 | return await interaction.response.send_message( 93 | "You can't do that!", ephemeral=True 94 | ) 95 | 96 | await self.ctx.invoke(self.ctx.bot.get_command("dadjoke")) 97 | 98 | for button in self.children: 99 | button.disabled = True 100 | await interaction.message.edit(view=self) 101 | 102 | 103 | class GetComicAgainButton(discord.ui.View): 104 | def __init__(self, ctx, url: str): 105 | super().__init__() 106 | self.ctx = ctx 107 | self.url = url 108 | 109 | self.add_item(discord.ui.Button(label="Link", url=self.url)) 110 | 111 | async def on_timeout(self) -> None: 112 | 113 | for button in self.children: 114 | button.disabled = True 115 | return await super().on_timeout() 116 | 117 | @discord.ui.button(label="Get one more", style=discord.ButtonStyle.green, emoji="📖") 118 | async def get_comic_again( 119 | self, button: discord.ui.Button, interaction: discord.Interaction 120 | ): 121 | if not interaction.user == self.ctx.author: 122 | return await interaction.response.send_message( 123 | "You can't do that!", ephemeral=True 124 | ) 125 | try: 126 | self.children[0].disabled = True 127 | await interaction.message.edit(view=self) 128 | except Exception as e: 129 | # Log the error 130 | logging.log(logging.ERROR, e, exc_info=True) 131 | 132 | await self.ctx.invoke(self.ctx.bot.get_command("comic")) 133 | 134 | 135 | class BeerOfferView(discord.ui.View): 136 | def __init__(self, ctx: commands.Context, user: discord.Member) -> None: 137 | super().__init__(timeout=120) 138 | 139 | self.user = user 140 | self.ctx = ctx 141 | self.reacted = False 142 | 143 | async def on_timeout(self) -> None: 144 | 145 | for button in self.children: 146 | button.disabled = True 147 | return await super().on_timeout() 148 | 149 | async def on_timeout(self) -> None: 150 | if not self.reacted: 151 | for button in self.children: 152 | button.disabled = True 153 | embed = discord.Embed( 154 | title="Beer Offer", 155 | description=f"{self.user.mention} doesn't want to have beer with you :( ", 156 | color=discord.Color.red(), 157 | ) 158 | embed.set_author( 159 | name=self.ctx.author.name, icon_url=self.ctx.author.avatar.url 160 | ) 161 | await self.ctx.send(embed=embed) 162 | return await super().on_timeout() 163 | 164 | @discord.ui.button(label="Beer", style=discord.ButtonStyle.green, emoji="🍺") 165 | async def beer_callback( 166 | self, button: discord.Button, interaction: discord.Interaction 167 | ): 168 | if not interaction.user.id == self.user.id: 169 | return await interaction.response.send_message( 170 | "That's not your beer!", ephemeral=True 171 | ) 172 | 173 | for button in self.children: 174 | button.disabled = True 175 | await interaction.message.edit(view=self) 176 | self.reacted = True 177 | return await interaction.response.send_message( 178 | f"{self.user.mention} has a *Parrtyyyyyyyy* with {self.ctx.author.mention}! :beer:" 179 | ) 180 | 181 | 182 | class CoinFlipAgainView(discord.ui.View): 183 | def __init__(self, ctx: commands.Context): 184 | super().__init__() 185 | self.ctx = ctx 186 | 187 | async def on_timeout(self) -> None: 188 | 189 | for button in self.children: 190 | button.disabled = True 191 | return await super().on_timeout() 192 | 193 | @discord.ui.button(label="Flip Again", style=discord.ButtonStyle.green, emoji="🎲") 194 | async def flip_again( 195 | self, button: discord.ui.Button, interaction: discord.Interaction 196 | ): 197 | if not interaction.user == self.ctx.author: 198 | return await interaction.response.send_message( 199 | "You can't do that!", ephemeral=True 200 | ) 201 | 202 | await interaction.response.send_message( 203 | "You flipped again!", ephemeral=True, delete_after=0.2 204 | ) 205 | return await self.ctx.invoke( 206 | self.ctx.bot.get_command("coinflip"), message=interaction.message 207 | ) 208 | 209 | 210 | class BeerPartyView(discord.ui.View): 211 | def __init__(self, msg: discord.Message, ctx: commands.Context): 212 | super().__init__(timeout=60) 213 | self.msg = msg 214 | self.clicked = [ctx.author.id] 215 | self.ctx = ctx 216 | 217 | @discord.ui.button( 218 | label="Join the Party", style=discord.ButtonStyle.green, emoji="🍻" 219 | ) 220 | async def callback( 221 | self, button: discord.ui.Button, interaction: discord.Interaction 222 | ): 223 | if interaction.user == self.ctx.author: 224 | embed = discord.Embed( 225 | description="**:x: You are already chilling in the party cuz the party is your's lol :beers:**", 226 | color=discord.Color.red(), 227 | ) 228 | return await interaction.response.send_message(embed=embed, ephemeral=True) 229 | if interaction.user.id in self.clicked: 230 | embed = discord.Embed( 231 | description="**:x: You are already chilling in the party :beers:**", 232 | color=discord.Color.red(), 233 | ) 234 | return await interaction.response.send_message(embed=embed, ephemeral=True) 235 | self.clicked.append(interaction.user.id) 236 | embed = discord.Embed( 237 | description=f"**{interaction.user.mention} has just joined the party 🍻!**", 238 | color=discord.Color.green(), 239 | ) 240 | await interaction.response.send_message(embed=embed) 241 | 242 | async def on_timeout(self): 243 | for child in self.children: 244 | child.disabled = True 245 | embed = discord.Embed( 246 | title="The Party Ended", 247 | description=f"**The party :beers: was joined by:\n\n{self.joins(self.clicked)}**", 248 | color=discord.Color.green(), 249 | ) 250 | await self.msg.channel.send(embed=embed) 251 | embed2 = discord.Embed( 252 | title="The Beer Party Ended, if you didn't join wait for the next one :beers:!", 253 | color=discord.Color.orange(), 254 | ) 255 | await self.msg.edit(embed=embed2, view=self) 256 | 257 | def joins(self, list: list): 258 | return "\n".join([f"<@{i}>" for i in list]) 259 | -------------------------------------------------------------------------------- /src/views/help_views.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import datetime 3 | 4 | 5 | def help_embed(): 6 | em = discord.Embed( 7 | title="🔴 ***SPACEBOT HELP***", 8 | description=f""" 9 | > SpaceBot is an open source feature packed discord bot. Navigate the help menu to see all commands! 10 | 11 | Use <@881862674051391499> help `command` to get more information about a command. 12 | 13 | [Invite](https://dsc.gg/spacebt) | [Spacebot is Open Source!](https://github.com/dhravya/spacebot) 14 | """, 15 | ) 16 | em.set_image( 17 | url="https://images-ext-2.discordapp.net/external/MWmqAGeEWIpEaaq9rcMCrPYzMEScRGxEOB4ao9Ph2s0/https/media.discordapp.net/attachments/888798533459775491/903219469650890862/standard.gif" 18 | ) 19 | em.set_footer(text="Join the Coding horizon now!!") 20 | # use custom colour 21 | em.colour = 0x2F3136 22 | return em 23 | 24 | 25 | def cog_help_generator(bot: discord.ext.commands.Bot, cog_name, title): 26 | """Generates the string with all command names with their description""" 27 | cog = bot.get_cog(cog_name) 28 | if cog is None: 29 | return f"{cog_name} is not a valid cog!" 30 | else: 31 | commands = cog.get_commands() 32 | if len(commands) == 0: 33 | return f"{cog_name} has no commands!" 34 | else: 35 | help_string = "" 36 | for command in commands: 37 | help_string += f"**{command.name}** - {command.help}\n" 38 | description = help_string 39 | return discord.Embed( 40 | title=title, description=description, color=0x2F3136 41 | ).set_footer( 42 | text="Use `@Spacebot help ` to get additional help on a specific command." 43 | ) 44 | 45 | 46 | class HelpOptions(discord.ui.View): 47 | def __init__(self, user, bot): 48 | super().__init__() 49 | self.user = user 50 | self.bot = bot 51 | self.add_item( 52 | discord.ui.Button( 53 | label="Join discord.io/code", 54 | url="https://discord.io/code", 55 | row=2, 56 | ) 57 | ) 58 | self.add_item( 59 | discord.ui.Button(label="Meet the Developer", url="https://dhravya.me") 60 | ) 61 | self.add_item( 62 | discord.ui.Button( 63 | label="Hosted on Epikhost", 64 | url="https://discord.gg/vTpkbk8Q64", 65 | row=2, 66 | emoji="<:epikhostlogo:859403955531939851>", 67 | style=discord.ButtonStyle.success, 68 | ) 69 | ) 70 | 71 | @discord.ui.button(label="Delete", style=discord.ButtonStyle.red, emoji="🗑️", row=3) 72 | async def delete_button( 73 | self, button: discord.ui.Button, interaction: discord.Interaction 74 | ): 75 | if not interaction.user == self.user: 76 | return await interaction.response.send_message( 77 | "You didn't ask for the help command!", ephemeral=True 78 | ) 79 | await interaction.message.delete() 80 | 81 | @discord.ui.button( 82 | label="Back to home", style=discord.ButtonStyle.primary, emoji="🏠", row=3 83 | ) 84 | async def back_button( 85 | self, button: discord.ui.Button, interaction: discord.Interaction 86 | ): 87 | if not interaction.user == self.user: 88 | return await interaction.response.send_message( 89 | "You didn't ask for the help command!", ephemeral=True 90 | ) 91 | view = HelpOptions(user=interaction.user, bot=self.bot) 92 | await interaction.message.edit(embed=help_embed(), view=view) 93 | 94 | @discord.ui.select( 95 | placeholder="Select a Command Category", 96 | min_values=1, 97 | max_values=1, 98 | # One option for every cog 99 | # Generate a list of cog names with their values being the cog name 100 | options=[discord.SelectOption(label="Fun commands", value="Fun", emoji="😂")], 101 | ) 102 | async def select_callback(self, select, interaction): 103 | if not interaction.user == self.user: 104 | return await interaction.response.send_message( 105 | "You didn't ask for the help command!", ephemeral=True 106 | ) 107 | 108 | if select.values[0]: 109 | await interaction.response.edit_message( 110 | embed=cog_help_generator( 111 | self.bot, select.values[0], f"**{select.values[0]}** Help!" 112 | ) 113 | ) 114 | -------------------------------------------------------------------------------- /src/views/ticket_views.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | import asyncio 4 | from typing import * 5 | 6 | 7 | async def memberCheck(guild: discord.Guild) -> List[int]: 8 | """Returns the memberList which contains memberIDs of all members combined""" 9 | memberList = [] 10 | for member in guild.members: 11 | memberList.append(member.id) 12 | return memberList 13 | 14 | 15 | class TicketPanelView(discord.ui.View): 16 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot]): 17 | super().__init__(timeout=None) 18 | self.bot = bot 19 | 20 | @discord.ui.button( 21 | label="Create Ticket", 22 | style=discord.ButtonStyle.grey, 23 | emoji="📩", 24 | custom_id="panel", 25 | ) 26 | async def ticket_open_callback( 27 | self, button: discord.ui.Button, interaction: discord.Interaction 28 | ): 29 | embed = discord.Embed( 30 | description="** Creating ticket**", 31 | color=0x2F3136, 32 | ) 33 | await interaction.response.send_message(embed=embed, ephemeral=True) 34 | message = await interaction.original_message() 35 | await self.bot.cursor.execute( 36 | "SELECT count FROM ticket WHERE guild_id=%s", (str(interaction.guild_id),) 37 | ) 38 | data = await self.bot.cursor.fetchone() 39 | if not data: 40 | await self.bot.cursor.execute( 41 | "INSERT INTO ticket(guild_id, count) VALUES(%s,%s)", 42 | (str(interaction.guild_id), 1), 43 | ) 44 | if data: 45 | await self.bot.cursor.execute( 46 | "UPDATE ticket SET count = count + 1 WHERE guild_id=%s", 47 | (str(interaction.guild_id),), 48 | ) 49 | 50 | await self.bot.cursor.execute( 51 | "SELECT category FROM ticket WHERE guild_id=%s", 52 | (str(interaction.guild_id),), 53 | ) 54 | categoryCheck = await self.bot.cursor.fetchone() 55 | 56 | if not categoryCheck: 57 | await self.bot.cursor.execute( 58 | "SELECT * FROM ticket WHERE guild_id=%s", (str(interaction.guild_id),) 59 | ) 60 | ticket_num = await self.bot.cursor.fetchone() 61 | ticket_channel = await interaction.guild.create_text_channel( 62 | f"ticket-{ticket_num[1]}" 63 | ) 64 | perms = ticket_channel.overwrites_for(interaction.guild.default_role) 65 | perms.view_channel = False 66 | await ticket_channel.set_permissions( 67 | interaction.guild.default_role, overwrite=perms 68 | ) 69 | perms2 = ticket_channel.overwrites_for(interaction.user) 70 | perms2.view_channel = True 71 | perms2.read_message_history = True 72 | await ticket_channel.set_permissions(interaction.user, overwrite=perms2) 73 | embed = discord.Embed( 74 | description=f"**:white_check_mark: Successfully created a ticket at {ticket_channel.mention}**", 75 | color=discord.Color.green(), 76 | ) 77 | await message.edit(embed=embed) 78 | embed1 = discord.Embed( 79 | description=f"**Support will be with you shortly.\nTo close this ticket react with 🔒**", 80 | color=discord.Color.green(), 81 | ).set_footer( 82 | text=f"{self.bot.user.name} - Ticket System", 83 | icon_url=self.bot.user.avatar.url, 84 | ) 85 | await ticket_channel.send( 86 | content=interaction.user.mention, 87 | embed=embed1, 88 | view=TicketCloseTop(self.bot), 89 | ) 90 | await self.bot.cursor.execute( 91 | "INSERT INTO tickets (guild_id, channel_id, opener, switch) VALUES(%s,%s,%s,%s)", 92 | ( 93 | str(interaction.guild_id), 94 | str(ticket_channel.id), 95 | str(interaction.user.id), 96 | True, 97 | ), 98 | ) 99 | await self.bot.conn.commit() 100 | 101 | if categoryCheck: 102 | await self.bot.cursor.execute( 103 | "SELECT * FROM ticket WHERE guild_id=%s", (str(interaction.guild_id),) 104 | ) 105 | data = await self.bot.cursor.fetchone() 106 | category = discord.utils.get(interaction.guild.categories, id=data[2]) 107 | ticketChannel = await interaction.guild.create_text_channel( 108 | name=f"ticket-{data[1]}", category=category 109 | ) 110 | perms = ticketChannel.overwrites_for(interaction.user) 111 | perms.view_channel = True 112 | perms.send_messages = True 113 | perms.read_message_history = True 114 | try: 115 | await ticketChannel.set_permissions(interaction.user, overwrite=perms) 116 | except discord.Forbidden: 117 | return await interaction.message.channel.send( 118 | "I do not have the permissions to manage the ticket channel. Please contact the administrator of this server." 119 | ) 120 | embed = discord.Embed( 121 | description=f"**:white_check_mark: Successfully created a ticket at {ticketChannel.mention}**", 122 | color=discord.Color.green(), 123 | ) 124 | await message.edit(embed=embed) 125 | embed1 = discord.Embed( 126 | description=f"**Support will be with you shortly.\nTo close this ticket react with 🔒**", 127 | color=discord.Color.green(), 128 | ).set_footer( 129 | text=f"{self.bot.user.name} - Ticket System", 130 | icon_url=self.bot.user.avatar.url, 131 | ) 132 | await ticketChannel.send( 133 | content=interaction.user.mention, 134 | embed=embed1, 135 | view=TicketCloseTop(self.bot), 136 | ) 137 | await self.bot.cursor.execute( 138 | "INSERT INTO tickets (guild_id, channel_id, opener, switch) VALUES(%s,%s,%s,%s)", 139 | ( 140 | str(interaction.guild_id), 141 | str(ticketChannel.id), 142 | str(interaction.user.id), 143 | True, 144 | ), 145 | ) 146 | await self.bot.conn.commit() 147 | 148 | 149 | class TicketCloseTop(discord.ui.View): 150 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot] = None): 151 | super().__init__(timeout=None) 152 | self.bot = bot 153 | 154 | @discord.ui.button( 155 | label="Close", style=discord.ButtonStyle.gray, emoji="🔒", custom_id="top:close" 156 | ) 157 | async def close_callback( 158 | self, button: discord.Button, interaction: discord.Interaction 159 | ): 160 | await self.bot.cursor.execute( 161 | "SELECT * FROM tickets WHERE guild_id=%s AND channel_id=%s", 162 | (str(interaction.guild_id), str(str(interaction.channel_id))), 163 | ) 164 | data = await self.bot.cursor.fetchone() 165 | if data[3] == "closed": 166 | return await interaction.response.send_message( 167 | embed=discord.Embed( 168 | description=f"**:x: The ticket is already closed!**", 169 | color=discord.Color.red(), 170 | ), 171 | ephemeral=True, 172 | ) 173 | await interaction.response.send_message( 174 | embed=discord.Embed( 175 | description="**Are you sure you want to close the ticket%s**", 176 | color=discord.Color.orange(), 177 | ) 178 | ) 179 | message = await interaction.original_message() 180 | await message.edit(view=TicketCloseTop2(interaction.user, message, self.bot)) 181 | 182 | 183 | class TicketCloseTop2(discord.ui.View): 184 | def __init__( 185 | self, 186 | buttonUser: discord.Member, 187 | msg: discord.Message, 188 | bot: Union[commands.Bot, commands.AutoShardedBot], 189 | ): 190 | super().__init__(timeout=15) 191 | self.user = buttonUser 192 | self.msg = msg 193 | self.bot = bot 194 | 195 | @discord.ui.button(label="Yes", style=discord.ButtonStyle.danger) 196 | async def yes_callback( 197 | self, button: discord.ui.Button, interaction: discord.Interaction 198 | ): 199 | if interaction.user != self.user: 200 | return await interaction.channel.send( 201 | embed=discord.Embed( 202 | description=f"**:x: You can't do that {interaction.user.mention}**", 203 | color=discord.Color.red(), 204 | ) 205 | ) 206 | for child in self.children: 207 | child.disabled = True 208 | await self.bot.cursor.execute( 209 | "SELECT * FROM tickets WHERE guild_id=%s AND channel_id=%s", 210 | (str(interaction.guild_id), str(interaction.channel_id)), 211 | ) 212 | member_id = await self.bot.cursor.fetchone() 213 | memCheck = await memberCheck(interaction.guild) 214 | if member_id[2] not in memCheck: 215 | await self.bot.cursor.execute( 216 | "UPDATE tickets SET switch = %s WHERE guild_id=%s AND channel_id=%s", 217 | (False, str(interaction.guild_id), str(interaction.channel_id)), 218 | ) 219 | await self.bot.conn.commit() 220 | await self.msg.delete() 221 | await interaction.channel.send( 222 | embed=discord.Embed( 223 | description=f"**Ticket closed by {interaction.user.mention}**", 224 | color=discord.Color.orange(), 225 | ) 226 | ) 227 | return 228 | member = interaction.guild.get_member(member_id[2]) 229 | if ( 230 | member.guild_permissions.administrator 231 | or member.guild_permissions.manage_channels 232 | ): 233 | pass 234 | else: 235 | perms = interaction.channel.overwrites_for(member) 236 | perms.view_channel = False 237 | perms.send_messages = False 238 | perms.read_message_history = False 239 | await interaction.channel.set_permissions(member, overwrite=perms) 240 | await self.bot.cursor.execute( 241 | "UPDATE tickets SET switch = %s WHERE guild_id=%s AND channel_id=%s", 242 | (False, str(interaction.guild_id), str(interaction.channel_id)), 243 | ) 244 | await self.bot.conn.commit() 245 | await self.msg.delete() 246 | await interaction.channel.send( 247 | embed=discord.Embed( 248 | description=f"**Ticket closed by {interaction.user.mention}**", 249 | color=discord.Color.orange(), 250 | ) 251 | ) 252 | 253 | @discord.ui.button(label="No", style=discord.ButtonStyle.gray) 254 | async def no_callback( 255 | self, button: discord.ui.Button, interaction: discord.Interaction 256 | ): 257 | if interaction.user != self.user: 258 | return await interaction.channel.send( 259 | embed=discord.Embed( 260 | description=f"**:x: You can't do that {interaction.user.mention}**", 261 | color=discord.Color.red(), 262 | ) 263 | ) 264 | for child in self.children: 265 | child.disabled = True 266 | await interaction.response.edit_message( 267 | embed=discord.Embed( 268 | description=f"**:white_check_mark: Canceled closing {interaction.channel.mention}**", 269 | color=discord.Color.green(), 270 | ), 271 | view=self, 272 | ) 273 | 274 | async def on_timeout(self): 275 | try: 276 | for child in self.children: 277 | if child.disabled: 278 | return 279 | for child in self.children: 280 | child.disabled = True 281 | embed = discord.Embed( 282 | description=f"**:x: Oops you didn't respond within time! So, Canceled closing the ticket!**", 283 | color=discord.Color.red(), 284 | ) 285 | await self.msg.edit(embed=embed, view=self) 286 | except discord.errors.NotFound: 287 | print("The ticket was deleted") 288 | 289 | 290 | class TicketControlsView(discord.ui.View): 291 | def __init__(self, bot: Union[commands.Bot, commands.AutoShardedBot] = None): 292 | super().__init__(timeout=None) 293 | self.bot = bot 294 | 295 | @discord.ui.button( 296 | label="Open", 297 | style=discord.ButtonStyle.gray, 298 | emoji="🔓", 299 | custom_id="controls:open", 300 | ) 301 | async def open_callback( 302 | self, button: discord.ui.Button, interaction: discord.Interaction 303 | ): 304 | if not interaction.user.guild_permissions.manage_channels: 305 | return await interaction.response.send_message( 306 | embed=discord.Embed( 307 | description=f":x: You can't do that {interaction.user.mention}!", 308 | color=discord.Color.red(), 309 | ) 310 | ) 311 | await self.bot.cursor.execute( 312 | "SELECT * FROM tickets WHERE guild_id=%s AND channel_id=%s", 313 | (str(interaction.guild_id), str(interaction.channel_id)), 314 | ) 315 | member_id = await self.bot.cursor.fetchone() 316 | memCheck = await memberCheck(interaction.guild) 317 | if member_id[2] not in memCheck: 318 | return await interaction.response.send_message( 319 | embed=discord.Embed( 320 | description="**:x: This user is no more in this server!\n------------------------------------------\nThere is no use of opening this ticket!**", 321 | color=discord.Color.red(), 322 | ) 323 | ) 324 | member = interaction.guild.get_member(member_id[2]) 325 | perms = interaction.channel.overwrites_for(member) 326 | perms.view_channel = True 327 | perms.send_messages = True 328 | perms.read_message_history = True 329 | await interaction.channel.set_permissions(member, overwrite=perms) 330 | await self.bot.cursor.execute( 331 | "UPDATE tickets SET switch = %s WHERE guild_id=%s AND channel_id=%s", 332 | (True, str(interaction.guild_id), str(interaction.channel_id)), 333 | ) 334 | await self.bot.conn.commit() 335 | await interaction.response.edit_message( 336 | embed=discord.Embed( 337 | description=f"**Ticket opened by {interaction.user.mention}**", 338 | color=discord.Color.green(), 339 | ), 340 | view=None, 341 | ) 342 | 343 | @discord.ui.button( 344 | label="Delete", 345 | style=discord.ButtonStyle.gray, 346 | emoji="⛔", 347 | custom_id="controls:close", 348 | ) 349 | async def delete_callback( 350 | self, button: discord.ui.Button, interaction: discord.Interaction 351 | ): 352 | if not interaction.user.guild_permissions.manage_channels: 353 | return await interaction.response.send_message( 354 | embed=discord.Embed( 355 | description=f":x: You can't do that!", color=discord.Color.red() 356 | ), 357 | ephemeral=True, 358 | ) 359 | try: 360 | await interaction.response.edit_message( 361 | embed=discord.Embed( 362 | description=f"**:white_check_mark: The ticket will be deleted soon**", 363 | color=discord.Color.orange(), 364 | ), 365 | view=None, 366 | ) 367 | await asyncio.sleep(3) 368 | await interaction.channel.delete() 369 | await self.bot.cursor.execute( 370 | "DELETE FROM tickets WHERE guild_id=%s AND channel_id=%s", 371 | (str(interaction.guild_id), str(interaction.channel_id)), 372 | ) 373 | await self.bot.conn.commit() 374 | except discord.NotFound: 375 | print("The ticket was deleted") 376 | 377 | 378 | class TicketResetView(discord.ui.View): 379 | def __init__( 380 | self, 381 | ctx: commands.Context, 382 | message: discord.Message, 383 | bot: Union[commands.Bot, commands.AutoShardedBot], 384 | ): 385 | super().__init__(timeout=15) 386 | self.ctx = ctx 387 | self.msg = message 388 | self.bot = bot 389 | 390 | @discord.ui.button(label="Yes", style=discord.ButtonStyle.green, emoji="✅") 391 | async def confirm_callback( 392 | self, button: discord.ui.Button, interaction: discord.Interaction 393 | ): 394 | if interaction.user != self.ctx.author: 395 | embed = discord.Embed( 396 | description=f":x: You can't do that {interaction.user.mention}!", 397 | color=discord.Color.red(), 398 | ) 399 | return await self.ctx.send(embed=embed, delete_after=5) 400 | for child in self.children: 401 | child.disabled = True 402 | await self.bot.cursor.execute( 403 | "UPDATE ticket SET count = 0 WHERE guild_id=%s", 404 | (str(interaction.guild_id),), 405 | ) 406 | await self.bot.conn.commit() 407 | embed = discord.Embed( 408 | description="**:white_check_mark: Succesfully resetted the ticket count!**", 409 | color=discord.Color.green(), 410 | ) 411 | await interaction.response.edit_message(embed=embed, view=self) 412 | 413 | @discord.ui.button(label="No", style=discord.ButtonStyle.red, emoji="❌") 414 | async def decline_callback( 415 | self, button: discord.ui.Button, interaction: discord.Interaction 416 | ): 417 | if interaction.user != self.ctx.author: 418 | embed = discord.Embed( 419 | description=f":x: You can't do that {interaction.user.mention}!", 420 | color=discord.Color.red(), 421 | ) 422 | return await self.ctx.send(embed=embed, delete_after=5) 423 | for child in self.children: 424 | child.disabled = True 425 | embed_ = discord.Embed( 426 | description="**:white_check_mark: Canceled resetting ticket count!**", 427 | color=discord.Color.green(), 428 | ) 429 | await interaction.response.edit_message(embed=embed_, view=self) 430 | 431 | async def on_timeout(self): 432 | try: 433 | for child in self.children: 434 | if child.disabled: 435 | return 436 | for child in self.children: 437 | child.disabled = True 438 | embed = discord.Embed( 439 | description=f"**:x: Oops you didn't respond within time! So, Canceled resetting ticket count!**", 440 | color=discord.Color.red(), 441 | ) 442 | await self.msg.edit(embed=embed, view=self) 443 | except discord.NotFound: 444 | pass 445 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | def main(): 5 | process = subprocess.Popen(["python3", "src/main.py"]) 6 | try: 7 | print("Running in process", process.pid) 8 | process.wait(timeout=15) 9 | except subprocess.TimeoutExpired: 10 | print("Timed out - killing", process.pid) 11 | process.kill() 12 | print("Success!") 13 | exit(0) 14 | 15 | 16 | if __name__ == "__main__": 17 | main() 18 | --------------------------------------------------------------------------------