diff --git a/.run/Rebuild all.run.xml b/.run/Rebuild all.run.xml index 15b4c09..7e8ed26 100644 --- a/.run/Rebuild all.run.xml +++ b/.run/Rebuild all.run.xml @@ -10,6 +10,7 @@ diff --git a/.run/Rebuild plugins.run.xml b/.run/Rebuild plugins.run.xml index ecc2410..caef427 100644 --- a/.run/Rebuild plugins.run.xml +++ b/.run/Rebuild plugins.run.xml @@ -10,6 +10,7 @@ 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 a57906c..6f5b313 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 @@ -94,19 +94,15 @@ class GameyfinPluginManager( } try { - log.info { "${"Start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)}"} + log.info { "Start plugin '${getPluginLabel(pluginWrapper.descriptor)}'"} pluginWrapper.plugin.start() pluginWrapper.pluginState = PluginState.STARTED pluginWrapper.failedException = null startedPlugins.add(pluginWrapper) - } catch (e: LinkageError) { - pluginWrapper.pluginState = PluginState.FAILED - pluginWrapper.failedException = e - log.error { "${"Unable to start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)} $e"} } catch (e: Exception) { pluginWrapper.pluginState = PluginState.FAILED pluginWrapper.failedException = e - log.error { "${"Unable to start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)} $e"} + log.error { "Unable to start plugin '${getPluginLabel(pluginWrapper.descriptor)}': $e"} } finally { firePluginStateEvent(PluginStateEvent(this, pluginWrapper, pluginState)) } diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/PluginManagementEndpoint.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/PluginManagementEndpoint.kt index 4b26d02..294f47e 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/PluginManagementEndpoint.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/PluginManagementEndpoint.kt @@ -9,9 +9,9 @@ import jakarta.annotation.security.RolesAllowed class PluginManagementEndpoint( private val pluginManagementService: PluginManagementService ) { - fun getPlugins() = pluginManagementService.getPlugins() + fun getPlugins() = pluginManagementService.getPluginDtos() - fun getPlugin(pluginId: String) = pluginManagementService.getPlugin(pluginId) + fun getPlugin(pluginId: String) = pluginManagementService.getPluginDto(pluginId) fun startPlugin(pluginId: String) = pluginManagementService.startPlugin(pluginId) diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/PluginManagementService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/PluginManagementService.kt index b0ed58e..f28cf45 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/PluginManagementService.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/PluginManagementService.kt @@ -1,12 +1,15 @@ package de.grimsi.gameyfin.core.plugins.management +import org.pf4j.ExtensionPoint +import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @Service class PluginManagementService( - private val pluginManager: GameyfinPluginManager + private val pluginManager: GameyfinPluginManager, + private val pluginManagementRepository: PluginManagementRepository ) { - fun getPlugins(): List { + fun getPluginDtos(): List { return pluginManager.plugins.map { PluginDto( it.pluginId, @@ -18,7 +21,7 @@ class PluginManagementService( } } - fun getPlugin(pluginId: String): PluginDto { + fun getPluginDto(pluginId: String): PluginDto { val plugin = pluginManager.getPlugin(pluginId) return PluginDto( plugin.pluginId, @@ -29,6 +32,17 @@ class PluginManagementService( ) } + fun getPluginManagementEntry(pluginId: String): PluginManagementEntry { + return pluginManagementRepository.findByIdOrNull(pluginId) + ?: throw IllegalArgumentException("Plugin with ID $pluginId not found") + } + + fun getPluginManagementEntry(clazz: Class): PluginManagementEntry { + val pluginWrapper = pluginManager.whichPlugin(clazz) + return pluginManagementRepository.findByIdOrNull(pluginWrapper.pluginId) + ?: throw IllegalArgumentException("Plugin with class $clazz not found") + } + fun startPlugin(pluginId: String) { pluginManager.startPlugin(pluginId) } diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/Game.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/Game.kt deleted file mode 100644 index b8781e5..0000000 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/Game.kt +++ /dev/null @@ -1,34 +0,0 @@ -package de.grimsi.gameyfin.games - -import jakarta.persistence.* -import java.time.Instant - -@Entity -class Game( - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - var id: Long? = null, - - val title: String, - - @Lob - @Column(columnDefinition = "CLOB") - val comment: String? = null, - - @Lob - @Column(columnDefinition = "CLOB") - val summary: String, - - val release: Instant, - - @ElementCollection - val publishers: List, - - @ElementCollection - val developers: List, - - @Column(unique = true) - val path: String, - - val source: String -) \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt index 93b47bb..126ccbf 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt @@ -1,5 +1,14 @@ package de.grimsi.gameyfin.games +import de.grimsi.gameyfin.core.plugins.management.PluginManagementService +import de.grimsi.gameyfin.games.entities.Company +import de.grimsi.gameyfin.games.entities.CompanyType +import de.grimsi.gameyfin.games.entities.Game +import de.grimsi.gameyfin.games.entities.Screenshot +import de.grimsi.gameyfin.games.repositories.CompanyRepository +import de.grimsi.gameyfin.games.repositories.GameRepository +import de.grimsi.gameyfin.games.repositories.ScreenshotContentStore +import de.grimsi.gameyfin.games.repositories.ScreenshotRepository import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider import io.github.oshai.kotlinlogging.KotlinLogging @@ -9,12 +18,18 @@ import kotlinx.coroutines.runBlocking import org.pf4j.PluginManager import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +import java.net.URL +import java.net.URLConnection import java.nio.file.Path @Service class GameService( + private val pluginManager: PluginManager, + private val pluginManagementService: PluginManagementService, private val gameRepository: GameRepository, - private val pluginManager: PluginManager + private val companyRepository: CompanyRepository, + private val screenshotRepository: ScreenshotRepository, + private val screenshotContentStore: ScreenshotContentStore ) { private val log = KotlinLogging.logger {} @@ -47,15 +62,11 @@ class GameService( val (plugin, metadata) = metadataResults.entries.firstOrNull { it.value != null } ?: throw NoMatchException("Could not match game at $path") - val game = Game( - title = metadata!!.title, - summary = metadata.description, - release = metadata.release, - publishers = metadata.publishedBy, - developers = metadata.developedBy, - path = path.toString(), - source = plugin.javaClass.name - ) + if (metadata == null) { + throw NoMatchException("Plugin ${plugin.javaClass} returned invalid metadata for game at $path") + } + + val game = toEntity(metadata, path, plugin) return createOrUpdate(game) } @@ -74,13 +85,48 @@ class GameService( } private fun toDto(game: Game): GameDto { - if (game.id == null) { - throw IllegalArgumentException("Game ID is null") - } + val gameId = game.id ?: throw IllegalArgumentException("Game ID is null") return GameDto( - id = game.id!!, + id = gameId, title = game.title ) } + + private fun toEntity(metadata: GameMetadata, path: Path, source: GameMetadataProvider): Game { + return Game( + title = metadata.title, + summary = metadata.description, + release = metadata.release, + publishers = metadata.publishedBy.map { toEntity(it, CompanyType.PUBLISHER) }.toSet(), + developers = metadata.developedBy.map { toEntity(it, CompanyType.DEVELOPER) }.toSet(), + genres = metadata.genres, + themes = metadata.themes, + keywords = metadata.keywords, + features = metadata.features, + perspectives = metadata.perspectives, + screenshots = metadata.screenshotUrls.map { downloadAndPersist(it) }.toSet(), + videoUrls = metadata.videoUrls, + path = path.toString(), + source = pluginManagementService.getPluginManagementEntry(source.javaClass) + ) + } + + private fun toEntity(companyName: String, companyType: CompanyType): Company { + companyRepository.findByNameAndType(companyName, companyType)?.let { return it } + val company = Company(name = companyName, type = companyType) + return companyRepository.save(company) + } + + private fun downloadAndPersist(screenshotUrl: URL): Screenshot { + screenshotRepository.findByOriginalUrl(screenshotUrl)?.let { return it } + + val screenshot = Screenshot(originalUrl = screenshotUrl) + screenshotUrl.openStream().use { input -> + val mimeType = URLConnection.guessContentTypeFromStream(input) + screenshot.mimeType = mimeType + screenshotContentStore.setContent(screenshot, input) + } + return screenshotRepository.save(screenshot) + } } \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Company.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Company.kt new file mode 100644 index 0000000..aba58bf --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Company.kt @@ -0,0 +1,18 @@ +package de.grimsi.gameyfin.games.entities + +import jakarta.persistence.* + +@Entity +@Table(uniqueConstraints = [UniqueConstraint(columnNames = ["name", "type"])]) +class Company( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + var id: Long? = null, + val name: String, + val type: CompanyType +) + +enum class CompanyType { + DEVELOPER, + PUBLISHER +} \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Game.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Game.kt new file mode 100644 index 0000000..f59c7d1 --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Game.kt @@ -0,0 +1,62 @@ +package de.grimsi.gameyfin.games.entities + +import de.grimsi.gameyfin.core.plugins.management.PluginManagementEntry +import de.grimsi.gameyfin.pluginapi.gamemetadata.GameFeature +import de.grimsi.gameyfin.pluginapi.gamemetadata.Genre +import de.grimsi.gameyfin.pluginapi.gamemetadata.PlayerPerspective +import de.grimsi.gameyfin.pluginapi.gamemetadata.Theme +import jakarta.persistence.* +import java.net.URL +import java.time.Instant + +@Entity +class Game( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + var id: Long? = null, + + val title: String, + + @Lob + @Column(columnDefinition = "CLOB") + val comment: String? = null, + + @Lob + @Column(columnDefinition = "CLOB") + val summary: String, + + val release: Instant, + + @OneToMany(cascade = [CascadeType.MERGE]) + val publishers: Set, + + @OneToMany(cascade = [CascadeType.MERGE]) + val developers: Set, + + @ElementCollection + val genres: Set, + + @ElementCollection + val themes: Set, + + @ElementCollection + val keywords: Set, + + @ElementCollection + val features: Set, + + @ElementCollection + val perspectives: Set, + + @OneToMany(cascade = [CascadeType.MERGE]) + val screenshots: Set, + + @ElementCollection + val videoUrls: Set, + + @Column(unique = true) + val path: String, + + @ManyToOne + val source: PluginManagementEntry +) \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Screenshot.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Screenshot.kt new file mode 100644 index 0000000..c85ee2a --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Screenshot.kt @@ -0,0 +1,32 @@ +package de.grimsi.gameyfin.games.entities + +import jakarta.annotation.Nullable +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import org.springframework.content.commons.annotations.ContentId +import org.springframework.content.commons.annotations.ContentLength +import org.springframework.content.commons.annotations.MimeType +import java.net.URL + +@Entity +class Screenshot( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + var id: Long? = null, + + val originalUrl: URL, + + @ContentId + @Nullable + var contentId: String? = null, + + @ContentLength + @Nullable + var contentLength: Long? = null, + + @MimeType + @Nullable + var mimeType: String? = null +) \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Video.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Video.kt new file mode 100644 index 0000000..d9d1d7d --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Video.kt @@ -0,0 +1,4 @@ +package de.grimsi.gameyfin.games.entities + +class Video { +} \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/CompanyRepository.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/CompanyRepository.kt new file mode 100644 index 0000000..e0325a0 --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/CompanyRepository.kt @@ -0,0 +1,9 @@ +package de.grimsi.gameyfin.games.repositories + +import de.grimsi.gameyfin.games.entities.Company +import de.grimsi.gameyfin.games.entities.CompanyType +import org.springframework.data.jpa.repository.JpaRepository + +interface CompanyRepository : JpaRepository { + fun findByNameAndType(name: String, type: CompanyType): Company? +} \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameRepository.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/GameRepository.kt similarity index 62% rename from gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameRepository.kt rename to gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/GameRepository.kt index 67257b2..91dc88d 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameRepository.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/GameRepository.kt @@ -1,5 +1,6 @@ -package de.grimsi.gameyfin.games +package de.grimsi.gameyfin.games.repositories +import de.grimsi.gameyfin.games.entities.Game import org.springframework.data.jpa.repository.JpaRepository interface GameRepository : JpaRepository { diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotContentStore.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotContentStore.kt new file mode 100644 index 0000000..f1e2ced --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotContentStore.kt @@ -0,0 +1,8 @@ +package de.grimsi.gameyfin.games.repositories + +import de.grimsi.gameyfin.games.entities.Screenshot +import org.springframework.content.commons.store.ContentStore +import org.springframework.stereotype.Repository + +@Repository +interface ScreenshotContentStore : ContentStore \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotRepository.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotRepository.kt new file mode 100644 index 0000000..132b94b --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotRepository.kt @@ -0,0 +1,9 @@ +package de.grimsi.gameyfin.games.repositories + +import de.grimsi.gameyfin.games.entities.Screenshot +import org.springframework.data.jpa.repository.JpaRepository +import java.net.URL + +interface ScreenshotRepository : JpaRepository { + fun findByOriginalUrl(originalUrl: URL): Screenshot? +} \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/Library.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/Library.kt index ef4b173..8ef3407 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/Library.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/Library.kt @@ -1,6 +1,6 @@ package de.grimsi.gameyfin.libraries -import de.grimsi.gameyfin.games.Game +import de.grimsi.gameyfin.games.entities.Game import jakarta.persistence.* @Entity diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryEndpoint.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryEndpoint.kt index e65a40d..d05e2d1 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryEndpoint.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryEndpoint.kt @@ -2,7 +2,7 @@ package de.grimsi.gameyfin.libraries import com.vaadin.hilla.Endpoint import de.grimsi.gameyfin.core.Role -import de.grimsi.gameyfin.games.Game +import de.grimsi.gameyfin.games.entities.Game import jakarta.annotation.security.RolesAllowed @Endpoint diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryService.kt index 7e0b38a..4232b58 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryService.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryService.kt @@ -2,8 +2,8 @@ package de.grimsi.gameyfin.libraries import de.grimsi.gameyfin.config.ConfigProperties import de.grimsi.gameyfin.config.ConfigService -import de.grimsi.gameyfin.games.Game import de.grimsi.gameyfin.games.GameService +import de.grimsi.gameyfin.games.entities.Game import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import java.nio.file.Path 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 a0432d3..c389173 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 @@ -9,14 +9,15 @@ class GameMetadata( val release: Instant, val userRating: Int?, val criticRating: Int?, - val developedBy: List, - val publishedBy: List, - val genres: List, - val themes: List, - val screenshotUrls: List, - val videoUrls: List, - val features: List, - val perspectives: List + val developedBy: Set, + val publishedBy: Set, + val genres: Set, + val themes: Set, + val keywords: Set, + val screenshotUrls: Set, + val videoUrls: Set, + val features: Set, + val perspectives: Set ) enum class Genre { @@ -95,10 +96,12 @@ enum class GameFeature { ONLINE_PVE, LOCAL_PVP, LOCAL_PVE, - CROSSPLAY + CROSSPLAY, + SPLITSCREEN } enum class PlayerPerspective { + UNKNOWN, FIRST_PERSON, THIRD_PERSON, BIRD_VIEW_ISOMETRIC, 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 6783fa3..81bfa72 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 @@ -59,9 +59,43 @@ class IgdbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) { @Extension class IgdbMetadataProvider : GameMetadataProvider { + + private val QUERY_FIELDS = listOf( + "slug", + "name", + "summary", + "first_release_date", + "rating", + "aggregated_rating", + "total_rating", + "category", + "multiplayer_modes.lancoop", + "game_modes.slug", + "game_modes.name", + "cover.image_id", + "screenshots.image_id", + "videos.video_id", + "involved_companies.company.slug", + "involved_companies.company.name", + "involved_companies.developer", + "involved_companies.publisher", + "involved_companies.company.logo.image_id", + "genres.slug", + "genres.name", + "keywords.slug", + "keywords.name", + "themes.slug", + "themes.name", + "player_perspectives.slug", + "player_perspectives.name", + "platforms.slug", + "platforms.name", + "platforms.platform_logo.image_id" + ).joinToString(",") + override fun fetchMetadata(gameId: String): GameMetadata? { val findBySlugQuery = APICalypse() - .fields("*") + .fields(QUERY_FIELDS) .where("slug = \"${guessSlug(gameId)}\"") // First step: Try to find the game by guessing the slug @@ -70,7 +104,7 @@ class IgdbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) { // Second step: Try a fuzzy search if (game == null) { val searchByNameQuery = APICalypse() - .fields("*") + .fields(QUERY_FIELDS) .limit(100) .search(gameId) @@ -91,14 +125,15 @@ class IgdbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) { 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 { Mapper.genre(it) }, - themes = game.themesList.map { Mapper.theme(it) }, - screenshotUrls = listOf(), - videoUrls = listOf(), - features = listOf(), - perspectives = listOf() + developedBy = game.involvedCompaniesList.filter { it.developer }.map { it.company.name }.toSet(), + publishedBy = game.involvedCompaniesList.filter { it.publisher }.map { it.company.name }.toSet(), + genres = game.genresList.map { Mapper.genre(it) }.toSet(), + themes = game.themesList.map { Mapper.theme(it) }.toSet(), + keywords = game.keywordsList.map { it.name }.toSet(), + screenshotUrls = game.screenshotsList.map { Mapper.screenshot(it) }.toSet(), + videoUrls = game.videosList.map { Mapper.video(it) }.toSet(), + features = Mapper.gameFeatures(game), + perspectives = game.playerPerspectivesList.map { Mapper.playerPerspective(it) }.toSet() ) } diff --git a/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/Mapper.kt b/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/Mapper.kt index 475975e..76b5d73 100644 --- a/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/Mapper.kt +++ b/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/Mapper.kt @@ -1,8 +1,15 @@ package de.grimsi.gameyfin.plugins.igdb +import com.api.igdb.utils.ImageSize +import com.api.igdb.utils.ImageType +import com.api.igdb.utils.imageBuilder +import de.grimsi.gameyfin.pluginapi.gamemetadata.GameFeature import de.grimsi.gameyfin.pluginapi.gamemetadata.Genre +import de.grimsi.gameyfin.pluginapi.gamemetadata.PlayerPerspective import de.grimsi.gameyfin.pluginapi.gamemetadata.Theme import org.slf4j.LoggerFactory +import java.net.URI +import java.net.URL class Mapper { companion object { @@ -70,5 +77,49 @@ class Mapper { } } } + + fun playerPerspective(perspective: proto.PlayerPerspective): PlayerPerspective { + return when (perspective.slug) { + "first-person" -> PlayerPerspective.FIRST_PERSON + "third-person" -> PlayerPerspective.THIRD_PERSON + "bird-view-isometric" -> PlayerPerspective.BIRD_VIEW_ISOMETRIC + "side-view" -> PlayerPerspective.SIDE_VIEW + "text" -> PlayerPerspective.TEXT + "auditory" -> PlayerPerspective.AUDITORY + "virtual-reality" -> PlayerPerspective.VIRTUAL_REALITY + else -> { + log.warn("Unknown player perspective: {}", perspective.slug) + PlayerPerspective.UNKNOWN + } + } + } + + fun screenshot(screenshot: proto.Screenshot): URL { + return URI(imageBuilder(screenshot.imageId, ImageSize.SCREENSHOT_HUGE, ImageType.PNG)).toURL() + } + + fun video(video: proto.GameVideo): URL { + return URI("https://www.youtube.com/watch?v=${video.videoId}").toURL() + } + + fun gameFeatures(game: proto.Game): Set { + var gameFeatures = mutableSetOf() + + // Get LAN support from multiplayer modes + if (game.multiplayerModesList.any { it.lancoop }) gameFeatures.add(GameFeature.LOCAL_MULTIPLAYER) + + for (gameMode in game.gameModesList) { + when (gameMode.slug) { + "single-player" -> gameFeatures.add(GameFeature.SINGLEPLAYER) + "multiplayer" -> gameFeatures.add(GameFeature.MULTIPLAYER) + "co-operative" -> gameFeatures.add(GameFeature.CO_OP) + "split-screen" -> gameFeatures.add(GameFeature.SPLITSCREEN) + else -> { + log.warn("Unknown game mode: {}", gameMode.slug) + } + } + } + return gameFeatures + } } } \ No newline at end of file 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 99eab6b..6dd1a0c 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 @@ -112,14 +112,15 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) { release = date(game["release_date"]?.jsonObject["date"]?.jsonPrimitive?.content!!), userRating = 0, criticRating = 0, - developedBy = stringList(game, "developers"), - publishedBy = stringList(game, "publishers"), - genres = emptyList(), - themes = emptyList(), - screenshotUrls = emptyList(), - videoUrls = emptyList(), - features = emptyList(), - perspectives = emptyList() + developedBy = stringList(game, "developers").toSet(), + publishedBy = stringList(game, "publishers").toSet(), + genres = emptySet(), + themes = emptySet(), + keywords = emptySet(), + screenshotUrls = emptySet(), + videoUrls = emptySet(), + features = emptySet(), + perspectives = emptySet() ) return metadata