mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 09:51:37 +00:00
Add ntfy.sh as notification provider alongside Apprise
Support sending notifications via ntfy.sh or self-hosted ntfy servers as an alternative to Apprise. Each notification can be configured to use either provider via a dropdown in the create/edit modal. Server changes: - Add ntfyUrl and ntfyToken fields to NotificationSettings - Add ntfyTopic field to Notification model with getNtfyPayload() - Route notifications to Apprise or ntfy based on ntfyTopic presence - Add null guard for appriseApiUrl before sending - Add undefined guards in update() to prevent partial PATCH from resetting unrelated fields Client changes: - Add ntfy.sh settings section on the notifications config page - Add provider dropdown (Apprise/ntfy) in notification edit modal - Extract shared saveSettings() helper to deduplicate form submissions - Use i18n for all user-visible strings
This commit is contained in:
parent
88667d00a1
commit
9391a940f5
8 changed files with 150 additions and 36 deletions
|
|
@ -14,7 +14,7 @@
|
||||||
<ui-icon-btn bg-color="bg-error" :size="7" icon-font-size="1.2rem" icon="delete" @click="deleteNotificationClick" />
|
<ui-icon-btn bg-color="bg-error" :size="7" icon-font-size="1.2rem" icon="delete" @click="deleteNotificationClick" />
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
<p class="text-gray-300 text-xs md:text-sm mb-2">{{ notification.urls.join(', ') }}</p>
|
<p class="text-gray-300 text-xs md:text-sm mb-2">{{ notification.ntfyTopic ? $getString('LabelNotificationNtfyTopicPrefix', [notification.ntfyTopic]) : notification.urls.join(', ') }}</p>
|
||||||
|
|
||||||
<p v-if="lastFiredAt && lastAttemptFailed" class="text-red-300 text-xs">Last attempt failed {{ $dateDistanceFromNow(lastFiredAt) }} ({{ numConsecutiveFailedAttempts }} attempt{{ numConsecutiveFailedAttempts === 1 ? '' : 's' }})</p>
|
<p v-if="lastFiredAt && lastAttemptFailed" class="text-red-300 text-xs">Last attempt failed {{ $dateDistanceFromNow(lastFiredAt) }} ({{ numConsecutiveFailedAttempts }} attempt{{ numConsecutiveFailedAttempts === 1 ? '' : 's' }})</p>
|
||||||
<p v-else-if="lastFiredAt" class="text-gray-400 text-xs">Last fired {{ $dateDistanceFromNow(lastFiredAt) }}</p>
|
<p v-else-if="lastFiredAt" class="text-gray-400 text-xs">Last fired {{ $dateDistanceFromNow(lastFiredAt) }}</p>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@
|
||||||
<div class="w-full px-3 py-5 md:p-12">
|
<div class="w-full px-3 py-5 md:p-12">
|
||||||
<ui-dropdown v-model="newNotification.eventName" :label="$strings.LabelNotificationEvent" :items="eventOptions" class="mb-4" @input="eventOptionUpdated" />
|
<ui-dropdown v-model="newNotification.eventName" :label="$strings.LabelNotificationEvent" :items="eventOptions" class="mb-4" @input="eventOptionUpdated" />
|
||||||
|
|
||||||
<ui-multi-select ref="urlsInput" v-model="newNotification.urls" :label="$strings.LabelNotificationAppriseURL" class="mb-2" />
|
<ui-dropdown v-model="selectedProvider" :label="$strings.LabelNotificationProvider" :items="providerOptions" class="mb-4" />
|
||||||
|
|
||||||
|
<ui-multi-select v-if="selectedProvider === 'apprise'" ref="urlsInput" v-model="newNotification.urls" :label="$strings.LabelNotificationAppriseURL" class="mb-2" />
|
||||||
|
|
||||||
|
<ui-text-input-with-label v-if="selectedProvider === 'ntfy'" v-model="newNotification.ntfyTopic" :label="$strings.LabelNotificationNtfyTopic" class="mb-2" />
|
||||||
|
|
||||||
<ui-text-input-with-label v-model="newNotification.titleTemplate" :label="$strings.LabelNotificationTitleTemplate" class="mb-2" />
|
<ui-text-input-with-label v-model="newNotification.titleTemplate" :label="$strings.LabelNotificationTitleTemplate" class="mb-2" />
|
||||||
|
|
||||||
|
|
@ -51,7 +55,8 @@ export default {
|
||||||
return {
|
return {
|
||||||
processing: false,
|
processing: false,
|
||||||
newNotification: {},
|
newNotification: {},
|
||||||
isNew: true
|
isNew: true,
|
||||||
|
selectedProvider: 'apprise'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -76,6 +81,12 @@ export default {
|
||||||
if (!this.notificationData) return []
|
if (!this.notificationData) return []
|
||||||
return this.notificationData.events || []
|
return this.notificationData.events || []
|
||||||
},
|
},
|
||||||
|
providerOptions() {
|
||||||
|
return [
|
||||||
|
{ value: 'apprise', text: 'Apprise' },
|
||||||
|
{ value: 'ntfy', text: 'ntfy.sh' }
|
||||||
|
]
|
||||||
|
},
|
||||||
eventOptions() {
|
eventOptions() {
|
||||||
return this.notificationEvents.map((e) => {
|
return this.notificationEvents.map((e) => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -109,11 +120,19 @@ export default {
|
||||||
if (this.$refs.modal) this.$refs.modal.setHide()
|
if (this.$refs.modal) this.$refs.modal.setHide()
|
||||||
},
|
},
|
||||||
submitForm() {
|
submitForm() {
|
||||||
this.$refs.urlsInput?.forceBlur()
|
if (this.selectedProvider === 'apprise') {
|
||||||
|
this.$refs.urlsInput?.forceBlur()
|
||||||
if (!this.newNotification.urls.length) {
|
if (!this.newNotification.urls.length) {
|
||||||
this.$toast.error(this.$strings.ToastAppriseUrlRequired)
|
this.$toast.error(this.$strings.ToastAppriseUrlRequired)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
this.newNotification.ntfyTopic = null
|
||||||
|
} else {
|
||||||
|
if (!this.newNotification.ntfyTopic) {
|
||||||
|
this.$toast.error(this.$strings.ToastNtfyTopicRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.newNotification.urls = []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isNew) {
|
if (this.isNew) {
|
||||||
|
|
@ -168,17 +187,20 @@ export default {
|
||||||
init() {
|
init() {
|
||||||
this.isNew = !this.notification
|
this.isNew = !this.notification
|
||||||
if (this.notification) {
|
if (this.notification) {
|
||||||
|
this.selectedProvider = this.notification.ntfyTopic ? 'ntfy' : 'apprise'
|
||||||
this.newNotification = {
|
this.newNotification = {
|
||||||
id: this.notification.id,
|
id: this.notification.id,
|
||||||
libraryId: this.notification.libraryId,
|
libraryId: this.notification.libraryId,
|
||||||
eventName: this.notification.eventName,
|
eventName: this.notification.eventName,
|
||||||
urls: [...this.notification.urls],
|
urls: [...(this.notification.urls || [])],
|
||||||
titleTemplate: this.notification.titleTemplate,
|
titleTemplate: this.notification.titleTemplate,
|
||||||
bodyTemplate: this.notification.bodyTemplate,
|
bodyTemplate: this.notification.bodyTemplate,
|
||||||
enabled: this.notification.enabled,
|
enabled: this.notification.enabled,
|
||||||
type: this.notification.type
|
type: this.notification.type,
|
||||||
|
ntfyTopic: this.notification.ntfyTopic || null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.selectedProvider = 'apprise'
|
||||||
this.newNotification = {
|
this.newNotification = {
|
||||||
libraryId: null,
|
libraryId: null,
|
||||||
eventName: 'onTest',
|
eventName: 'onTest',
|
||||||
|
|
@ -186,7 +208,8 @@ export default {
|
||||||
titleTemplate: '',
|
titleTemplate: '',
|
||||||
bodyTemplate: '',
|
bodyTemplate: '',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
type: null
|
type: null,
|
||||||
|
ntfyTopic: null
|
||||||
}
|
}
|
||||||
this.eventOptionUpdated()
|
this.eventOptionUpdated()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,22 @@
|
||||||
<ui-btn :loading="savingSettings" type="submit">{{ $strings.ButtonSave }}</ui-btn>
|
<ui-btn :loading="savingSettings" type="submit">{{ $strings.ButtonSave }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</app-settings-content>
|
||||||
|
|
||||||
<div class="w-full h-px bg-white/10 my-6" />
|
<app-settings-content :header-text="$strings.HeaderNtfyNotificationSettings" :description="$strings.MessageNtfyDescription">
|
||||||
|
<form @submit.prevent="submitNtfyForm">
|
||||||
|
<ui-text-input-with-label ref="ntfyUrlInput" v-model="ntfyUrl" :disabled="savingSettings" label="ntfy Server URL" placeholder="https://ntfy.sh" class="mb-2" />
|
||||||
|
|
||||||
|
<ui-text-input-with-label ref="ntfyTokenInput" v-model="ntfyToken" :disabled="savingSettings" label="ntfy Auth Token" type="password" class="mb-2" />
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end pt-4">
|
||||||
|
<ui-btn :loading="savingSettings" type="submit">{{ $strings.ButtonSave }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</app-settings-content>
|
||||||
|
|
||||||
|
<app-settings-content :header-text="$strings.HeaderNotifications">
|
||||||
<div class="flex items-center justify-between mb-6">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h2 class="text-xl font-semibold">{{ $strings.HeaderNotifications }}</h2>
|
|
||||||
<ui-btn small color="bg-success" class="flex items-center" @click="clickCreate">{{ $strings.ButtonCreate }} <span class="material-symbols text-lg pl-2">add</span></ui-btn>
|
<ui-btn small color="bg-success" class="flex items-center" @click="clickCreate">{{ $strings.ButtonCreate }} <span class="material-symbols text-lg pl-2">add</span></ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -56,6 +67,8 @@ export default {
|
||||||
loading: false,
|
loading: false,
|
||||||
savingSettings: false,
|
savingSettings: false,
|
||||||
appriseApiUrl: null,
|
appriseApiUrl: null,
|
||||||
|
ntfyUrl: null,
|
||||||
|
ntfyToken: null,
|
||||||
maxNotificationQueue: 0,
|
maxNotificationQueue: 0,
|
||||||
maxFailedAttempts: 0,
|
maxFailedAttempts: 0,
|
||||||
notifications: [],
|
notifications: [],
|
||||||
|
|
@ -116,17 +129,10 @@ export default {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
submitForm() {
|
saveSettings(payload) {
|
||||||
if (!this.validateForm()) return
|
|
||||||
|
|
||||||
const updatePayload = {
|
|
||||||
appriseApiUrl: this.appriseApiUrl || null,
|
|
||||||
maxNotificationQueue: Number(this.maxNotificationQueue),
|
|
||||||
maxFailedAttempts: Number(this.maxFailedAttempts)
|
|
||||||
}
|
|
||||||
this.savingSettings = true
|
this.savingSettings = true
|
||||||
this.$axios
|
this.$axios
|
||||||
.$patch('/api/notifications', updatePayload)
|
.$patch('/api/notifications', payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success(this.$strings.ToastNotificationSettingsUpdateSuccess)
|
this.$toast.success(this.$strings.ToastNotificationSettingsUpdateSuccess)
|
||||||
})
|
})
|
||||||
|
|
@ -138,6 +144,21 @@ export default {
|
||||||
this.savingSettings = false
|
this.savingSettings = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
submitForm() {
|
||||||
|
if (!this.validateForm()) return
|
||||||
|
|
||||||
|
this.saveSettings({
|
||||||
|
appriseApiUrl: this.appriseApiUrl || null,
|
||||||
|
maxNotificationQueue: Number(this.maxNotificationQueue),
|
||||||
|
maxFailedAttempts: Number(this.maxFailedAttempts)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitNtfyForm() {
|
||||||
|
this.saveSettings({
|
||||||
|
ntfyUrl: this.ntfyUrl || null,
|
||||||
|
ntfyToken: this.ntfyToken || null
|
||||||
|
})
|
||||||
|
},
|
||||||
async init() {
|
async init() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
const notificationResponse = await this.$axios.$get('/api/notifications').catch((error) => {
|
const notificationResponse = await this.$axios.$get('/api/notifications').catch((error) => {
|
||||||
|
|
@ -155,6 +176,8 @@ export default {
|
||||||
setNotificationSettings(notificationSettings) {
|
setNotificationSettings(notificationSettings) {
|
||||||
this.notificationSettings = notificationSettings
|
this.notificationSettings = notificationSettings
|
||||||
this.appriseApiUrl = notificationSettings.appriseApiUrl
|
this.appriseApiUrl = notificationSettings.appriseApiUrl
|
||||||
|
this.ntfyUrl = notificationSettings.ntfyUrl || null
|
||||||
|
this.ntfyToken = notificationSettings.ntfyToken || null
|
||||||
this.maxNotificationQueue = notificationSettings.maxNotificationQueue
|
this.maxNotificationQueue = notificationSettings.maxNotificationQueue
|
||||||
this.maxFailedAttempts = notificationSettings.maxFailedAttempts
|
this.maxFailedAttempts = notificationSettings.maxFailedAttempts
|
||||||
this.notifications = notificationSettings.notifications || []
|
this.notifications = notificationSettings.notifications || []
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@
|
||||||
"HeaderAdvanced": "Advanced",
|
"HeaderAdvanced": "Advanced",
|
||||||
"HeaderApiKeys": "API Keys",
|
"HeaderApiKeys": "API Keys",
|
||||||
"HeaderAppriseNotificationSettings": "Apprise Notification Settings",
|
"HeaderAppriseNotificationSettings": "Apprise Notification Settings",
|
||||||
|
"HeaderNtfyNotificationSettings": "ntfy.sh Notification Settings",
|
||||||
"HeaderAudioTracks": "Audio Tracks",
|
"HeaderAudioTracks": "Audio Tracks",
|
||||||
"HeaderAudiobookTools": "Audiobook File Management Tools",
|
"HeaderAudiobookTools": "Audiobook File Management Tools",
|
||||||
"HeaderAuthentication": "Authentication",
|
"HeaderAuthentication": "Authentication",
|
||||||
|
|
@ -489,6 +490,9 @@
|
||||||
"LabelNotificationAvailableVariables": "Available variables",
|
"LabelNotificationAvailableVariables": "Available variables",
|
||||||
"LabelNotificationBodyTemplate": "Body Template",
|
"LabelNotificationBodyTemplate": "Body Template",
|
||||||
"LabelNotificationEvent": "Notification Event",
|
"LabelNotificationEvent": "Notification Event",
|
||||||
|
"LabelNotificationNtfyTopicPrefix": "ntfy: {0}",
|
||||||
|
"LabelNotificationNtfyTopic": "ntfy Topic",
|
||||||
|
"LabelNotificationProvider": "Provider",
|
||||||
"LabelNotificationTitleTemplate": "Title Template",
|
"LabelNotificationTitleTemplate": "Title Template",
|
||||||
"LabelNotificationsMaxFailedAttempts": "Max failed attempts",
|
"LabelNotificationsMaxFailedAttempts": "Max failed attempts",
|
||||||
"LabelNotificationsMaxFailedAttemptsHelp": "Notifications are disabled once they fail to send this many times",
|
"LabelNotificationsMaxFailedAttemptsHelp": "Notifications are disabled once they fail to send this many times",
|
||||||
|
|
@ -736,6 +740,7 @@
|
||||||
"LabelYourProgress": "Your Progress",
|
"LabelYourProgress": "Your Progress",
|
||||||
"MessageAddToPlayerQueue": "Add to player queue",
|
"MessageAddToPlayerQueue": "Add to player queue",
|
||||||
"MessageAppriseDescription": "To use this feature you will need to have an instance of <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> running or an api that will handle those same requests. <br />The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at <code>http://192.168.1.1:8337</code> then you would put <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "To use this feature you will need to have an instance of <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> running or an api that will handle those same requests. <br />The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at <code>http://192.168.1.1:8337</code> then you would put <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
|
"MessageNtfyDescription": "Send notifications using <a href=\"https://ntfy.sh\" target=\"_blank\">ntfy.sh</a> or a self-hosted ntfy server. The server URL defaults to <code>https://ntfy.sh</code>. Set a topic for each notification when creating it.",
|
||||||
"MessageAsinCheck": "Ensure you are using the ASIN from the correct Audible region, not Amazon.",
|
"MessageAsinCheck": "Ensure you are using the ASIN from the correct Audible region, not Amazon.",
|
||||||
"MessageAuthenticationLegacyTokenWarning": "Legacy API tokens will be removed in the future. Use <a href=\"/config/api-keys\">API Keys</a> instead.",
|
"MessageAuthenticationLegacyTokenWarning": "Legacy API tokens will be removed in the future. Use <a href=\"/config/api-keys\">API Keys</a> instead.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Restart your server after saving to apply OIDC changes.",
|
"MessageAuthenticationOIDCChangesRestart": "Restart your server after saving to apply OIDC changes.",
|
||||||
|
|
@ -983,6 +988,7 @@
|
||||||
"StatsYearInReview": "YEAR IN REVIEW",
|
"StatsYearInReview": "YEAR IN REVIEW",
|
||||||
"ToastAccountUpdateSuccess": "Account updated",
|
"ToastAccountUpdateSuccess": "Account updated",
|
||||||
"ToastAppriseUrlRequired": "Must enter an Apprise URL",
|
"ToastAppriseUrlRequired": "Must enter an Apprise URL",
|
||||||
|
"ToastNtfyTopicRequired": "Must enter an ntfy topic",
|
||||||
"ToastAsinRequired": "ASIN is required",
|
"ToastAsinRequired": "ASIN is required",
|
||||||
"ToastAuthorImageRemoveSuccess": "Author image removed",
|
"ToastAuthorImageRemoveSuccess": "Author image removed",
|
||||||
"ToastAuthorNotFound": "Author \"{0}\" not found",
|
"ToastAuthorNotFound": "Author \"{0}\" not found",
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ class NotificationController {
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async sendNotificationTest(req, res) {
|
async sendNotificationTest(req, res) {
|
||||||
if (!Database.notificationSettings.isUseable) return res.status(400).send('Apprise is not configured')
|
if (!Database.notificationSettings.isUseable) return res.status(400).send('No notification service is configured')
|
||||||
|
|
||||||
const success = await NotificationManager.sendTestNotification(req.notification)
|
const success = await NotificationManager.sendTestNotification(req.notification)
|
||||||
if (success) res.sendStatus(200)
|
if (success) res.sendStatus(200)
|
||||||
|
|
|
||||||
|
|
@ -220,15 +220,45 @@ class NotificationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNotification(notification, eventData) {
|
sendNotification(notification, eventData) {
|
||||||
|
if (notification.ntfyTopic) {
|
||||||
|
return this.sendNtfyNotification(notification, eventData)
|
||||||
|
}
|
||||||
|
return this.sendAppriseNotification(notification, eventData)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAppriseNotification(notification, eventData) {
|
||||||
|
if (!Database.notificationSettings.appriseApiUrl) {
|
||||||
|
Logger.error(`[NotificationManager] sendAppriseNotification: Apprise API URL is not configured`)
|
||||||
|
return Promise.resolve(false)
|
||||||
|
}
|
||||||
const payload = notification.getApprisePayload(eventData)
|
const payload = notification.getApprisePayload(eventData)
|
||||||
return axios
|
return axios
|
||||||
.post(Database.notificationSettings.appriseApiUrl, payload, { timeout: 6000 })
|
.post(Database.notificationSettings.appriseApiUrl, payload, { timeout: 6000 })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
Logger.debug(`[NotificationManager] sendNotification: ${notification.eventName}/${notification.id} response=`, response.data)
|
Logger.debug(`[NotificationManager] sendAppriseNotification: ${notification.eventName}/${notification.id} response=`, response.data)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
Logger.error(`[NotificationManager] sendNotification: ${notification.eventName}/${notification.id} error=`, error)
|
Logger.error(`[NotificationManager] sendAppriseNotification: ${notification.eventName}/${notification.id} error=`, error)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNtfyNotification(notification, eventData) {
|
||||||
|
const payload = notification.getNtfyPayload(eventData)
|
||||||
|
const ntfyUrl = Database.notificationSettings.ntfyUrl || 'https://ntfy.sh'
|
||||||
|
const headers = { 'Content-Type': 'application/json' }
|
||||||
|
if (Database.notificationSettings.ntfyToken) {
|
||||||
|
headers['Authorization'] = `Bearer ${Database.notificationSettings.ntfyToken}`
|
||||||
|
}
|
||||||
|
return axios
|
||||||
|
.post(ntfyUrl, payload, { timeout: 6000, headers })
|
||||||
|
.then((response) => {
|
||||||
|
Logger.debug(`[NotificationManager] sendNtfyNotification: ${notification.eventName}/${notification.id} response=`, response.data)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
Logger.error(`[NotificationManager] sendNtfyNotification: ${notification.eventName}/${notification.id} error=`, error)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class Notification {
|
||||||
this.titleTemplate = ''
|
this.titleTemplate = ''
|
||||||
this.bodyTemplate = ''
|
this.bodyTemplate = ''
|
||||||
this.type = 'info'
|
this.type = 'info'
|
||||||
|
this.ntfyTopic = null
|
||||||
this.enabled = false
|
this.enabled = false
|
||||||
|
|
||||||
this.lastFiredAt = null
|
this.lastFiredAt = null
|
||||||
|
|
@ -30,6 +31,7 @@ class Notification {
|
||||||
this.titleTemplate = notification.titleTemplate || ''
|
this.titleTemplate = notification.titleTemplate || ''
|
||||||
this.bodyTemplate = notification.bodyTemplate || ''
|
this.bodyTemplate = notification.bodyTemplate || ''
|
||||||
this.type = notification.type || 'info'
|
this.type = notification.type || 'info'
|
||||||
|
this.ntfyTopic = notification.ntfyTopic || null
|
||||||
this.enabled = !!notification.enabled
|
this.enabled = !!notification.enabled
|
||||||
this.lastFiredAt = notification.lastFiredAt || null
|
this.lastFiredAt = notification.lastFiredAt || null
|
||||||
this.lastAttemptFailed = !!notification.lastAttemptFailed
|
this.lastAttemptFailed = !!notification.lastAttemptFailed
|
||||||
|
|
@ -48,6 +50,7 @@ class Notification {
|
||||||
bodyTemplate: this.bodyTemplate,
|
bodyTemplate: this.bodyTemplate,
|
||||||
enabled: this.enabled,
|
enabled: this.enabled,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
ntfyTopic: this.ntfyTopic,
|
||||||
lastFiredAt: this.lastFiredAt,
|
lastFiredAt: this.lastFiredAt,
|
||||||
lastAttemptFailed: this.lastAttemptFailed,
|
lastAttemptFailed: this.lastAttemptFailed,
|
||||||
numConsecutiveFailedAttempts: this.numConsecutiveFailedAttempts,
|
numConsecutiveFailedAttempts: this.numConsecutiveFailedAttempts,
|
||||||
|
|
@ -60,11 +63,12 @@ class Notification {
|
||||||
this.id = uuidv4()
|
this.id = uuidv4()
|
||||||
this.libraryId = payload.libraryId || null
|
this.libraryId = payload.libraryId || null
|
||||||
this.eventName = payload.eventName
|
this.eventName = payload.eventName
|
||||||
this.urls = payload.urls
|
this.urls = payload.urls || []
|
||||||
this.titleTemplate = payload.titleTemplate
|
this.titleTemplate = payload.titleTemplate
|
||||||
this.bodyTemplate = payload.bodyTemplate
|
this.bodyTemplate = payload.bodyTemplate
|
||||||
this.enabled = !!payload.enabled
|
this.enabled = !!payload.enabled
|
||||||
this.type = payload.type || null
|
this.type = payload.type || null
|
||||||
|
this.ntfyTopic = payload.ntfyTopic || null
|
||||||
this.createdAt = Date.now()
|
this.createdAt = Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,7 +80,7 @@ class Notification {
|
||||||
this.numConsecutiveFailedAttempts = 0
|
this.numConsecutiveFailedAttempts = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const keysToUpdate = ['libraryId', 'eventName', 'urls', 'titleTemplate', 'bodyTemplate', 'enabled', 'type']
|
const keysToUpdate = ['libraryId', 'eventName', 'urls', 'titleTemplate', 'bodyTemplate', 'enabled', 'type', 'ntfyTopic']
|
||||||
var hasUpdated = false
|
var hasUpdated = false
|
||||||
for (const key of keysToUpdate) {
|
for (const key of keysToUpdate) {
|
||||||
if (payload[key] !== undefined) {
|
if (payload[key] !== undefined) {
|
||||||
|
|
@ -129,5 +133,13 @@ class Notification {
|
||||||
body: this.parseBodyTemplate(data)
|
body: this.parseBodyTemplate(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNtfyPayload(data) {
|
||||||
|
return {
|
||||||
|
topic: this.ntfyTopic,
|
||||||
|
title: this.parseTitleTemplate(data),
|
||||||
|
message: this.parseBodyTemplate(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = Notification
|
module.exports = Notification
|
||||||
|
|
@ -7,6 +7,8 @@ class NotificationSettings {
|
||||||
this.id = 'notification-settings'
|
this.id = 'notification-settings'
|
||||||
this.appriseType = 'api'
|
this.appriseType = 'api'
|
||||||
this.appriseApiUrl = null
|
this.appriseApiUrl = null
|
||||||
|
this.ntfyUrl = null
|
||||||
|
this.ntfyToken = null
|
||||||
this.notifications = []
|
this.notifications = []
|
||||||
this.maxFailedAttempts = 5
|
this.maxFailedAttempts = 5
|
||||||
this.maxNotificationQueue = 20 // once reached events will be ignored
|
this.maxNotificationQueue = 20 // once reached events will be ignored
|
||||||
|
|
@ -20,6 +22,8 @@ class NotificationSettings {
|
||||||
construct(settings) {
|
construct(settings) {
|
||||||
this.appriseType = settings.appriseType
|
this.appriseType = settings.appriseType
|
||||||
this.appriseApiUrl = settings.appriseApiUrl || null
|
this.appriseApiUrl = settings.appriseApiUrl || null
|
||||||
|
this.ntfyUrl = settings.ntfyUrl || null
|
||||||
|
this.ntfyToken = settings.ntfyToken || null
|
||||||
this.notifications = (settings.notifications || []).map((n) => new Notification(n))
|
this.notifications = (settings.notifications || []).map((n) => new Notification(n))
|
||||||
this.maxFailedAttempts = settings.maxFailedAttempts || 5
|
this.maxFailedAttempts = settings.maxFailedAttempts || 5
|
||||||
this.maxNotificationQueue = settings.maxNotificationQueue || 20
|
this.maxNotificationQueue = settings.maxNotificationQueue || 20
|
||||||
|
|
@ -31,6 +35,8 @@ class NotificationSettings {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
appriseType: this.appriseType,
|
appriseType: this.appriseType,
|
||||||
appriseApiUrl: this.appriseApiUrl,
|
appriseApiUrl: this.appriseApiUrl,
|
||||||
|
ntfyUrl: this.ntfyUrl,
|
||||||
|
ntfyToken: this.ntfyToken,
|
||||||
notifications: this.notifications.map((n) => n.toJSON()),
|
notifications: this.notifications.map((n) => n.toJSON()),
|
||||||
maxFailedAttempts: this.maxFailedAttempts,
|
maxFailedAttempts: this.maxFailedAttempts,
|
||||||
maxNotificationQueue: this.maxNotificationQueue,
|
maxNotificationQueue: this.maxNotificationQueue,
|
||||||
|
|
@ -39,7 +45,7 @@ class NotificationSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isUseable() {
|
get isUseable() {
|
||||||
return !!this.appriseApiUrl
|
return !!this.appriseApiUrl || !!this.ntfyUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,29 +80,43 @@ class NotificationSettings {
|
||||||
if (!payload) return false
|
if (!payload) return false
|
||||||
|
|
||||||
var hasUpdates = false
|
var hasUpdates = false
|
||||||
if (payload.appriseApiUrl !== this.appriseApiUrl) {
|
if (payload.appriseApiUrl !== undefined && payload.appriseApiUrl !== this.appriseApiUrl) {
|
||||||
this.appriseApiUrl = payload.appriseApiUrl || null
|
this.appriseApiUrl = payload.appriseApiUrl || null
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const _maxFailedAttempts = isNullOrNaN(payload.maxFailedAttempts) ? 5 : Number(payload.maxFailedAttempts)
|
if (payload.ntfyUrl !== undefined && payload.ntfyUrl !== this.ntfyUrl) {
|
||||||
if (_maxFailedAttempts !== this.maxFailedAttempts) {
|
this.ntfyUrl = payload.ntfyUrl || null
|
||||||
this.maxFailedAttempts = _maxFailedAttempts
|
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const _maxNotificationQueue = isNullOrNaN(payload.maxNotificationQueue) ? 20 : Number(payload.maxNotificationQueue)
|
if (payload.ntfyToken !== undefined && payload.ntfyToken !== this.ntfyToken) {
|
||||||
if (_maxNotificationQueue !== this.maxNotificationQueue) {
|
this.ntfyToken = payload.ntfyToken || null
|
||||||
this.maxNotificationQueue = _maxNotificationQueue
|
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.maxFailedAttempts !== undefined) {
|
||||||
|
const _maxFailedAttempts = isNullOrNaN(payload.maxFailedAttempts) ? 5 : Number(payload.maxFailedAttempts)
|
||||||
|
if (_maxFailedAttempts !== this.maxFailedAttempts) {
|
||||||
|
this.maxFailedAttempts = _maxFailedAttempts
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.maxNotificationQueue !== undefined) {
|
||||||
|
const _maxNotificationQueue = isNullOrNaN(payload.maxNotificationQueue) ? 20 : Number(payload.maxNotificationQueue)
|
||||||
|
if (_maxNotificationQueue !== this.maxNotificationQueue) {
|
||||||
|
this.maxNotificationQueue = _maxNotificationQueue
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return hasUpdates
|
return hasUpdates
|
||||||
}
|
}
|
||||||
|
|
||||||
createNotification(payload) {
|
createNotification(payload) {
|
||||||
if (!payload) return false
|
if (!payload) return false
|
||||||
if (!payload.eventName || !payload.urls.length) return false
|
if (!payload.eventName || (!payload.urls?.length && !payload.ntfyTopic)) return false
|
||||||
|
|
||||||
const notification = new Notification()
|
const notification = new Notification()
|
||||||
notification.setData(payload)
|
notification.setData(payload)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue