
    ig                        d Z ddl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m	Z	m
Z
mZ  ej        e          ZddlmZmZmZmZ ddlmZ ddlmZmZ d Zded	efd
Zded	efdZ G d de          Zd	efdZ d	efdZ!ddZ"d	efdZ#d Z$dS )u  
IRC Platform Adapter for Hermes Agent.

A plugin-based gateway adapter that connects to an IRC server and relays
messages to/from the Hermes agent.  Zero external dependencies — uses
Python's stdlib asyncio for the IRC protocol.

Configuration in config.yaml::

    gateway:
      platforms:
        irc:
          enabled: true
          extra:
            server: irc.libera.chat
            port: 6697
            nickname: hermes-bot
            channel: "#hermes"
            use_tls: true
            server_password: ""       # optional server password
            nickserv_password: ""     # optional NickServ identification
            allowed_users: []         # empty = allow all, or list of nicks
            max_message_length: 450   # IRC line limit (safe default)

Or via environment variables (overrides config.yaml):
    IRC_SERVER, IRC_PORT, IRC_NICKNAME, IRC_CHANNEL, IRC_USE_TLS,
    IRC_SERVER_PASSWORD, IRC_NICKSERV_PASSWORD
    N)AnyDictListOptional)BasePlatformAdapter
SendResultMessageEventMessageType)SessionSource)PlatformConfigPlatformc                      dS )u>   No-op — kept for backward compatibility with any call sites.N r       B/home/ubuntu/.hermes/hermes-agent/plugins/platforms/irc/adapter.py_ensure_importsr   8   s    Dr   rawreturnc                    d}d}|                      d          r?	 | dd                             dd          \  }} n# t          $ r | dd         }d} Y nw xY wd| v r|                     dd          \  } }|                                 }|r|d         nd}t          |          dk    r
|dd         ng }|r|                    |           |||dS )	ziParse a raw IRC protocol line into components.

    Returns dict with keys: prefix, command, params.
     :   N  :r   )prefixcommandparams)
startswithsplit
ValueErrorlenappend)r   r   trailingpartsr   r   s         r   _parse_irc_messager%   A   s   
 FH
~~c 	abb'--Q//KFCC 	 	 	WFCCC	 s{{		$**XIIKKE'eAhhRGe**q..U122YYbF  hFCCCs   != AAr   c                 D    d| v r|                      d          d         n| S )z2Extract nickname from IRC prefix (nick!user@host).!r   )r   )r   s    r   _extract_nickr(   \   s&    #&&==6<<Qf<r   c                   R    e Zd ZdZ fdZedefd            ZdefdZ	ddZ
	 	 dded	ed
ee         deeeef                  fdZddeddfdZdedeeef         fdZd	ededee         fdZededefd            ZdeddfdZddZdeddfdZdedededededdfdZ xZS )
IRCAdapterzAsync IRC adapter implementing the BasePlatformAdapter interface.

    This class is instantiated by the adapter_factory passed to
    register_platform().
    c                 F   t          d          }t                                          ||           t          |di           pi }t	          j        d          p|                    dd          | _        t          t	          j        d          p|                    dd	                    | _	        t	          j        d
          p|                    dd          | _
        t	          j        d          p|                    dd          | _        t	          j        d          r)t	          j        dd                                          dv n|                    dd          | _        t	          j        d          p|                    dd          | _        t	          j        d          p|                    dd          | _        |                    dg           | _        d | j        D             | _        |                    d          }|=	 ddlm} |                    d          }|r|j        r|j        }n# t*          $ r Y nw xY wt          |pd          | _        d | _        d | _        d | _        | j
        | _        d| _        t7          j                    | _        d S )Nirc)configplatformextra
IRC_SERVERserverr   IRC_PORTporti)  IRC_NICKNAMEnicknamez
hermes-botIRC_CHANNELchannelIRC_USE_TLS)1trueyesuse_tlsTIRC_SERVER_PASSWORDserver_passwordIRC_NICKSERV_PASSWORDnickserv_passwordallowed_usersc                 `    h | ]+}t          |t                    |                                ,S r   )
isinstancestrlower).0us     r   	<setcomp>z&IRCAdapter.__init__.<locals>.<setcomp>   s2    )f)f)fS]^_adSeSe)f!'')))f)f)fr   max_message_lengthr   )platform_registry  F)r   super__init__getattrosgetenvgetr1   intr3   r5   r7   rE   r<   r>   r@   rA   _allowed_users_lowergateway.platform_registryrJ   rI   	Exception_reader_writer
_recv_task_current_nick_registeredasyncioEvent_registration_event)	selfr-   kwargsr.   r/   max_msgrJ   entry	__class__s	           r   rM   zIRCAdapter.__init__l   s|   E??:::,,2 i--H8R1H1H	*--H641H1HII		.11XUYYz<5X5Xy//K599Y3K3K y'',BImR((..004HHH9d++ 	
  "y)>??c599M^`bCcCc!#+B!C!C!iuyyQdfhGiGi $)99_b#A#A)f)fT=O)f)f)f! ))011?GGGGGG)--e44 7U5 7#6G   "%gn"5"5 8<7;26!] #*=??   s   +H; ;
IIr   c                     dS )NIRCr   )r^   s    r   namezIRCAdapter.name   s    ur   c                   K   | j         r| j        s4t                              d           |                     ddd           dS 	 ddlm}m} | j          d| j         } |d	|          s@t                              d
| j        | j                    |                     ddd           dS || _	        n# t          $ r
 d| _	        Y nw xY w	 d}| j        rt          j                    }t          j        t          j        | j         | j        |          d           d{V \  | _        | _        nd# t(          $ rW}t                              d| j         | j        |           |                     dt+          |          d           Y d}~dS d}~ww xY w| j        r#|                     d| j                    d{V  |                     d| j                    d{V  |                     d| j         d           d{V  t          j        |                                           | _        	 t          j        | j                                        d           d{V  nb# t          j        $ rP t                              d           |                                  d{V  |                     ddd           Y dS w xY w| j        r=|                     d| j                    d{V  t          j         d           d{V  |                     d| j                    d{V  | !                                 t          "                    d| j         | j        | j#        | j                   dS )z:Connect to the IRC server, register, and join the channel.z*IRC: server and channel must be configuredconfig_missingz&IRC_SERVER and IRC_CHANNEL must be setF	retryabler   )acquire_scoped_lockrelease_scoped_lockr   r,   z,IRC: %s@%s already in use by another profilelock_conflictz&IRC identity in use by another profileN)sslg      >@)timeoutu&   IRC: failed to connect to %s:%s — %sconnect_failedTzPASS NICK zUSER z 0 * :Hermes AgentzIRC: registration timed outregistration_timeoutz#IRC server did not send RPL_WELCOMEzPRIVMSG NickServ :IDENTIFY    zJOIN z(IRC: connected to %s:%s as %s, joined %s)$r1   r7   loggererror_set_fatal_errorgateway.statusrj   rk   r5   	_lock_keyImportErrorr<   rm   create_default_contextr[   wait_foropen_connectionr3   rV   rW   rU   rD   r>   	_send_rawcreate_task_receive_looprX   r]   waitTimeoutError
disconnectr@   sleep_mark_connectedinforY   )r^   rj   rk   lock_keyssl_ctxes         r   connectzIRCAdapter.connect   sB     { 	$, 	LLEFFF!! 8 "   
 5		"OOOOOOOO+7777H&&uh77 KT]\`\ghhh%%o7_kp%qqqu%DNN 	" 	" 	"!DNNN	"	G| 7466/6/?'TYGLLL0 0 0 * * * * * *&DL$,,  	 	 	LLA4;PTPY[\]]]!!"2CFFd!KKK55555	  	A..!?)=!?!?@@@@@@@@@nn4T]44555555555nnFT]FFFGGGGGGGGG "-d.@.@.B.BCC	"4#;#@#@#B#BDQQQQQQQQQQQ# 	 	 	LL6777//#########!!"8:_ko!ppp55		 ! 	#..!Wt?U!W!WXXXXXXXXX-""""""""" nn3T\33444444444>TYX\XjlplxyyytsF   A#B3 +B3 3CCA$D0 0
F:AFF13I% %AKKNc                   K   t          | dd          r)	 ddlm}  |d| j                   n# t          $ r Y nw xY w|                                  | j        r| j                                        s	 |                     d           d{V  t          j
        d           d{V  n# t          $ r Y nw xY w	 | j                                         | j                                         d{V  n# t          $ r Y nw xY w| j        rV| j                                        s=| j                                         	 | j         d{V  n# t          j        $ r Y nw xY wd| _        d| _        d| _        | j                                         dS )	zQuit and close the connection.rw   Nr   )rk   r,   z QUIT :Hermes Agent shutting downg      ?F)rN   rv   rk   rw   rU   _mark_disconnectedrW   
is_closingr|   r[   r   closewait_closedrX   donecancelCancelledErrorrV   rZ   r]   clear)r^   rk   s     r   r   zIRCAdapter.disconnect   s      4d++ 	>>>>>>##E4>::::   !!!< 
	 7 7 9 9 
	nn%GHHHHHHHHHmC((((((((((   ""$$$l..0000000000    ? 	4?#7#7#9#9 	O""$$$o%%%%%%%%)      &&(((((sB   - 
::25B( (
B54B598C2 2
C?>C?<E
 
EEchat_idcontentreply_tometadatac           	        K   | j         r| j                                         rt          dd          S |}|                     ||          }|D ]u}	 |                     d| d|            d {V  t          j        d           d {V  ?# t          $ r*}t          dt          |                    cY d }~c S d }~ww xY wt          dt          t          t          j
                    dz                      	          S )
NFzNot connected)successrt   PRIVMSG r   g333333?T  )r   
message_id)rW   r   r   _split_messager|   r[   r   rU   rD   rR   time)	r^   r   r   r   r   targetlinesliner   s	            r   sendzIRCAdapter.send  sA      | 	Dt|6688 	De?CCCC##GV44 	? 	?D?nn%@%@%@$%@%@AAAAAAAAAmC(((((((((( ? ? ?!%s1vv>>>>>>>>>>>? $3s49;;;M7N7N3O3OPPPPs   ;B
CB<4C<Cc                 
   K   dS )u&   IRC has no typing indicator — no-op.Nr   )r^   r   r   s      r   send_typingzIRCAdapter.send_typing  s      r   c                 l   K   |                     d          p|                     d          }||rdnddS )N#&groupdm)re   type)r   )r^   r   
is_channels      r   get_chat_infozIRCAdapter.get_chat_info   sJ      '',,G0B0B30G0G
)3GGt
 
 	
r   r   c                 r   |                      |          }t          d| d                    d                    dz   }d|z
  }| j        }g }|                    d          D ]N}|                                s	 |                    d          }t          ||          }	t          |          |	k    r*|                                r|                    |           ndt          |          }}
d	}|
|k    rI|
|z   dz  }t          |d
|                             d                    |	k    r|}|dz   }
n|dz
  }|
|k    I|}|                    dd	|          }||dz  k    r|}|                    |d
|         	                                           ||d
         
                                }5P|r|ndgS )zSplit a long message into IRC-safe chunks.

        IRC has a ~512 byte line limit.  After accounting for protocol
        overhead (``PRIVMSG <target> :``), we split content into chunks.
        r   r   utf-8rr   i  
Tr   r   Nr      r   )_strip_markdownr!   encoderI   r   stripminr"   rfindrstriplstrip)r^   r   r   overhead	max_bytes
user_limitr   	paragraph
para_byteslimitlowhighbestmidsplit_atspaces                   r   r   zIRCAdapter._split_message)  s    &&w//,&,,,33G<<==A(N	,
 t,, 	: 	:I??$$ :&--g66
J	22z??e++ (( 0Y///s9~~TTkk:!+C9TcT?11'::;;uDD"!Ag"Qw Tkk  !Q998q=(($HYyy188::;;;%hii07799	/: & 'uuB4'r   textc                 f   t          j        dd|           } t          j        dd|           } t          j        dd|           } t          j        dd|           } t          j        dd|           } t          j        dd|           } t          j        d	d
|           } t          j        dd|           } | S )z-Convert basic markdown to plain text for IRC.z\*\*(.+?)\*\*z\1z	__(.+?)__z	\*(.+?)\*z(?<!\w)_(.+?)_(?!\w)z`(.+?)`z	```\w*\n?r   z!\[([^\]]*)\]\(([^)]+)\)z\2z\[([^\]]+)\]\(([^)]+)\)z\1 (\2))resub)r   s    r   r   zIRCAdapter._strip_markdownU  s     v&t44vlE400vlE400v-ud;;vj%..vlB--v15$??v0*dCCr   r   c                    K   | j         r| j                                         rdS |dz                       d          }| j                             |           | j                                          d{V  dS )zSend a raw IRC protocol line.Nz
r   )rW   r   r   writedrain)r^   r   encodeds      r   r|   zIRCAdapter._send_rawj  s      | 	t|6688 	F&=((117###l  """""""""""r   c                   K   d}	 | j         r| j                                         s| j                             d           d{V }|sn||z  }d|v r|                    dd          \  }}	 |                    dd          }|                     |           d{V  n2# t          $ r%}t                              d	|           Y d}~nd}~ww xY wd|v | j         r| j                                         nA# t          j
        $ r  t          $ r%}t                              d
|           Y d}~nd}~ww xY w| j        rNt                              d           |                     ddd           |                                  d{V  dS dS # | j        rMt                              d           |                     ddd           |                                  d{V  w w xY w)u6   Main receive loop — reads lines and dispatches them.r   i   Ns   
r   r   replace)errorszIRC: error handling line: %szIRC: receive loop error: %sz*IRC: connection lost, marking disconnectedconnection_lostz"IRC connection closed unexpectedlyTrh   )rV   at_eofreadr   decode_handle_linerU   rs   warningr[   r   rt   is_connectedru   _notify_fatal_error)r^   bufferdatar   decodedr   s         r   r~   zIRCAdapter._receive_loopr  s     	1, Jt|':':'<'< J!\..t44444444 $''#)<<#;#;LD&J"&++gi+"H"H"//8888888888$ J J J'EqIIIIIIIIJ '' , Jt|':':'<'< J % 	 	 	 	; 	; 	;LL6::::::::	;   1KLLL%%&79]im%nnn..000000000001 1t  1KLLL%%&79]im%nnn..00000000001s[   A%C6 ,2B C6 
C)C	C6 	C'C6 5F 6D4D/*F /D44F AG$r   c                 "  K   t          |          }|d         }|d         }|dk    r,|r|d         nd}|                     d|            d{V  dS |dk    r1d	| _        | j                                         |r|d         | _        dS |d
k    r| j                            d          }t          j	        d| j                  }|r2t          |                    d                    dz   }| d| | _        n/| j        | j        k    r| j        dz   | _        n| j        dz   | _        |                     d| j                    d{V  dS |dk    rt          |          dk    rt          |d                   }	|d         }
|d         }|	                                | j                                        k    rdS |                    d          r%|                    d          rd|	 d|dd          }|                    d          rdS |
                    d          p|
                    d          }|r|
n|	}|rdnd}|rd}| j         d| j         d | j         dfD ]h}|                                                    |                                          r-|t          |          d                                         }d	} ni|sdS | j        r8|	                                | j        vrt&                              d!|	           dS |                     ||||	|	"           d{V  |d#k    rSt          |d                                                   | j                                        k    r|r|d         | _        dS dS dS dS )$z$Dispatch a single IRC protocol line.r   r   PINGr   r   zPONG :N001T433_0123456789z_(\d+)$r   __1rp   PRIVMSGrr   r   zACTION z* r      r   r   r   r   Fr   ,z/IRC: ignoring message from unauthorized user %s)r   r   	chat_typeuser_id	user_nameNICK)r%   r|   rZ   r]   setrY   r5   r   r   searchrR   r   r!   r(   rE   r   endswithr   rS   rs   debug_dispatch_message)r^   r   msgr   r   payloadbasesuffix_matchnext_numsender_nickr   r   r   r   r   	addressedr   s                    r   r   zIRCAdapter._handle_line  sS      %%i.X f#)1fQiirG..!3'!3!3444444444F e#D$((*** /%+AY"F e=''66D9Z1CDDL :|11!44559(,%9%9x%9%9""#t}44%)]S%8""%)]T%9"..!=);!=!=>>>>>>>>>F iCKK1$4$4'H66KAYF!9D   ""d&8&>&>&@&@@@ }-- 7$--2G2G 76K66$qt*66 v&&   **3//I63D3DS3I3IJ *;ffG#-74I  	!	"&"4777D<N9Q9Q9Q"&"47779  Fzz||..v||~~>> #CKKLL17799$(	 ! F ( [->->-@-@Ha-a-aNP[\\\((##% )          fs8}!=!=!C!C!E!EI[IaIaIcIc!c!c /%+AY""" !c!c/ /r   r   r   r   c                 f  K   | j         sdS |                     |||||          }t          |t          j        |t          t          t          j                    dz                      t          d          j	        
                                          }|                     |           d{V  dS )z;Build a MessageEvent and hand it to the base class handler.N)r   	chat_namer   r   r   r   datetime)r   message_typesourcer   	timestamp)_message_handlerbuild_sourcer	   r
   TEXTrD   rR   r   
__import__r   nowhandle_message)r^   r   r   r   r   r   r   events           r   r   zIRCAdapter._dispatch_message  s       $ 	F"" # 
 
 $)3ty{{T12233 ,,599;;
 
 
 !!%(((((((((((r   r   N)NNN)__name__
__module____qualname____doc__rM   propertyrD   re   boolr   r   r   r   r   r   r   r   r   r   staticmethodr   r|   r~   r   r   __classcell__)rb   s   @r   r*   r*   e   s[        *3 *3 *3 *3 *3X c    X
At A A A AF )  )  )  )P #'-1Q QQ Q 3-	Q
 4S>*Q Q Q Q.      
3 
4S> 
 
 
 
*(c *(3 *(49 *( *( *( *(X c c    \(#C #D # # # #1 1 1 14W/c W/d W/ W/ W/ W/r)) ) 	)
 ) ) 
) ) ) ) ) ) ) )r   r*   c                  x    t          j        dd          } t          j        dd          }t          | o|          S )uo   Check if IRC is configured.

    Only requires the server and channel — no external pip packages needed.
    r0   r   r6   )rO   rP   r  )r1   r7   s     r   check_requirementsr    s;    
 Y|R((Fir**G "7###r   c                     t          | di           pi }t          j        d          p|                    dd          }t          j        d          p|                    dd          }t	          |o|          S )z=Validate that the platform config has enough info to connect.r/   r0   r1   r   r6   r7   rN   rO   rP   rQ   r  r-   r/   r1   r7   s       r   validate_configr    o    FGR((.BEY|$$?		(B(?(?Fi&&B%))Ir*B*BG"7###r   c                  |   ddl m} m}m}m}m}m}m}m}  |d            |d          }|r |d| d            |dd          sd	S  |d
            |d           t                        | d|pd          }	|	s |d           d	S  |d|	
                                            |dd          }
 |d|
rdnd           |
rdnd} | d| d |d          pd          }|rF	  |dt          t          |                               n5# t          $ r  |d|            Y nw xY w |d          r |dd            | d |d          pd          }|s |d           d	S  |d|
                                            | d |d          pd          }|s |d           d	S  |d|
                                           t                        |d             |d!            |d"d          r | d#d$          }|r |d%|            |d&d          r | d'd$          }|r |d(|           t                        |d)            |d*            |d+            |d,            |d-d          }|r$ |d.d            |d/d            |d0           ni |d.d            | d1 |d/          pd          }|r, |d/|                    d2d                      |d3           n |d/d            |d4           t                        |d5            |d6           d	S )7zInteractive `hermes gateway setup` flow for the IRC platform.

    Lazy-imports ``hermes_cli.setup`` helpers so the plugin stays importable
    in non-CLI contexts (gateway runtime, tests).
    r   )promptprompt_yes_nosave_env_valueget_env_valueprint_header
print_infoprint_warningprint_successrd   r0   z!IRC: already configured (server: )zReconfigure IRC?FNuR   Connect Hermes to an IRC network. Uses Python stdlib — no extra packages needed.z<   Works with Libera.Chat, OFTC, your own ZNC/InspIRCd, etc.z*IRC server hostname (e.g. irc.libera.chat)r   )defaultu)   Server is required — skipping IRC setupzUse TLS (recommended)?Tr8   r:   false66976667zPort (default r2   u   Invalid port — using default zBot nickname (e.g. hermes-bot)r4   u+   Nickname is required — skipping IRC setupu>   Channel to join (e.g. #hermes — comma-separate for multiple)r6   u*   Channel is required — skipping IRC setupu   🔑 Optional authenticationz   Leave blank to skip.z+Configure a server password (PASS command)?zServer password)passwordr=   z"Identify with NickServ on connect?zNickServ passwordr?   u5   🔒 Access control: restrict who can message the botuA      IRC nicks are not authenticated — anyone can claim any nick.zD   For public channels, pair with NickServ-only mode on your networkz,   if you want stronger identity guarantees.z2Allow all users in the channel to talk to the bot?IRC_ALLOW_ALL_USERSIRC_ALLOWED_USERSuD   ⚠️  Open access — any nick in the channel can command the bot.z=Allowed nicks (comma-separated, leave empty to deny everyone)r   zAllowlist configureduJ   No nicks allowed — the bot will ignore all messages until you add nicks.z)IRC configuration saved to ~/.hermes/.envzFRestart the gateway for changes to take effect: hermes gateway restart)hermes_cli.setupr  r  r  r  r  r  r  r  printr   rD   rR   r    r   )r  r  r  r  r  r  r  r  existing_serverr1   r<   default_portr3   r5   r7   r>   nickserv	allow_allalloweds                      r   interactive_setupr*    s9   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 L#mL11O 
IIIIJJJ}/77 	FJcdddJMNNN	GGGV@/J_]_```F ABBBN<000m4d;;GN=G"@&&AAA$066&L62<222MM*<U<U<[Y[\\\D '	LN:s3t99~~6666 	L 	L 	LMJLJJKKKKK	L	z	"	" 'z2&&&v(n--3  H  CDDDN>8>>#3#3444fHm,,2  G  BCCCN='--//222	GGGJ-...J()))}BEJJ C &!2TBBB 	CN0/BBB}95AA >6-=== 	>N2H===	GGGJFGGGJRSSSJUVVVJ=>>>RTYZZI e,f555*B///\]]]],g666&K!M"566<"
 
 
  	eN.R0H0HIIIM01111N.333Jcddd	GGGM=>>>JWXXXXXs   +&D D-,D-c                     t          | di           pi }t          j        d          p|                    dd          }t          j        d          p|                    dd          }t	          |o|          S )z5Check whether IRC is configured (env or config.yaml).r/   r0   r1   r   r6   r7   r  r  s       r   r   r     r  r   c                 ~    |                      ddd t          t          t          g ddt          dddd	d
dd           dS )u:   Plugin entry point — called by the Hermes plugin system.r,   rd   c                      t          |           S r  )r*   )cfgs    r   <lambda>zregister.<locals>.<lambda>  s    JsOO r   )r0   r6   r4   z&No extra packages needed (stdlib only)r"  r!  rK   u   💬FTu  You are chatting via IRC. IRC does not support markdown formatting — use plain text only. Messages are limited to ~450 characters per line (long messages are automatically split). In channels, users address you by prefixing your nick. Keep responses concise and conversational.)re   labeladapter_factorycheck_fnr  r   required_envinstall_hintsetup_fnallowed_users_envallow_all_envrI   emojipii_safeallow_update_commandplatform_hintN)register_platformr  r  r   r*  )ctxs    r   registerr>    sd    33#'!BBB="-+!-      r   r  )%r  r[   loggingrO   r   rm   r   typingr   r   r   r   	getLoggerr  rs   gateway.platforms.baser   r   r	   r
   gateway.sessionr   gateway.configr   r   r   rD   dictr%   r(   r*   r  r  r  r*  r   r>  r   r   r   <module>rF     s   :   				 				 



  , , , , , , , , , , , ,		8	$	$            * ) ) ) ) ) 3 3 3 3 3 3 3 3	 	 	DC DD D D D D6=# =# = = = =\) \) \) \) \)$ \) \) \)F
$D 
$ 
$ 
$ 
$$t $ $ $ $hY hY hY hYV$D $ $ $ $    r   