
    Fij                    4   d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZmZmZmZ ddlmZmZ ddlmZ ddlmZmZmZmZ  ej        e          Zd	Zd
ddddZdZdZ dZ!d,dZ" G d de          Z#ddddd-d!Z$d.d#Z%d/d(Z&d,d)Z'd* Z(d.d+Z)dS )0u  Mattermost gateway adapter.

Connects to a self-hosted (or cloud) Mattermost instance via its REST API
(v4) and WebSocket for real-time events.  No external Mattermost library
required — uses aiohttp which is already a Hermes dependency.

Environment variables:
    MATTERMOST_URL              Server URL (e.g. https://mm.example.com)
    MATTERMOST_TOKEN            Bot token or personal-access token
    MATTERMOST_ALLOWED_USERS    Comma-separated user IDs
    MATTERMOST_HOME_CHANNEL     Channel ID for cron/notification delivery
    )annotationsN)Path)AnyDictListOptionalTuple)PlatformPlatformConfig)MessageDeduplicator)BasePlatformAdapterMessageEventMessageType
SendResulti  dmgroupchannel)DGPOg       @g      N@g?returnboolc                 2   t          j        dd          } t          j        dd          }| st                              d           dS |st                              d           dS 	 ddl}d	S # t          $ r t                              d
           Y dS w xY w)z2Return True if the Mattermost adapter can be used.MATTERMOST_TOKEN MATTERMOST_URLz$Mattermost: MATTERMOST_TOKEN not setFz"Mattermost: MATTERMOST_URL not setr   NTz!Mattermost: aiohttp not installed)osgetenvloggerdebugwarningaiohttpImportError)tokenurlr#   s      I/home/ubuntu/.hermes/hermes-agent/plugins/platforms/mattermost/adapter.pycheck_mattermost_requirementsr(   5   s    I("--E
)$b
)
)C ;<<<u ;<<<ut   :;;;uus   (A. .$BBc                  4    e Zd ZdZdH fdZdIdZdJdZdKdZdKdZ	 dLdMdZ	dNdZ
dOdZdPdZ	 	 dQdRd$ZdSd%Z	 dTdUd&Zd'd(dVd+Z	 	 	 dWdXd.Z	 	 	 dWdYd0Z	 	 	 	 dZd[d3Z	 	 	 dWd\d5Z	 	 	 dWd]d7Zd^d8Z	 d_d`d<Z	 dTdad=Z	 	 dbdc fdCZdOdDZdOdEZdddGZ xZS )eMattermostAdapterz6Gateway adapter for Mattermost (self-hosted or cloud).configr   c                H   t                                          |t          j                   |j                            dd          pt          j        dd                              d          | _	        |j
        pt          j        dd          | _        d| _        d| _        d | _        d | _        d | _        d | _        d| _        |j                            dd          pt          j        dd	                                          | _        t+                      | _        d S )
Nr&   r   r   /r   F
reply_modeMATTERMOST_REPLY_MODEoff)super__init__r
   
MATTERMOSTextragetr   r   rstrip	_base_urlr%   _token_bot_user_id_bot_username_session_ws_ws_task_reconnect_task_closinglower_reply_moder   _dedup)selfr+   	__class__s     r'   r2   zMattermostAdapter.__init__J   s   !4555 LUB'' /y)2..
&++ 	 "<L295G+L+L!#"$ "047; L\2.. 9y0%88
%'' 	 *++    r   Dict[str, str]c                    d| j          ddS )NBearer application/jsonAuthorizationzContent-Type)r8   rC   s    r'   _headerszMattermostAdapter._headersj   s!    4t{44.
 
 	
rE   pathstrDict[str, Any]c           	       K   ddl }| j         d|                    d           }	 | j                            ||                                 |                    d                    4 d{V }|j        dk    rX|                                 d{V }t          
                    d	||j        |dd
                    i cddd          d{V  S |                                 d{V cddd          d{V  S # 1 d{V swxY w Y   dS # |j        $ r(}t          
                    d||           i cY d}~S d}~ww xY w)zGET /api/v4/{path}.r   N/api/v4/r-      totalheaderstimeout  u   MM API GET %s → %s: %s   zMM API GET %s network error: %s)r#   r7   lstripr;   r5   rM   ClientTimeoutstatustextr    errorjsonClientError)rC   rN   r#   r&   respbodyexcs          r'   _api_getzMattermostAdapter._api_getp   s.     ;;S)9)9;;		}((dmmoowOdOdkmOdOnOn(oo ) ) ) ) ) ) )sw;#%%!%,,,,,,DLL!;T4;PTUYVYUYPZ[[[	) ) ) ) ) ) ) ) ) ) ) ) ) )
 "YY[[(((((() ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) " 	 	 	LL:D#FFFIIIIII	sO   A	D 0AD D D -D  
D

D D
D 
EE :E Epayloadc           	       K   ddl }| j         d|                    d           }	 | j                            ||                                 ||                    d                    4 d{V 	 }|j        dk    rX|                                 d{V }t          
                    d	||j        |dd
                    i cddd          d{V  S |                                 d{V cddd          d{V  S # 1 d{V swxY w Y   dS # |j        $ r(}t          
                    d||           i cY d}~S d}~ww xY w)z#POST /api/v4/{path} with JSON body.r   NrR   r-   rS   rT   )rW   r`   rX   rY   u   MM API POST %s → %s: %srZ   z MM API POST %s network error: %s)r#   r7   r[   r;   postrM   r\   r]   r^   r    r_   r`   ra   rC   rN   rf   r#   r&   rb   rc   rd   s           r'   	_api_postzMattermostAdapter._api_post   s@      	;;S)9)9;;	}))T]]__7--B-77 *   ) ) ) ) ) ) ) ) ;#%%!%,,,,,,DLL!<dDKQUVZWZVZQ[\\\) ) ) ) ) ) ) ) ) ) ) ) ) ) "YY[[(((((() ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) " 	 	 	LL;T3GGGIIIIII	sO   A
D 2ADD D/D 
DD DD 
EE<EEc           	     j  K   ddl }| j         d|                    d           }	 | j                            ||                                 |          4 d{V 	 }|j        dk    rX|                                 d{V }t          	                    d||j        |dd                    i cddd          d{V  S |
                                 d{V cddd          d{V  S # 1 d{V swxY w Y   dS # |j        $ r(}t          	                    d	||           i cY d}~S d}~ww xY w)
z"PUT /api/v4/{path} with JSON body.r   NrR   r-   rW   r`   rY   u   MM API PUT %s → %s: %srZ   zMM API PUT %s network error: %s)r#   r7   r[   r;   putrM   r]   r^   r    r_   r`   ra   ri   s           r'   _api_putzMattermostAdapter._api_put   s*      	;;S)9)9;;	}((T]]__7 )   ) ) ) ) ) ) ) );#%%!%,,,,,,DLL!;T4;PTUYVYUYPZ[[[) ) ) ) ) ) ) ) ) ) ) ) ) ) "YY[[(((((() ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) " 	 	 	LL:D#FFFIIIIII	sN   5D  AC-.D  C-D  -
C77D  :C7;D   
D2
D-'D2-D2application/octet-stream
channel_id	file_databytesfilenamecontent_typeOptional[str]c                  K   ddl }| j         d}|                                }|                    d|           |                    d|||           dd| j         i}| j                            ||||                    d	
                    4 d{V }	|	j        dk    rW|		                                 d{V }
t                              d|	j        |
dd                    	 ddd          d{V  dS |	                                 d{V }|                    dg           }|r|d         d         ndcddd          d{V  S # 1 d{V swxY w Y   dS )z9Upload a file and return its file ID, or None on failure.r   N/api/v4/filesrp   files)rs   rt   rK   rH   <   rT   )rW   datarX   rY   u   MM file upload → %s: %srZ   
file_infosid)r#   r7   FormData	add_fieldr8   r;   rh   r\   r]   r^   r    r_   r`   r5   )rC   rp   rq   rs   rt   r#   r&   formrW   rb   rc   rz   infoss                r'   _upload_filezMattermostAdapter._upload_file   sO      	...!!|Z000%	 	 	
 	
 	
 #$;dk$;$;<=%%c7wOdOdkmOdOnOn%oo 	5 	5 	5 	5 	5 	5 	5sw{c!!!YY[[((((((8$+tDSDzRRR		5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5
 $$$$$$DHH\2..E%*458D>>	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5s   AE;AE
EEr   c                  K   ddl }| j        r| j        st                              d           dS |                    |                    d                    | _        d| _        | 	                    d           d{V }|rd	|vr;t                              d
           | j        
                                 d{V  dS |d	         | _        |                    dd          | _        t                              d| j        | j        | j                   t          j        |                                           | _        |                                  dS )z7Connect to Mattermost and start the WebSocket listener.r   Nz'Mattermost: URL or token not configuredFrS   rT   rX   zusers/mer|   uP   Mattermost: failed to authenticate — check MATTERMOST_TOKEN and MATTERMOST_URLusernamer   z+Mattermost: authenticated as @%s (%s) on %sT)r#   r7   r8   r    r_   ClientSessionr\   r;   r?   re   closer9   r5   r:   infoasynciocreate_task_ws_loopr=   _mark_connected)rC   r#   mes      r'   connectzMattermostAdapter.connect   se     ~ 	T[ 	LLBCCC5--)))33 . 
 
  ==,,,,,,,, 	T^^LLklll-%%'''''''''5tHVVJ339N		
 	
 	
  +DMMOO<<trE   Nonec                D  K   d| _         | j        r]| j                                        sD| j                                         	 | j         d{V  n# t          j        t          f$ r Y nw xY w| j        r2| j                                        s| j                                         | j        r&| j        	                                 d{V  d| _        | j
        r+| j
        j        s| j
        	                                 d{V  t                              d           dS )zDisconnect from Mattermost.TNzMattermost: disconnected)r?   r=   donecancelr   CancelledError	Exceptionr>   r<   r   r;   closedr    r   rL   s    r'   
disconnectzMattermostAdapter.disconnect   sL     = 	!3!3!5!5 	M  """m########*I6     	*(<(A(A(C(C 	* '')))8 	(.."""""""""DH= 	(!5 	(-%%'''''''''./////s   A A+*A+post_idc                   K   |s|S |                      d|            d{V }|r|                    d          r|d         S |S )a=  Resolve a post_id to the thread root_id for Mattermost.

        Mattermost requires root_id to be the *root* post of a thread.
        If the post is a reply (has its own root_id), we must use that
        root_id instead.  Using a reply's own ID as root_id causes
        "Invalid RootId parameter" errors.
        posts/Nroot_id)re   r5   )rC   r   rz   s      r'   _resolve_root_idz"MattermostAdapter._resolve_root_id   sk        	N]]#5G#5#566666666 	#DHHY'' 	#	?"rE   Nchat_idcontentreply_tometadataOptional[Dict[str, Any]]r   c                  K   |st          d          S |                     |          }|                     |t                    }d}|D ]q}||d}	|r+| j        dk    r |                     |           d{V }
|
|	d<   |                     d|	           d{V }|rd|vrt          d	d
          c S |d         }rt          d|          S )z1Send a message (or multiple chunks) to a channel.T)successNrp   messagethreadr   postsr|   FzFailed to create postr   r_   r   
message_id)r   format_messagetruncate_messageMAX_POST_LENGTHrA   r   rj   )rC   r   r   r   r   	formattedchunkslast_idchunkrf   resolved_rootrz   s               r'   sendzMattermostAdapter.send  s'       	,d++++''00	&&y/BB 	! 	!E% ' 'G
  3D,88 '+&;&;H&E&E E E E E E E%2	"99999999D P4t++!%7NOOOOOO4jGG$7;;;;rE   c                  K   |                      d|            d{V }|s|ddS t                              |                    dd          d          }|                    d          p|                    d          p|}||dS )	zReturn channel name and type.z	channels/Nr   )nametyper   r   display_namer   )re   _CHANNEL_TYPE_MAPr5   )rC   r   rz   ch_typer   s        r'   get_chat_infozMattermostAdapter.get_chat_info/  s      ]]#8w#8#899999999 	8#Y777#''(=(=yIIxx//N488F3C3CNw$g666rE   c                X   K   |                      d| j         dd|i           d{V  dS )zSend a typing indicator.zusers/z/typingrp   N)rj   r9   )rC   r   r   s      r'   send_typingzMattermostAdapter.send_typing=  s_       nn/T&///7#
 
 	
 	
 	
 	
 	
 	
 	
 	
 	
rE   F)finalizer   r   c                  K   |                      |          }|                     d| dd|i           d{V }|rd|vrt          dd          S t          d	|d         
          S )zEdit an existing post.r   z/patchr   Nr|   FzFailed to edit postr   Tr   )r   rn   r   )rC   r   r   r   r   r   rz   s          r'   edit_messagezMattermostAdapter.edit_messageF  s       ''00	]]'Z'''	"
 
 
 
 
 
 
 
  	Jt4''e3HIIII$4:>>>>rE   	image_urlcaptionc                D   K   |                      ||||d           d{V S )z5Download an image and upload it as a file attachment.imageN)_send_url_as_file)rC   r   r   r   r   r   s         r'   
send_imagezMattermostAdapter.send_imageS  sI       ++Y7
 
 
 
 
 
 
 
 	
rE   
image_pathc                B   K   |                      ||||           d{V S )zUpload a local image file.N_send_local_file)rC   r   r   r   r   r   s         r'   send_image_filez!MattermostAdapter.send_image_file`  G       **Z(
 
 
 
 
 
 
 
 	
rE   	file_path	file_namec                D   K   |                      |||||           d{V S )z"Upload a local file as a document.Nr   )rC   r   r   r   r   r   r   s          r'   send_documentzMattermostAdapter.send_documentm  sI       **Y9
 
 
 
 
 
 
 
 	
rE   
audio_pathc                B   K   |                      ||||           d{V S )zUpload an audio file.Nr   )rC   r   r   r   r   r   s         r'   
send_voicezMattermostAdapter.send_voice{  r   rE   
video_pathc                B   K   |                      ||||           d{V S )zUpload a video file.Nr   )rC   r   r   r   r   r   s         r'   
send_videozMattermostAdapter.send_video  r   rE   c                2    t          j        dd|          }|S )u   Mattermost uses standard Markdown — mostly pass through.

        Strip image markdown into plain links (files are uploaded separately).
        z!\[([^\]]*)\]\(([^)]+)\)z\2)resub)rC   r   s     r'   r   z MattermostAdapter.format_message  s     &4eWEErE   filer&   kindc           	       K   ddl m}  ||          sPt                              d           |                     ||pd d|                                 |           d{V S ddl}d}d}	|                    dd	          d
                             d          d         p| d}
t          d          D ]}	 | j
                            ||                    d                    4 d{V }|j        dk    s|j        dk    rf|dk     r`t                              d|d	z   |dd         |j                   t          j        d|d	z   z             d{V  	 ddd          d{V  |j        dk    rJ|                     ||pd d|                                 |           d{V cddd          d{V  c S |                                 d{V }|j        pd}		 ddd          d{V   n# 1 d{V swxY w Y   V# |j        t          j        f$ r}|dk     r&t          j        d|d	z   z             d{V  Y d}~t                              d||d	z   |           |                     ||pd d|                                 |           d{V cY d}~c S d}~ww xY w|Qt                              d|           |                     ||pd d|                                 |           d{V S |                     |||
|	           d{V }|s6|                     ||pd d|                                 |           d{V S ||pd|gd}|r)| j        dk    r|                     |           d{V |d<   |                     d|           d{V }|rd|vrt3          dd !          S t3          d"|d         #          S )$z2Download a URL and upload it as a file attachment.r   is_safe_urlz0Mattermost: blocked unsafe URL (SSRF protection)r   
Nro   r-      ?.png   rS   rT   r   i  i     z1Mattermost download retry %d/2 for %s (status %d)P   g      ?rY   z7Mattermost: failed to download %s after %d attempts: %sz,Mattermost: download returned no data for %srp   r   file_idsr   r   r   r|   FFailed to post with filer   Tr   )tools.url_safetyr   r    r"   r   stripr#   rsplitsplitranger;   r5   r\   r]   r!   r   sleepreadrt   ra   TimeoutErrorr   rA   r   rj   r   )rC   r   r&   r   r   r   r   r#   rq   ctfnameattemptrb   rd   file_idrf   rz   s                    r'   r   z#MattermostAdapter._send_url_as_file  s      	100000{3 	YNNMNNN7w}",E,E,E,E,K,K,M,MxXXXXXXXXX	'

3""2&,,S11!4E4Qxx 	] 	]G]=,,S':O:OVX:O:Y:Y,ZZ       ^b{c))T[C-?-?"Q;;"LL)\)01c#2#hM M M")-w{0C"D"DDDDDDDD$             {c))%)YYw7=b8Q8QC8Q8Q8W8W8Y8Y[c%d%ddddddd                '+iikk 1 1 1 1 1 1I*H.HB                             ')=> ] ] ]Q;;!-w{(;<<<<<<<<<HHHHXZ]_fij_jloppp!YYw7=b0I0IC0I0I0O0O0Q0QS[\\\\\\\\\\\\\\\\] NNI3OOO7w}",E,E,E,E,K,K,M,MxXXXXXXXXX))'9eRHHHHHHHH 	Y7w}",E,E,E,E,K,K,M,MxXXXXXXXXX "}" 	#
 #

  	G(H44'+'<'<X'F'F!F!F!F!F!F!FGI^^GW55555555 	Ot4''e3MNNNN$4:>>>>sc   96H/A+G8H,A G8,H$G8%H8
H	HH	HJ/!&J*AJ*"J/*J/c                ~  K   ddl }t          |          }|                                s,t                              d|           t          dd          S |p|j        }|                    |          d         pd}	|                                }
| 	                    ||
||	           d{V }|st          dd	          S ||pd
|gd}|r)| j
        dk    r|                     |           d{V |d<   |                     d|           d{V }|rd|vrt          dd	          S t          d|d                   S )z,Upload a local file and attach it to a post.r   Nz.Mattermost: local file not found, skipping: %sTr   ro   FzFile upload failedr   r   r   r   r   r   r|   r   )	mimetypesr   existsr    r"   r   r   
guess_type
read_bytesr   rA   r   rj   )rC   r   r   r   r   r   r   pr   r   rq   r   rf   rz   s                 r'   r   z"MattermostAdapter._send_local_file  s      	OOxxzz 	=NN@)   dt<<<<#QV!!%((+I/ILLNN	))'9eRHHHHHHHH 	Ie3GHHHH "}" 	#
 #

  	G(H44'+'<'<X'F'F!F!F!F!F!F!FGI^^GW55555555 	Ot4''e3MNNNN$4:>>>>rE           imagesList[Tuple[str, str]]human_delayfloatc           
     P  K   sdS ddl }ddl}ddlm} dfdt	          dt                              D             }t          |          D ]\  }	}
|dk    r |	dk    rt          j        |           d{V  g }g }	 |
D ]k\  }}|r|	                    |           |
                    d          r ||dd                   }t          |          }|                                st                              d|           |j        }|                    |          d         pd	}|                                }nxdd
lm}  ||          st                              d           	 | j                            ||                    d                    4 d{V 	 }|j        dk    r=t                              d|j        |dd                    	 ddd          d{V  k|                                 d{V }|j        pd	}ddd          d{V  n# 1 d{V swxY w Y   n<# t4          $ r/}t                              d|dd         |           Y d}~d}~ww xY w|                    dd          d                             d          d         pdt          |           d}|                     ||||           d{V }|r|	                    |           m|s|d                    |          |d}t                              dt          |          |	dz   t          |                     |                      d|           d{V }|rd|vrEt                              d           tC                      "                    ||
||           d{V  `# t4          $ re}t                              d |	dz   t          |          |d!"           tC                      "                    ||
||           d{V  Y d}~d}~ww xY wdS )#a  Send a batch of images as a single Mattermost post with multiple attachments.

        Mattermost supports up to 5 ``file_ids`` per post. Each image is
        uploaded individually (Mattermost's file API is one-at-a-time),
        then a single post is created referencing all uploaded file_ids
        at once. Batches larger than 5 are chunked. Falls back to the
        base per-image loop on total failure.
        Nr   )unquote   c                *    g | ]}||z            S  r  ).0iCHUNKr   s     r'   
<listcomp>z:MattermostAdapter.send_multiple_images.<locals>.<listcomp>  s&    LLL!&1u9%LLLrE   zfile://   z%Mattermost: skipping missing image %sz	image/pngr   z-Mattermost: blocked unsafe image URL in batchrS   rT   r   rY   z2Mattermost: failed to download image (HTTP %d): %sr   z&Mattermost: download failed for %s: %sr-   r   r   r   image_r   r   r   z<Mattermost: sending %d image(s) as single post (chunk %d/%d)r   r|   z1Mattermost: multi-image post failed, falling back)r   zCMattermost: multi-image send failed (chunk %d/%d), falling back: %sT)exc_info)#r   r#   urllib.parser   r   len	enumerater   r   append
startswithr   r   r    r"   r   r   r   r   r   r;   r5   r\   r]   r   rt   r   r   r   r   joinr   rj   r1   send_multiple_images)rC   r   r   r   r   r   r#   _unquoter   	chunk_idxr   r   caption_partsr   alt_text
local_pathr   r   r   rq   r   rb   dl_errfidrf   rz   er  rD   s     `                        @r'   r  z&MattermostAdapter.send_multiple_images  s       	F444444LLLLLuQFU/K/KLLL )& 1 1 C	f C	fIuQ9q==mK000000000"$H')M=f+0 %- %-'Ix 7%,,X666 ++I66 k%-Xim%<%<
 ,, xxzz %"NN+RT^___$ !&11%88;J{$%LLNN		@@@@@@*{955 %"NN+Z[[[$%'+}'8'8 )73H3Hr3H3R3R (9 ( ( 
F 
F 
F 
F 
F 
F 
F 
F!%#';##5#5$*NN(\(,Yss^%& %& %& %-
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 37))++,=,=,=,=,=,=	%)%6%E+
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F 
F  ) % % %"NN+SU^_b`b_bUceklll$HHHH% !* 0 0a 8 8 < B B3 G G J jNjWZ[cWdWdNjNjNj $ 1 1'9eR P PPPPPPPC - ,,,  #*#yy77 (+ +
 RMM9q=#f++   "^^GW======== jt4//NN#VWWW''66wx]h6iiiiiiiii f f fYM3v;;D     gg227E8Yd2eeeeeeeeeeeeeef}C	f C	fs   C,N426H9)6H'H90N42#H'H9'
H11H94H15H98N49
I2$I-'N4-I22BN48B:N44
P#>APP#c                  K   t           }| j        su	 |                                  d{V  t           }n# t          j        $ r Y dS t
          $ r}| j        rY d}~dS ddl}t          |                                          }t          ||j
                  r/|j        dv r&t                              d|j                   Y d}~dS d|v sd|v sd|v r!t                              d|           Y d}~dS t                              d	||           Y d}~nd}~ww xY w| j        rdS ddl}|t           z  |                                z  }t          j        ||z              d{V  t%          |d
z  t&                    }| j        sdS dS )zHConnect to the WebSocket and listen for events, reconnecting on failure.Nr   >       u:   Mattermost WS auth failed (HTTP %d) — stopping reconnect401403unauthorizedu8   Mattermost WS permanent error: %s — stopping reconnectu1   Mattermost WS error: %s — reconnecting in %.0fsr   )_RECONNECT_BASE_DELAYr?   _ws_connect_and_listenr   r   r   r#   rO   r@   
isinstanceWSServerHandshakeErrorr]   r    r_   r"   random_RECONNECT_JITTERr   min_RECONNECT_MAX_DELAY)rC   delayrd   r#   err_strr#  jitters          r'   r   zMattermostAdapter._ws_loopj  s     %- 	9`11333333333-)    ` ` `= FFFFF c((..**c7#ABB szU_G_G_LL!]_b_ijjjFFFFFG##u'7'7>W;T;TLL![]`aaaFFFFFRTWY^________` }  MMM..@F-/////////	#788E; - 	9 	9 	9 	9 	9s.   !5 D	DDA#D'D3DDc                   K   t          j        dd| j                  dz   }t                              d|           | j                            |d           d{V | _        dd	d
| j        id}| j        	                    |           d{V  t                              d           | j        2 3 d{V }| j
        r dS |j        |j        j        |j        j        hv rS	 t          j        |j                  }n# t          j        t$          f$ r Y cw xY w|                     |           d{V  |j        |j        j        |j        j        |j        j        |j        j        hv r#t                              d|j                    dS 6 dS )z@Single WebSocket session: connect, authenticate, process events.z^httpwsz/api/v4/websocketzMattermost: connecting to %sg      >@)	heartbeatNr   authentication_challenger%   )seqactionrz   z1Mattermost: WebSocket connected and authenticatedz!Mattermost: WebSocket closed (%s))r   r   r7   r    r   r;   
ws_connectr<   r8   	send_jsonr?   r   TEXTBINARYr`   loadsrz   JSONDecodeError	TypeError_handle_ws_eventERRORCLOSECLOSINGCLOSED)rC   ws_urlauth_msgraw_msgevents        r'   r   z(MattermostAdapter._ws_connect_and_listen  s      $77:MM2F;;;11&D1IIIIIIII 0dk*
 

 h  *********GHHH!X 	 	 	 	 	 	 	'} |!#    Jw|44EE,i8   H++E2222222222""$#	"   ?NNN &XXs   1F"C<<DDr?  c           
       ,K   |                     d          }|dk    rdS |                     di           }|                     d          }|sdS 	 t          j        |          }n# t          j        t          f$ r Y dS w xY w|                     d          | j        k    rdS |                     d          rdS |                     dd	          }| j                            |          rdS |                     d
d	          }|                     dd          }t                               |d          }	|                     dd	          ,|dk    r| j	        j
        r| j	        j
                             d          nd}
|
t          j        dd	          }
t          |
t                    rd |
D             }n,d t          |
                              d          D             }|r!||vrt"                              d|           dS t          j        dd                                          dv}t          j        dd	          }d |                    d          D             }||v }d| j         d| j         g}t+          ,fd|D                       }|r!|s|st"                              d|           dS |rK|D ]H}t-          j        t-          j        |          d	,t,          j                                                  ,I|                     dd	          }|                     dd	                              d          p|}|                     d           pd}|                     d!          pg }t8          j        },                    d"          rt8          j        }g }g }|D ]9}	 |                      d#| d$           d{V }|                     d%d&|           }tC          |          j"        pd	}|                     d'd(          }d)dl#}| j$         d*| } | j%                             | d+d,| j&         i|'                    d-.          /          4 d{V 	 }!|!j(        d0k     r|!)                                 d{V }"d)d1l*m+}#m,}$ |                    d2          r9 |#|"|pd3          }%|-                    |%           |-                    |           n|                    d4          r?d)d5l*m.}&  |&|"|pd6          }%|-                    |%           |-                    |           nX |$|"|          }%|-                    |%           |-                    |           n!t"          /                    d7||!j(                   ddd          d{V  n# 1 d{V swxY w Y   # t`          $ r'}'t"          /                    d8||'           Y d}'~'3d}'~'ww xY w|rj|t8          j        k    rZt+          d9 |D                       rt8          j1        }n4t+          d: |D                       rt8          j2        }n|rt8          j3        }| 4                    ||	|||;          }(d)d<l*m5})  |)| j	        j
        |d          }*tm          ,||(|||r|nd|r|nd|*=          }+| 7                    |+           d{V  dS )>z!Process a single WebSocket event.r?  postedNrz   rh   user_idr   r|   r   rp   channel_typer   r   r   r   allowed_channelsMATTERMOST_ALLOWED_CHANNELSc                    h | ]D}t          |                                          #t          |                                          ES r  )rO   r   r  cs     r'   	<setcomp>z5MattermostAdapter._handle_ws_event.<locals>.<setcomp>  s9    #Z#Z#Zq3q66<<>>#ZCFFLLNN#Z#Z#ZrE   c                ^    h | ]*}|                                 |                                 +S r  r   rG  s     r'   rI  z5MattermostAdapter._handle_ws_event.<locals>.<setcomp>  s=     $ $ $"#aggii$GGII$ $ $rE   ,z7Mattermost: ignoring message in non-allowed channel: %sMATTERMOST_REQUIRE_MENTIONtrue>   0nofalse!MATTERMOST_FREE_RESPONSE_CHANNELSc                ^    h | ]*}|                                 |                                 +S r  rK  )r  chs     r'   rI  z5MattermostAdapter._handle_ws_event.<locals>.<setcomp>  s2    ]]]BRTRZRZR\R\]RXXZZ]]]rE   @c              3  h   K   | ],}|                                                                  v V  -d S N)r@   )r  patternmessage_texts     r'   	<genexpr>z5MattermostAdapter._handle_ws_event.<locals>.<genexpr>  sP         <#5#5#7#77     rE   zAMattermost: skipping non-DM message without @mention (channel=%s))flagssender_namer   r   r-   zfiles/z/infor   file_	mime_typero   r   z/api/v4/files/rK   rH   rS   rT   rV   rY   )cache_image_from_bytescache_document_from_bytesimage/r   audio/)cache_audio_from_bytesz.oggz/Mattermost: failed to download file %s: HTTP %sz)Mattermost: error downloading file %s: %sc              3  @   K   | ]}|                     d           V  dS )ra  Nr  r  ms     r'   rZ  z5MattermostAdapter._handle_ws_event.<locals>.<genexpr>G  s.      ??a1<<))??????rE   c              3  @   K   | ]}|                     d           V  dS )rb  Nre  rf  s     r'   rZ  z5MattermostAdapter._handle_ws_event.<locals>.<genexpr>I  s.      AAQ\\(++AAAAAArE   )r   	chat_typerB  	user_name	thread_id)resolve_channel_prompt)r^   message_typesourceraw_messager   
media_urlsmedia_typeschannel_prompt)8r5   r`   r4  r5  r6  r9   rB   is_duplicater   r+   r4   r   r   r!  listrO   r   r    r!   r@   r:   anyr   r   escape
IGNORECASEr   r[   r   r2  r  COMMANDre   r   suffixr#   r7   r;   r8   r\   r]   r   gateway.platforms.baser_  r`  r  rc  r"   r   PHOTOVOICEDOCUMENTbuild_sourcerl  r   handle_message)-rC   r?  
event_typerz   raw_post_strrh   r   rp   channel_type_rawri  allowed_rawrD  require_mentionfree_channels_rawfree_channelsis_free_channelmention_patternshas_mentionrX  	sender_idr\  rk  r   msg_typerp  rq  r  	file_infor   extmimer#   dl_urlrb   rq   r_  r`  r  rc  rd   rn  rl  _channel_prompt	msg_eventrY  s-                                               @r'   r7  z"MattermostAdapter._handle_ws_event  s     YYw''
!!Fyy$$xx'' 	F	:l++DD$i0 	 	 	FF	 88I$"333F 88F 	F((4$$ ;##G,, 	F XXlB//
88NC88%))*:IFF	 xx	2.. s"" HL{GXb$++//0BCCC^bK" i(ErJJ+t,, #Z#ZK#Z#Z#Z  $ $'*;'7'7'='=c'B'B$ $ $    J6F$F$FM    i,f egg12O !#	*Mr R R]]2C2I2I#2N2N]]]M(M9O )D&(('D%''      /    K
   { W     /  G#%6	'**BBM$ $ $egg !L
 HHY++	hh}b1188==J HHY''/4	 88J''-2#""3'' 	+"*H !#
!# !	V !	VC V"&--0C0C0C0C"D"DDDDDDD	!fmcmm<<5kk(.B }}[2LMM N??#??=,,,.E.E.EF#111;; -   l l l l l l l l {S((*.))++$5$5$5$5$5$5	llllllll??844 5)?)?	3=RX)Y)YJ&--j999'..t4444!__X66 5UUUUUU)?)?	3=RX)Y)YJ&--j999'..t4444)B)B9e)T)TJ&--j999'..t4444'XZ]_c_jkkk-l l l l l l l l l l l l l l l l l l l l l l l l l l l.  V V VJCQTUUUUUUUUV  	08{'777??;????? 0&,AA[AAAAA 0&, 0&/""! # 
 
 	BAAAAA00Kz4
 
 !!%/9zzT'2<*	
 	
 	
	 !!),,,,,,,,,,,sP   A& &B ?B +B5V.!D(V	V.
V%	%V.(V%	)V..
W8WW)r+   r   )r   rF   )rN   rO   r   rP   )rN   rO   rf   rP   r   rP   )ro   )
rp   rO   rq   rr   rs   rO   rt   rO   r   ru   r   r   r   r   )r   rO   r   rO   )NN)
r   rO   r   rO   r   ru   r   r   r   r   )r   rO   r   rP   rW  )r   rO   r   r   r   r   )
r   rO   r   rO   r   rO   r   r   r   r   )NNN)r   rO   r   rO   r   ru   r   ru   r   r   r   r   )r   rO   r   rO   r   ru   r   ru   r   r   r   r   )NNNN)r   rO   r   rO   r   ru   r   ru   r   ru   r   r   r   r   )r   rO   r   rO   r   ru   r   ru   r   r   r   r   )r   rO   r   rO   r   ru   r   ru   r   r   r   r   )r   rO   r   rO   )r   )r   rO   r&   rO   r   ru   r   ru   r   rO   r   r   )r   rO   r   rO   r   ru   r   ru   r   ru   r   r   )Nr   )
r   rO   r   r   r   r   r   r   r   r   )r?  rP   r   r   )__name__
__module____qualname____doc__r2   rM   re   rj   rn   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r   r   r7  __classcell__)rD   s   @r'   r*   r*   G   s       @@, , , , , ,@
 
 
 
      (   ( Uo5 5 5 5 5:       D0 0 0 00   ( #'-1 <  <  <  <  <D7 7 7 7 BF
 
 
 
 
 PU? ? ? ? ? ?" "&"&-1
 
 
 
 
" "&"&-1
 
 
 
 
" "&#'"&-1
 
 
 
 
$ "&"&-1
 
 
 
 
" "&"&-1
 
 
 
 
   ( <? <? <? <? <?H $(%? %? %? %? %?V .2 \f \f \f \f \f \f \fD 9  9  9  9D% % % %Nt- t- t- t- t- t- t- t-rE   r*   F)rk  media_filesforce_documentr   rO   r   rk  ru   r  Optional[list]r  rP   c          
     ~  K   	 ddl }n# t          $ r ddicY S w xY wt          | di           pi                     d          pt	          j        dd                              d	          }t          | d
d          pt	          j        dd                                          }|r|sddiS d| dd}	dd| i}
|pg }	 ddlm	}m
}  |d          } ||          \  }} |j        d/d|                    d          i|4 d{V 	 }g }|D ]}t          |t                    r|                    d          n|}|rt          j                            |          sQ|                                }|                    d|           t'          |d          5 }|                    d|                                t          j                            |                     ddd           n# 1 swxY w Y    |j        | df||
d|4 d{V 	 }|j        dvrW|                                 d{V }dd|j         d |dd!          icddd          d{V  c cddd          d{V  S |                                 d{V }|                    d"g           D ]2}|                    d#          r|                    |d#                    3	 ddd          d{V  n# 1 d{V swxY w Y   ||d$}|r||d%<   |r||d&<    |j        | d'f|	|d(|4 d{V 	 }|j        dvrU|                                 d{V }dd)|j         d |dd!          icddd          d{V  cddd          d{V  S |                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   d*d+||                    d#          d,cddd          d{V  S # 1 d{V swxY w Y   dS # |j        $ r}dd-| icY d}~S d}~wt8          $ r}dd.| icY d}~S d}~ww xY w)0u  Send via the Mattermost v4 REST API without a live gateway adapter.

    Used by ``tools/send_message_tool._send_via_adapter`` when the gateway
    runner is not in this process (typical for cron jobs running out-of-process).
    Reads ``MATTERMOST_TOKEN`` from ``pconfig.token`` (set by the gateway
    config loader from env) and falls back to the ``MATTERMOST_TOKEN`` env
    var.  Server URL comes from ``pconfig.extra["url"]`` (set by the YAML
    bridge / env loader) or the ``MATTERMOST_URL`` env var.

    Thread replies (Mattermost CRT) are supported via the ``root_id`` field
    on the ``POST /posts`` payload — pass ``thread_id`` when threading is
    desired.  ``media_files`` are uploaded via ``POST /files``
    (multipart/form-data), then their returned ``file_id`` values are
    attached to the post.

    ``force_document`` is accepted for signature parity with other
    standalone senders but unused — Mattermost stores every uploaded file
    as a generic attachment regardless.
    r   Nr_   z/aiohttp not installed. Run: pip install aiohttpr4   r&   r   r   r-   r%   r   zPMattermost standalone send: MATTERMOST_URL and MATTERMOST_TOKEN must both be setrH   rI   rJ   rK   )resolve_proxy_urlproxy_kwargs_for_aiohttpMATTERMOST_PROXY)platform_env_varrX   ry   rT   rN   rp   rbrx   )rs   rw   )rz   rW   >   rZ      zMattermost file upload failed (z): rY   r{   r|   r   r   r   z/api/v4/postsrl   zMattermost API error (T
mattermost)r   platformr   r   z"Mattermost send failed (network): zMattermost send failed: r  )r#   r$   getattrr5   r   r   r6   r   rz  r  r  r   r\   r!  dictrN   r   r}   r~   openr   basenamerh   r]   r^   r`   r  ra   r   )pconfigr   r   rk  r  r  r#   base_urlr%   rW   upload_headersr  r  _proxy_sess_kw_req_kwsessionr   mediar   r   fhupload_resprc   upload_datar   rf   rb   rz   rd   s                                 r'   _standalone_sendr  q  s     8L L L LJKKKKL 
'2	&	&	,"11%88 	+9%r**fSkk  Wgt,,Q	:Lb0Q0QXXZZE 
5 
4
 	
 +5*** G &'8'8'89N#KO; 	WVVVVVVV""4FGGG44V<<'(7( 
 
)))33

 
 D	 D	 D	 D	 D	 D	 D	 D	 "$H$ 8 81;E41H1HSEIIf---e	  y(A(A '')) |W555)T** bNN		!#!1!1)!<!< #                  (7<...*  	  8 8 8 8 8 8 8 8
 !");;%0%5%5%7%7777777#!H$/$6!H !H;?:!H !H 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8)D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	D )4(8(8(:(:":":":":":":K +b A A 8 888D>> 8$OODJ77788 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8* &"' 'G  /%.	" /&.
##w|***  	  ) ) ) ) ) ) ) )
 ;j00!%,,,,,,D,T[ , ,#DSDz, ,) ) ) ) ) ) ) ) ) ) ) ) )aD	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	| "YY[[(((((() ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )   (""hhtnn	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	 D	J  E E ECcCCDDDDDDD ; ; ;9C99:::::::;s   	 ?AP BO5AG/#O5/G33O56G37O5:K,O5#P 6A#K,O5,
K66O59K6:2O5-:N8'O59P N8&O58
O	O5O	O5"P 5
O??P O?P 
P<PP<P<+P71P<7P<r   c                 B   ddl m} m} ddlm}m}m}m}m}  |d            | d          }|r |d            |dd          sd	S  |d
            |d            |d           t                        |d          }|r |d|
                    d                      |dd          }	|	sd	S  |d|	            |d           t                        |d            |d            |d           t                        |d          }
|
r, |d|
                    dd                      |d           n |d           t                        |d            |d            |d            |d           }|r |d!|            |d"           d	S )#a  Guide the user through Mattermost bot setup.

    Mirrors Discord/Teams' ``interactive_setup`` shape: lazy-imports CLI
    helpers so the plugin's import surface stays small, prompts for the
    server URL + bot token, captures an allowlist, and offers to set a
    home channel.  Replaces the central
    ``hermes_cli/setup.py::_setup_mattermost`` function this migration
    removes.
    r   )get_env_valuesave_env_value)promptprompt_yes_noprint_header
print_infoprint_success
Mattermostr   zMattermost: already configuredzReconfigure Mattermost?FNz/Works with any self-hosted Mattermost instance.uF      1. In Mattermost: Integrations → Bot Accounts → Add Bot Accountz   2. Copy the bot tokenz3Mattermost server URL (e.g. https://mm.example.com)r   r-   z	Bot tokenT)passwordzMattermost token savedu,   🔒 Security: Restrict who can use your botu6      To find your user ID: click your avatar → Profilez'   or use the API: GET /api/v4/users/mez?Allowed user IDs (comma-separated, leave empty for open access)MATTERMOST_ALLOWED_USERS r   zMattermost allowlist configureduE   ⚠️  No allowlist set - anyone who can message the bot can use it!uL   📬 Home Channel: where Hermes delivers cron job results and notifications.uH      To get a channel ID: click channel name → View Info → copy the IDzK   You can also set this later by typing /set-home in a Mattermost channel.z9Home channel ID (leave empty to set later with /set-home)MATTERMOST_HOME_CHANNELz2   Open config in your editor:  hermes config edit)hermes_cli.configr  r  hermes_cli.cli_outputr  r  r  r  r  printr6   replace)r  r  r  r  r  r  r  existingmm_urlr%   allowed_usershome_channels               r'   interactive_setupr    s    @???????              L}/00H 
3444}6>> 	FJ@AAAJWXXXJ)***	GGGVIJJF ='s););<<<F;...E N%u---M*+++	GGGJ=>>>JGHHHJ8999	GGGF\]]M \1=3H3Hb3Q3QRRR78888
Z[[[	GGGJ]^^^JYZZZJ\]]]6UVVL @0,???JCDDDDDrE   yaml_cfgr  mattermost_cfgdict | Nonec                   d|v rHt          j        d          s4t          |d                                                   t           j        d<   |                    d          }|dt          j        d          sPt          |t                    rd                    d |D                       }t          |          t           j        d<   |                    d          }|dt          j        d	          sPt          |t                    rd                    d
 |D                       }t          |          t           j        d	<   dS )u  Translate ``config.yaml`` ``mattermost:`` keys into env vars.

    Implements the ``apply_yaml_config_fn`` contract (#24836 / #25443).
    Mirrors the legacy ``mattermost_cfg`` block that used to live in
    ``gateway/config.py::load_gateway_config()`` before this migration.

    The MattermostAdapter reads its runtime configuration via
    ``os.getenv()`` for ``MATTERMOST_REQUIRE_MENTION``,
    ``MATTERMOST_FREE_RESPONSE_CHANNELS``, and
    ``MATTERMOST_ALLOWED_CHANNELS``.  Rather than rewrite those call sites
    to read from ``PlatformConfig.extra``, this hook keeps the env-driven
    model and merely owns the YAML→env translation here, next to the
    adapter that consumes it.

    Env vars take precedence over YAML — every assignment is guarded
    by ``not os.getenv(...)`` so an explicit env var survives a config.yaml
    update.  Returns ``None`` because no extras are seeded into
    ``PlatformConfig.extra`` directly (everything flows through env).
    r  rM  free_response_channelsNrR  rL  c              3  4   K   | ]}t          |          V  d S rW  rO   r  vs     r'   rZ  z%_apply_yaml_config.<locals>.<genexpr>Y  s(      //a3q66//////rE   rD  rE  c              3  4   K   | ]}t          |          V  d S rW  r  r  s     r'   rZ  z%_apply_yaml_config.<locals>.<genexpr>_  s(      --Q#a&&------rE   )	r   r   rO   r@   environr5   r!  rt  r  )r  r  frcacs       r'   _apply_yaml_configr  @  s%   ( N**29=Y3Z3Z*36~FW7X3Y3Y3_3_3a3a
/0


5
6
6C
ry)LMMc4   	0((//3/////C:=c((
67			.	/	/B	~bi(EFF~b$ 	.--"-----B47GG
014rE   c                    ddl m} t          |                    d          pd                                o(|                    d          pd                                          S )ay  Mattermost is considered connected when BOTH MATTERMOST_TOKEN and
    MATTERMOST_URL are set.

    Looks up via ``hermes_cli.gateway.get_env_value`` at call time (not via
    the plugin's own bound import) so tests that patch
    ``gateway_mod.get_env_value`` can suppress ambient env vars.  Matches
    what the legacy connected-platforms check did before this migration.
    r   Nr   r   r   )hermes_cli.gatewaygatewayr   r  r   )r+   gateway_mods     r'   _is_connectedr  i  st     -,,,,,		"	"#5	6	6	<"CCEE 	H&&'788>BEEGG  rE   c                     t          |           S )zHFactory wrapper that constructs MattermostAdapter from a PlatformConfig.)r*   )r+   s    r'   _build_adapterr  ~  s    V$$$rE   c                    |                      ddt          t          t          ddgdt          t
          dddt          t          d	d
           dS )u:   Plugin entry point — called by the Hermes plugin system.r  r  r   r   zpip install aiohttpr  MATTERMOST_ALLOW_ALL_USERSr  u   💬T)r   labeladapter_factorycheck_fnis_connectedrequired_envinstall_hintsetup_fnapply_yaml_config_fnallowed_users_envallow_all_envcron_deliver_env_varstandalone_sender_fnmax_message_lengthemojiallow_update_commandN)register_platformr  r(   r  r  r  r  r   )ctxs    r'   registerr    sh    &."&(:;* # 0426
 . +!E  # # # # #rE   r  )r   rO   r   rO   rk  ru   r  r  r  r   r   rP   r  )r  r  r  r  r   r  )*r  
__future__r   r   r`   loggingr   r   pathlibr   typingr   r   r   r   r	   gateway.configr
   r   gateway.platforms.helpersr   rz  r   r   r   r   	getLoggerr  r    r   r   r  r&  r$  r(   r*   r  r  r  r  r  r  r  rE   r'   <module>r     s#    # " " " " "    				 				       3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 9 9 9 9 9 9            
	8	$	$  
				         $`- `- `- `- `-+ `- `- `-^  $"& E; E; E; E; E; E;Z:E :E :E :ED! ! ! !R   *% % %
% % % % % %rE   