Skip to content

feat(plugins): ntfy notification plugin for working-1.6 (+ pushbullet event fix)#842

Merged
mastacontrola merged 5 commits into
working-1.6from
feature-760-ntfy
Jun 18, 2026
Merged

feat(plugins): ntfy notification plugin for working-1.6 (+ pushbullet event fix)#842
mastacontrola merged 5 commits into
working-1.6from
feature-760-ntfy

Conversation

@mastacontrola

Copy link
Copy Markdown
Member

Summary

Adds an ntfy notification plugin to working-1.6 and fixes a working-1.6-only event regression in the existing pushbullet plugin. This is the working-1.6 home for #760 (which targeted dev-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.6 conventions and fixed from the original PR:

  • Schema migrations: createSql() + append-only schema() + non-destructive install() via Schema::applyUpdates() — no drop-and-recreate on re-install; participates in the dashboard "update available" flow (see docs/PLUGIN_SCHEMA_MIGRATIONS.md).
  • Correct ntfy protocol: message as the POST body, title via the Title header (the fork still spoke pushbullet's JSON). Optional credentials authenticate protected topics: user:pass → HTTP basic, otherwise → Authorization: Bearer <token>.
  • Added the missing API and JS hooks (the JS hook is why fog.ntfy.*.js loads); menu hook converted to the hook_main pattern; JS split into fog.ntfy.add.js / fog.ntfy.list.js.
  • Page rebuilt on makeInput/makeLabel/makeButton/formFields/makeFormTag with checkAuthAndCSRF() + HTTPResponseCodes; output escaped via the helpers. Renamed to ntfymanagement.page.php / class NtfyManagement so the page loader (*.page.php, class == filename) registers it.
  • Fixed: NtfyException typo, removed pushbullet-only recipient parsing and the undefined _apiKey path, bareword array keys, and slack leftovers.

Bugs found and fixed during live testing

  • Password reveal — wrapped the credentials field in .input-group so the show/hide eye toggle works.
  • False "Topic already linked" — core 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. Switched dedup to exists() on the topic endpoint.
  • HTTP 500 on a fired eventself::$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 missing HostName for host-less events (login failure).

pushbullet fix (same working-1.6 regression)

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 and HostName guard.

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 the working-1.6 foreach { self::$eventloop($x) } refactor. slack uses a direct per-event call and has no eventloop closure.

Verification

  • php -l clean across all files; full app bootstraps with the plugin (all classes autoload); schema() emits valid non-destructive SQL.
  • ntfy protocol confirmed against ntfy.sh (title + message land correctly).
  • End-to-end in a live install: install → table create, add-topic round-trip, dedup, and a real login-failure event delivering a clean notification to the topic.

🤖 Generated with Claude Code

mastacontrola and others added 5 commits June 18, 2026 09:07
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant