├── .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 |
--------------------------------------------------------------------------------