+
+ true
+ true
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/plugin-api/build.gradle.kts b/plugin-api/build.gradle.kts
index e34370a..c07376a 100644
--- a/plugin-api/build.gradle.kts
+++ b/plugin-api/build.gradle.kts
@@ -1,23 +1,49 @@
+import com.vanniktech.maven.publish.SonatypeHost
+
plugins {
kotlin("jvm")
- `maven-publish`
+ `java-library`
+ id("com.vanniktech.maven.publish") version "0.32.0"
}
group = "org.gameyfin"
-repositories {
- mavenCentral()
-}
-
-publishing {
- publications {
- create("maven") {
- from(components["java"])
- }
- }
-}
-
dependencies {
// PF4J (shared)
api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}")
+}
+
+mavenPublishing {
+ publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
+ signAllPublications()
+
+ coordinates(project.group.toString(), project.name, project.version.toString())
+
+ pom {
+ name = "Gameyfin Plugin API"
+ description =
+ "The Gameyfin Plugin API provides the necessary interfaces and classes to create plugins for Gameyfin."
+ url = "https://gameyfin.org/"
+
+ licenses {
+ license {
+ name = "GNU Affero General Public License v3.0"
+ url = "https://www.gnu.org/licenses/agpl-3.0.en.html"
+ }
+ }
+
+ developers {
+ developer {
+ id = "grimsi"
+ name = "Simon Grimme"
+ url = "https://github.com/grimsi"
+ }
+ }
+
+ scm {
+ url = "https://github.com/gameyfin/gameyfin"
+ connection = "scm:git:git://github.com/gameyfin/gameyfin.git"
+ developerConnection = "scm:git:ssh://git@github.com/gameyfin/gameyfin.git"
+ }
+ }
}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/ConfigMetadata.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/ConfigMetadata.kt
index 33ca38f..ba3f767 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/ConfigMetadata.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/ConfigMetadata.kt
@@ -2,8 +2,24 @@ package org.gameyfin.pluginapi.core.config
import java.io.Serializable
+/**
+ * Alias for a list of ConfigMetadata objects for plugin configuration.
+ */
typealias PluginConfigMetadata = List>
+/**
+ * Represents metadata for a configuration property.
+ *
+ * @param T The type of the configuration value, must be Serializable.
+ * @property key The unique (in the scope of the plugin) key for the configuration property.
+ * @property type The class type of the configuration value.
+ * @property label A human-readable label for the configuration property.
+ * @property description A short description of the configuration property.
+ * @property default The default value for the configuration property, if any.
+ * @property isSecret Whether the configuration value is secret (e.g., password). Affects how the value is displayed in the UI.
+ * @property isRequired Whether the configuration property is required.
+ * @property allowedValues The allowed values for the configuration property, if applicable (e.g., for enums). Will be populated automatically if the type is an enum.
+ */
data class ConfigMetadata(
val key: String,
val type: Class,
@@ -13,6 +29,9 @@ data class ConfigMetadata(
val isSecret: Boolean = false,
val isRequired: Boolean = true,
) {
+ /**
+ * List of allowed values for the configuration property, if the type is an enum.
+ */
var allowedValues: List? = null
init {
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/Configurable.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/Configurable.kt
index 204a01c..8d22a43 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/Configurable.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/Configurable.kt
@@ -2,14 +2,52 @@ package org.gameyfin.pluginapi.core.config
import java.io.Serializable
+/**
+ * Interface for classes that can be configured using plugin configuration metadata.
+ * Provides methods for loading, validating, and accessing configuration values.
+ */
interface Configurable {
+ /**
+ * The metadata describing the configuration options for this configurable instance.
+ */
val configMetadata: PluginConfigMetadata
+ /**
+ * Loads configuration values from the provided map.
+ *
+ * @param config A map of configuration keys to their string values (values are nullable).
+ */
fun loadConfig(config: Map)
+ /**
+ * Validates the current configuration state.
+ *
+ * @return The result of the configuration validation.
+ */
fun validateConfig(): PluginConfigValidationResult
+
+ /**
+ * Validates the provided configuration map.
+ *
+ * @param config A map of configuration keys to their string values (nullable).
+ * @return The result of the configuration validation.
+ */
fun validateConfig(config: Map): PluginConfigValidationResult
+ /**
+ * Retrieves a required configuration value by key.
+ *
+ * @param key The configuration key.
+ * @return The configuration value of type T.
+ * @throws Exception if the key is missing or the value is invalid.
+ */
fun config(key: String): T
+
+ /**
+ * Retrieves an optional configuration value by key.
+ *
+ * @param key The configuration key.
+ * @return The configuration value of type T, or null if not present.
+ */
fun optionalConfig(key: String): T?
}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/PluginConfigError.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/PluginConfigError.kt
index 0ea47b7..709c8d3 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/PluginConfigError.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/PluginConfigError.kt
@@ -1,3 +1,11 @@
package org.gameyfin.pluginapi.core.config
+/**
+ * Exception thrown when there is an error in plugin configuration.
+ *
+ * This exception is used to indicate problems such as missing, invalid, or malformed configuration values
+ * when loading or validating plugin configuration.
+ *
+ * @param message The detail message describing the configuration error.
+ */
class PluginConfigError(message: String) : RuntimeException(message)
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/PluginConfigValidationResult.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/PluginConfigValidationResult.kt
index 8429a3f..a116c8d 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/PluginConfigValidationResult.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/config/PluginConfigValidationResult.kt
@@ -1,22 +1,50 @@
package org.gameyfin.pluginapi.core.config
+/**
+ * Represents the result of plugin configuration validation.
+ *
+ * @property result The type of validation result (VALID, INVALID, UNKNOWN).
+ * @property errors A map of configuration keys to error messages, present if validation failed.
+ */
data class PluginConfigValidationResult(
val result: PluginConfigValidationResultType,
val errors: Map? = null
) {
companion object {
+ /**
+ * A valid configuration result with no errors.
+ */
val VALID = PluginConfigValidationResult(PluginConfigValidationResultType.VALID)
+
+ /**
+ * An unknown configuration validation result.
+ */
val UNKNOWN = PluginConfigValidationResult(PluginConfigValidationResultType.UNKNWOWN)
+
+ /**
+ * Creates an invalid configuration result with the specified errors.
+ *
+ * @param errors A map of configuration keys to error messages.
+ * @return An invalid PluginConfigValidationResult instance.
+ */
fun INVALID(errors: Map): PluginConfigValidationResult {
return PluginConfigValidationResult(PluginConfigValidationResultType.INVALID, errors)
}
}
+ /**
+ * Checks if the configuration is valid.
+ *
+ * @return True if the result is VALID, false otherwise.
+ */
fun isValid(): Boolean {
return result == PluginConfigValidationResultType.VALID
}
}
+/**
+ * Enum representing the possible types of plugin configuration validation results.
+ */
enum class PluginConfigValidationResultType {
VALID,
INVALID,
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt
index e6be0b0..6e65da0 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt
@@ -7,17 +7,47 @@ import org.gameyfin.pluginapi.core.config.PluginConfigValidationResult
import org.pf4j.PluginWrapper
import java.io.Serializable
+/**
+ * Abstract base class for Gameyfin plugins that support configuration.
+ *
+ * This class implements the [Configurable] interface and provides default logic for loading,
+ * validating, and accessing plugin configuration values using metadata.
+ *
+ * @constructor Creates a configurable Gameyfin plugin with the given [PluginWrapper].
+ * @param wrapper The plugin wrapper provided by the Gameyfin application.
+ */
@Suppress("UNCHECKED_CAST")
abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable {
+ /**
+ * The current configuration map, where keys are configuration property names and values are their string representations.
+ */
private var config: Map = emptyMap()
+ /**
+ * Loads configuration values from the provided map.
+ *
+ * @param config A map of configuration keys to their string values (nullable).
+ */
override fun loadConfig(config: Map) {
this.config = config
}
+ /**
+ * Validates the current configuration state.
+ *
+ * @return The result of the configuration validation.
+ */
override fun validateConfig(): PluginConfigValidationResult = validateConfig(config)
+ /**
+ * Validates the provided configuration map.
+ * The validation covers basic checks such as required fields, type casting, and allowed values.
+ * It's recommended to override this method in subclasses to implement additional validation logic specific to the plugin.
+ *
+ * @param config A map of configuration keys to their string values (nullable).
+ * @return The result of the configuration validation.
+ */
override fun validateConfig(config: Map): PluginConfigValidationResult {
val errors = mutableMapOf()
@@ -43,6 +73,13 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug
}
}
+ /**
+ * Retrieves an optional configuration value by key.
+ *
+ * @param key The configuration key.
+ * @return The configuration value of type T, or null if not present or invalid.
+ * @throws PluginConfigError if the value cannot be cast to the expected type.
+ */
override fun optionalConfig(key: String): T? {
val meta = resolveMetadata(key)
val value = resolveValue(key)
@@ -55,6 +92,13 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug
}
}
+ /**
+ * Retrieves a required configuration value by key.
+ *
+ * @param key The configuration key.
+ * @return The configuration value of type T.
+ * @throws PluginConfigError if the key is missing or the value is invalid.
+ */
override fun config(key: String): T {
val value = optionalConfig(key)
if (value == null) {
@@ -63,6 +107,16 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug
return value
}
+ /**
+ * Casts a configuration value to the expected type defined in the metadata.
+ *
+ * Handles enums, common primitive types, and attempts to use valueOf/parse methods via reflection for custom types.
+ *
+ * @param meta The configuration metadata describing the expected type.
+ * @param value The value to cast.
+ * @return The cast value, or throws PluginConfigError if casting fails.
+ * @throws PluginConfigError if the value cannot be cast to the expected type.
+ */
private fun castConfigValue(meta: ConfigMetadata<*>, value: Any): Any? {
val expectedType = meta.type
@@ -106,11 +160,25 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug
}
}
+ /**
+ * Resolves the configuration metadata for a given key.
+ *
+ * @param key The configuration key to look up.
+ * @return The corresponding ConfigMetadata instance.
+ * @throws PluginConfigError if the key is unknown.
+ */
private fun resolveMetadata(key: String): ConfigMetadata<*> {
return configMetadata.find { it.key == key }
?: throw PluginConfigError("Unknown configuration key: $key")
}
+ /**
+ * Resolves the value for a configuration key, optionally using an override map.
+ *
+ * @param key The configuration key to resolve.
+ * @param configOverride An optional map to override the current configuration.
+ * @return The resolved value, or the default from metadata if not present.
+ */
private fun resolveValue(key: String, configOverride: Map? = null): Serializable? {
val meta = resolveMetadata(key)
val conf = configOverride ?: config
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt
index f9b0447..ffef149 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt
@@ -3,21 +3,48 @@ package org.gameyfin.pluginapi.core.wrapper
import org.pf4j.Plugin
import org.pf4j.PluginWrapper
+/**
+ * Abstract base class for all Gameyfin plugins.
+ *
+ * This class extends the PF4J [Plugin] class and provides utility methods for plugin logo management.
+ * It also maintains a static reference to the current plugin instance.
+ *
+ * @constructor Creates a Gameyfin plugin with the given [PluginWrapper].
+ * @param wrapper The plugin wrapper provided by the Gameyfin application.
+ */
@Suppress("DEPRECATION")
abstract class GameyfinPlugin(wrapper: PluginWrapper) : Plugin(wrapper) {
companion object {
+ /**
+ * The base name of the logo file (without extension).
+ */
const val LOGO_FILE_NAME: String = "logo"
+
+ /**
+ * Supported logo file formats.
+ */
val SUPPORTED_LOGO_FORMATS: List = listOf("png", "jpg", "jpeg", "gif", "svg", "webp")
+ /**
+ * Reference to the current plugin instance.
+ */
lateinit var plugin: GameyfinPlugin
private set
}
+ /**
+ * Initializes the plugin and sets the static plugin reference.
+ */
init {
plugin = this
}
+ /**
+ * Checks if the plugin contains a logo file in any supported format.
+ *
+ * @return True if a logo file is found, false otherwise.
+ */
fun hasLogo(): Boolean {
for (format in SUPPORTED_LOGO_FORMATS) {
val resourcePath = "$LOGO_FILE_NAME.$format"
@@ -30,6 +57,11 @@ abstract class GameyfinPlugin(wrapper: PluginWrapper) : Plugin(wrapper) {
return false
}
+ /**
+ * Retrieves the logo file as a byte array, if present.
+ *
+ * @return The logo file as a byte array, or null if not found.
+ */
fun getLogo(): ByteArray? {
for (format in SUPPORTED_LOGO_FORMATS) {
val resourcePath = "$LOGO_FILE_NAME.$format"
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/download/Download.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/download/Download.kt
index e61b82e..1d270e0 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/download/Download.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/download/Download.kt
@@ -2,15 +2,29 @@ package org.gameyfin.pluginapi.download
import java.io.InputStream
+/**
+ * Represents a downloadable resource, which can be either a file or a link.
+ */
sealed interface Download
+/**
+ * Represents a file-based download.
+ *
+ * @property data The input stream containing the file data.
+ * @property fileExtension The file extension (e.g., "zip", "png"), or null if none.
+ * @property size The size of the file in bytes, or null if unknown.
+ */
data class FileDownload(
val data: InputStream,
val fileExtension: String? = null,
val size: Long? = null
) : Download
+/**
+ * Represents a link-based download.
+ *
+ * @property url The URL to the downloadable resource.
+ */
data class LinkDownload(
val url: String
) : Download
-
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/download/DownloadProvider.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/download/DownloadProvider.kt
index 6354a92..db8b55e 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/download/DownloadProvider.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/download/DownloadProvider.kt
@@ -3,7 +3,19 @@ package org.gameyfin.pluginapi.download
import org.pf4j.ExtensionPoint
import java.nio.file.Path
+/**
+ * Extension point for providing downloadable resources.
+ *
+ * Implementations of this interface are responsible for handling download requests for specific paths.
+ * This is typically used to allow plugins to serve files or links for download.
+ */
interface DownloadProvider : ExtensionPoint {
+ /**
+ * Downloads a resource for the given path.
+ *
+ * @param path The path to the resource to download.
+ * @return A [Download] representing the downloadable resource (file or link).
+ */
fun download(path: Path): Download
}
\ No newline at end of file
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameMetadata.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameMetadata.kt
index feb516d..4fb23ff 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameMetadata.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameMetadata.kt
@@ -3,6 +3,26 @@ package org.gameyfin.pluginapi.gamemetadata
import java.net.URI
import java.time.Instant
+/**
+ * Represents metadata for a game, including identifiers, descriptive information, media, ratings, and categorization.
+ *
+ * @property originalId The unique identifier for the game from the original source.
+ * @property title The title of the game.
+ * @property description A description of the game, or null if not available.
+ * @property coverUrl The URI to the game's cover image, or null if not available.
+ * @property release The release date and time of the game, or null if not available.
+ * @property userRating The user rating for the game, or null if not available.
+ * @property criticRating The critic rating for the game, or null if not available.
+ * @property developedBy The set of developer names, or null if not available.
+ * @property publishedBy The set of publisher names, or null if not available.
+ * @property genres The set of genres associated with the game, or null if not available.
+ * @property themes The set of themes associated with the game, or null if not available.
+ * @property keywords The set of keywords associated with the game, or null if not available.
+ * @property screenshotUrls The set of URIs to screenshots, or null if not available.
+ * @property videoUrls The set of URIs to videos, or null if not available.
+ * @property features The set of features associated with the game, or null if not available.
+ * @property perspectives The set of player perspectives, or null if not available.
+ */
data class GameMetadata(
val originalId: String,
val title: String,
@@ -22,6 +42,9 @@ data class GameMetadata(
val perspectives: Set? = null
)
+/**
+ * Enum representing the genre of a game.
+ */
enum class Genre {
UNKNOWN,
ACTION,
@@ -51,6 +74,9 @@ enum class Genre {
QUIZ_TRIVIA
}
+/**
+ * Enum representing the theme of a game.
+ */
enum class Theme {
UNKNOWN,
ACTION,
diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameMetadataProvider.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameMetadataProvider.kt
index 564832d..8bc1b86 100644
--- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameMetadataProvider.kt
+++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameMetadataProvider.kt
@@ -2,8 +2,27 @@ package org.gameyfin.pluginapi.gamemetadata
import org.pf4j.ExtensionPoint
+/**
+ * Extension point for providing game metadata.
+ *
+ * Implementations of this interface are responsible for fetching game metadata by title or by unique identifier.
+ * This is typically used to allow plugins to provide metadata for games from various sources.
+ */
interface GameMetadataProvider : ExtensionPoint {
+ /**
+ * Fetches a list of game metadata entries matching the given game title.
+ *
+ * @param gameTitle The title of the game to search for.
+ * @param maxResults The maximum number of results to return. Defaults to 1.
+ * @return A list of [GameMetadata] objects matching the title, or an empty list if none found.
+ */
fun fetchByTitle(gameTitle: String, maxResults: Int = 1): List
+ /**
+ * Fetches game metadata by its unique identifier.
+ *
+ * @param id The unique identifier of the game.
+ * @return The [GameMetadata] for the given id, or null if not found.
+ */
fun fetchById(id: String): GameMetadata?
}
\ No newline at end of file