+
    ia                      ^ RI Ht  ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIHt ^ RIHt ^ RIHtHtHtHt ]P(                  ! ]4      t0 Rmt ^ RIt^ RIHtHt ^ RIHt Rt^ RI t ^ RIHt! ] PD                  PG                  ^ ]$! ]!! ]%4      PM                  4       PN                  ^,          4      4       ^ R
I(H)t)H*t* ^ RI+t+^ RI,H-t-H.t.H/t/H0t0H1t1H2t2H3t3H4t4 R R lt5R R lt6 ! R R4      t7 ! R R]-4      t8]'       dA    ! R R]Pr                  Pt                  4      t; ! R R]Pr                  Pt                  4      t<R# R#   ] d    R	tRt]t]tRt Li ; i)    )annotationsN)defaultdict)Path)CallableDictOptionalAny)MessageIntents)commandsTF)PlatformPlatformConfig)BasePlatformAdapterMessageEventMessageType
SendResultcache_image_from_urlcache_audio_from_urlcache_document_from_bytesSUPPORTED_DOCUMENT_TYPESc                    V ^8  d   QhRRRR/# )   entrystrreturn )formats   "6/home/ubuntu/hermes-agent/gateway/platforms/discord.py__annotate__r   <   s      S S     c                :   V P                  4       p V P                  R4      '       d8   V P                  R4      '       d!   V P                  R4      P	                  R4      p V P                  4       P                  R4      '       d
   V R,          p V P                  4       # )zStrip common prefixes from a Discord user ID or username entry.

Users sometimes paste IDs with prefixes like ``user:123``, ``<@123>``,
or ``<@!123>`` from Discord's UI or other tools.  This normalises the
entry to just the bare ID or username.
<@><@!zuser::   NN)strip
startswithendswithlstriprstriplower)r   s   &r   _clean_discord_idr,   <   ss     KKME%.."5"5U#**3/{{}((b	;;=r    c                   V ^8  d   QhRR/# r   r   boolr   )r   s   "r   r   r   M   s      D r    c                     \         # )z,Check if Discord dependencies are available.)DISCORD_AVAILABLEr   r    r   check_discord_requirementsr2   M   s    r    c                      ] tR t^RtRtRtRtRt^tRR R llt	R t
R	 tR
 tR tR R ltR tR R ltR R ltR R lt]RR R ll4       tRtR# )VoiceReceivera#  Captures and decodes voice audio from a Discord voice channel.

Attaches to a VoiceClient's socket listener, decrypts RTP packets
(NaCl transport + DAVE E2EE), decodes Opus to PCM, and buffers
per-user audio.  A polling loop detects silence and delivers
completed utterances via a callback.
g      ?g      ?逻  Nc                   V ^8  d   QhRR/# )r   allowed_user_idssetr   )r   s   "r   r   VoiceReceiver.__annotate__`   s     % %s %r    c                	   Wn         T;'       g    \        4       V n        R V n        RV n        RV n        ^ V n        / V n        \        P                  ! 4       V n
        \        \        4      V n        / V n        / V n        R V n        ^ V n        R# FN)_vcr8   _allowed_user_ids_running_secret_key_dave_session	_bot_ssrc_ssrc_to_user	threadingLock_lockr   	bytearray_buffers_last_packet_time	_decoders_paused_packet_debug_count)selfvoice_clientr7   s   &&&r   __init__VoiceReceiver.__init__`   s    !1!:!:SU -1! .0^^%
 /:).D35 -/  $% r    c                P   V P                   P                  p\        VP                  4      V n        VP
                  V n        VP                  V n        V P                  V4       VP                  V P                  4       RV n        \        P                  RV P                  4       R# )z"Start listening for voice packets.Tz#VoiceReceiver started (bot_ssrc=%d)N)r<   _connectionbytes
secret_keyr?   dave_sessionr@   ssrcrA   _install_speaking_hookadd_socket_listener
_on_packetr>   loggerinfo)rL   conns   & r   startVoiceReceiver.start   su    xx## 1!..##D)  194>>Jr    c                   RV n          V P                  P                  P                  V P                  4       V P                  ;_uu_ 4        V P                  P                  4        V P                  P                  4        V P                  P                  4        V P                  P                  4        RRR4       \        P                  R4       R#   \
         d     Li ; i  + '       g   i     L8; i)zStop listening and clean up.FNzVoiceReceiver stopped)r>   r<   rQ   remove_socket_listenerrX   	ExceptionrE   rG   clearrH   rI   rB   rY   rZ   rL   s   &r   stopVoiceReceiver.stop   s    	HH  77H ZZZMM!""((*NN  "$$&	 
 	+,  		Zs   /C A)C%C"!C"%C5	c                	    R V n         R# TNrJ   rb   s   &r   pauseVoiceReceiver.pause   s	    r    c                	    R V n         R# r;   rg   rb   s   &r   resumeVoiceReceiver.resume   s	    r    c                    V ^8  d   QhRRRR/# )r   rU   intuser_idr   )r   s   "r   r   r9      s     / /S /3 /r    c                	    V P                   ;_uu_ 4        W P                  V&   R R R 4       R #   + '       g   i     R # ; iN)rE   rB   )rL   rU   ro   s   &&&r   map_ssrcVoiceReceiver.map_ssrc   s%    ZZZ'.t$ ZZZs   .?	c                B  aa VP                   oV oVV3R lpW!n          ^ RIHp \        VR4      '       d:   VP                  VJd(   W!P                  n        \        P                  R4       R# R# R#   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)a#  Wrap the voice websocket hook to capture SPEAKING events (op 5).

VoiceConnectionState stores the hook as ``conn.hook`` (public attr).
It is passed to DiscordVoiceWebSocket on each (re)connect, so we
must wrap it on the VoiceConnectionState level AND on the current
live websocket instance.
c                  <"   \        V\        4      '       d   VP                  R 4      ^8X  d   VP                  R/ 4      pVP                  R4      pVP                  R4      pV'       dC   V'       d;   \        P	                  RW44       SP                  \        V4      \        V4      4       S'       d   S! W4      G Rj  xL
  R# R#  L5i)opdrU   ro   z"SPEAKING event: ssrc=%d -> user=%sN)
isinstancedictgetrY   rZ   rr   rn   )wsmsgdatarU   ro   original_hookreceiver_selfs   &&   r   wrapped_hook:VoiceReceiver._install_speaking_hook.<locals>.wrapped_hook   s     #t$$!);wwsB'xx'((9-GKK DdT!**3t9c'lC#B,,, ,s%   A(C,C4AC6CC
	C)MISSINGr{   z)Speaking hook installed on live websocketz%Could not install hook on live ws: %sN)
hookdiscord.utilsr   hasattrr{   _hookrY   rZ   r`   warning)rL   r[   r   r   er~   r   s   &&   @@r   rV   $VoiceReceiver._install_speaking_hook   s     				- !		G-tT""twwg'= ,GH (>"  	GNNBAFF	Gs   A2 5A2 2B=BBc                   V ^8  d   QhRR/# )r   r}   rR   r   )r   s   "r   r   r9      s     j ju jr    c           	     	
   V P                   '       d   V P                  '       d   R # V ;P                  ^,          un        V P                  ^8:  dH   \        P	                  R\        V4      \        V4      ^8  d   VR,          P                  4       MR4       \        V4      ^8  d   R # V^ ,          ^,	          ^8w  g   V^,          ^,          ^x8w  d9   V P                  ^8:  d&   \        P	                  RV^ ,          V^,          4       R # V^ ,          p\        P                  ! RV^ 4      w   r4rVW`P                  8X  d   R # V^,          p\        V^,          4      p^^V,          ,           V'       d   ^M^ ,           p	\        V4      V	^,           8  d   R # ^ p
V'       d?   ^^V,          ,           p\        P                  ! RW^,           4      ^ ,          pV^,          p
V P                  ^
8:  dR   V P                  ;_uu_ 4        V P                  P                  VR4      pR R R 4       \        P	                  RWdXW4       \        VR V	 4      pWR  p\        V4      ^8  d   R # \        ^4      pVRR  VR&   \        VR R 4      p ^ R IpVP"                  P%                  V P&                  4      pVP)                  VV\        V4      4      pT
'       d   \        T4      T
8  d   TT
R  pT P.                  '       dx   T P                  ;_uu_ 4        T P                  P                  T^ 4      pR R R 4       X'       d7    ^ R IpT P.                  P)                  TTP2                  P4                  T4      p Y`P8                  9  d,   \:        P<                  P?                  4       T P8                  T&   T P8                  T,          PA                  T4      pT P                  ;_uu_ 4        T PB                  T,          PE                  T4       \F        PH                  ! 4       T PJ                  T&   R R R 4       R #   + '       g   i     EL; i  \*         d>   pT P                  ^
8:  d"   \        P-                  R	TT	\        T4      4        R p?R # R p?ii ; i  + '       g   i     ELo; i  \*         dJ   pR
\7        T4      9  d/   T P                  ^
8:  d   \        P-                  RTT4        R p?R #  R p?ELR p?ii ; i  + '       g   i     R # ; i  \*         d#   p\        P	                  RTT4        R p?R # R p?ii ; i)Nz&Raw UDP packet: len=%d, first_bytes=%s:N   Nshortz*Skipped non-RTP: byte0=0x%02x byte1=0x%02xz>BBHIIz>Hunknownz9RTP packet: ssrc=%d, seq=%d, user=%s, hdr=%d, ext_data=%dz(NaCl decrypt failed: %s (hdr=%d, enc=%d)Unencryptedz#DAVE decrypt failed for ssrc=%d: %sz!Opus decode error for SSRC %s: %s)&r>   rJ   rK   rY   debuglenhexstructunpack_fromrA   r/   rE   rB   rz   rR   rF   nacl.secretsecretAeadr?   decryptr`   r   r@   davey	MediaTypeaudior   rI   discordopusDecoderdecoderG   extendtime	monotonicrH   )rL   r}   
first_byte_seq	timestamprU   cchas_extensionheader_sizeext_data_lenext_preamble_offset	ext_words
known_userheaderpayload_with_noncenonce	encryptednaclbox	decryptedr   ro   r   pcms   &&                       r   rX   VoiceReceiver._on_packet   s    }}} 	  A% ##q(LL8D	SY!^48<<>
 t9r>
 GqLQ47T>d":''1,I4PQ7TXYZT[\!W
%+%7%7$%J"19 >>! $Z$./AFmMqqAt9{Q& "$B-**4Q7NOPQRI$q=L##r)!//33D)D
 LLK:{
 tL[)*!,/ !"Q&"&rs+b	,Sb12		++""4#3#34CIvuU|DI C	N\9!,-0I ,,00q9 
  $ 2 2 : :!6!6	!I		>>)'.||';';'=t$..&--i8Cd#**3//3~~/?&&t, o *  	''2-I1k[^_h[ij	  ! $CF233r9"NN+PRVXYZ 3   	LL<dAF	s   P4AQ R>5R' 4A1T %AS>*T 4Q	R2RRR$	'S;28S66S;>T		T T T?T::T?c                    V ^8  d   QhRRRR/# )r   rU   rn   r   r   )r   s   "r   r   r9   <  s        r    c                B    V P                   P                  pV'       g   ^ # V P                   P                  '       d!   V P                   P                  P                  M^ pV P                  pVP
                   Uu. uFF  pVP                  V8w  g   K  V'       d   \        VP                  4      V9   g   K:  VP                  NKH  	  pp\        V4      ^8X  d0   V^ ,          pWpP                  V&   \        P                  RW4       V#  ^ # u upi   \         d     ^ # i ; i)zTry to infer user_id for an unmapped SSRC.

When the bot rejoins a voice channel, Discord may not resend
SPEAKING events for users already speaking.  If exactly one
allowed user is in the channel, map the SSRC to them.
z4Auto-mapped ssrc=%d -> user=%d (sole allowed member))r<   channeluseridr=   membersr   r   rB   rY   rZ   r`   )rL   rU   r   bot_idallowedm
candidatesuids   &&      r   _infer_user_for_ssrc"VoiceReceiver._infer_user_for_ssrc<  s    	hh&&G)-TXX]]%%AF,,G%oo-446> +2c!$$i76J o   :!# m+.""4(RTX^
	 $   		s@   D D <D :D
D
D
6D
A D 
D DDc                   V ^8  d   QhRR/# )r   r   listr   )r   s   "r   r   r9   V  s      t r    c           	        \         P                  ! 4       p. pV P                  ;_uu_ 4        \        V P                  4      p\        V P                  P                  4       4      pV EFe  pV P                  P                  WQ4      pW,
          pV P                  V,          p\        V4      V P                  V P                  ,          ^,          ,          p	WpP                  8  d   WP                  8  d   VP                  V^ 4      p
V
'       g   V P                  V4      p
V
'       d   VP!                  V
\#        V4      34       \%        4       V P                  V&   V P                  P'                  VR4       EK  WpP                  ^,          8  g   EK-  V P                  P'                  VR4       V P                  P'                  VR4       EKh  	  RRR4       V#   + '       g   i     T# ; i)z=Return list of (user_id, pcm_bytes) for completed utterances.N)r   r   rE   ry   rB   r   rG   keysrH   rz   r   SAMPLE_RATECHANNELSSILENCE_THRESHOLDMIN_SPEECH_DURATIONr   appendrR   rF   pop)rL   now	completedssrc_user_map	ssrc_listrU   	last_timesilence_durationbufbuf_durationro   s   &          r   check_silenceVoiceReceiver.check_silenceV  sq   nn	ZZZ !3!34MT]]//12I! 2266tA	#&? mmD)"3x4+;+;dmm+Ka+OP#'='==,RjRjBj+//a8G" #'";";D"A!(('5:)>?*3+DMM$'**..tT:%)?)?!)CCMM%%dD1**..tT:) "	 4 5 Z4 s   C"GG(A&G=GG,	c               (    V ^8  d   QhRRRRRRRR/# )r   pcm_datarR   output_pathr   src_ratern   src_channelsr   )r   s   "r   r   r9   |  s*      U   8;r    c                   \         P                  ! RRR7      ;_uu_ 4       pVP                  V 4       VP                  pRRR4        \        P
                  ! RRRRR	R
R\        V4      R\        V4      RXRRRRV.R^
R7        \        P                  ! V4       R#   + '       g   i     Lf; i  \         d     R# i ; i   \        P                  ! X4       i   \         d     i i ; i; i)z-Convert raw PCM to 16kHz mono WAV via ffmpeg.z.pcmF)suffixdeleteNffmpegz-yz	-loglevelerrorz-fs16lez-arz-acz-i160001T)checktimeout)
tempfileNamedTemporaryFilewritename
subprocessrunr   osunlinkOSError)r   r   r   r   fpcm_paths   &&&&  r   
pcm_to_wavVoiceReceiver.pcm_to_wav{  s     ((uEEGGHvvH F	NNdK'3x=3|,(73	 		(#) FE*  		(# sM   B<C B0 B-	0B?>B?C,CC,C)&C,(C))C,)r=   rA   rG   r@   rI   rH   rE   rK   rJ   r>   r?   rB   r<   rq   )r5   r   )__name__
__module____qualname____firstlineno____doc__r   r   r   r   rN   r\   rc   rh   rk   rr   rV   rX   r   r   staticmethodr   __static_attributes__r   r    r   r4   r4   R   sk     KH%>
K-/GJj`4J  r    r4   c                     a  ] tR tRtRtRtRtR V 3R lltR R ltR	 R
 lt	R R lt
R R ltR R ltR R ltR R ltRtR R lltR R ltRtR R lltR R ltRuR V 3R llltR R  ltR! R" lt^xtR# R$ ltR% R& ltR' R( ltR) R* ltR+ R, ltR- R. ltR/ R0 lt^tR1 R2 ltR3 R4 lt R5 R6 lt!RuR7 V 3R8 lllt"RuR9 V 3R: lllt#RuR; V 3R< lllt$RvR= V 3R> lllt%RwR? R@ llt&RA RB lt'RC RD lt(RE RF lt)RG RH lt*RwRI RJ llt+RK RL lt,RM RN lt-RxRQ RR llt.RS RT lt/RU RV lt0RW RX lt1RYRORZRP/R[ R\ llt2R] R^ lt3RyR_ R` llt4RzRa Rb llt5Rc Rd lt6Re Rf lt7Rg Rh lt8]9Ri Rj l4       t:];Rk Rl l4       t<Rm Rn lt=Ro Rp lt>Rq Rr lt?Rst@V ;tA# ){DiscordAdapteri  a  
Discord bot adapter.

Handles:
- Receiving messages from servers and DMs
- Sending responses with Discord markdown
- Thread support
- Native slash commands (/ask, /reset, /status, /stop)
- Button-based exec approvals
- Auto-threading for long conversations
- Reaction-based feedback
  ,  c                   V ^8  d   QhRR/# )r   configr   r   )r   s   "r   r   DiscordAdapter.__annotate__  s      ~ r    c                	  < \         SV `  V\        P                  4       R V n        \
        P                  ! 4       V n        \        4       V n	        / V n
        / V n        / V n        / V n        / V n        R V n        R V n        V P#                  4       V n        / V n        R V n        RV n        / V n        RV n        RV n        R # )Ni  r   r   )superrN   r   DISCORD_clientasyncioEvent_ready_eventr8   r=   _voice_clients_voice_text_channels_voice_timeout_tasks_voice_receivers_voice_listen_tasks_voice_input_callback_on_voice_disconnect_load_participated_threads_bot_participated_threads_typing_tasks	_bot_task_MAX_TRACKED_THREADS_seen_messages	_SEEN_TTL	_SEEN_MAX)rL   r  	__class__s   &&r   rN   DiscordAdapter.__init__  s    !1!12/3#MMO&)e.046!=?!:<<> 9="8<! /3.M.M.O& 7915$'! 13r    c                   V ^8  d   QhRR/# r.   r   )r   s   "r   r   r    s     X Xt Xr    c                
  a a"   \         '       g#   \        P                  RS P                  4       R# \        P
                  P                  4       '       g   ^ RIpVP                  P                  R4      pV'       gJ   ^ RI
pR'pVP                  R8X  d3   V F,  p\        P                  P                  V4      '       g   K*  Tp M	  V'       d!    \        P
                  P                  V4       \        P
                  P                  4       '       g   \        P#                  R4       S P$                  P&                  '       g#   \        P                  RS P                  4       R#  ^ R	IHp S P$                  P&                  S n        V! R
S P,                  RR/R7      w  rxV'       g   \/        V\0        4      '       d   VP3                  R4      MRp	RV	'       d   RV	 R2MR,           R,           p
\        P                  RS P                  V
4       S P5                  RV
RR7       R# \        P6                  ! RR4      pV'       dH   VP9                  R4       Uu0 uF&  pVP;                  4       '       g   K  \=        V4      kK(  	  upS n        \@        PB                  ! 4       pRVn"        RVn#        RVn$        \J        ;QJ d&    R S P>                   4       F  '       g   K   RM	  RM! R S P>                   4       4      Vn&        RVn'        \P        PR                  ! RVR7      S n*        S oS PT                  PV                  V3R l4       pS PT                  PV                  R VV 3R ll4       pS PT                  PV                  V3R  l4       pS PY                  4        \Z        P\                  ! S PT                  P_                  S P$                  P&                  4      4      S n0        \Z        Pb                  ! S Pd                  Pg                  4       ^R!7      G Rj  xL
  RS n4        R#   \          d    \        P#                  RT4        ELYi ; iu upi  L:  \Z        Pj                   dm    \        P                  R"S P                  RR#7        ^ R$IH6p \o        S R%R4      '       d   T! R
S P,                  4       RS n        M  \          d     Mi ; i R# \          du   p\        P                  R&S P                  TRR#7        ^ R$IH6p \o        S R%R4      '       d   T! R
S P,                  4       RS n        M  \          d     Mi ; i Rp?R# Rp?ii ; i5i)(z.Connect to Discord and start receiving events.z:[%s] discord.py not installed. Run: pip install discord.pyFNr   darwinz)Opus codec found at %s but failed to loadu8   Opus codec not found — voice channel playback disabledz[%s] No bot token configured)acquire_scoped_lockdiscord-bot-tokenplatformr   metadatapidz Discord bot token already in usez (PID ) z. Stop the other gateway first.z[%s] %sdiscord_token_lock)	retryableDISCORD_ALLOWED_USERS,Tc              3  J   "   T F  qP                  4       '       * x  K  	  R # 5irq   )isdigit).0r   s   & r   	<genexpr>)DiscordAdapter.connect.<locals>.<genexpr>  s     !ZCY%mmo"5"5CYs   !#!)command_prefixintentsc                 
  <"   \         P                  R SP                  SP                  P                  4       SP                  4       G Rj  xL
   SP                  P                  P                  4       G Rj  xL
 p \         P                  RSP                  \        V 4      4       SP                  P                  4        R#  Lw LL  \         d.   p\         P                  RSP                  TRR7        Rp?LSRp?ii ; i5i)z[%s] Connected as %sNz[%s] Synced %d slash command(s)z"[%s] Slash command sync failed: %sTexc_info)rY   rZ   r   r  r   _resolve_allowed_usernamestreesyncr   r`   r   r	  r8   )syncedr   adapter_selfs     r   on_ready(DiscordAdapter.connect.<locals>.on_ready  s     2L4E4E|G[G[G`G`a #==???n#/#7#7#<#<#A#A#CCFKK A<CTCTVYZ`Vab ))--/ @ D  nNN#GIZIZ\]hlNmmnsN   A	DCD'C 9C:.C (DC D #C;6D;D  Dc                   V ^8  d   QhRR/# )r   messageDiscordMessager   )r   s   "r   r   ,DiscordAdapter.connect.<locals>.__annotate__(  s     84 84. 84r    c                  <"   \        V P                  4      p\        P                  ! 4       pVS	P                  9   d   R # VS	P                  V&   \	        S	P                  4      S	P
                  8  dO   VS	P                  ,
          pS	P                  P                  4        UUu/ uF  w  rEWS8  g   K  WEbK  	  uppS	n        V P                  S
P                  P                  8X  d   R # V P                  \        P                  P                  \        P                  P                  39  d   R # S
P!                  \        V P                  P                  4      4      '       g   R # \#        V P                  RR4      '       d   \$        P&                  ! RR4      P)                  4       P+                  4       pVR8X  d   R # VR8X  dD   S
P                  P                  '       d&   S
P                  P                  V P,                  9  d   R # \$        P&                  ! RR4      P)                  4       R9   pV'       d   V P,                  '       dx   \/        V P0                  \        P2                  4      '       gN   S
P                  P                  R J;'       d$    S
P                  P                  V P,                  9   pV'       g   R # S
P5                  V 4      G R j  xL
  R # u uppi  L5i)	NbotFDISCORD_ALLOW_BOTSnonementionsDISCORD_IGNORE_NO_MENTIONtruerF  r   yes)r   r   r   r  r   r  r  itemsauthorr  r   typer   r   defaultreply_is_allowed_usergetattrr   getenvr+   r&   rD  rx   r   	DMChannel_handle_message)r=  msg_idr   cutoffkv
allow_bots_ignore_no_mention_bot_mentionedr9  rL   s   &        r   
on_message*DiscordAdapter.connect.<locals>.on_message'  s6     WZZiik\88869++F3|223l6L6LL <#9#99F)5)D)D)J)J)L3)L: )L3L/ >>T\\%6%66 <<(;(;(C(CWEXEXE^E^'__ ,,S1B1B-CDD 7>>5%88!#+?!H!N!N!P!V!V!XJ!V+#z1#||000DLL4E4EWM]M]4]" &(YY/&%'1&2" &'*:*:*::goo_f_p_pCqCq))5 B B LL--1A1AA # ***7333_3^ 4s?   B!K#$
K3K9D&K# A(K#	AK#+K#>K#K!K#c           	     x  <"   \        SP                  P                  4       4      pV'       g   R# V P                  P                  pWC9  d   R# V SP
                  P                  8X  d   R# VP                  RJ ;'       d    VP                  RJpVP                  RJ;'       d    VP                  RJ pVP                  RJ;'       d0    VP                  RJ;'       d    VP                  VP                  8g  pV'       g   V'       g	   V'       d   \        P                  RV P                  V P                  V'       d   RVP                  P                  ,           MTV'       d   RVP                  P                  ,           M/RVP                  P                   RVP                  P                   2V4       R# R# 5i)z&Track voice channel join/leave events.Nz"Voice state: %s (%d) %s (guild %d)zjoined zleft zmoved z -> )r8   r
  r   guildr   r  r   r   rY   rZ   display_namer   )	memberbeforeafterbot_guild_idsguild_idjoinedleftswitchedr9  s	   &&&     r   on_voice_state_update5DiscordAdapter.connect.<locals>.on_voice_state_updateb  sQ     !$L$?$?$D$D$F G$!<<??0\116664/MMEMM4M~~T1KKemmt6KNN$. 8 8T18 8%--7  TXKK<++		:@	EMM$6$66>BWv~~':'::%fnn&9&9%:$u}}?Q?Q>RS  &.s=   A=F:#F:%#F:	F:!F:F:	F:-F:?$F:$AF:r   z.[%s] Timeout waiting for connection to Discordr3  release_scoped_lock_token_lock_identityz%[%s] Failed to connect to Discord: %s)z/opt/homebrew/lib/libopus.dylibz/usr/local/lib/libopus.dylib)8r1   rY   r   r   r   r   	is_loadedctypes.utilutilfind_librarysysr   r   pathisfile	load_opusr`   r   r  tokengateway.statusr  rl  rx   ry   rz   _set_fatal_errorrP  splitr&   r,   r=   r   rL  message_contentdm_messagesguild_messagesanyr   voice_statesr   Botr  event_register_slash_commandsr  create_taskr\   r  wait_forr	  waitr>   TimeoutErrorrk  rO  )rL   ctypes	opus_pathrq  _homebrew_paths_hpr  acquiredexisting	owner_pidr=  allowed_envr   r1  r:  rZ  rg  rk  r   r9  s   f                  @r   connectDiscordAdapter.connect  sW      LLUW[W`W`a ||%%''008I # <<8+.77>>#..(+I!  / [LL**95 <<))++YZ{{   LL7Cu	:(,(9(9D%!45H$JcJcoy  |E  oF  "GH3=h3M3MHLL/SW	<Zc6)TUAVikl  pQ  QY		7;%%&:Gu%U ))$;R@K6A6G6G6L*6Lsyy{ +%c*6L*& oo'G&*G#"&G%)G"!c!Z4CYCY!Zccc!Z4CYCY!ZZGO#'G  $<<"DL  L \\0  0 \\84 84  84t \\  B ))+ %001C1CDKKDUDU1VWDN ""4#4#4#9#9#;RHHH DMS ! [NN#NPYZ[2*Z I
 ## 		LLI499_cLd>4!7>>'(;T=V=VW04D-  		LL@$))QY]L^>4!7>>'(;T=V=VW04D- 		s  AU&U>>UUP$ /#U0U"U'A3Q A
Q %U'Q Q Q6Q<Q Q  D6Q QQ "U$!Q	UQ		UQ 7U3R?>U?S
USUUUU#U3T54U5U UUUUUUc                   V ^8  d   QhRR/# r   r   Noner   )r   s   "r   r   r    s     4 4$ 4r    c                  "   \        V P                  P                  4       4       F  p V P                  V4      G Rj  xL
  K  	  V P                  '       d$    V P                  P                  4       G Rj  xL
  RV n        RV n        V P                  P                  4         ^ RIHp \!        V RR4      '       d   V! R	V P"                  4       RV n        \
        P%                  R
T P                  4       R#  L  \         d.   p\
        P                  RT P                  Y4        Rp?EK	  Rp?ii ; i L  \         d.   p\
        P                  RT P                  TRR7        Rp?LRp?ii ; i  \         d     Li ; i5i)zDisconnect from Discord.Nz'[%s] Error leaving voice channel %s: %sz [%s] Error during disconnect: %sTr3  Frj  rl  r  z[%s] Disconnected)r   r
  r   leave_voice_channelr`   rY   r   r   r  closer   r>   r	  ra   rv  rk  rO  rl  rZ   )rL   rc  r   rk  s   &   r   
disconnectDiscordAdapter.disconnect  sD     T005578H`..x888 9 <<<`ll((*** !	:t3T::#$79R9RS,0) 	'3/ 9 `F		S[__`
 + `A499aZ^__`  		s   'FC<C:C<FD9 7D78D9 <(F%3E4 "F:C<<D4!D/(F/D44F7D9 9E1#E,'F,E11F4F?FFFc               $    V ^8  d   QhRRRRRR/# r   r=  r	   emojir   r   r/   r   )r   s   "r   r   r    s!     	 	3 	s 	t 	r    c                   "   V'       d   \        VR4      '       g   R#  VP                  V4      G Rj  xL
  R#  L  \         d-   p\        P	                  RT P
                  Y#4        Rp?R# Rp?ii ; i5i)z+Add an emoji reaction to a Discord message.add_reactionFNTz![%s] add_reaction failed (%s): %s)r   r  r`   rY   r   r   rL   r=  r  r   s   &&& r   _add_reactionDiscordAdapter._add_reaction  s_     gg~>>	&&u--- . 	LL<diiR	s7   A7= ;= A7= A4!A/)A7/A44A7c               $    V ^8  d   QhRRRRRR/# r  r   )r   s   "r   r   r    s!     	 	c 	# 	$ 	r    c                v  "   V'       dA   \        VR4      '       d/   V P                  '       d   V P                  P                  '       g   R#  VP                  W P                  P                  4      G Rj  xL
  R#  L  \         d-   p\
        P                  RT P                  Y#4        Rp?R# Rp?ii ; i5i)z;Remove the bot's own emoji reaction from a Discord message.remove_reactionFNTz$[%s] remove_reaction failed (%s): %s)r   r  r   r  r`   rY   r   r   r  s   &&& r   _remove_reactionDiscordAdapter._remove_reaction  s     gg/@AA]a]i]i]n]n]n	))%1B1BCCC D 	LL?EU	sL   ,B9B9B9(A? 6A=7A? ;B9=A? ?B6
!B1+B91B66B9c                   V ^8  d   QhRR/# r.   r   )r   s   "r   r   r    s     Z ZD Zr    c                R    \         P                  ! RR4      P                  4       R9  # )z6Check if message reactions are enabled via config/env.DISCORD_REACTIONSrF  false0no)r   rP  r+   rb   s   &r   _reactions_enabled!DiscordAdapter._reactions_enabled  s#    yy,f5;;=EYYYr    c                    V ^8  d   QhRRRR/# )r   r  r   r   r  r   )r   s   "r   r   r    s     6 6| 6 6r    c                   "   V P                  4       '       g   R# VP                  p\        VR4      '       d   V P                  VR4      G Rj  xL
  R# R#  L5i)z>Add an in-progress reaction for normal Discord message events.Nr     👀)r  raw_messager   r  )rL   r  r=  s   && r   on_processing_start"DiscordAdapter.on_processing_start  sM     &&((##7N++$$Wf555 ,5s   AAA	Ac               $    V ^8  d   QhRRRRRR/# )r   r  r   successr/   r   r  r   )r   s   "r   r   r    s'     K K, K KRV Kr    c                   "   V P                  4       '       g   R# VP                  p\        VR4      '       d@   V P                  VR4      G Rj  xL
  T P	                  Y2'       d   RMR4      G Rj  xL
  R# R#  L+ L
5i)zCSwap the in-progress reaction for a final success/failure reaction.Nr  r  u   ✅u   ❌)r  r  r   r  r  )rL   r  r  r=  s   &&& r   on_processing_complete%DiscordAdapter.on_processing_complete  sj     &&((##7N++''888$$WweEJJJ ,8Js$   AA>A:"A>1A<2	A><A>c          
     ,    V ^8  d   QhRRRRRRRRRR	/# )
r   chat_idr   contentreply_toOptional[str]r"  Optional[Dict[str, Any]]r   r   r   )r   s   "r   r   r    sC     E; E;E; E;  	E;
 +E; 
E;r    c                  "   V P                   '       g   \        RRR7      #  V P                   P                  \        V4      4      pV'       g-   V P                   P	                  \        V4      4      G Rj  xL
 pV'       g   \        RRV R2R7      # V P                  V4      pV P                  W`P                  4      p. pRp	V'       d&    VP                  \        V4      4      G Rj  xL
 p
T
p	\        V4       FP  w  rV^ 8X  d   T	MRp VP                  VVR7      G Rj  xL
 pTP#                  \        TP$                  4      4       KR  	  \        RV'       d
   V^ ,          MRRV/R7      #  L L  \         d!   p\        P                  RT4        Rp?LRp?ii ; i L  \         df   p\        T4      pTeQ   R	T9   dJ   R
T9   dC   \        P                  RT P                   T4       TP                  TRR7      G Rj  xL 
 p Rp?Lh Rp?ii ; i  \         dD   p\        P'                  RT P                   TRR7       \        R\        T4      R7      u Rp?# Rp?ii ; i5i)z$Send a message to a Discord channel.FNot connectedr  r   NChannel 
 not foundz$Could not fetch reply-to message: %s)r  	referencezerror code: 50035z Cannot reply to a system messagezW[%s] Reply target %s is a Discord system message; retrying send without reply referenceTmessage_ids)r  
message_idraw_responsez'[%s] Failed to send Discord message: %sr3  )r  r   get_channelrn   fetch_channelformat_messagetruncate_messageMAX_MESSAGE_LENGTHfetch_messager`   rY   r   	enumeratesendr   r   r   r   r   r   )rL   r  r  r  r"  r   	formattedchunksr  r  ref_msgr   ichunkchunk_referencer|   err_texts   &&&&&            r   r  DiscordAdapter.send  s-     |||e?CC:	;ll..s7|<G $ : :3w< HH!%'*7UVV ++G4I**96M6MNFKIL$+$9$9#h-$HHG 'I &f-/0Av)4 ' %"1 !- ! C, ""3svv;/3 .6 -8;q>d+[9 ] I I  LLL!GKKL ! "1vH'3/8;>(Ju II$
 %,LL$)&* %1 %  
 #4  	;LLBDIIq[_L`e3q6::	;s    I)AH 6E17H H I)8H E5 *E3+E5 1H F%#F#$F%(AH 0I)1H 3E5 5F  FH F  H #F%%H0AHHH
H HHH I&#8I!I&I)!I&&I)c               (    V ^8  d   QhRRRRRRRR/# )r   r  r   r  r  r   r   r   )r   s   "r   r   r  4  s2     ; ;; ; 	;
 
;r    c           	       "   V P                   '       g   \        RRR7      #  V P                   P                  \        V4      4      pV'       g-   V P                   P	                  \        V4      4      G Rj  xL
 pVP                  \        V4      4      G Rj  xL
 pV P                  V4      p\        V4      V P                  8  d   VRV P                  ^,
           R,           pVP                  VR7      G Rj  xL
  \        RVR7      #  L Lu L  \         dD   p\        P                  R	T P                  Y'RR
7       \        R\        T4      R7      u Rp?# Rp?ii ; i5i)z'Edit a previously sent Discord message.Fr  r  N...r  Tr  r  z*[%s] Failed to edit Discord message %s: %sr3  )r  r   r  rn   r  r  r  r   r  editr`   rY   r   r   r   )rL   r  r  r  r   r|   r  r   s   &&&&    r   edit_messageDiscordAdapter.edit_message4  s     |||e?CC	;ll..s7|<G $ : :3w< HH--c*o>>C++G4I9~ 7 77%&Bt'>'>'BCeK	((9(---dzBB I> . 	;LLEtyyR\jnLoe3q6::	;sl    E#AD 6D7!D DA!D :D;D E#D D D E 8EE E#E  E#c          
     ,    V ^8  d   QhRRRRRRRRRR/# )	r   r  r   	file_pathcaptionr  	file_namer   r   r   )r   s   "r   r   r  K  sC     @ @@ @ 	@
 !@ 
@r    c                  "   V P                   '       g   \        RRR7      # V P                   P                  \        V4      4      pV'       g-   V P                   P	                  \        V4      4      G Rj  xL
 pV'       g   \        RRV R2R7      # T;'       g     \
        P                  P                  V4      p\        VR4      ;_uu_ 4       p\        P                  ! WvR7      pTP                  V'       d   TMRVR	7      G Rj  xL
 p	RRR4       \        R
\        X	P                  4      R7      #  L L.  + '       g   i     L4; i5i)z*Send a local file as a Discord attachment.Fr  r  Nr  r  rbfilenamer  fileTr  )r  r   r  rn   r  r   rr  basenameopenr   Filer  r   r   )
rL   r  r  r  r  r   r  fhr  r|   s
   &&&&&     r   _send_file_attachment$DiscordAdapter._send_file_attachmentK  s      |||e?CC,,**3w<8 LL66s7|DDGeXgYj3QRR;; 0 0 ;)T""b<<6DWTPTUUC # $3svv;?? E V #"sH   A3E5D56EE4E8D9D7	D9)E7D99E		Ec               $    V ^8  d   QhRRRRRR/# )r   r  r   
audio_pathr   r   r   )r   s   "r   r   r  b  s-     W WW W
 
Wr    c                  "   V P                   P                  4        F  w  rE\        V4      \        V4      8X  g   K   V P                  V4      '       g   K9  \        P                  RV P                  V4       V P                  WB4      G Rj  xL
 p\        VR7      u # 	  V P                  ! RRVRV/VB G Rj  xL
 #  L2 L5i)zPlay auto-TTS audio.

When the bot is in a voice channel for this chat's guild, play
directly in the VC instead of sending as a file attachment.
z,[%s] Playing TTS in voice channel (guild=%d)N)r  r  r  r   )
r  rI  r   is_in_voice_channelrY   rZ   r   play_in_voice_channelr   
send_voice)rL   r  r  kwargsgid
text_ch_idr  s   &&&,   r   play_ttsDiscordAdapter.play_ttsb  s       $88>>@OC:#g,.43K3KC3P3PJDIIWZ[ $ : :3 KK!'22	  A
 __VWVVvVVV LVs.   6CC5CB>-C9C :C Cc               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r   r  r   r  r  r  r  r"  r  r   r   r   )r   s   "r   r   r  t  sV     Ig IgIg Ig 	Ig
  Ig +Ig 
Igr    c                t  <"    ^ RI pV P                  P                  \        V4      4      pV'       g-   V P                  P	                  \        V4      4      G Rj  xL
 pV'       g   \        RRV R2R7      # \        P                  P                  V4      '       g   \        RRV 2R7      # \        P                  P                  V4      p	\        VR4      ;_uu_ 4       p
V
P                  4       pRRR4        ^ RIpRp ^ R	IHp V! V4      pVP                  P                   p\)        ^.R,          4      pVP+                  V4      P-                  4       p^ RIpVP1                  RRRRRRRR\3        V^4      RV/./4      pRRRV/RRRXRRRR/.pV P                  P4                  P7                  \8        P4                  P;                  RRVP<                  R7      VR7      G Rj  xL
 p\        R \?        VR,          4      R!7      #  EL  + '       g   i     EL$; i  \"         d!    \%        R
\'        X4      R,          4      p EL%i ; i Lf  \"         d   p\@        PC                  R"T4       \8        PD                  ! TPG                  X4      T	R#7      pTPI                  TR$7      G Rj  xL 
 p\        R \?        TP<                  4      R!7      u Rp?# Rp?ii ; i  \"         dI   p\@        PK                  R%T PL                  TR R&7       \N        ST `  YY4TR'7      G Rj  xL 
 u Rp?# Rp?ii ; i5i)(z(Send audio as a Discord file attachment.NFr  r  r  zAudio file not found: r  g      @)OggOpus      ?g     @@   flagsi    attachmentsr   r  r  zvoice-message.oggduration_secswaveformr   payload_jsonvaluezfiles[0]content_typez	audio/oggPOSTz/channels/{channel_id}/messages
channel_id)formTr  z3Voice message flag failed, falling back to file: %sr  )r  z;[%s] Failed to send audio, falling back to base adapter: %sr3  r!  ))ior  r  rn   r  r   r   rr  existsr  r  readbase64mutagen.oggopusr  rZ   lengthr`   maxr   rR   	b64encoder   jsondumpsroundhttprequestr   Router   r   rY   r   r  BytesIOr  r   r   r  r  )rL   r  r  r  r  r"  r  r
  r   r  r   	file_datar  r  r  rZ   waveform_byteswaveform_b64_jsonpayloadr	  msg_data	voice_errr  r|   r   r  s   &&&&&&,                   r   r  DiscordAdapter.send_voicet  s    ?	gll..s7|<G $ : :3w< HH!%'*7UVV77>>*--!%9OPZ|7\]]ww''
3Hj$''1FFH	 (*H #F7":.D$(II$4$4M "'us{!3%//?FFH$++T!c"$7'}a)@"L	% $'  ^Wg>
"$7&	 "&!2!2!:!:LL&&v/P]d]g]g&h "; "  "$3x~;NOOe I ('' ! F$'S^f-D$EMF2
  HRT]^||BJJy$9HM#LLdL333!$3svv;GG	H
  	gLLVX\XaXacdosLt+G]e+ffff	gs   L8AK" HK" )K" 9L8:$K" K" .L8/3K" "H3K" <I $H" 'CI )I* I 
L8K" H		K" "'I	I II KAK/J20$KKK" L8KK" "L5-8L0%L(&L0*L5+L80L55L8c                   V ^8  d   QhRR/# r.   r   )r   s   "r   r   r    s      4 r    c                V  "   V P                   '       d   \        '       g   R# VP                  P                  pV P                  P                  V4      pV'       d{   VP                  4       '       de   VP                  P                  VP                  8X  d   V P                  V4       R# VP                  V4      G Rj  xL
  V P                  V4       R# VP                  4       G Rj  xL
 pW@P                  V&   V P                  V4        \        W@P                  R7      pVP                  4        WPP                  V&   \        P                   ! V P#                  V4      4      V P$                  V&   R#  L L  \&         d"   p\(        P+                  RT4        Rp?R# Rp?ii ; i5i)z6Join a Discord voice channel. Returns True on success.FTN)r7   z"Voice receiver failed to start: %s)r  r1   r]  r   r
  rz   is_connectedr   _reset_voice_timeoutmove_tor  r4   r=   r\   r  r  ensure_future_voice_listen_loopr  r`   rY   r   )rL   r   rc  r  vcreceiverr   s   &&     r   join_voice_channel!DiscordAdapter.join_voice_channel  sV    |||#4#4==## &&**84--//""gjj0))(3""7+++%%h/??$$(*H%!!(+	D$R:P:PQHNN.6!!(+181F1F''12D$$X. ' , %  	DNN?CC	Ds`   F):F)F)2AF)>E6?*F))E8*#F)A&E: 4F)8F):F&F!F)!F&&F)c                    V ^8  d   QhRRRR/# r   rc  rn   r   r  r   )r   s   "r   r   r    s     6 6# 6$ 6r    c                &  "   V P                   P                  VR4      pV'       d   VP                  4        V P                  P                  VR4      pV'       d   VP	                  4        V P
                  P                  VR4      pV'       d/   VP                  4       '       d   VP                  4       G Rj  xL
  V P                  P                  VR4      pV'       d   VP	                  4        V P                  P                  VR4       R#  LV5i)z-Disconnect from the voice channel in a guild.N)
r  r   rc   r  cancelr
  r#  r  r  r  )rL   rc  r)  listen_taskr(  tasks   &&    r   r  "DiscordAdapter.leave_voice_channel  s      ((,,Xt<MMO..228TB   $$Xt4"//##--/!!((,,Xt<KKM!!%%h5	 "s*   AD3DD%D8D9'D!/Dc               $    V ^8  d   QhRRRRRR/# )r   rc  rn   r  r   r   r/   r   )r   s   "r   r   r    s!     )" )"C )"S )"T )"r    c                  aa	"   V P                   P                  V4      pV'       d   VP                  4       '       g   R# V P                  P                  V4      pV'       d   VP	                  4         \
        P                  ! 4       pVP                  4       '       dr   \
        P                  ! 4       V,
          V P                  8  d'   \        P                  R4       VP                  4        M \        P                  ! R4      G Rj  xL
  K  \        P                  ! 4       o\        P                  ! 4       o	VV	3R lp\         P"                  ! V4      p\         P$                  ! VRR7      pVP'                  WvR7        \        P(                  ! SP+                  4       V P                  R	7      G Rj  xL
  V P/                  V4        V'       d   VP1                  4        R# R#  L L4  \        P,                   d4    \        P                  R
T P                  4       TP                  4         Lyi ; i  T'       d   TP1                  4        i i ; i5i)z2Play an audio file in the connected voice channel.Fz1Timed out waiting for previous playback to finishg?Nc                z   < V '       d   \         P                  R V 4       SP                  SP                  4       R# )zVoice playback error: %sN)rY   r   call_soon_threadsafer8   )r   doneloops   &r   _after4DiscordAdapter.play_in_voice_channel.<locals>._after  s'    LL!;UC))$((3r    r  )volume)ra  ri  z"Voice playback timed out after %dsT)r
  rz   r#  r  rh   r   r   
is_playingPLAYBACK_TIMEOUTrY   r   rc   r  sleepr  get_running_loopr   FFmpegPCMAudioPCMVolumeTransformerplayr  r  r  r$  rk   )
rL   rc  r  r(  r)  
wait_startr9  sourcer7  r8  s
   &&&     @@r   r  $DiscordAdapter.play_in_voice_channel  s      $$X.** ((,,X6NN	")J--//>>#j043H3HHNN#VWGGImmC(((==?D++-D4
 ++J7F11&EFGGFG)&&tyy{D<Q<QRRR %%h/! + ) S'' CTEZEZ[	 ! sy   9I$I#I4*H7 A*H7 	G(
A7H7 3G, 5G*6G, :H7 I(H7 *G, ,AH41H7 3H44H7 7IIc                    V ^8  d   QhRRRR/# )r   rc  rn   ro   r   r   )r   s   "r   r   r  $  s     
$ 
$S 
$3 
$r    c                  "   V P                   '       g   R# V P                   P                  V4      pV'       g   R# VP                  \        V4      4      pV'       d   VP                  '       g   R# VP                  P
                  # 5i)z;Return the voice channel the user is currently in, or None.N)r  	get_guild
get_memberrn   voicer   )rL   rc  ro   r]  r_  s   &&&  r   get_user_voice_channel%DiscordAdapter.get_user_voice_channel$  s]     |||&&x0!!#g,/V\\\||###s   7B5B0Bc                    V ^8  d   QhRRRR/# r-  r   )r   s   "r   r   r  0  s     
 
S 
T 
r    c                    V P                   P                  VR4      pV'       d   VP                  4        \        P                  ! V P                  V4      4      V P                   V&   R# )z+Reset the auto-disconnect inactivity timer.N)r  r   r/  r  r&  _voice_timeout_handler)rL   rc  r1  s   && r   r$  #DiscordAdapter._reset_voice_timeout0  sO    ((,,Xt<KKM.5.C.C''1/
!!(+r    c                    V ^8  d   QhRRRR/# r-  r   )r   s   "r   r   r  9  s      S T r    c                v  "    \         P                  ! V P                  4      G Rj  xL
  T P                  P                  T4      pT P                  T4      G Rj  xL
  T P                  '       d$   T'       d    T P                  \        T4      4       T'       dV   T P                  '       dB   T P                  P                  T4      pT'       d    TP                  R4      G Rj  xL
  R# R# R# R#  L  \         P                   d     R# i ; i L  \         d     Li ; i L=  \         d     R# i ; i5i)z:Auto-disconnect after VOICE_TIMEOUT seconds of inactivity.Nz(Left voice channel (inactivity timeout).)r  r>  VOICE_TIMEOUTCancelledErrorr  rz   r  r  r   r`   r  r  r  )rL   rc  r  chs   &&  r   rO  %DiscordAdapter._voice_timeout_handler9  s    	-- 2 2333 ..228<
&&x000$$$))#j/: $,,,))*5B''"LMMM  ': 4%% 		 	1
   N  s   D9#C6 C4C6 /D9DD92D9;D D9D9/"D9D' 'D%(D' ,D94C6 6DD9DD9D"D9!D""D9%D' 'D62D95D66D9c                    V ^8  d   QhRRRR/# )r   rc  rn   r   r/   r   )r   s   "r   r   r  O  s     4 4C 4D 4r    c                p    V P                   P                  V4      pVRJ;'       d    VP                  4       # )z?Check if the bot is connected to a voice channel in this guild.N)r
  rz   r#  )rL   rc  r(  s   && r   r  "DiscordAdapter.is_in_voice_channelO  s1      $$X.~33"//"33r    c                    V ^8  d   QhRRRR/# )r   rc  rn   r   r  r   )r   s   "r   r   r  T  s     2
 2
s 2
7O 2
r    c           
        V P                   P                  V4      pV'       d   VP                  4       '       g   R# VP                  pV'       g   R# . pV P                  '       d   V P                  P
                  MRpVP                   F]  pV'       d   VP                  VP                  8X  d   K(  VP                  RVP                  RVP                  RVP                  /4       K_  	  \        4       pV P                  P                  V4      pV'       d   ^ RIp	V	P                  4       p
VP                  ;_uu_ 4        VP                   P#                  4        FJ  w  rW,
          R8  g   K  VP$                  P                  V4      pV'       g   K9  VP'                  V4       KL  	  RRR4       V F  pVR,          V9   VR&   K  	  RVP(                  R\+        V4      R	VR
\+        V4      /#   + '       g   i     LN; i)zReturn voice channel awareness info for the given guild.

Returns None if the bot is not in a voice channel.  Otherwise
returns a dict with channel name, member list, count, and
currently-speaking user IDs (from SSRC mapping).
Nro   r^  is_botg       @is_speakingchannel_namemember_countr   speaking_count)r
  rz   r#  r   r  r   r   r   r   r^  rA  r8   r  r   r   rE   rH   rI  rB   addr   r   )rL   rc  r(  r   members_infobot_userr   speaking_user_idsr)  _timer   rU   last_tr   rZ   s   &&             r   get_voice_channel_info%DiscordAdapter.get_voice_channel_infoT  s      $$X.**** (,4<<$$$AADDHKK/144!%%!  ! "%((,,X6 //#C$,$>$>$D$D$FLD|c)&4488>3-11#6 %G   !D"&y/5F"FD ! GLLC-|c"34	
 	
  s    *G// G/G//G?	c                    V ^8  d   QhRRRR/# )r   rc  rn   r   r   r   )r   s   "r   r   r    s        #  #  r    c                
   V P                  V4      pV'       g   R# RVR,           RVR,           R2.pVR,           F3  pVR,          '       d   R	MRpVP                  R
VR,           V 24       K5  	  RP                  V4      # )zReturn a human-readable voice channel context string.

Suitable for injection into the system/ephemeral prompt so the
agent is always aware of voice channel state.
r%  z[Voice channel: #r^  u    — r_  z participant(s)]r   r]  z (speaking)z  - r^  
)rg  r   join)rL   rc  rZ   partsr   statuss   &&    r   get_voice_channel_context(DiscordAdapter.get_voice_channel_context  s     **84$T.%9$:%^@T?UUefgiA&'&6&6]BFLL4. 126(;< ! yyr    c                   V ^8  d   QhRR/# )r   rc  rn   r   )r   s   "r   r   r    s     J J Jr    c                X  "   V P                   P                  V4      pV'       g   R# \        P                  ! 4       p VP                  '       d   \
        P                  ! R4      G Rj  xL
  \        P                  ! 4       pWC,
          V P                  8  dX   Tp V P                  P                  V4      pV'       d2   VP                  4       '       d   VP                  P                  R4       VP                  4       pV FA  w  rxV P                  \        V4      4      '       g   K'  V P!                  WV4      G Rj  xL
  KC  	  EK  R#  L  \         d     Lki ; i L   \
        P"                   d     R# \         d$   p	\$        P'                  RT	RR7        Rp	?	R# Rp	?	ii ; i5i)z=Periodically check for completed utterances and process them.Ng?s   zVoice listen loop error: %sTr3  )r  rz   r   r   r>   r  r>  _KEEPALIVE_INTERVALr
  r#  rQ   send_packetr`   r   rN  r   _process_voice_inputrT  rY   r   )
rL   rc  r)  last_keepaliver   r(  r   ro   r   r   s
   &&        r   r'  !DiscordAdapter._voice_listen_loop  sV    ((,,X6)	J###mmC((( nn&'4+C+CC%(N!0044X>"//"3"3NN66G %224	)2%G00W>> 33HxPPP *3! $( %  Q%% 	 	JLL6DLII	Js   ;F*E" E" )E*2E" "E  E E 1AE" ?E  E" F*E" EE" EE" "F'7F*:F'F'F"F*"F''F*c               $    V ^8  d   QhRRRRRR/# )r   rc  rn   ro   r   rR   r   )r   s   "r   r   r    s"     " "3 " "PU "r    c                8  "   ^ RI Hp \        P                  ! RRRR7      pVP                  pVP                  4         \        P                  ! \        P                  W64      G Rj  xL
  ^ RI
HpHp V! 4       p	\        P                  ! WvV	R7      G Rj  xL
 p
V
P                  R	4      '       g     \        P                  ! V4       R# V
P                  R
R4      P#                  4       pV'       d   V! V4      '       d     \        P                  ! V4       R# \$        P'                  RW+R,          4       V P(                  '       d   V P)                  VVVR7      G Rj  xL
   \        P                  ! T4       R#  EL L  \          d     R# i ; i  \          d     R# i ; i LF  \*         d#   p\$        P-                  RTRR7        Rp?LlRp?ii ; i  \          d     R# i ; i   \        P                  ! T4       i   \          d     i i ; i; i5i)z&Convert PCM -> WAV -> STT -> callback.)is_whisper_hallucination.wav
vc_listen_F)r   prefixr   N)transcribe_audioget_stt_model_from_config)modelr  
transcriptr%  zVoice input from user %d: %s:Nd   N)rc  ro   r  z!Voice input processing failed: %sTr3  )tools.voice_moderz  r   r   r   r  r  	to_threadr4   r   tools.transcription_toolsr~  r  rz   r   r   r   r&   rY   rZ   r  r`   r   )rL   rc  ro   r   rz  tmp_fwav_pathr~  r  	stt_modelresultr  r   s   &&&&         r   ru  #DiscordAdapter._process_voice_input  s    =++6,W\]::	##M$<$<hQQQ]13I",,-=yYYF::i(( 		(#!  L"5;;=J!9*!E!E		(# KK6DAQR)))00%#) 1   		(#1 R Z*  7 
  	RNN>DNQQ	R
  		(# s  <H(F+ 'F (.F+ FF+ 4F 
H'F+ 4F+ F HAF+ "F)#F+ (G >H F+ F+ FHFHF&"H%F&&H)F+ +G6GG- GG- G*&H)G**H-H/HHHHHHHc                    V ^8  d   QhRRRR/# )r   ro   r   r   r/   r   )r   s   "r   r   r    s     1 1 1 1r    c                F    V P                   '       g   R# WP                   9   # )z*Check if user is in DISCORD_ALLOWED_USERS.T)r=   )rL   ro   s   &&r   rN  DiscordAdapter._is_allowed_user  s     %%%0000r    c               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r   r  r   
image_pathr  r  r  r"  r  r   r   r   )r   s   "r   r   r    sN     l ll l 	l
  l +l 
lr    c           	     *  <"    V P                  WV4      G Rj  xL
 #  L  \         d    \        RRT 2R7      u # \         dI   p\        P                  RT P                  TRR7       \        ST `!  YY4TR7      G Rj  xL 
 u Rp?# Rp?ii ; i5i)	z>Send a local image file natively as a Discord file attachment.NFzImage file not found: r  zA[%s] Failed to send local image, falling back to base adapter: %sTr3  r!  )	r  FileNotFoundErrorr   r`   rY   r   r   r  send_image_file)rL   r  r  r  r  r"  r   r  s   &&&&&& r   r  DiscordAdapter.send_image_file  s     	l33GQQQQ  	Ze5KJ<3XYY 	lLL\^b^g^gijuyLz0gbj0kkkk	lY   B! ! B! BBBB8B BBBBBBc               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r   r  r   	image_urlr  r  r  r"  r  r   r   r   )r   s   "r   r   r    sN     ?S ?S?S ?S 	?S
  ?S +?S 
?Sr    c           
       <"   V P                   '       g   \        RRR7      #  ^ RIpV P                   P                  \	        V4      4      pV'       g-   V P                   P                  \	        V4      4      G Rj  xL
 pV'       g   \        RRV R2R7      # VP                  4       ;_uu_4       GRj  xL
 pVP                  W&P                  ^R7      R7      ;_uu_4       GRj  xL
 p	V	P                  ^8w  d   \        R	V	P                   24      hV	P                  4       G Rj  xL
 p
V	P                  P                  R
R4      pRpRV9   g   RV9   d   RpMRV9   d   RpM	RV9   d   Rp^ RIp\        P                  ! VP!                  V
4      RV 2R7      pTP#                  V'       d   TMRVR7      G Rj  xL
 p\        R\%        VP&                  4      R7      uuRRR4      GRj  xL
  uuRRR4      GRj  xL
  #  EL ELU EL% L LS L$ L  + GRj  xL 
 '       g   i     M; iRRR4      GRj  xL 
  R#   + GRj  xL 
 '       g   i     R# ; i  \(         d?    \*        P-                  RT P.                  RR7       \0        ST `e  YY44      G Rj  xL 
 u # \         dG   p\*        P5                  RT P.                  TRR7       \0        ST `e  YY44      G Rj  xL 
 u Rp?# Rp?ii ; i5i)z4Send an image natively as a Discord file attachment.Fr  r  Nr  r  totalri  zFailed to download image: HTTP zcontent-typez	image/pngpngjpegjpggifwebpzimage.r  r  Tr  zI[%s] aiohttp not installed, falling back to URL. Run: pip install aiohttpr3  z=[%s] Failed to send image attachment, falling back to URL: %s)r  r   aiohttpr  rn   r  ClientSessionrz   ClientTimeoutrn  r`   r  headersr
  r   r  r  r  r   r   ImportErrorrY   r   r   r  
send_imager   )rL   r  r  r  r  r"  r  r   sessionresp
image_datar  extr
  r  r|   r   r  s   &&&&&&           r   r  DiscordAdapter.send_image  sr     |||e?CC3	Sll..s7|<G $ : :3w< HH!%'*7UVV ,,...'";;y:O:OVX:O:Y;ZZZ^b{{c)'*I$++(WXX'+yy{!2J $(<<#3#3NK#PLC-,1F#,.#</$"<<

:(>6RUQVXD '+2! !- ! C &ds366{K/ [ZZ /.. I /Z "3' [ /ZZZZ /.....4  	SNN[		  
 +GRRRR 	SLLO			   +GRRRR	Sso   K?AI$ ;H<I$ I$ K?I$ 5H
6I$ 9/I(H)I,=H	)H*BH	1H	>H?#H	"I/H
0I4I$ HI$ K?I$ 
I$ IH	H	II$ H1H!
H1*H1,I4I$ ?I I$ K?I!	I
I!	I!	I$ K?!I$ $A K<$J'%K<*K?,K<5K<66K7,K/-K71K<2K?7K<<K?c               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r   r  r   
video_pathr  r  r  r"  r  r   r   r   )r   s   "r   r   r  =  sN     g gg g 	g
  g +g 
gr    c           	     *  <"    V P                  WV4      G Rj  xL
 #  L  \         d    \        RRT 2R7      u # \         dI   p\        P                  RT P                  TRR7       \        ST `!  YY4TR7      G Rj  xL 
 u Rp?# Rp?ii ; i5i)	z9Send a local video file natively as a Discord attachment.NFzVideo file not found: r  zA[%s] Failed to send local video, falling back to base adapter: %sTr3  r!  )	r  r  r   r`   rY   r   r   r  
send_video)rL   r  r  r  r  r"  r   r  s   &&&&&& r   r  DiscordAdapter.send_video=  s     	g33GQQQQ  	Ze5KJ<3XYY 	gLL\^b^g^gijuyLz+G]e+ffff	gr  c               4    V ^8  d   QhRRRRRRRRRRRR	R
R/# )r   r  r   r  r  r  r  r  r"  r  r   r   r   )r   s   "r   r   r  N  sY     t tt t 	t
 !t  t +t 
tr    c           
     ,  <"    V P                  WW4R7      G Rj  xL
 #  L  \         d    \        RRT 2R7      u # \         dI   p\        P                  RT P                  TRR7       \        ST `!  YY4YVR	7      G Rj  xL 
 u Rp?# Rp?ii ; i5i)
z8Send an arbitrary file natively as a Discord attachment.)r  NFzFile not found: r  z>[%s] Failed to send document, falling back to base adapter: %sTr3  r!  )	r  r  r   r`   rY   r   r   r  send_document)	rL   r  r  r  r  r  r"  r   r  s	   &&&&&&& r   r  DiscordAdapter.send_documentN  s     	t33G3eeee  	Se5Ei[3QRR 	tLLY[_[d[dfgrvLw.w7W_.ssss	tsY   B"  " B" BBBB	8BBBBBBBc                    V ^8  d   QhRRRR/# r   r  r   r   r  r   )r   s   "r   r   r  `  s      J  J  J  Jr    c                   a a"   S P                   '       g   R# SS P                  9   d   R# R VV 3R llp\        P                  ! V! 4       4      S P                  S&   R# 5i)a8  Start a persistent typing indicator for a channel.

Discord's TYPING_START gateway event is unreliable in DMs for bots.
Instead, start a background loop that hits the typing endpoint every
8 seconds (typing indicator lasts ~10s).  The loop is cancelled when
stop_typing() is called (after the response is sent).
Nc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   0DiscordAdapter.send_typing.<locals>.__annotate__n  s     	 	D 	r    c                   <"      \         P                  P                  RRSR7      p SP                  P                  P	                  V 4      G Rj  xL
  \
        P                  ! ^4      G Rj  xL
  Kq   L$  \
        P                   d     R# \         d#   p\        P                  RST4        Rp?R# Rp?ii ; i LO  \
        P                   d     R# i ; i5i)Tr  z/channels/{channel_id}/typingr  Nz*Discord typing indicator failed for %s: %s)r   r  r  r  r  r  rT  r`   rY   r   r>  )router   r  rL   s     r   _typing_loop0DiscordAdapter.send_typing.<locals>._typing_loopn  s     
 ' 2 2"$C'. !3 ! #ll//77>>> "--*** ?"11 $ %QSZ\]^ +)) s   CC A
A8 A6A8 C /B?0C 6A8 8B<C CB<B<B71C 5C7B<<C CCCC)r  r  r  r  )rL   r  r"  r  s   ff& r   send_typingDiscordAdapter.send_typing`  sN      |||d(((	 	$ '.&9&9,.&I7#s   AA c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r    s        r    c                   "   V P                   P                  VR4      pV'       d   VP                  4         VG Rj  xL
  R# R#  L  \        P                  \
        3 d     R# i ; i5i)z3Stop the persistent typing indicator for a channel.N)r  r   r/  r  rT  r`   )rL   r  r1  s   && r   stop_typingDiscordAdapter.stop_typing  s[     !!%%gt4KKM

  **I6 s8   5A*A AA A*A A'#A*&A''A*c                    V ^8  d   QhRRRR/# )r   r  r   r   Dict[str, Any]r   )r   s   "r   r   r    s     %I %I3 %I> %Ir    c                  "   V P                   '       g   RRRR/#  V P                   P                  \        V4      4      pV'       g-   V P                   P                  \        V4      4      G Rj  xL
 pV'       g   R\	        V4      RR/# \        V\        P                  4      '       d7   RpVP                  '       d   VP                  P                  M
\	        V4      pM\        V\        P                  4      '       d   RpVP                  pMw\        V\        P                  4      '       d@   RpRVP                   2pVP                  '       d   VP                  P                   R	V 2pMRp\        VR\	        V4      4      pRTRTR
\        VR4      '       d2   VP                  '       d    \	        VP                  P                  4      MRR\        VR4      '       d*   VP                  '       d   VP                  P                  /# R/#  EL  \          dH   p\"        P%                  RT P                  YRR7       R\	        T4      RRR\	        T4      /u Rp?# Rp?ii ; i5i)z(Get information about a Discord channel.r   UnknownrK  dmNthreadr   # / rc  r]  
guild_namez'[%s] Failed to get chat info for %s: %sTr3  r   )r  r  rn   r  r   rx   r   rQ  	recipientr   ThreadTextChannelr]  rO  r   r   r`   rY   r   )rL   r  r   	chat_typer   r   s   &&    r   get_chat_infoDiscordAdapter.get_chat_info  s    |||Ivt44 	Ill..s7|<G $ : :3w< HHGfd;; '7#4#455 	181B1B1Bw((--GGW^^44$	||GW%8%899%	7<<.)===%mm001TF;D%	wG= 	WWg5N5NSZS`S`S`C 0 01fjGGW4M4MRYR_R_R_gmm00	  fj	 + I6  	ILLBDIIwdhLiCL&$QHH	Is   I'AH /H0H <H 
I'3H ?BH AH 0AH 5H I'H I'H I$<II$I'I$$I'c                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r    s     >g >g$ >gr    c                n  "   V P                   '       d   V P                  '       g   R# \        4       p\        4       pV P                    FK  pVP                  4       '       d   VP	                  V4       K,  VP	                  VP                  4       4       KM  	  V'       g   R# \        RV P                   R\        V4       RRP                  V4       24       ^ pV P                  P                   EFk  p VP                  p\        V4      VP                  8  d#   VP                  RR7       Uu. u Rj  xL
 qwNK  T EF  p	T	P                  P                  4       p
T	P"                  P                  4       pT	P$                  ;'       g    RP                  4       pY9   ;'       g    Y9   ;'       g    Y9   pT'       g   K  \'        T	P(                  4      pTP	                  T4       T^,          pY9   d   T
M	Y9   d   TMTpTP+                  T4       \        RT P                   R	T R
T RT	P                   RT	P,                   R24       EK  	  T'       d   EKl   M	  V'       d+   \        RV P                   RRP                  V4       24       Wn         RP                  \/        V4      4      \0        P2                  R&   V'       d   \        RV P                   RV R24       R# R#  ELEDMu upi ppEL  \         d.   p\        P!                  RTP                  T4        Rp?EK=  Rp?ii ; i5i)a  
Resolve non-numeric entries in DISCORD_ALLOWED_USERS to Discord user IDs.

Users can specify usernames (e.g. "teknium") or display names instead of
raw numeric IDs.  After resolution, the env var and internal set are updated
so authorization checks work with IDs only.
N[z] Resolving z username(s): , )limitz(Failed to fetch members for guild %s: %sr%  z] Resolved 'z' -> z (r  r$  z] Could not resolve usernames: r)  r(  z%] Updated DISCORD_ALLOWED_USERS with z resolved ID(s))r=   r  r8   r+  ra  r+   printr   r   rl  guildsr   r_  fetch_membersr`   rY   r   r^  global_namer   r   discarddiscriminatorsortedr   environ)rL   numeric_ids
to_resolver   resolved_countr]  r   r   r   r_  
name_lowerdisplay_lowerglobal_lowermatchedr   matched_names   &               r   r5  )DiscordAdapter._resolve_allowed_usernames  s     %%%T\\\eU
++E}}&u{{}-	 , $))LZ(9		R\H]G^_`\\((E--w<%"4"44050C0C$0C0OPP1q
 "#[[..0
 & 3 3 9 9 ; & 2 2 8 8b??A$2oom6QooUaUo7fii.COOC("a'N1;1I:)6)D, ! &&|4Adii[\N%uBv{{m[\]c]q]q\rrstu "  :7 ): Adii[ ?		*@U?VWX "-.1hhvk7J.K

*+Adii[ EnEUUdef = QPGP I5::WXYs   $L5A8L5 AL55:K:/K11K.5K+6K.9K1=AL5
L5&L52
L5BL5L5(BL5+K..K10K::L2!L-&L5-L22L5c                    V ^8  d   QhRRRR/# )r   r  r   r   r   )r   s   "r   r   r    s      c c r    c                    V# )zE
Format message for Discord.

Discord uses its own markdown variant.
r   )rL   r  s   &&r   r  DiscordAdapter.format_message  s	     r    c               (    V ^8  d   QhRRRRRRRR/# )	r   interactiondiscord.Interactioncommand_textr   followup_msgz
str | Noner   r  r   )r   s   "r   r   r    s8     F F(F F !	F
 
Fr    c                  "   VP                   P                  RR7      G Rj  xL
  V P                  W4      pV P                  V4      G Rj  xL
   V'       d   VP	                  VR7      G Rj  xL
  R# VP                  4       G Rj  xL
  R#  Lm LE L$ L  \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i5i)aE  Common handler for simple slash commands that dispatch a command string.

Defers the interaction (shows "thinking..."), dispatches the command,
then cleans up the deferred response.  If *followup_msg* is provided
the "thinking..." indicator is replaced with that text; otherwise it
is deleted so the channel isn't cluttered.
T	ephemeralNr  z&Discord interaction cleanup failed: %s)	responsedefer_build_slash_eventhandle_messageedit_original_responsedelete_original_responser`   rY   r   )rL   r  r  r  r  r   s   &&&&  r   _run_simple_slash DiscordAdapter._run_simple_slash  s      ""((4(888''B!!%(((	F!888NNN!::<<< 	9( O< 	FLLA1EE	Fs    CB)CBCB B /B0B 4C6B 	B
B CCB B C#B?9C?CCc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r    s     u_ u_$ u_r    c                  a  S P                   '       g   R# S P                   P                  pVP                  RRR7      R V 3R ll4       pVP                  RRR7      R	 V 3R
 ll4       pVP                  RRR7      \        P                  P                  RR7      RR V 3R lll4       4       pVP                  RRR7      \        P                  P                  RR7      RR V 3R lll4       4       pVP                  RRR7      \        P                  P                  RR7      RR V 3R lll4       4       pVP                  RRR7      R V 3R ll4       pVP                  R R!R7      R" V 3R# ll4       pVP                  R$R%R7      R& V 3R' ll4       p	VP                  R(R)R7      R* V 3R+ ll4       p
VP                  R,R-R7      R. V 3R/ ll4       pVP                  R0R1R7      R2 V 3R3 ll4       pVP                  R4R5R7      \        P                  P                  R6R7      RR7 V 3R8 lll4       4       pVP                  R9R:R7      \        P                  P                  R;R7      RR< V 3R= lll4       4       pVP                  R>R?R7      R@ V 3RA ll4       pVP                  RBRCR7      RD V 3RE ll4       pVP                  RFRGR7      RH V 3RI ll4       pVP                  RJRKR7      \        P                  P                  RLRM7      RRN V 3RO lll4       4       pVP                  RPRQR7      RR V 3RS ll4       pVP                  RTRUR7      \        P                  P                  RVRW7      \        P                  P                  \        P                  P                  RXRYRZ7      \        P                  P                  R[R\RZ7      \        P                  P                  R]R^RZ7      \        P                  P                  R_R`RZ7      \        P                  P                  RaRbRZ7      \        P                  P                  RcR$RZ7      .RW7      RRd V 3Re lll4       4       4       pVP                  RfRgR7      Rh V 3Ri ll4       pVP                  RjRkR7      \        P                  P                  RlRm7      RRn V 3Ro lll4       4       pVP                  RpRqR7      \        P                  P                  RrRm7      RRs V 3Rt lll4       4       pVP                  RuRvR7      \        P                  P                  RwRxRyRz7      RR{ V 3R| lll4       4       pVP                  R}R~R7      \        P                  P                  RR7      R V 3R ll4       4       pVP                  RRR7      \        P                  P                  RR7      R V 3R ll4       4       pVP                  RRR7      \        P                  P                  RR7      R V 3R ll4       4       p^dp ^ RIH	p VP                  4        Uu0 uF  pVP                  kK  	  pp\        ^ V\        V4      ,
          4      p V! V VR7      w  p!p"V! F  w  p#p$p%R V 3R llp&V&! V%4      p'RV#P                  RR4       2V'n        \        P                  P!                  V#V$V'R7      p\        P                  P                  RR7      ! V4       VP#                  V4       K  	  V"'       d%   \$        P'                  RS P                  VV"4       R# R# u upi   \(         d-   p(\$        P'                  RS P                  T(4        Rp(?(R# Rp(?(ii ; i)z4Register Discord slash commands on the command tree.NnewzStart a new conversation)r   descriptionc                   V ^8  d   QhRR/# r   r  r  r   )r   s   "r   r   =DiscordAdapter._register_slash_commands.<locals>.__annotate__  s     	] 	])< 	]r    c                J   <"   SP                  V R R4      G Rj  xL
  R#  L5i)/resetzNew conversation started~Nr  r  rL   s   &r   	slash_new:DiscordAdapter._register_slash_commands.<locals>.slash_new  s     ((h@[\\\   #!#resetzReset your Hermes sessionc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r      s     	R 	R+> 	Rr    c                J   <"   SP                  V R R4      G Rj  xL
  R#  L5i)r  zSession reset~Nr  r  s   &r   slash_reset<DiscordAdapter._register_slash_commands.<locals>.slash_reset  s     ((h@PQQQr  r  zShow or change the modelzHModel name (e.g. anthropic/claude-sonnet-4). Leave empty to see current.)r   c                    V ^8  d   QhRRRR/# r   r  r  r   r   r   )r   s   "r   r   r   %       	P 	P+> 	Pc 	Pr    c                j   <"   SP                  V R V 2P                  4       4      G Rj  xL
  R#  L5i)z/model Nr  r&   r  r   rL   s   &&r   slash_model<DiscordAdapter._register_slash_commands.<locals>.slash_model#  .      ((v6F6L6L6NOOO   '313	reasoningzShow or change reasoning effortz=Reasoning effort: xhigh, high, medium, low, minimal, or none.)effortc                    V ^8  d   QhRRRR/# )r   r  r  r  r   r   )r   s   "r   r   r   *  s     	V 	V/B 	VC 	Vr    c                j   <"   SP                  V R V 2P                  4       4      G Rj  xL
  R#  L5i)z/reasoning Nr  )r  r  rL   s   &&r   slash_reasoning@DiscordAdapter._register_slash_commands.<locals>.slash_reasoning(  s.      ((F86L6R6R6TUUUr  personalityzSet a personalityz0Personality name. Leave empty to list available.c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r   /  s     	V 	V1D 	VC 	Vr    c                j   <"   SP                  V R V 2P                  4       4      G Rj  xL
  R#  L5i)z/personality Nr  r  s   &&r   slash_personalityBDiscordAdapter._register_slash_commands.<locals>.slash_personality-  s.      ((dV6L6R6R6TUUUr  retryzRetry your last messagec                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   3  s     	M 	M+> 	Mr    c                J   <"   SP                  V R R4      G Rj  xL
  R#  L5i)z/retryz	Retrying~Nr  r  s   &r   slash_retry<DiscordAdapter._register_slash_commands.<locals>.slash_retry2  s     ((hLLLr  undozRemove the last exchangec                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   7       	? 	?*= 	?r    c                H   <"   SP                  V R 4      G Rj  xL
  R#  L5i)z/undoNr  r  s   &r   
slash_undo;DiscordAdapter._register_slash_commands.<locals>.slash_undo6       ((g>>>   " "rn  zShow Hermes session statusc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   ;  s     	Q 	Q,? 	Qr    c                J   <"   SP                  V R R4      G Rj  xL
  R#  L5i)z/statuszStatus sent~Nr  r  s   &r   slash_status=DiscordAdapter._register_slash_commands.<locals>.slash_status:  s     ((iPPPr  sethomez!Set this chat as the home channelc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   ?  s     	B 	B-@ 	Br    c                H   <"   SP                  V R 4      G Rj  xL
  R#  L5i)z/sethomeNr  r  s   &r   slash_sethome>DiscordAdapter._register_slash_commands.<locals>.slash_sethome>  s     ((jAAAr.  rc   zStop the running Hermes agentc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   C  s     	R 	R*= 	Rr    c                J   <"   SP                  V R R4      G Rj  xL
  R#  L5i)z/stopzStop requested~Nr  r  s   &r   
slash_stop;DiscordAdapter._register_slash_commands.<locals>.slash_stopB  s     ((g?PQQQr  compresszCompress conversation contextc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   G       	C 	C.A 	Cr    c                H   <"   SP                  V R 4      G Rj  xL
  R#  L5i)z	/compressNr  r  s   &r   slash_compress?DiscordAdapter._register_slash_commands.<locals>.slash_compressF       ((kBBBr.  titlezSet or show the session titlez+Session title. Leave empty to show current.c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r   L  r  r    c                j   <"   SP                  V R V 2P                  4       4      G Rj  xL
  R#  L5i)z/title Nr  r  s   &&r   slash_title<DiscordAdapter._register_slash_commands.<locals>.slash_titleJ  r  r  rk   z!Resume a previously-named sessionz5Session name to resume. Leave empty to list sessions.c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r   Q  s     	Q 	Q,? 	Qs 	Qr    c                j   <"   SP                  V R V 2P                  4       4      G Rj  xL
  R#  L5i)z/resume Nr  r  s   &&r   slash_resume=DiscordAdapter._register_slash_commands.<locals>.slash_resumeO  s.      ((6G6M6M6OPPPr  usagez!Show token usage for this sessionc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   U  s     	@ 	@+> 	@r    c                H   <"   SP                  V R 4      G Rj  xL
  R#  L5i)z/usageNr  r  s   &r   slash_usage<DiscordAdapter._register_slash_commands.<locals>.slash_usageT  s     ((h???r.  providerzShow available providersc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   Y  r>  r    c                H   <"   SP                  V R 4      G Rj  xL
  R#  L5i)z	/providerNr  r  s   &r   slash_provider?DiscordAdapter._register_slash_commands.<locals>.slash_providerX  rB  r.  helpzShow available commandsc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   ]  r)  r    c                H   <"   SP                  V R 4      G Rj  xL
  R#  L5i)z/helpNr  r  s   &r   
slash_help;DiscordAdapter._register_slash_commands.<locals>.slash_help\  r-  r.  insightsz!Show usage insights and analyticsz&Number of days to analyze (default: 7))daysc                    V ^8  d   QhRRRR/# )r   r  r  r\  rn   r   )r   s   "r   r   r   b  s     	K 	K.A 	K 	Kr    c                N   <"   SP                  V R V 24      G Rj  xL
  R#  L5i)z
/insights Nr  )r  r\  rL   s   &&r   slash_insights?DiscordAdapter._register_slash_commands.<locals>.slash_insights`  s%      ((
4&6IJJJ   %#%z
reload-mcpzReload MCP servers from configc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   f  s     	E 	E0C 	Er    c                H   <"   SP                  V R 4      G Rj  xL
  R#  L5i)z/reload-mcpNr  r  s   &r   slash_reload_mcpADiscordAdapter._register_slash_commands.<locals>.slash_reload_mcpe  s     ((mDDDr.  rJ  zToggle voice reply modez3Voice mode: on, off, tts, channel, leave, or status)modeu#   channel — join your voice channelr   )r   r  u   leave — leave voice channelleaveu$   on — voice reply to voice messagesonu#   tts — voice reply to all messagesttsu   off — text onlyoffu   status — show current modec                    V ^8  d   QhRRRR/# )r   r  r  rf  r   r   )r   s   "r   r   r   s  r  r    c                j   <"   SP                  V R V 2P                  4       4      G Rj  xL
  R#  L5i)z/voice Nr  )r  rf  rL   s   &&r   slash_voice<DiscordAdapter._register_slash_commands.<locals>.slash_voicei  s.      ((v6F6L6L6NOOOr  updatez)Update Hermes Agent to the latest versionc                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r   w  s     	V 	V,? 	Vr    c                J   <"   SP                  V R R4      G Rj  xL
  R#  L5i)z/updatezUpdate initiated~Nr  r  s   &r   slash_update=DiscordAdapter._register_slash_commands.<locals>.slash_updatev  s     ((iATUUUr  approvez#Approve a pending dangerous commandzAOptional: 'all', 'session', 'always', 'all session', 'all always')scopec                    V ^8  d   QhRRRR/# r   r  r  ru  r   r   )r   s   "r   r   r   |  s     	S 	S-@ 	S 	Sr    c                j   <"   SP                  V R V 2P                  4       4      G Rj  xL
  R#  L5i)z	/approve Nr  r  ru  rL   s   &&r   slash_approve>DiscordAdapter._register_slash_commands.<locals>.slash_approvez  s.      ((	%6I6O6O6QRRRr  denyz Deny a pending dangerous commandz,Optional: 'all' to deny all pending commandsc                    V ^8  d   QhRRRR/# rw  r   )r   s   "r   r   r     s     	P 	P*= 	Pc 	Pr    c                j   <"   SP                  V R V 2P                  4       4      G Rj  xL
  R#  L5i)z/deny Nr  ry  s   &&r   
slash_deny;DiscordAdapter._register_slash_commands.<locals>.slash_deny  s.      ((ug6F6L6L6NOOOr  r  z4Create a new thread and start a Hermes session in itzThread namez6Optional first message to send to Hermes in the threadz/Auto-archive in minutes (60, 1440, 4320, 10080)r   r=  auto_archive_durationc               (    V ^8  d   QhRRRRRRRR/# )r   r  r  r   r   r=  r  rn   r   )r   s   "r   r   r     s6     	f 	f,	f	f 	f $'		fr    c                   <"   V P                   P                  R R7      G Rj  xL
  SP                  WW#4      G Rj  xL
  R#  L  L5i)Tr  N)r  r  _handle_thread_create_slash)r  r   r=  r  rL   s   &&&&r   slash_thread=DiscordAdapter._register_slash_commands.<locals>.slash_thread  sC      &&,,t,<<<22;geee =es    AAAAAAqueuez4Queue a prompt for the next turn (doesn't interrupt)zThe prompt to queue)promptc                    V ^8  d   QhRRRR/# r   r  r  r  r   r   )r   s   "r   r   r     s     	g 	g+> 	g 	gr    c                P   <"   SP                  V R V 2R4      G Rj  xL
  R#  L5i)z/queue zQueued for the next turn.Nr  r  r  rL   s   &&r   slash_queue<DiscordAdapter._register_slash_commands.<locals>.slash_queue  s(      ((x6HJefff   &$&
backgroundzRun a prompt in the backgroundz#The prompt to run in the backgroundc                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r     s     	k 	k0C 	kS 	kr    c                P   <"   SP                  V R V 2R4      G Rj  xL
  R#  L5i)z/background zBackground task started~Nr  r  s   &&r   slash_backgroundADiscordAdapter._register_slash_commands.<locals>.slash_background  s(      ((VH6MOijjjr  btwz-Ephemeral side question using session contextz,Your side question (no tools, not persisted))questionc                    V ^8  d   QhRRRR/# )r   r  r  r  r   r   )r   s   "r   r   r     s     	J 	J)< 	J 	Jr    c                N   <"   SP                  V R V 24      G Rj  xL
  R#  L5i)z/btw Nr  )r  r  rL   s   &&r   	slash_btw:DiscordAdapter._register_slash_commands.<locals>.slash_btw  s%      ((hZ6HIIIra  )discord_skill_commands)	max_slotsreserved_namesc                   V ^8  d   QhRR/# )r   _keyr   r   )r   s   "r   r   r     s     ( (c (r    c                "   <a  RR V V3R lllpV# )r%  c                    V ^8  d   QhRRRR/# )r   r  r  argsr   r   )r   s   "r   r   ZDiscordAdapter._register_slash_commands.<locals>._make_skill_handler.<locals>.__annotate__  s      \ \8K \SV \r    c                n   <"   SP                  V S R V 2P                  4       4      G Rj  xL
  R#  L5i) Nr  )r  r  r  rL   s   &&r   _skill_slashZDiscordAdapter._register_slash_commands.<locals>._make_skill_handler.<locals>._skill_slash  s0     "44[TF!D6BRBXBXBZ[[[s   )535r%  r   )r  r  rL   s   f r   _make_skill_handlerDDiscordAdapter._register_slash_commands.<locals>._make_skill_handler  s    \ \''r    skill_-r   )r   r  callbackz Optional arguments for the skill)r  zI[%s] Discord slash command limit reached (%d): %d skill(s) not registeredz0[%s] Failed to register skill slash commands: %sr  )   r%    )r  r6  commandr   app_commandsdescribechoicesChoicehermes_cli.commandsr  get_commandsr   r  r   replacer   Commandadd_commandrY   r   r`   ))rL   r6  r  r  r  r  r   r%  r+  r1  r6  r:  r@  rF  rJ  rO  rT  rY  r_  rd  rm  rr  rz  r  r  r  r  r  _DISCORD_CMD_LIMITr  cmdexisting_namesremaining_slotsskill_entriesskippeddiscord_namer  cmd_keyr  handlerexcs)   f                                        r   r  'DiscordAdapter._register_slash_commands  s   |||||  	5.H	I	] 
J	] 
70K	L	R 
M	R 
70J	K				&	&,v	&	w	P 	P 
x 
L	P 
;4U	V				&	&.m	&	n	V 	V 
o 
W	V 
=6I	J				&	&,^	&	_	V 	V 
` 
K	V 
70I	J	M 
K	M 
6/I	J	? 
K	? 
81M	N	Q 
O	Q 
92U	V	B 
W	B 
6/N	O	R 
P	R 
:3R	S	C 
T	C 
70O	P				&	&,Y	&	Z	P 	P 
[ 
Q	P 
81T	U				&	&,c	&	d	Q 	Q 
e 
V	Q 
70S	T	@ 
U	@ 
:3M	N	C 
O	C 
6/H	I	? 
J	? 
:3V	W				&	&,T	&	U	K 	K 
V 
X	K 
<5U	V	E 
W	E 
70I	J				&	&,a	&	b				%	%  ''-RZc'd  ''-LT['\  ''-S[_'`  ''-RZ_'`  ''-@'N  ''-KS['\,
	% 
	P 	P
 
c 
K	P 
81\	]	V 
^	V 
92W	X				&	&-p	&	q	S 	S 
r 
Y	S 
6/Q	R				&	&-[	&	\	P 	P 
] 
S	P 
81g	h				&	&L"S 
' 


	f 	f

 
i	f 
70f	g				&	&.C	&	D	g 
E 
h	g 
<5U	V				&	&.S	&	T	k 
U 
W	k 
5.]	^				&	&0^	&	_	J 
` 
_	J !#	_B262C2C2EF2E3chh2ENF!!%7#n:M%MNO%;)-&"M7
 7D2k7( (
 .g6%+L,@,@c,J+K#L **22% +$ 3 
 $$--3U-VWZ[  %! 7D$ _II17 5 G>  	_NNMtyyZ]^^	_s1   \1 -\,C\1 "\1 ,\1 1](<!]##](c               $    V ^8  d   QhRRRRRR/# )r   r  r  textr   r   r   r   )r   s   "r   r   r    s#     '
 '
.A '
 '
Q] '
r    c           
        \        VP                  \        P                  4      p\        VP                  \        P                  4      pRpV'       d   RpM"V'       d   Rp\        VP                  4      pMRpRpV'       g   \        VP                  R4      '       dt   VP                  P                  p\        VP                  R4      '       dB   VP                  P                  '       d&   VP                  P                  P                   RV 2p\        VP                  R	R4      pV P                  \        VP                  4      VV\        VP                  P                  4      VP                  P                  VVR
7      p	VP                  R4      '       d   \         P"                  M\         P$                  p
\'        VV
V	VR7      # )z>Build a MessageEvent from a Discord slash command interaction.Nr  r  groupr%  r   r]   / #topicr  	chat_namer  ro   	user_name	thread_id
chat_topic/r  message_typerD  r  )rx   r   r   rQ  r  r   r  r   r   r]  rO  build_sourcer   r   r^  r'   r   COMMANDTEXTr   )rL   r  r  is_dm	is_threadr  r  r  r  rD  msg_types   &&&        r   r  !DiscordAdapter._build_slash_event  sp   ;..0A0AB{22GNNC		I IK223II	!4!4f==#++00I{**G449L9L9R9R9R*2288==>d9+N	 [00'4@
""../((++,!&&33! # 
 +///#*>*>;&&KDTDT!#	
 	
r    r%  r  c          
     ,    V ^8  d   QhRRRRRRRRRR	/# )
r   r  r  r   r   r=  r  rn   r   r  r   )r   s   "r   r   r    sC     "^ "^("^ "^ 	"^
  #"^ 
"^r    c                  "   V P                  VVVVR7      G Rj  xL
 pVP                  R4      '       g=   VP                  RR4      pVP                  P                  RV 2RR7      G Rj  xL
  R# VP                  R	4      pVP                  R
4      ;'       g    TpV'       d   RV R2MRV R2p	VP                  P                  RV	 2RR7      G Rj  xL
  V'       d   V P	                  V4       T;'       g    RP                  4       p
V
'       d'   V'       d   V P                  WW4      G Rj  xL
  R# R# R#  EL# L Ll L5i)zGCreate a Discord thread from a slash command and start a session in it.r  Nr  r   zunknown errorzFailed to create thread: Tr  r  thread_namez<#r#   **zCreated thread r%  )_create_threadrz   followupr  _track_threadr&   _dispatch_thread_session)rL   r  r   r=  r  r  r   r  r  linkstarters   &&&&&      r   r  *DiscordAdapter._handle_thread_create_slash  sB     **"7	 + 
 
 zz)$$JJw8E&&++.Gw,O[_+```JJ{+	jj/774 %.I;a R}B3G""''/$(@D'QQQ y) ==b'')y//]]] !73
 a 	R ^sn   ED>E5E,E-.E
E'/EEE#E=EEE3E4EEEEc          
     ,    V ^8  d   QhRRRRRRRRRR/# )	r   r  r  r  r   r  r  r   r  r   )r   s   "r   r   r    s<     ) )() ) 	)
 ) 
)r    c           	       "   Rp\        VR4      '       d)   VP                  '       d   VP                  P                  pV'       d   V RV 2MTpV P                  VVR\	        VP
                  P                  4      VP
                  P                  VR7      p\        V\        P                  VVR7      pV P                  V4      G Rj  xL
  R#  L5i)zMBuild a MessageEvent pointing at a thread and send it through handle_message.r%  r]  r  r  )r  r  r  ro   r  r  r  N)r   r]  r   r  r   r   r   r^  r   r   r  r  )	rL   r  r  r  r  r  r  rD  r  s	   &&&&&    r   r  'DiscordAdapter._dispatch_thread_session  s      
;(([->->->$**//J7Azl#k]3{	""((++,!&&33 # 
 $))#	
 !!%(((s   &CCBC	C
Cc                    V ^8  d   QhRRRR/# )r   r   r	   r   r   )r   s   "r   r   r  ;  s     ; ;c ;c ;r    c                0    \        VRR4      ;'       g    T# )z:Return the parent text channel when invoked from a thread.parentN)rO  )rL   r   s   &&r   _thread_parent_channel%DiscordAdapter._thread_parent_channel;  s    w$/::7:r    c                    V ^8  d   QhRRRR/# )r   r  r  r   Optional[Any]r   )r   s   "r   r   r  ?  s      >Q Vc r    c                T  "   \        VRR4      pVe   V# V P                  '       g   R# \        VRR4      pVf   R# V P                  P                  \        V4      4      pVe   V#  V P                  P	                  \        V4      4      G Rj  xL
 #  L  \
         d     R# i ; i5i)zFReturn the interaction channel, fetching it if the payload is partial.r   Nr  )rO  r  r  rn   r  r`   )rL   r  r   r  s   &&  r   _resolve_interaction_channel+DiscordAdapter._resolve_interaction_channel?  s     +y$7N|||[,=
,,**3z?;N	33C
ODDDD 		s@   %B(?B(('B BB B(B B%!B($B%%B(r=  r  c          
     ,    V ^8  d   QhRRRRRRRRRR	/# )
r   r  r  r   r   r=  r  rn   r   r  r   )r   s   "r   r   r  Q  sC     E E(E 	E
 E  #E 
Er    c               \  "   T;'       g    RP                  4       pV'       g   RR/# V\        9  d.   RP                  R \        \        4       4       4      pRRV R2/# V P	                  V4      G Rj  xL
 pVf   RR	/# \        V\        P                  4      '       d   RR
/# V P                  V4      pVf   RR/# \        \        VRR4      RR4      ;'       g    RpRV R2p	T;'       g    RP                  4       p
 VP                  VVV	R7      G Rj  xL
 pV
'       d   VP                  V
4      G Rj  xL
  RRR\        VP                  4      R\        VRR4      ;'       g    T/#  L LW L8  \         d   p T
;'       g    RT R2pTP                  T4      G Rj  xL 
 pTP                  TTT	R7      G Rj  xL 
 pRRR\        TP                  4      R\        TRR4      ;'       g    T/u Rp?#   \         d   pRRT RT 2/u Rp?u Rp?# Rp?ii ; iRp?ii ; i5i)zCreate a thread in the current Discord channel.

Tries ``parent_channel.create_thread()`` first.  If Discord rejects
that (e.g. permission issues), falls back to sending a seed message
and creating the thread from it.
r%  r   zThread name is required.r  c              3  8   "   T F  p\        V4      x  K  	  R # 5irq   )r   )r,  rV  s   & r   r-  0DiscordAdapter._create_thread.<locals>.<genexpr>d  s     Z0Y1A0Ys   z&auto_archive_duration must be one of: .Nz.Could not resolve the current Discord channel.zIDiscord threads can only be created inside server text channels, not DMs.z=Could not determine a parent text channel for the new thread.r   r^  zunknown userzRequested by z via /thread)r   r  reasonr  Tr  r  r   u!   🧵 Thread created by Hermes: **r  zTDiscord rejected direct thread creation and the fallback also failed. Direct error: z. Fallback error: )r&   !VALID_THREAD_AUTO_ARCHIVE_MINUTESrl  r  r  rx   r   rQ  r  rO  create_threadr  r   r   r`   )rL   r  r   r=  r  r   r   parent_channelr^  r  starter_messager  direct_errorseed_contentseed_msgfallback_errors   &&$$$           r   r  DiscordAdapter._create_threadQ  sh     

!!#788 (IIiiZ7X0YZZGEgYaPQQ99+FF?MNNgw0011hii44W=!\]]w{FDA>SWXjj\j l;"==b//1!	)77&; 8  F
 kk/2224S^wvvt<DD - G 3  	.dd4[\`[aac2d!/!4!4\!BBB'55*?!  6     tVYY!7664#@#H#HD 
  ))56HHXZ  	s   "H,AH,5E/6A'H,H,0H,E5 E1E5 &E5 :E3;/E5 +E5 .H,1E5 3E5 5H)$G=%F(&G=G0G=4G=7H)8H,=H!	HH!H$H)H,H!!H$$H))H,c                    V ^8  d   QhRRRR/# )r   r=  z'DiscordMessage'r   r  r   )r   s   "r   r   r    s      1A m r    c                l  "   VP                   ;'       g    RP                  4       pV'       d
   VR,          MRp\        V4      ^P8  d   VR,          R,           p VP                  VRR7      G Rj  xL
 pV#  L  \         d-   p\
        P                  R	T P                  T4        Rp?R# Rp?ii ; i5i)
ztCreate a thread from a user message for auto-threading.

Returns the created thread object, or ``None`` on failure.
r%  :NP   NHermes:NM   Nr  r  )r   r  Nz$[%s] Auto-thread creation failed: %s)r  r&   r   r  r`   rY   r   r   )rL   r=  r  r  r  r   s   &&    r   _auto_create_thread"DiscordAdapter._auto_create_thread  s      ??((b//1&-gcl8w<"%c*U2K	"00kY]0^^FM _ 	NNA499aP	sF   ,B4+B4A: 1A82A: 7B48A: :B1!B,&B4,B11B4c               0    V ^8  d   QhRRRRRRRRRRRR	/# )
r   r  r   r  session_keyr  r"  zOptional[dict]r   r   r   )r   s   "r   r   r    s@     +; +;+;%(+;7:+;+; !+; 
	+;r    c                T  "   V P                   '       d   \        '       g   \        RRR7      #  TpV'       d!   VP                  R4      '       d
   VR,          pV P                   P	                  \        V4      4      pV'       g-   V P                   P                  \        V4      4      G Rj  xL
 pRp\        V4      V8:  d   TMVRV^,
           R,           p	\        P                  ! RR	V	 R
2\        P                  P                  4       R7      p
V
P                  RVRR7       \        VV P                  R7      pVP                  WR7      G Rj  xL
 p\        R\!        VP"                  4      R7      #  L L&  \$         d!   p\        R\!        T4      R7      u Rp?# Rp?ii ; i5i)u   
Send a button-based exec approval prompt for a dangerous command.

The buttons call ``resolve_gateway_approval()`` to unblock the waiting
agent thread — this replaces the text-based ``/approve`` flow on Discord.
Fr  r  r  Ni  r  u    ⚠️ Command Approval Requiredz```
z
```rC  r  colorReason)r   r  inliner  r7   embedviewTr  )r  r1   r   rz   r  rn   r  r   r   EmbedColororange	add_fieldExecApprovalViewr=   r  r   r   r`   )rL   r  r  r  r  r"  	target_idr   max_desccmd_displayr  r  r|   r   s   &&&&&&        r   send_exec_approval!DiscordAdapter.send_exec_approval  sW     |||#4#4e?CC	;IHLL55$[1	ll..s9~>G $ : :3y> JJ H%(\X%='7>XXY\CZ]bCbKMM8#K=6mm**,E
 OOUOK#'!%!7!7D
  5<<Cds366{CC% K" =  	;e3q6::	;sj   F(F(	E: E: AE: ,E6-B$E: E8#E: 5F(6E: 8E: :F%F F%F( F%%F(c          
     ,    V ^8  d   QhRRRRRRRRRR/# )r   r  r   r  rL  r  r   r   r   )r   s   "r   r   r    s6     ; ;;$';25;; 
;r    c                  "   V P                   '       d   \        '       g   \        RRR7      #  V P                   P                  \	        V4      4      pV'       g-   V P                   P                  \	        V4      4      G Rj  xL
 pV'       d   RV R2MRp\        P                  ! RV V 2\        P                  P                  4       R	7      p\        VV P                  R
7      pVP                  WxR7      G Rj  xL
 p	\        R\        V	P                  4      R7      #  L L&  \         d!   p
\        R\        T
4      R7      u Rp
?
# Rp
?
ii ; i5i)zSend an interactive button-based update prompt (Yes / No).

Used by the gateway ``/update`` watcher when ``hermes update --gateway``
needs user input (stash restore, config migration).
Fr  r  Nz (default: r$  r%  u   ⚕ Update Needs Your Inputr  r  r  Tr  )r  r1   r   r  rn   r  r   r  r  goldUpdatePromptViewr=   r  r   r   r`   )rL   r  r  rL  r  r   default_hintr  r  r|   r   s   &&&&&      r   send_update_prompt!DiscordAdapter.send_update_prompt  s
     |||#4#4e?CC	;ll..s7|<G $ : :3w< HH7>[	3BLMM3%h|n5mm((*E
 $'!%!7!7D  5<<Cds366{CC I = 	;e3q6::	;se   EEAD% D!D% A-D% <D#=#D%  E!D% #D% %E0EEEEEc                    V ^8  d   QhRRRR/# )r   r   r	   r   r  r   )r   s   "r   r   r    s      c m r    c                    \        VRR4      pVe%   \        VRR4      e   \        VP                  4      # \        VRR4      pVe   \        V4      # R# )zKReturn the parent channel ID for a Discord thread-like channel, if present.r  Nr   	parent_id)rO  r   r   )rL   r   r  r(  s   &&  r   _get_parent_channel_id%DiscordAdapter._get_parent_channel_id  sS    (D1'&$"="Ivyy>!G[$7	 y>!r    c                    V ^8  d   QhRRRR/# )r   r   r	   r   r/   r   )r   s   "r   r   r    s        r    c                    Vf   R# \        \        RR4      pV'       d   \        W4      '       d   R# \        VRR4      pVe   \        VRV4      pV^8X  d   R# R# )zCBest-effort check for whether a Discord channel is a forum channel.NFForumChannelTrK  r  )rO  r   rx   )rL   r   	forum_clschannel_type
type_values   &&   r   _is_forum_parentDiscordAdapter._is_forum_parent  sX    ?G^T:	G77w5# wEJRr    c                    V ^8  d   QhRRRR/# )r   r  r	   r   r   r   )r   s   "r   r   r    s      s s r    c                   \        VRR4      ;'       g    \        \        VRR4      4      p\        VRR4      p\        VRR4      ;'       g    \        VRR4      p\        VRR4      p\        VRR4      pV P                  V4      '       d   V'       d   V'       d   V RV RV 2# V'       d   V'       d   V RV RV 2# V'       d   V RV 2# V# )	zdBuild a readable chat name for thread-like Discord channels, including forum context when available.r   Nr   r  r  r]  r  r  )rO  r   r1  )rL   r  r  r  r]  r  parent_names   &&     r   _format_thread_chat_name'DiscordAdapter._format_thread_chat_name  s    ffd3[[s764QY;Z7[40.PP'&'42PUFD1
ffd3  ((ZK \[M[MBB: \k]#k]CC!]#k]33r    c                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r  '  s     : : :r    c                 *    ^ RI Hp  V ! 4       R,          # )z/Path to the persisted thread participation set.get_hermes_homezdiscord_threads.json)hermes_cli.configr;  r:  s    r   _thread_state_path!DiscordAdapter._thread_state_path&  s     	6 #999r    c                   V ^8  d   QhRR/# )r   r   r8   r   )r   s   "r   r   r  -  s     
 
3 
r    c                `   V P                  4       p VP                  4       '       dH   \        P                  ! VP	                  RR7      4      p\        V\        4      '       d   \        V4      # \        4       #   \         d*   p\        P                  RT4        Rp?\        4       # Rp?ii ; i)z$Load persisted thread IDs from disk.utf-8encodingz'Could not load discord thread state: %sN)r=  r  r  loads	read_textrx   r   r8   r`   rY   r   )clsrr  r}   r   s   &   r   r  )DiscordAdapter._load_participated_threads,  s     %%'	G{{}}zz$..'."BCdD))t9$ u  	GLLBAFFu	Gs   A9 AA9 9B-B((B-c                   V ^8  d   QhRR/# r  r   )r   s   "r   r   r  9  s     G GD Gr    c                   V P                  4       p \        V P                  4      p\        V4      V P                  8  d    W P                  ) R p\        V4      V n        VP                  P                  RRR7       VP                  \        P                  ! V4      RR7       R#   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)z5Persist the current thread set to disk (best-effort).NT)parentsexist_okrA  rB  z'Could not save discord thread state: %s)r=  r   r  r   r  r8   r  mkdir
write_textr  r  r`   rY   r   )rL   rr  thread_listr   s   &   r   _save_participated_threads)DiscordAdapter._save_participated_threads9  s    &&(		Gt==>K;$";";;)+D+D*D*EF14[1A.KKdT:OODJJ{3gOF 	GLLBAFF	Gs   BB& &C1CCc                    V ^8  d   QhRRRR/# )r   r  r   r   r  r   )r   s   "r   r   r  G  s     . .s .t .r    c                    WP                   9  d.   V P                   P                  V4       V P                  4        R# R# )z2Add a thread to the participation set and persist.N)r  ra  rO  )rL   r  s   &&r   r  DiscordAdapter._track_threadG  s3    :::**..y9++- ;r    c                    V ^8  d   QhRRRR/# )r   r=  r>  r   r  r   )r   s   "r   r   r  M  s     c) c)^ c) c)r    c                  "   RpRp\        VP                  \        P                  4      pV'       d;   \	        VP                  P
                  4      pV P                  VP                  4      p\        VP                  \        P                  4      '       Eg	   \        P                  ! RR4      pVP                  R4       Uu0 uF*  qfP                  4       '       g   K  VP                  4       kK,  	  pp\	        VP                  P
                  4      0pV'       d   VP                  V4       \        P                  ! RR4      P                  4       R49  p	\        W,          4      p
T;'       d    W P                  9   pV	'       d8   V
'       g0   V'       g(   V P                   P"                  VP$                  9  d   R# V P                   P"                  '       d   V P                   P"                  VP$                  9   d   VP&                  P)                  RV P                   P"                  P
                   R2R4      P                  4       Vn        VP&                  P)                  R	V P                   P"                  P
                   R2R4      P                  4       Vn        RpV'       g   \        VP                  \        P                  4      '       g|   \        P                  ! R
R4      P                  4       R59   pV'       dL   V P+                  V4      G Rj  xL
 pV'       d+   Rp\	        VP
                  4      pTpV P-                  V4       \.        P0                  pVP&                  P3                  R4      '       d   \.        P4                  pEM<VP6                  '       Ed*   VP6                   EF  pVP8                  '       g   K  VP8                  P3                  R4      '       d   \.        P:                  pMVP8                  P3                  R4      '       d   \.        P<                  pMVP8                  P3                  R4      '       d   \.        P>                  pMkRpVP@                  '       d=   \        PB                  PE                  VP@                  4      w  ppVP                  4       pV\F        9   d   \.        PH                  p M	  T;'       g    VP                  p\        VP                  \        P                  4      '       d   RpVPJ                  PL                  pMV'       d   RpV PO                  V4      pMRp\Q        VP                  R\	        VP                  P
                  4      4      p\S        VP                  R4      '       dB   VP                  PT                  '       d&   VP                  PT                  PL                   RV 2p\Q        VP                  RR4      pV PW                  \	        VP
                  4      VV\	        VPJ                  P
                  4      VPJ                  PX                  VVR7      p. p. pRpVP6                   EF6  pVP8                  ;'       g    RpVP3                  R4      '       d    RVP                  R4      R6,          P                  R4      ^ ,          ,           pVR79  d   Rp\[        VP\                  VR7      G Rj  xL
 pVP_                  V4       VP_                  V4       \a        RV 2RR7       K  VP3                  R4      '       d    RVP                  R4      R6,          P                  R4      ^ ,          ,           pVR89  d   R p\e        VP\                  VR7      G Rj  xL
 pVP_                  V4       VP_                  V4       \a        R!V 2RR7       EKm  RpVP@                  '       d=   \        PB                  PE                  VP@                  4      w  ppVP                  4       pV'       gE   V'       d=   \F        Pf                  ! 4        UU u/ uF	  w  pp V VbK  	  p!pp V!Pi                  VR4      pV\F        9  d%   \j        Pm                  R#T;'       g    RV4       EK8  R9p"VPn                  '       d@   VPn                  V"8  d/   \j        Pm                  R$VPn                  VP@                  4       EK   ^ RI8p#V#Ps                  4       ;_uu_4       GRj  xL
 p$V$Pi                  VP\                  V#Pu                  ^R%7      R&7      ;_uu_4       GRj  xL
 p%V%Pv                  ^8w  d   \c        R'V%Pv                   24      hV%Py                  4       G Rj  xL
 p&RRR4      GRj  xL
  RRR4      GRj  xL
  \{        X&VP@                  ;'       g    R(V 24      p\F        V,          p'VP_                  V4       VP_                  V'4       \j        P}                  R)V4       R:p(VR;9   dv   \        V&4      V(8:  dc    V&P                  R*4      p)VP@                  ;'       g    R(V 2p*\        P                  ! R+R,V*4      p*R-V* R.V) 2p+V'       d   V R/V+ 2pEK.  T+pEK3  EK6  EK9  	  VP&                  p,V'       d   V,'       d   V R/V, 2MTp,V,'       d   V,P                  4       '       g   R2p,\        T,TTT\	        VP
                  4      TTVP                  '       d    \	        VP                  P                  4      MRVP                  R37	      p-V'       d   V P-                  V4       V P                  V-4      G Rj  xL
  R# u upi  EL^ EL  \b         dI   p\a        RT 2RR7       TP_                  TP\                  4       TP_                  T4        Rp?EKo  Rp?ii ; i ELD  \b         dI   p\a        R"T 2RR7       TP_                  TP\                  4       TP_                  T4        Rp?EK  Rp?ii ; iu up pi  EL* EL EL EL  + GRj  xL 
 '       g   i     EL; i EL  + GRj  xL 
 '       g   i     EL; i  \         d     EK.  i ; i  \b         d0   p\j        Pm                  R0TP@                  TRR17        Rp?EKg  Rp?ii ; i EL`5i)<z!Handle incoming Discord messages.NDISCORD_FREE_RESPONSE_CHANNELSr%  r)  DISCORD_REQUIRE_MENTIONrF  r"   r#   r$   DISCORD_AUTO_THREADTr  zimage/zvideo/zaudio/r  r  r  r   r]  r  r  r  r   r  ;.jpg)r  z[Discord] Cached user image: )flushz,[Discord] Failed to cache image attachment: .oggz[Discord] Cached user audio: z,[Discord] Failed to cache audio attachment: z7[Discord] Unsupported document type '%s' (%s), skippingz5[Discord] Document too large (%s bytes), skipping: %sr  ri  zHTTP documentz"[Discord] Cached user document: %srA  z	[^\w.\- ]r   z[Content of z]:
z

z)[Discord] Failed to cache document %s: %sr3  z.(The user sent a message with no text content))	r  r  rD  r  r  
media_urlsmedia_typesreply_to_message_idr   r  rG  )rZ  z.jpegz.pngz.gifz.webp)r\  z.mp3r{  z.webmz.m4ai  @i  )z.mdz.txt)Irx   r   r   r  r   r   r)  rQ  r   rP  rx  r&   ra  r+   r/   r  r  r   rD  r  r  r  r  r   r  r'   r  r   r  PHOTOVIDEOAUDIOr  rr  splitextr   DOCUMENTrJ  r   r6  rO  r   r]  r  r^  r   urlr   r  r`   r   rI  rz   rY   r   sizer  r  r  rn  r  r   rZ   r   r   resubUnicodeDecodeErrorr   r  r  
created_atr  ).rL   r=  r  parent_channel_idr  free_channels_rawrU  free_channelschannel_idsrequire_mentionis_free_channelin_bot_threadauto_threaded_channelauto_threadr  r  attdoc_extr   effective_channelr  r  r  rD  r^  r_  pending_text_injectionr  r  cached_pathr   rU  rV  mime_to_extMAX_DOC_BYTESr  r  r  	raw_bytesdoc_mimeMAX_TEXT_INJECT_BYTEStext_contentr^  	injection
event_textr  s.   &&                                            r   rR  DiscordAdapter._handle_messageM  s
     	 w?	GOO../I $ ; ;GOO L'//7+<+<== "		*JB O2C2I2I#2N]2NBRZRZR\ZRXXZ2NM]w1123K  12 ii(A6JPPRZnnO";#>?O &UU)7U7U*UM}<<$$G,<,<<||   T\\%6%6':J:J%J")//"9"9Bt||?P?P?S?S>TTU:VXZ"["a"a"c")//"9"9C@Q@Q@T@T?UUV:WY["\"b"b"d
 !%GOOW=N=N!O!O))$96BHHJNbbK#77@@ $I #FIII,2)&&y1 ##??%%c**"**H   **###''228<<#.#4#4))44X>>#.#4#4))44X>>#.#4#4"$<<<)+)9)9#,,)GJAw&-mmoG"&>>'2';';H +$ 2DDW__ goow'8'899I++I I556GHIIW__=O=O9PQIw00W__5J5J5J&4499:$ykJ	 W__gt<
 ""),,-))*nn11! # 
 
04&&C++88yL&&x005 2 23 7 ; A A# Fq IIC"LL$(<SWW#(N"NK%%k2&&|49+GtT ((225 2 23 7 ; A A# Fq IIC"KK$(<SWW#(N"NK%%k2&&|49+GtTT <<<WW--cll;FAs))+C|4L4R4R4T"U4TDAq1a44TK"U%//,;C66NNQ((y,
 %5MxxxCHH}$<SHHcll
#*'.'<'<'>'>'>'+2;;$'GG,3,A,A,A,K ,7 ," ," ," &*'+{{c'9.7%}8M.N(N6:iik0AI," ," (?'> +D )3<<+K+KXcU;K+K (@'DH&--k:'..x8"KK(LkZ4>1"o5#i.La:a
!)3<3C3CG3LL36<<3S3SXcUCSL3566,\3ZL2>|nDQ]P^0_I'=DZC[[_`i_jAk(>AJ(> ;b5O 'r __
!HR234
|DXnJ !1!1!3!3IJ!7::!#ELEVEVEVG$5$5$@$@ A\`((

 y)!!%(((_ ^6 AR #O ! 5HLTXY%%cgg.&&|44	5 #O ! 5HLTXY%%cgg.&&|445 #V" (?," 1B," ," ," ," (?'>'>'>6 (: !)$(!)( "NN K #a$ +  @ 	)sy  C sm1m12BsssAsCs*A.sm6s%As $s%s	 s*1s1s$s3A#sAs/Bs1C s2sAm<%m9&6m<s6Aoo6osAs s(s p+8sAs' rp1
r:qp4q	=p=p7p=qp:
qr&q'rA(r-#q:-q:?q:s	q:s*s2ssA+s%s*s
+s9m<<o<o
s
osop( <p#s#p((	s1r4q7p=:q=qq
qq	qrq7#q&$
q7/q71	r:r
rs	r

rs#s;sss)r  r  r  r=   r  r  r  r  r	  r>   r  rl  r  r
  r  r  r  r  r  )NN)NNN)NNNNrq   r  )zdangerous commandN)r%  r%  )Br   r   r   r   r   r  rS  rN   r  r  r  r  r  r  r  r  r  r  r  r  r*  r  r=  r  rK  r$  rO  r  rg  ro  rs  r'  ru  rN  r  r  r  r  r  r  r  r5  r  r  r  r  r  r  r  r  r  r  r  r$  r)  r1  r6  r   r=  classmethodr  rO  r  rR  r   __classcell__r  s   @r   r   r     s     M <Xt4<		Z6KE;N;.@.W$Ig Ig^B6& )"V
$
,4
2
h . J@"H1l l"?S ?SBg g"t t$ JD%IN>g@F0u_n'
Z"^H)>;$E
 E &*EV$+;Z;>( : :
 
 
G.c) c)r    r   c                     a  ] tR tRtRtR V 3R lltR R ltR R lt]P                  P                  R	]P                  P                  R
7      R R l4       t]P                  P                  R]P                  P                  R
7      R R l4       t]P                  P                  R]P                  P                   R
7      R R l4       t]P                  P                  R]P                  P$                  R
7      R R l4       tR tRtV ;t# )r  i9	  u]  
Interactive button view for exec approval of dangerous commands.

Shows four buttons: Allow Once, Allow Session, Always Allow, Deny.
Clicking a button calls ``resolve_gateway_approval()`` to unblock the
waiting agent thread — the same mechanism as the text ``/approve`` flow.
Only users in the allowed list can click.  Times out after 5 minutes.
c                    V ^8  d   QhRRRR/# r   r  r   r7   r8   r   )r   s   "r   r   ExecApprovalView.__annotate__C	       	" 	" 	"s 	"r    c                	N   < \         SV `  R R7       Wn        W n        RV n        R# r   ri  FNr  rN   r  r7   resolvedrL   r  r7   r  s   &&&r   rN   ExecApprovalView.__init__C	  &    GS)*$4!!DMr    c                    V ^8  d   QhRRRR/# r   r  r  r   r/   r   )r   s   "r   r   r  I	  s     	E 	E+> 	E4 	Er    c                    V P                   '       g   R# \        VP                  P                  4      V P                   9   # )z'Verify the user clicking is authorized.Tr7   r   r   r   rL   r  s   &&r   _check_authExecApprovalView._check_authI	  s3    ((({''**+t/D/DDDr    c               (    V ^8  d   QhRRRRRRRR/# )r   r  r  choicer   r  discord.Colorlabelr   )r   s   "r   r   r  O	  s2     (	X (	X2(	X<?(	X (	X),(	Xr    c                z  "   V P                   '       d(   VP                  P                  RRR7      G Rj  xL
  R# V P                  V4      '       g(   VP                  P                  RRR7      G Rj  xL
  R# RV n         VP                  P
                  '       d   VP                  P
                  ^ ,          MRpV'       d2   W5n        VP                  V RVP                  P                   2R7       V P                   F
  pRVn        K  	  VP                  P                  WPR7      G Rj  xL
   ^ R	IHp V! V P                  V4      p\         P#                  R
WP                  W!P                  P                  4       R#  ELO EL L[  \$         d"   p	\         P'                  RT	4        Rp	?	R# Rp	?	ii ; i5i)zIResolve the approval via the gateway approval queue and update the embed.z(This approval has already been resolved~Tr  Nz*You're not authorized to approve commands~ by r  r  )resolve_gateway_approvalzJDiscord button resolved %d approval(s) for session %s (choice=%s, user=%s)z2Failed to resolve gateway approval from button: %s)r  r  send_messager  r=  embedsr  
set_footerr   r^  childrendisabledr  tools.approvalr  r  rY   rZ   r`   r   )
rL   r  r  r  r  r  childr  countr  s
   &&&&&     r   _resolveExecApprovalView._resolveO	  s    
 }}}!**77>$ 8    ##K00!**77@D 8     DM 6A5H5H5O5O5OK''..q1UYE#  tK4D4D4Q4Q3R&S T !% ' &&33%3KKKXC01A1A6J`++V5E5E5R5R;" L  XQSVWWXsq   3F;FF; F;3F4(F;&F;A*F;.F
/F;4AF F;F;
F;F8F3-F;3F88F;z
Allow Once)r  stylec                    V ^8  d   QhRRRR/# r   r  r  buttonzdiscord.ui.Buttonr   )r   s   "r   r   r  z	  s      	] 	]2	]<M	]r    c                	   "   V P                  VR \        P                  P                  4       R4      G Rj  xL
  R#  L5i)oncezApproved onceN)r  r   r  greenrL   r  r  s   &&&r   
allow_onceExecApprovalView.allow_oncey	  s,      --VW]]5H5H5JO\\\   4?=?zAllow Sessionc                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r  	  s      	f 	f2	f<M	fr    c                	   "   V P                  VR \        P                  P                  4       R4      G Rj  xL
  R#  L5i)r  zApproved for sessionN)r  r   r  bluer  s   &&&r   allow_sessionExecApprovalView.allow_session	  s-      --Y8J8J8LNdeeer  zAlways Allowc                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r  	  s      	g 	g2	g<M	gr    c                	   "   V P                  VR \        P                  P                  4       R4      G Rj  xL
  R#  L5i)alwayszApproved permanentlyN)r  r   r  purpler  s   &&&r   allow_alwaysExecApprovalView.allow_always	  s-      --Xw}}7K7K7MOefffr  Denyc                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r  	  s      	T 	T2	T<M	Tr    c                	   "   V P                  VR \        P                  P                  4       R4      G Rj  xL
  R#  L5i)r|  DeniedN)r  r   r  redr  s   &&&r   r|  ExecApprovalView.deny	  s,      --VW]]5F5F5H(SSSr  c                P   "   RV n         V P                   F
  pRVn        K  	  R# 5i)z;Handle view timeout -- disable buttons and mark as expired.TNr  r  r  rL   r  s   & r   
on_timeoutExecApprovalView.on_timeout	  s!      DM!% '   $&r7   r  r  )r   r   r   r   r   rN   r  r  r   uir  ButtonStyler  r  greyr  blurpler  r  r|  r  r   r  r  s   @r   r  r  9	  s   		" 	"	E(	XT 
		W5H5H5N5N		O	] 
P	]
 
		8K8K8P8P		Q	f 
R	f
 
		w7J7J7R7R		S	g 
T	g
 
		w/B/B/F/F		G	T 
H	T
	& 	&r    r  c                  <  a  ] tR tRtRtR V 3R lltR R ltR R lt]P                  P                  R	]P                  P                  R
R7      R R l4       t]P                  P                  R]P                  P                  RR7      R R l4       tR tRtV ;t# )r"  i	  a!  Interactive Yes/No buttons for ``hermes update`` prompts.

Clicking a button writes the answer to ``.update_response`` so the
detached update process can pick it up.  Only authorized users can
click.  Times out after 5 minutes (the update process also has a
5-minute timeout on its side).
c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   UpdatePromptView.__annotate__	  r  r    c                	N   < \         SV `  R R7       Wn        W n        RV n        R# r  r  r  s   &&&r   rN   UpdatePromptView.__init__	  r  r    c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r  	  s     	E 	E+> 	E4 	Er    c                	    V P                   '       g   R # \        VP                  P                  4      V P                   9   # )Tr  r  s   &&r   r  UpdatePromptView._check_auth	  s3    ((({''**+t/D/DDDr    c               (    V ^8  d   QhRRRRRRRR/# )r   r  r  answerr   r  r  r  r   )r   s   "r   r   r  	  s2     (	I (	I2(	I<?(	I (	I),(	Ir    c                	  "   V P                   '       d(   VP                  P                  R RR7      G Rj  xL
  R# V P                  V4      '       g(   VP                  P                  RRR7      G Rj  xL
  R# RV n         VP                  P
                  '       d   VP                  P
                  ^ ,          MRpV'       d2   W5n        VP                  V RVP                  P                   2R7       V P                   F
  pRVn        K  	  VP                  P                  WPR7      G Rj  xL
   ^ RIHp V! 4       pVR	,          p	V	P                  R
4      p
V
P!                  V4       V
P#                  V	4       \$        P'                  RW!P                  P                  4       R#  ELt EL9 L  \(         d"   p\$        P+                  RT4        Rp?R# Rp?ii ; i5i)zAlready answered~Tr  NzYou're not authorized~r  r  r  r:  z.update_responsez.tmpz)Discord update prompt answered '%s' by %sz#Failed to write update response: %s)r  r  r  r  r=  r  r  r  r   r^  r  r  r  hermes_constantsr;  with_suffixrM  r  rY   rZ   r`   r   )rL   r  r  r  r  r  r  r;  homeresponse_pathtmpr  s   &&&&&       r   _respondUpdatePromptView._respond	  s     }}}!**77'4 8    ##K00!**77, 8     DM 6A5H5H5O5O5OK''..q1UYE#  tK4D4D4Q4Q3R&S T!% '&&33%3KKKI<&( $'9 9#//7v&M*?,,99=
 L  IBCHHIsq   3G F)G  G 3F,4(G &G A*G .F//G 4A3F1 'G ,G /G 1G<GG GG Yesu   ✓)r  r  r  c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r  	  s      	P 	P2	P<M	Pr    c                	   "   V P                  VR \        P                  P                  4       R4      G Rj  xL
  R#  L5i)yr  N)r  r   r  r  r  s   &&&r   yes_btnUpdatePromptView.yes_btn	  s,      --S'--2E2E2GOOOr  Nou   ✗c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r  	  s      	M 	M2	M<M	Mr    c                	   "   V P                  VR \        P                  P                  4       R4      G Rj  xL
  R#  L5i)nr  N)r  r   r  r  r  s   &&&r   no_btnUpdatePromptView.no_btn	  s,      --S'--2C2C2EtLLLr  c                	P   "   R V n         V P                   F
  pR Vn        K  	  R# 5irf   r  r  s   & r   r  UpdatePromptView.on_timeout	  s!      DM!% 'r  r  )r   r   r   r   r   rN   r  r  r   r  r  r  r  r  r  r  r  r   r  r  s   @r   r"  r"  	  s    		" 	"	E
(	IT 
		g.A.A.G.Gu		U	P 
V	P
 
		W-@-@-D-DE		R	M 
S	M
	& 	&r    r"  >   <   `'  r    )=
__future__r   r  r  loggingr   r   r   r   rC   r   collectionsr   pathlibr   typingr   r   r   r	   	getLoggerr   rY   r  r   r
   r>  r   discord.extr   r1   r  rq  _Pathrr  insertr   __file__resolverJ  gateway.configr   r   ri  gateway.platforms.baser   r   r   r   r   r   r   r   r,   r2   r4   r   r  Viewr  r"  r   r    r   <module>r     s8   "    	      #  0 0			8	$$; !
:$  ! 3uX..088;< = 3 		 	 	"
C CL
X)( X)~< \&7::?? \&|M&7::?? M&A kH  GNGHs   E EE