§
    ‰eîi$  ã                   óP  — d Z ddlZddlZddlZddlZddlZddlmZ ddlm	Z	m
Z
mZ e	rddlmZmZ  ej        e¦  «        Z G d„ d¦  «        Z G d„ d	¦  «        Z ej        d
ej        ¦  «        Z ej        dej        ¦  «        Z ej        dej        ¦  «        Z ej        dej        ¦  «        Z ej        d¦  «        Z ej        d¦  «        Z ej        dej        ¦  «        Z ej        d¦  «        Z ej        d¦  «        Zde de fd„Z! G d„ d¦  «        Z"de de fd„Z#dS )zßShared helper classes for gateway platform adapters.

Extracts common patterns that were duplicated across 5-7 adapters:
message deduplication, text batch aggregation, markdown stripping,
and thread participation tracking.
é    N)ÚPath)ÚTYPE_CHECKINGÚDictÚOptional)ÚBasePlatformAdapterÚMessageEventc                   ó:   — e Zd ZdZddedefd„Zdedefd	„Z	d
„ Z
dS )ÚMessageDeduplicatora|  TTL-based message deduplication cache.

    Replaces the identical ``_seen_messages`` / ``_is_duplicate()`` pattern
    previously duplicated in discord, slack, dingtalk, wecom, weixin,
    mattermost, and feishu adapters.

    Usage::

        self._dedup = MessageDeduplicator()

        # In message handler:
        if self._dedup.is_duplicate(msg_id):
            return
    éÐ  é,  Úmax_sizeÚttl_secondsc                 ó0   — i | _         || _        || _        d S ©N)Ú_seenÚ	_max_sizeÚ_ttl)Úselfr   r   s      ú;/home/div/.hermes/hermes-agent/gateway/platforms/helpers.pyÚ__init__zMessageDeduplicator.__init__)   s   € Ø')ˆŒ
Ø!ˆŒØˆŒ	ˆ	ˆ	ó    Úmsg_idÚreturnc                 óD  ‡— |sdS t          j         ¦   «         }|| j        v r#|| j        |         z
  | j        k     rdS | j        |= || j        |<   t          | j        ¦  «        | j        k    r4|| j        z
  Šˆfd„| j                             ¦   «         D ¦   «         | _        dS )z?Return True if *msg_id* was already seen within the TTL window.FTc                 ó(   •— i | ]\  }}|‰k    ¯||“ŒS © r   )Ú.0ÚkÚvÚcutoffs      €r   ú
<dictcomp>z4MessageDeduplicator.is_duplicate.<locals>.<dictcomp>;   s$   ø€ ÐLÐLÐL¡4 1 aÀÀVÂÀ˜!˜QÀÀÀr   )Útimer   r   Úlenr   Úitems)r   r   Únowr    s      @r   Úis_duplicatez MessageDeduplicator.is_duplicate.   sª   ø€ àð 	Ø�5ÝŒi‰kŒkˆØ�T”ZÐÐØ�T”Z Ô'Ñ'¨$¬)Ò3Ð3Ø�tà”
˜6Ð"Ø ˆŒ
�6ÑÝˆtŒz‰?Œ?˜Tœ^Ò+Ð+Ø˜4œ9‘_ˆFØLÐLÐLÐL¨4¬:×+;Ò+;Ñ+=Ô+=ÐLÑLÔLˆDŒJØˆur   c                 ó8   — | j                              ¦   «          dS )zClear all tracked messages.N)r   Úclear©r   s    r   r(   zMessageDeduplicator.clear>   s   € àŒ
×ÒÑÔÐÐÐr   N)r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__ÚintÚfloatr   ÚstrÚboolr&   r(   r   r   r   r
   r
      sv   € € € € € ðð ð ð   ð  ¸%ð  ð  ð  ð  ð
 3ð ¨4ð ð ð ð ð ð ð ð ð r   r
   c                   óh   — e Zd ZdZddddœdededefd	„Zd
efd„Zddde	d
dfd„Z
de	d
dfd„Zdd„ZdS )ÚTextBatchAggregatora@  Aggregates rapid-fire text events into single messages.

    Replaces the ``_enqueue_text_event`` / ``_flush_text_batch`` pattern
    previously duplicated in telegram, discord, matrix, wecom, and feishu.

    Usage::

        self._text_batcher = TextBatchAggregator(
            handler=self._message_handler,
            batch_delay=0.6,
            split_threshold=1900,
        )

        # In message dispatch:
        if msg_type == MessageType.TEXT and self._text_batcher.is_enabled():
            self._text_batcher.enqueue(event, session_key)
            return
    g333333ã?g       @i   )Úbatch_delayÚsplit_delayÚsplit_thresholdr4   r5   r6   c                óZ   — || _         || _        || _        || _        i | _        i | _        d S r   )Ú_handlerÚ_batch_delayÚ_split_delayÚ_split_thresholdÚ_pendingÚ_pending_tasks)r   Úhandlerr4   r5   r6   s        r   r   zTextBatchAggregator.__init__Z   s8   € ð  ˆŒØ'ˆÔØ'ˆÔØ /ˆÔØ35ˆŒØ79ˆÔÐÐr   r   c                 ó   — | j         dk    S )z.Return True if batching is active (delay > 0).r   )r9   r)   s    r   Ú
is_enabledzTextBatchAggregator.is_enabledi   s   € àÔ  1Ò$Ð$r   Úeventr   ÚkeyNc                 ó®  — t          |j        pd¦  «        }| j                             |¦  «        }|s||_        || j        |<   n|j        › d|j        › �|_        ||_        | j                             |¦  «        }|r(|                     ¦   «         s|                     ¦   «          t          j	        |  
                    |¦  «        ¦  «        | j        |<   dS )z+Add *event* to the pending batch for *key*.Ú Ú
N)r#   Útextr<   ÚgetÚ_last_chunk_lenr=   ÚdoneÚcancelÚasyncioÚcreate_taskÚ_flush)r   rA   rB   Ú	chunk_lenÚexistingÚpriors         r   ÚenqueuezTextBatchAggregator.enqueuem   sÑ   € å˜œ
Ð( bÑ)Ô)ˆ	Ø”=×$Ò$ SÑ)Ô)ˆØð 	1Ø$-ˆEÔ!Ø!&ˆDŒM˜#ÑÐà'œ}Ð<Ð<°´
Ð<Ð<ˆHŒMØ'0ˆHÔ$ð Ô#×'Ò'¨Ñ,Ô,ˆØð 	˜Ÿš™œð 	Ø�LŠL‰NŒNˆNÝ#*Ô#6°t·{²{À3Ñ7GÔ7GÑ#HÔ#HˆÔ˜CÑ Ð Ð r   c              ƒ   ó>  K  — | j                              |¦  «        }| j                             |¦  «        }|rt          |dd¦  «        nd}|| j        k    r| j        n| j        }t          j        |¦  «        ƒ d{V —† | j         	                    |d¦  «        }|rH	 |  
                    |¦  «        ƒ d{V —† n+# t          $ r t                               d|¦  «         Y nw xY w| j                              |¦  «        |u r| j          	                    |d¦  «         dS dS )z/Wait then dispatch the batched event for *key*.rH   r   Nz<[TextBatchAggregator] Error dispatching batched event for %s)r=   rG   r<   Úgetattrr;   r:   r9   rK   ÚsleepÚpopr8   Ú	ExceptionÚloggerÚ	exception)r   rB   Úcurrent_taskÚpendingÚlast_lenÚdelayrA   s          r   rM   zTextBatchAggregator._flush~   s]  è è € àÔ*×.Ò.¨sÑ3Ô3ˆØ”-×#Ò# CÑ(Ô(ˆØ=DÐK•7˜7Ð$5°qÑ9Ô9Ð9È!ˆð &.°Ô1FÒ%FÐ%F�Ô!Ð!ÈDÔL]ˆÝŒm˜EÑ"Ô"Ð"Ð"Ð"Ð"Ð"Ð"Ð"à”×!Ò! # tÑ,Ô,ˆØð 	fðfØ—m’m EÑ*Ô*Ð*Ð*Ð*Ð*Ð*Ð*Ð*Ð*øÝð fð fð fÝ× Ò Ð!_ÐadÑeÔeÐeÐeÐeðføøøð Ô×"Ò" 3Ñ'Ô'¨<Ð7Ð7ØÔ×#Ò# C¨Ñ.Ô.Ð.Ð.Ð.ð 8Ð7s   ÂB9 Â9%C!Ã C!c                 óò   — | j                              ¦   «         D ]*}|                     ¦   «         s|                     ¦   «          Œ+| j                              ¦   «          | j                             ¦   «          dS )zCancel all pending flush tasks.N)r=   ÚvaluesrI   rJ   r(   r<   )r   Útasks     r   Ú
cancel_allzTextBatchAggregator.cancel_all’   sm   € àÔ'×.Ò.Ñ0Ô0ð 	ð 	ˆDØ—9’9‘;”;ð Ø—’‘”�øØÔ×!Ò!Ñ#Ô#Ð#ØŒ×ÒÑÔÐÐÐr   ©r   N)r*   r+   r,   r-   r/   r.   r   r1   r@   r0   rQ   rM   r`   r   r   r   r3   r3   F   sæ   € € € € € ðð ð. !Ø Ø#ð:ð :ð :ð ð	:ð
 ð:ð ð:ð :ð :ð :ð%˜Dð %ð %ð %ð %ðI˜^ð I°#ð I¸$ð Ið Ið Ið Ið"/ ð /¨ð /ð /ð /ð /ð(ð ð ð ð ð r   r3   z\*\*(.+?)\*\*z	\*(.+?)\*z	__(.+?)__z_(.+?)_z```[a-zA-Z0-9_+-]*\n?z`(.+?)`z
^#{1,6}\s+z\[([^\]]+)\]\([^\)]+\)z\n{3,}rF   r   c                 ó  — t                                d| ¦  «        } t                               d| ¦  «        } t                               d| ¦  «        } t                               d| ¦  «        } t
                               d| ¦  «        } t                               d| ¦  «        } t                               d| ¦  «        } t                               d| ¦  «        } t                               d| ¦  «        } |  
                    ¦   «         S )zÎStrip markdown formatting for plain-text platforms (SMS, iMessage, etc.).

    Replaces the identical ``_strip_markdown()`` functions previously
    duplicated in sms.py, bluebubbles.py, and feishu.py.
    z\1rD   z

)Ú_RE_BOLDÚsubÚ_RE_ITALIC_STARÚ_RE_BOLD_UNDERÚ_RE_ITALIC_UNDERÚ_RE_CODE_BLOCKÚ_RE_INLINE_CODEÚ_RE_HEADINGÚ_RE_LINKÚ_RE_MULTI_NEWLINEÚstrip)rF   s    r   Ústrip_markdownrn   ©   sÉ   € õ �<Š<˜˜tÑ$Ô$€DÝ×Ò˜u dÑ+Ô+€DÝ×Ò˜e TÑ*Ô*€DÝ×Ò  tÑ,Ô,€DÝ×Ò˜b $Ñ'Ô'€DÝ×Ò˜u dÑ+Ô+€DÝ�?Š?˜2˜tÑ$Ô$€DÝ�<Š<˜˜tÑ$Ô$€DÝ× Ò  ¨Ñ.Ô.€DØ�:Š:‰<Œ<Ðr   c                   óp   — e Zd ZdZdZddedefd„Zdefd„Z	de
fd„Zdd
„Zdedd	fd„Zdedefd„Zdd„Zd	S )ÚThreadParticipationTrackeraÉ  Persistent tracking of threads the bot has participated in.

    Replaces the identical ``_load/_save_participated_threads`` +
    ``_mark_thread_participated`` pattern previously duplicated in
    discord.py and matrix.py.

    Usage::

        self._threads = ThreadParticipationTracker("discord")

        # Check membership:
        if thread_id in self._threads:
            ...

        # Mark participation:
        self._threads.mark(thread_id)
    éô  Úplatform_nameÚmax_trackedc                 óT   — || _         || _        |                      ¦   «         | _        d S r   )Ú	_platformÚ_max_trackedÚ_loadÚ_threads)r   rr   rs   s      r   r   z#ThreadParticipationTracker.__init__Ó   s$   € Ø&ˆŒØ'ˆÔØ!ŸZšZ™\œ\ˆŒˆˆr   r   c                 ó8   — ddl m}  |¦   «         | j        › d�z  S )Nr   )Úget_hermes_homez_threads.json)Úhermes_constantsrz   ru   )r   rz   s     r   Ú_state_pathz&ThreadParticipationTracker._state_pathØ   s2   € Ø4Ð4Ð4Ð4Ð4Ð4ØˆÑ Ô  d¤nÐ#CÐ#CÐ#CÑCÐCr   c                 óú   — |                       ¦   «         }|                     ¦   «         rF	 t          t          j        |                     d¬¦  «        ¦  «        ¦  «        S # t          $ r Y nw xY wt          ¦   «         S )Núutf-8©Úencoding)r|   ÚexistsÚsetÚjsonÚloadsÚ	read_textrV   )r   Úpaths     r   rw   z ThreadParticipationTracker._loadÜ   sw   € Ø×ÒÑ!Ô!ˆØ�;Š;‰=Œ=ð 	ðÝ�4œ: d§n¢n¸g nÑ&FÔ&FÑGÔGÑHÔHÐHøÝð ð ð Ø�ðøøøå‰uŒuˆs   ª4A Á
A,Á+A,Nc                 óX  — |                       ¦   «         }|j                             dd¬¦  «         t          | j        ¦  «        }t          |¦  «        | j        k    r$|| j         d …         }t          |¦  «        | _        |                     t          j
        |¦  «        d¬¦  «         d S )NT)ÚparentsÚexist_okr~   r   )r|   ÚparentÚmkdirÚlistrx   r#   rv   r‚   Ú
write_textrƒ   Údumps)r   r†   Úthread_lists      r   Ú_savez ThreadParticipationTracker._saveå   sž   € Ø×ÒÑ!Ô!ˆØŒ×Ò $°ÐÑ6Ô6Ð6Ý˜4œ=Ñ)Ô)ˆÝˆ{ÑÔ˜dÔ/Ò/Ð/Ø% tÔ'8Ð&8Ð&9Ð&9Ô:ˆKÝ Ñ,Ô,ˆDŒMØ�Š�œ
 ;Ñ/Ô/¸'ˆÑBÔBÐBÐBÐBr   Ú	thread_idc                 óx   — || j         vr0| j                              |¦  «         |                      ¦   «          dS dS )z-Mark *thread_id* as participated and persist.N)rx   Úaddr�   ©r   r‘   s     r   ÚmarkzThreadParticipationTracker.markî   s>   € à˜DœMÐ)Ð)ØŒM×Ò˜iÑ(Ô(Ð(Ø�JŠJ‰LŒLˆLˆLˆLð *Ð)r   c                 ó   — || j         v S r   )rx   r”   s     r   Ú__contains__z'ThreadParticipationTracker.__contains__ô   s   € Ø˜DœMÐ)Ð)r   c                 ó8   — | j                              ¦   «          d S r   )rx   r(   r)   s    r   r(   z ThreadParticipationTracker.clear÷   s   € ØŒ×ÒÑÔÐÐÐr   )rq   ra   )r*   r+   r,   r-   Ú_MAX_TRACKEDr0   r.   r   r   r|   r‚   rw   r�   r•   r1   r—   r(   r   r   r   rp   rp   ¾   s÷   € € € € € ðð ð$ €Lð*ð * cð *¸ð *ð *ð *ð *ð
D˜Tð Dð Dð Dð Dð�sð ð ð ð ðCð Cð Cð Cð˜cð  dð ð ð ð ð* cð *¨dð *ð *ð *ð *ðð ð ð ð ð r   rp   Úphonec                 óº   — | sdS t          | ¦  «        dk    r-t          | ¦  «        dk    r| dd…         dz   | dd…         z   ndS | dd…         dz   | dd…         z   S )	z³Redact a phone number for logging, preserving country code and last 4.

    Replaces the identical ``_redact_phone()`` functions in signal.py,
    sms.py, and bluebubbles.py.
    z<none>é   é   Né   z****éþÿÿÿéüÿÿÿ)r#   )rš   s    r   Úredact_phoner¡   þ   ss   € ð ð ØˆxÝ
ˆ5�z„z�Q‚€Ý25°e±*´*¸q².°.ˆu�R�a�RŒy˜6Ñ! E¨"¨#¨#¤JÑ.Ð.ÀfÐLØ��!�Œ9�vÑ  b c c¤
Ñ*Ð*r   )$r-   rK   rƒ   ÚloggingÚrer"   Úpathlibr   Útypingr   r   r   Úgateway.platforms.baser   r   Ú	getLoggerr*   rW   r
   r3   ÚcompileÚDOTALLrc   re   rf   rg   rh   ri   Ú	MULTILINErj   rk   rl   r0   rn   rp   r¡   r   r   r   ú<module>r«      s&  ððð ð €€€Ø €€€Ø €€€Ø 	€	€	€	Ø €€€Ø Ð Ð Ð Ð Ð Ø 0Ð 0Ð 0Ð 0Ð 0Ð 0Ð 0Ð 0Ð 0Ð 0àð IØHÐHÐHÐHÐHÐHÐHÐHà	ˆÔ	˜8Ñ	$Ô	$€ð'ð 'ð 'ð 'ð 'ñ 'ô 'ð 'ðZRð Rð Rð Rð Rñ Rô Rð Rðp ˆ2Œ:Ð&¨¬	Ñ2Ô2€Ø�"”*˜\¨2¬9Ñ5Ô5€Ø�”˜L¨"¬)Ñ4Ô4€Ø�2”:˜j¨"¬)Ñ4Ô4Ð Ø�”Ð4Ñ5Ô5€Ø�"”*˜ZÑ(Ô(€ØˆbŒj˜¨¬Ñ5Ô5€Øˆ2Œ:Ð/Ñ0Ô0€Ø�B”J˜yÑ)Ô)Ð ð˜ð  ð ð ð ð ð*:ð :ð :ð :ð :ñ :ô :ð :ð@
+˜ð 
+ ð 
+ð 
+ð 
+ð 
+ð 
+ð 
+r   