mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-17 16:20:03 +00:00
Preparations for plugin prioritisation and metadata merging
This commit is contained in:
+1
-1
@@ -4,8 +4,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -14,7 +14,8 @@ import kotlin.io.path.Path
|
|||||||
@Component
|
@Component
|
||||||
class GameyfinPluginManager(
|
class GameyfinPluginManager(
|
||||||
val pluginConfigRepository: PluginConfigRepository,
|
val pluginConfigRepository: PluginConfigRepository,
|
||||||
val dbPluginStatusProvider: DatabasePluginStatusProvider
|
val dbPluginStatusProvider: DatabasePluginStatusProvider,
|
||||||
|
val pluginManagementRepository: PluginManagementRepository
|
||||||
) : DefaultPluginManager(Path(System.getProperty("pf4j.pluginsDir", "plugins"))) {
|
) : DefaultPluginManager(Path(System.getProperty("pf4j.pluginsDir", "plugins"))) {
|
||||||
|
|
||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
@@ -55,8 +56,8 @@ class GameyfinPluginManager(
|
|||||||
override fun loadPluginFromPath(pluginPath: Path?): PluginWrapper? {
|
override fun loadPluginFromPath(pluginPath: Path?): PluginWrapper? {
|
||||||
val pluginWrapper = super.loadPluginFromPath(pluginPath)
|
val pluginWrapper = super.loadPluginFromPath(pluginPath)
|
||||||
|
|
||||||
// Inject config after loading, before starting
|
|
||||||
if (pluginWrapper != null) {
|
if (pluginWrapper != null) {
|
||||||
|
// Inject config after loading, before starting
|
||||||
configurePlugin(pluginWrapper)
|
configurePlugin(pluginWrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-8
@@ -1,19 +1,14 @@
|
|||||||
package de.grimsi.gameyfin.core.plugins.management
|
package de.grimsi.gameyfin.core.plugins.management
|
||||||
|
|
||||||
import jakarta.persistence.Column
|
|
||||||
import jakarta.persistence.Entity
|
import jakarta.persistence.Entity
|
||||||
import jakarta.persistence.Id
|
import jakarta.persistence.Id
|
||||||
import jakarta.persistence.Table
|
|
||||||
import jakarta.validation.constraints.NotNull
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "plugin_management")
|
|
||||||
data class PluginManagementEntry(
|
data class PluginManagementEntry(
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "plugin_id")
|
|
||||||
val pluginId: String,
|
val pluginId: String,
|
||||||
|
|
||||||
@NotNull
|
var enabled: Boolean = true,
|
||||||
@Column(name = "enabled")
|
|
||||||
var enabled: Boolean = true
|
var priority: Int = Int.MAX_VALUE
|
||||||
)
|
)
|
||||||
@@ -17,7 +17,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import org.pf4j.PluginManager
|
import org.pf4j.PluginManager
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.net.URL
|
import java.net.URI
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@@ -43,20 +43,7 @@ class GameService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createFromFile(path: Path): GameDto {
|
fun createFromFile(path: Path): GameDto {
|
||||||
val metadataResults: Map<GameMetadataProvider, GameMetadata?> = runBlocking {
|
val metadataResults = queryPlugins(path.fileName.toString())
|
||||||
coroutineScope {
|
|
||||||
metadataPlugins.associateWith {
|
|
||||||
async {
|
|
||||||
try {
|
|
||||||
it.fetchMetadata(path.fileName.toString())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
log.error(e) { "Error fetching metadata with plugin ${it.javaClass.name}" }
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val (plugin, metadata) = metadataResults.entries.firstOrNull { it.value != null }
|
val (plugin, metadata) = metadataResults.entries.firstOrNull { it.value != null }
|
||||||
?: throw NoMatchException("Could not match game at $path")
|
?: throw NoMatchException("Could not match game at $path")
|
||||||
@@ -84,25 +71,48 @@ class GameService(
|
|||||||
return gameRepository.findByIdOrNull(id) ?: throw IllegalArgumentException("Game with id $id not found")
|
return gameRepository.findByIdOrNull(id) ?: throw IllegalArgumentException("Game with id $id not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries all metadata plugins for metadata on the provided game title
|
||||||
|
* Runs the queries concurrently and asynchronously
|
||||||
|
* @return A map of metadata plugins and their respective results
|
||||||
|
*/
|
||||||
|
private fun queryPlugins(gameTitle: String): Map<GameMetadataProvider, GameMetadata?> {
|
||||||
|
return runBlocking {
|
||||||
|
coroutineScope {
|
||||||
|
metadataPlugins.associateWith {
|
||||||
|
async {
|
||||||
|
try {
|
||||||
|
it.fetchMetadata(gameTitle)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error(e) { "Error fetching metadata with plugin ${it.javaClass.name}" }
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun toDto(game: Game): GameDto {
|
private fun toDto(game: Game): GameDto {
|
||||||
val gameId = game.id ?: throw IllegalArgumentException("Game ID is null")
|
val gameId = game.id ?: throw IllegalArgumentException("Game ID is null")
|
||||||
|
|
||||||
return GameDto(
|
return GameDto(
|
||||||
id = gameId,
|
id = gameId,
|
||||||
title = game.title,
|
title = game.title,
|
||||||
coverId = game.coverImage.id,
|
coverId = game.coverImage?.id,
|
||||||
comment = game.comment,
|
comment = game.comment,
|
||||||
summary = game.summary,
|
summary = game.summary,
|
||||||
release = game.release,
|
release = game.release,
|
||||||
publishers = game.publishers.map { it.name },
|
publishers = game.publishers?.map { it.name },
|
||||||
developers = game.developers.map { it.name },
|
developers = game.developers?.map { it.name },
|
||||||
genres = game.genres.map { it.name },
|
genres = game.genres?.map { it.name },
|
||||||
themes = game.themes.map { it.name },
|
themes = game.themes?.map { it.name },
|
||||||
keywords = game.keywords.toList(),
|
keywords = game.keywords?.toList(),
|
||||||
features = game.features.map { it.name },
|
features = game.features?.map { it.name },
|
||||||
perspectives = game.perspectives.map { it.name },
|
perspectives = game.perspectives?.map { it.name },
|
||||||
imageIds = game.images.mapNotNull { it.id },
|
imageIds = game.images?.mapNotNull { it.id },
|
||||||
videoUrls = game.videoUrls.map { it.toString() },
|
videoUrls = game.videoUrls?.map { it.toString() },
|
||||||
|
path = game.path,
|
||||||
metadata = toDto(game.metadata)
|
metadata = toDto(game.metadata)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -124,16 +134,16 @@ class GameService(
|
|||||||
return Game(
|
return Game(
|
||||||
title = metadata.title,
|
title = metadata.title,
|
||||||
summary = metadata.description,
|
summary = metadata.description,
|
||||||
coverImage = downloadAndPersist(metadata.coverUrl, ImageType.COVER),
|
coverImage = metadata.coverUrl?.let { downloadAndPersist(it, ImageType.COVER) },
|
||||||
release = metadata.release,
|
release = metadata.release,
|
||||||
publishers = metadata.publishedBy.map { toEntity(it, CompanyType.PUBLISHER) }.toSet(),
|
publishers = metadata.publishedBy?.map { toEntity(it, CompanyType.PUBLISHER) }?.toSet(),
|
||||||
developers = metadata.developedBy.map { toEntity(it, CompanyType.DEVELOPER) }.toSet(),
|
developers = metadata.developedBy?.map { toEntity(it, CompanyType.DEVELOPER) }?.toSet(),
|
||||||
genres = metadata.genres,
|
genres = metadata.genres,
|
||||||
themes = metadata.themes,
|
themes = metadata.themes,
|
||||||
keywords = metadata.keywords,
|
keywords = metadata.keywords,
|
||||||
features = metadata.features,
|
features = metadata.features,
|
||||||
perspectives = metadata.perspectives,
|
perspectives = metadata.perspectives,
|
||||||
images = metadata.screenshotUrls.map { downloadAndPersist(it, ImageType.SCREENSHOT) }.toSet(),
|
images = metadata.screenshotUrls?.map { downloadAndPersist(it, ImageType.SCREENSHOT) }?.toSet(),
|
||||||
videoUrls = metadata.videoUrls,
|
videoUrls = metadata.videoUrls,
|
||||||
path = path.toString(),
|
path = path.toString(),
|
||||||
metadata = mapOf("title" to FieldMetadata(sourcePlugin))
|
metadata = mapOf("title" to FieldMetadata(sourcePlugin))
|
||||||
@@ -146,12 +156,13 @@ class GameService(
|
|||||||
return companyRepository.save(company)
|
return companyRepository.save(company)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadAndPersist(imageUrl: URL, type: ImageType): Image {
|
private fun downloadAndPersist(imageUrl: URI, type: ImageType): Image {
|
||||||
imageRepository.findByOriginalUrl(imageUrl)?.let { return it }
|
val parsedUrl = imageUrl.toURL()
|
||||||
|
imageRepository.findByOriginalUrl(parsedUrl)?.let { return it }
|
||||||
|
|
||||||
val image = Image(originalUrl = imageUrl, type = type)
|
val image = Image(originalUrl = parsedUrl, type = type)
|
||||||
imageUrl.openStream().use { input ->
|
parsedUrl.openStream().use { input ->
|
||||||
image.mimeType = URLConnection.guessContentTypeFromName(imageUrl.file)
|
image.mimeType = URLConnection.guessContentTypeFromName(parsedUrl.file)
|
||||||
imageContentStore.setContent(image, input)
|
imageContentStore.setContent(image, input)
|
||||||
}
|
}
|
||||||
return imageRepository.save(image)
|
return imageRepository.save(image)
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ class GameDto(
|
|||||||
val perspectives: List<String>?,
|
val perspectives: List<String>?,
|
||||||
val imageIds: List<Long>?,
|
val imageIds: List<Long>?,
|
||||||
val videoUrls: List<String>?,
|
val videoUrls: List<String>?,
|
||||||
|
val path: String,
|
||||||
val metadata: Map<String, GameMetadataDto>
|
val metadata: Map<String, GameMetadataDto>
|
||||||
)
|
)
|
||||||
@@ -5,7 +5,7 @@ import de.grimsi.gameyfin.pluginapi.gamemetadata.Genre
|
|||||||
import de.grimsi.gameyfin.pluginapi.gamemetadata.PlayerPerspective
|
import de.grimsi.gameyfin.pluginapi.gamemetadata.PlayerPerspective
|
||||||
import de.grimsi.gameyfin.pluginapi.gamemetadata.Theme
|
import de.grimsi.gameyfin.pluginapi.gamemetadata.Theme
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
import java.net.URL
|
import java.net.URI
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -17,7 +17,7 @@ class Game(
|
|||||||
val title: String,
|
val title: String,
|
||||||
|
|
||||||
@OneToOne(cascade = [CascadeType.MERGE])
|
@OneToOne(cascade = [CascadeType.MERGE])
|
||||||
val coverImage: Image,
|
val coverImage: Image? = null,
|
||||||
|
|
||||||
@Lob
|
@Lob
|
||||||
@Column(columnDefinition = "CLOB")
|
@Column(columnDefinition = "CLOB")
|
||||||
@@ -25,40 +25,40 @@ class Game(
|
|||||||
|
|
||||||
@Lob
|
@Lob
|
||||||
@Column(columnDefinition = "CLOB")
|
@Column(columnDefinition = "CLOB")
|
||||||
val summary: String,
|
val summary: String? = null,
|
||||||
|
|
||||||
val release: Instant,
|
val release: Instant? = null,
|
||||||
|
|
||||||
@ManyToMany(cascade = [CascadeType.MERGE])
|
@ManyToMany(cascade = [CascadeType.MERGE])
|
||||||
val publishers: Set<Company>,
|
val publishers: Set<Company>? = null,
|
||||||
|
|
||||||
@ManyToMany(cascade = [CascadeType.MERGE])
|
@ManyToMany(cascade = [CascadeType.MERGE])
|
||||||
val developers: Set<Company>,
|
val developers: Set<Company>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
val genres: Set<Genre>,
|
val genres: Set<Genre>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
val themes: Set<Theme>,
|
val themes: Set<Theme>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
val keywords: Set<String>,
|
val keywords: Set<String>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
val features: Set<GameFeature>,
|
val features: Set<GameFeature>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
val perspectives: Set<PlayerPerspective>,
|
val perspectives: Set<PlayerPerspective>? = null,
|
||||||
|
|
||||||
@OneToMany(cascade = [CascadeType.MERGE])
|
@OneToMany(cascade = [CascadeType.MERGE])
|
||||||
val images: Set<Image>,
|
val images: Set<Image>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
val videoUrls: Set<URL>,
|
val videoUrls: Set<URI>? = null,
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
val path: String,
|
val path: String,
|
||||||
|
|
||||||
@OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
|
@OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||||
val metadata: Map<String, FieldMetadata>
|
val metadata: Map<String, FieldMetadata> = emptyMap()
|
||||||
)
|
)
|
||||||
+17
-15
@@ -1,28 +1,29 @@
|
|||||||
package de.grimsi.gameyfin.pluginapi.gamemetadata
|
package de.grimsi.gameyfin.pluginapi.gamemetadata
|
||||||
|
|
||||||
import java.net.URL
|
import java.net.URI
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
class GameMetadata(
|
class GameMetadata(
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String,
|
val description: String? = null,
|
||||||
val coverUrl: URL,
|
val coverUrl: URI? = null,
|
||||||
val release: Instant,
|
val release: Instant? = null,
|
||||||
val userRating: Int?,
|
val userRating: Int? = null,
|
||||||
val criticRating: Int?,
|
val criticRating: Int? = null,
|
||||||
val developedBy: Set<String>,
|
val developedBy: Set<String>? = null,
|
||||||
val publishedBy: Set<String>,
|
val publishedBy: Set<String>? = null,
|
||||||
val genres: Set<Genre>,
|
val genres: Set<Genre>? = null,
|
||||||
val themes: Set<Theme>,
|
val themes: Set<Theme>? = null,
|
||||||
val keywords: Set<String>,
|
val keywords: Set<String>? = null,
|
||||||
val screenshotUrls: Set<URL>,
|
val screenshotUrls: Set<URI>? = null,
|
||||||
val videoUrls: Set<URL>,
|
val videoUrls: Set<URI>? = null,
|
||||||
val features: Set<GameFeature>,
|
val features: Set<GameFeature>? = null,
|
||||||
val perspectives: Set<PlayerPerspective>
|
val perspectives: Set<PlayerPerspective>? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class Genre {
|
enum class Genre {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
|
ACTION,
|
||||||
PINBALL,
|
PINBALL,
|
||||||
ADVENTURE,
|
ADVENTURE,
|
||||||
INDIE,
|
INDIE,
|
||||||
@@ -30,6 +31,7 @@ enum class Genre {
|
|||||||
VISUAL_NOVEL,
|
VISUAL_NOVEL,
|
||||||
CARD_AND_BOARD_GAME,
|
CARD_AND_BOARD_GAME,
|
||||||
MOBA,
|
MOBA,
|
||||||
|
MMO,
|
||||||
POINT_AND_CLICK,
|
POINT_AND_CLICK,
|
||||||
FIGHTING,
|
FIGHTING,
|
||||||
SHOOTER,
|
SHOOTER,
|
||||||
|
|||||||
@@ -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.SteamDetailsResultWrapper
|
||||||
import de.grimsi.gameyfin.plugins.steam.dto.SteamGame
|
import de.grimsi.gameyfin.plugins.steam.dto.SteamGame
|
||||||
import de.grimsi.gameyfin.plugins.steam.dto.SteamSearchResult
|
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.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.engine.cio.*
|
import io.ktor.client.engine.cio.*
|
||||||
@@ -18,9 +18,6 @@ import io.ktor.http.*
|
|||||||
import io.ktor.serialization.kotlinx.json.*
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.serialization.json.Json
|
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 me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
import org.pf4j.Extension
|
import org.pf4j.Extension
|
||||||
import org.pf4j.PluginWrapper
|
import org.pf4j.PluginWrapper
|
||||||
@@ -29,16 +26,12 @@ import org.slf4j.LoggerFactory
|
|||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
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) {
|
class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
|
||||||
override val configMetadata: List<PluginConfigElement> = emptyList()
|
override val configMetadata: List<PluginConfigElement> = emptyList()
|
||||||
|
|
||||||
override fun validateConfig(config: Map<String, String?>): Boolean {
|
override fun validateConfig(config: Map<String, String?>): Boolean {
|
||||||
|
// No config to validate
|
||||||
return true
|
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? {
|
override fun fetchMetadata(gameId: String): GameMetadata? {
|
||||||
val searchResult: List<SteamGame> = runBlocking { searchStore(gameId) }
|
val searchResult: List<SteamGame> = runBlocking { searchStore(gameId) }
|
||||||
if (searchResult.isEmpty()) return null
|
if (searchResult.isEmpty()) return null
|
||||||
@@ -91,9 +88,11 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
|
|||||||
val steamDetailsResultWrapper: Map<Int, SteamDetailsResultWrapper> = Json.decodeFromString(responseBody)
|
val steamDetailsResultWrapper: Map<Int, SteamDetailsResultWrapper> = Json.decodeFromString(responseBody)
|
||||||
|
|
||||||
if (!steamDetailsResultWrapper.containsKey(id)) return null
|
if (!steamDetailsResultWrapper.containsKey(id)) return null
|
||||||
|
if (steamDetailsResultWrapper[id]?.success != true) return null
|
||||||
|
|
||||||
val game = steamDetailsResultWrapper[id]?.data ?: 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(
|
val metadata = GameMetadata(
|
||||||
title = game.name,
|
title = game.name,
|
||||||
description = game.detailedDescription,
|
description = game.detailedDescription,
|
||||||
@@ -101,7 +100,7 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
|
|||||||
release = game.releaseDate?.date,
|
release = game.releaseDate?.date,
|
||||||
developedBy = game.developers.toSet(),
|
developedBy = game.developers.toSet(),
|
||||||
publishedBy = game.publishers.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(),
|
keywords = game.categories.mapNotNull { it.description }.toSet(),
|
||||||
screenshotUrls = game.screenshots.map { URI(it.pathFull!!) }.toSet(),
|
screenshotUrls = game.screenshots.map { URI(it.pathFull!!) }.toSet(),
|
||||||
videoUrls = game.movies.map { URI(it.webm?.max!!) }.toSet()
|
videoUrls = game.movies.map { URI(it.webm?.max!!) }.toSet()
|
||||||
@@ -109,19 +108,5 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
|
|||||||
|
|
||||||
return metadata
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -16,7 +16,7 @@ data class SteamGameDetails(
|
|||||||
val type: String,
|
val type: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
@SerialName("steam_appid") val steamAppId: Int,
|
@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("is_free") val isFree: Boolean,
|
||||||
@SerialName("controller_support") val controllerSupport: String?,
|
@SerialName("controller_support") val controllerSupport: String?,
|
||||||
val dlc: List<Int>?,
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user