build.cogs.utilscog   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 266
Duplicated Lines 12.41 %

Importance

Changes 0
Metric Value
wmc 33
eloc 222
dl 33
loc 266
rs 9.76
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A UtilsCog.reload_all_cogs() 0 9 2
A UtilsCog.reload_cog_command() 0 6 1
A UtilsCog.save_coordinates() 0 13 1
A UtilsCog.backup_database() 0 11 1
A UtilsCog.system_status() 0 33 1
A UtilsCog.change_monster_type() 0 16 4
A UtilsCog.reload_cog() 0 15 3
A UtilsCog.clear_temp_spots_table() 0 8 1
A UtilsCog.update_useful_guides() 0 6 1
A UtilsCog.__init__() 0 2 1
A UtilsCog.create_roles() 0 7 4
A UtilsCog.update_guides() 0 19 3
A UtilsCog.update_spoofing_guides() 18 18 1
A UtilsCog.update_game_guides() 15 15 1
A UtilsCog.pull_config_command() 0 15 3
A UtilsCog.member_info() 0 20 4

1 Function

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

How to fix   Duplicated Code   

Duplicated Code

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:

1
import json
2
import math
3
import os
4
import re
5
import time
6
from datetime import datetime
7
8
import discord
9
import psutil
10
from discord.ext import commands
11
from discord.utils import get
12
from discord_slash import cog_ext, SlashContext
13
from numpyencoder import NumpyEncoder
14
15
import cogs.cogbase as cogbase
16
from cogs.databasecog import DatabaseCog
17
from modules.get_settings import get_settings
18
from modules.pull_config.pull_config import get_config
19
20
21
class UtilsCog(cogbase.BaseCog):
22
    def __init__(self, base):
23
        super().__init__(base)
24
25
    # OTHER
26
    # Pull config.json from Google Sheets
27
    @cog_ext.cog_slash(name="pullConfig", guild_ids=cogbase.GUILD_IDS,
28
                       description="Pull config from google sheets",
29
                       default_permission=False,
30
                       permissions=cogbase.PERMISSION_ADMINS)
31
    async def pull_config_command(self, ctx: SlashContext) -> None:
32
        get_config()
33
        with open('server_files/config.json', 'r', encoding='utf-8-sig') as fp:
34
            self.bot.config = json.load(fp)
35
            await self.create_roles(ctx, True)
36
            await self.create_roles(ctx, False)
37
            self.create_log_msg('Finished data pull')
38
        try:
39
            await ctx.send('Config.json updated', hidden=True)
40
        except discord.errors.NotFound:
41
            self.create_log_msg('Error with printing data pull confirmation')
42
43
    # Create roles if pull_config gets non existent roles
44
    async def create_roles(self, ctx: SlashContext, common: bool) -> None:
45
        milestones = "common_milestones" if common else "total_milestones"
46
        for mon_type in self.bot.config[milestones][0]:
47
            if get(ctx.guild.roles, name=mon_type):
48
                continue
49
            await ctx.guild.create_role(name=mon_type)
50
            self.create_log_msg(f"{mon_type} role created")
51
52
    # Clear temp spots table in database
53
    @cog_ext.cog_slash(name="clearTempSpots", guild_ids=cogbase.GUILD_IDS,
54
                       description="Clear temp spots table in database",
55
                       default_permission=False,
56
                       permissions=cogbase.PERMISSION_ADMINS)
57
    async def clear_temp_spots_table(self, ctx: SlashContext) -> None:
58
        await DatabaseCog.db_clear_spots_temp_table()
59
        await ctx.send('Temp spots table was cleared', hidden=True)
60
        await self.reload_cog(ctx, "databasecog")
61
62
    # Reloads cog, very useful because there is no need to exit the bot after updating cog
63
    async def reload_cog(self, ctx: SlashContext, module: str):
64
        """Reloads a module."""
65
        module = f"cogs.{module}"
66
        try:
67
            self.bot.load_extension(f"{module}")
68
            await ctx.send(f'[{module}] loaded', delete_after=4.0)
69
            self.create_log_msg(f"{module} loaded")
70
        except commands.ExtensionAlreadyLoaded:
71
            self.bot.unload_extension(module)
72
            self.bot.load_extension(module)
73
            await ctx.send(f'[{module}] reloaded', delete_after=4.0)
74
            self.create_log_msg(f"{module} reloaded")
75
        except commands.ExtensionNotFound:
76
            await ctx.send(f'[{module}] not found', delete_after=2.0)
77
            self.create_log_msg(f"{module} not found")
78
79
    # Command for reloading specific cog
80
    @cog_ext.cog_slash(name="reloadCog", guild_ids=cogbase.GUILD_IDS,
81
                       description="Reload cog",
82
                       default_permission=False,
83
                       permissions=cogbase.PERMISSION_ADMINS)
84
    async def reload_cog_command(self, ctx: SlashContext, module: str) -> None:
85
        await self.reload_cog(ctx, module)
86
87
    # Command for reloading all cogs
88
    @cog_ext.cog_slash(name="reloadAllCogs", guild_ids=cogbase.GUILD_IDS,
89
                       description="Reload all bot cogs",
90
                       default_permission=False,
91
                       permissions=cogbase.PERMISSION_ADMINS)
92
    async def reload_all_cogs(self, ctx: SlashContext = None) -> None:
93
        for cog in list(self.bot.extensions.keys()):
94
            cog = cog.replace('cogs.', '')
95
            await self.reload_cog(ctx, cog)
96
        await ctx.send('All cogs reloaded', delete_after=2.0)
97
98
    @cog_ext.cog_slash(name="saveDatabaseCoordinates", guild_ids=cogbase.GUILD_IDS,
99
                       description="Save coordinates from database to a file",
100
                       default_permission=False,
101
                       permissions=cogbase.PERMISSION_ADMINS)
102
    async def save_coordinates(self, ctx: SlashContext) -> None:
103
        coords_df = await DatabaseCog.db_get_coords()
104
        coords_df = coords_df[coords_df.coords.str.contains(",")]
105
        print(coords_df)
106
        coords_df[['latitude', 'longitude']] = coords_df['coords'].str.split(',', 1, expand=True)
107
        path_coords = r"server_files/coords.xlsx"
108
        coords_df.to_excel(path_coords, index=False)
109
        await ctx.send("Coords saved", hidden=True)
110
        self.create_log_msg(f"Coords saved to {path_coords}")
111
112
    # Get member info
113
    @cog_ext.cog_slash(name="memberinfo", guild_ids=cogbase.GUILD_IDS,
114
                       description="Get member discord info",
115
                       default_permission=False,
116
                       permissions=cogbase.PERMISSION_ADMINS)
117
    async def member_info(self, ctx: SlashContext, user: discord.Member = None) -> None:
118
        if user is None:
119
            user = ctx.author
120
        date_format = "%a, %d %b %Y %I:%M %p"
121
        embed = discord.Embed(color=0xFF0000, description=user.mention)
122
        embed.set_author(name=str(user), icon_url=user.avatar_url)
123
        embed.set_thumbnail(url=user.avatar_url)
124
        embed.add_field(name="Joined Server", value=user.joined_at.strftime(date_format), inline=False)
125
        members = sorted(ctx.guild.members, key=lambda m: m.joined_at)
126
        embed.add_field(name="Join Position", value=str(members.index(user) + 1), inline=False)
127
        embed.add_field(name="Joined Discord", value=user.created_at.strftime(date_format), inline=False)
128
        if len(user.roles) > 1:
129
            role_string = ' '.join([r.mention for r in user.roles][1:])
130
            embed.add_field(name="Roles [{}]".format(len(user.roles) - 1), value=role_string, inline=False)
131
        embed.set_footer(text='ID: ' + str(user.id))
132
        await ctx.send(embed=embed, hidden=True)
133
134
    # Backup database to a file
135
    @cog_ext.cog_slash(name="backupDatabase", guild_ids=cogbase.GUILD_IDS,
136
                       description="Backup database to a file",
137
                       default_permission=False,
138
                       permissions=cogbase.PERMISSION_MODS)
139
    async def backup_database(self, ctx: SlashContext) -> None:
140
        now = datetime.now()
141
        cmd = f"mysqldump -u {get_settings('DB_U')} " \
142
              f"--result-file=database_backup/backup-{now.strftime('%m-%d-%Y')}.sql " \
143
              f"-p{get_settings('DB_P')} server_database"
144
        os.system(cmd)
145
        await ctx.send("Database backed up", hidden=True)
146
147
    # System stats
148
    @cog_ext.cog_slash(name="systemStatus", guild_ids=cogbase.GUILD_IDS,
149
                       description="Get status of the system",
150
                       default_permission=False,
151
                       permissions=cogbase.PERMISSION_MODS)
152
    async def system_status(self, ctx: SlashContext) -> None:
153
        """Get status of the system."""
154
        process_uptime = time.time() - self.bot.start_time
155
        process_uptime = time.strftime("%ed %Hh %Mm %Ss", time.gmtime(process_uptime))
156
        process_uptime = process_uptime.replace(re.search(r'\d+', process_uptime).group(),
157
                                                str(int(re.search(r'\d+', process_uptime).group()) - 1), 1)
158
        system_uptime = time.time() - psutil.boot_time()
159
        system_uptime = time.strftime("%ed %Hh %Mm %Ss", time.gmtime(system_uptime))
160
        system_uptime = system_uptime.replace(re.search(r'\d+', system_uptime).group(),
161
                                              str(int(re.search(r'\d+', system_uptime).group()) - 1), 1)
162
        mem = psutil.virtual_memory()
163
        pid = os.getpid()
164
        memory_use = psutil.Process(pid).memory_info()[0]
165
166
        data = [
167
            ("Version", self.bot.version),
168
            ("Process uptime", process_uptime),
169
            ("Process memory", f"{memory_use / math.pow(1024, 2):.2f}MB"),
170
            ("System uptime", system_uptime),
171
            ("CPU Usage", f"{psutil.cpu_percent()}%"),
172
            ("RAM Usage", f"{mem.percent}%"),
173
        ]
174
175
        content = discord.Embed(
176
            title=":computer: System status",
177
            colour=int("5dadec", 16),
178
            description="\n".join(f"**{x[0]}** {x[1]}" for x in data),
179
        )
180
        await ctx.send(embed=content, hidden=True)
181
182
    # Change monster type(for events)
183
    @cog_ext.cog_slash(name="changeMonsterType", guild_ids=cogbase.GUILD_IDS,
184
                       description="Change type of a monster",
185
                       default_permission=False,
186
                       permissions=cogbase.PERMISSION_ADMINS)
187
    async def change_monster_type(self, ctx: SlashContext, monster: str, new_type: int) -> None:
188
        config = self.bot.config
189
        for mon in config["commands"]:
190
            if mon["name"] == monster:
191
                mon["type"] = new_type
192
                self.create_log_msg(f"Changed type for {monster}")
193
                await ctx.send(f"{monster}'s type changed", hidden=True)
194
                break
195
196
        self.bot.config = config
197
        with open('server_files/config.json', 'w', encoding='utf8') as f:
198
            json.dump(config, f, indent=4, ensure_ascii=False, sort_keys=False, cls=NumpyEncoder)
199
200
    # Update guides channel
201
    @cog_ext.cog_slash(name="updateGuides", guild_ids=cogbase.GUILD_IDS,
202
                       description="Update #guides channel",
203
                       default_permission=False,
204
                       permissions=cogbase.PERMISSION_MODS)
205
    async def update_guides(self, ctx: SlashContext) -> None:
206
        await ctx.defer()
207
        guides_channel = self.bot.get_channel(self.bot.ch_guides)
208
        await guides_channel.purge(limit=10)
209
        await self.update_spoofing_guides(guides_channel)
210
        await self.update_game_guides(guides_channel)
211
        await self.update_useful_guides(guides_channel)
212
213
        self.create_log_msg("Guides updated")
214
        with open('./server_files/bot_guide.txt') as f:
215
            try:
216
                bot_guide = f.read()
217
            except ValueError:
218
                print(ValueError)
219
        await guides_channel.send(bot_guide)
220
221 View Code Duplication
    @staticmethod
222
    async def update_spoofing_guides(guides_channel):
223
        embed = discord.Embed(title="SPOOFING GUIDES", color=0x878a00)
224
        embed.add_field(name="__Recommended Fake GPS App for Android users__",
225
                        value="https://play.google.com/store/apps/details?id=com.theappninjas.fakegpsjoystick",
226
                        inline=False)
227
        embed.add_field(name="__Recommended Fake GPS App for iOS users__",
228
                        value="https://www.thinkskysoft.com/itools", inline=False)
229
        embed.add_field(name="Recommended android emulator for Windows and Mac",
230
                        value="https://www.bignox.com", inline=False)
231
        embed.add_field(name="YT Guide for Fake GPS Location recommended app (by @ChampattioNonNightMareMare)",
232
                        value="https://www.youtube.com/watch?v=wU7qOLEm7qQ", inline=False)
233
        embed.add_field(name="YT Guide for GPS spoofing with iTools (by @Loonasek)",
234
                        value="https://www.youtube.com/watch?v=1M8jq3JNAMM", inline=False)
235
        embed.add_field(name="Nox guide for creating macro and keyboard mapping; "
236
                             "it can help in automatically making potion, fight, blocks signs etc.",
237
                        value="https://support.bignox.com/en/keyboard/macro1", inline=False)
238
        await guides_channel.send(embed=embed)
239
240 View Code Duplication
    @staticmethod
241
    async def update_game_guides(guides_channel):
242
        embed = discord.Embed(title="GAME GUIDES", color=0x8a3c00)
243
        embed.add_field(name="__Game Wiki__",
244
                        value="https://witcher.fandom.com/wiki/The_Witcher_Monster_Slayer_bestiary", inline=False)
245
        embed.add_field(name="Great sheet for checking monster spawn conditions(credit to @TaraxGoat))",
246
                        value="https://docs.google.com/spreadsheets/d/"
247
                              "148qPGW9oYOaYAzpk_a06u2FBnw9rZzAmBZ6AxWFrryI/edit#gid=2093943306", inline=False)
248
        embed.add_field(name="Advanced Combat guide", value="https://www.youtube.com/watch?v=-D0wIzwxp0Y", inline=False)
249
        embed.add_field(name="Guide to the game quests",
250
                        value="https://docs.google.com/document/d/"
251
                              "1vK1HfJlglTluNdypzH3XbQDi0vgJ0SNi2lUHbk3lqcE/edit", inline=False)
252
        embed.add_field(name="Recommended Skill Tree (by @Sagar)",
253
                        value="https://pasteboard.co/LYjVo2u1aIDt.jpg", inline=False)
254
        await guides_channel.send(embed=embed)
255
256
    @staticmethod
257
    async def update_useful_guides(guides_channel):
258
        embed = discord.Embed(title="USEFUL TOOLS", color=0x019827)
259
        embed.add_field(name="Website for checking timezones/current time",
260
                        value="https://www.timeanddate.com/worldclock/?sort=2", inline=False)
261
        await guides_channel.send(embed=embed)
262
263
264
def setup(bot: commands.Bot) -> None:
265
    bot.add_cog(UtilsCog(bot))
266