From 149e1cc7e18e5c8dc37495713cd319ad07811f7d Mon Sep 17 00:00:00 2001 From: Simon Grimme Date: Thu, 14 Jul 2022 16:47:11 +0200 Subject: [PATCH] Use Protobuf endpoints (WIP) --- pom.xml | 42 + .../gameyfin/config/WebClientConfig.java | 36 + .../de/grimsi/gameyfin/igdb/IgdbWrapper.java | 57 +- ...essToken.java => TwitchOAuthTokenDto.java} | 2 +- .../gameyfin/rest/GameyfinDevController.java | 17 +- .../grimsi/gameyfin/util/ProtobufUtils.java | 11 + src/main/resources/application-dev.yml | 2 + src/main/resources/proto/igdbapi.proto | 907 ++++++++++++++++++ 8 files changed, 1042 insertions(+), 32 deletions(-) create mode 100644 src/main/java/de/grimsi/gameyfin/config/WebClientConfig.java rename src/main/java/de/grimsi/gameyfin/igdb/dto/{IgdbAccessToken.java => TwitchOAuthTokenDto.java} (91%) create mode 100644 src/main/java/de/grimsi/gameyfin/util/ProtobufUtils.java create mode 100644 src/main/resources/proto/igdbapi.proto diff --git a/pom.xml b/pom.xml index 64faf69..173f962 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 18 + 3.11.4 @@ -49,6 +50,19 @@ 2.11.0 + + + com.google.protobuf + protobuf-java + 3.21.2 + + + com.google.protobuf + protobuf-java-util + 3.21.1 + + + org.springframework.boot @@ -89,6 +103,34 @@ + + + + com.github.os72 + protoc-jar-maven-plugin + ${protoc.plugin.version} + + + generate-sources + + run + + + true + + ${project.basedir}/src/main/resources/proto + + + + java + ${project.build.directory}/generated-sources/protobuf + + + + + + + diff --git a/src/main/java/de/grimsi/gameyfin/config/WebClientConfig.java b/src/main/java/de/grimsi/gameyfin/config/WebClientConfig.java new file mode 100644 index 0000000..ae3e1aa --- /dev/null +++ b/src/main/java/de/grimsi/gameyfin/config/WebClientConfig.java @@ -0,0 +1,36 @@ +package de.grimsi.gameyfin.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; + +@Slf4j +@Configuration +public class WebClientConfig implements WebClientCustomizer { + + @Override + public void customize(WebClient.Builder webClientBuilder) { + webClientBuilder.filter(logResponse()); + webClientBuilder.filter(logRequest()); + webClientBuilder.clientConnector(new ReactorClientHttpConnector(HttpClient.create().proxyWithSystemProperties())); + } + + private ExchangeFilterFunction logResponse() { + return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { + log.debug("Response: {}", clientResponse.statusCode()); + return Mono.just(clientResponse); + }); + } + + private ExchangeFilterFunction logRequest() { + return (clientRequest, next) -> { + log.debug("Request: {} {}", clientRequest.method(), clientRequest.url()); + return next.exchange(clientRequest); + }; + } +} diff --git a/src/main/java/de/grimsi/gameyfin/igdb/IgdbWrapper.java b/src/main/java/de/grimsi/gameyfin/igdb/IgdbWrapper.java index 939d488..b105b98 100644 --- a/src/main/java/de/grimsi/gameyfin/igdb/IgdbWrapper.java +++ b/src/main/java/de/grimsi/gameyfin/igdb/IgdbWrapper.java @@ -1,15 +1,17 @@ package de.grimsi.gameyfin.igdb; -import de.grimsi.gameyfin.igdb.dto.IgdbAccessToken; -import de.grimsi.gameyfin.igdb.dto.IgdbGame; +import com.google.protobuf.InvalidProtocolBufferException; +import com.igdb.proto.Game; +import de.grimsi.gameyfin.igdb.dto.TwitchOAuthTokenDto; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; -import reactor.netty.http.client.HttpClient; import javax.annotation.PostConstruct; import java.net.URI; @@ -30,16 +32,18 @@ public class IgdbWrapper { @Value("${gameyfin.igdb.config.preferred-platform}") private int preferredPlatform; - private final WebClient twitchApiClient = WebClient.builder() - .clientConnector(new ReactorClientHttpConnector(HttpClient.create().proxyWithSystemProperties())) - .build(); + @Autowired + private WebClient.Builder webclientBuilder; + + private WebClient twitchApiClient; private WebClient igdbApiClient; - private IgdbAccessToken accessToken; + private TwitchOAuthTokenDto accessToken; @PostConstruct public void init() { + twitchApiClient = webclientBuilder.build(); authenticate(); initIgdbClient(); } @@ -57,25 +61,29 @@ public class IgdbWrapper { .uri(url) .accept(MediaType.APPLICATION_JSON) .retrieve() - .bodyToMono(IgdbAccessToken.class) + .bodyToMono(TwitchOAuthTokenDto.class) .block(); log.info("Successfully authenticated."); } - public IgdbGame findGameByTitle(String title) { + public Game findGameByTitle(String title) { return searchForGameByTitle(title).orElseThrow(() -> new RuntimeException("Could not find game with title: \"%s\"".formatted(title))); } - private Optional getGameById(Long id) { - return Optional.ofNullable( - igdbApiClient.post() - .uri("games") + public Optional getGameById(Long id) { + byte[] gameBytes = igdbApiClient.post() + .uri("games.pb") .bodyValue("fields *; where id = %d;".formatted(id)) .retrieve() - .bodyToMono(IgdbGame.class) - .block() - ); + .bodyToMono(byte[].class) + .block(); + + try { + return Optional.ofNullable(Game.parseFrom(gameBytes)); + } catch (InvalidProtocolBufferException e) { + return Optional.empty(); + } } private void initIgdbClient() { @@ -83,22 +91,21 @@ public class IgdbWrapper { authenticate(); } - igdbApiClient = WebClient.builder() - .clientConnector(new ReactorClientHttpConnector(HttpClient.create().proxyWithSystemProperties())) + igdbApiClient = webclientBuilder .baseUrl("https://api.igdb.com/v4/") .defaultHeader("Client-ID", clientId) .defaultHeader("Authorization", "Bearer %s".formatted(accessToken.getAccessToken())) .build(); } - private Optional searchForGameByTitle(String searchTerm) { - List games = new ArrayList<>(); + private Optional searchForGameByTitle(String searchTerm) { + List games = new ArrayList<>(); igdbApiClient.post() - .uri("games") + .uri("games.pb") .bodyValue("fields *; search \"%s\";".formatted(searchTerm)) .retrieve() - .bodyToFlux(IgdbGame.class) + .bodyToFlux(Game.class) .doOnNext(games::add) .blockLast(); @@ -112,10 +119,10 @@ public class IgdbWrapper { // 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 srExactTitleMatch = games.stream().filter(s -> s.getName().equals(searchTerm)).findFirst(); + Optional srExactTitleMatch = games.stream().filter(s -> s.getName().equals(searchTerm)).findFirst(); if (srExactTitleMatch.isPresent()) return srExactTitleMatch; - Optional srTitleEndsWithMatch = games.stream().filter(s -> s.getName().endsWith(searchTerm)).findFirst(); + Optional srTitleEndsWithMatch = games.stream().filter(s -> s.getName().endsWith(searchTerm)).findFirst(); if (srTitleEndsWithMatch.isPresent()) return srTitleEndsWithMatch; return Optional.of(games.get(0)); diff --git a/src/main/java/de/grimsi/gameyfin/igdb/dto/IgdbAccessToken.java b/src/main/java/de/grimsi/gameyfin/igdb/dto/TwitchOAuthTokenDto.java similarity index 91% rename from src/main/java/de/grimsi/gameyfin/igdb/dto/IgdbAccessToken.java rename to src/main/java/de/grimsi/gameyfin/igdb/dto/TwitchOAuthTokenDto.java index 70fce65..b971883 100644 --- a/src/main/java/de/grimsi/gameyfin/igdb/dto/IgdbAccessToken.java +++ b/src/main/java/de/grimsi/gameyfin/igdb/dto/TwitchOAuthTokenDto.java @@ -8,7 +8,7 @@ import lombok.NoArgsConstructor; @Data @NoArgsConstructor @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class IgdbAccessToken { +public class TwitchOAuthTokenDto { private String accessToken; private Long expiresIn; private String tokenType; diff --git a/src/main/java/de/grimsi/gameyfin/rest/GameyfinDevController.java b/src/main/java/de/grimsi/gameyfin/rest/GameyfinDevController.java index d4cc758..dc2ebcf 100644 --- a/src/main/java/de/grimsi/gameyfin/rest/GameyfinDevController.java +++ b/src/main/java/de/grimsi/gameyfin/rest/GameyfinDevController.java @@ -1,10 +1,10 @@ package de.grimsi.gameyfin.rest; +import com.igdb.proto.Game; import de.grimsi.gameyfin.dto.GameDto; import de.grimsi.gameyfin.igdb.IgdbWrapper; -import de.grimsi.gameyfin.igdb.dto.IgdbGame; import de.grimsi.gameyfin.service.FilesystemService; -import org.apache.commons.io.FilenameUtils; +import de.grimsi.gameyfin.util.ProtobufUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -15,7 +15,6 @@ import org.springframework.web.server.ResponseStatusException; import java.nio.file.Path; import java.util.List; -import java.util.stream.Collectors; @RestController public class GameyfinDevController { @@ -28,7 +27,7 @@ public class GameyfinDevController { @GetMapping(value = "/dev/findGameByTitle/{title}", produces = MediaType.APPLICATION_JSON_VALUE) public GameDto findGameByTitle(@PathVariable("title") String title) { - IgdbGame game; + Game game; try { game = igdbWrapper.findGameByTitle(title); @@ -36,7 +35,10 @@ public class GameyfinDevController { throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage()); } - return GameDto.builder().name(game.getName()).releaseDate(game.getFirstReleaseDate()).build(); + return GameDto.builder() + .name(game.getName()) + .releaseDate(ProtobufUtils.toInstant(game.getFirstReleaseDate())) + .build(); } @GetMapping(value = "/dev/gameFiles", produces = MediaType.APPLICATION_JSON_VALUE) @@ -48,7 +50,10 @@ public class GameyfinDevController { public List getAllGames() { return filesystemService.getGameFileNames().stream() .map(t -> igdbWrapper.findGameByTitle(t)) - .map(g -> GameDto.builder().name(g.getName()).releaseDate(g.getFirstReleaseDate()).build()) + .map(g -> GameDto.builder() + .name(g.getName()) + .releaseDate(ProtobufUtils.toInstant(g.getFirstReleaseDate())) + .build()) .toList(); } } diff --git a/src/main/java/de/grimsi/gameyfin/util/ProtobufUtils.java b/src/main/java/de/grimsi/gameyfin/util/ProtobufUtils.java new file mode 100644 index 0000000..faac3b9 --- /dev/null +++ b/src/main/java/de/grimsi/gameyfin/util/ProtobufUtils.java @@ -0,0 +1,11 @@ +package de.grimsi.gameyfin.util; + +import com.google.protobuf.Timestamp; + +import java.time.Instant; + +public class ProtobufUtils { + public static Instant toInstant(Timestamp t) { + return Instant.ofEpochSecond(t.getSeconds(), t.getNanos()); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index f0f8dcf..b735051 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,5 +1,7 @@ gameyfin: root: C:\Projects\privat\gameyfin-library + cache: C:\Projects\privat\gameyfin-library\.gameyfin + db: C:\Projects\privat\gameyfin-library\.gameyfin igdb: api: client-id: 23l3l5qshx4dwjuao6yb8jyf1qrd08 diff --git a/src/main/resources/proto/igdbapi.proto b/src/main/resources/proto/igdbapi.proto new file mode 100644 index 0000000..91d0b2a --- /dev/null +++ b/src/main/resources/proto/igdbapi.proto @@ -0,0 +1,907 @@ +syntax = "proto3"; + +package com.igdb.proto; + +import "google/protobuf/timestamp.proto"; + +//option java_outer_classname = "Igdb"; +option java_multiple_files = true; // Must be true because of private access in files. +option optimize_for = CODE_SIZE; + +message Count { + int64 count = 1; +} + +message MultiQueryResult { + string name = 1; + repeated bytes results = 2; + int64 count = 3; +} + +message MultiQueryResultArray { + repeated MultiQueryResult result = 1; +} + +message AgeRatingResult { + repeated AgeRating ageratings = 1; +} + +message AgeRating { + uint64 id = 1; + AgeRatingCategoryEnum category = 2; + repeated AgeRatingContentDescription content_descriptions = 3; + AgeRatingRatingEnum rating = 4; + string rating_cover_url = 5; + string synopsis = 6; + string checksum = 7; +} + + +enum AgeRatingCategoryEnum { + AGERATING_CATEGORY_NULL = 0; + ESRB = 1; + PEGI = 2; + CERO = 3; + USK = 4; + GRAC = 5; + CLASS_IND = 6; + ACB = 7; +} + + +enum AgeRatingRatingEnum { + AGERATING_RATING_NULL = 0; + THREE = 1; + SEVEN = 2; + TWELVE = 3; + SIXTEEN = 4; + EIGHTEEN = 5; + RP = 6; + EC = 7; + E = 8; + E10 = 9; + T = 10; + M = 11; + AO = 12; + CERO_A = 13; + CERO_B = 14; + CERO_C = 15; + CERO_D = 16; + CERO_Z = 17; + USK_0 = 18; + USK_6 = 19; + USK_12 = 20; + USK_18 = 21; + GRAC_ALL = 22; + GRAC_TWELVE = 23; + GRAC_FIFTEEN = 24; + GRAC_EIGHTEEN = 25; + GRAC_TESTING = 26; + CLASS_IND_L = 27; + CLASS_IND_TEN = 28; + CLASS_IND_TWELVE = 29; + CLASS_IND_FOURTEEN = 30; + CLASS_IND_SIXTEEN = 31; + CLASS_IND_EIGHTEEN = 32; + ACB_G = 33; + ACB_PG = 34; + ACB_M = 35; + ACB_MA15 = 36; + ACB_R18 = 37; + ACB_RC = 38; +} + +message AgeRatingContentDescriptionResult { + repeated AgeRatingContentDescription ageratingcontentdescriptions = 1; +} + +message AgeRatingContentDescription { + uint64 id = 1; + AgeRatingRatingEnum category = 2; + string description = 3; + string checksum = 4; +} + +message AlternativeNameResult { + repeated AlternativeName alternativenames = 1; +} + +message AlternativeName { + uint64 id = 1; + string comment = 2; + Game game = 3; + string name = 4; + string checksum = 5; +} + +message ArtworkResult { + repeated Artwork artworks = 1; +} + +message Artwork { + uint64 id = 1; + bool alpha_channel = 2; + bool animated = 3; + Game game = 4; + int32 height = 5; + string image_id = 6; + string url = 7; + int32 width = 8; + string checksum = 9; +} + +message CharacterResult { + repeated Character characters = 1; +} + +message Character { + uint64 id = 1; + repeated string akas = 2; + string country_name = 3; + google.protobuf.Timestamp created_at = 4; + string description = 5; + repeated Game games = 6; + GenderGenderEnum gender = 7; + CharacterMugShot mug_shot = 8; + string name = 9; + string slug = 10; + CharacterSpeciesEnum species = 11; + google.protobuf.Timestamp updated_at = 12; + string url = 13; + string checksum = 14; +} + + +enum GenderGenderEnum { + MALE = 0; + FEMALE = 1; + OTHER = 2; +} + + +enum CharacterSpeciesEnum { + CHARACTER_SPECIES_NULL = 0; + HUMAN = 1; + ALIEN = 2; + ANIMAL = 3; + ANDROID = 4; + UNKNOWN = 5; +} + +message CharacterMugShotResult { + repeated CharacterMugShot charactermugshots = 1; +} + +message CharacterMugShot { + uint64 id = 1; + bool alpha_channel = 2; + bool animated = 3; + int32 height = 4; + string image_id = 5; + string url = 6; + int32 width = 7; + string checksum = 8; +} + +message CollectionResult { + repeated Collection collections = 1; +} + +message Collection { + uint64 id = 1; + google.protobuf.Timestamp created_at = 2; + repeated Game games = 3; + string name = 4; + string slug = 5; + google.protobuf.Timestamp updated_at = 6; + string url = 7; + string checksum = 8; +} + +message CompanyResult { + repeated Company companies = 1; +} + +message Company { + uint64 id = 1; + google.protobuf.Timestamp change_date = 2; + DateFormatChangeDateCategoryEnum change_date_category = 3; + Company changed_company_id = 4; + int32 country = 5; + google.protobuf.Timestamp created_at = 6; + string description = 7; + repeated Game developed = 8; + CompanyLogo logo = 9; + string name = 10; + Company parent = 11; + repeated Game published = 12; + string slug = 13; + google.protobuf.Timestamp start_date = 14; + DateFormatChangeDateCategoryEnum start_date_category = 15; + google.protobuf.Timestamp updated_at = 16; + string url = 17; + repeated CompanyWebsite websites = 18; + string checksum = 19; +} + + +enum DateFormatChangeDateCategoryEnum { + YYYYMMMMDD = 0; + YYYYMMMM = 1; + YYYY = 2; + YYYYQ1 = 3; + YYYYQ2 = 4; + YYYYQ3 = 5; + YYYYQ4 = 6; + TBD = 7; +} + +message CompanyLogoResult { + repeated CompanyLogo companylogos = 1; +} + +message CompanyLogo { + uint64 id = 1; + bool alpha_channel = 2; + bool animated = 3; + int32 height = 4; + string image_id = 5; + string url = 6; + int32 width = 7; + string checksum = 8; +} + +message CompanyWebsiteResult { + repeated CompanyWebsite companywebsites = 1; +} + +message CompanyWebsite { + uint64 id = 1; + WebsiteCategoryEnum category = 2; + bool trusted = 3; + string url = 4; + string checksum = 5; +} + + +enum WebsiteCategoryEnum { + WEBSITE_CATEGORY_NULL = 0; + WEBSITE_OFFICIAL = 1; + WEBSITE_WIKIA = 2; + WEBSITE_WIKIPEDIA = 3; + WEBSITE_FACEBOOK = 4; + WEBSITE_TWITTER = 5; + WEBSITE_TWITCH = 6; + WEBSITE_INSTAGRAM = 8; + WEBSITE_YOUTUBE = 9; + WEBSITE_IPHONE = 10; + WEBSITE_IPAD = 11; + WEBSITE_ANDROID = 12; + WEBSITE_STEAM = 13; + WEBSITE_REDDIT = 14; + WEBSITE_ITCH = 15; + WEBSITE_EPICGAMES = 16; + WEBSITE_GOG = 17; + WEBSITE_DISCORD = 18; +} + +message CoverResult { + repeated Cover covers = 1; +} + +message Cover { + uint64 id = 1; + bool alpha_channel = 2; + bool animated = 3; + Game game = 4; + int32 height = 5; + string image_id = 6; + string url = 7; + int32 width = 8; + string checksum = 9; +} + +message ExternalGameResult { + repeated ExternalGame externalgames = 1; +} + +message ExternalGame { + uint64 id = 1; + ExternalGameCategoryEnum category = 2; + google.protobuf.Timestamp created_at = 3; + Game game = 4; + string name = 5; + string uid = 6; + google.protobuf.Timestamp updated_at = 7; + string url = 8; + int32 year = 9; + ExternalGameMediaEnum media = 10; + Platform platform = 11; + repeated int32 countries = 12; + string checksum = 13; +} + + +enum ExternalGameCategoryEnum { + EXTERNALGAME_CATEGORY_NULL = 0; + EXTERNALGAME_STEAM = 1; + EXTERNALGAME_GOG = 5; + EXTERNALGAME_YOUTUBE = 10; + EXTERNALGAME_MICROSOFT = 11; + EXTERNALGAME_APPLE = 13; + EXTERNALGAME_TWITCH = 14; + EXTERNALGAME_ANDROID = 15; + EXTERNALGAME_AMAZON_ASIN = 20; + EXTERNALGAME_AMAZON_LUNA = 22; + EXTERNALGAME_AMAZON_ADG = 23; + EXTERNALGAME_EPIC_GAME_STORE = 26; + EXTERNALGAME_OCULUS = 28; +} + + +enum ExternalGameMediaEnum { + EXTERNALGAME_MEDIA_NULL = 0; + EXTERNALGAME_DIGITAL = 1; + EXTERNALGAME_PHYSICAL = 2; +} + +message FranchiseResult { + repeated Franchise franchises = 1; +} + +message Franchise { + uint64 id = 1; + google.protobuf.Timestamp created_at = 2; + repeated Game games = 3; + string name = 4; + string slug = 5; + google.protobuf.Timestamp updated_at = 6; + string url = 7; + string checksum = 8; +} + +message GameResult { + repeated Game games = 1; +} + +message Game { + uint64 id = 1; + repeated AgeRating age_ratings = 2; + double aggregated_rating = 3; + int32 aggregated_rating_count = 4; + repeated AlternativeName alternative_names = 5; + repeated Artwork artworks = 6; + repeated Game bundles = 7; + GameCategoryEnum category = 8; + Collection collection = 9; + Cover cover = 10; + google.protobuf.Timestamp created_at = 11; + repeated Game dlcs = 12; + repeated Game expansions = 13; + repeated ExternalGame external_games = 14; + google.protobuf.Timestamp first_release_date = 15; + int32 follows = 16; + Franchise franchise = 17; + repeated Franchise franchises = 18; + repeated GameEngine game_engines = 19; + repeated GameMode game_modes = 20; + repeated Genre genres = 21; + int32 hypes = 22; + repeated InvolvedCompany involved_companies = 23; + repeated Keyword keywords = 24; + repeated MultiplayerMode multiplayer_modes = 25; + string name = 26; + Game parent_game = 27; + repeated Platform platforms = 28; + repeated PlayerPerspective player_perspectives = 29; + double rating = 30; + int32 rating_count = 31; + repeated ReleaseDate release_dates = 32; + repeated Screenshot screenshots = 33; + repeated Game similar_games = 34; + string slug = 35; + repeated Game standalone_expansions = 36; + GameStatusEnum status = 37; + string storyline = 38; + string summary = 39; + repeated int32 tags = 40; + repeated Theme themes = 41; + double total_rating = 42; + int32 total_rating_count = 43; + google.protobuf.Timestamp updated_at = 44; + string url = 45; + Game version_parent = 46; + string version_title = 47; + repeated GameVideo videos = 48; + repeated Website websites = 49; + string checksum = 50; + repeated Game remakes = 51; + repeated Game remasters = 52; + repeated Game expanded_games = 53; + repeated Game ports = 54; + repeated Game forks = 55; +} + + +enum GameCategoryEnum { + MAIN_GAME = 0; + DLC_ADDON = 1; + EXPANSION = 2; + BUNDLE = 3; + STANDALONE_EXPANSION = 4; + MOD = 5; + EPISODE = 6; + SEASON = 7; + REMAKE = 8; + REMASTER = 9; + EXPANDED_GAME = 10; + PORT = 11; + FORK = 12; +} + + +enum GameStatusEnum { + RELEASED = 0; + ALPHA = 2; + BETA = 3; + EARLY_ACCESS = 4; + OFFLINE = 5; + CANCELLED = 6; + RUMORED = 7; + DELISTED = 8; +} + +message GameEngineResult { + repeated GameEngine gameengines = 1; +} + +message GameEngine { + uint64 id = 1; + repeated Company companies = 2; + google.protobuf.Timestamp created_at = 3; + string description = 4; + GameEngineLogo logo = 5; + string name = 6; + repeated Platform platforms = 7; + string slug = 8; + google.protobuf.Timestamp updated_at = 9; + string url = 10; + string checksum = 11; +} + +message GameEngineLogoResult { + repeated GameEngineLogo gameenginelogos = 1; +} + +message GameEngineLogo { + uint64 id = 1; + bool alpha_channel = 2; + bool animated = 3; + int32 height = 4; + string image_id = 5; + string url = 6; + int32 width = 7; + string checksum = 8; +} + +message GameModeResult { + repeated GameMode gamemodes = 1; +} + +message GameMode { + uint64 id = 1; + google.protobuf.Timestamp created_at = 2; + string name = 3; + string slug = 4; + google.protobuf.Timestamp updated_at = 5; + string url = 6; + string checksum = 7; +} + +message GameVersionResult { + repeated GameVersion gameversions = 1; +} + +message GameVersion { + uint64 id = 1; + google.protobuf.Timestamp created_at = 2; + repeated GameVersionFeature features = 3; + Game game = 4; + repeated Game games = 5; + google.protobuf.Timestamp updated_at = 6; + string url = 7; + string checksum = 8; +} + +message GameVersionFeatureResult { + repeated GameVersionFeature gameversionfeatures = 1; +} + +message GameVersionFeature { + uint64 id = 1; + GameVersionFeatureCategoryEnum category = 2; + string description = 3; + int32 position = 4; + string title = 5; + repeated GameVersionFeatureValue values = 6; + string checksum = 7; +} + + +enum GameVersionFeatureCategoryEnum { + BOOLEAN = 0; + DESCRIPTION = 1; +} + +message GameVersionFeatureValueResult { + repeated GameVersionFeatureValue gameversionfeaturevalues = 1; +} + +message GameVersionFeatureValue { + uint64 id = 1; + Game game = 2; + GameVersionFeature game_feature = 3; + GameVersionFeatureValueIncludedFeatureEnum included_feature = 4; + string note = 5; + string checksum = 6; +} + + +enum GameVersionFeatureValueIncludedFeatureEnum { + NOT_INCLUDED = 0; + INCLUDED = 1; + PRE_ORDER_ONLY = 2; +} + +message GameVideoResult { + repeated GameVideo gamevideos = 1; +} + +message GameVideo { + uint64 id = 1; + Game game = 2; + string name = 3; + string video_id = 4; + string checksum = 5; +} + +message GenreResult { + repeated Genre genres = 1; +} + +message Genre { + uint64 id = 1; + google.protobuf.Timestamp created_at = 2; + string name = 3; + string slug = 4; + google.protobuf.Timestamp updated_at = 5; + string url = 6; + string checksum = 7; +} + +message InvolvedCompanyResult { + repeated InvolvedCompany involvedcompanies = 1; +} + +message InvolvedCompany { + uint64 id = 1; + Company company = 2; + google.protobuf.Timestamp created_at = 3; + bool developer = 4; + Game game = 5; + bool porting = 6; + bool publisher = 7; + bool supporting = 8; + google.protobuf.Timestamp updated_at = 9; + string checksum = 10; +} + +message KeywordResult { + repeated Keyword keywords = 1; +} + +message Keyword { + uint64 id = 1; + google.protobuf.Timestamp created_at = 2; + string name = 3; + string slug = 4; + google.protobuf.Timestamp updated_at = 5; + string url = 6; + string checksum = 7; +} + +message MultiplayerModeResult { + repeated MultiplayerMode multiplayermodes = 1; +} + +message MultiplayerMode { + uint64 id = 1; + bool campaigncoop = 2; + bool dropin = 3; + Game game = 4; + bool lancoop = 5; + bool offlinecoop = 6; + int32 offlinecoopmax = 7; + int32 offlinemax = 8; + bool onlinecoop = 9; + int32 onlinecoopmax = 10; + int32 onlinemax = 11; + Platform platform = 12; + bool splitscreen = 13; + bool splitscreenonline = 14; + string checksum = 15; +} + +message PlatformResult { + repeated Platform platforms = 1; +} + +message Platform { + uint64 id = 1; + string abbreviation = 2; + string alternative_name = 3; + PlatformCategoryEnum category = 4; + google.protobuf.Timestamp created_at = 5; + int32 generation = 6; + string name = 7; + PlatformLogo platform_logo = 8; + PlatformFamily platform_family = 9; + string slug = 10; + string summary = 11; + google.protobuf.Timestamp updated_at = 12; + string url = 13; + repeated PlatformVersion versions = 14; + repeated PlatformWebsite websites = 15; + string checksum = 16; +} + + +enum PlatformCategoryEnum { + PLATFORM_CATEGORY_NULL = 0; + CONSOLE = 1; + ARCADE = 2; + PLATFORM = 3; + OPERATING_SYSTEM = 4; + PORTABLE_CONSOLE = 5; + COMPUTER = 6; +} + +message PlatformFamilyResult { + repeated PlatformFamily platformfamilies = 1; +} + +message PlatformFamily { + uint64 id = 1; + string name = 2; + string slug = 3; + string checksum = 4; +} + +message PlatformLogoResult { + repeated PlatformLogo platformlogos = 1; +} + +message PlatformLogo { + uint64 id = 1; + bool alpha_channel = 2; + bool animated = 3; + int32 height = 4; + string image_id = 5; + string url = 6; + int32 width = 7; + string checksum = 8; +} + +message PlatformVersionResult { + repeated PlatformVersion platformversions = 1; +} + +message PlatformVersion { + uint64 id = 1; + repeated PlatformVersionCompany companies = 2; + string connectivity = 3; + string cpu = 4; + string graphics = 5; + PlatformVersionCompany main_manufacturer = 6; + string media = 7; + string memory = 8; + string name = 9; + string online = 10; + string os = 11; + string output = 12; + PlatformLogo platform_logo = 13; + repeated PlatformVersionReleaseDate platform_version_release_dates = 14; + string resolutions = 15; + string slug = 16; + string sound = 17; + string storage = 18; + string summary = 19; + string url = 20; + string checksum = 21; +} + +message PlatformVersionCompanyResult { + repeated PlatformVersionCompany platformversioncompanies = 1; +} + +message PlatformVersionCompany { + uint64 id = 1; + string comment = 2; + Company company = 3; + bool developer = 4; + bool manufacturer = 5; + string checksum = 6; +} + +message PlatformVersionReleaseDateResult { + repeated PlatformVersionReleaseDate platformversionreleasedates = 1; +} + +message PlatformVersionReleaseDate { + uint64 id = 1; + DateFormatChangeDateCategoryEnum category = 2; + google.protobuf.Timestamp created_at = 3; + google.protobuf.Timestamp date = 4; + string human = 5; + int32 m = 6; + PlatformVersion platform_version = 7; + RegionRegionEnum region = 8; + google.protobuf.Timestamp updated_at = 9; + int32 y = 10; + string checksum = 11; +} + + +enum RegionRegionEnum { + REGION_REGION_NULL = 0; + EUROPE = 1; + NORTH_AMERICA = 2; + AUSTRALIA = 3; + NEW_ZEALAND = 4; + JAPAN = 5; + CHINA = 6; + ASIA = 7; + WORLDWIDE = 8; + KOREA = 9; + BRAZIL = 10; +} + +message PlatformWebsiteResult { + repeated PlatformWebsite platformwebsites = 1; +} + +message PlatformWebsite { + uint64 id = 1; + WebsiteCategoryEnum category = 2; + bool trusted = 3; + string url = 4; + string checksum = 5; +} + +message PlayerPerspectiveResult { + repeated PlayerPerspective playerperspectives = 1; +} + +message PlayerPerspective { + uint64 id = 1; + google.protobuf.Timestamp created_at = 2; + string name = 3; + string slug = 4; + google.protobuf.Timestamp updated_at = 5; + string url = 6; + string checksum = 7; +} + +message ReleaseDateResult { + repeated ReleaseDate releasedates = 1; +} + +message ReleaseDate { + uint64 id = 1; + DateFormatChangeDateCategoryEnum category = 2; + google.protobuf.Timestamp created_at = 3; + google.protobuf.Timestamp date = 4; + Game game = 5; + string human = 6; + int32 m = 7; + Platform platform = 8; + RegionRegionEnum region = 9; + google.protobuf.Timestamp updated_at = 10; + int32 y = 11; + string checksum = 12; +} + +message ScreenshotResult { + repeated Screenshot screenshots = 1; +} + +message Screenshot { + uint64 id = 1; + bool alpha_channel = 2; + bool animated = 3; + Game game = 4; + int32 height = 5; + string image_id = 6; + string url = 7; + int32 width = 8; + string checksum = 9; +} + +message SearchResult { + repeated Search searches = 1; +} + +message Search { + uint64 id = 1; + string alternative_name = 2; + Character character = 3; + Collection collection = 4; + Company company = 5; + string description = 6; + Game game = 7; + string name = 8; + Platform platform = 9; + google.protobuf.Timestamp published_at = 10; + TestDummy test_dummy = 11; + Theme theme = 12; + string checksum = 13; +} + +message TestDummyResult { + repeated TestDummy testdummies = 1; +} + +message TestDummy { + uint64 id = 1; + bool bool_value = 2; + google.protobuf.Timestamp created_at = 3; + TestDummyEnumTestEnum enum_test = 4; + double float_value = 5; + Game game = 6; + repeated int32 integer_array = 7; + int32 integer_value = 8; + string name = 9; + int32 new_integer_value = 10; + bool private = 11; + string slug = 12; + repeated string string_array = 13; + repeated TestDummy test_dummies = 14; + TestDummy test_dummy = 15; + google.protobuf.Timestamp updated_at = 16; + string url = 17; + string checksum = 18; +} + + +enum TestDummyEnumTestEnum { + TESTDUMMY_ENUM_TEST_NULL = 0; + ENUM1 = 1; + ENUM2 = 2; +} + +message ThemeResult { + repeated Theme themes = 1; +} + +message Theme { + uint64 id = 1; + google.protobuf.Timestamp created_at = 2; + string name = 3; + string slug = 4; + google.protobuf.Timestamp updated_at = 5; + string url = 6; + string checksum = 7; +} + +message WebsiteResult { + repeated Website websites = 1; +} + +message Website { + uint64 id = 1; + WebsiteCategoryEnum category = 2; + Game game = 3; + bool trusted = 4; + string url = 5; + string checksum = 6; +}