§
    øž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mZmZ ddl	m
Z
mZmZmZ 	 ddlZdZn# e$ r dZdZY nw xY wddlmZmZ ddlmZmZmZmZ dd	lmZ dd
lmZ d(d„Z ej        e ¦  «        Z! G d„ de"¦  «        Z#dZ$dZ%dZ&dZ'g d¢Z(dZ)dZ*de+dee+e+f         fd„Z,de+de+de-fd„Z.de/fd„Z0de/fd„Z1de/fd„Z2 G d„ de¦  «        Z3de4dz  fd „Z5dddd!œd"e+de+d#ee+         d$eee+                  d%e/dee+e
f         fd&„Z6d)d'„Z7dS )*ul  ntfy platform adapter (Hermes plugin).

Subscribes to a topic on ntfy.sh or any self-hosted ntfy server via
HTTP streaming (``/json`` endpoint with ``poll=false``) and publishes
replies via HTTP POST. No external SDK â€” only httpx, which is already
a Hermes dependency.

This adapter ships as a Hermes platform plugin under
``plugins/platforms/ntfy/``. The Hermes plugin loader scans the
directory at startup, calls :func:`register`, and the platform becomes
available to ``gateway/run.py`` and ``tools/send_message_tool`` through
the registry â€” no edits to core files required.

Configuration in config.yaml::

    platforms:
      ntfy:
        enabled: true
        extra:
          server: "https://ntfy.sh"       # or self-hosted URL
          topic: "hermes-in"              # subscribe topic (incoming)
          publish_topic: "hermes-out"     # optional â€” defaults to topic
          token: "..."                    # optional Bearer / Basic auth token
          markdown: true                  # optional â€” enable markdown (default: false)

Environment variables (all read at adapter construct time, env wins over
config.yaml ``extra``):

    NTFY_TOPIC                 Topic to subscribe to (required)
    NTFY_SERVER_URL            Server URL (default: https://ntfy.sh)
    NTFY_TOKEN                 Bearer token or 'user:pass' for Basic auth
    NTFY_PUBLISH_TOPIC         Reply topic (defaults to NTFY_TOPIC)
    NTFY_MARKDOWN              "true"/"1"/"yes" enables X-Markdown header
    NTFY_ALLOWED_USERS         Allowlist (treated by gateway as user IDs;
                               on ntfy these are topic names)
    NTFY_ALLOW_ALL_USERS       Allow any topic â€” dev only
    NTFY_HOME_CHANNEL          Default topic for cron / notification delivery
    NTFY_HOME_CHANNEL_NAME     Human label for the home channel

Identity model: ntfy has no native authenticated user identity. The
``title`` field is publisher-controlled and is NOT used for
authorization. Each topic is treated as a single trusted channel â€”
``user_id`` is fixed to the topic name. Use a private topic protected
by a read token for any real trust boundary.
é    N)ÚdatetimeÚtimezone)ÚAnyÚDictÚListÚOptionalTF)ÚPlatformÚPlatformConfig)ÚBasePlatformAdapterÚMessageEventÚMessageTypeÚ
SendResult)Ú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/ntfy/adapter.pyÚ_get_scoped_secretr   K   sR   € ðÝ   wÑ/Ô/ˆˆøÝð ð ð ÝŒi˜‰oŒoˆˆˆðøøøà�/ˆ3ˆ3 wÐ.s   ‚ “4³4c                   ó   — e Zd ZdZdS )Ú_FatalStreamErrorz<Raised when a stream error is unrecoverable (e.g. 401, 404).N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© ó    r   r   r   b   s   € € € € € ØFÐFÐFÐFr"   r   zhttps://ntfy.shi   é,  iè  )é   é   é
   é   é<   éZ   zhermes-agentÚtokenÚreturnc                 óØ   — | si S |                       ¦   «         } | si S d| v rDddl}|                     |                      ¦   «         ¦  «                             ¦   «         }dd|› �iS dd| › �iS )u  Build an ``Authorization`` header from an ntfy token.

    Shared by :class:`NtfyAdapter._auth_headers` and :func:`_standalone_send`
    so both paths follow the same auth shape and whitespace-stripping rules.

    Tokens are stripped of surrounding whitespace â€” pasted tokens often
    carry trailing newlines that would otherwise render the header
    malformed (``Authorization: Bearer foo\n``).  ``user:pass`` tokens
    become Basic auth; anything else is treated as a Bearer token.
    Returns ``{}`` when no token is configured.
    ú:r   NÚAuthorizationzBasic zBearer )ÚstripÚbase64Ú	b64encodeÚencodeÚdecode)r*   r0   Úencodeds      r   Ú_build_auth_headerr5   o   sŒ   € ð ð Øˆ	Ø�KŠK‰MŒM€EØð Øˆ	Ø
ˆe€|€|ØˆˆˆØ×"Ò" 5§<¢<¡>¤>Ñ2Ô2×9Ò9Ñ;Ô;ˆØÐ!3¨'Ð!3Ð!3Ð4Ð4ØÐ. uÐ.Ð.Ð/Ð/r"   ÚmessageÚcontextc                óÔ   — t          | ¦  «        t          k    r/t                               d|t          | ¦  «        t          ¦  «         | dt          …                              d¦  «        S )zºApply the ntfy 4096-char limit, logging a warning on truncation.

    ``context`` is included in the log message so adapter and standalone
    truncations can be told apart in logs.
    z7%s: truncating message from %d to %d chars (ntfy limit)Núutf-8)ÚlenÚMAX_MESSAGE_LENGTHÚloggerÚwarningr2   )r6   r7   s     r   Ú_truncate_bodyr>   ‡   s_   € õ ˆ7�|„|Õ(Ò(Ð(Ý�ŠØEØ•S˜‘\”\Õ#5ñ	
ô 	
ð 	
ð Ð&Õ&Ð&Ô'×.Ò.¨wÑ7Ô7Ð7r"   c                  ó€   — t           sdS t          j        dd¦  «                             ¦   «         } t	          | ¦  «        S )zîCheck whether the ntfy adapter is installable and minimally configured.

    Reads ``NTFY_TOPIC`` directly to avoid the cost of a full
    ``load_gateway_config()`` (which also writes to ``os.environ``) on
    every pre-flight check.
    FÚ
NTFY_TOPICÚ )ÚHTTPX_AVAILABLEr   r   r/   Úbool)Útopics    r   Úcheck_requirementsrE   •   s:   € õ ð ØˆuÝŒI�l BÑ'Ô'×-Ò-Ñ/Ô/€EÝ�‰;Œ;Ðr"   c                 óš   — t          | di ¦  «        pi }|                     d¦  «        pt          j        dd¦  «        }t	          |¦  «        S )z;Validate that the configured ntfy platform has a topic set.ÚextrarD   r@   rA   )ÚgetattrÚgetr   r   rC   ©ÚconfigrG   rD   s      r   Úvalidate_configrL   ¢   sF   € å�F˜G RÑ(Ô(Ð.¨B€EØ�IŠI�gÑÔÐ=¥"¤)¨L¸"Ñ"=Ô"=€EÝ�‰;Œ;Ðr"   c                 óš   — t          | di ¦  «        pi }t          j        d¦  «        p|                     dd¦  «        }t	          |¦  «        S )z6Check whether ntfy is configured (env or config.yaml).rG   r@   rD   rA   )rH   r   r   rI   rC   rJ   s      r   Úis_connectedrN   ©   sF   € å�F˜G RÑ(Ô(Ð.¨B€EÝŒI�lÑ#Ô#Ð= u§y¢y°¸"Ñ'=Ô'=€EÝ�‰;Œ;Ðr"   c                   ó>  ‡ — e Zd ZdZeZdefˆ fd„Zddœdedefd„Zdd
„Z	de
dee
e
f         dd	fd„Zdd„Zdee
ef         dd	fd„Zde
def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e
ef         fd„Zdee
e
f         fd„Zˆ xZS )ÚNtfyAdapteruž   ntfy adapter.

    Subscribes to a topic via HTTP streaming (``/json`` endpoint) and
    publishes replies via HTTP POST. No external SDK â€” only httpx.
    rK   c                 óT  •— t          d¦  «        }t          ¦   «                              ||¬¦  «         |j        pi }|                     d¦  «        pt          j        dt          ¦  «                             d¦  «        | _	        |                     d¦  «        pt          j        dd¦  «        | _
        |                     d	¦  «        pt          j        d
d¦  «        p| j
        | _        |                     d¦  «        pt          dd¦  «        | _        d | _        d | _        i | _        d S )NÚntfy)rK   ÚplatformÚserverÚNTFY_SERVER_URLú/rD   r@   rA   Úpublish_topicÚNTFY_PUBLISH_TOPICr*   Ú
NTFY_TOKEN)r	   ÚsuperÚ__init__rG   rI   r   r   ÚDEFAULT_SERVERÚrstripÚ_serverÚ_topicÚ_publish_topicr   Ú_tokenÚ_stream_taskÚ_http_clientÚ_seen_messages)ÚselfrK   rS   rG   Ú	__class__s       €r   r[   zNtfyAdapter.__init__¹   s  ø€ Ý˜FÑ#Ô#ˆÝ‰Œ×Ò °ÐÑ:Ô:Ð:à”Ð" ˆà�IŠI�hÑÔð <ÝŒyÐ*­NÑ;Ô;ß
Š&�‰+Œ+ð 	Œð !Ÿ9š9 WÑ-Ô-ÐLµ´¸<ÈÑ1LÔ1LˆŒà�IŠI�oÑ&Ô&ð ÝŒyÐ-¨rÑ2Ô2ðàŒ{ð 	Ôð
 !Ÿ9š9 WÑ-Ô-ÐUÕ1CÀLÐRTÑ1UÔ1UˆŒà48ˆÔØ;?ˆÔð 13ˆÔÐÐr"   F)Úis_reconnectrg   r+   c             ƒ   ó,  K  — t           s"t                               d| j        ¦  «         dS | j        s"t                               d| j        ¦  «         dS 	 t          j        d¬¦  «        | _        t          j	        |  
                    ¦   «         ¦  «        | _        |                      ¦   «          t                               d| j        | j        | j        ¦  «         dS # t          $ r,}t                               d| j        |¦  «         Y d}~dS d}~ww xY w)	z<Connect to ntfy by starting the streaming subscription task.z0[%s] httpx not installed. Run: pip install httpxFz[%s] NTFY_TOPIC not configuredN©Útimeoutu'   [%s] Connected â€” subscribing to %s/%sTz[%s] Failed to connect: %s)rB   r<   r=   r   r_   ÚhttpxÚAsyncClientrc   ÚasyncioÚcreate_taskÚ_run_streamrb   Ú_mark_connectedÚinfor^   Ú	ExceptionÚerror)re   rg   Úes      r   ÚconnectzNtfyAdapter.connectÒ   s  è è € åð 	Ý�NŠNÐMÈtÌyÑYÔYÐYØ�5ØŒ{ð 	Ý�NŠNÐ;¸T¼YÑGÔGÐGØ�5ð	Ý %Ô 1¸$Ð ?Ñ ?Ô ?ˆDÔÝ 'Ô 3°D×4DÒ4DÑ4FÔ4FÑ GÔ GˆDÔØ× Ò Ñ"Ô"Ð"Ý�KŠKÐAÀ4Ä9ÈdÌlÐ\`Ô\gÑhÔhÐhØ�4øÝð 	ð 	ð 	Ý�LŠLÐ5°t´yÀ!ÑDÔDÐDØ�5�5�5�5�5øøøøð	øøøs   ÁBC Ã
DÃ'!DÄDNc              ƒ   ó"  K  — d}d}| j         › d| j        › d�}|                      ¦   «         }| j        �rZ	 t                               d| j        |¦  «         t          j        ¦   «         }|  	                    ||¦  «        ƒ d{V —† ni# t          j        $ r Y dS t          $ r d| _        Y dS t          $ r8}| j        sY d}~dS t                               d| j        |¦  «         Y d}~nd}~ww xY w| j        sdS t          j        ¦   «         |z
  d	k    rd}t          t!          |t#          t          ¦  «        d
z
  ¦  «                 }t                               d| j        |¦  «         t          j        |¦  «        ƒ d{V —† |d
z  }| j        �°XdS dS )z8Subscribe to the ntfy topic with automatic reconnection.r   g        rV   z/jsonz[%s] Opening stream to %sNFz[%s] Stream error: %sg      N@é   z[%s] Reconnecting in %ds...)r^   r_   Ú_auth_headersÚ_runningr<   Údebugr   ÚtimeÚ	monotonicÚ_consume_streamrm   ÚCancelledErrorr   rr   r=   ÚRECONNECT_BACKOFFÚminr:   rq   Úsleep)re   Úbackoff_idxÚstream_startÚurlÚheadersrt   Údelays          r   ro   zNtfyAdapter._run_streamå   sò  è è € àˆØ!ˆØ”Ð2Ð2 ¤Ð2Ð2Ð2ˆØ×$Ò$Ñ&Ô&ˆàŒmñ 	ðFÝ—’Ð8¸$¼)ÀSÑIÔIÐIÝ#œ~Ñ/Ô/�Ø×*Ò*¨3°Ñ8Ô8Ð8Ð8Ð8Ð8Ð8Ð8Ð8Ð8øÝÔ)ð ð ð Ø��Ý$ð ð ð Ø %�”Ø��Ýð Fð Fð FØ”}ð Ø�F�F�F�F�FÝ—’Ð6¸¼	À1ÑEÔEÐEÐEÐEÐEÐEÐEøøøøðFøøøð
 ”=ð Ø�õ Œ~ÑÔ ,Ñ.°$Ò6Ð6Ø�Ý%¥c¨+µsÕ;LÑ7MÔ7MÐPQÑ7QÑ&RÔ&RÔSˆEÝ�KŠKÐ5°t´yÀ%ÑHÔHÐHÝ”- Ñ&Ô&Ð&Ð&Ð&Ð&Ð&Ð&Ð&Ø˜1ÑˆKð1 Œmñ 	ð 	ð 	ð 	ð 	s*   ¶AB ÂC-ÂC-Â,	C-Â5C(Ã!C(Ã(C-r„   r…   c              ƒ   óÊ  K  — ddi}| j                              d|||t          j        dt          dd¬¦  «        ¬¦  «        4 ƒd{V —†	 }|j        dk    rGt                               d	| j        ¦  «         |  	                    d
dd¬¦  «         t          d¦  «        ‚|j        dk    rVt                               d| j        | j        ¦  «         |  	                    dd| j        › d�d¬¦  «         t          d¦  «        ‚|                     ¦   «          |                     ¦   «         2 3 d{V —†}| j        s ddd¦  «        ƒd{V —† dS |                     ¦   «         }|sŒ9	 t!          j        |¦  «        }n# t           j        $ r Y Œ`w xY w|                     d¦  «        dk    r|                      |¦  «        ƒ d{V —† Œ™6 	 ddd¦  «        ƒd{V —† dS # 1 ƒd{V —†swxY w Y   dS )z6Open an HTTP streaming connection and dispatch events.ÚpollÚfalseÚGETç      .@)ru   ÚreadÚwriteÚpool)r…   Úparamsrj   Ni‘  uO   [%s] Authentication failed (401) â€” stopping reconnect loop. Check NTFY_TOKEN.Úntfy_unauthorizedz2ntfy server rejected auth (401). Check NTFY_TOKEN.F)Ú	retryablez401 Unauthorizedi”  u;   [%s] Topic not found (404): %s â€” stopping reconnect loop.Úntfy_topic_not_foundzntfy topic 'z!' returned 404. Check NTFY_TOPIC.z404 Not FoundÚeventr6   )rc   Ústreamrk   ÚTimeoutÚSTREAM_TIMEOUT_SECONDSÚstatus_coder<   rs   r   Ú_set_fatal_errorr   r_   Úraise_for_statusÚaiter_linesry   r/   ÚjsonÚloadsÚJSONDecodeErrorrI   Ú_on_message)re   r„   r…   r�   ÚresponseÚliner“   s          r   r}   zNtfyAdapter._consume_stream  s&  è è € ð ˜'Ð"ˆØÔ$×+Ò+ØØØØÝ”M¨$Õ5KÐSWÐ^bÐcÑcÔcð ,ñ 
ô 
ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð ØÔ# sÒ*Ð*Ý—’ØeØ”Iñô ð ð ×%Ò%Ø'ØHØ#ð &ñ ô ð õ
 (Ð(:Ñ;Ô;Ð;ØÔ# sÒ*Ð*Ý—’ØQØ”I˜tœ{ñô ð ð ×%Ò%Ø*ØQ 4¤;ÐQÐQÐQØ#ð &ñ ô ð õ
 (¨Ñ8Ô8Ð8Ø×%Ò%Ñ'Ô'Ð'à&×2Ò2Ñ4Ô4ð 2ð 2ð 2ð 2ð 2ð 2ð 2�dØ”}ð ØðC*	2ð *	2ð *	2ñ *	2ô *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ðD —z’z‘|”|�Øð ØðÝ œJ tÑ,Ô,�E�EøÝÔ+ð ð ð Ø�Hðøøøà—9’9˜WÑ%Ô%¨Ò2Ð2Ø×*Ò*¨5Ñ1Ô1Ð1Ð1Ð1Ð1Ð1Ð1Ð1øð 5Ð4ð?*	2ð *	2ð *	2ñ *	2ô *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2ð *	2øøøð *	2ð *	2ð *	2ð *	2ð *	2ð *	2sI   ÁCGÄ$F=Ä*	GÅGÅE3Å2GÅ3FÆGÆFÆ9GÇ
GÇGc              ƒ   ó¢  K  — d| _         |                      ¦   «          | j        rD| j                             ¦   «          	 | j        ƒ d{V —† n# t          j        $ r Y nw xY wd| _        | j        r&| j                             ¦   «         ƒ d{V —† d| _        | j         	                    ¦   «          t                               d| j        ¦  «         dS )zDisconnect from ntfy.FNz[%s] Disconnected)ry   Ú_mark_disconnectedrb   Úcancelrm   r~   rc   Úacloserd   Úclearr<   rq   r   ©re   s    r   Ú
disconnectzNtfyAdapter.disconnect6  sù   è è € àˆŒØ×ÒÑ!Ô!Ð!àÔð 	%ØÔ×$Ò$Ñ&Ô&Ð&ðØÔ'Ð'Ð'Ð'Ð'Ð'Ð'Ð'Ð'øÝÔ)ð ð ð Ø�ðøøøà $ˆDÔàÔð 	%ØÔ#×*Ò*Ñ,Ô,Ð,Ð,Ð,Ð,Ð,Ð,Ð,Ø $ˆDÔàÔ×!Ò!Ñ#Ô#Ð#Ý�ŠÐ'¨¬Ñ3Ô3Ð3Ð3Ð3s   ¿A ÁAÁAr“   c              ƒ   óp  K  — |                      d¦  «        pt          j        ¦   «         j        }|                      |¦  «        r#t
                               d| j        |¦  «         dS |                      d¦  «        pg }t          |v r"t
                               d| j        ¦  «         dS |                      d¦  «        pd 	                    ¦   «         }|s"t
                               d| j        ¦  «         dS |                      d	¦  «        p| j
        }|}|}|                      ||d
||¬¦  «        }|                      d¦  «        }		 |	r-t          j        t          |	¦  «        t          j        ¬¦  «        nt          j        t          j        ¬¦  «        }
n<# t$          t&          t(          f$ r" t          j        t          j        ¬¦  «        }
Y nw xY wt+          |t,          j        ||||
¬¦  «        }t
                               d| j        ||dd…         ¦  «         |                      |¦  «        ƒ d{V —† dS )z'Process an incoming ntfy message event.Úidz#[%s] Duplicate message %s, skippingNÚtagsz$[%s] Skipping own message (echo tag)r6   rA   z![%s] Empty message body, skippingrD   Údm)Úchat_idÚ	chat_nameÚ	chat_typeÚuser_idÚ	user_namer{   )Útz)ÚtextÚmessage_typeÚsourceÚ
message_idÚraw_messageÚ	timestampz[%s] Message on topic %s: %séP   )rI   ÚuuidÚuuid4ÚhexÚ_is_duplicater<   rz   r   Ú	_ECHO_TAGr/   r_   Úbuild_sourcer   ÚfromtimestampÚintr   ÚutcÚnowÚ
ValueErrorÚOSErrorÚ	TypeErrorr   r   ÚTEXTÚhandle_message)re   r“   Úmsg_idrª   r²   rD   r¯   r°   r´   Úunix_tsr·   Úmessage_events               r   rž   zNtfyAdapter._on_messageL  s9  è è € à—’˜4‘”Ð4¥D¤J¡L¤LÔ$4ˆØ×Ò˜fÑ%Ô%ð 	Ý�LŠLÐ>ÀÄ	È6ÑRÔRÐRØˆFð �yŠy˜Ñ Ô Ð& BˆÝ˜ÐÐÝ�LŠLÐ?ÀÄÑKÔKÐKØˆFà—	’	˜)Ñ$Ô$Ð*¨×1Ò1Ñ3Ô3ˆØð 	Ý�LŠLÐ<¸d¼iÑHÔHÐHØˆFà—	’	˜'Ñ"Ô"Ð1 d¤kˆð ˆØˆ	à×"Ò"ØØØØØð #ñ 
ô 
ˆð —)’)˜FÑ#Ô#ˆð	6ð ð>•Ô&¥s¨7¡|¤|½¼ÐEÑEÔEÐEÝ (¤µ´Ð =Ñ =Ô =ð ˆIøõ �G¥YÐ/ð 	6ð 	6ð 	6Ý œ­¬Ð5Ñ5Ô5ˆIˆIˆIð	6øøøõ %ØÝ$Ô)ØØØØð
ñ 
ô 
ˆõ 	�ŠÐ3°T´YÀÀtÈCÈRÈCÄyÑQÔQÐQØ×!Ò! -Ñ0Ô0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0s   ÅAF Æ6GÇGrÈ   c                 óú   ‡— t          j         ¦   «         }t          | j        ¦  «        t          k    r4|t          z
  Šˆfd„| j                             ¦   «         D ¦   «         | _        || j        v rdS || j        |<   dS )zHReturn True if this message ID was already seen within the dedup window.c                 ó(   •— i | ]\  }}|‰k    ¯||“ŒS r!   r!   )Ú.0ÚkÚvÚcutoffs      €r   ú
<dictcomp>z-NtfyAdapter._is_duplicate.<locals>.<dictcomp>Œ  s+   ø€ Ð"^Ð"^Ð"^©D¨A¨qÐSTÐW]ÒS]ÐS] 1 aÐS]ÐS]ÐS]r"   TF)r{   r:   rd   ÚDEDUP_MAX_SIZEÚDEDUP_WINDOW_SECONDSÚitems)re   rÈ   rÂ   rÐ   s      @r   r¼   zNtfyAdapter._is_duplicate‡  s   ø€ åŒi‰kŒkˆÝˆtÔ"Ñ#Ô#¥nÒ4Ð4ØÕ/Ñ/ˆFØ"^Ð"^Ð"^Ð"^°DÔ4G×4MÒ4MÑ4OÔ4OÐ"^Ñ"^Ô"^ˆDÔà�TÔ(Ð(Ð(Ø�4Ø&)ˆÔ˜FÑ#Øˆur"   r¬   ÚcontentÚreply_toÚmetadatac           	   ƒ   ó  K  — |pi }|                      d¦  «        p| j        p|}| j        st          dd¬¦  «        S | j        › d|› �}| j        j        pi                       dd¦  «        }i |                      ¦   «         ¥dt          dœ¥}|rd	|d
<   t          |¦  «        | j
        k    r4t                               d| j        t          |¦  «        | j
        ¦  «         |d| j
        …         }		 | j                             ||	                     d¦  «        |d¬¦  «        ƒ d{V —†}
|
j        dk     rŒ	 |
                     ¦   «         }|                      d¦  «        pt%          j        ¦   «         j        dd…         }n0# t*          $ r# t%          j        ¦   «         j        dd…         }Y nw xY wt          d|¬¦  «        S |
j        }t                               d| j        |
j        |dd…         ¦  «         t          dd|
j        › d|dd…         › �¬¦  «        S # t.          j        $ r t          dd¬¦  «        cY S t*          $ rI}t                               d| j        |¦  «         t          dt5          |¦  «        ¬¦  «        cY d}~S d}~ww xY w)z2Publish a message to the configured publish topic.rW   FzHTTP client not initialized)Úsuccessrs   rV   Úmarkdownútext/plain; charset=utf-8©zContent-TypezX-TagsÚtrueú
X-Markdownz7[%s] Message truncated from %d to %d chars (ntfy limit)Nr9   r‹   )rÕ   r…   rj   r#   r©   é   T)rÙ   rµ   z[%s] Send failed HTTP %d: %séÈ   zHTTP ú: zTimeout publishing to ntfyz[%s] Send error: %s)rI   r`   rc   r   r^   rK   rG   rx   r½   r:   r;   r<   r=   r   Úpostr2   r—   r›   r¹   rº   r»   rr   r²   rk   ÚTimeoutExceptionrs   Ústr)re   r¬   rÕ   rÖ   r×   rW   r„   Úmarkdown_enabledr…   ÚbodyÚrespÚdataÚreturned_idÚ	body_textrt   s                  r   ÚsendzNtfyAdapter.send•  sû  è è € ð �>˜rˆØ Ÿš _Ñ5Ô5ÐW¸Ô9LÐWÐPWˆàÔ ð 	RÝ eÐ3PÐQÑQÔQÐQà”Ð/Ð/ Ð/Ð/ˆØ œKÔ-Ð3°×8Ò8¸ÀUÑKÔKÐð
Ø× Ò Ñ"Ô"ð
à7Ýð
ð 
ð 
ˆð
 ð 	+Ø$*ˆG�LÑ!åˆw‰<Œ<˜$Ô1Ò1Ð1Ý�NŠNØIØ”	�3˜w™<œ<¨Ô)@ñô ð ð Ð/˜Ô/Ð/Ô0ˆð	;ØÔ*×/Ò/Ø˜TŸ[š[¨Ñ1Ô1¸7ÈDð 0ñ ô ð ð ð ð ð ð ˆDð Ô #Ò%Ð%ð8ØŸ9š9™;œ;�DØ"&§(¢(¨4¡.¤.Ð"IµD´J±L´LÔ4DÀSÀbÀSÔ4I�K�KøÝ ð 8ð 8ð 8Ý"&¤*¡,¤,Ô"2°3°B°3Ô"7�K�K�Kð8øøøå!¨$¸;ÐGÑGÔGÐGØœ	ˆIÝ�NŠNÐ9¸4¼9ÀdÔFVÐXaÐbfÐcfÐbfÔXgÑhÔhÐhÝ eÐ3`¸4Ô;KÐ3`Ð3`ÈyÐY]ÐZ]ÐY]ÌÐ3`Ð3`ÐaÑaÔaÐaøÝÔ%ð 	Qð 	Qð 	QÝ eÐ3OÐPÑPÔPÐPÐPÐPÝð 	;ð 	;ð 	;Ý�LŠLÐ.°´	¸1Ñ=Ô=Ð=Ý eµ3°q±6´6Ð:Ñ:Ô:Ð:Ð:Ð:Ð:Ð:Ð:øøøøð	;øøøsQ   Ã.AH Ä1A	E; Å:H Å;*F(Æ%H Æ'F(Æ(H Æ<AH È J
È8	J
É>JÉ?J
ÊJ
c              ƒ   ó
   K  — dS )z(ntfy does not support typing indicators.Nr!   )re   r¬   r×   s      r   Úsend_typingzNtfyAdapter.send_typingÈ  s   è è € àˆr"   c              ƒ   ó   K  — |ddœS )z&Return basic info about an ntfy topic.r«   )r   Útyper!   )re   r¬   s     r   Úget_chat_infozNtfyAdapter.get_chat_infoÌ  s   è è € à¨Ð.Ð.Ð.r"   c                 ó*   — t          | j        ¦  «        S )z4Build Authorization header if a token is configured.)r5   ra   r¦   s    r   rx   zNtfyAdapter._auth_headersÒ  s   € å! $¤+Ñ.Ô.Ð.r"   ©r+   N)NN©N)r   r   r   r    r;   r
   r[   rC   ru   ro   rä   r   r}   r§   r   rž   r¼   r   r   rë   rí   rð   rx   Ú__classcell__)rf   s   @r   rP   rP   °   s  ø€ € € € € ðð ð ,Ðð3˜~ð 3ð 3ð 3ð 3ð 3ð 3ð2 5:ð ð ð ¨Tð ¸dð ð ð ð ð&ð ð ð ðB.2¨ð .2°t¸CÀ¸H´~ð .2È$ð .2ð .2ð .2ð .2ð`4ð 4ð 4ð 4ð,71 t¨C°¨H¤~ð 71¸$ð 71ð 71ð 71ð 71ðv
 Cð 
¨Dð 
ð 
ð 
ð 
ð$ #'Ø-1ð1;ð 1;àð1;ð ð1;ð ˜3”-ð	1;ð
 ˜4  S œ>Ô*ð1;ð 
ð1;ð 1;ð 1;ð 1;ðfð ¨ð Àð ð ð ð ð/¨3ð /°4¸¸S¸´>ð /ð /ð /ð /ð/˜t C¨ Hœ~ð /ð /ð /ð /ð /ð /ð /ð /r"   rP   c                  óz  — t          j        dd¦  «                             ¦   «         } | sdS | t          j        dt          ¦  «                             d¦  «        dœ}t          j        dd¦  «                             ¦   «         }|r||d<   t          d	d¦  «                             ¦   «         }|r||d
<   t          j        dd¦  «                             ¦   «                              ¦   «         }|r|dv |d<   t          j        dd¦  «                             ¦   «         p| }|r|t          j        d|¦  «        dœ|d<   |S )u]  Seed ``PlatformConfig.extra`` from env vars during gateway config load.

    Called by the platform registry's env-enablement hook BEFORE adapter
    construction, so ``gateway status`` and ``get_connected_platforms()``
    reflect env-only configuration without instantiating the HTTP client.
    Returns ``None`` when ntfy isn't minimally configured; the caller skips
    auto-enabling.

    The special ``home_channel`` key in the returned dict is handled by the
    core hook â€” it becomes a proper ``HomeChannel`` dataclass on the
    ``PlatformConfig`` rather than being merged into ``extra``.
    r@   rA   NrU   rV   )rD   rT   rX   rW   rY   r*   ÚNTFY_MARKDOWN©Ú1rÝ   ÚyesrÚ   ÚNTFY_HOME_CHANNELÚNTFY_HOME_CHANNEL_NAME)r¬   r   Úhome_channel)r   r   r/   r\   r]   r   Úlower)rD   ÚseedrW   r*   rÚ   Úhomes         r   Ú_env_enablementr   Ü  sP  € õ ŒI�l BÑ'Ô'×-Ò-Ñ/Ô/€EØð ØˆtàÝ”)Ð-­~Ñ>Ô>×EÒEÀcÑJÔJðð €Dõ ”IÐ2°BÑ7Ô7×=Ò=Ñ?Ô?€MØð .Ø -ˆˆ_ÑÝ˜|¨RÑ0Ô0×6Ò6Ñ8Ô8€EØð ØˆˆW‰ÝŒy˜¨"Ñ-Ô-×3Ò3Ñ5Ô5×;Ò;Ñ=Ô=€HØð <Ø#Ð';Ð;ˆˆZÑÝŒ9Ð(¨"Ñ-Ô-×3Ò3Ñ5Ô5Ð>¸€DØð 
àÝ”IÐ6¸Ñ=Ô=ð 
ð  
ˆˆ^Ñð €Kr"   )Ú	thread_idÚmedia_filesÚforce_documentr¬   r  r  r  c             ƒ   óˆ  K  — t           sddiS t          | di ¦  «        pi }|                     d¦  «        pt          j        dt
          ¦  «                             d¦  «        }|pw|                     d¦  «        pbt          j        dd	¦  «                             ¦   «         p;|                     d
¦  «        p&t          j        dd	¦  «                             ¦   «         }|sddiS |                     d¦  «        pt          dd	¦  «        }	t          j        dd	¦  «                             ¦   «          	                    ¦   «         }
t          |                     d¦  «        ¦  «        p|
dv }dt          dœt          |	¦  «        ¥}|rd|d<   t          |d¬¦  «        }|› d|› �}	 t          j        d¬¦  «        4 ƒd{V —†}|                     |||¬¦  «        ƒ d{V —†}ddd¦  «        ƒd{V —† n# 1 ƒd{V —†swxY w Y   |j        dk    rdd|j        › d|j        dd…         › �iS 	 |                     ¦   «         }|                     d ¦  «        pt)          j        ¦   «         j        dd!…         }n0# t.          $ r# t)          j        ¦   «         j        dd!…         }Y nw xY wd"d#||d$œS # t.          $ r}dd%|› �icY d}~S d}~ww xY w)&u9  Out-of-process publish for cron / send_message_tool fallbacks.

    Used by ``tools/send_message_tool._send_via_adapter`` and the cron
    scheduler when the gateway runner is not in this process (e.g.
    ``hermes cron`` running standalone). Without this hook,
    ``deliver=ntfy`` cron jobs fail with ``No live adapter for platform``.

    ``thread_id`` and ``media_files`` are accepted for signature parity
    only â€” ntfy has no thread or attachment primitive. Markdown is
    honored if ``NTFY_MARKDOWN`` is set OR ``pconfig.extra["markdown"]``
    is True.
    rs   z)ntfy standalone send: httpx not installedrG   rT   rU   rV   rW   rX   rA   rD   r@   z/ntfy standalone send: NTFY_TOPIC not configuredr*   rY   rö   rÚ   r÷   rÛ   rÜ   rÝ   rÞ   zntfy standalone)r7   r‹   ri   N)rÕ   r…   r#   z
ntfy HTTP rá   rà   r©   rß   TrR   )rÙ   rS   r¬   rµ   zntfy standalone send failed: )rB   rH   rI   r   r   r\   r]   r/   r   rý   rC   r½   r5   r>   rk   rl   râ   r—   r²   r›   r¹   rº   r»   rr   )Úpconfigr¬   r6   r  r  r  rG   rT   rW   r*   Úmarkdown_envrå   r…   ræ   r„   Úclientrç   rè   rÈ   rt   s                       r   Ú_standalone_sendr    sÄ  è è € õ* ð FØÐDÐEÐEå�G˜W bÑ)Ô)Ð/¨R€Eà�	Š	�(ÑÔð 	8ÝŒ9Ð&­Ñ7Ô7ß‚fˆS�k„kð ð
 	ð 	/Ø�9Š9�_Ñ%Ô%ð	/åŒ9Ð)¨2Ñ.Ô.×4Ò4Ñ6Ô6ð	/ð �9Š9�WÑÔð	/õ Œ9�\ 2Ñ&Ô&×,Ò,Ñ.Ô.ð ð ð LØÐJÐKÐKà�IŠI�gÑÔÐFÕ"4°\À2Ñ"FÔ"F€EÝ”9˜_¨bÑ1Ô1×7Ò7Ñ9Ô9×?Ò?ÑAÔA€LÝ˜EŸIšI jÑ1Ô1Ñ2Ô2ÐZ°lÐFZÐ6ZÐà:ÅiÐmÐmÕSeÐfkÑSlÔSlÐm€GØð 'Ø &ˆ�Ñå˜'Ð+<Ð=Ñ=Ô=€DàÐ
%Ð
%�mÐ
%Ð
%€Cð>ÝÔ$¨TÐ2Ñ2Ô2ð 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	I°fØŸš S°$À˜ÑHÔHÐHÐHÐHÐHÐHÐHˆDð	Ið 	Ið 	Iñ 	Iô 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Ið 	Iøøøð 	Ið 	Ið 	Ið 	IàÔ˜sÒ"Ð"ØÐO¨$Ô*:ÐOÐO¸d¼iÈÈÈ¼oÐOÐOÐPÐPð	+Ø—9’9‘;”;ˆDØ—X’X˜d‘^”^Ð<¥t¤z¡|¤|Ô'7¸¸¸Ô'<ˆFˆFøÝð 	+ð 	+ð 	+Ý”Z‘\”\Ô% c r cÔ*ˆFˆFˆFð	+øøøà¨VÀÐ]cÐdÐdÐdøÝð >ð >ð >ØÐ<¸Ð<Ð<Ð=Ð=Ð=Ð=Ð=Ð=Ð=øøøøð>øøøsg   Æ J& Æ;G,ÇJ& Ç,
G6Ç6J& Ç9G6Ç:)J& È%A	I/ É.J& É/*JÊJ& ÊJÊ	J& Ê&
KÊ0J<Ê6KÊ<Kc                 ó”   — |                       ddd„ t          t          t          dgdt          dt
          ddt          dd	d	d
¬¦  «         dS )uE   Plugin entry point â€” called by the Hermes plugin system at startup.rR   c                 ó    — t          | ¦  «        S ró   )rP   )Úcfgs    r   ú<lambda>zregister.<locals>.<lambda>H  s   € ¥K°Ñ$4Ô$4€ r"   r@   z1pip install httpx   # already a Hermes dependencyrú   ÚNTFY_ALLOWED_USERSÚNTFY_ALLOW_ALL_USERSu   ðŸ””Tu  You are communicating via ntfy push notifications. Use plain text by default â€” ntfy supports optional markdown (set markdown: true in config or NTFY_MARKDOWN=true). Keep responses concise; ntfy is a push notification service with a 4096-character per-message limit.)r   ÚlabelÚadapter_factoryÚcheck_fnrL   rN   Úrequired_envÚinstall_hintÚenv_enablement_fnÚcron_deliver_env_varÚstandalone_sender_fnÚallowed_users_envÚallow_all_envÚmax_message_lengthÚemojiÚpii_safeÚallow_update_commandÚplatform_hintN)Úregister_platformrE   rL   rN   r   r  r;   )Úctxs    r   Úregisterr   C  sn   € à×ÒØØØ4Ð4Ý#Ý'Ý!Ø"�^ØHõ *ð 1õ .à.Ø,Ý-Øð Ø!ð7ð= ñ $ô $ð $ð $ð $r"   ró   rò   )8r    rm   r›   Úloggingr   r{   r¹   r   r   Útypingr   r   r   r   rk   rB   ÚImportErrorÚgateway.configr	   r
   Úgateway.platforms.baser   r   r   r   Úagent.secret_scoper   r   r   r   r   Ú	getLoggerr   r<   rr   r   r\   r;   rÓ   rÒ   r   r–   r½   rä   r5   Úbytesr>   rC   rE   rL   rN   rP   Údictr   r  r   r!   r"   r   ú<module>r*     sW  ðð,ð ,ð\ €€€Ø €€€Ø €€€Ø 	€	€	€	Ø €€€Ø €€€Ø 'Ð 'Ð 'Ð 'Ð 'Ð 'Ð 'Ð 'Ø ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,ðØ€L€L€LØ€O€OøØð ð ð Ø€OØ€E€E€Eðøøøð 4Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3ðð ð ð ð ð ð ð ð ð ð ð ð KÐ JÐ JÐ JÐ JÐ JØ ?Ð ?Ð ?Ð ?Ð ?Ð ?ð/ð /ð /ð /ð( 
ˆÔ	˜8Ñ	$Ô	$€ðGð Gð Gð Gð G˜	ñ Gô Gð Gð #€ØÐ ØÐ Ø€Ø&Ð&Ð&Ð ØÐ Ø€	ð0˜cð 0 d¨3°¨8¤nð 0ð 0ð 0ð 0ð08˜Cð 8¨Sð 8°Uð 8ð 8ð 8ð 8ð
˜Dð 
ð 
ð 
ð 
ð˜tð ð ð ð ð˜Dð ð ð ð ðd/ð d/ð d/ð d/ð d/Ð%ñ d/ô d/ð d/ðX	#˜ ™ð #ð #ð #ð #ðV  $Ø'+Ø ð>>ð >>ð >>àð>>ð ð>>ð
 ˜Œ}ð>>ð ˜$˜sœ)Ô$ð>>ð ð>>ð 
ˆ#ˆsˆ(„^ð>>ð >>ð >>ð >>ðB&ð &ð &ð &ð &ð &s   °7 ·	AÁA