
    `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	 ddl
mZ ddlmZ  ej                  e      Z ej"                  d      Z ej"                  d      Z ej"                  d      Z ej"                  d	      Z ej"                  d
      Z ej"                  d      ZeZ eh d      Z ej"                  d      Z ej"                  dej8                        Z ej"                  d      ZddiZh dZ h dZ!h dZ"ddhZ#ddhZ$e e!z  h dz  Z%dZ&dZ'd Z( ej"                  dej8                        Z) ej"                  dej8                        Z*de+fd Z,d!e+de-fd"Z.d#e+d$e+de+fd%Z/d&e0d'e1de2dz  fd(Z3d)d*d+e1fd,Z4d-d.d/d0g d1d2d3d0d4d5d0d6d5d0d7d5d0d8d5d9g d:d;Z5d< Z6d= Z7dWd?Z8d@ Z9d#e+dAe+fdBZ:dC Z;dD Z<d#e+d$e+dEe+dz  fdFZ=ddd>dGdHZ>dXdIZ?dJe0de@fdKZAdYdLZBdZdMZCdZdNZDd[dOZEdP ZFdZdQZGdR ZHdS ZIdT ZJdZdUZKddVlLmMZM y)\a$  Send Message Tool -- cross-channel messaging via platform APIs.

Sends a message to a user or channel on any connected messaging platform
(Telegram, Discord, Slack). Supports listing available targets and resolving
human-friendly channel names to IDs. Works in both CLI and gateway contexts.
    N)
formatdate)redact_sensitive_text)
get_secretz^\s*(-?\d+)(?::(\d+))?\s*$zD^\s*((?:oc|ou|on|chat|open)_[-A-Za-z0-9]+)(?::([-A-Za-z0-9_]+))?\s*$z^\s*([CGDU][A-Z0-9]{8,})\s*$z%^\s*([CGD][A-Z0-9]{8,}):([^\s:]+)\s*$zS^\s*((?:wxid|gh|v\d+|wm|wb)_[A-Za-z0-9_-]+|[A-Za-z0-9._-]+@chatroom|filehelper)\s*$z ^\s*((?:group|direct):[^:]+)\s*$>   smsphotonsignalwhatsappz^\s*\+(\d{7,15})\s*$zB^\s*[\w-]+@(?:g\.us|s\.whatsapp\.net|lid|broadcast|newsletter)\s*$z6^\s*[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\s*$emailEMAIL_HOME_ADDRESS>   .gif.jpg.png.jpeg.webp>   .3gp.avi.mkv.mov.mp4>   .wav.flac.m4a.mp3.ogg.opusr   r   r   r   >   .md.csv.doc.pdf.txt.zip.docx.xlsxi      c                   | xs dj                         }|xs g }|rt        |      dk7  rd| fS |d   \  }}|rd| fS t        j                  j	                  |      d   j                         }|t        vrd| fS t        |      |kD  rd| fS |dfS )ux  Decide whether the accompanying text should ride on the media bubble.

    Single enforced chokepoint for the ``MEDIA:<path> caption`` behavior
    across every standalone sender. ``hermes send`` (and the send_message
    tool / cron) strips the ``MEDIA:`` tag and leaves the remaining prose as
    ``text``; historically each platform sent that ``text`` as a *separate*
    message before an uncaptioned media bubble, splitting the reported case
    ``hermes send --to whatsapp "MEDIA:/x.png This Caption"`` into two parts.

    Returns ``(caption, body_text)``:

    * ``(caption, "")`` — attach ``text`` to the media as its native caption
      and send *no* separate body message. Only when there is exactly one
      media file, it is a captionable kind (image/video/document, not a
      voice/audio note), and ``text`` fits ``max_caption_len``.
    * ``(None, text)`` — keep the historical behavior: ``text`` is a separate
      body message and the media carries no caption. Applies to multi-file
      sends (caption→file association is ambiguous), voice/audio notes, empty
      text, or text longer than the caption limit.
        Nr   )striplenospathsplitextlower_CAPTIONABLE_EXTS)textmedia_filesmax_caption_lenstrippedmedia
media_pathis_voiceexts           J/root/.hermes/venv/lib/python3.12/site-packages/tools/send_message_tool.py_media_caption_splitr8   V   s    * 
!!#H2Es5zQTz 8JTz
''

:
&q
)
/
/
1C
##Tz 8}&TzR<    zP([?&](?:access_token|api[_-]?key|auth[_-]?token|token|signature|sig)=)([^&#\s]+)zJ\b(access_token|api[_-]?key|auth[_-]?token|signature|sig)\s*=\s*([^\s,;]+)returnc                 x    t        |       }t        j                  d |      }t        j                  d |      }|S )zCRedact secrets from error text before surfacing it to users/models.c                 *    | j                  d       dS )Nr'   z***groupms    r7   <lambda>z&_sanitize_error_text.<locals>.<lambda>   s    QWWQZL2D r9   c                 *    | j                  d       dS )Nr'   z=***r=   r?   s    r7   rA   z&_sanitize_error_text.<locals>.<lambda>   s    !''!*T7J r9   )r   _URL_SECRET_QUERY_REsub_GENERIC_SECRET_ASSIGN_RE)r/   redacteds     r7   _sanitize_error_textrG      s7    $T*H#''(DhOH(,,-JHUHOr9   messagec                     dt        |       iS )z9Build a standardized error payload with redacted content.error)rG   )rH   s    r7   _errorrK      s    )'233r9   platform_namechat_idc                 F    | dk(  rt        |      j                  d      ry|S )zHReturn a result-safe chat identifier for tool transcripts/log consumers.r   group:z	group:***)str
startswith)rL   rM   s     r7   _display_chat_idrR      s#     S\%<%<X%FNr9   excattemptc                    t        | dd       }|	 t        t        |      d      S t        |       j                         }d|v sd|v ry d|v sd|v sd|v sd	|v sd
|v sd|v sd|v sd|v rt        d|z        S y # t        t        f$ r Y yw xY w)Nretry_afterg        g      ?z	timed outtimeoutzbad gateway502ztoo many requests429zservice unavailable503zgateway timeout504   )getattrmaxfloat	TypeError
ValueErrorrP   r-   )rS   rT   rV   r/   s       r7   _telegram_retry_delayrb      s    #}d3K	u[)3// s8>>Ddi4/D=$&D= D(D=$D=Q'\""# :& 		s   A7 7B	B	   )attemptsrd   c                H  K   t        |      D ]  }	  | j                  di | d {   c S  y 7 # t        $ rd}t        ||      }|||dz
  k\  r t        j                  d|dz   ||t        |             t        j                  |       d {  7   Y d }~d }~ww xY ww)Nr'   zFTransient Telegram send failure (attempt %d/%d), retrying in %.1fs: %s )	rangesend_message	Exceptionrb   loggerwarningrG   asynciosleep)botrd   kwargsrT   rS   delays         r7   !_send_telegram_message_with_retryrq      s     ? '	')))3F333'3 	')#w7E}8a< 7NNX!$S) --&&&	'sC   B"202B"2	BABBBB"BB"rh   a  Send a message to a connected messaging platform, or list available targets.

IMPORTANT: When the user asks to send to a specific channel or person (not just a bare platform name), call send_message(action='list') FIRST to see available targets, then send to the correct one.
If the user just says a platform name like 'send to telegram', send directly to the home channel without listing first.objectstring)sendlistreactunreacta  Action to perform. 'send' (default) sends a message. 'list' returns all available channels/contacts across connected platforms. 'react' attaches an emoji reaction to a message (platforms that support it, e.g. photon/iMessage tapbacks). 'unreact' retracts a previously-added reaction.)typeenumdescriptiona  Delivery target. Format: 'platform' (uses home channel), 'platform:#channel-name', 'platform:chat_id', or 'platform:chat_id:thread_id' for Telegram topics and Discord threads. Examples: 'telegram', 'telegram:-1001234567890:17585', 'discord:999888777:555444333', 'discord:#bot-home', 'slack:#engineering', 'signal:+155****4567', 'matrix:!roomid:server.org', 'matrix:@user:server.org', 'ntfy:alerts-channel' (explicit ntfy topic), 'yuanbao:direct:<account_id>' (DM), 'yuanbao:group:<group_code>' (group chat))rx   rz   u   The message text to send. To send an image or file, include MEDIA:<local_path> (e.g. 'MEDIA:/tmp/report.pdf') in the message — the platform will deliver it as a native media attachment.u   For action='react': the emoji to react with (e.g. '❤️'). On iMessage, ❤️👍👎😂‼️❓ render as native tapbacks; other emoji use custom-emoji reactions.zFor action='react'/'unreact': id of the message to react to. Omit to target the most recent message received in that chat (usually the one being replied to).)actiontargetrH   emoji
message_id)rx   
propertiesrequired)namerz   
parametersc                     | j                  dd      }|dk(  r
t               S |dk(  rt        |       S |dk(  rt        | d      S t        |       S )z-Handle cross-channel send_message tool calls.r{   rt   ru   rv   rw   T)remove)get_handle_list_handle_react_handle_send)argskwr{   s      r7   send_message_toolr      sU    XXh'F~T""T$//r9   c                      	 ddl m}  t        j                  d |        i      S # t        $ r+}t        j                  t        d|             cY d}~S d}~ww xY w)z5Return formatted list of available messaging targets.r   )format_directory_for_displaytargetsz"Failed to load channel directory: N)gateway.channel_directoryr   jsondumpsri   rK   )r   es     r7   r   r      sQ    LJzz9&B&DEFF Lzz&#EaS!IJKKLs   !$ 	A AAAFc                 b   | j                  dd      }| j                  d      xs dj                         }| j                  d      xs dj                         xs d}|r|s|st        |sd      S d      S |j                  dd	      }|d
   j                         j	                         }t        |      d	kD  r|d	   j                         nd}d}|r(t        ||      \  }}	}
|s	 d
dlm}  |||      }|xs |}	 d
dl
m}m}  ||      }|s9	  |       }|j                  |      }|st        d| d| d      S |j                   }d}	 d
dlm}  |       }||j&                  j                  |      nd}|t        d| d      S |rdnd}t)        ||d      }t+        |      st        d| d      S 	 d
dlm} |r | |||            }n | ||||            }t7        |t8              rt1        j2                  |      S t1        j2                  dt;        |      i      S # t        $ r d}Y =w xY w# t        t        f$ r t        d|       cY S w xY w# t        $ r d}Y @w xY w# t        $ r d}Y "w xY w# t        $ r+}t1        j2                  t5        d|             cY d}~S d}~ww xY w)u  Attach (or with ``remove=True`` retract) an emoji reaction on a message
    via a live gateway adapter.

    Only adapters that expose ``add_reaction(chat_id, emoji, message_id)`` /
    ``remove_reaction(chat_id, message_id)`` coroutines support this (e.g.
    photon/iMessage tapbacks). Requires the gateway to be running in this
    process — there is no standalone fallback, since reacting needs the
    adapter's live message-id state.
    r|   r&   r}   r~   Nz:Both 'target' and 'emoji' are required when action='react'z*'target' is required when action='unreact':r'   r   resolve_channel_name)Platformload_gateway_configUnknown platform: z.No chat specified and no home channel set for z. Use 'z
:chat_id'._gateway_runner_refzReactions require a live zN adapter in the running gateway (not available from cron/standalone contexts).remove_reactionadd_reaction
Platform 'z%' does not support message reactions.
_run_async)rM   r~   )rM   r}   r~   zReaction failed: success)r   r(   
tool_errorsplitr-   r)   _parse_target_refr   r   ri   gateway.configr   r   ra   KeyErrorget_home_channelrM   gateway.runr   adaptersr]   callablemodel_toolsr   r   r   rK   
isinstancedictbool)r   r   r|   r}   r~   partsrL   
target_refrM   
_thread_id_r   resolvedr   r   platformconfighomerunnerr   adapterfn_namereact_fnr   resultr   s                             r7   r   r     s    XXh#FXXg$"++-E((<(.B557?4J& I
 	
 >
 	
 LLa E!HNN$**,M%(Z!^q!JG!2=*!MQ J/zJ ,*G@@M* 	(*F**84D @ P%j2  ,,F3$& 06/Afoo!!(+tG' 7E E
 	
 $*~Gw.HH'LM
 	
;*Z@F  *MF
 &$zz&!!::y$v,/00w     ! @.}o>??@  	D	  2  ;zz&#4QC!89::;sf   #H! 9H3 I I( 2*I: !H0/H03III%$I%(I76I7:	J. J)#J.)J.c                 
  ) | j                  dd      }| j                  dd      }|r|st        d      S |j                  dd      }|d   j                         j	                         }t        |      dkD  r|d   j                         nd}d}d}|rt        ||      \  }}}nd	}|rD|sB	 dd
lm}	  |	||      }
|
rt        ||
      \  }}}nt        j                  dd| d| di      S 	 ddlm}  |       rt        d      S 	 ddlm}m}  |       }	  ||      }|j(                  j                  |      }|r|j*                  s|dk(  rt-        dd      j                         }t-        dd      j                         }|rH|rFddlm}  |d||t-        dd      j                         t-        dd      j                         d      }nt        d| d      S t        d| d      S dd lm} d!|v }|j5                  |      \  }}|j7                  |      }|j                         xs t9        |      }d	}|s|j;                  |      }|s<|dk(  r7t=        j>                  d"d      j                         }|rdd#lm }  |||d$%      }|r|jB                  }d}nHtD        j                  ||jG                          d&      }t        j                  dd'| d(| d)| d*i      S tI        |||      } | rt        j                  |       S |d+k(  r]|r[|jK                  d,      rJ	 ddl&))fd-}!dd.l'm(}"  |" |!|jR                  |            }#|#r|#}nt        j                  dd/| d0i      S 	 	 dd.l'm(}"  |"tU        |||||||2            }$|r-tW        |$tX              r|$j                  d3      rd4| d5| d6|$d7<   tW        |$tX              rI|$j                  d3      r8|r6	 dd8l-m.}% dd9l/m0}&  |&d:d;      }' |&d<d      xs d}( |%||||'||(=      rd|$d><   tW        |$tX              rd|$v rtc        |$d         |$d<   t        j                  |$      S # t        $ r! t        j                  dd| d| di      cY S w xY w# t        $ r+}t        j                  t#        d|             cY d}~S d}~ww xY w# t$        t&        f$ r t        d|       cY S w xY w# t        $ r$}t        j                  dd1| i      cY d}~S d}~ww xY w# t        $ r Y w xY w# t        $ r+}t        j                  t#        d?|             cY d}~S d}~ww xY w)@z$Send a message to a platform target.r|   r&   rH   z;Both 'target' and 'message' are required when action='send'r   r'   r   NFr   rJ   zCould not resolve 'z' on z;. Use send_message(action='list') to see available targets.z). Try using a numeric channel ID instead.)is_interruptedInterrupted)r   r   zFailed to load gateway config: r   weixinWEIXIN_TOKENWEIXIN_ACCOUNT_IDPlatformConfigTWEIXIN_BASE_URLWEIXIN_CDN_BASE_URL)
account_idbase_urlcdn_base_url)enabledtokenextrar   zZ' is not configured. Set up credentials in ~/.hermes/config.yaml or environment variables.)BasePlatformAdapterz[[as_document]]WEIXIN_HOME_CHANNEL)HomeChannelzWeixin Home)r   rM   r   _HOME_CHANNELzNo home channel set for zQ to determine where to send the message. Either specify a channel directly with 'z=:CHANNEL_NAME', or set a home channel via: hermes config set z <channel_id>slackUc                 4  K   d}d|  dd}j                  j                  d            4 d {   }|j                  ||d|gi	      4 d {   }|j                          d {   }|j	                  d
      r*|d   d   cd d d       d {    cd d d       d {    S 	 d d d       d {    d d d       d {    y 7 7 {7 e7 >7 /7 7 # 1 d {  7  sw Y   nxY wd d d       d {  7   y # 1 d {  7  sw Y   y xY ww)Nz(https://slack.com/api/conversations.openzBearer application/jsonAuthorizationzContent-Type
   )totalrW   users)headersr   okchannelid)ClientSessionClientTimeoutpostr   r   )r   user_idurlr   sessionrespdataaiohttps          r7   _open_slack_dmz$_handle_send.<locals>._open_slack_dm  s     @.5eW,=Oab"009N9NUW9N9X0Y $ $]d&||CRYQZG[|\ $ $`d%)YY[088D>#'	?4#8$ $ $$ $ $
  $	$ $$ $ $$0$$$$$ $ $$ $ $ $ $s   1DCDDCDC,C-C	DCDD&C'D,C-D8C9D=DC	DDCDDDDC.	"C%#C.	*D1D<C?=DD	D
DDr   z"Could not open DM with Slack user z#. Check bot permissions (im:write).zFailed to open Slack DM: 	thread_idr0   force_documentr   zSent to z home channel (chat_id: )note)mirror_to_sessionget_session_envHERMES_SESSION_PLATFORMcliHERMES_SESSION_USER_ID)source_labelr   r   mirroredzSend failed: )2r   r   r   r(   r-   r)   r   r   r   r   r   ri   tools.interruptr   r   r   r   rK   ra   r   	platformsr   r   r   gateway.platforms.baser   extract_mediafilter_media_delivery_paths_describe_media_for_mirrorr   r*   getenvr   rM   _HOME_CHANNEL_ENV_OVERRIDESupper_maybe_skip_cron_duplicate_sendrQ   r   r   r   r   _send_to_platformr   r   gateway.mirrorr   gateway.session_contextr   rG   )*r   r|   rH   r   rL   r   rM   r   is_explicitr   r   r   r   r   r   r   r   r   pconfigwx_token
wx_accountr   r   force_document_attachmentsr0   cleaned_messagemirror_textused_home_channelr   wx_homer   home_envduplicate_skipr   r   
dm_channelr   r   r   r   r   r   s*                                            @r7   r   r   c  s   XXh#Fhhy"%GWXXLLa E!HNN$**,M%(Z!^q!JGI*;M:*V'K +	F+M:FH(9-(R%Azz2:,eM? SP Q#   %& /-((I@$&@M* ""8,G'// H$!."5;;=H#$7<BBDJJ9( "&0$./@"$E$K$K$M(23H"(M(S(S(U "J}o  >X  #Y  Z  Z
=/  :T  U  V  V: "3g!=#6#D#DW#M K%AA+NK!'')T-G-TK&&x01ii 5r:@@BG6"HgMZllG $266-"5"5"7!8FH ::3M? C;;H/ J@@HzX   5]GYWNzz.)) G0B0B30G	J$ /#N7=='$JKJ$zz7.PQXPYY|,}"~ '7*#'9

 FD!9fjj>S'6NwiWXYF6N fd#

9(=+<C./H%P)*BBGO4$!!-'# *.F:& fd#6(926'?CF7Ozz&!!]  	::.zl% O: ;  	  Izz&#B1#!FGHHI ! @.}o>??@d  	J::w*CA3(GHII	JF    7zz&=!45667s   )?Q Q1 R( AS $A0T
 5S; 
9T
 'Q.-Q.1	R%: R R% R%(SS	S8S3-S83S8;	TT
 TT
 
	T> T93T>9T>r   c                    | dk(  rOt         j                  |      }|r#|j                  d      |j                  d      dfS ddlm}  ||      }|r|ddfS | dk(  r:t
        j                  |      }|r#|j                  d      |j                  d      dfS | d	k(  r:t        j                  |      }|r#|j                  d      |j                  d      dfS | d
k(  rnt        j                  |      }|r#|j                  d      |j                  d      dfS t        j                  |      }|r|j                  d      }|d   dv}|d|fS | dk(  r4|j                         }|j                  d      }|dkD  r|d| ||dz   d dfS | dk(  r+t        j                  |      }|r|j                  d      ddfS | dk(  r`t        j                  |      }|r|j                  d      ddfS |j                         j                         rd|j                          ddfS y| dk(  r|j                         }	|	r|	ddfS | dk(  r*t        j                  |      }|r|j                         ddfS | dk(  r(t        j                  |      r|j                         ddfS |j                         }
| dk(  r8|
j!                  d      r'|
t#        d      d j                         }|rd| ddfS y| t$        v r*t&        j                  |      }|r|j                         ddfS |j)                  d      j                         r|ddfS | dk(  r'|j!                  d      s|j!                  d      r|ddfS | dk(  r	d|v r|ddfS y)zFParse a tool target into chat_id/thread_id and whether it is explicit.telegramr'   r\   Tr   )parse_telegram_username_targetNfeishudiscordr   >   r   Wmatrixz:$r   yuanbaorO   NNFntfyr
   r	   r   -!@xmpp)_TELEGRAM_TOPIC_TARGET_RE	fullmatchr>   'plugins.platforms.telegram.telegram_idsr  _FEISHU_TARGET_RE_NUMERIC_TOPIC_RE_SLACK_THREAD_TARGET_RE_SLACK_TARGET_REr(   rfind_WEIXIN_TARGET_RE_YUANBAO_TARGET_REisdigit_EMAIL_TARGET_RE_WHATSAPP_JID_RErQ   r)   _PHONE_PLATFORMS_E164_TARGET_RElstrip)rL   r   matchr  usernamerM   r  trimmed	split_idxtopicstripped_targetgroup_ids               r7   r   r     s   
")33J?;;q>5;;q>477	
 2*=T4'' !++J7;;q>5;;q>477	!!++J7;;q>5;;q>477'11*=;;q>5;;q>477 **:6kk!nG "!*J6KD+-- ""$MM$'	q=:I&	A(@$FF !++J7;;q>4--	!",,Z8;;q>4--%%'J,,./0$<<   "$$$ **:6##%tT11
" %%j1##%tT11 &&(O _%?%?%I"3x=>288:H:&d22 (())*5 ##%tT11%%'4%% j&;&;C&@JDYDYZ]D^4%%3*#44%%r9   c                     | syt        |       dk(  r_| d   \  }}t        j                  j                  |      d   j	                         }|r	|t
        v ry|t        v ry|t        v ry|t        v ryyd	t        |        d
S )zJReturn a human-readable mirror summary when a message only contains media.r&   r'   r   z[Sent voice message]z[Sent image attachment]z[Sent video attachment]z[Sent audio attachment]z[Sent document attachment]z[Sent z media attachments])	r)   r*   r+   r,   r-   _VOICE_EXTS_IMAGE_EXTS_VIDEO_EXTS_AUDIO_EXTS)r0   r4   r5   r6   s       r7   r   r   l  s    
;1*1~
Hggz*1-335{*)+,+,+,+C$%%899r9   c                      ddl m}   | dd      j                         j                         } | dd      j                         }|r|sy | dd      j                         xs d}|||dS )	zMReturn the cron scheduler's auto-delivery target for the current run, if any.r   r   !HERMES_CRON_AUTO_DELIVER_PLATFORMr&    HERMES_CRON_AUTO_DELIVER_CHAT_IDN"HERMES_CRON_AUTO_DELIVER_THREAD_ID)r   rM   r   )r  r   r(   r-   )r   r   rM   r   s       r7   _get_cron_auto_delivery_targetr<    sq    7BBGMMOUUWH@"EKKMG7 DbIOOQYUYI r9   r   c                     t               }|sy|d   | k(  xr0 t        |d         t        |      k(  xr |j                  d      |k(  }|sy|  d| }||d| z  }ddd|d| d	d
S )zRSkip redundant cron send_message calls when the scheduler will auto-deliver there.Nr   rM   r   r   T#cron_auto_delivery_duplicate_targetzSkipped send_message to z. This cron job will already auto-deliver its final response to that same target. Put the intended user-facing content in your final response instead, or use a different target if you want an additional message.)r   skippedreasonr|   r   )r<  rP   r   )rL   rM   r   auto_targetsame_targettarget_labels         r7   r   r     s    02K 	J=0 	6I&'3w<7	6OOK(I5 
 #_AgY/L!I;' 7&|n 5h h
 
r9   r   c                  K   t        | d      r| j                  n
t        |       }d}	 ddlm}	  |	       }|	 |j                  j                  |       }
|
b	 i }|r||d<   |dk(  r|r||d<   |sd}|
j                  |||       d{   }|j                  rd|j                  dS d	d|j                   iS d}	 ddlm} |j                  |      }|}|j                   q	 |j!                  ||||||       d{   }t'        |t(              r$|j                  d      s|j                  d	      r|S d	d| dt+        |      j,                   iS d	d| diS # t
        $ r d}Y 1w xY w# t
        $ r d}
Y %w xY w7 # t        j                  $ r  t
        $ r}d	d
| icY d}~S d}~ww xY w# t
        $ r d}Y w xY w7 # t        j                  $ r  t
        $ r)}t"        j%                  d|d       d	d| icY d}~S d}~ww xY ww)a  Send a message via a live gateway adapter, with a standalone fallback
    for out-of-process callers (e.g. cron running separately from the gateway).

    Order of attempts:
      1. Live in-process adapter via ``_gateway_runner_ref()`` (the path that
         existed before this change).
      2. The plugin's ``standalone_sender_fn`` registered on its
         ``PlatformEntry`` (used when the gateway is not in this process, so
         the runner weakref is ``None``).
      3. A descriptive error explaining both options.
    valueNr   r   r   r  publish_topic)rM   contentmetadatarJ   zPlugin platform send failed: T)r   r~   zAdapter send failed: platform_registryr   z$Plugin standalone send for %s raisedexc_infozPlugin standalone send failed: r   zPlugin standalone send for 'zR' returned an invalid result: expected a dict with 'success' or 'error' keys, got zNo live adapter for platform 'z'. Is the gateway running with this platform connected? For out-of-process delivery (e.g. cron in a separate process), the platform plugin must register a standalone_sender_fn on its PlatformEntry.)hasattrrE  rP   r   r   ri   r   r   rt   rl   CancelledErrorr   r~   rJ   gateway.platform_registryrJ  standalone_sender_fnrj   debugr   r   rx   __name__)r   r  rM   chunkr   r0   r   rL   r   r   r   rH  r   r   entryrJ  s                   r7   _send_via_adapterrU    s    * '.h&@HNNc(mMF3$& 	oo))(3G F,5H[) F*w07H_-#H&||GUU]|^^
 ~~#'v7H7HII4V\\NCDDE?!%%m4 U77C	D 55#'- 6  F fd#I)>&**WBUM.}o >!&\2235
 	
 	,]O <D E w    	G	 _))  F#@!DEEF  
 %% 	 	DLL?Y]L^>qcBCC	Ds   &H
E H
E+ H
0E? E=E? .H
;F/ H
!G ;G <G  AH
E($H
'E((H
+E:6H
9E::H
=E? ?F,F'!F,"H
'F,,H
/F=:H
<F==H
 G HH<H=H
HH
c           
        K   ddl m} |xs g }| |j                  k(  rt        ||||       d{   S ddlm}m}	 	 ddlm}
 d}|xs g }|j                  |r
j                  nd	i}| |vrB	 dd
lm} |j                  | j                        }|r|j                   dkD  r|j                   || <   |j                  |       }|r(| |j                  k(  r|	nd}|j%                  |||      }n|g}| |j                  k(  rWt'        t)        |di       xr |j*                  j                  d            }t-        |j.                  ||||||       d{   S | |j0                  k(  rdd
lm} |j                  d      }||j2                  ddiS t5        |||xs t6              \  }}|D|j3                  ||d|||       d{   }t9        |t:              r|j                  d      r|S |S d}t=        |      D ]_  \  }}|t?        |      dz
  k(  }|j3                  |||||r|ng        d{   }t9        |t:              r|j                  d      r|c S |}a |S | |j@                  k(  rkd}t=        |      D ]Y  \  }}|t?        |      dz
  k(  }tC        ||||r|ng |       d{   }t9        |t:              r|j                  d      r|c S |}[ |S | |jD                  k(  rv|rtd}t=        |      D ]b  \  }}|t?        |      dz
  k(  }tG        |j*                  |||r|ng        d{   }t9        |t:              r|j                  d      r|c S |}d |S | |jH                  k(  rk|rid}t=        |      D ]W  \  }}|t?        |      dz
  k(  }tK        |||r|nd       d{   }t9        |t:              r|j                  d      r|c S |}Y |S | |jL                  k(  r|rdd
lm} ddl'm(}  |        |j                  d      }||j2                  ddiS d}t=        |      D ]_  \  }}|t?        |      dz
  k(  }|j3                  ||||r|nd|       d{   }t9        |t:              r|j                  d      r|c S |}a |S | |jR                  k(  r|rdd
lm} ddl'm(}  |        |j                  d      }||j2                  ddiS t5        |||xs t6              \  } }d}| E|j3                  ||d||||        d{   }t9        |t:              r|j                  d      r|S |S t=        |      D ]`  \  }}|t?        |      dz
  k(  }|j3                  ||||r|nd||       d{   }t9        |t:              r|j                  d      r|c S |}b |S |r"|jU                         sdd| j                   d iS d}!|rd!| j                   d"}!d}|D ]2  }| |jV                  k(  rJdd
lm} |j                  d#      }"|"|"j2                  dd$i}n|"j3                  ||||%       d{   }n| |jR                  k(  rtY        d||||       d{   }n| |jD                  k(  r!tG        |j*                  ||       d{   }nW| |jZ                  k(  rtY        d&||||       d{   }n/| |j\                  k(  rtY        d'||||       d{   }n| |j^                  k(  rtY        d(||||       d{   }n| |jL                  k(  rtY        d||||       d{   }n| |j`                  k(  rtY        d)||||       d{   }n| |jb                  k(  r te        |j*                  ||       d{   }nc| |jf                  k(  rti        |||       d{   }n>| |jH                  k(  rtK        ||       d{   }ntk        | ||||||*       d{   }t9        |t:              r|j                  d      r|c S |}5 |!rRt9        |t:              rB|j                  d+      r1tm        |j                  d,g             }#|#jo                  |!       |#|d,<   |S 7 ># t        $ r d}Y 9w xY w# t"        $ r Y w xY w7 F7 7 o7 7 v7 7 J7 7 57 v7 Q7 $7 7 7 7 7 m7 A7 7 7 w)-a  Route a message to the appropriate platform sender.

    Long messages are automatically chunked to fit within platform limits
    using the same smart-splitting algorithm as the gateway adapters
    (preserves code-block boundaries, adds part indicators).
    r   r   r0   Nr   	utf16_lenTelegramAdapterTFr$   rI  len_fnr   disable_link_previews)r0   r   r_  r   r  rJ   z=Discord plugin not registered or missing standalone_sender_fnr1   r&   )r   r0   captionr'   )r   r0   )r0   r   discover_pluginsr  z<Feishu plugin not registered or missing standalone_sender_fnr	   z>WhatsApp plugin not registered or missing standalone_sender_fn)r0   r   r   ra  )r0   r   r   zsend_message MEDIA delivery is currently only supported for telegram, discord, matrix, weixin, signal, yuanbao, feishu and whatsapp; target z had only media attachmentsz#MEDIA attachments were omitted for z; native send_message media delivery is currently only supported for telegram, discord, matrix, weixin, signal, yuanbao, feishu and whatsappr   z;Slack plugin not registered or missing standalone_sender_fnr   r
   r   dingtalkwecomr   r   warnings)8r   r   WEIXIN_send_weixinr   r   rZ  "plugins.platforms.telegram.adapterr\  ImportErrorTELEGRAMMAX_MESSAGE_LENGTHrO  rJ  r   rE  max_message_lengthri   truncate_messager   r]   r   _send_telegramr   DISCORDrP  r8   _DEFAULT_CAPTION_LIMITr   r   	enumerater)   MATRIX_send_matrix_via_adapterSIGNAL_send_signalYUANBAO_send_yuanbaoFEISHUhermes_cli.pluginsrc  WHATSAPPr(   SLACK_registry_standalone_sendEMAILSMSDINGTALKWECOMBLUEBUBBLES_send_bluebubblesQQBOT_send_qqbotrU  ru   append)$r   r  rM   rH   r   r0   r   r   r   rZ  r\  _telegram_available_MAX_LENGTHSrJ  rT  max_len_len_fnchunksr_  _dc_captionr   r   last_resultirS  is_last
_pr_feishu
_dp_feishu_feishu_entry_pr_wa_dp_wa	_wa_entry_wa_captionrk   _slack_entryrg  s$                                       r7   r   r     s	     (#K 8??"!'7GUUUE$F" #K 	AT?==Z^L
 |#	C%))(..9E11A5).)A)AX& x(G'8+<+<<)$$55gww5W 8$$$ $WWgr%B%qw}}GXGXYpGq r#MM#"7)
 
 	
" 8###?!%%i0=E66>\]]
 .[$>(>
Q " 55#'# 6  F &$'FJJw,?M!&) 	!HAuCK!O+G 55#+2K 6  F &$'FJJw,? K	!  8??"!&) 	!HAuCK!O+G3+2K# F &$'FJJw,? K	!  8??"{!&) 
	!HAuCK!O+G'+2K	 F &$'FJJw,? K
	!  8###!&) 		!HAuCK!O+G(+2K F
 &$'FJJw,? K		!  8??"{ME"x0 M$F$F$N[\\!&) 	!HAuCK!O+G(==+2K# >  F &$'FJJw,? K	!  8$$$IAJJz*		 > > F]^^
 .[$>(>
Q " %99'#-# :  F &$'FJJw,?M!&) 	!HAuCK!O+G$99+2K#- :  F &$'FJJw,? K	!  7==?"..))DF
 	
 G1(..1A BY Y 	
 K 0x~~% D,009L#|'H'H'P!#`a+@@Wey  A    ***4Z'SXZcddF('wFFF'4WgwPUW`aaF%4UGWeU^__F***4Z'SXZcddF(4XwQVXabbF'4WgwPUW`aaF---,W]]GUKKF'&w??F)))(%88F -#'- F fd#

7(;Ma0d :k40[__Y5O
B78 "*Jk	 V  $#$6  		*
>*" ,FN eFa_dbaK?8s  .ca.ca1 &c-Ab .BcbA1c?b A*c*b+A9c$b%Bc)b*A9c#b"$B7cb%B0cb(A)c6b+7Cc8b.9'c b1!/cb4'c8b79'c b:!'cb=	&c/c 0&cc.cc$c*c	+#ccc)c*Bc1b <c?b  c	bcbccccc"c%c(c+c.c1c4c7c:c=c ccc	cccrJ   c                 8    dt        |       j                         v S )zCheck if a Telegram error is a thread-not-found failure.

    Matches the gateway adapter's ``_is_thread_not_found_error`` for
    the standalone ``_send_telegram`` path (issue #27012).
    zthread not found)rP   r-   )rJ   s    r7   _is_telegram_thread_not_foundr  T  s     U!1!1!333r9   c           	      h  K   	 ddl m} ddlm} t	        t        j                  d|            }	|	r|}
|j                  }n5	 ddlm	} |j                  |      }|j                  |      }
|j                  }	 ddlm}  |ddg	      }|r7	 ddlm} t$        j'                  d|        ||  ||       ||            }n	 ||       }ddlm}  ||      }|xs g }i }|(	 ddlm	} |j/                  t1        |            }|||d<   t5        |      }|rd|d<   d
}g }d
}ddlm} t9        ||t:              \  }}| ||
      t:        k  r|
}d}
|
j=                         r<ddlm}m} |jA                  |
d|      } | D ]  }!	 tC        |f||!|d| d
{   } |D ]  \  }%}&tP        jR                  jU                  |%      sKd#|% }'t$        j)                  |'       |jW                  |'       ||	 tC        |f|||d| d
{   }d
}qtP        jR                  jY                  |%      d%   jK                         })	 t[        |%d&      5 }*t5        |      }+||)t\        v r|&s
||+d'<   ||+d(<   |)t\        v r|&s|)t^        v r,	 dd)lm0}, tc        jd                  |,|%       d
{   }-|-|-|+d*<   	 |)tf        v r |s |jh                  d<||*d+|+ d
{   }n|)tj        v r |jl                  d<||*d,|+ d
{   }nk|)t\        v r |&r |jn                  d<||*d-|+ d
{   }nC|)t^        v r |jp                  d<||*d.|+ d
{   }n |jr                  d<||*d/|+ d
{   }d
d
d
        |d4}0|r|0|d5S d6|0iS dd7|t1        |jx                        d8}1|r||1d9<   |1S # t        $ r |}
Y Iw xY w# t        $ r d
}Y =w xY w# t        $ r*}t$        j)                  d|        ||       }Y d
}~-d
}~ww xY w# t        $ r t1        |      dk(  rd
n
t3        |      }Y #w xY w7 # t        $ r)}"tE        |"      rb|jG                  d      Qt$        j)                  d|jG                  d             |jI                  dd
       tC        |f||!|d| d
{  7  }ndt1        |"      jK                         v s6dt1        |"      jK                         v sd t1        |"      jK                         v r_t$        j)                  d!|tM        |"             |	s!	 dd"lm'}#  |#|!      }$n# t        $ r |!}$Y nw xY w|!}$tC        |f||$d
d| d
{  7  }n Y d
}"~"d
}"~"ww xY w7 Z# t        $ r*}(t$        j)                  d$tM        |(             Y d
}(~(d
}(~(ww xY w7 # t        $ r Y w xY w7 7 7 7 s7 X# t        $ r[}.tE        |.      r|+jG                  d      r t$        j)                  d0|+d          |*ju                  d       |+jI                  dd
       |)tf        v r"|s  |jh                  d<||*d+|+ d
{  7  }n|)tj        v r  |jl                  d<||*d,|+ d
{  7  }n|)t\        v r"|&r  |jn                  d<||*d-|+ d
{  7  }nz|)t^        v r  |jp                  d<||*d.|+ d
{  7  }nR |jr                  d<||*d/|+ d
{  7  }n2|+jG                  d(      rdt1        |.      jK                         v sd't1        |.      jK                         v rt$        j)                  d1tM        |.             |*ju                  d       |+jI                  d(d
       |	s6|+jG                  d'      r%	 dd"lm'}#  |#|+d'         |+d'<   n# t        $ r Y nw xY w|)tf        v r!|s |jh                  d<||*d+|+ d
{  7  }nG|)tj        v r |jl                  d<||*d,|+ d
{  7  }n  |jr                  d<||*d/|+ d
{  7  }n Y d
}.~.d
}.~.ww xY w# 1 sw Y   xY w# t        $ rB}/tM        d2|% d3|/       }'t$        jw                  |'       |jW                  |'       Y d
}/~/d
}/~/ww xY w# tz        $ r d6d:icY S t        $ r}/t}        d;|/       cY d
}/~/S d
}/~/ww xY ww)=uD  Send via Telegram Bot API (one-shot, no polling needed).

    Applies markdown→MarkdownV2 formatting (same as the gateway adapter)
    so that bold, links, and headers render correctly.  If the message
    already contains HTML tags, it is sent with ``parse_mode='HTML'``
    instead, bypassing MarkdownV2 conversion.
    r   )Bot)	ParseModez<[a-zA-Z/][^>]*>r[  )resolve_proxy_urlTELEGRAM_PROXYzapi.telegram.org)target_hostsN)HTTPXRequestz>send_message: standalone Telegram send routed through proxy %s)proxy)r   requestget_updates_requestzUsend_message: failed to attach Telegram proxy (%s), falling back to direct connection)r   )normalize_telegram_chat_id1message_thread_idTdisable_web_page_preview)rZ  r`  r&   rY  r$   r]  )rM   r/   
parse_modezIThread %s not found in _send_telegram, retrying without message_thread_idparsemarkdownhtmlzFParse mode %s failed in _send_telegram, falling back to plain text: %s)_strip_mdv2z Media file not found, skipping: z;Telegram caption-fallback send failed for missing media: %sr'   rbra  r  )_probe_voice_duration_secondsduration)rM   photo)rM   video)rM   voice)rM   audio)rM   documentzFThread %s not found for media send, retrying without message_thread_idz7Caption parse failed for media send, retrying plain: %szFailed to send media : ANo deliverable text or media remained after processing MEDIA tags)rJ   rg  rJ   r  r   r   rM   r~   rg  zGpython-telegram-bot not installed. Run: pip install python-telegram-botzTelegram send failed: rf   )?r  r  telegram.constantsr  r   researchHTMLrj  r\  __new__format_messageri   MARKDOWN_V2r   r  telegram.requestr  rj   infork   r  r  _message_thread_id_for_sendrP   intr   rZ  r8   _TELEGRAM_CAPTION_LIMITr(   r   ro  rq   r  r   popr-   rG   r  r*   r+   existsr  r,   openr4  _TELEGRAM_SEND_AUDIO_EXTSr  rl   	to_threadr5  
send_photor6  
send_video
send_voice
send_audiosend_documentseekrJ   r~   rk  rK   )2r   rM   rH   r0   r   r_  r   r  r  	_has_html	formattedsend_parse_moder\  _adapterr  	_tg_proxyr  rn   
_proxy_errr  int_chat_idthread_kwargseffective_thread_idtext_kwargslast_msgrg  _tg_caption
_utf16_len_capr   r   rZ  text_chunksrS  md_errorr  plainr4   r5   rk   _cap_errr6   fmedia_kwargsr  r  	media_errr   rJ   r   s2                                                     r7   rp  rp  ]  s0
    u4 0 #6@A	I'nnO$N*22?C$33G<	 (33O	@)*:J\I]^I 
'9\^gh(y9(49(E E"C	
 19!'R 
N&5&Q&Q	N'# #.5H12 =) 6:K23 B&[2I
a 
9 59P P#KI?? N->>4	 ? K % *)%F& +%#2& 7B&  H*X %0 A	) J77>>*-<ZLIw'( *x/?
)J*)4;'6*:E* $ '+ ''"":.q1779Cj)*d+ e"q#'#6L
 #.{8Jx2=Y/5D\2{*xCC\<\!h-4->->?\^h-i'iH'3;CZ 8T"+-n-;S^^ .(31.@L. (H !K/-;S^^ .(31.@L. (H !K/H-;S^^ .(31.@L. (H !$==-;S^^ .(31.@L. (H .?S->-> .(3a.CO. (HGe"1A	)F WE!&H==U## "h112	
 !)F:A	  $#	$  	I	  'v  yC  D&'8    	Nc1Ds9~ $b 
 ! # 5X>;??SfCgCsg'OO,?@ $(;TB)J*$/e'6* ;F* $ $
 !CM$7$7$99Z3x=K^K^K`=`djnqrzn{  oB  oB  oD  eDd+0:
  ). Z(3E(:#, .(-. %*E)J*$/e'+* 0;* $ $ G#^$
 % Y0: * (j  ) ! !((((( % ?"8CHXHXYlHm #NN h ,-@ A
 FF1I(,,-@$G"k1.1? 2",7q2"DP2" ," ," "%!31? 2",7q2"DP2" ," ," "%!31? 2",7q2"DP2" ," ," "%(A!A1? 2",7q2"DP2" ," ," 2C1B1B 2",7!2"GS2" ," ," *--l;#s9~';';'==(C	N,@,@,BB
 #NN Y 4Y ? FF1I(,,\4@#,1A1A)1L!)$^>I,W`Ja>bL$;'0 !)$(!)"k1.1? 2",7q2"DP2" ," ," "%!31? 2",7q2"DP2" ," ," 2C1B1B 2",7!2"GS2" ," ," "?"Me" e"L  ).1FzlRTUVTW/XYW%(()(  dbcc 4.qc2334s  b2<b  (N$ )b  6N6 b  
5O ?"b  " O> B b  P,P)P,Ab  5U%U"	U%1b  `24`%V!V"V."V=V.%V=6V17'V=V4%V=V7V="V:#V='`2/b  <b2=b   b2"b  #b2$N3/b  2N33b  6Ob  Ob  	O;O60b  6O;;b  >$P&"b  %P&&b  )P,,
U6A&URA9UT&%U&T41U3T44UUUb  Ub  "U%%	V.Vb  Vb  V	V+'`%*V++`%.V=1V=4V=7V=:V==
`"A<`Y
'`+Y.
,)`Z
'`=[ 
>`[ 
B$`^`	^$	!`#^$	$%`	_

&`0_3
1``
``%`""`%%`/	*`22	a=;7a82b  8a==b   b/b2b/b*$b/%b2*b//b2c                    K   ddl m} ddlm}  |        |j	                  |       }||j
                  d|  diS |j                  ||||       d{   S 7 w)ak  Dispatch a one-shot send through a migrated platform plugin's
    standalone_sender_fn (registry hook).  Used for platforms whose adapter
    moved out of gateway/platforms/ into plugins/platforms/<name>/ (#41112):
    the legacy inline ``_send_<platform>`` helper now lives in the plugin as
    ``_standalone_send`` and is reached via the platform registry.
    r   rI  rb  NrJ   z6 plugin not registered or missing standalone_sender_fnrd  )rO  rJ  r{  rc  r   rP  )rL   r  rM   rH   r   rJ  rc  rT  s           r7   r~  r~    sh      <3!!-0E}22:M?*`abb++GWgQZ+[[[[s   AAAAc                 4	  %&'()K   	 ddl (ddlm}m}m}m}m}m}	m	%m
}
 ddlm} 	 | j                  dd      j                  d	      '| j                  d
d      &&sddiS |xs g }g }|D ]L  \  }}t        j                   j#                  |      r|j%                  |       7t&        j)                  d|       N |r*t+        dt-        |      |      D cg c]
  }||||z     }}ng g} ||      \  })%&'()fd}dt.        ddf&'(fd} |
       }t&        j1                  d|j3                         t-        |      t-        |             g }t5        |      D ]  \  }}t-        |      }|dkD  rB|j7                  |      }||k\  r, |d ||       d|dz    d	t-        |       d       d{    |dk(  r|nd}t+        d|dz         D ];  }	 |j9                  |       d{    t;        j<                         } |||       d{   }t;        j<                         |z
  }d|vr|j?                  ||       d{     |d   }  |	|       s$tA        d|dz    d	t-        |       d|        c c S  ||       }!|jC                  |!|       ||k\  rD|j%                  |dz          t&        jE                  d|dz   t-        |      ||!r|!ddnd        t&        j)                  d|dz   t-        |      |||!r|!ddnd       >  g }#t-        |      t-        |      k  r|#j%                  d        |r7|#j%                  d!t-        |       d"d#jI                  d$ |D               d%       |r/t-        |      t-        |      k(  rtA        d&t-        |       d'      S d(d)tK        d)      d*}$|#r|#|$d+<   |$S # t        $ r ddicY S w xY wc c}w 7 $7 7 7 # tF        $ r}"||k\  rI|j%                  |dz          t&        jE                  d|dz   t-        |      |t/        |"             Y d}"~" t&        j)                  d|dz   t-        |      ||t/        |"             Y d}"~"d}"~"ww xY w# tF        $ r}"tA        d,|"       cY d}"~"S d}"~"ww xY ww)-u  Send via signal-cli JSON-RPC API.

    Supports both text-only and text-with-attachments (images/audio/documents).
    Multi-attachment sends are chunked into batches of
    SIGNAL_MAX_ATTACHMENTS_PER_MSG and metered by the process-wide
    SignalAttachmentScheduler — same bucket the gateway adapter uses, so
    sends from this tool and inbound-driven replies share rate-limit state.
    r   NrJ   zhttpx not installed)$SIGNAL_BATCH_PACING_NOTICE_THRESHOLDSIGNAL_MAX_ATTACHMENTS_PER_MSGSIGNAL_RATE_LIMIT_MAX_ATTEMPTS_extract_retry_after_seconds_format_wait_is_signal_rate_limit_error_signal_send_timeoutget_scheduler)markdown_to_signalhttp_urlzhttp://127.0.0.1:8080/accountr&   zSignal account not configuredz)Signal media file not found, skipping: %sc                 (  K   |d}|rrt              dk(  r	d   |d<   n|d<   	j                  d      r		dd  |d<   n	g|d	<   | r| |d
<   dd|dt        t        j                         dz         d} | rt        |       nd      }j	                  |      4 d {   }|j                  
 d|       d {   }|j                          |j                         cd d d       d {    S 7 S7 77 	# 1 d {  7  sw Y   y xY ww)Nr  rH   r'   r   	textStyle
textStylesrO      groupId	recipientattachments2.0rt   send_  jsonrpcmethodparamsr   r   /api/v1/rpcr   )r)   rQ   r  timeAsyncClientr   raise_for_statusr   )batch_attachmentsbatch_messager  payloadrW   clientr   r  r  rM   r  httpxtext_styless          r7   _postz_send_signal.<locals>._post  s.    !(]CF{#q(*5a.F;'+6F<(!!(+$+ABKy!'.i{# (9}% !  c$))+"4567	G +EV3/@+A\]^G(((9 # #V#[[H:[)A[PP%%'yy{# # #P# # # #sZ   B D#C7$D'C=C9#C=%D1C;2D9C=;D=DDDDr/   r:   c                   K   | d}j                  d      r	dd |d<   ng|d<   	 j                  d      4 d{   }|j                   d	d
d|dt        t	        j                         dz         d       d{    ddd      d{    y7 Y7 7 	# 1 d{  7  sw Y   yxY w# t
        $ r }t        j                  d|       Y d}~yd}~ww xY ww)z9Best-effort one-shot RPC for a user-facing pacing notice.r  rO   r  Nr  r  g      >@r   r  r  rt   notice_r  r  r  z Signal: inline notice failed: %s)rQ   r  r   r  r  ri   rj   rk   )r/   notice_params_client_er  rM   r  r  s       r7   _send_inline_noticez)_send_signal.<locals>._send_inline_notice  s     (/DAM!!(++212;i(.5Yk*G ,,T,: 	 	g!,,#*K0',&,&3$+C		d0B,C+D"E	 '   	 	 		 	 	 	  GA2FFGs   &C!B5  BB5 A B BB 	B5 BB5 C!B5 B B5  B2&B)'B2.B5 1C!2B5 5	C>CC!CC!zIsend_message Signal: scheduler state=%s, %d attachment(s) in %d batch(es)u!   (More images coming — pausing ~z for Signal rate limit, batch r'   z.)zSignal RPC error on batch r  z`Signal: rate-limit retries exhausted on batch %d/%d (%d attachments lost, server retry_after=%s)z.0fsunknownziSignal: rate-limited on batch %d/%d (attempt %d/%d, server retry_after=%s); scheduler will pace the retryz7Signal: send error on batch %d/%d after %d attempts: %szFSignal: transient error on batch %d/%d (attempt %d/%d): %s; will retryz1Some media files were skipped (not found on disk)zSignal rate-limited z batch(es) (#z, #c              3   2   K   | ]  }t        |        y wN)rP   ).0bs     r7   	<genexpr>z_send_signal.<locals>.<genexpr>a  s     ?1A?s   r   zSignal: every batch (z*) hit rate limit; no attachments deliveredTr   )r   r   rM   rg  zSignal send failed: )&r  rk  #gateway.platforms.signal_rate_limitr  r  r  r  r  r  r  r  gateway.platforms.signal_formatr  r   rstripr*   r+   r  r  rj   rk   rg   r)   rP   r  staters  estimate_waitacquirer  	monotonicreport_rpc_durationrK   feedbackrJ   ri   joinrR   )*r   rM   rH   r0   r  r  r  r  r  r  r  r  valid_mediaattachment_pathsr4   	_is_voicer  att_batches
plain_textr  r  	schedulerfailed_batchesidx	att_batchn	estimatedr  rT   _rpc_t0r   _rpc_durationerrserver_retry_afterr   rg  r   r  r  r  r  r  s*    `                                   @@@@@r7   rw  rw    s    0	 	 	 C`299Z)@AHHM))Ir*<==!'R%0 	X!J	ww~~j) ''
3JJW		X  q#&6"79WX !1'E#EFK 
 $K"4W"=
K	# 	#4	GC 	GD 	G 	G* "O	WOOs#34c+6F	
 %''4 :	NCIA1u%33A6	 DD-;L<S;T U88;ay#kBRASSUW  
 +.(JM $BQ$FG .-#++A..."nn.G!&y-!@@D$(NN$4w$>Md*';;M1MMMw-C6s;%(B37)1SQ\M]L^^`ad`e&fgg)Ec)J&&&'91="@@&--cAg6K!GS%5q<N1#6a8T]	 NN8 a[!1!?8J-c2!4PY7.:	x  3{#33OOOPOO&s>':&; <ZZ???@C
 c.1S5EE'K(8'9 :+ , 
 "xDTU]_fDgh!)F:[  0.//0BF /@ N8 ! "@@&--cAg6U!GS%5wA NN`a[!17<Z\_`a\b @  2,QC0112s'  RN< R8Q4 !R"A3Q4 O$CQ4 0O1 Q4 O &O'$O O4O  OO Q4 ,O 3Q4 7R8A O Q4 .O 	BQ4 "R#Q4 ;R<O	RORQ4 O O O  	Q1)AQ,0Q4 7/Q,&Q4 ,Q11Q4 4	R=R
RRRRc                   K   |xs g }|rd|ind}d}d}	 ddl m}  |       }|,	 ddlm}	 |j
                  j                  |	j                        }|t        |||||       d{   S 	 dd	lm}
  |
|       }	 |j                          d{   }|s$t        d      	 |j!                          d{    S t        |||||       d{   	 |j!                          d{    S # t        $ r d}Y w xY w# t        $ r t        j                  dd       d}Y w xY w7 # t        $ r d
dicY S w xY w7 7 # t        $ r Y S w xY w7 ~7 h# t        $ r Y S w xY w# t        $ rA}t        d|       cY d}~	 |j!                          d{  7   S # t        $ r Y S w xY wd}~ww xY w# 	 |j!                          d{  7   w # t        $ r Y w w xY wxY ww)u  Send via the Matrix adapter so native Matrix media uploads are preserved.

    When a live gateway adapter is available (i.e. the tool runs inside a
    running gateway), the persistent connection is reused — one olm/megolm
    session for all sends.  This avoids per-message E2EE re-init storms
    that exhaust recipient OTKs and silently drop messages (issue #46310).

    Falls back to an ephemeral connect/disconnect cycle only when no gateway
    is running (standalone cron, ``hermes send`` CLI).
    r   Nr   r   rW  zxMatrix: live gateway adapter lookup failed; falling back to an ephemeral connect (may re-init E2EE per send, see #46310)TrK  )MatrixAdapterrJ   zIMatrix dependencies not installed. Run: pip install 'mautrix[encryption]'zMatrix connect failedMatrix send failed: )r   r   ri   r   r   r   r   rt  rj   rk   _matrix_send_core plugins.platforms.matrix.adapterr5  rk  connectrK   
disconnect)r  rM   rH   r0   r   rH  live_adapterr   r   r   r5  r   	connectedr   s                 r7   ru  ru  |  s"     #K+4Y'$H LF3$& 		 /!??..x?L 
 ''7K
 
 	

fB G$G!//++	12	$$&&& 'Wg{H
 
	$$&&&S    	 NNL  
  L	 
  fdeef
 , ' 		
 ' 		  2,QC011	$$&&& 		2	$$&&& 		s  GC( G+C9 G%D!&G+D# 1G:E D6E D:2D83D:7G8E 
E	E E"E#E'G(C63G5C66G9"DGDG#D30G2D33G6E 8D::	EGEG	E E	EGEG	F&%F!2F&3F) 8FFFG	FGFG!F&&F) )G+G>G?GG	GGGGGc                 |  K   d}|j                         r@| j                  |||       d{   }|j                  st        d|j                         S |D ]=  \  }}t
        j                  j                  |      st        d|       c S t
        j                  j                  |      d   j                         }|t        v r| j                  |||       d{   }n|t        v r| j                  |||       d{   }nh|t        v r|r| j                  |||       d{   }nA|t         v r| j                  |||       d{   }n| j#                  |||       d{   }|j                  r&t        d|j                         c S  |ddiS d	d
||j$                  dS 7 7 7 7 7 j7 Ow)z=Core send logic shared by live and ephemeral Matrix adapters.N)rH  r6  zMedia file not found: r'   zMatrix media send failed: rJ   r  Tr  r  )r(   rt   r   rK   rJ   r*   r+   r  r,   r-   r5  send_image_filer6  r  r4  r  r7  r  r~   )	r   rM   rH   r0   rH  r  r4   r5   r6   s	            r7   r7  r7    s    K}}#LL'HLMM""01B1B0CDEE + L
Hww~~j)2:,?@@ggz*1-335+ ' 7 7V^ 7 __KK ' 2 27JQY 2 ZZKKH ' 2 27JQY 2 ZZKK ' 2 27JQY 2 ZZK ' 5 5gzT\ 5 ]]K""6{7H7H6IJKK#L& \]] !,,	 5 N `ZZZ]sp   *F<F/B/F<F2$F<F4&F<(F6)$F<F8F<*F:+F<=3F<2F<4F<6F<8F<:F<c                    K   	 ddl m}m}  |       sddiS 	 	  || j                  | j
                  |||       d{   S # t        $ r ddicY S w xY w7 # t        $ r}t        d|       cY d}~S d}~ww xY ww)	z6Send via Weixin iLink using the native adapter helper.r   )check_weixin_requirementssend_weixin_directrJ   z9Weixin requirements not met. Need aiohttp + cryptography.zWeixin adapter not available.)r   r   rM   rH   r0   NzWeixin send failed: )gateway.platforms.weixinr@  rA  rk  r   r   ri   rK   )r  rM   rH   r0   r@  rA  r   s          r7   ri  ri    s     :Z(*XYY +
	2'----#
 
 	
	  :899:
  2,QC0112sf   A;A A;$A AA A;AA;AA;A 	A8 A3-A8.A;3A88A;c                 h  K   	 ddl m}m}  |       sddiS 	 	 ddlm}  ||       } ||      }|j                          d{   }|st        d	      S 	 |j                  ||       d{   }	|	j                  s0t        d
|	j                         |j                          d{    S dd||	j                  d|j                          d{    S # t        $ r ddicY S w xY w7 7 ~7 E7 # |j                          d{  7   w xY w# t        $ r}
t        d
|
       cY d}
~
S d}
~
ww xY ww)zBSend via BlueBubbles iMessage server using the adapter's REST API.r   )BlueBubblesAdaptercheck_bluebubbles_requirementsrJ   z8BlueBubbles requirements not met (need aiohttp + httpx).z"BlueBubbles adapter not available.r   )r   Nz(BlueBubbles: failed to connect to serverzBlueBubbles send failed: Tbluebubblesr  )gateway.platforms.bluebubblesrD  rE  rk  r   r   r9  rK   rt   r   rJ   r:  r~   ri   )r   rM   rH   rD  rE  r   r  r   r<  r   r   s              r7   r  r  
  s3    ?d-/WXX 0
71 u-$W-!//++	DEE	'"<<99F>> 9&,,HI $$&&&  $7bhbsbst$$&&&!  ?=>>? , :
 '&'$$&&& 71!5667s   D2C D2*D C(D D2C0 +C*,'C0 D &C,'D +D2,C0 <D C.D D2C%"D2$C%%D2(D *C0 ,D .D 0DDDD 	D/D*$D/%D2*D//D2c                      t         j                  j                  d      ryddlm}   | dd      }|r|dk7  ry	 ddlm}  |       S # t        $ r Y y	w xY w)
u  Gate send_message on gateway running (always available on messaging platforms).

    Also passes for kanban workers — the dispatcher sets ``HERMES_KANBAN_TASK``
    on every spawned worker, but those workers run with the assignee profile's
    ``HERMES_HOME`` which has no ``gateway.pid``, so the gateway-running check
    would fail even though the parent gateway is alive. Honoring the env var
    lets workers call ``send_message`` to deliver rich content directly to the
    originating chat (paired with ``kanban_complete`` for the short notifier
    summary), which is the canonical pattern for any worker that needs to
    reply with more than the ~200-char first-line truncation the kanban
    notifier applies.
    HERMES_KANBAN_TASKTr   r   r   r&   local)is_gateway_runningF)r*   environr   r  r   gateway.statusrK  ri   )r   r   rK  s      r7   _check_send_messagerN  *  sX     
zz~~*+78"=HH'5!## s   A 	AAc           	        K   	 ddl }| j                  xs i }|j	                  d      xs t        j                  dd      }| j                  xs) |j	                  d      xs t        j                  dd      }|r|st        d	      S 	 |j                  d
      4 d{   }|j                  dt        |      t        |      d       d{   }|j                  dk7  r)t        d|j                         cddd      d{    S |j                         }	|	j	                  d      }
|
st        d      cddd      d{    S d|
 dd}|dd dd}d| d}|j                  |||       d{   }|j                  dv r7|j                         }dd||j	                  d      dcddd      d{    S d | d}|j                  |||       d{   }|j                  dv r7|j                         }dd||j	                  d      dcddd      d{    S d!| d}|j                  |||       d{   }|j                  dv r7|j                         }dd||j	                  d      dcddd      d{    S t        d"|j                   d#|j                   d$|j                         cddd      d{    S # t        $ r t        d      cY S w xY w7 N7 !7 7 7 7 @7  7 7 7 {7 :# 1 d{  7  sw Y   yxY w# t        $ r}t        d%|       cY d}~S d}~ww xY ww)&a  Send via QQBot using the REST API directly (no WebSocket needed).

    Uses the QQ Bot Open Platform REST endpoints to get an access token
    and post a message. Supports guild channels, C2C (private) chats,
    and group chats by trying the appropriate endpoints.
    r   Nz8QQBot direct send requires httpx. Run: pip install httpxapp_id	QQ_APP_IDr&   client_secretQQ_CLIENT_SECRETz3QQBot: QQ_APP_ID / QQ_CLIENT_SECRET not configured.   r   z)https://bots.qq.com/app/getAppAccessToken)appIdclientSecretr     zQQBot token request failed: access_tokenz"QQBot: no access_token in responsezQQBot r   r   i  )rG  msg_typez#https://api.sgroup.qq.com/channels/z	/messages)r   r   >   rW     Tqqbotr   r  z#https://api.sgroup.qq.com/v2/users/z$https://api.sgroup.qq.com/v2/groups/zQQBot send failed: channel=z c2c=z group=zQQBot send failed: )r  rk  rK   r   r   r*   r   r   r  r   rP   status_coder   ri   )r  rM   rH   r  r   appidsecretr
  
token_resp
token_datarX  r   r	  r   r   r   url_c2cresp_c2c	url_group
resp_groupr   s                        r7   r  r  D  s    R MMREIIh=299["#=Emm 3uyy9 3-r2 KLL21$$R$0 /	F /	FF%{{;"5z3v;G  +   J %%, <Z=S=S<TUV/	F /	F /	F $*J%>>.9LBC/	F /	F /	F" $*,!8 2G #*%4.a@G 8y	JCSwHHD:-yy{#'W&*hhtn67/	F /	F /	F> <G9ING#[[w[PPH##z1}}#'W&*hhtn6G/	F /	F /	FN ?wiyQI%{{97G{TTJ%%3!(#'W&*hhtn6W/	F /	F /	F^ 78H8H7IxOcOcNddklv  mC  mC  lD  E  F_/	F /	F /	F  RPQQR/	F/	F /	F0 I1/	F@ QA/	FP UQ/	F /	F /	F /	F /	F`  1+A3/001s  MK A?ML K,L ",L	K/*L	9L K2L 
M-L	8L K5L 	M
-L	7K887L	/L ;K;<L  ML	K>7L	L "L#L 'M(L	L7L	=L 	L
L M1L	 L LL MK)&M(K))M,L /L	2L 5L 8L	;L >L	L L	L L 	LLLL ML 	L?'L:4L?5M:L??Mc                    K   	 ddl m}m}  |       }|t	        d      S 	  ||| ||       d{   S # t        $ r t	        d      cY S w xY w7 # t
        $ r}t	        d|       cY d}~S d}~ww xY ww)u  Send via Yuanbao using the running gateway adapter's WebSocket connection.

    Yuanbao uses a persistent WebSocket — unlike HTTP-based platforms, we
    cannot create a throwaway client.  We obtain the running singleton from
    the adapter module itself (``get_active_adapter``).

    chat_id format:
      - Group: "group:<group_code>"
      - DM:    "direct:<account_id>" or just "<account_id>"
    r   )get_active_adaptersend_yuanbao_directz%Yuanbao adapter module not available.NzVYuanbao adapter is not running. Start the gateway with yuanbao platform enabled first.rX  zYuanbao send failed: )gateway.platforms.yuanbaorf  rg  rk  rK   ri   )rM   rH   r0   rf  rg  r   r   s          r7   ry  ry    s     ?U !"GE
 	

3('7P[\\\  ?=>>? ] 3-aS1223sc   A55 A5A AA A5A	A5AA5A 	A2A-'A2(A5-A22A5)r   )Fr  )NNFFr  )NN)N__doc__rl   r   loggingr*   r  sslr  email.utilsr   agent.redactr   agent.secret_scoper   	getLoggerrR  rj   compiler  r  r"  r!  r$  r%  r   	frozensetr)  r*  
IGNORECASEr(  r'  r   r5  r6  r7  r4  r  r.   r  rr  r8   rC   rE   rP   rG   r   rK   rR   ri   r  r_   rb   rq   SEND_MESSAGE_SCHEMAr   r   r   r   r   r   r<  r   rU  r   r   r  rp  r~  rw  ru  r7  ri  r  rN  r  ry  tools.registryr   rf   r9   r7   <module>ru     sG      	 	 
  " . )			8	$&BJJ'DE BJJfg  2::=> $"**%MN BJJuv RZZ CD -  DE "**45
 2::IMM  2::WX 
  '(<= 86@w $V,   +- 1     &N "rzzWMM  'BJJQMM # 4C 4D 4
C # # y 3 54< 2 EF 'c '& 	5  !<  } !  \
 !  ]
 !  J
 !  %
. 3% P LY1xn7bUS Uc Up:&3  QTW[Q[ J \~EP
4 4t 4}4H
\(z2NGT$^2*7@4E1P3< &r9   