
    
Bj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y)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                 \    | j                  d      j                         j                         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|j                  d      rd| S | dk7  r|j                  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      sZ    6?D	!gkk'&:4&z	!gkk&&9r'&/*!,,Kr   originc                 t    | j                  d      }|sy | j                  d      }|r| d| S t        |      S )Nchat_id	thread_id:r   str)r   r    r!   s      r   _session_entry_idr%   $   sB    jj#G

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

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

;'I**\*Byk.BK[K=))r   adaptersc                 V  K   ddl m} i }| j                         D ]I  \  }}	 ||j                  k(  rt	        |      |d<   n%||j
                  k(  rt        |       d{   |d<   K t        h d      }|D ]%  }|j                  }||v s||v rt        |      ||<   ' 	 ddlm}	 |	j                         D ]B  }
|
j                   |vs|
j                   |vs!t        |
j                         ||
j                   <   D 	 t#        j$                         j'                         |d	}	 t)        t*        |       |S 7 # t        $ r,}t        j                  d|j                  |       Y d}~Jd}~ww xY w# t        $ r Y }w xY w# t        $ r!}t        j                  d
|       Y d}~|S d}~ww xY ww)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+@%@	'"[ ((JK ?JJ	//9	3I3I>	)	??&557 	IEzz!88UZZy=X(<UZZ(H	%**%	I lln..0I
D.)4 E &A 	[NNFXYZZ	[(    D?CCDs   F);D5D3D5%9F)'E- E- $E- :&F)!E< 1F)3D55	E*>!E%F)%E**F)-	E96F)8E99F)<	F&F!F)!F&&F)c           	         g }t        | dd      }|s|S 	 ddl}|j                  D ]  }|j                  D ]?  }|j                  t        |j                        |j                  |j                  dd       A t        |dd      xs g }|D ]?  }|j                  t        |j                        |j                  |j                  dd       A  |j                  t        d             |S # t        $ r |cY S w xY w)	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   s    HWi.F"  %% 	BOO"%%j!	 	  0$7=2 	BOO"%%j	 	* OO(34O3  s   C) )C76C7c           
        K   t        | dd      xs i }|st        d      S g }t               }|j                         D ]  \  }}	 d}t	        d      D ]  }|j                  ddd|       d{   }|j                  d	      s)t        j                  d
||j                  dd              n|j                  dg       D ]f  }	|	j                  d      }
|	j                  d      }|
r|r|
|v r.|j                  |
       |j                  |
||	j                  d      rdndd       h |j                  d      xs i j                  d      }|r n  t        d      D ]G  }|j                  d      |vs|j                  |       |j                  |j                  d             I |S 7 ># t        $ r"}t        j                  d||       Y d}~d}~ww xY ww)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EH'--/ $#	$(Fr !'!;!;:%)!	 "< "   ||D)NNW Wi8
 ",,z26 
B&&,C66&>DdcXo LL%OO! $-/VVL-A	y% 
 #,,':;ArFF}U7$N &g. *99T?(*OOE"LL4)*
 OQ6  	NNR 	sO   AG(F.-F+.CF.F.$G65G+F..	G7GGGGc           	      v   t               dz  dz  }|j                         sg S g }	 t        |d      5 }t        j                  |      }ddd       t               }j                         D ]  \  }}|j                  d      xs i }|j                  d      | k7  r0t        |      }	|	r|	|v rB|j                  |	       |j                  |	t        |      |j                  dd	      |j                  d
      d        	 |S # 1 sw Y   xY w# t        $ r"}
t        j                  d| |
       Y d}
~
|S d}
~
ww xY w)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      s4   #%
2_DM!	G`-'2 	 a99Q<D	  5!ZZ\ 	MD'[[*0bFzz*%6(0Hx83LL"NN+F3K6#ZZ4	 	" N+	  	 $  `Lm]^__N`s/   D D	B5D D
D 	D8D33D8c                      t         j                         sdi dS 	 t        t         d      5 } t        j                  |       cddd       S # 1 sw Y   yxY w# 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      sc      ""445.73 	 q99Q<	  	  	  5"445s.   A A	A AA A A('A(r    c                     t               }|j                  di       j                  | g       D ])  }|j                  d      |k(  s|j                  d      c S  y)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     sT     ImmK,00C "66$<7"66&>!" r   r   c                    t               }|j                  di       j                  | g       }|sy|j                         }|D ]  }|j                  d      |k(  s|d   c S  t        |      }|D ]9  }t        |d         |k(  r|d   c S t        t	        | |            |k(  s4|d   c S  d|v rh|j                  dd      \  }}|D ]N  }|j                  dd      j                         j                         }	|	|k(  s7t        |d         |k(  sI|d   c S  |D cg c]"  }t        |d         j                  |      s!|$ }
}t        |
      dk(  r|
d	   d   S yc c}w )
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    r   )	r   r   r   r   r   rsplitr   
startswithlen)r   r   rO   r\   rawr_   query
guild_partch_partr   matchess              r   resolve_channel_namer     s|     I}}["-11-DH
 **,C 66$<3d8O %T*E  #BvJ/58d8O#$8$KLPUUd8O	 e|#ll32
G 	 BFF7B'--/557E
"'?6
'Kw'V$x	  %_b(@F(L(W(WX](^r_G_
7|qqz$	 `s   "E>Ec                     t               } | j                  di       }t        |j                               sydg}t	        |j                               D ]m  \  }}|s
|dk(  ri }g }|D ]H  }|j                  d      }|r"|j                  |g       j                  |       8|j                  |       J t	        |j                               D ]K  \  }	}
|j                  d|	 d       t	        |
d 	      D ]   }|j                  d
t        ||              " M |r6|j                  d       |D ]   }|j                  d
t        ||              " |j                  d       |j                  |j                          d       |D ]#  }|j                  d| dt        ||              % |j                  d       p |j                  d       |j                  d       dj                  |      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!"P-.E%ioo&78 	8 	!&(FC #w%%eR077;JJrN# /5V\\^.D U*
NyB78 5HI UBLL:.B9b.Q-R!STUU -. UBLL:.B9b.Q-R!STULLLLIOO-.a01 Ur)A.B9b.Q-RSTULL9< 
LLDE	LLN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h"tDcN3 "J;4S#X#7 ;| T#s(^0D H5S#X 5s S Xc] , ,3 ,8C= ,^+c +r   