1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
import inspect
from pathlib import Path
from typing import Optional, Tuple, Union
from discord import Embed
from discord.ext import commands
from bot.constants import Source
SourceType = Union[commands.HelpCommand, commands.Command, commands.Cog, str, commands.ExtensionNotLoaded]
class SourceConverter(commands.Converter):
"""Convert an argument into a help command, tag, command, or cog."""
async def convert(self, ctx: commands.Context, argument: str) -> SourceType:
"""Convert argument into source object."""
if argument.lower().startswith("help"):
return ctx.bot.help_command
cog = ctx.bot.get_cog(argument)
if cog:
return cog
cmd = ctx.bot.get_command(argument)
if cmd:
return cmd
raise commands.BadArgument(
f"Unable to convert `{argument}` to valid command or Cog."
)
class BotSource(commands.Cog):
"""Displays information about the bot's source code."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="source", aliases=("src",))
async def source_command(self, ctx: commands.Context, *, source_item: SourceConverter = None) -> None:
"""Display information and a GitHub link to the source code of a command, tag, or cog."""
if not source_item:
embed = Embed(title="Seasonal Bot's GitHub Repository")
embed.add_field(name="Repository", value=f"[Go to GitHub]({Source.github})")
embed.set_thumbnail(url="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png")
await ctx.send(embed=embed)
return
embed = await self.build_embed(source_item)
await ctx.send(embed=embed)
def get_source_link(self, source_item: SourceType) -> Tuple[str, str, Optional[int]]:
"""
Build GitHub link of source item, return this link, file location and first line number.
Raise BadArgument if `source_item` is a dynamically-created object (e.g. via internal eval).
"""
if isinstance(source_item, commands.Command):
if source_item.cog_name == "Alias":
cmd_name = source_item.callback.__name__.replace("_alias", "")
cmd = self.bot.get_command(cmd_name.replace("_", " "))
src = cmd.callback.__code__
filename = src.co_filename
else:
src = source_item.callback.__code__
filename = src.co_filename
else:
src = type(source_item)
try:
filename = inspect.getsourcefile(src)
except TypeError:
raise commands.BadArgument("Cannot get source for a dynamically-created object.")
if not isinstance(source_item, str):
try:
lines, first_line_no = inspect.getsourcelines(src)
except OSError:
raise commands.BadArgument("Cannot get source for a dynamically-created object.")
lines_extension = f"#L{first_line_no}-L{first_line_no+len(lines)-1}"
else:
first_line_no = None
lines_extension = ""
# Handle tag file location differently than others to avoid errors in some cases
if not first_line_no:
file_location = Path(filename).relative_to("/bot/")
else:
file_location = Path(filename).relative_to(Path.cwd()).as_posix()
url = f"{Source.github}/blob/master/{file_location}{lines_extension}"
return url, file_location, first_line_no or None
async def build_embed(self, source_object: SourceType) -> Optional[Embed]:
"""Build embed based on source object."""
url, location, first_line = self.get_source_link(source_object)
if isinstance(source_object, commands.HelpCommand):
title = "Help Command"
description = source_object.__doc__.splitlines()[1]
elif isinstance(source_object, commands.Command):
if source_object.cog_name == "Alias":
cmd_name = source_object.callback.__name__.replace("_alias", "")
cmd = self.bot.get_command(cmd_name.replace("_", " "))
description = cmd.short_doc
else:
description = source_object.short_doc
title = f"Command: {source_object.qualified_name}"
elif isinstance(source_object, str):
title = f"Tag: {source_object}"
description = ""
else:
title = f"Cog: {source_object.qualified_name}"
description = source_object.description.splitlines()[0]
embed = Embed(title=title, description=description)
embed.set_thumbnail(url="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png")
embed.add_field(name="Source Code", value=f"[Go to GitHub]({url})")
line_text = f":{first_line}" if first_line else ""
embed.set_footer(text=f"{location}{line_text}")
return embed
def setup(bot: commands.Bot) -> None:
"""Load the BotSource cog."""
bot.add_cog(BotSource(bot))
|