mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Update plugin to use uuid for id, update example plugin with taskmanager and socketauthority test
This commit is contained in:
parent
cfe3deff3b
commit
fc17a74865
@ -42,7 +42,7 @@ export default {
|
|||||||
if (!store.getters['user/getIsAdminOrUp']) {
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
redirect('/')
|
redirect('/')
|
||||||
}
|
}
|
||||||
const plugin = store.state.plugins.find((plugin) => plugin.slug === params.slug)
|
const plugin = store.state.plugins.find((plugin) => plugin.id === params.id)
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
redirect('/config/plugins')
|
redirect('/config/plugins')
|
||||||
}
|
}
|
||||||
@ -95,14 +95,13 @@ export default {
|
|||||||
console.log('Form data', formData)
|
console.log('Form data', formData)
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
pluginSlug: this.plugin.slug,
|
|
||||||
config: formData
|
config: formData
|
||||||
}
|
}
|
||||||
|
|
||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/plugins/config`, payload)
|
.$post(`/api/plugins/${this.plugin.id}/config`, payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Plugin configuration saved')
|
console.log('Plugin configuration saved')
|
||||||
})
|
})
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<h2 class="text-xl font-medium">Installed Plugins</h2>
|
<h2 class="text-xl font-medium">Installed Plugins</h2>
|
||||||
<template v-for="plugin in plugins">
|
<template v-for="plugin in plugins">
|
||||||
<nuxt-link :key="plugin.slug" :to="`/config/plugins/${plugin.slug}`" class="flex items-center bg-primary rounded-md shadow-sm p-4 my-4 space-x-4">
|
<nuxt-link :key="plugin.id" :to="`/config/plugins/${plugin.id}`" class="flex items-center bg-primary rounded-md shadow-sm p-4 my-4 space-x-4">
|
||||||
<p class="text-lg">{{ plugin.name }}</p>
|
<p class="text-lg">{{ plugin.name }}</p>
|
||||||
<p class="text-sm text-gray-300">{{ plugin.description }}</p>
|
<p class="text-sm text-gray-300">{{ plugin.description }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
@ -434,11 +434,10 @@ export default {
|
|||||||
|
|
||||||
if (this.pluginExtensions.length) {
|
if (this.pluginExtensions.length) {
|
||||||
this.pluginExtensions.forEach((plugin) => {
|
this.pluginExtensions.forEach((plugin) => {
|
||||||
const pluginSlug = plugin.slug
|
|
||||||
plugin.extensions.forEach((pext) => {
|
plugin.extensions.forEach((pext) => {
|
||||||
items.push({
|
items.push({
|
||||||
text: pext.label,
|
text: pext.label,
|
||||||
action: `plugin-${pluginSlug}-action-${pext.name}`
|
action: `plugin-${plugin.id}-action-${pext.name}`
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -780,16 +779,15 @@ export default {
|
|||||||
this.$store.commit('globals/setShareModal', this.mediaItemShare)
|
this.$store.commit('globals/setShareModal', this.mediaItemShare)
|
||||||
} else if (action.startsWith('plugin-')) {
|
} else if (action.startsWith('plugin-')) {
|
||||||
const actionStrSplit = action.replace('plugin-', '').split('-action-')
|
const actionStrSplit = action.replace('plugin-', '').split('-action-')
|
||||||
const pluginSlug = actionStrSplit[0]
|
const pluginId = actionStrSplit[0]
|
||||||
const pluginAction = actionStrSplit[1]
|
const pluginAction = actionStrSplit[1]
|
||||||
console.log('Plugin action for', pluginSlug, 'with action', pluginAction)
|
console.log('Plugin action for', pluginId, 'with action', pluginAction)
|
||||||
this.onPluginAction(pluginSlug, pluginAction)
|
this.onPluginAction(pluginId, pluginAction)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPluginAction(pluginSlug, pluginAction) {
|
onPluginAction(pluginId, pluginAction) {
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/plugins/action`, {
|
.$post(`/api/plugins/${pluginId}/action`, {
|
||||||
pluginSlug,
|
|
||||||
pluginAction,
|
pluginAction,
|
||||||
target: 'item.detail.actions',
|
target: 'item.detail.actions',
|
||||||
data: {
|
data: {
|
||||||
|
@ -10,13 +10,14 @@ const ExtractJwt = require('passport-jwt').ExtractJwt
|
|||||||
const OpenIDClient = require('openid-client')
|
const OpenIDClient = require('openid-client')
|
||||||
const Database = require('./Database')
|
const Database = require('./Database')
|
||||||
const Logger = require('./Logger')
|
const Logger = require('./Logger')
|
||||||
const PluginManager = require('./managers/PluginManager')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Class for handling all the authentication related functionality.
|
* @class Class for handling all the authentication related functionality.
|
||||||
*/
|
*/
|
||||||
class Auth {
|
class Auth {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.pluginData = []
|
||||||
|
|
||||||
// Map of openId sessions indexed by oauth2 state-variable
|
// Map of openId sessions indexed by oauth2 state-variable
|
||||||
this.openIdAuthSession = new Map()
|
this.openIdAuthSession = new Map()
|
||||||
this.ignorePatterns = [/\/api\/items\/[^/]+\/cover/, /\/api\/authors\/[^/]+\/image/]
|
this.ignorePatterns = [/\/api\/items\/[^/]+\/cover/, /\/api\/authors\/[^/]+\/image/]
|
||||||
@ -939,7 +940,7 @@ class Auth {
|
|||||||
userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
|
userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
|
||||||
serverSettings: Database.serverSettings.toJSONForBrowser(),
|
serverSettings: Database.serverSettings.toJSONForBrowser(),
|
||||||
ereaderDevices: Database.emailSettings.getEReaderDevices(user),
|
ereaderDevices: Database.emailSettings.getEReaderDevices(user),
|
||||||
plugins: PluginManager.pluginData,
|
plugins: this.pluginData,
|
||||||
Source: global.Source
|
Source: global.Source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
class PluginAbstract {
|
|
||||||
constructor() {
|
|
||||||
if (this.constructor === PluginAbstract) {
|
|
||||||
throw new Error('Cannot instantiate abstract class')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
throw new Error('Method "init()" not implemented')
|
|
||||||
}
|
|
||||||
|
|
||||||
onAction() {
|
|
||||||
throw new Error('Method "onAction()" not implemented')
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy() {
|
|
||||||
throw new Error('Method "onDestroy()" not implemented')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = PluginAbstract
|
|
@ -154,7 +154,9 @@ class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize plugins
|
// Initialize plugins
|
||||||
PluginManager.init()
|
await PluginManager.init()
|
||||||
|
// TODO: Prevents circular dependency for SocketAuthority
|
||||||
|
this.auth.pluginData = PluginManager.pluginData
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,32 +6,32 @@ class PluginController {
|
|||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST: /api/plugins/action
|
* POST: /api/plugins/:id/action
|
||||||
*
|
*
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
handleAction(req, res) {
|
handleAction(req, res) {
|
||||||
const pluginSlug = req.body.pluginSlug
|
const pluginId = req.params.id
|
||||||
const actionName = req.body.pluginAction
|
const actionName = req.body.pluginAction
|
||||||
const target = req.body.target
|
const target = req.body.target
|
||||||
const data = req.body.data
|
const data = req.body.data
|
||||||
Logger.info(`[PluginController] Handle plugin action ${pluginSlug} ${actionName} ${target}`, data)
|
Logger.info(`[PluginController] Handle plugin action ${pluginId} ${actionName} ${target}`, data)
|
||||||
PluginManager.onAction(pluginSlug, actionName, target, data)
|
PluginManager.onAction(pluginId, actionName, target, data)
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST: /api/plugins/config
|
* POST: /api/plugins/:id/config
|
||||||
*
|
*
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
* @param {*} res
|
* @param {*} res
|
||||||
*/
|
*/
|
||||||
handleConfigSave(req, res) {
|
handleConfigSave(req, res) {
|
||||||
const pluginSlug = req.body.pluginSlug
|
const pluginId = req.params.id
|
||||||
const config = req.body.config
|
const config = req.body.config
|
||||||
Logger.info(`[PluginController] Saving config for plugin ${pluginSlug}`, config)
|
Logger.info(`[PluginController] Saving config for plugin ${pluginId}`, config)
|
||||||
PluginManager.onConfigSave(pluginSlug, config)
|
PluginManager.onConfigSave(pluginId, config)
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
const PluginAbstract = require('../PluginAbstract')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
|
const TaskManager = require('../managers/TaskManager')
|
||||||
const fsExtra = require('../libs/fsExtra')
|
const fsExtra = require('../libs/fsExtra')
|
||||||
const { isUUID, parseSemverStrict } = require('../utils')
|
const { isUUID, parseSemverStrict } = require('../utils')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef PluginContext
|
* @typedef PluginContext
|
||||||
* @property {import('../../server/Logger')} Logger
|
* @property {import('../Logger')} Logger
|
||||||
* @property {import('../../server/Database')} Database
|
* @property {import('../Database')} Database
|
||||||
|
* @property {import('../SocketAuthority')} SocketAuthority
|
||||||
|
* @property {import('../managers/TaskManager')} TaskManager
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class PluginManager {
|
class PluginManager {
|
||||||
@ -30,7 +33,9 @@ class PluginManager {
|
|||||||
get pluginContext() {
|
get pluginContext() {
|
||||||
return {
|
return {
|
||||||
Logger,
|
Logger,
|
||||||
Database
|
Database,
|
||||||
|
SocketAuthority,
|
||||||
|
TaskManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +45,7 @@ class PluginManager {
|
|||||||
*
|
*
|
||||||
* @param {string} dirname
|
* @param {string} dirname
|
||||||
* @param {string} pluginPath
|
* @param {string} pluginPath
|
||||||
* @returns {Promise<{manifest: Object, contents: PluginAbstract}>}
|
* @returns {Promise<{manifest: Object, contents: any}>}
|
||||||
*/
|
*/
|
||||||
async loadPlugin(dirname, pluginPath) {
|
async loadPlugin(dirname, pluginPath) {
|
||||||
const pluginFiles = await fsExtra.readdir(pluginPath, { withFileTypes: true }).then((files) => files.filter((file) => !file.isDirectory()))
|
const pluginFiles = await fsExtra.readdir(pluginPath, { withFileTypes: true }).then((files) => files.filter((file) => !file.isDirectory()))
|
||||||
@ -98,7 +103,11 @@ class PluginManager {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
manifest: manifestJson,
|
manifest: manifestJson,
|
||||||
instance: pluginInstance
|
instance: {
|
||||||
|
init: pluginInstance.init,
|
||||||
|
onAction: pluginInstance.onAction,
|
||||||
|
onConfigSave: pluginInstance.onConfigSave
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,10 +190,18 @@ class PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onAction(pluginSlug, actionName, target, data) {
|
/**
|
||||||
const plugin = this.plugins.find((plugin) => plugin.manifest.slug === pluginSlug)
|
*
|
||||||
|
* @param {string} pluginId
|
||||||
|
* @param {string} actionName
|
||||||
|
* @param {string} target
|
||||||
|
* @param {Object} data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onAction(pluginId, actionName, target, data) {
|
||||||
|
const plugin = this.plugins.find((plugin) => plugin.manifest.id === pluginId)
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
Logger.error(`[PluginManager] Plugin ${pluginSlug} not found`)
|
Logger.error(`[PluginManager] Plugin ${pluginId} not found`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,10 +217,15 @@ class PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onConfigSave(pluginSlug, config) {
|
/**
|
||||||
const plugin = this.plugins.find((plugin) => plugin.manifest.slug === pluginSlug)
|
*
|
||||||
|
* @param {string} pluginId
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
onConfigSave(pluginId, config) {
|
||||||
|
const plugin = this.plugins.find((plugin) => plugin.manifest.id === pluginId)
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
Logger.error(`[PluginManager] Plugin ${pluginSlug} not found`)
|
Logger.error(`[PluginManager] Plugin ${pluginId} not found`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,8 +324,8 @@ class ApiRouter {
|
|||||||
//
|
//
|
||||||
// Plugin routes
|
// Plugin routes
|
||||||
//
|
//
|
||||||
this.router.post('/plugins/action', PluginController.handleAction.bind(this))
|
this.router.post('/plugins/:id/action', PluginController.handleAction.bind(this))
|
||||||
this.router.post('/plugins/config', PluginController.handleConfigSave.bind(this))
|
this.router.post('/plugins/:id/config', PluginController.handleConfigSave.bind(this))
|
||||||
|
|
||||||
//
|
//
|
||||||
// Misc Routes
|
// Misc Routes
|
||||||
|
@ -1,26 +1,54 @@
|
|||||||
class ExamplePlugin {
|
/**
|
||||||
constructor() {
|
* Called on initialization of the plugin
|
||||||
this.name = 'Example'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
*
|
||||||
* @param {import('../../server/managers/PluginManager').PluginContext} context
|
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||||
*/
|
*/
|
||||||
async init(context) {
|
module.exports.init = async (context) => {
|
||||||
context.Logger.info('[ExamplePlugin] Example plugin loaded successfully')
|
context.Logger.info('[ExamplePlugin] Example plugin initialized')
|
||||||
|
|
||||||
context.Database.mediaProgressModel.addHook('afterSave', (instance, options) => {
|
context.Database.mediaProgressModel.addHook('afterSave', (instance, options) => {
|
||||||
context.Logger.debug(`[ExamplePlugin] mediaProgressModel afterSave hook for mediaProgress ${instance.id}`)
|
context.Logger.debug(`[ExamplePlugin] mediaProgressModel afterSave hook for mediaProgress ${instance.id}`)
|
||||||
this.handleMediaProgressUpdate(context, instance)
|
handleMediaProgressUpdate(context, instance)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
sendAdminMessageToast(context)
|
||||||
* @param {import('../../server/managers/PluginManager').PluginContext} context
|
}
|
||||||
* @param {import('../../server/models/MediaProgress')} mediaProgress
|
|
||||||
|
/**
|
||||||
|
* Called when an extension action is triggered
|
||||||
|
*
|
||||||
|
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||||
|
* @param {string} actionName
|
||||||
|
* @param {string} target
|
||||||
|
* @param {*} data
|
||||||
*/
|
*/
|
||||||
async handleMediaProgressUpdate(context, mediaProgress) {
|
module.exports.onAction = async (context, actionName, target, data) => {
|
||||||
|
context.Logger.info('[ExamplePlugin] Example plugin onAction', actionName, target, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin config page is saved
|
||||||
|
*
|
||||||
|
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||||
|
* @param {*} config
|
||||||
|
*/
|
||||||
|
module.exports.onConfigSave = async (context, config) => {
|
||||||
|
context.Logger.info('[ExamplePlugin] Example plugin onConfigSave', config)
|
||||||
|
|
||||||
|
createTask(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Helper functions
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrobble media progress update
|
||||||
|
*
|
||||||
|
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||||
|
* @param {import('../../../server/models/MediaProgress')} mediaProgress
|
||||||
|
*/
|
||||||
|
async function handleMediaProgressUpdate(context, mediaProgress) {
|
||||||
const mediaItem = await mediaProgress.getMediaItem()
|
const mediaItem = await mediaProgress.getMediaItem()
|
||||||
if (!mediaItem) {
|
if (!mediaItem) {
|
||||||
context.Logger.error(`[ExamplePlugin] Media item not found for mediaProgress ${mediaProgress.id}`)
|
context.Logger.error(`[ExamplePlugin] Media item not found for mediaProgress ${mediaProgress.id}`)
|
||||||
@ -29,26 +57,28 @@ class ExamplePlugin {
|
|||||||
const progressPercent = mediaProgressDuration > 0 ? (mediaProgress.currentTime / mediaProgressDuration) * 100 : 0
|
const progressPercent = mediaProgressDuration > 0 ? (mediaProgress.currentTime / mediaProgressDuration) * 100 : 0
|
||||||
context.Logger.info(`[ExamplePlugin] Media progress update for "${mediaItem.title}" ${Math.round(progressPercent)}%`)
|
context.Logger.info(`[ExamplePlugin] Media progress update for "${mediaItem.title}" ${Math.round(progressPercent)}%`)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {import('../../server/managers/PluginManager').PluginContext} context
|
|
||||||
* @param {string} actionName
|
|
||||||
* @param {string} target
|
|
||||||
* @param {*} data
|
|
||||||
*/
|
|
||||||
async onAction(context, actionName, target, data) {
|
|
||||||
context.Logger.info('[ExamplePlugin] Example plugin onAction', actionName, target, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {import('../../server/managers/PluginManager').PluginContext} context
|
|
||||||
* @param {*} config
|
|
||||||
*/
|
|
||||||
async onConfigSave(context, config) {
|
|
||||||
context.Logger.info('[ExamplePlugin] Example plugin onConfigSave', config)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
module.exports = new ExamplePlugin()
|
|
||||||
|
/**
|
||||||
|
* Test socket authority
|
||||||
|
*
|
||||||
|
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||||
|
*/
|
||||||
|
async function sendAdminMessageToast(context) {
|
||||||
|
setTimeout(() => {
|
||||||
|
context.SocketAuthority.adminEmitter('admin_message', 'Hello from ExamplePlugin!')
|
||||||
|
}, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test task manager
|
||||||
|
*
|
||||||
|
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||||
|
*/
|
||||||
|
async function createTask(context) {
|
||||||
|
const task = context.TaskManager.createAndAddTask('example_action', { text: 'Example Task' }, { text: 'This is an example task' }, true)
|
||||||
|
setTimeout(() => {
|
||||||
|
task.setFinished({ text: 'Example Task Finished' })
|
||||||
|
context.TaskManager.taskFinished(task)
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Example Plugin",
|
"id": "e6205690-916c-4add-9a2b-2548266996ef",
|
||||||
|
"name": "Example",
|
||||||
"slug": "example",
|
"slug": "example",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "This is an example plugin",
|
"description": "This is an example plugin",
|
||||||
|
Loading…
Reference in New Issue
Block a user