From ca18755d59898f725d6caae44e2431e583960fde Mon Sep 17 00:00:00 2001 From: vivekashok1221 Date: Thu, 10 Apr 2025 23:13:21 +0400 Subject: [PATCH 1/2] CodeSnippets: Add support for paste.pythondiscord.com --- bot/exts/info/code_snippets.py | 78 ++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/bot/exts/info/code_snippets.py b/bot/exts/info/code_snippets.py index a44b0c4755..0e80e60363 100644 --- a/bot/exts/info/code_snippets.py +++ b/bot/exts/info/code_snippets.py @@ -38,6 +38,13 @@ r"/(?P[^#>]+)(\?[^#>]+)?(#lines-(?P\d+)(:(?P\d+))?)" ) +PYDIS_PASTEBIN_RE = re.compile( + r"https://paste\.(?:pythondiscord\.com|pydis\.wtf)/(?P[a-zA-Z0-9]+)" + r"#(?P(?:\d+L\d+-L\d+)(?:,\d+L\d+-L\d+)*)" +) + +PASTEBIN_LINE_SELECTION_RE = re.compile(r"(\d+)L(\d+)-L(\d+)") + class CodeSnippets(Cog): """ @@ -54,7 +61,8 @@ def __init__(self, bot: Bot): (GITHUB_RE, self._fetch_github_snippet), (GITHUB_GIST_RE, self._fetch_github_gist_snippet), (GITLAB_RE, self._fetch_gitlab_snippet), - (BITBUCKET_RE, self._fetch_bitbucket_snippet) + (BITBUCKET_RE, self._fetch_bitbucket_snippet), + (PYDIS_PASTEBIN_RE, self._fetch_pastebin_snippets), ] async def _fetch_response(self, url: str, response_format: str, **kwargs) -> Any: @@ -170,7 +178,40 @@ async def _fetch_bitbucket_snippet( ) return self._snippet_to_codeblock(file_contents, file_path, start_line, end_line) - def _snippet_to_codeblock(self, file_contents: str, file_path: str, start_line: str, end_line: str) -> str: + async def _fetch_pastebin_snippets(self, paste_id: str, selections: str) -> str: + """Fetches snippets from paste.pythondiscord.com.""" + paste_data = await self._fetch_response( + f"https://paste.pythondiscord.com/api/v1/paste/{paste_id}", + "json" + ) + + snippets = [] + for match in PASTEBIN_LINE_SELECTION_RE.finditer(selections): + file_num, start, end = match.groups() + file_num = int(file_num) - 1 + + file = paste_data["files"][file_num] + file_name = file.get("name") or f"file {file_num + 1}" + snippet = self._snippet_to_codeblock( + file["content"], + file_name, + start, + end, + language=file["lexer"], + ) + + snippets.append(snippet) + + return snippets + + def _snippet_to_codeblock( + self, + file_contents: str, + file_path: str, + start_line: str, + end_line: str|None, + language: str|None = None + ) -> str: """ Given the entire file contents and target lines, creates a code block. @@ -203,15 +244,16 @@ def _snippet_to_codeblock(self, file_contents: str, file_path: str, start_line: required = "\n".join(split_file_contents[start_line - 1:end_line]) required = textwrap.dedent(required).rstrip().replace("`", "`\u200b") - # Extracts the code language and checks whether it's a "valid" language - language = file_path.split("/")[-1].split(".")[-1] - trimmed_language = language.replace("-", "").replace("+", "").replace("_", "") - is_valid_language = trimmed_language.isalnum() - if not is_valid_language: - language = "" + if language is None: + # Extracts the code language and checks whether it's a "valid" language + language = file_path.split("/")[-1].split(".")[-1] + trimmed_language = language.replace("-", "").replace("+", "").replace("_", "") + is_valid_language = trimmed_language.isalnum() + if not is_valid_language: + language = "" - if language == "pyi": - language = "py" + if language == "pyi": + language = "py" # Adds a label showing the file path to the snippet if start_line == end_line: @@ -231,8 +273,7 @@ async def _parse_snippets(self, content: str) -> str: for pattern, handler in self.pattern_handlers: for match in pattern.finditer(content): try: - snippet = await handler(**match.groupdict()) - all_snippets.append((match.start(), snippet)) + result = await handler(**match.groupdict()) except ClientResponseError as error: error_message = error.message log.log( @@ -241,8 +282,17 @@ async def _parse_snippets(self, content: str) -> str: f"{error_message} for GET {error.request_info.real_url.human_repr()}" ) - # Sorts the list of snippets by their match index and joins them into a single message - return "\n".join(x[1] for x in sorted(all_snippets)) + if isinstance(result, list): + # The handler returned multiple snippets (currently only possible with our pastebin) + all_snippets.extend((match.start(), snippet) for snippet in result) + else: + all_snippets.append((match.start(), result)) + + # Sort the list of snippets by ONLY their match index + all_snippets.sort(key=lambda item: item[0]) + + # Join them into a single message + return "\n".join(x[1] for x in all_snippets) @Cog.listener() async def on_message(self, message: discord.Message) -> None: From 90849917fad3c618ef27c78c4f8fe1b84bd6b9be Mon Sep 17 00:00:00 2001 From: Vivek Ashokkumar Date: Sat, 12 Apr 2025 14:06:59 +0400 Subject: [PATCH 2/2] Fix return type annotation Co-authored-by: Anand <40204976+anand2312@users.noreply.github.com> --- bot/exts/info/code_snippets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/code_snippets.py b/bot/exts/info/code_snippets.py index 0e80e60363..eba15e8256 100644 --- a/bot/exts/info/code_snippets.py +++ b/bot/exts/info/code_snippets.py @@ -178,7 +178,7 @@ async def _fetch_bitbucket_snippet( ) return self._snippet_to_codeblock(file_contents, file_path, start_line, end_line) - async def _fetch_pastebin_snippets(self, paste_id: str, selections: str) -> str: + async def _fetch_pastebin_snippets(self, paste_id: str, selections: str) -> list[str]: """Fetches snippets from paste.pythondiscord.com.""" paste_data = await self._fetch_response( f"https://paste.pythondiscord.com/api/v1/paste/{paste_id}",