
    soj2<                         d Z ddlZdedefdZdedefdZded	ed
edefdZdedefdZded	ededefdZ	dedefdZ
dedefdZd ZdS )aK  
MarkdownV2 (Telegram Bot API flavor) -> HTML converter.

The original codebase was written against python-telegram-bot and formats all
of its text using Bot API's MarkdownV2 dialect (see ``constants.TG_DEFAULT_PARSE_MODE``
and ``src/utils/string_utils.py``'s ``escape_valid_markdown_chars`` /
``escape_invalid_markdown_chars`` helpers).

Earlier versions of this compat layer parsed MarkdownV2 directly into raw
``telethon.tl.types.MessageEntity*`` objects and passed them via
``formatting_entities=``, bypassing Telethon's own parser entirely. That
approach turned out to be too fragile in practice (specific formatting -
plain hyperlinks in particular - silently failed to render, without a
reproducible root cause found through static analysis alone). This module
instead converts Bot-API-flavored MarkdownV2 into the HTML dialect Telethon's
own, battle-tested ``parse_mode='html'`` understands
(``telethon/extensions/html.py``), and lets Telethon do the actual parsing,
entity construction, and (for mentions) access-hash resolution - the same
code path used by every other Telethon-based bot, rather than a hand-rolled
one specific to this project.

Supported syntax (https://core.telegram.org/bots/api#markdownv2-style):
    *bold*
    _italic_
    __underline__
    ~strikethrough~
    ||spoiler||
    `inline code`
    ```lang
    pre block
    ```
    [text](http://example.com)      -> text link
    [text](tg://user?id=12345)      -> inline mention of a user by id
    >quoted line                    -> blockquote (consecutive '>' lines)
    >quoted line ending in ||       -> expandable/collapsible blockquote
                                        (this codebase's own convention, see
                                        resources/phrases_en.py's
                                        surround_with_expandable_quote)

Any character preceded by ``\`` is treated as a literal character.
Nesting is supported by recursively converting the inner text of each
construct - since the output is HTML, nesting falls out naturally from
properly nested tags rather than needing manual offset/length bookkeeping.
    Ntextreturnc                 .    t          j        | d          S )zHTML-escape literal text so it can't be misread as markup by
    Telethon's HTML parser. Does not escape quotes, since this text never
    ends up inside an HTML attribute.Fquote)_htmlescape)r   s    /app/src/tg_compat/_markdown.py_escaper   1   s     <E****    urlc                 B    d| v s|                      d          r| S d| z   S )a  
    A link whose url has no recognized scheme (e.g. a misconfigured env var
    storing "t.me/xxx" instead of "https://t.me/xxx") risks being silently
    rejected by Telegram's servers - the rest of the message still sends,
    but that one link renders as plain text with no visible error anywhere.
    tg://... links (used for user mentions, already a recognized scheme) are
    left untouched; anything else with no "scheme://" prefix gets "https://"
    prepended, matching how a browser address bar treats a bare domain.
    z://ztg:zhttps://)
startswith)r   s    r
   _normalize_urlr   8   s-     ||s~~e,,|
r   sstarttokenc                     |}t          |           }t          |          }||k     r0| |         dk    r|dz  }| |||z            |k    r|S |dz  }||k     0dS )z@Find the first unescaped occurrence of `token` at/after `start`.\      )len)r   r   r   intok_lens         r
   _find_unescapedr   G   sw    AAA%jjG
a%%Q44<<FAQW_&&H	Q a%% 2r   r   c                    | ||dz            dk    r&|                      d|dz             }|dk    r|dz   ndS | |         dk    r&|                      d|dz             }|dk    r|dz   ndS | |         dk    rOt          | |dz   d          }|dk    r3| |dz   |d	z            d
k    rt          | |d	z   d          }|dk    r|dz   S dS dS )a  
    If a self-contained, opaque construct starts at s[i] - a ```pre``` block,
    a `code` span, or a [text](url) link - return the index right after it
    ends. Otherwise return None, meaning s[i] is just an ordinary character.

    Mirrors the matching branches of _to_html() closely enough to identify
    the same spans, but only measures their extent rather than converting
    them - conversion still happens in the normal course of _to_html() once
    whatever *outer* delimiter search called this has been resolved.
       ```r   N`r   []r   ())findr   )r   r   endclose_bracketclose_parens        r
   _atomic_span_endr*   V   s    	QU|uffUAE""))sQww-ts{{ffS!a%  ))sQww-ts{{'1q5#66B1]Q%69J%J#Ks#R#R)!]Q->DDKb  "Q&t4r   delimc                     |}t          |           }t          |          }||k     rE| |         dk    r|dz  }| |||z            |k    r|S t          | |          }||}@|dz  }||k     EdS )a  
    Like _find_unescaped, but treats [text](url), `code`, and ```pre```
    spans as opaque - a markdown-special character living inside one of
    those (almost always inside a URL) is never a candidate closing
    delimiter for whatever *outer* span is being closed here, matching how
    a real MarkdownV2 parser treats them.

    Without this, a raw, unescaped underscore inside a URL nested in (for
    example) an italic span reads, to a plain left-to-right scan, exactly
    like that span's own closing "_" - so this codebase's
    "_Posted by [u/{}]({author_url}) on [r/{}]({sub_url})_" pattern
    (reddit_service.py) broke on any Reddit username containing an
    underscore: the italic span closed early, mid-way through the
    username's URL, which truncated the link before its own closing ")" -
    so the link-parsing branch in _to_html() never found one and the whole
    thing fell back to literal, broken-looking [brackets](and parens) in
    the sent message, with the swallowed "_" visibly missing from the
    leaked URL text (Telegram then auto-linkified that leaked plain-text
    URL on its own, which is what made it look like *some* kind of working
    link despite being neither the intended link nor the intended text).
    r   r   Nr   r   )r   r*   )r   r   r+   r   r   dlenskip_tos          r
   _find_closing_delimr/   q   s    , 	AAAu::D
a%%Q44<<FAQT\?e##H"1a((A	Q a%% 2r   c                 (   d| vr| S g }d}t          |           }||k     r_| |         dk    r-|dz   |k     r$|                    | |dz                       |dz  }n |                    | |                    |dz  }||k     _d                    |          S )a  
    Undo MarkdownV2 backslash-escaping ("\X" -> "X"), for the few substrings
    that are taken as a raw slice instead of being run back through
    _to_html() - a link's URL, and the contents of `code`/```pre``` blocks.

    Real Bot API MarkdownV2 (what PTB sends to Telegram's own server-side
    parser) unescapes "\X" to "X" uniformly, wherever it appears in a
    message - including inside a link's URL and inside code/pre text. But
    this codebase's escape_invalid_markdown_chars/escape_valid_markdown_chars
    helpers (src/service/message_service.py) run over whole, already-built
    messages - deeplinks and all - without excluding text that happens to
    sit inside a [text](url), `code`, or ```pre``` construct. So a URL like
    "https://t.me/x" commonly arrives here as "https://t\.me/x" (the "."
    got escaped along with the rest of the message), and a deeplink's base64
    payload commonly picks up an escaped "\=" from its padding.

    Without this, those literal backslashes were passed straight through:
    into the href attribute (producing a URL Telegram silently refuses to
    treat as clickable - the message still sends, the link just isn't a
    link) for links, and visibly in the rendered text for code/pre. Bold,
    italic, blockquotes etc. were never affected, since those recurse through
    _to_html() (which already unescapes via the `ch == "\"` branch below) -
    only these raw-slice cases skipped that step.
    r   r   r   r    )r   appendjoin)r   outr   r   s       r
   	_unescaper5      s    2 4
C	AD		A
a%%7d??q1uqyyJJtAE{###FAAJJtAwFA a%% 773<<r   sourcec                 N    dt                     }g d. fd	}|k     rl          }|dk    r:dz   |k     r1                    t           dz                                 dz  O|dk    r`dk    s dz
           d	k    rJg }}||k     r |         dk    r|dz  }                     d	|          }|d
k    r |                     |d                    |}nP|                     ||                    |dz   |k     r |dz            dk    r|dz   }n|}n||k     r |         dk    d}|r0|d
                             d          r|d
         dd         |d
<   d}d	                    |          }|rdnd}	                    d|	 d                               t          |                                         d           | dz            dk    r`                     ddz             }
|
d
k    r)                    t          |                     dz   dz   |
         }d}d	|v r8|                    d	d          \  }}|rt          d |D                       s|}|}|rk                    dt          |           d                               t          t          |                                                   d           nY                    d                               t          t          |                                                   d           |
dz   (|dk    r                     ddz             }
|
d
k    r)                    t          |                     dz  v dz   |
         }                    d                               t          t          |                                                   d           |
dz   |dk    rt           dz   d          }|d
k    r͉ |dz   |dz            dk    rt           |dz   d           }|d
k    r dz   |         }t          t           |dz   |                             }                    d!t          j        |d"           d                               t          |                                         d#           |dz   щ                    t          |                     dz   dz            dk    r9 |dd$d%          r                    t          |                     dz  D dz            d&k    r9 |d&d'd&%          re                    t          |                     dz  |d(k    r7 |d(d)          r                    t          |                     dz  |d*k    r7 |d*d+          r߉                    t          |                     dz  |d,k    r7 |d,d-          r                    t          |                     dz  E                    t          |                     dz  |k     ld                              S )/z<Recursively convert a MarkdownV2-formatted string into HTML.r   Nr1   c                 f   |p| }t          t          |           z   |          }|dk    rdS t          |           z   |         }                    d| | d                               t          |                                         d| d           |t          |          z   dS )ag  
        Parse `delim ... (closing or delim)`, recursing into the inner text
        and wrapping the result in `<tag attrs>...</tag>`. Returns True if a
        full match was consumed (i pointer already advanced), False if there
        was no valid closing delimiter (caller should emit the opening
        character literally and advance by one).
        r   F<>z</T)r/   r   r2   _to_html)	r+   tagclosingattrsr'   innerr   r4   r6   s	         r
   consume_wrappedz!_to_html.<locals>.consume_wrapped   s     "U!&!c%jj.'BB"995q3u::~+,

$s$E$$$%%%

8E??###

;;;;#g,,tr   r   r   r   r:   
r   Fz||Tz expandablez<blockquotez</blockquote>r   r    c              3   >   K   | ]}|                                 V  d S )N)isspace).0cs     r
   	<genexpr>z_to_html.<locals>.<genexpr>  s*      )J)J!!))++)J)J)J)J)J)Jr   z<pre><code class="language-z">z</code></pre>z<pre>z</pre>r!   z<code>z</code>r"   r#   r$   r%   z	<a href="r   z</a>z
tg-spoiler)r=   __u*b~r   _r   )Nr1   )r   r2   r   r&   endswithr3   r;   splitanyr5   r   r   r   r	   )r6   r   r@   chquote_linesjline_end
expandableinner_sourcer>   r'   blocklang
first_linerest	code_textr(   r)   	link_textr   r   r4   s   `                   @@r
   r;   r;      sm   	AFA
C       ( a%%AY ::!a%!))JJwva!e}--...FA 99!q&&F1q5MT$9$9KAa%%F1I,,Q!;;tQ//r>>&&vabbz222A""6!H*#5666a<!##x!|(<(C(C 1AA A a%%F1I,,$ J "{277== ""-b/#2#"6B!
99[11L%/7MMREJJ-U---...JJx--...JJ'''A !a!e)%%++eQU++Cbyy

72;;'''Q1q53;'EDu}}#(;;tQ#7#7 
D !c)J)Jz)J)J)J&J&J !%D E %

JJJJKKK

79U#3#344555

?++++

7###

79U#3#344555

8$$$aA 99++c1q5))Cbyy

72;;'''Qq1us{+IJJx   JJwy3344555JJy!!!aA 99+FAE3??M""vma.?-RSBS.S'TX['['[
 .fma6GMM"$$ &q1u}'< =I )6-!:Kk:Y3Z)[)[\\CJJL5<4+H+H+HLLLMMMJJx	22333JJv&&&#aAJJwr{{###FA !a!e)$$t\4@@@ JJwr{{###FA !a!e)$$tS$777 JJwr{{###FA 99sC(( JJwr{{###FA 99sC(( JJwr{{###FA 99sC(( JJwr{{###FA

72;;	Qu a%%x 773<<r   c                 (    | dS t          |           S )z
    Convert a MarkdownV2 (Bot API flavor) formatted string into HTML
    suitable for Telethon's parse_mode='html'.

    :param source: MarkdownV2 formatted text, or None
    :return: HTML string, or None if source is None
    N)r;   )r6   s    r
   markdown_v2_to_htmlr^   y  s     ~tFr   )__doc__htmlr   strr   r   intr   r*   r/   r5   r;   r^    r   r
   <module>rd      sJ  + +Z    +# +# + + + +     s 3 s s         6$3 $s $3 $3 $ $ $ $N%C %C % % % %PvS vS v v v vr
 
 
 
 
r   