mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +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({
|
let {dragAndDropHooks} = useDragAndDrop({
|
||||||
// @ts-ignore
|
|
||||||
getItems: (keys) =>
|
getItems: (keys) =>
|
||||||
// @ts-ignore
|
[...keys].map((key) => ({'text/plain': sortedPlugins.getItem(key)!.name})),
|
||||||
[...keys].map((key) => ({'text/plain': sortedPlugins.getItem(key).name})),
|
|
||||||
onReorder(e) {
|
onReorder(e) {
|
||||||
if (e.keys.has(e.target.key)) {
|
if (e.keys.has(e.target.key)) {
|
||||||
return; // Avoid placing a plugin before or after itself
|
return; // Avoid placing a plugin before or after itself
|
||||||
@@ -105,8 +103,9 @@ export default function PluginPrioritiesModal({plugins, isOpen, onOpenChange}: P
|
|||||||
key={plugin.id}
|
key={plugin.id}
|
||||||
className="flex flex-row p-2 rounded-lg justify-between items-center bg-foreground/5">
|
className="flex flex-row p-2 rounded-lg justify-between items-center bg-foreground/5">
|
||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<Chip size="sm"
|
<Chip size="sm" color="primary">
|
||||||
color="primary">{sortedPlugins.items.length - plugin.priority + 1}</Chip>
|
{sortedPlugins.items.length - plugin.priority + 1}
|
||||||
|
</Chip>
|
||||||
<p className="font-normal text-small">{plugin.name}</p>
|
<p className="font-normal text-small">{plugin.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<CaretUpDown/>
|
<CaretUpDown/>
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
val ktor_version = "3.1.2"
|
val ktor_version = "3.1.3"
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven(url = "https://jitpack.io")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
ksp("care.better.pf4j:pf4j-kotlin-symbol-processing:${rootProject.extra["pf4jKspVersion"]}")
|
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:igdb")
|
||||||
include(":plugins:steam")
|
include(":plugins:steam")
|
||||||
|
include(":plugins:steamgriddb")
|
||||||
Reference in New Issue
Block a user