Skip to content

feat: add On Grab notification event#162

Open
xFlawless11x wants to merge 1 commit intokikootwo:mainfrom
xFlawless11x:feature/on-grab-notification
Open

feat: add On Grab notification event#162
xFlawless11x wants to merge 1 commit intokikootwo:mainfrom
xFlawless11x:feature/on-grab-notification

Conversation

@xFlawless11x
Copy link
Copy Markdown

@xFlawless11x xFlawless11x commented Mar 25, 2026

Summary

Adds a new request_grabbed notification event that fires when a torrent or NZB is successfully handed off to the configured download client; the RMAB equivalent of Sonarr/Radarr's "On Grab" notification. Previously, no notification fired at the actual grab moment. Addresses #161


Background & Motivation

RMAB already had five notification events:

Event When it fires
request_pending_approval Request created, needs admin approval
request_approved Request approved (manual or auto)
request_available Book fully imported and ready
request_error Download or import failed
issue_reported User reports a problem with an available book

The gap: request_approved is not the same as "on grab." A request can be approved while still waiting for a search result (status awaiting_search or pending). The actual grab — the moment the torrent/NZB file is sent to the configured download client — happened silently with no notification.

This PR adds request_grabbed, which fires from inside download-torrent.processor.ts immediately after client.addDownload() succeeds and the DownloadHistory record is created. At that point the download client has definitively accepted the file.


Changes

1. src/lib/constants/notification-events.ts

  • Added request_grabbed to NOTIFICATION_EVENTS with:
    • titleByRequestType: resolves to "Audiobook Grabbed" or "Ebook Grabbed" depending on request type
    • severity: 'info', priority: 'normal'
    • messageLabel: 'Details' (see item 2 below)
  • Added messageLabel: 'Reason' to the existing issue_reported event (see item 2)
  • Added new NotificationEventConfig interface — a normalized, provider-facing type that broadens the as const literal union and explicitly declares messageLabel?: string as an optional field
  • Updated getEventMeta() return type from the raw inferred union to NotificationEventConfig, resolving TypeScript errors in all providers that access messageLabel

2. src/lib/services/notification/providers/ — all 4 providers

Root cause fixed: All four providers (Discord, ntfy, Pushover, Apprise) had hardcoded logic for labeling the message payload field:

// Before (all providers)
isIssue ? 'Reason' : 'Error'

This meant any future event that used the message field for non-error context — including request_grabbed — would have its content displayed under an "⚠️ Error:" label, which isn't exactly accurate.

Fix applied consistently across all providers:

// After (all providers)
const messageLabel = meta.messageLabel ?? 'Error';
const msgEmoji = meta.severity === 'error' ? '⚠️' : '📝';
// used as: `${msgEmoji} ${messageLabel}: ${message}`

The label and emoji now derive from event metadata:

  • request_error → no messageLabel → defaults to ⚠️ Error:
  • issue_reportedmessageLabel: 'Reason' → renders as 📝 Reason:
  • request_grabbedmessageLabel: 'Details' → renders as 📝 Details:

Per-provider changes:

  • Discord (discord.provider.ts): embed field name changed from isIssue ? 'Reason' : 'Error' to meta.messageLabel ?? 'Error'
  • ntfy (ntfy.provider.ts): added const meta = getEventMeta(event) inside formatMessage; replaced hardcoded label with meta.messageLabel ?? 'Error' + severity-driven emoji
  • Pushover (pushover.provider.ts): meta was already in scope; replaced hardcoded label with meta.messageLabel ?? 'Error' + severity-driven emoji
  • Apprise (apprise.provider.ts): added const meta = getEventMeta(event) inside formatMessage; replaced hardcoded label with meta.messageLabel ?? 'Error' + severity-driven emoji

3. src/lib/processors/download-torrent.processor.ts

Added grab notification trigger after the DownloadHistory record is created (i.e., after client.addDownload() has confirmed success):

  • Fetches request.user.plexUsername for the notification (same DB-lookup pattern used in monitor-download.processor.ts)
  • Queues request_grabbed via jobQueue.addNotificationJob()
  • message field carries: "<torrent title> via <indexer> (<clientType>)" — gives recipients full context on what was grabbed and from where
  • requestType is read from request.type (the actual DB value) rather than inferred from protocol, ensuring correct title resolution for ebooks vs audiobooks

4. documentation/backend/services/notifications.md

  • Updated events list and event types table to include request_grabbed
  • Added request_grabbed titleByRequestType examples to the Dynamic Titles section
  • Added "Download Grabbed" trigger block to the Notification Triggers section describing the exact fire point and message format

What the Grab Notification Looks Like

For a grab of an audiobook torrent, the notification payload resolves to:

Field Value
Title Audiobook Grabbed
Book The Name of the Wind
Author Patrick Rothfuss
Requested By user
Details The Name of the Wind [M4B/192kbps] via NZBGeek (SABnzbd)

This follows the same structure as all other RMAB notifications — book title, author, requesting user, and an optional labeled message field. The message field here carries torrent/indexer details the same way request_error carries error text and issue_reported carries the user's reason.


Provider Compatibility

request_grabbed is included in NOTIFICATION_EVENTS and will appear as a subscribable checkbox in the notification backend settings UI for all four providers automatically — no UI changes required. The event is delivered through the same async Bull job queue as all other events.

Provider Selectable Delivered Message field label
Discord "Details" (embed field name)
ntfy 📝 Details: (message body)
Pushover 📝 Details: (message body)
Apprise 📝 Details: (message body)

Tested on my UnRaid instance, screenshots here:

image image

AI Disclosure

Claude Sonnet 4.6 was used to develop this PR, I did not see any specific no AI use language for this project but apologies if I overlooked something!

Adds request_grabbed event that fires when a torrent/NZB is successfully
handed off to the configured download client, filling the gap between
request_approved (pre-search) and request_available (fully imported).

- Add request_grabbed to NOTIFICATION_EVENTS with titleByRequestType
  (Audiobook Grabbed / Ebook Grabbed), info severity, Details messageLabel
- Add NotificationEventConfig interface and update getEventMeta() return
  type to expose messageLabel to all providers without TypeScript errors
- Add messageLabel: 'Reason' to issue_reported event
- Fix all 4 providers (Discord, ntfy, Pushover, Apprise) to derive message
  field label from meta.messageLabel ?? 'Error' instead of hardcoded
  isIssue ternary — prevents grab details showing as Error
- Trigger request_grabbed in download-torrent.processor.ts after
  client.addDownload() succeeds; message carries torrent title, indexer,
  and download client name; requestType sourced from request.type
- Update notifications.md documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@xFlawless11x xFlawless11x force-pushed the feature/on-grab-notification branch from 3d769f3 to ba1efa8 Compare April 21, 2026 17:49
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