Total Complexity | 57 |
Total Lines | 421 |
Duplicated Lines | 10.93 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like build.cogs.commandscog often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | """ |
||
2 | Cog with general commands available in the Bot. |
||
3 | |||
4 | Current commands: |
||
5 | /ping - check Bot latency |
||
6 | /clear - clear x messages on the channel |
||
7 | /exit | !exit - end Bot's runtime and disconnect from the server |
||
8 | /warn - warn @user with reason |
||
9 | /warns - send @user warns to author's DM |
||
10 | /nword - Changes N-Word killed channel name - UNSTABLE |
||
11 | /updatetotmem - Update #TotalMembers channel |
||
12 | /updatecommon - Update common spotting channel with new monster name |
||
13 | """ |
||
14 | import asyncio |
||
15 | import json |
||
16 | import os |
||
17 | from datetime import datetime |
||
18 | import discord |
||
19 | from discord.utils import get |
||
20 | from modules.get_settings import get_settings |
||
21 | from googletrans import Translator |
||
22 | import cogs.cogbase as cogbase |
||
23 | from discord.ext import commands |
||
24 | from discord_slash import cog_ext, SlashContext |
||
25 | from cogs.databasecog import DatabaseCog |
||
26 | from cogs.leaderboardcog import legend_multiplier |
||
27 | from modules.pull_config.pull_config import get_config |
||
28 | |||
29 | |||
30 | class CommandsCog(cogbase.BaseCog): |
||
31 | def __init__(self, base): |
||
32 | super().__init__(base) |
||
33 | |||
34 | # GENERAL FUNCTIONS |
||
35 | # Check latency |
||
36 | @cog_ext.cog_slash(name="ping", guild_ids=cogbase.GUILD_IDS, |
||
37 | description="Function for checking latency", |
||
38 | default_permission=False, |
||
39 | permissions=cogbase.PERMISSION_MODS) |
||
40 | async def _ping(self, ctx: SlashContext): |
||
41 | await ctx.send(f"Pong! {round(self.bot.latency * 1000)}ms", delete_after=4.0) |
||
42 | |||
43 | # Clear messages |
||
44 | @cog_ext.cog_slash(name="clear", guild_ids=cogbase.GUILD_IDS, |
||
45 | description="Function for clearing messages on channel", |
||
46 | default_permission=False, |
||
47 | permissions=cogbase.PERMISSION_MODS) |
||
48 | async def _purge(self, ctx: SlashContext, number): |
||
49 | num_messages = int(number) |
||
50 | await ctx.channel.purge(limit=num_messages) |
||
51 | await ctx.send(f"Cleared {num_messages} messages!", delete_after=4.0) |
||
52 | |||
53 | # Disconnect Bot |
||
54 | @cog_ext.cog_slash(name="exit", guild_ids=cogbase.GUILD_IDS, |
||
55 | description="Turn off the bot", |
||
56 | default_permission=False, |
||
57 | permissions=cogbase.PERMISSION_ADMINS) |
||
58 | async def _exit(self, ctx: SlashContext): |
||
59 | await ctx.send(f"Closing Bot", delete_after=1.0) |
||
60 | dt_string = self.bot.get_current_time() |
||
61 | print(f"({dt_string})\t[{self.__class__.__name__}]: Exiting Bot") |
||
62 | await asyncio.sleep(3) |
||
63 | await self.bot.close() |
||
64 | |||
65 | # WARN FUNCTIONS |
||
66 | |||
67 | # Warn user |
||
68 | @cog_ext.cog_slash(name="warn", guild_ids=cogbase.GUILD_IDS, |
||
69 | description="Function for warning users", |
||
70 | default_permission=False, |
||
71 | permissions=cogbase.PERMISSION_MODS) |
||
72 | async def _warn(self, ctx: SlashContext, user: discord.User, reason: str): |
||
73 | |||
74 | await DatabaseCog.db_add_warn(user.id, reason) |
||
75 | await ctx.send( |
||
76 | f"{user.mention} was warned for:\n*\"{reason}\"*\n") # f"Number of warns: {len(current_user['reasons'])}") |
||
77 | |||
78 | # Get list of user's warns |
||
79 | @cog_ext.cog_slash(name="warns", guild_ids=cogbase.GUILD_IDS, |
||
80 | description="Function for warning users", |
||
81 | default_permission=False, |
||
82 | permissions=cogbase.PERMISSION_MODS) |
||
83 | async def _warns(self, ctx: SlashContext, user: discord.User): |
||
84 | warns, nr_of_warns = await DatabaseCog.db_get_warns(user.id) |
||
85 | nl = "\n" |
||
86 | message = f"**{user.name}** has been warned **{nr_of_warns}** times\n\n_Reasons_:\n" \ |
||
87 | f"{nl.join(warns)}\n" |
||
88 | await ctx.author.send(message) |
||
89 | await ctx.send(f"{user.name} warns has been sent to DM", hidden=True) |
||
90 | |||
91 | # Remove all member's warns |
||
92 | @cog_ext.cog_slash(name="removeWarns", guild_ids=cogbase.GUILD_IDS, |
||
93 | description="Function for removing user's all warns", |
||
94 | default_permission=False, |
||
95 | permissions=cogbase.PERMISSION_ADMINS) |
||
96 | async def remove_warns(self, ctx: SlashContext, user: discord.User): |
||
97 | await DatabaseCog.db_remove_warns(user.id) |
||
98 | await ctx.send(f"{user.display_name}'s warns were deleted", hidden=True) |
||
99 | |||
100 | # Mute member |
||
101 | @cog_ext.cog_slash(name="mute", guild_ids=cogbase.GUILD_IDS, |
||
102 | description="Mute member for x minutes", |
||
103 | default_permission=False, |
||
104 | permissions=cogbase.PERMISSION_MODS) |
||
105 | async def _mute(self, ctx: SlashContext, user: discord.User, time: int, reason: str): |
||
106 | duration = time * 60 |
||
107 | guild = ctx.guild |
||
108 | muted = discord.utils.get(guild.roles, name="Muted") |
||
109 | |||
110 | if not muted: |
||
111 | muted = await guild.create_role(name="Muted") |
||
112 | for channel in guild.channels: |
||
113 | await channel.set_permissions(muted, speak=False, send_messages=False, read_message_history=True, |
||
114 | read_messages=False) |
||
115 | await user.add_roles(muted, reason=reason) |
||
116 | await ctx.send(f"{user.mention} Was muted by {ctx.author.name} for {time} min\n" |
||
117 | f"Reason: {reason}", delete_after=10) |
||
118 | await asyncio.sleep(duration) |
||
119 | await user.remove_roles(muted) |
||
120 | await ctx.send(f"{user.mention}'s mute is over", delete_after=10) |
||
121 | |||
122 | # KICK FUNCTIONS |
||
123 | |||
124 | # Kick |
||
125 | View Code Duplication | @cog_ext.cog_slash(name="kick", guild_ids=cogbase.GUILD_IDS, |
|
126 | description="Kicks member from the server", |
||
127 | default_permission=False, |
||
128 | permissions=cogbase.PERMISSION_MODS) |
||
129 | async def kick(self, ctx, user: discord.Member, *, reason=None): |
||
130 | if user == ctx.author: |
||
131 | return await ctx.send( |
||
132 | f"{user.mention} You can't kick yourself", delete_after=5.0) |
||
133 | await user.kick(reason=reason) |
||
134 | if not reason: |
||
135 | await ctx.send(f"{user} was kicked", delete_after=10.0) |
||
136 | await user.send(f"You were kicked from {ctx.guild.name}") |
||
137 | else: |
||
138 | await ctx.send(f"{user} was kicked\nReason: {reason}", delete_after=10.0) |
||
139 | await user.send(f"You were kicked from {ctx.guild.name}\nReason: {reason}") |
||
140 | |||
141 | # TODO: Remove code repetition? |
||
142 | # Ban |
||
143 | View Code Duplication | @cog_ext.cog_slash(name="ban", guild_ids=cogbase.GUILD_IDS, |
|
144 | description="Bans member from the server", |
||
145 | default_permission=False, |
||
146 | permissions=cogbase.PERMISSION_ADMINS) |
||
147 | async def kick(self, ctx, user: discord.Member, *, reason=None): |
||
148 | if user == ctx.author: |
||
149 | return await ctx.send( |
||
150 | f"{user.mention} You can't ban yourself", delete_after=5.0) |
||
151 | await user.ban(reason=reason) |
||
152 | if not reason: |
||
153 | await ctx.send(f"{user} was banned", delete_after=10.0) |
||
154 | await user.send(f"You were banned from {ctx.guild.name}") |
||
155 | else: |
||
156 | await ctx.send(f"{user} was banned \nReason: {reason}", delete_after=10.0) |
||
157 | await user.send(f"You were banned from {ctx.guild.name}\nReason: {reason}") |
||
158 | |||
159 | # Softban |
||
160 | View Code Duplication | @cog_ext.cog_slash(name="softban", guild_ids=cogbase.GUILD_IDS, |
|
161 | description="Bans and unbans the user, so their messages are deleted", |
||
162 | default_permission=False, |
||
163 | permissions=cogbase.PERMISSION_MODS) |
||
164 | async def kick(self, ctx, user: discord.Member, *, reason=None): |
||
165 | if user == ctx.author: |
||
166 | return await ctx.send( |
||
167 | f"{user.mention} You can't softban yourself", delete_after=5.0) |
||
168 | await user.ban(reason=reason) |
||
169 | await user.unban(reason=reason) |
||
170 | if not reason: |
||
171 | await ctx.send(f"{user} was softbanned", delete_after=10.0) |
||
172 | await user.send(f"You were softbanned from {ctx.guild.name}") |
||
173 | else: |
||
174 | await ctx.send(f"{user} was softbanned \nReason: {reason}", delete_after=10.0) |
||
175 | await user.send(f"You were softbanned from {ctx.guild.name}\nReason: {reason}") |
||
176 | |||
177 | # CHANNEL NAMES UPDATES |
||
178 | # Total member channel name |
||
179 | @cog_ext.cog_slash(name="updateTotalMembers", guild_ids=cogbase.GUILD_IDS, |
||
180 | description="Update total number of members", |
||
181 | default_permission=False, |
||
182 | permissions=cogbase.PERMISSION_MODS) |
||
183 | async def update_member_count_command(self, ctx: SlashContext): |
||
184 | await self.bot.update_member_count(ctx) |
||
185 | await ctx.send(f"Total Members count updated", hidden=True) |
||
186 | |||
187 | # Commons channel name |
||
188 | @cog_ext.cog_slash(name="updateCommons", guild_ids=cogbase.GUILD_IDS, |
||
189 | description="Update common channel name", |
||
190 | default_permission=False, |
||
191 | permissions=cogbase.PERMISSION_MODS) |
||
192 | async def update_commons_ch_command(self, ctx: SlashContext): |
||
193 | with open('./server_files/commons.txt') as f: |
||
194 | try: |
||
195 | commons = f.read().splitlines() |
||
196 | except ValueError: |
||
197 | print(ValueError) |
||
198 | |||
199 | await self.update_commons_ch(ctx, commons) |
||
200 | |||
201 | commons.append(commons.pop(commons.index(commons[0]))) |
||
202 | with open('./server_files/commons.txt', 'w') as f: |
||
203 | for item in commons: |
||
204 | f.write("%s\n" % item) |
||
205 | |||
206 | async def update_commons_ch(self, ctx: SlashContext, commons): |
||
207 | new_name = f"common {commons[0]}" |
||
208 | common_ch = self.bot.get_channel(self.bot.ch_common) |
||
209 | await discord.TextChannel.edit(common_ch, name=new_name) |
||
210 | dt_string = self.bot.get_current_time() |
||
211 | print(f"({dt_string})\t[{self.__class__.__name__}]: Common channel name updated: {commons[0]}") |
||
212 | |||
213 | await common_ch.send(f"Common changed: {commons[0]}") |
||
214 | await ctx.send(f"Common changed: {commons[0]}", hidden=True) |
||
215 | |||
216 | # N-Word spotted channel name |
||
217 | # Doesn't work if used too many times in a short period of time |
||
218 | @cog_ext.cog_slash(name="nword", guild_ids=cogbase.GUILD_IDS, |
||
219 | description="Change N-Word channel name", |
||
220 | permissions=cogbase.PERMISSION_ADMINS) |
||
221 | async def rename_nword_channel(self, ctx, status: str): |
||
222 | new_status = status |
||
223 | channel = self.bot.get_channel(self.bot.ch_nightmare_killed) |
||
224 | if new_status in channel.name: |
||
225 | await ctx.send(f"{channel.name} has been changed", hidden=True) |
||
226 | else: |
||
227 | await discord.VoiceChannel.edit(channel, name=f"N-Word fixed: {new_status}") |
||
228 | await ctx.send(f"{channel.name} channel name has been changed", hidden=True) |
||
229 | |||
230 | # OTHER |
||
231 | |||
232 | # Pull config.json from Google Sheets |
||
233 | @cog_ext.cog_slash(name="pullConfig", guild_ids=cogbase.GUILD_IDS, |
||
234 | description="Pull config from google sheets", |
||
235 | default_permission=False, |
||
236 | permissions=cogbase.PERMISSION_ADMINS) |
||
237 | async def pull_config(self, ctx: SlashContext): |
||
238 | get_config() |
||
239 | with open('server_files/config.json', 'r', encoding='utf-8-sig') as fp: |
||
240 | self.bot.config = json.load(fp) |
||
241 | await self.create_roles(ctx, True) |
||
242 | await self.create_roles(ctx, False) |
||
243 | dt_string = self.bot.get_current_time() |
||
244 | print(f"({dt_string})\t[{self.__class__.__name__}]: Finished data pull") |
||
245 | await ctx.send(f"Config.json updated", hidden=True) |
||
246 | |||
247 | # Create roles if pull_config gets non existent roles |
||
248 | async def create_roles(self, ctx: SlashContext, common: bool): |
||
249 | milestones = "common_milestones" if common else "total_milestones" |
||
250 | for mon_type in self.bot.config[milestones][0]: |
||
251 | if get(ctx.guild.roles, name=mon_type): |
||
252 | continue |
||
253 | else: |
||
254 | await ctx.guild.create_role(name=mon_type) |
||
255 | dt_string = self.bot.get_current_time() |
||
256 | print(f"({dt_string})\t[{self.__class__.__name__}]: {mon_type} role created") |
||
257 | |||
258 | # Clear temp spots table in database |
||
259 | @cog_ext.cog_slash(name="clearTempSpots", guild_ids=cogbase.GUILD_IDS, |
||
260 | description="Clear temp spots table in database", |
||
261 | permissions=cogbase.PERMISSION_ADMINS) |
||
262 | async def clear_temp_spots_table(self, ctx): |
||
263 | await DatabaseCog.db_clear_spots_temp_table() |
||
264 | await ctx.send(f"Temp spots table was cleared", hidden=True) |
||
265 | await self.reload_cog(ctx, "cogs.databasecog") |
||
266 | |||
267 | # Reloads cog, very useful because there is no need to exit the bot after updating cog |
||
268 | async def reload_cog(self, ctx: SlashContext, module: str): |
||
269 | """Reloads a module.""" |
||
270 | dt_string = self.bot.get_current_time() |
||
271 | try: |
||
272 | self.bot.load_extension(f"{module}") |
||
273 | await ctx.send(f'[{module}] loaded', hidden=True) |
||
274 | print(f'({dt_string})\t[{self.__class__.__name__}]: {module} loaded') |
||
275 | except commands.ExtensionAlreadyLoaded: |
||
276 | self.bot.unload_extension(module) |
||
277 | self.bot.load_extension(module) |
||
278 | await ctx.send(f'[{module}] reloaded', hidden=True) |
||
279 | print(f'({dt_string})\t[{self.__class__.__name__}]: {module} reloaded') |
||
280 | except commands.ExtensionNotFound: |
||
281 | await ctx.send(f'[{module}] not found', hidden=True) |
||
282 | print(f'({dt_string})\t[{self.__class__.__name__}]: {module} not found') |
||
283 | |||
284 | # Command for reloading specific cog |
||
285 | @cog_ext.cog_slash(name="reloadCog", guild_ids=cogbase.GUILD_IDS, |
||
286 | description="Reload cog", |
||
287 | permissions=cogbase.PERMISSION_ADMINS) |
||
288 | async def reload_cog_command(self, ctx: SlashContext, module: str): |
||
289 | await self.reload_cog(ctx, module) |
||
290 | |||
291 | # Command for reloading all cogs |
||
292 | @cog_ext.cog_slash(name="reloadAllCogs", guild_ids=cogbase.GUILD_IDS, |
||
293 | description="Reload cog", |
||
294 | permissions=cogbase.PERMISSION_ADMINS) |
||
295 | async def reload_all_cogs(self, ctx: SlashContext = None): |
||
296 | for cog in list(self.bot.extensions.keys()): |
||
297 | await self.reload_cog(ctx, cog) |
||
298 | await ctx.send(f'All cogs reloaded', hidden=True) |
||
299 | |||
300 | # Get own spotting stats |
||
301 | @cog_ext.cog_slash(name="myStats", guild_ids=cogbase.GUILD_IDS, |
||
302 | description="Get your spot stats", |
||
303 | default_permission=True) |
||
304 | async def get_stats(self, ctx): |
||
305 | spot_roles = self.bot.config["total_milestones"][0] |
||
306 | guild = self.bot.get_guild(self.bot.guild[0]) |
||
307 | spots_df = await DatabaseCog.db_get_member_stats(ctx.author.id) |
||
308 | spots_df["total"] = spots_df["legendary"] * legend_multiplier + spots_df["rare"] |
||
309 | |||
310 | role_new = "" |
||
311 | spots_for_new = -1 |
||
312 | roles_list = [key for (key, value) in spot_roles.items() if spots_df.at[0, "total"] < value] |
||
313 | values_list = [value for (key, value) in spot_roles.items() if spots_df.at[0, "total"] < value] |
||
314 | if roles_list: |
||
315 | role_new = get(guild.roles, name=roles_list[0]) |
||
316 | spots_for_new = values_list[0] |
||
317 | |||
318 | message = f"**Legends**: {spots_df.at[0, 'legendary']}\n" \ |
||
319 | f"**Rares**: {spots_df.at[0, 'rare']}\n" \ |
||
320 | f"**Commons**: {spots_df.at[0, 'common']}\n\n" \ |
||
321 | f"**Total points**: {spots_df.at[0, 'total']}\n" \ |
||
322 | f"**Progress**: {spots_df.at[0, 'total']}/{spots_for_new}\n" \ |
||
323 | f"**Next role**: _{role_new}_" |
||
324 | |||
325 | await ctx.send(f"{ctx.author.mention} stats:\n{message}", hidden=True) |
||
326 | |||
327 | @cog_ext.cog_slash(name="saveCoordinates", guild_ids=cogbase.GUILD_IDS, |
||
328 | description="Get your spot stats", |
||
329 | permissions=cogbase.PERMISSION_ADMINS) |
||
330 | async def save_coordinates(self, ctx: SlashContext): |
||
331 | coords_df = await DatabaseCog.db_get_coords() |
||
332 | coords_df[['latitude', 'longitude']] = coords_df['coords'].str.split(',', expand=True) |
||
333 | coords_df.to_excel(r'server_files/coords.xlsx', index=False) |
||
334 | await ctx.send(f"Coords saved", hidden=True) |
||
335 | dt_string = self.bot.get_current_time() |
||
336 | print(f'({dt_string})\t[{self.__class__.__name__}]: Coords saved to server_files/coords.xlsx') |
||
337 | |||
338 | # Get member info |
||
339 | @cog_ext.cog_slash(name="memberinfo", guild_ids=cogbase.GUILD_IDS, |
||
340 | description="Get member info", |
||
341 | permissions=cogbase.PERMISSION_ADMINS) |
||
342 | async def memberinfo(self, ctx: SlashContext, *, user: discord.Member = None): |
||
343 | if user is None: |
||
344 | user = ctx.author |
||
345 | date_format = "%a, %d %b %Y %I:%M %p" |
||
346 | embed = discord.Embed(color=0xFF0000, description=user.mention) |
||
347 | embed.set_author(name=str(user), icon_url=user.avatar_url) |
||
348 | embed.set_thumbnail(url=user.avatar_url) |
||
349 | embed.add_field(name="Joined Server", value=user.joined_at.strftime(date_format), inline=False) |
||
350 | members = sorted(ctx.guild.members, key=lambda m: m.joined_at) |
||
351 | embed.add_field(name="Join Position", value=str(members.index(user) + 1), inline=False) |
||
352 | embed.add_field(name="Joined Discord", value=user.created_at.strftime(date_format), inline=False) |
||
353 | if len(user.roles) > 1: |
||
354 | role_string = ' '.join([r.mention for r in user.roles][1:]) |
||
355 | embed.add_field(name="Roles [{}]".format(len(user.roles) - 1), value=role_string, inline=False) |
||
356 | embed.set_footer(text='ID: ' + str(user.id)) |
||
357 | return await ctx.send(embed=embed) |
||
358 | |||
359 | # Backup database to a file |
||
360 | @cog_ext.cog_slash(name="backupDatabase", guild_ids=cogbase.GUILD_IDS, |
||
361 | description="Backup database to a file", |
||
362 | permissions=cogbase.PERMISSION_MODS) |
||
363 | async def backup_database(self, ctx: SlashContext): |
||
364 | now = datetime.now() |
||
365 | cmd = f"mysqldump -u {get_settings('DB_U')} " \ |
||
366 | f"--result-file=database_backup/backup-{now.strftime('%m-%d-%Y')}.sql " \ |
||
367 | f"-p{get_settings('DB_P')} server_database" |
||
368 | os.system(cmd) |
||
369 | await ctx.send(f"Database backed up", hidden=True) |
||
370 | |||
371 | # Slow mode |
||
372 | @cog_ext.cog_slash(name="slowmode", guild_ids=cogbase.GUILD_IDS, |
||
373 | description="Enable slowmode on current channel", |
||
374 | permissions=cogbase.PERMISSION_MODS) |
||
375 | async def slowmode(self, ctx, seconds: int = 0): |
||
376 | if seconds > 120: |
||
377 | return await ctx.send(":no_entry: Amount can't be over 120 seconds") |
||
378 | if seconds is 0: |
||
379 | await ctx.channel.edit(slowmode_delay=seconds) |
||
380 | a = await ctx.send("Slowmode is off for this channel") |
||
381 | await a.add_reaction("a:redcard:871861842639716472") |
||
382 | else: |
||
383 | if seconds is 1: |
||
384 | numofsecs = "second" |
||
385 | else: |
||
386 | numofsecs = "seconds" |
||
387 | await ctx.channel.edit(slowmode_delay=seconds) |
||
388 | confirm = await ctx.send( |
||
389 | f"{ctx.author.display_name} set the channel slow mode delay to `{seconds}` {numofsecs}\n" |
||
390 | f"To turn this off use /slowmode") |
||
391 | await confirm.add_reaction("a:ResidentWitcher:871872130021736519") |
||
392 | |||
393 | # Pool |
||
394 | @cog_ext.cog_slash(name="poll", guild_ids=cogbase.GUILD_IDS, |
||
395 | description="Create pool", |
||
396 | permissions=cogbase.PERMISSION_MODS) |
||
397 | async def poll(self, ctx, *, poll_info): |
||
398 | emb = (discord.Embed(description=poll_info, colour=0x36393e)) |
||
399 | emb.set_author(name=f"Poll by {ctx.author.display_name}") |
||
400 | try: |
||
401 | poll_message = await ctx.send(embed=emb) |
||
402 | await poll_message.add_reaction("\N{THUMBS UP SIGN}") |
||
403 | await poll_message.add_reaction("\N{THUMBS DOWN SIGN}") |
||
404 | except Exception as e: |
||
405 | await ctx.send(f"Oops, I couldn't react to the poll. Check that I have permission to add reactions! " |
||
406 | f"```py\n{e}```") |
||
407 | |||
408 | # Translate |
||
409 | @cog_ext.cog_slash(name="translate", guild_ids=cogbase.GUILD_IDS, |
||
410 | description="Translate message", |
||
411 | permissions=cogbase.PERMISSION_MODS) |
||
412 | async def translate(self, ctx: SlashContext, message: str): |
||
413 | # Translates the language and converts it to English |
||
414 | translator = Translator() |
||
415 | translated_message = translator.translate(message) |
||
416 | await ctx.send(f"`{message}` -> `{translated_message.text}`", hidden=True) |
||
417 | |||
418 | |||
419 | def setup(bot: commands.Bot): |
||
420 | bot.add_cog(CommandsCog(bot)) |
||
421 |