
    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 )zShared 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      A/home/agentuser/.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L41aV!Qr   )timer   r   lenr   items)r   r   nowr    s      @r   is_duplicatez MessageDeduplicator.is_duplicate.   s     	5ikkTZTZ''$)33t
6" 
6tz??T^++49_FLLLL4:+;+;+=+=LLLDJ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!!&DM#'}<<
<<HM'0H$ #'',, 	 	LLNNN#*#6t{{37G7G#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K77$5q999! &.1F%F%F!!DL]mE"""""""""!!#t,, 	ffmmE********** 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99;; !!###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 )zStrip 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ud++DeT**Dt,,Db$''Dud++D??2t$$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$    &'!ZZ\\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  dn#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:dnngn&F&FGGHHH   uu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,,DM
;//'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>    DM))Mi(((JJLLLLL *)r   c                     || j         v S r   )rx   r   s     r   __contains__z'ThreadParticipationTracker.__contains__   s    DM))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 )	zRedact 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
5zzQ25e**q..uRaRy6!E"##J..fL!9vbcc
**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"*\2955L")442:j")44 455"*Z((bj552:/00BJy))      *: : : : : : : :@
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+r   