
    (Gj                       d Z ddlZddlZddlZddlZddlZddlZddlZddlmZm	Z	 ddl
mZmZmZmZmZ 	 ddladdlma ddlmama dan## e$ r dadadada ed	d
ddd          aY nw xY w	 ddladan# e$ r dadaY nw xY w	 ddlmZmZ ddl mZ!mZ" ddl#mZ$ ddl%mZ& dZ'n# e$ r dZ'dZdZdZ!dZ"dZ$dZ&Y nw xY wddl(m)Z)m*Z* ddl+m,Z, ddl-m.Z.m/Z/m0Z0m1Z1  ej2        e3          Z4dZ5g dZ6dZ7 ej8        d          Z9dddZ:de;fdZ< G d de.          Z= G d dt*          rt           j>        ne?          Z@ddddd ZAd)d!ZBd)d"ZCd#eDd$eDdeDdz  fd%ZEde;fd&ZFd' ZGd)d(ZHdS )*u\  
DingTalk platform adapter using Stream Mode.

Uses dingtalk-stream SDK (>=0.20) for real-time message reception without webhooks.
Responses are sent via DingTalk's session webhook (markdown format).
Supports: text, images, audio, video, rich text, files, and group @mentions.

Requires:
    pip install "dingtalk-stream>=0.20" httpx
    DINGTALK_CLIENT_ID and DINGTALK_CLIENT_SECRET env vars

Configuration in config.yaml:
    platforms:
      dingtalk:
        enabled: true
        # Optional group-chat gating (mirrors Slack/Telegram/Discord):
        require_mention: true            # or DINGTALK_REQUIRE_MENTION env var
        # free_response_chats:           # conversations that skip require_mention
        #   - cidABC==
        # mention_patterns:              # regex wake-words (e.g. Chinese bot names)
        #   - "^小马"
        # allowed_users:                 # staff_id or sender_id list; "*" = any
        #   - "manager1234"
        extra:
          client_id: "your-app-key"      # or DINGTALK_CLIENT_ID env var
          client_secret: "your-secret"   # or DINGTALK_CLIENT_SECRET env var
    N)datetimetimezone)AnyDictListOptionalSetChatbotMessageCallbackMessage
AckMessageTFr       i  )	STATUS_OKSTATUS_SYSTEM_EXCEPTION)clientmodels)r   )PlatformPlatformConfig)MessageDeduplicator)BasePlatformAdapterMessageEventMessageType
SendResulti N  )      
      <   z%^https://(?:api|oapi)\.dingtalk\.com/imageaudio)picturevoicereturnc                  0   t           rt          s]	 ddlm}   | dd           n# t          $ r Y dS w xY w	 ddl}ddlm} ddlm}m	} ddl
}n# t          $ r Y dS w xY w|a|a|a|a	|a
d	a d	at          j        d
          rt          j        d          sdS d	S )zCheck if DingTalk dependencies are available and configured.

    Lazy-installs dingtalk-stream via ``tools.lazy_deps.ensure("platform.dingtalk")``
    on first call if not present.
    r   )ensurezplatform.dingtalkF)promptNr
   r   TDINGTALK_CLIENT_IDDINGTALK_CLIENT_SECRET)DINGTALK_STREAM_AVAILABLEHTTPX_AVAILABLEtools.lazy_depsr'   	Exceptiondingtalk_streamr   dingtalk_stream.framesr   r   httpxosgetenv)_lazy_ensure_ds_CM_CBM_AM_httpxs         H/home/rurouni/.hermes/hermes-agent/plugins/platforms/dingtalk/adapter.pycheck_dingtalk_requirementsr;   x   s$    % O 	>>>>>>L,U;;;;; 	 	 	55		))))======YYYYYYYY""""" 	 	 	55	
$(!9)** ")<T2U2U u4s   $ 
22A 
AAc                   l    e Zd ZdZeZedefd            Zedefd            Zde	f fdZ
ddd	edefd
ZdIdZdIdZdefdZdee         fdZdee         fdZdeej                 fdZdee         fdZdededefdZdddefdZdedefdZdddedededef
dZdIdZdeddfdZdeddfd Z	 	 	 	 dJd!Ze dddefd"            Z!dKd#Z"	 	 dLded$ed%e#e         d&e#e$ee%f                  de&f
d'Z'dMdeddfd(Z(	 	 	 dNded)ed*e#e         d%e#e         d&e#e$ee%f                  de&fd+Z)	 	 	 dNded,ed*e#e         d%e#e         d&e#e$ee%f                  de&fd-Z*	 	 	 	 dOded.ed*e#e         d/e#e         d%e#e         d&e#e$ee%f                  de&fd0Z+dede$ee%f         fd1Z,dede#e-ee.f                  fd2Z/d3d4dede%d$ed5ede#e&         f
d6Z0dd4ded7ed$ed5ede&f
d8Z1	 dPd9ed:ed$ed5eddf
d;Z2de#e         fd<Z3dd=d>ed?ed@edAeddf
dBZ4dJdCZ5dDedEed:edFeddf
dGZ6e dedefdH            Z7 xZ8S )QDingTalkAdaptera  DingTalk chatbot adapter using Stream Mode.

    The dingtalk-stream SDK maintains a long-lived WebSocket connection.
    Incoming messages arrive via a ChatbotHandler callback. Replies are
    sent via the incoming message's session_webhook URL using httpx.

    Features:
    - Text messages (plain + rich text)
    - Images, audio, video, files (via download codes)
    - Group chat @mention detection
    - Session webhook caching with expiry tracking
    - Markdown formatted replies
    r%   c                 8    t          | j        o| j                  S )zEdits only meaningful when AI Cards are configured.

        The gateway gates streaming cursor + edit behaviour on this flag,
        so we must reflect the actual adapter capability at runtime.
        bool_card_template_id	_card_sdkselfs    r:   SUPPORTS_MESSAGE_EDITINGz(DingTalkAdapter.SUPPORTS_MESSAGE_EDITING        D*=t~>>>    c                 8    t          | j        o| j                  S )u  AI Card lifecycle requires an explicit ``finalize=True`` edit
        to close the streaming indicator, even when the final content is
        identical to the last streamed update.  Enabled only when cards
        are configured — webhook-only DingTalk doesn't need it.
        r?   rC   s    r:   REQUIRES_EDIT_FINALIZEz&DingTalkAdapter.REQUIRES_EDIT_FINALIZE   rF   rG   configc                    t                                          |t          j                   |j        pi }|                    d          pt          j        dd          | _        |                    d          pt          j        dd          | _	        | 
                                | _        |                                 | _        d | _        d | _        d | _        d | _        d | _        |                    d          p| j        | _        t)          d          | _        i | _        i | _        |                    d	          | _        t3                      | _        i | _        t3                      | _        d S )
N	client_idr)    client_secretr*   
robot_code  )max_sizecard_template_id)super__init__r   DINGTALKextragetr2   r3   
_client_id_client_secret_compile_mention_patterns_mention_patterns_load_allowed_users_allowed_users_stream_client_stream_task_http_clientrB   
_robot_sdk_robot_coder   _dedup_session_webhooks_message_contextsrA   set_done_emoji_fired_streaming_cards	_bg_tasks)rD   rJ   rV   	__class__s      r:   rT   zDingTalkAdapter.__init__   sV   !2333"$yy55  
 ":
 :
 $)99_#=#= $
$bB
 B
 483Q3Q3S3S(,(@(@(B(B#'48;?(,)- %		, 7 7 J4? *4888=? 2405		:L0M0M ,/55 <> -0EErG   F)is_reconnectrk   c                n  K   t           s"t                              d| j                   dS t          s"t                              d| j                   dS | j        r| j        s"t                              d| j                   dS 	 ddlm} t          j
        d |                      | _        t          j        | j        | j                  }t          j        |          | _        t           r| j        rzt%          j                    }d	|_        d
|_        t-          j        |          | _        t3          j        |          | _        t                              d| j        | j                   nat           rZt%          j                    }d	|_        d
|_        t3          j        |          | _        t                              d| j                   t9          j                    }t=          | |          }| j                            t          j         j!        |           t9          j"        | #                                          | _$        | %                                 t                              d| j                   dS # tL          $ r,}t          '                    d| j        |           Y d}~dS d}~ww xY w)z$Connect to DingTalk via Stream Mode.zL[%s] dingtalk-stream not installed. Run: pip install 'dingtalk-stream>=0.20'Fz0[%s] httpx not installed. Run: pip install httpxz;[%s] DINGTALK_CLIENT_ID and DINGTALK_CLIENT_SECRET requiredr   )platform_httpx_limits      >@)timeoutlimitshttpscentralz+[%s] Card SDK initialized with template: %sz+[%s] Robot SDK initialized (media download)z[%s] Connected via Stream ModeTz[%s] Failed to connect: %sN)(r+   loggerwarningnamer,   rX   rY   %gateway.platforms._http_client_limitsrm   r1   AsyncClientr`   r/   
CredentialDingTalkStreamClientr^   CARD_SDK_AVAILABLErA   open_api_modelsConfigprotocol	region_iddingtalk_card_clientClientrB   dingtalk_robot_clientra   infoasyncioget_running_loop_IncomingHandlerregister_callback_handlerr   TOPICcreate_task_run_streamr_   _mark_connectedr.   error)rD   rk   rm   
credential
sdk_configloophandleres           r:   connectzDingTalkAdapter.connect   s     ( 	NN^	   5 	NNBDI   5 	d&9 	NNMty   5-	SSSSSS % 1%:%:%<%<! ! !D )3!4 J #2"Fz"R"RD " Vd&< V,355
&-
#'0
$!5!<Z!H!H"7">z"J"JAI*   
 $ V,355
&-
#'0
$"7">z"J"JI49UUU +--D&tT22G99.4g   !( 3D4D4D4F4F G GD  """KK8$)DDD4 	 	 	LL5ty!DDD55555	s   G6I> >
J4!J//J4Nc                 R  K   d}| j         r	 t                              d| j                   | j                                         d{V  nV# t          j        $ r Y dS t          $ r8}| j         sY d}~dS t          	                    d| j        |           Y d}~nd}~ww xY w| j         sdS t          t          |t          t                    dz
                     }t                              d| j        |           t          j        |           d{V  |dz  }| j         dS dS )z3Run the async stream client with auto-reconnection.r   z[%s] Starting stream client...Nz[%s] Stream client error: %s   z[%s] Reconnecting in %ds...)_runningrs   debugru   r^   startr   CancelledErrorr.   rt   RECONNECT_BACKOFFminlenr   sleep)rD   backoff_idxr   delays       r:   r   zDingTalkAdapter._run_stream4  ss     m 	M=tyIII)//1111111111)    M M M} FFFFF=ty!LLLLLLLLM
 = %c+s;L7M7MPQ7Q&R&RSEKK5ty%HHH-&&&&&&&&&1K# m 	 	 	 	 	s#   ?A B! 	B!)B6!BB!c                   K   d| _         |                                  | j        rt          | j        dd          nd}|T	 |                                 d{V  n8# t
          $ r+}t                              d| j        |           Y d}~nd}~ww xY w| j	        rt          | j        d          r6	 t          j        | j        j                   d{V  n# t
          $ r Y nw xY w| j	                                         	 t          j        | j	        d           d{V  nA# t          j        t          j        f$ r# t                              d| j                   Y nw xY wd| _	        | j        r`t%          | j                  D ]}|                                 t          j        | j        d	d
i d{V  | j                                         t%          | j                  D ]W}	 |                     |           d{V  # t
          $ r,}t                              d| j        ||           Y d}~Pd}~ww xY w| j        r&| j                                         d{V  d| _        d| _        | j                                         | j                                         | j                                         | j                                         | j                                         t                              d| j                   dS )zDisconnect from DingTalk.F	websocketNz1[%s] websocket close during disconnect failed: %scloseg      @ro   z7[%s] stream task did not exit cleanly during disconnectreturn_exceptionsTz?[%s] Failed to finalize streaming card on disconnect for %s: %sz[%s] Disconnected)r   _mark_disconnectedr^   getattrr   r.   rs   r   ru   r_   hasattrr   	to_threadcancelwait_forr   TimeoutErrorri   listgatherclearrh   _close_streaming_siblingsr`   acloserd   re   rg   rc   r   )rD   r   r   task_chat_id_excs         r:   
disconnectzDingTalkAdapter.disconnectJ  s     !!!
 HLGZdGD/dCCC`d	 `oo'''''''''' ` ` `PRVR[]^________`  	% t*G44 !+D,?,EFFFFFFFFFF    D $$&&&c&t'8#FFFFFFFFFFF*G,@A c c cVX\Xabbbbbc $D > 	#T^,,  .$.IDIIIIIIIIIN  """ T233 	 	H44X>>>>>>>>>>   UIx         	%#**,,,,,,,,, $D"$$&&&$$&&&##%%%$$&&&'33333sT    A 
B%!BB0$C 
C"!C"?!D! !;EE'H
H9"H44H9c                 
   | j         j                            d          }|:t          |t                    r|                                dv S t          |          S t          j        dd                                          dv S )zBReturn whether group chats should require an explicit bot trigger.require_mentionN>   1onyestrueDINGTALK_REQUIRE_MENTIONfalse)	rJ   rV   rW   
isinstancestrlowerr@   r2   r3   )rD   
configureds     r:   _dingtalk_require_mentionz)DingTalkAdapter._dingtalk_require_mention  s    [&**+<==
!*c** H!''))-GGG
###y3W==CCEEIcccrG   c                    | j         j                            d          }|t          j        dd          }t          |t                    rd |D             S d t          |                              d          D             S )Nfree_response_chatsDINGTALK_FREE_RESPONSE_CHATSrM   c                     h | ]D}t          |                                          #t          |                                          ES r   r   strip.0parts     r:   	<setcomp>z@DingTalkAdapter._dingtalk_free_response_chats.<locals>.<setcomp>  =    KKK$T9J9JKCIIOO%%KKKrG   c                 ^    h | ]*}|                                 |                                 +S r   r   r   s     r:   r   z@DingTalkAdapter._dingtalk_free_response_chats.<locals>.<setcomp>  -    MMM

M

MMMrG   ,	rJ   rV   rW   r2   r3   r   r   r   splitrD   raws     r:   _dingtalk_free_response_chatsz-DingTalkAdapter._dingtalk_free_response_chats  s    k##$9::;):B??Cc4   	LKK#KKKKMMS)<)<MMMMrG   c                 $   | j         j        r| j         j                            d          nd}|t          j        dd          }t          |t                    rd |D             S d t          |                              d          D             S )u(  Return the whitelist of group chat IDs the bot will respond in.

        When non-empty, group messages from chats NOT in this set are silently
        ignored — even if the bot is @mentioned.  DMs are never filtered.
        Empty set means no restriction (fully backward compatible).
        allowed_chatsNDINGTALK_ALLOWED_CHATSrM   c                     h | ]D}t          |                                          #t          |                                          ES r   r   r   s     r:   r   z:DingTalkAdapter._dingtalk_allowed_chats.<locals>.<setcomp>  r   rG   c                 ^    h | ]*}|                                 |                                 +S r   r   r   s     r:   r   z:DingTalkAdapter._dingtalk_allowed_chats.<locals>.<setcomp>  r   rG   r   r   r   s     r:   _dingtalk_allowed_chatsz'DingTalkAdapter._dingtalk_allowed_chats  s     9=8ISdk##O444t;)4b99Cc4   	LKK#KKKKMMS)<)<MMMMrG   c                    | j         j        r| j         j                            d          nd}|t          j        dd                                          }|rg	 t          j        |          }nO# t          $ rB d |	                                D             }|sd |
                    d          D             }Y nw xY w|}|g S t          |t                    r|g}t          |t                    s5t                              d| j        t#          |          j                   g S g }|D ]}t          |t                    r|                                s,	 |                    t)          j        |t(          j                             `# t(          j        $ r,}t                              d	| j        ||           Y d}~d}~ww xY w|r.t                              d
| j        t3          |                     |S )z=Compile optional regex wake-word patterns for group triggers.mention_patternsNDINGTALK_MENTION_PATTERNSrM   c                 ^    g | ]*}|                                 |                                 +S r   r   r   s     r:   
<listcomp>z=DingTalkAdapter._compile_mention_patterns.<locals>.<listcomp>  s-    XXXt4::<<XdjjllXXXrG   c                 ^    g | ]*}|                                 |                                 +S r   r   r   s     r:   r   z=DingTalkAdapter._compile_mention_patterns.<locals>.<listcomp>  s-    !Z!Z!Z4TZZ\\!Z$**,,!Z!Z!ZrG   r   z?[%s] dingtalk mention_patterns must be a list or string; got %sz,[%s] Invalid DingTalk mention pattern %r: %sz*[%s] Loaded %d DingTalk mention pattern(s))rJ   rV   rW   r2   r3   r   jsonloadsr.   
splitlinesr   r   r   r   rs   rt   ru   type__name__appendrecompile
IGNORECASEr   r   r   )rD   patternsr   loadedcompiledpatternexcs          r:   rZ   z)DingTalkAdapter._compile_mention_patterns  s#   @D@Q[4;$(();<<<W[)7<<BBDDC "[!Z__FF  [ [ [XXs~~7G7GXXXF! [!Z!Z399S>>!Z!Z!Z[ "Ih$$ 	" zH(D)) 	NNQ	X'  
 I%' 	h 	hGgs++ 7==?? h
7BM B BCCCC8 h h hMtyZacfggggggggh 	`KKDdiQTU]Q^Q^___s+   A/ /A	B;:B;2F

G"G  Gc                 >   | j         j        r| j         j                            d          nd}|t          j        dd          }t          |t                    rd |D             }n,d t          |                              d          D             }d |D             S )	zLoad allowed-users list from config.extra or env var.

        IDs are matched case-insensitively against the sender's ``staff_id`` and
        ``sender_id``. A wildcard ``*`` disables the check.
        allowed_usersNDINGTALK_ALLOWED_USERSrM   c                     g | ]D}t          |                                          #t          |                                          ES r   r   r   s     r:   r   z7DingTalkAdapter._load_allowed_users.<locals>.<listcomp>  s=    LLL4#d))//:K:KLSYY__&&LLLrG   c                 ^    g | ]*}|                                 |                                 +S r   r   r   s     r:   r   z7DingTalkAdapter._load_allowed_users.<locals>.<listcomp>  s-    RRRdTZZ\\RTZZ\\RRRrG   r   c                 6    h | ]}|                                 S r   r   )r   items     r:   r   z6DingTalkAdapter._load_allowed_users.<locals>.<setcomp>  s     ///

///rG   r   )rD   r   itemss      r:   r\   z#DingTalkAdapter._load_allowed_users  s     9=8ISdk##O444t;)4b99Cc4   	SLL3LLLEERRc#hhnnS.A.ARRRE//////rG   	sender_idsender_staff_idc                     | j         r	d| j         v rdS |pd                                |pd                                h}|                    d           t          || j         z            S )N*TrM   )r]   r   discardr@   )rD   r   r   
candidatess       r:   _is_user_allowedz DingTalkAdapter._is_user_allowed  st    " 	cT-@&@&@4 B--///2GR1N1N1P1PQ
2J!44555rG   messager   c                 >    t          t          |dd                    S )zTrue if the bot was @-mentioned in a group message.

        dingtalk-stream sets ``is_in_at_list`` on the incoming ChatbotMessage
        when the bot is addressed via @-mention.
        is_in_at_listF)r@   r   )rD   r   s     r:   _message_mentions_botz%DingTalkAdapter._message_mentions_bot  s     GG_e<<===rG   textc                 Z    r| j         sdS t          fd| j         D                       S )NFc              3   B   K   | ]}|                               V  d S N)search)r   r   r  s     r:   	<genexpr>zDDingTalkAdapter._message_matches_mention_patterns.<locals>.<genexpr>  s/      NNG7>>$''NNNNNNrG   )r[   any)rD   r  s    `r:   !_message_matches_mention_patternsz1DingTalkAdapter._message_matches_mention_patterns  s@     	41 	5NNNNt7MNNNNNNrG   is_groupchat_idc                     |sdS |                                  }|r|r||vrdS |r||                                 v rdS |                                 sdS |                     |          rdS |                     |          S )u  Apply DingTalk group trigger rules.

        DMs remain unrestricted (subject to ``allowed_users`` which is enforced
        earlier). Group messages are accepted when:
        - the chat passes the ``allowed_chats`` whitelist (when set)
        - the chat is explicitly allowlisted in ``free_response_chats``
        - ``require_mention`` is disabled
        - the bot is @mentioned (``is_in_at_list``)
        - the text matches a configured regex wake-word pattern

        When ``allowed_chats`` is non-empty, it acts as a hard gate — messages
        from any group chat not in the list are ignored regardless of the
        other rules.
        TF)r   r   r   r  r	  )rD   r   r  r
  r  alloweds         r:   _should_process_messagez'DingTalkAdapter._should_process_message  s      	4..00 	w 	7'#9#95 	w$"D"D"F"FFF4--// 	4%%g.. 	455d;;;rG   c                     t          j        |          }| j                            |           |                    | j        j                   dS )z;Start a fire-and-forget coroutine and track it for cleanup.N)r   r   ri   addadd_done_callbackr   )rD   coror   s      r:   	_spawn_bgzDingTalkAdapter._spawn_bg  sG    "4((4   t~566666rG   c                   K   | j                             |d          }|sdS |                                  d{V }|sdS t          |                                          D ]\  }}	 |                     |||d           d{V  t                              d| j        |           G# t          $ r,}t                              d| j        ||           Y d}~xd}~ww xY wdS )a  Finalize any previously-open streaming cards for this chat.

        Called at the start of every ``send()`` so lingering tool-progress
        cards that were reopened by ``edit_message(finalize=False)`` get
        cleanly closed before the next card is created.  Without this,
        tool-progress cards stay stuck in streaming state after the agent
        moves on (there is no explicit "turn end" signal from the gateway).
        NTfinalizez[%s] AI Card sibling closed: %sz$[%s] Sibling close failed for %s: %s)
rh   pop_get_access_tokenr   r   _stream_card_contentrs   r   ru   r.   )rD   r  cardstokenout_track_idlast_contentr   s          r:   r   z)DingTalkAdapter._close_streaming_siblings  sW      %))'488 	F,,........ 	F*.u{{}}*=*= 	 	&L,// % 0          5I|       :I|Q       	 	s   'A B((
C2"CCc                 4    | j         v rdS  j                             |            j                            |          }|sdS t	          |dd          pdt	          |dd          pdrsdS d fd}                      |                       dS )u   Swap 🤔Thinking → 🥳Done on the original user message.

        Idempotent per chat_id — safe to call from segment-break flushes
        and final-done flushes without double-firing.
        N
message_idrM   conversation_idr%   c                     K                         dd           d {V                        dd           d {V  d S )N   🤔ThinkingTrecallu   🥳DoneF)_send_emotion)r   msg_idrD   s   r:   _swapz2DingTalkAdapter._fire_done_reaction.<locals>._swapF  s      $$ %          $$E %           rG   r%   N)rg   r  re   rW   r   r  )rD   r  msgr'  r   r&  s   `   @@r:   _fire_done_reactionz#DingTalkAdapter._fire_done_reaction5  s     d,,,F""7+++$((11 	FlB//52!#'8"==C 	? 	F	 	 	 	 	 	 	 	 	uuwwrG   c           
        K   t          |dd          pt          j                    j        }| j                            |          r#t                              d| j        |           dS t          |dd          pd}t          |dd          }t          |          dk    }t          |d	d          pd}t          |d
d          p|}t          |dd          pd}|p|}	|rdnd}
| 
                    ||          s$t                              d| j        ||           dS |                     |          pd}|                     ||||	          s$t                              d| j        ||	           dS |	r$|| j        |	<   | j                            |	           t          |dd          pd}t          |dd          pd}|r|	rt                               |          rtt%          | j                  t(          k    rK	 | j                            t-          t/          | j                                       n# t0          $ r Y nw xY w||f| j        |	<   |                     |           d{V  |                     |          }|                     |          \  }}}|s$|s"t                              d| j                   dS |                     |	t          |dd          |
|||r|nd          }t          |dd          }	 |r0t9          j        t=          |          dz  t>          j                   nt9          j!        t>          j                   }n<# tD          tF          tH          f$ r" t9          j!        t>          j                   }Y nw xY wtK          ||||||||          }t                              d| j        ||	r
|	dd         nd|r
|dd         nd           | &                    |           d{V  dS )z-Process an incoming DingTalk chatbot message.r  Nz#[%s] Duplicate message %s, skippingr   rM   conversation_typer   2r   sender_nickr   groupdmzH[%s] Dropping message from non-allowlisted user staff_id=%s sender_id=%szM[%s] Dropping group message that failed mention gate message_id=%s chat_id=%ssession_webhooksession_webhook_expired_timer   z[%s] Empty message, skippingconversation_title)r  	chat_name	chat_typeuser_id	user_nameuser_id_alt	create_atrP   tz)r  message_typesourcer  raw_message
media_urlsmedia_types	timestampz[%s] Message from %s in %s: %s   ?P   z(media))'r   uuiduuid4hexrc   is_duplicaters   r   ru   r   r   _extract_textr  re   rg   r   _DINGTALK_WEBHOOK_REmatchr   rd   _SESSION_WEBHOOKS_MAXr  nextiterStopIteration_resolve_media_codes_extract_mediabuild_sourcer   fromtimestampintr   utcnow
ValueErrorOSError	TypeErrorr   handle_message)rD   r   r&  r   r,  r
  r   r.  r   r  r5  _early_textr1  r2  r  msg_typer?  r@  r=  r9  rA  events                         r:   _on_messagezDingTalkAdapter._on_messageR  s     
 ,55I9I;##F++ 	LL>	6RRRF "'+<bAAGR#G-@#FF())S0G["55;	g}b99FY!'+<bAAGR!.Y'1GGT	 $$Y@@ 	LLZ	?I   F ((117R++G[(GTT 	LL_	67   F  	4.5D"7+"**7333 "'+<dCCIrG;Q??D1 	%  		w 		+?+E+Eo+V+V 		4)**.CCC*..tD9O4P4P/Q/QRRRR$   D  ,/D"7+ ''000000000 !!'** -1,?,?,H,H)*k 	J 	LL7CCCF""g';TBB!+:D # 
 
 G[$77		6 3&s9~~'<NNNN\X\222 I
 GY/ 	6 	6 	6 555III	6 !!#	
 	
 	
 	,I#,GCRCLL,D"II9	
 	
 	
 !!%(((((((((((s%   89H2 2
H?>H?=AM 6NNc                 P   t          | dd          pd}t          |d          r|j        pd                                }n_t	          |t
                    r)|                    dd                                          }n!t          |                                          }|st          | dd          pt          | dd          }|rt          |dd          p|}t	          |t                    rg }|D ]}t	          |t
                    rD|                    d          p|                    d          pd}|r|	                    |           [t          |d          r!|j
        r|	                    |j
                   d                    |                                          }|S )	u=  Extract plain text from a DingTalk chatbot message.

        Handles both legacy and current dingtalk-stream SDK payload shapes:
          * legacy: ``message.text`` was a dict ``{"content": "..."}``
          * >= 0.20: ``message.text`` is a ``TextContent`` dataclass whose
            ``__str__`` returns ``"TextContent(content=...)"`` — never fall
            back to ``str(text)`` without extracting ``.content`` first.
          * rich text moved from ``message.rich_text`` (list) to
            ``message.rich_text_content.rich_text_list`` (list of dicts).
        r  NrM   contentrich_text_content	rich_textrich_text_list )r   r   r`  r   r   dictrW   r   r   r   r  join)r   r  r`  rb  	rich_listpartsr   ts           r:   rI  zDingTalkAdapter._extract_text  s    w--3 4## 	(|)r0022GGd## 	(hhy"--3355GG$iioo''G 	6)<dCC wdH HI  6#I/?FFS)	i.. 	6E ) 4 4%dD11 4 $ 0 0 MDHHY4G4G M2A  0 %Q$T622 4ty 4!LL333!hhuoo3355G rG   c                 @   t           j        }g }g }t          |dd          }|rIt          |dd          }|r6|                    |           |                    d           t           j        }t          |dd          pt          |dd          }|rt          |dd          p|}t          |t                    r|D ]}}	t          |	t                    rd|	                    d          p|	                    d          pd	}
|	                    d
d	          }|
rt                              |d          }|                    |
           |dk    r2|                    d           |t           j        k    rt           j        }|dk    rG|                    d           |t           j        k    r |dk    rt           j
        }t           j        }|dk    r3|                    d           |t           j        k    rt           j        }L|                    d           |t           j        k    rt           j        }t          |dd	          pd	}|dk    r|st           j        }n7|dk    r1t          d |D                       rt           j        nt           j        }|||fS )zMExtract media info from message. Returns (MessageType, [urls], [mime_types]).image_contentNdownload_coder!   ra  rb  rc  downloadCoderM   r   filer"   r$   videozapplication/octet-streamr<  r#   richTextc              3      K   | ]}d |v V  	dS )r!   Nr   )r   ri  s     r:   r  z1DingTalkAdapter._extract_media.<locals>.<genexpr>4  s&      99w!|999999rG   )r   TEXTr   r   PHOTOr   r   re  rW   DINGTALK_TYPE_MAPPINGVOICEAUDIOVIDEODOCUMENTr  )rD   r   r\  r?  r@  rk  rl  rb  rg  r   dl_code	item_typemappedmsg_type_strs                 r:   rQ  zDingTalkAdapter._extract_media  s   #
  $?? 	-#M?DIIM -!!-000""7+++&, G%8$?? 
7[$D
 D
	  "	D	+;TBBOiI)T**  D% D DD!$-- D HH^44W8Q8QWUW   %)HHVR$8$8	" D%:%>%>y&%Q%QF&--g666%00 + 2 27 ; ; ;#+{/?#?#?/:/@H!'7!2!2 + 2 27 ; ; ;#+{/?#?#?
 (1G';';3>3D3>3D!'7!2!2 + 2 27 ; ; ;#+{/?#?#?/:/@H + 2 23M N N N#+{/?#?#?/:/CHw;;Ar9$$Z$"(HHZ'' 99[99999&!! %  [00rG   r`  reply_tometadatac           	      :  K   |pi }t                               d| j        |t          | j        o| j                             |                    d          }|sN|                     |          }|s2t                               d| j        |           t          dd          S |\  }}| j
        st          dd          S | j                            |          }|du}	| j        r|r| j        r|                     |           d{V  |                     ||||		           d{V }
|
rD|
j        r=|	r|                     |           n#|| j                            |i           |
j        <   |
S t                               d
| j                   t                               d| j                   |                     |d| j                           }dd|dd}	 | j
                            ||d           d{V }|j        dk     rF|	r|                     |           t          dt/          j                    j        dd                   S |j        }t                               d| j        |j        |dd                    t          dd|j         d|dd                    S # t6          j        $ r t          dd          cY S t:          $ rI}t                               d| j        |           t          dt?          |                    cY d}~S d}~ww xY w)z3Send a markdown reply via DingTalk session webhook.z&[%s] send() chat_id=%s card_enabled=%sr1  z,[%s] No valid session_webhook for chat_id=%sFzJNo valid session_webhook available. Reply must follow an incoming message.successr   zHTTP client not initializedNr  z1[%s] AI Card send failed, falling back to webhookz[%s] Sending via webhookmarkdownHermes)titler  )msgtyper  g      .@)r   ro   i,  T   r  r  z[%s] Send failed HTTP %d: %sr   zHTTP z: z#Timeout sending message to DingTalkz[%s] Send error: %s) rs   r   ru   r@   rA   rB   rW   _get_valid_webhookrt   r   r`   re   r   _create_and_stream_cardr  r*  rh   
setdefaultr  _normalize_markdownMAX_MESSAGE_LENGTHpoststatus_coderE  rF  rG  r  r1   TimeoutExceptionr.   r   r   )rD   r  r`  r}  r~  r1  webhook_info_current_messageis_final_replyresult
normalizedpayloadrespbodyr   s                   r:   sendzDingTalkAdapter.send<  s      >r4I':DN;;		
 	
 	
 #,,'899 	.227;;L BIw   "!f    ".OQ  	Re3PQQQQ 044W== "- ! 	[o 	[$. 	[ 0099999999977'' 8        F  &. ! 
 ,,W5555   )44WbAA) NNNPTPYZZZ/;;;--g6O8O6O.PQQ
 ""*J??
 

	;*//gt 0        D #%% " 6,,W555!$4:<<;KCRC;PQQQQ9DNN.	4;KTRVSVRVZ   %MT-=%M%Mdsd%M%M    % 	 	 	%J       	; 	; 	;LL.	1===e3q66:::::::::	;s,   A3J& AJ& & L	L>LLLc                 
   K   dS )z,DingTalk does not support typing indicators.Nr   )rD   r  r~  s      r:   send_typingzDingTalkAdapter.send_typing  s      rG   	image_urlcaptionc                 f   K   d| d}|r| d| n|}|                      ||||           d{V S )aS  Send an image via DingTalk markdown.

        DingTalk's session webhook only supports text/markdown payloads, not
        native image/file attachments. For remote image URLs, render the image
        inline with markdown so the user still sees the image. Local files need
        OpenAPI media upload and are handled separately.
        z	![image]()z

)r  r`  r}  r~  N)r  )rD   r  r  r  r}  r~  image_blockr`  s           r:   
send_imagezDingTalkAdapter.send_image  sz       /)...3:KW//+///YY	  
 
 
 
 
 
 
 
 	
rG   
image_pathc                 (   K   t          dd          S )z@DingTalk webhook replies cannot send local image files directly.FzDingTalk session webhook replies do not support local image uploads. Only markdown/text replies are supported without OpenAPI media upload.r  r   )rD   r  r  r  r}  r~  kwargss          r:   send_image_filezDingTalkAdapter.send_image_file  s(       Y
 
 
 	
rG   	file_path	file_namec                 (   K   t          dd          S )zEDingTalk webhook replies cannot send local file attachments directly.FzDingTalk session webhook replies do not support local file attachments. Only markdown/text replies are supported without OpenAPI message send.r  r  )rD   r  r  r  r  r}  r~  r  s           r:   send_documentzDingTalkAdapter.send_document  s(       Y
 
 
 	
rG   c                 @   K   |d|                                 v rdnddS )z0Return basic info about a DingTalk conversation.r/  r0  )ru   r   r   )rD   r  s     r:   get_chat_infozDingTalkAdapter.get_chat_info  s3       &'--//99GGt
 
 	
rG   c                 .   | j                             |          }|sdS |\  }}|ro|dk    rit          t          j        t
          j                                                  dz            }d}||z   |k    r| j                             |d           dS |S )z@Get a valid (non-expired) session webhook for the given chat_id.Nr   r:  rP   i )	rd   rW   rT  r   rV  r   rU  rA  r  )rD   r  r   webhookexpired_time_msnow_mssafety_margin_mss          r:   r  z"DingTalkAdapter._get_valid_webhook  s    %))'22 	4#'  	22666@@BBTIJJF,((O;;&**7D999trG   Tr  r  c          
      .  K   	 |                                   d{V }|sdS dt          j                    j        dd          }t	          |dd          pd}t	          |dd          }t          |          dk    }	t	          |d	d          pd}
t          j                    }t          j	        | j
        |t          j        d
di          dt          j        d          t          j        d                    }t          j        |          }| j                            |||           d{V  |	r6d| }t          j        |d|t          j        | j                            }nT|
s"t(                              d| j                   dS d|
 }t          j        |d|t          j        d                    }t          j        |          }| j                            |||           d{V  |                     ||||           d{V  t(                              d| j        |rdnd|           t9          d|          S # t:          $ r>}t(                              d| j        |t=          j                               Y d}~dS d}~ww xY w) a  Create an AI Card, deliver it to the conversation, and stream initial content.

        Always called with ``finalize=True`` from ``send()`` (closed state).
        If the caller later issues ``edit_message(finalize=False)``, the
        DingTalk streaming_update API reopens the card into streaming
        state, and we track that in ``_streaming_cards`` for sibling
        cleanup on the next send.
        Nhermes_r  r   rM   r,  r   r-  r   r`  )card_param_mapSTREAMT)support_forward)rR   r  	card_datacallback_typeim_group_open_space_modelim_robot_open_space_modelx_acs_dingtalk_access_tokenzdtv1.card//IM_GROUP.r   )rO   )r  user_id_typeopen_space_idim_group_open_deliver_modelz4[%s] AI Card skipped: missing sender_staff_id for DMzdtv1.card//IM_ROBOT.IM_ROBOT)
space_type)r  r  r  im_robot_open_deliver_modelr  z[%s] AI Card %s: %szcreated+finalizedzcreated (streaming)r  z![%s] AI Card create failed: %s
%s) r  rE  rF  rG  r   r   tea_util_modelsRuntimeOptionsdingtalk_card_modelsCreateCardRequestrA   CreateCardRequestCardData&CreateCardRequestImGroupOpenSpaceModel&CreateCardRequestImRobotOpenSpaceModelCreateCardHeadersrB   create_card_with_options_asyncDeliverCardRequest)DeliverCardRequestImGroupOpenDeliverModelrb   rs   rt   ru   )DeliverCardRequestImRobotOpenDeliverModelDeliverCardHeadersdeliver_card_with_options_asyncr  r   r   r.   	traceback
format_exc)rD   r  r   r`  r  r  r  r   r,  r
  r   runtimecreate_requestcreate_headersr  deliver_requestdeliver_headersr   s                     r:   r  z'DingTalkAdapter._create_and_stream_card   s      f	0022222222E t<TZ\\%5crc%:<<L%g/@"EEKO '1Dc J J,--4H%g/@"EEKO%466G 2C!%!7).H$-r?   '(O(,  
 )O(,    N& 2C,1  N .??        
   H H H"6"I!-!""/,V'+'7  	# 	# 	# '  NNN	    4 H H H"6"I!-!""/,V'1  	# 	# 	# 3E,1  O .@@'         ++eWx ,          KK%	'/J##5J	   d|DDDD 	 	 	NN4	1i244   44444	s$   I E+I B<I 
J3JJr  c                
  K   |st          dd          S |                                  d{V }|st          dd          S 	 |                     ||||           d{V  |r| j                            |i                               |d           | j                            |          s| j                            |d           t                              d| j        |           | 	                    |           n|| j        
                    |i           |<   t          d|	          S # t          $ rI}t                              d
| j        |           t          dt          |                    cY d}~S d}~ww xY w)aA  Edit an AI Card by streaming updated content.

        ``message_id`` is the out_track_id returned by the initial ``send()``
        call that created this card.  Callers (stream_consumer, tool
        progress) track their own ids independently so two parallel flows
        on the same chat_id don't interfere.
        Fzmessage_id requiredr  NzNo access tokenr  z![%s] AI Card finalized (edit): %sTr  z[%s] Card edit failed: %s)r   r  r  rh   rW   r  rs   r   ru   r*  r  r.   rt   r   )rD   r  r  r`  r  r  r   s          r:   edit_messagezDingTalkAdapter.edit_messagex  s       	Je3HIIII,,........ 	Fe3DEEEE	;++E7X ,           T %))'266:::tLLL,0099 =)--gt<<<7Iz   ((1111
 MT%00"==jIdzBBBB 	; 	; 	;NN6	1EEEe3q66:::::::::	;s   C*D/ /
F9>E=7F=Fr  r  c           	      *  K   t          j        |t          t          j                              d|d| j                 d|d          }t          j        |          }t          j                    }| j	        
                    |||           d{V  dS )z&Stream content to an existing AI Card.r`  NTF)r  guidkeyr`  is_fullis_finalizeis_errorr  )r  StreamingUpdateRequestr   rE  rF  r  StreamingUpdateHeadersr  r  rB   #streaming_update_with_options_async)rD   r  r  r`  r  stream_requeststream_headersr  s           r:   r  z$DingTalkAdapter._stream_card_content  s       .D%TZ\\""5d556 
 
 
 .D(-
 
 
 "022n@@NG
 
 	
 	
 	
 	
 	
 	
 	
 	
 	
rG   c                    K   | j         sdS 	 t          j        | j         j                   d{V }|S # t          $ r,}t
                              d| j        |           Y d}~dS d}~ww xY w)z*Get access token using SDK's cached token.Nz#[%s] Failed to get access token: %s)r^   r   r   get_access_tokenr.   rs   r   ru   )rD   r  r   s      r:   r  z!DingTalkAdapter._get_access_token  s      " 	4	!+D,?,PQQQQQQQQEL 	 	 	LL>	1MMM44444	s   %3 
A)!A$$A)r#  open_msg_idopen_conversation_id
emoji_namer$  c          	        K   | j         r|r|sdS |rdnd}	 |                                  d{V }|sdS | j        ||d|d}t          j                    }|rdt          j        d||d          |d	<   t          j        di |}	t          j        |
          }
| j         	                    |	|
|           d{V  nct          j
        d||d          |d	<   t          j        di |}	t          j        |
          }
| j                             |	|
|           d{V  t                              d| j        |||dd                    dS # t"          $ r' t                              d| j        |d           Y dS w xY w)z-Add or recall an emoji reaction on a message.Nr$  replyr   )rO   r  r  emotion_typeemotion_name2659900im_bg_1)
emotion_idr  r  background_idtext_emotionr  z#[%s] _send_emotion: %s %s on msg=%s   z[%s] _send_emotion %s failedT)exc_infor   )ra   r  rb   r  r  dingtalk_robot_models$RobotRecallEmotionRequestTextEmotionRobotRecallEmotionRequestRobotRecallEmotionHeaders'robot_recall_emotion_with_options_async#RobotReplyEmotionRequestTextEmotionRobotReplyEmotionRequestRobotReplyEmotionHeaders&robot_reply_emotion_with_options_asyncrs   r   ru   r.   r   )rD   r  r  r  r$  actionr  emotion_kwargsr  requestsdk_headerss              r:   r%  zDingTalkAdapter._send_emotion  s       	k 	9M 	F#09	0022222222E  #.*(< ! * N &466G #)N#,%/'&/	   ~. 0I  $  4M05   oMM['         
 *M#,%/'&/	   ~. 0H  $  4L05   oLL['         KK5	6:{3B3/?      	 	 	LL.	6D       	s   E DE -E<;E<c           
        K   |                                   d{V }|sdS t          |dd          p| j        }g }t          |dd          }|r(t          |dd          r|                    |df           t          |dd          }|r^t          |dg           pg }|D ]H}t	          |t
                    r1dD ].}	|                    |	          r|                    ||	f           /I|sdS g }
|D ]i\  }}	t          ||	          rt          ||	d          n|                    |	          }|r,|
                    |                     |||||	                     jt          j
        |
dd	i d{V  dS )
z1Resolve download codes in message to actual URLs.NrO   rk  rl  ra  rc  )rm  pictureDownloadCoderl  r   T)r  r   rX   r   r   re  rW   r   _fetch_download_urlr   r   )rD   r   r  rO   codes_to_resolveimg_contentrb  rg  r   r  tasksobjcodes                r:   rP  z$DingTalkAdapter._resolve_media_codes  s     ,,........ 	FWlD99LT_
 g== 	D7;FF 	D##[/$BCCC G%8$??	 	A	+;R@@FBI! A AdD)) AW A A88C== A,33T3K@@@ 	F ( 	 	HC.5c3.?.?Q73T***SWWS\\D ,,T:uc3OO   ne<t<<<<<<<<<<<rG   r	  rO   r  c                   K   | j         s"t                              d| j                   dS 	 t	          j        ||          }t	          j        |          }t          j                    }| j         	                    |||           d{V }	|	r|	j
        nd}
|
rVt          |
dd          }|r?t          ||          rt          |||           dS t          |t                    r	|||<   dS dS dS t                              d| j        |           dS # t           $ r-}t                              d| j        ||           Y d}~dS d}~ww xY w)z9Fetch download URL for a single code using the robot SDK.z9[%s] Robot SDK not initialized, cannot resolve media codeN)rl  rO   r  download_urlz9[%s] Failed to download media: empty response for code %sz&[%s] Error resolving media code %s: %s)ra   rs   rt   ru   r  RobotMessageFileDownloadRequestRobotMessageFileDownloadHeadersr  r  .robot_message_file_download_with_options_asyncr  r   r   setattrr   re  r.   r   )rD   r	  rO   r  r  r  r   headersr  responser  urlr   s                r:   r  z#DingTalkAdapter._fetch_download_url<  s       	NNK	   F	W+K"%  G ,K,1  G &466G!_[['       H %-68==$D dND99 'sC(( 'S#.....#C.. '#&C	' '' ' OI    
  	W 	W 	WLLA49dTUVVVVVVVVV	Ws$   B!D D 0!D 
E
"EE
c                    |                      d          }g }t          |          D ]\  }}t          j        d|                                          }|ra|dk    r[||dz
           }|                                r<t          j        d|                                          s|                    d           |                                                    d          rS||                                k    r;t          |          t          |                                          z
  }||d         }|                    |           !d	                    |          S )zNormalize markdown for DingTalk's parser.

        DingTalk's markdown renderer has quirks:
        - Numbered lists need blank line before them
        - Indented code blocks may render incorrectly
        
z^\d+\.\sr   r   rM   z```N)
r   	enumerater   rK  r   r   
startswithlstripr   rf  )r  linesoutilineis_numberedprevindents           r:   r  z#DingTalkAdapter._normalize_markdownc  s'    

4   '' 	 	GAt(;

==K #q1uuQU|::<< #djjll(K(K #JJrNNNzz||&&u-- %$$++--2G2GTS%7%77FGG}JJtyy~~rG   r(  )r   r   r%   N)r   r   )NNr  )NNN)NNNN)F)9r   
__module____qualname____doc__r  propertyr@   rE   rI   r   rT   r   r   r   r   r	   r   r   r   r   r   PatternrZ   r\   r   r  r	  r  r  r   r*  r^  staticmethodrI  rQ  r   r   r   r   r  r  r  r  r  r  tuplerT  r  r  r  r  r  r%  rP  r  r  __classcell__rj   s   @r:   r=   r=      s         ,?$ ? ? ? X? ? ? ? ? X?22~ 22 22 22 22 22 22l 5: @ @ @T @d @ @ @ @D   ,=4 =4 =4 =4Bd4 d d d dNs3x N N N NNS N N N N$4
+; $ $ $ $L0SX 0 0 0 06# 6 6 6 6 6 6>-= >$ > > > >Oc Od O O O O
</? <s <VZ <eh <mq < < < <87 7 7 7s t    < 3  4        :t)!t) 
t) t) t) t)l ,/ ,C , , , \,\A1 A1 A1 A1R #'-1j; j;j; j; 3-	j;
 4S>*j; 
j; j; j; j;X       "&"&-1
 

 
 #	

 3-
 4S>*
 

 
 
 
8 "&"&-1
 

 
 #	

 3-
 4S>*
 

 
 
 
, "&#'"&-1
 

 
 #	

 C=
 3-
 4S>*
 

 
 
 
&
3 
4S> 
 
 
 
# (5c?2K    , v v vv v 	v v 
*	v v v v| -; -; -;-; -; 	-; -; 
-; -; -; -;h 
 

 
 	

 
 

 
 
 
6
# 
 
 
 
$ E E EE "E 	E E 
E E E EN%= %= %= %=N%W%W%(%W14%W@C%W	%W %W %W %WN # #    \    rG   r=   c                   \     e Zd ZdZddedeej                 f fdZddZ	dd
Z
ddZ xZS )r   a  dingtalk-stream ChatbotHandler that forwards messages to the adapter.

    SDK >= 0.20 changed process() from sync to async, and the message
    parameter from ChatbotMessage to CallbackMessage. We parse the
    CallbackMessage.data dict into a ChatbotMessage before forwarding.
    Nadapterr   c                 r    t           r t                                                       || _        || _        d S r  )r+   rS   rT   _adapter_loop)rD   r)  r   rj   s      r:   rT   z_IncomingHandler.__init__  s3    $ 	GG


rG   r%   c                     dS )aS  No-op pre-start hook required by dingtalk-stream SDK.

        The SDK calls ``pre_start()`` on every registered handler before
        opening the WebSocket connection.  Without this method, the SDK
        raises ``AttributeError: '_IncomingHandler' object has no
        attribute 'pre_start'`` and kills the stream connection.
        Nr   rC   s    r:   	pre_startz_IncomingHandler.pre_start  s	     	rG   r   r   c                   K   	 |j         }t          |t                    rt          j        |          }t          j        |          }t          |dd          sLt          |t                    r,|	                    d          p|	                    d          pdnd}|r||_
        t          |dd          s5t          |t                    r|	                    d          nd}|rd|_        t          |d	d          pd}t          |d
d          pd}|r8|r6| j                            | j                            ||dd                     t          j        |                     |                     nC# t$          $ r6 t&                              d| j        j                   t,          j        dfcY S w xY wt,          j        dfS )u  Called by dingtalk-stream (>=0.20) when a message arrives.

        dingtalk-stream >= 0.24 passes a CallbackMessage whose ``.data`` contains
        the chatbot payload. Convert it to ChatbotMessage via
        ``ChatbotMessage.from_dict()``.

        Message processing is dispatched as a background task so that this
        method returns the ACK immediately — blocking here would prevent the
        SDK from sending heartbeats, eventually causing a disconnect.
        r1  NsessionWebhookrM   r   F
isInAtListTr  r   r"  r#  z%[%s] Error preparing incoming messager   OK)datar   r   r   r   r   	from_dictr   re  rW   r1  r   r+  r  r%  r   r   _safe_on_messager.   rs   	exceptionru   r   r   r   )rD   r   r3  chatbot_msgr  raw_flagr&  r   s           r:   processz_IncomingHandler.process  s
     5	?<D$$$ (z$'' )2488K
 ;(94@@ :
  d++	4HH-.. xx 12213	 
  :29K/ ;?? 5.8t.D.DODHH\***%   504K-[,==CF%k3DdKKQrO  / ''M// 0      5 5k B BCCCC 	? 	? 	?79K   5w>>>>		? #T))s   E.E3 3=F32F3r7  r   c                    K   	 | j                             |           d{V  dS # t          $ r) t                              d| j         j                   Y dS w xY w)z1Wrapper that catches exceptions from _on_message.Nz&[%s] Error processing incoming message)r+  r^  r.   rs   r6  ru   )rD   r7  s     r:   r5  z!_IncomingHandler._safe_on_message  s      	-++K88888888888 	 	 	8$-:L     	s    & /AAr  r(  )r   r   )r7  r   r%   N)r   r  r   r!  r=   r   r   AbstractEventLooprT   r.  r9  r5  r&  r'  s   @r:   r   r     s           x@Y7Z         B* B* B* B*H       rG   r   )	thread_idmedia_filesforce_documentc                  K   t          | di           pi }	 ddl}n# t          $ r ddicY S w xY w	 |                    d          pt	          j        dd          }|sdd	iS  |j        d
          4 d{V }	|	                    |dd|id           d{V }
|
                                 |
	                                }|                    dd          dk    r-dd|                    dd           icddd          d{V  S 	 ddd          d{V  n# 1 d{V swxY w Y   dd|dS # t          $ r;}	 ddlm}  |d|           cY d}~S # t          $ r dd| icY cY d}~S w xY wd}~ww xY w)a  Out-of-process DingTalk delivery via a static robot webhook URL.

    Implements the standalone_sender_fn contract so deliver=dingtalk cron jobs
    succeed when cron runs separately from the gateway. The live adapter uses
    per-session webhook URLs from incoming messages, which aren't available
    out-of-process; this path uses the static DINGTALK_WEBHOOK_URL / extra
    webhook_url instead. Replaces the legacy _send_dingtalk helper.
    rV   r   Nr   zhttpx not installedwebhook_urlDINGTALK_WEBHOOK_URLrM   zkDingTalk not configured. Set DINGTALK_WEBHOOK_URL env var or webhook_url in dingtalk platform extra config.rn   r   r  r`  )r  r  )r   errcodezDingTalk API error: errmsgunknownTdingtalk)r  platformr  )_errorzDingTalk send failed: )r   r1   ImportErrorrW   r2   r3   rw   r  raise_for_statusr   r.   tools.send_message_toolrG  )pconfigr  r   r<  r=  r>  rV   r1   r@  r   r  r3  r   _redact_errors                 r:   _standalone_sendrM    s5     " GWb))/RE0 0 0 0.////0;ii..W")<RTV2W2W 	L  K  L  L$5$T222 	Y 	Y 	Y 	Y 	Y 	Y 	Yf!')W1EFF %        D !!###99;;Dxx	1%%**!W98U8U!W!WX	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y +	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y  ZGLLL 	; 	; 	;
	;GGGGGG =!=!!=!=>>>>>>>> 	; 	; 	;9a99:::::::::	;	;sx    --/D4 !D4 8A?D7D4 D4 
D''D4 *D'+D4 4
E9?EE9E1)E4*E90E11E44E9c                     ddl m} m} ddlm} ddlm}m}m}m	}m
}  |d            | d          }|r |d| d            |d	d
          sdS  |dddgd          }	|	dk    r	 ddlm}
 n8# t          $ r+} |d| d           t          |||           Y d}~dS d}~ww xY w |
            }| |d           t          |||           dS |\  }} |d|            |d|            |d           dS t          |||           dS )u  Configure DingTalk — QR scan (recommended) or manual credential entry.

    Replaces hermes_cli/setup.py-era _setup_dingtalk + the static
    _PLATFORMS["dingtalk"] dict in hermes_cli/gateway.py. CLI helpers are
    lazy-imported so the plugin's module-load surface stays minimal.
    r   )get_env_valuesave_env_value)prompt_choice)r(   prompt_yes_noprint_headerprint_successprint_warningDingTalkr)   z+DingTalk is already configured (Client ID: z).zReconfigure DingTalk?FNzChoose setup methodzCQR Code Scan (Recommended, auto-obtain Client ID and Client Secret)z*Manual Input (Client ID and Client Secret))default)dingtalk_qr_authzQR auth module failed to load (z ), falling back to manual input.z1QR auth incomplete, falling back to manual input.r*   z DingTalk configured via QR scan!)hermes_cli.configrO  rP  hermes_cli.setuprQ  hermes_cli.cli_outputr(   rR  rS  rT  rU  hermes_cli.dingtalk_authrX  rH  _manual_credential_entry)rO  rP  rQ  r(   rR  rS  rT  rU  existingmethodrX  r   r  rL   rN   s                  r:   interactive_setupr`  '  s    @???????......              L}122H PHPPPQQQ}4e<< 	F]Q8	
   F {{	AAAAAAA 	 	 	MaCaaabbb$V^]KKKFFFFF	 "!##>MMNNN$V^]KKKF#) 	=+Y777/???899999 GGGGGs   )A0 0
B%: B  B%c                      | d          }|sd S  |d|            | dd          }|r |d|            |d           d S )NzDingTalk Client ID (app key)r)   zDingTalk Client SecretT)passwordr*   zDingTalk credentials savedr   )r(   rP  rT  rL   rN   s        r:   r]  r]  \  sz    566I N'333F3dCCCM @/???M./////rG   yaml_cfgdingtalk_cfgc                    ddl }d|v rHt          j        d          s4t          |d                                                   t          j        d<   d|v r<t          j        d          s(|                    |d                   t          j        d<   |                    d          }|dt          j        d          sPt          |t                    rd	
                    d
 |D                       }t          |          t          j        d<   |                    d          }|dt          j        d          sPt          |t                    rd	
                    d |D                       }t          |          t          j        d<   |                    d          }|dt          j        d          sPt          |t                    rd	
                    d |D                       }t          |          t          j        d<   dS )uc  Translate config.yaml dingtalk: keys into DINGTALK_* env vars.

    Implements the apply_yaml_config_fn contract (#24849). Mirrors the legacy
    dingtalk_cfg block from gateway/config.py::load_gateway_config(). Env vars
    take precedence over YAML (each assignment guarded by not os.getenv(...)).
    Returns None — everything flows through env.
    r   Nr   r   r   r   r   r   r   c              3   4   K   | ]}t          |          V  d S r  r   r   vs     r:   r  z%_apply_yaml_config.<locals>.<genexpr>w  s(      //a3q66//////rG   r   r   c              3   4   K   | ]}t          |          V  d S r  rg  rh  s     r:   r  z%_apply_yaml_config.<locals>.<genexpr>|  s(      --Q#a&&------rG   r   r   c              3   4   K   | ]}t          |          V  d S r  rg  rh  s     r:   r  z%_apply_yaml_config.<locals>.<genexpr>  s(      77!s1vv777777rG   )r   r2   r3   r   r   environdumpsrW   r   r   rf  )rc  rd  _jsonfrcacr  s         r:   _apply_yaml_configrq  g  s    L((;U1V1V(14\BS5T1U1U1[1[1]1]
-.\))")<W2X2X)27++lK]>^2_2_
./


0
1
1C
ry)GHHc4   	0((//3/////C58XX
12			/	*	*B	~bi(@AA~b$ 	.--"-----B/22ww
+,//G29-E#F#Fgt$$ 	8hh77w77777G/27||
+,4rG   c                     t          | di           pi }t          |                    d          st          j        d          o(|                    d          pt          j        d                    S )zDingTalk is connected when client_id + client_secret are present.

    Mirrors the legacy _PLATFORM_CONNECTED_CHECKERS[Platform.DINGTALK] entry.
    Reads from PlatformConfig.extra first, then env vars.
    rV   rL   r)   rN   r*   )r   r@   rW   r2   r3   )rJ   rV   s     r:   _is_connectedrs    sp     FGR((.BE	;			B29-A#B#B 	PYY''N295M+N+N  rG   c                      t          |           S )zFFactory wrapper that constructs DingTalkAdapter from a PlatformConfig.)r=   )rJ   s    r:   _build_adapterru    s    6"""rG   c                     |                      ddt          t          t          t          ddgdt          t
          dddt          d	d
           dS )u:   Plugin entry point — called by the Hermes plugin system.rE  rV  r)   r*   z)pip install 'dingtalk-stream>=0.20' httpxr   DINGTALK_ALLOW_ALL_USERSDINGTALK_HOME_CHANNELu   🐳T)ru   label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emojiallow_update_commandN)register_platformru  r;   rs  r`  rq  rM  )ctxs    r:   registerr    s_    &,"%*,DE@"/204-!!      rG   r(  )Ir!  r   r   loggingr2   r   r  rE  r   r   typingr   r   r   r   r	   r/   r   r0   r   r   r+   r.   r   r1   r,   rH  alibabacloud_dingtalk.card_1_0r   r   r   r  alibabacloud_dingtalk.robot_1_0r   r  alibabacloud_tea_openapir{   alibabacloud_tea_utilr  rz   gateway.configr   r   gateway.platforms.helpersr   gateway.platforms.baser   r   r   r   	getLoggerr   rs   r  r   rL  r   rJ  rt  r@   r;   r=   ChatbotHandlerobjectr   rM  r`  r]  re  rq  rs  ru  r  r   rG   r:   <module>r     sR   8    				 				      ' ' ' ' ' ' ' ' 1 1 1 1 1 1 1 1 1 1 1 1 1 1......BBBBBBBB $    %ONO
'*	
 	
 JJJLLLOO   OEEE               CBBBBB??????     OOOO 4 3 3 3 3 3 3 3 9 9 9 9 9 9            
	8	$	$ &&&  !rz"JKK    T    B` ` ` ` `) ` ` `P'e e e e e&?KO""Ve e ev -; -; -; -; -;`2H 2H 2H 2Hj0 0 0 0 T dTk    >
T 
 
 
 
# # #
     s5   A A+*A+/A6 6	BBB% %B;:B;