
    ig                   `   U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	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mZ ddlmZ ddlmZ ddlmZ ddlmZmZmZmZ dd	lmZmZ dd
lm Z  ddl!m"Z"m#Z# 	 ddl$Z$ddl$m%Z% n# e&$ r dZ$dZ%Y nw xY w	 ddl'Z'n# e&$ r dZ'Y nw xY w	 ddl(Z)ddl*m+Z+ ddl,m-Z-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9m:Z: ddl;m<Z<m=Z= ddl>m?Z?m@Z@ ddlAmBZB ddlCmDZE dZFn# e&$ r dZFdZ)dZ?dZ@dZBdZEdZ<dZ=Y nw xY we'duZGe$duZHddlImJZJmKZK ddlLmMZMmNZNmOZOmPZPmQZQmRZRmSZSmTZTmUZU ddlVmWZWmXZX ddlYmZZZ  ej[        e\          Z] ej^        dej_                  Z` ej^        d          Za ej^        d          Zb ej^        d          Zc ej^        dejd                  Zeh dZfh dZgh d Zhd!  eQji                    D             Zjd"Zkd#Zld$d%hZmh d&Znd'd(d(d)d)d*d*d+Zod,Zpd-Zqd-Zrd.Zsd/Ztd0Zud1Zvd2Zwd3Zxd4Zyd5Zzd6Z{d7Z|d8Z}d9Z~d:Zd;Zd<Zd=Zd>Zd?Zd@ZdAdBdCdDdEZdFedG<   dHdIdJdKdLZdFedM<   dNZ edOdPh          ZdQZdRdSdTZdUdVdTZdWZdXZdYZdZZd[Zd\Zd]Zd^Zd_Z ej^        d`          Z ej^        d          Z ej^        da          ZdbZh dcZ edd           G de df                      Z edd           G dg dh                      Z edd           G di dj                      Z edd           G dk dl                      Ze G dm dn                      Ze G do dp                      ZddtZddxZdd|Zdd}ZddZddZddZddZdddZdddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZ G d deM          ZddZddZddZdddÄZdddĄZddŜddʄZ	 ddlZn# e&ef$ r dZY nw xY wdd̄Zѐd dτZҐddЄZӐdd҄ZԐd dӄZՐd dԄZdd8d՜dd؄ZאddلZdS (  a  
Feishu/Lark platform adapter.

Supports:
- WebSocket long connection and Webhook transport
- Direct-message and group @mention-gated text receive/send
- Inbound image/file/audio/media caching
- Gateway allowlist integration via FEISHU_ALLOWED_USERS
- Persistent dedup state across restarts
- Per-chat serial message processing (matches openclaw createChatQueue)
- Persistent ACK emoji reaction on inbound messages
- Reaction events routed as synthetic text events (matches openclaw)
- Interactive card button-click events routed as synthetic COMMAND events
- Webhook anomaly tracking (matches openclaw createWebhookAnomalyTracker)
- Verification token validation as second auth layer (matches openclaw)
    )annotationsN)	dataclassfield)datetime)Path)SimpleNamespace)AnyDictListOptional)	HTTPErrorURLError)	urlencode)Requesturlopen)web)GetApplicationRequest)CreateFileRequestCreateFileRequestBodyCreateImageRequestCreateImageRequestBodyCreateMessageRequestCreateMessageRequestBodyGetChatRequestGetMessageRequestGetMessageResourceRequestP2ImMessageMessageReadV1ReplyMessageRequestReplyMessageRequestBodyUpdateMessageRequestUpdateMessageRequestBody)FEISHU_DOMAINLARK_DOMAIN)CallBackCardP2CardActionTriggerResponse)EventDispatcherHandler)ClientTF)PlatformPlatformConfig)	BasePlatformAdapterMessageEventMessageType
SendResultSUPPORTED_DOCUMENT_TYPEScache_document_from_bytescache_image_from_urlcache_audio_from_bytescache_image_from_bytes)acquire_scoped_lockrelease_scoped_lock)get_hermes_homez(^#{1,6}\s)|(^\s*[-*]\s)|(^\s*\d+\.\s)|(^\s*---+\s*$)|(```)|(`[^`\n]+`)|(\*\*[^*\n].+?\*\*)|(~~[^~\n].+?~~)|(<u>.+?</u>)|(\*[^*\n]+\*)|(\[[^\]]+\]\([^)]+\))|(^>\s)z\[([^\]]+)\]\(([^)]+)\)z
@_user_\d+z	[ \t]{2,}z,content format of the post type is incorrect>   .bmp.png.webp.gif.jpg.jpeg>   .aac.m4a.mp3.wav.flac.ogg.opus.webm>   .3gp.mkv.avi.m4v.mov.mp4rC   c                    i | ]\  }}||	S  rK   ).0extmimes      @/home/agentuser/.hermes/hermes-agent/gateway/platforms/feishu.py
<dictcomp>rP      s    UUUysDsUUU    messagestreamrA   rB   >   rF   rG   rH   rI   pdfdocxlsppt)z.pdfz.docz.docxz.xlsz.xlsxz.pptz.pptxi     zfeishu-app-idg333333?     g?i   z	127.0.0.1i="  z/feishu/webhookiQ X  i   <   x   i         i`T  i  oncesessionalwaysdeny)approve_onceapprove_sessionapprove_alwaysrc   Dict[str, str]_APPROVAL_CHOICE_MAPzApproved oncezApproved for sessionzApproved permanentlyDenied)r`   ra   rb   rc   _APPROVAL_LABEL_MAPi   i{ i[ OKzhttps://accounts.feishu.cnzhttps://accounts.larksuite.com)feishularkzhttps://open.feishu.cnzhttps://open.larksuite.comz/oauth/v1/app/registration
   z[Rich text message]z[Merged forward message]z[Shared chat]z[Interactive message][Image][Attachment])zh_cnen_usz([\\`*_{}\[\]()#+\-!|>~])z\s+)titletextcontentlabelvaluenamesummarysubtitledescriptionplaceholderhint>   tagurlhreflinktypetokenlocalechat_idopen_iduser_idfile_keymsg_typetemplateunion_id	image_keymessage_typeopen_chat_idshare_chat_id)frozenc                  6    e Zd ZU ded<   dZded<   dZded<   dS )FeishuPostMediaRefstrr    	file_namefileresource_typeN)__name__
__module____qualname____annotations__r   r   rK   rQ   rO   r   r     s=         MMMIMrQ   r   c                      e Zd ZU ded<    ee          Zded<    ee          Zded<    ee          Zded<   d	S )
FeishuPostParseResultr   text_contentdefault_factory	List[str]
image_keysList[FeishuPostMediaRef]
media_refsmentioned_idsN)	r   r   r   r   r   listr   r   r   rK   rQ   rO   r   r   	  sx         !E$777J7777+05+F+F+FJFFFF$uT:::M::::::rQ   r   c                      e Zd ZU ded<   ded<   dZded<    ee          Zded<    ee          Zd	ed
<    ee          Z	ded<   dZ
ded<    ee          Zded<   dS )FeishuNormalizedMessager   raw_typer   rt   preferred_message_typer   r   r   r   r   r   plainrelation_kindDict[str, Any]metadataN)r   r   r   r   r   r   r   r   r   r   r   dictr   rK   rQ   rO   r   r     s         MMM"(((((!E$777J7777+05+F+F+FJFFFF$uT:::M:::: M    $uT:::H::::::rQ   r   c                  ^   e Zd ZU ded<   ded<   ded<   ded<   ded<   ded<   ded<   d	ed
<   ded<   ded<   ded<   ded<   ded<   ded<   ded<   ded<   ded<   ded<   ded<   ded<   dZded<   dZded<   dZded<   dZded <    e            Z	d	ed!<   d"Z
ded#<    ee$          Zd%ed&<   dS )'FeishuAdapterSettingsr   app_id
app_secretdomain_nameconnection_modeencrypt_keyverification_tokengroup_policyzfrozenset[str]allowed_group_usersbot_open_idbot_user_idbot_nameintdedup_cache_sizefloattext_batch_delay_secondstext_batch_split_delay_secondstext_batch_max_messagestext_batch_max_charsmedia_batch_delay_secondswebhook_hostwebhook_portwebhook_pathr^   ws_reconnect_noncer]   ws_reconnect_intervalNOptional[int]ws_ping_intervalws_ping_timeoutadminsr   default_group_policyr   zDict[str, FeishuGroupRule]group_rules)r   r   r   r   r   r   r   r   	frozensetr   r   r   r   r   rK   rQ   rO   r   r     s        KKKOOO''''MMM####))))    $$$$     !$$$$$&*****%)O))))&Y[[F(((( """"".3eD.I.I.IKIIIIIIrQ   r   c                  b    e Zd ZU dZded<    ee          Zded<    ee          Zded<   dS )	FeishuGroupRulezLPer-group policy rule for controlling which users may interact with the bot.r   policyr   set[str]	allowlist	blacklistN)	r   r   r   __doc__r   r   setr   r   rK   rQ   rO   r   r   <  s]         VVKKK%444I4444%444I444444rQ   r   c                  v    e Zd ZU  ee          Zded<    ee          Zded<    ee          Zded<   dS )	FeishuBatchStater   zDict[str, MessageEvent]eventsDict[str, asyncio.Task]taskszDict[str, int]countsN)	r   r   r   r   r   r   r   r   r   rK   rQ   rO   r   r   E  sl         &+eD&A&A&AFAAAA%*U4%@%@%@E@@@@"U4888F888888rQ   r   rt   r   returnc                8    t                               d|           S )Nz\\\1)_MARKDOWN_SPECIAL_CHARS_REsub)rt   s    rO   _escape_markdown_textr   Q  s    %))'4888rQ   rw   r	   boolc                "    | du p| dk    p| dk    S )NT   truerK   rw   s    rO   _to_booleanr   U  s    D=9EQJ9%6/9rQ   styleDict[str, Any] | Nonekeyc                N    | sdS t          |                     |                    S NF)r   get)r   r   s     rO   _is_style_enabledr   Y  s'     uuyy~~&&&rQ   c                    t          dgd t          j        d|           D                       }d|dz   z  }|                     d          s|                     d          rd|  dn| }| | | S )Nr   c                ,    g | ]}t          |          S rK   )len)rL   runs     rO   
<listcomp>z%_wrap_inline_code.<locals>.<listcomp>`  s    DDDSCDDDrQ   z`+`r    )maxrefindall
startswithendswith)rt   max_runfencebodys       rO   _wrap_inline_coder   _  s    1EDDBJud,C,CDDDEFFG7Q;E//#..N$--2D2DN;t;;;;$D"T"5"""rQ   languagec                z    |                                                      dd                              dd          S )N
r   )stripreplace)r  s    rO   _sanitize_fence_languager  f  s2    >>##D#..66tSAAArQ   elementr   c                   t          |                     dd          pd          }|                     d          }t          |t                    r|nd }t	          |d          rt          |          S t          |          }|sdS t	          |d          rd| d}t	          |d          rd| d}t	          |d	          rd
| d}t	          |d          rd| d}|S )Nrt   r   r   codeboldz**italic*	underlinez<u>z</u>strikethroughz~~)r   r   
isinstancer   r   r   r   )r  rt   r   
style_dictrendereds        rO   _render_text_elementr  j  s   w{{62&&,"--DKK  E$UD11;tJV,, ' &&&$T**H rV,, %$$$$X.. #"x???[11 (''''_55 %$$$$OrQ   c                   t          t          |                     dd          pd          p$t          |                     dd          pd                    }t          |                     dd          pd          p$t          |                     dd          pd                              dd          }|                    d          rdnd}d| d| | dS )	Nr  r   langrt   ru   
r  z```)r  r   r   r  r   )r  r  r
  trailing_newlines       rO   _render_code_block_elementr    s    'GKK
B''-2..T#gkk&"6M6M6SQS2T2T H 	GKK##)r**Sc'++i2L2L2RPR.S.Sgfd 	 "]]400:rrd888T8#38888rQ   c                l   ddl m} |                     dd          }t                              d |          }t          j        dd|t
          j                  }t          j        d	d
|t
          j                  }t          j        dd|          }t          j        dd|          } ||          }|S )a  Strip markdown formatting to plain text for Feishu text fallbacks.

    Delegates common markdown stripping to the shared helper and adds
    Feishu-specific patterns (blockquotes, strikethrough, underline tags,
    horizontal rules, \r\n normalisation).
    r   )strip_markdownr  r  c                    |                      d           d|                      d                                           dS )Nr   z (   ))groupr  )ms    rO   <lambda>z/_strip_markdown_to_plain_text.<locals>.<lambda>  s7    qwwqzz,R,RQWWQZZ=M=M=O=O,R,R,R rQ   z^>\s?r   )flagsz^\s*---+\s*$z---z~~([^~\n]+)~~z\1z<u>([\s\S]*?)</u>)gateway.platforms.helpersr  r  _MARKDOWN_LINK_REr   r   	MULTILINE)rt   r  r   s      rO   _strip_markdown_to_plain_textr%    s     988888LL&&E!!"R"RTYZZEF8Rbl;;;EF?E5EEEEF#UE22EF'66EN5!!ELrQ   defaultr   	min_valuer   c                j    	 t          |           }n# t          t          f$ r |cY S w xY w||k    r|n|S )zACoerce value to int with optional default and minimum constraint.)r   	TypeError
ValueErrorrw   r&  r'  parseds       rO   _coerce_intr-    sQ    Uz"   y((66g5s    ((c                2    t          | ||          }||n|S )Nr&  r'  )r-  r+  s       rO   _coerce_required_intr0    s$    9EEEFn77&0rQ   ru   c                @    t          j        ddd| dggiid          S )Nrq   ru   md)r~   rt   Fensure_ascii)jsondumps)ru   s    rO   _build_markdown_post_payloadr7    sL    : $($+ 		
    rQ   payloadc                   t          |           }|st          t                    S g g g g }t          t	          |                    dd                                                              }|r|                    |           |                    dg           pg D ]_}t          |t                    st          d
                    fd|D                                 }|r|                    |           `t          d
                    |                                          pt                    S )N)r   rs   r   ru   c              3  <   K   | ]}t          |          V  d S N)_render_post_element)rL   itemr   r   r   s     rO   	<genexpr>z,parse_feishu_post_payload.<locals>.<genexpr>  s3      ffZ^(z:}UUffffffrQ   r  )r   r   r   r   )_resolve_post_payloadr   FALLBACK_POST_TEXT_normalize_feishu_textr   r   r  appendr  r   join)	r8  resolvedpartsrs   rowrow_textr   r   r   s	         @@@rO   parse_feishu_post_payloadrH    sY   $W--H F$2DEEEEJ+-J!ME"3x||GR'@'@#A#A#G#G#I#IJJE U||Ir**0b # ##t$$ 	)GGffffffbefffff
 
  	#LL""" YYu%%++--C1C#	   rQ   c                    t          |           }|r|S t          | t                    si S |                     d          }t	          |          }|r|S t	          |           S )Npost)_to_post_payloadr  r   r   _resolve_locale_payload)r8  directwrappedwrapped_directs       rO   r?  r?    sm    g&&F gt$$ 	kk&!!G,W55N "7+++rQ   c                   t          |           }|r|S t          | t                    si S t          D ]*}t          |                     |                    }|r|c S +|                                 D ]}t          |          }|r|c S i S r;  )rK  r  r   _PREFERRED_LOCALESr   values)r8  rM  r   	candidaterw   s        rO   rL  rL    s    g&&F gt$$ 	!  $W[[%5%566	 		!!  $U++	 		IrQ   rS  c                    t          | t                    si S |                     d          }t          |t                    si S t	          |                     dd          pd          |dS )Nru   rs   r   )rs   ru   )r  r   r   r   r   )rS  ru   s     rO   rK  rK    sq    i&& 	mmI&&Ggt$$ 	Y]]7B//5266  rQ   r   r   r   r   r   c                J   t          | t                    r| S t          | t                    sdS t          |                     dd                                                                                    }|dk    rt          |           S |dk    rt          |                     dd                                                    }t          |                     d|          pd                                          }|sdS t          |          }|r	d| d| dn|S |d	k    r<t          |                     d
d                                                    p4t          |                     dd                                                    }|r||vr|                    |           t          |                     dd                                                    pkt          |                     dd                                                    p6t          |                     dd                                                    p|}	|	rdt          |	           ndS |dv rt          |                     dd                                                    }
|
r|
|vr|                    |
           t          |                     dd                                                    p4t          |                     dd                                                    }|rd| dndS |dv rt          |                     dd                                                    }t          |                     dd                                                    pit          |                     dd                                                    p4t          |                     dd                                                    }|r+|                    t          |||dv r|nd                     |rd| dndS |dv rt          |                     dd                                                    p4t          |                     dd                                                    }|rd t          |           d nd!S |d"k    rd#S |d$v rd%S |d&k    r]t          |                     dd          pd          p$t          |                     d'd          pd          }|rt          |          ndS |d(v rt          |           S g }d)D ]@}|                     |          }t          ||||          }|r|                    |           Ad*                    d+ |D                       S ),Nr   r~   rt   ar   [z](r  atr   r   	user_namerx   @>   imgimager   altz[Image: ]ro   >   r   audiomediavideor   r   rs   >   r_  ra  r   r   r   r   [Attachment: rp   >   emojiemotion
emoji_type:z[Emoji]brr  >   hrdividerz

---

r
  ru   >   pre
code_block)rt   rs   ru   childrenelementsr   c              3     K   | ]}||V  	d S r;  rK   )rL   parts     rO   r>  z'_render_post_element.<locals>.<genexpr>Y  s'      ::TT:D::::::rQ   )r  r   r   r   r  lowerr  r   rB  r   r   r  _render_nested_postrC  )r  r   r   r   r~   r   rv   escaped_labelmentioned_iddisplay_namer   r]  r   r   r
  nested_partsr   rw   	extracteds                      rO   r<  r<    s    '3 gt$$ r
gkk%$$
%
%
+
+
-
-
3
3
5
5C
f}}#G,,,
czz7;;vr**++1133GKK--344::<< 	2-e44/3F+=++D++++F
d{{Ir**++1133 77;;y"--..4466 	  	/L==  ...K,,--3355 7;;vr**++11337;;vr**++1133 	 	 =IQ8(66888cQ
K4455;;==	 	)*44i((('++fb))**0022Yc'++eR:P:P6Q6Q6W6W6Y6Y$'6 #    Y6
111w{{:r223399;;K,,--3355 47;;w++,,224447;;vr**++1133 	
  	"%'),0B)B)B##     09L+y++++nL
"""GKK++,,2244bGKKVX<Y<Y8Z8Z8`8`8b8b6;J2(//2222J
d{{t
}
f}}7;;vr**0b11ZSYPR9S9S9YWY5Z5Z*.6 &&&B6
###)'222 LC + +C  'z:}UU	 	+	***88::\::::::rQ   c                   t          | t                    rt          |           S t          | t                    r#d                    fd| D                       S t          | t
                    rKt          |           }|r|S d                    fd|                                 D                       S dS )Nr   c              3  D   K   | ]}t          |          }||V  d S r;  rr  rL   r=  rp  r   r   r   s      rO   r>  z&_render_nested_post.<locals>.<genexpr>e  P       
 
,T:z=YY	

 
 
 
 
 
rQ   c              3  D   K   | ]}t          |          }||V  d S r;  rz  r{  s      rO   r>  z&_render_nested_post.<locals>.<genexpr>o  r|  rQ   r   )r  r   r   r   rC  r   r<  rR  )rw   r   r   r   rM  s    ``` rO   rr  rr  \  s    % ,$U+++% 
xx 
 
 
 
 
 

 
 
 
 
 	
 % 	
%eZ]SS 	Mxx 
 
 
 
 
 

 
 
 
 
 	
 2rQ   r   raw_contentc                   t          | pd                                                                          }t          |          }|dk    rAt	          |t          t          |                    dd          pd                              S |dk    r_t          |          }t	          ||j        t          |j
                  t          |j                  t          |j                  d          S |dk    rt          |                    dd          pd                                          }t          t          |                    dd          pd          p+t          |                    dd          pd          pt                    }t	          ||t          k    r|ndd	|r|gng d
          S |dv rNt          ||          }t          |j                  }t	          |d|dk    rdnd|j        r|gng |d|i          S |dk    rt%          |          S |dk    rt'          |          S |dv rt)          ||          S t	          |d          S )Nr   rt   )r   r   rJ  )r   r   r   r   r   r   r\  r   r]  photo)r   r   r   r   r   >   r   r_  r`  )r   r_  documentplaceholder_text)r   r   r   r   r   r   merge_forward
share_chat>   cardinteractive)r   r  rq  _load_feishu_payloadr   rA  r   rH  r   r   r   r   r   FALLBACK_IMAGE_TEXT_build_media_ref_from_payload_attachment_placeholderr   r    _normalize_merge_forward_message_normalize_share_chat_message_normalize_interactive_message)	r   r~  normalized_typer8  parsed_postr   alt_text	media_refr|   s	            rO   normalize_feishu_messager  }  s   ,,"--3355;;==O";//G&  &$/GKK4K4K4Qr0R0RSS
 
 
 	
 &  /88&$$1K233K233{899 
 
 
 	
 '!!K44:;;AACC	)FB''-2.. #7;;ub))/R00#"
 

 '$%-1D%D%D"#*&/7	{{R!
 
 
 	
 4441'YYY	-i.ABB&$.=.H.H77j&/&8@	{{b)(+6
 
 
 	
 /))/888,&&,W555111-owGGG"O"MMMMrQ   c                    	 | rt          j        |           ni }n# t           j        $ r d| icY S w xY wt          |t                    r|nd|iS )Nrt   ru   )r5  loadsJSONDecodeErrorr  r   )r~  r,  s     rO   r  r    so    %,7?K(((R % % %$$$$%--F66Iv3FFs    11c           	        t          |                     d          |                     d          |                     d          t          | d                    }t          |           }g }|r|                    |           |                    |d d                    d                    |                                          pt          }t          d|dt          |          |d	
          S )Nrs   ry   preview)rs   ry   r  r{   keysrY   r  r  )entry_countrs   r   r   r   r   )_first_non_empty_textr   _find_first_text_collect_forward_entriesrB  extendrC  r  FALLBACK_FORWARD_TEXTr   r   )r8  rs   entrieslinesr   s        rO   r  r    s    !GII'UVVV	 E 'w//GE U	LL!99U##))++D/DL" !%!$W>>	   rQ   c           	     B   t          |                     d          |                     d          |                     d          t          | d                    }t          |                     d          |                     d          |                     d                    }g }|r|                    d	|            n|                    t                     |r|                    d
|            d                    |          }t          d|d||d          S )N	chat_namerx   rs   )r  rx   rs   r  r   r   r   zShared chat: z	Chat ID: r  r  )r   r  r  )r  r   r  rB  FALLBACK_SHARE_CHAT_TEXTrC  r   )r8  r  share_idr  r   s        rO   r  r    s,   %K  FG'EFFF	 I %IN##O$$ H
 E /0Y001111-... -+++,,,99U##L"!"%I>>	   rQ   c                   t          |                    d          t                    r|                    d          n|}t          t	          |          |                    d          t          |d                    }t          |          }t          |          }g }|r|                    |           |D ]}||k    r|                    |           |r+|                    dd	                    |                      d	                    |d d                   
                                pt          }t          | |d	||d
          S )Nr  rs   )rs   ry   rz   r  z	Actions: z, r     r  )rs   actionsr  )r  r   r   r  _find_header_titler  _collect_card_lines_collect_action_labelsrB  rC  r  FALLBACK_INTERACTIVE_TEXTr   )	r   r8  card_payloadrs   
body_linesr  r  liner   s	            rO   r  r    sY   *4W[[5H5H$*O*O\7;;v&&&U\L!<((G,LMMM E
 %\22J$\22GE U  5==LL 757!3!35566699U3B3Z((..00M4ML"!# W55	   rQ   c                   g }dD ]A}|                      |          }t          |t                    r|                    |           Bg }|D ]}t          |t                    s9t          t          |pd                    }|r|                    d|            Qt          |                     d          |                     d          |                     d          |                     d                    }t          |                     dd          p|                     d	d                    	                                
                                }|d
k    r*t          |                     d          p|          j        }	nnt          |                     d          |                     d          |                     d          |                     d          t          |d                    }	t          |	          }	|r|	r|                    d| d|	            |	r|                    d|	            t          |          S )N)messagesitemsmessage_listrecordsru   r   z- sender_namerY  senderrx   r   r   rJ  ru   rt   ry   r  )rt   ru   ry   r  rs   r  z: )r   r  r   r  r   rA  r   rB  r  r  rq  rH  r   r  _unique_lines)
r8  
candidatesr   rw   r  r=  rt   r  nested_typer   s
             rO   r  r    s>   JJ % %C  eT"" 	%e$$$G ( ($%% 	)#djb//::D ,{D{{+++&HH]##HH[!!HHXHHV	
 
 $((>266R$((:r:R:RSSYY[[aacc&  ,TXXi-@-@-HDIIVDD(  ###### ,^___ D &d++ 	(d 	(NN000$001111 	(NN;;;'''!!!rQ   c                n    t          | d          }d |D             }t          d |D                       S )NFin_rich_blockc                ,    g | ]}t          |          S rK   )rA  rL   r  s     rO   r   z'_collect_card_lines.<locals>.<listcomp>5  s!    AAA4(..AAArQ   c                    g | ]}||S rK   rK   r  s     rO   r   z'_collect_card_lines.<locals>.<listcomp>6  s    >>>4>$>>>rQ   )_collect_text_segmentsr  )r8  r  
normalizeds      rO   r  r  3  sD    "7%@@@EAA5AAAJ>>:>>>???rQ   c           
        g }t          |           D ]}t          |t                    st          |                    dd          p|                    dd                                                                                    }|dvrzt          |                    d          |                    d          |                    d          t          |d	                    }|r|	                    |           t          |          S )
Nr~   r   r   >   buttonpickeroverflowdate_pickerselect_staticrt   rx   rw   )rt   ru   rx   rw   r  )_walk_nodesr  r   r   r   r  rq  r  r  rB  r  )r8  labelsr=  r~   rv   s        rO   r  r  9  s    FG$$ ! !$%% 	$((5"%%=&")=)=>>DDFFLLNNVVV%HHVHHVHHWT(LMMM	
 
  	!MM%      rQ   r  c                  t          | t                    r|rt          |           gng S t          | t                    r-g }| D ]&}|                    t          ||                     '|S t          | t                    sg S t          |                     dd          p|                     dd                                                    	                                }|p|dv }g }t          D ]T}|                     |          }t          |t                    r(|r&t          |          }|r|                    |           U|                                 D ]3\  }}|t          v r|                    t          ||                     4|S )Nr  r~   r   r   >   divnoteactionr  columnlark_mdmarkdown
column_set
plain_textr  r  )r  r   rA  r   r  r  r   r   r  rq  _SUPPORTED_CARD_TEXT_KEYSrB  r  _SKIP_TEXT_KEYS)rw   r  segmentsr=  r~   next_in_rich_blockr   r  s           rO   r  r  L  s   % H2?G&u--..RG%   	W 	WDOO24}UUUVVVVeT"" 	
eiir"";eii&;&;
<
<
B
B
D
D
J
J
L
LC& # 2 + H( , ,yy~~dC   	,%7 	,/55J ,
+++[[]] X X	T/!!.tCUVVVWWWWOrQ   r   c               8   t          |                     dd          pd                                          }t          |                     d          |                     d          |                     d                    }|dv r|nd}t	          |||          S )	Nr   r   r   rs   rt   >   r_  ra  r   rb  )r   r   r  r  r   )r8  r   r   r   effective_types        rO   r  r  u  s    7;;z2..4"55;;==H%K  GF I
 '47I&I&I]]vNx9TbccccrQ   r   c                >    t          |           }|rd| dnt          S )Nrc  r^  )rA  FALLBACK_ATTACHMENT_TEXT)r   normalized_names     rO   r  r    s,    ,Y77O1@^-?----F^^rQ   c                   t          | t                    sdS |                     d          }t          |t                    sdS |                    d          }t          |t                    rJt          |                    d          |                    d          |                    d                    S t	          t          |pd                    S )Nr   headerrs   ru   rt   rx   )r  r   r   r  rA  r   )r8  r  rs   s      rO   r  r    s    gt$$ r[[""Ffd## rJJwE% a$UYYy%9%9599V;L;LeiiX^N_N_```!#ekr"2"2333rQ   r  tuple[str, ...]c                   t          |           D ]^}t          |t                    s|D ]C}|                    |          }t          |t                    rt          |          }|r|c c S D_dS Nr   )r  r  r   r   r   rA  )r8  r  noder   rw   r  s         rO   r  r    s    G$$ & &$%% 	 	& 	&CHHSMME%%% &3E::
 &%%%%%%	& 2rQ   c              #     K   t          | t                    r2| V  |                                 D ]}t          |          E d {V  d S t          | t                    r| D ]}t          |          E d {V  d S d S r;  )r  r   rR  r  r   )rw   r=  s     rO   r  r    s      % )LLNN 	) 	)D"4((((((((((	) 	)	E4	 	  ) 	) 	)D"4(((((((((() )	) 	)rQ   rR  c                     | D ]m}t          |t                    rt          |          }|r|c S -|>t          |t          t          f          s"t          t          |                    }|r|c S ndS r  )r  r   rA  r   r   )rR  rw   r  s      rO   r  r    s     " "eS!! 	"/66J "!!!!"z%$'F'F/E

;;J "!!!!2rQ   c                   t                               d| pd          }|                    dd                              dd          }d                    d |                    d          D                       }d                    d |                    d          D                       }t
                              d|          }|                                S )Nr   r   r  r  r  c              3  p   K   | ]1}t                               d |                                          V  2dS )r   N)_WHITESPACE_REr   r  r  s     rO   r>  z)_normalize_feishu_text.<locals>.<genexpr>  s>      ^^$**355;;==^^^^^^rQ   c              3     K   | ]}||V  	d S r;  rK   r  s     rO   r>  z)_normalize_feishu_text.<locals>.<genexpr>  s'      EEEEEEEEErQ   )_MENTION_PLACEHOLDER_REr   r  rC  split_MULTISPACE_REr  )rt   cleaneds     rO   rA  rA    s    %))#tzr::Goofd++33D$??Gii^^'--X\J]J]^^^^^GiiEEt)<)<EEEEEG  g..G==??rQ   r  c                    t                      }g }| D ]3}|r||v r	|                    |           |                    |           4|S r;  )r   addrB  )r  seenuniquer  s       rO   r  r    s[    UUDF   	tt||dMrQ   	ws_clientadapterNonec           	        	
 ddl mc m} t          j                    }t          j        |           ||_        |_        |j        j	        
t           dd          	d fdd
fd
}d	fd}||j        _	        	t           d|                         	                                   n# t          $ r Y nw xY w
|j        _	        	t           d	           d t          j        |          D             }|D ]}|                                 |r$|                    t          j        |ddi           	 |                                 n# t          $ r Y nw xY w	 |                                 n# t          $ r Y nw xY wd_        dS # 
|j        _	        	t           d	           d t          j        |          D             }|D ]}|                                 |r$|                    t          j        |ddi           	 |                                 n# t          $ r Y nw xY w	 |                                 n# t          $ r Y nw xY wd_        w xY w)zCRun the official Lark WS client in its own thread-local event loop.r   N
_configurer   r  c                     	 t          d j                   t          d j                    j        t          d j                   d S d S # t          $ r  t
                              dd           Y d S w xY w)N_reconnect_nonce_reconnect_interval_ping_intervalz4[Feishu] Failed to apply websocket runtime overridesTexc_info)setattr_ws_reconnect_nonce_ws_reconnect_interval_ws_ping_interval	Exceptionloggerdebug)r  r  s   rO   _apply_runtime_ws_overrideszC_run_official_feishu_ws_client.<locals>._apply_runtime_ws_overrides  s    	`I173NOOOI4g6TUUU(4	#3W5NOOOOO 54 	` 	` 	`LLOZ^L______	`s   A	A &A:9A:argsr	   kwargsc                 x   K   j         d|vr
j         |d<   j        d|vr
j        |d<    | i | d {V S )Nping_intervalping_timeout)r  _ws_ping_timeout)r  r  r  original_connects     rO   _connect_with_overridesz?_run_official_feishu_ws_client.<locals>._connect_with_overrides  so      $0_F5R5R&-&?F?##/N&4P4P%,%=F>"%%t6v666666666rQ   confc                T    t          d           |           }              |S )NzFFeishu _configure_with_overrides called but original_configure is None)RuntimeError)r  resultr  original_configures     rO   _configure_with_overrideszA_run_official_feishu_ws_client.<locals>._configure_with_overrides  s<    %ghhh##D))##%%%rQ   c                :    g | ]}|                                 |S rK   donerL   ts     rO   r   z2_run_official_feishu_ws_client.<locals>.<listcomp>  s%    FFFQVVXXF1FFFrQ   return_exceptionsTr   r  )r  r	   r  r	   r   r	   )r  r	   r   r	   )lark_oapi.ws.clientwsclientasyncionew_event_loopset_event_looploop_ws_thread_loop
websocketsconnectgetattrr  startr   	all_taskscancelrun_until_completegatherstopclose)r  r  ws_client_moduler  r  r  pendingtaskr  r  r
  s   ``      @@@rO   _run_official_feishu_ws_clientr.    s`   222222222!##D4    "G'2: L$??` ` ` ` ` ` `7 7 7 7 7 7 7       +B'%	<)BCCC!!!'    /?#+)I|-?@@@FFg/55FFF 	 	DKKMMMM 	V##GNG$Tt$T$TUUU	IIKKKK 	 	 	D		JJLLLL 	 	 	D	"&! /?#+)I|-?@@@FFg/55FFF 	 	DKKMMMM 	V##GNG$Tt$T$TUUU	IIKKKK 	 	 	D		JJLLLL 	 	 	D	"&&&&&s    B5 4F 5
C?F CF E 
E$#E$(E= =
F
	F
A=I'H)(I')
H63I'5H66I':II'
II'II'c                     t           S )z0Check if Feishu/Lark dependencies are available.)FEISHU_AVAILABLErK   rQ   rO   check_feishu_requirementsr1    s    rQ   c                      e Zd ZdZdZdZd fdZedd            ZddZ	ddZ
ddZddZd	dZddZddZddZ	 	 d
dd#Zdd%Z	 	 ddd*Zedd-            Z	 	 	 ddd0Z	 	 	 	 ddd3Z	 	 	 ddd5Z	 	 	 ddd7Zddd8Z	 	 	 dd fd:Z	 	 	 dd fd<Zdd=Zdd>Zdd@ZddAZddBZ ddCZ!ddEZ"ddFZ#ddGZ$ddHZ%ddIZ&ddKZ'd dLZ(ed!dN            Z)d"dPZ*d#dSZ+d$dUZ,ddVZ-d%dXZ.ddYZ/d&d[Z0d'd]Z1d(d^Z2d)daZ3d*dbZ4d+dfZ5d'dgZ6d,dhZ7d-diZ8ed.dl            Z9d'dmZ:d/doZ;d/dpZ<d/dqZ=d0drZ>d1dwZ?ed2dz            Z@ed3d}            ZAed4d            ZBd5dZCd6dZDd7dZEd-dZFed.d            ZGd'dZHd/dZIed8d            ZJd/dZKd/dZLd9dZMd:dZNed;d            ZOd<dZPd=dZQd>dZRd?dZSed@d            ZTedAd            ZUedBd            ZVedCd            ZWedDd            ZXedEd            ZYedDd            ZZedFd            Z[edGd            Z\dHdZ]dIdZ^dIdZ_d(dZ`dJdZaedKd            ZbedLd            ZcdMdNdĄZddMdOdńZedPdȄZfdQdʄZgdd˄Zhdd̄Zidd̈́ZjdRd΄ZkdSdτZlddddќdTdӄZmdUdՄZnedVdք            ZoedWd؄            ZpddٜdXd܄ZqdYd݄ZrddބZsdd߄ZtddZudZdZvdUdZwddZxed[d            Zyed\d            Zzed]d            Z{ed^d            Z|ed_d            Z}ed`d            Z~edad            Zed`d            Zedbd            Zedcd            Zeddd            Zeded            Zedfd            Zeded            ZddZdgd Zedhd            Z xZS (i  FeishuAdapterzFeishu/Lark bot adapter.i@  rZ   configr)   c                   t                                          |t          j                   |                     |j        pi           | _        |                     | j                   d | _        d | _	        d | _
        d | _        d | _        d | _        d | _        d | _        i | _        g | _        t%                      dz  | _        t)          j                    | _        i | _        i | _        i | _        i | _        g | _        t)          j                    | _        d| _        d| _        i | _        i | _         g | _!        i | _"        i | _#        d | _$        tK                      | _&        | j&        j'        | _(        | j&        j)        | _*        | j&        j+        | _,        tK                      | _-        | j-        j'        | _.        | j-        j)        | _/        i | _0        tc          j2        d          | _3        | 4                                 d S )Nzfeishu_seen_message_ids.jsonFi  r   )5super__init__r(   FEISHU_load_settingsextra	_settings_apply_settings_client
_ws_client
_ws_futurer   _loop_webhook_runner_webhook_site_event_handler_seen_message_ids_seen_message_orderr5   _dedup_state_path	threadingLock_dedup_lock_sender_name_cache_webhook_rate_counts_webhook_anomaly_counts_card_action_tokens_pending_inbound_events_pending_inbound_lock_pending_drain_scheduled_pending_inbound_max_depth_chat_locks_sent_message_ids_to_chat_sent_message_id_order_chat_info_cache_message_text_cache_app_lock_identityr   _text_batch_stater   _pending_text_batchesr   _pending_text_batch_tasksr   _pending_text_batch_counts_media_batch_state_pending_media_batches_pending_media_batch_tasks_approval_state	itertoolscount_approval_counter_load_seen_message_ids)selfr4  	__class__s     rO   r7  zFeishuAdapter.__init__  s   111,,V\-?R@@T^,,,&*)-48DH:>
.2,0-135.0 !0!2!25S!S$>++@BBD!JL$57  35$%.^%5%5"(-%*.'469;&13#;==? 15!1!3!3%)%;%B")-)?)E&*.*@*G'"2"4"4&*&=&D#*.*A*G':<!*!3!3##%%%%%rQ   r:  r   r   r   c                   |                      di           }i }t          |t                    r|                                D ]\  }}t          |t                    st	          t          |                     dd                                                                                    t          d |                     dg           D                       t          d |                     dg           D                                 |t          |          <   |                      d	g           }t          d
 |D                       }t          |                      dd                                                                                    }t          dEi dt          |                      d          pt          j        dd                                                    dt          |                      d          pt          j        dd                                                    dt          |                      d          pt          j        dd                                                                                    dt          |                      d          pt          j        dd                                                                                    dt          j        dd                                          dt          j        dd                                          dt          j        dd                                                                          dt          d t          j        d d                              d!          D                       d"t          j        d#d                                          d$t          j        d%d                                          d&t          j        d'd                                          d(t          d)t          t          j        d*t          t                                                   d+t#          t          j        d,t          t$                                        d-t#          t          j        d.d/                    d0t          d1t          t          j        d2t          t&                                                  d3t          d1t          t          j        d4t          t(                                                  d5t#          t          j        d6t          t*                                        d7t          |                      d7          pt          j        d8t,                                                              d9t          |                      d9          p&t          j        d:t          t.                                        d;t          |                      d;          pt          j        d<t0                                                              pt0          d=t3          |                      d=          d>d?@          dAt3          |                      dA          dBd1@          dCt5          |                      dC          d d1@          dDt5          |                      dD          d d1@          d	|d|d|S )FNr   r   openc              3     K   | ]F}t          |                                          #t          |                                          V  Gd S r;  r   r  rL   us     rO   r>  z/FeishuAdapter._load_settings.<locals>.<genexpr>X  K      !j!jQ[^_`[a[a[g[g[i[i!j#a&&,,..!j!j!j!j!j!jrQ   r   c              3     K   | ]F}t          |                                          #t          |                                          V  Gd S r;  ri  rj  s     rO   r>  z/FeishuAdapter._load_settings.<locals>.<genexpr>Y  rl  rQ   r   )r   r   r   r   c              3     K   | ]F}t          |                                          #t          |                                          V  Gd S r;  ri  rj  s     rO   r>  z/FeishuAdapter._load_settings.<locals>.<genexpr>^  sC      PPaQP3q66<<>>PPPPPPrQ   r   r   r   FEISHU_APP_IDr   FEISHU_APP_SECRETr   domainr"   rl   r   FEISHU_CONNECTION_MODE	websocketr   FEISHU_ENCRYPT_KEYr   FEISHU_VERIFICATION_TOKENr   FEISHU_GROUP_POLICYr   c              3  f   K   | ],}|                                 |                                 V  -d S r;  r  rL   r=  s     rO   r>  z/FeishuAdapter._load_settings.<locals>.<genexpr>m  sL       * *::<<*

* * * * * *rQ   FEISHU_ALLOWED_USERS,r   FEISHU_BOT_OPEN_IDr   FEISHU_BOT_USER_IDr   FEISHU_BOT_NAMEr       HERMES_FEISHU_DEDUP_CACHE_SIZEr   &HERMES_FEISHU_TEXT_BATCH_DELAY_SECONDSr   ,HERMES_FEISHU_TEXT_BATCH_SPLIT_DELAY_SECONDSz2.0r   r   %HERMES_FEISHU_TEXT_BATCH_MAX_MESSAGESr   "HERMES_FEISHU_TEXT_BATCH_MAX_CHARSr   'HERMES_FEISHU_MEDIA_BATCH_DELAY_SECONDSr   FEISHU_WEBHOOK_HOSTr   FEISHU_WEBHOOK_PORTr   FEISHU_WEBHOOK_PATHr   r^   r   r/  r   r]   r   r   rK   )r   r  r   r  r   r   r  rq  r   r   r   osgetenvr  r   r   _DEFAULT_DEDUP_CACHE_SIZEr   !_DEFAULT_TEXT_BATCH_DELAY_SECONDS _DEFAULT_TEXT_BATCH_MAX_MESSAGES_DEFAULT_TEXT_BATCH_MAX_CHARS"_DEFAULT_MEDIA_BATCH_DELAY_SECONDS_DEFAULT_WEBHOOK_HOST_DEFAULT_WEBHOOK_PORT_DEFAULT_WEBHOOK_PATHr0  r-  )r:  raw_group_rulesr   r   rule_cfg
raw_adminsr   r   s           rO   r9  zFeishuAdapter._load_settingsM  s     ))M26624ot,, 	%4%:%:%<%<  !!(D11 ,;x||Hf==>>DDFFLLNN!!j!j(,,{TV:W:W!j!j!jjj!!j!j(,,{TV:W:W!j!j!jjj- - -CLL)) YYx,,
PP:PPPPP  #599-CR#H#HIIOOQQWWYY$ 8
 8
 8
uyy**Lbi.L.LMMSSUUU8
599\22Xbi@SUW6X6XYY__aaa8
 EIIh//W29_h3W3WXX^^``ffhhh8
  		+,,`	:RT_0`0` eggeeggg8
 	"6;;AACCC8
  "y)DbIIOOQQQ8
 #8+FFLLNNTTVVV8
 !* * *I&<bAAGGLL* * * ! ! !8
 	"6;;AACCC8
  	"6;;AACCC!8
" Y0"55;;===#8
$ !BI>D]@^@^__``  %8
, &+	BCHiDjDjkk& & &-8
2 ,1	H%PP, , ,38
8 %(BIEsKkGlGlmmnn% % %98
@ "%BIBCHeDfDfgghh" " "A8
H ',	CSIkElElmm' ' 'I8
N 		.))dRY7LNc-d-d egggS8
T 		.))iRY7LcRgNhNh-i-i  U8
\ EIIn--h;PRg1h1hiiooqq )(_8
b  4EII>R4S4S]_klmmmmc8
d #7uyyAX7Y7Ycfrs"t"t"t"te8
f )3E)F)FPT`abbbbg8
h (		2C(D(Dd^_````i8
j 6k8
l "6!5m8
n $o8
 8	
rQ   settingsr  c                   |j         | _        |j        | _        |j        | _        |j        | _        |j        | _	        |j
        | _        |j        | _        t          |j                  | _        t          |j                  | _        |j        p|j        | _        |j        | _        |j        | _        |j        | _        |j        | _        |j        | _        |j        | _         |j!        | _"        |j#        | _$        |j%        | _&        |j'        | _(        |j)        | _*        |j+        | _,        |j-        | _.        |j/        | _0        |j1        | _2        |j3        | _4        |j5        | _6        d S r;  )7r   _app_idr   _app_secretr   _domain_namer   _connection_moder   _encrypt_keyr   _verification_tokenr   _group_policyr   r   _allowed_group_usersr   _adminsr   _default_group_policyr   _group_rulesr   _bot_open_idr   _bot_user_idr   	_bot_namer   _dedup_cache_sizer   _text_batch_delay_secondsr   _text_batch_split_delay_secondsr   _text_batch_max_messagesr   _text_batch_max_charsr   _media_batch_delay_secondsr   _webhook_hostr   _webhook_portr   _webhook_pathr   r  r   r  r   r  r   r	  )rd  r  s     rO   r<  zFeishuAdapter._apply_settings  sD   #.$0 ( 8$0#+#> %2$'(D$E$E!8?++%-%B%[hF["$0$0$0!*!)!:)1)J&/7/V,(0(H%%-%B"*2*L'%2%2%2#+#> &.&D#!)!: ( 8rQ   r	   c                     t           d S t          j         j         j                                       j                                       j                                       fd          	                     fd          
                     j                                       j                                       j                                       j                                       j                                                  S )Nc                0                         d|           S )Nim.message.reaction.created_v1_on_reaction_eventdatard  s    rO   r   z4FeishuAdapter._build_event_handler.<locals>.<lambda>      T445UW[\\ rQ   c                0                         d|           S )Nim.message.reaction.deleted_v1r  r  s    rO   r   z4FeishuAdapter._build_event_handler.<locals>.<lambda>  r  rQ   )r&   builderr  r  &register_p2_im_message_message_read_v1_on_message_read_event!register_p2_im_message_receive_v1_on_message_event*register_p2_im_message_reaction_created_v1*register_p2_im_message_reaction_deleted_v1register_p2_card_action_trigger_on_card_action_trigger'register_p2_im_chat_member_bot_added_v1_on_bot_added_to_chat)register_p2_im_chat_member_bot_deleted_v1_on_bot_removed_from_chat8register_p2_im_chat_access_event_bot_p2p_chat_entered_v1_on_p2p_chat_entered"register_p2_im_message_recalled_v1_on_message_recalledbuildrd  s   `rO   _build_event_handlerz"FeishuAdapter._build_event_handler  s    !)4"*!(  43D4OPP..t/EFF77\\\\  87\\\\  -,T-IJJ44T5OPP66t7UVVEEdF_``//0IJJUWW%	
rQ   r   c                   K   t           st                              d           dS | j        r| j        st                              d           dS | j        dvr"t                              d| j                   dS 	 | j        | _        t          t          | j        d| j	        j
        i          \  }}|sqt          |t                    r|                    d          nd	}d
|rd| dndz   dz   }t                              d|           |                     d|d           dS t          j                    | _        |                                  d	{V  |                                  t                              d| j        | j                   dS # t,          $ r_}|                                  d	{V  d| }|                     d|d           t                              d|d           Y d	}~dS d	}~ww xY w)zConnect to Feishu/Lark.z [Feishu] lark-oapi not installedFz3[Feishu] FEISHU_APP_ID or FEISHU_APP_SECRET not set>   webhookrs  zT[Feishu] Unsupported FEISHU_CONNECTION_MODE=%s. Supported modes: websocket, webhook.platform)r   pidNz@Another local Hermes gateway is already using this Feishu app_idz (PID z)..zI Stop the other gateway before starting a second Feishu websocket client.z[Feishu] %sfeishu_app_lock)	retryablez"[Feishu] Connected in %s mode (%s)TzFeishu startup failed: feishu_connect_errorz[Feishu] Failed to connect: %sr  )r0  r  errorr  r  r  rW  r3   _FEISHU_APP_LOCK_SCOPEr  rw   r  r   r   _set_fatal_errorr  get_running_loopr@  _connect_with_retry_mark_connectedinfor  r   _release_app_lock)rd  acquiredexisting	owner_pidrR   excs         rO   r"  zFeishuAdapter.connect  sW      	LL;<<<5| 	4#3 	LLNOOO5 (@@@LLf%   5	&*lD#!4&'$dm&9:" " "Hh
  	3=h3M3MWHLL///SW	V1:C-	----Eab 
 ]G444%%&7E%RRRu 133DJ**,,,,,,,,,  """KK<d>SUYUfggg4 	 	 	((*********555G!!"8'T!RRRLL93LNNN55555	s!   <B(F &A,F 
G=AG88G=c                  K   d| _         |                     | j                   d{V  |                     | j                   d{V  |                                  |                                  |                                  d{V  | j        I                                s5t          
                    d           dfd}                    |           | j        }|	 t          
                    d           t          j        t          j        |          d	           d{V  t          
                    d
           n# t          j        $ r t                              d           Y n]t          j        $ r t          
                    d           Y n3t&          $ r'}t          
                    d|d           Y d}~nd}~ww xY wd| _        d| _        d| _        d| _        |                                  |                                  d{V  |                                  t                              d           dS )zDisconnect from Feishu/Lark.FNz<[Feishu] Cancelling websocket thread tasks and stopping loopr   r  c                     d t          j                  D             } t                              dt	          |                      | D ]}|                                                     dj                   d S )Nc                :    g | ]}|                                 |S rK   r  r  s     rO   r   zFFeishuAdapter.disconnect.<locals>.cancel_all_tasks.<locals>.<listcomp>  s%    VVVqQVVXXVVVVrQ   z3[Feishu] Found %d pending tasks in websocket threadg?)r  r%  r  r  r   r&  
call_laterr)  )r   r-  ws_thread_loops     rO   cancel_all_tasksz2FeishuAdapter.disconnect.<locals>.cancel_all_tasks  s}    VVG$5n$E$EVVVRTWX]T^T^___! " "DKKMMMM))#~/BCCCCCrQ   z;[Feishu] Waiting for websocket thread to exit (timeout=10s)g      $@timeoutz([Feishu] Websocket thread exited cleanlyz@[Feishu] Websocket thread did not exit within 10s - may be stuckz5[Feishu] Websocket thread cancelled during disconnectz/[Feishu] Websocket thread exited with error: %sTr  z[Feishu] Disconnectedr  )_running_cancel_pending_tasksrZ  r^  _reset_batch_buffers!_disable_websocket_auto_reconnect_stop_webhook_serverr   	is_closedr  r  call_soon_threadsafer?  r  wait_forshieldTimeoutErrorwarningCancelledErrorr   r@  rC  _persist_seen_message_idsr  _mark_disconnectedr  )rd  r  	ws_futurer  r  s       @rO   
disconnectzFeishuAdapter.disconnect  s     (()GHHHHHHHHH(()HIIIIIIIII!!###..000'')))))))))-%n.F.F.H.H%LLWXXXD D D D D D //0@AAAO	 	dZ[[[&w~i'@'@$OOOOOOOOOOGHHHH' c c cabbbbb) V V VTUUUUU d d dNPS^bccccccccd #
"&&((($$&&&&&&&&&!!!+,,,,,s%   )A"E )G7(G!	G*GGr   r   c                   K   d |                                 D             }|D ]}|                                 |rt          j        |ddi d {V  |                                 d S )Nc                >    g | ]}||                                 |S rK   r  )rL   r-  s     rO   r   z7FeishuAdapter._cancel_pending_tasks.<locals>.<listcomp>-  s*    OOODdO499;;O4OOOrQ   r  T)rR  r&  r  r(  clear)rd  r   r,  r-  s       rO   r  z#FeishuAdapter._cancel_pending_tasks,  s      OOELLNNOOO 	 	DKKMMMM 	C.'BTBBBBBBBBBrQ   c                    | j                                          | j                                         | j                                         d S r;  )rY  r  r[  r]  r  s    rO   r  z"FeishuAdapter._reset_batch_buffers4  sG    "((***'--///#))+++++rQ   c                    | j         d S 	 t          | j         dd           n# t          $ r Y nw xY wd | _         d S # d | _         w xY w)N_auto_reconnectF)r>  r  r   r  s    rO   r  z/FeishuAdapter._disable_websocket_auto_reconnect9  sl    ?"F	#DO%6>>>> 	 	 	D	 #DOOOdDO""""s   " ; 
/; /; 	Ac                   K   | j         d S 	 | j                                          d {V  d | _         d | _        d S # d | _         d | _        w xY wr;  )rA  cleanuprB  r  s    rO   r  z"FeishuAdapter._stop_webhook_serverC  su      'F	&&..000000000#'D !%D $(D !%D%%%%s	   < ANr   r   ru   reply_toOptional[str]r   Optional[Dict[str, Any]]r-   c                  K   | j         st          dd          S |                     |          }|                     || j                  }d}	 |D ]}|                     |          \  }	}
	 |                     ||	|
||           d{V }n# t          $ r}|	dk    s't          	                    t          |                    s t                              d           |                     |dt          j        dt          |          id	          ||           d{V }Y d}~nd}~ww xY w|	dk    r|                     |          st          	                    t          t#          |d
d          pd                    r]t                              d           |                     |dt          j        dt          |          id	          ||           d{V }|}|                     |d          S # t          $ rE}t                              d|d           t          dt          |                    cY d}~S d}~ww xY w)zSend a Feishu message.FNot connectedsuccessr  Nr   r   r8  r  r   rJ  zI[Feishu] Invalid post payload rejected by API; falling back to plain textrt   r3  msgr   zJ[Feishu] Post payload rejected by API response; falling back to plain textzsend failedz[Feishu] Send error: %sTr  )r=  r-   format_messagetruncate_messageMAX_MESSAGE_LENGTH_build_outbound_payload_feishu_send_with_retryr   _POST_CONTENT_INVALID_REsearchr   r  r  r5  r6  r%  _response_succeededr#  _finalize_send_resultr  )rd  r   ru   r  r   	formattedchunkslast_responsechunkr   r8  responser  s                rO   sendzFeishuAdapter.sendP  s      | 	De?CCCC''00	&&y$2IJJ(	= ") ")$($@$@$G$G!'%)%A%A '!) '!)!) &B & &            HH ! 
 
 
6))1I1P1PQTUXQYQY1Z1Z)NN#nooo%)%A%A '!' $
F4QRW4X4X+Yhm n n n!)!) &B & &            HHHHHH	
 && 44X>> '077GHeUW<X<X<^\^8_8_`` ' NN#oppp%)%A%A '!' $
F4QRW4X4X+Yhm n n n!)!) &B & &            H !)--m]KKK 	= 	= 	=LL2C$LGGGe3s88<<<<<<<<<	=sJ   G9 , BG9 
D,BD'"G9 'D,,CG9 9
I:I=II
message_idc           	       K   | j         st          dd          S 	 |                     |          \  }}|                     ||          }|                     ||          }t          j        | j         j        j        j	        j
        |           d{V }|                     |d          }	|	j        s|dk    rt                              |	j        pd	          rt                               d
           |                     dt%          j        dt)          |          id                    }
|                     ||
          }t          j        | j         j        j        j	        j
        |           d{V }|                     |d          }	|	j        r||	_        |	S # t,          $ rF}t                               d||d           t          dt/          |                    cY d}~S d}~ww xY w)z0Edit a previously sent Feishu text/post message.Fr  r  r   ru   r  request_bodyNzupdate failedrJ  r   zP[Feishu] Invalid post update payload rejected by API; falling back to plain textrt   r3  z&[Feishu] Failed to edit message %s: %sTr  )r=  r-   r  _build_update_message_body_build_update_message_requestr  	to_threadimv1rR   updater  r  r  r  r  r  r  r5  r6  r%  r  r   r   )rd  r   r  ru   r   r8  r   requestr  r  fallback_bodyfallback_requestfallback_responser  s                 rO   edit_messagezFeishuAdapter.edit_message  s       | 	De?CCCC	= $ < <W E EHg22Hg2VVD88J]a8bbG$.t|/A/I/PRYZZZZZZZZH///JJF> Xh&&8&8=U=\=\]c]i]omo=p=p&8qrrr $ ? ?# J0Mg0V0V'Wfklll !@ ! ! $(#E#EQ[jw#E#x#x *1*;DLO<N<V<]_o*p*p$p$p$p$p$p$p!334EWW~ /$.!M 	= 	= 	=LLA:s]aLbbbe3s88<<<<<<<<<	=s   FF 
G/);G*$G/*G/dangerous commandcommandsession_keyr{   c                  K   | j         st          dd          S 	 t          | j                  t	          |          dk    r|dd         dz   n|}d.d/fd}ddiddddddd| d| dd |ddd           |dd           |d d!           |d"d#d$          gd%gd&}t          j        |d'          }	|                     |d(|	d|)           d{V }
|                     |
d*          }|j	        r||j
        pd+|d,| j        <   |S # t          $ rC}t                              d-|           t          dt          |                    cY d}~S d}~ww xY w)0a  Send an interactive card with approval buttons.

        The buttons carry ``hermes_action`` in their value dict so that
        ``_handle_card_action_event`` can intercept them and call
        ``resolve_gateway_approval()`` to unblock the waiting agent thread.
        Fr  r  i  Nz...r&  rv   r   action_namebtn_typer   r   c                    dd| d||ddS )Nr  r  r~   ru   )hermes_actionapproval_id)r~   rt   r   rw   rK   )rv   r-  r.  r2  s      rO   _btnz.FeishuAdapter.send_exec_approval.<locals>._btn  s.    #$0UCC$/:;WW	  rQ   wide_screen_modeTu    ⚠️ Command Approval Requiredr  ru   r~   orangers   r   r  z```
z
```
**Reason:** r0  r  u   ✅ Allow Oncerd   primaryu   ✅ Sessionre   u
   ✅ Alwaysrf   u   ❌ Denyrc   danger)r~   r  r4  r  rn  r3  r  r  zsend_exec_approval failedr   )r+  r  r   z&[Feishu] send_exec_approval failed: %sr&  )rv   r   r-  r   r.  r   r   r   )r=  r-   nextrb  r   r5  r6  r  r  r  r  r_  r   r  r  r   )rd  r   r*  r+  r{   r   cmd_previewr3  r  r8  r  r  r  r2  s                @rO   send_exec_approvalz FeishuAdapter.send_exec_approval  s:      | 	De?CCCC6	=t566K47LL44G4G'%4%.500WK       .t4)KT`aa (   *#X;#X#X;#X#X 
  ( D!1>9MM D0ABB D/?@@ DVX>>	$  D. jE:::G!99&! :        H //:UVVF~ #."("3"9r&5 5$[1
 M 	= 	= 	=NNCSIIIe3s88<<<<<<<<<	=s   C0D 
E8EEEchoicerY  c                    | dk    rdnd}t                               | d          }ddi| d| dd	| dk    rd
nddd| d| d| dgdS )z3Build raw card JSON for a resolved approval action.rc   u   ❌u   ✅Resolvedr4  Tr   r  r5  redgreenr7  r  z **z** by r0  r:  )rj   r   )r?  rY  iconrv   s       rO   _build_resolved_approval_cardz+FeishuAdapter._build_resolved_approval_card  s     &((uue#''
;;)40(,%6%6u%6%6|LL%+v%5%5EE7  &"&CC5CC	CC 
 
 	
rQ   
audio_pathcaptionc                H   K   |                      |||||d           d{V S )z@Send audio to Feishu as a file attachment plus optional caption.r_  r   	file_pathr  r   rG  outbound_message_typeN_send_uploaded_file_message)rd  r   rF  rG  r  r   r  s          rO   
send_voicezFeishuAdapter.send_voice  U       55 ") 6 
 
 
 
 
 
 
 
 	
rQ   rJ  r   c                H   K   |                      ||||||           d{V S )z*Send a document/file attachment to Feishu.)r   rJ  r  r   rG  r   NrL  )rd  r   rJ  rG  r   r  r   r  s           rO   send_documentzFeishuAdapter.send_document  sU       55 6 
 
 
 
 
 
 
 
 	
rQ   
video_pathc                H   K   |                      |||||d           d{V S )zSend a video file to Feishu.r`  rI  NrL  )rd  r   rR  rG  r  r   r  s          rO   
send_videozFeishuAdapter.send_video)  rO  rQ   
image_pathc                r  K   | j         st          dd          S t          j                            |          st          dd|           S 	 ddl}t          |d          5 }|                                }	ddd           n# 1 swxY w Y   |                    |	          }
t          j        	                    |          |
_
        |                     t          |
          }|                     |          }t          j        | j         j        j        j        j        |           d{V }|                     |d	          }|s|                     |d
d          S |r;|                     |d|d          }|                     |d|||           d{V }n6|                     |dt1          j        d	|id          ||           d{V }|                     |d          S # t6          $ rF}t8                              d||d           t          dt=          |                    cY d}~S d}~ww xY w)z"Send a local image file to Feishu.Fr  r  zImage file not found: r   Nrb
image_typer\  r   zimage upload failedz%Feishu image upload missing image_keydefault_messageoverride_errorr[  )r~   r   rG  	media_tagrJ  r  r\  r3  zimage send failedz$[Feishu] Failed to send image %s: %sTr  )r=  r-   r  pathexistsiorg  readBytesIObasenamerx   _build_image_upload_body_FEISHU_IMAGE_UPLOAD_TYPE_build_image_upload_requestr  r   r!  r"  r\  create_extract_response_field_response_error_result_build_media_post_payloadr  r5  r6  r  r   r  r  r   )rd  r   rU  rG  r  r   r  _iofimage_bytes
image_filer   r$  upload_responser   post_payloadmessage_responser  s                     rO   send_image_filezFeishuAdapter.send_image_file<  s	      | 	De?CCCCw~~j)) 	Ze3XJ3X3XYYYY,	=j$'' '1ffhh' ' ' ' ' ' ' ' ' ' ' ' ' ' ' [11J g..z::JO004  1  D 66t<<G$+$5dlo6H6N6UW^$_$_______O44_kRRI 22#$9#J 3     #==#&+)DD  >     *.)E)E##(%% *F * * $ $ $ $ $ $   *.)E)E#$ JY'?eTTT%% *F * * $ $ $ $ $ $  --.>@STTT 	= 	= 	=LL?S[_L```e3s88<<<<<<<<<	=sJ   G& #B8G& BG& BCG& BG& &
H60;H1+H61H6c                
   K   dS )z2Feishu bot API does not expose a typing indicator.NrK   )rd  r   r   s      rO   send_typingzFeishuAdapter.send_typingy  s      trQ   	image_urlc                D  K   	 |                      |           d{V }na# t          $ rT}t                              d||d           t	                                          |||||           d{V cY d}~S d}~ww xY w|                     |||||           d{V S )zJDownload a remote image then send it through the native Feishu image flow.Nz([Feishu] Failed to download image %s: %sTr  )r   rv  rG  r  r   )r   rU  rG  r  r   )_download_remote_imager   r  r  r6  
send_imagers  )	rd  r   rv  rG  r  r   rU  r  re  s	           rO   ry  zFeishuAdapter.send_image}  s"     
	#::9EEEEEEEEJJ 	 	 	LLCYPS^bLccc++#!! ,              	 ))! * 
 
 
 
 
 
 
 
 	
s   ! 
A?A	A:4A?:A?animation_urlc                d  K   	 |                      |dd           d{V \  }}na# t          $ rT}t                              d||d           t	                                          |||||           d{V cY d}~S d}~ww xY w|rd	| nd
}	|                     ||||	||           d{V S )z@Feishu has no native GIF bubble; degrade to a downloadable file.r9   zanimation.gif)default_extpreferred_nameNz,[Feishu] Failed to download animation %s: %sTr  )r   rz  rG  r  r   z[GIF downgraded to file]
z[GIF downgraded to file])r   rJ  r   rG  r  r   )_download_remote_documentr   r  r  r6  send_animationrQ  )rd  r   rz  rG  r  r   rJ  r   r  degraded_captionre  s             rO   r  zFeishuAdapter.send_animation  se     	)-)G)G". *H * * $ $ $ $ $ $ Iyy
  	 	 	LLGX[fjLkkk//+!! 0              	 FMlAAAARl''$ ( 
 
 
 
 
 
 
 
 	
s   !' 
BA	B :B Bc                z  K   ||dd}| j         s|S | j                            |          }|t          |          S 	 |                     |          }t          j        | j         j        j        j	        j        |           d{V }|r t          |dd                       du rAt          |dd          }t          |d	d
          }t                              d|||           |S t          |dd          }t          t          |dd          pd                                                                          }	|t          t          |dd          p|          |                     |	          |	pdd}
|
| j        |<   t          |
          S # t"          $ r" t                              d|d           |cY S w xY w)z5Return real chat metadata from Feishu when available.dm)r   rx   r   Nr  c                     dS r   rK   rK   rQ   rO   r   z-FeishuAdapter.get_chat_info.<locals>.<lambda>      E rQ   Fr
  unknownr	  zchat lookup failedz0[Feishu] Failed to get chat info for %s: [%s] %sr  	chat_typer   rx   )r   rx   r   r   z'[Feishu] Failed to get chat info for %sTr  )r=  rU  r   r   _build_get_chat_requestr  r   r!  r"  chatr#  r  r  r   r  rq  _map_chat_typer   )rd  r   fallbackcachedr$  r  r
  r	  r  raw_chat_typer  s              rO   get_chat_infozFeishuAdapter.get_chat_info  s      
 

 | 	O&**733<<	227;;G$.t|/A/F/JGTTTTTTTTH  JwxMMJJLLPUUUx;;h/CDDQSZ\`befff8VT22Dk2 > > D"EEKKMMSSUUM"GD&$77B7CC++M::)1T	 D .2D!'*:: 	 	 	NNDgX\N]]]OOO	s   B'F &B'F )F:9F:c                *    |                                 S )z/Feishu text messages are plain text by default.rx  rd  ru   s     rO   r
  zFeishuAdapter.format_message  s    }}rQ   r  c                P   | j         }|                     |          sG|                     |          }|r.t          j        | j        dd                                           dS t          j        | 	                    |          |          }|
                    | j                   dS )aP  Normalize Feishu inbound events into MessageEvent.

        Called by the lark_oapi SDK's event dispatcher on a background thread.
        If the adapter loop is not currently accepting callbacks (brief window
        during startup/restart or network-flap reconnect), the event is queued
        for replay instead of dropped.
        zfeishu-pending-inbound-drainerT)targetrx   daemonN)r@  _loop_accepts_callbacks_enqueue_pending_inbound_eventrG  Thread_drain_pending_inbound_eventsr$  r  run_coroutine_threadsafe_handle_message_event_dataadd_done_callback_log_background_failure)rd  r  r  start_drainerfutures        rO   r  zFeishuAdapter._on_message_event  s     z++D11 	 ??EEM  =9   %'''F1++D11
 
 	  !=>>>>>rQ   c                T   | j         5  t          | j                  | j        k    r| j                            d          }	 t          |dd          }t          |dd          }t          t          |dd          pd          }n# t          $ r d}Y nw xY wt          	                    d| j        |           | j        
                    |           t          | j                  }| j         }|rd	| _        ddd           n# 1 swxY w Y   t                              d
|           |S )a  Append an event to the pending-inbound queue.

        Returns True if the caller should spawn a drainer thread (no drainer
        currently scheduled), False if a drainer is already running and will
        pick up the new event on its next pass.
        r   eventNrR   r  r   r  zA[Feishu] Pending-inbound queue full (%d); dropped oldest event %sTzI[Feishu] Queued inbound event for replay (loop not ready, queue depth=%d))rO  r   rN  rQ  popr#  r   r   r  r  rB  rP  r  )rd  r  droppedr  rR   r  depthshould_starts           rO   r  z,FeishuAdapter._enqueue_pending_inbound_event   s    ' 	5 	54/00D4SSS 6::1==+#GWd;;E%eY==G!$WWlB%G%G%T9!U!UJJ  + + +!*JJJ+W3  
 (//555455E#<<L 504-+	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5, 	W	
 	
 	
 s7   8DABDBDBA#DD	Dc                   d}d}d}	 	 t          | dd          s| j        5  t          | j                  }| j                                         ddd           n# 1 swxY w Y   |rt
                              d|           	 | j        5  d| _        ddd           dS # 1 swxY w Y   dS | j        }| 	                    |          r| j        5  | j        dd         }| j                                         ddd           n# 1 swxY w Y   |s^| j        5  | j        s5	 ddd           | j        5  d| _        ddd           dS # 1 swxY w Y   dS 	 ddd           n# 1 swxY w Y   id	}g }|D ]o}		 t          j        |                     |	          |          }
|
                    | j                   |d
z  }K# t          $ r |                    |	           Y lw xY w|r+| j        5  || j        dd	<   ddd           n# 1 swxY w Y   |rt
                              d|           |s\| j        5  | j        s5	 ddd           | j        5  d| _        ddd           dS # 1 swxY w Y   dS 	 ddd           n# 1 swxY w Y   ||k    r| j        5  t          | j                  }| j                                         ddd           n# 1 swxY w Y   t
                              d||           	 | j        5  d| _        ddd           dS # 1 swxY w Y   dS t'          j        |           ||z  };# | j        5  d| _        ddd           w # 1 swxY w Y   w xY w)aU  Replay queued inbound events once the adapter loop is ready.

        Runs in a dedicated daemon thread. Polls ``_running`` and
        ``_loop_accepts_callbacks`` until events can be dispatched or the
        adapter shuts down. A single drainer handles the entire queue;
        concurrent ``_on_message_event`` calls just append.
        g      ?g      ^@        Tr  Nz;[Feishu] Dropped %d queued inbound event(s) during shutdownFr   r   z,[Feishu] Replayed %d queued inbound event(s)zO[Feishu] Adapter loop unavailable for %.0fs; dropped %d queued inbound event(s))r#  rO  r   rN  r  r  r  rP  r@  r  r  r  r  r  r  r  rB  r  r  timesleep)rd  poll_intervalmax_wait_secondswaitedr  r  batch
dispatchedrequeuer  futs              rO   r  z+FeishuAdapter._drain_pending_inbound_events#  s=     F	6B(tZ66  3 = ="%d&B"C"C4::<<<= = = = = = = = = = = = = = =  Y#   p + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6o z//55 '3 = = $ <QQQ ?4::<<<= = = = = = = = = = = = = = = ! !!7 ' '#'#? ' &' ' ' ' ' '` + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6_'' ' ' ' ' ' ' ' ' ' ' ' ' ' ' !!"J)+G!& 2 2
2")"B $ ? ? F F $# #C  11$2NOOO&!OJJ+ 2 2 2 $NN5111112  G!7 G G?FD8!<G G G G G G G G G G G G G G G! J&   # ' "7 ' '#'#? ' &' ' ' ' ' '& + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6%'' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ---3 = ="%d&B"C"C4::<<<= = = = = = = = = = = = = = = LL=(	    + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 
=)))-'EB(H + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6s  M .AM AM "A# M B  B$'B$-$M )D:M D

M D
M 	E$#M 5E

EEM $E((M +E(,M :AGM G$!M #G$$M 0H	=M 	HM H)M :	JM I**I.1I.8M JM JM .KM KM K M LL #L )M M,M M, M$$M,'M$(M,c                  K   t          |dd          }t          |dd          }t          |dd          }t          |dd          }|r|st                              d           dS t          |dd          }|r|                     |          rt                              d|           dS t          |d	d
          dk    rt                              d|           dS t          |dd          }t          |dd
          pd
}|dk    r4|                     |||          st                              d|           dS |                     |||||           d{V  dS )zEShared inbound message handling for websocket and webhook transports.r  NrR   r  	sender_idzG[Feishu] Dropping malformed inbound event: missing message or sender_idr  z2[Feishu] Dropping duplicate/missing message_id: %ssender_typer   botz*[Feishu] Dropping bot-originated event: %sr  p2pr   zC[Feishu] Dropping group message that failed mention/policy gate: %s)r  rR   r  r  r  )r#  r  r  _is_duplicate_should_accept_group_message_process_inbound_message)	rd  r  r  rR   r  r  r  r  r   s	            rO   r  z(FeishuAdapter._handle_message_event_datav  s     gt,,%D11$//FK66	 	i 	LLbcccFWlD99
 	T//
;; 	LLMzZZZF6="--66LLEzRRRFG[%88	'9b117Rd&G&GQZ\c&d&dLL^`jkkkF++! , 
 
 	
 	
 	
 	
 	
 	
 	
 	
 	
rQ   r   c                    t          |dd          }t          |dd          }t          |dd          pd}t                              d|           dS )z7Ignore read-receipt events that Hermes does not act on.r  NrR   r  r   z([Feishu] Ignoring message_read event: %s)r#  r  r  )rd  r  r  rR   r  s        rO   r  z$FeishuAdapter._on_message_read_event  sU    gt,,%D11WlD99?R
?LLLLLrQ   c                    t          |dd          }t          t          |dd          pd          }t                              d|           | j                            |d           dS )z'Handle bot being added to a group chat.r  Nr   r   z[Feishu] Bot added to chat: %sr#  r   r  r  rU  r  rd  r  r  r   s       rO   r  z#FeishuAdapter._on_bot_added_to_chat  se    gt,,geY339r::4g>>>!!'400000rQ   c                    t          |dd          }t          t          |dd          pd          }t                              d|           | j                            |d           dS )z+Handle bot being removed from a group chat.r  Nr   r   z"[Feishu] Bot removed from chat: %sr  r  s       rO   r  z'FeishuAdapter._on_bot_removed_from_chat  se    gt,,geY339r::8'BBB!!'400000rQ   c                :    t                               d           d S )Nz'[Feishu] User entered P2P chat with botr  r  rd  r  s     rO   r  z"FeishuAdapter._on_p2p_chat_entered  s    >?????rQ   c                :    t                               d           d S )Nz![Feishu] Message recalled by userr  r  s     rO   r  z"FeishuAdapter._on_message_recalled  s    899999rQ   
event_typec                b   t          |dd          }t          t          |dd          pd          }t          t          |dd          pd          }t          |dd          }t          t          |dd          pd          }d|v rd	nd
}t                              d||||           | j        }	|dv s6|t
          k    s+|r)|	't           t          |	dd                                 rdS t          j        | 	                    ||          |	          }
|

                    | j                   dS )z>Route user reactions on bot messages as synthetic text events.r  Nr  r   operator_typereaction_typerf  createdaddedremovedz?[Feishu] Reaction %s on message %s (operator_type=%s, emoji=%s)>   appr  r  c                     dS r   rK   rK   rQ   rO   r   z2FeishuAdapter._on_reaction_event.<locals>.<lambda>  s    u rQ   )r#  r   r  r  r@  _FEISHU_ACK_EMOJIr   r  r  _handle_reaction_eventr  r  )rd  r  r  r  r  r  reaction_type_objrf  r  r  r  s              rO   r  z FeishuAdapter._on_reaction_event  sZ   gt,,b99?R@@
GE?B??E2FF#E?DAA!2L"EEKLL
%33M	
 	
 	
 z^++... /|=GD+}}==??@@  F1''
D99
 
 	  !=>>>>>rQ   c                   | j         }|                     |          s1t                              d           t          rt	                      ndS t          |dd          }t          |dd          }t          |di           pi }t          |t                    r|                    d          nd}|r| 	                    |||          S | 
                    ||                     |                     t          dS t	                      S )ap  Handle card-action callback from the Feishu SDK (synchronous).

        For approval actions: parses the event once, returns the resolved card
        inline (the only reliable way to sync all clients), and schedules a
        lightweight async method to actually unblock the agent.

        For other card actions: delegates to ``_handle_card_action_event``.
        z:[Feishu] Dropping card action before adapter loop is readyNr  r  rw   r1  )r  action_valuer  )r@  r  r  r  r%   r#  r  r   r   _handle_approval_card_action_submit_on_loop_handle_card_action_event)rd  r  r  r  r  r  r1  s          rO   r  z%FeishuAdapter._on_card_action_trigger  s    z++D11 	ZNNWXXX4OY.000UYYgt,,$//vw339r=GVZ=[=[e((999ae 	h445|bf4gggT4#A#A$#G#GHHH&.4*,,,rQ   r  c                Z    | duo't           t          | dd                                  S )zEReturn True when the adapter loop can accept thread-safe submissions.Nr  c                     dS r   rK   rK   rQ   rO   r   z7FeishuAdapter._loop_accepts_callbacks.<locals>.<lambda>  s    PU rQ   r   r#  )r  s    rO   r  z%FeishuAdapter._loop_accepts_callbacks  s6     4Y-VWT;-V-V-X-X(Y(Y$YYrQ   coroc                d    t          j        ||          }|                    | j                   dS )zISchedule background work on the adapter loop with shared failure logging.N)r  r  r  r  )rd  r  r  r  s       rO   r  zFeishuAdapter._submit_on_loop  s1    1$==  !=>>>>>rQ   r  r  c                  |                     d          }|1t                              d           t          rt                      ndS t                               |                     d          d          }t          |dd          }t          t          |dd          pd          }|                     |          p|}|                     || 	                    |||                     t          dS t                      }	t          8t                      }
d	|
_        |                     ||
          |
_        |
|	_        |	S )zISchedule approval resolution and build the synchronous callback response.r2  Nz2[Feishu] Card action missing approval_id, ignoringr1  rc   operatorr   r   raw)r?  rY  )r   r  r  r%   rh   r#  r   _get_cached_sender_namer  _resolve_approvalr$   r   rE  r  r  )rd  r  r  r  r2  r?  r  r   rY  r  r  s              rO   r  z*FeishuAdapter._handle_approval_card_action  s+   "&&}55LLMNNN4OY.000UYY%)),*:*:?*K*KVTT5*d33gh	266<"==0099DW	T4#9#9+vy#Y#YZZZ&.4.00#>>DDI::&T]:^^DI HMrQ   r2  c                ^  K   | j                             |d          }|st                              d|           dS 	 ddlm}  ||d         |          }t                              d||d         ||           dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)z8Pop approval state and unblock the waiting agent thread.Nz0[Feishu] Approval %s already resolved or unknownr   )resolve_gateway_approvalr+  zIFeishu button resolved %d approval(s) for session %s (choice=%s, user=%s)z9Failed to resolve gateway approval from Feishu button: %s)	r_  r  r  r  tools.approvalr  r  r   r  )rd  r2  r?  rY  stater  ra  r  s           rO   r  zFeishuAdapter._resolve_approval  s      $((d;; 	LLK[YYYF	[??????,,U=-A6JJEKK[u]+VY      	[ 	[ 	[LLTVYZZZZZZZZZ	[s   <A< <
B,B''B,c           
       K   | j         sdS t          |dd          }t          t          |dd          pd          }|sdS 	 |                     |          }t	          j        | j         j        j        j        j	        |           d{V }|r t          |dd                       sdS t          t          |dd          dd          pg }|r|d	         nd}|sdS t          |d
d          }	t          t          |	dd          pd          
                                }
|
dk    rdS t          t          |dd          pd          }t          t          |dd          pd          }|sdS n-# t          $ r  t                              dd           Y dS w xY wt          |dd          }t          |dd          }t          t          |dd          pd          }d|v rdnd}d| d| }|                     |           d{V }|                     |           d{V }|                     ||	                    d          p|pd|                     ||          |d         |d         d|d          !          }t%          |t&          j        |||t+          j                    "          }t                              d#|||           |                     |           d{V  dS )$zVFetch the reacted-to message; if it was sent by this bot, emit a synthetic text event.Nr  r  r   r  c                     dS r   rK   rK   rQ   rO   r   z6FeishuAdapter._handle_reaction_event.<locals>.<lambda>*  s     rQ   r  r  r   r  r  r  r   r  r  z5[Feishu] Failed to fetch message for reaction routingTr  r   r  rf  UNKNOWNr  r  r  z	reaction:rg  rx   Feishu Chat	chat_infoevent_chat_typerY  user_id_altr   r  r  r   rY  	thread_idr  rt   r   sourceraw_messager  	timestampzD[Feishu] Routing reaction %s:%s on bot message %s as synthetic event)r=  r#  r   _build_get_message_requestr  r   r!  r"  rR   r   rq  r   r  r  _resolve_sender_profiler  build_source_resolve_source_chat_typer+   r,   TEXTr   nowr  _handle_message_with_guards)rd  r  r  r  r  r$  r  r  r	  r  r  r   chat_type_rawuser_id_objr  rf  r  synthetic_textsender_profiler  r  synthetic_events                         rO   r  z$FeishuAdapter._handle_reaction_event  sr     | 	Fgt,,b99?R@@
 	F	55jAAG$.t|/A/I/MwWWWWWWWWH #N78Y#N#N#P#P GHfd;;WdKKQrE#-%((C S(D11FgfmR@@FBGGMMOOKe##'#y"55;<<G[% @ @ IEJJM  	 	 	LLP[_L```FF	 eY55#E?DAA!2L"EERSS
%33:V::j::#;;KHHHHHHHH,,W55555555	""mmF++GwG-44yZg4hh"9-$[1&}5 # 
 
 '$)!lnn
 
 
 	Z\bdnpz{{{..???????????s'   A%E+ )0E+ A	E+ &AE+ +&FFr   c                    t          j                     fd| j                                        D             }|D ]
}| j        |= || j        v rdS | j        |<   dS )zTReturn True if this card action token was already processed within the dedup window.c                6    g | ]\  }}|z
  t           k    |S rK   )%_FEISHU_CARD_ACTION_DEDUP_TTL_SECONDS)rL   r  tsr  s      rO   r   z;FeishuAdapter._is_card_action_duplicate.<locals>.<listcomp>\  s,    wwwBcBhQvFvFv1FvFvFvrQ   TF)r  rM  r  )rd  r   expiredr  r  s       @rO   _is_card_action_duplicatez'FeishuAdapter._is_card_action_duplicateX  sx    ikkwwww$":"@"@"B"Bwww 	, 	,A(++D,,,4*- 'urQ   c           
       K   t          |dd          }t          t          |dd          pd          }|r2|                     |          rt                              d|           dS t          |dd          }t          t          |dd          pd          }t          |dd          }t          t          |d	d          pd          }|r|st                              d
           dS t          |dd          }t          t          |dd          pd          }	t          |di           pi }
d|	 }|
r.	 |dt          j        |
d           z  }n# t          $ r Y nw xY wt          |dd          }| 	                    |           d{V }| 
                    |           d{V }|                     ||                    d          p|pd|                     |d          |d         |d         d|d                   }t          |t          j        |||pt          t#          j                              t'          j                              }t                              d|	||           |                     |           d{V  dS )zHRoute Feishu interactive card button clicks as synthetic COMMAND events.r  Nr   r   z1[Feishu] Dropping duplicate card action token: %scontextr   r  r   zB[Feishu] Card action missing chat_id or operator open_id, droppingr  r~   r  rw   z/card r   Fr3  )r   r   r   rx   r  r  r  r   rY  r  r  r  zB[Feishu] Routing card action %r from %s in %s as synthetic command)r#  r   r  r  r  r5  r6  r   r   r  r  r  r   r  r+   r,   COMMANDuuiduuid4r   r  r  r  )rd  r  r  r   r  r   r  r   r  
action_tagr  r  r  r  r  r  r  s                    rO   r  z'FeishuAdapter._handle_card_action_eventd  s     gt,,GE7B//5266 	T33E:: 	LLLeTTTF%D11gg~r::@bAA5*d33gh	266<"== 	g 	LL]^^^F$//33?x@@
vw339r.*.. 	"TdjE&R&R&R"T"TT    $GTDQQQ	#;;IFFFFFFFF,,W55555555	""mmF++GwG-44yZa4bb"9-$[1&}5 # 
 
 '$,1DJLL 1 1lnn
 
 
 	XZdfmovwww..???????????s   6E 
E E asyncio.Lockc                x    | j                             |          }|t          j                    }|| j         |<   |S )zTReturn (creating if needed) the per-chat asyncio.Lock for serial message processing.)rR  r   r  rH  )rd  r   locks      rO   _get_chat_lockzFeishuAdapter._get_chat_lock  s:    ##G,,<<>>D(,DW%rQ   r+   c                N  K   |j         rt          |j         dd          pdnd}|                     |          }|4 d{V  |j        }|r|                     |           d{V  |                     |           d{V  ddd          d{V  dS # 1 d{V swxY w Y   dS )a  Dispatch a single event through the agent pipeline with per-chat serialization
        and a persistent ACK emoji reaction before processing starts.

        - Per-chat lock: ensures messages in the same chat are processed one at a time
          (matches openclaw's createChatQueue serial queue behaviour).
        - ACK indicator: adds a CHECK reaction to the triggering message before handing
          off to the agent and leaves it in place as a receipt marker.
        r   r   N)r  r#  r  r  _add_ack_reactionhandle_message)rd  r  r   	chat_lockr  s        rO   r  z)FeishuAdapter._handle_message_with_guards  s`      AFT'%,	266<"RT''00	 	- 	- 	- 	- 	- 	- 	- 	-)J 9,,Z888888888%%e,,,,,,,,,		- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	-s   A B
B!Bc           
        K   | j         r|sdS 	 ddlm}m} |                                                    dt          i                                          }|                                                    |          	                    |                                          }t          j        | j         j        j        j        j        |           d{V }|r< t!          |dd                       r"t!          |dd          }t!          |dd          S t"                              d	|t!          |d
d          t!          |dd                     n-# t&          $ r  t"                              d|d           Y nw xY wdS )zGAdd a persistent ACK emoji reaction to signal the message was received.Nr   )CreateMessageReactionRequest CreateMessageReactionRequestBodyrf  r  c                     dS r   rK   rK   rQ   rO   r   z1FeishuAdapter._add_ack_reaction.<locals>.<lambda>  s     rQ   r  reaction_idz9[Feishu] Failed to add ack reaction to %s: code=%s msg=%sr
  r	  z)[Feishu] Failed to add ack reaction to %sTr  )r=  lark_oapi.api.im.v1r  r  r  r  r  r  r  r  r  r   r!  r"  message_reactionrh  r#  r  r  r   )rd  r  r  r  r   r$  r  r  s           rO   r	  zFeishuAdapter._add_ack_reaction  s     | 	: 	4	c       
 188::.?@AA  -4466J''d##	  %.t|/A/R/Y[bccccccccH :GGHiGGII :x66t]D999NNK&$//%..	     	c 	c 	cNNF
]aNbbbbb	cts   DE ;E 'E;:E;	remote_ipstatusc                ,   t          j                     }| j                            |          }|W|\  }}}||z
  t          k     rC|dz  }|t          z  dk    r!t
                              d|||||z
             |||f| j        |<   dS d||f| j        |<   dS )zIncrement the anomaly counter for remote_ip and emit a WARNING every threshold hits.

        Mirrors openclaw's createWebhookAnomalyTracker: TTL 6 hours, log every 25 consecutive
        error responses from the same IP.
        Nr   r   zY[Feishu] Webhook anomaly: %d consecutive error responses (%s) from %s over the last %.0fs)r  rL  r   #_FEISHU_WEBHOOK_ANOMALY_TTL_SECONDS!_FEISHU_WEBHOOK_ANOMALY_THRESHOLDr  r  )rd  r  r  r  entryra  _last_status
first_seens           rO   _record_webhook_anomalyz%FeishuAdapter._record_webhook_anomaly  s     ikk,00;;.3+E<Z"EEE
<<AANN.!j(   <A&*:U,Y734fc2B$Y///rQ   c                <    | j                             |d           dS )zCReset the anomaly counter for remote_ip after a successful request.N)rL  r  )rd  r  s     rO   _clear_webhook_anomalyz$FeishuAdapter._clear_webhook_anomaly  s!    $((D99999rQ   rR   r  r  c               J  K   |                      |           d {V \  }}}}	|t          j        k    r0|s.|s,t                              dt          |dd                     d S |t          j        k    r!|                    d          rt          j        }t          |dd           pt          |dd           pd }
|
r|                     |
           d {V nd }t          	                    d|dk    rd	nd
||j
        t          |dd          pd|d d         t          |                     t          |dd          pd}|                     |           d {V }|                     |           d {V }|                     ||                    d          p|pd|                     ||          |d         |d         t          |dd           pd |d                   }t#          |||||||	|
|t%          j                    
  
        }|                     |           d {V  d S )Nz7[Feishu] Ignoring unsupported or empty message type: %sr   r   /	parent_idupper_message_idzO[Feishu] Inbound %s message received: id=%s type=%s chat_id=%s text=%r media=%dr  r  r  r   r]   rx   r  r  r   rY  r  r  r  )
rt   r   r  r  r  
media_urlsmedia_typesreply_to_message_idreply_to_textr  )_extract_message_contentr,   r  r  r  r#  r   r   _fetch_message_textr  rw   r   r  r  r  r   r  r+   r   r  _dispatch_inbound_event)rd  r  rR   r  r  r  rt   inbound_typer"  r#  r$  r%  r   r  r  r  r  s                    rO   r  z&FeishuAdapter._process_inbound_message  s      =A<Y<YZa<b<b6b6b6b6b6b6b3lJ;+++D++LLRT[\cesuwTxTxyyyF;+++0D0D+&.L G[$// w 2D99 	
 Pcld667JKKKKKKKKKhl]&&DDGGY++1r#J
OO	
 	
 	
 '9b117R,,W55555555	#;;IFFFFFFFF""mmF++GwG-44yZc4dd"9-$[1g{D99AT&}5 # 
 
 "%!!# 3'lnn
 
 

 **:66666666666rQ   c                0  K   |j         t          j        k    r1|                                s|                     |           d{V  dS |                     |          r|                     |           d{V  dS |                     |           d{V  dS )zHApply Feishu-specific burst protection before entering the base adapter.N)r   r,   r  
is_command_enqueue_text_event_should_batch_media_event_enqueue_media_eventr  rd  r  s     rO   r(  z%FeishuAdapter._dispatch_inbound_event7	  s      !111%:J:J:L:L1**5111111111F))%00 	++E222222222F..u55555555555rQ   c                    t          |j        o4|j        t          j        t          j        t          j        t          j        hv           S r;  )r   r"  r   r,   PHOTOVIDEODOCUMENTAUDIOr/  s     rO   r-  z'FeishuAdapter._should_batch_media_eventE	  sA     v"{'8+:K[Macnct&uu
 
 	
rQ   c                    ddl m}  ||j        | j        j                            dd          | j        j                            dd                    }| d|j        j         S )	Nr   build_session_keygroup_sessions_per_userTthread_sessions_per_userFr8  r9  z:media:)gateway.sessionr7  r  r4  r:  r   r   rw   )rd  r  r7  r+  s       rO   _media_batch_keyzFeishuAdapter._media_batch_keyK	  s    555555''L$(K$5$9$9:SUY$Z$Z%)[%6%:%:;UW\%]%]
 
 

 @@e&8&>@@@rQ   r  incomingc                    | j         |j         k    o9| j        |j        k    o)| j        |j        k    o| j        j        |j        j        k    S r;  )r   r$  r%  r  r  r  r=  s     rO   _media_batch_is_compatiblez(FeishuAdapter._media_batch_is_compatibleU	  sY     !X%:: G,0LLG&(*@@G )X_-FF		
rQ   c                  K   |                      |          }| j                            |          }|!|| j        |<   |                     |           d S |                     ||          s<|                     |           d {V  || j        |<   |                     |           d S |j                            |j                   |j                            |j                   |j	        r%| 
                    |j	        |j	                  |_	        |j        |_        |j        r|j        |_        |                     |           d S r;  )r<  r]  r   _schedule_media_batch_flushr@  _flush_media_batch_nowr"  r  r#  rt   _merge_captionr  r  )rd  r  r   r  s       rO   r.  z"FeishuAdapter._enqueue_media_event^	  sS     ##E**.22377/4D',,,S111F..x?? 	--c222222222/4D',,,S111F""5#3444##E$5666: 	K //uzJJHM"_ 	3"'"2H((-----rQ   r   c                H    |                      | j        || j                   d S r;  )_reschedule_batch_taskr^  _flush_media_batchrd  r   s     rO   rB  z)FeishuAdapter._schedule_media_batch_flushs	  s3    ##+#	
 	
 	
 	
 	
rQ   c                  K   t          j                    }	 t          j        | j                   d {V  |                     |           d {V  | j                            |          |u r| j                            |d            d S d S # | j                            |          |u r| j                            |d            w w xY wr;  )r  current_taskr  r  rC  r^  r   r  )rd  r   rJ  s      rO   rG  z FeishuAdapter._flush_media_batchz	  s      +--	?- ?@@@@@@@@@--c222222222.22377<GG/33C>>>>> HGt.22377<GG/33C>>>> Hs   :B :Cc                   K   | j                             |d           }|sd S t                              d|t	          |j                             |                     |           d {V  d S )Nz6[Feishu] Flushing media batch %s with %d attachment(s))r]  r  r  r  r   r"  r  rd  r   r  s      rO   rC  z$FeishuAdapter._flush_media_batch_now	  s      +//T:: 	FD !!	
 	
 	

 ..u55555555555rQ   c                b   K   |                      |d          }t          ||           d {V S )Nr:   r;  rM   )_guess_remote_extensionr0   )rd  rv  rM   s      rO   rx  z$FeishuAdapter._download_remote_image	  sC      **9f*EE))==========rQ   file_urlr|  r}  tuple[str, str]c          	       K   ddl m}  ||          st          d|d d                    dd l}|                    dd          4 d {V }|                    |dd	d
           d {V }|                                 d d d           d {V  n# 1 d {V swxY w Y   |                     |t          |j	                            dd                    ||          }t          |j        |          }	|	|fS )Nr   )is_safe_urlz&Blocked unsafe URL (SSRF protection): P   g      >@T)r  follow_redirectsz)Mozilla/5.0 (compatible; HermesAgent/1.0)z*/*)z
User-AgentAcceptheadersContent-Typer   )content_typedefault_namer|  )tools.url_safetyrS  r*  httpxAsyncClientr   raise_for_status_derive_remote_filenamer   rX  r/   ru   )
rd  rP  r|  r}  rS  r]  r  r  filenamecached_paths
             rO   r~  z'FeishuAdapter._download_remote_document	  s      	100000{8$$ 	WUhsPRsmUUVVV$$TD$II 	( 	( 	( 	( 	( 	( 	(V#ZZ"M#  (        H %%'''	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( //X-11."EEFF'#	 0 
 
 00@(KKH$$s   5B
B #B r   r&  c                   t          | pd                    dd          d                   j                                        }|t          t
          z  t          z  t          t                    z  v r|n|S )Nr   ?r   r   )	r   r  suffixrq  _IMAGE_EXTENSIONS_AUDIO_EXTENSIONS_VIDEO_EXTENSIONSr   r.   )r   r&  rM   s      rO   rO  z%FeishuAdapter._guess_remote_extension	  so    CI2$$S!,,Q/007==??/2CCFWWZ]^vZwZwwxxss  F  	FrQ   rZ  r[  c               v   t          | pd                    dd          d                   j        p|}t          |          j                                        }|s^t          j        |pd                    dd          d                                                                         pd          p|}| | }|S )Nr   rd  r   r   ;)r   r  rx   re  rq  	mimetypesguess_extensionr  )rP  rZ  r[  r|  rS  rM   guesseds          rO   r`  z%FeishuAdapter._derive_remote_filename	  s    (.b//Q77:;;@PL	9oo$**,, 	0/1C0J0J3PQ0R0RST0U0[0[0]0]0c0c0e0e0kikll{p{G$/g//IrQ   rw   c                    t          | t                    r(t          di d |                                 D             S t          | t                    rd | D             S | S )Nc                J    i | ] \  }}|t                               |          !S rK   r3  _namespace_from_mapping)rL   r   r=  s      rO   rP   z9FeishuAdapter._namespace_from_mapping.<locals>.<dictcomp>	  s0    %v%v%v[d[^`dc=+P+PQU+V+V%v%v%vrQ   c                B    g | ]}t                               |          S rK   rp  ry  s     rO   r   z9FeishuAdapter._namespace_from_mapping.<locals>.<listcomp>	  s&    RRRDM99$??RRRrQ   rK   )r  r   r   r  r   r   s    rO   rq  z%FeishuAdapter._namespace_from_mapping	  sq    eT"" 	x"ww%v%vhmhshshuhu%v%v%vwwweT"" 	SRRERRRRrQ   r$  c                  K   t          |dd           pd}| j         d| j         d| }|                     |          sGt                              d|           |                     |d           t          j        dd          S t          |d	i           pi }t          |
                    d
d          pd                              d          d                                                                         }|rN|dk    rHt                              d||           |                     |d           t          j        dd          S t          |dd           }|S|t          k    rHt                              d||           |                     |d           t          j        dd          S 	 t          j        |                                t$                     d {V }n# t          j        $ rP t                              dt$          |           |                     |d           t          j        dd          cY S t(          $ r2 |                     |d           t          j        ddd d!          cY S w xY wt-          |          t          k    rUt                              d"t-          |          |           |                     |d           t          j        dd          S 	 t/          j        |                    d#                    }nK# t.          j        t6          f$ r2 |                     |d           t          j        dd$d d!          cY S w xY w|
                    d%          d&k    r*t          j        d'|
                    d'd          i          S | j        r|
                    d(          pi }	t          |	
                    d)          p|
                    d)          pd          }
|
rt;          j        |
| j                  sGt                              d*|           |                     |d+           t          j        d,d-          S | j        rb|                      |j!        |          sGt                              d.|           |                     |d/           t          j        d,d0          S |
                    d1          rIt          "                    d2           |                     |d3           t          j        dd4d d!          S | #                    |           t          |
                    d(          pi 
                    d5          pd          }| $                    |          }|d6k    r| %                    |           n|d7k    r| &                    |           n|d8k    r| '                    |           np|d9k    r| (                    |           nT|d:v r| )                    ||           n9|d;k    r| *                    |           nt          +                    d<|pd           t          j        dd=d           S )>Nremoter  rg  z+[Feishu] Webhook rate limit exceeded for %s429i  zToo Many Requests)r  rt   rX  rY  r   rj  r   application/jsonz=[Feishu] Webhook rejected: unexpected Content-Type %r from %s415i  zUnsupported Media Typecontent_lengthz2[Feishu] Webhook body too large (%d bytes) from %s413i  zRequest body too larger  z6[Feishu] Webhook body read timed out after %ds from %s408i  zRequest Timeout400i  zfailed to read body)r
  r	  )r  z6[Feishu] Webhook body exceeds limit (%d bytes) from %sutf-8zinvalid jsonr   url_verification	challenger  r   z=[Feishu] Webhook rejected: invalid verification token from %sz	401-tokeni  zInvalid verification tokenz4[Feishu] Webhook rejected: invalid signature from %sz401-sigzInvalid signatureencryptzL[Feishu] Encrypted webhook payloads are not supported by Hermes webhook modez400-encryptedz,encrypted webhook payloads are not supportedr  zim.message.receive_v1zim.message.message_read_v1zim.chat.member.bot.added_v1zim.chat.member.bot.deleted_v1)r  r  zcard.action.triggerz([Feishu] Ignoring webhook event type: %sok),r#  r  r  _check_webhook_rate_limitr  r  r  r   Responser   r   r  r  rq  _FEISHU_WEBHOOK_MAX_BODY_BYTESr  r  rb  $_FEISHU_WEBHOOK_BODY_TIMEOUT_SECONDSr  r   json_responser   r5  r  decoder  UnicodeDecodeErrorr  hmaccompare_digestr  _is_webhook_signature_validrX  r  r  rq  r  r  r  r  r  r  r  )rd  r$  r  rate_keyrX  rZ  rx  
body_bytesr8  r  incoming_tokenr  r  s                rO   _handle_webhook_requestz%FeishuAdapter._handle_webhook_request	  s     Wh55B	 lEET%7EE)EE--h77 	FNNH)TTT((E:::<s1DEEEE '9b117R7;;~r::@bAAGGLLQOUUWW]]__ 	KL,>>>NNZ\hjsttt((E:::<s1IJJJJ !*:DAA%.;Y*Y*YNNOQ_ajkkk((E:::<s1IJJJJ	^&-&6<' ' ' ! ! ! ! ! !JJ # 	D 	D 	DNNSUy  |E  F  F  F((E:::<s1BCCCCCC 	^ 	^ 	^((E:::$c:O%P%PY\]]]]]]	^ z??;;;NNSUXYcUdUdfoppp((E:::<s1IJJJJ	Wj!2!27!;!;<<GG$&89 	W 	W 	W((E:::$c.%I%IRUVVVVVV	W ;;v"444$k7;;{B3O3O%PQQQ # 	S[[**0bF G!4!4!RG8L8L!RPRSSN! S)<^TMe)f)f S^`ijjj,,YDDD|35QRRRR  	FT%E%EgoWa%b%b 	FNNQS\]]]((I>>><s1DEEEE;;y!! 	wLLghhh((ODDD$c:h%i%iruvvvv##I...'++h//52::<HHNBOO
++G44000""4((((777''----888&&t,,,,:::**40000___##J5555000((....LLCZE\S\]]] !D!9!9:::s,   23G& &AI?8I?>I?0'L AM M rX  r  bytesc                4   t          |                    dd          pd          }t          |                    dd          pd          }t          |                    dd          pd          }|r|r|sdS 	 |                    dd          }| | | j         | }t	          j        |                    d                                                    }t          j	        ||          S # t          $ r  t                              d	d
           Y dS w xY w)a  Verify Feishu webhook signature using timing-safe comparison.

        Feishu signature algorithm:
            SHA256(timestamp + nonce + encrypt_key + body_string)
        Headers checked: x-lark-request-timestamp, x-lark-request-nonce, x-lark-signature.
        zx-lark-request-timestampr   zx-lark-request-noncezx-lark-signatureFr|  r  )errorsz3[Feishu] Signature verification raised an exceptionTr  )r   r   r  r  hashlibsha256encode	hexdigestr  r  r   r  r  )	rd  rX  r  r  nonce	signaturebody_strru   computeds	            rO   r  z)FeishuAdapter._is_webhook_signature_valid%
  s0    $>CCIrJJ	GKK 6;;ArBB$6;;ArBB	 	 	Y 	5	!(((CCH"HEH4+<HhHHG~gnnW&=&=>>HHJJH&x;;; 	 	 	LLNY]L^^^55	s   9A3C- -&DDr  c                   t          j                     | j                            |          }|1|\  }}|z
  t          k     r|t          k    rdS |dz   |f| j        |<   dS t          | j                  t          k    rZfd| j                                        D             }|D ]
}| j        |= || j        vrt          | j                  t          k    rdS df| j        |<   dS )u*  Return False when the composite rate_key has exceeded _FEISHU_WEBHOOK_RATE_LIMIT_MAX.

        The rate_key is composed as "{app_id}:{path}:{remote_ip}" — matching openclaw's key
        structure so the limit is scoped to a specific (account, endpoint, IP) triple rather
        than a bare IP, which causes fewer false-positive denials in multi-tenant setups.

        The tracking dict is capped at _FEISHU_WEBHOOK_RATE_MAX_KEYS entries to prevent unbounded
        memory growth. Stale (expired) entries are pruned when the cap is reached.
        NFr   Tc                <    g | ]\  }\  }}|z
  t           k    |S rK   )#_FEISHU_WEBHOOK_RATE_WINDOW_SECONDS)rL   k_r  r  s       rO   r   z;FeishuAdapter._check_webhook_rate_limit.<locals>.<listcomp>P
  s;        a!R8BBB BBBrQ   )r  rK  r   r  _FEISHU_WEBHOOK_RATE_LIMIT_MAXr   _FEISHU_WEBHOOK_RATE_MAX_KEYSr  )rd  r  r  ra  window_start
stale_keysr  r  s          @rO   r  z'FeishuAdapter._check_webhook_rate_limit:
  s    ikk)--h77"'E<\!$GGG::: 57<qy,6O)(3tt())-JJJ   $($=$C$C$E$E  J   1 1-a00t888SAZ=[=[_|=|=|t/0#h!(+trQ   c                    ddl m}  ||j        | j        j                            dd          | j        j                            dd                    S )z?Return the session-scoped key used for Feishu text aggregation.r   r6  r8  Tr9  Fr:  )r;  r7  r  r4  r:  r   )rd  r  r7  s      rO   _text_batch_keyzFeishuAdapter._text_batch_key`
  sg    555555  L$(K$5$9$9:SUY$Z$Z%)[%6%:%:;UW\%]%]
 
 
 	
rQ   c                v    | j         |j         k    o)| j        |j        k    o| j        j        |j        j        k    S )z>Only merge text events when reply/thread context is identical.)r$  r%  r  r  r?  s     rO   _text_batch_is_compatiblez'FeishuAdapter._text_batch_is_compatiblej
  sC     (H,HH G&(*@@G)X_-FF	
rQ   c                  K   |                      |          }t          |j        pd          }| j                            |          }|2||_        || j        |<   d| j        |<   |                     |           dS |                     ||          sF| 	                    |           d{V  || j        |<   d| j        |<   |                     |           dS | j                            |d          }|dz   }|j        pd}|j        r|r|j         d| n|j        p|}|| j
        k    st          |          | j        k    rF| 	                    |           d{V  || j        |<   d| j        |<   |                     |           dS ||_        ||_        |j        |_        |j        r|j        |_        || j        |<   |                     |           dS )z=Debounce rapid Feishu text bursts into a single MessageEvent.r   Nr   r  )r  r   rt   rY  r   _last_chunk_lenr[  _schedule_text_batch_flushr  _flush_text_batch_nowr  r  r  r  )	rd  r  r   	chunk_lenr  existing_count
next_countappended_text	next_texts	            rO   r,  z!FeishuAdapter._enqueue_text_events
  s)     ""5))
(b))	-11#66$-E!.3D&s+34D+C0++C000F--h>> 	,,S111111111.3D&s+34D+C0++C000F8<<S!DD#a'

(b;C=  A]  Ax}77777aianar	555Y$Jd9d9d,,S111111111.3D&s+34D+C0++C000F!#, "_ 	3"'"2H/9','',,,,,rQ   c                H    |                      | j        || j                   dS )z9Reset the debounce timer for a pending Feishu text batch.N)rF  rZ  _flush_text_batchrH  s     rO   r  z(FeishuAdapter._schedule_text_batch_flush
  s3    ##*"	
 	
 	
 	
 	
rQ   task_mapflush_fnc                    |                      |          }|r(|                                s|                                 t          j         ||                    | |<   d S r;  )r   r  r&  r  create_task)r  r   r  
prior_tasks       rO   rF  z$FeishuAdapter._reschedule_batch_task
  s`     \\#&&
 	 joo// 	 +HHSMM::rQ   c                  K   t          j                    }	 | j                            |          }|rt	          |dd          nd}|| j        k    r| j        }n| j        }t          j        |           d{V  | 	                    |           d{V  | j
                            |          |u r| j
                            |d           dS dS # | j
                            |          |u r| j
                            |d           w w xY w)zFlush a pending text batch after the quiet period.

        Uses a longer delay when the latest chunk is near Feishu's ~4096-char
        split point, since a continuation chunk is almost certain.
        r  r   N)r  rJ  rY  r   r#  _SPLIT_THRESHOLDr  r  r  r  rZ  r  )rd  r   rJ  r,  last_lendelays         rO   r  zFeishuAdapter._flush_text_batch
  s<      +--	> 044S99GAHOww(91===aH4000<6-&&&&&&&&&,,S111111111-11#66,FF.223===== GFt-11#66,FF.223==== Gs   A>C :D
c                  K   | j                             |d          }| j                            |d           |sdS t                              d|t          |j        pd                     |                     |           d{V  dS )z,Dispatch the current text batch immediately.Nz*[Feishu] Flushing text batch %s (%d chars)r   )rY  r  r[  r  r  r   rt   r  rL  s      rO   r  z#FeishuAdapter._flush_text_batch_now
  s      *..sD99'++C666 	F8
 b!!	
 	
 	

 ..u55555555555rQ   -tuple[str, MessageType, List[str], List[str]]c                b  K   t          |dd          pd}t          |dd          pd}t          t          |dd          pd          }t                              d||           t	          ||          }|                     ||           d{V \  }}|                     ||          }|j        }	|t          j	        t          j
        t          j        t          j        hv rHt          |          d	k    r5|j        d
v r,|                     |d         |d                    d{V }
|
r|
}	|	|||fS )z?Extract text and cached media from a normalized Feishu message.ru   r   r   r  z3[Feishu] Received raw message type=%s message_id=%sr   r~  )r  r  Nr   >   r_  r  r   )r#  r   r  r  r  "_download_feishu_message_resources _resolve_normalized_message_typer   r,   r3  r4  r2  r1  r   r   _maybe_extract_text_document)rd  rR   r~  r   r  r  r"  r#  r)  rt   injecteds              rO   r&  z&FeishuAdapter._extract_message_content
  sm     gy"55;7NB77=2,;;ArBB
I8U_```-8Q\]]]
(,(O(O!! )P )
 )
 #
 #
 #
 #
 #
 #

K <<ZUU& [1;3DkFWYdYjkkkJ1$$15JJJ!>>z!}kZ[n]]]]]]]]H  \:{::rQ   r  r   tuple[List[str], List[str]]c                 K   g }g }|j         D ]N}|                     ||           d {V \  }}|r*|                    |           |                    |           O|j        D ]_}|                     ||j        |j        |j                   d {V \  }}|r*|                    |           |                    |           `||fS )N)r  r   )r  r   r   fallback_filename)r   _download_feishu_imagerB  r   !_download_feishu_message_resourcer   r   r   )	rd  r  r  r"  r#  r   rb  
media_typer  s	            rO   r  z0FeishuAdapter._download_feishu_message_resources
  s>      !#
!##. 	/ 	/I,0,G,G%# -H - - ' ' ' ' ' '#K  /!!+..."":...#. 		/ 		/I,0,R,R%"+'5"+"5	 -S - - ' ' ' ' ' '#K  /!!+..."":...;&&rQ   r  r,   c                   | pd                                 }|                    d          rt          j        S |                    d          rt          j        S |                    d          rt          j        S |S )Nr   image/audio/video/)rq  r   r,   r1  r4  r2  )r  r&  r  s      rO   _resolve_media_message_typez)FeishuAdapter._resolve_media_message_type  sz     &B--//
  ** 	%$$  ** 	%$$  ** 	%$$rQ   r#  r   c                N   |j         }|dk    r+|                     |r|d         ndt          j                  S |dk    r+|                     |r|d         ndt          j                  S |dk    r+|                     |r|d         ndt          j                  S t          j        S )Nr  r   r   r;  r_  r  )r   r  r,   r1  r4  r3  r  )rd  r  r#  	preferreds       rO   r  z.FeishuAdapter._resolve_normalized_message_type  s    
 5	33k4YKNNWYcnct3uuu33k4YKNNWYcnct3uuu
""33k4YKNNWYcncw3xxxrQ   rb  c                  K   |r|                     d          sdS 	 t          j                            |          t          k    rdS t          |          j                                        }|dvr|dvrdS t          |                              d          }| 	                    |          }d| d| S # t          t          f$ r! t                              d	|d
           Y dS w xY w)Nztext/r   >   .md.txt>   
text/plaintext/markdownr|  encodingz[Content of z]:
z7[Feishu] Failed to inject text document content from %sTr  )r   r  r_  getsize_MAX_TEXT_INJECT_BYTESr   re  rq  	read_text_display_name_from_cached_pathOSErrorr  r  r  )rd  rb  r  rM   ru   ru  s         rO   r  z*FeishuAdapter._maybe_extract_text_document%  s      	*"7"7"@"@ 	2	w{++.DDDr{##*0022C/))j@_._._r;''1171CCG>>{KKL=,==G===+, 	 	 	NNTValpNqqq22	s   (B7 .B7 7?B7 7.C)(C)r   c          
       K   | j         r|sdS 	 |                     ||d          }t          j        | j         j        j        j        j        |           d {V }|r|                                s=t          
                    d|t          |dd          t          |dd                     dS |                     |          }|sdS |                     |d	          }t          |d
d           p| d}|                     ||dt                    }t!          ||          }	|                     ||                     |                    }
|	|
fS # t&          $ r! t          
                    d|d           Y dS w xY w)Nr   r   r\  r  r   r   z+[Feishu] Failed to download image %s: %s %sr
  r  r	  request failedrY  r   r:   allowedrN  r;  z*[Feishu] Failed to cache image resource %sTr  )r=  _build_message_resource_requestr  r   r!  r"  message_resourcer   r  r  r  r#  _read_binary_response_get_response_header_guess_extensionrf  r2   _normalize_media_type_default_image_media_typer   )rd  r  r   r$  r  	raw_bytesrZ  ra  rM   rb  r  s              rO   r  z$FeishuAdapter._download_feishu_image5  s     | 	: 	6	::%"% ;  G
 %.t|/A/R/VX_````````H 8#3#3#5#5 AHfi88He-=>>	   v228<<I v44X~NNLxd;;Q)?Q?Q?QH'',Pa'bbC0DDDK33L$JhJhilJmJm3nnJ
** 	 	 	NNG]aNbbb66	s   BE .E BE 'E;:E;r   r   r  c                 K   | j         r|sdS |g}|dv r|                    d           |D ]}	 |                     |||          }t          j        | j         j        j        j        j        |           d {V }|r|	                                s>t                              d|||t          |dd          t          |dd	                     |                     |          }	|	s|                     |d
          }
t          |dd           pd}|p|p| d| }|                     |
|                     |                    }|                    d          re|                     ||
dt&                    }t)          |	|          }t                              d|           ||p|                     |          fc S |dk    s|                    d          rj|                     ||
dt.                    }t1          |	|          }t                              d|           ||pd|                    d          pd fc S |                    d          rJt5          |          j        s| d}t9          |	|          }t                              d|           ||fc S t5          |          j        s|t:          v r| t:          |          }t9          |	|          }t                              d|           ||p|                     |          fc S # t>          $ r" t                               d||d            Y w xY wdS )!Nr  >   r_  r`  r   r  z>[Feishu] Resource download failed for %s/%s via type=%s: %s %sr
  r  r	  r  rY  r   r   r  r;  r  r:   r  rN  z,[Feishu] Cached message image resource at %sr_  r  rA   z,[Feishu] Cached message audio resource at %sr  oggr  rI   z,[Feishu] Cached message video resource at %sz/[Feishu] Cached message document resource at %sz/[Feishu] Failed to cache message resource %s/%sTr  )!r=  rB  r  r  r   r!  r"  r  r   r  r  r  r#  r  r  r  _guess_media_type_from_filenamer   r  rf  r2   r  r  rg  r1   lstripr   re  r/   _DOCUMENT_MIME_TO_EXT_guess_document_media_typer   r  )rd  r  r   r   r  request_typesrequest_typer$  r  r  rZ  response_filenamera  r  rM   rb  s                   rO   r  z/FeishuAdapter._download_feishu_message_resourceT  s      | 	: 	6&...  ((() <	 <	L;>>)%". ?  
 ")!24<?3E3V3Z\c!d!ddddddd 	x'7'7'9'9 	LLX" $&)<<%1ABB    66x@@	  #88>RR$+Hk4$H$H$NB!,a0AaEaEaW_EaEa!77  @@JJ 8  

 ((22 Z//,Xi/jjC"8"L"L"LKKK NP[\\\&
(Yd6T6TUX6Y6YYYYY7**j.C.CH.M.M*//,Xi/jjC"8"L"L"LKKK NP[\\\&)Z7Z

3@XSX7Z7Z[[[[((22 3>>0 5&.#4#4#4";Ix"P"PKKK NP[\\\&
2222H~~, P?T1T1T"*O,A*,MOOH7	8LLM{[[["Z%\43R3RS[3\3\]]]]   E!	       vs4   BLL(CL BLAL$A1L(MMr  c                    t          | dd           }|dS t          |d          r!t          |                                          S t          |                                          S )Nr   rQ   getvalue)r#  hasattrr  r  rb  )r  file_objs     rO   r  z#FeishuAdapter._read_binary_response  s`    8VT2238Z(( 	.**,,---X]]__%%%rQ   rx   c           	     \   t          | dd           }t          |di           pi }t          |                    ||                    |                                d                    pd                              dd          d                                                                         S )Nr  rX  r   rj  r   r   )r#  r   r   rq  r  r  )r  rx   r  rX  s       rO   r  z"FeishuAdapter._get_response_header  s    ht,,#y"--37;;tW[[r%B%BCCIrJJPPQTVWXXYZ[aacciikkkrQ   ra  r  r   c                  t          | pd          j                                        }||v r|S t          j        |pd                    dd          d                                                                         pd          }||v r|S |S Nr   rj  r   r   )r   re  rq  rk  rl  r  r  )ra  rZ  r&  r  rM   rm  s         rO   r  zFeishuAdapter._guess_extension  s    8>r"")//11'>>J+\-?R,F,FsA,N,Nq,Q,W,W,Y,Y,_,_,a,a,geghhgNrQ   c                   | pd                     dd          d                                                                         }|p|S r  )r  r  rq  )rZ  r&  r  s      rO   r  z#FeishuAdapter._normalize_media_type  sD    "(b//Q77:@@BBHHJJ
$W$rQ   c                    t          | pd          j                                        }t          j        |t          j        | pd          d         pd          S )Nr   r   zapplication/octet-stream)r   re  rq  r.   r   rk  
guess_type)ra  rM   s     rO   r  z(FeishuAdapter._guess_document_media_type  sQ    8>r"")//11'+C1EhnRT1U1UVW1X1v\vwwwrQ   r_  c                    t           j                            |           }|                    dd          }t	          |          dk    r|d         n|}t          j        dd|          S )Nr  r  rX   z	[^\w.\- ])r  r_  rd  r  r   r   r   )r_  rd  rE  ru  s       rO   r  z,FeishuAdapter._display_name_from_cached_path  sY    7##D))sA&&#&u::??uQxxvlC666rQ   c                   t          j        | pd          d         pd                                }|r|S t          | pd          j                                        }|t
          v rd|                    d           S |t          v rd|                    d           S |t          v rt          
                    |          S dS )Nr   r   r  r  r  )rk  r  rq  r   re  rh  r  rg  rf  r3  r  )ra  rm  rM   s      rO   r  z-FeishuAdapter._guess_media_type_from_filename  s    'B77:@bGGII 	N8>r"")//11###-CJJsOO---###-CJJsOO---### ::3???rrQ   r  c                    | pd                                                                 }|dk    rdS d|v sd|v sd|v rdS |dk    rdS dS )Nr   r  r  topicthreadforumr  )r  rq  )r  r  s     rO   r  zFeishuAdapter._map_chat_type  sm    #)r002288::
4j  H
$:$:g>S>S7  7trQ   r  r  c                    t          |                     d          pd                                                                          }|dv r|S |dk    rdS dS )Nr   r   >   r  r  r  r  r  )r   r   r  rq  )r  r  rD  s      rO   r  z'FeishuAdapter._resolve_source_chat_type  s^    y}}V,,23399;;AACC)))Oe##4wrQ   Dict[str, Optional[str]]c                   K   t          |dd           pd }t          |dd           pd }t          |dd           pd }|p|}|                     |p|           d {V }|||dS )Nr   r   r   )r   rY  r  )r#  _resolve_sender_name_from_api)rd  r  r   r   r   
primary_idru  s          rO   r  z%FeishuAdapter._resolve_sender_profile  s      )Y55=)Y55=9j$77?4'
!??
@VhWWWWWWWW!%#
 
 	
rQ   c                    |sdS | j                             |          }|dS |\  }}t          j                    |k     r|S | j                             |d           dS )z>Return a cached sender name only while its TTL is still valid.N)rJ  r   r  r  )rd  r  r  rx   	expire_ats        rO   r  z%FeishuAdapter._get_cached_sender_name  sl     	4(,,Y77>4 i9;;""K##It444trQ   c                  K   |r| j         sdS |                                }|sdS t          j                    }|                     |          }||S 	 ddlm} |                    d          rd}n|                    d          rd}nd}|                                                    |          	                    |          
                                }t          j        | j         j        j        j        j        |           d{V }|r|                                sdS t%          t%          |d	d          d
d          }	t%          |	dd          p2t%          |	dd          p!t%          |	dd          pt%          |	dd          }
|
rAt'          |
t(                    r,|
                                }
|
r|
|t*          z   f| j        |<   |
S n-# t.          $ r  t0                              d|d           Y nw xY wdS )u  Fetch the sender's display name from the Feishu contact API with a 10-minute cache.

        ID-type detection mirrors openclaw: ou_ → open_id, on_ → union_id, else user_id.
        Failures are silently suppressed; the message pipeline must not block on name resolution.
        Nr   )GetUserRequestou_r   on_r   r   r  userrx   ru  nicknameen_namez-[Feishu] Failed to resolve sender name for %sTr  )r=  r  r  r  lark_oapi.api.contact.v3r  r   r  r   user_id_typer  r  r   contactv3r  r   r  r#  r  r   _FEISHU_SENDER_NAME_TTL_SECONDSrJ  r   r  r  )rd  r  trimmedr  cached_namer  id_typer$  r  r  rx   s              rO   r  z+FeishuAdapter._resolve_sender_name_from_api  sH       	 	4//## 	4ikk227;;"	d??????!!%(( $###E** $$#$,,..66w??LLWUU[[]]G$.t|/C/F/K/OQXYYYYYYYYH 8#3#3#5#5 t78VT::FDIIDfd++ 246624T222 4D11	    
4--  zz||  8<cDc>c7dD+G4K 	d 	d 	dLLH)^bLccccc	dts   CG #B&G 'G54G5c                  K   | j         r|sd S || j        v r| j        |         S 	 |                     |          }t          j        | j         j        j        j        j        |           d {V }|r t          |dd                       du rAt          |dd          }t          |dd          }t                              d|||           d S t          t          |d	d           d
d           pg }|r|d         nd }t          |dd           }t          |dd          pd}	t          |dd          pd}
|                     |	|
          }|| j        |<   |S # t          $ r! t                              d|d           Y d S w xY w)Nr  c                     dS r   rK   rK   rQ   rO   r   z3FeishuAdapter._fetch_message_text.<locals>.<lambda>>  r  rQ   Fr
  r  r	  zmessage lookup failedz3[Feishu] Failed to fetch parent message %s: [%s] %sr  r  r   r   r   r   ru   )r   r~  z*[Feishu] Failed to fetch parent message %sTr  )r=  rV  r  r  r   r!  r"  rR   r   r#  r  r  _extract_text_from_raw_contentr   )rd  r  r$  r  r
  r	  r  parentr   r   r~  rt   s               rO   r'  z!FeishuAdapter._fetch_message_text6  s     | 	: 	4111+J77	55jAAG$.t|/A/I/MwWWWWWWWWH JwxMMJJLLPUUUx;;h/FGGTV`bfhkllltGHfd;;WdKKQrE!&0U1XXDF66400Dvz266<"H!$	266<"K66Va6bbD37D$Z0K 	 	 	NNG^bNccc44	s   B&E BE 'F ?F r   r~  c                   t          ||          }|j        r|j        S t          |j        t                    r|j                            d          nd }t          |                                          pd S )Nr  r  )r  r   r  r   r   r   r   r  )rd  r   r~  r  r|   s        rO   r  z,FeishuAdapter._extract_text_from_raw_contentO  sy    -8Q\]]]
" 	+**EOPZPceiEjEjtj)--.@AAApt;%%''/4/rQ   rM   c                n    | pd                                 }|dv rdS d|                    d          pd S )Nr   >   r:   r;   z
image/jpegr  r  jpeg)rq  r  )rM   normalized_exts     rO   r  z'FeishuAdapter._default_image_media_typeV  sI    )**,,...<>--c22<f>>>rQ   r  c                    	 |                                   d S # t          $ r t                              d           Y d S w xY w)Nz-[Feishu] Background inbound processing failed)r  r   r  	exception)r  s    rO   r  z%FeishuAdapter._log_background_failure]  sV    	NMMOOOOO 	N 	N 	NLMMMMMM	Ns    $A A r   c                   t          |dd          }t          |dd          }||hdhz
  }|r| j        r|| j        z  rdS |r| j                            |          nd}|r|j        }|j        }|j        }	n#| j        p| j        }| j	        }t                      }	|dk    rdS |dk    rdS |dk    rdS |d	k    rt          |o||z            S |d
k    rt          |o||	z             S t          |o	|| j	        z            S )z)Per-group policy gate for non-DM traffic.r   Nr   TdisabledFrg  
admin_onlyr   r   )r#  r  r  r   r   r   r   r  r  r  r   r   )
rd  r  r   sender_open_idsender_user_id
sender_idsruler   r   r   s
             rO   _allow_group_messagez"FeishuAdapter._allow_group_messageh  sM    It<< It<<$n5>
 	$, 	J,E 	418Bt $$W---d 	[FIII/E43EF1IIZ5V4\!!5[  
?
Y(>@@@[  
CJ,B'CDDDJKJ1J$JLLLrQ   c                B   |                      ||          sdS t          |dd          pd}d|v rdS t          |dd          pg }|r|                     |          S t          t          |dd          pd|	          }|j        r|                     |j                  S dS )
zCRequire an explicit @mention before group messages enter the agent.Fru   r   z@_allTmentionsNr   r  )r*  r#  _message_mentions_botr  r   _post_mentions_bot)rd  rR   r  r   r~  r,  r  s          rO   r  z*FeishuAdapter._should_accept_group_message  s    ((G<< 	5gy"55;k!!47J55; 	8--h777- ."==C#
 
 

 # 	E**:+CDDDurQ   r,  	List[Any]c                >   |D ]}t          |dd          }t          |dd          }t          |dd          }t          |dd          pd                                }| j        r|| j        k    r dS | j        r|| j        k    r dS | j        r|| j        k    r dS dS )	zJCheck whether any mention targets the configured or inferred bot identity.idNr   r   rx   r   TF)r#  r  r  r  r  )rd  r,  mention
mention_idmention_open_idmention_user_idmention_names          rO   r-  z#FeishuAdapter._message_mentions_bot  s     	 	G $55J%j)TBBO%j)TBBO#GVT::@bGGIIL  _8I%I%Itt  _8I%I%Itt~ ,$."@"@tturQ   r   c                V    |sdS | j         r| j         |v rdS | j        r| j        |v rdS dS )NFT)r  r  )rd  r   s     rO   r.  z FeishuAdapter._post_mentions_bot  sN     	5 	!2m!C!C4 	!2m!C!C4urQ   c                  K   | j         sdS t          | j        | j        | j        f          rdS 	 |                     | j        d          }t          j        | j         j	        j
        j	        j        |           d{V }|r|                                s3t          |dd          }|dk    rt                              d           dS t          t          |dd          dd          }t          |d	d          pd
                                }|r	|| _        dS dS # t"          $ r  t                              dd           Y dS w xY w)zGBest-effort discovery of bot identity for precise group mention gating.Nrr   r   r  r
  ixz[Feishu] Unable to hydrate bot identity from application info. Grant admin:app.info:readonly or application:application:self_manage so group @mention gating can resolve the bot name precisely.r  r  app_namer   z'[Feishu] Failed to hydrate bot identityTr  )r=  anyr  r  r  _build_get_application_requestr  r  r   applicationv6r   r  r#  r  r  r  r   r  )rd  r$  r  r
  r  r:  s         rO   _hydrate_bot_identityz#FeishuAdapter._hydrate_bot_identity  s     | 	F!4#4dnEFF 	F	S99T[9\\G$.t|/G/J/V/Z\cddddddddH 8#3#3#5#5 x668##NNW  
 '(FD995$GGCZ66<"CCEEH *!)* * 	S 	S 	SLLBTLRRRRRR	Ss   BD 	AD &EEc                ,   	 t          j        | j                            d                    }nK# t          $ r Y d S t
          t           j        f$ r& t                              d| j        d           Y d S w xY wt          |t                    r|                    di           ni }t          j                    t          t          |t                    rd |D             }n6t          |t                    rd |                                D             }nd S fd	|                                D             t!          fd
d          d | j                 }t          t%          |                    | _        fd|D             | _        d S )Nr|  r  z5[Feishu] Failed to load persisted dedup state from %sTr  message_idsc                    i | ]E}t          |                                          #t          |                                          d FS )r  ri  ry  s     rO   rP   z8FeishuAdapter._load_seen_message_ids.<locals>.<dictcomp>  sE    (k(k(kDY\]aYbYbYhYhYjYj(kT):):C(k(k(krQ   c                    i | ]>\  }}t          |t                    |                                .|t          |          ?S rK   )r  r   r  r   )rL   r  vs      rO   rP   z8FeishuAdapter._load_seen_message_ids.<locals>.<dictcomp>  sF    eeetq!JqRUDVDVe[\[b[b[d[deq%((eeerQ   c                F    i | ]\  }}|d k    sdk    s	|z
  k     ||S )r  r   rK   )rL   msg_idr  r  ttls      rO   rP   z8FeishuAdapter._load_seen_message_ids.<locals>.<dictcomp>  sD     #
 #
 #
%62SyyC1HHb3 B(6rQ   c                    |          S r;  rK   )r  valids    rO   r   z6FeishuAdapter._load_seen_message_ids.<locals>.<lambda>  s    q rQ   )r   reversec                "    i | ]}||         S rK   rK   )rL   r  rI  s     rO   rP   z8FeishuAdapter._load_seen_message_ids.<locals>.<dictcomp>  s    !B!B!B!!U1X!B!B!BrQ   )r5  r  rF  r  FileNotFoundErrorr  r  r  r  r  r   r   r  _FEISHU_DEDUP_TTL_SECONDSr   r  sortedr  reversedrE  rD  )rd  r8  	seen_datar  
sorted_idsr  rG  rI  s        @@@rO   rc  z$FeishuAdapter._load_seen_message_ids  s   	j!7!A!A7!A!S!STTGG  	 	 	FF-. 	 	 	NNRTXTjuyNzzzFF	 7A$6O6OWGKKr222UW	ikk'i&& 	(k(kI(k(k(kGG	4(( 	eey/@/@eeeGGF#
 #
 #
 #
 #
)0#
 #
 #

 E'9'9'9'94HHHI`$J`I`a
#'(<(<#=#= !B!B!B!Bz!B!B!Bs   -3 
A; 7A;:A;c                \    	  j         j                            dd            j         j         d          }d fd|D             i} j                             t          j        |d          d           d S # t          $ r& t          
                    d	 j         d
           Y d S w xY w)NT)parentsexist_okrA  c                >    i | ]}|j         v |j         |         S rK   )rD  )rL   r  rd  s     rO   rP   z;FeishuAdapter._persist_seen_message_ids.<locals>.<dictcomp>  s2    &s&s&sWX\`\rWrWrq$*@*CWrWrWrrQ   Fr3  r|  r  z,[Feishu] Failed to persist dedup state to %sr  )rF  r  mkdirrE  r  
write_textr5  r6  r  r  r  )rd  recentr8  s   `  rO   r  z'FeishuAdapter._persist_seen_message_ids  s    	r")//t/LLL-t/E.E.F.FGF$&s&s&s&sV&s&s&stG"--dju.U.U.U`g-hhhhh 	r 	r 	rNNI4KalpNqqqqqq	rs   A6A; ;,B+*B+c                <   t          j                     }t          }| j        5  | j                            |          }||dk    s	||z
  |k     r	 d d d            dS || j        |<   | j                            |           t          | j                  | j        k    rR| j        	                    d          }| j        	                    |d            t          | j                  | j        k    R| 
                                 	 d d d            dS # 1 swxY w Y   d S )Nr   TF)r  rM  rI  rD  r   rE  rB  r   r  r  r  )rd  r  r  rG  seen_atstales         rO   r  zFeishuAdapter._is_duplicate  s|   ikk' 	 	,00<<G"qC'MC4G4G	 	 	 	 	 	 	 	
 25D":.$++J777d.//$2HHH044Q77&**5$777 d.//$2HHH **,,,	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   -DB(DDDc                    t                               |          rdt          |          fS d|i}dt          j        |d          fS )NrJ  rt   Fr3  )_MARKDOWN_HINT_REr  r7  r5  r6  )rd  ru   text_payloads      rO   r  z%FeishuAdapter._build_outbound_payload  sQ    ##G,, 	A7@@@@(tz,UCCCCCrQ   r   )rG  r   rK  rK  c               @  K   | j         st          dd          S t          j                            |          st          dd|           S |pt          j                            |          }|                     ||          \  }	}
	 t          |d          5 }|                     |	||          }| 	                    |          }t          j        | j         j        j        j        j        |           d {V }d d d            n# 1 swxY w Y   |                     |d          }|s|                     |d	d
          S |r<d||d}|                     |d|                     ||          ||           d {V }n6|                     ||
t)          j        d|id          ||           d {V }|                     |d          S # t.          $ rF}t0                              d||d           t          dt5          |                    cY d }~S d }~ww xY w)NFr  r  zFile not found: )rJ  requested_message_typerW  	file_typer   r   r   zfile upload failedz#Feishu file upload missing file_keyrZ  r`  )r~   r   r   rJ  r]  r  r3  zfile send failedz#[Feishu] Failed to send file %s: %sTr  )r=  r-   r  r_  r`  rd  _resolve_outbound_file_routingrg  _build_file_upload_body_build_file_upload_requestr  r   r!  r"  r   rh  ri  rj  r  rk  r5  r6  r  r   r  r  r   )rd  r   rJ  r  r   rG  r   rK  ru  upload_file_typeresolved_message_typer  r   r$  rp  r   r^  rr  r  s                      rO   rM  z)FeishuAdapter._send_uploaded_file_message  s      | 	De?CCCCw~~i(( 	Se3Qi3Q3QRRRR ?BG$4$4Y$?$?262U2U"#8 3V 3
 3
//)	=i&& c(33.*! 4  
 99$??(/(9$,/:L:Q:XZa(b(b"b"b"b"b"b"bc c c c c c c c c c c c c c c 33OZPPH 22#$8#H 3     " (!- 	
 *.)E)E## ::7V_:``%% *F * * $ $ $ $ $ $   *.)E)E#2 J
H'=ERRR%% *F * * $ $ $ $ $ $  --.>@RSSS 	= 	= 	=LL>	3Y]L^^^e3s88<<<<<<<<<	=sJ   
G A"D<G DG D2G B	G 
H;HHHr8  c          	     V  K   t          |pi                     d                    }|r|                     |||t          t	          j                                        }|                     ||          }t          j        | j	        j
        j        j        j        |           d {V S |                     |||t          t	          j                                        }|                     d|          }t          j        | j	        j
        j        j        j        |           d {V S )Nr  ru   r   reply_in_thread
uuid_value
receive_idr   ru   rk  r   )r   r   _build_reply_message_bodyr   r  r  _build_reply_message_requestr  r   r=  r!  r"  rR   reply_build_create_message_body_build_create_message_requestrh  )	rd  r   r   r8  r  r   rj  r   r$  s	            rO   _send_raw_messagezFeishuAdapter._send_raw_messageS  s*      B33K@@AA 	V11! /tz||,,	 2  D 77$GGG *4<?+=+E+KWUUUUUUUUU..4:<<((	 / 
 
 44YEE&t|'9'A'H'RRRRRRRRRrQ   c                T    t          | o t          | dd                                 S )Nr  c                     dS r   rK   rK   rQ   rO   r   z3FeishuAdapter._response_succeeded.<locals>.<lambda>r  s    e rQ   r  )r  s    rO   r  z!FeishuAdapter._response_succeededp  s,    HN!L9mm!L!L!N!NOOOrQ   
field_namec                    t                               |           sd S t          | dd           }|rt          ||d           nd S )Nr  )r3  r  r#  )r  rv  r  s      rO   ri  z%FeishuAdapter._extract_response_fieldt  sJ    00:: 	4x..26@wtZ...D@rQ   )r\  r[  r\  c                   |rt          d||          S t          |dd          }t          |d|          }t          dd| d| |          S )NF)r  r  raw_responser
  r  r	  rW  z] )r-   r#  )rd  r  r[  r\  r
  r	  s         rO   rj  z$FeishuAdapter._response_error_result{  sk      	Ze>PXYYYYx33h77%/@4/@/@3/@/@xXXXXrQ   c                    |                      |          s|                     ||          S t          d|                     |d          |          S )N)r[  Tr  )r  r  ry  )r  rj  r-   ri  )rd  r  r[  s      rO   r  z#FeishuAdapter._finalize_send_result  sb    ''11 	Z..x.YYY33HlKK!
 
 
 	
rQ   c           	       K   t          t                    D ]}	 | j        dk    r|                                  d {V  n|                                  d {V   d S # t
          $ r}d| _        |                                  d | _        | 	                                 d {V  |t          dz
  k    r d|z  }t                              d|dz   t          ||           t          j        |           d {V  Y d }~d }~ww xY wd S )Nrs  Fr   r  z:[Feishu] Connect attempt %d/%d failed; retrying in %ds: %s)range_FEISHU_CONNECT_ATTEMPTSr  _connect_websocket_connect_webhookr   r  r  r?  r  r  r  r  r  )rd  attemptr  wait_secondss       rO   r  z!FeishuAdapter._connect_with_retry  si     566 	2 	2G2(K77113333333333//111111111 2 2 2 %66888"&//1111111116::: G|PaK,    mL111111111111112	2 	2s   A A
D'BC<<Dc                B  K   t           st          d          | j        dk    rt          nt          }|                     |          | _        |                                 | _        | j        t          d          | j	        }||
                                rt          d          |                                  d {V  t          | j        | j        t          j        j        | j        |          | _        |                    d t(          | j        |           | _        d S )Nz4websockets not installed; websocket mode unavailablerm   $failed to build Feishu event handlerzadapter loop is not ready)r   r   	log_levelevent_handlerrq  )FEISHU_WEBSOCKET_AVAILABLEr  r  r"   r#   _build_lark_clientr=  r  rC  r@  r  r?  FeishuWSClientr  r  rm   LogLevelINFOr>  run_in_executorr.  r?  )rd  rq  r  s      rO   r~  z FeishuAdapter._connect_websocket  s     ) 	WUVVV"&"3v"="=;..v66"7799&EFFFz<4>>++<:;;;((*********(<'m(-
 
 
 ..*O	
 
rQ   c                  K   t           st          d          | j        dk    rt          nt          }|                     |          | _        |                                 | _        | j        t          d          | 	                                 d {V  t          j                    }|j                            | j        | j                   t          j        |          | _        | j                                         d {V  t          j        | j        | j        | j                  | _        | j                                         d {V  d S )Nz/aiohttp not installed; webhook mode unavailablerm   r  )FEISHU_WEBHOOK_AVAILABLEr  r  r"   r#   r  r=  r  rC  r?  r   Applicationrouteradd_postr  r  	AppRunnerrA  setupTCPSiter  r  rB  r$  )rd  rq  r  s      rO   r  zFeishuAdapter._connect_webhook  sJ     ' 	RPQQQ"&"3v"="=;..v66"7799&EFFF((*********o
D.0LMMM"}S11"((********* [)=t?QSWSeff &&(((((((((((rQ   rq  c                ,   t           j                                                            | j                                      | j                                      |                              t           j	        j
                                                  S r;  )rm   r'   r  r   r  r   r  rq  r  r  WARNINGr  )rd  rq  s     rO   r  z FeishuAdapter._build_lark_client  s]    K!!VDL!!Z())VF^^Yt},--UWW	
rQ   c          
       K   d }|}t          t                    D ]3}	 |                     |||||           d {V }	|rn|                     |	          sYt	          |	dd           }
|
t
          v r?t                              d||
|           d }|                     |||d |           d {V }	|	c S # t          $ r}|}|dk    r(t          
                    t          |                    r |t          dz
  k    r d|z  }t                              d|dz   t          |||           t          j        |           d {V  Y d }~-d }~ww xY w|pt          d          )	Nr  r
  uk   [Feishu] Reply to %s failed (code %s — message withdrawn/missing); falling back to new message in chat %srJ  r   r  zC[Feishu] Send attempt %d/%d failed for chat %s; retrying in %ds: %szFeishu send failed)r|  _FEISHU_SEND_ATTEMPTSrs  r  r#  _FEISHU_REPLY_FALLBACK_CODESr  r  r   r  r  r   r  r  r  )rd  r   r   r8  r  r   
last_erroractive_reply_tor  r  r
  r  r  s                rO   r  z%FeishuAdapter._feishu_send_with_retry  s      +/
"233 -	2 -	2G,2!%!7!7#%#,% "8 " "       # 4+C+CH+M+M "8VT::D;;;E+ #   +/)-)?)?$+%-$+%)%- *@ * * $ $ $ $ $ $   2 2 2 
v%%*B*I*I#c((*S*S%3a777 G|YaK)    mL111111111111112  >L)=>>>s   BB33
E=BEEc                   K   | j         sd S 	 t          t          | j                    n4# t          $ r'}t                              d|d           Y d }~nd }~ww xY wd | _         d S # d | _         w xY w)Nz'[Feishu] Failed to release app lock: %sTr  )rW  r4   r  r   r  r  )rd  r  s     rO   r  zFeishuAdapter._release_app_lock  s      & 	F	+ 68OPPPP 	Z 	Z 	ZNNDcTXNYYYYYYYY	Z '+D###dD#****s+   ( A% 
AAA% AA% %	A.c                    dt                      v r8t          j                                        |                                           S t          |           S )Nr   r   )globalsr   r  r   r  r   r  s    rO   r  z%FeishuAdapter._build_get_chat_request+  sK    wyy((!)++33G<<BBDDDw////rQ   c                    dt                      v r8t          j                                        |                                           S t          |           S )Nr   r  )r  r   r  r  r  r   r  s    rO   r  z(FeishuAdapter._build_get_message_request1  sK    '))++$,..99*EEKKMMM*5555rQ   c                   dt                      v r^t          j                                        |                               |                              |                                          S t          | ||          S )Nr   )r  r   r   )r  r   r  r  r   r   r  r   r  s      rO   r  z-FeishuAdapter._build_message_resource_request7  si    &'))33)133J''(##m$$ *xm\\\\rQ   r   r  c                    dt                      v rKt          j                                        |                               |                                          S t          | |          S )Nr   r9  )r  r   r  r   r  r  r   r9  s     rO   r<  z,FeishuAdapter._build_get_application_requestC  sV    "gii//%-//d	 f48888rQ   rj  rk  c                *   dt                      v rqt          j                                        |                               |                              |                              |                                          S t          | |||          S )Nr   )ru   r   rj  r  )	r  r   r  ru   r   rj  r  r  r   ri  s       rO   rn  z'FeishuAdapter._build_reply_message_bodyN  s    $		11'/11!!(## 11j!! +	
 
 
 	
rQ   r  c                    dt                      v rKt          j                                        |                               |                                          S t          | |          S )Nr   r  )r  r   r  r  r  r  r   r  s     rO   ro  z*FeishuAdapter._build_reply_message_request`  sZ     GII--#+--J''l++	 *<PPPPrQ   c                    dt                      v rKt          j                                        |                               |                                          S t          | |          S )Nr!   r  )r  r!   r  r   ru   r  r   r  s     rO   r  z(FeishuAdapter._build_update_message_bodyk  sZ    %22(022(##!!	 'BBBBrQ   c                    dt                      v rKt          j                                        |                               |                                          S t          | |          S )Nr    r  )r  r    r  r  r  r  r   r  s     rO   r  z+FeishuAdapter._build_update_message_requestv  sZ    !WYY..$,..J''l++	 *<PPPPrQ   rm  c                *   dt                      v rqt          j                                        |                               |                              |                              |                                          S t          | |||          S )Nr   )rm  r   ru   r  )	r  r   r  rm  r   ru   r  r  r   rl  s       rO   rq  z(FeishuAdapter._build_create_message_body  s    %22(022J''(##!!j!! !	
 
 
 	
rQ   receive_id_typec                    dt                      v rKt          j                                        |                               |                                          S t          | |          S )Nr   r  r  )r  r   r  r  r  r  r   r  s     rO   rr  z+FeishuAdapter._build_create_message_request  sZ    !WYY..$,.. 11l++	 \ZZZZrQ   rY  r\  c                    dt                      v rKt          j                                        |                               |                                          S t          | |          S )Nr   rX  )r  r   r  rY  r\  r  r   rX  s     rO   re  z&FeishuAdapter._build_image_upload_body  sX    #wyy00&.00J''u	 *EBBBBrQ   c                    dt                      v r8t          j                                        |                                           S t          |           S )Nr   r  )r  r   r  r  r  r   r  s    rO   rg  z)FeishuAdapter._build_image_upload_request  sK    799,,%-//<<\JJPPRRRL9999rQ   rb  c                   dt                      v r^t          j                                        |                               |                              |                                          S t          | ||          S )Nr   ra  )r  r   r  rb  r   r   r  r   ra  s      rO   rd  z%FeishuAdapter._build_file_upload_body  sg    "gii//%-//9%%9%%d idSSSSrQ   c                    dt                      v r8t          j                                        |                                           S t          |           S )Nr   r  )r  r   r  r  r  r   r  s    rO   re  z(FeishuAdapter._build_file_upload_request  sK    '))++$,..;;LIIOOQQQL9999rQ   c                     t          |          S r;  )r7  r  s     rO   _build_post_payloadz!FeishuAdapter._build_post_payload  s    +G444rQ   r^  rg   c                   t          j        |                     |                    }|                    di                               dg           }|                    |g           t          j        |d          S )Nrq   ru   Fr3  )r5  r  r  
setdefaultrB  r6  )rd  rG  r^  r8  ru   s        rO   rk  z'FeishuAdapter._build_media_post_payload  sm    *T55g>>??$$Wb11<<YKK	{###z'6666rQ   r`  c                    t          |           j                                        }|t          v rdS |t          v rdS |t
          v rt
          |         dfS |dk    r	t          dfS t          dfS )N)opusr_  )mp4r`  r   )r   re  rq  _FEISHU_OPUS_UPLOAD_EXTENSIONS_FEISHU_MEDIA_UPLOAD_EXTENSIONS_FEISHU_DOC_UPLOAD_TYPES_FEISHU_FILE_UPLOAD_TYPE)rJ  r`  rM   s      rO   rc  z,FeishuAdapter._resolve_outbound_file_routing  s}     9oo$**,,000"?111!>***+C0&88!V+++V33'//rQ   )r4  r)   )r:  r   r   r   )r  r   r   r  )r   r	   r   r   r  )r   r   r   r  )NN)
r   r   ru   r   r  r  r   r  r   r-   )r   r   r  r   ru   r   r   r-   )r)  N)r   r   r*  r   r+  r   r{   r   r   r  r   r-   )r?  r   rY  r   r   r   )NNN)r   r   rF  r   rG  r  r  r  r   r  r   r-   )NNNN)r   r   rJ  r   rG  r  r   r  r  r  r   r  r   r-   )r   r   rR  r   rG  r  r  r  r   r  r   r-   )r   r   rU  r   rG  r  r  r  r   r  r   r-   r;  )r   r   r   r  )r   r   rv  r   rG  r  r  r  r   r  r   r-   )r   r   rz  r   rG  r  r  r  r   r  r   r-   )r   r   r   r   ru   r   r   r   )r  r	   r   r  )r  r	   r   r   )r  r   r   r  )r  r   r  r	   r   r  )r  r	   r   r	   )r  r	   r   r   )r  r	   r  r	   r   r  )r  r	   r  r   r  r	   r   r	   )r2  r	   r?  r   rY  r   r   r  )r   r   r   r   )r   r   r   r  )r  r+   r   r  )r  r   r   r  )r  r   r  r   r   r  )r  r   r   r  )r  r	   rR   r	   r  r	   r  r   r  r   r   r  )r  r+   r   r   )r  r+   r   r   )r  r+   r=  r+   r   r   )r   r   r   r  )rv  r   r   r   )rP  r   r|  r   r}  r   r   rQ  )r   r   r&  r   r   r   )
rP  r   rZ  r   r[  r   r|  r   r   r   )rw   r	   r   r	   )r$  r	   r   r	   )rX  r	   r  r  r   r   )r  r   r   r   )r  r   r   r   r  r	   r   r  )rR   r	   r   r  )r  r   r  r   r   r  )r  r   r&  r,   r   r,   )r  r   r#  r   r   r,   )rb  r   r  r   r   r   )r  r   r   r   r   rQ  )
r  r   r   r   r   r   r  r   r   rQ  )r  r	   r   r  )r  r	   rx   r   r   r   )
ra  r   rZ  r   r&  r   r  r   r   r   )rZ  r   r&  r   r   r   )ra  r   r   r   )r_  r   r   r   )r  r   r   r   )r  r   r  r   r   r   )r  r	   r   r  )r  r  r   r  )r   r   r~  r   r   r  )rM   r   r   r   )r  r	   r   r  )r   )r  r	   r   r   r   r   )rR   r	   r  r	   r   r   r   r   )r,  r/  r   r   )r   r   r   r   )r  r   r   r   )ru   r   r   rQ  )r   r   rJ  r   r  r  r   r  rG  r  r   r  rK  r   r   r-   )r   r   r   r   r8  r   r  r  r   r  r   r	   )r  r	   r   r   )r  r	   rv  r   r   r	   )r  r	   r[  r   r\  r  r   r-   )r  r	   r[  r   r   r-   )rq  r	   r   r	   )r   r   r   r	   )r  r   r   r	   )r  r   r   r   r   r   r   r	   )r   r   r  r   r   r	   )
ru   r   r   r   rj  r   rk  r   r   r	   )r  r   r  r	   r   r	   )r   r   ru   r   r   r	   )
rm  r   r   r   ru   r   rk  r   r   r	   )r  r   r  r	   r   r	   )rY  r   r\  r	   r   r	   )r  r	   r   r	   )rb  r   r   r   r   r	   r   r	   )rG  r   r^  rg   r   r   )rJ  r   r`  r   r   rQ  )r   r   r   r   r  r  r7  staticmethodr9  r<  r  r"  r  r  r  r  r  r  r(  r>  rE  rN  rQ  rT  rs  ru  ry  r  r  r
  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r	  r  r  r  r(  r-  r<  r@  r.  rB  rG  rC  rx  r~  rO  r`  rq  r  r  r  r  r  r,  r  rF  r  r  r&  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r'  r  r  r  r*  r  r-  r.  r?  rc  r  r  r  rM  rs  r  ri  rj  r  r  r~  r  r  r  r  r  r  r  r<  rn  ro  r  r  rq  rr  re  rg  rd  re  r  rk  rc  __classcell__)re  s   @rO   r3  r3    s       "" ,& ,& ,& ,& ,& ,&\ M
 M
 M
 \M
^9 9 9 9:
 
 
 
0+ + + +Z+- +- +- +-Z   , , , ,
# # # #& & & &" #'-17= 7= 7= 7= 7=r= = = =D /-1D= D= D= D= D=L 
 
 
 \
, "&"&-1
 
 
 
 
. "&#'"&-1
 
 
 
 
0 "&"&-1
 
 
 
 
. "&"&-1;= ;= ;= ;= ;=z     "&"&-1
 
 
 
 
 
 
@ "&"&-1 
  
  
  
  
  
  
D# # # #J   ? ? ? ?0! ! ! !FQ6 Q6 Q6 Q6f
 
 
 
>M M M M1 1 1 11 1 1 1@ @ @ @: : : :? ? ? ?@- - - -6 Z Z Z \Z? ? ? ?
   0[ [ [ [ 9@ 9@ 9@ 9@v
 
 
 
0@ 0@ 0@ 0@l   - - - -"       LC C C C4: : : ::7 :7 :7 :7x6 6 6 6
 
 
 
A A A A 
 
 
 \
. . . .*
 
 
 
? ? ? ?	6 	6 	6 	6> > > >% % % %> F F F \F    \    \\; \; \; \;|   *       L
 
 
 
 
 
 
 \
$- $- $- $-L
 
 
 
 ; ; ; \;> > > >,6 6 6 6"; ; ; ;4' ' ' '>    \              >L L L Ld & & & \& l l l \l
    \ % % % \% x x x \x 7 7 7 \7    \    \    \

 

 

 

   ) ) ) )V   20 0 0 0 ? ? ? \? N N N \NM M M M M@    &   "   S S S S:C C C C:r r r r   (D D D D "&#'%+>= >= >= >= >= >=@S S S S: P P P \P A A A \A )-Y Y Y Y Y Y
 
 
 
2 2 2 22
 
 
 
4) ) ) ) 
 
 
 
9? 9? 9? 9?v+ + + + 0 0 0 \0
 6 6 6 \6
 	] 	] 	] \	] 9 9 9 \9 
 
 
 \
" Q Q Q \Q C C C \C Q Q Q \Q 
 
 
 \
" [ [ [ \[ C C C \C : : : \:
 	T 	T 	T \	T : : : \:
5 5 5 57 7 7 7 0 0 0 \0 0 0 0 0rQ   r3  rq  c                N    t                               | t           d                   S Nrl   )_ONBOARD_ACCOUNTS_URLSr   rq  s    rO   _accounts_base_urlr    s    !%%f.DX.NOOOrQ   c                N    t                               | t           d                   S r  )_ONBOARD_OPEN_URLSr   r  s    rO   _onboard_open_base_urlr    s    !!&*<X*FGGGrQ   base_urlr   r   c                >   |  t            }t          |                              d          }t          ||ddi          }	 t	          |t
                    5 }t          j        |                                	                    d                    cddd           S # 1 swxY w Y   dS # t          $ rf}|                                }|rJ	 t          j        |	                    d                    cY d}~S # t          t          j        f$ r |dw xY w d}~ww xY w)zPOST form-encoded data to the registration endpoint, return parsed JSON.

    The registration endpoint returns JSON even on 4xx (e.g. poll returns
    authorization_pending as a 400). We always parse the body regardless of
    HTTP status.
    r|  rY  z!application/x-www-form-urlencodedr  rX  r  N)_REGISTRATION_PATHr   r  r   r   _ONBOARD_REQUEST_TIMEOUT_Sr5  r  rb  r  r   r*  r  )r  r   r   r  reqrespr  r  s           rO   _post_registrationr    sy    
+)
+
+CT??!!'**D
#D>;^*_
`
`
`C
S"<=== 	;:diikk0099::	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	;   XXZZ
 	$$z*"3"3G"<"<======== 45 $ $ $t#$sT   B, 9BB, B##B, &B#'B, ,
D6D&C93D9DDDrl   c                    t          |           }t          |ddi          }|                    d          pg }d|vrt          d|           dS )zcVerify the environment supports client_secret auth.

    Raises RuntimeError if not supported.
    r  initsupported_auth_methodsclient_secretzWFeishu / Lark registration environment does not support client_secret auth. Supported: Nr  r  r   r  )rq  r  resmethodss       rO   _init_registrationr  	  ss    
 "&))H
X&'9
:
:Cgg.//52Gg%%$!$ $
 
 	
 &%rQ   c                l   t          |           }t          |ddddd          }|                    d          }|st          d          |                    dd	          }d
|v r|dz  }n|dz  }|||                    dd	          |                    d          pd|                    d          pddS )zXStart the device-code flow. Returns device_code, qr_url, user_code, interval, expire_in.beginPersonalAgentr  r   )r  	archetypeauth_methodrequest_user_infodevice_codez7Feishu / Lark registration did not return a device_codeverification_uri_completer   rd  z&from=hermes&tp=hermesz?from=hermes&tp=hermes	user_codeinterval   	expire_inr[   )r  qr_urlr  r  r  r  )rq  r  r  r  r  s        rO   _begin_registrationr    s    !&))H
X$&&	( (  C ''-((K VTUUUWW0"55F
f}}****"WW["--GGJ'',1WW[))0S  rQ   r  r  r  r  Optional[dict]c                   t          j                     |z   }|}d}d}t          j                     |k     rt          |          }	 t          |d| dd          }	n6# t          t          t
          j        f$ r t          j        |           Y ow xY w|dz  }|dk    rt          ddd	
           n|dz  dk    rt          ddd	
           |		                    d          pi }
|
	                    d          }|dk    r|sd}d	}|		                    d          rO|		                    d          r:|dk    rt                       |	d         |	d         ||
	                    d          dS |		                    dd          }|dv r1|dk    rt                       t                              d|           dS t          j        |           t          j                     |k     |dk    rt                       t                              d|           dS )zPoll until the user scans the QR code, or timeout/denial.

    Returns dict with app_id, app_secret, domain, open_id on success.
    Returns None on failure.
    Fr   pollob_app)r  r  tpr   z#  Fetching configuration results...r   Tendflush   r  	user_infotenant_brandrm   	client_idr  r   )r   r   rq  r   r  )access_deniedexpired_tokenz [Feishu onboard] Registration %sNz)[Feishu onboard] Poll timed out after %ds)r  r  r  r   r  r5  r  r  printr   r  r  )r  r  r  rq  deadlinecurrent_domaindomain_switched
poll_countr  r  r  r  r  s                rO   _poll_registrationr  2  sJ    y{{Y&HNOJ
)++
 
 %n55	$X *0 0  CC
 '4#78 	 	 	Jx   H	 	a
??7RtLLLLL!^q  #2T**** GGK((.B	 }}^446!!/!#N"O 77; 	CGGO$<$< 	A~~k*!/2($==33	   $$666A~~NN=uEEE4 	
8] )++
 
 ` A~~
NN>	JJJ4s   A 0BBr   c                    t           dS 	 t          j                    }|                    |            |                    d           |                    d           dS # t
          $ r Y dS w xY w)zDTry to render a QR code in the terminal. Returns True if successful.NFT)fit)invert)_qrcode_modQRCodeadd_datamakeprint_asciir   )r   qrs     rO   
_render_qrr    s    u!!
C
D
d###t   uus   AA! !
A/.A/r   r   c                T    t           rt          | ||          S t          | ||          S )zVerify bot connectivity via /open-apis/bot/v3/info.

    Uses lark_oapi SDK when available, falls back to raw HTTP otherwise.
    Returns {"bot_name": ..., "bot_open_id": ...} on success, None on failure.
    )r0  _probe_bot_sdk_probe_bot_http)r   r   rq  s      rO   	probe_botr    s0      :fj&9996:v666rQ   c                @   |dk    rt           nt          }t          j                                                            |                               |                              |                              t          j	        j
                                                  S )z9Build a lark Client for the given credentials and domain.rm   )r#   r"   rm   r'   r  r   r   rq  r  r  r  r  )r   r   rq  
sdk_domains       rO   _build_onboard_clientr	    sk     && 0 0mJ		J			
			4=(	)	)	rQ   r  c                   |                      d          dk    rdS |                      d          p*|                      di                                d          pi }|                     d          |                     d          dS )	z>Extract bot_name and bot_open_id from a /bot/v3/info response.r
  r   Nr  r  r   r   )r   r   )r   )r  r  s     rO   _parse_bot_responser    s    xx1t
((5//
BTXXfb1155e<<
BCGGJ''wwy))  rQ   c                
   	 t          | ||          }|                    dddd          }t          t          j        |j                            S # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)z#Probe bot info using lark_oapi SDK.GET/open-apis/bot/v3/infoNT)methodr   r   ry  z%[Feishu onboard] SDK probe failed: %s)	r	  r$  r  r5  r  ru   r   r  r  )r   r   rq  r  r  r  s         rO   r  r    s    &vz6BB~~(	  
 
 #4:dl#;#;<<<   <cBBBttttts   AA 
BA==Bc                Z   t          |          }	 t          j        | |d                              d          }t	          | d|ddi          }t          |t                    5 }t          j        |                                	                    d                    }ddd           n# 1 swxY w Y   |
                    d	          }|sdS t	          | d
d| dd          }	t          |	t                    5 }t          j        |                                	                    d                    }
ddd           n# 1 swxY w Y   t          |
          S # t          t          t          t          j        f$ r&}t                               d|           Y d}~dS d}~ww xY w)z@Fallback probe using raw HTTP (when lark_oapi is not installed).)r   r   r|  z//open-apis/auth/v3/tenant_access_token/internalrY  rv  r  r  Ntenant_access_tokenr  zBearer )AuthorizationrY  rW  z&[Feishu onboard] HTTP probe failed: %s)r  r5  r6  r  r   r   r  r  rb  r  r   r  r   r  KeyErrorr  r  r  )r   r   rq  r  
token_data	token_reqr  	token_resaccess_tokenbot_reqbot_resr  s               rO   r  r    sF   %f--HZ6 L LMMTTU\]]
HHH#%78
 
 
	
 Y(BCCC 	@t
499;;#5#5g#>#>??I	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ !}}%:;; 	4///!9<!9!9 2 
 
 
 W&@AAA 	>Tj!3!3G!<!<==G	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> #7+++gx)=>   =sCCCtttttsf   AE" (:B."E" .B22E" 5B26E" 0E" :E<E" EE" EE" ""F*F%%F*initial_domaintimeout_secondsr  r  c                    	 t          | |          S # t          t          t          t          j        f$ r&}t                              d|           Y d}~dS d}~ww xY w)a  Run the Feishu / Lark scan-to-create QR registration flow.

    Returns on success::

        {
            "app_id": str,
            "app_secret": str,
            "domain": "feishu" | "lark",
            "open_id": str | None,
            "bot_name": str | None,
            "bot_open_id": str | None,
        }

    Returns None on expected failures (network, auth denied, timeout).
    Unexpected errors (bugs, protocol regressions) propagate to the caller.
    r  z([Feishu onboard] Registration failed: %sN)_qr_register_innerr  r   r  r5  r  r  r  )r  r  r  s      rO   qr_registerr    sh    *!Q`aaaa(GT-AB   A3GGGttttts    "AAAc                   t          ddd           t          |            t          |           }t          d           t                       |d         }t          |          rt          d|            n"t          d| d	           t          d
           t                       t	          |d         |d         t          |d         |          |           }|sdS t          |d         |d         |d                   }|r1|                    d          |d<   |                    d          |d<   n
d|d<   d|d<   |S )uI   Run init → begin → poll → probe. Raises on network/protocol errors.z   Connecting to Feishu / Lark...r   Tr  z done.r  z8
  Scan the QR code above, or open this URL directly:
  z3  Open this URL in Feishu / Lark on your phone:

  r  zH  Tip: pip install qrcode  to display a scannable QR code here next timer  r  r  )r  r  r  rq  Nr   r   rq  r   r   )r  r  r  r  r  minr  r   )r  r  r  r  r  bot_infos         rO   r  r    si    

,"DAAAA~&&&//E	(OOO	GGG8_F& ZS6SSTTTTPfPPPQQQXYYY	GGG-(z"eK(/::	  F  t )6,+?AQRRH %%\\*55z (] ; ;}!z $}MrQ   )rt   r   r   r   )rw   r	   r   r   )r   r   r   r   r   r   )r  r   r   r   )r  r   r   r   )Nr   )rw   r	   r&  r   r'  r   r   r   )r   )rw   r	   r&  r   r'  r   r   r   r  )r8  r	   r   r   )r8  r	   r   r   )rS  r	   r   r   )
r  r	   r   r   r   r   r   r   r   r   )
rw   r	   r   r   r   r   r   r   r   r   )r   r   r~  r   r   r   )r~  r   r   r   )r8  r   r   r   )r   r   r8  r   r   r   )r8  r   r   r   )r8  r	   r   r   )rw   r	   r  r   r   r   )r8  r   r   r   r   r   )r   r   r   r   )r8  r	   r   r   )r8  r	   r  r  r   r   )rw   r	   )rR  r	   r   r   )r  r   r   r   )r  r	   r  r	   r   r  r  )rq  r   r   r   )r  r   r   rg   r   r   )rl   )rq  r   r   r  )rq  r   r   r   )
r  r   r  r   r  r   rq  r   r   r  )r   r   r   r   )r   r   r   r   rq  r   r   r  )r   r   r   r   rq  r   r   r	   )r  r   r   r  )r  r   r  r   r   r  )r   
__future__r   r  r  r  r`  r5  loggingrk  r  r   rG  r  r  dataclassesr   r   r   pathlibr   typesr   typingr	   r
   r   r   urllib.errorr   r   urllib.parser   urllib.requestr   r   aiohttpr   ImportErrorr!  	lark_oapirm   lark_oapi.api.application.v6r   r  r   r   r   r   r   r   r   r   r   r   r   r   r    r!   lark_oapi.core.constr"   r#   5lark_oapi.event.callback.model.p2_card_action_triggerr$   r%   "lark_oapi.event.dispatcher_handlerr&   lark_oapi.wsr'   r  r0  r  r  gateway.configr(   r)   gateway.platforms.baser*   r+   r,   r-   r.   r/   r0   r1   r2   gateway.statusr3   r4   hermes_constantsr5   	getLoggerr   r  compiler$  r]  r#  _MENTION_REr  
IGNORECASEr  rf  rg  rh  r  r  rf  r  r  r  r  r  r}  r  r  r  r  r  r  r  r  r  r  rM  r  r  r  r  r  r  r  r  r  rh   r   rj   _FEISHU_BOT_MSG_TRACK_SIZEr   r  r  r  r  r  r  r@  r  r  r  r  r  rQ  r   r  r  r  r  r   r   r   r   r   r   r   r   r   r   r  r  r  r%  r-  r0  r7  rH  r?  rL  rK  r<  rr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rA  r  r.  r1  r3  r  r  r  r  r  r  qrcoder  r)  r  r  r	  r  r  r  r  r  rK   rQ   rO   <module>r>     s2    " # " " " " "              				 				       ( ( ( ( ( ( ( (             ! ! ! ! ! ! , , , , , , , , , , , , , , , , , , , , " " " " " " + + + + + + + +NNN   G
CCC   JJJ$BBBBBB                                 @???????        JIIIII555555   DL"&!NMKKK (t3 "$.  3 3 3 3 3 3 3 3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 D C C C C C C C , , , , , ,		8	$	$ BJ kL   BJ9:: bj''L))%2:&UWYWdee 
 GFF WWW MMM UU4R4L4R4T4TUUU % # "('!2 "B"B"B    $   ( $' !#$   $ %( "  #  ) 
 ) ") !0 &( #!$  $ ') $$& !&1 #(/ %  	( (      %$	' '      ! (y&&)9::   +,  
 '(   2   + 2 * 3  ) 
 ( 'RZ(DEE $"*]33 F##   , $                $; ; ; ; ; ; ; ; $; ; ; ; ; ; ; ; $J J J J J J J J< 5 5 5 5 5 5 5 5 9 9 9 9 9 9 9 99 9 9 9: : : :' ' ' '# # # #B B B B   ,9 9 9 9   $6 6 6 6 61 1 1 1 1   $   >, , , ,   $	 	 	 	L; L; L; L;^   B3N 3N 3N 3NlG G G G   *   8   B#" #" #" #"L@ @ @ @! ! ! !&& & & &Rd d d d_ _ _ _
	4 	4 	4 	4
 
 
 
) ) ) )
 
 
 
$      <' <' <' <'~   
L+0 L+0 L+0 L+0 L+0' L+0 L+0 L+0pVP P P PH H H H   .
 
 
 
 
    > D D D D D DN     Y   KKK   7 7 7 7
 
 
 
          F #     8& & & & & &sI   7
B 	BBB B! B!%AC. .DD/O4 4	P ?P 