mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-15 16:20:03 +00:00
Release v2.2.0 (#741)
* Migrate to TailwindCSS v4 (#740) * Remove "material-tailwind" dependencies due to incompatibility of Stepper component with Tailwind v4 * Clean up Tailwind configs before upgrade * Run HeroUI upgrade * Run TailwindCSS upgrade * Replace PostCSS with Vite * Migrate custom styles to v4 * Remove tailwind.config.ts * Add heroui.ts Add tailwind vite plugin * Fix small UI color inconsistency * Fix theming system Rename purple theme to pink * Re-implement stepper in HeroUI * Fix RoleChip colors * Migrate icon names (#743) * Add migration script for phosphor-icons * Migrate icon usages * Update version to 2.2.0-preview * Revert accidental rename of menu title * Bump stefanzweifel/git-auto-commit-action from 6 to 7 (#750) Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 6 to 7. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v6...v7) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Improve library scanning (#749) * Update script to generate example libraries using SteamSpy API * Refactor library scanning process * Display Flyway startup log by default * Fix race condition in CompanyService * Fix race condition in ImageService Remove obsolete table * Fix SMTP config requiring an email as username (#755) * Disable length limit for config values (#757) * Deprecate DockerHub image (#759) * Remove deprecation warning from web UI * Reworked the CICD pipelines * Optimize container image (#761) * Fix Gradle warning * Rework Docker image to improve layer caching * Bump stefanzweifel/git-auto-commit-action from 6 to 7 (#765) Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 6 to 7. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v6...v7) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Multi platform support (#764) * Remove migrate-phosphor-icons.js since migration has been successful * Refactor GameMetadata into separate files * Add Platform enum * Implement platform support in Plugin API * Implement platform support in Steam Plugin * Implement platform support in IGDB Plugin * Add database migration for platform support * Implement platform support in GameService * Implement platform support on most endpoints and features, some are still missing Implemented platform support in all bundled plugins (although not finished polishing yet) * Implement platforms in UI * Make GameRequest platform aware * Return headerImages from IGDB * Implement proper PlatformMapper for IGDB plugin * Fix various smaller issues and inconsistencies * Replace placeholder in LibraryOverviewCard (#767) * Bump actions/download-artifact from 5 to 6 (#769) * Bump actions/upload-artifact from 4 to 5 (#770) * Multi platform support (#773) * Fix bug in Plugin API related to state loading/saving * Hide Flyway query logs by default * Extend migration script for multi platform tables * Plugins now store their data and state in ./plugindata * Add "plugindata" directory to entrypoint scripts * Improve download handling (#756) * Process download in background thread to avoid session timeout affecting it * Increase default session timeout to 24h * Use virtual thread pool for download task in background * Make KSP extensions.idx generation more robust * Implement download bandwidth limiter Implement SliderInput Refactor NumberInput * Implement download bandwidth throttling Implement real-time download monitoring * Improve UI for DownloadManagement Track more stats in SessionStats * Update Hilla Use React 19 * Implement real-time graph to track bandwidth usage Implement downloaded data sum over last day Small bug fixes Small refactorings * Update docker-compose.example.yml * Improve DownloadSessionCard (#784) * Fix unit on y-axis of download graph * Show game size and library in tooltip Make game chips interactive in DownloadSessionCard (leads to game page when clicked) Optimize graph settings * Migrate torrent plugin to libtorrent (#775) * Disable TorrentDownloadPlugin in Alpine based Docker image * Improve test coverage (#785) * Fix potential divide by zero bug * Add mockk dependency * Add tests for org.gameyfin.app.core.download * Add tests for Filesytem package Fix DownloadServiceTest * Fix FilesystemServiceTest * Add tests for "job" package * Upgrade Gradle wrapper Enable Gradle config cache * Added more tests * Added tests for the "security" package * Add tests for "game" package * Fix AsyncFileTailer not shutting down properly on Windows * Fix GameServiceTest * Added tests for "libraries" package * Added tests for "media" package * Fix warning in ImageService * Add tests fpr "messages" package Make sure transport is closed even in case an exception is thrown * Add tests for "platforms" package * Add tests for "requests" package * Moved "token" package to "core" package (from "shared") * Add tests for "token" package * Fix issue in RoleEnum.safeValueOf() throwing Exception * Fix potential issue in UserEndpoint.getUserInfo() when auth is null * Added tests for "user" package * Migrate package for "token" in FE * Publish test report in CI * Fix workflow permissions * Remove test because of timing issue in CI * Replaced "unmatched paths" with "ignored paths" (#791) * Use new "AutoComplete" component (#793) * Use ArrayInputAutocomplete in EditGameMetadataModal * Add test for getEnumPropertyValues --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -17,6 +17,8 @@ import org.gameyfin.pluginapi.core.config.PluginConfigValidationResult
|
||||
import org.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
|
||||
import org.gameyfin.pluginapi.gamemetadata.GameMetadata
|
||||
import org.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
|
||||
import org.gameyfin.pluginapi.gamemetadata.Platform
|
||||
import org.gameyfin.plugins.metadata.igdb.mapper.*
|
||||
import org.pf4j.Extension
|
||||
import org.pf4j.PluginWrapper
|
||||
import proto.Game
|
||||
@@ -88,6 +90,7 @@ class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
|
||||
log.debug("Authentication successful")
|
||||
}
|
||||
|
||||
@Suppress("Unused")
|
||||
@Extension(ordinal = 2)
|
||||
class IgdbMetadataProvider : GameMetadataProvider {
|
||||
|
||||
@@ -121,6 +124,7 @@ class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
|
||||
"game_modes.slug",
|
||||
"game_modes.name",
|
||||
"cover.image_id",
|
||||
"artworks.image_id",
|
||||
"screenshots.image_id",
|
||||
"videos.name",
|
||||
"videos.video_id",
|
||||
@@ -143,15 +147,26 @@ class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
|
||||
).joinToString(",")
|
||||
}
|
||||
|
||||
override fun fetchByTitle(gameTitle: String, maxResults: Int): List<GameMetadata> {
|
||||
// Note: Limit is intentionally set high because IGDBs ranking algorithm is not very good
|
||||
override val supportedPlatforms: Set<Platform>
|
||||
get() = Platform.entries.toSet()
|
||||
|
||||
override fun fetchByTitle(
|
||||
gameTitle: String,
|
||||
platformFilter: Set<Platform>,
|
||||
maxResults: Int
|
||||
): List<GameMetadata> {
|
||||
val searchByNameQuery = APICalypse()
|
||||
.fields(QUERY_FIELDS)
|
||||
// TODO: Change for multi-platform support
|
||||
.where("platforms.slug = \"win\"")
|
||||
// Note: Limit is intentionally set high because IGDBs ranking algorithm is not very good
|
||||
.limit(100)
|
||||
.search(gameTitle)
|
||||
|
||||
if (platformFilter.isNotEmpty()) {
|
||||
val platformFilterQuery = PlatformMapper.toIgdb(platformFilter)
|
||||
.joinToString(separator = "\", \"", prefix = "platforms.slug = (\"", postfix = "\")")
|
||||
searchByNameQuery.where(platformFilterQuery)
|
||||
}
|
||||
|
||||
// Use IGDBs search function to get a list of games that match the search query
|
||||
var games = queryIgdbGames(searchByNameQuery)
|
||||
|
||||
@@ -170,7 +185,7 @@ class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
|
||||
.sortedByDescending { bestMatchesMap[it.name] }
|
||||
.take(maxResults)
|
||||
|
||||
return games.map { toGameMetadata(it) }
|
||||
return games.map { toGameMetadata(it, platformFilter) }
|
||||
}
|
||||
|
||||
override fun fetchById(id: String): GameMetadata? {
|
||||
@@ -181,7 +196,7 @@ class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
|
||||
.where("slug = \"$id\"")
|
||||
|
||||
val game = queryIgdbGames(findBySlugQuery).firstOrNull()
|
||||
return game?.let { toGameMetadata(it) }
|
||||
return game?.let { toGameMetadata(it, null) }
|
||||
}
|
||||
|
||||
private fun queryIgdbGames(query: APICalypse): List<Game> {
|
||||
@@ -193,29 +208,35 @@ class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
|
||||
return decorated.get()
|
||||
}
|
||||
|
||||
private fun toGameMetadata(game: Game): GameMetadata {
|
||||
private fun toGameMetadata(game: Game, platformFilter: Set<Platform>?): GameMetadata {
|
||||
val supportedPlatforms = game.platformsList.map { PlatformMapper.toGameyfin(it.slug) }
|
||||
val filteredPlatforms = platformFilter?.let { filter -> supportedPlatforms.filter { it in filter } }
|
||||
?: supportedPlatforms
|
||||
|
||||
return GameMetadata(
|
||||
originalId = game.slug,
|
||||
title = game.name,
|
||||
platforms = filteredPlatforms.toSet(),
|
||||
description = game.summary,
|
||||
coverUrls = Mapper.cover(game.cover)?.let { listOf(it) },
|
||||
coverUrls = MediaMapper.cover(game.cover)?.let { listOf(it) }?.toSet(),
|
||||
headerUrls = game.artworksList.map { MediaMapper.header(it) }.toSet(),
|
||||
release = if (game.firstReleaseDate.seconds > 0) Instant.ofEpochSecond(game.firstReleaseDate.seconds) else null,
|
||||
userRating = game.rating.toInt(),
|
||||
criticRating = game.aggregatedRating.toInt(),
|
||||
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(),
|
||||
genres = game.genresList.map { GenreMapper.genre(it) }.toSet(),
|
||||
themes = game.themesList.map { ThemeMapper.theme(it) }.toSet(),
|
||||
keywords = game.keywordsList.map { it.name }.toSet(),
|
||||
screenshotUrls = game.screenshotsList.map { Mapper.screenshot(it) }.toSet(),
|
||||
screenshotUrls = game.screenshotsList.map { MediaMapper.screenshot(it) }.toSet(),
|
||||
videoUrls = game.videosList
|
||||
// Lots of gameplay videos hosted on YouTube are blocked from viewing on external sites due to age ratings
|
||||
// Trailers usually are not affected so we filter for them
|
||||
// see https://support.google.com/youtube/answer/2802167
|
||||
.filter { it.name.equals("trailer", ignoreCase = true) }
|
||||
.map { Mapper.video(it) }.toSet(),
|
||||
features = Mapper.gameFeatures(game),
|
||||
perspectives = game.playerPerspectivesList.map { Mapper.playerPerspective(it) }.toSet()
|
||||
.map { MediaMapper.video(it) }.toSet(),
|
||||
features = GameFeatureMapper.gameFeatures(game),
|
||||
perspectives = game.playerPerspectivesList.map { PlayerPerspectiveMapper.playerPerspective(it) }.toSet()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
package org.gameyfin.plugins.metadata.igdb
|
||||
|
||||
import com.api.igdb.utils.ImageSize
|
||||
import com.api.igdb.utils.ImageType
|
||||
import com.api.igdb.utils.imageBuilder
|
||||
import org.gameyfin.pluginapi.gamemetadata.GameFeature
|
||||
import org.gameyfin.pluginapi.gamemetadata.Genre
|
||||
import org.gameyfin.pluginapi.gamemetadata.PlayerPerspective
|
||||
import org.gameyfin.pluginapi.gamemetadata.Theme
|
||||
import org.slf4j.LoggerFactory
|
||||
import proto.Cover
|
||||
import proto.Game
|
||||
import proto.GameVideo
|
||||
import proto.Screenshot
|
||||
import java.net.URI
|
||||
|
||||
class Mapper {
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(Mapper::class.java)
|
||||
|
||||
fun genre(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun theme(theme: proto.Theme): Theme {
|
||||
return when (theme.slug) {
|
||||
"action" -> Theme.ACTION
|
||||
"fantasy" -> Theme.FANTASY
|
||||
"horror" -> Theme.HORROR
|
||||
"sci-fi" -> Theme.SCIENCE_FICTION
|
||||
"science-fiction" -> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
"bird-view-slash-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: Screenshot): URI {
|
||||
return URI(imageBuilder(screenshot.imageId, ImageSize.FHD, ImageType.PNG))
|
||||
}
|
||||
|
||||
fun cover(cover: Cover): URI? {
|
||||
if (cover.imageId.isEmpty()) return null
|
||||
return URI(imageBuilder(cover.imageId, ImageSize.COVER_BIG, ImageType.PNG))
|
||||
}
|
||||
|
||||
fun video(video: GameVideo): URI {
|
||||
return URI("https://www.youtube.com/watch?v=${video.videoId}")
|
||||
}
|
||||
|
||||
fun gameFeatures(game: Game): Set<GameFeature> {
|
||||
val gameFeatures = mutableSetOf<GameFeature>()
|
||||
|
||||
// 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)
|
||||
"massively-multiplayer-online-mmo" -> gameFeatures.add(GameFeature.MULTIPLAYER)
|
||||
"battle-royale" -> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.gameyfin.plugins.metadata.igdb.mapper
|
||||
|
||||
import org.gameyfin.pluginapi.gamemetadata.GameFeature
|
||||
import org.slf4j.LoggerFactory
|
||||
import proto.Game
|
||||
|
||||
class GameFeatureMapper {
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
fun gameFeatures(game: Game): Set<GameFeature> {
|
||||
val gameFeatures = mutableSetOf<GameFeature>()
|
||||
|
||||
// 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)
|
||||
"massively-multiplayer-online-mmo" -> gameFeatures.add(GameFeature.MULTIPLAYER)
|
||||
"battle-royale" -> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.gameyfin.plugins.metadata.igdb.mapper
|
||||
|
||||
import org.gameyfin.pluginapi.gamemetadata.Genre
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class GenreMapper {
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
fun genre(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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.gameyfin.plugins.metadata.igdb.mapper
|
||||
|
||||
import com.api.igdb.utils.ImageSize
|
||||
import com.api.igdb.utils.ImageType
|
||||
import com.api.igdb.utils.imageBuilder
|
||||
import proto.Artwork
|
||||
import proto.Cover
|
||||
import proto.GameVideo
|
||||
import proto.Screenshot
|
||||
import java.net.URI
|
||||
|
||||
class MediaMapper {
|
||||
companion object {
|
||||
fun cover(cover: Cover): URI? {
|
||||
if (cover.imageId.isEmpty()) return null
|
||||
return URI(imageBuilder(cover.imageId, ImageSize.COVER_BIG, ImageType.PNG))
|
||||
}
|
||||
|
||||
fun header(header: Artwork): URI {
|
||||
return URI(imageBuilder(header.imageId, ImageSize.FHD, ImageType.PNG))
|
||||
}
|
||||
|
||||
fun screenshot(screenshot: Screenshot): URI {
|
||||
return URI(imageBuilder(screenshot.imageId, ImageSize.FHD, ImageType.PNG))
|
||||
}
|
||||
|
||||
fun video(video: GameVideo): URI {
|
||||
return URI("https://www.youtube.com/watch?v=${video.videoId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
package org.gameyfin.plugins.metadata.igdb.mapper
|
||||
|
||||
import org.gameyfin.pluginapi.gamemetadata.Platform
|
||||
|
||||
/**
|
||||
* Mapper for converting between IGDB platform slugs and Gameyfin Platform enums
|
||||
*/
|
||||
class PlatformMapper {
|
||||
companion object {
|
||||
/**
|
||||
* Map from IGDB platform slug to Gameyfin Platform enum
|
||||
*/
|
||||
private val igdbToGameyfinMap: Map<String, Platform> = mapOf(
|
||||
"linux" to Platform.LINUX,
|
||||
"n64" to Platform.NINTENDO_64,
|
||||
"wii" to Platform.WII,
|
||||
"win" to Platform.PC_MICROSOFT_WINDOWS,
|
||||
"ps" to Platform.PLAYSTATION,
|
||||
"ps2" to Platform.PLAYSTATION_2,
|
||||
"ps3" to Platform.PLAYSTATION_3,
|
||||
"xbox" to Platform.XBOX,
|
||||
"xbox360" to Platform.XBOX_360,
|
||||
"dos" to Platform.DOS,
|
||||
"mac" to Platform.MAC,
|
||||
"c64" to Platform.COMMODORE_C64_128_MAX,
|
||||
"amiga" to Platform.AMIGA,
|
||||
"nes" to Platform.NINTENDO_ENTERTAINMENT_SYSTEM,
|
||||
"snes" to Platform.SUPER_NINTENDO_ENTERTAINMENT_SYSTEM,
|
||||
"nds" to Platform.NINTENDO_DS,
|
||||
"ngc" to Platform.NINTENDO_GAMECUBE,
|
||||
"gbc" to Platform.GAME_BOY_COLOR,
|
||||
"dc" to Platform.DREAMCAST,
|
||||
"gba" to Platform.GAME_BOY_ADVANCE,
|
||||
"acpc" to Platform.AMSTRAD_CPC,
|
||||
"zxs" to Platform.ZX_SPECTRUM,
|
||||
"msx" to Platform.MSX,
|
||||
"genesis-slash-megadrive" to Platform.SEGA_MEGA_DRIVE_GENESIS,
|
||||
"sega32" to Platform.SEGA_32X,
|
||||
"saturn" to Platform.SEGA_SATURN,
|
||||
"gb" to Platform.GAME_BOY,
|
||||
"android" to Platform.ANDROID,
|
||||
"gamegear" to Platform.SEGA_GAME_GEAR,
|
||||
"3ds" to Platform.NINTENDO_3DS,
|
||||
"psp" to Platform.PLAYSTATION_PORTABLE,
|
||||
"ios" to Platform.IOS,
|
||||
"wiiu" to Platform.WII_U,
|
||||
"ngage" to Platform.N_GAGE,
|
||||
"zod" to Platform.TAPWAVE_ZODIAC,
|
||||
"psvita" to Platform.PLAYSTATION_VITA,
|
||||
"vc" to Platform.VIRTUAL_CONSOLE,
|
||||
"ps4--1" to Platform.PLAYSTATION_4,
|
||||
"xboxone" to Platform.XBOX_ONE,
|
||||
"3do" to Platform._3DO_INTERACTIVE_MULTIPLAYER,
|
||||
"fds" to Platform.FAMILY_COMPUTER_DISK_SYSTEM,
|
||||
"arcade" to Platform.ARCADE,
|
||||
"msx2" to Platform.MSX2,
|
||||
"mobile" to Platform.LEGACY_MOBILE_DEVICE,
|
||||
"wonderswan" to Platform.WONDERSWAN,
|
||||
"sfam" to Platform.SUPER_FAMICOM,
|
||||
"atari2600" to Platform.ATARI_2600,
|
||||
"atari7800" to Platform.ATARI_7800,
|
||||
"lynx" to Platform.ATARI_LYNX,
|
||||
"jaguar" to Platform.ATARI_JAGUAR,
|
||||
"atari-st" to Platform.ATARI_ST_STE,
|
||||
"sms" to Platform.SEGA_MASTER_SYSTEM_MARK_III,
|
||||
"atari8bit" to Platform.ATARI_8_BIT,
|
||||
"atari5200" to Platform.ATARI_5200,
|
||||
"intellivision" to Platform.INTELLIVISION,
|
||||
"colecovision" to Platform.COLECOVISION,
|
||||
"bbcmicro" to Platform.BBC_MICROCOMPUTER_SYSTEM,
|
||||
"vectrex" to Platform.VECTREX,
|
||||
"vic-20" to Platform.COMMODORE_VIC_20,
|
||||
"ouya" to Platform.OUYA,
|
||||
"blackberry" to Platform.BLACKBERRY_OS,
|
||||
"winphone" to Platform.WINDOWS_PHONE,
|
||||
"appleii" to Platform.APPLE_II,
|
||||
"x1" to Platform.SHARP_X1,
|
||||
"sega-cd" to Platform.SEGA_CD,
|
||||
"neogeomvs" to Platform.NEO_GEO_MVS,
|
||||
"neogeoaes" to Platform.NEO_GEO_AES,
|
||||
"browser" to Platform.WEB_BROWSER,
|
||||
"sg1000" to Platform.SG_1000,
|
||||
"donner30" to Platform.DONNER_MODEL_30,
|
||||
"turbografx16--1" to Platform.TURBOGRAFX_16_PC_ENGINE,
|
||||
"virtualboy" to Platform.VIRTUAL_BOY,
|
||||
"odyssey--1" to Platform.ODYSSEY,
|
||||
"microvision--1" to Platform.MICROVISION,
|
||||
"cpet" to Platform.COMMODORE_PET,
|
||||
"astrocade" to Platform.BALLY_ASTROCADE,
|
||||
"c16" to Platform.COMMODORE_16,
|
||||
"c-plus-4" to Platform.COMMODORE_PLUS_4,
|
||||
"pdp1" to Platform.PDP_1,
|
||||
"pdp10" to Platform.PDP_10,
|
||||
"pdp-8--1" to Platform.PDP_8,
|
||||
"gt40" to Platform.DEC_GT40,
|
||||
"famicom" to Platform.FAMILY_COMPUTER,
|
||||
"analogueelectronics" to Platform.ANALOGUE_ELECTRONICS,
|
||||
"nimrod" to Platform.FERRANTI_NIMROD_COMPUTER,
|
||||
"edsac--1" to Platform.EDSAC,
|
||||
"pdp-7--1" to Platform.PDP_7,
|
||||
"hp2100" to Platform.HP_2100,
|
||||
"hp3000" to Platform.HP_3000,
|
||||
"sdssigma7" to Platform.SDS_SIGMA_7,
|
||||
"call-a-computer" to Platform.CALL_A_COMPUTER_TIME_SHARED_MAINFRAME_COMPUTER_SYSTEM,
|
||||
"pdp11" to Platform.PDP_11,
|
||||
"cdccyber70" to Platform.CDC_CYBER_70,
|
||||
"plato--1" to Platform.PLATO,
|
||||
"imlac-pds1" to Platform.IMLAC_PDS_1,
|
||||
"microcomputer--1" to Platform.MICROCOMPUTER,
|
||||
"onlive" to Platform.ONLIVE_GAME_SYSTEM,
|
||||
"amiga-cd32" to Platform.AMIGA_CD32,
|
||||
"apple-iigs" to Platform.APPLE_IIGS,
|
||||
"acorn-archimedes" to Platform.ACORN_ARCHIMEDES,
|
||||
"philips-cdi" to Platform.PHILIPS_CD_I,
|
||||
"fm-towns" to Platform.FM_TOWNS,
|
||||
"neo-geo-pocket" to Platform.NEO_GEO_POCKET,
|
||||
"neo-geo-pocket-color" to Platform.NEO_GEO_POCKET_COLOR,
|
||||
"sharp-x68000" to Platform.SHARP_X68000,
|
||||
"nuon" to Platform.NUON,
|
||||
"wonderswan-color" to Platform.WONDERSWAN_COLOR,
|
||||
"swancrystal" to Platform.SWANCRYSTAL,
|
||||
"pc-8800-series" to Platform.PC_8800_SERIES,
|
||||
"trs-80" to Platform.TRS_80,
|
||||
"fairchild-channel-f" to Platform.FAIRCHILD_CHANNEL_F,
|
||||
"supergrafx" to Platform.PC_ENGINE_SUPERGRAFX,
|
||||
"ti-99" to Platform.TEXAS_INSTRUMENTS_TI_99,
|
||||
"switch" to Platform.NINTENDO_SWITCH,
|
||||
"super-nes-cd-rom-system" to Platform.SUPER_NES_CD_ROM_SYSTEM,
|
||||
"firetv" to Platform.AMAZON_FIRE_TV,
|
||||
"odyssey-2-slash-videopac-g7000" to Platform.ODYSSEY_2_VIDEOPAC_G7000,
|
||||
"acorn-electron" to Platform.ACORN_ELECTRON,
|
||||
"hyper-neo-geo-64" to Platform.HYPER_NEO_GEO_64,
|
||||
"neo-geo-cd" to Platform.NEO_GEO_CD,
|
||||
"new-3ds" to Platform.NEW_NINTENDO_3DS,
|
||||
"vc-4000" to Platform.VC_4000,
|
||||
"1292-advanced-programmable-video-system" to Platform._1292_ADVANCED_PROGRAMMABLE_VIDEO_SYSTEM,
|
||||
"ay-3-8500" to Platform.AY_3_8500,
|
||||
"ay-3-8610" to Platform.AY_3_8610,
|
||||
"pc-50x-family" to Platform.PC_50X_FAMILY,
|
||||
"ay-3-8760" to Platform.AY_3_8760,
|
||||
"ay-3-8710" to Platform.AY_3_8710,
|
||||
"ay-3-8603" to Platform.AY_3_8603,
|
||||
"ay-3-8605" to Platform.AY_3_8605,
|
||||
"ay-3-8606" to Platform.AY_3_8606,
|
||||
"ay-3-8607" to Platform.AY_3_8607,
|
||||
"pc-9800-series" to Platform.PC_9800_SERIES,
|
||||
"turbografx-16-slash-pc-engine-cd" to Platform.TURBOGRAFX_16_PC_ENGINE_CD,
|
||||
"trs-80-color-computer" to Platform.TRS_80_COLOR_COMPUTER,
|
||||
"fm-7" to Platform.FM_7,
|
||||
"dragon-32-slash-64" to Platform.DRAGON_32_64,
|
||||
"apcw" to Platform.AMSTRAD_PCW,
|
||||
"tatung-einstein" to Platform.TATUNG_EINSTEIN,
|
||||
"thomson-mo5" to Platform.THOMSON_MO5,
|
||||
"nec-pc-6000-series" to Platform.NEC_PC_6000_SERIES,
|
||||
"commodore-cdtv" to Platform.COMMODORE_CDTV,
|
||||
"nintendo-dsi" to Platform.NINTENDO_DSI,
|
||||
"windows-mixed-reality" to Platform.WINDOWS_MIXED_REALITY,
|
||||
"oculus-vr" to Platform.OCULUS_VR,
|
||||
"steam-vr" to Platform.STEAMVR,
|
||||
"daydream" to Platform.DAYDREAM,
|
||||
"psvr" to Platform.PLAYSTATION_VR,
|
||||
"pokemon-mini" to Platform.POKEMON_MINI,
|
||||
"ps5" to Platform.PLAYSTATION_5,
|
||||
"series-x-s" to Platform.XBOX_SERIES_X_S,
|
||||
"stadia" to Platform.GOOGLE_STADIA,
|
||||
"duplicate-stadia" to Platform.DUPLICATE_STADIA,
|
||||
"exidy-sorcerer" to Platform.EXIDY_SORCERER,
|
||||
"sol-20" to Platform.SOL_20,
|
||||
"dvd-player" to Platform.DVD_PLAYER,
|
||||
"blu-ray-player" to Platform.BLU_RAY_PLAYER,
|
||||
"zeebo" to Platform.ZEEBO,
|
||||
"pc-fx" to Platform.PC_FX,
|
||||
"satellaview" to Platform.SATELLAVIEW,
|
||||
"g-and-w" to Platform.GAME_AND_WATCH,
|
||||
"playdia" to Platform.PLAYDIA,
|
||||
"evercade" to Platform.EVERCADE,
|
||||
"sega-pico" to Platform.SEGA_PICO,
|
||||
"ooparts" to Platform.OOPARTS,
|
||||
"sinclair-zx81" to Platform.SINCLAIR_ZX81,
|
||||
"sharp-mz-2200" to Platform.SHARP_MZ_2200,
|
||||
"epoch-cassette-vision" to Platform.EPOCH_CASSETTE_VISION,
|
||||
"epoch-super-cassette-vision" to Platform.EPOCH_SUPER_CASSETTE_VISION,
|
||||
"plug-and-play" to Platform.PLUG_AND_PLAY,
|
||||
"gamate" to Platform.GAMATE,
|
||||
"game-dot-com" to Platform.GAME_COM,
|
||||
"casio-loopy" to Platform.CASIO_LOOPY,
|
||||
"playdate" to Platform.PLAYDATE,
|
||||
"intellivision-amico" to Platform.INTELLIVISION_AMICO,
|
||||
"oculus-quest" to Platform.OCULUS_QUEST,
|
||||
"oculus-rift" to Platform.OCULUS_RIFT,
|
||||
"meta-quest-2" to Platform.META_QUEST_2,
|
||||
"oculus-go" to Platform.OCULUS_GO,
|
||||
"gear-vr" to Platform.GEAR_VR,
|
||||
"airconsole" to Platform.AIRCONSOLE,
|
||||
"psvr2" to Platform.PLAYSTATION_VR2,
|
||||
"windows-mobile" to Platform.WINDOWS_MOBILE,
|
||||
"sinclair-ql" to Platform.SINCLAIR_QL,
|
||||
"hyperscan" to Platform.HYPERSCAN,
|
||||
"mega-duck-slash-cougar-boy" to Platform.MEGA_DUCK_COUGAR_BOY,
|
||||
"legacy-computer" to Platform.LEGACY_COMPUTER,
|
||||
"atari-jaguar-cd" to Platform.ATARI_JAGUAR_CD,
|
||||
"handheld" to Platform.HANDHELD_ELECTRONIC_LCD,
|
||||
"leapster" to Platform.LEAPSTER,
|
||||
"leapster-explorer-slash-leadpad-explorer" to Platform.LEAPSTER_EXPLORER_LEADPAD_EXPLORER,
|
||||
"leaptv" to Platform.LEAPTV,
|
||||
"watara-slash-quickshot-supervision" to Platform.WATARA_QUICKSHOT_SUPERVISION,
|
||||
"64dd" to Platform._64DD,
|
||||
"palm-os" to Platform.PALM_OS,
|
||||
"arduboy" to Platform.ARDUBOY,
|
||||
"vsmile" to Platform.V_SMILE,
|
||||
"visual-memory-unit-slash-visual-memory-system" to Platform.VISUAL_MEMORY_UNIT_VISUAL_MEMORY_SYSTEM,
|
||||
"pocketstation" to Platform.POCKETSTATION,
|
||||
"meta-quest-3" to Platform.META_QUEST_3,
|
||||
"visionos" to Platform.VISIONOS,
|
||||
"arcadia-2001" to Platform.ARCADIA_2001,
|
||||
"gizmondo" to Platform.GIZMONDO,
|
||||
"r-zone" to Platform.R_ZONE,
|
||||
"apple-pippin" to Platform.APPLE_PIPPIN,
|
||||
"panasonic-jungle" to Platform.PANASONIC_JUNGLE,
|
||||
"panasonic-m2" to Platform.PANASONIC_M2,
|
||||
"terebikko-slash-see-n-say-video-phone" to Platform.TEREBIKKO_SEE_N_SAY_VIDEO_PHONE,
|
||||
"super-acan" to Platform.SUPER_ACAN,
|
||||
"tomy-tutor-slash-pyuta-slash-grandstand-tutor" to Platform.TOMY_TUTOR_PYUTA_GRANDSTAND_TUTOR,
|
||||
"sega-cd-32x" to Platform.SEGA_CD_32X,
|
||||
"digiblast" to Platform.DIGIBLAST,
|
||||
"laseractive" to Platform.LASERACTIVE,
|
||||
"uzebox" to Platform.UZEBOX,
|
||||
"elektor-tv-games-computer" to Platform.ELEKTOR_TV_GAMES_COMPUTER,
|
||||
"gx4000" to Platform.AMSTRAD_GX4000,
|
||||
"advanced-pico-beena" to Platform.ADVANCED_PICO_BEENA,
|
||||
"switch-2" to Platform.NINTENDO_SWITCH_2,
|
||||
"polymega" to Platform.POLYMEGA,
|
||||
"e-reader-slash-card-e-reader" to Platform.E_READER_CARD_E_READER
|
||||
)
|
||||
|
||||
/**
|
||||
* Map from Gameyfin Platform enum to IGDB platform slug
|
||||
*/
|
||||
private val gameyfinToIgdbMap: Map<Platform, String> =
|
||||
igdbToGameyfinMap.entries.associateBy({ it.value }, { it.key })
|
||||
|
||||
/**
|
||||
* Convert an IGDB platform slug to a Gameyfin Platform enum
|
||||
* @param igdbPlatformSlug The IGDB platform slug
|
||||
* @return The corresponding Platform enum value, or null if not found
|
||||
*/
|
||||
fun toGameyfin(igdbPlatformSlug: String): Platform {
|
||||
val platform = igdbToGameyfinMap[igdbPlatformSlug]
|
||||
?: throw IllegalArgumentException("Could not map IGDB platform with slug '$igdbPlatformSlug' to Gameyfin Platform")
|
||||
return platform
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert multiple IGDB platform slugs to Gameyfin Platform enums
|
||||
* @param igdbPlatformSlugs Collection of IGDB platform slugs
|
||||
* @return Set of Platform enums (unmapped slugs are filtered out)
|
||||
*/
|
||||
fun toGameyfin(igdbPlatformSlugs: Collection<String>): Set<Platform> {
|
||||
return igdbPlatformSlugs.map { toGameyfin(it) }.toSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Gameyfin Platform enum to an IGDB platform slug
|
||||
* @param platform The Platform enum value
|
||||
* @return The corresponding IGDB platform slug, or null if not found
|
||||
*/
|
||||
fun toIgdb(platform: Platform): String {
|
||||
val slug = gameyfinToIgdbMap[platform]
|
||||
?: throw IllegalArgumentException("Could not map Gameyfin Platform '${platform.displayName}' to IGDB platform slug")
|
||||
return slug
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert multiple Gameyfin Platform enums to IGDB platform slugs
|
||||
* @param platforms Collection of Platform enums
|
||||
* @return Set of IGDB platform slugs (unmapped platforms are filtered out)
|
||||
*/
|
||||
fun toIgdb(platforms: Collection<Platform>): Set<String> {
|
||||
return platforms.map { toIgdb(it) }.toSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all IGDB platform slugs that have mappings
|
||||
* @return Set of all mapped IGDB platform slugs
|
||||
*/
|
||||
fun getAllMappedIgdbSlugs(): Set<String> {
|
||||
return igdbToGameyfinMap.keys
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package org.gameyfin.plugins.metadata.igdb.mapper
|
||||
|
||||
import org.gameyfin.pluginapi.gamemetadata.PlayerPerspective
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class PlayerPerspectiveMapper {
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
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
|
||||
"bird-view-slash-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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.gameyfin.plugins.metadata.igdb.mapper
|
||||
|
||||
import org.gameyfin.pluginapi.gamemetadata.Theme
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class ThemeMapper {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
fun theme(theme: proto.Theme): Theme {
|
||||
return when (theme.slug) {
|
||||
"action" -> Theme.ACTION
|
||||
"fantasy" -> Theme.FANTASY
|
||||
"horror" -> Theme.HORROR
|
||||
"sci-fi" -> Theme.SCIENCE_FICTION
|
||||
"science-fiction" -> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
Plugin-Version: 1.0.0
|
||||
Plugin-Version: 1.1.0
|
||||
Plugin-Class: org.gameyfin.plugins.metadata.igdb.IgdbPlugin
|
||||
Plugin-Id: org.gameyfin.plugins.metadata.igdb
|
||||
Plugin-Name: IGDB Metadata
|
||||
|
||||
Reference in New Issue
Block a user