
    `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	 ddlada	 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'ddl(m)Z)m*Z* ddl+m,Z, ddl-m.Z.m/Z/m0Z0m1Z1  ejd                  e3      Z4dZ5g dZ6dZ7 ejp                  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(ZHy# e$ r dadadada ed	d
ddd      aY w xY w# e$ r dadaY w xY w# e$ r dZ'dZdZdZ!dZ"dZ$dZ&Y w xY w)*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                     t         rt        s6	 ddlm}   | dd       	 ddl}ddlm} ddlm}m	} ddl
}|a|a|a|a	|a
d	a d	at        j                  d
      rt        j                  d      syy	# t        $ r Y yw xY w# t        $ r Y yw xY w)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         U/root/.hermes/venv/lib/python3.12/site-packages/plugins/platforms/dingtalk/adapter.pycheck_dingtalk_requirementsr;   x   s     %O	>,U;	)=Y" 
$(!99)*"))<T2U%  		  		s"   A/ A> /	A;:A;>	B
	B
c                       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                 H    t        | j                  xr | 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                 H    t        | j                  xr | 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                  xs i }|j                  d      xs t        j                  dd      | _        |j                  d      xs t        j                  dd      | _	        | j                         | _        | j                         | _        d | _        d | _        d | _        d | _        d | _        |j                  d      xs | j                  | _        t)        d      | _        i | _        i | _        |j                  d	      | _        t3               | _        i | _        t3               | _        y )
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__   s+   !2!23"$yy5  
 ":
 $)99_#= $
$bB
 483Q3Q3S(,(@(@(B#'48;?(,)- %		, 7 J4?? *48=? 2405		:L0M ,/5 <> -0ErG   F)is_reconnectrk   c                  K   t         s!t        j                  d| j                         yt        s!t        j                  d| j                         y| j
                  r| j                  s!t        j                  d| j                         y	 ddlm} t        j                  d |             | _        t        j                  | j
                  | j                        }t        j                  |      | _        t         r| j"                  rt%        j&                         }d	|_        d
|_        t-        j.                  |      | _        t3        j.                  |      | _        t        j7                  d| j                  | j"                         nbt         r\t%        j&                         }d	|_        d
|_        t3        j.                  |      | _        t        j7                  d| j                         t9        j:                         }t=        | |      }| j                  j?                  t        j@                  jB                  |       t9        jD                  | jG                               | _$        | jK                          t        j7                  d| j                         y# tL        $ r+}t        jO                  d| j                  |       Y d}~yd}~ww xY w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^		 NNBDII d&9&9NNMtyy -	S % 1 1%:%<!D )33!4!4J #2"F"Fz"RD "d&<&<,335
&-
#'0
$!5!<!<Z!H"7">">z"JAII**
 $,335
&-
#'0
$"7">">z"JI499U ++-D&tT2G99..44g !( 3 3D4D4D4F GD  "KK8$))D 	LL5tyy!D	s1   BKHJ K	K"!KKKKNc                 p  K   d}| j                   r	 t        j                  d| j                         | j                  j                          d{    | j                   syt        t        |t        t              dz
           }t        j                  d| j                  |       t        j                  |       d{    |dz  }| j                   ryy7 # t        j                  $ r Y yt        $ r<}| j                   sY d}~yt        j                  d| j                  |       Y d}~d}~ww xY w7 vw)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  s     mmM=tyyI))//111 ==%c+s;L7MPQ7Q&RSEKK5tyy%H--&&&1K# mm 2))  M}}=tyy!LLM 'sk   D6=C CC A)D6=D4>D6D6C D1+D6-D15D,D6!D,'D6,D11D6c                   K   d| _         | j                          | j                  rt        | j                  dd      nd}|	 |j	                          d{    | j                  rt        | j                  d      r2	 t        j                  | j                  j                         d{    | j                  j                          	 t        j                  | j                  d       d{    d| _	        | j"                  rkt%        | j"                        D ]  }|j                           t        j&                  | j"                  d	d
i d{    | j"                  j)                          t%        | j*                        D ]  }	 | j-                  |       d{     | j.                  r)| j.                  j1                          d{    d| _        d| _        | j2                  j)                          | j4                  j)                          | j*                  j)                          | j6                  j)                          | j8                  j)                          t        j;                  d| j                         y7 .# t
        $ r,}t        j                  d| j                  |       Y d}~\d}~ww xY w7 # t
        $ r Y w xY w7 # t        j                  t        j                   f$ r$ t        j                  d| j                         Y (w xY w7 7 # t
        $ r-}t        j                  d| j                  ||       Y d}~d}~ww xY w7 w)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GZGZGD//dC`d	 `oo'''  t**G4!++D,?,?,E,EFFF $$&c&&t'8'8#FFF !%D >>T^^, ..$..IDIIINN  " T223 	H44X>>>	 ##**,,, $D"$$&$$&##%$$&'3e ( `PRVR[R[]^__` G  
 G**G,@,@A cVX\XaXabc J ? UIIx  -s  ?M J JJ "M =,K )J>*K .M 	$K -K.K 2AM L6M 	L$L!L$"+M MB5M J 	J;!J60M 6J;;M >K 	K
M KM K ALM LM !L$$	M-"MM MM c                     | j                   j                  j                  d      }|-t        |t              r|j                         dv S t        |      S t        j                  dd      j                         dv S )zBReturn whether group chats should require an explicit bot trigger.require_mention>   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  sn    [[&&**+<=
!*c*!'')-GGG
##yy3W=CCEIcccrG   c                    | j                   j                  j                  d      }|t        j                  dd      }t        |t              rA|D ch c]5  }t        |      j                         st        |      j                         7 c}S t        |      j                  d      D ch c]#  }|j                         s|j                         % c}S c c}w c c}w )Nfree_response_chatsDINGTALK_FREE_RESPONSE_CHATSrM   ,
rJ   rV   rW   r2   r3   r   r   r   stripsplitrD   rawparts      r:   _dingtalk_free_response_chatsz-DingTalkAdapter._dingtalk_free_response_chats  s    kk##$9:;)):B?Cc4 25K$T9JCIOO%KK),S)<M



MM LMs   C1C+CCc                    | j                   j                  r%| j                   j                  j                  d      nd}|t        j                  dd      }t        |t              rA|D ch c]5  }t        |      j                         st        |      j                         7 c}S t        |      j                  d      D ch c]#  }|j                         s|j                         % c}S c c}w c c}w )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   r   r   r   s      r:   _dingtalk_allowed_chatsz'DingTalkAdapter._dingtalk_allowed_chats  s     9=8I8Idkk##O4t;))4b9Cc4 25K$T9JCIOO%KK),S)<M



MM LMs   *C.	C.C3C3c                    | j                   j                  r%| j                   j                  j                  d      nd}|>t        j                  dd      j                         }|r	 t        j                  |      }|}|g S t        |t              r|g}t        |t              s6t        j                  d| j                   t#        |      j$                         g S g }|D ]W  }t        |t              r|j                         s$	 |j'                  t)        j*                  |t(        j,                               Y |r*t        j1                  d| j                   t3        |             |S # t        $ r |j                         D cg c]#  }|j                         s|j                         % nc c}w }}|sC|j                  d      D cg c]#  }|j                         s|j                         % nc c}w }}Y ~w xY w# t(        j.                  $ r-}t        j                  d| j                   ||       Y d}~Zd}~ww xY w)	z=Compile optional regex wake-word patterns for group triggers.mention_patternsNDINGTALK_MENTION_PATTERNSrM   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   loadedr   compiledpatternexcs           r:   rZ   z)DingTalkAdapter._compile_mention_patterns  s   @D@Q@Q4;;$$(();<W[))7<BBDC[!ZZ_F
 "Ih$ zH(D)NNQ		X''
 I%' 	hGgs+7==?h

7BMM BC		h KKDdiiQTU]Q^_9 ! [7:~~7GXt4::<djjlXXFX!;>99S>!Z4TZZ\$**,!Z!Z!Z[0 88 hMtyyZacfgghsN   'E' 3G>'G;F+F+*G;G0G0/G;:G;>H>"H99H>c                 4   | j                   j                  r%| j                   j                  j                  d      nd}|t        j                  dd      }t        |t              rA|D cg c]5  }t        |      j                         st        |      j                         7 }}nFt        |      j                  d      D cg c]#  }|j                         s|j                         % }}|D ch c]  }|j                          c}S c c}w c c}w c c}w )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   r   )rJ   rV   rW   r2   r3   r   r   r   r   r   r   )rD   r   r   itemsitems        r:   r\   z#DingTalkAdapter._load_allowed_users  s     9=8I8Idkk##O4t;))4b9Cc4 36L4#d)//:KSY__&LEL.1#hnnS.ARdTZZ\TZZ\RER)./

// MR/s   *D	DDD1D	sender_idsender_staff_idc                     | j                   rd| j                   v ry|xs dj                         |xs dj                         h}|j                  d       t        || j                   z        S )N*TrM   )r]   r   discardr@   )rD   r   r   
candidatess       r:   _is_user_allowedz DingTalkAdapter._is_user_allowed  sd    ""cT-@-@&@ B--//2GR1N1N1PQ
2J!4!44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                 ^    r| j                   syt        fd| j                   D              S )NFc              3   @   K   | ]  }|j                          y wN)search).0r   r   s     r:   	<genexpr>zDDingTalkAdapter._message_matches_mention_patterns.<locals>.<genexpr>  s     NG7>>$'Ns   )r[   any)rD   r   s    `r:   !_message_matches_mention_patternsz1DingTalkAdapter._message_matches_mention_patterns  s(    411Nt7M7MNNNrG   is_groupchat_idc                     |sy| j                         }|r|r||vry|r|| j                         v ry| j                         sy| j                  |      ry| j	                  |      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  sn     ..0w7'#9w$"D"D"FF--/%%g.55d;;rG   c                     t        j                  |      }| j                  j                  |       |j	                  | j                  j
                         y)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  s<    ""4(4 t~~556rG   c                   K   | j                   j                  |d      }|sy| j                          d{   }|syt        |j	                               D ]D  \  }}	 | j                  |||d       d{    t        j                  d| j                  |       F y7 h7 *# t        $ r,}t        j                  d| j                  ||       Y d}~|d}~ww xY ww)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  s      %%))'48,,..*.u{{}*= 	&L,// % 0    5II|	 /
  :II|Q sK   3CB&CB"5B 6%B"C B""	C+"CCCCc                 $    | j                   v ry j                   j                  |        j                  j                  |      }|syt	        |dd      xs dt	        |dd      xs drsyd fd} j                   |              y)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_idc                     K   j                   dd       d {    j                   dd       d {    y 7 "7 w)N   🤔ThinkingTrecallu   🥳DoneF)_send_emotion)r  msg_idrD   s   r:   _swapz2DingTalkAdapter._fire_done_reaction.<locals>._swapF  s`     $$ %    $$E %   s   A?AAAAr%   N)rg   r   re   rW   r   r  )rD   r   msgr  r  r  s   `   @@r:   _fire_done_reactionz#DingTalkAdapter._fire_done_reaction5  s     d,,,""7+$$((1lB/52!#'8"=C?	 	uwrG   c           
        K   t        |dd      xs t        j                         j                  }| j                  j                  |      r"t        j                  d| j                  |       yt        |dd      xs d}t        |dd      }t        |      dk(  }t        |d	d      xs d}t        |d
d      xs |}t        |dd      xs d}|xs |}	|rdnd}
| j                  ||      s#t        j                  d| j                  ||       y| j                  |      xs d}| j                  ||||	      s#t        j                  d| j                  ||	       y|	r*|| j                  |	<   | j                  j                  |	       t        |dd      xs d}t        |dd      xs d}|r||	rzt         j#                  |      ret%        | j&                        t(        k\  r8	 | j&                  j+                  t-        t/        | j&                                     ||f| j&                  |	<   | j3                  |       d{    | j                  |      }| j5                  |      \  }}}|s#|s!t        j                  d| j                         y| j7                  |	t        |dd      |
|||r|nd      }t        |dd      }	 |r1t9        j:                  t=        |      dz  t>        j@                        n#t9        jB                  t>        j@                        }tK        ||||||||      }t        j                  d| j                  ||	r|	dd nd|r|dd nd       | jM                  |       d{    y# t0        $ r Y jw xY w7 I# tD        tF        tH        f$ r' t9        jB                  t>        j@                        }Y w xY w7 Vw)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   r$  _early_textr   r!  r   msg_typer.  r/  r,  r(  r0  events                         r:   _on_messagezDingTalkAdapter._on_messageR  s    
 ,5I9I9I;;##F+LL>		6R "'+<bAGR#G-@#F()S0G["5;	g}b9FY!'+<bAGR!.Y'GT	 $$Y@LLZ		?I  ((17R++G[(GTLL_		67  .5D""7+""**73 "'+<dCIrG;Q?D1 	% w+?+E+Eo+V4))*.CC**..tD9O9O4P/QR  ,/D""7+ ''000 !!'* -1,?,?,H)*kJLL7C""g';TB!+: # 
 G[$7		6  &&s9~'<N\\X\\2  !!#	
 	,II#GCRLD"I9	
 !!%(((u %  	1: GY/ 	6 5I	6* 	)si   GO7M2 %O9N:BO=AN AO,O-O2	M?;O>M??O8O =O?O  Oc                    t        | dd      xs d}t        |d      r|j                  xs dj                         }nJt	        |t
              r!|j                  dd      j                         }nt        |      j                         }|st        | dd      xs t        | dd      }|rt        |dd      xs |}t	        |t              rg }|D ]  }t	        |t
              r=|j                  d      xs |j                  d      xs d}|s>|j                  |       Pt        |d      s]|j                  sj|j                  |j                          dj                  |      j                         }|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   rO  r   r   dictrW   r   r   r   r   join)r   r   rO  rQ  	rich_listpartsr   ts           r:   r8  zDingTalkAdapter._extract_text  sB    w-3 4#||)r002Gd#hhy"-335G$ioo'G)<dC wdHI #I/?FS)	i.E ) 4%dD1 $ 0 MDHHY4G M2A  %Q$T62tyy!LL34 "hhuo335G rG   c                    t         j                  }g }g }t        |dd      }|rAt        |dd      }|r2|j                  |       |j                  d       t         j                  }t        |dd      xs t        |dd      }|rt        |dd      xs |}t        |t              r~|D ]x  }	t        |	t              s|	j                  d      xs |	j                  d      xs d	}
|	j                  d
d	      }|
sRt        j                  |d      }|j                  |
       |dk(  r6|j                  d       |t         j                  k(  st         j                  }|dk(  rM|j                  d       |t         j                  k(  s|dk(  rt         j                  }t         j                  }|dk(  r8|j                  d       |t         j                  k(  s1t         j                  }C|j                  d       |t         j                  k(  sit         j                  }{ t        |dd	      xs d	}|dk(  r|st         j                  }n7|dk(  r2t        d |D              rt         j                  nt         j                  }|||fS )zMExtract media info from message. Returns (MessageType, [urls], [mime_types]).image_contentNdownload_coder!   rP  rQ  rR  downloadCoderM   r   filer"   r$   videozapplication/octet-streamr+  r#   richTextc              3   $   K   | ]  }d |v  
 yw)r!   Nr   )r   rX  s     r:   r   z1DingTalkAdapter._extract_media.<locals>.<genexpr>4  s     9w!|9   )r   TEXTr   r   PHOTOr   r   rT  rW   DINGTALK_TYPE_MAPPINGVOICEAUDIOVIDEODOCUMENTr   )rD   r   rK  r.  r/  rZ  r[  rQ  rV  r   dl_code	item_typemappedmsg_type_strs                 r:   r@  zDingTalkAdapter._extract_media  sT   ##
  $?#M?DIM!!-0""7+&,, G%8$? 
7[$D
	 	+;TBOiI)T*% DD!$- HH^4W8QWUW   %)HHVR$8	"%:%>%>y&%QF&--g6%0 + 2 27 ;#+{/?/?#?/:/@/@H!'7!2 + 2 27 ;#+{/?/?#?
 (1G';3>3D3D3>3D3D!'7!2 + 2 27 ;#+{/?/?#?/:/@/@H + 2 23M N#+{/?/?#?/:/C/CH?DB w;Ar9$Z"((HZ' 9[99 !! %%  [00rG   rO  reply_tometadatac           	      R  K   |xs i }t         j                  d| j                  |t        | j                  xr | j
                               |j                  d      }|sF| j                  |      }|s.t         j                  d| j                  |       t        dd      S |\  }}| j                  st        dd      S | j                  j                  |      }|du}	| j                  r|r| j
                  r| j                  |       d{    | j                  ||||		       d{   }
|
rL|
j                  r@|	r| j                  |       |
S || j                   j#                  |i       |
j$                  <   |
S t         j                  d
| j                         t         j                  d| j                         | j'                  |d| j(                         }dd|dd}	 | j                  j+                  ||d       d{   }|j,                  dk  r?|	r| j                  |       t        dt/        j0                         j2                  dd       S |j4                  }t         j                  d| j                  |j,                  |dd        t        dd|j,                   d|dd        S 7 7 7 # t6        j8                  $ r t        dd      cY S t:        $ rA}t         j=                  d| j                  |       t        dt?        |            cY d}~S d}~ww xY ww)z3Send a markdown reply via DingTalk session webhook.z&[%s] send() chat_id=%s card_enabled=%sr   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   )msgtyperr  g      .@)r   ro   i,  T   rq  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_cardrq  r  rh   
setdefaultr  _normalize_markdownMAX_MESSAGE_LENGTHpoststatus_coder4  r5  r6  r   r1   TimeoutExceptionr.   r   r   )rD   r   rO  rm  rn  r   webhook_info_current_messageis_final_replyresult
normalizedpayloadrespbodyr   s                   r:   sendzDingTalkAdapter.send<  s     >r4II'':DNN;		
 #,,'89227;LBIIw "!f  ".OQ  e3PQQ 0044W= "- !!o$.. 0099977'' 8  F &..!,,W5    ))44WbA)) NNNPTPYPYZ/;--g6O8O8O.PQ
 ""*J?

	;**//gt 0  D #% ",,W5!$4::<;K;KCR;PQQ99DNN.		4;K;KTRVSVZ uT-=-=,>bds%M [ :<  %% 	%J   	;LL.		1=e3q6::	;s   D	L'J1L'(J4)B8L'"!J9 J7AJ9 L'AJ9 0L'4L'7J9 9 L$L'L$#6LL$L'L$$L'c                    K   yw)z,DingTalk does not support typing indicators.Nr   )rD   r   rn  s      r:   send_typingzDingTalkAdapter.send_typing  s	     s   	image_urlcaptionc                 j   K   d| d}|r| d| n|}| j                  ||||       d{   S 7 w)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   rO  rm  rn  N)r  )rD   r   r  r  rm  rn  image_blockrO  s           r:   
send_imagezDingTalkAdapter.send_image  sV      ")A.3:WIT+/YY	  
 
 	
 
s   *313
image_pathc                 $   K   t        dd      S w)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.rp  r   )rD   r   r  r  rm  rn  kwargss          r:   send_image_filezDingTalkAdapter.send_image_file  s      Y
 	
ra  	file_path	file_namec                 $   K   t        dd      S w)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.rp  r  )rD   r   r  r  r  rm  rn  r  s           r:   send_documentzDingTalkAdapter.send_document  s      Y
 	
ra  c                 @   K   |d|j                         v rddS ddS w)z0Return basic info about a DingTalk conversation.r  r  )ru   r   )r   )rD   r   s     r:   get_chat_infozDingTalkAdapter.get_chat_info  s2      &'--/9G
 	
?C
 	
s   c                 $   | j                   j                  |      }|sy|\  }}|rj|dkD  ret        t        j                  t
        j                        j                         dz        }d}||z   |k\  r| j                   j                  |d       y|S )z@Get a valid (non-expired) session webhook for the given chat_id.Nr   r)  rP   i )	rd   rW   rC  r   rE  r   rD  r0  r  )rD   r   r   webhookexpired_time_msnow_mssafety_margin_mss          r:   rx  z"DingTalkAdapter._get_valid_webhook  s    %%))'2#' 26@@BTIJF,((O;&&**7D9rG   Tr  r  c          
      N  K   	 | j                          d{   }|sydt        j                         j                  dd  }t	        |dd      xs d}t	        |dd      }t        |      dk(  }	t	        |d	d      xs d}
t        j                         }t        j                  | j                  |t        j                  d
di      dt        j                  d      t        j                  d            }t        j                  |      }| j                  j!                  |||       d{    |	r=d| }t        j"                  |d|t        j$                  | j&                              }nU|
s!t(        j+                  d| j,                         yd|
 }t        j"                  |d|t        j.                  d            }t        j0                  |      }| j                  j3                  |||       d{    | j5                  ||||       d{    t(        j7                  d| j,                  |rdnd|       t9        d|      S 7 :7 '7 Z7 ?# t:        $ r>}t(        j+                  d| j,                  |t=        j>                                Y d}~yd}~ww xY w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_rv  r  rM   r  r   r  r   rO  )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)rw  z![%s] AI Card create failed: %s
%s) r  r4  r5  r6  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   rO  r  r
  r  r  r  r   r   runtimecreate_requestcreate_headersr  deliver_requestdeliver_headersr   s                     r:   ry  z'DingTalkAdapter._create_and_stream_card   s     f	0022E$TZZ\%5%5cr%:$;<L%g/@"EKO '1Dc J,-4H%g/@"EKO%446G 2CC!%!7!7).HH$-r? '(OO(,
 )OO(,N& 2CC,1N ..??  
 "66G H"6"I"I!-!""/,VV'+'7'7	# 'NNN		  "66G H"6"I"I!-!""/,VV'1	# 3EE,1O ..@@'   ++eWx ,    KK%		'/#5J	 d|DD} 3JP  	NN4		1i224 	s   J%I II J%DI -I.A%I J%A(I <I=I I6I J%I I I I 	J"$4JJ%J""J%r  c                  K   |st        dd      S | j                          d{   }|st        dd      S 	 | j                  ||||       d{    |r| j                  j	                  |i       j                  |d       | j                  j	                  |      s| j                  j                  |d       t        j                  d| j                  |       | j                  |       n|| j                  j                  |i       |<   t        d|	      S 7 7 # t        $ rA}t        j                  d
| j                  |       t        dt        |            cY d}~S d}~ww xY w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 requiredrp  NzNo access tokenr  z![%s] AI Card finalized (edit): %sTrw  z[%s] Card edit failed: %s)r   r  r  rh   rW   r  rs   r   ru   r  rz  r.   rt   r   )rD   r   r  rO  r  r
  r   s          r:   edit_messagezDingTalkAdapter.edit_messagex  sR     e3HII,,..e3DEE	;++E7X ,     %%))'26:::tL,,009))--gt<7IIz ((1
 MT%%00"=jIdzBB3 /
*  	;NN6		1Ee3q6::	;sQ   #E,DE,D DCD E,D 	E)(6E$E)E,$E))E,r  r
  c           	      6  K   t        j                  |t        t        j                               d|d| j
                   d|d      }t        j                  |      }t        j                         }| j                  j                  |||       d{    y7 w)z&Stream content to an existing AI Card.rO  NTF)r  guidkeyrO  is_fullis_finalizeis_errorr  )r  StreamingUpdateRequestr   r4  r5  r|  StreamingUpdateHeadersr  r  rB   #streaming_update_with_options_async)rD   r  r
  rO  r  stream_requeststream_headersr  s           r:   r  z$DingTalkAdapter._stream_card_content  s      .DD%TZZ\"5d556 
 .DD(-
 "002nn@@NG
 	
 	
s   BBBBc                    K   | j                   sy	 t        j                  | j                   j                         d{   }|S 7 # t        $ r+}t
        j                  d| j                  |       Y d}~yd}~ww xY w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  sh     ""	!++D,?,?,P,PQQEL R 	LL>		1M	s>   A=,A AA A=A 	A:!A50A=5A::A=r  open_msg_idopen_conversation_id
emoji_namer  c          	      2  K   | j                   r|r|sy|rdnd}	 | j                          d{   }|sy| j                  ||d|d}t        j                         }|rmt        j                  d||d      |d	<   t        j                  di |}	t        j                  |
      }
| j                   j                  |	|
|       d{    nlt        j                  d||d      |d	<   t        j                  di |}	t        j                  |
      }
| j                   j                  |	|
|       d{    t        j                  d| j                   |||dd        y7 07 7 0# t"        $ r& t        j%                  d| j                   |d       Y yw xY w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     k9M#9	0022E #..*(< ! *N &446G)NN#,%/'&/	 ~. 0II $ 4MM05 ooMM['  
 *MM#,%/'&/	 ~. 0HH $ 4LL05 ooLL['   KK5		6:{3B/?c 38$  	LL.		6D  	sh   FE% EE% FBE% E!A,E% 2E#3*E% FE% !E% #E% %,FFFFc           
        K   | j                          d{   }|syt        |dd      xs | j                  }g }t        |dd      }|r t        |dd      r|j                  |df       t        |dd      }|rUt        |dg       xs g }|D ]?  }t	        |t
              sdD ]'  }	|j                  |	      s|j                  ||	f       ) A |syg }
|D ]V  \  }}	t        ||	      rt        ||	d      n|j                  |	      }|s3|
j                  | j                  |||||	             X t        j                  |
dd	i d{    y7 67 w)
z1Resolve download codes in message to actual URLs.NrO   rZ  r[  rP  rR  )r\  pictureDownloadCoder[  r   T)r  r   rX   r   r   rT  rW   r   _fetch_download_urlr   r   )rD   r   r
  rO   codes_to_resolveimg_contentrQ  rV  r   r  tasksobjcodes                r:   r?  z$DingTalkAdapter._resolve_media_codes  sj    ,,..WlD9LT__
 g=7;F##[/$BC G%8$?		+;R@FBI! AdD)W A88C=,33T3K@AA   ( 	HC.5c3.?73T*SWWS\D,,T:uc3O	 nne<t<<<G /F 	=s5   EEBE E7AE>EEEEr  rO   r  c                   K   | j                   s!t        j                  d| j                         y	 t	        j
                  ||      }t	        j                  |      }t        j                         }| j                   j                  |||       d{   }	|	r|	j                  nd}
|
r?t        |
dd      }|rSt        ||      rt        |||       yt        |t              r(|||<   yt        j                  d| j                  |       yyy7 y# t         $ r,}t        j#                  d| j                  ||       Y d}~yd}~ww xY ww)z9Fetch download URL for a single code using the robot SDK.z9[%s] Robot SDK not initialized, cannot resolve media codeN)r[  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   rT  r.   r   )rD   r  rO   r
  r  r  r  headersr  responser  urlr   s                r:   r  z#DingTalkAdapter._fetch_download_url<  s-     NNK		 	W+KK"%G ,KK,1G &446G!__[[' H %-8==$DdND9sC(S#.#C.#&COII / "  	WLLA499dTUVV	WsY   .EA!D D>D ED 'E(!D 	ED 	E"D>9E>EEc                 :   | j                  d      }g }t        |      D ]  \  }}t        j                  d|j	                               }|rR|dkD  rM||dz
     }|j	                         r5t        j                  d|j	                               s|j                  d       |j	                         j                  d      r=||j                         k7  r*t        |      t        |j                               z
  }||d }|j                  |        dj                  |      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   r:  r   r   
startswithlstripr   rU  )r   linesoutilineis_numberedprevindents           r:   r{  z#DingTalkAdapter._normalize_markdownc  s     

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

=Kq1uQU|::<djjl(KJJrNzz|&&u-$$++-2GTS%77FG}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  rM  staticmethodr8  r@  r   r   r   r   r  r  r  r  r  r  tuplerC  rx  ry  r  r  r  r  r?  r  r{  __classcell__rj   s   @r:   r=   r=      s    ,?$ ? ? ? ? ?22~ 22l 5: @T @d @D,=4Bd4 dNs3x NNS N$4

+; $L0SX 06# 6 6 6>-= >$ >Oc Od O
</? <s <VZ <eh <mq <87s t < 3  4  :t)!t) 
t)l ,/ ,C , ,\A1R #'-1j;j; j; 3-	j;
 4S>*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| -;-; -; 	-; -; 
-;h 

 
 	

 
 

6
# 
$ EE "E 	E E 
EN%=N%W%W%(%W14%W@C%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.
    adapterr   c                 J    t         rt        | 	          || _        || _        y r   )r+   rS   rT   _adapter_loop)rD   r  r   rj   s      r:   rT   z_IncomingHandler.__init__  s    $G
rG   c                      y)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   c                 h  K   	 |j                   }t        |t              rt        j                  |      }t        j                  |      }t        |dd      sCt        |t              r(|j                  d      xs |j                  d      xs dnd}|r||_
        t        |dd      s,t        |t              r|j                  d      nd}|rd|_        t        |d	d      xs d}t        |d
d      xs d}|r:|r8| j                  j                  | j                  j                  ||dd             t        j                   | j#                  |             t,        j0                  dfS # t$        $ r? t&        j)                  d| j                  j*                         t,        j.                  dfcY S w xY ww)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.
        r   NsessionWebhookrM   r   F
isInAtListTr  r  r  r  z%[%s] Error preparing incoming messager   OK)datar   r   r   r   r   	from_dictr   rT  rW   r   r   r  r  r  r   r   _safe_on_messager.   rs   	exceptionru   r   r   r   )rD   r   r#  chatbot_msgr  raw_flagr  r  s           r:   processz_IncomingHandler.process  s    5	?<<D$$zz$' )2248K
 ;(94@
  d+ HH-. xx 1213	 
 29K/ ;?.8t.DDHH\*%  04K-[,=CF%k3DdKQrO /''MM// 0   5 5k BC ##T))  	?79K9K 55w>>		?s*   F2EE' F2'AF/,F2.F//F2c                    K   	 | j                   j                  |       d{    y7 # t        $ r- t        j	                  d| j                   j
                         Y yw xY ww)z1Wrapper that catches exceptions from _on_message.Nz&[%s] Error processing incoming message)r  rM  r.   rs   r&  ru   )rD   r'  s     r:   r%  z!_IncomingHandler._safe_on_message  sN     	--++K888 	8$--:L:L	s1   A#* (* A#* 3A A#A  A#r   r  )r   r   )r'  r   r%   N)r   r  r  r  r=   r   r   AbstractEventLooprT   r  r)  r%  r  r  s   @r:   r   r     s7     x@Y@Y7Z B*HrG   r   )	thread_idmedia_filesforce_documentc                  K   t        | di       xs i }	 ddl}	 |j                  d      xs t	        j
                  dd      }|sdd	iS  |j                  d
      4 d{   }	|	j                  |dd|id       d{   }
|
j                          |
j                         }|j                  dd      dk7  r(dd|j                  dd       icddd      d{    S ddd      d{    dd|dS # t        $ r ddicY S w xY w7 7 7 27 ## 1 d{  7  sw Y   3xY w# t        $ r7}	 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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   rO  )ru  r   )r   errcodezDingTalk API error: errmsgunknownTdingtalk)rq  platformr   )_errorzDingTalk send failed: )r   r1   ImportErrorrW   r2   r3   rw   r}  raise_for_statusr   r.   tools.send_message_toolr7  )pconfigr   r   r,  r-  r.  rV   r1   r0  r   r  r#  r   _redact_errors                 r:   _standalone_sendr=    s    " GWb)/RE0;ii.W"))<RTV2W  K  L  L$5$$T2 	Y 	Yf!')W1EF %  D !!#99;Dxx	1%*#798U7V!WX	Y 	Y 	Y 	Y 	Y  ZGLL  0.//0	Y	Y 	Y 	Y 	Y 	Y  	;
	;G #9!!=>> 	;5aS9::	;	;s   E+C8 .D( E+	D(  D!D( $D DADD( DD( !E+"D( -D.	D( 7E+8DE+DE+D( DD( D( D%DD%!D( (	E(2EE(E+E E#E(E+E  E##E((E+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y |dddgd      }	|	dk(  rF	 ddlm}
  |
       }| |d       t        |||       y|\  }} |d|        |d|        |d       yt        |||       y# t        $ r#} |d| d       t        |||       Y d}~yd}~ww xY w)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.configr?  r@  hermes_cli.setuprA  hermes_cli.cli_outputr(   rB  rC  rD  rE  hermes_cli.dingtalk_authrH  r8  _manual_credential_entry)r?  r@  rA  r(   rB  rC  rD  rE  existingmethodrH  r   r  rL   rN   s                  r:   interactive_setuprP  '  s    @.  12HCH:RPQ4e<Q8	
 F {	A
 "#>MN$V^]K#) 	=+Y7/?89 G  	;C5@`ab$V^]K	s   B+ +	C4CCc                 f     | d      }|sy  |d|        | dd      }|r	 |d|        |d       y )NzDingTalk Client ID (app key)r)   zDingTalk Client SecretT)passwordr*   zDingTalk credentials savedr   )r(   r@  rD  rL   rN   s        r:   rM  rM  \  sB    56I'33dCM/?./rG   yaml_cfgdingtalk_cfgc                    ddl }d|v rBt        j                  d      s-t        |d         j	                         t        j
                  d<   d|v r:t        j                  d      s%|j                  |d         t        j
                  d<   |j                  d      }|Yt        j                  d      sDt        |t              rd	j                  d
 |D              }t        |      t        j
                  d<   |j                  d      }|Yt        j                  d      sDt        |t              rd	j                  d |D              }t        |      t        j
                  d<   |j                  d      }||j                  d      }t        |t              r|j                  d      }|| j                  d      }t        |t              r|j                  d      nd}|| j                  d      fD ]|  }	t        |	t              s|	j                  d      }
t        |
t              r|
j                  d      nd}t        |t              sY|j                  d      k|j                  d      } n |Yt        j                  d      sDt        |t              rd	j                  d |D              }t        |      t        j
                  d<   y)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   2   K   | ]  }t        |        y wr   r   r   vs     r:   r   z%_apply_yaml_config.<locals>.<genexpr>w  s     /a3q6/   r   r   c              3   2   K   | ]  }t        |        y wr   rW  rX  s     r:   r   z%_apply_yaml_config.<locals>.<genexpr>|  s     -Q#a&-rZ  r   rV   gateway	platformsr5  r   c              3   2   K   | ]  }t        |        y wr   rW  rX  s     r:   r   z%_apply_yaml_config.<locals>.<genexpr>  s     7!s1v7rZ  )r   r2   r3   r   r   environdumpsrW   r   r   rU  rT  )rS  rT  _jsonfrcacr   _extra_gw_gw_platforms
_container_dt	_dt_extras               r:   _apply_yaml_configrj  g  sB    L(;U1V14\BS5T1U1[1[1]

-.\)"))<W2X27++lK]>^2_

./


0
1C
ryy)GHc4 ((/3//C58X

12			/	*B	~bii(@Ab$-"--B/22w

+,/G !!'*fd#jj1G?,,y)C4>sD4ICGGK0tM,hll;.GH 
!*d3 nnZ00:30ECGGG,4	i.9==3Q3]'mmO<G 299-E#Fgt$hh7w77G/27|

+,rG   c                     t        | di       xs i }t        |j                  d      xs t        j                  d      xr( |j                  d      xs 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_connectedrl    s`     FGR(.BE	;		B299-A#B 	PYY'N2995M+N rG   c                     t        |       S )zFFactory wrapper that constructs DingTalkAdapter from a PlatformConfig.)r=   )rJ   s    r:   _build_adapterrn    s    6""rG   c                     | j                  ddt        t        t        t        ddgdt        t
        dddt        d	d
       y)u:   Plugin entry point — called by the Hermes plugin system.r5  rF  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_platformrn  r;   rl  rP  rj  r=  )ctxs    r:   registerr    sK    &,"%*,DE@"/204-!!  rG   r  )Ir  r   r   loggingr2   r   r  r4  r   r   typingr   r   r   r   r	   r/   r   r0   r   r   r+   r.   r   r1   r,   r8  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   r;  r   r9  rd  r@   r;   r=   ChatbotHandlerobjectr   r=  rP  rM  rT  rj  rl  rn  r  r   rG   r:   <module>r     s  8    	 	   ' 1 1.B $O C? 4 9  
		8	$ &  !rzz"JK   T B`) `P'e&?O""Vev -;`2Hj05 5T 5dTk 5p
T 
#
I4   %ONO
'*	
J$  OE2    OOs5   D D8 E D54D58	EEEE