diff --git a/gameyfin/build.gradle.kts b/gameyfin/build.gradle.kts
index f271060..92b302c 100644
--- a/gameyfin/build.gradle.kts
+++ b/gameyfin/build.gradle.kts
@@ -35,7 +35,7 @@ dependencies {
implementation("jakarta.validation:jakarta.validation-api:3.1.0")
// Kotlin extensions
- implementation("org.jetbrains.kotlin:kotlin-reflect")
+ implementation(kotlin("reflect"))
// Reactive
implementation("org.springframework.boot:spring-boot-starter-webflux")
diff --git a/gameyfin/src/main/frontend/components/administration/LibraryManagement.tsx b/gameyfin/src/main/frontend/components/administration/LibraryManagement.tsx
index c58855c..62ed3d5 100644
--- a/gameyfin/src/main/frontend/components/administration/LibraryManagement.tsx
+++ b/gameyfin/src/main/frontend/components/administration/LibraryManagement.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect} from "react";
+import React from "react";
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
import withConfigPage from "Frontend/components/administration/withConfigPage";
import Section from "Frontend/components/general/Section";
@@ -11,16 +11,12 @@ import LibraryCreationModal from "Frontend/components/general/modals/LibraryCrea
import LibraryUpdateDto from "Frontend/generated/de/grimsi/gameyfin/libraries/dto/LibraryUpdateDto";
import LibraryDto from "Frontend/generated/de/grimsi/gameyfin/libraries/dto/LibraryDto";
import {useSnapshot} from "valtio/react";
-import {initializeLibraryState, libraryState} from "Frontend/state/LibraryState";
+import {libraryState} from "Frontend/state/LibraryState";
function LibraryManagementLayout({getConfig, formik}: any) {
const libraryCreationModal = useDisclosure();
const state = useSnapshot(libraryState);
- useEffect(() => {
- initializeLibraryState();
- }, []);
-
async function updateLibrary(library: LibraryUpdateDto) {
await LibraryEndpoint.updateLibrary(library);
addToast({
diff --git a/gameyfin/src/main/frontend/components/administration/SsoManagement.tsx b/gameyfin/src/main/frontend/components/administration/SsoManagement.tsx
index 0a29bad..3d4cab7 100644
--- a/gameyfin/src/main/frontend/components/administration/SsoManagement.tsx
+++ b/gameyfin/src/main/frontend/components/administration/SsoManagement.tsx
@@ -52,9 +52,11 @@ function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
diff --git a/gameyfin/src/main/frontend/components/general/cards/PluginManagementCard.tsx b/gameyfin/src/main/frontend/components/general/cards/PluginManagementCard.tsx
index dbdafa2..301a086 100644
--- a/gameyfin/src/main/frontend/components/general/cards/PluginManagementCard.tsx
+++ b/gameyfin/src/main/frontend/components/general/cards/PluginManagementCard.tsx
@@ -23,9 +23,9 @@ import PluginTrustLevel from "Frontend/generated/de/grimsi/gameyfin/core/plugins
import {PluginEndpoint} from "Frontend/generated/endpoints";
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/dto/PluginDto";
import PluginConfigValidationResult
- from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigValidationResult";
+ from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/config/PluginConfigValidationResult";
import PluginConfigValidationResultType
- from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigValidationResultType";
+ from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/config/PluginConfigValidationResultType";
export function PluginManagementCard({plugin}: { plugin: PluginDto }) {
const pluginDetailsModal = useDisclosure();
diff --git a/gameyfin/src/main/frontend/components/general/input/PluginConfigFormField.tsx b/gameyfin/src/main/frontend/components/general/input/PluginConfigFormField.tsx
new file mode 100644
index 0000000..9deec2a
--- /dev/null
+++ b/gameyfin/src/main/frontend/components/general/input/PluginConfigFormField.tsx
@@ -0,0 +1,58 @@
+import SelectInput from "Frontend/components/general/input/SelectInput";
+import CheckboxInput from "Frontend/components/general/input/CheckboxInput";
+import Input from "Frontend/components/general/input/Input";
+import React from "react";
+import PluginConfigMetadataDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/dto/PluginConfigMetadataDto";
+
+export default function PluginConfigFormField({pluginConfigMetadata, ...props}: any) {
+ function inputElement(metadata: PluginConfigMetadataDto) {
+
+ if (metadata.allowedValues != null && metadata.allowedValues.length > 0) {
+ return (
+
+ );
+ }
+
+ switch (metadata.type) {
+ case "Boolean":
+ return (
+
+ );
+ case "String":
+ return (
+
+ );
+ case "Float":
+ return (
+
+ );
+ case "Int":
+ return (
+
+ );
+ default:
+ return Unsupported type: {metadata.type} for key {metadata.key};
+ }
+ }
+
+ return inputElement(pluginConfigMetadata!);
+}
\ No newline at end of file
diff --git a/gameyfin/src/main/frontend/components/general/input/SelectInput.tsx b/gameyfin/src/main/frontend/components/general/input/SelectInput.tsx
index 85165d4..01d0824 100644
--- a/gameyfin/src/main/frontend/components/general/input/SelectInput.tsx
+++ b/gameyfin/src/main/frontend/components/general/input/SelectInput.tsx
@@ -6,23 +6,19 @@ const SelectInput = ({label, values, ...props}) => {
// @ts-ignore
const [field] = useField(props);
+ const items = values.map((v: string) => ({key: v, label: v}));
+
return (
-
-
-
+
);
}
diff --git a/gameyfin/src/main/frontend/components/general/modals/PluginDetailsModal.tsx b/gameyfin/src/main/frontend/components/general/modals/PluginDetailsModal.tsx
index 981c6e3..f99993b 100644
--- a/gameyfin/src/main/frontend/components/general/modals/PluginDetailsModal.tsx
+++ b/gameyfin/src/main/frontend/components/general/modals/PluginDetailsModal.tsx
@@ -1,14 +1,14 @@
import React, {useState} from "react";
import {addToast, Button, Link, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, Tooltip} from "@heroui/react";
import {Form, Formik} from "formik";
-import PluginConfigElement from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement";
-import Input from "Frontend/components/general/input/Input";
import PluginLogo from "Frontend/components/general/plugin/PluginLogo";
import Markdown from "react-markdown";
import remarkBreaks from "remark-breaks";
import {PluginEndpoint} from "Frontend/generated/endpoints";
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/dto/PluginDto";
import {ArrowClockwise} from "@phosphor-icons/react";
+import PluginConfigMetadataDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/dto/PluginConfigMetadataDto";
+import PluginConfigFormField from "Frontend/components/general/input/PluginConfigFormField";
interface PluginDetailsModalProps {
plugin: PluginDto;
@@ -35,11 +35,28 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: Plugi
});
}
+ function getEffectiveConfig(): Record {
+ const effectiveConfig: Record = {};
+ if (!plugin.configMetadata) return effectiveConfig;
+
+ for (const meta of plugin.configMetadata) {
+ const key = meta.key;
+ let value = plugin.config?.[key]?.toString();
+ if (value == null && meta.default != null) {
+ value = meta.default.toString();
+ }
+ if (value) {
+ effectiveConfig[key] = value;
+ }
+ }
+ return effectiveConfig;
+ }
+
return (
{(onClose) => (
- {
@@ -138,10 +155,11 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: Plugi
>}
{(plugin.configMetadata && plugin.configMetadata.length > 0) ?
- plugin.configMetadata.map((entry: PluginConfigElement) => (
-
+ plugin.configMetadata.map((entry: PluginConfigMetadataDto) => (
+
)) : "This plugin has no configuration options."
}
diff --git a/gameyfin/src/main/frontend/index.tsx b/gameyfin/src/main/frontend/index.tsx
index 9f6ea4a..0c4daa5 100644
--- a/gameyfin/src/main/frontend/index.tsx
+++ b/gameyfin/src/main/frontend/index.tsx
@@ -1,7 +1,7 @@
import {createRoot} from 'react-dom/client';
import {StrictMode} from "react";
import {RouterProvider} from "react-router";
-import router from "./routes";
+import {router} from './routes';
const container = document.getElementById('outlet')!;
const root = createRoot(container);
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/PluginEndpoint.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/PluginEndpoint.kt
index 75a7988..171f4c8 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/PluginEndpoint.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/PluginEndpoint.kt
@@ -3,7 +3,7 @@ package de.grimsi.gameyfin.core.plugins
import com.vaadin.hilla.Endpoint
import de.grimsi.gameyfin.core.Role
import de.grimsi.gameyfin.core.plugins.dto.PluginUpdateDto
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigValidationResult
import de.grimsi.gameyfin.users.util.isAdmin
import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/PluginService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/PluginService.kt
index d9749ee..db87df1 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/PluginService.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/PluginService.kt
@@ -3,16 +3,16 @@ package de.grimsi.gameyfin.core.plugins
import de.grimsi.gameyfin.core.plugins.config.PluginConfigEntry
import de.grimsi.gameyfin.core.plugins.config.PluginConfigEntryKey
import de.grimsi.gameyfin.core.plugins.config.PluginConfigRepository
+import de.grimsi.gameyfin.core.plugins.dto.PluginConfigMetadataDto
import de.grimsi.gameyfin.core.plugins.dto.PluginDto
import de.grimsi.gameyfin.core.plugins.dto.PluginUpdateDto
import de.grimsi.gameyfin.core.plugins.management.GameyfinPluginDescriptor
import de.grimsi.gameyfin.core.plugins.management.GameyfinPluginManager
import de.grimsi.gameyfin.core.plugins.management.PluginManagementEntry
import de.grimsi.gameyfin.core.plugins.management.PluginManagementRepository
-import de.grimsi.gameyfin.pluginapi.core.Configurable
-import de.grimsi.gameyfin.pluginapi.core.GameyfinPlugin
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigElement
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
+import de.grimsi.gameyfin.pluginapi.core.config.Configurable
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigValidationResult
+import de.grimsi.gameyfin.pluginapi.core.wrapper.GameyfinPlugin
import io.github.oshai.kotlinlogging.KotlinLogging
import org.pf4j.ExtensionPoint
import org.pf4j.PluginWrapper
@@ -96,11 +96,24 @@ class PluginService(
return plugin.getLogo()
}
- fun getConfigMetadata(pluginWrapper: PluginWrapper): List {
+ fun getConfigMetadata(pluginWrapper: PluginWrapper): List? {
log.debug { "Getting config metadata for plugin ${pluginWrapper.pluginId}" }
val plugin = pluginWrapper.plugin
- if (plugin !is Configurable) return emptyList()
- return plugin.configMetadata
+
+ if (plugin !is Configurable) return null
+
+ return plugin.configMetadata.map { meta ->
+ PluginConfigMetadataDto(
+ key = meta.key,
+ type = meta.type.simpleName ?: "Unknown",
+ label = meta.label,
+ description = meta.description,
+ default = meta.default,
+ isSecret = meta.isSecret,
+ isRequired = meta.isRequired,
+ allowedValues = meta.allowedValues?.map { it.toString() }
+ )
+ }
}
fun getConfig(pluginWrapper: PluginWrapper): Map {
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginConfigMetadataDto.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginConfigMetadataDto.kt
new file mode 100644
index 0000000..d98f9ad
--- /dev/null
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginConfigMetadataDto.kt
@@ -0,0 +1,16 @@
+package de.grimsi.gameyfin.core.plugins.dto
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import java.io.Serializable
+
+@JsonInclude(JsonInclude.Include.ALWAYS)
+class PluginConfigMetadataDto(
+ val key: String,
+ val type: String,
+ val label: String,
+ val description: String,
+ val default: Serializable?,
+ val isSecret: Boolean,
+ val isRequired: Boolean,
+ val allowedValues: List?
+)
\ No newline at end of file
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginDto.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginDto.kt
index 1b569ad..bbf092f 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginDto.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginDto.kt
@@ -1,8 +1,7 @@
package de.grimsi.gameyfin.core.plugins.dto
import de.grimsi.gameyfin.core.plugins.management.PluginTrustLevel
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigElement
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigValidationResult
import org.pf4j.PluginState
data class PluginDto(
@@ -17,7 +16,7 @@ data class PluginDto(
val url: String? = null,
val hasLogo: Boolean,
val state: PluginState,
- val configMetadata: List? = null,
+ val configMetadata: List? = null,
val config: Map? = null,
val configValidation: PluginConfigValidationResult? = null,
val priority: Int,
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginUpdateDto.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginUpdateDto.kt
index e673bc7..6a84ba8 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginUpdateDto.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/dto/PluginUpdateDto.kt
@@ -1,7 +1,7 @@
package de.grimsi.gameyfin.core.plugins.dto
import com.fasterxml.jackson.annotation.JsonInclude
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigValidationResult
import org.pf4j.PluginState
@JsonInclude(JsonInclude.Include.NON_NULL)
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/GameyfinPluginManager.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/GameyfinPluginManager.kt
index 19fc3e3..70800df 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/GameyfinPluginManager.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/GameyfinPluginManager.kt
@@ -1,9 +1,9 @@
package de.grimsi.gameyfin.core.plugins.management
import de.grimsi.gameyfin.core.plugins.config.PluginConfigRepository
-import de.grimsi.gameyfin.pluginapi.core.Configurable
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResultType
+import de.grimsi.gameyfin.pluginapi.core.config.Configurable
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigValidationResult
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigValidationResultType
import io.github.oshai.kotlinlogging.KotlinLogging
import org.pf4j.*
import org.springframework.data.repository.findByIdOrNull
@@ -164,7 +164,7 @@ class GameyfinPluginManager(
fun restart(pluginId: String) {
val plugin = getPlugin(pluginId)?.plugin ?: return
stopPlugin(pluginId)
- if (plugin is Configurable) plugin.config = getConfig(pluginId)
+ if (plugin is Configurable) plugin.loadConfig(getConfig(pluginId))
startPlugin(pluginId)
}
@@ -213,7 +213,7 @@ class GameyfinPluginManager(
val plugin = pluginWrapper.plugin
if (plugin is Configurable) {
val config = getConfig(pluginWrapper.pluginId)
- plugin.config = config
+ plugin.loadConfig(config)
}
}
diff --git a/gameyfin/vite.generated.ts b/gameyfin/vite.generated.ts
index 862286c..7667173 100644
--- a/gameyfin/vite.generated.ts
+++ b/gameyfin/vite.generated.ts
@@ -16,20 +16,19 @@ import settings from './build/vaadin-dev-server-settings.json';
import {
AssetInfo,
ChunkInfo,
- build,
defineConfig,
mergeConfig,
OutputOptions,
PluginOption,
- InlineConfig,
UserConfigFn
} from 'vite';
-import { getManifest, type ManifestTransform } from 'workbox-build';
import * as rollup from 'rollup';
import brotli from 'rollup-plugin-brotli';
import checker from 'vite-plugin-checker';
import postcssLit from './build/plugins/rollup-plugin-postcss-lit-custom/rollup-plugin-postcss-lit.js';
+import vaadinI18n from './build/plugins/rollup-plugin-vaadin-i18n/rollup-plugin-vaadin-i18n.js';
+import serviceWorkerPlugin from './build/plugins/vite-plugin-service-worker';
import { createRequire } from 'module';
@@ -41,8 +40,6 @@ import vitePluginFileSystemRouter from '@vaadin/hilla-file-router/vite-plugin.js
// Make `require` compatible with ES modules
const require = createRequire(import.meta.url);
-const appShellUrl = '.';
-
const frontendFolder = path.resolve(__dirname, settings.frontendFolder);
const themeFolder = path.resolve(frontendFolder, settings.themeFolder);
const frontendBundleFolder = path.resolve(__dirname, settings.frontendBundleOutput);
@@ -56,6 +53,7 @@ const buildOutputFolder = devBundle ? devBundleFolder : frontendBundleFolder;
const statsFolder = path.resolve(__dirname, devBundle ? settings.devBundleStatsOutput : settings.statsOutput);
const statsFile = path.resolve(statsFolder, 'stats.json');
const bundleSizeFile = path.resolve(statsFolder, 'bundle-size.html');
+const i18nFolder = path.resolve(__dirname, settings.i18nOutput);
const nodeModulesFolder = path.resolve(__dirname, 'node_modules');
const webComponentTags = '';
@@ -91,94 +89,6 @@ const target = ['safari15', 'es2022'];
console.trace = () => {};
console.debug = () => {};
-function injectManifestToSWPlugin(): rollup.Plugin {
- const rewriteManifestIndexHtmlUrl: ManifestTransform = (manifest) => {
- const indexEntry = manifest.find((entry) => entry.url === 'index.html');
- if (indexEntry) {
- indexEntry.url = appShellUrl;
- }
-
- return { manifest, warnings: [] };
- };
-
- return {
- name: 'vaadin:inject-manifest-to-sw',
- async transform(code, id) {
- if (/sw\.(ts|js)$/.test(id)) {
- const { manifestEntries } = await getManifest({
- globDirectory: buildOutputFolder,
- globPatterns: ['**/*'],
- globIgnores: ['**/*.br', 'pwa-icons/**'],
- manifestTransforms: [rewriteManifestIndexHtmlUrl],
- maximumFileSizeToCacheInBytes: 100 * 1024 * 1024 // 100mb,
- });
-
- return code.replace('self.__WB_MANIFEST', JSON.stringify(manifestEntries));
- }
- }
- };
-}
-
-function buildSWPlugin(opts: { devMode: boolean }): PluginOption {
- let buildConfig: InlineConfig;
- let buildOutput: rollup.RollupOutput;
- const devMode = opts.devMode;
-
- return {
- name: 'vaadin:build-sw',
- enforce: 'post',
- async configResolved(viteConfig) {
- buildConfig = {
- base: viteConfig.base,
- root: viteConfig.root,
- mode: viteConfig.mode,
- resolve: viteConfig.resolve,
- define: {
- ...viteConfig.define,
- 'process.env.NODE_ENV': JSON.stringify(viteConfig.mode),
- },
- build: {
- write: !devMode,
- minify: viteConfig.build.minify,
- outDir: viteConfig.build.outDir,
- target,
- sourcemap: viteConfig.command === 'serve' || viteConfig.build.sourcemap,
- emptyOutDir: false,
- modulePreload: false,
- rollupOptions: {
- input: {
- sw: settings.clientServiceWorkerSource
- },
- output: {
- exports: 'none',
- entryFileNames: 'sw.js',
- inlineDynamicImports: true,
- },
- },
- },
- };
- },
- async buildStart() {
- if (devMode) {
- buildOutput = await build(buildConfig) as rollup.RollupOutput;
- }
- },
- async load(id) {
- if (id.endsWith('sw.js')) {
- return buildOutput.output[0].code;
- }
- },
- async closeBundle() {
- if (!devMode) {
- await build({
- ...buildConfig,
- plugins: [injectManifestToSWPlugin(), brotli()]
- });
- }
- },
- };
-}
-
function statsExtracterPlugin(): PluginOption {
function collectThemeJsonsInFrontend(themeJsonContents: Record, themeName: string) {
const themeJson = path.resolve(frontendFolder, settings.themeFolder, themeName, 'theme.json');
@@ -276,7 +186,7 @@ function statsExtracterPlugin(): PluginOption {
const frontendFiles: Record = {};
frontendFiles['index.html'] = createHash('sha256').update(customIndexData.replace(/\r\n/g, '\n'), 'utf8').digest('hex');
- const projectFileExtensions = ['.js', '.js.map', '.ts', '.ts.map', '.tsx', '.tsx.map', '.css', '.css.map', '.'];
+ const projectFileExtensions = ['.js', '.js.map', '.ts', '.ts.map', '.tsx', '.tsx.map', '.css', '.css.map'];
const isThemeComponentsResource = (id: string) =>
id.startsWith(themeOptions.frontendGeneratedFolder.replace(/\\/g, '/'))
@@ -753,7 +663,9 @@ export const vaadinConfig: UserConfigFn = (env) => {
productionMode && brotli(),
devMode && vaadinBundlesPlugin(),
devMode && showRecompileReason(),
- settings.offlineEnabled && buildSWPlugin({ devMode }),
+ settings.offlineEnabled && serviceWorkerPlugin({
+ srcPath: settings.clientServiceWorkerSource,
+ }),
!devMode && statsExtracterPlugin(),
!productionMode && preserveUsageStats(),
themePlugin({ devMode }),
@@ -795,6 +707,14 @@ export const vaadinConfig: UserConfigFn = (env) => {
].filter(Boolean)
}
}),
+ productionMode && vaadinI18n({
+ cwd: __dirname,
+ meta: {
+ output: {
+ dir: i18nFolder,
+ },
+ },
+ }),
{
name: 'vaadin:force-remove-html-middleware',
configureServer(server) {
diff --git a/plugin-api/build.gradle.kts b/plugin-api/build.gradle.kts
index 8269de3..f7b212d 100644
--- a/plugin-api/build.gradle.kts
+++ b/plugin-api/build.gradle.kts
@@ -20,13 +20,4 @@ publishing {
dependencies {
// PF4J (shared)
api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}")
-
- implementation(kotlin("stdlib"))
-
- // Test dependencies
- testImplementation(kotlin("test"))
-}
-
-tasks.test {
- useJUnitPlatform()
}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/Configurable.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/Configurable.kt
deleted file mode 100644
index af07549..0000000
--- a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/Configurable.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package de.grimsi.gameyfin.pluginapi.core
-
-interface Configurable {
- val configMetadata: List
- var config: Map
-
- fun validateConfig(): PluginConfigValidationResult = validateConfig(config)
- fun validateConfig(config: Map): PluginConfigValidationResult
-}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/ConfigurableGameyfinPlugin.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/ConfigurableGameyfinPlugin.kt
deleted file mode 100644
index fc54003..0000000
--- a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/ConfigurableGameyfinPlugin.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package de.grimsi.gameyfin.pluginapi.core
-
-import org.pf4j.PluginWrapper
-
-abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable {
-
- companion object {
- lateinit var plugin: ConfigurableGameyfinPlugin
- private set
- }
-
- init {
- plugin = this
- }
-
- override var config: Map = emptyMap()
-}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement.kt
deleted file mode 100644
index 293c8a7..0000000
--- a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package de.grimsi.gameyfin.pluginapi.core
-
-data class PluginConfigElement(
- val key: String,
- val name: String,
- val description: String,
- val isSecret: Boolean = false
-)
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/ConfigMetadata.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/ConfigMetadata.kt
new file mode 100644
index 0000000..f92e869
--- /dev/null
+++ b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/ConfigMetadata.kt
@@ -0,0 +1,21 @@
+package de.grimsi.gameyfin.pluginapi.core.config
+
+import java.io.Serializable
+
+typealias PluginConfigMetadata = List>
+
+data class ConfigMetadata(
+ val key: String,
+ val type: Class,
+ val label: String,
+ val description: String,
+ val default: T? = null,
+ val isSecret: Boolean = false,
+ val isRequired: Boolean = true,
+) {
+ var allowedValues: List? = null
+
+ init {
+ allowedValues = type.enumConstants?.toList()
+ }
+}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/Configurable.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/Configurable.kt
new file mode 100644
index 0000000..ba173e8
--- /dev/null
+++ b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/Configurable.kt
@@ -0,0 +1,15 @@
+package de.grimsi.gameyfin.pluginapi.core.config
+
+import java.io.Serializable
+
+interface Configurable {
+ val configMetadata: PluginConfigMetadata
+
+ fun loadConfig(config: Map)
+
+ fun validateConfig(): PluginConfigValidationResult
+ fun validateConfig(config: Map): PluginConfigValidationResult
+
+ fun config(key: String): T
+ fun optionalConfig(key: String): T?
+}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigError.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/PluginConfigError.kt
similarity index 58%
rename from plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigError.kt
rename to plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/PluginConfigError.kt
index c461e7c..4b8719c 100644
--- a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigError.kt
+++ b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/PluginConfigError.kt
@@ -1,3 +1,3 @@
-package de.grimsi.gameyfin.pluginapi.core
+package de.grimsi.gameyfin.pluginapi.core.config
class PluginConfigError(message: String) : RuntimeException(message)
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigValidationResult.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/PluginConfigValidationResult.kt
similarity index 92%
rename from plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigValidationResult.kt
rename to plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/PluginConfigValidationResult.kt
index 338b5d3..2495116 100644
--- a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/PluginConfigValidationResult.kt
+++ b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/config/PluginConfigValidationResult.kt
@@ -1,4 +1,4 @@
-package de.grimsi.gameyfin.pluginapi.core
+package de.grimsi.gameyfin.pluginapi.core.config
data class PluginConfigValidationResult(
val result: PluginConfigValidationResultType,
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt
new file mode 100644
index 0000000..f7e5632
--- /dev/null
+++ b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt
@@ -0,0 +1,92 @@
+package de.grimsi.gameyfin.pluginapi.core.wrapper
+
+import de.grimsi.gameyfin.pluginapi.core.config.ConfigMetadata
+import de.grimsi.gameyfin.pluginapi.core.config.Configurable
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigError
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigValidationResult
+import org.pf4j.PluginWrapper
+import java.io.Serializable
+
+@Suppress("UNCHECKED_CAST")
+abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable {
+
+ private var config: Map = emptyMap()
+
+ override fun loadConfig(config: Map) {
+ this.config = config
+ }
+
+ override fun validateConfig(): PluginConfigValidationResult = validateConfig(config)
+
+ override fun validateConfig(config: Map): PluginConfigValidationResult {
+ val errors = mutableMapOf()
+
+ for (meta in configMetadata) {
+ val value = resolveValue(meta.key, config)
+ if (meta.isRequired && value == null) {
+ errors[meta.key] = "${meta.label} is required"
+ continue
+ }
+ if (value != null) {
+ try {
+ castConfigValue(meta, value)
+ } catch (e: PluginConfigError) {
+ errors[meta.key] = e.message ?: "Invalid value"
+ }
+ }
+ }
+
+ return if (errors.isEmpty()) {
+ PluginConfigValidationResult.VALID
+ } else {
+ PluginConfigValidationResult.INVALID(errors)
+ }
+ }
+
+ override fun optionalConfig(key: String): T? {
+ val meta = resolveMetadata(key)
+ val value = resolveValue(key)
+ if (value == null) return null
+
+ return try {
+ castConfigValue(meta, value) as T
+ } catch (e: Exception) {
+ throw PluginConfigError("Failed to cast value for key '$key' to type ${meta.type.simpleName}: ${e.message}")
+ }
+ }
+
+ private fun castConfigValue(meta: ConfigMetadata<*>, value: Any): Any? {
+ val expectedType = meta.type
+ return if (expectedType.isEnum) {
+ try {
+ java.lang.Enum.valueOf(expectedType as Class>, value.toString())
+ } catch (_: IllegalArgumentException) {
+ throw PluginConfigError("Invalid value '${value}', must be one of ${meta.allowedValues!!.joinToString(", ")}")
+ }
+ } else {
+ if (!expectedType.isInstance(value)) {
+ throw PluginConfigError("Value for key '${meta.key}' is not of type ${expectedType.simpleName}")
+ }
+ value
+ }
+ }
+
+ override fun config(key: String): T {
+ val value = optionalConfig(key)
+ if (value == null) {
+ throw PluginConfigError("Required configuration key '$key' is missing or has no value")
+ }
+ return value
+ }
+
+ private fun resolveMetadata(key: String): ConfigMetadata<*> {
+ return configMetadata.find { it.key == key }
+ ?: throw PluginConfigError("Unknown configuration key: $key")
+ }
+
+ private fun resolveValue(key: String, configOverride: Map? = null): Serializable? {
+ val meta = resolveMetadata(key)
+ val conf = configOverride ?: config
+ return conf[key] ?: meta.default
+ }
+}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/GameyfinPlugin.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt
similarity index 93%
rename from plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/GameyfinPlugin.kt
rename to plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt
index ed15165..46e1f49 100644
--- a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/GameyfinPlugin.kt
+++ b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt
@@ -1,8 +1,9 @@
-package de.grimsi.gameyfin.pluginapi.core
+package de.grimsi.gameyfin.pluginapi.core.wrapper
import org.pf4j.Plugin
import org.pf4j.PluginWrapper
+@Suppress("DEPRECATION")
abstract class GameyfinPlugin(wrapper: PluginWrapper) : Plugin(wrapper) {
companion object {
diff --git a/plugins/directdownload/src/main/kotlin/de/grimsi/gameyfin/plugins/directdownload/CompressionMode.kt b/plugins/directdownload/src/main/kotlin/de/grimsi/gameyfin/plugins/directdownload/CompressionMode.kt
new file mode 100644
index 0000000..c571604
--- /dev/null
+++ b/plugins/directdownload/src/main/kotlin/de/grimsi/gameyfin/plugins/directdownload/CompressionMode.kt
@@ -0,0 +1,18 @@
+package de.grimsi.gameyfin.plugins.directdownload
+
+import de.grimsi.gameyfin.plugins.directdownload.CompressionMode.*
+import java.util.zip.Deflater
+
+enum class CompressionMode {
+ NONE,
+ FAST,
+ BEST;
+}
+
+fun CompressionMode.deflaterLevel(): Int {
+ return when (this) {
+ NONE -> Deflater.NO_COMPRESSION
+ FAST -> Deflater.BEST_SPEED
+ BEST -> Deflater.BEST_COMPRESSION
+ }
+}
\ No newline at end of file
diff --git a/plugins/directdownload/src/main/kotlin/de/grimsi/gameyfin/plugins/directdownload/DirectDownloadPlugin.kt b/plugins/directdownload/src/main/kotlin/de/grimsi/gameyfin/plugins/directdownload/DirectDownloadPlugin.kt
index 470caef..81f0fac 100644
--- a/plugins/directdownload/src/main/kotlin/de/grimsi/gameyfin/plugins/directdownload/DirectDownloadPlugin.kt
+++ b/plugins/directdownload/src/main/kotlin/de/grimsi/gameyfin/plugins/directdownload/DirectDownloadPlugin.kt
@@ -1,8 +1,8 @@
package de.grimsi.gameyfin.plugins.directdownload
-import de.grimsi.gameyfin.pluginapi.core.ConfigurableGameyfinPlugin
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigElement
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
+import de.grimsi.gameyfin.pluginapi.core.config.ConfigMetadata
+import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigMetadata
+import de.grimsi.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
import de.grimsi.gameyfin.pluginapi.download.Download
import de.grimsi.gameyfin.pluginapi.download.DownloadProvider
import de.grimsi.gameyfin.pluginapi.download.FileDownload
@@ -14,7 +14,6 @@ import java.io.PipedInputStream
import java.io.PipedOutputStream
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
-import java.util.zip.Deflater
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.io.path.exists
@@ -24,31 +23,25 @@ import kotlin.io.path.isDirectory
class DirectDownloadPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
- override val configMetadata: List = listOf(
- PluginConfigElement(
+ companion object {
+ lateinit var plugin: DirectDownloadPlugin
+ private set
+ }
+
+ init {
+ plugin = this
+ }
+
+ override val configMetadata: PluginConfigMetadata = listOf(
+ ConfigMetadata(
key = "compressionMode",
- name = "Compression mode (\"none\" = default, \"fast\", \"best\")",
+ type = CompressionMode::class.java,
+ label = "Compression mode",
description = "Higher compression modes are more resource intensive, but save bandwidth",
+ default = CompressionMode.NONE
)
)
- override fun validateConfig(config: Map): PluginConfigValidationResult {
- val compressionMode = config["compressionMode"]
-
- if (compressionMode != null) {
- return try {
- CompressionMode.valueOf(compressionMode.uppercase())
- PluginConfigValidationResult.VALID
- } catch (_: IllegalArgumentException) {
- PluginConfigValidationResult.INVALID(
- mapOf("compressionMode" to "Invalid compression mode: $compressionMode (must be \"none\", \"fast\", or \"best\")")
- )
- }
- }
-
- return PluginConfigValidationResult.VALID
- }
-
@Extension
class DirectDownloadProvider : DownloadProvider {
override fun download(path: Path): Download {
@@ -95,9 +88,8 @@ class DirectDownloadPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(
try {
ZipOutputStream(pipeOut).use { zos ->
- zos.setLevel(CompressionMode.toDeflaterLevel(plugin.config["compressionMode"]?.let {
- CompressionMode.valueOf(it.uppercase())
- } ?: CompressionMode.NONE))
+ val compressionMode = plugin.config("compressionMode")
+ zos.setLevel(compressionMode.deflaterLevel())
Files.walkFileTree(path, object : SimpleFileVisitor() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
@@ -124,21 +116,4 @@ class DirectDownloadPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(
return pipeIn
}
}
-}
-
-
-enum class CompressionMode {
- NONE,
- FAST,
- BEST;
-
- companion object {
- fun toDeflaterLevel(mode: CompressionMode): Int {
- return when (mode) {
- NONE -> Deflater.NO_COMPRESSION
- FAST -> Deflater.BEST_SPEED
- BEST -> Deflater.BEST_COMPRESSION
- }
- }
- }
}
\ No newline at end of file
diff --git a/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/IgdbPlugin.kt b/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/IgdbPlugin.kt
index bd587ee..eab29fb 100644
--- a/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/IgdbPlugin.kt
+++ b/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/IgdbPlugin.kt
@@ -5,7 +5,8 @@ import com.api.igdb.exceptions.RequestException
import com.api.igdb.request.IGDBWrapper
import com.api.igdb.request.TwitchAuthenticator
import com.api.igdb.request.games
-import de.grimsi.gameyfin.pluginapi.core.*
+import de.grimsi.gameyfin.pluginapi.core.config.*
+import de.grimsi.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
import me.xdrop.fuzzywuzzy.FuzzySearch
@@ -16,26 +17,35 @@ import proto.Game
import java.time.Instant
import java.util.concurrent.TimeUnit
-class IgdbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable {
+class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
- override val configMetadata = listOf(
- PluginConfigElement(
+ override val configMetadata: PluginConfigMetadata = listOf(
+ ConfigMetadata(
key = "clientId",
- name = "Twitch client ID",
+ type = String::class.java,
+ label = "Twitch client ID",
description = "Your Twitch Client ID"
),
- PluginConfigElement(
+ ConfigMetadata(
key = "clientSecret",
- name = "Twitch client secret",
+ type = String::class.java,
+ label = "Twitch client secret",
description = "Your Twitch Client Secret",
isSecret = true
)
)
- override var config: Map = emptyMap()
override fun validateConfig(config: Map): PluginConfigValidationResult {
+ val pluginConfigValidationResult = super.validateConfig(config)
+
+ if (pluginConfigValidationResult.result == PluginConfigValidationResultType.INVALID) {
+ return pluginConfigValidationResult
+ }
+
try {
- authenticate(config["clientId"], config["clientSecret"])
+ val clientIdToValidate = config["clientId"]
+ val clientSecretToValidate = config["clientSecret"]
+ authenticate(clientIdToValidate, clientSecretToValidate)
return PluginConfigValidationResult.VALID
} catch (e: PluginConfigError) {
log.error(e.message)
@@ -50,7 +60,7 @@ class IgdbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable
override fun start() {
try {
- authenticate(config["clientId"], config["clientSecret"])
+ authenticate(config("clientId"), config("clientSecret"))
} catch (e: PluginConfigError) {
log.error(e.message)
}
diff --git a/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt b/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt
index 68c6e3c..ad3dd89 100644
--- a/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt
+++ b/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt
@@ -1,6 +1,6 @@
package de.grimsi.gameyfin.plugins.steam
-import de.grimsi.gameyfin.pluginapi.core.GameyfinPlugin
+import de.grimsi.gameyfin.pluginapi.core.wrapper.GameyfinPlugin
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
import de.grimsi.gameyfin.plugins.steam.dto.SteamDetailsResultWrapper
diff --git a/plugins/steamgriddb/src/main/kotlin/de/grimsi/gameyfin/plugins/steamgriddb/SteamGridDbPlugin.kt b/plugins/steamgriddb/src/main/kotlin/de/grimsi/gameyfin/plugins/steamgriddb/SteamGridDbPlugin.kt
index 5c8ebf7..0b4b4be 100644
--- a/plugins/steamgriddb/src/main/kotlin/de/grimsi/gameyfin/plugins/steamgriddb/SteamGridDbPlugin.kt
+++ b/plugins/steamgriddb/src/main/kotlin/de/grimsi/gameyfin/plugins/steamgriddb/SteamGridDbPlugin.kt
@@ -1,9 +1,7 @@
package de.grimsi.gameyfin.plugins.steamgriddb
-import de.grimsi.gameyfin.pluginapi.core.ConfigurableGameyfinPlugin
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigElement
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigError
-import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
+import de.grimsi.gameyfin.pluginapi.core.config.*
+import de.grimsi.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
import de.grimsi.gameyfin.plugins.steamgriddb.api.SteamGridDbApiClient
@@ -20,18 +18,26 @@ class SteamGridDbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wra
private var client: SteamGridDbApiClient? = null
}
- override val configMetadata: List = listOf(
- PluginConfigElement(
+ override val configMetadata: PluginConfigMetadata = listOf(
+ ConfigMetadata(
key = "apiKey",
- name = "SteamGridDB API key",
- description = "Your SteamGridDB API key",
+ type = String::class.java,
+ label = "SteamGridDB API key",
+ description = "The API key can be obtained from your SteamGridDB account preferences",
isSecret = true
)
)
override fun validateConfig(config: Map): PluginConfigValidationResult {
+ val pluginConfigValidationResult = super.validateConfig(config)
+
+ if (pluginConfigValidationResult.result == PluginConfigValidationResultType.INVALID) {
+ return pluginConfigValidationResult
+ }
+
try {
- runBlocking { authenticate(config["apiKey"]) }
+ val apiKeyToValidate = config["apiKey"]
+ runBlocking { authenticate(apiKeyToValidate) }
return PluginConfigValidationResult.VALID
} catch (e: PluginConfigError) {
log.error(e.message)
@@ -43,7 +49,7 @@ class SteamGridDbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wra
override fun start() {
try {
- runBlocking { authenticate(config["apiKey"]) }
+ runBlocking { authenticate(config("apiKey")) }
} catch (e: PluginConfigError) {
log.error(e.message)
}