Repository: python-discord/bot

Issue:
Catch error when source command cannot retrieve lines or file
Sentry Issue: [BOT-7K](https://sentry.io/organizations/python-discord/issues/1816540252/?referrer=github_integration)

This happens if the target command is one which has been added via internal eval or some other dynamic method, so it's _really_ low priority.

I think it'd be most convenient to let the error propagate a bit and handle it here https://github.com/python-discord/bot/blob/3649c2a03dc158a25f2c3be98db8691f903a1953/bot/cogs/source.py#L59

```
OSError: could not get source code
(1 additional frame(s) were not displayed)
...
  File "bot/cogs/source.py", line 59, in source_command
    embed = await self.build_embed(source_item)
  File "bot/cogs/source.py", line 102, in build_embed
    url, location, first_line = self.get_source_link(source_object)
  File "bot/cogs/source.py", line 84, in get_source_link
    lines, first_line_no = inspect.getsourcelines(src)
  File "inspect.py", line 967, in getsourcelines
    lines, lnum = findsource(object)
  File "inspect.py", line 798, in findsource
    raise OSError('could not get source code')
```


Discussion:

Resolving patch:
diff --git a/bot/cogs/source.py b/bot/cogs/source.py
--- a/bot/cogs/source.py
+++ b/bot/cogs/source.py
@@ -60,11 +60,12 @@ async def source_command(self, ctx: commands.Context, *, source_item: SourceConv
         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."""
-        if isinstance(source_item, commands.HelpCommand):
-            src = type(source_item)
-            filename = inspect.getsourcefile(src)
-        elif isinstance(source_item, commands.Command):
+        """
+        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("_", " "))
@@ -78,10 +79,17 @@ def get_source_link(self, source_item: SourceType) -> Tuple[str, str, Optional[i
             filename = tags_cog._cache[source_item]["location"]
         else:
             src = type(source_item)
-            filename = inspect.getsourcefile(src)
+            try:
+                filename = inspect.getsourcefile(src)
+            except TypeError:
+                raise commands.BadArgument("Cannot get source for a dynamically-created object.")
 
         if not isinstance(source_item, str):
-            lines, first_line_no = inspect.getsourcelines(src)
+            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


Test patch: