
    iz&                     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/piyush/.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                   |    e Zd ZdZdZddedefdZdefdZ	de
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                 h    || _         || _        d |                                 D             | _        d S )Nc                 .    i | ]}t          |          d S r   r7   r   	thread_ids     r   r    z7ThreadParticipationTracker.__init__.<locals>.<dictcomp>   s-     *
 *
 *
%.C	NND*
 *
 *
r   )	_platform_max_tracked_load_threads)r   rx   ry   s      r   r   z#ThreadParticipationTracker.__init__   s=    &'*
 *
26**,,*
 *
 *
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                    |                                  }|                                r[	 t          j        |                    d                    }t          |t                    rd |D             S n# t          $ r Y nw xY wg S )Nzutf-8)encodingc                 ,    g | ]}t          |          S r   r|   r}   s     r   
<listcomp>z4ThreadParticipationTracker._load.<locals>.<listcomp>   s    AAAyC	NNAAAr   )r   existsjsonloads	read_text
isinstancelistr\   )r   pathdatas      r   r   z ThreadParticipationTracker._load   s    !!;;== 	z$..'."B"BCCdD)) BAADAAAAB   	s   AA4 4
B BNc                     |                                  }t          | j                  }t          |          | j        k    r!|| j         d          }d |D             | _        t          ||d            d S )Nc                     i | ]}|d S r   r   r}   s     r   r    z4ThreadParticipationTracker._save.<locals>.<dictcomp>   s    JJJYJJJr   )indent)r   r   r   r'   r   r   )r   r   thread_lists      r   _savez ThreadParticipationTracker._save   s~    !!4=)){d///%t'8&8&9&9:KJJkJJJDM$D999999r   r~   c                 X    || j         vr d| j         |<   |                                  dS dS )z-Mark *thread_id* as participated and persist.N)r   r   r   r~   s     r   markzThreadParticipationTracker.mark   s4    DM))'+DM)$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	tCy 	 	 	 	: : : :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))      *= = = = = = = =F
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+r   