Preparations for plugin prioritisation and metadata merging

This commit is contained in:
grimsi
2025-01-04 18:26:11 +01:00
parent 046b550cc9
commit 8fdb6ac24d
11 changed files with 140 additions and 141 deletions
@@ -7,7 +7,7 @@ import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
import de.grimsi.gameyfin.plugins.steam.dto.SteamDetailsResultWrapper
import de.grimsi.gameyfin.plugins.steam.dto.SteamGame
import de.grimsi.gameyfin.plugins.steam.dto.SteamSearchResult
import de.grimsi.gameyfin.plugins.steam.mapper.toGenre
import de.grimsi.gameyfin.plugins.steam.mapper.Mapper
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
@@ -18,9 +18,6 @@ import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import me.xdrop.fuzzywuzzy.FuzzySearch
import org.pf4j.Extension
import org.pf4j.PluginWrapper
@@ -29,16 +26,12 @@ import org.slf4j.LoggerFactory
import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.*
class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
override val configMetadata: List<PluginConfigElement> = emptyList()
override fun validateConfig(config: Map<String, String?>): Boolean {
// No config to validate
return true
}
@@ -52,6 +45,10 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
}
}
/**
* The Steam Store API I am using provides far less info than IGDB for example
* See it more as a proof of concept than a fully functional plugin
**/
override fun fetchMetadata(gameId: String): GameMetadata? {
val searchResult: List<SteamGame> = runBlocking { searchStore(gameId) }
if (searchResult.isEmpty()) return null
@@ -91,9 +88,11 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
val steamDetailsResultWrapper: Map<Int, SteamDetailsResultWrapper> = Json.decodeFromString(responseBody)
if (!steamDetailsResultWrapper.containsKey(id)) return null
if (steamDetailsResultWrapper[id]?.success != true) return null
val game = steamDetailsResultWrapper[id]?.data ?: return null
// This is as much as I can get from the Steam Store API
val metadata = GameMetadata(
title = game.name,
description = game.detailedDescription,
@@ -101,7 +100,7 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
release = game.releaseDate?.date,
developedBy = game.developers.toSet(),
publishedBy = game.publishers.toSet(),
genres = game.genres.map { toGenre(it) }.toSet(),
genres = game.genres.map { Mapper.genre(it) }.toSet(),
keywords = game.categories.mapNotNull { it.description }.toSet(),
screenshotUrls = game.screenshots.map { URI(it.pathFull!!) }.toSet(),
videoUrls = game.movies.map { URI(it.webm?.max!!) }.toSet()
@@ -109,19 +108,5 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
return metadata
}
private fun string(json: JsonObject, key: String): String {
return json[key]?.jsonPrimitive?.content ?: ""
}
private fun stringList(json: JsonObject, key: String): List<String> {
return json[key]?.jsonArray?.map { it.jsonPrimitive.content } ?: emptyList()
}
fun date(dateString: String): Instant {
val formatter = DateTimeFormatter.ofPattern("dd MMM, yyyy", Locale.ENGLISH)
val localDate = LocalDate.parse(dateString, formatter)
return localDate.atStartOfDay().toInstant(ZoneOffset.UTC)
}
}
}
@@ -16,7 +16,7 @@ data class SteamGameDetails(
val type: String,
val name: String,
@SerialName("steam_appid") val steamAppId: Int,
@SerialName("required_age") val requiredAge: String,
@SerialName("required_age") val requiredAge: Int,
@SerialName("is_free") val isFree: Boolean,
@SerialName("controller_support") val controllerSupport: String?,
val dlc: List<Int>?,
@@ -1,39 +0,0 @@
package de.grimsi.gameyfin.plugins.steam.mapper
import de.grimsi.gameyfin.pluginapi.gamemetadata.Genre
import de.grimsi.gameyfin.plugins.steam.dto.SteamGenre
fun toGenre(steamGenre: SteamGenre): Genre {
return when (steamGenre.id) {
1 -> Genre.ACTION
2 -> Genre.STRATEGY
25 -> Genre.ADVENTURE
23 -> Genre.INDIE
3 -> Genre.ROLE_PLAYING
28 -> Genre.SIMULATOR
29 -> Genre.MMO
9 -> Genre.RACING
18 -> Genre.SPORT
37 -> Genre.UNKNOWN // Free to Play doesn't match any genre directly
51 -> Genre.UNKNOWN // Animation & Modeling doesn't match any genre directly
58 -> Genre.UNKNOWN // Video Production doesn't match any genre directly
4 -> Genre.UNKNOWN // Casual doesn't match any genre directly
73 -> Genre.UNKNOWN // Violent doesn't map directly to a genre
72 -> Genre.UNKNOWN // Nudity doesn't match any genre directly
70 -> Genre.UNKNOWN // Early Access doesn't map directly to a genre
74 -> Genre.UNKNOWN // Gore doesn't match any genre directly
57 -> Genre.UNKNOWN // Utilities doesn't match any genre directly
52 -> Genre.UNKNOWN // Audio Production doesn't match any genre directly
53 -> Genre.UNKNOWN // Design & Illustration doesn't match any genre directly
59 -> Genre.UNKNOWN // Web Publishing doesn't map directly to a genre
55 -> Genre.UNKNOWN // Photo Editing doesn't map directly to a genre
54 -> Genre.UNKNOWN // Education doesn't match any genre directly
56 -> Genre.UNKNOWN // Software Training doesn't map directly to a genre
71 -> Genre.UNKNOWN // Sexual Content doesn't match any genre directly
60 -> Genre.UNKNOWN // Game Development doesn't map directly to a genre
50 -> Genre.UNKNOWN // Accounting doesn't map directly to a genre
81 -> Genre.UNKNOWN // Documentary doesn't map directly to a genre
84 -> Genre.UNKNOWN // Tutorial doesn't map directly to a genre
else -> Genre.UNKNOWN
}
}
@@ -0,0 +1,43 @@
package de.grimsi.gameyfin.plugins.steam.mapper
import de.grimsi.gameyfin.pluginapi.gamemetadata.Genre
import de.grimsi.gameyfin.plugins.steam.dto.SteamGenre
class Mapper {
companion object {
fun genre(steamGenre: SteamGenre): Genre {
return when (steamGenre.id) {
1 -> Genre.ACTION
2 -> Genre.STRATEGY
25 -> Genre.ADVENTURE
23 -> Genre.INDIE
3 -> Genre.ROLE_PLAYING
28 -> Genre.SIMULATOR
29 -> Genre.MMO
9 -> Genre.RACING
18 -> Genre.SPORT
37 -> Genre.UNKNOWN // Free to Play doesn't match any genre directly
51 -> Genre.UNKNOWN // Animation & Modeling doesn't match any genre directly
58 -> Genre.UNKNOWN // Video Production doesn't match any genre directly
4 -> Genre.UNKNOWN // Casual doesn't match any genre directly
73 -> Genre.UNKNOWN // Violent doesn't map directly to a genre
72 -> Genre.UNKNOWN // Nudity doesn't match any genre directly
70 -> Genre.UNKNOWN // Early Access doesn't map directly to a genre
74 -> Genre.UNKNOWN // Gore doesn't match any genre directly
57 -> Genre.UNKNOWN // Utilities doesn't match any genre directly
52 -> Genre.UNKNOWN // Audio Production doesn't match any genre directly
53 -> Genre.UNKNOWN // Design & Illustration doesn't match any genre directly
59 -> Genre.UNKNOWN // Web Publishing doesn't map directly to a genre
55 -> Genre.UNKNOWN // Photo Editing doesn't map directly to a genre
54 -> Genre.UNKNOWN // Education doesn't match any genre directly
56 -> Genre.UNKNOWN // Software Training doesn't map directly to a genre
71 -> Genre.UNKNOWN // Sexual Content doesn't match any genre directly
60 -> Genre.UNKNOWN // Game Development doesn't map directly to a genre
50 -> Genre.UNKNOWN // Accounting doesn't map directly to a genre
81 -> Genre.UNKNOWN // Documentary doesn't map directly to a genre
84 -> Genre.UNKNOWN // Tutorial doesn't map directly to a genre
else -> Genre.UNKNOWN
}
}
}
}