
    i%                        d 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 ddl	m
Z
 ddlmZ  ej        e          Z e
            dz  Zded	efd
Zdedeeef         d	efdZdeeef         d	ee         fdZdeeef         d	efdZdeeef         d	eeef         fdZd	eeeef                  fdZd	eeeef                  fdZded	eeeef                  fdZd	eeef         fdZdeded	ee         fdZd	efdZdS )a6  
Channel directory -- cached map of reachable channels/contacts per platform.

Built on gateway startup, refreshed periodically (every 5 min), and saved to
~/.hermes/channel_directory.json.  The send_message tool reads this file for
action="list" and for resolving human-friendly channel names to numeric IDs.
    N)datetime)AnyDictListOptional)get_hermes_home)atomic_json_writezchannel_directory.jsonvaluereturnc                 t    |                      d                                                                          S )N#)lstripstriplower)r
   s    A/home/agentuser/.hermes/hermes-agent/gateway/channel_directory.py_normalize_channel_queryr      s,    <<""$$**,,,    platform_namechannelc                     |d         }| dk    r|                     d          rd| S | dk    r#|                     d          r| d|d          dS |S )zHReturn the human-facing target label shown to users for a channel entry.namediscordguildr   typez ())get)r   r   r   s      r   _channel_target_namer      so    6?D	!!gkk'&:&:!4zz	!!gkk&&9&9!,,'&/,,,,Kr   originc                     |                      d          }|sd S |                      d          }|r| d| S t          |          S )Nchat_id	thread_id:r   str)r   r    r!   s      r   _session_entry_idr%   $   sY    jj##G t

;''I (''I'''w<<r   c                    |                      d          p6|                      d          p!t          |                      d                    }|                      d          }|s|S |                      d          pd| }| d| S )N	chat_name	user_namer    r!   
chat_topicztopic z / r#   )r   	base_namer!   topic_labels       r   _session_entry_namer,   .   s    

;''`6::k+B+B`c&**U^J_J_F`F`I

;''I **\**B.By.B.BK))K)))r   adaptersc                    ddl m} i }|                                 D ]y\  }}	 ||j        k    rt	          |          |d<   n||j        k    rt          |          |d<   B# t          $ r+}t          	                    d|j
        |           Y d}~rd}~ww xY wt          h d          }|D ]$}|j
        }||v s||v rt          |          ||<   %t          j                                                    |d}		 t!          t"          |	           n2# t          $ r%}t          	                    d	|           Y d}~nd}~ww xY w|	S )
z
    Build a channel directory from connected platform adapters and session data.

    Returns the directory dict and writes it to DIRECTORY_PATH.
    r   )Platformr   slackz)Channel directory: failed to build %s: %sN>   localwebhook
api_server
updated_at	platformsz&Channel directory: failed to write: %s)gateway.configr/   itemsDISCORD_build_discordSLACK_build_slack	Exceptionloggerwarningr
   	frozenset_build_from_sessionsr   now	isoformatr	   DIRECTORY_PATH)
r-   r/   r6   platformadaptere_SKIP_SESSION_DISCOVERYplat	plat_name	directorys
             r   build_channel_directoryrL   <   s    ('''''13I%^^-- [ ['	[8+++'5g'>'>	)$$X^++%1'%:%:	'" 	[ 	[ 	[NNFXYZZZZZZZZ	[ ((J(J(JKK ? ?J	///9	3I3I3I>>	) lnn..00 I
D.)4444 D D D?CCCCCCCCD s/   ;A
B)!BB8D 
D=D88D=c           	      <   g }t          | dd          }|s|S 	 ddl}n# t          $ r |cY S w xY w|j        D ]B}|j        D ]8}|                    t          |j                  |j        |j        dd           9C|	                    t          d                     |S )z4Enumerate all text channels the Discord bot can see._clientNr   r   )idr   r   r   r   )getattrr   ImportErrorguildstext_channelsappendr$   rO   r   extendrA   )rF   channelsclient_discordr   chs         r   r:   r:   f   s    HWi..F """""      % 	 	BOO"%jj!	     	 OO(33444Os    --c                     t          | dd          pt          | dd          }|st          d          S 	 ddlm} n# t          $ r Y nw xY wt          d          S )z'List Slack channels the bot has joined._appNrN   r0   r   )_send_slack)rP   rA   tools.send_message_toolr\   r=   )rF   rW   r\   s      r   r<   r<      s     Wfd++Pww	4/P/PF -#G,,,7777777     (((s   < 
A	A	c           	         t                      dz  dz  }|                                sg S g }	 t          |d          5 }t          j        |          }ddd           n# 1 swxY w Y   t                      }|                                D ]\  }}|                    d          pi }|                    d          | k    r6t          |          }	|	r|	|v rL|	                    |	           |
                    |	t          |          |                    dd	          |                    d
          d           n3# t          $ r&}
t                              d| |
           Y d}
~
nd}
~
ww xY w|S )z<Pull known channels/contacts from sessions.json origin data.sessionszsessions.jsonutf-8encodingNr   rE   	chat_typedmr!   )rO   r   r   r!   z5Channel directory: failed to read sessions for %s: %s)r   existsopenjsonloadsetr8   r   r%   addrT   r,   r=   r>   debug)r   sessions_pathentriesfdataseen_ids_keysessionr   entry_idrG   s              r   rA   rA      s   #%%
2_DM!! 	G`-'222 	 a9Q<<D	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  55!ZZ\\ 	 	MD'[[**0bFzz*%%66(00H x833LL"""NN+F33K66#ZZ44	     	  ` ` `Lm]^________` Ns;   D> A D>  A$$D> 'A$(CD> >
E.E))E.c                      t                                           sdi dS 	 t          t           d          5 } t          j        |           cddd           S # 1 swxY w Y   dS # t
          $ r di dcY S w xY w)z,Load the cached channel directory from disk.Nr4   r`   ra   )rD   re   rf   rg   rh   r=   )rn   s    r   load_directoryru      s      "" 5"4445.7333 	 q9Q<<	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  5 5 5"444445s4   A$ A
A$ AA$ AA$ $A65A6r   c                   	 t                      }|                    di                               | g           }|sdS t          |          	|D ]P}t          |d                   	k    r
|d         c S t          t          | |                    	k    r
|d         c S Qd	v r	                    dd          \  }}|D ]e}|                    dd                                                                          }||k    r#t          |d                   |k    r
|d         c S f	fd	|D             }t          |          dk    r|d
         d         S dS )a  
    Resolve a human-friendly channel name to a numeric ID.

    Matching strategy (case-insensitive, first match wins):
    - Discord: "bot-home", "#bot-home", "GuildName/bot-home"
    - Telegram: display name or group name
    - Slack: "engineering", "#engineering"
    r6   Nr   rO   /   r    c                 d    g | ],}t          |d                                                  *|-S )r   )r   
startswith).0rY   querys     r   
<listcomp>z(resolve_channel_name.<locals>.<listcomp>   s:    ___b(@F(L(L(W(WX](^(^_r___r   r   )ru   r   r   r   rsplitr   r   len)
r   r   rK   rV   rY   
guild_partch_partr   matchesr}   s
            @r   resolve_channel_namer      s      I}}["--11-DDH t$T**E   #BvJ//588d8OOO#$8$K$KLLPUUUd8OOO V e||#ll322
G 	  	 BFF7B''--//5577E
""'?6
'K'Kw'V'V$x `___H___G
7||qqz$4r   c                     t                      } |                     di           }t          |                                          sdS dg}t	          |                                          D ]\  }}|s	|dk    r3i }g }|D ]X}|                    d          }|r*|                    |g                               |           C|                    |           Yt	          |                                          D ]Y\  }	}
|                    d|	 d           t	          |
d 	          D ](}|                    d
t          ||                      )Z|r@|                    d           |D ](}|                    d
t          ||                      )|                    d           C|                    |	                                 d           |D ]+}|                    d| dt          ||                      ,|                    d           |                    d           |                    d           d
                    |          S )zDFormat the channel directory as a human-readable list for the model.r6   z?No messaging platforms connected or no channels discovered yet.zAvailable messaging targets:
r   r   z	Discord (z):c                     | d         S )Nr    )cs    r   <lambda>z.format_directory_for_display.<locals>.<lambda>  s
    qy r   )keyz
  discord:zDiscord (DMs):ry   r"   z  z1Use these as the "target" parameter when sending.z;Bare platform name (e.g. "telegram") sends to home channel.
)ru   r   anyvaluessortedr8   
setdefaultrT   r   titlejoin)rK   r6   linesrJ   rV   rR   dmsrY   r   
guild_nameguild_channelss              r   format_directory_for_displayr      s     Ik2..Iy!!"" QPP-.E%ioo&7&788  	8 	 	!!&(FC # #w #%%eR0077;;;;JJrNNNN.4V\\^^.D.D U U*
N7777888 5H5HIII U UBLL!S.B9b.Q.Q!S!STTTTU U-... U UBLL!S.B9b.Q.Q!S!STTTTLLLLIOO--000111 U US)SS.B9b.Q.QSSTTTTLL	LLDEEE	LLNOOO99Ur   )__doc__rg   loggingr   typingr   r   r   r   hermes_cli.configr   utilsr	   	getLogger__name__r>   rD   r$   r   r   r%   r,   rL   r:   r<   rA   ru   r   r   r   r   r   <module>r      sv            , , , , , , , , , , , , - - - - - - # # # # # #		8	$	$ ""%==-C -C - - - - d38n     d38n #    *S#X *3 * * * *'d38n 'c3h ' ' ' 'TtDcN3    8)T$sCx.1 ) ) ) )" T#s(^0D    H5S#X 5 5 5 5$ $3 $8C= $ $ $ $N+c + + + + + +r   