mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Add:Notification settings, notification manager trigger #996
This commit is contained in:
parent
9a7503cde2
commit
ff04eb8d5e
@ -82,6 +82,11 @@ export default {
|
||||
id: 'config-log',
|
||||
title: 'Logs',
|
||||
path: '/config/log'
|
||||
},
|
||||
{
|
||||
id: 'config-notifications',
|
||||
title: 'Notifications',
|
||||
path: '/config/notifications'
|
||||
}
|
||||
]
|
||||
|
||||
|
73
client/pages/config/notifications.vue
Normal file
73
client/pages/config/notifications.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-8 mb-2 max-w-3xl mx-auto">
|
||||
<h2 class="text-xl font-semibold mb-2">Apprise Notification Settings</h2>
|
||||
<p class="mb-6">Insert some text here describing this feature</p>
|
||||
|
||||
<form @submit.prevent="submitForm">
|
||||
<ui-text-input-with-label v-model="appriseApiUrl" label="Apprise API Url" />
|
||||
<div class="flex items-center justify-end pt-4">
|
||||
<ui-btn type="submit">Save</ui-btn>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
appriseApiUrl: null,
|
||||
notifications: [],
|
||||
notificationSettings: null
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
submitForm() {
|
||||
if (this.notificationSettings && this.notificationSettings.appriseApiUrl == this.appriseApiUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Validate apprise api url
|
||||
|
||||
const updatePayload = {
|
||||
appriseApiUrl: this.appriseApiUrl || null
|
||||
}
|
||||
this.loading = true
|
||||
this.$axios
|
||||
.$patch('/api/notifications', updatePayload)
|
||||
.then(() => {
|
||||
this.$toast.success('Notification settings updated')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update notification settings', error)
|
||||
this.$toast.error('Failed to update notification settings')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
async init() {
|
||||
this.loading = true
|
||||
const notificationSettings = await this.$axios.$get('/api/notifications').catch((error) => {
|
||||
console.error('Failed to get notification settings', error)
|
||||
this.$toast.error('Failed to load notification settings')
|
||||
return null
|
||||
})
|
||||
this.loading = false
|
||||
if (!notificationSettings) {
|
||||
return
|
||||
}
|
||||
this.notificationSettings = notificationSettings
|
||||
this.appriseApiUrl = notificationSettings.appriseApiUrl
|
||||
this.notifications = notificationSettings.notifications || []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
12
server/Db.js
12
server/Db.js
@ -9,8 +9,8 @@ const Library = require('./objects/Library')
|
||||
const Author = require('./objects/entities/Author')
|
||||
const Series = require('./objects/entities/Series')
|
||||
const ServerSettings = require('./objects/settings/ServerSettings')
|
||||
const NotificationSettings = require('./objects/settings/NotificationSettings')
|
||||
const PlaybackSession = require('./objects/PlaybackSession')
|
||||
const Feed = require('./objects/Feed')
|
||||
|
||||
class Db {
|
||||
constructor() {
|
||||
@ -43,6 +43,7 @@ class Db {
|
||||
this.series = []
|
||||
|
||||
this.serverSettings = null
|
||||
this.notificationSettings = null
|
||||
|
||||
// Stores previous version only if upgraded
|
||||
this.previousVersion = null
|
||||
@ -125,6 +126,10 @@ class Db {
|
||||
this.serverSettings = new ServerSettings()
|
||||
await this.insertEntity('settings', this.serverSettings)
|
||||
}
|
||||
if (!this.notificationSettings) {
|
||||
this.notificationSettings = new NotificationSettings()
|
||||
await this.insertEntity('settings', this.notificationSettings)
|
||||
}
|
||||
global.ServerSettings = this.serverSettings.toJSON()
|
||||
}
|
||||
|
||||
@ -166,6 +171,11 @@ class Db {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var notificationSettings = this.settings.find(s => s.id === 'notification-settings')
|
||||
if (notificationSettings) {
|
||||
this.notificationSettings = new NotificationSettings(notificationSettings)
|
||||
}
|
||||
}
|
||||
})
|
||||
var p5 = this.collectionsDb.select(() => true).then((results) => {
|
||||
|
25
server/controllers/NotificationController.js
Normal file
25
server/controllers/NotificationController.js
Normal file
@ -0,0 +1,25 @@
|
||||
const Logger = require('../Logger')
|
||||
|
||||
class NotificationController {
|
||||
constructor() { }
|
||||
|
||||
get(req, res) {
|
||||
res.json(this.db.notificationSettings)
|
||||
}
|
||||
|
||||
async update(req, res) {
|
||||
const updated = this.db.notificationSettings.update(req.body)
|
||||
if (updated) {
|
||||
await this.db.updateEntity('settings', this.db.notificationSettings)
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
middleware(req, res, next) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
module.exports = new NotificationController()
|
@ -1,10 +1,55 @@
|
||||
const axios = require('axios')
|
||||
const Logger = require("../Logger")
|
||||
|
||||
class NotificationManager {
|
||||
constructor() { }
|
||||
constructor(db) {
|
||||
this.db = db
|
||||
|
||||
onNewPodcastEpisode(libraryItem, episode) {
|
||||
Logger.debug(`[NotificationManager] onNewPodcastEpisode: Episode "${episode.title}" for podcast ${libraryItem.media.metadata.title}`)
|
||||
this.notificationFailedMap = {}
|
||||
}
|
||||
|
||||
onPodcastEpisodeDownloaded(libraryItem, episode) {
|
||||
if (!this.db.notificationSettings.isUseable) return
|
||||
|
||||
Logger.debug(`[NotificationManager] onPodcastEpisodeDownloaded: Episode "${episode.title}" for podcast ${libraryItem.media.metadata.title}`)
|
||||
this.triggerNotification('onPodcastEpisodeDownloaded', { libraryItem, episode })
|
||||
}
|
||||
|
||||
onTest() {
|
||||
this.triggerNotification('onTest')
|
||||
}
|
||||
|
||||
async triggerNotification(eventName, eventData) {
|
||||
if (!this.db.notificationSettings.isUseable) return
|
||||
|
||||
const notifications = this.db.notificationSettings.getNotificationsForEvent(eventName)
|
||||
for (const notification of notifications) {
|
||||
Logger.debug(`[NotificationManager] triggerNotification: Sending ${eventName} notification ${notification.id}`)
|
||||
const success = await this.sendNotification(notification, eventData)
|
||||
|
||||
if (!success) { // Failed notification
|
||||
if (!this.notificationFailedMap[notification.id]) this.notificationFailedMap[notification.id] = 1
|
||||
else this.notificationFailedMap[notification.id]++
|
||||
|
||||
if (this.notificationFailedMap[notification.id] > 2) {
|
||||
Logger.error(`[NotificationManager] triggerNotification: ${notification.eventName}/${notification.id} reached max failed attempts`)
|
||||
// TODO: Do something like disable the notification
|
||||
}
|
||||
} else { // Successful notification
|
||||
delete this.notificationFailedMap[notification.id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendNotification(notification, eventData) {
|
||||
const payload = notification.getApprisePayload(eventData)
|
||||
return axios.post(`${this.db.notificationSettings.appriseApiUrl}/notify`, payload, { timeout: 6000 }).then((data) => {
|
||||
Logger.debug(`[NotificationManager] sendNotification: ${notification.eventName}/${notification.id} response=${data}`)
|
||||
return true
|
||||
}).catch((error) => {
|
||||
Logger.error(`[NotificationManager] sendNotification: ${notification.eventName}/${notification.id} error=`, error)
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = NotificationManager
|
@ -136,7 +136,7 @@ class PodcastManager {
|
||||
this.emitter('item_updated', libraryItem.toJSONExpanded())
|
||||
|
||||
if (this.currentDownload.isAutoDownload) { // Notifications only for auto downloaded episodes
|
||||
this.notificationManager.onNewPodcastEpisode(libraryItem, podcastEpisode)
|
||||
this.notificationManager.onPodcastEpisodeDownloaded(libraryItem, podcastEpisode)
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -1,10 +1,12 @@
|
||||
class Notification {
|
||||
constructor(notification = null) {
|
||||
this.id = null
|
||||
this.libraryId = null
|
||||
this.eventName = ''
|
||||
this.urls = []
|
||||
this.titleTemplate = ''
|
||||
this.bodyTemplate = ''
|
||||
this.type = 'info'
|
||||
this.enabled = false
|
||||
|
||||
this.createdAt = null
|
||||
@ -16,10 +18,12 @@ class Notification {
|
||||
|
||||
construct(notification) {
|
||||
this.id = notification.id
|
||||
this.libraryId = notification.libraryId || null
|
||||
this.eventName = notification.eventName
|
||||
this.urls = notification.urls || []
|
||||
this.titleTemplate = notification.titleTemplate || ''
|
||||
this.bodyTemplate = notification.bodyTemplate || ''
|
||||
this.type = notification.type || 'info'
|
||||
this.enabled = !!notification.enabled
|
||||
this.createdAt = notification.createdAt
|
||||
}
|
||||
@ -27,13 +31,33 @@ class Notification {
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
libraryId: this.libraryId,
|
||||
eventName: this.eventName,
|
||||
urls: this.urls,
|
||||
titleTemplate: this.titleTemplate,
|
||||
bodyTemplate: this.bodyTemplate,
|
||||
enabled: this.enabled,
|
||||
type: this.type,
|
||||
createdAt: this.createdAt
|
||||
}
|
||||
}
|
||||
|
||||
parseTitleTemplate(data) {
|
||||
// TODO: Implement template parsing
|
||||
return 'Test Title'
|
||||
}
|
||||
|
||||
parseBodyTemplate(data) {
|
||||
// TODO: Implement template parsing
|
||||
return 'Test Body'
|
||||
}
|
||||
|
||||
getApprisePayload(data) {
|
||||
return {
|
||||
urls: this.urls,
|
||||
title: this.parseTitleTemplate(data),
|
||||
body: this.parseBodyTemplate(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = Notification
|
45
server/objects/settings/NotificationSettings.js
Normal file
45
server/objects/settings/NotificationSettings.js
Normal file
@ -0,0 +1,45 @@
|
||||
class NotificationSettings {
|
||||
constructor(settings = null) {
|
||||
this.id = 'notification-settings'
|
||||
this.appriseType = 'api'
|
||||
this.appriseApiUrl = null
|
||||
this.notifications = []
|
||||
|
||||
if (settings) {
|
||||
this.construct(settings)
|
||||
}
|
||||
}
|
||||
|
||||
construct(settings) {
|
||||
this.appriseType = settings.appriseType
|
||||
this.appriseApiUrl = settings.appriseApiUrl || null
|
||||
this.notifications = (settings.notifications || []).map(n => ({ ...n }))
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
appriseType: this.appriseType,
|
||||
appriseApiUrl: this.appriseApiUrl,
|
||||
notifications: this.notifications.map(n => n.toJSON())
|
||||
}
|
||||
}
|
||||
|
||||
get isUseable() {
|
||||
return !!this.appriseApiUrl
|
||||
}
|
||||
|
||||
getNotificationsForEvent(eventName) {
|
||||
return this.notifications.filter(n => n.eventName === eventName)
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
if (!payload) return false
|
||||
if (payload.appriseApiUrl !== this.appriseApiUrl) {
|
||||
this.appriseApiUrl = payload.appriseApiUrl || null
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
module.exports = NotificationSettings
|
@ -14,6 +14,7 @@ const SeriesController = require('../controllers/SeriesController')
|
||||
const AuthorController = require('../controllers/AuthorController')
|
||||
const SessionController = require('../controllers/SessionController')
|
||||
const PodcastController = require('../controllers/PodcastController')
|
||||
const NotificationController = require('../controllers/NotificationController')
|
||||
const MiscController = require('../controllers/MiscController')
|
||||
|
||||
const BookFinder = require('../finders/BookFinder')
|
||||
@ -199,6 +200,12 @@ class ApiRouter {
|
||||
this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this))
|
||||
this.router.delete('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.removeEpisode.bind(this))
|
||||
|
||||
//
|
||||
// Notification Routes
|
||||
//
|
||||
this.router.get('/notifications', NotificationController.middleware.bind(this), NotificationController.get.bind(this))
|
||||
this.router.patch('/notifications', NotificationController.middleware.bind(this), NotificationController.update.bind(this))
|
||||
|
||||
//
|
||||
// Misc Routes
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user