Skip to content

Commit c6f7914

Browse files
committed
♻️ refactor markdown and mention handling functions in __init__.py and public.py
1 parent a021450 commit c6f7914

File tree

2 files changed

+203
-184
lines changed

2 files changed

+203
-184
lines changed

discord/utils/__init__.py

Lines changed: 6 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@
5959
Undefined,
6060
MISSING,
6161
format_dt,
62+
escape_mentions,
63+
raw_mentions,
64+
raw_channel_mentions,
65+
raw_mentions,
66+
remove_markdown,
67+
escape_markdown,
6268
)
6369

6470
try:
@@ -350,187 +356,3 @@ def get(self, element: int) -> int | None:
350356
def has(self, element: int) -> bool:
351357
i = bisect_left(self, element)
352358
return i != len(self) and self[i] == element
353-
354-
355-
_MARKDOWN_ESCAPE_SUBREGEX = "|".join(r"\{0}(?=([\s\S]*((?<!\{0})\{0})))".format(c) for c in ("*", "`", "_", "~", "|"))
356-
357-
# regular expression for finding and escaping links in markdown
358-
# note: technically, brackets are allowed in link text.
359-
# perhaps more concerning, parentheses are also allowed in link destination.
360-
# this regular expression matches neither of those.
361-
# this page provides a good reference: http://blog.michaelperrin.fr/2019/02/04/advanced-regular-expressions/
362-
_MARKDOWN_ESCAPE_LINKS = r"""
363-
\[ # matches link text
364-
[^\[\]]* # link text can contain anything but brackets
365-
\]
366-
\( # matches link destination
367-
[^\(\)]+ # link destination cannot contain parentheses
368-
\)""" # note 2: make sure this regex is consumed in re.X (extended mode) since it has whitespace and comments
369-
370-
_MARKDOWN_ESCAPE_COMMON = rf"^>(?:>>)?\s|{_MARKDOWN_ESCAPE_LINKS}"
371-
372-
_MARKDOWN_ESCAPE_REGEX = re.compile(
373-
rf"(?P<markdown>{_MARKDOWN_ESCAPE_SUBREGEX}|{_MARKDOWN_ESCAPE_COMMON})",
374-
re.MULTILINE | re.X,
375-
)
376-
377-
_URL_REGEX = r"(?P<url><[^: >]+:\/[^ >]+>|(?:https?|steam):\/\/[^\s<]+[^<.,:;\"\'\]\s])"
378-
379-
_MARKDOWN_STOCK_REGEX = rf"(?P<markdown>[_\\~|\*`]|{_MARKDOWN_ESCAPE_COMMON})"
380-
381-
382-
def remove_markdown(text: str, *, ignore_links: bool = True) -> str:
383-
"""A helper function that removes markdown characters.
384-
385-
.. versionadded:: 1.7
386-
387-
.. note::
388-
This function is not markdown aware and may remove meaning from the original text. For example,
389-
if the input contains ``10 * 5`` then it will be converted into ``10 5``.
390-
391-
Parameters
392-
----------
393-
text: :class:`str`
394-
The text to remove markdown from.
395-
ignore_links: :class:`bool`
396-
Whether to leave links alone when removing markdown. For example,
397-
if a URL in the text contains characters such as ``_`` then it will
398-
be left alone. Defaults to ``True``.
399-
400-
Returns
401-
-------
402-
:class:`str`
403-
The text with the markdown special characters removed.
404-
"""
405-
406-
def replacement(match):
407-
groupdict = match.groupdict()
408-
return groupdict.get("url", "")
409-
410-
regex = _MARKDOWN_STOCK_REGEX
411-
if ignore_links:
412-
regex = f"(?:{_URL_REGEX}|{regex})"
413-
return re.sub(regex, replacement, text, 0, re.MULTILINE)
414-
415-
416-
def escape_markdown(text: str, *, as_needed: bool = False, ignore_links: bool = True) -> str:
417-
r"""A helper function that escapes Discord's markdown.
418-
419-
Parameters
420-
-----------
421-
text: :class:`str`
422-
The text to escape markdown from.
423-
as_needed: :class:`bool`
424-
Whether to escape the markdown characters as needed. This
425-
means that it does not escape extraneous characters if it's
426-
not necessary, e.g. ``**hello**`` is escaped into ``\*\*hello**``
427-
instead of ``\*\*hello\*\*``. Note however that this can open
428-
you up to some clever syntax abuse. Defaults to ``False``.
429-
ignore_links: :class:`bool`
430-
Whether to leave links alone when escaping markdown. For example,
431-
if a URL in the text contains characters such as ``_`` then it will
432-
be left alone. This option is not supported with ``as_needed``.
433-
Defaults to ``True``.
434-
435-
Returns
436-
--------
437-
:class:`str`
438-
The text with the markdown special characters escaped with a slash.
439-
"""
440-
441-
if not as_needed:
442-
443-
def replacement(match):
444-
groupdict = match.groupdict()
445-
is_url = groupdict.get("url")
446-
if is_url:
447-
return is_url
448-
return f"\\{groupdict['markdown']}"
449-
450-
regex = _MARKDOWN_STOCK_REGEX
451-
if ignore_links:
452-
regex = f"(?:{_URL_REGEX}|{regex})"
453-
return re.sub(regex, replacement, text, 0, re.MULTILINE | re.X)
454-
else:
455-
text = re.sub(r"\\", r"\\\\", text)
456-
return _MARKDOWN_ESCAPE_REGEX.sub(r"\\\1", text)
457-
458-
459-
def escape_mentions(text: str) -> str:
460-
"""A helper function that escapes everyone, here, role, and user mentions.
461-
462-
.. note::
463-
464-
This does not include channel mentions.
465-
466-
.. note::
467-
468-
For more granular control over what mentions should be escaped
469-
within messages, refer to the :class:`~discord.AllowedMentions`
470-
class.
471-
472-
Parameters
473-
----------
474-
text: :class:`str`
475-
The text to escape mentions from.
476-
477-
Returns
478-
-------
479-
:class:`str`
480-
The text with the mentions removed.
481-
"""
482-
return re.sub(r"@(everyone|here|[!&]?[0-9]{17,20})", "@\u200b\\1", text)
483-
484-
485-
def raw_mentions(text: str) -> list[int]:
486-
"""Returns a list of user IDs matching ``<@user_id>`` in the string.
487-
488-
.. versionadded:: 2.2
489-
490-
Parameters
491-
----------
492-
text: :class:`str`
493-
The string to get user mentions from.
494-
495-
Returns
496-
-------
497-
List[:class:`int`]
498-
A list of user IDs found in the string.
499-
"""
500-
return [int(x) for x in re.findall(r"<@!?([0-9]+)>", text)]
501-
502-
503-
def raw_channel_mentions(text: str) -> list[int]:
504-
"""Returns a list of channel IDs matching ``<@#channel_id>`` in the string.
505-
506-
.. versionadded:: 2.2
507-
508-
Parameters
509-
----------
510-
text: :class:`str`
511-
The string to get channel mentions from.
512-
513-
Returns
514-
-------
515-
List[:class:`int`]
516-
A list of channel IDs found in the string.
517-
"""
518-
return [int(x) for x in re.findall(r"<#([0-9]+)>", text)]
519-
520-
521-
def raw_role_mentions(text: str) -> list[int]:
522-
"""Returns a list of role IDs matching ``<@&role_id>`` in the string.
523-
524-
.. versionadded:: 2.2
525-
526-
Parameters
527-
----------
528-
text: :class:`str`
529-
The string to get role mentions from.
530-
531-
Returns
532-
-------
533-
List[:class:`int`]
534-
A list of role IDs found in the string.
535-
"""
536-
return [int(x) for x in re.findall(r"<@&([0-9]+)>", text)]

0 commit comments

Comments
 (0)