mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 00:30:02 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package de.grimsi.gameyfin;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class GameyfinApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GameyfinApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.grimsi.gameyfin.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class GameDto {
|
||||
private String name;
|
||||
private String publisher;
|
||||
private Long igdbGameId;
|
||||
private Instant releaseDate;
|
||||
|
||||
private List<File> files;
|
||||
private Long fileSize;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package de.grimsi.gameyfin.entities;
|
||||
|
||||
public class Game {
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package de.grimsi.gameyfin.igdb;
|
||||
|
||||
import de.grimsi.gameyfin.dto.GameDto;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class IgdbWrapper {
|
||||
|
||||
@Value("${gameyfin.igdb.api.client-id}")
|
||||
private String clientId;
|
||||
|
||||
@Value("${gameyfin.igdb.api.client-secret}")
|
||||
private String clientSecret;
|
||||
|
||||
@Value("${gameyfin.igdb.config.preferred-platform}")
|
||||
private int preferredPlatform;
|
||||
|
||||
private final WebClient twitchApiClient = WebClient.create();
|
||||
|
||||
private WebClient igdbApiClient;
|
||||
|
||||
private IgdbAccessToken accessToken;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
authenticate();
|
||||
}
|
||||
|
||||
public void authenticate() {
|
||||
log.info("Authenticating on Twitch API...");
|
||||
|
||||
URI url = UriComponentsBuilder
|
||||
.fromHttpUrl("https://id.twitch.tv/oauth2/token?client_id={client_id}&client_secret={client_secret}&grant_type=client_credentials")
|
||||
.buildAndExpand(clientId, clientSecret)
|
||||
.toUri();
|
||||
|
||||
this.accessToken = twitchApiClient
|
||||
.post()
|
||||
.uri(url)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(IgdbAccessToken.class)
|
||||
.block();
|
||||
|
||||
log.info("Successfully authenticated.");
|
||||
}
|
||||
|
||||
private void initIgdbClient() {
|
||||
if(accessToken == null) {
|
||||
authenticate();
|
||||
}
|
||||
|
||||
igdbApiClient = WebClient.builder()
|
||||
.baseUrl("https://api.igdb.com/v4/")
|
||||
.defaultHeader("Client-ID", clientId)
|
||||
.defaultHeader("Authorization", "Bearer %s".formatted(accessToken.getAccessToken()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public GameDto findGameByTitle(String title) {
|
||||
if (igdbApiClient == null) {
|
||||
initIgdbClient();
|
||||
}
|
||||
|
||||
IgdbSearchResultDto searchResult = searchForGameByTitle(title).orElseThrow(() -> new RuntimeException("Could not find game with title : \"%s\"".formatted(title)));
|
||||
|
||||
return GameDto.builder()
|
||||
.name(searchResult.getName())
|
||||
.releaseDate(Instant.ofEpochSecond(searchResult.getPublishedAt()))
|
||||
.igdbGameId(searchResult.getGame())
|
||||
.build();
|
||||
}
|
||||
|
||||
public Optional<IgdbSearchResultDto> searchForGameByTitle(String searchTerm) {
|
||||
List<IgdbSearchResultDto> searchResults = new ArrayList<>();
|
||||
|
||||
igdbApiClient.post()
|
||||
.uri("search")
|
||||
.bodyValue("fields *; search \"%s\"; limit 50;".formatted(searchTerm))
|
||||
.retrieve()
|
||||
.bodyToFlux(IgdbSearchResultDto.class)
|
||||
.doOnNext(searchResults::add)
|
||||
.blockLast();
|
||||
|
||||
if(searchResults.isEmpty()) return Optional.empty();
|
||||
|
||||
// First check if there are any matches with the exact search term
|
||||
// If no exact match has been found, check if there are matches where the name ends with the search term
|
||||
// This will filter out most DLCs and similiar stuff, but will detect a game even when your search term is not exactly the title
|
||||
//
|
||||
// Example: Searching for "Rainbow Six Siege" will result in returning "Tom Clancy's Rainbow Six Siege" (the game we want)
|
||||
// If we just used the first result from IGDB we would get something like "Tom Clancy's Rainbow Six Siege Demon Veil" as a result
|
||||
|
||||
Optional<IgdbSearchResultDto> srExactTitleMatch = searchResults.stream().filter(s -> s.getName().equals(searchTerm)).findFirst();
|
||||
if(srExactTitleMatch.isPresent()) return srExactTitleMatch;
|
||||
|
||||
Optional<IgdbSearchResultDto> srTitleEndsWithMatch = searchResults.stream().filter(s -> s.getName().endsWith(searchTerm)).findFirst();
|
||||
if(srTitleEndsWithMatch.isPresent()) return srTitleEndsWithMatch;
|
||||
|
||||
return Optional.of(searchResults.get(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.grimsi.gameyfin.igdb;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class IgdbAccessToken {
|
||||
private String accessToken;
|
||||
private Long expiresIn;
|
||||
private String tokenType;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
package de.grimsi.gameyfin.igdb.dto;public class IgdbGame {
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.grimsi.gameyfin.rest;
|
||||
|
||||
import de.grimsi.gameyfin.dto.GameDto;
|
||||
import de.grimsi.gameyfin.igdb.IgdbWrapper;
|
||||
import de.grimsi.gameyfin.igdb.dto.IgdbGame;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@RestController
|
||||
public class GameyfinController {
|
||||
|
||||
@Autowired
|
||||
IgdbWrapper igdbWrapper;
|
||||
|
||||
@GetMapping(value = "/findGameByTitle/{title}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public GameDto findGameByTitle(@PathVariable("title") String title) {
|
||||
IgdbGame game;
|
||||
|
||||
try {
|
||||
game = igdbWrapper.findGameByTitle(title);
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
|
||||
}
|
||||
|
||||
return GameDto.builder().name(game.getName()).releaseDate(game.getFirstReleaseDate()).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
package de.grimsi.gameyfin.service;public class FilesystemService {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
gameyfin:
|
||||
root: F:\Spiele
|
||||
igdb:
|
||||
api:
|
||||
client-id: 23l3l5qshx4dwjuao6yb8jyf1qrd08
|
||||
client-secret: hf4iivmkzgne552j17p2d64xm03die
|
||||
|
||||
logging:
|
||||
level:
|
||||
de.grimsi: debug
|
||||
@@ -0,0 +1,11 @@
|
||||
server.port: 8080
|
||||
spring.jackson.default-property-inclusion: non_null
|
||||
|
||||
gameyfin:
|
||||
root: ""
|
||||
igdb:
|
||||
config:
|
||||
preferred-platform: 6
|
||||
api:
|
||||
client-id: ""
|
||||
client-secret: ""
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.grimsi.gameyfin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class GameyfinApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user