Skip to content

Commit c2f4cb4

Browse files
committed
Seperate commands into cogs
1 parent 09bfc6b commit c2f4cb4

File tree

4 files changed

+375
-463
lines changed

4 files changed

+375
-463
lines changed

cogs/utility.py

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
import discord
2+
from discord.ext import commands
3+
import traceback
4+
import inspect
5+
import io
6+
import textwrap
7+
from contextlib import redirect_stdout
8+
from difflib import get_close_matches
9+
10+
from core.paginator import PaginatorSession
11+
from core.decorators import auth_required, owner_only, trigger_typing
12+
13+
class Utility:
14+
'''General commands that provide utility'''
15+
16+
def __init__(self, bot):
17+
self.bot = bot
18+
19+
def format_cog_help(self, ctx, cog):
20+
"""Formats the text for a cog help"""
21+
sigs = []
22+
prefix = self.bot.config.get('PREFIX', 'm.')
23+
24+
for cmd in self.bot.commands:
25+
if cmd.hidden:
26+
continue
27+
if cmd.instance is cog:
28+
sigs.append(len(cmd.qualified_name) + len(prefix))
29+
if hasattr(cmd, 'all_commands'):
30+
for c in cmd.all_commands.values():
31+
sigs.append(len('\u200b └─ ' + c.name) + 1)
32+
33+
if not sigs:
34+
return
35+
36+
maxlen = max(sigs)
37+
38+
fmt = ['']
39+
index = 0
40+
for cmd in self.bot.commands:
41+
if cmd.instance is cog:
42+
if cmd.hidden:
43+
continue
44+
if len(fmt[index] + f'`{prefix+cmd.qualified_name:<{maxlen}}` - ' + f'{cmd.short_doc:<{maxlen}}\n') > 1024:
45+
index += 1
46+
fmt.append('')
47+
fmt[index] += f'`{prefix+cmd.qualified_name:<{maxlen}}` - '
48+
fmt[index] += f'{cmd.short_doc:<{maxlen}}\n'
49+
if hasattr(cmd, 'commands'):
50+
for c in cmd.commands:
51+
branch = '\u200b └─ ' + c.name
52+
if len(fmt[index] + f"`{branch:<{maxlen+1}}` - " + f"{c.short_doc:<{maxlen}}\n") > 1024:
53+
index += 1
54+
fmt.append('')
55+
fmt[index] += f"`{branch:<{maxlen+1}}` - "
56+
fmt[index] += f"{c.short_doc:<{maxlen}}\n"
57+
58+
em = discord.Embed(
59+
description='*' + inspect.getdoc(cog) + '*',
60+
color=discord.Colour.green()
61+
)
62+
em.set_author(name=cog.__class__.__name__ + ' - Help', icon_url=ctx.bot.user.avatar_url)
63+
64+
for n, i in enumerate(fmt):
65+
if n == 0:
66+
em.add_field(name='Commands', value=i)
67+
else:
68+
em.add_field(name=u'\u200b', value=i)
69+
70+
em.set_footer(text=f'Type {prefix}command for more info on a command.')
71+
return em
72+
73+
def format_command_help(self, ctx, cmd):
74+
'''Formats command help.'''
75+
prefix = self.bot.config.get('PREFIX', 'm.')
76+
em = discord.Embed(
77+
color=discord.Color.green(),
78+
description=cmd.help
79+
)
80+
81+
if hasattr(cmd, 'invoke_without_command') and cmd.invoke_without_command:
82+
em.title = f'`Usage: {prefix}{cmd.signature}`'
83+
else:
84+
em.title = f'`{prefix}{cmd.signature}`'
85+
86+
if not hasattr(cmd, 'commands'):
87+
return em
88+
89+
maxlen = max(len(prefix + str(c)) for c in cmd.commands)
90+
fmt = ''
91+
92+
for i, c in enumerate(cmd.commands):
93+
if len(cmd.commands) == i + 1: # last
94+
branch = '└─ ' + c.name
95+
else:
96+
branch = '├─ ' + c.name
97+
fmt += f"`{branch:<{maxlen+1}}` - "
98+
fmt += f"{c.short_doc:<{maxlen}}\n"
99+
100+
em.add_field(name='Subcommands', value=fmt)
101+
em.set_footer(text=f'Type {prefix}help {cmd} command for more info on a command.')
102+
103+
return em
104+
105+
def format_not_found(self, ctx, command):
106+
prefix = ctx.prefix
107+
em = discord.Embed()
108+
em.title = 'Could not find a cog or command by that name.'
109+
em.color = discord.Color.green()
110+
em.set_footer(text=f'Type {prefix}help to get a full list of commands.')
111+
cogs = get_close_matches(command, self.bot.cogs.keys())
112+
cmds = get_close_matches(command, self.bot.all_commands.keys())
113+
if cogs or cmds:
114+
em.description = 'Did you mean...'
115+
if cogs:
116+
em.add_field(name='Cogs', value='\n'.join(f'`{x}`' for x in cogs))
117+
if cmds:
118+
em.add_field(name='Commands', value='\n'.join(f'`{x}`' for x in cmds))
119+
return em
120+
121+
@commands.command()
122+
async def help(self, ctx, *, command: str=None):
123+
"""Shows the help message."""
124+
125+
await ctx.trigger_typing()
126+
127+
if command is not None:
128+
cog = self.bot.cogs.get(command)
129+
cmd = self.bot.get_command(command)
130+
if cog is not None:
131+
em = self.format_cog_help(ctx, cog)
132+
elif cmd is not None:
133+
em = self.format_command_help(ctx, cmd)
134+
else:
135+
em = self.format_not_found(ctx, command)
136+
if em:
137+
return await ctx.send(embed=em)
138+
139+
pages = []
140+
141+
prefix = self.bot.config.get('PREFIX', 'm.')
142+
143+
for _, cog in sorted(self.bot.cogs.items()):
144+
em = self.format_cog_help(ctx, cog)
145+
if em:
146+
pages.append(em)
147+
148+
p_session = PaginatorSession(ctx, *pages)
149+
150+
await p_session.run()
151+
152+
@commands.command()
153+
@trigger_typing
154+
async def about(self, ctx):
155+
'''Shows information about the bot.'''
156+
em = discord.Embed(color=discord.Color.green(), timestamp=datetime.datetime.utcnow())
157+
em.set_author(name='Mod Mail - Information', icon_url=self.bot.user.avatar_url)
158+
em.set_thumbnail(url=self.bot.user.avatar_url)
159+
160+
em.description = 'This is an open source discord bot made by kyb3r and '\
161+
'improved upon suggestions by the users! This bot serves as a means for members to '\
162+
'easily communicate with server leadership in an organised manner.'
163+
164+
try:
165+
async with self.bot.session.get('https://api.modmail.tk/metadata') as resp:
166+
meta = await resp.json()
167+
except:
168+
meta = None
169+
170+
em.add_field(name='Uptime', value=self.bot.uptime)
171+
if meta:
172+
em.add_field(name='Instances', value=meta['instances'])
173+
else:
174+
em.add_field(name='Latency', value=f'{self.bot.latency*1000:.2f} ms')
175+
176+
177+
em.add_field(name='Version', value=f'[`{__version__}`](https://github.com/kyb3r/modmail/blob/master/bot.py#L25)')
178+
em.add_field(name='Author', value='[`kyb3r`](https://github.com/kyb3r)')
179+
180+
em.add_field(name='Latest Updates', value=await self.bot.get_latest_updates())
181+
182+
footer = f'Bot ID: {self.bot.user.id}'
183+
184+
if meta:
185+
if __version__ != meta['latest_version']:
186+
footer = f"A newer version is available v{meta['latest_version']}"
187+
else:
188+
footer = 'You are up to date with the latest version.'
189+
190+
em.add_field(name='Github', value='https://github.com/kyb3r/modmail', inline=False)
191+
192+
em.set_footer(text=footer)
193+
194+
await ctx.send(embed=em)
195+
196+
@commands.command()
197+
@owner_only()
198+
@auth_required
199+
@trigger_typing
200+
async def github(self, ctx):
201+
'''Shows the github user your access token is linked to.'''
202+
if ctx.invoked_subcommand:
203+
return
204+
205+
data = await self.bot.modmail_api.get_user_info()
206+
print(data)
207+
208+
prefix = self.bot.config.get('PREFIX', 'm.')
209+
210+
em = discord.Embed(title='Github')
211+
212+
user = data['user']
213+
em.color = discord.Color.green()
214+
em.description = f"Current user."
215+
em.set_author(name=user['username'], icon_url=user['avatar_url'], url=user['url'])
216+
em.set_thumbnail(url=user['avatar_url'])
217+
await ctx.send(embed=em)
218+
219+
220+
@commands.command()
221+
@owner_only()
222+
@auth_required
223+
@trigger_typing
224+
async def update(self, ctx):
225+
'''Updates the bot, this only works with heroku users.'''
226+
metadata = await self.bot.modmail_api.get_metadata()
227+
228+
em = discord.Embed(
229+
title='Already up to date',
230+
description=f'The latest version is [`{__version__}`](https://github.com/kyb3r/modmail/blob/master/bot.py#L25)',
231+
color=discord.Color.green()
232+
)
233+
234+
if metadata['latest_version'] == __version__:
235+
data = await self.bot.modmail_api.get_user_info()
236+
if not data['error']:
237+
user = data['user']
238+
em.set_author(name=user['username'], icon_url=user['avatar_url'], url=user['url'])
239+
else:
240+
data = await self.bot.modmail_api.update_repository()
241+
242+
commit_data = data['data']
243+
user = data['user']
244+
em.title = 'Success'
245+
em.set_author(name=user['username'], icon_url=user['avatar_url'], url=user['url'])
246+
em.set_footer(text=f"Updating modmail v{__version__} -> v{metadata['latest_version']}")
247+
248+
if commit_data:
249+
em.description = 'Bot successfully updated, the bot will restart momentarily'
250+
message = commit_data['commit']['message']
251+
html_url = commit_data["html_url"]
252+
short_sha = commit_data['sha'][:6]
253+
em.add_field(name='Merge Commit', value=f"[`{short_sha}`]({html_url}) {message} - {user['username']}")
254+
else:
255+
em.description = 'Already up to date with master repository.'
256+
257+
em.add_field(name='Latest Commit', value=await self.bot.get_latest_updates(limit=1), inline=False)
258+
259+
await ctx.send(embed=em)
260+
261+
@commands.command(name="status", aliases=['customstatus', 'presence'])
262+
@commands.has_permissions(administrator=True)
263+
async def _status(self, ctx, *, message):
264+
'''Set a custom playing status for the bot.'''
265+
if message == 'clear':
266+
return await self.bot.change_presence(activity=None)
267+
await self.bot.change_presence(activity=discord.Game(message))
268+
em = discord.Embed(title='Status Changed')
269+
em.description = message
270+
em.color = discord.Color.green()
271+
em.set_footer(text='Note: this change is temporary.')
272+
await ctx.send(embed=em)
273+
274+
@commands.command()
275+
@trigger_typing
276+
@commands.has_permissions(administrator=True)
277+
async def ping(self, ctx):
278+
"""Pong! Returns your websocket latency."""
279+
em = discord.Embed()
280+
em.title ='Pong! Websocket Latency:'
281+
em.description = f'{self.bot.ws.latency * 1000:.4f} ms'
282+
em.color = 0x00FF00
283+
await ctx.send(embed=em)
284+
285+
@commands.command(hidden=True, name='eval')
286+
@owner_only()
287+
async def _eval(self, ctx, *, body):
288+
"""Evaluates python code"""
289+
env = {
290+
'ctx': ctx,
291+
'bot': self.bot,
292+
'channel': ctx.channel,
293+
'author': ctx.author,
294+
'guild': ctx.guild,
295+
'message': ctx.message,
296+
'source': inspect.getsource,
297+
}
298+
299+
env.update(globals())
300+
301+
def cleanup_code(content):
302+
"""Automatically removes code blocks from the code."""
303+
# remove ```py\n```
304+
if content.startswith('```') and content.endswith('```'):
305+
return '\n'.join(content.split('\n')[1:-1])
306+
307+
# remove `foo`
308+
return content.strip('` \n')
309+
310+
body = cleanup_code(body)
311+
stdout = io.StringIO()
312+
err = out = None
313+
314+
to_compile = f'async def func():\n{textwrap.indent(body, " ")}'
315+
316+
def paginate(text: str):
317+
'''Simple generator that paginates text.'''
318+
last = 0
319+
pages = []
320+
for curr in range(0, len(text)):
321+
if curr % 1980 == 0:
322+
pages.append(text[last:curr])
323+
last = curr
324+
appd_index = curr
325+
if appd_index != len(text) - 1:
326+
pages.append(text[last:curr])
327+
return list(filter(lambda a: a != '', pages))
328+
329+
try:
330+
exec(to_compile, env)
331+
except Exception as e:
332+
err = await ctx.send(f'```py\n{e.__class__.__name__}: {e}\n```')
333+
return await ctx.message.add_reaction('\u2049')
334+
335+
func = env['func']
336+
try:
337+
with redirect_stdout(stdout):
338+
ret = await func()
339+
except Exception as e:
340+
value = stdout.getvalue()
341+
err = await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
342+
else:
343+
value = stdout.getvalue()
344+
if ret is None:
345+
if value:
346+
try:
347+
out = await ctx.send(f'```py\n{value}\n```')
348+
except:
349+
paginated_text = paginate(value)
350+
for page in paginated_text:
351+
if page == paginated_text[-1]:
352+
out = await ctx.send(f'```py\n{page}\n```')
353+
break
354+
await ctx.send(f'```py\n{page}\n```')
355+
else:
356+
try:
357+
out = await ctx.send(f'```py\n{value}{ret}\n```')
358+
except:
359+
paginated_text = paginate(f"{value}{ret}")
360+
for page in paginated_text:
361+
if page == paginated_text[-1]:
362+
out = await ctx.send(f'```py\n{page}\n```')
363+
break
364+
await ctx.send(f'```py\n{page}\n```')
365+
366+
if out:
367+
await ctx.message.add_reaction('\u2705') # tick
368+
elif err:
369+
await ctx.message.add_reaction('\u2049') # x
370+
else:
371+
await ctx.message.add_reaction('\u2705')
372+
373+
374+
def setup(bot):
375+
bot.add_cog(Utility(bot))

0 commit comments

Comments
 (0)