diff --git a/server/SocketAuthority.js b/server/SocketAuthority.js index f2d634b2b..6d13d42ef 100644 --- a/server/SocketAuthority.js +++ b/server/SocketAuthority.js @@ -3,6 +3,7 @@ const Logger = require('./Logger') const Database = require('./Database') const Auth = require('./Auth') const NotificationManager = require('./managers/NotificationManager') +const { flattenAny } = require('./utils/objectUtils') /** * @typedef SocketClient @@ -16,7 +17,7 @@ class SocketAuthority { constructor() { this.Server = null this.socketIoServers = [] - this.emittedNotifications = new Set(['item_created', 'item_updated']); + this.emittedNotifications = new Set(['item_added', 'item_updated', 'user_online']); /** @type {Object.} */ this.clients = {} @@ -110,7 +111,7 @@ class SocketAuthority { * @param {import('./models/LibraryItem')} libraryItem */ libraryItemEmitter(evt, libraryItem) { - void this._fireNotification(evt, libraryItem) + void this._fireNotification(evt, flattenAny(libraryItem.toOldJSONMinified())) for (const socketId in this.clients) { if (this.clients[socketId].user?.checkCanAccessLibraryItem(libraryItem)) { this.clients[socketId].socket.emit(evt, libraryItem.toOldJSONExpanded()) @@ -126,7 +127,6 @@ class SocketAuthority { * @param {import('./models/LibraryItem')[]} libraryItems */ libraryItemsEmitter(evt, libraryItems) { - void this._fireNotification(evt, libraryItems) for (const socketId in this.clients) { if (this.clients[socketId].user) { const libraryItemsAccessibleToUser = libraryItems.filter((li) => this.clients[socketId].user.checkCanAccessLibraryItem(li)) diff --git a/server/managers/NotificationManager.js b/server/managers/NotificationManager.js index fec77c4a5..4ccdde198 100644 --- a/server/managers/NotificationManager.js +++ b/server/managers/NotificationManager.js @@ -90,14 +90,6 @@ class NotificationManager { this.triggerNotification('onBackupFailed', eventData) } - /** - * @param - */ - async onItemUpdated(libraryItem) { - console.log('onItemUpdated', libraryItem) - this.triggerNotification('onItemUpdated', libraryItem) - } - onTest() { this.triggerNotification('onTest') } @@ -132,7 +124,8 @@ class NotificationManager { } await Database.updateSetting(Database.notificationSettings) - //SocketAuthority.emitter('notifications_updated', Database.notificationSettings.toJSON()) + // Currently results in circular dependency TODO: Fix this + // SocketAuthority.emitter('notifications_updated', Database.notificationSettings.toJSON()) this.notificationFinished() } @@ -199,6 +192,9 @@ class NotificationManager { const eventNameModified = eventName.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); const eventKey = `on${eventNameModified.charAt(0).toUpperCase()}${eventNameModified.slice(1)}`; + console.log(eventData) + console.log(Object.keys(eventData)) + if (!Database.notificationSettings.getHasActiveNotificationsForEvent(eventKey)) { // No logging to prevent console spam Logger.debug(`[NotificationManager] fireSocketNotification: No active notifications`) @@ -207,11 +203,7 @@ class NotificationManager { Logger.debug(`[NotificationManager] fireNotificationFromSocket: ${eventKey} event fired`) - switch (eventKey) { - case 'onItemUpdated': - void this.onItemUpdated(eventData) - break - } + void this.triggerNotification(eventKey, eventData) } } module.exports = new NotificationManager() diff --git a/server/objects/Notification.js b/server/objects/Notification.js index d075e1011..8d97697c0 100644 --- a/server/objects/Notification.js +++ b/server/objects/Notification.js @@ -102,7 +102,7 @@ class Notification { } replaceVariablesInTemplate(templateText, data) { - const ptrn = /{{ ?([a-zA-Z]+) ?}}/mg + const ptrn = /{{ ?([a-zA-Z.]+) ?}}/mg var match var updatedTemplate = templateText @@ -130,4 +130,4 @@ class Notification { } } } -module.exports = Notification \ No newline at end of file +module.exports = Notification diff --git a/server/utils/notifications.js b/server/utils/notifications.js index 8295a503c..dccd43b66 100644 --- a/server/utils/notifications.js +++ b/server/utils/notifications.js @@ -4,29 +4,48 @@ const LibraryItem = require('../models/LibraryItem') const libraryItemVariables = [ 'id', 'ino', + 'oldLibraryItemId', + 'libraryId', + 'folderId', 'path', 'relPath', - 'mediaId', - 'mediaType', 'isFile', + 'mtimeMs', + 'ctimeMs', + 'birthtimeMs', + 'addedAt', + 'updatedAt', 'isMissing', 'isInvalid', - 'mtime', - 'ctime', - 'birthtime', - 'size', - 'lastScan', - 'lastScanVersion', - 'libraryFiles', - 'extraData', - 'title', - 'titleIgnorePrefix', - 'authorNamesFirstLast', - 'authorNamesLastFirst', - 'createdAt', - 'updatedAt', - 'libraryId', - 'libraryFolderId', + 'mediaType', + 'media.id', + 'media.metadata.title', + 'media.metadata.titleIgnorePrefix', + 'media.metadata.subtitle', + 'media.metadata.authorName', + 'media.metadata.authorNameLF', + 'media.metadata.narratorName', + 'media.metadata.seriesName', + 'media.metadata.genres', + 'media.metadata.publishedYear', + 'media.metadata.publishedDate', + 'media.metadata.publisher', + 'media.metadata.description', + 'media.metadata.isbn', + 'media.metadata.asin', + 'media.metadata.language', + 'media.metadata.explicit', + 'media.metadata.abridged', + 'media.coverPath', + 'media.tags', + 'media.numTracks', + 'media.numAudioFiles', + 'media.numChapters', + 'media.duration', + 'media.size', + 'media.ebookFormat', + 'numFiles', + 'size' ] const libraryItemTestData = { @@ -119,6 +138,18 @@ module.exports.notificationData = { }, // Sockets - Silently crying because not using typescript + { + name: 'onItemAdded', + requiresLibrary: true, + description: 'Triggered when an item is added', + descriptionKey: 'NotificationOnItemAddedDescription', + variables: libraryItemVariables, + defaults: { + title: 'Item Added: {{media.metadata.title}}', + body: 'Item {{media.metadata.title}} has been added.\n\nPath: {{path}}\nSize: {{size}} bytes\nLibrary ID: {{libraryId}}' + }, + testData: libraryItemTestData + }, { name: 'onItemUpdated', requiresLibrary: true, @@ -126,11 +157,22 @@ module.exports.notificationData = { descriptionKey: 'NotificationOnItemUpdatedDescription', variables: libraryItemVariables, defaults: { - title: 'Item Updated: {{title}}', - body: 'Item {{title}} has been updated.\n\nPath: {{path}}\nSize: {{size}} bytes\nLast Scan: {{lastScan}}\nLibrary ID: {{libraryId}}\nLibrary Folder ID: {{libraryFolderId}}' + title: 'Item Updated: {{media.metadata.title}}', + body: 'Item {{media.metadata.title}} has been added.\n\nPath: {{path}}\nSize: {{size}} bytes\nLibrary ID: {{libraryId}}' }, testData: libraryItemTestData }, + { + name: 'onUserOnline', + requiresLibrary: false, + description: 'Triggered when a user comes online', + descriptionKey: 'NotificationOnUserOnlineDescription', + variables: [ 'id', 'username', 'type', 'session', 'lastSeen', 'createdAt'], + defaults: { + title: 'User Online: {{username}}', + body: 'User {{username}} (ID: {{id}}) is now online.' + }, + }, // Test { diff --git a/server/utils/objectUtils.js b/server/utils/objectUtils.js new file mode 100644 index 000000000..6ac3133e5 --- /dev/null +++ b/server/utils/objectUtils.js @@ -0,0 +1,26 @@ +function flattenAny(obj, prefix = '', result = {}) { + const entries = + obj instanceof Map + ? obj.entries() + : Object.entries(obj); + + for (const [key, value] of entries) { + const newKey = prefix ? `${prefix}.${key}` : `${key}`; + if ( + value instanceof Map || + (typeof value === 'object' && + value !== null && + !Array.isArray(value)) + ) { + flattenAny(value, newKey, result); + } else { + result[newKey] = value; + } + } + return result; +} + + +module.exports = { + flattenAny +}