Passed
Branch BonHowi (9be8a5)
by Bartosz
01:18
created

CommandsCog.save_coordinates()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 10
rs 9.9
c 0
b 0
f 0
cc 1
nop 2
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