
    i%                     T   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
 ddlmZ e	rddl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)atomic_json_write)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/ubuntu/.hermes/hermes-agent/gateway/platforms/helpers.py__init__zMessageDeduplicator.__init__+   s    ')
!			    msg_idreturnc                    |sdS t          j                     }|| j        v r#|| j        |         z
  | j        k     rdS | j        |= || j        |<   t          | j                  | j        k    r|| j        z
  fd| j                                        D             | _        t          | j                  | j        k    rKt          | j                                        d           | j         d         }t          |          | _        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   c                     | d         S )N   r   )items    r   <lambda>z2MessageDeduplicator.is_duplicate.<locals>.<lambda>D   s
    T!W r   )keyN)timer   r   lenr   itemssorteddict)r   r   nownewestr   s       @r   is_duplicatez MessageDeduplicator.is_duplicate0   s    	5ikkTZTZ''$)33t
6" 
6tz??T^++49_FLLLL4:+;+;+=+=LLLDJ4://  J$$&&,,   >/""$ "&\\
ur   c                 8    | j                                          dS )zClear all tracked messages.N)r   clearr   s    r   r/   zMessageDeduplicator.clearI   s    
r   N)r
   r   )__name__
__module____qualname____doc__intfloatr   strboolr-   r/   r   r   r   r	   r	      sv              %        
3 4    2    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_thresholdr;   r<   r=   c                Z    || _         || _        || _        || _        i | _        i | _        d S r   )_handler_batch_delay_split_delay_split_threshold_pending_pending_tasks)r   handlerr;   r<   r=   s        r   r   zTextBatchAggregator.__init__e   s8      '' /3579r   r   c                     | j         dk    S )z.Return True if batching is active (delay > 0).r   )r@   r0   s    r   
is_enabledzTextBatchAggregator.is_enabledt   s     1$$r   eventr   r%   Nc                    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'   textrC   get_last_chunk_lenrD   donecancelasynciocreate_task_flush)r   rH   r%   	chunk_lenexistingpriors         r   enqueuezTextBatchAggregator.enqueuex   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*.rN   r   Nz<[TextBatchAggregator] Error dispatching batched event for %s)rD   rM   rC   getattrrB   rA   r@   rQ   sleeppopr?   	Exceptionlogger	exception)r   r%   current_taskpendinglast_lendelayrH   s          r   rS   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)rD   valuesrO   rP   r/   rC   )r   tasks     r   
cancel_allzTextBatchAggregator.cancel_all   sm    '..00 	 	D99;; !!###r   r   N)r1   r2   r3   r4   r6   r5   r   r8   rG   r7   rW   rS   rf   r   r   r   r:   r:   Q   s         . ! #: : : 	:
 : : : : :%D % % % %I^ I# I$ I I I I"/ / / / / /(     r   r:   z\*\*(.+?)\*\*z	\*(.+?)\*z	__(.+?)__z_(.+?)_z```[a-zA-Z0-9_+-]*\n?z`(.+?)`z
^#{1,6}\s+z\[([^\]]+)\]\([^\)]+\)z\n{3,}rL   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\1rJ   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)rL   s    r   strip_markdownrt      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   rx   ry   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_constantsr   r{   )r   r   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 )Nzutf-8)encoding)r   existssetjsonloads	read_textr\   )r   paths     r   r}   z ThreadParticipationTracker._load   sw    !!;;== 	4:dnngn&F&FGGHHH   uus   4A 
A,+A,Nc                     |                                  }t          | j                  }t          |          | j        k    r$|| j         d          }t          |          | _        t          ||d            d S )N)indent)r   listr~   r'   r|   r   r   )r   r   thread_lists      r   _savez ThreadParticipationTracker._save   sx    !!4=)){d///%t'8&8&9&9:K,,DM$D999999r   	thread_idc                 x    || j         vr0| j                             |           |                                  dS dS )z-Mark *thread_id* as participated and persist.N)r~   addr   r   r   s     r   markzThreadParticipationTracker.mark   s>    DM))Mi(((JJLLLLL *)r   c                     || j         v S r   )r~   r   s     r   __contains__z'ThreadParticipationTracker.__contains__   s    DM))r   c                 8    | j                                          d S r   )r~   r/   r0   s    r   r/   z ThreadParticipationTracker.clear  s    r   )rw   rg   )r1   r2   r3   r4   _MAX_TRACKEDr7   r5   r   r   r   r   r}   r   r   r8   r   r/   r   r   r   rv   rv      s         $ L* *c * * * * *
DT D D D Ds    : : : :c d    *c *d * * * *     r   rv   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   )$r4   rQ   r   loggingrer&   pathlibr   typingr   r   utilsr   gateway.platforms.baser   	getLoggerr1   r]   r	   r:   compileDOTALLri   rk   rl   rm   rn   ro   	MULTILINErp   rq   rr   r7   rt   rv   r   r   r   r   <module>r      s+       				        & & & & & & & & # # # # # # 4333333		8	$	$0 0 0 0 0 0 0 0l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))      *9 9 9 9 9 9 9 9~
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+r   