
    `gj^                   
   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ZddlZddlm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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+ 	 ddl-Z-	 ddl.Z/ddl0m1Z1 ddl2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9m:Z:m;Z;m<Z<m=Z=m>Z>m?Z?m@Z@ ddlAmBZBmCZC ddlDmEZEmFZF ddlGmHZH ddlImJZJmKZK ddlLmMZM ddlNmOZP dZQe-duZRe*duZSddlTmUZUmVZV ddlWmXZXmYZYmZZZm[Z[m\Z\m]Z]m^Z^m_Z_m`Z`maZa ddlbmcZcmdZd ddlemfZf ddlgmhZhmiZimjZj  ej                  el      Zm ej                  dej                        Zp ej                  dej                        Zq ej                  d      Zr ej                  d       Zs ej                  d!      Zt ej                  d"      Zu ej                  d#      Zv ej                  d$ej                        Zxh d%Zyh d&Zzh d'Z{ e]j                         D  ci c]  \  } }|| 
 c}} Z}d(Z~d)Zd*d+hZh d,Zd-d.d.d/d/d0d0d1Zd2Zd3Zd3Zd4Zd5Zd6Zd7Zd8Zd9Zd:Zd;Zd<Zd=Zd>Zd?Zd@ZdAZdBZdCZdDZdEZdFZdGdHdIdJdKZdLedM<   dNdOdPdQdRZdLedS<   ddTZdUZ edVdWh      ZdXZdYZdZZdUZd[d\d]Zd^d_d]Zd`ZdaZdbZdcZddZdeZdfZdgZdhZ ej                  di      Z ej                  d"      Z edj      Z edk      Z ej                  dl      ZdmZh dnZ edo       G dp dq             Z edo       G dr ds             Z edo       G dt du             Z edo       G dv dw             Z edo       G dx dy             Z edo       G dz d{             Ze G d| d}             Ze G d~ d             Zed   ZddZddZddZddZddZddZddZddZddZddZdddZdddZddZddZdd	 	 	 	 	 ddZddZddZddZ	 d	 	 	 	 	 	 	 	 	 ddZ	 d	 	 	 	 	 	 	 	 	 ddZd e       d	 	 	 	 	 	 	 	 	 ddZddZddZddZddZddZddZddZddZddZddZddZddZddZddZ	 d	 	 	 	 	 ddZddZddZ	 	 	 	 	 	 ddZddZ	 	 	 	 	 	 ddZddZddZ G d deX      ZddZddZddZdddZdddZdd	 	 	 	 	 	 	 	 	 ddZ	 ddlZddZddZddZddZddZddZdd>d	 	 	 	 	 ddZ	 	 	 	 	 	 ddZh dZh d£Zh dãZd*d+hZddddĜdńZ ddƄZd dǄZddȄZdɄ ZddʄZy# e,$ r dZ*dZ+Y w xY w# e,$ r dZ-Y w xY w# e,$ r dZQdZ/dZJdZKdZMdZPdZEdZFY tw xY wc c}} w # e,ef$ r dZY w xY w(  u  
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)
- Processing status reactions: Typing while working, removed on success,
  swapped for CrossMark on failure
- 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)

Feishu identity model
---------------------
Feishu uses three user-ID tiers (official docs:
https://open.feishu.cn/document/home/user-identity-introduction/introduction):

  open_id  (ou_xxx)  — **App-scoped**.  The same person gets a different
                        open_id under each Feishu app.  Always available in
                        event payloads without extra permissions.
  user_id  (u_xxx)   — **Tenant-scoped**.  Stable within a company but
                        requires the ``contact:user.employee_id:readonly``
                        scope.  May not be present.
  union_id (on_xxx)  — **Developer-scoped**.  Same across all apps owned by
                        one developer/ISV.  Best cross-app stable ID.

For bots specifically:

  app_id              — The application's canonical credential identifier.
  bot open_id         — Returned by ``/bot/v3/info``.  This is the bot's own
                        open_id *within its app context* and is what Feishu
                        puts in ``mentions[].id.open_id`` when someone
                        @-mentions the bot.  Used for mention gating only.

In single-bot mode (what Hermes currently supports), open_id works as a
de-facto unique user identifier since there is only one app context.

Session-key participant isolation prefers ``union_id`` (via user_id_alt)
over ``open_id`` (via user_id) so that sessions stay stable if the same
user is seen through different apps in the future.
    )annotationsN)OrderedDict)	dataclassfield)datetime)Path)SimpleNamespace)AnyDictListLiteralOptionalSequence)	HTTPErrorURLError)	urlencode)Requesturlopen)webGetApplicationRequestCreateFileRequestCreateFileRequestBodyCreateImageRequestCreateImageRequestBodyCreateMessageRequestCreateMessageRequestBodyGetChatRequestGetMessageRequestGetMessageResourceRequestP2ImMessageMessageReadV1ReplyMessageRequestReplyMessageRequestBodyUpdateMessageRequestUpdateMessageRequestBodyAccessTokenType
HttpMethodFEISHU_DOMAINLARK_DOMAINBaseRequestCallBackCardP2CardActionTriggerResponseEventDispatcherHandlerClientTF)PlatformPlatformConfig)
BasePlatformAdapterMessageEventMessageTypeProcessingOutcome
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_home)atomic_json_write	env_floatenv_intz(^\|.*\|\s*\n\|[-:|\s]+\|)|(^#{1,6}\s)|(^\s*[-*]\s)|(^\s*\d+\.\s)|(^\s*---+\s*$)|(```)|(`[^`\n]+`)|(\*\*[^*\n].+?\*\*)|(~~[^~\n].+?~~)|(<u>.+?</u>)|(\*[^*\n]+\*)|(\[[^\]]+\]\([^)]+\))|(^>\s)z^\|.*\|\n\|[-|: ]+\|z\[([^\]]+)\]\(([^)]+)\)z^```([^\n`]*)\s*$z^```\s*$z
@_user_\d+z	[ \t]{2,}z,content format of the post type is incorrect>   .bmp.gif.jpg.png.jpeg.webp>   .aac.m4a.mp3.ogg.wav.flac.opus.webm>   .3gp.avi.m4v.mkv.mov.mp4rU   messagestreamrQ   rT   >   rW   rX   rZ   r[   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_alwaysrm   Dict[str, str]_APPROVAL_CHOICE_MAPzApproved oncezApproved for sessionzApproved permanentlyDenied)rj   rk   rl   rm   _APPROVAL_LABEL_MAPc                   K   	 | j                   j                  |dz          d{   }t        |      |kD  rt        d      |S 7 # t        j                  $ r}|j                  }Y d}~Ad}~ww xY ww)z8Read at most ``max_bytes`` from an aiohttp request body.   Nzpayload too large)contentreadexactlyasyncioIncompleteReadErrorpartiallen
ValueError)request	max_bytesbodyexcs       S/root/.hermes/venv/lib/python3.12/site-packages/plugins/platforms/feishu/adapter.py!_read_limited_feishu_webhook_bodyr      si     __00Q?? 4y9,--K @&& {{s=   A3!A AA A3A A0A+&A3+A00A3i   i{ i[ Typing	CrossMarki   z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([\\`*_{}\[\]()#+\-!|>~])u*    	
.,;:!?、，。；：！？()[]{}<>"'`u    	
.!?。！？z\s+)titletextrw   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                  4    e Zd ZU ded<   dZded<   dZded<   y)FeishuPostMediaRefstrr    	file_namefileresource_typeN)__name__
__module____qualname____annotations__r   r        r   r   r   S  s    MIsM3r   r   c                  F    e Zd ZU dZded<   dZded<   dZded<   dZded<   y	)
FeishuMentionRefr   r   r   r   Fboolis_allis_selfN)r   r   r   r   r   r   r   r   r   r   r   r   r   Z  s)    D#NGSFDGTr   r   c                  @    e Zd ZU dZded<   dZded<   dZded<   ddZy)	_FeishuBotIdentityr   r   r   r   r   c                   |r| j                   r|| j                   k(  S |r| j                  r|| j                  k(  S t        | j                        xr || j                  k(  S N)r   r   r   r   )selfr   r   r   s       r   matchesz_FeishuBotIdentity.matchesh  sP     t||dll**t||dll**DII44499#44r   N)r   r   r   r   r   r   returnr   )r   r   r   r   r   r   r   r   r   r   r   r   r   b  s$    GSGSD#N5r   r   c                  P    e Zd ZU ded<    ee      Zded<    ee      Zded<   y)	FeishuPostParseResultr   text_contentdefault_factory	List[str]
image_keysList[FeishuPostMediaRef]
media_refsN)r   r   r   r   r   listr   r   r   r   r   r   r   s  s'    !$7J	7+0+FJ(Fr   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<   y)FeishuNormalizedMessager   raw_typer   r   preferred_message_typer   r   r   r   r   List[FeishuMentionRef]mentionsplainrelation_kindDict[str, Any]metadataN)r   r   r   r   r   r   r   r   r   r   r   dictr   r   r   r   r   r   z  s`    M"(C(!$7J	7+0+FJ(F',T'BH$B M3 $T:Hn:r   r   c                  l   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'Zded(<   d)Zd*ed+<   y),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_pathrh   ws_reconnect_noncerg   ws_reconnect_intervalNOptional[int]ws_ping_intervalws_ping_timeoutadminsr   default_group_policyr   zDict[str, FeishuGroupRule]group_rulesnone
allow_botsTr   require_mention)r   r   r   r   r   r   r   r   	frozensetr   r   r   r   r   r   r   r   r   r   r   r     s    KO'' M##$))  $$  !$3$&*m*%)O])&[FN( "#".3D.IK+IJ OT r   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Z	d	ed
<   y)FeishuGroupRulezLPer-group policy rule for controlling which users may interact with the bot.r   policyr   set[str]	allowlist	blacklistNzOptional[bool]r   )
r   r   r   __doc__r   r   setr   r  r   r   r   r   r   r     s2    VK4Ix44Ix4&*O^*r   r   c                  b    e Zd ZU  ee      Zded<    ee      Zded<    ee      Zded<   y)	FeishuBatchStater   zDict[str, MessageEvent]eventsDict[str, asyncio.Task]taskszDict[str, int]countsN)	r   r   r   r   r   r  r   r  r	  r   r   r   r  r    s1    &+D&AF#A%*4%@E"@"48FN8r   r  )	self_echoself_ids_unknownbots_disabledbot_not_mentionedgroup_policy_rejectedc                     t        | dd      dv S )Nsender_typer   >   appbot)getattr)senders    r   _is_bot_senderr    s    6="-??r   c           
         t        | dd       }|
t               S t        d t        |dd       t        |dd       t        |dd       fD              S )N	sender_idc              3  $   K   | ]  }|r| 
 y wr   r   .0vs     r   	<genexpr>z#_sender_identity.<locals>.<genexpr>  s      
  	
s   r   r   r   )r  r   )r  sids     r   _sender_identityr    s]    
&+t
,C
{{ CD)CD)CT*
  r   c                .    t         j                  d|       S )Nz\\\1)_MARKDOWN_SPECIAL_CHARS_REsub)r   s    r   _escape_markdown_textr"    s    %))'488r   c                &    | du xs | dk(  xs | dk(  S )NTrv   truer   )r   s    r   _to_booleanr%    s    D=9EQJ9%6/9r   c                <    | syt        | j                  |            S NF)r%  get)stylekeys     r   _is_style_enabledr+    s    uyy~&&r   c           	         t        dgt        j                  d|       D cg c]  }t        |       c}      }d|dz   z  }| j	                  d      s| j                  d      rd|  dn| }| | | S c c}w )Nr   z`+`rv    )maxrefindallr|   
startswithendswith)r   runmax_runfencer   s        r   _wrap_inline_coder7    sz    1EBJJud,CDSCDEFG7Q;E//#.$--2DQtfA;$DWTF5'"" Es   A4
c                b    | j                         j                  dd      j                  dd      S )N
r.  )stripreplace)languages    r   _sanitize_fence_languager>    s)    >>##D#.66tSAAr   c                h   t        | j                  dd      xs d      }| j                  d      }t        |t              r|nd }t	        |d      rt        |      S t        |      }|syt	        |d      rd| d}t	        |d      rd| d}t	        |d	      rd
| d}t	        |d      rd| d}|S )Nr   r   r)  codebold**italic*	underlinez<u>z</u>strikethroughz~~)r   r(  
isinstancer   r+  r7  r"  )elementr   r)  
style_dictrendereds        r   _render_text_elementrK    s    w{{62&,"-DKK E$UD1tJV, &&$T*HV,z$X.xj?[1
$'_5z$Or   c                t   t        t        | j                  dd      xs d      xs t        | j                  dd      xs d            }t        | j                  dd      xs d      xs t        | j                  dd      xs d      j                  dd      }|j	                  d      rdnd}d| d| | dS )	Nr=  r   langr   rw   
r9  ```)r>  r   r(  r<  r3  )rH  r=  r@  trailing_newlines       r   _render_code_block_elementrQ    s    'GKK
B'-2.T#gkk&"6M6SQS2TH 	GKK#)r*Sc'++i2L2RPR.Sgfd 	 "]]40rd
"TF#3"4C88r   c                l   ddl m} | j                  dd      }t        j	                  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_markdownrN  r9  c                j    | j                  d       d| j                  d      j                          dS )Nrv   z (   ))groupr;  )ms    r   <lambda>z/_strip_markdown_to_plain_text.<locals>.<lambda>&  s.    qwwqzl"QWWQZ=M=M=O<PPQ,R r   z^>\s?r   )flagsz^\s*---+\s*$z---z~~([^~\n]+)~~z\1z<u>([\s\S]*?)</u>)gateway.platforms.helpersrS  r<  _MARKDOWN_LINK_REr!  r0  	MULTILINE)r   rS  r   s      r   _strip_markdown_to_plain_textr^    s     9LL&E!!"RTYZEFF8Rbll;EFF?E5EEFF#UE2EFF'6E5!ELr   c                Z    	 t        |       }||k\  r|S |S # t        t        f$ r |cY S w xY w)zACoerce value to int with optional default and minimum constraint.)r   	TypeErrorr}   r   default	min_valueparseds       r   _coerce_intre  /  s?    U y(65g5 z" s    **c                *    t        | ||      }||S |S )Nrb  rc  )re  ra  s       r   _coerce_required_intrh  8  s    9EFn70&0r   c                N    t        |       }t        j                  dd|iid      S Nr   rw   Fensure_ascii)_build_markdown_post_rowsjsondumps)rw   rowss     r   _build_markdown_post_payloadrq  B  s4    $W-D::4	

  r   c                   | sdddggS d| vrd| dggS g g d}dfd}| j                         D ]  }|j                         }t        |rt        j	                  |      nt
        j	                  |            }|r'|s |        j                  |       | }|s |        qj                  |         |        xs d| dggS )aF  Build Feishu post rows while isolating fenced code blocks.

    Feishu's `md` renderer can swallow trailing content when a fenced code block
    appears inside one large markdown element. Split the reply at real fence
    lines so prose before/after the code block remains visible while code stays
    in a dedicated row.
    mdr   r   r   rO  Fc                 |    sy dj                        } | j                         rj                  d| dg       g y )Nr9  rs  rt  )joinr;  append)segmentcurrentrp  s    r   _flush_currentz1_build_markdown_post_rows.<locals>._flush_current_  s9    ))G$==?KKw789r   r   None)
splitlinesr;  r   _MARKDOWN_FENCE_CLOSE_REmatch_MARKDOWN_FENCE_OPEN_RErw  )rw   in_code_blockrz  raw_linestripped_lineis_fencery  rp  s         @@r   rm  rm  N  s     r*+,,Gw/011')DGM &&( ! ( %**=9(..}=
   NN8$ --M  x #!& 5T73455r   mentions_mapc                  t        |       }|st        t              S g g g }t        t	        |j                  dd            j                               }|r|j                  |       |j                  dg       xs g D ]L  }t        |t              st        dj                  fd|D                    }|s<|j                  |       N t        dj                  |      j                         xs t              S )N)r   r   r   rw   c              3  :   K   | ]  }t        |        y wr   )_render_post_element)r  itemr   r   r  s     r   r  z,parse_feishu_post_payload.<locals>.<genexpr>  s$       %T:z<Ps   r9  )r   r   r   )_resolve_post_payloadr   FALLBACK_POST_TEXT_normalize_feishu_textr   r(  r;  rw  rG  r   rv  )	payloadr  resolvedpartsr   rowrow_textr   r   s	    `     @@r   parse_feishu_post_payloadr    s    
 %W-H$2DEEJ+-JE"3x||GR'@#A#G#G#IJEU||Ir*0b 
##t$)GG  
 LL"
# !YYu%++-C1C r   c                    t        |       }|r|S t        | t              si S | j                  d      }t	        |      }|r|S t	        |       S )Npost)_to_post_payloadrG  r   r(  _resolve_locale_payload)r  directwrappedwrapped_directs       r   r  r    sO    g&Fgt$	kk&!G,W5N"7++r   c                    t        |       }|r|S t        | t              si S t        D ]"  }t        | j	                  |            }|s |c S  | j                         D ]  }t        |      }|s|c S  i S r   )r  rG  r   _PREFERRED_LOCALESr(  values)r  r  r*  	candidater   s        r   r  r    s    g&Fgt$	! $W[[%56	 ! $U+	 Ir   c                    t        | t              si S | j                  d      }t        |t              si S t	        | j                  dd      xs d      |dS )Nrw   r   r   )r   rw   )rG  r   r(  r   r   )r  rw   s     r   r  r    sS    i&	mmI&Ggt$	Y]]7B/526 r   c                   t        | t              r| S t        | t              syt        | j                  dd            j	                         j                         }|dk(  rt        |       S |dk(  rqt        | j                  dd            j	                         }t        | j                  d|      xs d      j	                         }|syt        |      }|r	d| d| dS |S |d	k(  rt        | j                  d
d            j	                         }|dk(  r|d|vrt        d      |d<   y|xs i j                  |      }	|	|	j                  xs |	j                  xs d}
n-t        | j                  dd            j	                         xs d}
dt        |
       S |dv rt        | j                  dd            j	                         }|r||vr|j                  |       t        | j                  dd            j	                         xs) t        | j                  dd            j	                         }|rd| dS dS |dv rt        | j                  dd            j	                         }t        | j                  dd            j	                         xsT t        | j                  dd            j	                         xs) t        | j                  dd            j	                         }|r#|j                  t        |||dv r|nd             |rd| dS d S |d!v rgt        | j                  dd            j	                         xs) t        | j                  d"d            j	                         }|rd#t        |       d#S d$S |d%k(  ry&|d'v ry(|d)k(  rOt        | j                  dd      xs d      xs t        | j                  d*d      xs d      }|rt        |      S dS |d+v rt        |       S g }d,D ]3  }t        | j                  |      |||      }|s#|j                  |       5 d-j!                  d. |D              S )/Nr   r   r   ar   [z](rV  atr   @_allTr   @alluser	user_name@>   imgimager   altz[Image: ]r   >   r   audiomediavideor   r   r   >   r  r  r   r   r   r   [Attachment: r   >   emojiemotion
emoji_type:z[Emoji]brr9  >   hrdividerz

---

r@  rw   >   pre
code_block)r   r   rw   childrenelementsr.  c              3  &   K   | ]	  }|s|  y wr   r   )r  parts     r   r  z'_render_post_element.<locals>.<genexpr>  s     :TTD:   )rG  r   r   r(  r;  lowerrK  r"  r   r   r   rw  r   r7  rQ  _render_nested_postrv  )rH  r   r   r  r   r   r   escaped_labelr   refdisplay_namer   r  r   r   r@  nested_partsr*  	extracteds                      r   r  r    s    '3gt$
gkk%$
%
+
+
-
3
3
5C
f}#G,,
cz7;;vr*+113GKK-34::<-e4/3=/D6+FF
d{ '++i45;;='! 'G<,G(8(EW%!r&&{3?88<s{{<fLw{{;;<BBDNL(6788
K45;;=	*4i('++fb)*002Yc'++eR:P6Q6W6W6Y$'#a 6Y6
11w{{:r2399;K,-335 47;;w+,22447;;vr*+113 	
 "%'),0B)B# 09yk+LnL
""GKK+,224bGKKVX<Y8Z8`8`8b6;(/02JJ
d{

f}7;;vr*0b1ZSYPR9S9YWY5Z*. &6B6
##)'22 LC +'C(8*jR^_		*+ 88:\:::r   c                0   t        | t              rt        |       S t        | t              rdj	                  fd| D              S t        | t
              r<t        |       }|r|S dj	                  fd| j                         D              S y)Nr.  c              3  B   K   | ]  }t        |      }|r|  y wr   r  r  r  r  r   r   r  s      r   r  z&_render_nested_post.<locals>.<genexpr>(  ,      
,T:z<X 
   c              3  B   K   | ]  }t        |      }|r|  y wr   r  r  s      r   r  z&_render_nested_post.<locals>.<genexpr>2  r  r  r   )rG  r   r"  r   rv  r   r  r  )r   r   r   r  r  s    ``` r   r  r    s     %$U++%xx 

 
 	
 %%eZ\RMxx 

 
 	
 r   )r   r  c           
        t        | xs d      j                         j                         }t        |      }t	        ||      }|dk(  ret        |j                  dd      xs d      }d|v rd|vrt        d      |d<   t        |t        ||      t        |j                                     S |dk(  ret        ||      }t        ||j                  t        |j                        t        |j                        t        |j                               d	      S t        |j                               }	|d
k(  rt        |j                  dd      xs d      j                         }
t        t        |j                  dd      xs d      xs' t        |j                  dd      xs d      xs t        |      }t        ||t        k7  r|ndd|
r|
gng d
|	      S |dv rLt!        ||      }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   r   r  Tr  )r   r   r   r  r  )r   r   r   r   r   r   r  r   r  photo)r   r   r   r   r   r   >   r   r  r  )r   r  documentplaceholder_text)r   r   r   r   r   r   r   merge_forward
share_chat>   cardinteractive)r   r   )r   r;  r  _load_feishu_payload_build_mentions_mapr(  r   r   r  r   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   raw_contentr   r  normalized_typer  r  r   parsed_postmention_refsr   alt_text	media_refr   s                 r   normalize_feishu_messager  @  sq    ,,"-335;;=O";/G&x5L& 7;;vr*0b1 d?wl:$4D$AL!&$/lC,--/0
 	

 &  0lS&$$11K223K223,--/0 
 	
 ++-.L'!K4:;AAC	)FB'-2. #7;;ub)/R0#"	
 '$%-1D%D"#*&/	{R!!
 	
 441'Y	-i.A.AB&$.=.H7j&/&8&8	{b)(+6!
 	
 /)/88,&,W5511-owGG"O"MMr   c                    	 | rt        j                  |       ni }t        |t              r|S d|iS # t         j                  $ r d| icY S w xY w)Nr   rw   )rn  loadsJSONDecodeErrorrG  r   )r  rd  s     r   r  r    sV    %,7K(R  -6FIv3FF  %$$%s   1 A
Ac           	     z   t        | j                  d      | j                  d      | j                  d      t        | d            }t        |       }g }|r|j	                  |       |j                  |d d        dj                  |      j                         xs t        }t        d|dt        |      |d	
      S )Nr   r   preview)r   r   r  r   keysrc   r9  r  )entry_countr   r   r   r   r   )_first_non_empty_textr(  _find_first_text_collect_forward_entriesrw  extendrv  r;  FALLBACK_FORWARD_TEXTr   r|   )r  r   entrieslinesr   s        r   r  r    s    !GII'UV	E 'w/GEU	LL!99U#))+D/DL" !%!$W>	 r   c           	        t        | j                  d      | j                  d      | j                  d      t        | d            }t        | j                  d      | j                  d      | j                  d            }g }|r|j                  d	|        n|j                  t               |r|j                  d
|        dj                  |      }t        d|d||d      S )N	chat_namer   r   )r  r   r   r  r   r   r   zShared chat: z	Chat ID: r9  r  )r   r  r  )r  r(  r  rw  FALLBACK_SHARE_CHAT_TEXTrv  r   )r  r  share_idr  r   s        r   r  r    s    %K FG'EF	I %IN#O$H
 E}YK01-.y
+,99U#L"!"%I>	 r   c                   t        |j                  d      t              r|j                  d      n|}t        t	        |      |j                  d      t        |d            }t        |      }t        |      }g }|r|j                  |       |D ]  }||k7  s	|j                  |        |r#|j                  ddj                  |              dj                  |d d       j                         xs t        }t        | |d	||d
      S )Nr  r   )r   r   r   r  z	Actions: , r9     r  )r   actionsr  )rG  r(  r   r  _find_header_titler  _collect_card_lines_collect_action_labelsrw  rv  r;  FALLBACK_INTERACTIVE_TEXTr   )	r   r  card_payloadr   
body_linesr  r  liner   s	            r   r  r    s    *4W[[5H$*O7;;v&U\L!<(G,LME
 %\2J$\2GEU 5=LL y7!3 45699U3BZ(..0M4ML"!# W5	 r   c                   g }dD ]5  }| j                  |      }t        |t              s%|j                  |       7 g }|D ]  }t        |t              s/t        t        |xs d            }|r|j                  d|        Ct        |j                  d      |j                  d      |j                  d      |j                  d            }t        |j                  dd      xs |j                  d	d            j                         j                         }|d
k(  r)t        |j                  d      xs |      j                  }	nVt        |j                  d      |j                  d      |j                  d      |j                  d      t        |d            }	t        |	      }	|r|	r|j                  d| d|	        |	s|j                  d|	         t        |      S )N)messagesitemsmessage_listrecordsrw   r   z- sender_namer  r  r   r   r   r  rw   r   r   r  )r   rw   r   r  r   r  z: )r(  rG  r   r  r   r  r   rw  r  r;  r  r  r   r  _unique_lines)
r  
candidatesr*  r   r  r  r   r  nested_typer   s
             r   r  r    s   JJ %C eT"e$% G ($%)#djb/:DD6{+&HH]#HH[!HHXHHV	
 $((>26R$((:r:RSYY[aac& ,TXXi-@-HDIVVD( ### ,^_D &d+dNNRxr$01NNRv;'7(8 !!r   c                    t        | d      }|D cg c]  }t        |       }}t        |D cg c]  }|s|	 c}      S c c}w c c}w )NFin_rich_block)_collect_text_segmentsr  r  )r  r  r  
normalizeds       r   r  r  	  sH    "7%@E;@A4(.AJA:>4$>?? B>s   AAAc           
        g }t        |       D ]  }t        |t              st        |j	                  dd      xs |j	                  dd            j                         j                         }|dvrdt        |j	                  d      |j	                  d      |j	                  d      t        |d	            }|s|j                  |        t        |      S )
Nr   r   r   >   buttonpickeroverflowdate_pickerselect_staticr   r   r   )r   rw   r   r   r  )_walk_nodesrG  r   r   r(  r;  r  r  r  rw  r  )r  labelsr  r   r   s        r   r   r     s    FG$ !$%$((5"%=&")=>DDFLLNVV%HHVHHVHHWT(LM	
 MM% !   r   c                  t        | t              r|rt        |       gS g S t        | t              r'g }| D ]  }|j	                  t        ||               |S t        | t              sg S t        | j                  dd      xs | j                  dd            j                         j                         }|xs |dv }g }t        D ]F  }| j                  |      }t        |t              s%|s(t        |      }|s6|j                  |       H | j                         D ]*  \  }}|t        v r|j	                  t        ||             , |S )Nr  r   r   r   >   divnoteactionr  columnlark_mdmarkdown
column_set
plain_textr  r  )rG  r   r  r   r  r  r   r(  r;  r  _SUPPORTED_CARD_TEXT_KEYSrw  r  _SKIP_TEXT_KEYS)r   r  segmentsr  r   next_in_rich_blockr*  r  s           r   r  r  "  sE   %2?&u-.GRG%  	WDOO24}UV	WeT"	
eiir";eii&;
<
B
B
D
J
J
LC& # 2 + H( ,yy~dC %7/5J
+, [[] X	T/!.tCUVWX Or   c                   t        | j                  dd      xs d      j                         }t        | j                  d      | j                  d      | j                  d            }|dv r|nd}t	        |||      S )	Nr   r   r   r   r   >   r  r  r   r  )r   r(  r;  r  r   )r  r   r   r   effective_types        r   r  r  K  st    7;;z2.4"5;;=H%K GFI
 '47I&I]vNx9Tbccr   c                4    t        |       }|rd| dS t        S )Nr  r  )r  FALLBACK_ATTACHMENT_TEXT)r   normalized_names     r   r  r  V  s$    ,Y7O1@]?+1-^F^^r   c                N   t        | t              sy| j                  d      }t        |t              sy|j                  d      }t        |t              r:t        |j                  d      |j                  d      |j                  d            S t	        t        |xs d            S )Nr   headerr   rw   r   r   )rG  r   r(  r  r  r   )r  r.  r   s      r   r  r  [  s    gt$[["Ffd#JJwE%$UYYy%9599V;LeiiX^N_``!#ekr"233r   c                   t        |       D ]O  }t        |t              s|D ]7  }|j                  |      }t        |t              s%t        |      }|s3|c c S  Q yNr   )r  rG  r   r(  r   r  )r  r  noder*  r   r  s         r   r  r  g  sa    G$ &$% 	&CHHSME%%3E:
%%	&& r   c              #     K   t        | t              r-|  | j                         D ]  }t        |      E d {     y t        | t              r| D ]  }t        |      E d {     y y 7 37 
wr   )rG  r   r  r  r   )r   r  s     r   r  r  t  so     %LLN 	)D"4(((	)	E4	  	)D"4(((	) 
! ) )s!   6A0A,*A0#A.$	A0.A0c                     | D ]X  }t        |t              rt        |      }|s!|c S |(t        |t        t        f      r?t        t        |            }|sV|c S  yr0  )rG  r   r  r   r   )r  r   r  s      r   r  r  ~  s]     "eS!/6J!!z%$'F/E
;J!!" r   c                   dfd}t         j                  || xs d      }|j                  dd      }|j                  dd      j                  dd      }dj                  d |j	                  d      D              }dj                  d	 |j	                  d      D              }t
        j                  d
|      }|j                         S )Nc                    | j                  d      }xs i j                  |      }|y|j                  xs |j                  xs d}d| S )Nr   r.  r  r  )rW  r(  r   r   )r  r*  r  r   r  s       r   _subz$_normalize_feishu_text.<locals>._sub  sP    kk!n!r&&s+;xx03;;0&4&zr   r   r  r  rN  r9  r:  c              3  d   K   | ](  }t         j                  d |      j                          * yw)r.  N)_WHITESPACE_REr!  r;  r  r  s     r   r  z)_normalize_feishu_text.<locals>.<genexpr>  s&     ^$**35;;=^s   .0c              3  &   K   | ]	  }|s|  y wr   r   r9  s     r   r  z)_normalize_feishu_text.<locals>.<genexpr>  s     EEr  r.  )r  z're.Match[str]'r   r   )_MENTION_PLACEHOLDER_REr!  r<  rv  split_MULTISPACE_REr;  )r   r  r6  cleaneds    `  r   r  r    s     &))$
;Googv.Goofd+33D$?Gii^'--X\J]^^GiiEt)<EEG  g.G==?r   c                ~    t               }g }| D ]+  }|r||v r
|j                  |       |j                  |       - |S r   )r  addrw  )r  seenuniquer  s       r   r  r    sG    UDF tt|d	
 Mr   c                    t        | dd       }t        |t              r;t        t        | dd      xs d      j                         }|dk(  r|dfS |dk(  rd|fS y|yt        t        |dd      xs d      t        t        |dd      xs d      fS )Nidid_typer   r   r   r   r   )r  rG  r   r  )mention
mention_idrE  s      r   _extract_mention_idsrI    s     $-J*c"ggy"5;<BBDir>!iz>!GJ	2.4"5GJ	2.4"5 r   c           
     6   i }| xs g D ]  }t        t        |dd      xs d      }|s |dk(  rt        d      ||<   5t        |      \  }}t        t        |dd      xs d      j	                         }t        |||j                  |||            ||<    |S )	Nr*  r   r  Tr  r   r   r   r   )r   r   r   )r   r  r   rI  r;  r   )r   r  resultrG  r*  r   r   r   s           r   r  r    s     +-F>r 
''5"-34'>*$7F3K/877FB/526<<>&KKtKL
s
 Mr   c                   g }t               }| D ]  }|j                  r|j                  |j                  |j                  f}||v r8|j                  |       |j                  r|j                  d       g|j                  r0|j                  |j                  xs d d|j                   d       |j                  |j                  xs d        |rddj                  |       dS dS )	Nr  unknownz
 (open_id=rV  z[Mentioned: r  r  r   )r  r   r   r   r   r@  rw  rv  )r   r  rA  r  	signatures        r   _build_mention_hintrP    s    ED 0;;ZZchh7	::LL [[LLCHH1	2*S[[MKLLL.Y/0 27\$))E*+1->B>r   c                @   | s| S |D cg c]/  }|j                   r!d|j                  xs |j                  xs d 1 }}|s| S | j                         }	 |D ]A  }|j	                  |      s|t        |      d  }|r|d   t        vr1|j                         } n nH	 t        |      }|dkD  r'||dz
     t        v r|dz  }|dkD  r||dz
     t        v r|d | }||d  }	|D ]5  }|j                  |      s|d t        |        j                         |	z   } n |S ~c c}w )Nr  r  r   rv   )
r   r   r   lstripr2  r|   _MENTION_BOUNDARY_CHARS_TRAILING_TERMINAL_PUNCTr3  rstrip)
r   r   r  
self_names	remainingnmafterir   tails
             r   _strip_edge_self_mentionsr\    sm     ;; CHH--v./J 
 I
 		B''+c"gh'Eq)@@I		   	N!e	!a%(,DDFA !e	!a%(,DD!}} 	B}}R  CG8,335<		
  +s   4Dc           	     &   	
 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|        	        	  j                          |j                  _	        
t         d
       t        j                  |      D cg c]  }|j                         r| }}|D ]  }|j!                           |r$|j#                  t        j$                  |ddi       	 |j'                          	 |j)                          d_        y# t        $ r Y w xY wc c}w # t        $ r Y 8w xY w# t        $ r
 Y d_        yw xY w# |j                  _	        
t         d
       t        j                  |      D cg c]  }|j                         r| nc c}w }}|D ]  }|j!                           |r$|j#                  t        j$                  |ddi       	 |j'                          n# t        $ r Y nw xY w	 |j)                          d_        w # t        $ r
 Y d_        w w xY wxY w)zCRun the official Lark WS client in its own thread-local event loop.r   N
_configurec                     	 t        d j                         t        d j                          j                  t        d j                         y y # t        $ r t
        j                  dd       Y y 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)adapter	ws_clients   r   _apply_runtime_ws_overrideszC_run_official_feishu_ws_client.<locals>._apply_runtime_ws_overrides+  sv    	`I173N3NOI4g6T6TU((4	#3W5N5NO 5 	`LLOZ^L_	`s   AA  A98A9c                     j                   d|vrj                   |d<   j                  d|vrj                  |d<    | i |S )Nping_intervalping_timeout)rh  _ws_ping_timeout)argskwargsrl  original_connects     r   _connect_with_overridesz?_run_official_feishu_ws_client.<locals>._connect_with_overrides4  sY    $$0_F5R&-&?&?F?###/N&4P%,%=%=F>"000r   c                @    t        d       |       }         |S )NzFFeishu _configure_with_overrides called but original_configure is None)RuntimeError)confrL  rn  original_configures     r   _configure_with_overrideszA_run_official_feishu_ws_client.<locals>._configure_with_overrides;  s*    %ghh#D)#%r   return_exceptionsTr{  )rs  r
   rt  r
   r   r
   )ry  r
   r   r
   )lark_oapi.ws.clientwsclientry   new_event_loopset_event_looploop_ws_thread_loop
websocketsconnectr  re  startri  	all_tasksdonecancelrun_until_completegatherstopclose)rm  rl  ws_client_moduler  rv  r{  tpendingtaskrn  rz  ru  s   ``       @@@r   _run_official_feishu_ws_clientr    sh   22!!#D4  "G'22:: L$?`1 +B'%	<)BC!' /?##+)I|-?@%//5FQVVX1FF 	DKKM	##GNNG$Tt$TU	IIK	JJL #''   G  		  	"&	 /?##+)I|-?@%//5FQVVX1FFF 	DKKM	##GNNG$Tt$TU	IIK 			JJL #'  	"&	s   &E0 -E?E?F F 0	E<9F) ;E<<F) 	FF	F&%F&)8J!G<7G<;AJIJ	IJIJ"I:2J:	J	JJJc                 J    t         ryd } ddlm}  |d| t               d      S )zCheck if Feishu/Lark dependencies are available.

    Lazy-installs lark-oapi via ``tools.lazy_deps.ensure("platform.feishu")``
    on first call if not present. Rebinds all module-level globals on success.
    Tc                 ,   dd l } ddlm} ddlm}m}m}m}m}m	}m
}m}	m}
m}m}m}m}m} ddlm}m} ddlm}m} ddlm} ddlm}m} ddlm} dd	lm } i d
| d|d|d|d|d|d|d|d|d|	d|
d|d|d|d|d|d|||||||||dd	S )Nr   r   r   r'   r*   r-   r/   r2   r4   r   r   r   r   r   r   r   r   r   r    r!   r"   r#   r$   r%   r&   r(   T)	r)   r+   r,   r.   r0   r1   r3   FeishuWSClientFEISHU_AVAILABLE)!	lark_oapilark_oapi.api.application.v6r   lark_oapi.api.im.v1r   r   r   r   r   r   r   r    r!   r"   r#   r$   r%   r&   lark_oapi.corer(   r)   lark_oapi.core.constr+   r,   lark_oapi.core.modelr.   5lark_oapi.event.callback.model.p2_card_action_triggerr0   r1   "lark_oapi.event.dispatcher_handlerr3   lark_oapi.wsr5   )r   r   r   r   r   r   r   r   r   r    r!   r"   r#   r$   r%   r&   r(   r)   r+   r,   r.   r0   r1   r3   r  s                            r   _importz*check_feishu_requirements.<locals>._importg  s7    F	
 	
 	
 	
 	?C4	
 	N9
D
#%:
  !2
 $%:	

 !"4
 %&<
 #$8
 '(@
 n
  !2
 ()B
 '(@
 "#6
 &'>
 #$8
  '(@!
" #
$ %*&&(+F&<, $5
 	
r   r   )ensure_and_bindzplatform.feishuF)prompt)r  tools.lazy_depsr  globals)r  r  s     r   check_feishu_requirementsr  ^  s)     /
b 0,gwyOOr   c                  	    e Zd ZU dZdZdZdZdZded<   dZ	d fdZ
edd	       Zdd
ZddZddZd ZddZddddZddZddZddZddZddZ	 	 d	 	 	 	 	 	 	 	 	 ddZdd	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZedd       Z	 	 	 d	 	 	 	 	 	 	 	 	 	 	 ddZedd       Zedd        Zedd!       Z	 	 	 d	 	 	 	 	 	 	 	 	 	 	 dd"Z 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 dd#Z!	 	 	 d	 	 	 	 	 	 	 	 	 	 	 dd$Z"	 	 	 d	 	 	 	 	 	 	 	 	 	 	 dd%Z#ddd&Z$	 	 	 d	 	 	 	 	 	 	 	 	 	 	 dɈ fd'Z%	 	 	 d	 	 	 	 	 	 	 	 	 	 	 dʈ fd(Z&dd)Z'dd*Z(dd+Z)dd,Z*dd-Z+dd.Z,dd/Z-dd0Z.dd1Z/dd2Z0dd3Z1dd4Z2dd5Z3dd6Z4dd7Z5edd8       Z6dd9Z7dd:Z8dd;Z9dd<Z:ddd=	 	 	 	 	 	 	 	 	 	 	 dd>Z;ddd=	 	 	 	 	 	 	 	 	 	 	 dd?Z<dd@Z=ddAZ>ddBZ?ddCZ@ddDZAddEZBddFZCddGZDddHZEddIZFddJZG	 	 	 	 	 	 ddKZHddLZIddMZJdddNZKddO	 	 	 	 	 	 	 	 	 	 	 	 	 ddPZLddQZMddRZNddSZOeddT       ZPddUZQddVZRddWZSddXZTddYZU	 	 	 	 	 	 	 	 ddZZVedd[       ZWedd\       ZXedd]       ZYdd^ZZdd_Z[dd`Z\ddaZ]eddb       Z^ddcZ_dddZ`e	 	 	 	 	 	 	 	 dde       ZaddfZbddgZc	 	 	 	 ddhZd	 	 	 	 	 	 ddiZeeddj       Zf	 	 	 	 	 	 ddkZgddlZhddmZi	 	 	 	 	 	 	 	 	 	 ddnZjeddo       Zkeddp       Zleddq       Zmeddr       Znedds       Zoeddt       Zpeddu       Zqeddv       Zred dw       ZsddO	 	 	 	 	 ddxZtddyZuddO	 	 	 	 	 ddzZvdd{Zwdd|Zxdd}	 	 	 	 	 	 	 dd~Zyedd       Zzedd       Z{ddZ|d	dZ}	 d
ddO	 	 	 	 	 	 	 ddZ~ddZddZddZddZddZddZddZddZdd	 	 	 	 	 ddZdddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 	 	 	 	 ddZedd       Zedd       Zdd	 	 	 	 	 	 	 ddZddZddZddZddZddZ	 	 	 	 	 	 	 	 	 	 	 	 ddZddZedd       Zedd       Zedd       Zedd       Zedd       Zedd       Zedd       Zedd       Zed d       Zed!d       Zed"d       Zed#d       Zed$d       Zed#d       ZddZd%dZe	 	 	 	 	 	 d&d       Z xZS ('  FeishuAdapterzFeishu/Lark bot adapter.T@    r   CHAT_LOCK_MAX_SIZErd   c                   t         |   |t        j                         | j	                  |j
                  xs i       | _        | j                  | j                         d | _        t        j                         | _        d | _        d| _        d | _        d | _        d | _        d | _        d | _        d | _        d | _        i | _        g | _        t/               dz  | _        t        j                         | _        i | _        i | _        i | _        i | _        g | _        t        j                         | _        d| _         d| _!        tE        jF                         | _$        i | _%        g | _&        i | _'        tG               | _(        d | _)        tU               | _+        | jV                  jX                  | _-        | jV                  j\                  | _/        | jV                  j`                  | _1        tU               | _2        | jd                  jX                  | _3        | jd                  j\                  | _4        i | _5        tm        jn                  d      | _8        i | _9        tm        jn                  d      | _:        tG               | _;        | jy                          y )NFzfeishu_seen_message_ids.jsonr  rv   )=super__init__r6   FEISHU_load_settingsextra	_settings_apply_settings_client	threadingLock_sdk_executor_lock_sdk_executor_sdk_executor_closing
_ws_client
_ws_futurer  _loop_webhook_runner_webhook_site_event_handler_seen_message_ids_seen_message_orderrD   _dedup_state_path_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collectionsr   _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_update_prompt_state_update_prompt_counter_pending_processing_reactions_load_seen_message_ids)r   config	__class__s     r   r  zFeishuAdapter.__init__  s   1,,V\\-?R@T^^,&* #,.."2NR &+")-48DH:>
.2,0-135.0 !0!25S!S$>>+@BBD!JL$57  35$%.^^%5"(-%*.'ITI`I`Ib9;&13#;=FQm 15!1!3%)%;%;%B%B")-)?)?)E)E&*.*@*@*G*G'"2"4&*&=&=&D&D#*.*A*A*G*G':<!*!3?A!&/ooa&8# GRm*##%r   c                6   | j                  di       }i }t        |t              r9|j                         D ]%  \  }}t        |t              sd }d|v rt	        |j                  d            }t        t        |j                  dd            j                         j                         |j                  dg       D ch c]5  }t        |      j                         st        |      j                         7 c}|j                  dg       D ch c]5  }t        |      j                         st        |      j                         7 c}|      |t        |      <   ( | j                  dg       }t        d	 |D              }t        | j                  d
d            j                         j                         }	t        j                  dd      j                         j                         }
|
dvrt        j                  d|
       d}
t        dKi dt        | j                  d      xs t        j                  dd            j                         dt        | j                  d      xs t        j                  dd            j                         dt        | j                  d      xs t        j                  dd            j                         j                         dt        | j                  d      xs t        j                  dd            j                         j                         dt        | j                  d      xs t        j                  dd            j                         dt        | j                  d      xs t        j                  dd            j                         dt        j                  d d      j                         j                         d!t        d" t        j                  d#d      j                  d$      D              d%t        j                  d&d      j                         d't        j                  d(d      j                         d)t        j                  d*d      j                         d+t!        d,t#        d-t$                    d.t'        d/t(              d0t'        d1d2      d3t!        d4t#        d5t*                    d6t!        d4t#        d7t,                    d8t'        d9t.              d:t        | j                  d:      xs t        j                  d;t0                    j                         d<t3        | j                  d<      xs# t        j                  d=t        t4                          d>t        | j                  d>      xs t        j                  d?t6                    j                         xs t6        d@t9        | j                  d@      dAdBC      dDt9        | j                  dD      dEd4C      dFt;        | j                  dF      d d4C      dGt;        | j                  dG      d d4C      d|d
|	d|dH|
dt	        | j                  dt        j                  dIdJ                  S c c}w c c}w )LNr   r   r   openr   r  )r   r   r  r   r   c              3     K   | ]7  }t        |      j                         st        |      j                          9 y wr   )r   r;  )r  us     r   r  z/FeishuAdapter._load_settings.<locals>.<genexpr>  s&     PaQ3q6<<>Ps   ??r   r   FEISHU_ALLOW_BOTSr   >   allr   r   zS[Feishu] Unknown allow_bots=%r, falling back to 'none'. Valid: none, mentions, all.r   FEISHU_APP_IDr   FEISHU_APP_SECRETr   domainr+   r   r   FEISHU_CONNECTION_MODE	websocketr   FEISHU_ENCRYPT_KEYr   FEISHU_VERIFICATION_TOKENr   FEISHU_GROUP_POLICYr   c              3  \   K   | ]$  }|j                         r|j                          & y wr   r;  )r  r  s     r   r  z/FeishuAdapter._load_settings.<locals>.<genexpr>  s'      *::< 

*s   *,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_SECONDSg       @r   rv   %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   rh   r   rg  r   rg   r   r   r   FEISHU_REQUIRE_MENTIONr$  r   )r(  rG  r   r  r%  r   r   r;  r  r   osgetenvrj  warningr   r<  r/  rG   _DEFAULT_DEDUP_CACHE_SIZErF   !_DEFAULT_TEXT_BATCH_DELAY_SECONDS _DEFAULT_TEXT_BATCH_MAX_MESSAGES_DEFAULT_TEXT_BATCH_MAX_CHARS"_DEFAULT_MEDIA_BATCH_DELAY_SECONDS_DEFAULT_WEBHOOK_HOSTr   _DEFAULT_WEBHOOK_PORT_DEFAULT_WEBHOOK_PATHrh  re  )r  raw_group_rulesr   r   rule_cfgper_chat_require_mentionr  
raw_adminsr   r   r   s              r   r  zFeishuAdapter._load_settings  s     ))M2624ot,%4%:%:%< !!(D1 <@($0/:8<<HY;Z/[,,;x||Hf=>DDFLLN7?||KQS7Tg!X[\]X^XdXdXfs1v||~g7?||KQS7Tg!X[\]X^XdXdXfs1v||~g$<	-CL)  YYx,
P:PP  #599-CR#HIOOQWWY YY2F;AACIIK
88NNe  J$ >
uyy*Lbii.LMSSU>
599\2Xbii@SUW6XY__a>
 EIIh/W299_h3WX^^`ffh>
  		+,`		:RT_0`egeeg>
 EIIm4[		BVXZ8[\bbd>
  #		./]299=XZ\3] eg>
 #8+FLLNTTV>
 !* *II&<bAGGL* !>
" 		"6;AAC#>
$ 		"6;AAC%>
& YY0"5;;='>
( !8:ST)>
0 &/8:[&1>
6 ,5>,7>
< %(?Aab%=>
D "%<>[\"E>
L '09;]'M>
R 		.)dRYY7LNc-degW>
X 		.)iRYY7LcRgNh-iY>
` EIIn-h;PRg1hiooq )(c>
f  4EII>R4S]_klmg>
h #7uyyAX7Ycfrs"ti>
j )3E)FPT`abk>
l (		2C(Dd^_`m>
n o>
p "6q>
r $s>
t "u>
v (		+RYY7OQW-XYw>
 >	
- hgs   =ZZZ,Zc                   |j                   | _        |j                  | _        |j                  | _        |j                  | _        |j                  | _	        |j                  | _        |j                  | _        t        |j                        | _        t        |j"                        | _        |j&                  xs |j                  | _        |j*                  | _        |j.                  | _        |j2                  | _        |j6                  | _        |j:                  | _        |j>                  | _         |jB                  | _"        |jF                  | _$        |jJ                  | _&        |jN                  | _(        |jR                  | _*        |jV                  | _,        |jZ                  | _.        |j^                  | _0        |jb                  | _2        |jf                  | _4        |jj                  | _6        |jn                  | _8        |jr                  | _:        y r   );r   _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   rf  r   rg  r   rh  r   rr  r   _allow_botsr   _require_mention)r   settingss     r   r  zFeishuAdapter._apply_settingsR  s   #..$00 ( 8 8$00#+#>#> %22$'(D(D$E!8??+%-%B%B%[hF[F["$00$00$00!**!)!:!:)1)J)J&/7/V/V,(0(H(H%%-%B%B"*2*L*L'%22%22%22#+#>#> &.&D&D#!)!:!: ( 8 8#.. ( 8 8r   c                    t         y t        j                   j                   j                        j	                   j
                        j                   j                        j                   fd      j                   fd      j                   j                        j                   j                        j                   j                        j!                   j"                        j%                   j&                        j)                  d j*                        j)                  d j,                        j/                         S )Nc                (    j                  d|       S )Nim.message.reaction.created_v1_on_reaction_eventdatar   s    r   rY  z4FeishuAdapter._build_event_handler.<locals>.<lambda>|      T445UW[\ r   c                (    j                  d|       S )Nim.message.reaction.deleted_v1r0  r2  s    r   rY  z4FeishuAdapter._build_event_handler.<locals>.<lambda>  r4  r   drive.notice.comment_add_v1vc.bot.meeting_invited_v1)r3   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register_p2_customized_event_on_drive_comment_event_on_meeting_invited_eventbuildr   s   `r   _build_event_handlerz"FeishuAdapter._build_event_handlerq  s   !)"**!!(( 43D4O4OP..t/E/EF77\ 87\ -,T-I-IJ44T5O5OP66t7U7UVEEdF_F_`//0I0IJ))-,, *)+.. UW5	
r   c                B   t        | dd      }|t        j                         }|| _        |5  t        | dd      rt	        d      t        | dd      }|t        |dd      r(t
        j                  j                  dd	
      }|| _        |cddd       S # 1 sw Y   yxY w)uy  Return the adapter-owned executor for blocking Feishu SDK calls.

        Recreates the pool if it was never built or was shut down by an
        *external* teardown of the loop's default executor, so that can no
        longer permanently wedge sends (#10849). Refuses to resurrect once
        the adapter itself is closing — a real disconnect/shutdown stays shut.
        r  Nr  Fz9Feishu adapter is shutting down; SDK executor unavailabler  	_shutdownr   zhermes-feishu-sdk)max_workersthread_name_prefix)	r  r  r  r  rx  
concurrentfuturesThreadPoolExecutorr  r   lockexecutors      r   _get_sdk_executorzFeishuAdapter._get_sdk_executor  s     t148<>>#D&*D# 
	t4e<"#^__t_d;H78[%#H%--@@ "': A  &."
	 
	 
	s   ABBc                   K   t        j                         } |j                  | j                         |g|  d{   S 7 w)z@Run a blocking Feishu SDK call on the adapter-owned thread pool.N)ry   get_running_looprun_in_executorrZ  )r   funcrs  r  s       r   _run_blockingzFeishuAdapter._run_blocking  s=     ''))T))$*@*@*BDP4PPPPs   :AAAc                    t        | dd      }|y|5  d| _        t        | dd      }d| _        ddd       y	 |j                  dd       y# 1 sw Y   !xY w# t        $ r |j                  d       Y yw xY w)zFStop the adapter-owned SDK executor without touching the loop default.r  NTr  F)waitcancel_futures)ra  )r  r  r  shutdownr`  rW  s      r   _shutdown_sdk_executorz$FeishuAdapter._shutdown_sdk_executor  s    t148< 	&)-D&t_d;H!%D	& 	*5>	& 	&  	*5)	*s   AA AA98A9F)is_reconnectc               p  K   d| _         t        st        j                  d       y| j                  r| j
                  st        j                  d       y| j                  dvr!t        j                  d| j                         y| j                  dk(  r.| j                  s"| j                  st        j                  d       y	 | j                  | _	        t        t        | j                  d| j                  j                  i	      \  }}|s^t        |t              r|j!                  d
      nd}d|rd| dndz   dz   }t        j                  d|       | j#                  d|d       yt%        j&                         | _        | j+                          d{    | j-                          t        j/                  d| j                  | j0                         y7 @# t2        $ rT}| j5                          d{  7   d| }| j#                  d|d       t        j                  d|d       Y d}~yd}~ww xY ww)zConnect to Feishu/Lark.Fz [Feishu] lark-oapi not installedz3[Feishu] FEISHU_APP_ID or FEISHU_APP_SECRET not set>   webhookr  zT[Feishu] Unsupported FEISHU_CONNECTION_MODE=%s. Supported modes: websocket, webhook.rg  zO[Feishu] Webhook mode requires FEISHU_VERIFICATION_TOKEN or FEISHU_ENCRYPT_KEY.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: %src  )r  r  rj  errorr  r  r  r  r  r  rB   _FEISHU_APP_LOCK_SCOPErh  r   rG  r   r(  _set_fatal_errorry   r\  r  _connect_with_retry_mark_connectedinfor  ri  _release_app_lock)r   re  acquiredexisting	owner_pidr\   r   s          r   r  zFeishuAdapter.connect  s     &+"LL;<||4#3#3LLNO  (@@LLf%%   I-t7O7OSWSdSdLLa 	&*llD#!4&''$dmm&9&9:"Hh
 3=h3MHLL/SW	V1:	{"-Eab 
 ]G4%%&7E%R 113DJ**,,,  "KK<d>S>SUYUfUfg -  	((***/u5G!!"8'T!RLL93LN	s[   B>H6B%G &H6',G G?G H6G 	H3H.2G536H.)H6.H33H6c                ~  K   d| _         | j                  | j                         d{    | j                  | j                         d{    | j	                          | j
                  }| j                  | j                          | j                          d{    |j                         sxt        |d      rl	 t        j                  |j                               }t        j                  t        j                  |      d       d{    t         j#                  d       <j                         s,t         j#                  d       dfd}j+                  |       | j,                  }|]	 t         j#                  d       t        j                  t        j.                  |      d       d{    t         j#                  d       d| _        d| _        d| _        d| _        | j7                          | j9                          | j;                          d{    | j=                          t         j?                  d       y7 '7 7 7 F# t        j$                  $ r t         j'                  d       Y Zt(        $ r#}t         j#                  d|d	
       Y d}~d}~ww xY w7 # t        j$                  $ r t         j'                  d       Y t        j0                  $ r t         j#                  d       Y 6t(        $ r#}t         j#                  d|d	
       Y d}~\d}~ww xY w7 w)zDisconnect from Feishu/Lark.FN_disconnectg      @timeoutz-[Feishu] Sent WebSocket CLOSE frame to Feishuu   [Feishu] CLOSE frame not acknowledged within 5s — Feishu may briefly route messages to the stale connection until server-side timeoutz1[Feishu] Could not send WebSocket CLOSE frame: %sTrc  z<[Feishu] Cancelling websocket thread tasks and stopping loopc                    t        j                        D  cg c]  } | j                         r|  }} t        j	                  dt        |             |D ]  }|j                           j                  dj                         y c c} w )Nz3[Feishu] Found %d pending tasks in websocket threadg?)	ry   r  r  rj  rk  r|   r  
call_laterr  )r  r  r  ws_thread_loops      r   cancel_all_tasksz2FeishuAdapter.disconnect.<locals>.cancel_all_tasks'  ss    $+$5$5n$EVqQVVXVVRTWX]T^_! "DKKM"))#~/B/BC	 Ws
   BBz;[Feishu] Waiting for websocket thread to exit (timeout=10s)g      $@z([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: %sz[Feishu] Disconnectedr{  ) _running_cancel_pending_tasksr  r  _reset_batch_buffersr  r  !_disable_websocket_auto_reconnect_stop_webhook_server	is_closedhasattrry   run_coroutine_threadsaferz  wait_forwrap_futurerj  rk  TimeoutErrorr  ri  call_soon_threadsafer  shieldCancelledErrorr  r  rd  _persist_seen_message_idsru  _mark_disconnectedrt  )r   rm  futurer   r  	ws_futurer  s         @r   
disconnectzFeishuAdapter.disconnect  s    (()G)GHHH(()H)HIII!!# OO	--..0''))) !*",,.	=1 99))+^ &&w':':6'BCPPPLM %n.F.F.HLLWXD //0@AOO	 	dZ[&&w~~i'@$OOOGH #
"##%&&($$&&&!+,a 	II 	*  Q'' ;
  G!   . P'' cab)) VTU dNPS^bccd 	's   &L=I"L=IAL=I$L=AI II -AL=:AJ7 <J5=J7 AL=%L:&+L=L=L=I (J2L=J2J-'L=-J22L=5J7 7(L7L="'L7	L=L7L2,L=2L77L=c                  K   |j                         D cg c]  }|s|j                         r| }}|D ]  }|j                           |rt        j                  |ddi d {    |j                          y c c}w 7 w)Nr|  T)r  r  r  ry   r  clear)r   r  r  r  s       r   r  z#FeishuAdapter._cancel_pending_tasksH  sp     $)LLNODd499;4OO 	DKKM	..'BTBBB P Cs%   BA;A;A;3B%B &Bc                    | j                   j                          | j                  j                          | j                  j                          y r   )r  r  r  r  rN  s    r   r  z"FeishuAdapter._reset_batch_buffersP  s8    ""((*''--/##))+r   c                    | j                   y 	 t        | j                   dd       d | _         y # t        $ r Y w xY w# d | _         w xY w)N_auto_reconnectF)r  re  ri  rN  s    r   r  z/FeishuAdapter._disable_websocket_auto_reconnectU  sL    ??"	#DOO%6> #DO  		 #DOs   . 	:= := 	Ac                   K   | j                   y 	 | j                   j                          d {    d | _         d | _        y 7 # d | _         d | _        w xY wwr   )r  cleanupr  rN  s    r   r  z"FeishuAdapter._stop_webhook_server_  sX     '	&&&..000#'D !%D 1#'D !%Ds+   AA AA AA AANc                R  K   | j                   st        dd      S | j                  |      }| j                  || j                        }t        t        j                  |            }d}	 |D ]  }	| j                  |	|      \  }
}	 | j                  ||
|||       d{   }|
dk(  r| j%                  |      st        j                  t        t'        |dd      xs d            rSt        j                  d       | j                  |d	t        j                   d	t#        |	      id
      ||       d{   }|} | j)                  |d      S 7 # t        $ r}|
dk7  st        j                  t        |            s t        j                  d       | j                  |d	t        j                   d	t#        |	      id
      ||       d{  7  }Y d}~5d}~ww xY w7 # t        $ r8}t        j+                  d|d       t        dt        |            cY d}~S d}~ww xY ww)zSend a Feishu message.FNot connectedsuccessro  Nprefer_postr   r   r  reply_tor   r  zI[Feishu] Invalid post payload rejected by API; falling back to plain textr   rk  msgr   zJ[Feishu] Post payload rejected by API response; falling back to plain textzsend failedz[Feishu] Send error: %sTrc  )r  r<   format_messagetruncate_messageMAX_MESSAGE_LENGTHr   _MARKDOWN_HINT_REsearch_build_outbound_payload_feishu_send_with_retryri  _POST_CONTENT_INVALID_REr   rj  r  rn  ro  r^  _response_succeededr  _finalize_send_resultro  )r   r   rw   r  r   	formattedchunksr  last_responsechunkr   r  responser   s                 r   sendzFeishuAdapter.sendl  s2     ||e?CC''0	&&y$2I2IJ ,33I>?*	= $)$($@$@{ %A %!'%)%A%A '!) '!)!) &B &  H& & 44X>077GHeUW<X<^\^8_`NN#op%)%A%A '!' $

F4QRW4X+Yhm n!)!) &B &  H !)I$)L --m]KKC  ! 
6)1I1P1PQTUXQY1ZNN#no%)%A%A '!' $

F4QRW4X+Yhm n!)!) &B &    H	
"   	=LL2C$LGe3s8<<	=s   A'H'*G# EE E$BG# 5G!6G# H'E	GA2GGGG# GG# #	H$,-HH$H'H$$H')finalizec          	     (  K   | j                   st        dd      S | j                  |      }	 | j                  |      \  }}| j	                  ||      }| j                  ||      }| j                  | j                   j                  j                  j                  j                  |       d{   }	| j                  |	d      }
|
j                  s|dk(  rt        j                  |
j                  xs d	      rt         j#                  d
       | j	                  dt%        j&                  dt)        |      id            }| j                  ||      }| j                  | j                   j                  j                  j                  j                  |       d{   }| j                  |d      }
|
j                  r||
_        |
S 7 7 .# t,        $ r9}t         j                  d||d       t        dt/        |            cY d}~S d}~ww xY ww)z0Edit a previously sent Feishu text/post message.Fr  r  r   rw   
message_idrequest_bodyNzupdate failedr  r   zP[Feishu] Invalid post update payload rejected by API; falling back to plain textr   rk  z&[Feishu] Failed to edit message %s: %sTrc  )r  r<   r  r  _build_update_message_body_build_update_message_requestr_  imv1r\   updater  r  r  r  ro  rj  r  rn  ro  r^  r  ri  r   )r   r   r  rw   r  r   r  r   r~   r  rL  fallback_bodyfallback_requestfallback_responser   s                  r   edit_messagezFeishuAdapter.edit_message  s     ||e?CC%%g.	= $ < <W EHg22Hg2VD88J]a8bG!//0B0B0J0J0Q0QSZ[[H///JF>>h&&8=U=\=\]c]i]i]omo=pqr $ ? ?# JJ0Mg0V'Wfkl !@ ! $(#E#EQ[jw#E#x *.*<*<T\\__=O=O=W=W=^=^`p*q$q!334EW~~$.!M \ %r
  	=LLA:s]aLbe3s8<<	=sZ   +HBG /G0C,G G*G HG G 	H.H
HH
HHc                  K   | j                   st        dd      S 	 t        | j                        t	        |      dkD  r|dd dz   n|}d(d)fd}	 |	dd	d
      g}
|s2|
j                   |	dd             |r|
j                   |	dd             |
j                   |	ddd             |rdnd}ddiddddddd| d| | dd|
d gd!}t        j                  |d"      }| j                  |d#|d|$       d{   }| j                  |d%      }|j                  r!||j                  xs d|d&| j                  <   |S 7 E# t        $ r6}t        j                  d'|       t        dt!        |            cY d}~S d}~ww xY ww)*a  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...c                    dd| d||ddS )Nr  r#  r   rw   )hermes_actionapproval_idr   r   r   r   r   )r   action_namebtn_typer  s      r   _btnz.FeishuAdapter.send_exec_approval.<locals>._btn  s#    #$0UC$/:;W	 r   u   ✅ Allow Oncern   primaryu   ✅ Sessionro   u
   ✅ Alwaysrp   u   ❌ Denyrm   dangerzD

**Smart DENY:** owner override applies to this one operation only.r   wide_screen_modeTu    ⚠️ Command Approval Requiredr#  rw   r   oranger   r   r!  z```
z
```
**Reason:** r  r  r   r  r  r.  r  rk  r  r  zsend_exec_approval failedsession_keyr  r   z&[Feishu] send_exec_approval failed: %srb  )r   r   r  r   r  r   r   r   )r  r<   nextr  r|   rw  rn  ro  r  r  r  r  r  ri  rj  r  r   )r   r   commandr  r   r   allow_permanentsmart_deniedcmd_previewr  r  
scope_noter  r  r  rL  r   r  s                    @r   send_exec_approvalz FeishuAdapter.send_exec_approval  s     ||e?CC8	=t556K47L44G'%4.50WK ,niHIGtM3DEF"NN46F#GHNN4
FH=>eqawyJ-t4)KT`a (  *%*;-7J;-XbWc#d
  (#*	D$ jjE:G!99&! :  H //:UVF~~#."("3"3"9r&5$$[1
 M   	=NNCSIe3s8<<	=sH   FC E >E?AE FE 	F+F :F;F FFc           
     |    |rd| dnd}dfd}ddiddd	d
dd|  | dd |ddd       |ddd      gdgdS )Nz

Default: `r-  r   c                    dd| d||ddS )Nr  r#  r  )hermes_update_prompt_actionupdate_prompt_idr  r   )r   answerr  	prompt_ids      r   r  z5FeishuAdapter._build_update_prompt_card.<locals>._btn   s&     ,? 39(1	 r   r  Tu   ⚕ Update Needs Your Inputr#  r  r  r  r!  r  r  u   ✓ Yesyr  u   ✗ Nonr  r  r  )r   r   r  r   r  r   r   r   r   )r  rb  r  default_hintr  s     `  r   _build_update_prompt_cardz'FeishuAdapter._build_update_prompt_card  s{    6=y22		 *40%B<X$
 #&,/HI#YY7XsH5 	
 	
r   r   c                   K   | j                   st        dd      S 	 t        | j                        }t	        j
                  | j                  |||      d      }| j                  |d|d|       d{   }| j                  |d	      }	|	j                  r!||	j                  xs d
|d| j                  |<   |	S 7 E# t        $ r6}
t        j                  d|
       t        dt        |
            cY d}
~
S d}
~
ww xY ww)z6Send an interactive update prompt with Yes/No buttons.Fr  r  )r  rb  r  rk  r  Nr  zsend_update_prompt failedr   r  z&[Feishu] send_update_prompt failed: %s)r  r<   r  r  rn  ro  r  r  r  r  r  r  ri  rj  r  r   )r   r   r  rb  r  r   r  r  r  rL  r   s              r   send_update_promptz FeishuAdapter.send_update_prompt=  s     ||e?CC	=T889Ijj..fgYb.c"G "99&! :  H //:UVF~~#."("3"3"9r&8)))4
 M   	=NNCSIe3s8<<	=sH   C>AB< 4B:5AB< 9C>:B< <	C;+C60C;1C>6C;;C>c                    | dk(  rdnd}t         j                  | 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.rm      ❌   ✅Resolvedr  Tr.  r#  r  redgreenr  r!  z **z** by r  r  )rt   r(  )choicer  iconr   s       r   _build_resolved_approval_cardz+FeishuAdapter._build_resolved_approval_card`  s}     &(ue#''
;)40(,vQug%6|L%+v%5E7 &"&s5'	{C
 	
r   c                ^    | dk(  }|rdnd}ddi|rdnd d| d	d
|rdndddd| ddgdS )Nr  YesNor  Tr  r  z Update prompt answered: r#  r  r  r  r  r!  zAnswered by **rB  r  r  r   )r  r  yesr   s       r   "_build_resolved_update_prompt_cardz0FeishuAdapter._build_resolved_update_prompt_cards  sf    m$)4014%'@@YZ_Y`%ajvw'*G
 #	{"/MN	
 		
r   c                    t               dz  }|j                  d      }|j                  |        |j                  |       y )Nz.update_responsez.tmp)rD   with_suffix
write_textr<  )r  response_pathtmp_paths      r   _write_update_prompt_responsez+FeishuAdapter._write_update_prompt_response  s<    '),>> ,,V4F#'r   c                L   K   | j                  |||||d       d{   S 7 w)z@Send audio to Feishu as a file attachment plus optional caption.r  r   	file_pathr  r   captionoutbound_message_typeN_send_uploaded_file_message)r   r   
audio_pathr  r  r   rt  s          r   
send_voicezFeishuAdapter.send_voice  <      55 ") 6 
 
 	
 
   $"$c                L   K   | j                  ||||||       d{   S 7 w)z*Send a document/file attachment to Feishu.)r   r   r  r   r  r   Nr  )r   r   r   r  r   r  r   rt  s           r   send_documentzFeishuAdapter.send_document  s<      55 6 
 
 	
 
r  c                L   K   | j                  |||||d       d{   S 7 w)zSend a video file to Feishu.r  r  Nr  )r   r   
video_pathr  r  r   rt  s          r   
send_videozFeishuAdapter.send_video  r  r  c                <  K   | j                   st        dd      S t        j                  j	                  |      st        dd|       S 	 ddl}t        |d      5 }|j                         }	ddd       |j                  	      }
t        j                  j                  |      |
_
        | j                  t        |
      }| j                  |      }| j                  | j                   j                  j                   j"                  j$                  |       d{   }| j'                  |d	      }|s| j)                  |d
d      S |r5| j+                  |d|d      }| j-                  |d|||       d{   }n5| j-                  |dt/        j0                  d	|id      ||       d{   }| j3                  |d      S # 1 sw Y   YxY w7 7 [7 '# t4        $ r9}t6        j9                  d||d       t        dt;        |            cY d}~S d}~ww xY w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   r  	media_tagr  r  r  rk  zimage send failedz$[Feishu] Failed to send image %s: %sTrc  )r  r<   r  pathexistsior  readBytesIObasenamer   _build_image_upload_body_FEISHU_IMAGE_UPLOAD_TYPE_build_image_upload_requestr_  r  r  r  create_extract_response_field_response_error_result_build_media_post_payloadr  rn  ro  r  ri  rj  ro  r   )r   r   
image_pathr  r  r   rt  _iofimage_bytes
image_filer   r~   upload_responser   post_payloadmessage_responser   s                     r   send_image_filezFeishuAdapter.send_image_file  s     ||e?CCww~~j)e5KJ<3XYY,	=j$' '1ffh' [1J gg..z:JO004  1 D 66t<G$($6$6t||7I7I7O7O7V7VX_$``O44_kRI22#$9#J 3   #==#&+)D  >   *.)E)E##(%% *F * $  *.)E)E#$ JJY'?eT%% *F * $  --.>@STTO' ' a$$  	=LL?S[_L`e3s8<<	=s   A	HG G-B,G G+G H1G 7G85G -G.G HG		G G G 	H .HHHHHc                   K   yw)z2Feishu bot API does not expose a typing indicator.Nr   )r   r   r   s      r   send_typingzFeishuAdapter.send_typing 	  s	     s   c                  K   	 | j                  |       d{   }| j                  |||||       d{   S 7 "# t        $ r@}t        j                  d||d       t        |   |||||       d{  7  cY d}~S d}~ww xY w7 Rw)zJDownload a remote image then send it through the native Feishu image flow.Nz([Feishu] Failed to download image %s: %sTrc  )r   	image_urlr  r  r   )r   r$  r  r  r   )_download_remote_imageri  rj  ro  r  
send_imager,  )	r   r   r0  r  r  r   r$  r   r  s	           r   r2  zFeishuAdapter.send_image	  s     
	#::9EEJ ))! * 
 
 	
 F 	LLCYPS^bLc+#!! ,    	
sS   B> <> BB
B> 	B0B7A:8B<B=BBBc                <  K   	 | j                  |dd       d{   \  }}|rd	| nd
}	| j                  ||||	||       d{   S 7 /# t        $ r@}t        j                  d||d       t        
|   |||||       d{  7  cY d}~S d}~ww xY w7 Rw)z@Feishu has no native GIF bubble; degrade to a downloadable file.rI   zanimation.gif)default_extpreferred_nameNz,[Feishu] Failed to download animation %s: %sTrc  )r   animation_urlr  r  r   z[GIF downgraded to file]
z[GIF downgraded to file])r   r   r   r  r  r   )_download_remote_documentri  rj  ro  r  send_animationr
  )r   r   r6  r  r  r   r   r   r   degraded_captionr  s             r   r8  zFeishuAdapter.send_animation 	  s     	)-)G)G". *H * $ Iy FM7yARl''$ ( 
 
 	
$
  	LLGX[fjLk/+!! 0    	
s[   BA AA #BBBA 	B0BB
BBBBBc                H  K   ||dd}| j                   s|S | j                  j                  |      }|t        |      S 	 | j	                  |      }| j                  | j                   j                  j                  j                  j                  |       d{   }|r t        |dd              du r4t        |dd      }t        |d	d
      }t        j                  d|||       |S t        |dd      }t        t        |dd      xs d      j                         j                         }	|t        t        |dd      xs |      | j                  |	      |	xs dd}
|
| j                  |<   t        |
      S 7 # t         $ r t        j                  d|d       |cY S w xY ww)z5Return real chat metadata from Feishu when available.dm)r   r   r   Nr  c                      yr'  r   r   r   r   rY  z-FeishuAdapter.get_chat_info.<locals>.<lambda>S	      r   Fr@  rN  r  zchat lookup failedz0[Feishu] Failed to get chat info for %s: [%s] %sr3  	chat_typer   r   )r   r   r   r   z'[Feishu] Failed to get chat info for %sTrc  )r  r  r(  r   _build_get_chat_requestr_  r  r  chatr  rj  r  r   r;  r  _map_chat_typeri  )r   r   fallbackcachedr~   r  r@  r  r3  raw_chat_typert  s              r   get_chat_infozFeishuAdapter.get_chat_infoB	  s     

 ||O&&**73<	227;G!//0B0B0G0G0K0KWUUHJwxMJLPUUx;h/CDQSZ\`bef8VT2Dk2 > D"EKKMSSUM"GD&$7B7C++M:)1T	D .2D!!'*:! V"  	NNDgX\N]O	sJ   =F" AE9 E7AE9 'F"(BE9 6F"7E9 9#FF"FF"c                "    |j                         S )z/Feishu text messages are plain text by default.r  r   rw   s     r   r  zFeishuAdapter.format_messageg	  s    }}r   c                   | j                   }| j                  |      sD| j                  |      }|r0t        j                  | j
                  dd      j                          y| j                  || j                  |             y)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)targetr   daemonN)	r  _loop_accepts_callbacks_enqueue_pending_inbound_eventr  Thread_drain_pending_inbound_eventsr  _submit_on_loop_handle_message_event_data)r   r3  r  start_drainers       r   r=  zFeishuAdapter._on_message_evento	  sr     zz++D1 ??EM  ==9 %'T4#B#B4#HIr   c                H   | j                   5  t        | j                        | j                  k\  rq| j                  j	                  d      }	 t        |dd      }t        |dd      }t        t        |dd      xs d      }t        j                  d| j                  |       | j                  j                  |       t        | j                        }| j                   }|rd	| _        ddd       t        j                  d
       S # t        $ r d}Y w xY w# 1 sw Y   2xY w)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   eventNr\   r  r   rN  zA[Feishu] Pending-inbound queue full (%d); dropped oldest event %sTzI[Feishu] Queued inbound event for replay (loop not ready, queue depth=%d))r  r|   r  r  popr  r   ri  rj  ro  rw  r  r  )r   r3  droppedrS  r\   r  depthshould_starts           r   rL  z,FeishuAdapter._enqueue_pending_inbound_event	  s    '' 	54//0D4S4SS 66::1=+#GWd;E%eY=G!$WWlB%G%T9!UJ W33
 ((//5445E#<<<L04-+	5, 	W	
 ! ! +!*J+	5 	5s0   >D4D A'DDDDDD!c                   d}d}d}	 	 t        | dd      sz| j                  5  t        | j                        }| j                  j	                          ddd       rt
        j                  d|       	 | j                  5  d| _        ddd       y| j                  }| j                  |      rW| j                  5  | j                  dd }| j                  j	                          ddd       sJ| j                  5  | j                  s&	 ddd       | j                  5  d| _        ddd       y	 ddd       0d	}g }|D ]:  }	| j                  || j                  |	            r|d
z  }*|j                  |	       < |r$| j                  5  || j                  dd	 ddd       |rt
        j                  d|       |sH| j                  5  | j                  s&	 ddd       | j                  5  d| _        ddd       y	 ddd       ||k\  ry| j                  5  t        | j                        }| j                  j	                          ddd       t
        j                  d|       	 | j                  5  d| _        ddd       yt        j                   |       ||z  }# 1 sw Y   OxY w# 1 sw Y   yxY w# 1 sw Y   xY w# 1 sw Y   yxY w# 1 sw Y   xY w# 1 sw Y   GxY w# 1 sw Y   yxY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   yxY w# | j                  5  d| _        ddd       w # 1 sw Y   w x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   rv   z,[Feishu] Replayed %d queued inbound event(s)zO[Feishu] Adapter loop unavailable for %.0fs; dropped %d queued inbound event(s))r  r  r|   r  r  rj  r  r  r  rK  rO  rP  rw  rt  ro  timesleep)
r   poll_intervalmax_wait_secondswaitedrU  r  batch
dispatchedrequeuerS  s
             r   rN  z+FeishuAdapter._drain_pending_inbound_events	  sd     B	6tZ6 33 ="%d&B&B"C44::<= Y# h ++ 605-6 6g zz//533 = $ < <Q ?44::<= !!77 '#'#?#? &'X ++ 605-6 6W $@' !!"J)+G!& 2// $"A"A%"H '!OJ $NN512 !77 G?FD88!<G!J& # "77 '#'#?#? &'& ++ 605-6 6% $@' --33 ="%d&B&B"C44::<= LL=(	  ++ 605-6 6 

=)-'} = =x6 6c= =b6 6Y' 'G G:6 6'' '= =6 6++ 605-6 6 6s   L 0J L ?J,*L :*J8$L :KL K.AL 	K.L K7L )K+;L 0LL 2LL J)$L ,J58K=L KKL K(#L +K47L <L LL LM(L90	M9M>Mc           
       K   t        |dd      }t        |dd      }t        |dd      }|r|rt        |dd      st        j                  d       yt        |dd      }|r| j                  |      rt        j                  d|       y| j	                  ||      }|t        j                  d	|       yt        |d
d      }| j                  ||t        |dd      ||t        |             d{    y7 w)zEShared inbound message handling for websocket and webhook transports.rS  Nr\   r  r  zA[Feishu] Dropping malformed inbound event: missing message/senderr  z2[Feishu] Dropping duplicate/missing message_id: %sz#[Feishu] dropping inbound event: %sr>  p2p)r3  r\   r  r>  r  is_bot)r  rj  rk  _is_duplicate_admit_process_inbound_messager  )r   r3  rS  r\   r  r  reasonr>  s           r   rP  z(FeishuAdapter._handle_message_event_data	  s     gt,%D1$/fGFK,NLL\]WlD9
T//
;LLMzZVW-LL>GG[%8	++fk48!!&) , 
 	
 	
s   C,C6.C4/C6c                    t        |dd      }t        |dd      }t        |dd      xs d}t        j                  d|       y)z7Ignore read-receipt events that Hermes does not act on.rS  Nr\   r  r   z([Feishu] Ignoring message_read event: %s)r  rj  rk  )r   r3  rS  r\   r  s        r   r;  z$FeishuAdapter._on_message_read_event
  s@    gt,%D1WlD9?R
?Lr   c                    t        |dd      }t        t        |dd      xs d      }t        j                  d|       | j                  j                  |d       y)z'Handle bot being added to a group chat.rS  Nr   r   z[Feishu] Bot added to chat: %sr  r   rj  rt  r  rT  r   r3  rS  r   s       r   rC  z#FeishuAdapter._on_bot_added_to_chat
  sM    gt,geY39r:4g>!!'40r   c                    t        |dd      }t        t        |dd      xs d      }t        j                  d|       | j                  j                  |d       y)z+Handle bot being removed from a group chat.rS  Nr   r   z"[Feishu] Bot removed from chat: %srk  rl  s       r   rE  z'FeishuAdapter._on_bot_removed_from_chat 
  sM    gt,geY39r:8'B!!'40r   c                .    t         j                  d       y )Nz'[Feishu] User entered P2P chat with botrj  rk  r   r3  s     r   rG  z"FeishuAdapter._on_p2p_chat_entered'
  s    >?r   c                .    t         j                  d       y )Nz![Feishu] Message recalled by userro  rp  s     r   rI  z"FeishuAdapter._on_message_recalled*
  s    89r   c                    ddl m} | j                  }| j                  |      st        j                  d       y| j                  | || j                  || j                               y)a%  Handle drive document comment notification (drive.notice.comment_add_v1).

        Delegates to :mod:`gateway.platforms.feishu_comment` for parsing,
        logging, and reaction.  Scheduling follows the same
        ``run_coroutine_threadsafe`` pattern used by ``_on_message_event``.
        r   )handle_drive_comment_eventzB[Feishu] Dropping drive comment event before adapter loop is readyN)self_open_id)	'plugins.platforms.feishu.feishu_commentrs  r  rK  rj  r  rO  r  r  )r   r3  rs  r  s       r   rK  z%FeishuAdapter._on_drive_comment_event-
  sV     	Wzz++D1NN_`&t||THYHYZ	
r   c                    ddl m} | j                  }| j                  |      st        j                  d       y| j                  | || |             y)zJHandle VC bot meeting invitation notification (vc.bot.meeting_invited_v1).r   )handle_meeting_invited_eventzC[Feishu] Dropping meeting invite event before adapter loop is readyN).plugins.platforms.feishu.feishu_meeting_inviterw  r  rK  rj  r  rO  )r   r3  rw  r  s       r   rL  z'FeishuAdapter._on_meeting_invited_event?
  sD    _zz++D1NN`aT#?d#KLr   c                   t        |dd      }t        t        |dd      xs d      }t        t        |dd      xs d      }t        |dd      }t        t        |dd      xs d      }d|v rd	nd
}t        j                  d||||       | j                  }	|dv s |r|	t         t        |	dd                    ry| j                  |	| j                  ||             y)z>Route user reactions on bot messages as synthetic text events.rS  Nr  r   operator_typereaction_typer  createdaddedremovedz?[Feishu] Reaction %s on message %s (operator_type=%s, emoji=%s)>   r  r  r  c                      yr'  r   r   r   r   rY  z2FeishuAdapter._on_reaction_event.<locals>.<lambda>`
  r=  r   )r  r   rj  rk  r  r   rO  _handle_reaction_event)
r   
event_typer3  rS  r  rz  reaction_type_objr  r  r  s
             r   r1  z FeishuAdapter._on_reaction_eventI
  s    gt,b9?R@
GE?B?E2F#E?DA!2L"EKL
%3M	
 zz^+|=GD+}=?@T4#>#>z4#PQr   c                (   | j                   }| j                  |      s't        j                  d       t        r
t	               S dS t        |dd      }t        |dd      }t        |di       xs i }t        |t              r|j                  d      nd}t        |t              r|j                  d      nd}|r| j                  |||      S |r| j                  |||      S | j                  || j                  |             t        y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 readyNrS  r  r   r  r  )rS  action_valuer  )r  rK  rj  r  r1   r  rG  r   r(  _handle_approval_card_action!_handle_update_prompt_card_actionrO  _handle_card_action_event)r   r3  r  rS  r  r  r  update_prompt_actions           r   rA  z%FeishuAdapter._on_card_action_triggere
  s    zz++D1NNWX4O.0YUYYgt,$/vw39r=GVZ=[((9ae ,- :;37 	
 445|bf4gg99) :   	T4#A#A$#GH&.*,,r   c                H    | duxr t         t        | dd                     S )zEReturn True when the adapter loop can accept thread-safe submissions.Nr  c                      yr'  r   r   r   r   rY  z7FeishuAdapter._loop_accepts_callbacks.<locals>.<lambda>
  r=  r   r   r  )r  s    r   rK  z%FeishuAdapter._loop_accepts_callbacks
  s+     4Y-VWT;-V-X(Y$YYr   c                    ddl m}  |||t        dt        j                        }|y|j                  | j                         y)zISchedule background work on the adapter loop with shared failure logging.r   )safe_schedule_threadsafez4[Feishu] Failed to schedule background callback work)rj  log_message	log_levelFT)agent.async_utilsr  rj  loggingWARNINGadd_done_callback_log_background_failure)r   r  coror  r  s        r   rO  zFeishuAdapter._submit_on_loop
  sB    >)$Noo	
 >  !=!=>r   c                    t        |xs d      j                         }|syt        | j                        t        | j                        z  }|syd|v xs ||v S )zBReturn whether this card-action operator may answer gated prompts.r   FTrD  )r   r;  r  r  r  )r   r   r  allowed_idss       r   #_is_interactive_operator_authorizedz1FeishuAdapter._is_interactive_operator_authorized
  sX    B'--/
$,,'#d.G.G*HHk!>Z;%>>r   c          
        |j                  d      }|'t        j                  d       t        r
t               S dS | j                  j                  |      }|s(t        j                  d|       t        r
t               S dS t
        j                  |j                  d      d      }t        |dd      }t        t        |dd	      xs d	      }t        |t        t        |d
d	      xs d	            }	| j                  |	|j                  dd	      d      s,t        j                  d|xs d       t        r
t               S dS t        t        t        |dd      dd	      xs d	      }
t        |j                  dd	      xs d	      }|
r1|r/|
|k7  r*t        j                  d|||
       t        r
t               S dS | j                  |      xs |}t        |dd      }t        t        |dd	      xs d	      }| j                  || j                  |||||            st        r
t               S dS t        yt               }t        0t               }d|_        | j!                  ||      |_        ||_        |S )zISchedule approval resolution and build the synchronous callback response.r  Nz2[Feishu] Card action missing approval_id, ignoring0[Feishu] Approval %s already resolved or unknownr  rm   operatorr   r   r   r   r   r   Frd  z*[Feishu] Unauthorized approval click by %s	<unknown>contextr   zE[Feishu] Approval callback chat mismatch for %s (expected=%s, got=%s))r  r  r  r   r   raw)r  r  )r(  rj  rk  r1   r  rr   r  r   r	   _allow_group_messager  _get_cached_sender_namerO  _resolve_approvalr0   r   r  r3  r  )r   rS  r  r  r  stater  r  r   r  callback_chat_idexpected_chat_idr  chat_contextr   r  r  s                    r   r  z*FeishuAdapter._handle_approval_card_action
  se   "&&}5LLMN4O.0YUYY$$((5LLK[Y4O.0YUYY%)),*:*:?*KVT5*d3gh	26<"=#GSS\^`AaAgeg=hi	((EIIi4LUZ([NNGI_T_`4O.0YUYYwwui'FXZ[a_abuyyB7=2> 05EIY5YNNW  	 5P.0YUYY009DW	ui6glNB?E2F##""'# # 	
 5P.0YUYY&..0#>DDI::&T]:^DI HMr   c          
         |j                  d      }|'t        j                  d       t        r
t               S dS | j                  j                  |      }|s(t        j                  d|       t        r
t               S dS t        |j                  dd      xs d      j                         j                         }|dvr(t        j                  d|       t        r
t               S dS t        |d	d      }t        t        |d
d      xs d      }t        |t        t        |dd      xs d            }	| j                  |	|j                  dd      d      s,t        j                  d|xs d       t        r
t               S dS t        t        t        |dd      dd      xs d      }
t        |j                  dd      xs d      }|
r1|r/|
|k7  r*t        j                  d|||
       t        r
t               S dS | j                  |      xs |}| j                  || j                  |||||
            st        r
t               S dS t        yt               }t        0t               }d|_        | j#                  ||      |_        ||_        |S )zNSchedule update prompt resolution and build the synchronous callback response.r  Nz7[Feishu] Card action missing update_prompt_id, ignoring5[Feishu] Update prompt %s already resolved or unknownr  r   >   r  r  z8[Feishu] Card action has invalid update prompt answer=%rr  r   r   r  r   Fr  z/[Feishu] Unauthorized update prompt click by %sr  r  r   zJ[Feishu] Update prompt callback chat mismatch for %s (expected=%s, got=%s)r   r   r  )r  r  )r(  rj  rk  r1   r  r   r;  r  r  r	   r  r  r  rO  _resolve_update_promptr0   r   r  r3  r  )r   rS  r  r  r  r  r  r  r   r  r  r  r  r  r  s                  r   r  z/FeishuAdapter._handle_update_prompt_card_action
  s}    $$%78	LLRS4O.0YUYY))--i8LLPR[\4O.0YUYY\%%&CRHNBOUUW]]_#LLSU[\4O.0YUYY5*d3gh	26<"=#GSS\^`AaAgeg=hi	((EIIi4LUZ([NNLgNdYde4O.0YUYYwwui'FXZ[a_abuyyB7=2> 05EIY5YNN\  	 5P.0YUYY009DW	##''( ( 	
 5P.0YUYY&..0#>DDI??vYb?cDI HMr   r  c               p  K   | j                   j                  |      }|st        j                  d|       y| j	                  |      st        j                  d|xs d|       yt        |j                  dd      xs d      }|r |r||k7  rt        j                  d|||       y| j                   j                  |d      }|st        j                  d|       y	 d	d
lm	}  ||d   |      }	t        j                  d|	|d   ||       y# t        $ r }
t        j                  d|
       Y d}
~
yd}
~
ww xY ww)z8Pop approval state and unblock the waiting agent thread.r  Nz:[Feishu] Unauthorized approval click by %s for approval %sr  r   r   z8[Feishu] Approval %s chat mismatch (expected=%s, got=%s)z?[Feishu] Approval %s already resolved while validating callbackr   )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(  rj  rk  r  r  r   rT  tools.approvalr  rt  ri  ro  )r   r  r  r  r   r   r  r  r  r  r   s              r   r  zFeishuAdapter._resolve_approval  s,     $$((5LLK[Y77@NNWY`Yodoq|}uyyB7=2>,<,GNNJ-w $$((d;LLZ\gh	[?,U=-A6JEKK[u]+VY  	[LLTVYZZ	[s0   CD6.D
 	D6
	D3D.)D6.D33D6c                 K   | j                   j                  |      }|st        j                  d|       y|rIt	        |d      }| j                  ||j                  dd      d      st        j                  d||       yt        |j                  dd      xs d      }|r |r||k7  rt        j                  d	|||       y| j                   j                  |d      }|st        j                  d
|       y	 | j                  |       t        j                  d|d   ||       y# t        $ r }	t        j                  d|	       Y d}	~	yd}	~	ww xY ww)z@Persist an update prompt answer for the detached update process.r  Nr   r  r   Fr  z=[Feishu] Unauthorized update prompt click by %s for prompt %sz=[Feishu] Update prompt %s chat mismatch (expected=%s, got=%s)zD[Feishu] Update prompt %s already resolved while validating callbackzAFeishu update prompt resolved for session %s (answer=%s, user=%s)r  z*Failed to resolve Feishu update prompt: %s)r  r(  rj  rk  r	   r  r  r   rT  r  rt  ri  ro  )
r   r  r  r  r   r   r  r  r  r   s
             r   r  z$FeishuAdapter._resolve_update_prompt>  sA     ))--i8LLPR[\'DI,,Y		)R8PY^,_^`girsuyyB7=2>,<,GNNO 	 ))--i>LL_ajk	L..v6KKSm$fi  	LLLEsKK	Ls0   C6E9,D& %E&	E/E
E
EEc           
     N  K   | j                   syt        |dd      }t        t        |dd      xs d      }|sy	 | j                  |      }| j	                  | j                   j
                  j                  j                  j                  |       d{   }|r t        |dd              syt        t        |dd      dd      xs g }|r|d	   nd}|syt        |d
d      }	t        t        |	dd      xs d      | j                  k7  ryt        t        |dd      xs d      }
t        t        |dd      xs d      }|
sy	 t        |dd      }t        |dd      }t        t        |dd      xs d      }d|v rdnd}d| d| }| j                  |       d{   }| j                  |
       d{   }| j                  |
|j                  d      xs |
xs d| j!                  ||      |d   |d   d|d          }t#        |t$        j&                  |||| j)                  |
      t+        j,                         !      }t        j/                  d"|||       | j1                  |       d{    y7 # t        $ r t        j                  dd       Y yw xY w7 7 7 3w)#zVFetch the reacted-to message; if it was sent by this bot, emit a synthetic text event.NrS  r  r   r  c                      yr'  r   r   r   r   rY  z6FeishuAdapter._handle_reaction_event.<locals>.<lambda>t  r=  r   r3  r  r   r  rD  r   r>  rc  z5[Feishu] Failed to fetch message for reaction routingTrc  r   r{  r  UNKNOWNr|  r}  r~  z	reaction:r  r   Feishu Chat	chat_infoevent_chat_typer  user_id_altr   r  r>  r   r  	thread_idr  r   r   sourceraw_messager  channel_prompt	timestampzD[Feishu] Routing reaction %s:%s on bot message %s as synthetic event)r  r  r   _build_get_message_requestr_  r  r  r\   r(  r  ri  rj  rk  _resolve_sender_profilerE  build_source_resolve_source_chat_typer9   r:   TEXT_resolve_channel_promptr   nowrt  _handle_message_with_guards)r   r  r3  rS  r  r~   r  r  r  r  r   chat_type_rawuser_id_objr  r  r  synthetic_textsender_profiler  r  synthetic_events                        r   r  z$FeishuAdapter._handle_reaction_eventg  s    ||gt,b9?R@
	55jAG!//0B0B0J0J0N0NPWXXH#N78Y#N#PGHfd;WdKQrE#%(C S(D1F764,23t||C'#y"5;<G[% @ IEJM  eY5#E?DA!2L"ERS
%3$VHAj\:#;;KHH,,W55	""mmF+GwG-44yZg4h"9-$[1&}5 # 
 '$))!77@lln
 	Z\bdnpz{..???_ Y   	LLP[_L`	 I5( 	@s   8J%AI8 I5I8 -J%.'I8 J%4I8 
J%6I8 AJ%JJ%4J!5B:J%/J#0J%5I8 8 JJ%JJ%!J%#J%c                   t        j                          }| j                  j                         D cg c]  \  }}||z
  t        kD  s| }}}|D ]  }| j                  |=  || j                  v ry|| j                  |<   yc c}}w )zTReturn True if this card action token was already processed within the dedup window.TF)rZ  r  r  %_FEISHU_CARD_ACTION_DEDUP_TTL_SECONDS)r   r   r  r  tsexpireds         r   _is_card_action_duplicatez'FeishuAdapter._is_card_action_duplicate  s    iik"&":":"@"@"BwBcBhQvFv1ww 	,A((+	,D,,,*-  ' xs   BBc           
       K   t        |dd      }t        t        |dd      xs d      }|r(| j                  |      rt        j	                  d|       yt        |dd      }t        t        |dd      xs d      }t        |dd      }t        t        |d	d      xs d      }|r|st        j	                  d
       yt        |dd      }t        t        |dd      xs d      }	t        |di       xs i }
d|	 }|
r	 |dt        j                  |
d       z  }t        |dd      }| j                  |       d{   }| j                  |       d{   }| j                  ||j                  d      xs |xs d| j                  |d      |d   |d   d|d         }t        |t        j                   |||xs t        t#        j$                               | j'                  |      t)        j*                               }t        j-                  d|	||       | j/                  |       d{    y# t        $ r Y 'w xY w7 	7 7 w)zHRoute Feishu interactive card button clicks as synthetic COMMAND events.rS  Nr   r   z1[Feishu] Dropping duplicate card action token: %sr  r   r  r   zB[Feishu] Card action missing chat_id or operator open_id, droppingr  r   r  r   z/card r.  Frk  )r   r   r   r   r  rW  r  r   r  r  r  r  zB[Feishu] Routing card action %r from %s in %s as synthetic command)r  r   r  rj  rk  rn  ro  ri  r	   r  rE  r  r(  r  r9   r:   COMMANDuuiduuid4r  r   r  rt  r  )r   r3  rS  r   r  r   r  r   r  
action_tagr  r  r  r  r  r  r  s                    r   r  z'FeishuAdapter._handle_card_action_event  sE    gt,GE7B/526T33E:LLLeT%D1gg~r:@bA5*d3gh	26<"=gLL]^$/3?x@
vw39r!*.AdjjE&R%S"TT $GTDQ	#;;IFF,,W55	""mmF+GwG-44yZa4b"9-$[1&}5 # 
 '$,,1DJJL 177@lln
 	XZdfmovw..???3   G5( 	@s[   C9I<H4 "I;I<IICI.I	/I4	I=I III	Ic                &   | j                   j                  |      }|| j                   j                  |       |S t        | j                         | j                  k\  rd}t        | j                         D ]>  }| j                   |   j                         r!| j                   j                  |       d} n |s7| j                   j                  t        t        | j                                      t        j                         }|| j                   |<   |S )a}  Return (creating if needed) the per-chat asyncio.Lock for serial message processing.

        Bounded with LRU eviction so a long-running gateway that sees many
        distinct chats does not grow ``_chat_locks`` without limit. Locks that
        are currently held are never evicted; if every entry is locked we fall
        back to dropping the least-recently-used one.
        FT)r  r(  move_to_endr|   r  r   lockedrT  r  iterry   r  )r   r   rX  evictedr*  s        r   _get_chat_lockzFeishuAdapter._get_chat_lock  s     ##G,((1Kt D$;$;;GD,,- '',335$$((-"G	
   $$T$t/?/?*@%AB||~$(!r   c                  K   |j                   rt        |j                   dd      xs dnd}| j                  |      }|4 d{    | j                  |       d{    ddd      d{    y7 .7 7 	# 1 d{  7  sw Y   yxY ww)a$  Dispatch a single event through the agent pipeline with per-chat serialization
        before handing the event off to the agent.

        Per-chat lock ensures messages in the same chat are processed one at a
        time (matches openclaw's createChatQueue serial queue behaviour).
        r   r   N)r  r  r  handle_message)r   rS  r   	chat_locks       r   r  z)FeishuAdapter._handle_message_with_guards   s      AF'%,,	26<"RT''0	 	- 	-%%e,,,	- 	- 	-,	- 	- 	- 	-sZ   ABA2BA8A4A8!B,A6-B4A86B8B
>B?B
Bc                j    t        j                  dd      j                         j                         dvS )NFEISHU_REACTIONSr$  >   0nofalse)r  r  r;  r  rN  s    r   _reactions_enabledz FeishuAdapter._reactions_enabled  s,    yy+V4::<BBDL```r   c                  K   | j                   r|r|sy	 ddlm}m} |j	                         j                  d|i      j                         }|j	                         j                  |      j                  |      j                         }| j                  | j                   j                  j                  j                  j                  |       d{   }|r- t        |dd              rt        |dd      }t        |dd      S t        j!                  d	||t        |d
d      t        |dd             y7 c# t"        $ r t        j%                  d||d       Y yw xY ww)zRReturn the reaction_id on success, else None. The id is needed later for deletion.Nr   )CreateMessageReactionRequest CreateMessageReactionRequestBodyr  r  c                      yr'  r   r   r   r   rY  z-FeishuAdapter._add_reaction.<locals>.<lambda>(  r=  r   r3  reaction_idz7[Feishu] Add reaction %s on %s rejected: code=%s msg=%sr@  r  z%[Feishu] Add reaction %s on %s raisedTrc  )r  r  r  r  r9  r{  rM  r  r  r_  r  r  message_reactionr   r  rj  rk  ri  r  )	r   r  r  r  r  r   r~   r  r3  s	            r   _add_reactionzFeishuAdapter._add_reaction  sB    ||:Z!	
 188:j9:  -446J'd#	  "//0B0B0S0S0Z0Z\cddHGGHiGIx6t]D99LLI&$/%. % e  	NN7	   	sG   EB:D5 D32D5 E/D5 2E3D5 5"EEEEc                *  K   | j                   r|r|sy	 ddlm} |j                         j	                  |      j                  |      j                         }| j                  | j                   j                  j                  j                  j                  |       d {   }|r t        |dd              ryt        j                  d||t        |dd       t        |d	d              y7 J# t        $ r t        j!                  d
||d       Y yw xY ww)NFr   )DeleteMessageReactionRequestr  c                      yr'  r   r   r   r   rY  z0FeishuAdapter._remove_reaction.<locals>.<lambda>G  r=  r   Tz:[Feishu] Remove reaction %s on %s rejected: code=%s msg=%sr@  r  z([Feishu] Remove reaction %s on %s raisedrc  )r  r  r  r9  r  r  rM  r_  r  r  r  deleter  rj  rk  ri  r  )r   r  r  r  r~   r  s         r   _remove_reactionzFeishuAdapter._remove_reaction;  s     ||:[	H,446J'[)	  "//0B0B0S0S0Z0Z\cddHGGHiGILLL&$/%. # e  	NN:	   	sG   DB	C+ C)C+ 8D9/C+ (D)C+ +"DDDDc                    | j                   }|||<   |j                  |       t        |      t        kD  r&|j	                  d       t        |      t        kD  r%y y )NFlast)r  r  r|   &_FEISHU_PROCESSING_REACTION_CACHE_SIZEpopitem)r   r  r  caches       r   _remember_processing_reactionz+FeishuAdapter._remember_processing_reactionY  sO    22'j*%%jAAMMuM% %jAAr   c                :    | j                   j                  |d       S r   )r  rT  )r   r  s     r   _pop_processing_reactionz&FeishuAdapter._pop_processing_reaction`  s    1155j$GGr   c                   K   | j                         sy |j                  }|r|| j                  v ry | j                  |t               d {   }|r| j                  ||       y y 7 wr   )r  r  r  r  _FEISHU_REACTION_IN_PROGRESSr  )r   rS  r  r  s       r   on_processing_startz!FeishuAdapter.on_processing_startc  sg     &&(%%
Z4+M+MM ..z;WXX..z;G  Ys   AA'
A%A'c                H  K   | j                         sy |j                  }|sy | j                  j                  |      }|r,| j	                  ||       d {   sy | j                  |       |t        j                  u r| j                  |t               d {    y y 7 H7 wr   )
r  r  r  r(  r  r  r;   FAILUREr  _FEISHU_REACTION_FAILURE)r   rS  outcomer  start_reaction_ids        r   on_processing_completez$FeishuAdapter.on_processing_completem  s      &&(%%
 >>BB:N..z;LMMM ))*5'///$$Z1IJJJ 0 N Ks%   AB"BAB"B B" B"c                .   t        j                          }| j                  j                  |      }|R|\  }}}||z
  t        k  r@|dz  }|t        z  dk(  rt
        j                  d|||||z
         |||f| j                  |<   yd||f| j                  |<   y)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.
        Nrv   r   zY[Feishu] Webhook anomaly: %d consecutive error responses (%s) from %s over the last %.0fs)rZ  r  r(  #_FEISHU_WEBHOOK_ANOMALY_TTL_SECONDS!_FEISHU_WEBHOOK_ANOMALY_THRESHOLDrj  r  )r   	remote_ipstatusr  entryr  _last_status
first_seens           r   _record_webhook_anomalyz%FeishuAdapter._record_webhook_anomaly  s     iik,,00;.3+E<Z"EE
<<ANN.!j( <A&*:U,,Y734fc2B$$Y/r   c                <    | j                   j                  |d       y)zCReset the anomaly counter for remote_ip after a successful request.N)r  rT  )r   r  s     r   _clear_webhook_anomalyz$FeishuAdapter._clear_webhook_anomaly  s    $$((D9r   c                ^    ddl m} t        | dd      }t        |dd      xs i } ||||      S )zResolve a Feishu per-channel system prompt.

        Mirrors the Discord/Slack behaviour so ``channel_prompts: {<chat_id>:
        "<prompt>"}`` in ``PlatformConfig.extra`` is honoured for Feishu chats
        instead of being silently ignored.
        r   )resolve_channel_promptr  Nr  )gateway.platforms.baser  r  )r   r   	parent_idr  _config_extras         r   r  z%FeishuAdapter._resolve_channel_prompt  s8     	B$$/'406B%fgyAAr   r  c               ,  K   | j                  |       d {   \  }}}	}
}|t        j                  k(  r-t        ||      }|j	                  d      rt        j
                  }|t        j                  k(  r|s|	st        j                  d|       y |t        j
                  k7  rt        |      }|r|r| d| n|}t        |dd       xs t        |dd       xs d }t        |dd       xs  t        |dd       xs t        |dd       xs d }|r| j                  |       d {   nd }t        |dd       xs  t        |d	d       xs t        |d
d       xs d}t        j                  d|dk(  rdnd||j                  t        |dd      xs d|rdnd||d d t        |	      	       t        |dd      xs d}| j                  |       d {   }| j                  ||       d {   }| j!                  ||j#                  d      xs |xs d| j%                  ||      |d	   |d   ||d   |      }t'        ||||||	|
||| j)                  ||xs d       t+        j,                               }| j/                  |       d {    y 7 q7 k7 7 7 w)N/z*[Feishu] Ignoring empty text message id=%sz

r  root_idr  upper_message_idr   r   r   r  z\[Feishu] Inbound %s message received: id=%s type=%s chat_id=%s sender=%s:%s text=%r media=%drc  r;  rW  r   r   r  r  rg   r  r   r  r  r  r  )r   r  r>  r   r  r  r  rd  )r   r   r  r  r  
media_urlsmedia_typesreply_to_message_idreply_to_textr  r  )_extract_message_contentr:   r  r\  r2  r  rj  rk  rP  r  _fetch_message_textrt  r   r|   rE  r  r  r(  r  r9   r  r   r  _dispatch_inbound_event)r   r3  r\   r  r>  r  rd  r   inbound_typer  r  r   r   r  r  r  sender_primaryr   r  r  r  r  s                         r   rg  z&FeishuAdapter._process_inbound_message  s     GKFcFcdkFl@l=lJX;+++,T8<Ds#*22 ;+++DLLEzR;...&x0D.2$tD6*G[$7d77IW[;\d`d	G[$/ w 2D9w	40 	 	 Pcd667JKKKhl Iy$/ y)T2y*d3 	 	 	j&DGGY+1rE#J
O
	
 '9b17R,,W55	#;;If;UU""mmF+GwG-44yZc4d"9-$[1&}5 # 	
 "%!!# 3'77ARdSlln

 **:666M Am0 L* 6U0 	7sZ   JJDJ J!B'JJ	J#J$BJJJJJJJc                .  K   |j                   t        j                  k(  r*|j                         s| j	                  |       d{    y| j                  |      r| j                  |       d{    y| j                  |       d{    y7 J7 !7 	w)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  r   rS  s     r   r  z%FeishuAdapter._dispatch_inbound_event  s     !1!11%:J:J:L**5111))%0++E222..u555 2 35s6   ABB*B/B0B	B
BBBc                    t        |j                  xrJ |j                  t        j                  t        j
                  t        j                  t        j                  hv       S r   )r   r  r   r:   PHOTOVIDEODOCUMENTAUDIOr&  s     r   r$  z'FeishuAdapter._should_batch_media_event  sN     v""{'8'8+:K:K[MaMacnctct&uu
 	
r   c                    ddl m}  ||j                  | j                  j                  j                  dd      | j                  j                  j                  dd            }| d|j                  j                   S )	Nr   build_session_keygroup_sessions_per_userTthread_sessions_per_userF)r/  r0  z:media:)gateway.sessionr.  r  r  r  r(  r   r   )r   rS  r.  r  s       r   _media_batch_keyzFeishuAdapter._media_batch_key  sn    5'LL$(KK$5$5$9$9:SUY$Z%)[[%6%6%:%:;UW\%]

 ge&8&8&>&>%?@@r   c                    | j                   |j                   k(  xrc | j                  |j                  k(  xrH | j                  |j                  k(  xr- | j                  j                  |j                  j                  k(  S r   )r   r  r  r  r  rw  incomings     r   _media_batch_is_compatiblez(FeishuAdapter._media_batch_is_compatible$  su     !!X%:%:: G,,0L0LLG&&(*@*@@G ))X__-F-FF		
r   c                  K   | j                  |      }| j                  j                  |      }|!|| j                  |<   | j                  |       y | j	                  ||      s:| j                  |       d {    || j                  |<   | j                  |       y |j                  j                  |j                         |j                  j                  |j                         |j                  r+| j                  |j                  |j                        |_	        |j                  |_        |j                  r|j                  |_        | j                  |       y 7 wr   )r2  r  r(  _schedule_media_batch_flushr6  _flush_media_batch_nowr  r  r  r   _merge_captionr  r  )r   rS  r*  rw  s       r   r%  z"FeishuAdapter._enqueue_media_event-  s%    ##E*..2237/4D'',,,S1..x?--c222/4D'',,,S1""5#3#34##E$5$56:: //uzzJHM"__"'"2"2H((- 3s   A6E!8E9C'E!c                R    | j                  | j                  || j                         y r   )_reschedule_batch_taskr  _flush_media_batchr   r*  s     r   r8  z)FeishuAdapter._schedule_media_batch_flushB  s%    ##++##	
r   c                  K   t        j                         }	 t        j                  | j                         d {    | j	                  |       d {    | j
                  j                  |      |u r| j
                  j                  |d        y y 7 X7 A# | j
                  j                  |      |u r| j
                  j                  |d        w w xY wwr   )ry   current_taskr[  r&  r9  r  r(  rT  )r   r*  r@  s      r   r=  z FeishuAdapter._flush_media_batchI  s     ++-	?-- ? ?@@@--c222..2237<G//33C> H A2..2237<G//33C> Hs>   C"B BB BB ;CB B <CCc                   K   | j                   j                  |d       }|sy t        j                  d|t	        |j
                               | j                  |       d {    y 7 w)Nz6[Feishu] Flushing media batch %s with %d attachment(s))r  rT  rj  rt  r|   r  r  r   r*  rS  s      r   r9  z$FeishuAdapter._flush_media_batch_nowR  sZ     ++//T:D  !	

 ..u555s   AA( A&!A(c                ^   K   | j                  |d      }t        ||       d {   S 7 w)NrJ   r  ext)_guess_remote_extensionr?   )r   r0  rE  s      r   r1  z$FeishuAdapter._download_remote_image]  s.     **9f*E))====s   $-+-c                 K   ddl m}  ||      st        d|d d        dd l}|j	                  dd      4 d {   }|j                  |dd	d
       d {   }|j                          t        |j                  j                  dd            }|j                  }	d d d       d {    | j                  |||      }
t        	|
      }||
fS 7 7 |7 -# 1 d {  7  sw Y   =xY ww)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_namer4  )tools.url_safetyrH  r}   httpxAsyncClientr(  raise_for_statusr   rM  rw   _derive_remote_filenamer>   )r   file_urlr4  r5  rH  rR  r  r  content_type_hdrr   filenamecached_paths               r   r7  z'FeishuAdapter._download_remote_documenta  s     	18$EhsPRm_UVV$$TD$I 	$ 	$V#ZZ"M# (  H %%'  #8#3#3#7#7#KL##D	$ 	$ //)'#	 0 
 0h?H$$+	$	$ 	$ 	$ 	$sX   ;C1CC1CCAC!C1,C-*C1CC1C."C%#C.*C1c                   t        | xs dj                  dd      d         j                  j                         }|t        t
        z  t        z  t        t              z  v r|S |S )Nr   ?rv   r   )	r   r<  suffixr  _IMAGE_EXTENSIONS_AUDIO_EXTENSIONS_VIDEO_EXTENSIONSr  r=   )r   rb  rE  s      r   rF  z%FeishuAdapter._guess_remote_extension  sg    CI2$$S!,Q/077==?/2CCFWWZ]^vZwwxs  	F  F  	Fr   c               \   t        | xs dj                  dd      d         j                  xs |}t        |      j                  j	                         }|sVt        j                  |xs dj                  dd      d   j                         j	                         xs d      xs |}| | }|S )Nr   r[  rv   r   ;)r   r<  r   r\  r  	mimetypesguess_extensionr;  )rV  rO  rP  r4  r  rE  guesseds          r   rU  z%FeishuAdapter._derive_remote_filename  s    (.b//Q7:;@@PL	9o$$**,//1C0J0J3PQ0RST0U0[0[0]0c0c0e0kikl{p{G$+gY/Ir   c                "   t        | t              r@t        di | j                         D ci c]  \  }}|t        j                  |       c}}S t        | t              r#| D cg c]  }t        j                  |       c}S | S c c}}w c c}w )Nr   )rG  r   r	   r  r  _namespace_from_mappingr   )r   r*  r  s      r   rf  z%FeishuAdapter._namespace_from_mapping  s{    eT""whmhshshu%v[d[^`dc=+P+PQU+V&V%vwweT"LQRDM99$?RR &wRs    B%Bc                  K   t        |dd       xs d}| j                   d| j                   d| }| j                  |      s?t        j                  d|       | j                  |d       t        j                  dd      S t        |d	i       xs i }t        |j                  d
d      xs d      j                  d      d   j                         j                         }|rE|dk7  r@t        j                  d||       | j                  |d       t        j                  dd      S t        |dd       }|I|t        kD  r@t        j                  d||       | j                  |d       t        j                  dd      S 	 t        j                   t#        |t              t$               d {   }	 t/        j0                  |j3                  d#            }| j8                  r|j                  d%      xs i }	t        |	j                  d&      xs |j                  d&      xs d      }
|
r<t;        j<                  |
j?                         | j8                  j?                               s?t        j                  d'|       | j                  |d(       t        j                  d)d*      S |j                  d+      d,k(  r't        j,                  d-|j                  d-d      i      S | j@                  r[| jC                  |jD                  |      s?t        j                  d.|       | j                  |d/       t        j                  d)d0      S |j                  d1      rAt        jG                  d2       | j                  |d3       t        j,                  dd4d!d"      S | jI                  |       t        |j                  d%      xs i j                  d5      xs d      }| jK                  |      }|d6k(  r| jM                  |       n|d7k(  r| jO                  |       n|d8k(  r| jQ                  |       n|d9k(  r| jS                  |       nv|d:v r| jU                  ||       n_|d;k(  r| jW                  |       nH|d<k(  r| jY                  |       n1|d=k(  r| j[                  |       nt        j]                  d>|xs d       t        j,                  dd?d!      S 7 *# t&        $ rB t        j                  d|       | j                  |d       t        j                  dd      cY S t        j(                  $ rG t        j                  dt$        |       | j                  |d       t        j                  dd      cY S t*        $ r/ | j                  |d       t        j,                  dd d!d"      cY S w xY w# t.        j4                  t6        f$ r/ | j                  |d       t        j,                  dd$d!d"      cY S w xY ww)@NremoterN  r  z+[Feishu] Webhook rate limit exceeded for %s429i  zToo Many Requests)r  r   rM  rN  r   ra  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{  z+[Feishu] Webhook body exceeds limit from %sz6[Feishu] Webhook body read timed out after %ds from %s408i  zRequest Timeout400i  zfailed to read body)r@  r  )r  utf-8zinvalid jsonr.  r   z=[Feishu] Webhook rejected: invalid verification token from %sz	401-tokeni  zInvalid verification tokenr   url_verification	challengez4[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/  r6  zcard.action.triggerr7  r8  z([Feishu] Ignoring webhook event type: %sok)/r  r  r)  _check_webhook_rate_limitrj  r  r  r   Responser   r(  r<  r;  r  _FEISHU_WEBHOOK_MAX_BODY_BYTESry   r  r   $_FEISHU_WEBHOOK_BODY_TIMEOUT_SECONDSr}   r  ri  json_responsern  r  decoder  UnicodeDecodeErrorr  hmaccompare_digestencoder  _is_webhook_signature_validrM  ro  r  rf  r=  r;  rC  rE  r1  rA  rK  rL  rk  )r   r~   r  rate_keyrM  rO  rl  
body_bytesr  r.  incoming_tokenr  r3  s                r   _handle_webhook_requestz%FeishuAdapter._handle_webhook_request  sW    Wh5B	 ll^1T%7%7$8)E--h7NNH)T((E:<<s1DEE '9b17R7;;~r:@bAGGLQOUUW]]_L,>>NNZ\hjst((E:<<s1IJJ !*:DA%.;Y*YNNOQ_ajk((E:<<s1IJJ	^&-&6&612 =' !J&	Wjj!2!27!;<G ##[[*0bF G!4!RG8L!RPRSN ")<)<%%')A)A)H)H)J* ^`ij,,YD||35QRR ;;v"44$$k7;;{B3O%PQQ T%E%EgooWa%bNNQS\]((I><<s1DEE;;y!LLgh((OD$$c:h%iruvv##I.'++h/52::<HNBO
++G400""4(77''-88&&t,::**40__##J500((.88((.66**40LLCZE\S\]  !D!9::]!  	KNNH)T((E:<<s1IJJ## 	DNNSUy  |E  F((E:<<s1BCC 	^((E:$$c:O%PY\]]	^ $$&89 	W((E:$$c.%IRUVV	Wsv   E=X  ,S ,S-S 2$V5 L X S AV2!X #AV29X ;4V2/X 1V22X 5AW=:X <W==X c                .   t        |j                  dd      xs d      }t        |j                  dd      xs d      }t        |j                  dd      xs d      }|r|r|sy	 |j                  dd      }| | | j                   | }t	        j
                  |j                  d            j                         }t        j                  |j                         |j                               S # t        $ r t        j                  d	d
       Y y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-signatureFrp  r<  )errorsz3[Feishu] Signature verification raised an exceptionTrc  )r   r(  rz  r  hashlibsha256r~  	hexdigestr|  r}  ri  rj  rk  )	r   rM  r  r  noncerO  body_strrw   computeds	            r   r  z)FeishuAdapter._is_webhook_signature_valid  s     $>CIrJ	GKK 6;ArB$6;ArB	Y		!(((CH"E74+<+<*=hZHG~~gnnW&=>HHJH &&x'8):J:J:LMM 	LLNY]L^	s   &B
C1 1 DDc                Z   t        j                          }| j                  j                  |      }|0|\  }}||z
  t        k  r|t        k\  ry|dz   |f| j                  |<   yt        | j                        t        k\  r| j                  j                         D cg c]  \  }\  }}||z
  t        k\  r| }	}}}|	D ]  }| j                  |=  || j                  vr7t        | j                        t        k\  rt        j                  dt               yd|f| j                  |<   yc c}}}w )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.
        Frv   TuQ   [Feishu] Webhook rate-limit table at capacity (%d keys) — denying untracked key)
rZ  r  r(  #_FEISHU_WEBHOOK_RATE_WINDOW_SECONDS_FEISHU_WEBHOOK_RATE_LIMIT_MAXr|   _FEISHU_WEBHOOK_RATE_MAX_KEYSr  rj  r  )
r   r  r  r  r  window_startk_r~  
stale_keyss
             r   ru  z'FeishuAdapter._check_webhook_rate_limit  s:    iik))--h7"'E<\!$GG:: 7<qy,6O))(3t(()-JJ$($=$=$C$C$E  a!R8BB J    1--a01 t888SAZAZ=[_|=|g1 /0#h!!(+#s   D&c                    ddl m}  ||j                  | j                  j                  j                  dd      | j                  j                  j                  dd      |j                  j                        S )z?Return the session-scoped key used for Feishu text aggregation.r   r-  r/  Tr0  F)r/  r0  profile)r1  r.  r  r  r  r(  r  )r   rS  r.  s      r   _text_batch_keyzFeishuAdapter._text_batch_keyI  s^    5 LL$(KK$5$5$9$9:SUY$Z%)[[%6%6%:%:;UW\%]LL((	
 	
r   c                    | j                   |j                   k(  xrH | j                  |j                  k(  xr- | j                  j                  |j                  j                  k(  S )z>Only merge text events when reply/thread context is identical.)r  r  r  r  r4  s     r   _text_batch_is_compatiblez'FeishuAdapter._text_batch_is_compatibleT  sY     ((H,H,HH G&&(*@*@@G))X__-F-FF	
r   c                   K   | j                  |      }t        |j                  xs d      }| j                  j	                  |      }|7||_        || j                  |<   d| j                  |<   | j                  |       y| j                  ||      sI| j                  |       d{    || j                  |<   d| j                  |<   | j                  |       y| j                  j	                  |d      }|dz   }|j                  xs d}|j                  r|r|j                   d| n|j                  xs |}|| j                  kD  st        |      | j                  kD  rI| j                  |       d{    || j                  |<   d| j                  |<   | j                  |       y||_        ||_        |j                  |_        |j                  r|j                  |_        || j                  |<   | j                  |       y7 b7 w)z=Debounce rapid Feishu text bursts into a single MessageEvent.r   Nrv   r9  )r  r|   r   r  r(  _last_chunk_lenr  _schedule_text_batch_flushr  _flush_text_batch_nowr$  r%  r  r  )	r   rS  r*  	chunk_lenrw  existing_count
next_countappended_text	next_texts	            r   r#  z!FeishuAdapter._enqueue_text_event]  s    ""5)

(b)	--11#6$-E!.3D&&s+34D++C0++C0--h>,,S111.3D&&s+34D++C0++C088<<S!D#a'


(b;C==]x}}oR7aiananar	555Y$JdJd9d,,S111.3D&&s+34D++C0++C0!#, "__"'"2"2H/9'','',/ 2 2s&   B%H'H	(CH7H8BHHc                R    | j                  | j                  || j                         y)z9Reset the debounce timer for a pending Feishu text batch.N)r<  r  _flush_text_batchr>  s     r   r  z(FeishuAdapter._schedule_text_batch_flush  s%    ##**""	
r   c                    | j                  |      }|r |j                         s|j                          t        j                   ||            | |<   y r   )r(  r  r  ry   create_task)task_mapr*  flush_fn
prior_tasks       r   r<  z$FeishuAdapter._reschedule_batch_task  sB     \\#&
joo/++HSM:r   c                D  K   t        j                         }	 | j                  j                  |      }|rt	        |dd      nd}|| j
                  k\  r| j                  }n| j                  }t        j                  |       d{    | j                  |       d{    | j                  j                  |      |u r| j                  j                  |d       yy7 X7 A# | j                  j                  |      |u r| j                  j                  |d       w w xY w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)ry   r@  r  r(  r  _SPLIT_THRESHOLDr#  r"  r[  r  r  rT  )r   r*  r@  r  last_lendelays         r   r  zFeishuAdapter._flush_text_batch  s     ++-	> 0044S9GAHww(91=aH4000<<66--&&&,,S111--11#6,F..223= G '1--11#6,F..223= GsA   D A,C! CC! CC! ";D C! C! !<DD c                  K   | j                   j                  |d      }| j                  j                  |d       |syt        j	                  d|t        |j                  xs d             | j                  |       d{    y7 w)z,Dispatch the current text batch immediately.Nz*[Feishu] Flushing text batch %s (%d chars)r   )r  rT  r  rj  rt  r|   r   r  rB  s      r   r  z#FeishuAdapter._flush_text_batch_now  ss     **..sD9''++C68

 b!	

 ..u555s   A>B BBc           	       K   t        |dd      xs d}t        |dd      xs d}t        t        |dd      xs d      }t        j                  d||       t	        ||t        |dd       | j                               }| j                  ||       d {   \  }}| j                  ||      }|j                  }	|t        j                  t        j                  t        j                  t        j                  hv r@t        |      d	k(  r2|j                  d
v r$| j!                  |d   |d          d {   }
|
r|
}	|	|||t#        |j$                        fS 7 7 $w)Nrw   r   r   r  z3[Feishu] Received raw message type=%s message_id=%sr   r   r  r   r  )r  r  rv   >   r  r  r   )r  r   rj  rt  r  _bot_identity"_download_feishu_message_resources _resolve_normalized_message_typer   r:   r*  r+  r)  r(  r|   r   _maybe_extract_text_documentr   r   )r   r\   r  r   r  r  r  r  r  r   injecteds              r   r  z&FeishuAdapter._extract_message_content  s\     gy"5;7NB7=2,;ArB
I8U_`-!#Wj$7""$	

 )-(O(O!! )P )
 #

K <<ZU&& [11;3D3DkFWFWYdYjYjkkJ1$115JJ!>>z!}kZ[n]]H\:{DATAT<UUU!#
 ^s%   BEEBE1E2#EEc                 K   g }g }|j                   D ]E  }| j                  ||       d {   \  }}|s$|j                  |       |j                  |       G |j                  D ]e  }| j	                  ||j
                  |j                  |j                         d {   \  }}|sD|j                  |       |j                  |       g ||fS 7 7 4w)N)r  r   )r  r   r   fallback_filename)r   _download_feishu_imagerw  r   !_download_feishu_message_resourcer   r   r   )	r   r  r  r  r  r   rY  
media_typer  s	            r   r  z0FeishuAdapter._download_feishu_message_resources  s      !#
!##.. 	/I,0,G,G%# -H - '#K !!+."":.	/ $.. 		/I,0,R,R%"++'55"+"5"5	 -S - '#K !!+."":.		/ ;&&'''s-   *CC	CA)C C!	C+)CCc                   | xs dj                         }|j                  d      rt        j                  S |j                  d      rt        j                  S |j                  d      rt        j
                  S |S )Nr   image/audio/video/)r  r2  r:   r(  r+  r)  )r  rb  r  s      r   _resolve_media_message_typez)FeishuAdapter._resolve_media_message_type  sh     &B--/
  *$$$  *$$$  *$$$r   c                H   |j                   }|dk(  r(| j                  |r|d   ndt        j                        S |dk(  r(| j                  |r|d   ndt        j                        S |dk(  r(| j                  |r|d   ndt        j
                        S t        j                  S )Nr  r   r   r  r  r  )r   r  r:   r(  r+  r*  r  )r   r  r  	preferreds       r   r  z.FeishuAdapter._resolve_normalized_message_type  s    
 55	33kKNWYcnctct3uu33kKNWYcnctct3uu
"33kKNWYcncwcw3xxr   c                  K   |r|j                  d      sy	 t        j                  j                  |      t        kD  ryt        |      j                  j                         }|dvr|dvryt        |      j                  d      }| j                  |      }d| d| S # t        t        f$ r t        j                  d	|d
       Y yw xY ww)Nztext/r   >   .md.txt>   
text/plaintext/markdownrp  encodingz[Content of z]:
z7[Feishu] Failed to inject text document content from %sTrc  )r2  r  r  getsize_MAX_TEXT_INJECT_BYTESr   r\  r  	read_text_display_name_from_cached_pathOSErrorr{  rj  r  )r   rY  r  rE  rw   r  s         r   r  z*FeishuAdapter._maybe_extract_text_document  s     *"7"7"@	ww{+.DD{#**002C/)j@_._;'1171CG>>{KL!,tG9==+, 	NNTValpNq	s>   C&B C+B *C+3B C'C	CC		Cc          
       K   | j                   r|sy	 | j                  ||d      }| j                  | j                   j                  j                  j
                  j                  |       d {   }|r|j                         s/t        j                  d|t        |dd      t        |dd             y| j                  |      }|sy| j                  |d	      }t        |d
d       xs | d}| j                  ||dt              }t        ||      }	| j!                  || j#                  |            }
|	|
fS 7 # t$        $ r t        j                  d|d       Y yw xY ww)NrF  r  r  r   r   z+[Feishu] Failed to download image %s: %s %sr@  rN  r  request failedrN  r   rJ   allowedrD  r  z*[Feishu] Failed to cache image resource %sTrc  )r  _build_message_resource_requestr_  r  r  message_resourcer(  r  rj  r  r  _read_binary_response_get_response_header_guess_extensionr]  rA   _normalize_media_type_default_image_media_typeri  )r   r  r   r~   r  	raw_bytesrO  rX  rE  rY  r  s              r   r  z$FeishuAdapter._download_feishu_image%  sf    ||:	::%"% ; G
 "//0B0B0S0S0W0WY`aaH8#3#3#5AHfi8He-=>	 228<I44X~NLxd;Q)D?QH'',Pa'bC0DK33L$JhJhilJm3nJ
**# b$  	NNG]aNb	sU   E#AD< .D:/AD< 3E#4D< E#A1D< 9E#:D< <!E E#E  E#c                 K   | j                   r|sy|g}|dv r|j                  d       |D ]  }	 | j                  |||      }| j                  | j                   j                  j
                  j                  j                  |       d {   }|r|j                         s1t        j                  d|||t        |dd      t        |dd	             | j                  |      }	|	s| j                  |d
      }
t        |dd       xs d}|xs |xs | d| }| j                  |
| j                  |            }|j!                  d      rU| j#                  ||
dt$              }t'        |	|      }t        j)                  d|       ||xs | j+                  |      fc S |dk(  s|j!                  d      r\| j#                  ||
dt,              }t/        |	|      }t        j)                  d|       ||xs d|j1                  d      xs d fc S |j!                  d      rBt3        |      j4                  s| d}t7        |	|      }t        j)                  d|       ||fc S t3        |      j4                  s|t8        v r| t8        |    }t7        |	|      }t        j)                  d|       ||xs | j;                  |      fc S  y7 B# t<        $ r t        j?                  d||d        Y w xY ww)!NrF  >   r  r  r   r  z>[Feishu] Resource download failed for %s/%s via type=%s: %s %sr@  rN  r  r  rN  r   r   r  r  r  rJ   r  rD  z,[Feishu] Cached message image resource at %sr  r  rQ   z,[Feishu] Cached message audio resource at %srk  oggr  r[   z,[Feishu] Cached message video resource at %sz/[Feishu] Cached message document resource at %sz/[Feishu] Failed to cache message resource %s/%sTrc  ) r  rw  r  r_  r  r  r  r(  r  rj  rk  r  r  r  r  _guess_media_type_from_filenamer2  r  r]  rA   rt  r  r^  r@   rR  r   r\  r>   _DOCUMENT_MIME_TO_EXT_guess_document_media_typeri  r  )r   r  r   r   r  request_typesrequest_typer~   r  r  rO  response_filenamerX  r  rE  rY  s                   r   r  z/FeishuAdapter._download_feishu_message_resourceD  s     ||:&..  () <	L;>>)%". ? 
 "&!3!3DLLOO4F4F4W4W4[4[]d!eex'7'7'9LLX" $&)<%1AB  66x@	 #88>R$+Hk4$H$NB!,a0Aa~UVW_V`Ea!77  @@J 8 

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

3@XSX?Y7Z[[((2>00&.Zt#4";Ix"PKKK NP[\&
22H~,,?T1T"*,A*,M+NOH7	8LM{["Z%\43R3RS[3\]]k<	z m f^  E!	  s|   .K:AKKAKK:K'K:(B7KK:"A/KK:AK$K:'A"K	K:K"K73K:6K77K:c                    t        | dd       }|yt        |d      rt        |j                               S t        |j	                               S )Nr   r   getvalue)r  r  bytesr  r  )r  file_objs     r   r  z#FeishuAdapter._read_binary_response  sH    8VT28Z(**,--X]]_%%r   c           	        t        | dd       }t        |di       xs i }t        |j                  ||j                  |j                         d            xs d      j	                  dd      d   j                         j                         S )Nr  rM  r   ra  rv   r   )r  r   r(  r  r<  r;  )r  r   r  rM  s       r   r  z"FeishuAdapter._get_response_header  sv    ht,#y"-37;;tW[[r%BCIrJPPQTVWXYZ[aaciikkr   c                  t        | xs d      j                  j                         }||v r|S t        j                  |xs dj                  dd      d   j                         j                         xs d      }||v r|S |S Nr   ra  rv   r   )r   r\  r  rb  rc  r<  r;  )rX  rO  rb  r  rE  rd  s         r   r  zFeishuAdapter._guess_extension  s|    8>r"))//1'>J++\-?R,F,FsA,Nq,Q,W,W,Y,_,_,a,geghgNr   c               x    | xs dj                  dd      d   j                         j                         }|xs |S r  )r<  r;  r  )rO  rb  r  s      r   r  z#FeishuAdapter._normalize_media_type  s;    "(b//Q7:@@BHHJ
$W$r   c                    t        | xs d      j                  j                         }t        j                  |t        j                  | xs d      d   xs d      S )Nr   r   zapplication/octet-stream)r   r\  r  r=   r(  rb  
guess_type)rX  rE  s     r   r  z(FeishuAdapter._guess_document_media_type  sN    8>r"))//1'++C1E1EhnRT1UVW1X1v\vwwr   c                    t         j                  j                  |       }|j                  dd      }t	        |      dk\  r|d   n|}t        j                  dd|      S )Nr  rU  rb   z	[^\w.\- ])r  r  r  r<  r|   r0  r!  )r  r  r  r  s       r   r  z,FeishuAdapter._display_name_from_cached_path  sN    77##D)sA&#&u:?uQxvvlC66r   c                `   t        j                  | xs d      d   xs dj                         }|r|S t        | xs d      j                  j                         }|t
        v rd|j                  d       S |t        v rd|j                  d       S |t        v rt        j                  |      S y)Nr   r   r  rk  r  )rb  r  r  r   r\  r_  rR  r^  r]  r  r  )rX  rd  rE  s      r   r  z-FeishuAdapter._guess_media_type_from_filename  s    ''B7:@bGGIN8>r"))//1##CJJsO,--##CJJsO,--## ::3??r   c                z    | xs dj                         j                         }|dk(  ryd|v sd|v sd|v ry|dk(  ryy)Nr   rc  r;  topicthreadforumrW  )r;  r  )rD  r  s     r   rA  zFeishuAdapter._map_chat_type  sP    #)r00288:
j H
$:g>S r   c                    t        | j                  d      xs d      j                         j                         }|dv r|S |dk(  ryy)Nr   r   >   r  rW  rc  r;  rW  )r   r(  r;  r  )r  r  r  s      r   r  z'FeishuAdapter._resolve_source_chat_type  sH    y}}V,2399;AAC))Oe#r   c                  K   t        |dd      xs d}t        |dd      xs d}t        |dd      xs d}|xs |}|r|n|xs |}| j                  ||       d{   }|||dS 7 
w)uC  Map Feishu's three-tier user IDs onto Hermes' SessionSource fields.

        Preference order for the primary ``user_id`` field:
          1. user_id  (tenant-scoped, most stable — requires permission scope)
          2. open_id  (app-scoped, always available — different per bot app)

        ``user_id_alt`` carries the union_id (developer-scoped, stable across
        all apps by the same developer).  Session-key generation prefers
        user_id_alt when present, so participant isolation stays stable even
        if the primary ID is the app-scoped open_id.
        r   Nr   r   r  )r   r  r  )r  _resolve_sender_name_from_api)	r   r  rd  r   r   r   
primary_idname_lookup_idr  s	            r   r  z%FeishuAdapter._resolve_sender_profile  s     " )Y5=)Y5=9j$7?4'
$*1Gx!??6 @ 
 
 "%#
 	

s   AA)A'A)c                    |sy| j                   j                  |      }|y|\  }}t        j                         |k  r|S | j                   j                  |d       y)z>Return a cached sender name only while its TTL is still valid.N)r  r(  rZ  rT  )r   r  rC  r   	expire_ats        r   r  z%FeishuAdapter._get_cached_sender_name  s[    ((,,Y7> i99;"K##It4r   c                 K   |r| j                   sy|j                         }|syt        j                         }| j                  |      }||xs dS |rw| j	                  |g       d{   }|y|t
        z   }|j                         D ]  \  }}	|	|f| j                  |<    | j                  j                  |      }
|
r	|
d   xs dS dS 	 ddl	m
} |j                  d      rd}n|j                  d      rd}nd}|j                         j                  |      j                  |      j                         }| j!                  | j                   j"                  j$                  j&                  j                  |       d{   }|r|j)                         syt+        t+        |d	d      d
d      }t+        |dd      xs+ t+        |dd      xs t+        |dd      xs t+        |dd      }	|	r<t-        |	t.              r,|	j                         }	|	r|	|t
        z   f| j                  |<   |	S y7 7 # t0        $ r t2        j5                  d|d       Y yw xY ww)u   Bots divert to bot/basic_batch — contact API doesn't return bot names.
        Failures are silent so the pipeline never blocks on name resolution.
        Nr   )GetUserRequestou_r   on_r   r   r3  r  r   r  nicknameen_namez-[Feishu] Failed to resolve sender name for %sTrc  )r  r;  rZ  r  _fetch_bot_names_FEISHU_SENDER_NAME_TTL_SECONDSr  r  r(  lark_oapi.api.contact.v3r  r2  r9  r   user_id_typerM  r_  contactv3r  r  r  rG  r   ri  rj  rk  )r   r  rd  trimmedr  cached_namenamesr  oidr   hitr  rE  r~   r  r  s                   r   r  z+FeishuAdapter._resolve_sender_name_from_api  sP     //#iik227;"&$&//	::E}==I"[[] A	T04i/@'',A))--g6C'*CFNd44	d?!!%(###E*$#$,,.66w?LLWU[[]G!//0D0D0G0G0L0L0P0PRYZZH8#3#3#578VT:FDIDfd+ 24624T22 4D1	  
4-zz|8<cDc>c7dD++G4K E ;" [  	dLLH)^bLc	dsW   A'I)H(*A!IB3H- ?H+ H- IBH- &I+H- -!IIIIc           
     ~  K   | j                   r|sy 	 t        j                         j                  t        j
                        j                  d      j                  |D cg c]  }d|f c}      j                  t        j                  h      j                         }| j                  | j                   j                  |       d {   }t        t        |dd       dd       }|sy t        j                   |      }|j#                  d      dk7  ry |j#                  d      xs i j#                  d      xs i }|j%                         D ci c]4  \  }}|r-|t'        |j#                  d	      xs d
      j)                         6 c}}S c c}w 7 c c}}w # t*        $ r t,        j/                  d|d       Y y w xY ww)Nz"/open-apis/bot/v3/bots/basic_batchbot_idsr  rw   r@  r   r3  botsr   r   z)[Feishu] Failed to fetch bot names for %sTrc  )r  r.   r9  http_methodr)   GETuriqueriestoken_typesr(   TENANTrM  r_  r~   r  rn  r  r(  r  r   r;  ri  rj  rk  )	r   r  r  reqresprw   r  r  rt  s	            r   r  zFeishuAdapter._fetch_bot_namesG  sz    ||7	##%Z^^,9:g>s9c*>?o4456  ++DLL,@,@#FFDgdE48)TJGjj)G{{6"a'KK'-2226:@bD "&C S&)/R06688  ? G
  	LLDgX\L]	sr   F=AF  F	+AF FF &F=')F F=;F 9FF F=	F F !F:7F=9F::F=c                  K   | j                   r|sy || j                  v r*| j                  j                  |       | j                  |   S 	 | j                  |      }| j	                  | j                   j
                  j                  j                  j                  |       d {   }|r t        |dd              du r3t        |dd      }t        |dd      }t        j                  d|||       y t        t        |d	d       d
d       xs g }|r|d   nd }t        |dd       }t        |dd      xs d}	t        |dd      xs d}
|rt        |dd       nd }| j                  |	|
|      }|| j                  |<   t        | j                        t        kD  r9| j                  j                  d       t        | j                        t        kD  r9|S 7 .# t         $ r t        j                  d|d       Y y w xY ww)Nr  c                      yr'  r   r   r   r   rY  z3FeishuAdapter._fetch_message_text.<locals>.<lambda>m  r=  r   Fr@  rN  r  zmessage lookup failedz3[Feishu] Failed to fetch parent message %s: [%s] %sr3  r  r   r   r   r   rw   r   )r   r  r   r  z*[Feishu] Failed to fetch parent message %sTrc  )r  r  r  r  r_  r  r  r\   r(  r  rj  r  _extract_text_from_raw_contentr|   _FEISHU_MESSAGE_TEXT_CACHE_SIZEr  ri  )r   r  r~   r  r@  r  r  parentr   r   r  parent_mentionsr   s                r   r  z!FeishuAdapter._fetch_message_textd  s    ||:111$$00<++J77	55jAG!//0B0B0J0J0N0NPWXXHJwxMJLPUUx;h/FGTV`bfhklGHfd;WdKQrE!&U1XDF6640Dvz26<"H!$	26<"KCIgfj$?tO66!'( 7 D
 48D$$Z0d../2QQ((00e0< d../2QQK) Y*  	NNG^bNc	sQ   AG;AG #G$AG 1G;2CG G G;G !G85G;7G88G;)r   c                  t        |||| j                               }|j                  r|j                  S t        |j                  t
              r|j                  j                  d      nd }t        |      j                         xs d S )Nr  r  )	r  r  r   rG  r   r   r(  r   r;  )r   r   r  r   r  r   s         r   r  z,FeishuAdapter._extract_text_from_raw_content  s~     .!#""$	

 ""***EOPZPcPceiEjj))--.@Apt;%%'/4/r   c                d    | xs dj                         }|dv ryd|j                  d      xs d S )Nr   >   rJ   rL   z
image/jpegr  rk  jpeg)r  rR  )rE  normalized_exts     r   r  z'FeishuAdapter._default_image_media_type  s>    )**,..--c2<f=>>r   c                n    	 | j                          y # t        $ r t        j                  d       Y y w xY w)Nz-[Feishu] Background inbound processing failed)rL  ri  rj  	exception)r  s    r   r  z%FeishuAdapter._log_background_failure  s1    	NMMO 	NLM	Ns    44c                   t        |      }t        d | j                  | j                  fD              }t	        |      }t        |dd      dk7  }t        |dd      xs d}|xr | j                  |      }|r||z  ry|r5| j                  }	|	dk7  r|	dk7  ry	|r|sy
|	dk(  r|s| j                  |      sy|st        j                  dd      j                         j                         dv ry t        j                  dd      j                         j                         dv ry | j                  sy |r|| j                  z  syy | j                  t        |dd       ||      sy|r| j                  |      syy )Nc              3  &   K   | ]	  }|s|  y wr   r   r  s     r   r  z'FeishuAdapter._admit.<locals>.<genexpr>  s     T1RSQTr  r>  rc  r   r   r
  r   r  r  r  r  FEISHU_ALLOW_ALL_USERS>   1r  r$  GATEWAY_ALLOW_ALL_USERSdm_policy_rejectedr  r  r  )r  r   r  r  r  r  _require_mention_forr*  _mentions_selfr  r  r;  r  r  r  )
r   r  r\   
sender_idsself_idsrd  is_groupr   r   modes
             r   rf  zFeishuAdapter._admit  s~   %f-
T):):D<M<M(NTT'7K75@'9b17R"It'@'@'I 
X-##Dz!dem&:) z!/$BUBUV]B^*yy126<<>DDFJ^^yy2B7==?EEGK__ ,,J1J1J$J+((FK. ) 
 +4#6#6w#?*r   c                    |r| j                   j                  |      nd }|r|j                  |j                  S | j                  S r   )r  r(  r   r+  )r   r   rules      r   r'  z"FeishuAdapter._require_mention_for  sB    18t  $$W-dD((4'''$$$r   c               .   t        |dd      }t        |dd      }||hdhz
  }|r| j                  r|| j                  z  ry|r| j                  j                  |      nd}|r%|j                  }|j
                  }	|j                  }
n0| j                  xs | j                  }| j                  }	t               }
|dk(  ry|dk(  ry|dk(  ry|ry|d	k(  rt        |xr ||	z        S |d
k(  rt        |xr ||
z         S t        |xr || j                  z        S )z)Per-group policy gate for non-DM traffic.r   Nr   TdisabledFr  
admin_onlyr   r  )r  r  r  r(  r   r   r  r  r  r  r  r   )r   r  r   rd  sender_open_idsender_user_idr)  r.  r   r   r  s              r   r  z"FeishuAdapter._allow_group_message  s)    !It< It<$n5>
$,,J,E18t  $$W-d[[FII//E43E3EF11II ZV\![ 
?
Y(>@@[ 
CJ,B'CDDJKJ1J1J$JLLr   c           	        t        |dd      xs d}d|v ryt        |dd       xs g }|r| j                  |      ryt        t        |dd      xs d|t        |dd       | j                               }| j	                  |j
                        S )Nrw   r   r  Tr   r   r  )r  _message_mentions_botr  r  _post_mentions_botr   )r   r\   r  r   r  s        r   r(  zFeishuAdapter._mentions_self  s    gy"5;k!7J5;228<- ."=C#Wj$7""$	

 &&z':':;;r   c                   |D ]  }t        |dd       }t        |dd       xs dj                         }t        |dd       xs dj                         }t        |dd       xs dj                         }|r| j                  r|| j                  k(  r y|r| j                  r|| j                  k(  r y| j                  s|| j                  k(  s y y)NrD  r   r   r   r   TF)r  r;  r  r  r   )r   r   rG  rH  mention_open_idmention_user_idmention_names          r   r5  z#FeishuAdapter._message_mentions_bot  s       	G $5J&z9dCIrPPRO&z9dCIrPPRO#GVT:@bGGIL4#4#4"d&7&774#4#4"d&7&77~~,$.."@	" r   c                &    t        d |D              S )Nc              3  4   K   | ]  }|j                     y wr   )r   )r  rX  s     r   r  z3FeishuAdapter._post_mentions_bot.<locals>.<genexpr>5  s     /199/s   )any)r   r   s     r   r6  z FeishuAdapter._post_mentions_bot4  s    /h///r   c                Z    t        | j                  | j                  | j                        S )NrK  )r   r  r  r   rN  s    r   r  zFeishuAdapter._bot_identity7  s(    !%%%%
 	
r   c                  K   | j                   sy	 t        j                         j                  t        j
                        j                  d      j                  t        j                  h      j                         }| j                  | j                   j                  |       d{   }t        t        |dd      dd      }|rt        j                  |      }t!        |      xs i }|j#                  d      xs dj%                         }|j#                  d      xs dj%                         }|r7| j&                  r$| j&                  |k7  rt(        j+                  d       || _        |r7| j,                  r$| j,                  |k7  rt(        j/                  d	       || _        | j,                  ry	 | j5                  | j6                  d      }| j                  | j                   j8                  j:                  j8                  j"                  |       d{   }	|	r|	j=                         s(t        |	dd      }
|
dk(  rt(        j+                  d       yt        t        |	dd      dd      }t        |dd      xs dj%                         }|r| j,                  s|| _        yyy7 # t0        $ r t(        j3                  d
d       Y &w xY w7 # t0        $ r t(        j3                  dd       Y yw xY ww)aP  Best-effort discovery of bot identity for precise group mention gating
        and self-sent bot event filtering.

        Populates ``_bot_open_id`` and ``_bot_name`` from /open-apis/bot/v3/info
        (no extra scopes required beyond the tenant access token). The probe
        always runs when a client is available so stale env vars from app/bot
        migrations do not break group @mention gating. Falls back to the
        application info endpoint for ``_bot_name`` only when the first probe
        doesn't return it. If the probe fails, env-provided values are preserved.
        N/open-apis/bot/v3/infor  rw   r   r   r   z[[Feishu] FEISHU_BOT_OPEN_ID is stale; using /bot/v3/info open_id for group @mention gating.zf[Feishu] FEISHU_BOT_NAME differs from /bot/v3/info; using hydrated bot name for group @mention gating.z3[Feishu] /bot/v3/info probe failed during hydrationTrc  r   r   rM  r@  ixz[Feishu] Unable to hydrate bot name from application info. Grant admin:app.info:readonly or application:application:self_manage so group @mention gating can resolve the bot name precisely.r3  r  app_namez9[Feishu] Failed to hydrate bot name from application info)r  r.   r9  r  r)   r  r  r  r(   r  rM  r_  r~   r  rn  r  _parse_bot_responser(  r;  r  rj  r  r   rt  ri  rk  _build_get_application_requestr  applicationv6r  )r   r  r  rw   r  rd  r   r   r~   r  r@  r  rB  s                r   _hydrate_bot_identityz#FeishuAdapter._hydrate_bot_identity>  s     ||
	##%Z^^,-.o4456  ++DLL,@,@#FFDgdE48)TJG**W-,W5;!::m4:AAC"JJz28b??A((T->->'-Iy )0D%~~$..H*D E &.DN >>	e99T[9\G!//0H0H0K0K0W0W0[0[]deeH8#3#3#5x68#NNW
 '(FD95$GCZ6<"CCEH!) !/xY G&  	LLE  	 f  	eLLT_cLd	es   K3BJ$ &J!'C:J$ !K3/A$K K=K K3AK K3!J$ $ KK3KK3K  K0-K3/K00K3c                ,   	 t        j                  | j                  j                  d            }t        |t              r|j                  di       ni }t        j                         }t        }t        |t              rB|D ci c]6  }t        |      j!                         st        |      j!                         d8 }}n\t        |t              rKi }|j#                         D ]5  \  }}t        |t              r|j!                         s'	 t%        |      ||<   7 ny |j#                         D 	
ci c]  \  }	}
|
dk(  s|dk  s||
z
  |k  r|	|
 c}
}	t+        fd	d
      d | j,                   }t        t/        |            | _        |D ci c]  }||   
 c}| _        y # t        $ r Y y t
        t         j                  f$ r% t        j                  d| j                  d       Y y w xY wc c}w # t&        t(        f$ r Y #w xY wc c}
}	w c c}w )Nrp  r  z5[Feishu] Failed to load persisted dedup state from %sTrc  message_idsrY  r   c                    |    S r   r   )r  valids    r   rY  z6FeishuAdapter._load_seen_message_ids.<locals>.<lambda>  s    q r   )r*  reverse)rn  r  r  r  FileNotFoundErrorr  r  rj  r  rG  r   r(  rZ  _FEISHU_DEDUP_TTL_SECONDSr   r   r;  r  r   r`  r}   sortedr!  reversedr  r  )r   r  	seen_datar  ttlr  r  r*  r   msg_idr  
sorted_idsr  rK  s                @r   r  z$FeishuAdapter._load_seen_message_ids  s   	jj!7!7!A!A7!A!STG 7A$6OGKKr2UW	iik'i&LU(kDY\]aYbYhYhYjT):C)?(kG(k	4(G'oo/ 
U!#s+399;#(<GCL	  *1#
%62SyC1Hb3 BJ#

 E'94HI`$J`J`a
#'(<#= 7A!B!!U1X+!B? ! 	--. 	NNRTXTjTjuyNz	 )l ":. #
 "CsA   /F% G0#G0G51HH%	G-0:G-,G-5HHc                   	 | j                   j                  j                  dd       | j                  | j                   d  }d|D ci c]!  }|| j
                  v s|| j
                  |   # c}i}t        | j                   |d        y c c}w # t        $ r% t        j                  d| j                   d       Y y w xY w)NT)parentsexist_okrI  )indentz,[Feishu] Failed to persist dedup state to %src  )
r  r  mkdirr  r!  r  rE   r  rj  r  )r   recentr  r  s       r   r  z'FeishuAdapter._persist_seen_message_ids  s    	r""))//t/L--t/E/E.E.FGF$V&sWX\`\r\rWrq$*@*@*C'C&stGd44gdK 't 	rNNI4KaKalpNq	rs*   AB BB.B B +B>=B>c                P   t        j                          }t        }| j                  5  | j                  j	                  |      }||dk  s||z
  |k  r
	 d d d        y|| j                  |<   | j
                  j                  |       t        | j
                        | j                  kD  rZ| j
                  j                  d      }| j                  j                  |d        t        | j
                        | j                  kD  rZ| j                          	 d d d        y# 1 sw Y   y xY w)Nr   TF)rZ  rN  r  r  r(  r  rw  r|   r!  rT  r  )r   r  r  rR  seen_atstales         r   re  zFeishuAdapter._is_duplicate  s   iik' 	,,00<G"qC'MC4G	 	
 25D"":.$$++J7d../$2H2HH0044Q7&&**5$7 d../$2H2HH **,	 	 	s   ,DB%DDD%r  c                   |st         j                  |      rdt        |      fS d|i}dt        j                  |d      fS )Nr  r   Frk  )r  r  rq  rn  ro  )r   rw   r  text_payloads       r   r  z%FeishuAdapter._build_outbound_payload  sF     +227;7@@@(tzz,UCCCr   r   )r  r   r  c                 K   | j                   st        dd      S t        j                  j	                  |      st        dd|       S |xs t        j                  j                  |      }| j                  ||      \  }	}
	 t        |d      5 }| j                  |	||      }| j                  |      }| j                  | j                   j                  j                  j                  j                  |       d {   }d d d        | j                  d      }|s| j!                  |d	d
      S |r6d||d}| j#                  |d| j%                  ||      ||       d {   }n5| j#                  ||
t'        j(                  d|id      ||       d {   }| j+                  |d      S 7 # 1 sw Y   xY w7 Z7 &# t,        $ r9}t.        j1                  d||d       t        dt3        |            cY d }~S d }~ww xY ww)NFr  r  zFile not found: )r   requested_message_typer  	file_typer   r   r   zfile upload failedz#Feishu file upload missing file_keyr  r  )r   r   r   r  r  r  rk  zfile send failedz#[Feishu] Failed to send file %s: %sTrc  )r  r<   r  r  r  r  _resolve_outbound_file_routingr  _build_file_upload_body_build_file_upload_requestr_  r  r  r   r   r!  r"  r  r#  rn  ro  r  ri  rj  ro  r   )r   r   r   r  r   r  r   r  r  upload_file_typeresolved_message_typer  r   r~   r)  r   r  r+  r   s                      r   r  z)FeishuAdapter._send_uploaded_file_message  s0     ||e?CCww~~i(e5Ei[3QRR ?BGG$4$4Y$?262U2U"#8 3V 3
//)	=i& d(33.*! 4 
 99$?(,(:(:4<<??;M;M;R;R;Y;Y[b(c"cd 33OZPH22#$8#H 3   " (!-	
 *.)E)E## ::7V_:`%% *F * $  *.)E)E#2 JJ
H'=ER%% *F * $  --.>@RSS= #dd d,$$  	=LL>	3Y]L^e3s8<<	=s   BH	G A-F4>F2?F4/G 2H	32G %G &5G GG 1H	2F44F=9G G 	H.H;H<H	HH	c          	       K   |}|s$|r"|j                  d      r|j                  d      }t        |xs i j                  d            }|r| j                  |||t        t	        j
                                     }| j                  ||      }	| j                  | j                  j                  j                  j                  j                  |	       d {   S |xs i j                  d      }
|
rC| j                  |
||t        t	        j
                                     }| j                  d|      }	n|}d}|j                  d      r|j!                  dd      d   }d	}n|j                  d
      rd}| j                  |||t        t	        j
                                     }| j                  ||      }	| j                  | j                  j                  j                  j                  j"                  |	       d {   S 7 -7 w)Nr  r  rw   r   reply_in_thread
uuid_value
receive_idr   rw   rl  r   zfeishu_user_id:r  rv   r   r  r   )r(  r   _build_reply_message_bodyr   r  r  _build_reply_message_requestr_  r  r  r  r\   reply_build_create_message_body_build_create_message_requestr2  r<  r   )r   r   r   r  r  r   effective_reply_tork  r   r~   
_thread_idrn  receive_id_types                r   _send_raw_messagezFeishuAdapter._send_raw_message!  s     &!h8<<3L!).C!DB33K@A11! /tzz|,	 2 D 778JDQG++DLLOO,>,>,F,F,L,LgVVV
 n"))+6
22%!tzz|,	 3 D 88dKG J'O!!"34$]]3215
"+##E*"+22%!tzz|,	 3 D 88$OG''(:(:(B(B(I(I7SSS= W< Ts%   CHHD'H<H=HHc                B    t        | xr  t        | dd                    S )Nr  c                      yr'  r   r   r   r   rY  z3FeishuAdapter._response_succeeded.<locals>.<lambda>X  r=  r   r  )r  s    r   r  z!FeishuAdapter._response_succeededV  s!    HN!L9m!L!NOOr   c                j    t         j                  |       sy t        | dd       }|rt        ||d       S d S )Nr3  )r  r  r  )r  
field_namer3  s      r   r!  z%FeishuAdapter._extract_response_fieldZ  s7    00:x.26wtZ.@D@r   )r  c               ~    |rt        d||      S t        |dd      }t        |d|      }t        dd| d| |      S )NF)r  ro  raw_responser@  rN  r  r  z] )r<   r  )r   r  r  r  r@  r  s         r   r"  z$FeishuAdapter._response_error_resulta  sO     e>PXYYx3h7%4&3%/@xXXr   c                    | j                  |      s| j                  ||      S t        d| j                  |d      |      S )N)r  Tr  )r  r  r}  )r  r"  r<   r!  )r   r  r  s      r   r  z#FeishuAdapter._finalize_send_resultn  sI    ''1..x.YY33HlK!
 	
r   c           	       K   t        t              D ]E  }	 | j                  dk(  r| j                          d {     y | j	                          d {     y  y 7 "7 
# t
        $ r}d| _        | j                          d | _        | j                          d {  7   |t        dz
  k\  r d|z  }t        j                  d|dz   t        ||       t        j                  |       d {  7   Y d }~d }~ww xY ww)Nr  Frv   rU  z:[Feishu] Connect attempt %d/%d failed; retrying in %ds: %s)range_FEISHU_CONNECT_ATTEMPTSr  _connect_websocket_connect_webhookri  r  r  r  r  rj  r  ry   r[  )r   attemptr   wait_secondss       r   rr  z!FeishuAdapter._connect_with_retry{  s     56 	2G2((K711333  //111	2 41 2 %668"&//1116:: G|PaK,  mmL1112ss   C<"AAAC<AAAC<AA	C9(1C4BAC4)C,*C4/C<4C99C<c                V  K   t         st        d      | j                  dk7  rt        nt        }| j                  |      | _        | j                         | _        | j                  t        d      | j                  }||j                         rt        d      | j                          d {    t        | j                  | j                  t        j                   j"                  | j                  |dg      | _        |j'                  d t(        | j$                  |       | _        y 7 yw)Nz4websockets not installed; websocket mode unavailabler   $failed to build Feishu event handlerzadapter loop is not readychannel)r   r   r  event_handlerr  extra_ua_tags)FEISHU_WEBSOCKET_AVAILABLErx  r  r+   r,   _build_lark_clientr  rO  r  r  r  rG  r  r  r  r   LogLevelINFOr  r]  r  r  )r   r  r  s      r   r  z FeishuAdapter._connect_websocket  s     )UVV"&"3"3v"=;..v6"779&EFFzz<4>>+:;;((***(<<''mm((-- %+
 ..*OO	
 	+s   B+D)-D'.A:D)c                  K   t         st        d      | j                  dk7  rt        nt        }| j                  |      | _        | j                         | _        | j                  t        d      | j                          d {    t        j                  t              }|j                  j                  | j                  | j                          t        j"                  |      | _        | j$                  j'                          d {    t        j(                  | j$                  | j*                  | j,                        | _        | j.                  j1                          d {    y 7 7 c7 	w)Nz/aiohttp not installed; webhook mode unavailabler   r  )client_max_size)FEISHU_WEBHOOK_AVAILABLErx  r  r+   r,   r  r  rO  r  rG  r   Applicationrw  routeradd_postr)  r  	AppRunnerr  setupTCPSiter'  r(  r  r  )r   r  r  s      r   r  zFeishuAdapter._connect_webhook  s    'PQQ"&"3"3v"=;..v6"779&EFF((*** oo.LM

D..0L0LM"}}S1""((*** [[)=)=t?Q?QSWSeSef  &&((( 	+ 	+(s8   BE2E,BE2
E.AE2&E0'E2.E20E2c                *   t         j                  j                         j                  | j                        j                  | j                        j                  |      j                  t         j                  j                        j                         S r   )r   r5   r9  r   r  r   r  r  r  r  r  rM  )r   r  s     r   r  z FeishuAdapter._build_lark_client  sY    KK!VDLL!Z(()VF^Yt}},,-UW	
r   c          
       K   d }|}t        t              D ]  }	 | j                  |||||       d {   }	|r| j                  |	      st	        |	dd       }
|
t
        v r||xs i j                  d      r/t        j                  d||xs i j                  d      |
       |	c S t        j                  d||
|       d }| j                  |||d |       d {   }	|	c S  |xs t        d
      7 7 # t        $ r}|}|dk(  rt        j                  t        |            r |t        dz
  k\  r d|z  }t        j                  d	|dz   t        |||       t        j                  |       d {  7   Y d }~dd }~ww xY ww)Nr  r@  r  u   [Feishu] Reply to %s failed in thread %s (code %s — message withdrawn/missing); skipping top-level fallback to avoid creating a new topicuk   [Feishu] Reply to %s failed (code %s — message withdrawn/missing); falling back to new message in chat %sr  rv   rU  zC[Feishu] Send attempt %d/%d failed for chat %s; retrying in %ds: %szFeishu send failed)r  _FEISHU_SEND_ATTEMPTSrw  r  r  _FEISHU_REPLY_FALLBACK_CODESr(  rj  r  ri  r  r  r   ry   r[  rx  )r   r   r   r  r  r   
last_erroractive_reply_tor  r  r@  r   r  s                r   r  z%FeishuAdapter._feishu_send_with_retry  s     +/
"23 6	2G52!%!7!7#%#,% "8 "  #4+C+CH+M"8VT:D;;$N//<"NN!\ /!)R 4 4[ A $ $,OE+ # +/)-)?)?$+%-$+%)%- *@ * $  M6	2n >L)=>>k:$  2 
v%*B*I*I#c(*S3a77 G|YaK)  mmL1112sj   FC4C0A-C4!F$3C4C2C4F0C42C44	E?=A1E:.E1/E:4F:E??Fc                   K   | j                   sy 	 t        t        | j                          d | _         y # t        $ r"}t        j                  d|d       Y d }~.d }~ww xY w# d | _         w xY ww)Nz'[Feishu] Failed to release app lock: %sTrc  )r  rC   rp  ri  rj  r  )r   r   s     r   ru  zFeishuAdapter._release_app_lock  sg     &&	+ 68O8OP '+D#  	ZNNDcTXNYY	Z '+D#s6   A-3 A-	AAA! AA! !	A**A-c                    dt               v r1t        j                         j                  |       j	                         S t        |       S )Nr   r   )r  r   r9  r   rM  r	   r  s    r   r?  z%FeishuAdapter._build_get_chat_request#  s:    wy(!))+33G<BBDDw//r   c                    dt               v r1t        j                         j                  |       j	                         S t        |       S )Nr    r  )r  r    r9  r  rM  r	   r  s    r   r  z(FeishuAdapter._build_get_message_request)  s:    ')+$,,.99*EKKMM*55r   c                    dt               v rOt        j                         j                  |       j	                  |      j                  |      j                         S t        | ||      S )Nr!   )r  r   r   )r  r!   r9  r  r   r   rM  r	   r  s      r   r  z-FeishuAdapter._build_message_resource_request/  sS    &')3)113J'(#m$ *xm\\r   c                    dt               v r@t        j                         j                  |       j	                  |      j                         S t        | |      S )Nr   rA  )r  r   r9  r   rM  rM  r	   rA  s     r   rD  z,FeishuAdapter._build_get_application_request;  sE    "gi/%--/d	 f488r   c                    dt               v r^t        j                         j                  |       j	                  |      j                  |      j                  |      j                         S t        | |||      S )Nr$   )rw   r   rk  r  )	r  r$   r9  rw   r   rk  r  rM  r	   rj  s       r   ro  z'FeishuAdapter._build_reply_message_bodyF  sg    $	1'//1!(# 1j! +	
 	
r   c                    dt               v r@t        j                         j                  |       j	                  |      j                         S t        | |      S )Nr#   r  )r  r#   r9  r  r  rM  r	   r  s     r   rp  z*FeishuAdapter._build_reply_message_requestX  sG     GI-#++-J'l+	 *<PPr   c                    dt               v r@t        j                         j                  |       j	                  |      j                         S t        | |      S )Nr&   r  )r  r&   r9  r   rw   rM  r	   r  s     r   r  z(FeishuAdapter._build_update_message_bodyc  sG    %2(002(#!	 'BBr   c                    dt               v r@t        j                         j                  |       j	                  |      j                         S t        | |      S )Nr%   r  )r  r%   r9  r  r  rM  r	   r  s     r   r  z+FeishuAdapter._build_update_message_requestn  sG    !WY.$,,.J'l+	 *<PPr   c                    dt               v r^t        j                         j                  |       j	                  |      j                  |      j                  |      j                         S t        | |||      S )Nr   )rn  r   rw   r  )	r  r   r9  rn  r   rw   r  rM  r	   rm  s       r   rr  z(FeishuAdapter._build_create_message_bodyy  sg    %2(002J'(#!j! !	
 	
r   c                    dt               v r@t        j                         j                  |       j	                  |      j                         S t        | |      S )Nr   rv  r  )r  r   r9  rv  r  rM  r	   r  s     r   rs  z+FeishuAdapter._build_create_message_request  sG    !WY.$,,. 1l+	 \ZZr   c                    dt               v r@t        j                         j                  |       j	                  |      j                         S t        | |      S )Nr   r  )r  r   r9  r  r  rM  r	   r  s     r   r  z&FeishuAdapter._build_image_upload_body  sF    #wy0&..0J'u	 *EBBr   c                    dt               v r1t        j                         j                  |       j	                         S t        |       S )Nr   r  )r  r   r9  r  rM  r	   r  s    r   r  z)FeishuAdapter._build_image_upload_request  s:    79,%--/<<\JPPRRL99r   c                    dt               v rOt        j                         j                  |       j	                  |      j                  |      j                         S t        | ||      S )Nr   rb  )r  r   r9  rc  r   r   rM  r	   rb  s      r   re  z%FeishuAdapter._build_file_upload_body  sR    "gi/%--/9%9%d idSSr   c                    dt               v r1t        j                         j                  |       j	                         S t        |       S )Nr   r  )r  r   r9  r  rM  r	   r  s    r   rf  z(FeishuAdapter._build_file_upload_request  s:    ')+$,,.;;LIOOQQL99r   c                    t        |      S r   )rq  rG  s     r   _build_post_payloadz!FeishuAdapter._build_post_payload  s    +G44r   c                   t        j                  | j                  |            }|j                  di       j                  dg       }|j	                  |g       t        j
                  |d      S rj  )rn  r  r  
setdefaultrw  ro  )r   r  r  r  rw   s        r   r#  z'FeishuAdapter._build_media_post_payload  sY    **T55g>?$$Wb1<<YK	{#zz'66r   c                    t        |       j                  j                         }|t        v ry|t        v ry|t
        v rt
        |   dfS |dk(  rt        dfS t        dfS )N)opusr  )mp4r  r   )r   r\  r  _FEISHU_OPUS_UPLOAD_EXTENSIONS_FEISHU_MEDIA_UPLOAD_EXTENSIONS_FEISHU_DOC_UPLOAD_TYPES_FEISHU_FILE_UPLOAD_TYPE)r   ra  rE  s      r   rd  z,FeishuAdapter._resolve_outbound_file_routing  sk     9o$$**,00"11!**+C0&88!V++V33'//r   )r  r7   )r  r   r   r   )r,  r   r   r|  )r   r
   )r   z%concurrent.futures.ThreadPoolExecutorr{  )re  r   r   r   )r  r  r   r|  )NN)
r   r   rw   r   r  Optional[str]r   Optional[Dict[str, Any]]r   r<   )
r   r   r  r   rw   r   r  r   r   r<   )zdangerous commandNTF)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   N)r   r   r  r   rb  r   r  r   r   r  r   r<   )r  r   r  r   r   r   )r  r   r  r   r   r   )r  r   r   r|  )NNN)r   r   r  r   r  r  r  r  r   r  r   r<   )NNNN)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|  )r   r   r0  r   r  r  r  r  r   r  r   r<   )r   r   r6  r   r  r  r  r  r   r  r   r<   )r   r   r   r   rw   r   r   r   )r3  r
   r   r|  )r3  r
   r   r   )r3  r"   r   r|  )r  r   r3  r
   r   r|  )r3  r
   r   r
   )r  r
   r   r   )r  r
   r  r
   r   r   )r   r   r   r   )rS  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   zasyncio.Lock)rS  r9   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  )rS  r9   r   r;   r   r|  )r  r   r  r   r   r|  )r  r   r   r|  )r   r   r  
str | Noner   r  )r3  r
   r\   r
   r  r
   r>  r   r  r   rd  r   r   r|  )rS  r9   r   r   )rS  r9   r   r   )rw  r9   r5  r9   r   r   )r*  r   r   r|  )r0  r   r   r   )rV  r   r4  r   r5  r   r   tuple[str, str])r   r   rb  r   r   r   )
rV  r   rO  r   rP  r   r4  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  r
   r   r|  )r\   r
   r   zEtuple[str, MessageType, List[str], List[str], List[FeishuMentionRef]])r  r   r  r   r   ztuple[List[str], List[str]])r  r   rb  r:   r   r:   )r  r   r  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   )
rX  r   rO  r   rb  r   r  r   r   r   )rO  r   rb  r   r   r   )rX  r   r   r   )r  r   r   r   )rD  r   r   r   )r  r   r  r   r   r   )r  r
   rd  r   r   zDict[str, Optional[str]])r  r  r   r  )r  r  rd  r   r   r  )r  r   r   zOptional[Dict[str, str]])r   r   r  r   r   Optional[Sequence[Any]]r   r  )rE  r   r   r   )r  r
   r   r|  )r  r
   r\   r
   r   zOptional[RejectReason])r   r   r   r   )r   )r  r
   r   r   rd  r   r   r   )r\   r
   r   r   )r   z	List[Any]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   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   r   r   r
   )r  r   r   r
   )r  r   r   r   r   r   r   r
   )r   r   rM  r   r   r
   )
rw   r   r   r   rk  r   rl  r   r   r
   )r  r   r  r
   r   r
   )r   r   rw   r   r   r
   )
rn  r   r   r   rw   r   rl  r   r   r
   )rv  r   r  r
   r   r
   )r  r   r  r
   r   r
   )r  r
   r   r
   )rc  r   r   r   r   r
   r   r
   )r  r   r  rq   r   r   )r   r   ra  r   r   r  )r   r   r   r  supports_code_blockssplits_long_messagesr  r  r   r  r  staticmethodr  r  rO  rZ  r_  rd  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r,  r.  r2  r8  rE  r  r=  rL  rN  rP  r;  rC  rE  rG  rI  rK  rL  r1  rA  rK  rO  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rg  r  r$  r2  r6  r%  r8  r=  r9  r1  r7  rF  rU  rf  r  r  ru  r  r  r#  r  r<  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rA  r  r  r  r  r  r  r  r  r  rf  r'  r  r(  r5  r6  r  rG  r  r  re  r  r  rw  r  r!  r"  r  rr  r  r  r  r  ru  r?  r  r  rD  ro  rp  r  r  rr  rs  r  r  re  rf  r  r#  rd  __classcell__)r  s   @r   r  r    sz   """ <&| c
 c
J9>
@0Q
*  5: 3jS-j,
#&" #'-1A=A= A=  	A=
 +A= 
A=R !=!= != 	!= != 
!=J /-1 $"H=H=%(H=7:H=H= +H= 	H=
 H= 
H=T 
 
B 9;-1!=!=$'!=25!=!= +!= 
	!=F 
 
$ 
 
 ( ( "&"&-1

 
 	

  
 +
 

. "&#'"&-1

 
 	

 !
  
 +
 

0 "&"&-1

 
 	

  
 +
 

. "&"&-1;=;= ;= 	;=
  ;= +;= 
;=z "&"&-1

 
 	

  
 +
 

@ "&"&-1 
 
  
 	 

   
 + 
 
 
D#JJ(!FM6^
:M11@:
$MR8#-J Z Z?6p7~ $[$[ $[ 	$[ $[ $[ 
$[X 'L'L 'L 	'L 'L 'L 
'LR;@z
1@n2
- a&P<&HHK!K,=K	K2C4:
B( P7 P7 	P7
 P7 P7 P7 P7 
P7d6
A 
 
.*
?	6>"%"% 	"%
 "% 
"%H F F    h;T.'Z	
 
 
$-L
 ;);; ; 
	; ;>,6"VV	NV@' ' ,	'
 
%'>   +    
	  >L L 	L
 L L 
Ld & & l l
   % % x x 7 7       	

 	

 
"
B" 	5 5 	5
 
5n:L -10 0 	0
 *0 
0$ ? ? N N-^% )M
 )M)M )M
 )M 
)MZ< .0
He\"CHr* 49DD,0D	D4 "&#'%+>= >= 	>=
  >= +>= >= !>=  #>= 
>=@3T 3T 	3T
 3T  3T +3T 
3Tj P P A A )-YY 	Y
 &Y 
Y
22
@)&
B? B? 	B?
 B?  B? +B? 
B?H+ 0 0
 6 6
 	] 	] 9 9 
 
" Q Q C C Q Q 
 
" [ [ C C : :
 	T 	T : :
57 00 !$0 
	0 0r   r  c                <    t         j                  | t         d         S Nr   )_ONBOARD_ACCOUNTS_URLSr(  r  s    r   _accounts_base_urlr    s    !%%f.DX.NOOr   c                <    t         j                  | t         d         S r  )_ONBOARD_OPEN_URLSr(  r  s    r   _onboard_open_base_urlr    s    !!&*<X*FGGr   c                   |  t          }t        |      j                  d      }t        ||ddi      }	 t	        |t
              5 }t        j                  |j                         j                  d            cddd       S # 1 sw Y   yxY w# t        $ rb}|j                         }|rJ	 t        j                  |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.
    rp  rN  z!application/x-www-form-urlencodedr3  rM  r{  N)_REGISTRATION_PATHr   r~  r   r   _ONBOARD_REQUEST_TIMEOUT_Srn  r  r  rz  r   r}   r  )base_urlr   r   r3  r  r  r   r  s           r   _post_registrationr    s     J)*
+CT?!!'*D
#D>;^*_
`C
S"<= 	;::diik009:	; 	; 	; XXZ
$zz*"3"3G"<== 4 45 $t#$sM   B 2B9	B BB B 	C:C5+#CC:C11C55C:r   c                    t        |       }t        |ddi      }|j                  d      xs g }d|vrt        d|       y)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(  rx  )r  r  resmethodss       r   _init_registrationr    s\    
 "&)H
X&'9
:Cgg./52Gg%!$
 	
 &r   c                4   t        |       }t        |ddddd      }|j                  d      }|st        d      |j                  dd	      }d
|v r|dz  }n|dz  }|||j                  dd	      |j                  d      xs d|j                  d      xs 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   r[  z&from=hermes&tp=hermesz?from=hermes&tp=hermes	user_codeinterval   	expire_inre   )r  qr_urlr  r  r  r  )r  r  r  r  r  s        r   _begin_registrationr    s    !&)H
X$&&	( C ''-(KTUUWW0"5F
f}****"WW["-GGJ',1WW[)0S r   r  c                   t        j                         |z   }|}d}d}t        j                         |k  r3t        |      }	 t        |d| dd      }	|dz  }|dk(  rt        ddd	
       n|dz  dk(  rt        ddd	
       |	j                  d      xs i }
|
j                  d      }|dk(  r|sd}d	}|	j                  d      r<|	j                  d      r+|dkD  r
t                |	d   |	d   ||
j                  d      dS |	j                  dd      }|dv r&|dkD  r
t                t        j                  d|       yt        j                  |       t        j                         |k  r3|dkD  r
t                t        j                  d|       y# t        t
        t        j                  f$ r t        j                  |       Y w xY w)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  tprv   z#  Fetching configuration results...r   Tendflush   rk  	user_infotenant_brandr   	client_idr  r   )r   r   r  r   ro  >   access_deniedexpired_tokenz [Feishu onboard] Registration %sNz)[Feishu onboard] Poll timed out after %ds)rZ  	monotonicr  r  r   r  rn  r  r[  printr(  rj  r  )r  r  r  r  deadlinecurrent_domaindomain_switched
poll_countr  r  r  r  ro  s                r   _poll_registrationr  *  s    ~~)+HNOJ
..
X
%%n5	$X *0 C 	a
?7RtL!^q #2T* GGK(.B	 }}^46!/#N"O 77;CGGO$<A~k*!/2($==3	  $66A~NN=uE 	

8] ..
X
%` A~
NN>	JW '4#7#78 	JJx 	s   F 3GGc                    t         y	 t        j                         }|j                  |        |j                  d       |j	                  d       y# t
        $ r Y yw xY w)zDTry to render a QR code in the terminal. Returns True if successful.FT)fit)invert)_qrcode_modQRCodeadd_datamakeprint_asciiri  )r   qrs     r   
_render_qrr  w  s[    !
C
D
d# s   A	A 	AAc                B    t         rt        | ||      S t        | ||      S )uc  Verify 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.

    Note: ``bot_open_id`` here is the bot's app-scoped open_id — the same ID
    that Feishu puts in @mention payloads.  It is NOT the app_id.
    )r  _probe_bot_sdk_probe_bot_http)r   r   r  s      r   	probe_botr    s%     fj&996:v66r   c                $   |dk(  rt         nt        }t        j                  j	                         j                  |       j                  |      j                  |      j                  t        j                  j                        j                         S )z9Build a lark Client for the given credentials and domain.r   )r,   r+   r   r5   r9  r   r   r  r  r  r  rM  )r   r   r  
sdk_domains       r   _build_onboard_clientr    s]     && 0mJ		J		
		4==((	)	r   c                   | j                  d      dk7  ry | j                  d      xs% | j                  di       j                  d      xs i }|j                  d      xs |j                  d      |j                  d      dS )	Nr@  r   r  r3  rB  r   r   )r   r   )r(  )r3  r  s     r   rC  rC    ss    xx1
((5/
BTXXfb155e<
BCGGJ'>377:+>wwy) r   c                   	 t        | ||      }t        j                         j                  t        j
                        j                  d      j                  t        j                  h      j                         }|j                  |      }t        t        |dd      dd      }|yt        t        j                  |            S # t         $ r }t"        j%                  d|       Y d}~yd}~ww xY w)z#Probe bot info using lark_oapi SDK.r@  r  Nrw   z%[Feishu onboard] SDK probe failed: %s)r  r.   r9  r  r)   r  r  r  r(   r  rM  r~   r  rC  rn  r  ri  rj  rk  )r   r   r  r  r  r  rw   r   s           r   r
  r
    s    &vz6B![(S)*[/0012UW 	 ~~c"'$t4iF?"4::g#677 <cBs   B$C 'C 	C.C))C.c                   t        |      }	 t        j                  | |d      j                  d      }t	        | d|ddi      }t        |t              5 }t        j                  |j                         j                  d            }ddd       j                  d	      }|syt	        | d
d| dd      }	t        |	t              5 }t        j                  |j                         j                  d            }
ddd       t        
      S # 1 sw Y   xY w# 1 sw Y    xY w# t        t        t        t        j                  f$ r }t         j#                  d|       Y d}~yd}~ww xY w)z@Fallback probe using raw HTTP (when lark_oapi is not installed).)r   r   rp  z//open-apis/auth/v3/tenant_access_token/internalrN  rj  r  r{  Ntenant_access_tokenr@  zBearer )AuthorizationrN  rL  z&[Feishu onboard] HTTP probe failed: %s)r  rn  ro  r~  r   r   r  r  r  rz  r(  rC  r   r  KeyErrorr  rj  rk  )r   r   r  r  
token_data	token_reqr  	token_resaccess_tokenbot_reqbot_resr   s               r   r  r    sX   %f-HZZ6 LMTTU\]
jGH#%78
	
 Y(BC 	@t

499;#5#5g#>?I	@ !}}%:;j./#*<.!9 2
 W&@A 	>Tjj!3!3G!<=G	> #7++#	@ 	@	> 	> gx)=)=> =sCsN   AD, 3DD, ''D, 3D D, DD,  D)%D, ,#E/E**E/initial_domaintimeout_secondsc                    	 t        | |      S # t        t        t        t        j
                  f$ r }t        j                  d|       Y d}~y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_innerrx  r   r  rn  r  rj  r  )r  r  r   s      r   qr_registerr!    sF    *!Q`aa(GT-A-AB A3G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yt        |d   |d   |d         }|r*|j                  d      |d<   |j                  d      |d<   |S 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:

  r9  zH  Tip: pip install qrcode  to display a scannable QR code here next timer  r  r  )r  r  r  r  Nr   r   r  r   r   )r  r  r  r  r  minr  r(  )r  r  r  r  rL  bot_infos         r   r   r     s    

,"DA~&/E	(O	G8_F&J6(STEfXRPQXY	G-(z"eK(/:	F  )6,+?AQRH%\\*5z (] ;}
 M "z $}Mr   >   rI   rJ   rK   rL   rM   >   rV   rW   rY   rZ   r[   >   rO   rP   rQ   rR   rS   rT   )r  media_filesforce_documentc               <  K   t         sddiS |xs g }	 t        |       }t        |dd      }|dk7  rt        nt        }|j                  |      |_        |rd|ind}	d}
|j                         r9|j                  |||	       d{   }
|
j                  sdd	|
j                   iS |D ]/  \  }}t        j                  j                  |      s	dd
| ic S t        j                  j                  |      d   j                         }|t         v r|j#                  |||	       d{   }
n|t$        v r|j'                  |||	       d{   }
nh|t(        v r|r|j+                  |||	       d{   }
nA|t,        v r|j+                  |||	       d{   }
n|j/                  |||	       d{   }
|
j                  rdd|
j                   ic S  |
ddiS dd||
j0                  dS 7 n7 7 7 7 c7 H# t2        $ r}dd	| icY d}~S d}~ww xY ww)a}  Out-of-process Feishu/Lark delivery via the adapter's send pipeline.

    Implements the standalone_sender_fn contract so deliver=feishu cron jobs
    succeed when cron runs separately from the gateway. Builds a transient
    FeishuAdapter, hydrates its lark client, and sends text + native media
    (images, video, voice, documents). Replaces the legacy _send_feishu helper.
    ro  zJFeishu dependencies not installed. Run: pip install 'hermes-agent[feishu]'r  r   r   r  Nri  zFeishu send failed: zMedia file not found: rv   zFeishu media send failed: zANo deliverable text or media remained after processing MEDIA tagsT)r  rh  r   r  )r  r  r  r+   r,   r  r  r;  r  r  ro  r  r  r  splitextr  _MIGRATION_IMAGE_EXTSr,  _MIGRATION_VIDEO_EXTSr  _MIGRATION_VOICE_EXTSr  _MIGRATION_AUDIO_EXTSr
  r  ri  )pconfigr   r\   r  r%  r&  rl  r   r  r   last_result
media_pathis_voicerE  es                  r   _standalone_sendr2  :  sP      eff#K'5(g~x@"-"7[!44V</8K+d==? 'Wg QQK&&#78I8I7J!KLL$/ 	S J77>>*-#9*!FGG''"":.q1779C++$+$;$;GZZb$;$cc--$+$6$6w
U]$6$^^--($+$6$6w
U]$6$^^--$+$6$6w
U]$6$^^$+$9$9':X`$9$aa&&#=k>O>O=P!QRR	S" `aa %00	
 	
/ R d^^^a  5/s3445s   HA0G? G2 G? %H&0G? HAG? &G5'$G? G7&G? 2G93$G? G;G? 4G=5G? G? HG?  H!G? 1H2G? 5G? 7G? 9G? ;G? =G? ?	HHHHHHc                 8   ddl m} m} ddlm} ddlm}m}m}m	}m
}m}m}	  |d        | d      }
 | d      }|
r|r |d        |d	d
      sy |dddgd      }d}d
}|dk(  r	 t               }|rd}n |d       |s |d        |d        |dd
      }|s	 |d       y |dd      }|s	 |d       y |dddgd      }|dk(  rdnd}d}	 t!        |||      }|r!|j#                  d       } |d!|xs d"        n |d#       |||d|d%}|d&   }|d'   }|j#                  d(d      }|j#                  d)      }|j#                  d       } |d|        |d|        |d*|       |rd+}n2 |d,d-d.gd      }|dk(  rd/nd+}|d/k(  r |d0        |d1        |d2        |d3|       |r |d4|         |d5g d6d      }|dk(  r# |d7d8        |d9d:        |d;        |d<       n[|dk(  r |d7d=        |d9d:        |d>       n; |d7d8       |xs d:} |d?|d
      j%                  d@d:      } |d9|        |dA        |dBdCdDgd      }|dk(  r |dEdF        |dG       n |dEdH        |dI        |dJd
      }|r |dK|        |dL|         |dM        |dN|         |dO|        |r |dP|        yy# t        $ r  |d       Y yt        $ r} |d|        Y d}~d}~ww xY w# t        $ r} |d$|        Y d}~d}~ww xY w)Qu   Interactive setup for Feishu / Lark — scan-to-create or manual creds.

    Replaces the central _setup_feishu in hermes_cli/gateway.py and the static
    _PLATFORMS["feishu"] dict. CLI helpers are lazy-imported.
    r   )get_env_valuesave_env_value)prompt_choice)r  prompt_yes_noprint_header
print_infoprint_successprint_warningprint_errorFeishu / Larkr  r  z$Feishu / Lark is already configured.zReconfigure Feishu / Lark?FNz+How would you like to set up Feishu / Lark?z<Scan QR code to create a new bot automatically (recommended)z-Enter existing App ID and App Secret manuallyzFeishu / Lark setup cancelled.zQR registration failed: Tz8QR setup did not complete. Continuing with manual input.zGGo to https://open.feishu.cn/ (or https://open.larksuite.com/ for Lark)zCCreate an app, enable the Bot capability, and copy the credentials.zApp ID)passwordu7   Skipped — Feishu / Lark won't work without an App ID.z
App Secretu;   Skipped — Feishu / Lark won't work without an App Secret.Domainzfeishu (China)zlark (International)rv   r   r   r   u   Credentials verified — bot: unnamedz:Could not verify bot connection. Credentials saved anyway.z!Credential verification skipped: )r   r   r  r   r   r   r   r  r   r+   r  zConnection modeu0   WebSocket (recommended — no public URL needed)z,Webhook (requires a reachable HTTP endpoint)rg  z/Webhook defaults: 127.0.0.1:8765/feishu/webhookzMOverride with FEISHU_WEBHOOK_HOST / FEISHU_WEBHOOK_PORT / FEISHU_WEBHOOK_PATHzPFor signature verification, set FEISHU_ENCRYPT_KEY and FEISHU_VERIFICATION_TOKENr  zBot created: z)How should direct messages be authorized?)z%Use DM pairing approval (recommended)zAllow all direct messageszOnly allow listed user IDsr#  r  r  r   zDM pairing enabled.zHUnknown users can request access; approve with `hermes pairing approve`.r$  z)Open DM access enabled for Feishu / Lark.z"Allowed user IDs (comma-separated)r.  zAllowlist saved.z"How should group chats be handled?z4Respond only when @mentioned in groups (recommended)zDisable group chatsr  r  z-Group chats enabled (bot must be @mentioned).r0  zGroup chats disabled.z/Home chat ID (optional, for cron/notifications)FEISHU_HOME_CHANNELzHome channel set to u   🪽 Feishu / Lark configured!zApp ID: zDomain: zBot: )hermes_cli.configr4  r5  hermes_cli.setupr6  hermes_cli.cli_outputr  r7  r8  r9  r:  r;  r<  r!  KeyboardInterruptri  r  r(  r<  )r4  r5  r6  r  r7  r8  r9  r:  r;  r<  existing_app_idexisting_secret
method_idxcredentialsused_qrr   r   r   
domain_idxr  r   r$  r   r   mode_idx
access_idxdefault_allowr   	group_idxhome_channels                                 r   interactive_setuprQ  x  s    @.   !#O4O#$78O?<=95A5J;	
 	
J KGQ	<%-K GQR\]XY51STL48
WX"8.>@V-WYZ[
%?	E V<H#<<
3 >x?T9>UVWZ[
 $ 
 "F\*J__Xx0Fooi(Gz*H?F+&
3?F+% B> 
 (01})+i'HIfgij+_=hZ013	

 	
J Q/9-r2+,]^	q/8-r2AB/920-%

'#r
 	 	-y9(),B!	
 	
I A~,f5BC,j9*+KV[\L,l;,\N;<23&"#&"#U8*%& y ! 	:; 	<4SE:;;	<:  	E=cUCDD	Es6   %
K 8K: K7K7!K22K7:	LLLc                    d|v rBt        j                  d      s-t        |d         j                         t         j                  d<   y)u+  Translate config.yaml feishu: keys into FEISHU_* env vars.

    Implements the apply_yaml_config_fn contract (#24849). Mirrors the legacy
    feishu_cfg block from gateway/config.py::load_gateway_config() (allow_bots).
    Env vars take precedence over YAML. Returns None — flows through env.
    r   r  N)r  r  r   r  environ)yaml_cfg
feishu_cfgs     r   _apply_yaml_configrV  !  s=     z!"))4G*H*-j.F*G*M*M*O

&'r   c                X    t        | di       xs i }t        |j                  d            S )zFeishu is connected when app_id is configured. Mirrors the legacy
    _PLATFORM_CONNECTED_CHECKERS[Platform.FEISHU] = lambda cfg: bool(app_id).r  r   )r  r   r(  )r  r  s     r   _is_connectedrX  -  s+     FGR(.BE		(#$$r   c                    t        |       S )zDFactory wrapper that constructs FeishuAdapter from a PlatformConfig.)r  )r  s    r   _build_adapterrZ  4  s      r   c                    | j                  ddt        t        t        t        ddgdt        t
        dddt        d	d
d       y)u:   Plugin entry point — called by the Hermes plugin system.r   r=  r  r  z"pip install 'hermes-agent[feishu]'r  r#  rA  r  u   🪽T)r   r   adapter_factorycheck_fnis_connectedvalidate_configrequired_envinstall_hintsetup_fnapply_yaml_config_fnallowed_users_envallow_all_envcron_deliver_env_varstandalone_sender_fnmax_message_lengthr  allow_update_commandN)register_platformrZ  r  rX  rQ  rV  r2  )ctxs    r   registerrl  9  sN    &*"%%':;9"/0.2-!#  r   )r~   r
   r   r   r   r  )r  r
   r   r   )r  r
   r   r   )r   r   r   r   )r   r
   r   r   )r)  zDict[str, Any] | Noner*  r   r   r   )r=  r   r   r   )rH  r   r   r   )Nr   )r   r
   rb  r   rc  r   r   r   )r   )r   r
   rb  r   rc  r   r   r   r  )rw   r   r   zList[List[Dict[str, str]]])r  r
   r  %Optional[Dict[str, FeishuMentionRef]]r   r   )r  r
   r   r   )r  r
   r   r   r   )
rH  r
   r   r   r   r   r  rm  r   r   )
r   r
   r   r   r   r   r  rm  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   r   r   r   )r   r   r   r   )r  r
   r   r   )r  r
   r  ztuple[str, ...]r   r   )r   r
   )r  r
   r   r   )r   r   r  rm  r   r   )r  r   r   r   )rG  r
   r   r  )r   r  r  r   r   zDict[str, FeishuMentionRef])r   Sequence[FeishuMentionRef]r   r   )r   r   r   rn  r   r   )rm  r
   rl  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   Optional[dict])r   r   r   r   )r   r   r   r   r  r   r   ro  )r   r   r   r   r  r   r   r
   )r3  r   r   ro  )r  r   r  r   r   ro  r{  )rT  r   rU  r   r   zdict | None(  r  
__future__r   ry   r  concurrent.futuresrT  r  r|  r  rn  r  rb  r  r0  r  rZ  r  r   dataclassesr   r   r   pathlibr   typesr	   typingr
   r   r   r   r   r   urllib.errorr   r   urllib.parser   urllib.requestr   r   aiohttpr   ImportErrorr  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  r0   r1   r  r3   r  r5   r  r  r  r  gateway.configr6   r7   r  r8   r9   r:   r;   r<   r=   r>   r?   r@   rA   gateway.statusrB   rC   hermes_constantsrD   utilsrE   rF   rG   	getLoggerr   rj  compiler]  r  _MARKDOWN_TABLE_REr\  r  r~  _MENTION_REr=  
IGNORECASEr  r]  r^  r_  r  r  r  r  r  r  r  r  r  r  rp  r  r  r	  r
  r  r  r  r  rN  r   rw  r  r  r  rx  r  r  r  rr   r   rt   r   _FEISHU_BOT_MSG_TRACK_SIZEr   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r+  r  r   r;  rS  rT  r8  r$  r%  r   r   r   r   r   r   r   r  RejectReasonr  r  r"  r%  r+  r7  r>  rK  rQ  r^  re  rh  rq  rm  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r  r  r  r  r  r  rI  r  rP  r\  r  r  r  r  r  r  r  r  r  qrcoder  r`  r  r  r  rC  r
  r  r!  r   r)  r*  r,  r+  r2  rQ  rV  rX  rZ  rl  )rE  mimes   00r   <module>r     s  -^ #          	 	    # (   ! ? ? , " +
&B     ;?0 J5 (t3 "$.  3   D , 7 7			8	$ BJJ LL! &  RZZ 7F BJJ9: $"**%9: %2::k2 bjj'L)%2::&UWYWdWde 
 G W M 4R4L4R4R4TUysDsU % # "('!2 "B   $   ( $' !#$   $ %( "  #  ) 
 ) ") !0 &( #!$  $ ') $$& !&1 #(/ %  	( n  %$	' ^  ! (&&)9:   ( &  *. &"%  +, 
 '(  2   + 2 * 3  ) 
 ( 'RZZ(DE $"**]3 #$TU $%:; F# , $      $   $5 5 5  $G G G $; ; ; $!! !! !!H + + + 9 9 9 @
(9:'#B,9$61	.6h ;?!! 8! 	!H,$	  ;?	M;M;M; )M; 8	M;
 	M;h ;?	 ) 8	
 	J )-02FNFN FN &	FN
 
FN FNRG*8B#"L@!&&Rd_
	4
)
( ;?
7 	* (%	 !,?&*
*(* 	*Z<'~;P|z80' z80LrPH.
> DD D 	D
 D DN 
7
*F #  	8&& & 	&n C @ J )  ;5|f'R	%!
Uo  G
C  JB  DL"&!NMKJ Vp] 	Y KsO   	
T- T= AU U'U- -	T:9T:=UUU$#U$-	U98U9