
    i3                     $   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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    >/home/ubuntu/.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                 N  K   ddl m} i }|                                 D ]\  }}	 ||j        k    rt	          |          |d<   n#||j        k    rt          |           d{V |d<   H# t          $ r+}t          	                    d|j
        |           Y d}~xd}~ww xY wt          h d          }|D ]$}|j
        }||v s||v rt          |          ||<   %	 ddlm}	 |	                                D ]0}
|
j        |vr%|
j        |vrt          |
j                  ||
j        <   1n# t          $ r Y nw xY w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   Nslackz)Channel directory: failed to build %s: %s>   localwebhook
api_server)platform_registry
updated_at	platformsz&Channel directory: failed to write: %s)gateway.configr/   itemsDISCORD_build_discordSLACK_build_slack	Exceptionloggerwarningr
   	frozenset_build_from_sessionsgateway.platform_registryr4   plugin_entriesr   r   now	isoformatr	   DIRECTORY_PATH)r-   r/   r7   platformadaptere_SKIP_SESSION_DISCOVERYplat	plat_namer4   entry	directorys               r   build_channel_directoryrP   <   s\      ('''''13I%^^-- [ ['	[8+++'5g'>'>	)$$X^+++7+@+@%@%@%@%@%@%@	'" 	[ 	[ 	[NNFXYZZZZZZZZ	[ ((J(J(JKK ? ?J	///9	3I3I3I>>	)??????&5577 	I 	IEz!888UZy=X=X(<UZ(H(H	%*%	I     lnn..00 I
D.)4444 D D D?CCCCCCCCD sC   AA''
B1!BBAD$ $
D10D1E3 3
F"=FF"c           	         g }t          | dd          }|s|S 	 ddl}n# t          $ r |cY S w xY w|j        D ]}|j        D ]8}|                    t          |j                  |j        |j        dd           9t          |dd          pg }|D ]8}|                    t          |j                  |j        |j        dd           9|	                    t          d                     |S )	zGEnumerate all text channels and forum channels the Discord bot can see._clientNr   r   )idr   r   r   forum_channelsforumr   )getattrr   ImportErrorguildstext_channelsappendr$   rS   r   extendrB   )rI   channelsclient_discordr   chforumss          r   r;   r;   p   sK   HWi..F """""      % 	 	BOO"%jj!	       0$77=2 	 	BOO"%jj	     	 OO(33444Os    --c           
      V  K   t          | dd          pi }|st          d          S g }t                      }|                                D ]v\  }}	 d}t	          d          D ](}|                    ddd|           d{V }|                    d	          s2t                              d
||                    dd                      n|                    dg           D ]z}	|	                    d          }
|	                    d          }|
r|r|
|v r5|	                    |
           |
                    |
||	                    d          rdndd           {|                    d          pi                     d          }|s n*D# t          $ r'}t                              d||           Y d}~pd}~ww xY wt          d          D ]V}|                    d          |vr=|
                    |           |	                    |                    d                     W|S )a.  List Slack channels the bot has joined across all workspaces.

    Uses ``users.conversations`` against each workspace's web client. Pulls
    public + private channels the bot is a member of, then merges in DMs
    discovered from session history (IMs aren't useful to enumerate
    proactively).
    _team_clientsNr0      zpublic_channel,private_channelT   )typesexclude_archivedlimitcursorokz=Channel directory: users.conversations not ok for team %s: %serrorunknownr\   rS   r   
is_privateprivater   )rS   r   r   response_metadatanext_cursorz@Channel directory: failed to list Slack channels for team %s: %s)rV   rB   setr9   rangeusers_conversationsr   r?   r@   addrZ   r>   )rI   team_clientsr\   seen_idsteam_idr]   rh   _pageresponser_   cidr   rJ   rN   s                 r   r=   r=      s      7OT::@bL -#G,,,%'HEEH'--// $ $#	$(Fr  !'!;!;:%)!	 "< " "        ||D)) NNW Wi88  
 E",,z266 
 
B&&,,C66&>>D !d !cXoo LL%%%OO! $-/VVL-A-A P		y% %    
 #,,':;;ArFF}UU E 	 	 	NNR   HHHH	 &g.. * *99T??(**OOE"""LL4)))Os   D;F
G F;;G 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   rH   	chat_typedmr!   )rS   r   r   r!   z5Channel directory: failed to read sessions for %s: %s)r   existsopenjsonloadrp   r9   r   r%   rs   rZ   r,   r>   r?   debug)r   sessions_pathentriesfdataru   _keysessionr   entry_idrJ   s              r   rB   rB      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.Nr5   r|   r}   )rG   r   r   r   r   r>   )r   s    r   load_directoryr      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           D ]2}|                    d          |k    r|                    d          c S 3dS )ziReturn the channel ``type`` string (e.g. ``"channel"``, ``"forum"``) for *chat_id*, or *None* if unknown.r7   rS   r   N)r   r   )r   r    rO   r_   s       r   lookup_channel_typer     so      ImmK,,00CC " "66$<<7""66&>>!!! #4r   r   c                 $  
 t                      }|                    di                               | g           }|sdS |                                }|D ]%}|                    d          |k    r
|d         c 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"
    r7   NrS   r   /   r    c                 d    g | ],}t          |d                                                  *|-S )r   )r   
startswith).0r_   querys     r   
<listcomp>z(resolve_channel_name.<locals>.<listcomp>3  s:    ___b(@F(L(L(W(WX](^(^_r___r   r   )r   r   r   r   r   rsplitr   len)r   r   rO   r\   rawr_   
guild_partch_partr   matchesr   s             @r   resolve_channel_namer     s      I}}["--11-DDH t
 **,,C  66$<<3d8OOO  %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.r7   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>U  s
    qy r   )keyz
  discord:zDiscord (DMs):r   r"   z  z1Use these as the "target" parameter when sending.z;Bare platform name (e.g. "telegram") sends to home channel.
)r   r   anyvaluessortedr9   
setdefaultrZ   r   titlejoin)rO   r7   linesrM   r\   rX   dmsr_   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__r   loggingr   typingr   r   r   r   hermes_cli.configr   utilsr	   	getLogger__name__r?   rG   r$   r   r   r%   r,   rP   r;   r=   rB   r   r   r   r   r   r   r   <module>r      s            , , , , , , , , , , , , - - - - - - # # # # # #		8	$	$ ""%==-C -C - - - - d38n     d38n #    *S#X *3 * * * *1DcN 1tCH~ 1 1 1 1h"tDcN3 " " " "J;4S#X#7 ; ; ; ;| T#s(^0D    H5S#X 5 5 5 5s S Xc]    , ,3 ,8C= , , , ,^+c + + + + + +r   