mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Add plugin for SteamGridDB game covers
This commit is contained in:
@@ -37,10 +37,8 @@ export default function PluginPrioritiesModal({plugins, isOpen, onOpenChange}: P
|
||||
}
|
||||
|
||||
let {dragAndDropHooks} = useDragAndDrop({
|
||||
// @ts-ignore
|
||||
getItems: (keys) =>
|
||||
// @ts-ignore
|
||||
[...keys].map((key) => ({'text/plain': sortedPlugins.getItem(key).name})),
|
||||
[...keys].map((key) => ({'text/plain': sortedPlugins.getItem(key)!.name})),
|
||||
onReorder(e) {
|
||||
if (e.keys.has(e.target.key)) {
|
||||
return; // Avoid placing a plugin before or after itself
|
||||
@@ -105,8 +103,9 @@ export default function PluginPrioritiesModal({plugins, isOpen, onOpenChange}: P
|
||||
key={plugin.id}
|
||||
className="flex flex-row p-2 rounded-lg justify-between items-center bg-foreground/5">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Chip size="sm"
|
||||
color="primary">{sortedPlugins.items.length - plugin.priority + 1}</Chip>
|
||||
<Chip size="sm" color="primary">
|
||||
{sortedPlugins.items.length - plugin.priority + 1}
|
||||
</Chip>
|
||||
<p className="font-normal text-small">{plugin.name}</p>
|
||||
</div>
|
||||
<CaretUpDown/>
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
val ktor_version = "3.1.2"
|
||||
val ktor_version = "3.1.3"
|
||||
|
||||
plugins {
|
||||
id("com.google.devtools.ksp")
|
||||
kotlin("plugin.serialization")
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven(url = "https://jitpack.io")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
ksp("care.better.pf4j:pf4j-kotlin-symbol-processing:${rootProject.extra["pf4jKspVersion"]}")
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
val ktor_version = "3.1.3"
|
||||
|
||||
plugins {
|
||||
id("com.google.devtools.ksp")
|
||||
kotlin("plugin.serialization")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
ksp("care.better.pf4j:pf4j-kotlin-symbol-processing:${rootProject.extra["pf4jKspVersion"]}")
|
||||
|
||||
implementation("io.ktor:ktor-client-core:${ktor_version}")
|
||||
implementation("io.ktor:ktor-client-cio:${ktor_version}")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:${ktor_version}")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:${ktor_version}")
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package de.grimsi.gameyfin.plugins.steamgriddb
|
||||
|
||||
import de.grimsi.gameyfin.pluginapi.core.GameyfinPlugin
|
||||
import de.grimsi.gameyfin.pluginapi.core.PluginConfigElement
|
||||
import de.grimsi.gameyfin.pluginapi.core.PluginConfigError
|
||||
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
|
||||
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
|
||||
import de.grimsi.gameyfin.plugins.steamgriddb.api.SteamGridDbApiClient
|
||||
import de.grimsi.gameyfin.plugins.steamgriddb.dto.SteamGridDbGame
|
||||
import de.grimsi.gameyfin.plugins.steamgriddb.dto.SteamGridDbGrid
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.pf4j.Extension
|
||||
import org.pf4j.PluginWrapper
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.URI
|
||||
|
||||
class SteamGridDbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
|
||||
|
||||
companion object {
|
||||
private var client: SteamGridDbApiClient? = null
|
||||
}
|
||||
|
||||
val log: Logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
override val configMetadata: List<PluginConfigElement> = listOf(
|
||||
PluginConfigElement("apiKey", "SteamGridDB API key", "Your SteamGridDB API key", true)
|
||||
)
|
||||
|
||||
override fun validateConfig(config: Map<String, String?>): Boolean {
|
||||
try {
|
||||
runBlocking { authenticate() }
|
||||
return true
|
||||
} catch (e: PluginConfigError) {
|
||||
log.error(e.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
try {
|
||||
runBlocking { authenticate() }
|
||||
} catch (e: PluginConfigError) {
|
||||
log.error(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun authenticate() {
|
||||
log.debug("Authenticating on SteamGridDB API...")
|
||||
|
||||
val apiKey: String = config["apiKey"] ?: throw PluginConfigError("SteamGridDB API key not set")
|
||||
val client = SteamGridDbApiClient(apiKey)
|
||||
|
||||
if (!client.isApiKeyValid()) {
|
||||
throw PluginConfigError("Failed to authenticate on SteamGridDB API with provided credentials")
|
||||
}
|
||||
|
||||
SteamGridDbPlugin.client = client
|
||||
log.debug("Authentication successful")
|
||||
}
|
||||
|
||||
@Extension
|
||||
class SteamGridDBGameCoverProvider : GameMetadataProvider {
|
||||
|
||||
override fun fetchMetadata(gameId: String, maxResults: Int): List<GameMetadata> {
|
||||
return runBlocking {
|
||||
var searchResults = searchSteamGridDb(gameId)
|
||||
|
||||
if (searchResults.isEmpty()) return@runBlocking emptyList()
|
||||
if (searchResults.size > maxResults) searchResults = searchResults.slice(0 until maxResults)
|
||||
|
||||
return@runBlocking searchResults
|
||||
.map { game ->
|
||||
GameMetadata(
|
||||
originalId = game.id.toString(),
|
||||
title = game.name,
|
||||
coverUrl = getGridForGame(game.id)?.let { grid -> URI(grid.url) }
|
||||
)
|
||||
}
|
||||
.filter { it.coverUrl != null }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun searchSteamGridDb(term: String): List<SteamGridDbGame> {
|
||||
val client = client ?: throw PluginConfigError("SteamGridDB API client not initialized")
|
||||
|
||||
val searchResult = client.search(term)
|
||||
|
||||
return if (searchResult.success && searchResult.data !== null) {
|
||||
searchResult.data
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGridForGame(gameId: Int): SteamGridDbGrid? {
|
||||
val client = client ?: throw PluginConfigError("SteamGridDB API client not initialized")
|
||||
|
||||
val gameDetails = client.grids(gameId)
|
||||
|
||||
return gameDetails.data?.firstOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package de.grimsi.gameyfin.plugins.steamgriddb.api
|
||||
|
||||
import de.grimsi.gameyfin.plugins.steamgriddb.dto.SteamGridDbGridResult
|
||||
import de.grimsi.gameyfin.plugins.steamgriddb.dto.SteamGridDbSearchResult
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
|
||||
class SteamGridDbApiClient(private val apiKey: String) {
|
||||
companion object {
|
||||
private val json = Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
private const val BASE_URL = "https://www.steamgriddb.com/api/v2"
|
||||
}
|
||||
|
||||
private val client = HttpClient(CIO) {
|
||||
install(ContentNegotiation) {
|
||||
json(json)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun isApiKeyValid(): Boolean {
|
||||
return try {
|
||||
val response = get("grids/game/1")
|
||||
response.status == HttpStatusCode.OK
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun search(term: String, block: HttpRequestBuilder.() -> Unit = {}): SteamGridDbSearchResult {
|
||||
return get("search/autocomplete/$term", block).body()
|
||||
}
|
||||
|
||||
suspend fun grids(gameId: Int, block: HttpRequestBuilder.() -> Unit = {}): SteamGridDbGridResult {
|
||||
return get("grids/game/$gameId", block).body()
|
||||
}
|
||||
|
||||
private suspend fun get(endpoint: String, block: HttpRequestBuilder.() -> Unit = {}): HttpResponse {
|
||||
return client.get("$BASE_URL/$endpoint".encodeURLPath(encodeEncoded = false)) {
|
||||
bearerAuth(apiKey)
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package de.grimsi.gameyfin.plugins.steamgriddb.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SteamGridDbSearchResult(
|
||||
val success: Boolean,
|
||||
val data: List<SteamGridDbGame>?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SteamGridDbGame(
|
||||
val id: Int,
|
||||
val name: String
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package de.grimsi.gameyfin.plugins.steamgriddb.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SteamGridDbGridResult(
|
||||
val success: Boolean,
|
||||
val data: List<SteamGridDbGrid>?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SteamGridDbGrid(
|
||||
val id: Int,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val url: String
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
Manifest-Version: 1.0
|
||||
Plugin-Class: de.grimsi.gameyfin.plugins.steamgriddb.SteamGridDbPlugin
|
||||
Plugin-Id: steamgriddb
|
||||
Plugin-Description: Steam Grid DB covers
|
||||
Plugin-Version: 1.0.0-alpha1
|
||||
Plugin-Provider: grimsi
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1" viewBox="0 0 50 24"><path fill="#305b79" d="M0 0h41v18H0V0z"/><path fill="#4787b4" d="M3 2h41v18H3V2z"/><path fill="#5fb4f0" d="M6 4h41v18H6V4z"/><path fill="#3a6e92" d="M9 6h41v18H9V6z"/></svg>
|
||||
|
After Width: | Height: | Size: 246 B |
@@ -25,3 +25,4 @@ include(":plugins")
|
||||
|
||||
include(":plugins:igdb")
|
||||
include(":plugins:steam")
|
||||
include(":plugins:steamgriddb")
|
||||
Reference in New Issue
Block a user