
    epj                   
   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+ n# e,$ r dZ*dZ+Y nw xY w	 ddl-Z-n# e,$ r dZ-Y nw xY wdZ.dZ/dZ0dZ1dZ2dZ3dZ4dZ5dZ6dZ7dZ8dZ9dZ:dZ;dZ<dZ=dZ>dZ?dZ@dZAdZBdZCdZDdZEdZFdZG ejH                    ZIe-duZJe*duZKddlLmMZMmNZN ddlOmPZPmQZQmRZRmSZSmTZTmUZUmVZVmWZWmXZXmYZY ddlZm[Z[m\Z\ ddl]m^Z^ ddl_m`Z`maZambZb ddlcmdZe ddlcmfZg ddZh eji        ej          Zk ejl        dejm                  Zn ejl        dejm                  Zo ejl        d          Zp ejl        d          Zq ejl        d          Zr ejl        d          Zs ejl        d          Zt ejl        deju                  Zvh dZwh d Zxh d!Zyd"  eUjz                    D             Z{d#Z|d$Z}d%d&hZ~h d'Zd(d)d)d*d*d+d+d,Zd-Zd.Zd.Zd/Zd0Zd1Zd2Zd3Zd4Zd5Zd6Zd7Zd8Zd9Zd:Zd;Zd<Zd=Zd>Zd?Zd@ZdAZdBdCdDdEdFZdGedH<   dIdJdKdLdMZdGedN<   ddUZdVZ edWdXh          ZdYZdZZd[ZdVZd\d]d^Zd_d`d^ZdaZdbZdcZddZdeZdfZdgZdhZdiZ ejl        dj          Z ejl        d          Z edk          Z edl          Z ejl        dm          ZdnZh doZ edpq           G dr ds                      Z edpq           G dt du                      Z edpq           G dv dw                      Z edpq           G dx dy                      Z edpq           G dz d{                      Z edpq           G d| d}                      Ze G d~ d                      Ze G d d                      Zed         ZddZddZddZddZddZddZddZÐddZĐddZŐddZƐdddZǐdddZȐddZɐddZddddZːddZ̐ddZ͐ddZ	 dddZ	 dddZd e            dddZѐd dZҐd!dZӐd!dZԐd"dZՐd#dZ֐d$dZאd$dZؐd%dZِd&dZڐd'dĄZېd(dńZܐd)dȄZݐd*dɄZސd+d˄Z	 dd,d̄Zd-d΄Zd.dфZd/dӄZd0dՄZd1dքZd2dڄZd3dۄZd3d܄Z G d݄ deP          Zd4dZd4dZd5dZd6d7dZd6d8dZddd9dZ	 ddlZn# e,ef$ r dZY nw xY wd:dZd;dZd<dZd=dZd;dZd;dZdd9dd>dZd>dZh dZh dZh d Zd%d&hZdddddZd?dZ d@dZd3dZd	 Zd?d
ZdS (A  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)webF)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_int)UnscopedSecretError)
get_secretc                z    	 t          | |          }n$# t          $ r t          j        |           }Y nw xY w||n|S )a  Scope-aware credential read with the default-profile startup fallback.

    Secondary profiles construct their adapters under a profile secret
    scope -- the scope is authoritative and a scoped miss returns ``default``
    (no cross-profile borrow from ``os.environ``, which may hold another
    profile's value). The DEFAULT profile's adapter constructs and sends
    *unscoped* under multiplexing, where a bare ``get_secret`` would raise
    ``UnscopedSecretError`` and crash this path; there ``os.environ`` is that
    profile's own value, so fall back to it. Same pattern as the Slack
    ``SLACK_APP_TOKEN`` read (#59739) and
    ``gateway/platforms/whatsapp_common.py::_get_wsecret``.
    )_scoped_get_secret_UnscopedSecretErrorosgetenv)namedefaultvals      F/home/thesage/.hermes/hermes-agent/plugins/platforms/feishu/adapter.py_get_scoped_secretr3      sR     w//   ioo/33w.s    44z(^\|.*\|\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.mp4rA   c                    i | ]\  }}||	S  rI   ).0extmimes      r2   
<dictcomp>rM      s    UUUysDsUUU    messagestreamr=   r@   >   rC   rD   rF   rG   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_alwaysr`   Dict[str, str]_APPROVAL_CHOICE_MAPzApproved oncezApproved for sessionzApproved permanentlyDenied)r]   r^   r_   r`   _APPROVAL_LABEL_MAPrequestr
   	max_bytesintreturnbytesc                   K   	 | j                             |dz              d{V }n## t          j        $ r}|j        }Y d}~nd}~ww xY wt          |          |k    rt          d          |S )z8Read at most ``max_bytes`` from an aiohttp request body.   Nzpayload too large)contentreadexactlyasyncioIncompleteReadErrorpartiallen
ValueError)rh   ri   bodyexcs       r2   !_read_limited_feishu_webhook_bodyrx     s      _00Q????????&   {
4yy9,---Ks   #( AAA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textro   labelvaluer/   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_idT)frozenc                  6    e Zd ZU ded<   dZded<   dZded<   dS )FeishuPostMediaRefstrr    	file_namefileresource_typeN)__name__
__module____qualname____annotations__r   r   rI   rN   r2   r   r   a  s=         MMMIMrN   r   c                  H    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<   d	S )
FeishuMentionRefr   r   r/   r   Fboolis_allis_selfN)r   r   r   r/   r   r   r   r   rI   rN   r2   r   r   h  sR         DNNNNGFGrN   r   c                  B    e Zd ZU dZded<   dZded<   dZded<   d
dZd	S )_FeishuBotIdentityr   r   r   r   r/   rk   r   c                   |r| j         r|| j         k    S |r| j        r|| j        k    S t          | j                  o
|| j        k    S N)r   r   r   r/   )selfr   r   r/   s       r2   matchesz_FeishuBotIdentity.matchesv  sZ      	+t| 	+dl** 	+t| 	+dl**DI4449#44rN   N)r   r   r   r   r/   r   rk   r   )r   r   r   r   r   r   r/   r   rI   rN   r2   r   r   p  sY         GGDNNNN5 5 5 5 5 5rN   r   c                  ^    e Zd ZU ded<    ee          Zded<    ee          Zded<   dS )	FeishuPostParseResultr   text_contentdefault_factory	List[str]
image_keysList[FeishuPostMediaRef]
media_refsN)r   r   r   r   r   listr   r   rI   rN   r2   r   r     sZ         !E$777J7777+05+F+F+FJFFFFFFrN   r   c                      e Zd ZU ded<   ded<   dZded<    ee          Zded<    ee          Zd	ed
<    ee          Z	ded<   dZ
ded<    ee          Zded<   dS )FeishuNormalizedMessager   raw_typer   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   rI   rN   r2   r   r     s         MMM"(((((!E$777J7777+05+F+F+FJFFFF',uT'B'B'BHBBBB M    $uT:::H::::::rN   r   c                  z   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+<   dS ),FeishuAdapterSettingsr   app_id
app_secretdomain_nameconnection_modeencrypt_keyverification_tokengroup_policyzfrozenset[str]allowed_group_usersbot_open_idbot_user_idbot_namerj   dedup_cache_sizefloattext_batch_delay_secondstext_batch_split_delay_secondstext_batch_max_messagestext_batch_max_charsmedia_batch_delay_secondswebhook_hostwebhook_portwebhook_pathr[   ws_reconnect_noncerZ   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   rI   rN   r2   r   r     s        KKKOOO'''' MMM####))))    $$$$     !$$$$$&*****%)O))))&Y[[F(((( """"".3eD.I.I.IKIIIIJ O      rN   r   c                  p    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
<   dS )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   rI   rN   r2   r   r     sn         VVKKK%444I4444%444I4444&*O******rN   r   c                  v    e Zd ZU  ee          Zded<    ee          Zded<    ee          Zded<   dS )	FeishuBatchStater   zDict[str, MessageEvent]eventsDict[str, asyncio.Task]taskszDict[str, int]countsN)	r   r   r   r   r   r   r   r   r   rI   rN   r2   r   r     sl         &+eD&A&A&AFAAAA%*U4%@%@%@E@@@@"U4888F888888rN   r   )	self_echoself_ids_unknownbots_disabledbot_not_mentionedgroup_policy_rejectedsenderr   c                (    t          | dd          dv S )Nsender_typer   >   appbot)getattr)r   s    r2   _is_bot_senderr    s    6="--??rN   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   | ]}||V  	d S r   rI   rJ   vs     r2   	<genexpr>z#_sender_identity.<locals>.<genexpr>  s>        
 	     rN   r   r   r   )r  r   )r   sids     r2   _sender_identityr    s    
&+t
,
,C
{{{  CD))CD))CT**
     rN   r   r   c                8    t                               d|           S )Nz\\\1)_MARKDOWN_SPECIAL_CHARS_REsub)r   s    r2   _escape_markdown_textr    s    %))'4888rN   r   c                "    | du p| dk    p| dk    S )NTrn   truerI   r   s    r2   _to_booleanr    s    D=9EQJ9%6/9rN   styleDict[str, Any] | Nonekeyc                N    | sdS t          |                     |                    S NF)r  get)r  r  s     r2   _is_style_enabledr    s'     uuyy~~&&&rN   c                    t          dgd t          j        d|           D                       }d|dz   z  }|                     d          s|                     d          rd|  dn| }| | | S )Nr   c                ,    g | ]}t          |          S rI   )rt   )rJ   runs     r2   
<listcomp>z%_wrap_inline_code.<locals>.<listcomp>   s    DDDSCDDDrN   z`+`rn    )maxrefindall
startswithendswith)r   max_runfencerv   s       r2   _wrap_inline_coder*    s    1EDDBJud,C,CDDDEFFG7Q;E//#..N$--2D2DN;t;;;;$D"T"5"""rN   languagec                z    |                                                      dd                              dd          S )N
r"  )stripreplace)r+  s    r2   _sanitize_fence_languager1    s2    >>##D#..66tSAAArN   elementr   c                   t          |                     dd          pd          }|                     d          }t          |t                    r|nd }t	          |d          rt          |          S t          |          }|sdS t	          |d          rd| d}t	          |d          rd| d}t	          |d	          rd
| d}t	          |d          rd| d}|S )Nr   r   r  codebold**italic*	underlinez<u>z</u>strikethroughz~~)r   r  
isinstancer   r  r*  r  )r2  r   r  
style_dictrendereds        r2   _render_text_elementr>  
  s   w{{62&&,"--DKK  E$UD11;tJV,, ' &&&$T**H rV,, %$$$$X.. #"x???[11 (''''_55 %$$$$OrN   c                   t          t          |                     dd          pd          p$t          |                     dd          pd                    }t          |                     dd          pd          p$t          |                     dd          pd                              dd          }|                    d          rdnd}d| d| | dS )	Nr+  r   langr   ro   
r-  ```)r1  r   r  r0  r'  )r2  r+  r4  trailing_newlines       r2   _render_code_block_elementrD     s    'GKK
B''-2..T#gkk&"6M6M6SQS2T2T H 	GKK##)r**Sc'++i2L2L2RPR.S.Sgfd 	 "]]400:rrd888T8#38888rN   c                l   ddl m} |                     dd          }t                              d |          }t          j        dd|t
          j                  }t          j        d	d
|t
          j                  }t          j        dd|          }t          j        dd|          } ||          }|S )a  Strip markdown formatting to plain text for Feishu text fallbacks.

    Delegates common markdown stripping to the shared helper and adds
    Feishu-specific patterns (blockquotes, strikethrough, underline tags,
    horizontal rules, \r\n normalisation).
    r   )strip_markdownrA  r-  c                    |                      d           d|                      d                                           dS )Nrn   z (   ))groupr/  )ms    r2   <lambda>z/_strip_markdown_to_plain_text.<locals>.<lambda>4  s7    qwwqzz,R,RQWWQZZ=M=M=O=O,R,R,R rN   z^>\s?r   )flagsz^\s*---+\s*$z---z~~([^~\n]+)~~z\1z<u>([\s\S]*?)</u>)gateway.platforms.helpersrF  r0  _MARKDOWN_LINK_REr  r$  	MULTILINE)r   rF  r   s      r2   _strip_markdown_to_plain_textrQ  +  s     988888LL&&E!!"R"RTYZZEF8Rbl;;;EF?E5EEEEF#UE22EF'66EN5!!ELrN   r0   r   	min_valuec                j    	 t          |           }n# t          t          f$ r |cY S w xY w||k    r|n|S )zACoerce value to int with optional default and minimum constraint.)rj   	TypeErrorru   r   r0   rR  parseds       r2   _coerce_intrW  =  sQ    Uz"   y((66g5s    ((c                2    t          | ||          }||n|S )Nr0   rR  )rW  rU  s       r2   _coerce_required_intrZ  F  s$    9EEEFn77&0rN   ro   c                T    t          |           }t          j        dd|iid          S Nr   ro   Fensure_ascii)_build_markdown_post_rowsjsondumps)ro   rowss     r2   _build_markdown_post_payloadrc  P  s?    $W--D:4	

    rN   List[List[Dict[str, str]]]c                   | sdddggS d| vrd| dggS g g d}d	fd}|                                  D ]}|                                }t          |rt                              |          nt
                              |                    }|r1|s
 |                                 |           | }|s
 |                                 |            |             p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   rB  Frk   Nonec                     sd S d                               } |                                 r                    d| dg           g d S )Nr-  rf  rg  )joinr/  append)segmentcurrentrb  s    r2   _flush_currentz1_build_markdown_post_rows.<locals>._flush_currentm  sW     	F))G$$==?? 	:KKw778999rN   rk   rh  )
splitlinesr/  r   _MARKDOWN_FENCE_CLOSE_REmatch_MARKDOWN_FENCE_OPEN_RErk  )ro   in_code_blockrn  raw_linestripped_lineis_fencerm  rb  s         @@r2   r_  r_  \  sj     -r**+,,Gw//011')DGM       &&(( ! ! ((>$**=999(..}==
 
  	  !   NN8$$$ --M  !   x    N5T733455rN   mentions_mappayloadry  %Optional[Dict[str, FeishuMentionRef]]c               ~   t          |           }|st          t                    S g g g }t          t	          |                    dd                                                              }|r|                    |           |                    dg           pg D ]_}t          |t                    st          d
                    fd|D                                 }|r|                    |           `t          d
                    |                                          pt                    S )N)r   r   r   ro   c              3  <   K   | ]}t          |          V  d S r   )_render_post_element)rJ   itemr   r   ry  s     r2   r  z,parse_feishu_post_payload.<locals>.<genexpr>  sE         %T:z<PP     rN   r-  )r   r   r   )_resolve_post_payloadr   FALLBACK_POST_TEXT_normalize_feishu_textr   r  r/  rk  r;  r   rj  )	rz  ry  resolvedpartsr   rowrow_textr   r   s	    `     @@r2   parse_feishu_post_payloadr    si   
 %W--H F$2DEEEEJ+-JE"3x||GR'@'@#A#A#G#G#I#IJJE U||Ir**0b 
# 
##t$$ 	)GG          
 
  	#LL""" YYu%%++--C1C   rN   c                    t          |           }|r|S t          | t                    si S |                     d          }t	          |          }|r|S t	          |           S )Npost)_to_post_payloadr;  r   r  _resolve_locale_payload)rz  directwrappedwrapped_directs       r2   r  r    sm    g&&F gt$$ 	kk&!!G,W55N "7+++rN   c                   t          |           }|r|S t          | t                    si S t          D ]*}t          |                     |                    }|r|c S +|                                 D ]}t          |          }|r|c S i S r   )r  r;  r   _PREFERRED_LOCALESr  values)rz  r  r  	candidater   s        r2   r  r    s    g&&F gt$$ 	!  $W[[%5%566	 		!!  $U++	 		IrN   r  c                    t          | t                    si S |                     d          }t          |t                    si S t	          |                     dd          pd          |dS )Nro   r   r   )r   ro   )r;  r   r  r   r   )r  ro   s     r2   r  r    sq    i&& 	mmI&&Ggt$$ 	Y]]7B//5266  rN   r   r   r   r   c                ^
   t          | t                    r| S t          | t                    sdS t          |                     dd                                                                                    }|dk    rt          |           S |dk    rt          |                     dd                                                    }t          |                     d|          pd                                          }|sdS t          |          }|r	d| d| dn|S |d	k    rt          |                     d
d                                                    }|dk    r|d|vrt          d          |d<   dS |pi                     |          }	|	|	j	        p|	j
        pd}
n7t          |                     dd                                                    pd}
dt          |
           S |dv rt          |                     dd                                                    }|r||vr|                    |           t          |                     dd                                                    p4t          |                     dd                                                    }|rd| dndS |dv rt          |                     dd                                                    }t          |                     dd                                                    pit          |                     dd                                                    p4t          |                     dd                                                    }|r+|                    t          |||dv r|nd                     |rd| dnd S |d!v rt          |                     dd                                                    p4t          |                     d"d                                                    }|rd#t          |           d#nd$S |d%k    rd&S |d'v rd(S |d)k    r]t          |                     dd          pd          p$t          |                     d*d          pd          }|rt          |          ndS |d+v rt          |           S g }d,D ]>}t          |                     |          |||          }|r|                    |           ?d-                    d. |D                       S )/Nr   r   r   ar   [z](rI  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]brr-  >   hrdividerz

---

r4  ro   >   pre
code_block)r   r   ro   childrenelementsr"  c              3     K   | ]}||V  	d S r   rI   )rJ   parts     r2   r  z'_render_post_element.<locals>.<genexpr>*  s'      ::TT:D::::::rN   )r;  r   r   r  r/  lowerr>  r  r   r/   r   rk  r   r*  rD  _render_nested_postrj  )r2  r   r   ry  r   r   r   escaped_labelr   refdisplay_namer   r  r   r   r4  nested_partsr  	extracteds                      r2   r~  r~    sa    '3 gt$$ r
gkk%$$
%
%
+
+
-
-
3
3
5
5C
f}}#G,,,
czz7;;vr**++1133GKK--344::<< 	2-e44/3F+=++D++++F
d{{ '++i4455;;=='!! 'G<,G,G(8(E(E(EW%6!r&&{33?8<s{<fLLw{{;;;<<BBDDNL8(66888
K4455;;==	 	)*44i((('++fb))**0022Yc'++eR:P:P6Q6Q6W6W6Y6Y$'6 #    Y6
111w{{:r223399;;K,,--3355 47;;w++,,224447;;vr**++1133 	
  	"%'),0B)B)B##     09L+y++++nL
"""GKK++,,2244bGKKVX<Y<Y8Z8Z8`8`8b8b6;J2(//2222J
d{{t
}
f}}7;;vr**0b11ZSYPR9S9S9YWY5Z5Z*.6 &&&B6
###)'222 LC + +'C(8(8*jR^__	 	+	***88::\::::::rN   c                   t          | t                    rt          |           S t          | t                    r#d                    fd| D                       S t          | t
                    rKt          |           }|r|S d                    fd|                                 D                       S dS )Nr"  c              3  D   K   | ]}t          |          }||V  d S r   r  rJ   r  r  r   r   ry  s      r2   r  z&_render_nested_post.<locals>.<genexpr>6  P       
 
,T:z<XX	

 
 
 
 
 
rN   c              3  D   K   | ]}t          |          }||V  d S r   r  r  s      r2   r  z&_render_nested_post.<locals>.<genexpr>@  r  rN   r   )r;  r   r  r   rj  r   r~  r  )r   r   r   ry  r  s    ``` r2   r  r  -  s    % ,$U+++% 
xx 
 
 
 
 
 

 
 
 
 
 	
 % 	
%eZ\RR 	Mxx 
 
 
 
 
 

 
 
 
 
 	
 2rN   )r   r  r   raw_contentr   Optional[Sequence[Any]]r  c           
        t          | pd                                                                          }t          |          }t	          ||          }|dk    rt          |                    dd          pd          }d|v rd|vrt          d          |d<   t          |t          ||          t          |
                                                    S |dk    rnt          ||          }t          ||j        t          |j                  t          |j                  t          |
                                          d	          S t          |
                                          }	|d
k    rt          |                    dd          pd                                          }
t          t          |                    dd          pd          p+t          |                    dd          pd          pt          |          }t          ||t          k    r|ndd|
r|
gng d
|	          S |dv rOt!          ||          }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  rx  )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   r  r   r  normalized_typerz  ry  r   parsed_postmention_refsr   alt_text	media_refr   s                 r2   normalize_feishu_messager  N  s&    ,,"--3355;;==O";//G&x55L&  7;;vr**0b11 d??wl::$4D$A$A$AL!&$/lCC,--//00
 
 
 	

 &   0lSSS&$$1K233K233,--//00 
 
 
 	
 ++--..L'!!K44:;;AACC	)FB''-2.. #7;;ub))/R00#"	
 
 '$%-1D%D%D"#*&/7	{{R!!
 
 
 	
 4441'YYY	-i.ABB&$.=.H.H77j&/&8@	{{b)(+6!
 
 
 	
 /))/888,&&,W555111-owGGG"O"MMMMrN   c                    	 | rt          j        |           ni }n# t           j        $ r d| icY S w xY wt          |t                    r|nd|iS )Nr   ro   )r`  loadsJSONDecodeErrorr;  r   )r  rV  s     r2   r  r    so    %,7?K(((R % % %$$$$%--F66Iv3FFs    11c           	        t          |                     d          |                     d          |                     d          t          | d                    }t          |           }g }|r|                    |           |                    |d d                    d                    |                                          pt          }t          d|dt          |          |d	
          S )Nr   r   preview)r   r   r  r   keysrV   r-  r  )entry_countr   r   r   r   r   )_first_non_empty_textr  _find_first_text_collect_forward_entriesrk  extendrj  r/  FALLBACK_FORWARD_TEXTr   rt   )rz  r   entrieslinesr   s        r2   r  r    s    !GII'UVVV	 E 'w//GE U	LL!99U##))++D/DL" !%!$W>>	   rN   c           	     B   t          |                     d          |                     d          |                     d          t          | d                    }t          |                     d          |                     d          |                     d                    }g }|r|                    d	|            n|                    t                     |r|                    d
|            d                    |          }t          d|d||d          S )N	chat_namer/   r   )r  r/   r   r  r   r   r   zShared chat: z	Chat ID: r-  r  )r   r  r  )r  r  r  rk  FALLBACK_SHARE_CHAT_TEXTrj  r   )rz  r  share_idr  r   s        r2   r  r    s,   %K  FG'EFFF	 I %IN##O$$ H
 E /0Y001111-... -+++,,,99U##L"!"%I>>	   rN   c                   t          |                    d          t                    r|                    d          n|}t          t	          |          |                    d          t          |d                    }t          |          }t          |          }g }|r|                    |           |D ]}||k    r|                    |           |r+|                    dd	                    |                      d	                    |d d                   
                                pt          }t          | |d	||d
          S )Nr  r   )r   r   r   r  z	Actions: , r-     r  )r   actionsr  )r;  r  r   r  _find_header_titler  _collect_card_lines_collect_action_labelsrk  rj  r/  FALLBACK_INTERACTIVE_TEXTr   )	r   rz  card_payloadr   
body_linesr  r  liner   s	            r2   r  r    sY   *4W[[5H5H$*O*O\7;;v&&&U\L!<((G,LMMM E
 %\22J$\22GE U  5==LL 757!3!35566699U3B3Z((..00M4ML"!# W55	   rN   c                   g }dD ]A}|                      |          }t          |t                    r|                    |           Bg }|D ]}t          |t                    s9t          t          |pd                    }|r|                    d|            Qt          |                     d          |                     d          |                     d          |                     d                    }t          |                     dd          p|                     d	d                    	                                
                                }|d
k    r*t          |                     d          p|          j        }	nnt          |                     d          |                     d          |                     d          |                     d          t          |d                    }	t          |	          }	|r|	r|                    d| d|	            |	r|                    d|	            t          |          S )N)messagesitemsmessage_listrecordsro   r   z- sender_namer  r   r/   r   r   r  ro   r   r   r  )r   ro   r   r  r   r  z: )r  r;  r   r  r   r  r   rk  r  r/  r  r  r   r  _unique_lines)
rz  
candidatesr  r   r  r  r   r   nested_typerv   s
             r2   r  r    s>   JJ % %C  eT"" 	%e$$$G ( ($%% 	)#djb//::D ,{D{{+++&HH]##HH[!!HHXHHV	
 
 $((>266R$((:r:R:RSSYY[[aacc&  ,TXXi-@-@-HDIIVDD(  ###### ,^___ D &d++ 	(d 	(NN000$001111 	(NN;;;'''!!!rN   c                n    t          | d          }d |D             }t          d |D                       S )NFin_rich_blockc                ,    g | ]}t          |          S rI   )r  rJ   r  s     r2   r   z'_collect_card_lines.<locals>.<listcomp>  s!    AAA4(..AAArN   c                    g | ]}||S rI   rI   r  s     r2   r   z'_collect_card_lines.<locals>.<listcomp>  s    >>>4>$>>>rN   )_collect_text_segmentsr  )rz  r  
normalizeds      r2   r  r    sD    "7%@@@EAA5AAAJ>>:>>>???rN   c           
        g }t          |           D ]}t          |t                    st          |                    dd          p|                    dd                                                                                    }|dvrzt          |                    d          |                    d          |                    d          t          |d	                    }|r|	                    |           t          |          S )
Nr   r   r   >   buttonpickeroverflowdate_pickerselect_staticr   r/   r   )r   ro   r/   r   r  )_walk_nodesr;  r   r   r  r/  r  r  r  rk  r  )rz  labelsr  r   r   s        r2   r  r    s    FG$$ ! !$%% 	$((5"%%=&")=)=>>DDFFLLNNVVV%HHVHHVHHWT(LMMM	
 
  	!MM%      rN   r  c                  t          | t                    r|rt          |           gng S t          | t                    r-g }| D ]&}|                    t          ||                     '|S t          | t                    sg S t          |                     dd          p|                     dd                                                    	                                }|p|dv }g }t          D ]T}|                     |          }t          |t                    r(|r&t          |          }|r|                    |           U|                                 D ]3\  }}|t          v r|                    t          ||                     4|S )Nr  r   r   r   >   divnoteactionr
  columnlark_mdmarkdown
column_set
plain_textr  r  )r;  r   r  r   r  r  r   r  r/  r  _SUPPORTED_CARD_TEXT_KEYSrk  r  _SKIP_TEXT_KEYS)r   r  segmentsr  r   next_in_rich_blockr  r  s           r2   r  r  0  s   % H2?G&u--..RG%   	W 	WDOO24}UUUVVVVeT"" 	
eiir"";eii&;&;
<
<
B
B
D
D
J
J
L
LC& # 2 + H( , ,yy~~dC   	,%7 	,/55J ,
+++[[]] X X	T/!!.tCUVVVWWWWOrN   r   c               8   t          |                     dd          pd                                          }t          |                     d          |                     d          |                     d                    }|dv r|nd}t	          |||          S )	Nr   r   r   r   r   >   r  r  r   r  )r   r  r/  r  r   )rz  r   r   r   effective_types        r2   r  r  Y  s    7;;z2..4"55;;==H%K  GF I
 '47I&I&I]]vNx9TbccccrN   r   c                >    t          |           }|rd| dnt          S )Nr  r  )r  FALLBACK_ATTACHMENT_TEXT)r   normalized_names     r2   r  r  d  s,    ,Y77O1@^-?----F^^rN   c                   t          | t                    sdS |                     d          }t          |t                    sdS |                    d          }t          |t                    rJt          |                    d          |                    d          |                    d                    S t	          t          |pd                    S )Nr   headerr   ro   r   r/   )r;  r   r  r  r  r   )rz  r$  r   s      r2   r  r  i  s    gt$$ r[[""Ffd## rJJwE% a$UYYy%9%9599V;L;LeiiX^N_N_```!#ekr"2"2333rN   r  tuple[str, ...]c                   t          |           D ]^}t          |t                    s|D ]C}|                    |          }t          |t                    rt          |          }|r|c c S D_dS Nr   )r  r;  r   r  r   r  )rz  r  noder  r   r  s         r2   r  r  u  s    G$$ & &$%% 	 	& 	&CHHSMME%%% &3E::
 &%%%%%%	& 2rN   c              #     K   t          | t                    r2| V  |                                 D ]}t          |          E d {V  d S t          | t                    r| D ]}t          |          E d {V  d S d S r   )r;  r   r  r  r   )r   r  s     r2   r  r    s      % )LLNN 	) 	)D"4((((((((((	) 	)	E4	 	  ) 	) 	)D"4(((((((((() )	) 	)rN   r  c                     | D ]m}t          |t                    rt          |          }|r|c S -|>t          |t          t          f          s"t          t          |                    }|r|c S ndS r'  )r;  r   r  r   r   )r  r   r  s      r2   r  r    s     " "eS!! 	"/66J "!!!!"z%$'F'F/E

;;J "!!!!2rN   c                   dfd}t                               || pd          }|                    dd          }|                    d	d
                              dd
          }d
                    d |                    d
          D                       }d
                    d |                    d
          D                       }t
                              d|          }|                                S )Nrr  're.Match[str]'rk   r   c                    |                      d          }pi                     |          }|dS |j        p|j        pd}d| S )Nr   r"  r  r  )rJ  r  r/   r   )rr  r  r  r/   ry  s       r2   _subz$_normalize_feishu_text.<locals>._sub  sS    kk!nn!r&&s++;3x03;0&4zzrN   r   r  r  rA  r-  r.  c              3  p   K   | ]1}t                               d |                                          V  2dS )r"  N)_WHITESPACE_REr  r/  r  s     r2   r  z)_normalize_feishu_text.<locals>.<genexpr>  s>      ^^$**355;;==^^^^^^rN   c              3     K   | ]}||V  	d S r   rI   r  s     r2   r  z)_normalize_feishu_text.<locals>.<genexpr>  s'      EEEEEEEEErN   r"  )rr  r,  rk   r   )_MENTION_PLACEHOLDER_REr  r0  rj  split_MULTISPACE_REr/  )r   ry  r.  cleaneds    `  r2   r  r    s          &))$
;;Googv..Goofd++33D$??Gii^^'--X\J]J]^^^^^GiiEEt)<)<EEEEEG  g..G==??rN   r  c                    t                      }g }| D ]3}|r||v r	|                    |           |                    |           4|S r   )r   addrk  )r  seenuniquer  s       r2   r  r    s[    UUDF   	tt||dMrN   mentiontuple[str, str]c                f   t          | dd           }t          |t                    rHt          t          | dd          pd                                          }|dk    r|dfS |dk    rd|fS dS |dS t          t          |dd          pd          t          t          |dd          pd          fS )Nidid_typer   r   r   r   r   )r  r;  r   r  )r:  
mention_idr>  s      r2   _extract_mention_idsrA    s     $--J*c"" ggy"55;<<BBDDir>!iz>!vvGJ	2..4"55GJ	2..4"55 rN   Dict[str, FeishuMentionRef]c           
     p   i }| pg D ]}t          t          |dd          pd          }|s%|dk    rt          d          ||<   ?t          |          \  }}t          t          |dd          pd                                          }t          |||                    |||                    ||<   |S )	Nr  r   r  Tr  r/   r   r   r/   )r/   r   r   )r   r  r   rA  r/  r   )r   r  resultr:  r  r   r   r/   s           r2   r  r    s     +-F>r 
 
''5"--344 	'>>*$777F3K/8877FB//5266<<>>&KKtKLL
 
 
s
 MrN   Sequence[FeishuMentionRef]c                   g }t                      }| D ]}|j        r
|j        |j        |j        f}||v r#|                    |           |j        r|                    d           U|j        r(|                    |j        pd d|j         d           |                    |j        pd           |rdd                    |           dndS )	Nr  unknownz
 (open_id=rI  z[Mentioned: r  r  r   )r   r   r   r   r/   r7  rk  rj  )r   r  r8  r  	signatures        r2   _build_mention_hintrJ    s    ED 0 0; 	Zch7	: 	0LL    [ 	0LLCH1	KKS[KKKLLLLLL.Y////16>-$))E**----B>rN   c                T   | s| S d |D             }|s| S |                                  }	 |D ]V}|                    |          s|t          |          d          }|r|d         t          vrA|                                 } nn[	 t          |          }|dk    r/||dz
           t          v r|dz  }|dk    r||dz
           t          v |d |         }||d          }|D ]F}|                    |          r/|d t          |                                                    |z   } nG|S )Nc                B    g | ]}|j         	d |j        p|j        pd S )r  r  )r   r/   r   )rJ   r  s     r2   r   z-_strip_edge_self_mentions.<locals>.<listcomp>
  sF       ;/CH--v//  rN   Tr   rn   )lstripr&  rt   _MENTION_BOUNDARY_CHARS_TRAILING_TERMINAL_PUNCTr'  rstrip)	r   r   
self_names	remainingnmafterirv   tails	            r2   _strip_edge_self_mentionsrW     s        J
  I
 		 		B''++ c"gghh'E q)@@@IE
	NN!ee	!a%(,DDDFA !ee	!a%(,DDD!}} 	 	B}}R    CGG8,3355<	 rN   	ws_clientadapterrh  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|                         	                                   n# t          $ r Y nw xY w
|j        _	        	t           d	           d t          j        |          D             }|D ]}|                                 |r$|                    t          j        |ddi           	 |                                 n# t          $ r Y nw xY w	 |                                 n# t          $ r Y nw xY wd_        dS # 
|j        _	        	t           d	           d t          j        |          D             }|D ]}|                                 |r$|                    t          j        |ddi           	 |                                 n# t          $ r Y nw xY w	 |                                 n# t          $ r Y nw xY wd_        w xY w)zCRun the official Lark WS client in its own thread-local event loop.r   N
_configurerk   rh  c                     	 t          d j                   t          d j                    j        t          d j                   d S d S # t          $ r  t
                              dd           Y d S w xY w)N_reconnect_nonce_reconnect_interval_ping_intervalz4[Feishu] Failed to apply websocket runtime overridesTexc_info)setattr_ws_reconnect_nonce_ws_reconnect_interval_ws_ping_interval	Exceptionloggerdebug)rY  rX  s   r2   _apply_runtime_ws_overrideszC_run_official_feishu_ws_client.<locals>._apply_runtime_ws_overrides9  s    	`I173NOOOI4g6TUUU(4	#3W5NOOOOO 54 	` 	` 	`LLOZ^L______	`s   A	A &A:9A:argsr
   kwargsc                 h    j         d|vr
j         |d<   j        d|vr
j        |d<    | i |S )Nping_intervalping_timeout)re  _ws_ping_timeout)rj  rk  rY  original_connects     r2   _connect_with_overridesz?_run_official_feishu_ws_client.<locals>._connect_with_overridesB  sY    $0_F5R5R&-&?F?##/N&4P4P%,%=F>"0000rN   confc                T    t          d           |           }              |S )NzFFeishu _configure_with_overrides called but original_configure is None)RuntimeError)rr  rE  ri  original_configures     r2   _configure_with_overrideszA_run_official_feishu_ws_client.<locals>._configure_with_overridesI  s<    %ghhh##D))##%%%rN   c                :    g | ]}|                                 |S rI   donerJ   ts     r2   r   z2_run_official_feishu_ws_client.<locals>.<listcomp>\  s%    FFFQVVXXF1FFFrN   return_exceptionsTro  )rj  r
   rk  r
   rk   r
   )rr  r
   rk   r
   )lark_oapi.ws.clientwsclientrq   new_event_loopset_event_looploop_ws_thread_loop
websocketsconnectr  rb  startrf  	all_taskscancelrun_until_completegatherstopclose)rX  rY  ws_client_moduler  rq  rv  pendingtaskri  ru  rp  s   ``      @@@r2   _run_official_feishu_ws_clientr  -  s`   222222222!##D4    "G'2: L$??` ` ` ` ` ` `1 1 1 1 1 1 1       +B'%	<)BCCC!!!'    /?#+)I|-?@@@FFg/55FFF 	 	DKKMMMM 	V##GNG$Tt$T$TUUU	IIKKKK 	 	 	D		JJLLLL 	 	 	D	"&! /?#+)I|-?@@@FFg/55FFF 	 	DKKMMMM 	V##GNG$Tt$T$TUUU	IIKKKK 	 	 	D		JJLLLL 	 	 	D	"&&&&&s    B5 4F 5
C?F CF E 
E$#E$(E= =
F
	F
A=I'H)(I')
H63I'5H66I':II'
II'II'c                 (   t           rdS t          5  t           r	 ddd           dS 	 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"} n# tF          $ r Y ddd           dS w xY wtI                      %                    i d| d|d|d|d|d|d|d|d|d|	d|
d|d|d|d|d|d|||||||||dd	           	 ddd           dS # 1 swxY w Y   dS )zDImport and bind the Feishu SDK after an explicit connection request.TNr   )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)ClientFr|   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  )	r  r  r  r  r  r  r  FeishuWSClientFEISHU_AVAILABLE)&r  _lark_import_lock	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_triggerr  r  "lark_oapi.event.dispatcher_handlerr  lark_oapi.wsr  ImportErrorglobalsupdate)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  s                            r2   _load_lark_oapir  l  sw    t	 6 6 	6 6 6 6 6 6 6 6	$$$$JJJJJJ                                CBBBBBBBGGGGGGGG888888        RQQQQQ======= 	 	 	16 6 6 6 6 6 6 6.	 			 
D
#%:
  !2
 $%:	

 !"4
 %&<
 #$8
 '(@
 n
  !2
 ()B
 '(@
 "#6
 &'>
 #$8
  '(@!
" #
$ %*&&(+F&<, $5
 
 
 	 	 	8 m6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6s6   	DAA=<D=
BDBA$DDDc                 b    t           rdS ddlm}  	  | dd           dS # t          $ r Y dS w xY w)zCEnsure Feishu dependencies are installed without importing the SDK.Tr   ensurezplatform.feishuF)prompt)r  tools.lazy_depsr  rf  r  s    r2   check_feishu_requirementsr    sc     t&&&&&& ////t   uus     
..c                  $
    e Zd ZU dZdZdZdZdZded<   dZ	dF fd
Z
edGd            ZdHdZdIdZdJdZd ZdKdZdddLdZdKdZdMd!ZdKd"ZdKd#ZdKd$Z	 	 dNdOd.Zdd/dPd2Zd3Zd4Zd5Zd6Z	 	 	 	 	 dQdRd>ZedSdB            Z	 	 	 dTdUdCZ edVdF            Z!edWdH            Z"edXdI            Z#	 	 	 dYdZdLZ$	 	 	 	 d[d\dOZ%	 	 	 dYd]dQZ&	 	 	 dYd^dSZ'd_d`dTZ(	 	 	 dYda fdVZ)	 	 	 dYdb fdXZ*dcdYZ+dddZZ,ded\Z-dfd]Z.dKd^Z/ded_Z0dgdaZ1dedbZ2dedcZ3deddZ4dedeZ5dedfZ6dedgZ7dhdiZ8didjZ9edjdl            Z:dkdnZ;dldpZ<dmdsZ=dmdtZ>d3d3dudndwZ?d3d3dudodxZ@dhdyZAdpd{ZBded|ZCdqd~ZDdrdZEdsdZFdtdZGdudZHdvdZIdwdZJdrdZKdxdZLdydZMdzdZNd_d{dZOddd|dZPdrdZQd}dZRd~dZSedd            ZTdrdZUddZVddZWddZXddZYddZZedd            Z[edd            Z\edd            Z]ddZ^ddZ_ddZ`d~dZaedd            ZbdrdZcddZdedd            ZeddZfddZgddÄZhddǄZieddʄ            Zjdd̈́ZkddτZlddфZmddՄZneddׄ            Zoeddل            Zpedd݄            Zqeddބ            Zredd߄            Zsedd            Ztedd            Zuedd            Zvedd            ZwddddZxddZyddddZzddZ{dwdZ|d%dddZ}edd            Z~edd            ZddZddZ	 dddddZddZddZddZddZdKdZdKdZdKdZdd	Zdd
ddZedd            Zd%d%ddddZddZddZedd            Zedd            Zd%dddZddZdKdZdKdZdKd Zdd"Zdd#ZdKd$Zedd%            Zedd&            Zedd'            Zedd*            Zedd-            Zedd/            Zedd0            Zedd1            Zedd3            Zedd5            Zedd8            Zedd9            Zed:d;dd>            Zedd?            Zddd@ZddCZeddE            Z xZS (  FeishuAdapterzFeishu/Lark bot adapter.T@    rj   CHAT_LOCK_MAX_SIZErW   configr   c                   t                                          |t          j                   |                     |j        pi           | _        |                     | 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          j#                    | _$        i | _%        g | _&        i | _'        tG                      | _(        d | _)        tU                      | _+        | j+        j,        | _-        | j+        j.        | _/        | j+        j0        | _1        tU                      | _2        | j2        j,        | _3        | j2        j.        | _4        i | _5        tm          j7        d          | _8        i | _9        tm          j7        d          | _:        tG                      | _;        | <                                 d S )NFzfeishu_seen_message_ids.jsonr  rn   )=super__init__r   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_orderr$   _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   r  	__class__s     r2   r  zFeishuAdapter.__init__  s*   111,,V\-?R@@T^,,,&* #,."2"2NR &+")-48DH:>
.2,0-135.0 !0!2!25S!S$>++@BBD!JL$57  35$%.^%5%5"(-%*.'ITI`IbIb9;&13#;=FQmm 15!1!3!3%)%;%B")-)?)E&*.*@*G'"2"4"4&*&=&D#*.*A*G':<!*!3!3?A!&/oa&8&8# GRmm*##%%%%%rN   r  r   rk   r   c                t   |                      di           }i }t          |t                    r|                                D ]\  }}t          |t                    sd }d|v r"t	          |                     d                    }t          t          |                     dd                                                                                    d |                     dg           D             d |                     dg           D             |	          |t          |          <   |                      d
g           }t          d |D                       }t          |                      dd                                                                                    }t          j        dd                                                                          }	|	dvrt                              d|	           d}	t          dMi dt          |                      d          pt          j        dd                                                    dt          |                      d          pt          dd                                                    dt          |                      d          pt          j        dd                                                                                    dt          |                      d          pt          j        dd                                                                                    dt          |                      d          pt          dd                                                    dt          |                      d          pt          d d                                                    d!t          j        d"d                                                                          d#t          d$ t          j        d%d                              d&          D                       d't          j        d(d                                          d)t          j        d*d                                          d+t          j        d,d                                          d-t#          d.t%          d/t&                              d0t)          d1t*                    d2t)          d3d4          d5t#          d6t%          d7t,                              d8t#          d6t%          d9t.                              d:t)          d;t0                    d<t          |                      d<          pt          j        d=t2                                                              d>t5          |                      d>          p&t          j        d?t          t6                                        d@t          |                      d@          pt          j        dAt8                                                              pt8          dBt;          |                      dB          dCdDE          dFt;          |                      dF          dGd6E          dHt=          |                      dH          d d6E          dIt=          |                      dI          d d6E          d
|d|d|dJ|	dt	          |                      dt          j        dKdL                              S )NNr   r   r   openc                    h | ]D}t          |                                          #t          |                                          ES rI   r   r/  rJ   us     r2   	<setcomp>z/FeishuAdapter._load_settings.<locals>.<setcomp>  A    ggg!X[\]X^X^XdXdXfXfgs1vv||~~gggrN   r   c                    h | ]D}t          |                                          #t          |                                          ES rI   r   r  s     r2   r  z/FeishuAdapter._load_settings.<locals>.<setcomp>  r  rN   r   )r   r   r   r   r   c              3     K   | ]F}t          |                                          #t          |                                          V  Gd S r   r   r  s     r2   r  z/FeishuAdapter._load_settings.<locals>.<genexpr>  sC      PPaQP3q66<<>>PPPPPPrN   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  f   K   | ],}|                                 |                                 V  -d S r   r/  rJ   r  s     r2   r  z/FeishuAdapter._load_settings.<locals>.<genexpr>:  sL       * *::<<*

* * * * * *rN   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   rn   %HERMES_FEISHU_TEXT_BATCH_MAX_MESSAGESr   "HERMES_FEISHU_TEXT_BATCH_MAX_CHARSr   'HERMES_FEISHU_MEDIA_BATCH_DELAY_SECONDSr   FEISHU_WEBHOOK_HOSTr   FEISHU_WEBHOOK_PORTr   FEISHU_WEBHOOK_PATHr   r[   r   rY  r   rZ   r   r   r   FEISHU_REQUIRE_MENTIONr  rI   )r  r;  r   r  r  r   r   r/  r  r   r-   r.   rg  warningr   r3   r3  r#  r'   _DEFAULT_DEDUP_CACHE_SIZEr&   !_DEFAULT_TEXT_BATCH_DELAY_SECONDS _DEFAULT_TEXT_BATCH_MAX_MESSAGES_DEFAULT_TEXT_BATCH_MAX_CHARS"_DEFAULT_MEDIA_BATCH_DELAY_SECONDS_DEFAULT_WEBHOOK_HOSTrj   _DEFAULT_WEBHOOK_PORT_DEFAULT_WEBHOOK_PATHrZ  rW  )
r  raw_group_rulesr   r   rule_cfgper_chat_require_mention
raw_adminsr   r   r   s
             r2   r  zFeishuAdapter._load_settings  s[     ))M26624ot,, 	%4%:%:%<%<  !!(D11  <@($00/:8<<HY;Z;Z/[/[,,;x||Hf==>>DDFFLLNNggx||KQS7T7Tgggggx||KQS7T7Tggg$<	- - -CLL)) YYx,,
PP:PPPPP  #599-CR#H#HIIOOQQWWYY Y2F;;AACCIIKK
888NNe    J$ >
 >
 >
uyy**Lbi.L.LMMSSUUU>
599\22a6HI\^`6a6abbhhjjj>
 EIIh//W29_h3W3WXX^^``ffhhh>
  		+,,`	:RT_0`0` eggeeggg>
 EIIm44d8JK_ac8d8deekkmmm>
  #		.//f3EFace3f3f   eggg>
 #8+FFLLNNTTVVV>
 !* * *I&<bAAGGLL* * * ! ! !>
" 	"6;;AACCC#>
$ 	"6;;AACCC%>
& Y0"55;;==='>
( !8:STT  )>
0 &/8:[& & &1>
6 ,5>, , ,7>
< %(?Aabb% % %=>
D "%<>[\\" " "E>
L '09;]' ' 'M>
R 		.))dRY7LNc-d-d egggW>
X 		.))iRY7LcRgNhNh-i-i  Y>
` EIIn--h;PRg1h1hiiooqq )(c>
f  4EII>R4S4S]_klmmmmg>
h #7uyyAX7Y7Ycfrs"t"t"t"ti>
j )3E)F)FPT`abbbbk>
l (		2C(D(Dd^_````m>
n 6o>
p "6!5q>
r $s>
t "zu>
v (		+RY7OQW-X-XYY  w>
 >	
rN   settingsrh  c                    |j         | _        |j        | _        |j        | _        |j        | _        |j        | _	        |j
        | _        |j        | _        t          |j                  | _        t          |j                  | _        |j        p|j        | _        |j        | _        |j        | _        |j        | _        |j        | _        |j        | _        |j        | _         |j!        | _"        |j#        | _$        |j%        | _&        |j'        | _(        |j)        | _*        |j+        | _,        |j-        | _.        |j/        | _0        |j1        | _2        |j3        | _4        |j5        | _6        |j7        | _8        |j9        | _:        d S 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   rc  r   rd  r   re  r   ro  r   _allow_botsr   _require_mention)r   r1  s     r2   r  zFeishuAdapter._apply_settingsn  sZ   #.$0 ( 8$0#+#> %2$'(D$E$E!8?++%-%B%[hF["$0$0$0!*!)!:)1)J&/7/V,(0(H%%-%B"*2*L'%2%2%2#+#> &.&D#!)!: ( 8#. ( 8rN   r
   c                    t           d S t                                j         j                                       j                                       j                                       fd          	                     fd          
                     j                                       j                                       j                                       j                                       j                                      d j                                      d j                                                  S )Nc                0                         d|           S )Nim.message.reaction.created_v1_on_reaction_eventdatar   s    r2   rL  z4FeishuAdapter._build_event_handler.<locals>.<lambda>      T445UW[\\ rN   c                0                         d|           S )Nim.message.reaction.deleted_v1rO  rQ  s    r2   rL  z4FeishuAdapter._build_event_handler.<locals>.<lambda>  rS  rN   drive.notice.comment_add_v1vc.bot.meeting_invited_v1)r  builderr7  r8  &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   `r2   _build_event_handlerz"FeishuAdapter._build_event_handler  s&   !)4"**!(  43D4OPP..t/EFF77\\\\  87\\\\  -,T-IJJ44T5OPP66t7UVVEEdF_``//0IJJ))-,  *)+.  UWW5	
rN   %concurrent.futures.ThreadPoolExecutorc                p   t          | dd          }|t          j                    }|| _        |5  t          | dd          rt	          d          t          | dd          }|t          |dd          r(t
          j                            dd	
          }|| _        |cddd           S # 1 swxY w Y   dS )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  rt  
concurrentfuturesThreadPoolExecutorr  r   lockexecutors      r2   _get_sdk_executorzFeishuAdapter._get_sdk_executor  s    t1488<>##D&*D# 
	 
	t4e<< `"#^___t_d;;H78[%#H#H%-@@ "': A   &."
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	s   A.B++B/2B/c                z   K   t          j                    } |j        |                                 |g|R   d{V S )z@Run a blocking Feishu SDK call on the adapter-owned thread pool.N)rq   get_running_looprun_in_executorrz  )r   funcrj  r  s       r2   _run_blockingzFeishuAdapter._run_blocking  sO      ')))T)$*@*@*B*BDP4PPPPPPPPPPrN   c                (   t          | dd          }|dS |5  d| _        t          | dd          }d| _        ddd           n# 1 swxY w Y   |dS 	 |                    dd           dS # t          $ r |                    d           Y dS w xY w)zFStop the adapter-owned SDK executor without touching the loop default.r  NTr  F)waitcancel_futures)r  )r  r  r  shutdownrT  rw  s      r2   _shutdown_sdk_executorz$FeishuAdapter._shutdown_sdk_executor  s   t1488<F 	& 	&)-D&t_d;;H!%D	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& F	*5>>>>> 	* 	* 	*5))))))	*s#    AAAA- - BBF)is_reconnectr  r   c                 K   d| _         | j        r| j        st                              d           dS | j        dvr"t                              d| j                   dS | j        dk    r*| j        s#| j        st                              d           dS t          j	        t                     d{V st                              d           dS 	 | j        | _        t          t          | j        d	| j        j        i
          \  }}|sqt!          |t"                    r|                    d          nd}d|rd| dndz   dz   }t                              d|           |                     d|d           dS t          j                    | _        |                                  d{V  |                                  t                              d| j        | j                   dS # t4          $ r_}|                                  d{V  d| }|                     d|d           t                              d|d           Y d}~dS d}~ww xY w)zConnect to Feishu/Lark.Fz3[Feishu] FEISHU_APP_ID or FEISHU_APP_SECRET not set>   webhookr  zT[Feishu] Unsupported FEISHU_CONNECTION_MODE=%s. Supported modes: websocket, webhook.r  zO[Feishu] Webhook mode requires FEISHU_VERIFICATION_TOKEN or FEISHU_ENCRYPT_KEY.Nz [Feishu] lark-oapi not installedplatformr   pidz@Another local Hermes gateway is already using this Feishu app_idz (PID z)..zI Stop the other gateway before starting a second Feishu websocket client.z[Feishu] %sfeishu_app_lock)	retryablez"[Feishu] Connected in %s mode (%s)TzFeishu startup failed: feishu_connect_errorz[Feishu] Failed to connect: %sr`  )r  r3  r4  rg  errorr6  r8  r7  rq   	to_threadr  r  r"   _FEISHU_APP_LOCK_SCOPEr  r   r;  r   r  _set_fatal_errorr|  r  _connect_with_retry_mark_connectedinfor5  rf  _release_app_lock)r   r  acquiredexisting	owner_pidrO   rw   s          r2   r  zFeishuAdapter.connect  s      &+"| 	4#3 	LLNOOO5 (@@@LLf%   5 I--t7O-SWSd-LLa   5&77777777 	LL;<<<5	&*lD#!4&'$dm&9:" " "Hh
  	3=h3M3MWHLL///SW	V1:C-	----Eab 
 ]G444%%&7E%RRRu 133DJ**,,,,,,,,,  """KK<d>SUYUfggg4 	 	 	((*********555G!!"8'T!RRRLL93LNNN55555	s!   B(G( :A,G( (
I2AIIc                  K   d| _         |                     | j                   d{V  |                     | j                   d{V  |                                  | j        }| j        |                                  |                                  d{V  |	                                st          |d          r	 t          j        |                                          }t          j        t          j        |          d           d{V  t                               d           n^# t          j        $ r t                               d           Y n3t(          $ r'}t                               d|d	
           Y d}~nd}~ww xY wI	                                s5t                               d           dfd}                    |           | j        }|	 t                               d           t          j        t          j        |          d           d{V  t                               d           n# t          j        $ r t                               d           Y n]t          j        $ r t                               d           Y n3t(          $ r'}t                               d|d	
           Y d}~nd}~ww xY wd| _        d| _        d| _        d| _        |                                  |                                  |                                  d{V  |                                  t                               d           dS )zDisconnect from Feishu/Lark.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: %sTr`  z<[Feishu] Cancelling websocket thread tasks and stopping looprk   rh  c                     d t          j                  D             } t                              dt	          |                      | D ]}|                                                     dj                   d S )Nc                :    g | ]}|                                 |S rI   rx  rz  s     r2   r   zFFeishuAdapter.disconnect.<locals>.cancel_all_tasks.<locals>.<listcomp>D  s%    VVVqQVVXXVVVVrN   z3[Feishu] Found %d pending tasks in websocket threadg?)rq   r  rg  rh  rt   r  
call_laterr  )r   r  ws_thread_loops     r2   cancel_all_tasksz2FeishuAdapter.disconnect.<locals>.cancel_all_tasksC  s}    VVG$5n$E$EVVVRTWX]T^T^___! " "DKKMMMM))#~/BCCCCCrN   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] Disconnectedro  ) _running_cancel_pending_tasksr  r  _reset_batch_buffersr  r  !_disable_websocket_auto_reconnect_stop_webhook_server	is_closedhasattrrq   run_coroutine_threadsafer  wait_forwrap_futurerg  rh  TimeoutErrorr$  rf  call_soon_threadsafer  shieldCancelledErrorr  r  r  _persist_seen_message_idsr  _mark_disconnectedr  )r   rX  futurerw   r  	ws_futurer  s         @r2   
disconnectzFeishuAdapter.disconnect  s     (()GHHHHHHHHH(()HIIIIIIIII!!### O	-..000''))))))))) !*",,.. +	=11 + 9))++^  &w':6'B'BCPPPPPPPPPPLMMMM'   ;    
    G!          %n.F.F.H.H%LLWXXXD D D D D D //0@AAAO	 	dZ[[[&w~i'@'@$OOOOOOOOOOGHHHH' c c cabbbbb) V V VTUUUUU d d dNPS^bccccccccd #
"##%%%&&((($$&&&&&&&&&!!!+,,,,,sD   A/D4 4)F	F(F

F'A"I
 
)K5(K	K(K

Kr   r   c                   K   d |                                 D             }|D ]}|                                 |rt          j        |ddi d {V  |                                 d S )Nc                >    g | ]}||                                 |S rI   rx  )rJ   r  s     r2   r   z7FeishuAdapter._cancel_pending_tasks.<locals>.<listcomp>e  s*    OOODdO499;;O4OOOrN   r|  T)r  r  rq   r  clear)r   r   r  r  s       r2   r  z#FeishuAdapter._cancel_pending_tasksd  s      OOELLNNOOO 	 	DKKMMMM 	C.'BTBBBBBBBBBrN   c                    | j                                          | j                                         | j                                         d S r   )r  r  r  r  rm  s    r2   r  z"FeishuAdapter._reset_batch_buffersl  sG    "((***'--///#))+++++rN   c                    | j         d S 	 t          | j         dd           n# t          $ r Y nw xY wd | _         d S # d | _         w xY w)N_auto_reconnectF)r  rb  rf  rm  s    r2   r  z/FeishuAdapter._disable_websocket_auto_reconnectq  sl    ?"F	#DO%6>>>> 	 	 	D	 #DOOOdDO""""s   " ; 
/; /; 	Ac                   K   | j         d S 	 | j                                          d {V  d | _         d | _        d S # d | _         d | _        w xY wr   )r  cleanupr  rm  s    r2   r  z"FeishuAdapter._stop_webhook_server{  su      'F	&&..000000000#'D !%D $(D !%D%%%%s	   < ANr   r   ro   reply_toOptional[str]r   Optional[Dict[str, Any]]r   c                  K   | j         st          dd          S |                     |          }|                     || j                  }t          t                              |                    }d}	 |D ]}	|                     |	|          \  }
}	 | 	                    ||
|||           d{V }n# t          $ r}|
dk    s't                              t          |                    s t                              d           | 	                    |d	t          j        d	t#          |	          id
          ||           d{V }Y d}~nd}~ww xY w|
dk    r|                     |          st                              t          t'          |dd          pd                    r]t                              d           | 	                    |d	t          j        d	t#          |	          id
          ||           d{V }|}|                     |d          S # t          $ rE}t                              d|d           t          dt          |                    cY d}~S d}~ww xY w)zSend a Feishu message.FNot connectedsuccessr  Nprefer_postr   r   rz  r  r   r  zI[Feishu] Invalid post payload rejected by API; falling back to plain textr   r]  msgr   zJ[Feishu] Post payload rejected by API response; falling back to plain textzsend failedz[Feishu] Send error: %sTr`  )r  r   format_messagetruncate_messageMAX_MESSAGE_LENGTHr   _MARKDOWN_HINT_REsearch_build_outbound_payload_feishu_send_with_retryrf  _POST_CONTENT_INVALID_REr   rg  r$  r`  ra  rQ  _response_succeededr  _finalize_send_resultr  )r   r   ro   r  r   	formattedchunksr  last_responsechunkr   rz  responserw   s                 r2   sendzFeishuAdapter.send  s      | 	De?CCCC''00	&&y$2IJJ ,33I>>??*	= $) $)$($@$@{ %A % %!'%)%A%A '!) '!)!) &B & &            HH ! 
 
 
6))1I1P1PQTUXQYQY1Z1Z)NN#nooo%)%A%A '!' $
F4QRW4X4X+Yhm n n n!)!) &B & &            HHHHHH	
 && 44X>> '077GHeUW<X<X<^\^8_8_`` ' NN#oppp%)%A%A '!' $
F4QRW4X4X+Yhm n n n!)!) &B & &            H !)--m]KKK 	= 	= 	=LL2C$LGGGe3s88<<<<<<<<<	=sJ   5H"  B65H" 6
E BEH" ECH" "
I1,:I,&I1,I1)finalize
message_idr  c          	       K   | j         st          dd          S |                     |          }	 |                     |          \  }}|                     ||          }|                     ||          }|                     | j         j        j        j	        j
        |           d{V }	|                     |	d          }
|
j        s|dk    rt                              |
j        pd	          rt                               d
           |                     dt%          j        dt)          |          id                    }|                     ||          }|                     | j         j        j        j	        j
        |           d{V }|                     |d          }
|
j        r||
_        |
S # t,          $ rF}t                               d||d           t          dt/          |                    cY d}~S d}~ww xY w)z0Edit a previously sent Feishu text/post message.Fr  r  r   ro   r  request_bodyNzupdate failedr  r   zP[Feishu] Invalid post update payload rejected by API; falling back to plain textr   r]  z&[Feishu] Failed to edit message %s: %sTr`  )r  r   r  r  _build_update_message_body_build_update_message_requestr  imv1rO   r  r  r  r  r  r  rg  r$  r`  ra  rQ  r  rf  r   )r   r   r  ro   r  r   rz  rv   rh   r  rE  fallback_bodyfallback_requestfallback_responserw   s                  r2   edit_messagezFeishuAdapter.edit_message  s9      | 	De?CCCC%%g..	= $ < <W E EHg22Hg2VVD88J]a8bbG!//0B0J0QSZ[[[[[[[[H///JJF> Xh&&8&8=U=\=\]c]i]omo=p=p&8qrrr $ ? ?# J0Mg0V0V'Wfklll !@ ! ! $(#E#EQ[jw#E#x#x *.*<*<T\_=O=W=^`p*q*q$q$q$q$q$q$q!334EWW~ /$.!M 	= 	= 	=LLA:s]aLbbbe3s88<<<<<<<<<	=s   FF6 6
H ;H;HHr   z**Reason:** zD

**Smart DENY:** owner override applies to this one operation only.i  dangerous commandcommandsession_keyr   allow_permanentallow_sessionsmart_deniedc	                  K   | j         st          dd          S 	 t          | j                  d*d+fd}	 |	ddd          g}
|sB|r@|
                     |	dd                     |r|
                     |	dd                     |
                     |	ddd                     ddidddddd|                     |||          dd|
d gd!}t          j        |d"          }|                     |d#|d$|%           d${V }| 	                    |d&          }|j
        r||j        pd'|d(| j        <   |S # t          $ rC}t                              d)|           t          dt!          |                    cY d$}~S d$}~ww xY w),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  r0   r   r   action_namebtn_typerk   r   c                    dd| d||ddS )Nr
  r  r   ro   )hermes_actionapproval_idr   r   r   r   rI   )r   r  r  r  s      r2   _btnz.FeishuAdapter.send_exec_approval.<locals>._btn	  s.    #$0UCC$/:;WW	  rN   u   ✅ Allow Oncera   primaryu   ✅ Sessionrb   u
   ✅ Alwaysrc   u   ❌ Denyr`   dangerwide_screen_modeTu    ⚠️ Command Approval Requiredr  ro   r   oranger   r   r  r  r  r   r  r  r$  r  r]  r  Nr  zsend_exec_approval failedr   r  r  r   z&[Feishu] send_exec_approval failed: %sr0   )r   r   r  r   r  r   rk   r   )r  r   nextr  rk  _format_exec_approvalr`  ra  r  r  r  r  r  rf  rg  r$  r   )r   r   r  r  r   r   r  r  r  r  r  r  rz  r  rE  rw   r  s                   @r2   send_exec_approvalz FeishuAdapter.send_exec_approval  s`      | 	De?CCCC6	=t566K       t,niHHIG IM IttM3DEEFFF" INN446F#G#GHHHNN44
FH==>>>-t4)KT`aa (   *#'#=#=g{T`#a#a 
  (#* 	 D$ jE:::G!99&! :        H //:UVVF~ #."("3"9r&5 5$[1
 M 	= 	= 	=NNCSIIIe3s88<<<<<<<<<	=s   D!D? ?
F	8FFFr  r0   	prompt_idc           
         |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   r   r   answerr  rk   r   c                    dd| d||ddS )Nr
  r  r  )hermes_update_prompt_actionupdate_prompt_idr  rI   )r   r  r  r  s      r2   r  z5FeishuAdapter._build_update_prompt_card.<locals>._btnB  s3     ,?? 39(1 	  rN   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   rk   r   rI   )r  r0   r  default_hintr  s     `  r2   _build_update_prompt_cardz'FeishuAdapter._build_update_prompt_card>  s    6=E222222		 		 		 		 		 		 *40%B<XX$ 
 #&/H,/H/HII#YY77XsH55  	
 
 	
rN   c                   K   | j         st          dd          S 	 t          | j                  }t	          j        |                     |||          d          }|                     |d|d|           d{V }|                     |d	          }	|	j	        r||	j
        pd
|d| j        |<   |	S # t          $ rC}
t                              d|
           t          dt          |
                    cY d}
~
S d}
~
ww xY w)z6Send an interactive update prompt with Yes/No buttons.Fr  r  )r  r0   r  r]  r  Nr  zsend_update_prompt failedr   r  z&[Feishu] send_update_prompt failed: %s)r  r   r   r  r`  ra  r  r  r  r  r  r  rf  rg  r$  r   )r   r   r  r0   r  r   r  rz  r  rE  rw   s              r2   send_update_promptz FeishuAdapter.send_update_prompt_  s`      | 	De?CCCC	=T899Ij..fgYb.cc"  G "99&! :        H //:UVVF~ #."("3"9r&8 8))4
 M 	= 	= 	=NNCSIIIe3s88<<<<<<<<<	=s   BB0 0
C=:8C82C=8C=choicer  c                    | dk    rdnd}t                               | d          }ddi| d| dd	| dk    rd
nddd| d| d| dgdS )z3Build raw card JSON for a resolved approval action.r`      ❌   ✅Resolvedr  Tr"  r  r  redgreenr  r  z **z** by r  r  )rg   r  )r  r  iconr   s       r2   _build_resolved_approval_cardz+FeishuAdapter._build_resolved_approval_card  s     &((uue#''
;;)40(,%6%6u%6%6|LL%+v%5%5EE7  &"&CC5CC	CC 
 
 	
rN   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 **r6  r  r  rI   )r  r  yesr   s       r2   "_build_resolved_update_prompt_cardz0FeishuAdapter._build_resolved_update_prompt_card  s    m&$)4014(?%%a%aZ_%a%ajvww'*5GG 
 #/M	/M/M/MNN	
 	
 		
rN   c                    t                      dz  }|                    d          }|                    | d           |                    |           d S )Nz.update_responsez.tmputf-8encoding)r$   with_suffix
write_textr0  )r  response_pathtmp_paths      r2   _write_update_prompt_responsez+FeishuAdapter._write_update_prompt_response  sY    ')),>> ,,V44FW555'''''rN   
audio_pathcaptionc                H   K   |                      |||||d           d{V S )z@Send audio to Feishu as a file attachment plus optional caption.r  r   	file_pathr  r   r'  outbound_message_typeN_send_uploaded_file_message)r   r   r&  r'  r  r   rk  s          r2   
send_voicezFeishuAdapter.send_voice  U       55 ") 6 
 
 
 
 
 
 
 
 	
rN   r*  r   c                H   K   |                      ||||||           d{V S )z*Send a document/file attachment to Feishu.)r   r*  r  r   r'  r   Nr,  )r   r   r*  r'  r   r  r   rk  s           r2   send_documentzFeishuAdapter.send_document  sU       55 6 
 
 
 
 
 
 
 
 	
rN   
video_pathc                H   K   |                      |||||d           d{V S )zSend a video file to Feishu.r  r)  Nr,  )r   r   r2  r'  r  r   rk  s          r2   
send_videozFeishuAdapter.send_video  r/  rN   
image_pathc                t  K   | j         st          dd          S t          j                            |          st          dd|           S 	 ddl}t          |d          5 }|                                }	ddd           n# 1 swxY w Y   |                    |	          }
t          j        	                    |          |
_
        |                     t          |
          }|                     |          }|                     | j         j        j        j        j        |           d{V }|                     |d	          }|s|                     |d
d          S |r;|                     |d|d          }|                     |d|||           d{V }n6|                     |dt/          j        d	|id          ||           d{V }|                     |d          S # t4          $ rF}t6                              d||d           t          dt;          |                    cY d}~S d}~ww xY w)z"Send a local image file to Feishu.Fr  r  zImage file not found: r   Nrb
image_typer  r   zimage upload failedz%Feishu image upload missing image_keydefault_messageoverride_errorr  )r   r   r'  	media_tagr  r  r  r]  zimage send failedz$[Feishu] Failed to send image %s: %sTr`  )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  r`  ra  r  rf  rg  r  r   )r   r   r5  r'  r  r   rk  _iofimage_bytes
image_filerv   rh   upload_responser   post_payloadmessage_responserw   s                     r2   send_image_filezFeishuAdapter.send_image_file  s      | 	De?CCCCw~~j)) 	Ze3XJ3X3XYYYY,	=j$'' '1ffhh' ' ' ' ' ' ' ' ' ' ' ' ' ' ' [11J g..z::JO004  1  D 66t<<G$($6$6t|7I7O7VX_$`$```````O44_kRRI 22#$9#J 3     #==#&+)DD  >     *.)E)E##(%% *F * * $ $ $ $ $ $   *.)E)E#$ JY'?eTTT%% *F * * $ $ $ $ $ $  --.>@STTT 	= 	= 	=LL?S[_L```e3s88<<<<<<<<<	=sJ   G' #B8G' BG' BCG' BG' '
H71;H2,H72H7c                
   K   dS )z2Feishu bot API does not expose a typing indicator.NrI   )r   r   r   s      r2   send_typingzFeishuAdapter.send_typing"	  s      trN   	image_urlc                D  K   	 |                      |           d{V }na# t          $ rT}t                              d||d           t	                                          |||||           d{V cY d}~S d}~ww xY w|                     |||||           d{V S )zJDownload a remote image then send it through the native Feishu image flow.Nz([Feishu] Failed to download image %s: %sTr`  )r   rV  r'  r  r   )r   r5  r'  r  r   )_download_remote_imagerf  rg  r  r  
send_imagerS  )	r   r   rV  r'  r  r   r5  rw   r  s	           r2   rY  zFeishuAdapter.send_image&	  s"     
	#::9EEEEEEEEJJ 	 	 	LLCYPS^bLccc++#!! ,              	 ))! * 
 
 
 
 
 
 
 
 	
s   ! 
A?A	A:4A?:A?animation_urlc                d  K   	 |                      |dd           d{V \  }}na# t          $ rT}t                              d||d           t	                                          |||||           d{V cY d}~S d}~ww xY w|rd	| nd
}	|                     ||||	||           d{V S )z@Feishu has no native GIF bubble; degrade to a downloadable file.r5   zanimation.gif)default_extpreferred_nameNz,[Feishu] Failed to download animation %s: %sTr`  )r   rZ  r'  r  r   z[GIF downgraded to file]
z[GIF downgraded to file])r   r*  r   r'  r  r   )_download_remote_documentrf  rg  r  r  send_animationr1  )r   r   rZ  r'  r  r   r*  r   rw   degraded_captionr  s             r2   r_  zFeishuAdapter.send_animationB	  se     	)-)G)G". *H * * $ $ $ $ $ $ Iyy
  	 	 	LLGX[fjLkkk//+!! 0              	 FMlAAAARl''$ ( 
 
 
 
 
 
 
 
 	
s   !' 
BA	B :B Bc                |  K   ||dd}| j         s|S | j                            |          }|t          |          S 	 |                     |          }|                     | j         j        j        j        j        |           d{V }|r t          |dd                       du rAt          |dd          }t          |d	d
          }t                              d|||           |S t          |dd          }t          t          |dd          pd                                                                          }	|t          t          |dd          p|          |                     |	          |	pdd}
|
| j        |<   t          |
          S # t           $ r" t                              d|d           |cY S w xY w)z5Return real chat metadata from Feishu when available.dm)r   r/   r   Nr  c                     dS r  rI   rI   rN   r2   rL  z-FeishuAdapter.get_chat_info.<locals>.<lambda>u	      E rN   Fr4  rH  r  zchat lookup failedz0[Feishu] Failed to get chat info for %s: [%s] %srR  	chat_typer   r/   )r   r/   r   r   z'[Feishu] Failed to get chat info for %sTr`  )r  r  r  r   _build_get_chat_requestr  r  r  chatr  rg  r$  r   r/  r  _map_chat_typerf  )r   r   fallbackcachedrh   r  r4  r  rR  raw_chat_typer  s              r2   get_chat_infozFeishuAdapter.get_chat_infod	  s      
 

 | 	O&**733<<	227;;G!//0B0G0KWUUUUUUUUH  JwxMMJJLLPUUUx;;h/CDDQSZ\`befff8VT22Dk2 > > D"EEKKMMSSUUM"GD&$77B7CC++M::)1T	 D .2D!'*:: 	 	 	NNDgX\N]]]OOO	s   B(F 'B'F )F;:F;c                *    |                                 S )z/Feishu text messages are plain text by default.r  r   ro   s     r2   r  zFeishuAdapter.format_message	  s    }}rN   rR  c                   | j         }|                     |          sG|                     |          }|r.t          j        | j        dd                                           dS |                     ||                     |                     dS )aP  Normalize Feishu inbound events into MessageEvent.

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

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

        Runs in a dedicated daemon thread. Polls ``_running`` and
        ``_loop_accepts_callbacks`` until events can be dispatched or the
        adapter shuts down. A single drainer handles the entire queue;
        concurrent ``_on_message_event`` calls just append.
        g      ?g      ^@        Tr  Nz;[Feishu] Dropped %d queued inbound event(s) during shutdownFr   rn   z,[Feishu] Replayed %d queued inbound event(s)zO[Feishu] Adapter loop unavailable for %.0fs; dropped %d queued inbound event(s))r  r  rt   r  r  rg  r$  r  r  rr  rv  rw  rk  r  r  timesleep)
r   poll_intervalmax_wait_secondswaitedr|  r  batch
dispatchedrequeuerz  s
             r2   ru  z+FeishuAdapter._drain_pending_inbound_events	  s     B	6>(tZ66  3 = ="%d&B"C"C4::<<<= = = = = = = = = = = = = = =  Y#   h + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6g z//55 #3 = = $ <QQQ ?4::<<<= = = = = = = = = = = = = = = ! !!7 ' '#'#? ' &' ' ' ' ' 'X + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6W'' ' ' ' ' ' ' ' ' ' ' ' ' ' ' !!"J)+G!& 2 2// $"A"A%"H"H  2 '!OJJ $NN51111 G!7 G G?FD8!<G G G G G G G G G G G G G G G! J&   # ' "7 ' '#'#? ' &' ' ' ' ' '& + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6%'' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ---3 = ="%d&B"C"C4::<<<= = = = = = = = = = = = = = = LL=(	    + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 
=)))-'}>(@ + 6 605-6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6sg  L .AL AL "A# L B  B$'B$-$L )D:L D

L D
L 	E$#L 5E

EEL $E((L +E(,AL G L  G$$L 'G$()L 	IL ,IIIL IL "I#L 5.J/#L /J33L 6J37L K33K7:K7 L M#L7+M7L;;M>L;?Mc           
     b  K   t          |dd          }t          |dd          }t          |dd          }|r|rt          |dd          st                              d           dS t          |dd          }|r|                     |          rt                              d|           dS |                     ||          }|t                              d	|           dS t          |d
d          }|                     ||t          |dd          ||t          |                     d{V  dS )zEShared inbound message handling for websocket and webhook transports.rz  NrO   r   r  zA[Feishu] Dropping malformed inbound event: missing message/senderr  z2[Feishu] Dropping duplicate/missing message_id: %sz#[Feishu] dropping inbound event: %sre  p2p)rR  rO   r  re  r  is_bot)r  rg  rh  _is_duplicate_admit_process_inbound_messager  )r   rR  rz  rO   r   r  reasonre  s           r2   rw  z(FeishuAdapter._handle_message_event_data
  sj     gt,,%D11$// 	f 	GFK,N,N 	LL\]]]FWlD99
 	T//
;; 	LLMzZZZFVW--LL>GGGFG[%88	++fk488!!&)) , 
 
 	
 	
 	
 	
 	
 	
 	
 	
 	
rN   r  c                    t          |dd          }t          |dd          }t          |dd          pd}t                              d|           dS )z7Ignore read-receipt events that Hermes does not act on.rz  NrO   r  r   z([Feishu] Ignoring message_read event: %s)r  rg  rh  )r   rR  rz  rO   r  s        r2   rZ  z$FeishuAdapter._on_message_read_event4
  sU    gt,,%D11WlD99?R
?LLLLLrN   c                    t          |dd          }t          t          |dd          pd          }t                              d|           | j                            |d           dS )z'Handle bot being added to a group chat.rz  Nr   r   z[Feishu] Bot added to chat: %sr  r   rg  r  r  r{  r   rR  rz  r   s       r2   rb  z#FeishuAdapter._on_bot_added_to_chat;
  se    gt,,geY339r::4g>>>!!'400000rN   c                    t          |dd          }t          t          |dd          pd          }t                              d|           | j                            |d           dS )z+Handle bot being removed from a group chat.rz  Nr   r   z"[Feishu] Bot removed from chat: %sr  r  s       r2   rd  z'FeishuAdapter._on_bot_removed_from_chatB
  se    gt,,geY339r::8'BBB!!'400000rN   c                :    t                               d           d S )Nz'[Feishu] User entered P2P chat with botrg  rh  r   rR  s     r2   rf  z"FeishuAdapter._on_p2p_chat_enteredI
  s    >?????rN   c                :    t                               d           d S )Nz![Feishu] Message recalled by userr  r  s     r2   rh  z"FeishuAdapter._on_message_recalledL
  s    899999rN   c                    ddl m} | j        }|                     |          st                              d           dS |                     | || j        || j                             dS )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_commentr  r  rr  rg  r$  rv  r  r>  )r   rR  r  r  s       r2   rj  z%FeishuAdapter._on_drive_comment_eventO
  s     	WVVVVVz++D11 	NN_```F&&t|THYZZZ	
 	
 	
 	
 	
rN   c                    ddl m} | j        }|                     |          st                              d           dS |                     | || |                     dS )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_inviter  r  rr  rg  r$  rv  )r   rR  r  r  s       r2   rk  z'FeishuAdapter._on_meeting_invited_eventa
  ss    ______z++D11 	NN`aaaFT#?#?d#K#KLLLLLrN   
event_typec                   t          |dd          }t          t          |dd          pd          }t          t          |dd          pd          }t          |dd          }t          t          |dd          pd          }d|v rd	nd
}t                              d||||           | j        }	|dv s+|r)|	't           t          |	dd                                 rdS |                     |	|                     ||                     dS )z>Route user reactions on bot messages as synthetic text events.rz  Nr  r   operator_typereaction_typer  createdaddedremovedz?[Feishu] Reaction %s on message %s (operator_type=%s, emoji=%s)>   r  r  r  c                     dS r  rI   rI   rN   r2   rL  z2FeishuAdapter._on_reaction_event.<locals>.<lambda>
  s    u rN   )r  r   rg  rh  r  r   rv  _handle_reaction_event)
r   r  rR  rz  r  r  reaction_type_objr  r  r  s
             r2   rP  z FeishuAdapter._on_reaction_eventk
  s3   gt,,b99?R@@
GE?B??E2FF#E?DAA!2L"EEKLL
%33M	
 	
 	
 z^++ ,|=GD+}}==??@@  FT4#>#>z4#P#PQQQQQrN   c                   | j         }|                     |          s1t                              d           t          rt	                      ndS t          |dd          }t          |dd          }t          |di           pi }t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}|r| 	                    |||          S |r| 
                    |||          S |                     ||                     |                     t          dS t	                      S )	ap  Handle card-action callback from the Feishu SDK (synchronous).

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

        For other card actions: delegates to ``_handle_card_action_event``.
        z:[Feishu] Dropping card action before adapter loop is readyNrz  r  r   r  r  )rz  action_valuer  )r  rr  rg  r$  r  r  r;  r   r  _handle_approval_card_action!_handle_update_prompt_card_actionrv  _handle_card_action_event)r   rR  r  rz  r  r  r  update_prompt_actions           r2   r`  z%FeishuAdapter._on_card_action_trigger
  sp    z++D11 	ZNNWXXX4OY.000UYYgt,,$//vw339r=GVZ=[=[e((999ae ,--8L:;;;37 	
  	h445|bf4ggg 	99) :    	T4#A#A$#G#GHHH&.4*,,,rN   r  c                Z    | duo't           t          | dd                                  S )zEReturn True when the adapter loop can accept thread-safe submissions.Nr  c                     dS r  rI   rI   rN   r2   rL  z7FeishuAdapter._loop_accepts_callbacks.<locals>.<lambda>
  s    PU rN   r   r  )r  s    r2   rr  z%FeishuAdapter._loop_accepts_callbacks
  s6     4Y-VWT;-V-V-X-X(Y(Y$YYrN   coroc                    ddl m}  |||t          dt          j                  }|dS |                    | j                   dS )zISchedule background work on the adapter loop with shared failure logging.r   )safe_schedule_threadsafez4[Feishu] Failed to schedule background callback work)rg  log_message	log_levelNFT)agent.async_utilsr  rg  loggingWARNINGadd_done_callback_log_background_failure)r   r  r  r  r  s        r2   rv  zFeishuAdapter._submit_on_loop
  sc    >>>>>>))$No	
 
 
 >5  !=>>>trN   r   c                    t          |pd                                          }|sdS t          | j                  t          | j                  z  }|sdS d|v p||v S )zBReturn whether this card-action operator may answer gated prompts.r   FTr8  )r   r/  r   r;  r:  )r   r   r  allowed_idss       r2   #_is_interactive_operator_authorizedz1FeishuAdapter._is_interactive_operator_authorized
  sm    B''--//
 	5$,''#d.G*H*HH 	4k!>Z;%>>rN   rz  r  c          
        |                     d          }|1t                              d           t          rt                      ndS | j                             |          }|s2t                              d|           t          rt                      ndS t
                               |                     d          d          }t          |dd          }t          t          |dd	          pd	          }t          |t          t          |d
d	          pd	                    }	| 	                    |	|                     dd	          d          s4t          
                    d|pd           t          rt                      ndS t          t          t          |dd          dd	          pd	          }
t          |                     dd	          pd	          }|
r<|r:|
|k    r4t          
                    d|||
           t          rt                      ndS |                     |          p|}t          |dd          }t          t          |dd	          pd	          }|                     ||                     |||||                    st          rt                      ndS t          dS t                      }t          8t                      }d|_        |                     ||          |_        ||_        |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  r`   operatorr   r   r   r   r   r   Fr  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  rg  rh  r  r  re   r  r   r	   _allow_group_messager$  _get_cached_sender_namerv  _resolve_approvalr  r   r  rR  r  )r   rz  r  r  r  stater  r  r   r  callback_chat_idexpected_chat_idr  chat_contextr   r  r  s                    r2   r  z*FeishuAdapter._handle_approval_card_action
  s   "&&}55LLMNNN4OY.000UYY$((55 	ZLLK[YYY4OY.000UYY%)),*:*:?*K*KVTT5*d33gh	266<"==#GSS\^`AaAaAgeg=h=hiii	((EIIi4L4LUZ([[ 	ZNNGI_T_```4OY.000UYYwwui'F'FXZ[[a_abbuyyB77=2>> 	Z 0 	Z5EIY5Y5YNNW  	   5PY.000UYY0099DW	ui66glNB??E2FF##""'# #  	
 	
 
	Z 5PY.000UYY&.4.00#>>DDI::&T]:^^DI HMrN   c          
     8   |                     d          }|1t                              d           t          rt                      ndS | j                             |          }|s2t                              d|           t          rt                      ndS t          |                     dd          pd                                                                          }|dvr2t                              d|           t          rt                      ndS t          |d	d          }t          t          |d
d          pd          }t          |t          t          |dd          pd                    }	| 
                    |	|                     dd          d          s4t                              d|pd           t          rt                      ndS t          t          t          |dd          dd          pd          }
t          |                     dd          pd          }|
r<|r:|
|k    r4t                              d|||
           t          rt                      ndS |                     |          p|}|                     ||                     |||||
                    st          rt                      ndS t          dS t                      }t          8t                      }d|_        |                     ||          |_        ||_        |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  rg  rh  r  r  r   r/  r  r  r	   r  r$  r  rv  _resolve_update_promptr  r   r  rR  r  )r   rz  r  r  r  r  r  r  r   r  r  r  r  r  r  s                  r2   r  z/FeishuAdapter._handle_update_prompt_card_action  s7    $$%788	LLRSSS4OY.000UYY)--i88 	ZLLPR[\\\4OY.000UYY\%%&CRHHNBOOUUWW]]__##LLSU[\\\4OY.000UYY5*d33gh	266<"==#GSS\^`AaAaAgeg=h=hiii	((EIIi4L4LUZ([[ 	ZNNLgNdYdeee4OY.000UYYwwui'F'FXZ[[a_abbuyyB77=2>> 	Z 0 	Z5EIY5Y5YNN\  	   5PY.000UYY0099DW	##''( (  	
 	
 
	Z 5PY.000UYY&.4.00#>>DDI??vYb?ccDI HMrN   r  r  c                 K   | j                             |          }|st                              d|           dS |                     |          s t                              d|pd|           dS t          |                    dd          pd          }|r'|r%||k    rt                              d|||           dS | j                             |d          }|st                              d|           dS 	 d	d
lm	}  ||d         |          }	t          
                    d|	|d         ||           |	s{|dk    rwt          |                    dd          p|pd          }
|
rP	 |                     |
d           d{V  dS # t          $ r  t                              dd           Y dS w xY wdS dS dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)z8Pop approval state and unblock the waiting agent thread.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)r`   uk   ⌛ That approval had already expired — the command was not run (it timed out or was resolved elsewhere).z'[Feishu] expired-approval notice failedTr`  z9Failed to resolve gateway approval from Feishu button: %s)r  r  rg  rh  r  r$  r   r{  tools.approvalr  r  r  rf  r  )r   r  r  r  r   r   r  r  r  r  _chatrw   s               r2   r  zFeishuAdapter._resolve_approval:  s      $((55 	LLK[YYYF77@@ 	NNWY`Yodoq|}}}FuyyB77=2>> 	 	,<,G,GNNJ-w   F$((d;; 	LLZ\ghhhF	[??????,,U=-A6JJEKK[u]+VY    _Vv-- EIIi44EE2FF __"ii!T          
 % _ _ _%NY]^^^^^^__ _--_ _  	[ 	[ 	[LLTVYZZZZZZZZZ	[s7   :A-F9 (F &F0,F9 /F00F9 9
G)G$$G)c                 K   | j                             |          }|st                              d|           dS |r[t	          |d          }|                     ||                    dd          d          st                              d||           dS t          |                    dd          pd          }|r'|r%||k    rt                              d	|||           dS | j                             |d          }|st                              d
|           dS 	 | 	                    |           t          
                    d|d         ||           dS # t          $ r&}	t                              d|	           Y d}	~	dS d}	~	ww xY 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  rg  rh  r	   r  r$  r   r{  r%  r  rf  r  )
r   r  r  r  r   r   r  r  r  rw   s
             r2   r  z$FeishuAdapter._resolve_update_promptp  s      )--i88 	LLPR[\\\F 	'DDDI,,Y		)R8P8PY^,__ ^`girsssuyyB77=2>> 	 	,<,G,GNNO 	   F)--i>> 	LL_ajkkkF	L..v666KKSm$fi      	L 	L 	LLLEsKKKKKKKKK	Ls   "8E 
F&FFc           
       K   | j         sdS t          |dd          }t          t          |dd          pd          }|sdS 	 |                     |          }|                     | j         j        j        j        j        |           d{V }|r t          |dd                       sdS t          t          |dd          dd          pg }|r|d	         nd}|sdS t          |d
d          }	t          t          |	dd          pd          | j	        k    rdS t          t          |dd          pd          }
t          t          |dd          pd          }|
sdS n-# t          $ r  t                              dd           Y dS w xY wt          |dd          }t          |dd          }t          t          |dd          pd          }d|v rdnd}d| d| }|                     |           d{V }|                     |
           d{V }|                     |
|                    d          p|
pd|                     ||          |d         |d         d|d                    }t#          |t$          j        ||||                     |
          t+          j                    !          }t                              d"|||           |                     |           d{V  dS )#zVFetch the reacted-to message; if it was sent by this bot, emit a synthetic text event.Nrz  r  r   r  c                     dS r  rI   rI   rN   r2   rL  z6FeishuAdapter._handle_reaction_event.<locals>.<lambda>  s     rN   rR  r  r   r   r=  r   re  r  z5[Feishu] Failed to fetch message for reaction routingTr`  r   r  r  UNKNOWNr  r  r  z	reaction:r  r/   Feishu Chat	chat_infoevent_chat_typer  user_id_altr   r  re  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  rO   r  r3  rf  rg  rh  _resolve_sender_profilerl  build_source_resolve_source_chat_typer   r   TEXT_resolve_channel_promptr   nowr  _handle_message_with_guards)r   r  rR  rz  r  rh   r  r  r  r   r   chat_type_rawuser_id_objr  r  r  synthetic_textsender_profiler  r  synthetic_events                        r2   r  z$FeishuAdapter._handle_reaction_event  sz     | 	Fgt,,b99?R@@
 	F	55jAAG!//0B0J0NPWXXXXXXXXH #N78Y#N#N#P#P GHfd;;WdKKQrE#-%((C  S(D11F764,,233t|CC'#y"55;<<G[% @ @ IEJJM  	 	 	LLP[_L```FF	 eY55#E?DAA!2L"EERSS
%33:V::j::#;;KHHHHHHHH,,W55555555	""mmF++GwG-44yZg4hh"9-$[1&}5 # 
 
 '$)!77@@lnn
 
 
 	Z\bdnpz{{{..???????????s&   A&E *0E :E AE &FFr   c                    t          j                     fd| j                                        D             }|D ]
}| j        |= || j        v rdS | j        |<   dS )zTReturn True if this card action token was already processed within the dedup window.c                6    g | ]\  }}|z
  t           k    |S rI   )%_FEISHU_CARD_ACTION_DEDUP_TTL_SECONDS)rJ   r{  tsr  s      r2   r   z;FeishuAdapter._is_card_action_duplicate.<locals>.<listcomp>  s,    wwwBcBhQvFvFv1FvFvFvrN   TF)r  r  r  )r   r   expiredr{  r  s       @r2   _is_card_action_duplicatez'FeishuAdapter._is_card_action_duplicate  sx    ikkwwww$":"@"@"B"Bwww 	, 	,A(++D,,,4*- 'urN   c           
     F  K   t          |dd          }t          t          |dd          pd          }|r2|                     |          rt                              d|           dS t          |dd          }t          t          |dd          pd          }t          |dd          }t          t          |d	d          pd          }|r|st                              d
           dS t          |dd          }t          t          |dd          pd          }	t          |di           pi }
d|	 }|
r.	 |dt          j        |
d           z  }n# t          $ r Y nw xY wt          |dd          }| 	                    |           d{V }| 
                    |           d{V }|                     ||                    d          p|pd|                     |d          |d         |d         d|d                   }t          |t          j        |||pt          t#          j                              |                     |          t)          j                              }t                              d|	||           |                     |           d{V  dS )zHRoute Feishu interactive card button clicks as synthetic COMMAND events.rz  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"  Fr]  )r   r   r   r/   r  rJ  r  r   r  r  r  r  zB[Feishu] Routing card action %r from %s in %s as synthetic command)r  r   r  rg  rh  r`  ra  rf  r	   r  rl  r  r  r  r   r   COMMANDuuiduuid4r  r   r  r  r  )r   rR  rz  r   r  r   r  r   r  
action_tagr  r  r  r  r  r  r  s                    r2   r  z'FeishuAdapter._handle_card_action_event  s     gt,,GE7B//5266 	T33E:: 	LLLeTTTF%D11gg~r::@bAA5*d33gh	266<"== 	g 	LL]^^^F$//33?x@@
vw339r.*.. 	"TdjE&R&R&R"T"TT    $GTDQQQ	#;;IFFFFFFFF,,W55555555	""mmF++GwG-44yZa4bb"9-$[1&}5 # 
 
 '$,1DJLL 1 177@@lnn
 
 
 	XZdfmovwww..???????????s   6E 
E E asyncio.Lockc                   | j                             |          }|| j                             |           |S t          | j                   | j        k    rd}t          | j                   D ]?}| j         |                                         s| j                             |           d} n@|s9| 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.
        NFT)r  r  move_to_endrt   r  r   lockedr{  r   iterrq   r  )r   r   rx  evictedr  s        r2   _get_chat_lockzFeishuAdapter._get_chat_lock  s    ##G,,((111Kt  D$;;;GD,--  ',3355 $((---"GE  C $$T$t/?*@*@%A%ABBB|~~$(!rN   r   c                  K   |j         rt          |j         dd          pdnd}|                     |          }|4 d{V  |                     |           d{V  ddd          d{V  dS # 1 d{V swxY w Y   dS )a$  Dispatch a single event through the agent pipeline with per-chat serialization
        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   rz  r   	chat_locks       r2   r  z)FeishuAdapter._handle_message_with_guards2  s*      AFT'%,	266<"RT''00	 	- 	- 	- 	- 	- 	- 	- 	-%%e,,,,,,,,,	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	-s   A00
A:=A:c                x    t          j        dd                                                                          dvS )NFEISHU_REACTIONSr  >   0nofalse)r-   r.   r/  r  rm  s    r2   _reactions_enabledz FeishuAdapter._reactions_enabledB  s3    y+V44::<<BBDDL```rN   r  c                   K   | j         r|r|sdS 	 ddlm}m} |                                                    d|i                                          }|                                                    |                              |                                          }| 	                    | j         j
        j        j        j        |           d{V }|r< t          |dd                       r"t          |dd          }t          |dd          S t                              d	||t          |d
d          t          |dd                     n.# t"          $ r! t                              d||d           Y nw xY wdS )zRReturn the reaction_id on success, else None. The id is needed later for deletion.Nr   )CreateMessageReactionRequest CreateMessageReactionRequestBodyr  r  c                     dS r  rI   rI   rN   r2   rL  z-FeishuAdapter._add_reaction.<locals>.<lambda>Z       rN   rR  reaction_idz7[Feishu] Add reaction %s on %s rejected: code=%s msg=%sr4  r  z%[Feishu] Add reaction %s on %s raisedTr`  )r  r  r  r  rX  r  rl  r  r  r  r  r  message_reactionrH  r  rg  rh  rf  r$  )	r   r  r  r  r  rv   rh   r  rR  s	            r2   _add_reactionzFeishuAdapter._add_reactionE  s     | 	: 	Z 	4!	       
 188::j9::  -4466J''d##	  "//0B0S0Z\cddddddddH :GGHiGGII :x66t]D999LLI&$//%..     	 	 	NN7	      	 ts   DE <E (E;:E;r  c                F  K   | j         r|r|sdS 	 ddlm} |                                                    |                              |                                          }|                     | j         j        j	        j
        j        |           d {V }|r t          |dd                       rdS t                              d||t          |dd           t          |d	d                      n.# t          $ r! t                              d
||d           Y nw xY wdS )NFr   )DeleteMessageReactionRequestr  c                     dS r  rI   rI   rN   r2   rL  z0FeishuAdapter._remove_reaction.<locals>.<lambda>y  r  rN   Tz:[Feishu] Remove reaction %s on %s rejected: code=%s msg=%sr4  r  z([Feishu] Remove reaction %s on %s raisedr`  )r  r  r  rX  r  r  rl  r  r  r  r  deleter  rg  rh  rf  r$  )r   r  r  r  rh   r  s         r2   _remove_reactionzFeishuAdapter._remove_reactionm  sj     | 	: 	[ 	5	HHHHHH,4466J''[))	  "//0B0S0Z\cddddddddH GGHiGGII tLLL&$//%..     	 	 	NN:	      	 us   B#C3 6<C3 3(DDc                    | j         }|||<   |                    |           t          |          t          k    r0|                    d           t          |          t          k    .d S d S )NFlast)r  r  rt   &_FEISHU_PROCESSING_REACTION_CACHE_SIZEpopitem)r   r  r  caches       r2   _remember_processing_reactionz+FeishuAdapter._remember_processing_reaction  sn    2'j*%%%%jjAAAMMuM%%% %jjAAAAAAArN   c                8    | j                             |d           S r   )r  r{  )r   r  s     r2   _pop_processing_reactionz&FeishuAdapter._pop_processing_reaction  s    155j$GGGrN   c                   K   |                                  sd S |j        }|r	|| j        v rd S |                     |t                     d {V }|r|                     ||           d S d S r   )r  r  r  r  _FEISHU_REACTION_IN_PROGRESSr"  )r   rz  r  r  s       r2   on_processing_startz!FeishuAdapter.on_processing_start  s      &&(( 	F%
 	Z4+MMMF ..z;WXXXXXXXX 	H..z;GGGGG	H 	HrN   outcomer   c                L  K   |                                  sd S |j        }|sd S | j                            |          }|r3|                     ||           d {V sd S |                     |           |t          j        u r#|                     |t                     d {V  d S d S r   )
r  r  r  r  r  r$  r   FAILUREr  _FEISHU_REACTION_FAILURE)r   rz  r(  r  start_reaction_ids        r2   on_processing_completez$FeishuAdapter.on_processing_complete  s       &&(( 	F%
 	F >BB:NN 	6..z;LMMMMMMMM  ))*555'///$$Z1IJJJJJJJJJJJ 0/rN   	remote_ipstatusc                ,   t          j                     }| j                            |          }|W|\  }}}||z
  t          k     rC|dz  }|t          z  dk    r!t
                              d|||||z
             |||f| j        |<   dS d||f| j        |<   dS )zIncrement the anomaly counter for remote_ip and emit a WARNING every threshold hits.

        Mirrors openclaw's createWebhookAnomalyTracker: TTL 6 hours, log every 25 consecutive
        error responses from the same IP.
        Nrn   r   zY[Feishu] Webhook anomaly: %d consecutive error responses (%s) from %s over the last %.0fs)r  r  r  #_FEISHU_WEBHOOK_ANOMALY_TTL_SECONDS!_FEISHU_WEBHOOK_ANOMALY_THRESHOLDrg  r$  )r   r.  r/  r  entryr  _last_status
first_seens           r2   _record_webhook_anomalyz%FeishuAdapter._record_webhook_anomaly  s     ikk,00;;.3+E<Z"EEE
<<AANN.!j(   <A&*:U,Y734fc2B$Y///rN   c                <    | j                             |d           dS )zCReset the anomaly counter for remote_ip after a successful request.N)r  r{  )r   r.  s     r2   _clear_webhook_anomalyz$FeishuAdapter._clear_webhook_anomaly  s!    $((D99999rN   	parent_id
str | Nonec                p    ddl m} t          | dd          }t          |dd          p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   r9  r<  _config_extras         r2   r  z%FeishuAdapter._resolve_channel_prompt  sS     	BAAAAA$$//'4006B%%fgyAAArN   r  rO   r  re  r  c                 K   |                      |           d {V \  }}}	}
}|t          j        k    r1t          ||          }|                    d          rt          j        }|t          j        k    r!|s|	st                              d|           d S |t          j        k    rt          |          }|r|r| d| n|}t          |dd           pt          |dd           pd }t          |dd           p#t          |dd           pt          |dd           pd }|r| 
                    |           d {V nd }t          |dd           p#t          |d	d           pt          |d
d           pd}t                              d|dk    rdnd||j        t          |dd          pd|rdnd||d d         t          |	          	  	         t          |dd          pd}|                     |           d {V }|                     ||           d {V }|                     ||                    d          p|pd|                     ||          |d	         |d         ||d         |          }t'          ||||||	|
|||                     ||pd           t+          j                              }|                     |           d {V  d S )N/z*[Feishu] Ignoring empty text message id=%sz

r  root_idr9  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=%dr  rb  rJ  r   r   r  r  rZ   r  r/   r  r  r  r  )r   r  re  r   r  r  r  r  )r   r   r  r  r  
media_urlsmedia_typesreply_to_message_idreply_to_textr  r  )_extract_message_contentr   r  rW  r&  r  rg  rh  rJ  r  _fetch_message_textr  r   rt   rl  r  r  r  r  r   r  r   r  _dispatch_inbound_event)r   rR  rO   r  re  r  r  r   inbound_typerD  rE  r   r   r  rF  rG  sender_primaryr   r  r  r  r  s                         r2   r  z&FeishuAdapter._process_inbound_message  s      GKFcFcdkFlFl@l@l@l@l@l@l=lJX;+++,T8<<Ds## 3*2 ;+++D++LLEzRRRF;...&x00D =.2<$**D***G[$77d77IW[;\;\d`d	G[$// w 2D99w	400 	 	 Pcld667JKKKKKKKKKhl Iy$// y)T22y*d33 	 	 	j&&DDGGY++1r'EE#J
OO
	
 
	
 
	
 '9b117R,,W55555555	#;;If;UUUUUUUU""mmF++GwG-44yZc4dd"9-$[1&}5 # 	
 	
 "%!!# 3'77ARdSSlnn
 
 

 **:66666666666rN   c                0  K   |j         t          j        k    r1|                                s|                     |           d{V  dS |                     |          r|                     |           d{V  dS |                     |           d{V  dS )zHApply Feishu-specific burst protection before entering the base adapter.N)r   r   r  
is_command_enqueue_text_event_should_batch_media_event_enqueue_media_eventr  r   rz  s     r2   rJ  z%FeishuAdapter._dispatch_inbound_event8  s      !111%:J:J:L:L1**5111111111F))%00 	++E222222222F..u55555555555rN   c                    t          |j        o4|j        t          j        t          j        t          j        t          j        hv           S r   )r   rD  r   r   PHOTOVIDEODOCUMENTAUDIOrR  s     r2   rP  z'FeishuAdapter._should_batch_media_eventF  sA     v"{'8+:K[Macnct&uu
 
 	
rN   c                    ddl m}  ||j        | j        j                            dd          | j        j                            dd                    }| d|j        j         S )	Nr   build_session_keygroup_sessions_per_userTthread_sessions_per_userF)r[  r\  z:media:)gateway.sessionrZ  r  r  r  r  r   r   )r   rz  rZ  r  s       r2   _media_batch_keyzFeishuAdapter._media_batch_keyL  s    555555''L$(K$5$9$9:SUY$Z$Z%)[%6%:%:;UW\%]%]
 
 

 @@e&8&>@@@rN   r  incomingc                    | j         |j         k    o9| j        |j        k    o)| j        |j        k    o| j        j        |j        j        k    S r   )r   rF  rG  r  r  r  r_  s     r2   _media_batch_is_compatiblez(FeishuAdapter._media_batch_is_compatibleV  sY     !X%:: G,0LLG&(*@@G )X_-FF		
rN   c                  K   |                      |          }| j                            |          }|!|| j        |<   |                     |           d S |                     ||          s<|                     |           d {V  || j        |<   |                     |           d S |j                            |j                   |j                            |j                   |j	        r%| 
                    |j	        |j	                  |_	        |j        |_        |j        r|j        |_        |                     |           d S r   )r^  r  r  _schedule_media_batch_flushrb  _flush_media_batch_nowrD  r  rE  r   _merge_captionr  r  )r   rz  r  r  s       r2   rQ  z"FeishuAdapter._enqueue_media_event_  sS     ##E**.22377/4D',,,S111F..x?? 	--c222222222/4D',,,S111F""5#3444##E$5666: 	K //uzJJHM"_ 	3"'"2H((-----rN   r  c                H    |                      | j        || j                   d S r   )_reschedule_batch_taskr  _flush_media_batchr   r  s     r2   rd  z)FeishuAdapter._schedule_media_batch_flusht  s3    ##+#	
 	
 	
 	
 	
rN   c                  K   t          j                    }	 t          j        | j                   d {V  |                     |           d {V  | j                            |          |u r| j                            |d            d S d S # | j                            |          |u r| j                            |d            w w xY wr   )rq   current_taskr  rF  re  r  r  r{  )r   r  rl  s      r2   ri  z FeishuAdapter._flush_media_batch{  s      +--	?- ?@@@@@@@@@--c222222222.22377<GG/33C>>>>> HGt.22377<GG/33C>>>> Hs   :B :Cc                   K   | j                             |d           }|sd S t                              d|t	          |j                             |                     |           d {V  d S )Nz6[Feishu] Flushing media batch %s with %d attachment(s))r  r{  rg  r  rt   rD  r  r   r  rz  s      r2   re  z$FeishuAdapter._flush_media_batch_now  s      +//T:: 	FD !!	
 	
 	

 ..u55555555555rN   c                b   K   |                      |d          }t          ||           d {V S )Nr6   r  rK   )_guess_remote_extensionr   )r   rV  rK   s      r2   rX  z$FeishuAdapter._download_remote_image  sC      **9f*EE))==========rN   file_urlr\  r]  r;  c                 K   ddl m} ddlm}m}  ||          st          d|d d                     |ddd|gi	          4 d {V 	 }|                    |d
dd           d {V }|                                 t          |j	                            dd                    }	|j
        }
d d d           d {V  n# 1 d {V swxY w Y   |                     ||	||          }t          |
|          }||fS )Nr   )_ssrf_redirect_guard)create_ssrf_safe_async_clientis_safe_urlz&Blocked unsafe URL (SSRF protection): P   g      >@Tr  )r  follow_redirectsevent_hooksz)Mozilla/5.0 (compatible; HermesAgent/1.0)z*/*)z
User-AgentAcceptheadersContent-Typer   )content_typedefault_namer\  )r=  rt  tools.url_safetyru  rv  ru   r  raise_for_statusr   r|  ro   _derive_remote_filenamer   )r   rr  r\  r]  rt  ru  rv  r  r  content_type_hdrrv   filenamecached_paths                r2   r^  z'FeishuAdapter._download_remote_document  s      	@?????OOOOOOOO{8$$ 	WUhsPRsmUUVVV00!#&:%;<
 
 
 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ #ZZ"M#  (        H %%'''  #8#3#7#7#K#KLL#D#	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$$ //)'#	 0 
 
 0h??H$$s   A$C
CCr   c                   t          | pd                    dd          d                   j                                        }|t          t
          z  t          z  t          t                    z  v r|n|S )Nr   ?rn   r   )	r   r3  suffixr  _IMAGE_EXTENSIONS_AUDIO_EXTENSIONS_VIDEO_EXTENSIONSr   r   )r   r0   rK   s      r2   rq  z%FeishuAdapter._guess_remote_extension  so    CI2$$S!,,Q/007==??/2CCFWWZ]^vZwZwwxxss  F  	FrN   r~  r  c               v   t          | pd                    dd          d                   j        p|}t          |          j                                        }|s^t          j        |pd                    dd          d                                                                         pd          p|}| | }|S )Nr   r  rn   r   ;)r   r3  r/   r  r  	mimetypesguess_extensionr/  )rr  r~  r  r\  r  rK   guesseds          r2   r  z%FeishuAdapter._derive_remote_filename  s    (.b//Q77:;;@PL	9oo$**,, 	0/1C0J0J3PQ0R0RST0U0[0[0]0]0c0c0e0e0kikll{p{G$/g//IrN   r   c                    t          | t                    r(t          di d |                                 D             S t          | t                    rd | D             S | S )Nc                J    i | ] \  }}|t                               |          !S rI   r  _namespace_from_mapping)rJ   r  r  s      r2   rM   z9FeishuAdapter._namespace_from_mapping.<locals>.<dictcomp>  s0    %v%v%v[d[^`dc=+P+PQU+V+V%v%v%vrN   c                B    g | ]}t                               |          S rI   r  r  s     r2   r   z9FeishuAdapter._namespace_from_mapping.<locals>.<listcomp>  s&    RRRDM99$??RRRrN   rI   )r;  r   r	   r  r   r  s    r2   r  z%FeishuAdapter._namespace_from_mapping  sq    eT"" 	x"ww%v%vhmhshshuhu%v%v%vwwweT"" 	SRRERRRRrN   rh   c                N  K   t          |dd           pd}| j         d| j         d| }|                     |          sGt                              d|           |                     |d           t          j        dd          S t          |d	i           pi }t          |
                    d
d          pd                              d          d                                                                         }|rN|dk    rHt                              d||           |                     |d           t          j        dd          S t          |dd           }|S|t          k    rHt                              d||           |                     |d           t          j        dd          S 	 t          j        t#          |t                    t$                     d {V }n# t&          $ rJ t                              d|           |                     |d           t          j        dd          cY S t          j        $ rP t                              dt$          |           |                     |d           t          j        dd          cY S t*          $ r2 |                     |d           t          j        dd d!d"          cY S w xY w	 t/          j        |                    d#                    }nK# t.          j        t6          f$ r2 |                     |d           t          j        dd$d!d"          cY S w xY w| j        r|
                    d%          pi }	t          |	
                    d&          p|
                    d&          pd          }
|
r>t;          j        |
                                | j                                                  sGt                              d'|           |                     |d(           t          j        d)d*          S |
                    d+          d,k    r*t          j        d-|
                    d-d          i          S | j         rb| !                    |j"        |          sGt                              d.|           |                     |d/           t          j        d)d0          S |
                    d1          rIt          #                    d2           |                     |d3           t          j        dd4d!d"          S | $                    |           t          |
                    d%          pi 
                    d5          pd          }| %                    |          }|d6k    r| &                    |           n|d7k    r| '                    |           n|d8k    r| (                    |           n|d9k    r| )                    |           n|d:v r| *                    ||           nq|d;k    r| +                    |           nU|d<k    r| ,                    |           n9|d=k    r| -                    |           nt          .                    d>|pd           t          j        dd?d!          S )@NremoterH  r  z+[Feishu] Webhook rate limit exceeded for %s429i  zToo Many Requests)r/  r   r|  r}  r   r  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)r4  r  )r/  r  z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>   rN  rU  zcard.action.triggerrV  rW  z([Feishu] Ignoring webhook event type: %sok)/r  r3  rI  _check_webhook_rate_limitrg  r$  r6  r   Responser   r  r3  r/  r  _FEISHU_WEBHOOK_MAX_BODY_BYTESrq   r  rx   $_FEISHU_WEBHOOK_BODY_TIMEOUT_SECONDSru   r  rf  json_responser`  r  decoder  UnicodeDecodeErrorr8  hmaccompare_digestencoder7  _is_webhook_signature_validr|  r  r8  r  r\  rZ  rb  rd  rP  r`  rj  rk  rh  )r   rh   r.  rate_keyr|  r~  r  
body_bytesrz  r$  incoming_tokenr  rR  s                r2   _handle_webhook_requestz%FeishuAdapter._handle_webhook_request  sG     Wh55B	 lEET%7EE)EE--h77 	FNNH)TTT((E:::<s1DEEEE '9b117R7;;~r::@bAAGGLLQOUUWW]]__ 	KL,>>>NNZ\hjsttt((E:::<s1IJJJJ !*:DAA%.;Y*Y*YNNOQ_ajkkk((E:::<s1IJJJJ	^&-&612  =' ' ' ! ! ! ! ! !JJ  	K 	K 	KNNH)TTT((E:::<s1IJJJJJJ# 	D 	D 	DNNSUy  |E  F  F  F((E:::<s1BCCCCCC 	^ 	^ 	^((E:::$c:O%P%PY\]]]]]]	^	Wj!2!27!;!;<<GG$&89 	W 	W 	W((E:::$c.%I%IRUVVVVVV	W
 # 
	S[[**0bF G!4!4!RG8L8L!RPRSSN " S)<%%'')A)H)H)J)J* * S ^`ijjj,,YDDD|35QRRRR ;;v"444$k7;;{B3O3O%PQQQ  	FT%E%EgoWa%b%b 	FNNQS\]]]((I>>><s1DEEEE;;y!! 	wLLghhh((ODDD$c:h%i%iruvvvv##I...'++h//52::<HHNBOO
++G44000""4((((777''----888&&t,,,,:::**40000___##J5555000((....888((....666**40000LLCZE\S\]]] !D!9!9:::s3   24G' 'AK:AK8KK'K> >AMMr|  r  rl   c                |   t          |                    dd          pd          }t          |                    dd          pd          }t          |                    dd          pd          }|r|r|sdS 	 |                    dd          }| | | j         | }t	          j        |                    d                                                    }t          j	        |                                |                                          S # t          $ r  t                              d	d
           Y dS w xY w)a  Verify Feishu webhook signature using timing-safe comparison.

        Feishu signature algorithm:
            SHA256(timestamp + nonce + encrypt_key + body_string)
        Headers checked: x-lark-request-timestamp, x-lark-request-nonce, x-lark-signature.
        zx-lark-request-timestampr   zx-lark-request-noncezx-lark-signatureFr  r0  )errorsz3[Feishu] Signature verification raised an exceptionTr`  )r   r  r  r7  hashlibsha256r  	hexdigestr  r  rf  rg  rh  )	r   r|  r  r  noncerI  body_strro   computeds	            r2   r  z)FeishuAdapter._is_webhook_signature_valid;  sH    $>CCIrJJ	GKK 6;;ArBB$6;;ArBB	 	 	Y 	5		!(((CCH"HEH4+<HhHHG~gnnW&=&=>>HHJJH &x'8'8):J:J:L:LMMM 	 	 	LLNY]L^^^55	s   9BD &D;:D;r  c                   t          j                     | j                            |          }|1|\  }}|z
  t          k     r|t          k    rdS |dz   |f| j        |<   dS t          | j                  t          k    rzfd| j                                        D             }|D ]
}| j        |= || j        vr?t          | j                  t          k    r"t          	                    dt                     dS df| j        |<   dS )u*  Return False when the composite rate_key has exceeded _FEISHU_WEBHOOK_RATE_LIMIT_MAX.

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

        The tracking dict is capped at _FEISHU_WEBHOOK_RATE_MAX_KEYS entries to prevent unbounded
        memory growth. Stale (expired) entries are pruned when the cap is reached.
        NFrn   Tc                <    g | ]\  }\  }}|z
  t           k    |S rI   )#_FEISHU_WEBHOOK_RATE_WINDOW_SECONDS)rJ   k_r~  r  s       r2   r   z;FeishuAdapter._check_webhook_rate_limit.<locals>.<listcomp>h  s;        a!R8BBB BBBrN   uQ   [Feishu] Webhook rate-limit table at capacity (%d keys) — denying untracked key)
r  r  r  r  _FEISHU_WEBHOOK_RATE_LIMIT_MAXrt   _FEISHU_WEBHOOK_RATE_MAX_KEYSr  rg  r$  )r   r  r3  r  window_start
stale_keysr  r  s          @r2   r  z'FeishuAdapter._check_webhook_rate_limitR  s?    ikk)--h77"'E<\!$GGG::: 57<qy,6O)(3tt())-JJJ   $($=$C$C$E$E  J   1 1-a00
 t888SAZ=[=[_|=|=|g1   u/0#h!(+trN   c                    ddl m}  ||j        | j        j                            dd          | j        j                            dd          |j        j                  S )z?Return the session-scoped key used for Feishu text aggregation.r   rY  r[  Tr\  F)r[  r\  profile)r]  rZ  r  r  r  r  r  )r   rz  rZ  s      r2   _text_batch_keyzFeishuAdapter._text_batch_key  so    555555  L$(K$5$9$9:SUY$Z$Z%)[%6%:%:;UW\%]%]L(	
 
 
 	
rN   c                v    | j         |j         k    o)| j        |j        k    o| j        j        |j        j        k    S )z>Only merge text events when reply/thread context is identical.)rF  rG  r  r  ra  s     r2   _text_batch_is_compatiblez'FeishuAdapter._text_batch_is_compatible  sC     (H,HH G&(*@@G)X_-FF	
rN   c                  K   |                      |          }t          |j        pd          }| j                            |          }|2||_        || j        |<   d| j        |<   |                     |           dS |                     ||          sF| 	                    |           d{V  || j        |<   d| j        |<   |                     |           dS | j                            |d          }|dz   }|j        pd}|j        r|r|j         d| n|j        p|}|| j
        k    st          |          | j        k    rF| 	                    |           d{V  || j        |<   d| j        |<   |                     |           dS ||_        ||_        |j        |_        |j        r|j        |_        || j        |<   |                     |           dS )z=Debounce rapid Feishu text bursts into a single MessageEvent.r   Nrn   r-  )r  rt   r   r  r  _last_chunk_lenr  _schedule_text_batch_flushr  _flush_text_batch_nowrD  rE  r  r  )	r   rz  r  	chunk_lenr  existing_count
next_countappended_text	next_texts	            r2   rO  z!FeishuAdapter._enqueue_text_event  s)     ""5))
(b))	-11#66$-E!.3D&s+34D+C0++C000F--h>> 	,,S111111111.3D&s+34D+C0++C000F8<<S!DD#a'

(b;C=  A]  Ax}77777aianar	555Y$Jd9d9d,,S111111111.3D&s+34D+C0++C000F!#, "_ 	3"'"2H/9','',,,,,rN   c                H    |                      | j        || j                   dS )z9Reset the debounce timer for a pending Feishu text batch.N)rh  r  _flush_text_batchrj  s     r2   r  z(FeishuAdapter._schedule_text_batch_flush  s3    ##*"	
 	
 	
 	
 	
rN   task_mapflush_fnc                    |                      |          }|r(|                                s|                                 t          j         ||                    | |<   d S r   )r  ry  r  rq   create_task)r  r  r  
prior_tasks       r2   rh  z$FeishuAdapter._reschedule_batch_task  s`     \\#&&
 	 joo// 	 +HHSMM::rN   c                  K   t          j                    }	 | j                            |          }|rt	          |dd          nd}|| j        k    r| j        }n| j        }t          j        |           d{V  | 	                    |           d{V  | j
                            |          |u r| j
                            |d           dS dS # | j
                            |          |u r| j
                            |d           w w xY w)zFlush a pending text batch after the quiet period.

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

 ..u55555555555rN   Etuple[str, MessageType, List[str], List[str], List[FeishuMentionRef]]c           	       K   t          |dd          pd}t          |dd          pd}t          t          |dd          pd          }t                              d||           t	          ||t          |dd           |                                           }|                     ||           d {V \  }}|                     ||          }|j        }	|t          j
        t          j        t          j        t          j        hv rHt          |          d	k    r5|j        d
v r,|                     |d         |d                    d {V }
|
r|
}	|	|||t#          |j                  fS )Nro   r   r   r  z3[Feishu] Received raw message type=%s message_id=%sr   r   r  r   r  )r  r  rn   >   r  r  r   )r  r   rg  r  r  _bot_identity"_download_feishu_message_resources _resolve_normalized_message_typer   r   rV  rW  rU  rT  rt   r   _maybe_extract_text_documentr   r   )r   rO   r  r   r  r  rD  rE  rK  r   injecteds              r2   rH  z&FeishuAdapter._extract_message_content  s      gy"55;7NB77=2,;;ArBB
I8U_```-!#Wj$77""$$	
 
 

 )-(O(O!! )P )
 )
 #
 #
 #
 #
 #
 #

K <<ZUU& [1;3DkFWYdYjkkkJ1$$15JJJ!>>z!}kZ[n]]]]]]]]H  \:{DAT<U<UUUrN   r  r   tuple[List[str], List[str]]c                 K   g }g }|j         D ]N}|                     ||           d {V \  }}|r*|                    |           |                    |           O|j        D ]_}|                     ||j        |j        |j                   d {V \  }}|r*|                    |           |                    |           `||fS )N)r  r   )r  r   r   fallback_filename)r   _download_feishu_imagerk  r   !_download_feishu_message_resourcer   r   r   )	r   r  r  rD  rE  r   r  
media_typer  s	            r2   r  z0FeishuAdapter._download_feishu_message_resources  s>      !#
!##. 	/ 	/I,0,G,G%# -H - - ' ' ' ' ' '#K  /!!+..."":...#. 		/ 		/I,0,R,R%"+'5"+"5	 -S - - ' ' ' ' ' '#K  /!!+..."":...;&&rN   r  r   c                   | pd                                 }|                    d          rt          j        S |                    d          rt          j        S |                    d          rt          j        S |S )Nr   image/audio/video/)r  r&  r   rT  rW  rU  )r  r0   r  s      r2   _resolve_media_message_typez)FeishuAdapter._resolve_media_message_type2  sz     &B--//
  ** 	%$$  ** 	%$$  ** 	%$$rN   rE  r   c                   |j         }|dk    r+|                     |r|d         ndt          j                  S |dk    rt          j        S |dk    r+|                     |r|d         ndt          j                  S t          j        S )Nr  r   r   r  r  r  )r   r  r   rT  VOICErV  r  )r   r  rE  	preferreds       r2   r  z.FeishuAdapter._resolve_normalized_message_type=  s    
 5	33k4YKNNWYcnct3uuu $$
""33k4YKNNWYcncw3xxxrN   r  c                  K   |r|                     d          sdS 	 t          j                            |          t          k    rdS t          |          j                                        }|dvr|dvrdS t          |                              d          }| 	                    |          }d| d| S # t          t          f$ r! t                              d	|d
           Y dS w xY w)Nztext/r   >   .md.txt>   
text/plaintext/markdownr  r  z[Content of z]:
z7[Feishu] Failed to inject text document content from %sTr`  )r&  r-   r?  getsize_MAX_TEXT_INJECT_BYTESr   r  r  	read_text_display_name_from_cached_pathOSErrorr  rg  r$  )r   r  r  rK   ro   r  s         r2   r  z*FeishuAdapter._maybe_extract_text_documentS  s      	*"7"7"@"@ 	2	w{++.DDDr{##*0022C/))j@_._._r;''1171CCG>>{KKL=,==G===+, 	 	 	NNTValpNqqq22	s   (B7 .B7 7?B7 7.C)(C)r   c          
       K   | j         r|sdS 	 |                     ||d          }|                     | j         j        j        j        j        |           d {V }|r|                                s=t          	                    d|t          |dd          t          |dd                     dS |                     |          }|sdS |                     |d	          }t          |d
d           p| d}|                     ||dt                    }t          ||          }	|                     ||                     |                    }
|	|
fS # t$          $ r! t          	                    d|d           Y dS w xY w)Nr?  r  r  r   r   z+[Feishu] Failed to download image %s: %s %sr4  rH  r  request failedr}  r   r6   allowedrp  r  z*[Feishu] Failed to cache image resource %sTr`  )r  _build_message_resource_requestr  r  r  message_resourcer  r  rg  r$  r  _read_binary_response_get_response_header_guess_extensionr  r!   _normalize_media_type_default_image_media_typerf  )r   r  r   rh   r  	raw_bytesr~  r  rK   r  r  s              r2   r  z$FeishuAdapter._download_feishu_imagec  s     | 	: 	6	::%"% ;  G
 "//0B0S0WY`aaaaaaaaH 8#3#3#5#5 AHfi88He-=>>	   v228<<I v44X~NNLxd;;Q)?Q?Q?QH'',Pa'bbC0DDDK33L$JhJhilJmJm3nnJ
** 	 	 	NNG]aNbbb66	s   BE /E BE 'E<;E<r   r   r  c                 K   | j         r|sdS |g}|dv r|                    d           |D ]}	 |                     |||          }|                     | j         j        j        j        j        |           d {V }|r|                                s>t          
                    d|||t          |dd          t          |dd	                     |                     |          }	|	s|                     |d
          }
t          |dd           pd}|p|p| d| }|                     |
|                     |                    }|                    d          re|                     ||
dt$                    }t'          |	|          }t                              d|           ||p|                     |          fc S |dk    s|                    d          rj|                     ||
dt,                    }t/          |	|          }t                              d|           ||pd|                    d          pd fc S |                    d          rJt3          |          j        s| d}t7          |	|          }t                              d|           ||fc S t3          |          j        s|t8          v r| t8          |          }t7          |	|          }t                              d|           ||p|                     |          fc S # t<          $ r" t                              d||d            Y w xY wdS )!Nr?  >   r  r  r   r  z>[Feishu] Resource download failed for %s/%s via type=%s: %s %sr4  rH  r  r  r}  r   r   r  r  r  r6   r   rp  z,[Feishu] Cached message image resource at %sr  r  r=   z,[Feishu] Cached message audio resource at %sr  oggr  rG   z,[Feishu] Cached message video resource at %sz/[Feishu] Cached message document resource at %sz/[Feishu] Failed to cache message resource %s/%sTr`  ) r  rk  r  r  r  r  r  r  r  rg  rh  r  r  r  r  _guess_media_type_from_filenamer&  r  r  r!   r  r  r  r    rM  r   r  r   _DOCUMENT_MIME_TO_EXT_guess_document_media_typerf  r$  )r   r  r   r   r  request_typesrequest_typerh   r  r	  r~  response_filenamer  r  rK   r  s                   r2   r  z/FeishuAdapter._download_feishu_message_resource  s      | 	: 	6&...  ((() <	 <	L;>>)%". ?  
 "&!3!3DLO4F4W4[]d!e!eeeeeee 	x'7'7'9'9 	LLX" $&)<<%1ABB    66x@@	  #88>RR$+Hk4$H$H$NB!,a0AaEaEaW_EaEa!77  @@JJ 8  

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

3@XSX7Z7Z[[[[((22 3>>0 5&.#4#4#4";Ix"P"PKKK NP[\\\&
2222H~~, P?T1T1T"*O,A*,MOOH7	8LLM{[[["Z%\43R3RS[3\3\]]]]   E!	       vs4   B LL)CLBLAL%A1L(MMr  c                    t          | dd           }|dS t          |d          r!t          |                                          S t          |                                          S )Nr   rN   getvalue)r  r  rl   r  rB  )r  file_objs     r2   r  z#FeishuAdapter._read_binary_response  s`    8VT2238Z(( 	.**,,---X]]__%%%rN   r/   c           	     \   t          | dd           }t          |di           pi }t          |                    ||                    |                                d                    pd                              dd          d                                                                         S )Nr  r|  r   r  rn   r   )r  r   r  r  r3  r/  )r  r/   r  r|  s       r2   r  z"FeishuAdapter._get_response_header  s    ht,,#y"--37;;tW[[r%B%BCCIrJJPPQTVWXXYZ[aacciikkkrN   r  r  r   c                  t          | pd          j                                        }||v r|S t          j        |pd                    dd          d                                                                         pd          }||v r|S |S Nr   r  rn   r   )r   r  r  r  r  r3  r/  )r  r~  r0   r  rK   r  s         r2   r  zFeishuAdapter._guess_extension  s    8>r"")//11'>>J+\-?R,F,FsA,N,Nq,Q,W,W,Y,Y,_,_,a,a,geghhgNrN   c                   | pd                     dd          d                                                                         }|p|S r  )r3  r/  r  )r~  r0   r  s      r2   r  z#FeishuAdapter._normalize_media_type  sD    "(b//Q77:@@BBHHJJ
$W$rN   c                    t          | pd          j                                        }t          j        |t          j        | pd          d         pd          S )Nr   r   zapplication/octet-stream)r   r  r  r   r  r  
guess_type)r  rK   s     r2   r  z(FeishuAdapter._guess_document_media_type  sQ    8>r"")//11'+C1EhnRT1U1UVW1X1v\vwwwrN   r?  c                    t           j                            |           }|                    dd          }t	          |          dk    r|d         n|}t          j        dd|          S )Nr  rH  rU   z	[^\w.\- ])r-   r?  rD  r3  rt   r$  r  )r?  rD  r  r  s       r2   r  z,FeishuAdapter._display_name_from_cached_path  sY    7##D))sA&&#&u::??uQxxvlC666rN   c                   t          j        | pd          d         pd                                }|r|S t          | pd          j                                        }|t
          v rd|                    d           S |t          v rd|                    d           S |t          v rt          
                    |          S dS )Nr   r   r  r  r  )r  r  r  r   r  r  rM  r  r  r  r  )r  r  rK   s      r2   r  z-FeishuAdapter._guess_media_type_from_filename  s    'B77:@bGGII 	N8>r"")//11###-CJJsOO---###-CJJsOO---### ::3???rrN   rk  c                    | pd                                                                 }|dk    rdS d|v sd|v sd|v rdS |dk    rdS dS )Nr   r  rb  topicthreadforumrJ  )r/  r  )rk  r  s     r2   rh  zFeishuAdapter._map_chat_type  sm    #)r002288::
4j  H
$:$:g>S>S7  7trN   r  r  c                    t          |                     d          pd                                                                          }|dv r|S |dk    rdS dS )Nr   r   >   r   rJ  r  rb  rJ  )r   r  r/  r  )r  r  r  s      r2   r  z'FeishuAdapter._resolve_source_chat_type  s^    y}}V,,23399;;AACC)))Oe##4wrN   Dict[str, Optional[str]]c                  K   t          |dd          pd}t          |dd          pd}t          |dd          pd}|p|}|r|n|p|}|                     ||           d{V }|||dS )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  r  r   r   r   
primary_idname_lookup_idr  s	            r2   r  z%FeishuAdapter._resolve_sender_profile   s      " )Y55=)Y55=9j$77?4'
$*H1Gx!??6 @ 
 
 
 
 
 
 
 
 "%#
 
 	
rN   c                    |sdS | j                             |          }|dS |\  }}t          j                    |k     r|S | j                             |d           dS )z>Return a cached sender name only while its TTL is still valid.N)r  r  r  r{  )r   r  rj  r/   	expire_ats        r2   r  z%FeishuAdapter._get_cached_sender_nameA  sl     	4(,,Y77>4 i9;;""K##It444trN   c                 K   |r| j         sdS |                                }|sdS t          j                    }|                     |          }||pdS |rx|                     |g           d{V }|dS |t
          z   }|                                D ]\  }}	|	|f| j        |<   | j                            |          }
|
r
|
d         pdndS 	 ddl	m
} |                    d          rd}n|                    d          rd}nd}|                                                    |                              |                                          }|                     | j         j        j        j        j        |           d{V }|r|                                sdS t+          t+          |d	d          d
d          }t+          |dd          p2t+          |dd          p!t+          |dd          pt+          |dd          }	|	rAt-          |	t.                    r,|	                                }	|	r|	|t
          z   f| j        |<   |	S n-# t0          $ r  t2                              d|d           Y nw xY wdS )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   rR  r  r/   r  nicknameen_namez-[Feishu] Failed to resolve sender name for %sTr`  )r  r/  r  r  _fetch_bot_names_FEISHU_SENDER_NAME_TTL_SECONDSr  r  r  lark_oapi.api.contact.v3r*  r&  rX  r   user_id_typerl  r  contactv3r  r  r  r;  r   rf  rg  rh  )r   r  r  trimmedr  cached_namenamesr(  oidr/   hitr*  r>  rh   r  r  s                   r2   r$  z+FeishuAdapter._resolve_sender_name_from_apiN  s       	 	4//## 	4ikk227;;"&$& 	5//	::::::::E}t==I"[[]] A A	T04i/@',,)--g66C'*4CFNd4	d??????!!%(( $###E** $$#$,,..66w??LLWUU[[]]G!//0D0G0L0PRYZZZZZZZZH 8#3#3#5#5 t78VT::FDIIDfd++ 246624T222 4D11	    
4--  zz||  8<cDc>c7dD+G4K 	d 	d 	dLLH)^bLccccc	dts   CI  B&I 'I21I2bot_idsOptional[Dict[str, str]]c                ,  K   | j         r|sd S 	 t                                                              t          j                                      d                              d |D                                           t          j
        h                                          }|                     | j         j        |           d {V }t          t          |dd           dd           }|sd S t          j        |          }|                    d          dk    rd S |                    d          pi                     d          pi }d	 |                                D             S # t&          $ r! t(                              d
|d           Y d S w xY w)Nz"/open-apis/bot/v3/bots/basic_batchc                    g | ]}d |fS )r:  rI   )rJ   r8  s     r2   r   z2FeishuAdapter._fetch_bot_names.<locals>.<listcomp>  s    >>>s9c*>>>rN   r  ro   r4  r   rR  botsc                    i | ]>\  }}||t          |                    d           pd                                          ?S )r/   r   )r   r  r/  )rJ   r8  r  s      r2   rM   z2FeishuAdapter._fetch_bot_names.<locals>.<dictcomp>  sY       CS&))/R006688  rN   z)[Feishu] Failed to fetch bot names for %sTr`  )r  r  rX  http_methodr  GETuriqueriestoken_typesr  TENANTrl  r  rh   r  r`  r  r  r  rf  rg  rh  )r   r:  reqrespro   rz  r>  s          r2   r/  zFeishuAdapter._fetch_bot_names  s     | 	7 	4	##%%Z^,,9::>>g>>>??o4566  ++DL,@#FFFFFFFFDgdE488)TJJG tj))G{{6""a''tKK''-2226::@bD !%   
  	 	 	LLDgX\L]]]44	s   CE( /-E( A	E( ('FFc                  K   | j         r|sd S || j        v r'| j                            |           | j        |         S 	 |                     |          }|                     | j         j        j        j        j        |           d {V }|r t          |dd                       du rAt          |dd          }t          |dd          }t                              d|||           d S t          t          |d	d           d
d           pg }|r|d         nd }t          |dd           }t          |dd          pd}	t          |dd          pd}
|rt          |dd           nd }|                     |	|
|          }|| j        |<   t          | j                  t          k    r8| j                            d           t          | j                  t          k    8|S # t           $ r! t                              d|d           Y d S w xY w)Nr  c                     dS r  rI   rI   rN   r2   rL  z3FeishuAdapter._fetch_message_text.<locals>.<lambda>  rd  rN   Fr4  rH  r  zmessage lookup failedz3[Feishu] Failed to fetch parent message %s: [%s] %srR  r  r   rv   r   r   ro   r   )r   r  r   r  z*[Feishu] Failed to fetch parent message %sTr`  )r  r  r  r  r  r  r  rO   r  r  rg  r$  _extract_text_from_raw_contentrt   _FEISHU_MESSAGE_TEXT_CACHE_SIZEr   rf  )r   r  rh   r  r4  r  r  parentrv   r   r  parent_mentionsr   s                r2   rI  z!FeishuAdapter._fetch_message_text  sS     | 	: 	4111$00<<<+J77	55jAAG!//0B0J0NPWXXXXXXXXH JwxMMJJLLPUUUx;;h/FGGTV`bfhkllltGHfd;;WdKKQrE!&0U1XXDF66400Dvz266<"H!$	266<"KCISgfj$???tO66!'( 7  D
 48D$Z0d.//2QQQ(00e0<<< d.//2QQQK 	 	 	NNG^bNccc44	s   B'G (C2G 'HH)r   r   r  r   r  c                  t          ||||                                           }|j        r|j        S t          |j        t
                    r|j                            d          nd }t          |                                          pd S )Nr  r  )	r  r  r   r;  r   r   r  r   r/  )r   r   r  r   r  r   s         r2   rJ  z,FeishuAdapter._extract_text_from_raw_content  s     .!#""$$	
 
 

 " 	+**EOPZPceiEjEjtj)--.@AAApt;%%''/4/rN   rK   c                n    | pd                                 }|dv rdS d|                    d          pd S )Nr   >   r6   r8   z
image/jpegr  r  jpeg)r  rM  )rK   normalized_exts     r2   r  z'FeishuAdapter._default_image_media_type  sI    )**,,...<>--c22<f>>>rN   r  c                    	 |                                   d S # t          $ r t                              d           Y d S w xY w)Nz-[Feishu] Background inbound processing failed)rE  rf  rg  	exception)r  s    r2   r  z%FeishuAdapter._log_background_failure  sV    	NMMOOOOO 	N 	N 	NLMMMMMM	Ns    $A A r   Optional[RejectReason]c                B   t          |          }t          d | j        | j        fD                       }t	          |          }t          |dd          dk    }t          |dd          pd}|o|                     |          }|r||z  rdS |r:| j        }	|	dk    r|	dk    rd	S |r|sd
S |	dk    r|s|                     |          sdS |st          j
        dd                                                                          dv rd S t          j
        dd                                                                          dv rd S | j        sd S |r
|| j        z  sdS d S |                     t          |dd           ||          sdS |r|                     |          sdS d S )Nc              3     K   | ]}||V  	d S r   rI   r	  s     r2   r  z'FeishuAdapter._admit.<locals>.<genexpr>  s(      TT1RSTQTTTTTTrN   re  r  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_forrJ  _mentions_selfr-   r.   r/  r  r:  r  )
r   r   rO   
sender_idsself_idsr  is_groupr   r   modes
             r2   r  zFeishuAdapter._admit  s   %f--
TT):D<M(NTTTTT''7K775@'9b117R"It'@'@'I'I  	
X- 	; 
	+#Dz!!demm& *: *)) z!!/!$BUBUV]B^B^!** 	y1266<<>>DDFFJ^^^ty2B77==??EEGGK___t , t ,J1J$J ,++4((FK.. ) 
 
 	+ +* 	+4#6#6w#?#? 	+**trN   c                l    |r| j                             |          nd }|r|j        |j        S | j        S r   )r=  r  r   rK  )r   r   rules      r2   r[  z"FeishuAdapter._require_mention_for  sD    18Bt $$W---d 	(D(4''$$rN   c                  t          |dd          }t          |dd          }||hdhz
  }|r| j        r|| j        z  rdS |r| j                            |          nd}|r|j        }|j        }	|j        }
n#| j        p| j        }| j	        }	t                      }
|dk    rdS |dk    rdS |dk    rdS |rdS |d	k    rt          |o||	z            S |d
k    rt          |o||
z             S t          |o	|| j	        z            S )z)Per-group policy gate for non-DM traffic.r   Nr   TdisabledFr  
admin_onlyr   r   )r  r;  r=  r  r   r   r   r<  r9  r:  r   r   )r   r  r   r  sender_open_idsender_user_idr]  rb  r   r   r   s              r2   r  z"FeishuAdapter._allow_group_message  s^    !It<< It<<$n5>
 	$, 	J,E 	418Bt $$W---d 	[FIII/E43EF1II Z5V4\!!5 	4[  
?
Y(>@@@[  
CJ,B'CDDDJKJ1J$JLLLrN   c           	     J   t          |dd          pd}d|v rdS t          |dd           pg }|r|                     |          rdS t          t          |dd          pd|t          |dd           |                                           }|                     |j                  S )Nro   r   r  Tr   r   r  )r  _message_mentions_botr  r  _post_mentions_botr   )r   rO   r  r   r  s        r2   r\  zFeishuAdapter._mentions_selfK  s    gy"55;k!!47J55; 	228<< 	4- ."==C#Wj$77""$$	
 
 

 &&z':;;;rN   	List[Any]c                   |D ]}t          |dd           }t          |dd           pd                                }t          |dd           pd                                }t          |dd           pd                                }|r| j        r|| j        k    r dS |r| j        r|| j        k    r dS | j        r|| j        k    r dS dS )Nr=  r   r   r   r/   TF)r  r/  r>  r?  r@  )r   r   r:  r@  mention_open_idmention_user_idmention_names          r2   ri  z#FeishuAdapter._message_mentions_bot[  s      	 	G $55J&z9dCCIrPPRRO&z9dCCIrPPRRO#GVT::@bGGIIL 4#4 "d&77744 4#4 "d&77744~ ,$."@"@tturN   r   c                4    t          d |D                       S )Nc              3  $   K   | ]}|j         V  d S r   )r   )rJ   rK  s     r2   r  z3FeishuAdapter._post_mentions_bot.<locals>.<genexpr>s  s$      //19//////rN   )any)r   r   s     r2   rj  z FeishuAdapter._post_mentions_botr  s    //h//////rN   r   c                D    t          | j        | j        | j                  S )NrD  )r   r>  r?  r@  rm  s    r2   r  zFeishuAdapter._bot_identityu  s*    !%%
 
 
 	
rN   c                  K   | j         sdS 	 t                                                              t          j                                      d                              t          j	        h          
                                }|                     | j         j        |           d{V }t          t          |dd          dd          }|rt          j        |          }t!          |          pi }|                    d          pd                                }|                    d          pd                                }|r3| j        r%| j        |k    rt(                              d           || _        |r3| j        r%| j        |k    rt(                              d	           || _        n,# t0          $ r t(                              d
d           Y nw xY w| j        rdS 	 |                     | j        d          }|                     | j         j        j        j        j        |           d{V }	|	r|	                                s3t          |	dd          }
|
dk    rt(                              d           dS t          t          |	dd          dd          }t          |dd          pd                                }|r| j        s|| _        dS dS dS # t0          $ r  t(                              dd           Y dS w xY 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  ro   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 hydrationTr`  r   r   r@  r4  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.rR  r  app_namez9[Feishu] Failed to hydrate bot name from application info)r  r  rX  r@  r  rA  rB  rD  r  rE  rl  r  rh   r  r`  r  _parse_bot_responser  r/  r>  rg  r$  r@  r  rf  rh  _build_get_application_requestr3  applicationv6r  )r   rF  rG  ro   rz  rV  r   r   rh   r  r4  r  rw  s                r2   _hydrate_bot_identityz#FeishuAdapter._hydrate_bot_identity|  s_      | 	F
	##%%Z^,,-..o4566  ++DL,@#FFFFFFFFDgdE488)TJJG .*W--,W55;!::m44:AACC"JJz228b??AA 0( T->'-I-Iy   )0D% .~ $.H*D*D E   &.DN 	 	 	LLE      	 > 	F	e99T[9\\G!//0H0K0W0[]deeeeeeeeH 8#3#3#5#5 x668##NNW  
 '(FD995$GGCZ66<"CCEEH * *!)* * * * 	e 	e 	eLLT_cLdddddd	es,   F"F0 0&GG&BK  AK &LLc                  	 	 t          j        | j                            d                    }nK# t          $ r Y d S t
          t           j        f$ r& t                              d| j        d           Y d S w xY wt          |t                    r|                    di           ni }t          j                    t          t          |t                    rd |D             }nt          |t                    rqi }|                                D ]Y\  }}t          |t                     r|                                s/	 t%          |          ||<   C# t&          t(          f$ r Y Vw xY wnd S fd|                                D             	t+          		fd	d
          d | j                 }t          t/          |                    | _        	fd|D             | _        d S )Nr  r  z5[Feishu] Failed to load persisted dedup state from %sTr`  message_idsc                    i | ]E}t          |                                          #t          |                                          d FS )r  r   r  s     r2   rM   z8FeishuAdapter._load_seen_message_ids.<locals>.<dictcomp>  sE    (k(k(kDY\]aYbYbYhYhYjYj(kT):):C(k(k(krN   c                F    i | ]\  }}|d k    sdk    s	|z
  k     ||S )r  r   rI   )rJ   msg_idr  r  ttls      r2   rM   z8FeishuAdapter._load_seen_message_ids.<locals>.<dictcomp>  sD     #
 #
 #
%62SyyC1HHb3 B(6rN   c                    |          S r   rI   )r  valids    r2   rL  z6FeishuAdapter._load_seen_message_ids.<locals>.<lambda>  s    q rN   )r  reversec                "    i | ]}||         S rI   rI   )rJ   r  r  s     r2   rM   z8FeishuAdapter._load_seen_message_ids.<locals>.<dictcomp>  s    !B!B!B!!U1X!B!B!BrN   )r`  r  r  r  FileNotFoundErrorr  r  rg  r$  r;  r   r  r  _FEISHU_DEDUP_TTL_SECONDSr   r  r   r/  r   rT  ru   sortedrA  reversedr  r  )
r   rz  	seen_datar  r  r   
sorted_idsr  r  r  s
          @@@r2   r  z$FeishuAdapter._load_seen_message_ids  s.   	j!7!A!A7!A!S!STTGG  	 	 	FF-. 	 	 	NNRTXTjuyNzzzFF	 7A$6O6OWGKKr222UW	ikk'i&& 	(k(kI(k(k(kGG	4(( 
	G'oo//  
U!#s++ 399;; #(<<GCLL!:.   H F#
 #
 #
 #
 #
)0#
 #
 #

 E'9'9'9'94HHHI`$J`I`a
#'(<(<#=#= !B!B!B!Bz!B!B!Bs'   -3 
A; 7A;:A;EE)(E)c                *    	  j         j                            dd            j         j         d          }d fd|D             i}t           j         |d            d S # t          $ r& t                              d j         d           Y d S w xY w)NT)parentsexist_okr~  c                >    i | ]}|j         v |j         |         S rI   )r  )rJ   r  r   s     r2   rM   z;FeishuAdapter._persist_seen_message_ids.<locals>.<dictcomp>  s2    &s&s&sWX\`\rWrWrq$*@*CWrWrWrrN   )indentz,[Feishu] Failed to persist dedup state to %sr`  )	r  rL  mkdirr  rA  r%   r  rg  r$  )r   recentrz  s   `  r2   r  z'FeishuAdapter._persist_seen_message_ids  s    	r")//t/LLL-t/E.E.F.FGF$&s&s&s&sV&s&s&stGd4gdKKKKKK 	r 	r 	rNNI4KalpNqqqqqq	rs   AA" ",BBc                <   t          j                     }t          }| j        5  | j                            |          }||dk    s	||z
  |k     r	 d d d            dS || j        |<   | j                            |           t          | j                  | j        k    rR| j        	                    d          }| j        	                    |d            t          | j                  | j        k    R| 
                                 	 d d d            dS # 1 swxY w Y   d S )Nr   TF)r  r  r  r  r  r  rk  rt   rA  r{  r  )r   r  r  r  seen_atstales         r2   r  zFeishuAdapter._is_duplicate  s|   ikk' 	 	,00<<G"qC'MC4G4G	 	 	 	 	 	 	 	
 25D":.$++J777d.//$2HHH044Q77&**5$777 d.//$2HHH **,,,	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   -DB(DDDr  r  c                   |st                               |          rdt          |          fS d|i}dt          j        |d          fS )Nr  r   Fr]  )r  r  rc  r`  ra  )r   ro   r  text_payloads       r2   r  z%FeishuAdapter._build_outbound_payload  s\      	A+227;; 	A7@@@@(tz,UCCCCCrN   c                   ddl }	 t          | d          5 }|                                }ddd           n# 1 swxY w Y   d}d}|t          |          dz
  k     r|                    d|          }|dk    rn|}|dz   t          |          k    rn|                    d||dz             d         }||d	z            }|dk    r|}|dz   |z   }	|	t          |          k    rn@|}
t          |          D ]}|
||dz   |z            z  }
||
z  }|t          |          dz
  k     |dk    rt          |d
z  dz            ndS # t          $ r Y dS w xY w)zExtract OGG/Opus audio duration in milliseconds (pure Python, no deps).

        Parses the OGG container to find the last granule position and divides
        by the Opus sample rate (48000 Hz). Returns 0 for non-OGG files or on error.
        r   Nr7     s   OggSz<q      i  r  )	structr  rB  rt   findunpack_fromrangerj   rf  )r*  r  rM  rR  poslast_granuleidxgranulenum_segmentssegment_end	page_sizerU  s               r2   _get_audio_duration_msz$FeishuAdapter._get_audio_duration_ms  s    		i&&  !vvxx                             CLD		B&&ii--"998c$ii'' ,,T4qAA!D#C"H~Q;;#*L!Bh5T**(	|,, 4 4AcBhl!33IIy # D		B&&$ 8Da7G7G3|e+d2333QN 	 	 	11	s-   D> 7D> ;D> ;C>D> >
EEr   )r'  r   r+  r+  c                 K   | j         st          dd          S t          j                            |          st          dd|           S |pt          j                            |          }|                     ||          \  }	}
	 d}|	dk    r|                     |          }t          |d          5 }| 	                    |	|||	          }| 
                    |          }|                     | j         j        j        j        j        |           d {V }d d d            n# 1 swxY w Y   |                     |d
          }|s|                     |dd          S |r=d||d}|                     |d|                     ||          ||           d {V }n|                     ||
t)          j        d
|id          ||           d {V }|                     |          s5t/          |dd           dk    r|
dk    r|pi                     d          r |pi                     d          }|s0|                     |pi                     d                     d {V }|rPt4                              d           |                     ||
t)          j        d
|id          ||           d {V }|                     |          sPt4                              d           |                     ||
t)          j        d
|id          d d            d {V }|                     |d          S # t<          $ rF}t4                              d||d           t          dtA          |                    cY d }~S d }~ww xY w)NFr  r  zFile not found: )r*  requested_message_typer   opusr7  	file_typer   r   durationr   zfile upload failedz#Feishu file upload missing file_keyr:  r  )r   r   r   r  r=  r  r]  r4  iRr  r  rF  z0[Feishu] Audio: retrying via reply API in threadz;[Feishu] Audio send failed in thread, retrying with chat_idzfile send failedz#[Feishu] Failed to send file %s: %sTr`  )!r  r   r-   r?  r@  rD  _resolve_outbound_file_routingr  r  _build_file_upload_body_build_file_upload_requestr  r  r  r   rH  rI  rJ  r  rK  r`  ra  r  r  r  _fetch_last_message_in_threadrg  r  r$  r  rf  r  r   )r   r   r*  r  r   r'  r   r+  r  upload_file_typeresolved_message_typeduration_msr  rv   rh   rP  r   r>  rR  thread_msg_idrw   s                        r2   r-  z)FeishuAdapter._send_uploaded_file_messageB  s      | 	De?CCCCw~~i(( 	Se3Qi3Q3QRRRR ?BG$4$4Y$?$?262U2U"#8 3V 3
 3
//K	=K6))"99)DDi&& d(33.*!(	 4   99$??(,(:(:4<?;M;R;Y[b(c(c"c"c"c"c"c"cd d d d d d d d d d d d d d d 33OZPPH 22#$8#H 3     2" (!- 	
 *.)E)E## ::7V_:``%% *F * * $ $ $ $ $ $   *.)E)E#2 J
H'=ERRR%% *F * * $ $ $ $ $ $  001ABB #$4fdCCxOO1W<<%^00== = &.^$8$89N$O$OM( .2.P.P%^00==/ / ) ) ) ) ) ) % $VWWW151M1M$+%:$(J
H/ETY$Z$Z$Z%2%- 2N 2 2 , , , , , ,(  334DEE 'deee151M1M$+%:$(J
H/ETY$Z$Z$Z%)%) 2N 2 2 , , , , , ,( --.>@RSSS 	= 	= 	=LL>	3Y]L^^^e3s88<<<<<<<<<	=sJ   
-L8 7A$D'L8 'D++L8 .D+/2L8 "GL8 8
N;N=NNr  c                  K   | j         r|sdS 	 ddlm} |                                                    d                              |                              d                                          }t          j	        | j         j
        j        j        j        |           d{V }|rf t          |dd                       rLt          t          |dd          d	d          }|r*t          |          dk    rt          |d         d
d          S n3# t           $ r&}t"                              d||           Y d}~nd}~ww xY wdS )z>Fetch the last message_id in a thread for reply-based routing.Nr   )ListMessageRequestr  rn   r  c                     dS r  rI   rI   rN   r2   rL  z=FeishuAdapter._fetch_last_message_in_thread.<locals>.<lambda>  r  rN   rR  r  r  z6[Feishu] Failed to fetch last message in thread %s: %s)r  r  r  rX  container_id_typecontainer_idr  rl  rq   r  r  r  rO   r   r  rt   rf  rg  rh  )r   r  r  rh   r  r  rw   s          r2   r  z+FeishuAdapter._fetch_last_message_in_thread  sn     | 	9 	4	c>>>>>>"**,,""8,,i((1  %.t|/A/I/NPWXXXXXXXXH AGGHiGGII A&$ ? ?$OO ASZZ!^^"58\4@@@ 	c 	c 	cLLQS\^abbbbbbbb	cts   D D 
ED<<Erz  c          	     &  K   |}|s,|r*|                     d          r|                     d          }t          |pi                      d                    }|r|                     |||t          t	          j                                        }|                     ||          }	|                     | j        j	        j
        j        j        |	           d {V S |pi                      d          }
|
rN|                     |
||t          t	          j                                        }|                     d|          }	n|}d}|                    d          r|                    dd          d         }d	}n|                    d
          rd}|                     |||t          t	          j                                        }|                     ||          }	|                     | j        j	        j
        j        j        |	           d {V S )Nr  rF  ro   r   reply_in_thread
uuid_value
receive_idr   ro   r  r   zfeishu_user_id:r  rn   r   r+  r   )r  r   _build_reply_message_bodyr   r  r  _build_reply_message_requestr  r  r  r  rO   reply_build_create_message_body_build_create_message_requestr&  r3  rH  )r   r   r   rz  r  r   effective_reply_tor  rv   rh   
_thread_idr  receive_id_types                r2   _send_raw_messagezFeishuAdapter._send_raw_message  s9      &! 	Eh 	E8<<3L3L 	E!).C!D!DB33K@@AA 	W11! /tz||,,	 2  D 778JDQQG++DLO,>,F,LgVVVVVVVVV
 n"))+66
 	P22%!tz||,,	 3  D 88dKKGG J'O!!"344 ,$]]32215
"+##E** ,"+22%!tz||,,	 3  D 88$OOG''(:(B(I7SSSSSSSSSrN   c                T    t          | o t          | dd                                 S )Nr  c                     dS r  rI   rI   rN   r2   rL  z3FeishuAdapter._response_succeeded.<locals>.<lambda>  s    e rN   r  )r  s    r2   r  z!FeishuAdapter._response_succeeded  s,    HN!L9mm!L!L!N!NOOOrN   
field_namec                    t                               |           sd S t          | dd           }|rt          ||d           nd S )NrR  )r  r  r  )r  r  rR  s      r2   rI  z%FeishuAdapter._extract_response_field  sJ    00:: 	4x..26@wtZ...D@rN   )r<  r;  r<  c                   |rt          d||          S t          |dd          }t          |d|          }t          dd| d| |          S )NF)r  r  raw_responser4  rH  r  r  z] )r   r  )r   r  r;  r<  r4  r  s         r2   rJ  z$FeishuAdapter._response_error_result  sk      	Ze>PXYYYYx33h77%/@4/@/@3/@/@xXXXXrN   c                    |                      |          s|                     ||          S t          d|                     |d          |          S )N)r;  Tr  )r  r  r  )r  rJ  r   rI  )r   r  r;  s      r2   r  z#FeishuAdapter._finalize_send_result  sb    ''11 	Z..x.YYY33HlKK!
 
 
 	
rN   c           	       K   t          t                    D ]}	 | j        dk    r|                                  d {V  n|                                  d {V   d S # t
          $ r}d| _        |                                  d | _        | 	                                 d {V  |t          dz
  k    r d|z  }t                              d|dz   t          ||           t          j        |           d {V  Y d }~d }~ww xY wd S )Nr  Frn   rH  z:[Feishu] Connect attempt %d/%d failed; retrying in %ds: %s)r  _FEISHU_CONNECT_ATTEMPTSr6  _connect_websocket_connect_webhookrf  r  r  r  r  rg  r$  rq   r  )r   attemptrw   wait_secondss       r2   r  z!FeishuAdapter._connect_with_retry  si     566 	2 	2G2(K77113333333333//111111111 2 2 2 %66888"&//1111111116::: G|PaK,    mL111111111111112	2 	2s   A A
D'BC<<Dc                F  K   t           st          d          | j        dk    rt          nt          }|                     |          | _        |                                 | _        | j        t          d          | j	        }||
                                rt          d          |                                  d {V  t          | j        | j        t          j        j        | j        |dg          | _        |                    d t(          | j        |           | _        d S )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_AVAILABLErt  r5  r  r  _build_lark_clientr  rn  r  r  r  r|  r  r3  r4  r|   LogLevelINFOr  r}  r  r  )r   r  r  s      r2   r  z FeishuAdapter._connect_websocket-  s&     ) 	WUVVV"&"3v"="=;..v66"7799&EFFFz<4>>++<:;;;((*********(<'m(- %+
 
 
 ..*O	
 
rN   c                  K   t           st          d          | j        dk    rt          nt          }|                     |          | _        |                                 | _        | j        t          d          | 	                                 d {V  t          j        t                    }|j                            | j        | j                   t          j        |          | _        | j                                         d {V  t          j        | j        | j        | j                  | _        | j                                         d {V  d S )Nz/aiohttp not installed; webhook mode unavailabler|   r  )client_max_size)FEISHU_WEBHOOK_AVAILABLErt  r5  r  r  r  r  rn  r  r|  r   Applicationr  routeradd_postrI  r  	AppRunnerr  setupTCPSiterG  rH  r  r  )r   r  r  s      r2   r  zFeishuAdapter._connect_webhookM  sR     ' 	RPQQQ"&"3v"="=;..v66"7799&EFFF((********* o.LMMM
D.0LMMM"}S11"((********* [)=t?QSWSeff &&(((((((((((rN   r  c                ,   t           j                                                            | j                                      | j                                      |                              t           j	        j
                                                  S r   )r|   r  rX  r   r3  r   r4  r  r  r  r  rl  )r   r  s     r2   r  z FeishuAdapter._build_lark_client`  s]    K!!VDL!!Z())VF^^Yt},--UWW	
rN   c          
     \  K   d }|}t          t                    D ]}	 |                     |||||           d {V }	|r|                     |	          st	          |	dd           }
|
t
          v r|pi                     d          r6t                              d||pi                     d          |
           |	c S t                              d||
|           d }|                     |||d |           d {V }	|	c S # t          $ r}|}|dk    r(t                              t          |                    r |t          dz
  k    r d|z  }t                              d	|dz   t          |||           t          j        |           d {V  Y d }~zd }~ww xY w|pt          d
          )Nr  r4  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  rn   rH  zC[Feishu] Send attempt %d/%d failed for chat %s; retrying in %ds: %szFeishu send failed)r  _FEISHU_SEND_ATTEMPTSr  r  r  _FEISHU_REPLY_FALLBACK_CODESr  rg  r$  rf  r  r  r   rq   r  rt  )r   r   r   rz  r  r   
last_erroractive_reply_tor  r  r4  rw   r  s                r2   r  z%FeishuAdapter._feishu_send_with_retryj  sk      +/
"233 6	2 6	2G52!%!7!7#%#,% "8 " "       # 4+C+CH+M+M "8VT::D;;;$N//<< ,"NN!\ /!)R 4 4[ A A $   $,OOOE+ #   +/)-)?)?$+%-$+%)%- *@ * * $ $ $ $ $ $   2 2 2 
v%%*B*I*I#c((*S*S%3a777 G|YaK)    mL111111111111112  >L)=>>>s    BD =A D  
F
BFFc                   K   | j         sd S 	 t          t          | j                    n4# t          $ r'}t                              d|d           Y d }~nd }~ww xY wd | _         d S # d | _         w xY w)Nz'[Feishu] Failed to release app lock: %sTr`  )r  r#   r  rf  rg  r$  )r   rw   s     r2   r  zFeishuAdapter._release_app_lock  s      & 	F	+ 68OPPPP 	Z 	Z 	ZNNDcTXNYYYYYYYY	Z '+D###dD#****s+   ( A% 
AAA% AA% %	A.c                    t           >t                                                               |                                           S t	          |           S )Nr   )r  rX  r   rl  r	   r  s    r2   rf  z%FeishuAdapter._build_get_chat_request  sE    %!))++33G<<BBDDDw////rN   c                    t           >t                                                               |                                           S t	          |           S )Nr  )r  rX  r  rl  r	   r  s    r2   r  z(FeishuAdapter._build_get_message_request  sE    ($,,..99*EEKKMMM*5555rN   c                    t           dt                                                               |                               |                              |                                          S t          | ||          S )N)r  r   r   )r  rX  r  r   r   rl  r	   r  s      r2   r  z-FeishuAdapter._build_message_resource_request  sc    $0)1133J''(##m$$ *xm\\\\rN   r   r@  c                    t           Qt                                                               |                               |                                          S t          | |          S )Nrv  )r  rX  r   r@  rl  r	   rv  s     r2   ry  z,FeishuAdapter._build_get_application_request  sP     ,%--//d	 f48888rN   r  r  c                $   t           wt                                                               |                               |                              |                              |                                          S t          | |||          S )N)ro   r   r  r  )r  rX  ro   r   r  r  rl  r	   r  s       r2   r  z'FeishuAdapter._build_reply_message_body  s~    ".'//11!!(## 11j!! +	
 
 
 	
rN   r  c                    t           Qt                                                               |                               |                                          S t          | |          S Nr  )r  rX  r  r  rl  r	   r  s     r2   r  z*FeishuAdapter._build_reply_message_request  sT    *#++--J''l++	 *<PPPPrN   c                    t           Qt                                                               |                               |                                          S t          | |          S )Nr  )r  rX  r   ro   rl  r	   r  s     r2   r  z(FeishuAdapter._build_update_message_body  sT    #/(0022(##!!	 'BBBBrN   c                    t           Qt                                                               |                               |                                          S t          | |          S r  )r  rX  r  r  rl  r	   r  s     r2   r  z+FeishuAdapter._build_update_message_request  sT    +$,,..J''l++	 *<PPPPrN   r  c                $   t           wt                                                               |                               |                              |                              |                                          S t          | |||          S )N)r  r   ro   r  )r  rX  r  r   ro   r  rl  r	   r  s       r2   r  z(FeishuAdapter._build_create_message_body  s~    #/(0022J''(##!!j!! !	
 
 
 	
rN   r  c                    t           Qt                                                               |                               |                                          S t          | |          S )Nr  r  )r  rX  r  r  rl  r	   r  s     r2   r  z+FeishuAdapter._build_create_message_request$  sT    +$,,.. 11l++	 \ZZZZrN   r9  r  c                    t           Qt                                                               |                               |                                          S t          | |          S )Nr8  )r  rX  r9  r  rl  r	   r8  s     r2   rE  z&FeishuAdapter._build_image_upload_body/  sR    !-&..00J''u	 *EBBBBrN   c                    t           >t                                                               |                                           S t	          |           S Nr  )r  rX  r  rl  r	   r  s    r2   rG  z)FeishuAdapter._build_image_upload_request:  sE    )%--//<<\JJPPRRRL9999rN   r   )r  r  r  c                8   t           t                                                               |                               |                              |          }|dk    r|                    |          }|                                S t          | |||          S )Nr   r  )r  rX  r  r   r   r  rl  r	   )r  r   r   r  rX  s        r2   r  z%FeishuAdapter._build_file_upload_body@  s     ,%--//9%%9%%d	  !||!**844==??"id]effffrN   c                    t           >t                                                               |                                           S t	          |           S r  )r  rX  r  rl  r	   r  s    r2   r  z(FeishuAdapter._build_file_upload_requestN  sE    ($,,..;;LIIOOQQQL9999rN   c                     t          |          S r   )rc  rn  s     r2   _build_post_payloadz!FeishuAdapter._build_post_payloadT  s    +G444rN   r>  rd   c                   t          j        |                     |                    }|                    di                               dg           }|                    |g           t          j        |d          S r\  )r`  r  r  
setdefaultrk  ra  )r   r'  r>  rz  ro   s        r2   rK  z'FeishuAdapter._build_media_post_payloadW  sm    *T55g>>??$$Wb11<<YKK	{###z'6666rN   r  c                    t          |           j                                        }|t          v rdS |t          v rdS |t
          v rt
          |         dfS |dk    r	t          dfS t          dfS )N)r  r  )mp4r  r   )r   r  r  _FEISHU_OPUS_UPLOAD_EXTENSIONS_FEISHU_MEDIA_UPLOAD_EXTENSIONS_FEISHU_DOC_UPLOAD_TYPES_FEISHU_FILE_UPLOAD_TYPE)r*  r  rK   s      r2   r  z,FeishuAdapter._resolve_outbound_file_routing]  s}     9oo$**,,000"?111!>***+C0&88!V+++V33'//rN   )r  r   )r  r   rk   r   )r1  r   rk   rh  )rk   r
   )rk   ro  ro  )r  r   rk   r   )r   r   rk   rh  )NN)
r   r   ro   r   r  r  r   r  rk   r   )
r   r   r  r   ro   r   r  r   rk   r   )r  NTTF)r   r   r  r   r  r   r   r   r   r  r  r   r  r   r  r   rk   r   )r  r   r0   r   r  rj   rk   r   )r   r   N)r   r   r  r   r0   r   r  r   r   r  rk   r   )r  r   r  r   rk   r   )r  r   r  r   rk   r   )r  r   rk   rh  )NNN)r   r   r&  r   r'  r  r  r  r   r  rk   r   )NNNN)r   r   r*  r   r'  r  r   r  r  r  r   r  rk   r   )r   r   r2  r   r'  r  r  r  r   r  rk   r   )r   r   r5  r   r'  r  r  r  r   r  rk   r   r   )r   r   rk   rh  )r   r   rV  r   r'  r  r  r  r   r  rk   r   )r   r   rZ  r   r'  r  r  r  r   r  rk   r   )r   r   rk   r   ro   r   rk   r   )rR  r
   rk   rh  )rR  r
   rk   r   )rR  r  rk   rh  )r  r   rR  r
   rk   rh  )rR  r
   rk   r
   )r  r
   rk   r   )r  r
   r  r
   rk   r   )r   r   rk   r   )rz  r
   r  r   r  r
   rk   r
   )r  r
   r  r   r  r   r   r   r   r   rk   rh  )r  r
   r  r   r  r   r   r   r   r   rk   rh  )r   r   rk   r   )r   r   rk   r  )rz  r   rk   rh  rk   r   )r  r   r  r   rk   r  )r  r   r  r   rk   r   )r  r   r  r   rk   rh  )r  r   rk   r  )rz  r   r(  r   rk   rh  )r.  r   r/  r   rk   rh  )r.  r   rk   rh  )r   r   r9  r:  rk   r:  )rR  r
   rO   r
   r  r
   re  r   r  r   r  r   rk   rh  )rz  r   rk   r   )rz  r   rk   r   )r  r   r_  r   rk   r   )r  r   rk   rh  )rV  r   rk   r   )rr  r   r\  r   r]  r   rk   r;  )r   r   r0   r   rk   r   )
rr  r   r~  r   r  r   r\  r   rk   r   )r   r
   rk   r
   )rh   r
   rk   r
   )r|  r
   r  rl   rk   r   )r  r   rk   r   )r  r   r  r   r  r
   rk   rh  )rO   r
   rk   r  )r  r   r  r   rk   r  )r  r   r0   r   rk   r   )r  r   rE  r   rk   r   )r  r   r  r   rk   r   )r  r   r   r   rk   r;  )
r  r   r   r   r   r   r  r   rk   r;  )r  r
   rk   rl   )r  r
   r/   r   rk   r   )
r  r   r~  r   r0   r   r  r   rk   r   )r~  r   r0   r   rk   r   )r  r   rk   r   )r?  r   rk   r   )rk  r   rk   r   )r  r   r  r   rk   r   )r  r
   r  r   rk   r"  )r  r  rk   r  )r  r  r  r   rk   r  )r:  r   rk   r;  )r   r   r  r   r   r  rk   r  )rK   r   rk   r   )r  r
   rk   rh  )r   r
   rO   r
   rk   rT  )r   r   rk   r   )r   )r  r
   r   r   r  r   rk   r   )rO   r
   rk   r   )r   rk  rk   r   )r   r   rk   r   )rk   r   )r  r   rk   r   )ro   r   r  r   rk   r;  )r*  r   rk   rj   )r   r   r*  r   r  r  r   r  r'  r  r   r  r+  r   rk   r   )r  r   rk   r  )r   r   r   r   rz  r   r  r  r   r  rk   r
   )r  r
   rk   r   )r  r
   r  r   rk   r
   )r  r
   r;  r   r<  r  rk   r   )r  r
   r;  r   rk   r   )r  r
   rk   r
   )r   r   rk   r
   )r  r   rk   r
   )r  r   r   r   r   r   rk   r
   )r   r   r@  r   rk   r
   )
ro   r   r   r   r  r   r  r   rk   r
   )r  r   r  r
   rk   r
   )r   r   ro   r   rk   r
   )
r  r   r   r   ro   r   r  r   rk   r
   )r  r   r  r
   rk   r
   )r9  r   r  r
   rk   r
   )r  r
   rk   r
   )
r  r   r   r   r   r
   r  rj   rk   r
   )r'  r   r>  rd   rk   r   )r*  r   r  r   rk   r;  )r   r   r   r   supports_code_blockssplits_long_messagesr  r  r   r  r  staticmethodr  r  rn  rz  r  r  r  r  r  r  r  r  r  r  
_EA_HEADER_EA_REASON_LABEL_EA_SMART_DENY_LINE_EA_CMD_BUDGETr  r  r  r  r  r%  r.  r1  r4  rS  rU  rY  r_  rl  r  r\  rs  ru  rw  rZ  rb  rd  rf  rh  rj  rk  rP  r`  rr  rv  r  r  r  r  r  r  r  r  r  r  r  r  r  r"  r$  r'  r-  r6  r8  r  r  rJ  rP  r^  rb  rQ  rd  ri  re  rX  r^  rq  r  r  r  r  r  r  r  rO  r  rh  r  r  rH  r  r  r  r  r  r  r  r  r  r  r  r  r  rh  r  r  r  r$  r/  rI  rJ  r  r  r  r[  r  r\  ri  rj  r  r|  r  r  r  r  r  r-  r  r  r  rI  rJ  r  r  r  r  r  r  r  rf  r  r  ry  r  r  r  r  r  r  rE  rG  r  r  r  rK  r  __classcell__)r  s   @r2   r  r    s)        """"""" <& <& <& <& <& <&| c
 c
 c
 \c
J9 9 9 9>
 
 
 
@   0Q Q Q
* * * *  5: 3 3 3 3 3 3jS- S- S- S-j   , , , ,
# # # #& & & &" #'-1A= A= A= A= A=R != != != != != !=J J%bN /-1 $""G= G= G= G= G=R 
 
 
 \
B 9;-1!= != != != !=F 
 
 
 \
$ 
 
 
 \
 ( ( ( \( "&"&-1
 
 
 
 
. "&#'"&-1
 
 
 
 
0 "&"&-1
 
 
 
 
. "&"&-1;= ;= ;= ;= ;=z     "&"&-1
 
 
 
 
 
 
@ "&"&-1 
  
  
  
  
  
  
D# # # #J   J J J J(! ! ! !FM6 M6 M6 M6^
 
 
 
:M M M M1 1 1 11 1 1 1@ @ @ @: : : :
 
 
 
$M M M MR R R R8#- #- #- #-J Z Z Z \Z   ? ? ? ?6 6 6 6p7 7 7 7~ 4[ 4[ 4[ 4[ 4[ 4[x 'L 'L 'L 'L 'L 'LR;@ ;@ ;@ ;@z
 
 
 
1@ 1@ 1@ 1@n   2
- 
- 
- 
- a a a a& & & &P   <& & & &H H H HH H H HK K K K2C C C C4: : : :
B 
B 
B 
B 
B( P7 P7 P7 P7 P7 P7d6 6 6 6
 
 
 
A A A A 
 
 
 \
. . . .*
 
 
 
? ? ? ?	6 	6 	6 	6> > > >&% &% &% &%P F F F \F    \    \h; h; h; h;T   .' ' ' 'Z	
 	
 	
 	
 
 
 
 \
$- $- $- $-L
 
 
 
 ; ; ; \;> > > >,6 6 6 6"V V V V@' ' ' '>    \       ,       >L L L Ld & & & \& l l l \l
    \ % % % \% x x x \x 7 7 7 \7    \    \    \ 	
 
 
 
 
 
B   " 	5 5 5 5 5 5n   :   L -10 0 0 0 0 0$ ? ? ? \? N N N \N- - - -^% % % % )M
 )M )M )M )M )M )MZ< < < <    .0 0 0 0
 
 
 
He He He He\"C "C "C "CHr r r r   * 49D D D D D D&       \ R "&#'%+`= `= `= `= `= `=D   ,3T 3T 3T 3Tj P P P \P A A A \A )-Y Y Y Y Y Y
 
 
 
2 2 2 22
 
 
 
@) ) ) )&
 
 
 
B? B? B? B?H+ + + + 0 0 0 \0
 6 6 6 \6
 	] 	] 	] \	] 9 9 9 \9 
 
 
 \
" Q Q Q \Q C C C \C Q Q Q \Q 
 
 
 \
" [ [ [ \[ C C C \C : : : \:
 ^_ g g g g g \g : : : \:
5 5 5 57 7 7 7 0 0 0 \0 0 0 0 0rN   r  r  c                N    t                               | t           d                   S Nr{   )_ONBOARD_ACCOUNTS_URLSr  r  s    r2   _accounts_base_urlr  }  s    !%%f.DX.NOOOrN   c                N    t                               | t           d                   S r  )_ONBOARD_OPEN_URLSr  r  s    r2   _onboard_open_base_urlr!    s    !!&*<X*FGGGrN   base_urlrv   r   c                >   |  t            }t          |                              d          }t          ||ddi          }	 t	          |t
                    5 }t          j        |                                	                    d                    cddd           S # 1 swxY w Y   dS # t          $ rf}|                                }|rJ	 t          j        |	                    d                    cY d}~S # t          t          j        f$ r |dw xY w d}~ww xY w)zPOST form-encoded data to the registration endpoint, return parsed JSON.

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

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

                    d          }|dk    r|sd}d	}|	
                    d          rO|	
                    d          r:|dk    rt                       |	d         |	d         ||

                    d          dS |	
                    dd          }|dv r1|dk    rt                       t                              d|           dS t          j        |           t          j                    |k     |dk    rt                       t                              d|           dS )zPoll until the user scans the QR code, or timeout/denial.

    Returns dict with app_id, app_secret, domain, open_id on success.
    Returns None on failure.
    Fr   pollob_app)r  r6  tprn   z#  Fetching configuration results...r   Tendflushr  r  	user_infotenant_brandr|   	client_idr+  r   )r   r   r  r   r  >   access_deniedexpired_tokenz [Feishu onboard] Registration %sNz)[Feishu onboard] Poll timed out after %ds)r  	monotonicr  r'  r   r  r`  r  r  printr  rg  r$  )r6  r9  r;  r  deadlinecurrent_domaindomain_switched
poll_countr"  r-  rF  rG  r  s                r2   _poll_registrationrQ    sP    ~)+HNOJ
.

X
%
%%n55	$X *0 0  CC
 '4#78 	 	 	Jx   H	 	a
??7RtLLLLL!^q  #2T**** GGK((.B	 }}^446!!/!#N"O 77; 	CGGO$<$< 	A~~k*!/2($==33	   $$666A~~NN=uEEE4 	
8] .

X
%
%` A~~
NN>	JJJ4s   A 0BBr   c                    t           dS 	 t          j                    }|                    |            |                    d           |                    d           dS # t
          $ r Y dS w xY w)zDTry to render a QR code in the terminal. Returns True if successful.NFT)fit)invert)_qrcode_modQRCodeadd_datamakeprint_asciirf  )r   qrs     r2   
_render_qrr[    s    u!!
C
D
d###t   uus   AA! !
A/.A/r   r   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      r2   	probe_botr_     s6      :fj&9996:v666rN   c                @   |dk    rt           nt          }t          j                                                            |                               |                              |                              t          j	        j
                                                  S )z9Build a lark Client for the given credentials and domain.r|   )r  r  r|   r  rX  r   r   r  r  r  r  rl  )r   r   r  
sdk_domains       r2   _build_onboard_clientrb  1  sk     && 0 0mJ		J			
			4=(	)	)	rN   rR  c                8   |                      d          dk    rd S |                      d          p*|                      di                                d          pi }|                     d          p|                     d          |                     d          dS )	Nr4  r   r  rR  rw  r   r   )r   r   )r  )rR  r  s     r2   rx  rx  >  s    xx1t
((5//
BTXXfb1155e<<
BCGGJ''>377:+>+>wwy))  rN   c                2   	 t          | ||          }t                                                              t          j                                      d                              t          j	        h          
                                }|                    |          }t          t          |dd          dd          }|dS t          t          j        |                    S # t           $ r&}t"                              d|           Y d}~dS d}~ww xY w)z#Probe bot info using lark_oapi SDK.ru  r  Nro   z%[Feishu onboard] SDK probe failed: %s)rb  r  rX  r@  r  rA  rB  rD  r  rE  rl  rh   r  rx  r`  r  rf  rg  rh  )r   r   r  r  rF  rG  ro   rw   s           r2   r]  r]  I  s    &vz6BB!![((S)**[/0122UWW 	 ~~c""'$t44iFF?4"4:g#6#6777   <cBBBttttts   CC&  C& &
D0DDc                Z   t          |          }	 t          j        | |d                              d          }t	          | d|ddi          }t          |t                    5 }t          j        |                                	                    d                    }ddd           n# 1 swxY w Y   |
                    d	          }|sdS t	          | d
d| dd          }	t          |	t                    5 }t          j        |                                	                    d                    }
ddd           n# 1 swxY w Y   t          |
          S # t          t          t          t          j        f$ r&}t                               d|           Y d}~dS d}~ww xY w)z@Fallback probe using raw HTTP (when lark_oapi is not installed).)r   r   r  z//open-apis/auth/v3/tenant_access_token/internalr}  r  r$  r  Ntenant_access_tokenru  zBearer )Authorizationr}  r{  z&[Feishu onboard] HTTP probe failed: %s)r!  r`  ra  r  r   r   r&  r  rB  r  r  rx  r   r  KeyErrorr  rg  rh  )r   r   r  r"  
token_data	token_reqrG  	token_resaccess_tokenbot_reqbot_resrw   s               r2   r^  r^  ^  sF   %f--HZ6 L LMMTTU\]]
HHH#%78
 
 
	
 Y(BCCC 	@t
499;;#5#5g#>#>??I	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ !}}%:;; 	4///!9<!9!9 2 
 
 
 W&@AAA 	>Tj!3!3G!<!<==G	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> #7+++gx)=>   =sCCCtttttsf   AE" (:B."E" .B22E" 5B26E" 0E" :E<E" EE" EE" ""F*F%%F*initial_domaintimeout_secondsrp  rq  c                    	 t          | |          S # t          t          t          t          j        f$ r&}t                              d|           Y d}~dS d}~ww xY w)a  Run the Feishu / Lark scan-to-create QR registration flow.

    Returns on success::

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

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

  r-  zH  Tip: pip install qrcode  to display a scannable QR code here next timer6  r9  r;  )r6  r9  r;  r  Nr   r   r  r   r   )rL  r/  r=  r[  rQ  minr_  r  )rp  rq  r1  r<  rE  bot_infos         r2   rs  rs    si    

,"DAAAA~&&&//E	(OOO	GGG8_F& ZS6SSTTTTPfPPPQQQXYYY	GGG-(z"eK(/::	  F  t )6,+?AQRRH %%\\*55z (] ; ;}!z $}MrN   >   r5   r6   r7   r8   r9   >   rB   rC   rE   rF   rG   rA   >   r;   r<   r=   r>   r?   r@   )r  media_filesforce_documentc               l  K   t          j        t                     d{V sddiS |pg }	 t          |           }t	          |dd          }|dk    rt
          nt          }|                    |          |_        |rd|ind}	d}
|	                                r1|
                    |||	           d{V }
|
j        sdd	|
j         iS |D ]:\  }}t          j                            |          s	dd
| ic S t          j                            |          d                                         }|t$          v r|                    |||	           d{V }
n|t(          v r|                    |||	           d{V }
np|t,          v r!|r|                    |||	           d{V }
nF|t0          v r|                    |||	           d{V }
n|                    |||	           d{V }
|
j        sdd|
j         ic S <|
ddiS dd||
j        dS # t6          $ r}dd	| icY d}~S d}~ww xY 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.
    Nr  zPFeishu dependencies not installed. Run `hermes setup` to install Feishu support.r5  r{   r|   r  r  zFeishu send failed: zMedia file not found: rn   zFeishu media send failed: zANo deliverable text or media remained after processing MEDIA tagsT)r  r  r   r  )rq   r  r  r  r  r  r  r  r  r/  r  r  r  r-   r?  r@  splitextr  _MIGRATION_IMAGE_EXTSrS  _MIGRATION_VIDEO_EXTSr4  _MIGRATION_VOICE_EXTSr.  _MIGRATION_AUDIO_EXTSr1  r  rf  )pconfigr   rO   r  rx  ry  rY  r   r  r   last_result
media_pathis_voicerK   es                  r2   _standalone_sendr    s5       "?33333333 mkll#K'5((g~x@@"-"7"7[!44V<</8BK++d==?? 	M 'Wg Q QQQQQQQK& M!K8I!K!KLL$/ 	S 	S J7>>*-- H!F*!F!FGGGG'"":..q17799C+++$+$;$;GZZb$;$c$ccccccc---$+$6$6w
U]$6$^$^^^^^^^---(-$+$6$6w
U]$6$^$^^^^^^^---$+$6$6w
U]$6$^$^^^^^^^$+$9$9':X`$9$a$aaaaaaa& S!Qk>O!Q!QRRRRS `aa %0	
 
 	
  5 5 533344444445s7   BH /H 8DH H H 
H3"H.(H3.H3c                 8   ddl m} 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dS  |dddgd          }d}d
}|dk    r\	 t                      }n<# t          $ r  |	d           Y dS t           $ r} |	d|            Y d}~nd}~ww xY w|rd}n |d           |s |d            |d            |dd
          }|s |	d           dS  |dd          }|s |	d           dS  |dddgd          }|dk    rdnd}d}	 t#          |||          }|r&|                    d           } |d!|pd"            n |	d#           n%# t           $ r} |	d$|            Y d}~nd}~ww xY w|||d|d%}|d&         }|d'         }|                    d(d          }|                    d)          }|                    d           } |d|            |d|            |d*|           |rd+}n@ |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<           ns|dk    r$ |d7d=            |d9d:            |	d>           nI |d7d8           |pd:} |d?|d
                              d@d:          } |d9|            |dA            |dBdCdDgd          }|dk    r |dEdF            |dG           n |dEdH            |dI            |dJ            |dKd
                                          }|r |dL|            |dM|            n |dL          r |dN            |dO            |dP|             |dQ|            |r |dR|            dS dS )Su   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remove_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)rn   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)r  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 IDsrW  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).rd  zGroup chats disabled.zLLeave blank to clear a previously saved home channel (cron / notifications).z/Home chat ID (optional, for cron/notifications)FEISHU_HOME_CHANNELzHome channel set to zHome channel cleared.u   🪽 Feishu / Lark configured!zApp ID: zDomain: zBot: )hermes_cli.configr  r  r  hermes_cli.setupr  hermes_cli.cli_outputr  r  r  r  r  r  r  rt  KeyboardInterruptrf  r_  r  r0  r/  )r  r  r  r  r  r  r  r  r  r  r  existing_app_idexisting_secret
method_idxcredentialsused_qrrw   r   r   
domain_idxr  r   rw  r   r   mode_idx
access_idxdefault_allowr   	group_idxhome_channels                                  r2   interactive_setupr    sT    RQQQQQQQQQ......                  L!!!#mO44O#m$788O ? <===}95AA 	F5J;	
 	
 J KGQ	<%--KK  	 	 	M:;;;FF 	< 	< 	<M:S::;;;;;;;;	< 	SGGJQRRR 

\]]]
XYYY5111 	MSTTTFVL4888
 	MWXXXF"]8.>@V-WYZ[[
%??	E V<<H \#<<
33Vx?T9VVWWWWZ[[[ 	E 	E 	EMCcCCDDDDDDDD	E $ 
 
 "F\*J__Xx00Fooi((Gz**HN?F+++N&
333N?F+++ k% =B> 
 
 (01}}))+i''JHIIIJfgggJijjjN+_=== 20h001113	
 	
 	

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

'#r

 	 	-y999())),B!	
 	
 I A~~,f555
BCCCC,j999
*+++J	"   6KV[\\\bbddL 0,l;;;;\;;<<<<122 	0J.///M2333J"&""###J"&""### '
%8%%&&&&&' 's7   ;B
 
C"	C+B>>CAF 
F)F$$F)yaml_cfg
feishu_cfgdict | Nonec                    d|v rHt          j        d          s4t          |d                                                   t           j        d<   dS )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)r  r  s     r2   _apply_yaml_configr    sJ     z!!")4G*H*H!*-j.F*G*G*M*M*O*O
&'4rN   c                l    t          | di           pi }t          |                    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     r2   _is_connectedr    s4     FGR((.BE		(##$$$rN   c                     t          |           S )zDFactory wrapper that constructs FeishuAdapter from a PlatformConfig.)r  )r  s    r2   _build_adapterr    s       rN   c                    |                      ddt          t          t          t          ddgdt          t
          dddt          d	d
d           dS )u:   Plugin entry point — called by the Hermes plugin system.r{   r  r	  r
  z-Run `hermes setup` to install Feishu support.r  rW  r  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_platformr  r  r  r  r  r  )ctxs    r2   registerr    sb    &*"%%':;D"/0.2-!#      rN   r   )rh   r
   ri   rj   rk   rl   )r   r
   rk   r   )r   r
   rk   r   )r   r   rk   r   )r   r
   rk   r   )r  r  r  r   rk   r   )r+  r   rk   r   )r2  r   rk   r   )Nr   )r   r
   r0   r   rR  rj   rk   r   )r   )r   r
   r0   rj   rR  rj   rk   rj   r  )ro   r   rk   rd  )rz  r
   ry  r{  rk   r   )rz  r
   rk   r   )r  r
   rk   r   )
r2  r
   r   r   r   r   ry  r{  rk   r   )
r   r
   r   r   r   r   ry  r{  rk   r   )
r   r   r  r   r   r  r  r   rk   r   )r  r   rk   r   )rz  r   rk   r   )r   r   rz  r   rk   r   )rz  r   rk   r   )rz  r
   rk   r   )r   r
   r  r   rk   r   )rz  r   r   r   rk   r   )r   r   rk   r   )rz  r
   rk   r   )rz  r
   r  r%  rk   r   )r   r
   )r  r
   rk   r   )r   r   ry  r{  rk   r   )r  r   rk   r   )r:  r
   rk   r;  )r   r  r  r   rk   rB  )r   rF  rk   r   )r   r   r   rF  rk   r   )rX  r
   rY  r
   rk   rh  r  )r  r   rk   r   )r"  r   rv   rd   rk   r   )r{   )r  r   rk   rh  )r  r   rk   r   )
r6  r   r9  rj   r;  rj   r  r   rk   r>  )r   r   rk   r   )r   r   r   r   r  r   rk   r>  )r   r   r   r   r  r   rk   r
   )rR  r   rk   r>  )rp  r   rq  rj   rk   r>  ro  )r  r   r  r   rk   r  (  r   
__future__r   rq   r  concurrent.futuresrt  r  r  r  r`  r  r  r-   r$  r  r  r  r   dataclassesr   r   r   pathlibr   typesr	   typingr
   r   r   r   r   r   urllib.errorr   r   urllib.parser   urllib.requestr   r   aiohttpr   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  gateway.configr   r   r=  r   r   r   r   r   r   r   r   r    r!   gateway.statusr"   r#   hermes_constantsr$   utilsr%   r&   r'   agent.secret_scoper(   r,   r)   r+   r3   	getLoggerr   rg  compilerP  r  _MARKDOWN_TABLE_RErO  rs  rq  _MENTION_REr4  
IGNORECASEr  r  r  r  r  r  rF  r  r  r  r  r  r  r  r  r&  r'  r(  r)  r%  r*  r+  r,  r  r0  r  r  r  r  r  r2  r1  r  re   r   rg   rx   _FEISHU_BOT_MSG_TRACK_SIZEr   r  r&  r+  r  rK  r  r   r%  r&  r  r  r  r  r  r!  r  r  r2  rN  rO  r0  r  r  r   r   r   r   r   r   r   r   RejectReasonr  r  r  r  r  r*  r1  r>  rD  rQ  rW  rZ  rc  r_  r  r  r  r  r~  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rA  r  rJ  rW  r  r  r  r  r  r!  r'  r/  r=  rQ  qrcoderU  rT  r[  r_  rb  rx  r]  r^  rt  rs  r|  r}  r  r~  r  r  r  r  r  r  rI   rN   r2   <module>r     s  - - -^ # " " " " "                      				 				       # # # # # # ( ( ( ( ( ( ( (             ! ! ! ! ! ! ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? , , , , , , , , " " " " " " + + + + + + + +NNN   G
CCC   JJJ
                
"   "IN$$ 't3 "$.  3 3 3 3 3 3 3 3                        D C C C C C C C , , , , , , 7 7 7 7 7 7 7 7 7 7 J J J J J J ? ? ? ? ? ?/ / / /( 
	8	$	$ BJ L!  &  RZ 7FF BJ9:: $"*%9:: %2:k22 bj''L))%2:&UWYWdee 
 GFF WWW MMM UU4R4L4R4T4TUUU % # "('!2 "B"B"B    $   ( $' !#$   $ %( "  #  ) 
 ) ") !0 &( #!$  $ ') $$& !&1 #(/ %  	( (      %$	' '         ! (y&&)9::   ( &  *. &"%  +,  
 '(   2   + 2 * 3  ) 
 ( 'RZ(DEE $"*]33 #)$TUU $9%:;; F##   , $                $        $5 5 5 5 5 5 5 5  $G G G G G G G G $; ; ; ; ; ; ; ; $!! !! !! !! !! !! !! !!H + + + + + + + + 9 9 9 9 9 9 9 9 @ @ @ @
   (9 9 9 9: : : :' ' ' '# # # #B B B B   ,9 9 9 9   $6 6 6 6 61 1 1 1 1	 	 	 	.6 .6 .6 .6h ;?! ! ! ! ! !H, , , ,   $	 	 	 	  ;?	M; M; M; M; M;h ;?	    J )-0022FN FN FN FN FN FNRG G G G   *   8   B#" #" #" #"L@ @ @ @! ! ! !&& & & &Rd d d d_ _ _ _
	4 	4 	4 	4
 
 
 
) ) ) )
 
 
 
( ;?    *       (   ,? ? ? ?&* * * *Z<' <' <' <'~; ; ; ;|   y:0 y:0 y:0 y:0 y:0' y:0 y:0 y:0JvP P P PH H H H   .
 
 
 
 
    > D D D D D DN     Y   KKK   7 7 7 7"
 
 
 
      *   F #     8& & & &n CBB III JJJ )  ;5 ;5 ;5 ;5 ;5|m' m' m' m'`	 	 	 	% % % %! ! !
     s6   	
B 	B B $B) )B32B3T 	TT