§
    øžyjÂ ã                   ó¾  — 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Zddlm	Z	 ddl
m
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 dPd
„Z ej        e¦  «        Zddlm Z m!Z!m"Z"m#Z# ddl$m%Z% dZ&dZ'dZ(dZ)dZ*dZ+dZ,dZ-dZ.dZ/dZ0 ed¦  «         1                    ¦   «         Z2d„ Z3dZ4dee5         de5fd„Z6de7dee5         fd„Z8dQd!e5d"e5d#e9deee5                  fd$„Z:d%e7dee7         fd&„Z;d'e7dee7         fd(„Z<d)e7dee7         fd*„Z=dRd,e7de7fd-„Z>dPd.ee?         de7fd/„Z@de,d0œd1e7d2ee7         d3e7d4e7d5ee7         d6eAdee5e7e7f         fd7„ZBd8e7d9e5de7fd:„ZCd;e7dee?         fd<„ZD G d=„ d>e ¦  «        ZEde9fd?„ZFde9fd@„ZGde9fdA„ZHdBe?dCe?dee?         fdD„ZIdee?         fdE„ZJdddFdGœdHe7dIe7dJee7         dKeee7                  dLe9dee7ef         fdM„ZKdSdN„ZLdO„ ZMdS )Tuf  
Buzz Platform Adapter for Hermes Agent.

A plugin-based gateway adapter that connects to a Buzz community relay
(Block's open-source human+agent collaboration platform, built on the
Nostr protocol) and relays messages to/from the Hermes agent.

The adapter does not speak Nostr itself â€” it shells out to the ``buzz``
CLI binary ("JSON in, JSON out") via ``asyncio.create_subprocess_exec``.
Inbound delivery uses a poll loop (the CLI is request/response); see the
"Known limitations" note in the platform docs.

Configuration in config.yaml::

    gateway:
      platforms:
        buzz:
          enabled: true
          extra:
            relay_url: https://mycommunity.communities.buzz.xyz
            channels:                  # channel UUIDs to watch (empty = all joined)
              - ccc2bc1a-7a82-5a8f-8c4e-57a070cbe7cd
            home_channel: ccc2bc1a-7a82-5a8f-8c4e-57a070cbe7cd
            poll_interval: 4           # seconds between poll sweeps
            cli_path: ""               # path to the buzz binary (default: PATH, then ~/bin/buzz)
            credentials_file: ""       # JSON file holding the nsec (fallback for BUZZ_PRIVATE_KEY)
            allowed_users: []          # empty = allow all; entries are hex pubkeys or npubs

Or via environment variables (overrides config.yaml):
    BUZZ_RELAY_URL, BUZZ_CHANNELS, BUZZ_HOME_CHANNEL, BUZZ_POLL_INTERVAL,
    BUZZ_CLI_PATH, BUZZ_CREDENTIALS_FILE, BUZZ_ALLOWED_USERS,
    BUZZ_ALLOW_ALL_USERS

The only secret is BUZZ_PRIVATE_KEY (nsec or hex) â€” it belongs in
``~/.hermes/.env``.  It is passed to the CLI via the subprocess
environment and is never logged.
é    N)ÚOrderedDict)Údatetime)ÚPath)ÚAnyÚDictÚListÚOptionalÚTuple)ÚurlsplitÚ
urlunsplit)ÚUnscopedSecretError)Ú
get_secretc                 óz   — 	 t          | |¦  «        }n$# t          $ r t          j        | ¦  «        }Y nw xY w|�|n|S )a¹  Scope-aware credential read with the default-profile startup fallback.

    Secondary profiles construct their adapters under a profile secret
    scope -- the scope is authoritative and a scoped miss returns ``default``
    (no cross-profile borrow from ``os.environ``, which may hold another
    profile's value). The DEFAULT profile's adapter constructs and sends
    *unscoped* under multiplexing, where a bare ``get_secret`` would raise
    ``UnscopedSecretError`` and crash this path; there ``os.environ`` is that
    profile's own value, so fall back to it. Same pattern as the Slack
    ``SLACK_APP_TOKEN`` read (#59739) and
    ``gateway/platforms/whatsapp_common.py::_get_wsecret``.
    )Ú_scoped_get_secretÚ_UnscopedSecretErrorÚosÚgetenv)ÚnameÚdefaultÚvals      úD/home/ragecks/.hermes/hermes-agent/plugins/platforms/buzz/adapter.pyÚ_get_scoped_secretr   8   sR   € ðÝ   wÑ/Ô/ˆˆøÝð ð ð ÝŒi˜‰oŒoˆˆˆðøøøà�/ˆ3ˆ3 wÐ.s   ‚ “4³4)ÚBasePlatformAdapterÚ
SendResultÚMessageEventÚMessageType)ÚPlatformé	   é2   iô  é   g      @ç      ð?ç      >@g      4@i€„ iD¬  zhermes-buzz-membershipz~/.config/buzzc                  óB  — 	 ddl m}  | S # t          $ rŠ ddl}t	          t
          ¦  «                             d¦  «        }|j                             d|¦  «        }|�|j	        €J ‚|j         
                    |¦  «        }|j	                             |¦  «         |cY S w xY w)a  Import the sibling nostr_auth module in a loader-agnostic way.

    The adapter is imported both as a package module
    (``plugins.platforms.buzz.adapter``) and as a bare single-file module by
    the test plugin loader, where relative imports have no parent package.
    é   )Ú
nostr_authr   Nznostr_auth.pyÚplugin_adapter_buzz_nostr_auth)Ú r%   ÚImportErrorÚimportlib.utilr   Ú__file__Ú	with_nameÚutilÚspec_from_file_locationÚloaderÚmodule_from_specÚexec_module)r%   Ú	importlibÚpathÚspecÚmodules        r   Ú_load_nostr_authr5   s   s¿   € ðØ Ð Ð Ð Ð Ð àÐøÝð ð ð ØÐÐÐå•H‰~Œ~×'Ò'¨Ñ8Ô8ˆØŒ~×5Ò5Ð6VÐX\Ñ]Ô]ˆØÐ D¤KÐ$;Ð$;Ð;Ø”×0Ò0°Ñ6Ô6ˆØŒ×Ò Ñ'Ô'Ð'Øˆˆˆðøøøs   ‚
 ŠBBÂBÚ qpzry9x8gf2tvdw0s3jn54khce6mua7lÚvaluesÚreturnc                 ó†   — d}d}| D ]9}|dz	  }|dz  dz  |z  }t          d¦  «        D ]}|||z	  dz  r||         ndz  }ŒŒ:|S )N)i²Wj;imŽP&iú¡iÝ3B=i³b*r$   é   iÿÿÿr    r   )Úrange)r7   Ú	generatorÚchkÚvalueÚtopÚis         r   Ú_bech32_polymodrA   ‘   sw   € ØL€IØ
€CØð ;ð ;ˆØ�R‰iˆØ�Y‰ 1Ñ$ uÑ,ˆÝ�q‘”ð 	;ð 	;ˆAØ S¨A¡X°¡NÐ:�9˜Q”<�<¸Ñ:ˆCˆCð	;à€Jó    Úhrpc                 ó<   — d„ | D ¦   «         dgz   d„ | D ¦   «         z   S )Nc                 ó2   — g | ]}t          |¦  «        d z	  ‘ŒS )r    ©Úord©Ú.0Úcs     r   ú
<listcomp>z&_bech32_hrp_expand.<locals>.<listcomp>�   s"   € Ð%Ð%Ð%˜A�C�‰FŒF�a‰KÐ%Ð%Ð%rB   r   c                 ó2   — g | ]}t          |¦  «        d z  ‘ŒS )é   rF   rH   s     r   rK   z&_bech32_hrp_expand.<locals>.<listcomp>�   s"   € Ð.HÐ.HÐ.H¸q­s°1©v¬v¸©{Ð.HÐ.HÐ.HrB   © )rC   s    r   Ú_bech32_hrp_expandrO   œ   s1   € Ø%Ð% Ð%Ñ%Ô%¨¨Ñ+Ð.HÐ.HÀCÐ.HÑ.HÔ.HÑHÐHrB   TÚfrombitsÚtobitsÚpadc                 ó&  — d}d}g }d|z  dz
  }| D ]I}|dk     s||z	  r d S ||z  |z  }||z  }||k    r&||z  }|                      ||z	  |z  ¦  «         ||k    °&ŒJ|r!|r|                      |||z
  z  |z  ¦  «         n||k    s|||z
  z  |z  rd S |S )Nr   r$   )Úappend)	ÚdatarP   rQ   rR   ÚaccÚbitsÚretÚmaxvr>   s	            r   Ú_convertbitsrZ       só   € Ø
€CØ€DØ
€CØ�‰K˜1Ñ€DØð -ð -ˆØ�1Š9ˆ9˜ (Ñ*ˆ9Ø�4�4Ø�h‰ %Ñ'ˆØ�ÑˆØ�fŠnˆnØ�F‰NˆDØ�JŠJ˜˜t™ tÑ+Ñ,Ô,Ð,ð �fŠnˆnøð ð Øð 	8Ø�JŠJ˜ ¨¡Ñ.°$Ñ6Ñ7Ô7Ð7øØ	�Ò	Ð	˜s v°¡}Ñ5¸Ñ=Ð	ØˆtØ€JrB   Ú
pubkey_hexc                 ó„  ‡— 	 t                                | ¦  «        }n# t          $ r Y dS w xY wt          |¦  «        dk    rdS t	          |dd¦  «        }|€dS t          d¦  «        |z   }t          |g d¢z   ¦  «        dz  Šˆfd„t          d	¦  «        D ¦   «         }d
d                     d„ ||z   D ¦   «         ¦  «        z   S )u=   Encode a 64-char hex pubkey as an ``npub1â€¦`` bech32 string.Né    é   r    Únpub)r   r   r   r   r   r   r$   c                 ó,   •— g | ]}‰d d |z
  z  z	  dz  ‘ŒS )r    rM   rN   )rI   r@   Úpolymods     €r   rK   zhex_to_npub.<locals>.<listcomp>Â   s+   ø€ Ð@Ð@Ð@°!�˜A  Q¡™KÑ'¨2Ñ-Ð@Ð@Ð@rB   é   Únpub1r'   c              3   ó0   K  — | ]}t           |         V — Œd S ©N)Ú_BECH32_CHARSET)rI   Úds     r   ú	<genexpr>zhex_to_npub.<locals>.<genexpr>Ã   s'   è è € ÐIÐI°A�_¨QÔ/ÐIÐIÐIÐIÐIÐIrB   )	ÚbytesÚfromhexÚ
ValueErrorÚlenrZ   rO   rA   r;   Újoin)r[   ÚrawrU   r7   Úchecksumra   s        @r   Úhex_to_npubrp   µ   så   ø€ ðÝ�mŠm˜JÑ'Ô'ˆˆøÝð ð ð Øˆtˆtðøøøå
ˆ3�x„x�2‚~€~ØˆtÝ˜˜Q Ñ"Ô"€DØ€|ØˆtÝ Ñ'Ô'¨$Ñ.€FÝ˜fÐ'9Ð'9Ð'9Ñ9Ñ:Ô:¸QÑ>€GØ@Ð@Ð@Ð@µu¸Q±x´xÐ@Ñ@Ô@€HØ�R—W’WÐIÐI¸À¹ÐIÑIÔIÑIÔIÑIÐIs   ƒ ž
,«,r_   c                 óØ  — |                       ¦   «                              ¦   «         } |                      d¦  «        sdS | t          d¦  «        d…         }	 d„ |D ¦   «         }n# t          $ r Y dS w xY wt          t          d¦  «        |z   ¦  «        dk    rdS t          |dd…         ddd	¬
¦  «        }|�t          |¦  «        dk    rdS t          |¦  «         	                    ¦   «         S )u=   Decode an ``npub1â€¦`` bech32 string to a 64-char hex pubkey.rc   Nc                 óB   — g | ]}t                                |¦  «        ‘ŒS rN   )rf   ÚindexrH   s     r   rK   znpub_to_hex.<locals>.<listcomp>Í   s&   € Ð<Ð<Ð<¨Q•×%Ò% aÑ(Ô(Ð<Ð<Ð<rB   r_   r$   iúÿÿÿr    r^   F)rR   r]   )
ÚstripÚlowerÚ
startswithrl   rk   rA   rO   rZ   ri   Úhex)r_   Ú	data_partrU   Údecodeds       r   Únpub_to_hexrz   Æ   sú   € à�:Š:‰<Œ<×ÒÑÔ€DØ�?Š?˜7Ñ#Ô#ð ØˆtØ•S˜‘\”\�]�]Ô#€IðØ<Ð<°)Ð<Ñ<Ô<ˆˆøÝð ð ð ØˆtˆtðøøøåÕ)¨&Ñ1Ô1°DÑ8Ñ9Ô9¸QÒ>Ð>ØˆtÝ˜4   œ9 a¨°Ð6Ñ6Ô6€GØ€�#˜g™,œ,¨"Ò,Ð,ØˆtÝ�‰>Œ>×ÒÑÔÐs   ÁA# Á#
A1Á0A1Úrefc                 óÔ   — | pd                      ¦   «                              ¦   «         } | sdS |                      d¦  «        rt          | ¦  «        S t	          j        d| ¦  «        r| S dS )zANormalize a user reference (hex pubkey or npub) to lowercase hex.r'   Nrc   z[0-9a-f]{64})rt   ru   rv   rz   ÚreÚ	fullmatch)r{   s    r   Ú_normalize_user_refr   Ø   sp   € àˆ9�"×
Ò
Ñ
Ô
×
#Ò
#Ñ
%Ô
%€CØð ØˆtØ
‡~‚~�gÑÔð  Ý˜3ÑÔÐÝ	„|�O SÑ)Ô)ð Øˆ
Øˆ4rB   r'   Ú
configuredc                 ó>  — | rFt          | ¦  «                             ¦   «         }|                     ¦   «         rt          |¦  «        ndS t	          j        d¦  «        }|r|S t          j        ¦   «         dz  dz  }|                     ¦   «         rt          |¦  «        ndS )uÄ   Resolve the buzz CLI binary path portably.

    Order: explicit config value â†’ ``buzz`` on PATH â†’ ``~/bin/buzz``.
    Returns "" when nothing is found so callers can raise a config error.
    r'   ÚbuzzÚbin)r   Ú
expanduserÚis_fileÚstrÚshutilÚwhichÚhome)r€   ÚpÚfoundÚfallbacks       r   Ú_resolve_cli_pathr�   è   s”   € ð ð -Ý�ÑÔ×'Ò'Ñ)Ô)ˆØŸš™œÐ,�s�1‰vŒvˆv¨"Ð,ÝŒL˜Ñ Ô €EØð ØˆÝŒy‰{Œ{˜UÑ" VÑ+€HØ$×,Ò,Ñ.Ô.Ð6�3ˆx‰=Œ=ˆ=°BÐ6rB   Úextrac                 óþ  — t          dd¦  «                             ¦   «         }|r|S t          j        dd¦  «                             ¦   «         p| pi                      dd¦  «        }|r#t          |¦  «                             ¦   «         g}n;	 t          t           	                    d¦  «        ¦  «        }n# t          $ r g }Y nw xY w|D ]´}	 t          j        |                     d¬¦  «        ¦  «        }n# t          t          f$ r Y Œ?w xY wt          |t           ¦  «        sŒYdD ]X}|                     |¦  «        }t          |t"          ¦  «        r,|                     ¦   «         r|                     ¦   «         c c S ŒYŒµdS )	zhResolve the Nostr private key: env first, then a credentials JSON.

    NEVER log the return value.
    ÚBUZZ_PRIVATE_KEYr'   ÚBUZZ_CREDENTIALS_FILEÚcredentials_filez*credentials*.jsonúutf-8)Úencoding)ÚnsecÚprivate_key_hexÚprivate_key)r   rt   r   r   Úgetr   r„   ÚsortedÚ_DEFAULT_CREDENTIALS_DIRÚglobÚOSErrorÚjsonÚloadsÚ	read_textrk   Ú
isinstanceÚdictr†   )rŽ   Úkeyr€   Ú
candidatesr2   rU   Úfieldr>   s           r   Ú_resolve_private_keyr¥   ø   s§  € õ
 Ð/°Ñ
4Ô
4×
:Ò
:Ñ
<Ô
<€CØ
ð Øˆ
Ý”Ð2°BÑ7Ô7×=Ò=Ñ?Ô?ÐlÀEÀKÈR×CTÒCTÐUgÐikÑClÔCl€JØð Ý˜:Ñ&Ô&×1Ò1Ñ3Ô3Ð4ˆ
ˆ
ð	ÝÕ 8× =Ò =Ð>RÑ SÔ SÑTÔTˆJˆJøÝð 	ð 	ð 	ØˆJˆJˆJð	øøøàð 
%ð 
%ˆð	Ý”:˜dŸnšn°g˜nÑ>Ô>Ñ?Ô?ˆDˆDøÝ�Ð$ð 	ð 	ð 	ØˆHð	øøøå˜$¥Ñ%Ô%ð 	ØØ?ð 	%ð 	%ˆEØ—H’H˜U‘O”OˆEÝ˜%¥Ñ%Ô%ð %¨%¯+ª+©-¬-ð %Ø—{’{‘}”}Ð$Ð$Ð$Ð$Ð$øð	%ð ˆ2s$   Â'B4 Â4CÃCÃ(C4Ã4DÄD)Ú
input_textÚtimeoutÚcli_pathÚargsÚ	relay_urlr—   r¦   r§   c             ƒ   óþ  K  — t           j                             ¦   «         }||d<   ||d<   t          j        | g|¢R |�t          j        j        nt          j        j        t          j        j        t          j        j        |dœŽƒ d{V —†}	 t          j        | 	                    |�| 
                    d¦  «        nd¦  «        |¬¦  «        ƒ d{V —†\  }}	nn# t          j        $ r\ |                     ¦   «          |                     ¦   «         ƒ d{V —† ddt          j        d	d
|r|d         nd› d|› d�dœ¦  «        fcY S w xY w|j        �|j        nd|                     dd¬¦  «        |	                     dd¬¦  «        fS )uþ   Run the buzz CLI with an argument list (never a shell) and return
    ``(returncode, stdout, stderr)``.

    The private key travels via the subprocess environment only â€” it never
    appears in argv, so process listings and error logs stay clean.
    ÚBUZZ_RELAY_URLr�   N)ÚstdinÚstdoutÚstderrÚenvr“   ©r§   é|   r'   r§   zbuzz r   z timed out after Ús)ÚerrorÚmessageé   Úreplace)Úerrors)r   ÚenvironÚcopyÚasyncioÚcreate_subprocess_execÚ
subprocessÚPIPEÚDEVNULLÚwait_forÚcommunicateÚencodeÚTimeoutErrorÚkillÚwaitr�   ÚdumpsÚ
returncodeÚdecode)
r¨   r©   rª   r—   r¦   r§   r°   Úprocr®   r¯   s
             r   Ú
_exec_buzzrÊ     s  è è € õ Œ*�/Š/Ñ
Ô
€CØ%€CÐÑØ)€CÐÑÝÔ/Øðà	ðð ð *4Ð)?�gÔ Ô%Ð%ÅWÔEWÔE_ÝÔ!Ô&ÝÔ!Ô&Øðð ð ð ð ð ð ð ð €DðBÝ&Ô/Ø×Ò¸:Ð;Q˜Z×.Ò.¨wÑ7Ô7Ð7ÐW[Ñ\Ô\Øð 
ñ  
ô  
ð 
ð 
ð 
ð 
ð 
ð 
‰ˆ��øõ Ôð Bð Bð BØ�	Š	‰ŒˆØ�iŠi‰kŒkÐÐÐÐÐÐÐØ�B�œ
¨YÐCÐVZÐKbÈ4ÐPQÌ7È7Ð`bÐCÐCÐu|ÐCÐCÐCð  $Að  $Añ  Bô  Bð  Bð  	Bð  	Bð  	BðBøøøð
  œ?Ð6ˆŒˆ¸AØ�Š�g iˆÑ0Ô0Ø�Š�g iˆÑ0Ô0ðð s   Â
A	C ÃA(D?Ä>D?r¯   rÇ   c                 ó&  — | pd                      ¦   «         }	 t          j        |¦  «        }t          |t          ¦  «        r:|                     d¦  «        r%|                     dd¦  «        › d|d         › d|› d�S n# t          $ r Y nw xY w|pd|› �S )zØExtract the human-readable message from the CLI's JSON error contract.

    stderr is ``{"error": "<category>", "message": "<detail>"}`` on failure;
    fall back to the raw (stripped) stderr when it isn't JSON.
    r'   rµ   r´   z: z (exit ú)zbuzz CLI failed with exit code )rt   r�   rž   r    r¡   r˜   rk   )r¯   rÇ   ÚtextrU   s       r   Ú_cli_error_messagerÎ   @  sº   € ð ˆL�b×ÒÑ!Ô!€DðÝŒz˜$ÑÔˆÝ�d�DÑ!Ô!ð 	Z d§h¢h¨yÑ&9Ô&9ð 	ZØ—h’h˜w¨Ñ0Ô0ÐYÐY°D¸´OÐYÐYÈJÐYÐYÐYÐYøøÝð ð ð ØˆðøøøàÐAÐA°ZÐAÐAÐAs   ˜A"A< Á<
B	ÂB	r®   c                 óœ   — 	 t          j        | pd¦  «        }n# t          $ r g cY S w xY wt          |t          ¦  «        sg S d„ |D ¦   «         S )z8Parse CLI stdout expected to be a JSON array of objects.z[]c                 ó<   — g | ]}t          |t          ¦  «        ¯|‘ŒS rN   )r    r¡   )rI   Úitems     r   rK   z$_parse_json_list.<locals>.<listcomp>X  s'   € Ð<Ð<Ð<�T¥Z°µdÑ%;Ô%;Ð<ˆDÐ<Ð<Ð<rB   )r�   rž   rk   r    Úlist)r®   rU   s     r   Ú_parse_json_listrÓ   P  sk   € ðÝŒz˜&˜. DÑ)Ô)ˆˆøÝð ð ð Øˆ	ˆ	ˆ	ðøøøå�d�DÑ!Ô!ð Øˆ	Ø<Ð<˜TÐ<Ñ<Ô<Ð<s   ‚ ™(§(c                   ó@  ‡ — e Zd ZdZˆ fd„Zedefd„¦   «         Zddœdee         de	e         de
eeef         fd	„Zd
dœdedefd„Zd>d„Z	 	 d?dedede	e         de	eeef                  def
d„Zd@deddfd„Zdedededefd„Z	 	 	 dAdedede	e         de	e         de	eeef                  defd„Zdedeeef         fd„Zdefd„Zdefd„Zd>d„Zded eddfd!„Zdeee	e         f         fd"„Zd#eee	e         f         d$eddfd%„Zd>d&„Zd>d'„Z d ed(eddfd)„Z!d*eddfd+„Z"d eddfd,„Z#d ed-ed$eddfd.„Z$d edefd/„Z%d ed$edefd0„Z&d ed-ed$eddfd1„Z'dedefd2„Z(dedefd3„Z)d4edefd5„Z*e+d-eddfd6„¦   «         Z,d ed7eddfd8„Z-d9eded(ed:ed;eded<eddfd=„Z.ˆ xZ/S )BÚBuzzAdapterz”Poll-based Buzz adapter implementing the BasePlatformAdapter interface.

    Instantiated by the adapter_factory passed to register_platform().
    c                 ó|  •‡— t          d¦  «        }t          ¦   «                              ||¬¦  «         t          |di ¦  «        pi }|| _        t          j        d¦  «        p|                     dd¦  «                             ¦   «         | _	        t          t          j        dd¦  «                             ¦   «         p$t          |                     dd¦  «        pd¦  «        ¦  «        | _        t          j        d	¦  «        p|                     d
g ¦  «        }t          |t          ¦  «        r|                     d¦  «        }d„ |D ¦   «         | _        t          j        d¦  «        p$t          |                     dd¦  «        pd¦  «                             ¦   «         | _        	 t#          t          j        d¦  «        p|                     dt$          ¦  «        ¦  «        }n# t&          t(          f$ r
 t$          }Y nw xY wt+          t,          |¦  «        | _        t          j        d¦  «        }|€|                     dd¦  «        }n|}t          |¦  «                             ¦   «                              ¦   «         dv| _        t          j        d¦  «        p$t          |                     dd¦  «        pd¦  «                             ¦   «                              ¦   «         }	|	dv r|	nd| _        t          j        d¦  «        p|                     dg ¦  «        }
t          |
t          ¦  «        r|
                     d¦  «        }
ˆfd„|
D ¦   «         | _        d| _        d| _        d| _        d| _        d | _         d | _!        d | _"        d| _#        d| _$        d | _%        i | _&        i | _'        i | _(        i | _)        d| _*        d S )Nr‚   )ÚconfigÚplatformrŽ   r¬   rª   r'   ÚBUZZ_CLI_PATHr¨   ÚBUZZ_CHANNELSÚchannelsÚ,c                 óˆ   — g | ]?}t          |t          ¦  «        ¯|                     ¦   «         ¯+|                     ¦   «         ‘Œ@S rN   )r    r†   rt   rH   s     r   rK   z(BuzzAdapter.__init__.<locals>.<listcomp>v  sC   € Ð#hÐ#hÐ#h°!ÅzÐRSÕUXÑGYÔGYÐ#hÐ^_×^eÒ^eÑ^gÔ^gÐ#h A§G¢G¡I¤IÐ#hÐ#hÐ#hrB   ÚBUZZ_HOME_CHANNELÚhome_channelÚBUZZ_POLL_INTERVALÚpoll_intervalÚBUZZ_REQUIRE_MENTIONÚrequire_mentionT)ÚfalseÚ0ÚnoÚoffÚBUZZ_TRANSPORTÚ	transportÚauto)rê   Ú	websocketÚpollÚBUZZ_ALLOWED_USERSÚallowed_usersc                 ó`   •— h | ]*}t          |t          ¦  «        ¯t          |¦  «        xŠ¯(‰’Œ+S rN   )r    r†   r   )rI   ÚentryÚ
normalizeds     €r   ú	<setcomp>z'BuzzAdapter.__init__.<locals>.<setcomp>˜  sQ   ø€ ð &
ð &
ð &
àÝ˜%¥Ñ%Ô%ð&
õ :MÈUÑ9SÔ9SÐ+S¨:ð&
Øð&
ð &
ð &
rB   Fr   )+r   ÚsuperÚ__init__ÚgetattrÚ_extrar   r   r˜   rt   rª   r�   r†   r¨   r    ÚsplitrÛ   rß   ÚfloatÚ_DEFAULT_POLL_INTERVALÚ	TypeErrorrk   ÚmaxÚ_MIN_POLL_INTERVALrá   ru   rã   ré   Ú_allowed_pubkeysÚ_private_keyÚ_self_pubkeyÚ
_self_npubÚ_display_nameÚ
_poll_taskÚ_ws_taskÚ	_ws_readyÚ
_ws_activeÚ_membership_sinceÚ	_lock_keyÚ_channel_stateÚ_channel_namesÚ_channel_metaÚ_user_namesÚ_poll_count)Úselfr×   ÚkwargsrØ   rŽ   Úraw_channelsÚintervalÚ_rm_rawÚ_rm_cfgÚ
_transportÚraw_allowedrñ   Ú	__class__s              @€r   rô   zBuzzAdapter.__init__e  s�  øø€ Ý˜FÑ#Ô#ˆÝ‰Œ×Ò °ÐÑ:Ô:Ð:å˜ ¨Ñ,Ô,Ð2°ˆØˆŒõ œ)Ð$4Ñ5Ô5ÐS¸¿ºÀ;ÐPRÑ9SÔ9S×ZÒZÑ\Ô\ˆŒÝ)ÝŒI�o rÑ*Ô*×0Ò0Ñ2Ô2ÐZµc¸%¿)º)ÀJÐPRÑ:SÔ:SÐ:YÐWYÑ6ZÔ6Zñ
ô 
ˆŒõ
 ”y Ñ1Ô1ÐN°U·Y²Y¸zÈ2Ñ5NÔ5NˆÝ�l¥CÑ(Ô(ð 	3Ø'×-Ò-¨cÑ2Ô2ˆLØ#hÐ#h°|Ð#hÑ#hÔ#hˆŒåœYÐ':Ñ;Ô;Ðg½sÀ5Ç9Â9È^Ð]_ÑC`ÔC`ÐCfÐdfÑ?gÔ?g×nÒnÑpÔpˆÔð	.Ý�RœYÐ';Ñ<Ô<ÐrÀÇ	Â	È/Õ[qÑ@rÔ@rÑsÔsˆHˆHøÝ�:Ð&ð 	.ð 	.ð 	.Ý-ˆHˆHˆHð	.øøøå Õ!3°XÑ>Ô>ˆÔõ ”)Ð2Ñ3Ô3ˆØˆ?Ø—i’iÐ 1°4Ñ8Ô8ˆGˆGàˆGÝ" 7™|œ|×1Ò1Ñ3Ô3×9Ò9Ñ;Ô;ÐC^Ð^ˆÔõ ŒIÐ&Ñ'Ô'ÐX­3¨u¯yªy¸ÀfÑ/MÔ/MÐ/WÐQWÑ+XÔ+Xß
Š%‰'Œ'—%’%‘'”'ð 	ð (2Ð5RÐ'RÐ'R˜˜ÐX^ˆŒõ ”iÐ 4Ñ5Ô5ÐW¸¿ºÀ?ÐTVÑ9WÔ9WˆÝ�k¥3Ñ'Ô'ð 	1Ø%×+Ò+¨CÑ0Ô0ˆKð&
ð &
ð &
ð &
à$ð&
ñ &
ô &
ˆÔð "$ˆÔð "$ˆÔØ!ˆŒØ"$ˆÔð 37ˆŒØ04ˆŒØ26ˆŒØˆŒØ!"ˆÔØ(,ˆŒà/1ˆÔØ.0ˆÔð /1ˆÔØ+-ˆÔØˆÔÐÐs   Æ$<G! Ç!G<Ç;G<r8   c                 ó   — dS )NÚBuzzrN   )r  s    r   r   zBuzzAdapter.name¸  s   € àˆvrB   N©r¦   r©   r¦   c             ƒ   ó˜   K  — | j         st          | j        ¦  «        | _         t          | j        || j        | j         |¬¦  «        ƒ d {V —†S )N©rª   r—   r¦   )rþ   r¥   rö   rÊ   r¨   rª   )r  r©   r¦   s      r   Ú_run_clizBuzzAdapter._run_cli¾  so   è è € ØÔ ð 	BÝ 4°T´[Ñ AÔ AˆDÔÝØŒMØØ”nØÔ)Ø!ð
ñ 
ô 
ð 
ð 
ð 
ð 
ð 
ð 
ð 	
rB   F)Úis_reconnectr  c          
   ƒ   ó8
  K  — | j         s4t                               d¦  «         |                      ddd¬¦  «         dS | j        s4t                               d¦  «         |                      ddd¬¦  «         dS t          | j        ¦  «        | _        | j        s4t                               d	¦  «         |                      dd
d¬¦  «         dS |                      ddg¦  «        ƒ d{V —†\  }}}|dk    rOt          ||¦  «        }t                               d| j         |¦  «         |                      d||dk    ¬¦  «         dS t          |¦  «        }|r|d                              d¦  «        s4t                               d¦  «         |                      ddd¬¦  «         dS t          |d         d         ¦  «                             ¦   «         | _        t          |d                              d¦  «        pd¦  «                             ¦   «         | _        t#          | j        ¦  «        pd| _        	 ddlm} | j         › d| j        › �} |d|¦  «        sHt                               d| j        dd…         | j         ¦  «         |                      ddd¬¦  «         dS || _        n# t,          $ r
 d| _        Y nw xY w|                      dd g¦  «        ƒ d{V —†\  }}}|dk    rIt          ||¦  «        }t                               d!|¦  «         |                      d||dk    ¬¦  «         dS t          |¦  «        }	d"„ |	D ¦   «         | _        |	D ]4}
|
                     d#¦  «        r|
| j        t          |
d#         ¦  «        <   Œ5| j        pt5          | j        ¦  «        }|s4t                               d$¦  «         |                      dd%d¬¦  «         dS |D ]}|                      |d&¬'¦  «        ƒ d{V —† Œ |                      d¬(¦  «        ƒ d{V —† d)}| j        d*v r\|                      ¦   «         ƒ d{V —†rd+}n?| j        d+k    r4|                      d,d-d¬¦  «         |                      ¦   «         ƒ d{V —† dS |d)k    r+tA          j!        |  "                    ¦   «         ¦  «        | _#        |  $                    ¦   «          t           %                    d.| j         | j        p| j        dd/…         tM          | j'        ¦  «        ||d+k    rdnd0| j(        d1›d2�¦  «         dS )3z?Verify relay credentials, seed high-water marks, start polling.z"Buzz: relay URL must be configuredÚconfig_missingzBUZZ_RELAY_URL must be setF)Ú	retryablezIBuzz: buzz CLI binary not found (set BUZZ_CLI_PATH or put 'buzz' on PATH)Úcli_missingzbuzz CLI binary not foundzABuzz: no private key (set BUZZ_PRIVATE_KEY or a credentials file)zBUZZ_PRIVATE_KEY must be setÚusersr˜   Nr   u0   Buzz: failed to fetch own profile from %s â€” %sÚconnect_failedé   ÚpubkeyuP   Buzz: 'users get' returned no profile â€” is the key a member of this community?z"buzz users get returned no profileTÚdisplay_namer'   )Úacquire_scoped_lockú:r‚   u<   Buzz: identity %sâ€¦ on %s already in use by another profiler^   Úlock_conflictz'Buzz identity in use by another profilerÛ   rÒ   u$   Buzz: failed to list channels â€” %sc                 óè   — i | ]o}|                      d ¦  «        ¯t          |                      d ¦  «        ¦  «        t          |                      d¦  «        p|                      d ¦  «        ¦  «        “ŒpS )Ú
channel_idr   )r˜   r†   )rI   Úchs     r   ú
<dictcomp>z'BuzzAdapter.connect.<locals>.<dictcomp>	  ss   € ð 
ð 
ð 
àØ�vŠv�lÑ#Ô#ð
Ý�—’�|Ñ$Ô$Ñ%Ô%¥s¨2¯6ª6°&©>¬>Ð+Q¸R¿VºVÀLÑ=QÔ=QÑ'RÔ'Rð
ð 
ð 
rB   r*  zFBuzz: no channels to watch (configure BUZZ_CHANNELS or join a channel)zno Buzz channels to watchÚgroup©Ú	chat_type©Úseedrì   )rê   rë   rë   Úws_auth_failedzCBuzz WebSocket transport did not authenticate (transport=websocket)z<Buzz: connected to %s as %s, watching %d channel(s) via %s%sé   z, poll interval z.1fr³   ))rª   Úloggerr´   Ú_set_fatal_errorr¨   r¥   rö   rþ   r  rÎ   rÓ   r˜   r†   ru   rÿ   rt   r  rp   r   Úgateway.statusr&  r  r(   r	  r
  rÛ   rÒ   Ú_seed_channelÚ_discover_dmsré   Ú_start_websocketÚ
disconnectr»   Úcreate_taskÚ
_poll_loopr  Ú_mark_connectedÚinforl   r  rá   )r  r  ÚcodeÚoutÚerrrµ   Úprofilesr&  Úlock_keyÚlistedr+  Úwatchr*  Útransport_useds                 r   ÚconnectzBuzzAdapter.connectË  sò  è è € àŒ~ð 	Ý�LŠLÐ=Ñ>Ô>Ð>Ø×!Ò!Ð"2Ð4PÐ\aÐ!ÑbÔbÐbØ�5ØŒ}ð 	Ý�LŠLÐdÑeÔeÐeØ×!Ò! -Ð1LÐX]Ð!Ñ^Ô^Ð^Ø�5Ý0°´Ñ=Ô=ˆÔØÔ ð 	Ý�LŠLÐ\Ñ]Ô]Ð]Ø×!Ò!Ð"2Ð4RÐ^cÐ!ÑdÔdÐdØ�5ð  $Ÿ}š}¨g°uÐ-=Ñ>Ô>Ð>Ð>Ð>Ð>Ð>Ð>‰ˆˆc�3Ø�1Š9ˆ9Ý(¨¨dÑ3Ô3ˆGÝ�LŠLÐKÈTÌ^Ð]dÑeÔeÐeØ×!Ò!Ð"2°GÀtÈqÂyÐ!ÑQÔQÐQØ�5Ý# CÑ(Ô(ˆØð 	˜x¨œ{Ÿš¨xÑ8Ô8ð 	Ý�LŠLÐkÑlÔlÐlØ×!Ò!Ð"2Ð4XÐdhÐ!ÑiÔiÐiØ�5Ý ¨¤¨HÔ 5Ñ6Ô6×<Ò<Ñ>Ô>ˆÔÝ  ¨!¤§¢°Ñ!@Ô!@Ð!FÀBÑGÔG×MÒMÑOÔOˆÔÝ% dÔ&7Ñ8Ô8Ð>¸BˆŒð
	"Ø:Ð:Ð:Ð:Ð:Ð:àœ.Ð>Ð>¨4Ô+<Ð>Ð>ˆHØ&Ð& v¨xÑ8Ô8ð 	Ý—’ØRØÔ% b q bÔ)Ø”Nñô ð ð
 ×%Ò%Ø#Ð%NÐZ_ð &ñ ô ð ð �uØ%ˆDŒNˆNøÝð 	"ð 	"ð 	"Ø!ˆDŒNˆNˆNð	"øøøð  $Ÿ}š}¨j¸&Ð-AÑBÔBÐBÐBÐBÐBÐBÐB‰ˆˆc�3Ø�1Š9ˆ9Ý(¨¨dÑ3Ô3ˆGÝ�LŠLÐ?ÀÑIÔIÐIØ×!Ò!Ð"2°GÀtÈqÂyÐ!ÑQÔQÐQØ�5Ý! #Ñ&Ô&ˆð
ð 
àð
ñ 
ô 
ˆÔð
 ð 	?ð 	?ˆBØ�vŠv�lÑ#Ô#ð ?Ø<>�Ô"¥3 r¨,Ô'7Ñ#8Ô#8Ñ9øØ”Ð:¥ dÔ&9Ñ!:Ô!:ˆØð 	Ý�LŠLÐaÑbÔbÐbØ×!Ò!Ð"2Ð4OÐ[`Ð!ÑaÔaÐaØ�5ð  ð 	Dð 	DˆJØ×$Ò$ Z¸7Ð$ÑCÔCÐCÐCÐCÐCÐCÐCÐCÐCØ× Ò  dÐ Ñ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð+ð  ˆØŒ>Ð2Ð2Ð2Ø×*Ò*Ñ,Ô,Ð,Ð,Ð,Ð,Ð,Ð,ð 	Ø!,��Ø” ;Ò.Ð.Ø×%Ò%Ø$ØYØ"ð &ñ ô ð ð
 —o’oÑ'Ô'Ð'Ð'Ð'Ð'Ð'Ð'Ð'Ø�uØ˜VÒ#Ð#Ý%Ô1°$·/²/Ñ2CÔ2CÑDÔDˆDŒOØ×ÒÑÔÐÝ�ŠØJØŒNØÔÐ6 $¤/°#°2°#Ô"6Ý�Ô#Ñ$Ô$ØØ  KÒ/Ð/ˆBˆBÐ5aÈÔHZÐ5aÐ5aÐ5aÐ5añ	
ô 	
ð 	
ð ˆts   È2A)J% ÊJ% Ê%J9Ê8J9c              ƒ   óh  K  — |                       ¦   «          t          | dd¦  «        }|r+	 ddlm}  |d|¦  «         n# t          $ r Y nw xY wd| _        d| _        | j        rV| j                             ¦   «         s=| j         	                    ¦   «          	 | j        ƒ d{V —† n# t          j        $ r Y nw xY wd| _        | j        rV| j                             ¦   «         s=| j         	                    ¦   «          	 | j        ƒ d{V —† n# t          j        $ r Y nw xY wd| _        i | _        d| _        dS )z2Stop the inbound transport and drop runtime state.r  Nr   )Úrelease_scoped_lockr‚   F)Ú_mark_disconnectedrõ   r6  rI  Ú	Exceptionr  r  r  ÚdoneÚcancelr»   ÚCancelledErrorr  r  r  )r  rC  rI  s      r   r:  zBuzzAdapter.disconnect:  s   è è € à×ÒÑ!Ô!Ð!Ý˜4 ¨dÑ3Ô3ˆØð 	"ðØ>Ð>Ð>Ð>Ð>Ð>à#Ð# F¨HÑ5Ô5Ð5Ð5øÝð ð ð Ø�ðøøøà!ˆDŒNØˆŒØŒ=ð 	 ¤×!3Ò!3Ñ!5Ô!5ð 	ØŒM× Ò Ñ"Ô"Ð"ðØ”mÐ#Ð#Ð#Ð#Ð#Ð#Ð#Ð#øÝÔ)ð ð ð Ø�ðøøøàˆŒØŒ?ð 	 4¤?×#7Ò#7Ñ#9Ô#9ð 	ØŒO×"Ò"Ñ$Ô$Ð$ðØ”oÐ%Ð%Ð%Ð%Ð%Ð%Ð%Ð%øÝÔ)ð ð ð Ø�ðøøøàˆŒØ ˆÔØˆÔÐÐs3   «> ¾
AÁ
AÂB$ Â$B6Â5B6Ã:D ÄDÄDÚchat_idÚcontentÚreply_toÚmetadatac              ƒ   ó´  K  — |st          dd¬¦  «        S dddt          |¦  «        ddg}|p|pi                      d	¦  «        }|r|d
t          |¦  «        gz  }|                      ||¬¦  «        ƒ d {V —†\  }}}	|dk    r$t          dt	          |	|¦  «        |dk    ¬¦  «        S 	 t          j        |pd¦  «        }
n# t          $ r i }
Y nw xY w|
                     d¦  «        }|r0|                      t          |¦  «        t          |¦  «        ¦  «         t          t          |
                     dd¦  «        ¦  «        |rt          |¦  «        nd |
¬¦  «        S )NFzEmpty message)Úsuccessr´   ÚmessagesÚsendú	--channelú	--contentÚ-Ú	thread_idú
--reply-tor  r   r#  ©rT  r´   r  ú{}Úevent_idÚacceptedT©rT  Ú
message_idÚraw_response)
r   r†   r˜   r  rÎ   r�   rž   rk   Ú
_mark_seenÚbool)r  rO  rP  rQ  rR  r©   Úreply_targetr?  r@  rA  rU   r^  s               r   rV  zBuzzAdapter.sendZ  s™  è è € ð ð 	DÝ e°?ÐCÑCÔCÐCØ˜F Kµ°W±´¸{ÈCÐPˆØÐD H N°×#7Ò#7¸Ñ#DÔ#DˆØð 	6Ø�\¥3 |Ñ#4Ô#4Ð5Ñ5ˆDØ#Ÿ}š}¨T¸g˜}ÑFÔFÐFÐFÐFÐFÐFÐF‰ˆˆc�3Ø�1Š9ˆ9ÝØÝ(¨¨dÑ3Ô3Ø !š)ðñ ô ð ð
	Ý”:˜c˜k TÑ*Ô*ˆDˆDøÝð 	ð 	ð 	ØˆDˆDˆDð	øøøà—8’8˜JÑ'Ô'ˆØð 	9ð �OŠO�C ™LœL­#¨h©-¬-Ñ8Ô8Ð8ÝÝ˜Ÿš *¨dÑ3Ô3Ñ4Ô4Ø(0Ð:•s˜8‘}”}�}°dØð
ñ 
ô 
ð 	
s   Â&B= Â=CÃCc              ƒ   ó
   K  — dS )u+   Buzz has no typing indicator API â€” no-op.NrN   )r  rO  rR  s      r   Úsend_typingzBuzzAdapter.send_typing}  s   è è € àˆrB   ra  Úemojic           	   ƒ   ó  K  — | j         r|r|sdS dddt          |¦  «        d|g}|                      |¦  «        ƒ d{V —†\  }}}|dk    r5t                               d|dd	…         |t          ||¦  «        ¦  «         dS d
S )uã   Add a reaction to a message via buzz-cli.

        Returns True on success, False on failure. Errors are logged but not
        raised â€” reactions are best-effort and should never block the main
        message flow.
        FÚ	reactionsÚaddz--eventz--emojiNr   u5   Buzz: reaction add failed for message %s in %s â€” %sé   T)r¨   r†   r  r4  ÚdebugrÎ   )r  rO  ra  rh  r©   r?  Ú_outrA  s           r   Úsend_reactionzBuzzAdapter.send_reaction�  s¶   è è € ð Œ}ð 	 Eð 	°ð 	Ø�5ð
 ˜Ø•s˜:‘”Ø�uð
ˆð
 !%§¢¨dÑ 3Ô 3Ð3Ð3Ð3Ð3Ð3Ð3‰ˆˆd�CØ�1Š9ˆ9Ý�LŠLØGØ˜3˜B˜3” Õ*<¸SÀ$Ñ*GÔ*Gñô ð ð �5ØˆtrB   Ú	image_urlÚcaptionc              ƒ   ór  K  — |                      d¦  «        s!t          |¦  «                             ¦   «         nd}|��Q|                     ¦   «         �r<dddt	          |¦  «        dt	          |¦  «        ddg}|r|d	t	          |¦  «        gz  }|                      ||pd
¬¦  «        ƒ d{V —†\  }}	}
|dk    r$t          dt          |
|¦  «        |dk    ¬¦  «        S 	 t          j	        |	pd¦  «        }n# t          $ r i }Y nw xY w|                     d¦  «        }|r0|                      t	          |¦  «        t	          |¦  «        ¦  «         t          t          |                     dd¦  «        ¦  «        |rt	          |¦  «        nd|¬¦  «        S |r|› d|› �n|}|                      ||||¬¦  «        ƒ d{V —†S )z@Send an image: local files upload via --file, URLs go as a link.)zhttp://zhttps://NrU  rV  rW  ú--filerX  rY  r[  r'   r  r   Fr#  r\  r]  r^  r_  Tr`  Ú
)rQ  rR  )rv   r   r„   r…   r†   r  r   rÎ   r�   rž   rk   r˜   rc  rd  rV  )r  rO  rp  rq  rQ  rR  Úlocalr©   r?  r@  rA  rU   r^  rÍ   s                 r   Ú
send_imagezBuzzAdapter.send_image›  sú  è è € ð 5>×4HÒ4HÐI`Ñ4aÔ4aÐk•�Y‘”×*Ò*Ñ,Ô,Ð,ÐgkˆØÑ §¢¡¤Ñà˜FØ�S ™\œ\Ø�#˜e™*œ*Ø˜Sð	ˆDð ð 6Ø˜¥s¨8¡}¤}Ð5Ñ5�Ø#'§=¢=°À'À-ÈR =Ñ#PÔ#PÐPÐPÐPÐPÐPÐP‰NˆD�#�sØ�qŠyˆyÝ!¨%Õ7IÈ#ÈtÑ7TÔ7TÐ`dÐhiÒ`iÐjÑjÔjÐjðÝ”z # +¨Ñ.Ô.��øÝð ð ð Ø���ðøøøà—x’x 
Ñ+Ô+ˆHØð =Ø—’¥ G¡¤­c°(©m¬mÑ<Ô<Ð<ÝÝ˜TŸXšX j°$Ñ7Ô7Ñ8Ô8Ø,4Ð>�3˜x™=œ=˜=¸$Ø!ðñ ô ð ð -4ÐB�'Ð(Ð(˜YÐ(Ð(Ð(¸ˆØ—Y’Y˜w¨°xÈ(�YÑSÔSÐSÐSÐSÐSÐSÐSÐSs   ÃC2 Ã2DÄ Dc              ƒ   óú  K  — t          |¦  «        }| j                             |¦  «        }|r|d         nd}| j                             |¦  «        }|€¡| j        rš|                      ddd|g¦  «        ƒ d {V —†\  }}}|dk    rq	 t          j        |pd¦  «        }t          |t          ¦  «        r4|                     d¦  «        rt          |d         ¦  «        }|| j        |<   n# t          $ r Y nw xY w|p|||d	œS )
Nr/  r-  rÛ   r˜   rW  r   r]  r   )r   ÚtyperO  )r†   r  r˜   r	  r¨   r  r�   rž   r    r¡   rk   )	r  rO  Ústater/  r   r?  r@  Ú_errrU   s	            r   Úget_chat_infozBuzzAdapter.get_chat_infoÁ  s(  è è € Ý�g‘,”,ˆØÔ#×'Ò'¨Ñ0Ô0ˆØ*/Ð<�E˜+Ô&Ð&°Wˆ	ØÔ"×&Ò& wÑ/Ô/ˆØˆ<˜DœMˆ<Ø$(§M¢M°:¸uÀkÐSZÐ2[Ñ$\Ô$\Ð\Ð\Ð\Ð\Ð\Ð\‰OˆD�#�tØ�qŠyˆyðÝœ: c k¨TÑ2Ô2�DÝ! $­Ñ-Ô-ð <°$·(²(¸6Ñ2BÔ2Bð <Ý" 4¨¤<Ñ0Ô0˜Ø7;˜Ô+¨GÑ4øøÝ!ð ð ð Ø�Dðøøøà˜ °ÀwÐOÐOÐOs   ÂAC% Ã%
C2Ã1C2c                 ó  — t          | j                             ¦   «         ¦  «        }dddœ                     |j        |j        ¦  «        }|dvs|j        st          d¦  «        ‚t          ||j        |j        pd|j	        df¦  «        S )NÚwsÚwss)ÚhttpÚhttps)r}  r~  z(Buzz relay URL must use http(s) or ws(s)r'   )
r   rª   rt   r˜   ÚschemeÚnetlocrk   r   r2   Úquery)r  Úparsedr�  s      r   Ú_websocket_urlzBuzzAdapter._websocket_urlÙ  s„   € Ý˜$œ.×.Ò.Ñ0Ô0Ñ1Ô1ˆØ¨Ð/Ð/×3Ò3°F´MÀ6Ä=ÑQÔQˆØ˜Ð&Ð&¨f¬mÐ&ÝÐGÑHÔHÐHÝ˜6 6¤=°&´+Ð2CÀÀVÄ\ÐSUÐVÑWÔWÐWrB   c              ƒ   ó2  K  — 	 ddl }|                      ¦   «          n3# t          $ r&}t                               d|¦  «         Y d}~dS d}~ww xY wt          j        ¦   «         | _        t          t          j	        ¦   «         ¦  «        | _
        t          j        |                      ¦   «         ¦  «        | _        	 t          j        | j                             ¦   «         t           dz   ¬¦  «        ƒ d{V —† n¢# t
          j        t"          f$ r‰ t                               d¦  «         d| _        | j        rV| j                             ¦   «         s=| j                             ¦   «          	 | j        ƒ d{V —† n# t
          j        $ r Y nw xY wd| _        Y dS w xY wdS )	zAStart the WS loop; True when it authenticates within the timeout.r   NzCBuzz: WebSocket transport unavailable (%s); falling back to pollingFr    r±   z,Buzz: WebSocket did not authenticate in timeT)Ú
websocketsr…  rK  r4  r>  r»   ÚEventr  ÚintÚtimer  r;  Ú_websocket_loopr  rÀ   rÅ   Ú_WS_AUTH_TIMEOUTrÃ   Úwarningr  rL  rM  rN  )r  r‡  Úes      r   r9  zBuzzAdapter._start_websocketà  sÀ  è è € ð	ØÐÐÐà×ÒÑ!Ô!Ð!Ð!øÝð 	ð 	ð 	Ý�KŠKÐ]Ð_`ÑaÔaÐaØ�5�5�5�5�5øøøøð	øøøõ !œ™œˆŒÝ!$¥T¤Y¡[¤[Ñ!1Ô!1ˆÔÝÔ+¨D×,@Ò,@Ñ,BÔ,BÑCÔCˆŒð	ÝÔ" 4¤>×#6Ò#6Ñ#8Ô#8ÕBRÐUVÑBVÐWÑWÔWÐWÐWÐWÐWÐWÐWÐWÐWøÝÔ$¥lÐ3ð 
	ð 
	ð 
	Ý�NŠNÐIÑJÔJÐJØ#ˆDŒOØŒ}ð  T¤]×%7Ò%7Ñ%9Ô%9ð Ø”×$Ò$Ñ&Ô&Ð&ðØœ-Ð'Ð'Ð'Ð'Ð'Ð'Ð'Ð'øÝÔ-ð ð ð Ø�Dðøøøà ˆDŒMØ�5�5ð
	øøøð ˆtsK   „ �
A§AÁAÂ9;C5 Ã5A0FÅ&E4Å3FÅ4FÆFÆFÆ
FÆFc           	   ƒ   ó  K  — t          ¦   «         j        }t          j        |                     ¦   «         t
          ¬¦  «        ƒ d{V —†}t          j        |¦  «        }t          |t          ¦  «        rt          |¦  «        dk     s|d         dk    rt          d¦  «        ‚ || j        t          |d         ¦  «        |                      ¦   «         t          j        dd	¦  «        ¬
¦  «        }|                     t          j        d|gd¬¦  «        ¦  «        ƒ d{V —† 	 t          j        |                     ¦   «         t
          ¬¦  «        ƒ d{V —†}t          j        |¦  «        }t          |t          ¦  «        r|sŒ`|d         dk    rIt          |¦  «        dk    r6|d         |d         k    r$|d         du rdS t          d|d         › �¦  «        ‚|d         dv r/t          |¦  «        dk    r|d         nd}t          d|› �¦  «        ‚Œî)zËNIP-42: wait for the relay's AUTH challenge, answer with a signed
        kind-22242 event (plus the optional NIP-OA owner-attestation tag from
        BUZZ_AUTH_TAG), and wait for the OK acknowledgment.r±   Nr#  r   ÚAUTHz/Buzz relay did not send a NIP-42 AUTH challenger$   ÚBUZZ_AUTH_TAGr'   )r—   Ú	challengerª   Úauth_tag_json©rÜ   r'  ©Ú
separatorsTÚOKr¶   ÚidzBuzz WebSocket AUTH rejected: é   )ÚNOTICEÚCLOSEDéÿÿÿÿzauthentication failedzBuzz WebSocket AUTH failed: )r5   Úbuild_auth_eventr»   rÀ   ÚrecvrŒ  r�   rž   r    rÒ   rl   ÚConnectionErrorrþ   r†   r…  r   r   rV  rÆ   )r  rë   r�  rn   rµ   ÚeventÚresponseÚdetails           r   Ú_authenticate_websocketz#BuzzAdapter._authenticate_websocketû  s%  è è € õ ,Ñ-Ô-Ô>ÐåÔ$ Y§^¢^Ñ%5Ô%5Õ?OÐPÑPÔPÐPÐPÐPÐPÐPÐPˆÝ”*˜S‘/”/ˆÝ˜'¥4Ñ(Ô(ð 	U­C°©L¬L¸1Ò,<Ð,<ÀÈÄ
ÈfÒ@TÐ@TÝ!Ð"SÑTÔTÐTØ Ð ØÔ)Ý˜' !œ*‘o”oØ×)Ò)Ñ+Ô+Ýœ) O°RÑ8Ô8ð	
ñ 
ô 
ˆð �nŠn�TœZ¨°¨ÀJÐOÑOÔOÑPÔPÐPÐPÐPÐPÐPÐPÐPð	OÝÔ(¨¯ªÑ)9Ô)9ÕCSÐTÑTÔTÐTÐTÐTÐTÐTÐTˆCÝ”z #‘”ˆHÝ˜h­Ñ-Ô-ð °Xð ØØ˜Œ{˜dÒ"Ð"¥s¨8¡}¤}¸Ò'9Ð'9¸hÀq¼kÈUÐSWÌ[Ò>XÐ>XØ˜A”; $Ð&Ð&Ø�FÝ%Ð&TÀxÐPQÄ{Ð&TÐ&TÑUÔUÐUØ˜Œ{Ð2Ð2Ð2Ý),¨X©¬¸Ò):Ð):˜ "œ˜Ð@W�Ý%Ð&MÀVÐ&MÐ&MÑNÔNÐNð	OrB   Úsubscription_idr*  c              ƒ   óL  K  — | j                              |¦  «        pi }t          t          |                     d¦  «        pt	          j        ¦   «         ¦  «        dz
  d¦  «        }d|t
          g|g|dœg}|                     t          j        |d¬¦  «        ¦  «        ƒ d {V —† d S )NÚlast_tsr$   r   ÚREQ)Úkindsz#hÚsincer”  r•  )	r  r˜   rû   r‰  rŠ  Ú
_CHAT_KINDrV  r�   rÆ   )r  rë   r¤  r*  ry  r©  Úrequests          r   Ú_send_channel_subscriptionz&BuzzAdapter._send_channel_subscription  s­   è è € ØÔ#×'Ò'¨
Ñ3Ô3Ð9°rˆÝ•C˜Ÿ	š	 )Ñ,Ô,Ð;µ´	±´Ñ<Ô<¸qÑ@À!ÑDÔDˆàØÝ!�l¨:¨,ÀÐGÐGð
ˆð
 �nŠn�TœZ¨¸JÐGÑGÔGÑHÔHÐHÐHÐHÐHÐHÐHÐHÐHÐHrB   c              ƒ   óŠ  K  — i }t          t          | j        ¦  «        ¦  «        D ],\  }}d|› �}|||<   |                      |||¦  «        ƒ d{V —† Œ-| j        ridt
          t          g| j        gt          | j        dz
  d¦  «        dœg}| 	                    t          j        |d¬¦  «        ¦  «        ƒ d{V —† d|t
          <   |S )	zySubscribe to every watched conversation plus membership events
        (kind 44100 p-tagged to us) for live DM discovery.zhermes-buzz-Nr§  r$   r   )r¨  z#pr©  r”  r•  )Ú	enumeraterÒ   r  r¬  rÿ   Ú_WS_MEMBERSHIP_SUB_IDÚ_WS_MEMBERSHIP_KINDrû   r  rV  r�   rÆ   )r  rë   Úsubscriptionsrs   r*  r¤  r«  s          r   Ú_subscribe_websocketz BuzzAdapter._subscribe_websocket#  s  è è € ð 35ˆÝ!*­4°Ô0CÑ+DÔ+DÑ!EÔ!Eð 	Zð 	ZÑˆE�:Ø4¨UÐ4Ð4ˆOØ-7ˆM˜/Ñ*Ø×1Ò1°)¸_ÈjÑYÔYÐYÐYÐYÐYÐYÐYÐYÐYØÔð 	8àÝ%å1Ð2ØÔ,Ð-Ý  Ô!7¸!Ñ!;¸QÑ?Ô?ðð ðˆGð —.’.¥¤¨GÀ
Ð!KÑ!KÔ!KÑLÔLÐLÐLÐLÐLÐLÐLÐLØ37ˆMÕ/Ñ0ØÐrB   r±  r   c              ƒ   óž  K  — t          | j        t          |                     d¦  «        pd¦  «        ¦  «        | _        t	          | j        ¦  «        }|                      d¬¦  «        ƒ d{V —† | j        D ]V}||v rŒdt          |¦  «        › �}|||<   |                      |||¦  «        ƒ d{V —† t           
                    d|¦  «         ŒWdS )zŒA membership event p-tagged to us: rediscover conversations and
        subscribe to any new ones (fresh DMs dispatch from their beginning).Ú
created_atr   Fr0  Nzhermes-buzz-dm-z'Buzz: subscribed to new conversation %s)rû   r  r‰  r˜   Úsetr  r8  rl   r¬  r4  r>  )r  rë   r±  r   Úbeforer*  r¤  s          r   Ú_handle_membership_eventz$BuzzAdapter._handle_membership_event9  sÿ   è è € õ "% TÔ%;½SÀÇÂÈ<ÑAXÔAXÐA]Ð\]Ñ=^Ô=^Ñ!_Ô!_ˆÔÝ�TÔ(Ñ)Ô)ˆØ× Ò  eÐ Ñ,Ô,Ð,Ð,Ð,Ð,Ð,Ð,Ð,ØÔ-ð 	Oð 	OˆJØ˜VÐ#Ð#ØØDµ°MÑ0BÔ0BÐDÐDˆOØ-7ˆM˜/Ñ*Ø×1Ò1°)¸_ÈjÑYÔYÐYÐYÐYÐYÐYÐYÐYÝ�KŠKÐAÀ:ÑNÔNÐNÐNð	Oð 	OrB   c              ƒ   ó(  K  — ddl }d}	 	 	 |                     |                      ¦   «         t          dddt          ¬¦  «        4 ƒd{V —†	 }|                      |¦  «        ƒ d{V —† |                      |¦  «        ƒ d{V —†}d| _        | j        �| j         	                    ¦   «          d}|2 3 d{V —†}	 t          j        |¦  «        }n1# t          t          f$ r t                               d¦  «         Y ŒJw xY wt!          |t"          ¦  «        r|sŒf|d         d	k    rØt%          |¦  «        d
k    rÅt'          |d         ¦  «        }|d         }t!          |t(          ¦  «        sŒ¸|t*          k    r|                      |||¦  «        ƒ d{V —† Œá|                     |¦  «        }	| j                             |	pd¦  «        }
|	r4|
�2|                      |	|
|¦  «        ƒ d{V —† |                      |
¦  «         �ŒJ|d         dk    r9t%          |¦  «        dk    r|d         nd}t7          t'          |¦  «        ¦  «        ‚|d         dk    r!t                               d|d         ¦  «         �Œ¾6 	 ddd¦  «        ƒd{V —† n# 1 ƒd{V —†swxY w Y   nv# t8          j        $ r ‚ t<          $ rZ}d| _        t                               d||¦  «         t9          j        |¦  «        ƒ d{V —† tA          |dz  d¦  «        }Y d}~nd}~ww xY w�Œý# d| _        w xY w)u3  Persistent authenticated subscription with bounded reconnect
        backoff. Events route through _handle_event() â€” identical semantics
        to the poll loop. On reconnect, per-channel `since` filters resume
        from the last observed timestamps (same-second overlap de-duped by
        event id).r   Nr!   Tr    é   )Úopen_timeoutÚclose_timeoutÚping_intervalÚping_timeoutÚmax_sizez(Buzz: ignoring malformed WebSocket frameÚEVENTr™  r$   r#  r'   r›  rœ  zsubscription closedrš  zBuzz: relay notice: %sFz3Buzz: WebSocket disconnected; retrying in %.1fs: %sr"   )!r‡  rG  r…  rŒ  Ú_WS_MAX_MESSAGE_BYTESr£  r²  r  r  rµ  r�   rž   rk   rú   r4  r�  r    rÒ   rl   r†   r¡   r¯  r·  r˜   r  Ú_handle_eventÚ
_trim_seenrŸ  r»   rN  rK  ÚsleepÚmin)r  r‡  Úbackoffrë   r±  rn   rµ   r¤  r   r*  ry  r¢  rŽ  s                r   r‹  zBuzzAdapter._websocket_loopG  sm  è è € ð 	ÐÐÐàˆð3	$ð05ð/5Ø)×1Ò1Ø×+Ò+Ñ-Ô-Ý%5Ø&'Ø&(Ø%'Ý!6ð  2ñ  ô  ð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð #Ø"×:Ò:¸9ÑEÔEÐEÐEÐEÐEÐEÐEÐEØ.2×.GÒ.GÈ	Ñ.RÔ.RÐ(RÐ(RÐ(RÐ(RÐ(RÐ(R˜Ø*.˜œØœ>Ð5Ø œN×.Ò.Ñ0Ô0Ð0Ø"%˜Ø)2ð Vð Vð Vð Vð Vð Vð V #ð)Ý*.¬*°S©/¬/  øÝ$.µ	Ð#:ð )ð )ð )Ý &§¢Ð/YÑ ZÔ ZÐ ZØ ( ð)øøøõ $.¨gµtÑ#<Ô#<ð )ÀGð )Ø (Ø& qœz¨WÒ4Ð4½¸W¹¼ÈÒ9JÐ9JÝ25°g¸a´j±/´/ Ø(/°¬
 Ý'1°%½Ñ'>Ô'>ð !-Ø$,Ø#2Õ6KÒ#KÐ#KØ*.×*GÒ*GÈ	ÐS`ÐbgÑ*hÔ*hÐ$hÐ$hÐ$hÐ$hÐ$hÐ$hÐ$hØ$,Ø-:×->Ò->¸Ñ-OÔ-O 
Ø(,Ô(;×(?Ò(?À
Ð@PÈbÑ(QÔ(Q Ø#-ð !;°%Ð2CØ*.×*<Ò*<¸ZÈÐPUÑ*VÔ*VÐ$VÐ$VÐ$VÐ$VÐ$VÐ$VÐ$VØ$(§O¢O°EÑ$:Ô$:Ð$:ùØ!(¨¤¨xÒ!7Ð!7Ý8;¸G¹¼ÀqÒ8HÐ8H¨°¬¨ÐNc Ý&5µc¸&±k´kÑ&BÔ&BÐ BØ!(¨¤¨xÒ!7Ð!7Ý &§¢Ð/GÈÐQSÌÑ UÔ UÐ Uùð3 *3¨ð'Vð 'Vð 'Vñ 'Vô 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vð 'Vøøøð 'Vð 'Vð 'Vð 'VøøõP Ô-ð ð ð ØÝ ð 5ð 5ð 5Ø&+�D”OÝ—N’NÐ#XÐZaÐcdÑeÔeÐeÝ!œ-¨Ñ0Ô0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ý! '¨A¡+¨tÑ4Ô4�G�G�G�G�G�Gøøøøð	5øøøñY05øðd $ˆDŒOÐ#Ð#Ð#Ð#s‹   ŠL Œ=J Á
A"I>Â,I*Â2I>Â4C	ÃI>Ã	+C7Ã4I>Ã6C7Ã7E4I>É,J É>
JÊJ ÊJÊJ ÊL ÊLÊ)AK>Ë9L Ë>LÌL Ì	Lc              ƒ   óÎ  K  — 	 	 t          j        | j        ¦  «        ƒ d{V —† | xj        dz  c_        	 | j        t          z  dk    r|                      d¬¦  «        ƒ d{V —† t          | j        ¦  «        D ]}|                      |¦  «        ƒ d{V —† Œn;# t           j	        $ r ‚ t          $ r t                               dd¬¦  «         Y nw xY wŒÎ# t           j	        $ r ‚ w xY w)	z:Poll every watched channel for new events until cancelled.TNr$   r   Fr0  zBuzz: poll sweep failed©Úexc_info)r»   rÃ  rá   r  Ú_DM_DISCOVERY_EVERYr8  rÒ   r  Ú_poll_channelrN  rK  r4  r�  )r  r*  s     r   r<  zBuzzAdapter._poll_loop‡  sR  è è € ð	ðMÝ”m DÔ$6Ñ7Ô7Ð7Ð7Ð7Ð7Ð7Ð7Ð7ØÐ Ô  AÑ%Ð Ô ðMØÔ'Õ*=Ñ=ÀÒBÐBØ"×0Ò0°eÐ0Ñ<Ô<Ð<Ð<Ð<Ð<Ð<Ð<Ð<Ý&*¨4Ô+>Ñ&?Ô&?ð =ð =˜
Ø"×0Ò0°Ñ<Ô<Ð<Ð<Ð<Ð<Ð<Ð<Ð<Ð<ð=øåÔ-ð ð ð ØÝ ð Mð Mð MÝ—N’NÐ#<Àt�NÑLÔLÐLÐLÐLðMøøøðMøõ Ô%ð 	ð 	ð 	Øð	øøøs/   „0C µA!B ÂC Â5CÃC ÃCÃC ÃC$r/  c           
   ƒ   ó®  K  — |dt          ¦   «         dœ}|| j        |<   |                      ddd|dt          t          ¦  «        g¦  «        ƒ d{V —†\  }}}|dk    rOt
                               d|t          ||¦  «        ¦  «         t          t          j	        ¦   «         ¦  «        |d	<   dS t          |¦  «        D ]…}|                     d
¦  «        }t          |                     d¦  «        pd¦  «        }	|rd|d         t          |¦  «        <   t          |d	         |	¦  «        |d	<   |                      |||¦  «         Œ†|                      |¦  «         dS )z>Initialize a channel's high-water mark from its newest events.r   ©r/  r¦  ÚseenrU  r˜   rW  ú--limitNu&   Buzz: could not seed channel %s â€” %sr¦  r˜  r´  rÍ  )r   r  r  r†   Ú_FETCH_LIMITr4  r�  rÎ   r‰  rŠ  rÓ   r˜   rû   Ú_maybe_latch_dmrÂ  )
r  r*  r/  ry  r?  r@  rA  r   r^  r´  s
             r   r7  zBuzzAdapter._seed_channel™  sw  è è € à'°A½{¹}¼}ÐMÐMˆØ*/ˆÔ˜JÑ'Ø#Ÿ}š}Ø˜ ¨Z¸ÅCÍÑDUÔDUÐVñ 
ô  
ð 
ð 
ð 
ð 
ð 
ð 
‰ˆˆc�3ð �1Š9ˆ9Ý�NŠNØ8¸*ÕFXÐY\Ð^bÑFcÔFcñô ð õ
  #¥4¤9¡;¤;Ñ/Ô/ˆE�)ÑØˆFÝ% cÑ*Ô*ð 		;ð 		;ˆEØ—y’y ‘”ˆHÝ˜UŸYšY |Ñ4Ô4Ð9¸Ñ:Ô:ˆJØð 4Ø/3��f”�c (™mœmÑ,Ý" 5¨Ô#3°ZÑ@Ô@ˆE�)Ñð × Ò  ¨U°EÑ:Ô:Ð:Ð:Ø�Š˜ÑÔÐÐÐrB   r1  c          	   ƒ   óŠ  K  — |                       ddg¦  «        ƒ d{V —†\  }}}|dk    r—t          |¦  «        D ]‡}t          |                     d¦  «        pd¦  «        }|r	|| j        v rŒ2|r|                      |d¬¦  «        ƒ d{V —† nddt          ¦   «         d	œ| j        |<   | j                             |d
¦  «         Œˆ|                       ddg¦  «        ƒ d{V —†\  }}}|dk    rdS t          |¦  «        D ]É}t          |                     d¦  «        pd¦  «        }|sŒ)|| j	        |<   | j                             |t          |                     d¦  «        p|¦  «        ¦  «         || j        v s|  
                    |¦  «        sŒ�|r|                      |d¬¦  «        ƒ d{V —† Œ¯ddt          ¦   «         d	œ| j        |<   ŒÊdS )aÂ  Watch DM conversations.  New ones found mid-run dispatch from their
        beginning (a fresh conversation has no history worth suppressing);
        ones present at startup are seeded like channels.

        ``dms list`` is only a best-effort source: on some hosted relays it
        returns ``[]`` even when DM conversations exist (#68871).  Those DMs
        DO surface in ``channels list`` as entries named "DM" with an empty
        description, so that listing is scanned as a fallback.  Fallback
        finds are watched as ``group`` and latch to ``dm`` via p-tag
        detection (_is_direct_message_event) rather than trusting the name
        alone to unlock the mention-free DM path.
        ÚdmsrÒ   Nr   Údm_idr'   Údmr.  rÌ  ÚDMrÛ   r*  r   r-  )r  rÓ   r†   r˜   r  r7  r   r	  Ú
setdefaultr
  Ú_may_reclassify_as_dm)	r  r1  r?  r@  rz  rÔ  rÓ  r+  Úch_ids	            r   r8  zBuzzAdapter._discover_dms´  s>  è è € ð !%§¢¨u°f¨oÑ >Ô >Ð>Ð>Ð>Ð>Ð>Ð>‰ˆˆc�4Ø�1Š9ˆ9Ý& sÑ+Ô+ð <ð <�Ý˜BŸFšF 7™OœOÐ1¨rÑ2Ô2�Øð  ¨Ô)<Ð <Ð <ØØð jØ×,Ò,¨U¸dÐ,ÑCÔCÐCÐCÐCÐCÐCÐCÐCÐCà?CÐPQÕ[fÑ[hÔ[hÐ1iÐ1i�DÔ'¨Ñ.ØÔ#×.Ò.¨u°dÑ;Ô;Ð;Ð;à $§¢¨z¸6Ð.BÑ CÔ CÐCÐCÐCÐCÐCÐC‰ˆˆc�4Ø�1Š9ˆ9ØˆFÝ" 3Ñ'Ô'ð 	ið 	iˆBÝ˜Ÿš˜|Ñ,Ô,Ð2°Ñ3Ô3ˆEØð ØØ(*ˆDÔ˜uÑ%ØÔ×*Ò*¨5µ#°b·f²f¸V±n´nÐ6MÈÑ2NÔ2NÑOÔOÐOØ˜Ô+Ð+Ð+°4×3MÒ3MÈeÑ3TÔ3TÐ+ØØð iØ×(Ò(¨¸'Ð(ÑBÔBÐBÐBÐBÐBÐBÐBÐBÐBà;BÈqÕZeÑZgÔZgÐ-hÐ-h�Ô# EÑ*Ð*ð	ið 	irB   c              ƒ   óè  K  — | j                              |¦  «        }|€d S ddd|dt          t          ¦  «        g}|d         r|dt          |d         ¦  «        gz  }|                      |¦  «        ƒ d {V —†\  }}}|dk    r,t
                               d|t          ||¦  «        ¦  «         d S t          |¦  «        D ]}|  	                    |||¦  «        ƒ d {V —† Œ |  
                    |¦  «         d S )	NrU  r˜   rW  rÎ  r¦  z--sincer   u&   Buzz: poll of channel %s failed â€” %s)r  r˜   r†   rÏ  r  r4  rm  rÎ   rÓ   rÁ  rÂ  )r  r*  ry  r©   r?  r@  rA  r   s           r   rÊ  zBuzzAdapter._poll_channelÝ  s*  è è € ØÔ#×'Ò'¨
Ñ3Ô3ˆØˆ=ØˆFØ˜E ;°
¸IÅsÍ<ÑGXÔGXÐYˆØ�Ôð 	7ð �Y¥ E¨)Ô$4Ñ 5Ô 5Ð6Ñ6ˆDØ#Ÿ}š}¨TÑ2Ô2Ð2Ð2Ð2Ð2Ð2Ð2‰ˆˆc�3Ø�1Š9ˆ9Ý�LŠLØ8¸*ÕFXÐY\Ð^bÑFcÔFcñô ð ð ˆFÝ% cÑ*Ô*ð 	?ð 	?ˆEØ×$Ò$ Z°¸Ñ>Ô>Ð>Ð>Ð>Ð>Ð>Ð>Ð>Ð>Ø�Š˜ÑÔÐÐÐrB   ry  c           	   ƒ   óþ  K  — t          |                     d¦  «        pd¦  «        }t          |                     d¦  «        pd¦  «        }|r
||d         v rdS d|d         |<   t          |d         |¦  «        |d<   t          |                     d¦  «        pd¦  «        t          k    rdS t          |                     d	¦  «        pd¦  «                             ¦   «         }|                     d
¦  «        }|r)t          |t           ¦  «        r|                     ¦   «         sdS || j        k    rdS |  	                    |||¦  «         |d         dk    }|s| j
        r|                      |¦  «        sdS | j        r.|| j        vr%t                               d|dd…         ¦  «         dS |                      |¦  «        }	|                      |	||rdnd||                      |¦  «        ƒ d{V —†||¬¦  «        ƒ d{V —† dS )z>De-dupe, filter, and dispatch a single ``messages get`` event.r˜  r'   r´  r   rÍ  Nr¦  Úkindr$  rP  r/  rÔ  u5   Buzz: ignoring message from unauthorized pubkey %sâ€¦r^   r-  )rÍ   rO  r/  Úuser_idÚ	user_namera  r´  )r†   r˜   r‰  rû   rª  ru   r    rt   rÿ   rÐ  rã   Ú_is_mentionedrý   r4  rm  Ú_strip_mentionÚ_dispatch_messageÚ_resolve_user_name)
r  r*  ry  r   r^  r´  r$  rP  Úis_dmÚdispatch_texts
             r   rÁ  zBuzzAdapter._handle_eventð  sX  è è € å�u—y’y ‘”Ð,¨"Ñ-Ô-ˆÝ˜Ÿš <Ñ0Ô0Ð5°AÑ6Ô6ˆ
Øð 	˜8 u¨V¤}Ð4Ð4ØˆFØ"&ˆˆfŒ�hÑÝ˜u YÔ/°Ñ<Ô<ˆˆiÑåˆu�yŠy˜Ñ Ô Ð% AÑ&Ô&­*Ò4Ð4ØˆFÝ�U—Y’Y˜xÑ(Ô(Ð.¨BÑ/Ô/×5Ò5Ñ7Ô7ˆØ—)’)˜IÑ&Ô&ˆØð 	�Z¨µÑ5Ô5ð 	¸W¿]º]¹_¼_ð 	ØˆFð �TÔ&Ò&Ð&ØˆFð 	×Ò˜Z¨°Ñ6Ô6Ð6à�kÔ" dÒ*ˆð ð 	˜Ô-ð 	°d×6HÒ6HÈÑ6QÔ6Qð 	ØˆFð Ô ð 	 V°4Ô3HÐ%HÐ%HÝ�LŠLÐPÐRXÐY[ÐZ[ÐY[ÔR\Ñ]Ô]Ð]ØˆFð ×+Ò+¨GÑ4Ô4ˆà×$Ò$ØØØ#Ð0�d�d¨ØØ ×3Ò3°FÑ;Ô;Ð;Ð;Ð;Ð;Ð;Ð;ØØ!ð %ñ 
ô 
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
rB   c                 ó6  — | j                              |¦  «        }|€	|| j        vS t          |                     d¦  «        pd¦  «                             ¦   «         }t          |                     d¦  «        pd¦  «                             ¦   «         }|dk    o| S )av  True when the conversation's metadata does not rule out a DM.

        Known real community channels (real name or non-empty description in
        ``channels list``) must never turn into DMs just because a message
        p-tags us.  A conversation with no metadata at all is trusted only
        when the user did not explicitly configure it as a watched channel.
        Nr   r'   ÚdescriptionrÕ  )r
  r˜   rÛ   r†   rt   )r  r*  Úmetar   rå  s        r   r×  z!BuzzAdapter._may_reclassify_as_dm?  s‘   € ð Ô!×%Ò% jÑ1Ô1ˆØˆ<Ø T¤]Ð2Ð2Ý�4—8’8˜FÑ#Ô#Ð) rÑ*Ô*×0Ò0Ñ2Ô2ˆÝ˜$Ÿ(š( =Ñ1Ô1Ð7°RÑ8Ô8×>Ò>Ñ@Ô@ˆØ�tŠ|Ð/ K Ð/rB   c                 ó>  ‡ — ‰ j         r‰                      |¦  «        sdS t          |                     d¦  «        pd¦  «        t          k    rdS t          |                     d¦  «        pd¦  «                             ¦   «         }|r|‰ j         k    rdS |                     d¦  «        }t          |t          ¦  «        sdS t          ˆ fd„|D ¦   «         ¦  «        }|sdS |                     d¦  «        }t          |t
          ¦  «        o‰  
                    |¦  «         S )	u&  True when ``event`` is shaped like a direct message to us: a chat
        message from another user, p-tagged to our pubkey, whose content does
        NOT visibly mention us â€” i.e. the p-tag is structural DM addressing,
        not the artifact of a typed @mention (see block comment above).FrÛ  r   r$  r'   Útagsc              3   óî   •K  — | ]o}t          |t          t          f¦  «        oNt          |¦  «        d k    o;|d         dk    o/t	          |d          ¦  «                             ¦   «         ‰j        k    V — ŒpdS )r$   r   rŠ   N)r    rÒ   Útuplerl   r†   ru   rÿ   )rI   Útagr  s     €r   rh   z7BuzzAdapter._is_direct_message_event.<locals>.<genexpr>]  s‘   øè è € ð 
ð 
ð
 õ	 �s�T¥5˜MÑ*Ô*ð 9Ý�C‘”˜1’ð9à�A”˜#’ð9õ �C˜”F‘”×!Ò!Ñ#Ô# tÔ'8Ò8ð	
ð 
ð 
ð 
ð 
ð 
rB   rP  )rÿ   r×  r‰  r˜   rª  r†   ru   r    rÒ   ÚanyrÞ  )r  r*  r   r$  rè  Úp_tagged_to_selfrP  s   `      r   Ú_is_direct_message_eventz$BuzzAdapter._is_direct_message_eventN  s=  ø€ ð
 Ô ð 	¨×(BÒ(BÀ:Ñ(NÔ(Nð 	Ø�5Ýˆu�yŠy˜Ñ Ô Ð% AÑ&Ô&­*Ò4Ð4Ø�5Ý�U—Y’Y˜xÑ(Ô(Ð.¨BÑ/Ô/×5Ò5Ñ7Ô7ˆØð 	˜ 4Ô#4Ò4Ð4Ø�5Ø�yŠy˜Ñ Ô ˆÝ˜$¥Ñ%Ô%ð 	Ø�5Ýð 
ð 
ð 
ð 
ð
 ð
ñ 
ô 
ñ 
ô 
Ðð  ð 	Ø�5Ø—)’)˜IÑ&Ô&ˆÝ˜'¥3Ñ'Ô'ÐK°×0BÒ0BÀ7Ñ0KÔ0KÐ,KÐKrB   c                 óÄ   — |d         dk    s|                       ||¦  «        sdS d|d<   | j                             |d¦  «         t                               d|¦  «         dS )zÂLatch a group conversation to chat_type="dm" once any direct
        message is seen; the classification then sticks so subsequent
        un-mentioned messages in the conversation dispatch too.r/  rÔ  NrÕ  zCBuzz: conversation %s reclassified as DM (message p-tagged to self))rî  r	  rÖ  r4  r>  )r  r*  ry  r   s       r   rÐ  zBuzzAdapter._maybe_latch_dmi  sn   € ð �Ô Ò%Ð%¨T×-JÒ-JÈ:ÐW\Ñ-]Ô-]Ð%ØˆFØ!ˆˆkÑØÔ×&Ò& z°4Ñ8Ô8Ð8Ý�ŠÐYÐ[eÑfÔfÐfÐfÐfrB   c                 ó  — |                      ¦   «         }| j        r| j        |v rdS | j        r| j        |v rdS | j        rFdt	          j        | j                              ¦   «         ¦  «        › d�}t	          j        ||¦  «        rdS dS )z@True when the message addresses this agent (npub, hex, or name).Tz	(?<!\w)@?z(?!\w)F)ru   rÿ   r   r  r}   ÚescapeÚsearch)r  rP  ÚloweredÚpatterns       r   rÞ  zBuzzAdapter._is_mentioneds  s™   € à—-’-‘/”/ˆØÔð 	 Ô!2°gÐ!=Ð!=Ø�4ØŒ?ð 	˜tœ°'Ð9Ð9Ø�4ØÔð 	ØP¥2¤9¨TÔ-?×-EÒ-EÑ-GÔ-GÑ#HÔ#HÐPÐPÐPˆGÝŒy˜ 'Ñ*Ô*ð Ø�tØˆurB   c                 ó  — |                      ¦   «         }g }| j        r,|                     t          j        | j        ¦  «        ¦  «         | j        r,|                     t          j        | j        ¦  «        ¦  «         | j        r,|                     t          j        | j        ¦  «        ¦  «         |s|S dd                     |¦  «        › d�}t          j        |d|dt          j	        ¬¦  «        }|                      ¦   «         S )aC  Remove a leading @mention of this agent so the remaining text can be
        recognized as a slash command or clean prompt.

        Mirrors the Discord adapter, which strips its own ``<@id>`` mention
        before dispatch. Without this a channel message like ``@Chip /whoami``
        arrives with a leading ``@Chip``; the gateway's ``is_command()`` checks
        ``text.lstrip().startswith("/")`` and never fires the command. Only a
        LEADING mention is stripped (case-insensitive); mentions mid-sentence
        are left intact so normal prose is unaffected.
        z^@?(?:Ú|z)[\s:,]*r'   r$   )ÚcountÚflags)
rt   r  rT   r}   rñ  r   rÿ   rm   ÚsubÚ
IGNORECASE)r  rP  rÍ   r£   rô  Ústrippeds         r   rß  zBuzzAdapter._strip_mention€  sï   € ð �}Š}‰ŒˆØˆ
ØÔð 	=Ø×Ò�bœi¨Ô(:Ñ;Ô;Ñ<Ô<Ð<ØŒ?ð 	:Ø×Ò�bœi¨¬Ñ8Ô8Ñ9Ô9Ð9ØÔð 	<Ø×Ò�bœi¨Ô(9Ñ:Ô:Ñ;Ô;Ð;Øð 	ØˆKð ;˜CŸHšH ZÑ0Ô0Ð:Ð:Ð:ˆÝ”6˜' 2 t°1½B¼MÐJÑJÔJˆØ�~Š~ÑÔÐrB   r$  c              ƒ   ó€  K  — | j                              |¦  «        }|�|S d}|                      ddd|g¦  «        ƒ d{V —†\  }}}|dk    rMt          |¦  «        }|r<t	          |d                              d¦  «        pd¦  «                             ¦   «         }|st          |¦  «        p|dd…         }|| j         |<   |S )	aC  Resolve a pubkey to a display name (cached; falls back to npub prefix).

        Failures are cached too (negative caching): without it, every message
        from a profile-less pubkey re-runs ``users get`` each poll sweep,
        which amplifies badly when several adapter instances poll in one
        process.
        Nr'   r!  r˜   z--pubkeyr   r%  r3  )r  r˜   r  rÓ   r†   rt   rp   )r  r$  Úcachedr   r?  r@  rz  rB  s           r   rá  zBuzzAdapter._resolve_user_name›  sâ   è è € ð Ô!×%Ò% fÑ-Ô-ˆØÐØˆMØˆØ $§¢¨w¸¸zÈ6Ð.RÑ SÔ SÐSÐSÐSÐSÐSÐS‰ˆˆc�4Ø�1Š9ˆ9Ý'¨Ñ,Ô,ˆHØð JÝ˜8 Aœ;Ÿ?š?¨>Ñ:Ô:Ð@¸bÑAÔA×GÒGÑIÔI�Øð 	8Ý Ñ'Ô'Ð1¨6°3°B°3Ô7ˆDØ#'ˆÔ˜Ñ ØˆrB   c                 ó¦   — | d         }t          |¦  «        t          k    r0|                     d¬¦  «         t          |¦  «        t          k    °.d S d S )NrÍ  F)Úlast)rl   Ú	_SEEN_CAPÚpopitem)ry  rÍ  s     r   rÂ  zBuzzAdapter._trim_seen±  sO   € à�VŒ}ˆÝ�$‰iŒi�)Ò#Ð#Ø�LŠL˜eˆLÑ$Ô$Ð$õ �$‰iŒi�)Ò#Ð#Ð#Ð#Ð#Ð#rB   r^  c                 ó‚   — | j                              |¦  «        }|�"d |d         |<   |                      |¦  «         d S d S )NrÍ  )r  r˜   rÂ  )r  r*  r^  ry  s       r   rc  zBuzzAdapter._mark_seen·  sL   € ØÔ#×'Ò'¨
Ñ3Ô3ˆØÐØ&*ˆE�&ŒM˜(Ñ#Ø�OŠO˜EÑ"Ô"Ð"Ð"Ð"ð ÐrB   rÍ   rÜ  rÝ  r´  c           	   ƒ   óê  K  — | j         sdS |                      || j                             ||¦  «        |||¬¦  «        }t	          |t
          j        |||rt          j        |¦  «        nt          j	        ¦   «         ¬¦  «        }	|  
                    |	¦  «        ƒ d{V —† 	 |                      ||d¦  «        ƒ d{V —† dS # t          $ r) t                               d|dd…         d¬¦  «         Y dS w xY w)	z;Build a MessageEvent and hand it to the base class handler.N)rO  Ú	chat_namer/  rÜ  rÝ  )rÍ   Úmessage_typeÚsourcera  Ú	timestampu   ðŸ‘€z$Buzz: reaction failed for message %srl  TrÇ  )Ú_message_handlerÚbuild_sourcer	  r˜   r   r   ÚTEXTr   ÚfromtimestampÚnowÚhandle_messagero  rK  r4  rm  )
r  rÍ   rO  r/  rÜ  rÝ  ra  r´  r  r   s
             r   rà  zBuzzAdapter._dispatch_message½  s?  è è € ð Ô$ð 	ØˆFà×"Ò"ØØÔ)×-Ò-¨g°wÑ?Ô?ØØØð #ñ 
ô 
ˆõ ØÝ$Ô)ØØ!Ø<FÐZ•hÔ,¨ZÑ8Ô8Ð8ÍHÌLÉNÌNð
ñ 
ô 
ˆð ×!Ò! %Ñ(Ô(Ð(Ð(Ð(Ð(Ð(Ð(Ð(ð	aØ×$Ò$ W¨j¸&ÑAÔAÐAÐAÐAÐAÐAÐAÐAÐAÐAøÝð 	að 	að 	aÝ�LŠLÐ?ÀÈCÈRÈCÄÐ[_ˆLÑ`Ô`Ð`Ð`Ð`Ð`ð	aøøøs   Â B? Â?/C2Ã1C2©r8   N)NNre   )NNN)0Ú__name__Ú
__module__Ú__qualname__Ú__doc__rô   Úpropertyr†   r   r   r	   r
   r‰  r  rd  rG  r:  r   r   r   rV  rg  ro  rv  r{  r…  r9  r£  r¬  r²  r¡   r·  r‹  r<  r7  r8  rÊ  rÁ  r×  rî  rÐ  rÞ  rß  rá  ÚstaticmethodrÂ  rc  rà  Ú__classcell__)r  s   @r   rÕ   rÕ   _  só  ø€ € € € € ðð ð
Qð Qð Qð Qð Qðf ð�cð ð ð ñ „Xðð
 NRð 	
ð 	
ð 	
 4¨¤9ð 	
¸XÀc¼]ð 	
ÐV[Ð\_ÐadÐfiÐ\iÔVjð 	
ð 	
ð 	
ð 	
ð 5:ð mð mð m¨Tð m¸dð mð mð mð mð^ð ð ð ðH #'Ø-1ð!
ð !
àð!
ð ð!
ð ˜3”-ð	!
ð
 ˜4  S œ>Ô*ð!
ð 
ð!
ð !
ð !
ð !
ðFð ¨ð Àð ð ð ð ð¨3ð ¸Cð Èð ÐPTð ð ð ð ð< "&Ø"&Ø-1ð$Tð $Tàð$Tð ð$Tð ˜#”ð	$Tð
 ˜3”-ð$Tð ˜4  S œ>Ô*ð$Tð 
ð$Tð $Tð $Tð $TðLP¨3ð P°4¸¸S¸´>ð Pð Pð Pð Pð0X ð Xð Xð Xð Xð¨ð ð ð ð ð6Oð Oð Oð Oð<IÈ3ð IÐ\_ð IÐdhð Ið Ið Ið Ið°t¸CÀÈ#ÄÐ<NÔ7Oð ð ð ð ð,OÀtÈCÐQYÐZ]ÔQ^ÐL^ÔG_ð OÐhlð OÐquð Oð Oð Oð Oð<$ð <$ð <$ð <$ð@ð ð ð ð$¨cð ¸cð Àdð ð ð ð ð6'i¨4ð 'i°Dð 'ið 'ið 'ið 'iðR¨cð °dð ð ð ð ð&3
¨cð 3
¸$ð 3
Àtð 3
ÐPTð 3
ð 3
ð 3
ð 3
ð^0°ð 0¸ð 0ð 0ð 0ð 0ðL°3ð L¸tð LÈð Lð Lð Lð Lð6g¨#ð g°dð gÀ4ð gÈDð gð gð gð gð Sð ¨Tð ð ð ð ð  cð  ¨cð  ð  ð  ð  ð6¨sð °sð ð ð ð ð, ð%˜$ð % 4ð %ð %ð %ñ „\ð%ð
# Sð #°Cð #¸Dð #ð #ð #ð #ð%aàð%að ð%að ð	%að
 ð%að ð%að ð%að ð%að 
ð%að %að %að %að %að %að %að %arB   rÕ   c                  óŠ   — t          j        dd¦  «                             ¦   «         sdS t          t	          ¦   «         ¦  «        S )z?Check if Buzz is configured: a relay URL plus a resolvable key.r¬   r'   F)r   r   rt   rd  r¥   rN   rB   r   Úcheck_requirementsr  é  s=   € åŒ9Ð% rÑ*Ô*×0Ò0Ñ2Ô2ð ØˆuÝÕ$Ñ&Ô&Ñ'Ô'Ð'rB   c                 ó¸   — t          | di ¦  «        pi }t          j        d¦  «        p|                     dd¦  «        }t	          |ot          |¦  «        ¦  «        S )z=Validate that the platform config has enough info to connect.rŽ   r¬   rª   r'   )rõ   r   r   r˜   rd  r¥   )r×   rŽ   Úrelays      r   Úvalidate_configr  ð  sW   € å�F˜G RÑ(Ô(Ð.¨B€EÝŒIÐ&Ñ'Ô'ÐE¨5¯9ª9°[À"Ñ+EÔ+E€EÝ�Ð5Õ.¨uÑ5Ô5Ñ6Ô6Ð6rB   c                 ó    — t          | ¦  «        S )z6Check whether Buzz is configured (env or config.yaml).)r  )r×   s    r   Úis_connectedr  ÷  s   € å˜6Ñ"Ô"Ð"rB   Úyaml_cfgÚbuzz_cfgc                 óú  — |                      d|¦  «        pi }t          |t          ¦  «        sdS dddddœ}|                     ¦   «         D ]L\  }}|                      |¦  «        }|r0t	          j        |¦  «        st          |¦  «        t          j        |<   ŒM|                      d¦  «        }|�0t	          j        d	¦  «        st          |¦  «        t          j        d	<   |                      d
¦  «        }|�kt	          j        d¦  «        sWt          |t          t          f¦  «        rd 
                    d„ |D ¦   «         ¦  «        }t          |¦  «        t          j        d<   |                      d¦  «        }	|	�kt	          j        d¦  «        sWt          |	t          t          f¦  «        rd 
                    d„ |	D ¦   «         ¦  «        }	t          |	¦  «        t          j        d<   d|v rHt	          j        d¦  «        s4t          |d         ¦  «                             ¦   «         t          j        d<   d|v rHt	          j        d¦  «        s4t          |d         ¦  «                             ¦   «         t          j        d<   dS )uæ  Translate ``config.yaml`` ``buzz.extra`` keys into ``BUZZ_*`` env vars.

    Implements the ``apply_yaml_config_fn`` contract.  ``check_requirements``
    and the adapter's connect path read configuration from the environment, so
    a config.yaml-only setup (no ``BUZZ_*`` env vars beyond the secret) would
    otherwise fail the ``check_fn`` gate and be silently skipped at gateway
    startup.  This hook bridges the ``extra`` block into env, mirroring the
    Slack/Telegram pattern.  Env vars win over YAML â€” every assignment is
    guarded by ``not os.getenv(...)`` so explicit env overrides survive a
    config.yaml update.  ``BUZZ_PRIVATE_KEY`` is a secret and stays in ``.env``;
    it is never sourced from config.yaml here.
    rŽ   Nr¬   rÙ   rÞ   rè   )rª   r¨   rß   ré   rá   rà   rÛ   rÚ   rÜ   c              3   ó4   K  — | ]}t          |¦  «        V — Œd S re   ©r†   rH   s     r   rh   z%_apply_yaml_config.<locals>.<genexpr>  s(   è è € Ð9Ð9¨1¥ A¡¤Ð9Ð9Ð9Ð9Ð9Ð9rB   rî   rí   c              3   ó4   K  — | ]}t          |¦  «        V — Œd S re   r!  )rI   Úas     r   rh   z%_apply_yaml_config.<locals>.<genexpr>!  s(   è è € Ð7Ð7¨!�s 1™vœvÐ7Ð7Ð7Ð7Ð7Ð7rB   Úallow_all_usersÚBUZZ_ALLOW_ALL_USERSrã   râ   )r˜   r    r¡   Úitemsr   r   r†   r¹   rÒ   rê  rm   ru   )
r  r  rŽ   Ú	_str_keysÚsrcr°   r   r  rÛ   Úalloweds
             r   Ú_apply_yaml_configr*  ü  sC  € ð �LŠL˜ (Ñ+Ô+Ð1¨r€EÝ�e�TÑ"Ô"ð Øˆtà%Ø#Ø+Ø%ð	ð €Ið —O’OÑ%Ô%ð 'ð '‰ˆˆSØ�iŠi˜‰nŒnˆØð 	'•r”y ‘~”~ð 	'Ý! #™hœh�BŒJ�s‰OøØ�yŠy˜Ñ)Ô)€HØÐ¥B¤IÐ.BÑ$CÔ$CÐÝ+.¨x©=¬=�Œ
Ð'Ñ(Ø�yŠy˜Ñ$Ô$€HØÐ¥B¤I¨oÑ$>Ô$>ÐÝ�h¥¥u Ñ.Ô.ð 	:Ø—x’xÐ9Ð9°Ð9Ñ9Ô9Ñ9Ô9ˆHÝ&)¨(¡m¤m�Œ
�?Ñ#Ø�iŠi˜Ñ(Ô(€GØÐ¥2¤9Ð-AÑ#BÔ#BÐÝ�g¥¥e˜}Ñ-Ô-ð 	8Ø—h’hÐ7Ð7¨wÐ7Ñ7Ô7Ñ7Ô7ˆGÝ+.¨w©<¬<�Œ
Ð'Ñ(Ø˜EÐ!Ð!­"¬)Ð4JÑ*KÔ*KÐ!Ý-0°Ð7HÔ1IÑ-JÔ-J×-PÒ-PÑ-RÔ-R�Œ
Ð)Ñ*Ø˜EÐ!Ð!­"¬)Ð4JÑ*KÔ*KÐ!Ý-0°Ð7HÔ1IÑ-JÔ-J×-PÒ-PÑ-RÔ-R�Œ
Ð)Ñ*Øˆ4rB   c                  óÐ  — t          j        dd¦  «                             ¦   «         } | rt          ¦   «         sdS d| i}t          j        dd¦  «                             ¦   «         }|r"d„ |                     d¦  «        D ¦   «         |d<   t          j        d	d¦  «                             ¦   «         }|r$	 t          |¦  «        |d
<   n# t          $ r Y nw xY wt          j        dd¦  «                             ¦   «         }|r||d<   t          j        dd¦  «                             ¦   «         p|                     d¦  «        pdgd         }|r|t          j        d|¦  «        dœ|d<   |S )u˜  Seed ``PlatformConfig.extra`` from env vars during gateway config load.

    Called BEFORE adapter construction so env-only setups show up in
    ``hermes gateway status`` and ``get_connected_platforms()``.  Returns
    ``None`` when Buzz isn't minimally configured.

    The special ``home_channel`` key is handled by the core hook â€” it becomes
    a proper ``HomeChannel`` on the ``PlatformConfig``.
    r¬   r'   Nrª   rÚ   c                 ó^   — g | ]*}|                      ¦   «         ¯|                      ¦   «         ‘Œ+S rN   )rt   rH   s     r   rK   z#_env_enablement.<locals>.<listcomp>:  s-   € ÐPÐPÐP¨!ÀaÇgÂgÁiÄiÐP˜AŸGšG™IœIÐPÐPÐPrB   rÜ   rÛ   rà   rá   rÙ   r¨   rÞ   r   ÚBUZZ_HOME_CHANNEL_NAME)rO  r   rß   )r   r   rt   r¥   r÷   rø   rk   r˜   )r  r1  rÛ   r  r¨   r‰   s         r   Ú_env_enablementr.  *  s‘  € õ ŒIÐ&¨Ñ+Ô+×1Ò1Ñ3Ô3€EØð Õ,Ñ.Ô.ð ØˆtØ˜uÐ%€DÝŒy˜¨"Ñ-Ô-×3Ò3Ñ5Ô5€HØð QØPÐP¨x¯~ª~¸cÑ/BÔ/BÐPÑPÔPˆˆZÑÝŒyÐ-¨rÑ2Ô2×8Ò8Ñ:Ô:€HØð ð	Ý$)¨(¡O¤OˆD�Ñ!Ð!øÝð 	ð 	ð 	ØˆDð	øøøåŒy˜¨"Ñ-Ô-×3Ò3Ñ5Ô5€HØð $Ø#ˆˆZÑõ Œ9Ð(¨"Ñ-Ô-×3Ò3Ñ5Ô5ÐZ¸$¿(º(À:Ñ:NÔ:NÐ:VÐSUÐRVÐXYÔ9Z€DØð 
àÝ”IÐ6¸Ñ=Ô=ð 
ð  
ˆˆ^Ñð €Ks   Â3C Ã
CÃCF)rZ  Úmedia_filesÚforce_documentrO  rµ   rZ  r/  r0  c             ƒ   óB  K  — t          | di ¦  «        pi }t          j        d¦  «        p|                     dd¦  «                             ¦   «         }t          |¦  «        }t          t          j        dd¦  «                             ¦   «         p$t          |                     dd¦  «        pd¦  «        ¦  «        }	|r|sddiS |	sdd	iS |pd                     ¦   «         pJt          j        d
¦  «        p$t          |                     dd¦  «        pd¦  «                             ¦   «         }
|
sddiS ddd|
ddg}|r|dt          |¦  «        gz  }|pg D ]}|dt          |¦  «        gz  }Œ	 t          |	||||¬¦  «        ƒ d{V —†\  }}}n-# t          j
        $ r ‚ t          $ r}dd|› �icY d}~S d}~ww xY w|dk    rddt          ||¦  «        › �iS 	 t          j        |pd¦  «        }n# t          $ r i }Y nw xY wdt          |                     d¦  «        pd¦  «        dœS )a  One-shot send without a live adapter (out-of-process cron delivery).

    Used by ``tools/send_message_tool`` when ``hermes cron`` runs separately
    from the gateway process.  Without this hook, ``deliver=buzz`` cron jobs
    fail with ``No live adapter for platform 'buzz'``.
    rŽ   r¬   rª   r'   rÙ   r¨   r´   zLBuzz standalone send: BUZZ_RELAY_URL and BUZZ_PRIVATE_KEY must be configuredz/Buzz standalone send: buzz CLI binary not foundrÞ   rß   z?Buzz standalone send: no target channel (set BUZZ_HOME_CHANNEL)rU  rV  rW  rX  rY  r[  rs  r  Nz+Buzz standalone send failed to launch CLI: r   zBuzz standalone send failed: r]  Tr^  )rT  ra  )rõ   r   r   r˜   rt   r¥   r�   r†   rÊ   r»   rN  rœ   rÎ   r�   rž   rk   )ÚpconfigrO  rµ   rZ  r/  r0  rŽ   r  r—   r¨   Útargetr©   r2   r?  r@  rA  rŽ  rU   s                     r   Ú_standalone_sendr4  O  sÖ  è è € õ �G˜W bÑ)Ô)Ð/¨R€EÝŒYÐ'Ñ(Ô(ÐF¨E¯IªI°kÀ2Ñ,FÔ,F×MÒMÑOÔO€EÝ& uÑ-Ô-€KÝ Ý
Œ	�/ 2Ñ&Ô&×,Ò,Ñ.Ô.ÐVµ#°e·i²iÀ
ÈBÑ6OÔ6OÐ6UÐSUÑ2VÔ2Vñô €Hð ð i˜ð iØÐgÐhÐhØð LØÐJÐKÐKØˆm˜×"Ò"Ñ$Ô$Ð|­¬Ð3FÑ)GÔ)GÐ)sÍ3ÈuÏyÊyÐYgÐikÑOlÔOlÐOrÐprÑKsÔKs×(zÒ(zÑ(|Ô(|€FØð \ØÐZÐ[Ð[à˜ ¨V°[À#ÐF€DØð /Ø��s 9™~œ~Ð.Ñ.ˆØÐ!˜rð &ð &ˆØ��3˜t™9œ9Ð%Ñ%ˆˆðLÝ)Ø�d e¸ÐQXð 
ñ  
ô  
ð 
ð 
ð 
ð 
ð 
ð 
‰ˆˆc�3�3øõ Ô!ð ð ð ØÝð Lð Lð LØÐJÀqÐJÐJÐKÐKÐKÐKÐKÐKÐKøøøøðLøøøàˆq‚y€yØÐXÕ9KÈCÐQUÑ9VÔ9VÐXÐXÐYÐYðÝŒz˜#˜+ Ñ&Ô&ˆˆøÝð ð ð Øˆˆˆðøøøà­3¨t¯xªx¸
Ñ/CÔ/CÐ/IÀrÑ+JÔ+JÐKÐKÐKs0   Å)F ÆF2Æ!F-Æ'F2Æ-F2ÇG( Ç(G7Ç6G7c                  ó8  — 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¬¦  «        }
|
r |d|
 
                    ¦   «         ¦  «         nt          ¦   «         s |d¦  «          | d |d¦  «        pd¬¦  «        }|r  |d|                     dd¦  «        ¦  «          | d |d¦  «        pd¬¦  «        }|r |d| 
                    ¦   «         ¦  «         t          ¦   «           |d¦  «          |dd¦  «        }|r$ |dd¦  «          |dd¦  «          |d¦  «         nH |dd ¦  «          | d! |d¦  «        pd¬¦  «        } |d|r|                     dd¦  «        nd¦  «         t          ¦   «           |d"¦  «          |d#¦  «         d	S )$zÅInteractive ``hermes gateway setup`` flow for the Buzz 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_successr  r¬   z!Buzz: already configured (relay: rÌ   zReconfigure Buzz?FNzNConnect Hermes to a Buzz community (Block's Nostr-based human+agent platform).zK   Requires the buzz CLI binary and a Nostr key that is a community member.z9Relay URL (e.g. https://mycommunity.communities.buzz.xyz)r'   )r   u-   Relay URL is required â€” skipping Buzz setupz<Nostr private key (nsec or hex; leave blank to keep current)T)Úpasswordr�   uN   No private key configured â€” set BUZZ_PRIVATE_KEY before starting the gatewayzEChannel UUIDs to watch (comma-separated, empty = all joined channels)rÚ   Ú z;Home channel UUID for cron/notification delivery (optional)rÞ   u7   ðŸ”’ Access control: restrict who can talk to the agentz1Allow all community members to talk to the agent?r%  Útruerí   uF   âš ï¸�  Open access â€” anyone in the community can command the agent.rä   zLAllowed users (comma-separated npubs or hex pubkeys, empty to deny everyone)z*Buzz configuration saved to ~/.hermes/.envzFRestart the gateway for changes to take effect: hermes gateway restart)Úhermes_cli.setupr6  r7  r8  r9  r:  r;  r<  r=  Úprintrt   r¥   r·   )r6  r7  r8  r9  r:  r;  r<  r=  Úexisting_relayr  r¢   rÛ   r‰   Ú	allow_allr)  s                  r   Úinteractive_setuprE  ‚  sa  € ð	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð €L�ÑÔÐØ"�]Ð#3Ñ4Ô4€NØð Øˆ
ÐH°~ÐHÐHÐHÑIÔIÐIØˆ}Ð0°%Ñ8Ô8ð 	ØˆFà€JÐ_Ñ`Ô`Ð`Ø€JÐ\Ñ]Ô]Ð]Ý	�G„G€GàˆFØCØÐ$ "ðñ ô €Eð ð ØˆÐEÑFÔFÐFØˆØ€NÐ# U§[¢[¡]¤]Ñ3Ô3Ð3à
ˆ&ÐOÐZ^Ð
_Ñ
_Ô
_€CØ
ð hØˆÐ)¨3¯9ª9©;¬;Ñ7Ô7Ð7Ð7Ý!Ñ#Ô#ð hØˆÐfÑgÔgÐgàˆvØOØ�˜oÑ.Ô.Ð4°"ðñ ô €Hð ð CØˆ�¨×(8Ò(8¸¸bÑ(AÔ(AÑBÔBÐBàˆ6ØEØ�Ð1Ñ2Ô2Ð8°bðñ ô €Dð ð :ØˆÐ*¨D¯JªJ©L¬LÑ9Ô9Ð9å	�G„G€GØ€JÐHÑIÔIÐIØ�ÐQÐSXÑYÔY€IØð 
ZØˆÐ-¨vÑ6Ô6Ð6ØˆÐ+¨RÑ0Ô0Ð0ØˆÐ^Ñ_Ô_Ð_Ð_àˆÐ-¨wÑ7Ô7Ð7Ø�&ØZØ!�MÐ"6Ñ7Ô7Ð=¸2ð
ñ 
ô 
ˆð 	ˆÐ+ÈÐ-X¨W¯_ª_¸SÀ"Ñ-EÔ-EÐ-EÐVXÑYÔYÐYå	�G„G€GØ€MÐ>Ñ?Ô?Ð?Ø€JÐWÑXÔXÐXÐXÐXrB   c                 ó¢   — |                       ddd„ t          t          t          ddgdt          t
          t          dt          dd	d
ddd¬¦  «         dS )z7Plugin entry point: called by the Hermes plugin system.r‚   r  c                 ó    — t          | ¦  «        S re   )rÕ   )Úcfgs    r   ú<lambda>zregister.<locals>.<lambda>Ô  s   € ¥K°Ñ$4Ô$4€ rB   r¬   r�   zXRequires the buzz CLI binary (https://github.com/block/buzz) on PATH or at BUZZ_CLI_PATHrÞ   rí   r%  u   ðŸ��FTzùYou are collaborating in a Buzz workspace (Block's Nostr-based human+agent platform). Markdown IS supported. Users address you by @-mentioning your name or npub in channels; direct messages reach you without a mention. Keep responses conversational.)r   ÚlabelÚadapter_factoryÚcheck_fnr  r  Úrequired_envÚinstall_hintÚsetup_fnÚenv_enablement_fnÚapply_yaml_config_fnÚcron_deliver_env_varÚstandalone_sender_fnÚallowed_users_envÚallow_all_envrh  Úpii_safeÚallow_update_commandÚplatform_hintN)Úregister_platformr  r  r  rE  r.  r*  r4  )Úctxs    r   Úregisterr[  Ï  su   € à×ÒØØØ4Ð4Ý#Ý'Ý!Ø&Ð(:Ð;ØoÝ"õ *õ 0à0õ .à.Ø,ààØ!ðJðE ñ 'ô 'ð 'ð 'ð 'rB   re   )T)r'   r  )Nr  r»   r�   Úloggingr   r}   r‡   rŠ  Úcollectionsr   r   Úpathlibr   Útypingr   r   r   r	   r
   Úurllib.parser   r   Úagent.secret_scoper   r   r   r   r   Ú	getLoggerr  r4  Úgateway.platforms.baser   r   r   r   Úgateway.configr   rª  rÏ  r   rÉ  rù   rü   Ú_CLI_TIMEOUTrŒ  rÀ  r°  r¯  r„   rš   r5   rf   r‰  rA   r†   rO   rd  rZ   rp   rz   r   r�   r¡   r¥   rø   rÊ   rÎ   rÓ   rÕ   r  r  r  r*  r.  r4  rE  r[  rN   rB   r   ú<module>rf     s¤  ðð$ð $ðL €€€Ø €€€Ø €€€Ø 	€	€	€	Ø 	€	€	€	Ø €€€Ø €€€Ø #Ð #Ð #Ð #Ð #Ð #Ø Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð Ø 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ø -Ð -Ð -Ð -Ð -Ð -Ð -Ð -à JÐ JÐ JÐ JÐ JÐ JØ ?Ð ?Ð ?Ð ?Ð ?Ð ?ð/ð /ð /ð /ð( 
ˆÔ	˜8Ñ	$Ô	$€ðð ð ð ð ð ð ð ð ð ð ð ð $Ð #Ð #Ð #Ð #Ð #ð €
à€à€	ð Ð àÐ ØÐ Ø€ð Ð Ø!Ð ØÐ Ø0Ð ð  ˜4Ð 0Ñ1Ô1×<Ò<Ñ>Ô>Ð ðð ð ð6 5€ð˜D œIð ¨#ð ð ð ð ðI˜Cð I D¨¤Ið Ið Ið Ið Iðð  ð ¨cð ¸ð ÈÐQUÐVYÔQZÔH[ð ð ð ð ð*J˜Cð J H¨S¤Mð Jð Jð Jð Jð" �cð  ˜h sœmð  ð  ð  ð  ð$	˜Sð 	 X¨c¤]ð 	ð 	ð 	ð 	ð 7ð 7 #ð 7¨sð 7ð 7ð 7ð 7ð ð  ¨¤ð ¸#ð ð ð ð ðH !%Ø!ð'ð 'ð 'Øð'à
ˆsŒ)ð'ð ð	'ð
 ð'ð ˜”ð'ð ð'ð ˆ3��Sˆ=Ôð'ð 'ð 'ð 'ðTB˜sð B°ð B¸ð Bð Bð Bð Bð =˜Sð = T¨$¤Zð =ð =ð =ð =ðCað Cað Cað Cað CaÐ%ñ Caô Cað CaðT(˜Dð (ð (ð (ð (ð7˜tð 7ð 7ð 7ð 7ð#˜Dð #ð #ð #ð #ð
+ ð +°ð +¸(À4¼.ð +ð +ð +ð +ð\"˜ $œð "ð "ð "ð "ðT  $Ø'+Ø ð0Lð 0Lð 0Làð0Lð ð0Lð
 ˜Œ}ð0Lð ˜$˜sœ)Ô$ð0Lð ð0Lð 
ˆ#ˆsˆ(„^ð0Lð 0Lð 0Lð 0LðfJYð JYð JYð JYðZ)ð )ð )ð )ð )rB   