
    epj<                       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,m-Z- ddl.m/Z/m0Z0m1Z1m2Z2 ddl3m4Z5 ddl3m6Z7 d;dZ8 ej9        e:          Z;dZ<g dZ=dZ> ej?        d          Z@dddZAdddddd d!d"d#d$d%d&d'd(d)d*ZBd+eCfd,ZD G d- d.e/          ZE G d/ d0t*          rt           jF        neG          ZHdddd1d2ZId<d3ZJd<d4ZKd5eLd6eLd+eLdz  fd7ZMd+eCfd8ZNd9 ZOd<d:ZPd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compile_mention_patterns)BasePlatformAdapterMessageEventMessageType
SendResult)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      H/home/thesage/.hermes/hermes-agent/plugins/platforms/dingtalk/adapter.py_get_scoped_secretr(   n   sR     w//   ioo/33w.s    44i N  )      
      <   z%^https://(?:api|oapi)\.dingtalk\.com/imageaudio)picturevoicezapplication/pdfz	image/pngz
image/jpegz	image/gifz
image/webpzapplication/mswordzGapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentzapplication/vnd.ms-excelzAapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetztext/markdownz
text/plainztext/csvzapplication/zipz	video/mp4)pdfpngjpgjpeggifwebpdocdocxxlsxlsxmdtxtcsvzipmp4returnc                  &   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          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_depsrC   	Exceptiondingtalk_streamr   dingtalk_stream.framesr   r   httpxr"   r#   r(   )_lazy_ensure_ds_CM_CBM_AM_httpxs         r'   check_dingtalk_requirementsrT      s#    % O 	>>>>>>L,U;;;;; 	 	 	55		))))======YYYYYYYY""""" 	 	 	55	
$(!9)** 2DE]2^2^ 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
    rA   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.
        rX   r\   s    r'   REQUIRES_EDIT_FINALIZEz&DingTalkAdapter.REQUIRES_EDIT_FINALIZE   r_   r`   configc                    t                                          |t          j                   |j        pi }|                    d          pt          j        dd          | _        |                    d          pt          dd          | _
        |                                 | _        |                                 | _        d | _        d | _        d | _        d | _        d | _        |                    d          p| j        | _        t+          d          | _        i | _        i | _        |                    d	          | _        t5                      | _        i | _        t5                      | _        d S )
N	client_idrE    client_secretrF   
robot_code  )max_sizecard_template_id)super__init__r   DINGTALKextragetr"   r#   
_client_idr(   _client_secret_compile_mention_patterns_mention_patterns_load_allowed_users_allowed_users_stream_client_stream_task_http_clientr[   
_robot_sdk_robot_coder   _dedup_session_webhooks_message_contextsrZ   set_done_emoji_fired_streaming_cards	_bg_tasks)r]   rc   ro   	__class__s      r'   rm   zDingTalkAdapter.__init__   sU   !2333"$yy55  
 ":
 :
 $)99_#=#= $
AS$bB
 B
 483Q3Q3S3S(,(@(@(B(B#'48;?(,)- %		, 7 7 J4? *4888=? 2405		:L0M0M ,/55 <> -0EEr`   F)is_reconnectr   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)(rG   loggerwarningr$   rH   rq   rr   %gateway.platforms._http_client_limitsr   rM   AsyncClientry   rK   
CredentialDingTalkStreamClientrw   CARD_SDK_AVAILABLErZ   open_api_modelsConfigprotocol	region_iddingtalk_card_clientClientr[   dingtalk_robot_clientrz   infoasyncioget_running_loop_IncomingHandlerregister_callback_handlerr   TOPICcreate_task_run_streamrx   _mark_connectedrJ   error)r]   r   r   
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...)_runningr   debugr$   rw   startr   CancelledErrorrJ   r   RECONNECT_BACKOFFminlenr   sleep)r]   backoff_idxr   delays       r'   r   zDingTalkAdapter._run_streama  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      @r   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_disconnectedrw   getattrr   rJ   r   r   r$   rx   hasattrr   	to_threadcancelwait_forr   TimeoutErrorr   listgatherclearr   _close_streaming_siblingsry   acloser}   r~   r   r|   r   )r]   r   r   task_chat_id_excs         r'   
disconnectzDingTalkAdapter.disconnectw  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)	rc   ro   rp   
isinstancestrlowerrY   r"   r#   )r]   
configureds     r'   _dingtalk_require_mentionz)DingTalkAdapter._dingtalk_require_mention  s    [&**+<==
!*c** H!''))-GGG
###y3W==CCEEIcccr`   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_CHATSrf   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Kr`   c                 ^    h | ]*}|                                 |                                 +S r   r   r   s     r'   r   z@DingTalkAdapter._dingtalk_free_response_chats.<locals>.<setcomp>  -    MMM

M

MMMr`   ,	rc   ro   rp   r"   r#   r   r   r   splitr]   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Mr`   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_CHATSrf   c                     h | ]D}t          |                                          #t          |                                          ES r   r   r   s     r'   r   z:DingTalkAdapter._dingtalk_allowed_chats.<locals>.<setcomp>  r   r`   c                 ^    h | ]*}|                                 |                                 +S r   r   r   s     r'   r   z:DingTalkAdapter._dingtalk_allowed_chats.<locals>.<setcomp>  r   r`   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Mr`   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          || j        dd	t          
          S )z=Compile optional regex wake-word patterns for group triggers.mention_patternsNDINGTALK_MENTION_PATTERNSrf   c                 ^    g | ]*}|                                 |                                 +S r   r   r   s     r'   
<listcomp>z=DingTalkAdapter._compile_mention_patterns.<locals>.<listcomp>  s-    XXXt4::<<XdjjllXXXr`   c                 ^    g | ]*}|                                 |                                 +S r   r   r   s     r'   r   z=DingTalkAdapter._compile_mention_patterns.<locals>.<listcomp>  s-    !Z!Z!Z4TZZ\\!Z$**,,!Z!Z!Zr`   r   dingtalkDingTalk)
log_prefixplatform_labeldisplay_labellogger_)rc   ro   rp   r"   r#   r   jsonloadsrJ   
splitlinesr   r   r$   r   )r]   patternsr   loadeds       r'   rs   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'y%$
 
 
 	
s   A/ /A	B;:B;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_USERSrf   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Lr`   c                 ^    g | ]*}|                                 |                                 +S r   r   r   s     r'   r   z7DingTalkAdapter._load_allowed_users.<locals>.<listcomp>  s-    RRRdTZZ\\RTZZ\\RRRr`   r   c                 6    h | ]}|                                 S r   r   )r   items     r'   r   z6DingTalkAdapter._load_allowed_users.<locals>.<setcomp>   s     ///

///r`   r   )r]   r   itemss      r'   ru   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//////r`   	sender_idsender_staff_idc                     | j         r	d| j         v rdS |pd                                |pd                                h}|                    d           t          || j         z            S )N*Trf   )rv   r   discardrY   )r]   r  r  
candidatess       r'   _is_user_allowedz DingTalkAdapter._is_user_allowed  st    " 	cT-@&@&@4 B--///2GR1N1N1P1PQ
2J!44555r`   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)rY   r   )r]   r  s     r'   _message_mentions_botz%DingTalkAdapter._message_mentions_bot	  s     GG_e<<===r`   textc                 Z    r| j         sdS t          fd| j         D                       S )NFc              3   B   K   | ]}|                               V  d S N)search)r   patternr  s     r'   	<genexpr>zDDingTalkAdapter._message_matches_mention_patterns.<locals>.<genexpr>  s/      NNG7>>$''NNNNNNr`   )rt   any)r]   r  s    `r'   !_message_matches_mention_patternsz1DingTalkAdapter._message_matches_mention_patterns  s@     	41 	5NNNNt7MNNNNNNr`   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  )r]   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;;;r`   c                     t          j        |          }| j                            |           |                    | j        j                   dS )z;Start a fire-and-forget coroutine and track it for cleanup.N)r   r   r   addadd_done_callbackr  )r]   coror   s      r'   	_spawn_bgzDingTalkAdapter._spawn_bg2  sG    "4((4   t~566666r`   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)
r   pop_get_access_tokenr   r  _stream_card_contentr   r   r$   rJ   )r]   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_idrf   conversation_idrA   c                     K                         dd           d {V                        dd           d {V  d S )N   🤔ThinkingTrecallu   🥳DoneF)_send_emotion)r6  msg_idr]   s   r'   _swapz2DingTalkAdapter._fire_done_reaction.<locals>._swapi  s      $$ %          $$E %           r`   rA   N)r   r&  r~   rp   r   r)  )r]   r!  msgr=  r6  r<  s   `   @@r'   _fire_done_reactionz#DingTalkAdapter._fire_done_reactionX  s     d,,,F""7+++$((11 	FlB//52!#'8"==C 	? 	F	 	 	 	 	 	 	 	 	uuwwr`   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.r5  Nz#[%s] Duplicate message %s, skippingr6  rf   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_atri   tz)r  message_typesourcer5  raw_message
media_urlsmedia_types	timestampz[%s] Message from %s in %s: %s   ?P   z(media))'r   uuiduuid4hexr|   is_duplicater   r   r$   r   r  _extract_textr$  r~   r   r  _DINGTALK_WEBHOOK_REmatchr   r}   _SESSION_WEBHOOKS_MAXr-  nextiterStopIteration_resolve_media_codes_extract_mediabuild_sourcer   fromtimestampintr   utcnow
ValueErrorOSError	TypeErrorr   handle_message)r]   r  r<  r6  rB  r   r  rD  r  r!  rK  _early_textrG  rH  r  msg_typerU  rV  rS  rO  rW  events                         r'   _on_messagezDingTalkAdapter._on_messageu  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                    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                    |                                          }|st          | d	d          }|d
k    rjt          | di           pi }	|	                    di           }
t	          |
t
                    r,|
                    dd          }|r|                                }|srt          | d	d          }|dk    r[t          | di           pi }	|	                    di           }t	          |t
                    r|                    dd          }|rd| }|st          | d	d          }|dk    rt          | di           pi }	|	                    di           }t	          |t
                    r]|                    dd          }|                    dd          }d}|d}nt	          |t
                    r-|                    dd          p|                    dd          }nt	          |t                    r|                                }|sd}np	 t          j        |          }t	          |t
                    r,|                    dd          p|                    dd          }n# t          t          f$ r |}Y nw xY wg }|r|	                    d|            |r|	                    |           |rd                    |          }|sU|	                    di           }t	          |t
                    r*|                    dd          pd                                }|dk    r|st          | di           pi }	|	                    di           }t	          |t
                    r|                    dd          }|                    dd          }|s|r^g }|r|	                    d|            n|	                    d           |r|	                    |           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  Nrf   contentrich_text_content	rich_textrich_text_list rR  r/   
extensionsrecognitionfilefileNameu	   [文件] cardtitleurldocUrlu	   [文档] interactiveCardbiz_custom_action_urlu   [文档卡片] u   [文档卡片])r   r   rv  r   r   dictrp   r   r   appendr  joinr   r   rm  ro  )r  r  rv  rx  	rich_listpartsr
  trr  r{  audio_contentr|  file_contentfnamer  r  raw_contentdoc_urlstrippedparsedext_textext_contents                         r'   r_  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  	6w;;H7""$WlB??E2
 *y" = =mT22 6"/"3"3M2"F"FK" 6"-"3"3"5"5  	6w;;H6!!$WlB??E2
)~~i<<lD11 6(,,Z<<E 6"5e"5"5  7	2w;;H6!!$WlB??E2
!~~fb11dD)) 2 HHWb11E"&((9b"9"9K G"*"$#K66 6"-//%"<"<"]PXZ\@]@]#K55 
6#.#4#4#6#6' 6&(GG6)-H)=)=#-fd#;#; !`.4jj.C.C._vzzRZ\^G_G_G$.	#: 6 6 6*56E :%8%8%8999 .W--- 2"%((5// N)~~fb99H!(D11 N#+<<	2#>#>#D""K"K"M"M
 ,,,W,$WlB??E2
(nnY;;k400 2)oo.ErJJG'OOGR88E 2% 2 "  ;!LL)B5)B)BCCCC!LL)9:::" 2!LL111"%((5// s   AO1 1PP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        }n|dk    r7|t           j        k    r%t          d |D                       rt           j        }nW|dk    r|t           j        k    rt           j
        }n3|dv r.t          |di           pi }|                    di           }t          |t                    r|                    d          pd	}
|                    dd	          }|
r|                    |
           d}|rOd|v r.|                    dd          d                                         nd	}t"                              ||          }|                    |           |t           j        k    r4|dk    s|                    d          rt           j        }nt           j        }|||fS )zMExtract media info from message. Returns (MessageType, [urls], [mime_types]).image_contentNdownload_coder.   rw  rx  ry  downloadCoderf   typer}  r/   r1   videozapplication/octet-streamrR  r0   richTextc              3      K   | ]}d |v V  	dS )r.   Nr   )r   r  s     r'   r  z1DingTalkAdapter._extract_media.<locals>.<genexpr>  s7       4 4!"14 4 4 4 4 4r`   r}  r.   r{  rv  r~  .r   zimage/)r   TEXTr   r  PHOTOr   r   r  rp   DINGTALK_TYPE_MAPPINGVOICEAUDIOVIDEODOCUMENTr  rsplitr   EXT_MAP
startswith)r]   r  rr  rU  rV  r  r  rx  r  r
  dl_code	item_typemappedmsg_type_strr{  r  r  mimeexts                      r'   rg  zDingTalkAdapter._extract_median  s8   #
  $?? 	-#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'' ;+++ 4 4&14 4 4 1 1+ ',W$$ ;+++&,... ,;;ArJ$..B77K+t,, <%//.99?R#
B77 <%%g...5D 6BE,,ell32226<<>>>TV&{{355&&t,,,;#333 (722dooh6O6O2'2'8HH'2';H[00r`   rv  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=%srG  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)r  r  )msgtyper  g      .@)r   r   i,  T   r  r5  z[%s] Send failed HTTP %d: %sr   zHTTP z: z#Timeout sending message to DingTalkz[%s] Send error: %s) r   r   r$   rY   rZ   r[   rp   _get_valid_webhookr   r   ry   r~   r   _create_and_stream_cardr  r@  r   
setdefaultr5  _normalize_markdownMAX_MESSAGE_LENGTHpoststatus_coder[  r\  r]  r  rM   TimeoutExceptionrJ   r   r   )r]   r!  rv  r  r  rG  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   )r]   r!  r  s      r'   send_typingzDingTalkAdapter.send_typingE  s      r`   	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!  rv  r  r  N)r  )r]   r!  r  r  r  r  image_blockrv  s           r'   
send_imagezDingTalkAdapter.send_imageI  sz       /)...3:KW//+///YY	  
 
 
 
 
 
 
 
 	
r`   
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   )r]   r!  r  r  r  r  kwargss          r'   send_image_filezDingTalkAdapter.send_image_filea  s(       Y
 
 
 	
r`   	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  )r]   r!  r  r  r  r  r  r  s           r'   send_documentzDingTalkAdapter.send_documents  s(       Y
 
 
 	
r`   c                 @   K   |d|                                 v rdnddS )z0Return basic info about a DingTalk conversation.rE  rF  )r$   r  r	  )r]   r!  s     r'   get_chat_infozDingTalkAdapter.get_chat_info  s3       &'--//99GGt
 
 	
r`   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   rP  ri   i )	r}   rp   rj  r   rl  r   rk  rW  r-  )r]   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r`   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  r6  rf   rB  r   rC  r  rv  )card_param_mapSTREAMT)support_forward)rk   r2  	card_datacallback_typeim_group_open_space_modelim_robot_open_space_modelx_acs_dingtalk_access_tokenzdtv1.card//IM_GROUP.r   )rh   )r2  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)r2  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.  r[  r\  r]  r   r   tea_util_modelsRuntimeOptionsdingtalk_card_modelsCreateCardRequestrZ   CreateCardRequestCardData&CreateCardRequestImGroupOpenSpaceModel&CreateCardRequestImRobotOpenSpaceModelCreateCardHeadersr[   create_card_with_options_asyncDeliverCardRequest)DeliverCardRequestImGroupOpenDeliverModelr{   r   r   r$   )DeliverCardRequestImRobotOpenDeliverModelDeliverCardHeadersdeliver_card_with_options_asyncr/  r   r   rJ   	traceback
format_exc)r]   r!  r  rv  r,  r1  r2  r6  rB  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r5  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/  r   rp   r-  r   r   r$   r@  r  rJ   r   r   )r]   r!  r5  rv  r,  r1  r   s          r'   edit_messagezDingTalkAdapter.edit_message  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r2  r1  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.rv  NTF)r2  guidkeyrv  is_fullis_finalizeis_errorr  )r  StreamingUpdateRequestr   r[  r\  r  StreamingUpdateHeadersr  r  r[   #streaming_update_with_options_async)r]   r2  r1  rv  r,  stream_requeststream_headersr  s           r'   r/  z$DingTalkAdapter._stream_card_contentD  s       .D%TZ\\""5d556 
 
 
 .D(-
 
 
 "022n@@NG
 
 	
 	
 	
 	
 	
 	
 	
 	
 	
r`   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)rw   r   r   get_access_tokenrJ   r   r   r$   )r]   r1  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)r9  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)   )rh   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   )rz   r.  r{   r  r  dingtalk_robot_models$RobotRecallEmotionRequestTextEmotionRobotRecallEmotionRequestRobotRecallEmotionHeaders'robot_recall_emotion_with_options_async#RobotReplyEmotionRequestTextEmotionRobotReplyEmotionRequestRobotReplyEmotionHeaders&robot_reply_emotion_with_options_asyncr   r   r$   rJ   r   )r]   r  r  r  r:  actionr1  emotion_kwargsr  requestsdk_headerss              r'   r;  zDingTalkAdapter._send_emotionk  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           /It          |dd	          pd	}
|
d
v rjt          |di           pi }|                    di           }t	          |t
                    r,|                    d          r|                    |df           |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.Nrh   r  r  rw  ry  )r  pictureDownloadCoder  rR  rf   r  r{  rv  r  r   T)r.  r   rq   r  r   r  rp   r   _fetch_download_urlr   r   )r]   r  r1  rh   codes_to_resolveimg_contentrx  r  r
  r  r  r{  r  tasksobjcodes                   r'   rf  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@@@ w;;Ar,,, ,;;ArJ$..B77K+t,, G1P1P G ''n(EFFF 	F ( 	 	HC.5c3.?.?Q73T***SWWS\\D ,,T:uc3OO   ne<t<<<<<<<<<<<r`   r:  rh   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)r  rh   r  download_urlz9[%s] Failed to download media: empty response for code %sz&[%s] Error resolving media code %s: %s)rz   r   r   r$   r&  RobotMessageFileDownloadRequestRobotMessageFileDownloadHeadersr  r  .robot_message_file_download_with_options_asyncr  r   r   setattrr   r  rJ   r   )r]   r:  rh   r1  r9  r  r1  headersr  responser  r  r   s                r'   r5  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   rf   z```N)
r   	enumeraterera  r   r  r  lstripr   r  )r  linesoutilineis_numberedprevindents           r'   r  z#DingTalkAdapter._normalize_markdown  s'    

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

==K #q1uuQU|::<< #djjll(K(K #JJrNNNzz||&&u-- %$$++--2G2GTS%7%77FGG}JJtyy~~r`   r>  )r  r   rA   N)r  r   )NNr  )NNN)NNNN)F)9__name__
__module____qualname____doc__r  propertyrY   r^   rb   r   rm   r   r   r   r   r	   r   r   r   r   rF  Patternrs   ru   r  r  r  r$  r)  r   r@  rt  staticmethodr_  rg  r   r   r   r   r  r  r  r  r  r  tuplerj  r  r  r
  r/  r.  r;  rf  r5  r  __classcell__r   s   @r'   rV   rV      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
+; 
 
 
 
8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 @ @ @ \@Dg1 g1 g1 g1^ #'-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-= -= -= -=^%W%W%(%W14%W@C%W	%W %W %W %WN # #    \    r`   rV   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  )rG   rl   rm   _adapter_loop)r]   rZ  r   r   s      r'   rm   z_IncomingHandler.__init__0  s3    $ 	GG


r`   rA   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   r\   s    r'   	pre_startz_IncomingHandler.pre_start6  s	     	r`   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.
        rG  NsessionWebhookrf   r  F
isInAtListTr5  r6  r8  r9  z%[%s] Error preparing incoming messager   OK)datar   r   r   r   r   	from_dictr   r  rp   rG  r  r\  r)  r;  r   r   _safe_on_messagerJ   r   	exceptionr$   r   r   r   )r]   r  rd  chatbot_msgr  raw_flagr<  r6  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rh  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\  rt  rJ   r   rg  r$   )r]   rh  s     r'   rf  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   )rh  r   rA   N)rO  rP  rQ  rR  rV   r   r   AbstractEventLooprm   r_  rj  rf  rW  rX  s   @r'   r   r   &  s           x@Y7Z         B* B* B* B*H       r`   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.
    ro   r   Nr   zhttpx not installedwebhook_urlDINGTALK_WEBHOOK_URLrf   zkDingTalk not configured. Set DINGTALK_WEBHOOK_URL env var or webhook_url in dingtalk platform extra config.r   r   r  rv  )r  r  )r   errcodezDingTalk API error: errmsgunknownTr   )r  platformr!  )_errorzDingTalk send failed: )r   rM   ImportErrorrp   r"   r#   r   r  raise_for_statusr   rJ   tools.send_message_toolrw  )pconfigr!  r  rm  rn  ro  ro   rM   rq  r   r  rd  r   _redact_errors                 r'   _standalone_sendr}    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)rD   prompt_yes_noprint_headerprint_successprint_warningr   rE   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))r%   )dingtalk_qr_authzQR auth module failed to load (z ), falling back to manual input.z1QR auth incomplete, falling back to manual input.rF   z DingTalk configured via QR scan!)hermes_cli.configr  r  hermes_cli.setupr  hermes_cli.cli_outputrD   r  r  r  r  hermes_cli.dingtalk_authr  rx  _manual_credential_entry)r  r  r  rD   r  r  r  r  existingmethodr  excr  re   rg   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)rE   zDingTalk Client SecretT)passwordrF   zDingTalk credentials savedr   )rD   r  r  re   rg   s        r'   r  r    sz    566I N'333F3dCCCM @/???M./////r`   yaml_cfgdingtalk_cfgc                 v   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          }|4|                    d          }t          |t                    r|                    d          }||                     d          }t          |t                    r|                    d          nd}||                     d          fD ]}	t          |	t                    s|	                    d          }
t          |
t                    r|
                    d          nd}t          |t                    r,|                    d          |                    d          } n|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>  s(      //a3q66//////r`   r   r   c              3   4   K   | ]}t          |          V  d S r  r  r  s     r'   r  z%_apply_yaml_config.<locals>.<genexpr>!  s(      --Q#a&&------r`   r  ro   gateway	platformsr   r  c              3   4   K   | ]}t          |          V  d S r  r  r  s     r'   r  z%_apply_yaml_config.<locals>.<genexpr>?  s(      77!s1vv777777r`   )r   r"   r#   r   r   environdumpsrp   r   r   r  r  )r  r  _jsonfrcacr#  _extra_gw_gw_platforms
_container_dt	_dt_extras               r'   _apply_yaml_configr    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 !!'**fd## 	2jj11G?,,y))C4>sD4I4ISCGGK000tM,hll;.G.GH  
!*d33  nnZ000:30E0EOCGGG,,,4	i.. 9==3Q3Q3]'mmO<<GE29-E#F#Fgt$$ 	8hh77w77777G/27||
+,4r`   c                     t          | di           pi }t          |                    d          st          j        d          o#|                    d          pt          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.
    ro   re   rE   rg   rF   )r   rY   rp   r"   r#   r(   )rc   ro   s     r'   _is_connectedr  D  so     FGR((.BE	;			B29-A#B#B 	YYY''W+=>V+W+W  r`   c                      t          |           S )zFFactory wrapper that constructs DingTalkAdapter from a PlatformConfig.)rV   )rc   s    r'   _build_adapterr  Q  s    6"""r`   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.r   r   rE   rF   z)pip install 'dingtalk-stream>=0.20' httpxr  DINGTALK_ALLOW_ALL_USERSDINGTALK_HOME_CHANNELu   🐳T)r$   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_platformr  rT   r  r  r  r}  )ctxs    r'   registerr  V  s_    &,"%*,DE@"/204-!!      r`   r  r>  )QrR  r   r   loggingr"   rF  r  r[  r   r   typingr   r   r   r   r	   rK   r   rL   r   r   rG   rJ   r  rM   rH   rx  alibabacloud_dingtalk.card_1_0r   r   r   r  alibabacloud_dingtalk.robot_1_0r   r&  alibabacloud_tea_openapir   alibabacloud_tea_utilr  r   gateway.configr   r   gateway.platforms.helpersr   r   gateway.platforms.baser   r   r   r   agent.secret_scoper   r!   r   r    r(   	getLoggerrO  r   r  r   rb  compiler`  r  r  rY   rT   rV   ChatbotHandlerobjectr   r}  r  r  r  r  r  r  r  r   r`   r'   <module>r     s   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 S S S S S S S S            K J J J J J ? ? ? ? ? ?/ / / /( 
	8	$	$ &&&  !rz"JKK     U%O
 &T    BX X X X X) X X X@+e e e e e&?KO""Ve e ev -; -; -; -; -;`2H 2H 2H 2Hj0 0 0 05 5T 5dTk 5 5 5 5p
T 
 
 
 
# # #
     s5   A A+*A+/A6 6	BBB% %B;:B;