mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-14 15:59:38 +00:00
Add:Email smtp config & send ebooks to devices #1474
This commit is contained in:
parent
15aaf2863c
commit
05ce9c6eda
40 changed files with 1077 additions and 99 deletions
|
|
@ -120,6 +120,7 @@ class Auth {
|
|||
user: user.toJSONForBrowser(),
|
||||
userDefaultLibraryId: user.getDefaultLibraryId(this.db.libraries),
|
||||
serverSettings: this.db.serverSettings.toJSONForBrowser(),
|
||||
ereaderDevices: this.db.emailSettings.getEReaderDevices(user),
|
||||
Source: global.Source
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
server/Db.js
11
server/Db.js
|
|
@ -12,6 +12,7 @@ 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 EmailSettings = require('./objects/settings/EmailSettings')
|
||||
const PlaybackSession = require('./objects/PlaybackSession')
|
||||
|
||||
class Db {
|
||||
|
|
@ -49,6 +50,7 @@ class Db {
|
|||
|
||||
this.serverSettings = null
|
||||
this.notificationSettings = null
|
||||
this.emailSettings = null
|
||||
|
||||
// Stores previous version only if upgraded
|
||||
this.previousVersion = null
|
||||
|
|
@ -156,6 +158,10 @@ class Db {
|
|||
this.notificationSettings = new NotificationSettings()
|
||||
await this.insertEntity('settings', this.notificationSettings)
|
||||
}
|
||||
if (!this.emailSettings) {
|
||||
this.emailSettings = new EmailSettings()
|
||||
await this.insertEntity('settings', this.emailSettings)
|
||||
}
|
||||
global.ServerSettings = this.serverSettings.toJSON()
|
||||
}
|
||||
|
||||
|
|
@ -202,6 +208,11 @@ class Db {
|
|||
if (notificationSettings) {
|
||||
this.notificationSettings = new NotificationSettings(notificationSettings)
|
||||
}
|
||||
|
||||
const emailSettings = this.settings.find(s => s.id === 'email-settings')
|
||||
if (emailSettings) {
|
||||
this.emailSettings = new EmailSettings(emailSettings)
|
||||
}
|
||||
}
|
||||
})
|
||||
const p5 = this.collectionsDb.select(() => true).then((results) => {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const HlsRouter = require('./routers/HlsRouter')
|
|||
const StaticRouter = require('./routers/StaticRouter')
|
||||
|
||||
const NotificationManager = require('./managers/NotificationManager')
|
||||
const EmailManager = require('./managers/EmailManager')
|
||||
const CoverManager = require('./managers/CoverManager')
|
||||
const AbMergeManager = require('./managers/AbMergeManager')
|
||||
const CacheManager = require('./managers/CacheManager')
|
||||
|
|
@ -66,6 +67,7 @@ class Server {
|
|||
// Managers
|
||||
this.taskManager = new TaskManager()
|
||||
this.notificationManager = new NotificationManager(this.db)
|
||||
this.emailManager = new EmailManager(this.db)
|
||||
this.backupManager = new BackupManager(this.db)
|
||||
this.logManager = new LogManager(this.db)
|
||||
this.cacheManager = new CacheManager()
|
||||
|
|
|
|||
86
server/controllers/EmailController.js
Normal file
86
server/controllers/EmailController.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
|
||||
class EmailController {
|
||||
constructor() { }
|
||||
|
||||
getSettings(req, res) {
|
||||
res.json({
|
||||
settings: this.db.emailSettings
|
||||
})
|
||||
}
|
||||
|
||||
async updateSettings(req, res) {
|
||||
const updated = this.db.emailSettings.update(req.body)
|
||||
if (updated) {
|
||||
await this.db.updateEntity('settings', this.db.emailSettings)
|
||||
}
|
||||
res.json({
|
||||
settings: this.db.emailSettings
|
||||
})
|
||||
}
|
||||
|
||||
async sendTest(req, res) {
|
||||
this.emailManager.sendTest(res)
|
||||
}
|
||||
|
||||
async updateEReaderDevices(req, res) {
|
||||
if (!req.body.ereaderDevices || !Array.isArray(req.body.ereaderDevices)) {
|
||||
return res.status(400).send('Invalid payload. ereaderDevices array required')
|
||||
}
|
||||
|
||||
const ereaderDevices = req.body.ereaderDevices
|
||||
for (const device of ereaderDevices) {
|
||||
if (!device.name || !device.email) {
|
||||
return res.status(400).send('Invalid payload. ereaderDevices array items must have name and email')
|
||||
}
|
||||
}
|
||||
|
||||
const updated = this.db.emailSettings.update({
|
||||
ereaderDevices
|
||||
})
|
||||
if (updated) {
|
||||
await this.db.updateEntity('settings', this.db.emailSettings)
|
||||
SocketAuthority.adminEmitter('ereader-devices-updated', {
|
||||
ereaderDevices: this.db.emailSettings.ereaderDevices
|
||||
})
|
||||
}
|
||||
res.json({
|
||||
ereaderDevices: this.db.emailSettings.ereaderDevices
|
||||
})
|
||||
}
|
||||
|
||||
async sendEBookToDevice(req, res) {
|
||||
Logger.debug(`[EmailController] Send ebook to device request for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`)
|
||||
|
||||
const libraryItem = this.db.getLibraryItem(req.body.libraryItemId)
|
||||
if (!libraryItem) {
|
||||
return res.status(404).send('Library item not found')
|
||||
}
|
||||
|
||||
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const ebookFile = libraryItem.media.ebookFile
|
||||
if (!ebookFile) {
|
||||
return res.status(404).send('EBook file not found')
|
||||
}
|
||||
|
||||
const device = this.db.emailSettings.getEReaderDevice(req.body.deviceName)
|
||||
if (!device) {
|
||||
return res.status(404).send('E-reader device not found')
|
||||
}
|
||||
|
||||
this.emailManager.sendEBookToDevice(ebookFile, device, res)
|
||||
}
|
||||
|
||||
middleware(req, res, next) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
module.exports = new EmailController()
|
||||
73
server/managers/EmailManager.js
Normal file
73
server/managers/EmailManager.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
const nodemailer = require('nodemailer')
|
||||
const Logger = require("../Logger")
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
|
||||
class EmailManager {
|
||||
constructor(db) {
|
||||
this.db = db
|
||||
}
|
||||
|
||||
getTransporter() {
|
||||
return nodemailer.createTransport(this.db.emailSettings.getTransportObject())
|
||||
}
|
||||
|
||||
async sendTest(res) {
|
||||
Logger.info(`[EmailManager] Sending test email`)
|
||||
const transporter = this.getTransporter()
|
||||
|
||||
const success = await transporter.verify().catch((error) => {
|
||||
Logger.error(`[EmailManager] Failed to verify SMTP connection config`, error)
|
||||
return false
|
||||
})
|
||||
|
||||
if (!success) {
|
||||
return res.status(400).send('Failed to verify SMTP connection configuration')
|
||||
}
|
||||
|
||||
transporter.sendMail({
|
||||
from: this.db.emailSettings.fromAddress,
|
||||
to: this.db.emailSettings.fromAddress,
|
||||
subject: 'Test email from Audiobookshelf',
|
||||
text: 'Success!'
|
||||
}).then((result) => {
|
||||
Logger.info(`[EmailManager] Test email sent successfully`, result)
|
||||
res.sendStatus(200)
|
||||
}).catch((error) => {
|
||||
Logger.error(`[EmailManager] Failed to send test email`, error)
|
||||
res.status(400).send(error.message || 'Failed to send test email')
|
||||
})
|
||||
}
|
||||
|
||||
async sendEBookToDevice(ebookFile, device, res) {
|
||||
Logger.info(`[EmailManager] Sending ebook "${ebookFile.metadata.filename}" to device "${device.name}"/"${device.email}"`)
|
||||
const transporter = this.getTransporter()
|
||||
|
||||
const success = await transporter.verify().catch((error) => {
|
||||
Logger.error(`[EmailManager] Failed to verify SMTP connection config`, error)
|
||||
return false
|
||||
})
|
||||
|
||||
if (!success) {
|
||||
return res.status(400).send('Failed to verify SMTP connection configuration')
|
||||
}
|
||||
|
||||
transporter.sendMail({
|
||||
from: this.db.emailSettings.fromAddress,
|
||||
to: device.email,
|
||||
html: '<div dir="auto"></div>',
|
||||
attachments: [
|
||||
{
|
||||
filename: ebookFile.metadata.filename,
|
||||
path: ebookFile.metadata.path,
|
||||
}
|
||||
]
|
||||
}).then((result) => {
|
||||
Logger.info(`[EmailManager] Ebook sent to device successfully`, result)
|
||||
res.sendStatus(200)
|
||||
}).catch((error) => {
|
||||
Logger.error(`[EmailManager] Failed to send ebook to device`, error)
|
||||
res.status(400).send(error.message || 'Failed to send ebook to device')
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = EmailManager
|
||||
101
server/objects/settings/EmailSettings.js
Normal file
101
server/objects/settings/EmailSettings.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
const Logger = require('../../Logger')
|
||||
const { areEquivalent, copyValue, isNullOrNaN } = require('../../utils')
|
||||
|
||||
// REF: https://nodemailer.com/smtp/
|
||||
class EmailSettings {
|
||||
constructor(settings = null) {
|
||||
this.id = 'email-settings'
|
||||
this.host = null
|
||||
this.port = 465
|
||||
this.secure = true
|
||||
this.user = null
|
||||
this.pass = null
|
||||
this.fromAddress = null
|
||||
|
||||
// Array of { name:String, email:String }
|
||||
this.ereaderDevices = []
|
||||
|
||||
if (settings) {
|
||||
this.construct(settings)
|
||||
}
|
||||
}
|
||||
|
||||
construct(settings) {
|
||||
this.host = settings.host
|
||||
this.port = settings.port
|
||||
this.secure = !!settings.secure
|
||||
this.user = settings.user
|
||||
this.pass = settings.pass
|
||||
this.fromAddress = settings.fromAddress
|
||||
this.ereaderDevices = settings.ereaderDevices?.map(d => ({ ...d })) || []
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
secure: this.secure,
|
||||
user: this.user,
|
||||
pass: this.pass,
|
||||
fromAddress: this.fromAddress,
|
||||
ereaderDevices: this.ereaderDevices.map(d => ({ ...d }))
|
||||
}
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
if (!payload) return false
|
||||
|
||||
if (payload.port !== undefined) {
|
||||
if (isNullOrNaN(payload.port)) payload.port = 465
|
||||
else payload.port = Number(payload.port)
|
||||
}
|
||||
if (payload.secure !== undefined) payload.secure = !!payload.secure
|
||||
|
||||
if (payload.ereaderDevices !== undefined && !Array.isArray(payload.ereaderDevices)) payload.ereaderDevices = undefined
|
||||
|
||||
let hasUpdates = false
|
||||
|
||||
const json = this.toJSON()
|
||||
for (const key in json) {
|
||||
if (key === 'id') continue
|
||||
|
||||
if (payload[key] !== undefined && !areEquivalent(payload[key], json[key])) {
|
||||
this[key] = copyValue(payload[key])
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
getTransportObject() {
|
||||
const payload = {
|
||||
host: this.host,
|
||||
secure: this.secure
|
||||
}
|
||||
if (this.port) payload.port = this.port
|
||||
if (this.user && this.pass !== undefined) {
|
||||
payload.auth = {
|
||||
user: this.user,
|
||||
pass: this.pass
|
||||
}
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
getEReaderDevices(user) {
|
||||
// Only accessible to admin or up
|
||||
if (!user.isAdminOrUp) {
|
||||
return []
|
||||
}
|
||||
|
||||
return this.ereaderDevices.map(d => ({ ...d }))
|
||||
}
|
||||
|
||||
getEReaderDevice(deviceName) {
|
||||
return this.ereaderDevices.find(d => d.name === deviceName)
|
||||
}
|
||||
}
|
||||
module.exports = EmailSettings
|
||||
|
|
@ -20,6 +20,7 @@ const AuthorController = require('../controllers/AuthorController')
|
|||
const SessionController = require('../controllers/SessionController')
|
||||
const PodcastController = require('../controllers/PodcastController')
|
||||
const NotificationController = require('../controllers/NotificationController')
|
||||
const EmailController = require('../controllers/EmailController')
|
||||
const SearchController = require('../controllers/SearchController')
|
||||
const CacheController = require('../controllers/CacheController')
|
||||
const ToolsController = require('../controllers/ToolsController')
|
||||
|
|
@ -50,6 +51,7 @@ class ApiRouter {
|
|||
this.rssFeedManager = Server.rssFeedManager
|
||||
this.cronManager = Server.cronManager
|
||||
this.notificationManager = Server.notificationManager
|
||||
this.emailManager = Server.emailManager
|
||||
this.taskManager = Server.taskManager
|
||||
|
||||
this.bookFinder = new BookFinder()
|
||||
|
|
@ -259,6 +261,15 @@ class ApiRouter {
|
|||
this.router.patch('/notifications/:id', NotificationController.middleware.bind(this), NotificationController.updateNotification.bind(this))
|
||||
this.router.get('/notifications/:id/test', NotificationController.middleware.bind(this), NotificationController.sendNotificationTest.bind(this))
|
||||
|
||||
//
|
||||
// Email Routes (Admin and up)
|
||||
//
|
||||
this.router.get('/emails/settings', EmailController.middleware.bind(this), EmailController.getSettings.bind(this))
|
||||
this.router.patch('/emails/settings', EmailController.middleware.bind(this), EmailController.updateSettings.bind(this))
|
||||
this.router.post('/emails/test', EmailController.middleware.bind(this), EmailController.sendTest.bind(this))
|
||||
this.router.post('/emails/ereader-devices', EmailController.middleware.bind(this), EmailController.updateEReaderDevices.bind(this))
|
||||
this.router.post('/emails/send-ebook-to-device', EmailController.middleware.bind(this), EmailController.sendEBookToDevice.bind(this))
|
||||
|
||||
//
|
||||
// Search Routes
|
||||
//
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue