Passed
Push — main ( 47b94a...ca5c80 )
by Bartosz
02:27 queued 57s
created

build.cogs.commandscog   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 41
eloc 236
dl 0
loc 320
rs 9.1199
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A CommandsCog.__init__() 0 2 1
A CommandsCog.reload_all_cogs() 0 7 2
A CommandsCog.reload_cog() 0 15 3
A CommandsCog.update_member_count_command() 0 7 1
A CommandsCog.create_roles() 0 9 4
A CommandsCog.save_coordinates() 0 10 1
A CommandsCog.rename_nword_channel() 0 11 2
A CommandsCog._ping() 0 6 1
A CommandsCog._purge() 0 8 1
A CommandsCog._warn() 0 9 1
A CommandsCog.reload_cog_command() 0 5 1
A CommandsCog._mute() 0 20 3
A CommandsCog.memberinfo() 0 19 4
A CommandsCog.pull_config() 0 13 2
A CommandsCog.update_commons_ch_command() 0 17 5
A CommandsCog._warns() 0 11 1
A CommandsCog.remove_warns() 0 7 1
A CommandsCog.backup_database() 0 10 1
A CommandsCog._exit() 0 10 1
A CommandsCog.clear_temp_spots_table() 0 7 1
A CommandsCog.get_stats() 0 25 2
A CommandsCog.update_commons_ch() 0 10 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A setup() 0 2 1

How to fix   Complexity   

Complexity

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
19
import discord
20
from discord.utils import get
21
from modules.get_settings import get_settings
22
23
import cogs.cogbase as cogbase
24
from discord.ext import commands
25
from discord_slash import cog_ext, SlashContext
26
from cogs.databasecog import DatabaseCog
27
from cogs.leaderboardcog import legend_multiplier
28
from modules.pull_config.pull_config import get_config
29
30
31
class CommandsCog(cogbase.BaseCog):
32
    def __init__(self, base):
33
        super().__init__(base)
34
35
    # GENERAL FUNCTIONS
36
    # Check latency
37
    @cog_ext.cog_slash(name="ping", guild_ids=cogbase.GUILD_IDS,
38
                       description="Test function for checking latency",
39
                       default_permission=False,
40
                       permissions=cogbase.PERMISSION_MODS)
41
    async def _ping(self, ctx: SlashContext):
42
        await ctx.send(f"Pong! {round(self.bot.latency * 1000)}ms", delete_after=4.0)
43
44
    # Clear messages
45
    @cog_ext.cog_slash(name="clear", guild_ids=cogbase.GUILD_IDS,
46
                       description="Function for clearing messages on channel",
47
                       default_permission=False,
48
                       permissions=cogbase.PERMISSION_MODS)
49
    async def _purge(self, ctx: SlashContext, number):
50
        num_messages = int(number)
51
        await ctx.channel.purge(limit=num_messages)
52
        await ctx.send(f"Cleared {num_messages} messages!", delete_after=4.0)
53
54
    # Disconnect Bot
55
    @cog_ext.cog_slash(name="exit", guild_ids=cogbase.GUILD_IDS,
56
                       description="Turn off the bot",
57
                       default_permission=False,
58
                       permissions=cogbase.PERMISSION_ADMINS)
59
    async def _exit(self, ctx: SlashContext):
60
        await ctx.send(f"Closing Bot", delete_after=1.0)
61
        dt_string = self.bot.get_current_time()
62
        print(f"({dt_string})\t[{self.__class__.__name__}]: Exiting Bot")
63
        await asyncio.sleep(3)
64
        await self.bot.close()
65
66
    # WARN FUNCTIONS
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
    # CHANNEL NAMES UPDATES
123
    # Total member channel name
124
    @cog_ext.cog_slash(name="updateTotalMembers", guild_ids=cogbase.GUILD_IDS,
125
                       description="Update total number of members",
126
                       default_permission=False,
127
                       permissions=cogbase.PERMISSION_MODS)
128
    async def update_member_count_command(self, ctx: SlashContext):
129
        await self.bot.update_member_count(ctx)
130
        await ctx.send(f"Total Members count updated", hidden=True)
131
132
    # Commons channel name
133
    @cog_ext.cog_slash(name="updateCommons", guild_ids=cogbase.GUILD_IDS,
134
                       description="Update common channel name",
135
                       default_permission=False,
136
                       permissions=cogbase.PERMISSION_MODS)
137
    async def update_commons_ch_command(self, ctx: SlashContext):
138
        with open('./server_files/commons.txt') as f:
139
            try:
140
                commons = f.read().splitlines()
141
            except ValueError:
142
                print(ValueError)
143
144
        await self.update_commons_ch(ctx, commons)
145
146
        commons.append(commons.pop(commons.index(commons[0])))
147
        with open('./server_files/commons.txt', 'w') as f:
148
            for item in commons:
149
                f.write("%s\n" % item)
150
151
    async def update_commons_ch(self, ctx: SlashContext, commons):
152
        new_name = f"common {commons[0]}"
153
        common_ch = self.bot.get_channel(self.bot.ch_common)
154
        await discord.TextChannel.edit(common_ch, name=new_name)
155
        dt_string = self.bot.get_current_time()
156
        print(f"({dt_string})\t[{self.__class__.__name__}]: Common channel name updated: {commons[0]}")
157
158
        admin_posting = self.bot.get_channel(self.bot.ch_admin_posting)
159
        await admin_posting.send(f"Common changed: {commons[0]}")
160
        await ctx.send(f"Common changed: {commons[0]}", hidden=True)
161
162
    # N-Word spotted channel name
163
    # Doesn't work if used too many times in a short period of time
164
    @cog_ext.cog_slash(name="nword", guild_ids=cogbase.GUILD_IDS,
165
                       description="Change N-Word channel name",
166
                       permissions=cogbase.PERMISSION_ADMINS)
167
    async def rename_nword_channel(self, ctx, status: str):
168
        new_status = status
169
        channel = self.bot.get_channel(self.bot.ch_nightmare_killed)
170
        if new_status in channel.name:
171
            await ctx.send(f"{channel.name} has been changed", hidden=True)
172
        else:
173
            await discord.VoiceChannel.edit(channel, name=f"N-Word spotted: {new_status}")
174
            await ctx.send(f"{channel.name} channel name has been changed", hidden=True)
175
176
    # OTHER
177
178
    # Pull config.json from Google Sheets
179
    @cog_ext.cog_slash(name="pullConfig", guild_ids=cogbase.GUILD_IDS,
180
                       description="Pull config from google sheets",
181
                       default_permission=False,
182
                       permissions=cogbase.PERMISSION_ADMINS)
183
    async def pull_config(self, ctx: SlashContext):
184
        get_config()
185
        with open('server_files/config.json', 'r', encoding='utf-8-sig') as fp:
186
            self.bot.config = json.load(fp)
187
            await self.create_roles(ctx, True)
188
            await self.create_roles(ctx, False)
189
            dt_string = self.bot.get_current_time()
190
            print(f"({dt_string})\t[{self.__class__.__name__}]: Finished data pull")
191
        await ctx.send(f"Config.json updated", hidden=True)
192
193
    # Create roles if pull_config gets non existent roles
194
    async def create_roles(self, ctx: SlashContext, common: bool):
195
        milestones = "common_milestones" if common else "total_milestones"
196
        for mon_type in self.bot.config[milestones][0]:
197
            if get(ctx.guild.roles, name=mon_type):
198
                continue
199
            else:
200
                await ctx.guild.create_role(name=mon_type)
201
                dt_string = self.bot.get_current_time()
202
                print(f"({dt_string})\t[{self.__class__.__name__}]: {mon_type} role created")
203
204
    # Clear temp spots table in database
205
    @cog_ext.cog_slash(name="clearTempSpots", guild_ids=cogbase.GUILD_IDS,
206
                       description="Clear temp spots table in database",
207
                       permissions=cogbase.PERMISSION_ADMINS)
208
    async def clear_temp_spots_table(self, ctx):
209
        await DatabaseCog.db_clear_spots_temp_table()
210
        await ctx.send(f"Temp spots table was cleared", hidden=True)
211
        await self.reload_cog(ctx, "cogs.databasecog")
212
213
    # Reloads cog, very useful because there is no need to exit the bot after updating cog
214
    async def reload_cog(self, ctx: SlashContext, module: str):
215
        """Reloads a module."""
216
        dt_string = self.bot.get_current_time()
217
        try:
218
            self.bot.load_extension(f"{module}")
219
            await ctx.send(f'[{module}] loaded', hidden=True)
220
            print(f'({dt_string})\t[{self.__class__.__name__}]: {module} loaded')
221
        except commands.ExtensionAlreadyLoaded:
222
            self.bot.unload_extension(module)
223
            self.bot.load_extension(module)
224
            await ctx.send(f'[{module}] reloaded', hidden=True)
225
            print(f'({dt_string})\t[{self.__class__.__name__}]: {module} reloaded')
226
        except commands.ExtensionNotFound:
227
            await ctx.send(f'[{module}] not found', hidden=True)
228
            print(f'({dt_string})\t[{self.__class__.__name__}]: {module} not found')
229
230
    # Command for reloading specific cog
231
    @cog_ext.cog_slash(name="reloadCog", guild_ids=cogbase.GUILD_IDS,
232
                       description="Reload cog",
233
                       permissions=cogbase.PERMISSION_ADMINS)
234
    async def reload_cog_command(self, ctx: SlashContext, module: str):
235
        await self.reload_cog(ctx, module)
236
237
    # Command for reloading all cogs
238
    @cog_ext.cog_slash(name="reloadAllCogs", guild_ids=cogbase.GUILD_IDS,
239
                       description="Reload cog",
240
                       permissions=cogbase.PERMISSION_ADMINS)
241
    async def reload_all_cogs(self, ctx: SlashContext = None):
242
        for cog in list(self.bot.extensions.keys()):
243
            await self.reload_cog(ctx, cog)
244
        await ctx.send(f'All cogs reloaded', hidden=True)
245
246
    # Get own spotting stats
247
    @cog_ext.cog_slash(name="myStats", guild_ids=cogbase.GUILD_IDS,
248
                       description="Get your spot stats",
249
                       default_permission=True)
250
    async def get_stats(self, ctx):
251
        spot_roles = self.bot.config["total_milestones"][0]
252
        guild = self.bot.get_guild(self.bot.guild[0])
253
        spots_df = await DatabaseCog.db_get_member_stats(ctx.author.id)
254
        spots_df["total"] = spots_df["legendary"] * legend_multiplier + spots_df["rare"]
255
256
        role_new = ""
257
        spots_for_new = -1
258
        roles_list = [key for (key, value) in spot_roles.items() if spots_df.at[0, "total"] < value]
259
        values_list = [value for (key, value) in spot_roles.items() if spots_df.at[0, "total"] < value]
260
        if roles_list:
261
            role_new = get(guild.roles, name=roles_list[0])
262
            spots_for_new = values_list[0]
263
264
        message = f"**Legends**: {spots_df.at[0, 'legendary']}\n" \
265
                  f"**Rares**: {spots_df.at[0, 'rare']}\n" \
266
                  f"**Commons**: {spots_df.at[0, 'common']}\n\n" \
267
                  f"**Total points**: {spots_df.at[0, 'total']}\n" \
268
                  f"**Progress**: {spots_df.at[0, 'total']}/{spots_for_new}\n" \
269
                  f"**Next role**: _{role_new}_"
270
271
        await ctx.send(f"{ctx.author.mention} stats:\n{message}", hidden=True)
272
273
    @cog_ext.cog_slash(name="saveCoordinates", guild_ids=cogbase.GUILD_IDS,
274
                       description="Get your spot stats",
275
                       permissions=cogbase.PERMISSION_ADMINS)
276
    async def save_coordinates(self, ctx: SlashContext):
277
        coords_df = await DatabaseCog.db_get_coords()
278
        coords_df[['latitude', 'longitude']] = coords_df['coords'].str.split(',', expand=True)
279
        coords_df.to_excel(r'server_files/coords.xlsx', index=False)
280
        await ctx.send(f"Coords saved", hidden=True)
281
        dt_string = self.bot.get_current_time()
282
        print(f'({dt_string})\t[{self.__class__.__name__}]: Coords saved to server_files/coords.xlsx')
283
284
    # Get member info
285
    @cog_ext.cog_slash(name="memberinfo", guild_ids=cogbase.GUILD_IDS,
286
                       description="Get member info",
287
                       permissions=cogbase.PERMISSION_ADMINS)
288
    async def memberinfo(self, ctx: SlashContext, *, user: discord.Member = None):
289
        if user is None:
290
            user = ctx.author
291
        date_format = "%a, %d %b %Y %I:%M %p"
292
        embed = discord.Embed(color=0xFF0000, description=user.mention)
293
        embed.set_author(name=str(user), icon_url=user.avatar_url)
294
        embed.set_thumbnail(url=user.avatar_url)
295
        embed.add_field(name="Joined Server", value=user.joined_at.strftime(date_format), inline=False)
296
        members = sorted(ctx.guild.members, key=lambda m: m.joined_at)
297
        embed.add_field(name="Join Position", value=str(members.index(user) + 1), inline=False)
298
        embed.add_field(name="Joined Discord", value=user.created_at.strftime(date_format), inline=False)
299
        if len(user.roles) > 1:
300
            role_string = ' '.join([r.mention for r in user.roles][1:])
301
            embed.add_field(name="Roles [{}]".format(len(user.roles) - 1), value=role_string, inline=False)
302
        embed.set_footer(text='ID: ' + str(user.id))
303
        return await ctx.send(embed=embed)
304
305
    # Backup database to a file
306
    @cog_ext.cog_slash(name="backupDatabase", guild_ids=cogbase.GUILD_IDS,
307
                       description="Backup database to a file",
308
                       permissions=cogbase.PERMISSION_MODS)
309
    async def backup_database(self, ctx: SlashContext):
310
        now = datetime.now()
311
        cmd = f"mysqldump -u {get_settings('DB_U')} " \
312
              f"--result-file=database_backup/backup-{now.strftime('%m-%d-%Y')}.sql " \
313
              f"-p{get_settings('DB_P')} server_database"
314
        os.system(cmd)
315
        await ctx.send(f"Database backed up", hidden=True)
316
317
318
def setup(bot: commands.Bot):
319
    bot.add_cog(CommandsCog(bot))
320