mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Updates on plugin apis and example
This commit is contained in:
parent
fc17a74865
commit
048790b33a
@ -31,6 +31,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<button v-else :key="index" role="menuitem" class="flex items-center px-2 py-1.5 hover:bg-white/5 text-white text-xs cursor-pointer w-full" @click.stop="clickAction(item.action)">
|
||||
<span v-if="item.icon" class="material-symbols text-base mr-1">{{ item.icon }}</span>
|
||||
<p class="text-left">{{ item.text }}</p>
|
||||
</button>
|
||||
</template>
|
||||
|
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<app-settings-content :header-text="`Plugin: ${plugin.name}`">
|
||||
<app-settings-content :header-text="`Plugin: ${pluginManifest.name}`">
|
||||
<template #header-prefix>
|
||||
<nuxt-link to="/config/plugins" class="w-8 h-8 flex items-center justify-center rounded-full cursor-pointer hover:bg-white hover:bg-opacity-10 text-center mr-2">
|
||||
<span class="material-symbols text-2xl">arrow_back</span>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template #header-items>
|
||||
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||
<a href="https://www.audiobookshelf.org/guides" target="_blank" class="inline-flex">
|
||||
<ui-tooltip v-if="pluginManifest.documentationUrl" :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||
<a :href="pluginManifest.documentationUrl" target="_blank" class="inline-flex">
|
||||
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
|
||||
</a>
|
||||
</ui-tooltip>
|
||||
@ -38,16 +38,24 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
asyncData({ store, redirect, params }) {
|
||||
async asyncData({ store, redirect, params, app }) {
|
||||
if (!store.getters['user/getIsAdminOrUp']) {
|
||||
redirect('/')
|
||||
}
|
||||
const plugin = store.state.plugins.find((plugin) => plugin.id === params.id)
|
||||
if (!plugin) {
|
||||
const pluginConfigData = await app.$axios.$get(`/api/plugins/${params.id}/config`).catch((error) => {
|
||||
console.error('Failed to get plugin config', error)
|
||||
return null
|
||||
})
|
||||
if (!pluginConfigData?.config) {
|
||||
redirect('/config/plugins')
|
||||
}
|
||||
const pluginManifest = store.state.plugins.find((plugin) => plugin.id === params.id)
|
||||
if (!pluginManifest) {
|
||||
redirect('/config/plugins')
|
||||
}
|
||||
return {
|
||||
plugin
|
||||
pluginManifest,
|
||||
pluginConfig: pluginConfigData.config
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -56,11 +64,11 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pluginConfig() {
|
||||
return this.plugin.config
|
||||
pluginManifestConfig() {
|
||||
return this.pluginManifest.config
|
||||
},
|
||||
pluginLocalization() {
|
||||
return this.plugin.localization || {}
|
||||
return this.pluginManifest.localization || {}
|
||||
},
|
||||
localizedStrings() {
|
||||
const localeKey = this.$languageCodes.current
|
||||
@ -68,14 +76,14 @@ export default {
|
||||
return this.pluginLocalization[localeKey] || {}
|
||||
},
|
||||
configDescription() {
|
||||
if (this.pluginConfig.descriptionKey && this.localizedStrings[this.pluginConfig.descriptionKey]) {
|
||||
return this.localizedStrings[this.pluginConfig.descriptionKey]
|
||||
if (this.pluginManifestConfig.descriptionKey && this.localizedStrings[this.pluginManifestConfig.descriptionKey]) {
|
||||
return this.localizedStrings[this.pluginManifestConfig.descriptionKey]
|
||||
}
|
||||
|
||||
return this.pluginConfig.description
|
||||
return this.pluginManifestConfig.description
|
||||
},
|
||||
configFormFields() {
|
||||
return this.pluginConfig.formFields || []
|
||||
return this.pluginManifestConfig.formFields || []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -101,21 +109,37 @@ export default {
|
||||
this.processing = true
|
||||
|
||||
this.$axios
|
||||
.$post(`/api/plugins/${this.plugin.id}/config`, payload)
|
||||
.$post(`/api/plugins/${this.pluginManifest.id}/config`, payload)
|
||||
.then(() => {
|
||||
console.log('Plugin configuration saved')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error saving plugin configuration', error)
|
||||
this.$toast.error('Error saving plugin configuration')
|
||||
const errorMsg = error.response?.data || 'Error saving plugin configuration'
|
||||
console.error('Failed to save config:', error)
|
||||
this.$toast.error(errorMsg)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
})
|
||||
},
|
||||
initializeForm() {
|
||||
this.configFormFields.forEach((field) => {
|
||||
if (this.pluginConfig[field.name] === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const value = this.pluginConfig[field.name]
|
||||
if (field.type === 'checkbox') {
|
||||
document.getElementById(field.name).checked = value
|
||||
} else {
|
||||
document.getElementById(field.name).value = value
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('Plugin', this.plugin)
|
||||
console.log('Plugin manifest', this.pluginManifest, 'config', this.pluginConfig)
|
||||
this.initializeForm()
|
||||
},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
|
@ -437,7 +437,8 @@ export default {
|
||||
plugin.extensions.forEach((pext) => {
|
||||
items.push({
|
||||
text: pext.label,
|
||||
action: `plugin-${plugin.id}-action-${pext.name}`
|
||||
action: `plugin-${plugin.id}-action-${pext.name}`,
|
||||
icon: 'extension'
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -781,7 +782,6 @@ export default {
|
||||
const actionStrSplit = action.replace('plugin-', '').split('-action-')
|
||||
const pluginId = actionStrSplit[0]
|
||||
const pluginAction = actionStrSplit[1]
|
||||
console.log('Plugin action for', pluginId, 'with action', pluginAction)
|
||||
this.onPluginAction(pluginId, pluginAction)
|
||||
}
|
||||
},
|
||||
@ -799,7 +799,9 @@ export default {
|
||||
console.log('Plugin action response', data)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Plugin action failed', error)
|
||||
const errorMsg = error.response?.data || 'Plugin action failed'
|
||||
console.error('Plugin action failed:', error)
|
||||
this.$toast.error(errorMsg)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -69,8 +69,8 @@ export const getters = {
|
||||
const extensionsMatchingTarget = pext.extensions?.filter((ext) => ext.target === target) || []
|
||||
if (!extensionsMatchingTarget.length) return null
|
||||
return {
|
||||
id: pext.id,
|
||||
name: pext.name,
|
||||
slug: pext.slug,
|
||||
extensions: extensionsMatchingTarget
|
||||
}
|
||||
})
|
||||
|
@ -16,7 +16,7 @@ const Logger = require('./Logger')
|
||||
*/
|
||||
class Auth {
|
||||
constructor() {
|
||||
this.pluginData = []
|
||||
this.pluginManifests = []
|
||||
|
||||
// Map of openId sessions indexed by oauth2 state-variable
|
||||
this.openIdAuthSession = new Map()
|
||||
@ -940,7 +940,7 @@ class Auth {
|
||||
userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
|
||||
serverSettings: Database.serverSettings.toJSONForBrowser(),
|
||||
ereaderDevices: Database.emailSettings.getEReaderDevices(user),
|
||||
plugins: this.pluginData,
|
||||
plugins: this.pluginManifests,
|
||||
Source: global.Source
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ class Server {
|
||||
// Initialize plugins
|
||||
await PluginManager.init()
|
||||
// TODO: Prevents circular dependency for SocketAuthority
|
||||
this.auth.pluginData = PluginManager.pluginData
|
||||
this.auth.pluginManifests = PluginManager.pluginManifests
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,38 +1,82 @@
|
||||
const { Request, Response } = require('express')
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const PluginManager = require('../managers/PluginManager')
|
||||
const Logger = require('../Logger')
|
||||
|
||||
class PluginController {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
getConfig(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
res.json({
|
||||
config: req.pluginData.instance.config
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* POST: /api/plugins/:id/action
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
handleAction(req, res) {
|
||||
const pluginId = req.params.id
|
||||
async handleAction(req, res) {
|
||||
const actionName = req.body.pluginAction
|
||||
const target = req.body.target
|
||||
const data = req.body.data
|
||||
Logger.info(`[PluginController] Handle plugin action ${pluginId} ${actionName} ${target}`, data)
|
||||
PluginManager.onAction(pluginId, actionName, target, data)
|
||||
Logger.info(`[PluginController] Handle plugin "${req.pluginData.manifest.name}" action ${actionName} ${target}`, data)
|
||||
const actionData = await PluginManager.onAction(req.pluginData, actionName, target, data)
|
||||
if (!actionData || actionData.error) {
|
||||
return res.status(400).send(actionData?.error || 'Error performing action')
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
/**
|
||||
* POST: /api/plugins/:id/config
|
||||
*
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
handleConfigSave(req, res) {
|
||||
const pluginId = req.params.id
|
||||
async handleConfigSave(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
if (!req.body.config || typeof req.body.config !== 'object') {
|
||||
return res.status(400).send('Invalid config')
|
||||
}
|
||||
|
||||
const config = req.body.config
|
||||
Logger.info(`[PluginController] Saving config for plugin ${pluginId}`, config)
|
||||
PluginManager.onConfigSave(pluginId, config)
|
||||
Logger.info(`[PluginController] Handle save config for plugin ${req.pluginData.manifest.name}`, config)
|
||||
const saveData = await PluginManager.onConfigSave(req.pluginData, config)
|
||||
if (!saveData || saveData.error) {
|
||||
return res.status(400).send(saveData?.error || 'Error saving config')
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
if (req.params.id) {
|
||||
const pluginData = PluginManager.getPluginDataById(req.params.id)
|
||||
if (!pluginData) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
await pluginData.instance.reload()
|
||||
req.pluginData = pluginData
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
module.exports = new PluginController()
|
||||
|
@ -12,10 +12,22 @@ const { isUUID, parseSemverStrict } = require('../utils')
|
||||
* @property {import('../Database')} Database
|
||||
* @property {import('../SocketAuthority')} SocketAuthority
|
||||
* @property {import('../managers/TaskManager')} TaskManager
|
||||
* @property {import('../models/Plugin')} pluginInstance
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef PluginData
|
||||
* @property {string} id
|
||||
* @property {Object} manifest
|
||||
* @property {import('../models/Plugin')} instance
|
||||
* @property {Function} init
|
||||
* @property {Function} onAction
|
||||
* @property {Function} onConfigSave
|
||||
*/
|
||||
|
||||
class PluginManager {
|
||||
constructor() {
|
||||
/** @type {PluginData[]} */
|
||||
this.plugins = []
|
||||
}
|
||||
|
||||
@ -23,29 +35,41 @@ class PluginManager {
|
||||
return Path.posix.join(global.MetadataPath, 'plugins')
|
||||
}
|
||||
|
||||
get pluginData() {
|
||||
get pluginManifests() {
|
||||
return this.plugins.map((plugin) => plugin.manifest)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/Plugin')} pluginInstance
|
||||
* @returns {PluginContext}
|
||||
*/
|
||||
get pluginContext() {
|
||||
getPluginContext(pluginInstance) {
|
||||
return {
|
||||
Logger,
|
||||
Database,
|
||||
SocketAuthority,
|
||||
TaskManager
|
||||
TaskManager,
|
||||
pluginInstance
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @returns {PluginData}
|
||||
*/
|
||||
getPluginDataById(id) {
|
||||
return this.plugins.find((plugin) => plugin.manifest.id === id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and load a plugin from a directory
|
||||
* TODO: Validatation
|
||||
*
|
||||
* @param {string} dirname
|
||||
* @param {string} pluginPath
|
||||
* @returns {Promise<{manifest: Object, contents: any}>}
|
||||
* @returns {Promise<PluginData>}
|
||||
*/
|
||||
async loadPlugin(dirname, pluginPath) {
|
||||
const pluginFiles = await fsExtra.readdir(pluginPath, { withFileTypes: true }).then((files) => files.filter((file) => !file.isDirectory()))
|
||||
@ -88,26 +112,25 @@ class PluginManager {
|
||||
return null
|
||||
}
|
||||
|
||||
let pluginInstance = null
|
||||
let pluginContents = null
|
||||
try {
|
||||
pluginInstance = require(Path.join(pluginPath, indexFile.name))
|
||||
pluginContents = require(Path.join(pluginPath, indexFile.name))
|
||||
} catch (error) {
|
||||
Logger.error(`Error loading plugin ${pluginPath}`, error)
|
||||
return null
|
||||
}
|
||||
|
||||
if (typeof pluginInstance.init !== 'function') {
|
||||
if (typeof pluginContents.init !== 'function') {
|
||||
Logger.error(`Plugin ${pluginPath} does not have an init function`)
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: manifestJson.id,
|
||||
manifest: manifestJson,
|
||||
instance: {
|
||||
init: pluginInstance.init,
|
||||
onAction: pluginInstance.onAction,
|
||||
onConfigSave: pluginInstance.onConfigSave
|
||||
}
|
||||
init: pluginContents.init,
|
||||
onAction: pluginContents.onAction,
|
||||
onConfigSave: pluginContents.onConfigSave
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,8 +177,9 @@ class PluginManager {
|
||||
} else {
|
||||
Logger.debug(`[PluginManager] Plugin "${plugin.manifest.name}" already exists in the database with version "${plugin.manifest.version}"`)
|
||||
}
|
||||
plugin.instance = existingPlugin
|
||||
} else {
|
||||
await Database.pluginModel.create({
|
||||
plugin.instance = await Database.pluginModel.create({
|
||||
id: plugin.manifest.id,
|
||||
name: plugin.manifest.name,
|
||||
version: plugin.manifest.version
|
||||
@ -183,81 +207,49 @@ class PluginManager {
|
||||
await this.loadPlugins()
|
||||
|
||||
for (const plugin of this.plugins) {
|
||||
if (plugin.instance.init) {
|
||||
Logger.info(`[PluginManager] Initializing plugin ${plugin.manifest.name}`)
|
||||
plugin.instance.init(this.pluginContext)
|
||||
}
|
||||
Logger.info(`[PluginManager] Initializing plugin ${plugin.manifest.name}`)
|
||||
plugin.init(this.getPluginContext(plugin.instance))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} pluginId
|
||||
* @param {PluginData} plugin
|
||||
* @param {string} actionName
|
||||
* @param {string} target
|
||||
* @param {Object} data
|
||||
* @returns
|
||||
* @returns {Promise<boolean|{error:string}>}
|
||||
*/
|
||||
onAction(pluginId, actionName, target, data) {
|
||||
const plugin = this.plugins.find((plugin) => plugin.manifest.id === pluginId)
|
||||
if (!plugin) {
|
||||
Logger.error(`[PluginManager] Plugin ${pluginId} not found`)
|
||||
return
|
||||
onAction(plugin, actionName, target, data) {
|
||||
if (!plugin.onAction) {
|
||||
Logger.error(`[PluginManager] onAction not implemented for plugin ${plugin.manifest.name}`)
|
||||
return false
|
||||
}
|
||||
|
||||
const pluginExtension = plugin.manifest.extensions.find((extension) => extension.name === actionName)
|
||||
if (!pluginExtension) {
|
||||
Logger.error(`[PluginManager] Extension ${actionName} not found for plugin ${plugin.manifest.name}`)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if (plugin.instance.onAction) {
|
||||
Logger.info(`[PluginManager] Calling onAction for plugin ${plugin.manifest.name}`)
|
||||
plugin.instance.onAction(this.pluginContext, actionName, target, data)
|
||||
}
|
||||
Logger.info(`[PluginManager] Calling onAction for plugin ${plugin.manifest.name}`)
|
||||
return plugin.onAction(this.getPluginContext(plugin.instance), actionName, target, data)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} pluginId
|
||||
* @param {PluginData} plugin
|
||||
* @param {Object} config
|
||||
* @returns {Promise<boolean|{error:string}>}
|
||||
*/
|
||||
onConfigSave(pluginId, config) {
|
||||
const plugin = this.plugins.find((plugin) => plugin.manifest.id === pluginId)
|
||||
if (!plugin) {
|
||||
Logger.error(`[PluginManager] Plugin ${pluginId} not found`)
|
||||
return
|
||||
onConfigSave(plugin, config) {
|
||||
if (!plugin.onConfigSave) {
|
||||
Logger.error(`[PluginManager] onConfigSave not implemented for plugin ${plugin.manifest.name}`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (plugin.instance.onConfigSave) {
|
||||
Logger.info(`[PluginManager] Calling onConfigSave for plugin ${plugin.manifest.name}`)
|
||||
plugin.instance.onConfigSave(this.pluginContext, config)
|
||||
}
|
||||
}
|
||||
|
||||
pluginExists(name) {
|
||||
return this.plugins.some((plugin) => plugin.name === name)
|
||||
}
|
||||
|
||||
registerPlugin(plugin) {
|
||||
if (!plugin.name) {
|
||||
throw new Error('The plugin name and package are required')
|
||||
}
|
||||
|
||||
if (this.pluginExists(plugin.name)) {
|
||||
throw new Error(`Cannot add existing plugin ${plugin.name}`)
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to load the plugin
|
||||
const pluginPath = Path.join(this.pluginMetadataPath, plugin.name)
|
||||
const packageContents = require(pluginPath)
|
||||
console.log('packageContents', packageContents)
|
||||
packageContents.init()
|
||||
this.plugins.push(packageContents)
|
||||
} catch (error) {
|
||||
console.log(`Cannot load plugin ${plugin.name}`, error)
|
||||
}
|
||||
Logger.info(`[PluginManager] Calling onConfigSave for plugin ${plugin.manifest.name}`)
|
||||
return plugin.onConfigSave(this.getPluginContext(plugin.instance), config)
|
||||
}
|
||||
}
|
||||
module.exports = new PluginManager()
|
||||
|
@ -324,8 +324,9 @@ class ApiRouter {
|
||||
//
|
||||
// Plugin routes
|
||||
//
|
||||
this.router.post('/plugins/:id/action', PluginController.handleAction.bind(this))
|
||||
this.router.post('/plugins/:id/config', PluginController.handleConfigSave.bind(this))
|
||||
this.router.get('/plugins/:id/config', PluginController.middleware.bind(this), PluginController.getConfig.bind(this))
|
||||
this.router.post('/plugins/:id/action', PluginController.middleware.bind(this), PluginController.handleAction.bind(this))
|
||||
this.router.post('/plugins/:id/config', PluginController.middleware.bind(this), PluginController.handleConfigSave.bind(this))
|
||||
|
||||
//
|
||||
// Misc Routes
|
||||
|
@ -4,14 +4,22 @@
|
||||
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||
*/
|
||||
module.exports.init = async (context) => {
|
||||
context.Logger.info('[ExamplePlugin] Example plugin initialized')
|
||||
// Set default config on first init
|
||||
if (!context.pluginInstance.config) {
|
||||
context.Logger.info('[ExamplePlugin] First init. Setting default config')
|
||||
context.pluginInstance.config = {
|
||||
requestAddress: '',
|
||||
enable: false
|
||||
}
|
||||
await context.pluginInstance.save()
|
||||
}
|
||||
|
||||
context.Database.mediaProgressModel.addHook('afterSave', (instance, options) => {
|
||||
context.Logger.debug(`[ExamplePlugin] mediaProgressModel afterSave hook for mediaProgress ${instance.id}`)
|
||||
handleMediaProgressUpdate(context, instance)
|
||||
})
|
||||
|
||||
sendAdminMessageToast(context)
|
||||
context.Logger.info('[ExamplePlugin] Example plugin initialized')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -21,9 +29,14 @@ module.exports.init = async (context) => {
|
||||
* @param {string} actionName
|
||||
* @param {string} target
|
||||
* @param {*} data
|
||||
* @returns {Promise<boolean|{error: string}>}
|
||||
*/
|
||||
module.exports.onAction = async (context, actionName, target, data) => {
|
||||
context.Logger.info('[ExamplePlugin] Example plugin onAction', actionName, target, data)
|
||||
|
||||
createTask(context)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,31 +44,85 @@ module.exports.onAction = async (context, actionName, target, data) => {
|
||||
*
|
||||
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||
* @param {*} config
|
||||
* @returns {Promise<boolean|{error: string}>}
|
||||
*/
|
||||
module.exports.onConfigSave = async (context, config) => {
|
||||
context.Logger.info('[ExamplePlugin] Example plugin onConfigSave', config)
|
||||
|
||||
createTask(context)
|
||||
if (!config.requestAddress || typeof config.requestAddress !== 'string') {
|
||||
context.Logger.error('[ExamplePlugin] Invalid request address')
|
||||
return {
|
||||
error: 'Invalid request address'
|
||||
}
|
||||
}
|
||||
if (typeof config.enable !== 'boolean') {
|
||||
context.Logger.error('[ExamplePlugin] Invalid enable value')
|
||||
return {
|
||||
error: 'Invalid enable value'
|
||||
}
|
||||
}
|
||||
|
||||
// Config would need to be validated
|
||||
const updatedConfig = {
|
||||
requestAddress: config.requestAddress,
|
||||
enable: config.enable
|
||||
}
|
||||
context.pluginInstance.config = updatedConfig
|
||||
await context.pluginInstance.save()
|
||||
context.Logger.info('[ExamplePlugin] Example plugin config saved', updatedConfig)
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// Helper functions
|
||||
//
|
||||
let numProgressSyncs = 0
|
||||
|
||||
/**
|
||||
* Scrobble media progress update
|
||||
* Send media progress update to external requestAddress defined in config
|
||||
*
|
||||
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||
* @param {import('../../../server/models/MediaProgress')} mediaProgress
|
||||
*/
|
||||
async function handleMediaProgressUpdate(context, mediaProgress) {
|
||||
// Need to reload the model instance since it was passed in during init and may have values changed
|
||||
await context.pluginInstance.reload()
|
||||
|
||||
if (!context.pluginInstance.config?.enable) {
|
||||
return
|
||||
}
|
||||
const requestAddress = context.pluginInstance.config.requestAddress
|
||||
if (!requestAddress) {
|
||||
context.Logger.error('[ExamplePlugin] Request address not set')
|
||||
return
|
||||
}
|
||||
|
||||
const mediaItem = await mediaProgress.getMediaItem()
|
||||
if (!mediaItem) {
|
||||
context.Logger.error(`[ExamplePlugin] Media item not found for mediaProgress ${mediaProgress.id}`)
|
||||
} else {
|
||||
const mediaProgressDuration = mediaProgress.duration
|
||||
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)}% (total numProgressSyncs: ${numProgressSyncs})`)
|
||||
|
||||
fetch(requestAddress, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: mediaItem.title,
|
||||
progress: progressPercent
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
context.Logger.info(`[ExamplePlugin] Media progress update sent for "${mediaItem.title}" ${Math.round(progressPercent)}%`)
|
||||
numProgressSyncs++
|
||||
sendAdminMessageToast(context, `Synced "${mediaItem.title}" (total syncs: ${numProgressSyncs})`)
|
||||
})
|
||||
.catch((error) => {
|
||||
context.Logger.error(`[ExamplePlugin] Error sending media progress update: ${error.message}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,11 +130,10 @@ async function handleMediaProgressUpdate(context, mediaProgress) {
|
||||
* Test socket authority
|
||||
*
|
||||
* @param {import('../../../server/managers/PluginManager').PluginContext} context
|
||||
* @param {string} message
|
||||
*/
|
||||
async function sendAdminMessageToast(context) {
|
||||
setTimeout(() => {
|
||||
context.SocketAuthority.adminEmitter('admin_message', 'Hello from ExamplePlugin!')
|
||||
}, 10000)
|
||||
async function sendAdminMessageToast(context, message) {
|
||||
context.SocketAuthority.adminEmitter('admin_message', message)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,8 +143,10 @@ async function sendAdminMessageToast(context) {
|
||||
*/
|
||||
async function createTask(context) {
|
||||
const task = context.TaskManager.createAndAddTask('example_action', { text: 'Example Task' }, { text: 'This is an example task' }, true)
|
||||
const pluginConfigEnabled = !!context.pluginInstance.config.enable
|
||||
|
||||
setTimeout(() => {
|
||||
task.setFinished({ text: 'Example Task Finished' })
|
||||
task.setFinished({ text: `Plugin is ${pluginConfigEnabled ? 'enabled' : 'disabled'}` })
|
||||
context.TaskManager.taskFinished(task)
|
||||
}, 5000)
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
{
|
||||
"id": "e6205690-916c-4add-9a2b-2548266996ef",
|
||||
"name": "Example",
|
||||
"slug": "example",
|
||||
"version": "1.0.0",
|
||||
"owner": "advplyr",
|
||||
"repositoryUrl": "https://github.com/example/example-plugin",
|
||||
"documentationUrl": "https://example.com",
|
||||
"description": "This is an example plugin",
|
||||
"extensions": [
|
||||
{
|
||||
@ -13,16 +15,9 @@
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"description": "This is an example plugin",
|
||||
"description": "This is a description on how to configure the plugin",
|
||||
"descriptionKey": "ExamplePluginConfigurationDescription",
|
||||
"formFields": [
|
||||
{
|
||||
"name": "apiKey",
|
||||
"label": "API Key",
|
||||
"labelKey": "LabelApiKey",
|
||||
"type": "text",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "requestAddress",
|
||||
"label": "Request Address",
|
||||
@ -41,10 +36,17 @@
|
||||
"localization": {
|
||||
"en-us": {
|
||||
"ItemExampleAction": "Item Example Action",
|
||||
"LabelApiKey": "API Key",
|
||||
"LabelEnable": "Enable",
|
||||
"ExamplePluginConfigurationDescription": "This is an example plugin",
|
||||
"ExamplePluginConfigurationDescription": "This is a description on how to configure the plugin",
|
||||
"LabelRequestAddress": "Request Address"
|
||||
}
|
||||
}
|
||||
},
|
||||
"releases": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"changelog": "Initial release",
|
||||
"timestamp": "2022-01-01T00:00:00Z",
|
||||
"sourceUrl": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user