Passed
Branch unstable (79b831)
by Sydney
01:37
created

botutils   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 51
eloc 203
dl 0
loc 271
rs 7.92
c 0
b 0
f 0

15 Functions

Rating   Name   Duplication   Size   Complexity  
A onInit() 0 17 1
A command_uptime() 0 4 1
A convert_size() 0 8 2
B commands_detect_dups() 0 16 7
A command_getprefix() 0 2 1
B command_addowner() 0 21 6
A command_owners() 0 12 4
B command_cpuinfo() 0 28 6
B command_plugintree() 0 21 6
A command_commands() 0 12 4
A command_speedtest() 0 11 2
A command_info() 0 15 4
A command_setprefix() 0 7 2
A command_plugins() 0 5 2
A command_hostinfo() 0 46 3

How to fix   Complexity   

Complexity

Complex classes like botutils 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
#    Copyright 2017 Starbot Discord Project
2
# 
3
#    Licensed under the Apache License, Version 2.0 (the "License");
4
#    you may not use this file except in compliance with the License.
5
#    You may obtain a copy of the License at
6
# 
7
#        http://www.apache.org/licenses/LICENSE-2.0
8
# 
9
#    Unless required by applicable law or agreed to in writing, software
10
#    distributed under the License is distributed on an "AS IS" BASIS,
11
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
#    See the License for the specific language governing permissions and
13
#    limitations under the License.
14
15
import math
16
import os
17
import platform
18
import sys
19
import time
20
21
import discord
22
import psutil
23
import pyspeedtest
24
25
from api import settings, logging, git
26
from api.bot import Bot
27
from api.message import Message
28
from api.command import Command
29
from api.plugin import Plugin
30
from libs import progressBar, readableTime, displayname
31
32
def commands_detect_dups():
33
    duplicates = []
34
    commands_list = []
35
    for plugin_in in Bot.plugins:
36
        for command_in in plugin_in.commands:
37
            commands_list.append(command_in.name)
38
39
    for command_in in commands_list:
40
        commandOccurances = 0
41
        for command2 in commands_list:
42
            if command_in == command2:
43
                commandOccurances += 1
44
        if commandOccurances > 1:
45
            duplicates.append(command_in)
46
47
    return list(set(duplicates))
48
49
def convert_size(size_bytes: int) -> str:
50
    if size_bytes == 0:
51
        return '0B'
52
    size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
53
    i = int(math.floor(math.log(size_bytes, 1024)))
54
    p = math.pow(1024, i)
55
    s = round(size_bytes/p, 2)
56
    return '%s %s' % (s, size_name[i])
57
58
def onInit(plugin_in: Plugin) -> Plugin:
59
    plugins_command    = Command(plugin_in, 'plugins',    command_plugins,    shortdesc='Print a list of plugins',                devcommand=True)
60
    commands_command   = Command(plugin_in, 'commands',   command_commands,   shortdesc='Print a list of commands',               devcommand=True)
61
    help_command       = Command(plugin_in, 'help',       command_commands,   shortdesc='Print a list of commands')
62
    info_command       = Command(plugin_in, 'info',       command_info,       shortdesc='Print some basic bot info')
63
    plugintree_command = Command(plugin_in, 'plugintree', command_plugintree, shortdesc='Print a tree of plugins and commands',   devcommand=True)
64
    uptime_command     = Command(plugin_in, 'uptime',     command_uptime,     shortdesc='Print the bot\'s uptime',                devcommand=True)
65
    hostinfo_command   = Command(plugin_in, 'hostinfo',   command_hostinfo,   shortdesc='Prints information about the bots home', devcommand=True)
66
    cpuinfo_command    = Command(plugin_in, 'cpuinfo',    command_cpuinfo,    shortdesc='Prints info about the system CPUs',      devcommand=True)
67
    setprefix_command  = Command(plugin_in, 'setprefix',  command_setprefix,  shortdesc='Set the server prefix',                  devcommand=True)
68
    getprefix_command  = Command(plugin_in, 'getprefix',  command_getprefix,  shortdesc='Get the server prefix',                  devcommand=True)
69
    speedtest_command  = Command(plugin_in, 'speedtest',  command_speedtest,  shortdesc='Run a speedtest',                        devcommand=True)
70
    addowner_command   = Command(plugin_in, 'addowner',   command_addowner,   shortdesc='Add a bot owner',                        devcommand=True)
71
    owners_command     = Command(plugin_in, 'owners',     command_owners,     shortdesc='Print the bot owners',                   devcommand=True)
72
    return Plugin(plugin_in, 'botutils', [plugins_command, commands_command, help_command, info_command, plugintree_command, uptime_command,
73
                                                 hostinfo_command, cpuinfo_command, setprefix_command, getprefix_command, speedtest_command, addowner_command,
74
                                                 owners_command])
75
76
async def command_plugins(message_in: Message) -> Message:
77
    plugin_list = []
78
    for plugin_in in Bot.plugins:
79
        plugin_list.append(plugin_in.name)
80
    return Message(body='```{}```'.format(', '.join(plugin_list)))
81
82
async def command_commands(message_in: Message) -> Message:
83
    cmd_names = []
84
    cmd_descs = []
85
    for botcommand in Bot.commands:
86
        if botcommand.devcommand != True:
87
            cmd_names.append(botcommand.name)
88
            cmd_descs.append(botcommand.shortdesc)
89
    cmd_list = []
90
    pad_len = len(max(cmd_names, key=len))
91
    for index, value in enumerate(cmd_names):
92
        cmd_list.append('{} - {}'.format(cmd_names[index].ljust(pad_len), cmd_descs[index]))
93
    return Message(body='```{}```'.format('\n'.join(cmd_list)))
94
95
async def command_info(message_in: Message) -> Message:
96
    sha = git.git_commit()
97
    track = git.git_branch()
98
    remote = git.get_remote()
99
    if track == 'master':
100
        embed = discord.Embed(color=discord.Color.red())
101
    elif track == 'unstable':
102
        embed = discord.Embed(color=discord.Color.gold())
103
    elif track == 'stable':
104
        embed = discord.Embed(color=discord.Color.green())
105
    else:
106
        embed = discord.Embed(color=discord.Color.light_grey())
107
    embed.set_author(name='Project StarBot v0.2.0-{} on track {}'.format(sha[:7], track))
108
    embed.set_footer(text="Pulled from {}".format(remote))
109
    return Message(embed=embed)
110
111
async def command_plugintree(message_in: Message) -> Message:
112
    dups = commands_detect_dups()
113
    plugin_string = '```\n'
114
    for plugin_in in Bot.plugins:
115
        plugin_string += '{}\n'.format(plugin_in.name)
116
        plugin_commands = len(plugin_in.commands)
117
        index = 0
118
        for command_in in plugin_in.commands:
119
            index += 1
120
            if plugin_commands != index:
121
                if command_in.name in dups:
122
                    plugin_string += '├ {} <-- duplicate\n'.format(command_in.name)
123
                else:
124
                    plugin_string += '├ {}\n'.format(command_in.name)
125
            else:
126
                if command_in.name in dups:
127
                    plugin_string += '└ {} <-- duplicate\n'.format(command_in.name)
128
                else:
129
                    plugin_string += '└ {}\n'.format(command_in.name)
130
    plugin_string += '```'
131
    return Message(body=plugin_string)
132
133
async def command_uptime(message_in: Message) -> Message:
134
    time_current = int(time.time())
135
    time_str = readableTime.getReadableTimeBetween(Bot.startTime, time_current)
136
    return Message(body='I\'ve been up for *{}*.'.format(time_str))
137
138
async def command_hostinfo(message_in: Message) -> Message:
139
    # Get information about host environment.
140
    time_current = int(time.time())
141
142
    # CPU stats.
143
    cpu_threads = os.cpu_count()
144
    cpu_usage = psutil.cpu_percent(interval=1)
145
146
    # Memory stats.
147
    mem_stats = psutil.virtual_memory()
148
    mem_percent = mem_stats.percent
149
    mem_used = convert_size(mem_stats.used)
150
    mem_total = convert_size(mem_stats.total)
151
152
    # Platform info.
153
    platform_current = platform.platform()
154
155
    # Python version info.
156
    pyver_major = sys.version_info.major
157
    pyver_minor = sys.version_info.minor
158
    pyver_micro = sys.version_info.micro
159
    pyver_release = sys.version_info.releaselevel
160
161
    # Storage info.
162
    stor = psutil.disk_usage('/')
163
    stor_used = convert_size(stor.used)
164
    stor_total = convert_size(stor.total)
165
    stor_free = convert_size(stor.total - stor.used)
166
167
    # Format hostinfo with OS, CPU, RAM, storage, and other bot info.
168
    msg = '***{}\'s*** **Home:**\n'.format(displayname.name(message_in.guild.me))
169
    msg += '```Host OS       : {}\n'.format(platform_current)
170
    msg += 'Host Python   : {}.{}.{} {}\n'.format(pyver_major, pyver_minor, pyver_micro, pyver_release)
171
    if not isinstance(cpu_threads, int):
172
        msg += 'Host CPU usage: {}% of {}\n'.format(cpu_usage, platform.machine())
173
    elif cpu_threads > 1:
174
        msg += 'Host CPU usage: {}% of {} ({} threads)\n'.format(cpu_usage, platform.machine(), cpu_threads)
175
    else:
176
        msg += 'Host CPU usage: {}% of {} ({} thread)\n'.format(cpu_usage, platform.machine(), cpu_threads)
177
    msg += 'Host RAM      : {} ({}%) of {}\n'.format(mem_used, mem_percent, mem_total)
178
    msg += 'Host storage  : {} ({}%) of {} - {} free\n'.format(stor_used, stor.percent, stor_total, stor_free)
179
    msg += 'Hostname      : {}\n'.format(platform.node())
180
    msg += 'Host uptime   : {}```'.format(readableTime.getReadableTimeBetween(psutil.boot_time(), time.time()))
181
182
    # Return completed message.
183
    return Message(body=msg)
184
185
async def command_cpuinfo(message_in: Message) -> Message:
186
    # Get CPU usage and create string for message.
187
    cpu_pcts = psutil.cpu_percent(interval=0.1, percpu=True)
188
    cpu_pct_str = '{}\n'.format(platform.processor())
189
    cpu_threads = psutil.cpu_count()
190
    cpu_cores = psutil.cpu_count(logical=False)
191
    cpu_arch = platform.machine()
192
    # First, check to see if we can accurately determine the number of physical cores. If not, omit the core count.
193
    if not cpu_cores:
194
        if cpu_threads > 1:
195
            cpu_pct_str += '{} threads of {}'.format(cpu_threads, cpu_arch)
196
        else:
197
            cpu_pct_str += '{} thread of {}'.format(cpu_threads, cpu_arch)
198
    elif cpu_cores > 1: # Multiple cores.
199
        cpu_pct_str += '{} threads - {} cores of {}'.format(cpu_threads, cpu_cores, cpu_arch)
200
    else:
201
        if psutil.cpu_count() > 1: # Multiple threads, single core.
202
            cpu_pct_str += '{} threads - {} core of {}'.format(cpu_threads, cpu_cores, cpu_arch)
203
        else: # Single thread, single core.
204
            cpu_pct_str += '{} thread - {} core of {}'.format(cpu_threads, cpu_cores, cpu_arch)
205
206
    # Build CPU usage graph.
207
    cpu_pct_str += '\n\n'
208
    for index, value in enumerate(cpu_pcts):
209
        cpu_pct_str += 'CPU {}: {}\n'.format(str(index), progressBar.makeBar(cpu_pcts[index]))
210
211
    # Return completed message.
212
    return Message(body='```{}```'.format(cpu_pct_str))
213
214
async def command_setprefix(message_in: Message) -> Message:
215
    if settings.owners_check(message_in.author.id):
216
        prefix = message_in.body.split(' ', 1)[-1]
217
        settings.prefix_set(message_in.guild.id, prefix)
218
        return Message(body='Prefix set to {}'.format(prefix))
219
    else:
220
        return Message(body='Only my owner can set the prefix!')
221
222
async def command_getprefix(message_in: Message) -> Message:
223
    return Message(body='Prefix is {}'.format(settings.prefix_get(message_in.guild.id)))
224
225
async def command_speedtest(message_in: Message) -> Message:
226
    if settings.owners_check(message_in.author.id):
227
        speed = pyspeedtest.SpeedTest()
228
        msg = '**Speed Test Results:**\n'
229
        msg += '```\n'
230
        msg += '    Ping: {}\n'.format(round(speed.ping(), 2))
231
        msg += 'Download: {}MB/s\n'.format(round(speed.download()/1024/1024, 2))
232
        msg += '  Upload: {}MB/s```'.format(round(speed.upload()/1024/1024, 2))
233
        return Message(body=msg)
234
    else:
235
        return Message(body='You do not have permisison to run a speedtest.')
236
237
async def command_addowner(message_in: Message) -> Message:
238
    if settings.owners_get():
239
        try:
240
            if settings.owners_check(message_in.author.id):
241
                member = message_in.body.strip()
242
                new_member = displayname.memberForName(member, message_in.guild.members, message_in.guild.me)
243
244
                if settings.owners_check(new_member.id):
245
                    return Message(body="User is already an owner.")
246
                elif new_member.bot:
247
                    return Message(body="Bots cannot be owners.")
248
                else:
249
                    settings.owners_add(new_member.id)
250
                    return Message(body="Added owner successfully.")
251
            else:
252
                return Message(body="You aren't an owner of the bot.")
253
        except AttributeError:
254
            return Message(body="Invalid user.")
255
    else:
256
        settings.owners_add(message_in.author.id)
257
        return Message(body="You have successfully claimed yourself as the first owner!")
258
259
async def command_owners(message_in: Message) -> Message:
260
    owners = []
261
    if not settings.owners_get():
262
        return Message(body='I have no owners')
263
    for owner in settings.owners_get():
264
        user = displayname.memberForID(owner, message_in.guild.members, message_in.guild.me)
265
        if user:
266
            owners.append(str(user.name))
267
        else:
268
            owners.append(str(owner))
269
    owner_list = ', '.join(owners)
270
    return Message(body=owner_list)