feat(plugins): ntfy notification plugin for working-1.6 (+ pushbullet event fix)#842
Merged
Conversation
Port of PR #760 (originally against dev-branch, forked from the older pushbullet plugin) brought up to working-1.6 conventions and fixed. Sends FOG event notifications (image complete/fail, snapin complete, snapin-task complete, login failure) to an ntfy server -- ntfy.sh or self-hosted -- plus a per-topic management page. Reworked from the PR: - Schema now follows the new per-plugin migration contract: createSql() + append-only schema() + non-destructive install() via Schema::applyUpdates(); no more drop-and-recreate on re-install. - Handler now speaks ntfy's protocol (message as POST body, title via the Title header) instead of pushbullet's JSON; optional credentials authenticate protected topics (user:pass -> basic, else Bearer token). - Added the missing API and JS hooks (so fog.ntfy.*.js actually loads); menu hook converted to the working-1.6 hook_main pattern. - Page rebuilt on makeInput/makeLabel/makeButton/formFields/makeFormTag with checkAuthAndCSRF() and HTTPResponseCodes; output escaped via the helpers (fixes unescaped value attributes). Renamed to ntfymanagement.page.php / class NtfyManagement so the page loader (.page.php, class==filename) actually registers it. - Fixed NtfyException typo, removed pushbullet-only recipient parsing, the undefined _apiKey path, bareword array keys, and slack leftovers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The password show/hide toggle (setupPasswordReveal) finds the input via
.closest('.input-group'); a bare password input has no such ancestor so
the eye icon is unclickable. Wrap it like the core password fields. Also
make the topic endpoint placeholder explain what to enter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FOGManagerController::distinct() reuses a single :countVal placeholder for every condition, so a multi-field check returned a bogus non-zero count on an empty table -- every create failed with "Topic already linked." Switch to exists() on the topic endpoint (reliable, single placeholder, matches the error wording and pushbullet's dedup pattern). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
self::$eventloop($x) is parsed as a static method call named by a local variable, not as invoking the closure in the static property -- every fired event fataled with "Method name must be a string" (HTTP 500 on a failed login). Assign the closure to a local before calling it. Also guard self::$elements['HostName']: login-failure events carry no host, so build the title with a null-coalesce + trim instead of emitting an "undefined array key" warning and a leading space. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tName Same working-1.6 regression as ntfy: the foreach refactor calls self::$eventloop($x), which PHP parses as a static method named by a local variable rather than invoking the closure -- every fired event fatals with "Method name must be a string" (HTTP 500 on e.g. a failed login). Assign the closure to a local before calling it. Also guard self::$elements['HostName'] for host-less events (login failure) to avoid an undefined-key warning and a leading-space title. dev-branch is unaffected: it invokes via array_map(self::$eventloop, ...) which passes the closure as a value. slack uses a direct per-event call and has no eventloop closure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an ntfy notification plugin to
working-1.6and fixes aworking-1.6-only event regression in the existing pushbullet plugin. This is theworking-1.6home for #760 (which targeteddev-branch, forked from the older pushbullet plugin).Original author credit (Tony Lam / @TonyKaito) is preserved in the new file headers.
ntfy plugin
Sends FOG event notifications (image complete/fail, snapin complete, snapin-task complete, login failure) to an ntfy server — ntfy.sh or self-hosted — plus a per-topic management page with optional auth for protected topics.
Brought up to current
working-1.6conventions and fixed from the original PR:createSql()+ append-onlyschema()+ non-destructiveinstall()viaSchema::applyUpdates()— no drop-and-recreate on re-install; participates in the dashboard "update available" flow (seedocs/PLUGIN_SCHEMA_MIGRATIONS.md).Titleheader (the fork still spoke pushbullet's JSON). Optionalcredentialsauthenticate protected topics:user:pass→ HTTP basic, otherwise →Authorization: Bearer <token>.fog.ntfy.*.jsloads); menu hook converted to thehook_mainpattern; JS split intofog.ntfy.add.js/fog.ntfy.list.js.makeInput/makeLabel/makeButton/formFields/makeFormTagwithcheckAuthAndCSRF()+HTTPResponseCodes; output escaped via the helpers. Renamed tontfymanagement.page.php/ classNtfyManagementso the page loader (*.page.php, class == filename) registers it.NtfyExceptiontypo, removed pushbullet-only recipient parsing and the undefined_apiKeypath, bareword array keys, and slack leftovers.Bugs found and fixed during live testing
.input-groupso the show/hide eye toggle works.FOGManagerController::distinct()reuses a single:countValplaceholder for every condition, so a multi-field check returned a bogus non-zero count on an empty table. Switched dedup toexists()on the topic endpoint.self::$eventloop($x)is parsed by PHP as a static method named by a local variable, not as invoking the closure. Assign the closure to a local first. Also guard a missingHostNamefor host-less events (login failure).pushbullet fix (same
working-1.6regression)pushbullet has the identical
self::$eventloop($x)bug — it would 500 on any fired event under PHP 8; it just hadn't been exercised. Applied the same closure-invocation fix andHostNameguard.dev-branch status
Not affected by the eventloop bug. dev-branch invokes via
array_map(self::$eventloop, ...->find()), which passes the closure as a value (correct). The bug was introduced by theworking-1.6foreach { self::$eventloop($x) }refactor. slack uses a direct per-event call and has no eventloop closure.Verification
php -lclean across all files; full app bootstraps with the plugin (all classes autoload);schema()emits valid non-destructive SQL.🤖 Generated with Claude Code