From a4ce0826cc8687f5bde3ededec92ba24b0d244ca Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:30:28 +0100 Subject: [PATCH] Very basic implementation of IGDB plugin --- .../pluginapi/gamemetadata/GameMetadata.kt | 3 + plugins/build.gradle.kts | 16 +++ plugins/igdb/build.gradle.kts | 15 -- .../gameyfin/plugins/igdb/IgdbPlugin.kt | 130 ++++++++++++++---- 4 files changed, 125 insertions(+), 39 deletions(-) diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/gamemetadata/GameMetadata.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/gamemetadata/GameMetadata.kt index b1ebeac..a0432d3 100644 --- a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/gamemetadata/GameMetadata.kt +++ b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/gamemetadata/GameMetadata.kt @@ -1,4 +1,5 @@ package de.grimsi.gameyfin.pluginapi.gamemetadata + import java.net.URL import java.time.Instant @@ -19,6 +20,7 @@ class GameMetadata( ) enum class Genre { + UNKNOWN, PINBALL, ADVENTURE, INDIE, @@ -45,6 +47,7 @@ enum class Genre { } enum class Theme { + UNKNOWN, ACTION, FANTASY, SCIENCE_FICTION, diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index 7e80179..7f92301 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -7,6 +7,7 @@ subprojects { dependencies { compileOnly(project(":plugin-api")) + implementation("io.github.oshai:kotlin-logging-jvm:7.0.0") } tasks.jar { @@ -26,4 +27,19 @@ subprojects { from(sourceSets["main"].output.classesDirs) from(sourceSets["main"].resources) } + + tasks.register("copyDependencyClasses") { + dependsOn(tasks.jar) + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + from(configurations.runtimeClasspath.get().map { project.zipTree(it) }) { + include("**/*.class") + } + into(layout.buildDirectory.get().asFile.resolve("classes/kotlin/main")) + } + + tasks.build { + dependsOn("copyDependencyClasses") + } } \ No newline at end of file diff --git a/plugins/igdb/build.gradle.kts b/plugins/igdb/build.gradle.kts index c3f4084..672c109 100644 --- a/plugins/igdb/build.gradle.kts +++ b/plugins/igdb/build.gradle.kts @@ -7,19 +7,4 @@ dependencies { // IGDB API client implementation("io.github.husnjak:igdb-api-jvm:1.2.0") -} - -tasks.register("copyDependencyClasses") { - dependsOn(tasks.jar) - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - - from(configurations.runtimeClasspath.get().map { project.zipTree(it) }) { - include("**/*.class") - } - into(layout.buildDirectory.get().asFile.resolve("classes/kotlin/main")) -} - -tasks.build { - dependsOn("copyDependencyClasses") } \ 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 668d2cf..279e2ec 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 @@ -1,60 +1,142 @@ package de.grimsi.gameyfin.plugins.igdb +import com.api.igdb.apicalypse.APICalypse import com.api.igdb.request.IGDBWrapper import com.api.igdb.request.TwitchAuthenticator +import com.api.igdb.request.games +import de.grimsi.gameyfin.pluginapi.core.PluginConfigError import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataFetcher +import de.grimsi.gameyfin.pluginapi.gamemetadata.Genre +import de.grimsi.gameyfin.pluginapi.gamemetadata.Theme +import io.github.oshai.kotlinlogging.KotlinLogging import org.pf4j.Extension import org.pf4j.Plugin import org.pf4j.PluginWrapper import java.time.Instant +import kotlin.collections.filter class IgdbPlugin(wrapper: PluginWrapper) : Plugin(wrapper) { + private val log = KotlinLogging.logger {} + + companion object { + val config: IgdbPluginConfig = IgdbPluginConfig(null, null) + } + override fun start() { authenticate() } override fun stop() { - println("IgdbPlugin.stop()") + log.debug { "IgdbPlugin.stop()" } } private fun authenticate() { - // Kotlin example - val token = TwitchAuthenticator.requestTwitchToken("CLIENT_ID", "CLIENT_SECRET") - if (token == null) { - println("Failed to authenticate with Twitch") - return - } - IGDBWrapper.setCredentials("client_id", token.access_token) + log.debug { "Authenticating on Twitch API..." } + + val clientId: String = config.clientId ?: throw PluginConfigError("Twitch Client ID not set") + val clientSecret: String = config.clientSecret ?: throw PluginConfigError("Twitch Client Secret not set") + + val token = TwitchAuthenticator.requestTwitchToken(clientId, clientSecret) + ?: throw PluginConfigError("Failed to authenticate on Twitch API") + + IGDBWrapper.setCredentials(clientId, token.access_token) + + log.debug { "Authentication successful" } } @Extension class IgdbMetadataFetcher : GameMetadataFetcher { - override fun getConfig(): Map { - TODO("Not yet implemented") - } - - override fun setConfig(config: Map) { - TODO("Not yet implemented") - } + private val log = KotlinLogging.logger {} override fun fetchMetadata(gameId: String): GameMetadata { + val findGameByName = APICalypse() + .fields("*") + .limit(100) + .search(gameId) + + val game = IGDBWrapper.games(findGameByName).filter { it.slug == gameId.lowercase() }.firstOrNull() + ?: throw IllegalArgumentException("Could not match game with ID '$gameId'") + return GameMetadata( - title = "Test Game", - description = "This is a test game", - release = Instant.now(), - userRating = 0, - criticRating = 0, - developedBy = listOf("Test Developer"), - publishedBy = listOf("Test Publisher"), - genres = listOf(), - themes = listOf(), + title = game.name, + description = game.summary, + release = Instant.ofEpochSecond(game.firstReleaseDate.seconds), + userRating = game.rating.toInt(), + criticRating = game.aggregatedRating.toInt(), + developedBy = game.involvedCompaniesList.filter { it.developer }.map { it.company.name }, + publishedBy = game.involvedCompaniesList.filter { it.publisher }.map { it.company.name }, + genres = game.genresList.map { mapGenre(it) }, + themes = game.themesList.map { mapTheme(it) }, screenshotUrls = listOf(), videoUrls = listOf(), features = listOf(), perspectives = listOf() ) } + + private fun mapGenre(genre: proto.Genre): Genre { + return when (genre.slug) { + "pinball" -> Genre.PINBALL + "adventure" -> Genre.ADVENTURE + "indie" -> Genre.INDIE + "arcade" -> Genre.ARCADE + "visual-novel" -> Genre.VISUAL_NOVEL + "card-and-board-game" -> Genre.CARD_AND_BOARD_GAME + "moba" -> Genre.MOBA + "point-and-click" -> Genre.POINT_AND_CLICK + "fighting" -> Genre.FIGHTING + "shooter" -> Genre.SHOOTER + "music" -> Genre.MUSIC + "platform" -> Genre.PLATFORM + "puzzle" -> Genre.PUZZLE + "racing" -> Genre.RACING + "real-time-strategy-rts" -> Genre.REAL_TIME_STRATEGY + "role-playing-rpg" -> Genre.ROLE_PLAYING + "simulator" -> Genre.SIMULATOR + "sport" -> Genre.SPORT + "strategy" -> Genre.STRATEGY + "turn-based-strategy-tbs" -> Genre.TURN_BASED_STRATEGY + "tactical" -> Genre.TACTICAL + "hack-and-slash-beat-em-up" -> Genre.HACK_AND_SLASH_BEAT_EM_UP + "quiz-trivia" -> Genre.QUIZ_TRIVIA + else -> { + log.warn { "Unknown genre: ${genre.slug}" } + Genre.UNKNOWN + } + } + } + + private fun mapTheme(theme: proto.Theme): Theme { + return when (theme.slug) { + "action" -> Theme.ACTION + "fantasy" -> Theme.FANTASY + "horror" -> Theme.HORROR + "sci-fi" -> Theme.SCIENCE_FICTION + "mystery" -> Theme.MYSTERY + "thriller" -> Theme.THRILLER + "survival" -> Theme.SURVIVAL + "historical" -> Theme.HISTORICAL + "stealth" -> Theme.STEALTH + "comedy" -> Theme.COMEDY + "business" -> Theme.BUSINESS + "drama" -> Theme.DRAMA + "non-fiction" -> Theme.NON_FICTION + "sandbox" -> Theme.SANDBOX + "educational" -> Theme.EDUCATIONAL + "kids" -> Theme.KIDS + "open-world" -> Theme.OPEN_WORLD + "warfare" -> Theme.WARFARE + "party" -> Theme.PARTY + "4x-explore-expand-exploit-and-exterminate" -> Theme.FOUR_X + "erotic" -> Theme.EROTIC + "romance" -> Theme.ROMANCE + else -> { + log.warn { "Unknown theme: ${theme.slug}" } + Theme.UNKNOWN + } + } + } } } \ No newline at end of file