X-Git-Url: https://git.cweiske.de/stouyapi.git/blobdiff_plain/5ef938a8037f5aadd9c8c6c5f5fba19daca313e8..d33aeec287423662ca1e63252d6b63f550d37e83:/bin/import-game-data.php diff --git a/bin/import-game-data.php b/bin/import-game-data.php index e962638..2685b3f 100755 --- a/bin/import-game-data.php +++ b/bin/import-game-data.php @@ -7,6 +7,7 @@ * @author Christian Weiske */ ini_set('xdebug.halt_level', E_WARNING|E_NOTICE|E_USER_WARNING|E_USER_NOTICE); +require_once __DIR__ . '/functions.php'; require_once __DIR__ . '/filters.php'; if (!isset($argv[1])) { error('Pass the path to a "folders" file with game data json files folder names'); @@ -16,14 +17,21 @@ if (!is_file($foldersFile)) { error('Given path is not a file: ' . $foldersFile); } -$GLOBALS['packagelists']['cweiskepicks'] = [ - 'de.eiswuxe.blookid2', - 'com.cosmos.babyloniantwins', - 'com.inverseblue.skyriders', -]; +//default configuration values +$GLOBALS['packagelists'] = []; +$GLOBALS['urlRewrites'] = []; +$cfgFile = __DIR__ . '/../config.php'; +if (file_exists($cfgFile)) { + include $cfgFile; +} $wwwDir = __DIR__ . '/../www/'; +$qrDir = $wwwDir . 'gen-qr/'; +if (!is_dir($qrDir)) { + mkdir($qrDir, 0775); +} + $baseDir = dirname($foldersFile); $gameFiles = []; foreach (file($foldersFile) as $line) { @@ -43,6 +51,8 @@ foreach (file($foldersFile) as $line) { $games = []; $count = 0; $developers = []; + +//load game data. doing early to collect a developer's games foreach ($gameFiles as $gameFile) { $game = json_decode(file_get_contents($gameFile)); if ($game === null) { @@ -51,18 +61,18 @@ foreach ($gameFiles as $gameFile) { addMissingGameProperties($game); $games[$game->packageName] = $game; - writeJson( - 'api/v1/details-data/' . $game->packageName . '.json', - buildDetails($game) - ); - if (!isset($developers[$game->developer->uuid])) { $developers[$game->developer->uuid] = [ - 'info' => $game->developer, - 'products' => [], + 'info' => $game->developer, + 'products' => [], + 'gameNames' => [], ]; } + $developers[$game->developer->uuid]['gameNames'][] = $game->packageName; +} +//write json api files +foreach ($games as $game) { $products = $game->products ?? []; foreach ($products as $product) { writeJson( @@ -73,7 +83,14 @@ foreach ($gameFiles as $gameFile) { $developers[$game->developer->uuid]['products'][] = $product; } - /**/ + writeJson( + 'api/v1/details-data/' . $game->packageName . '.json', + buildDetails( + $game, + count($developers[$game->developer->uuid]['gameNames']) > 1 + ) + ); + writeJson( 'api/v1/games/' . $game->packageName . '/purchases', buildPurchases($game) @@ -109,11 +126,20 @@ foreach ($developers as $developer) { buildDeveloperProducts($developer['products'], $developer['info']) ); writeJson( - //index.htm does not need a rewrite rule 'api/v1/developers/' . $developer['info']->uuid . '/current_gamer', buildDeveloperCurrentGamer() ); + + if (count($developer['gameNames']) > 1) { + writeJson( + 'api/v1/discover-data/dev--' . $developer['info']->uuid . '.json', + buildSpecialCategory( + 'Developer: ' . $developer['info']->name, + filterByPackageNames($games, $developer['gameNames']) + ) + ); + } } writeJson('api/v1/discover-data/discover.json', buildDiscover($games)); @@ -145,30 +171,40 @@ function buildDiscover(array $games) ]; addDiscoverRow( - $data, 'Last Updated', - filterLastUpdated($games, 10) - ); - addDiscoverRow( - $data, 'Best rated', - filterBestRated($games, 10) + $data, 'New games', + filterLastAdded($games, 10) ); addDiscoverRow( - $data, "cweiske's picks", - filterByPackageNames($games, $GLOBALS['packagelists']['cweiskepicks']) + $data, 'Best rated games', + filterBestRatedGames($games, 10), + true ); + foreach ($GLOBALS['packagelists'] as $listTitle => $listPackageNames) { + addDiscoverRow( + $data, $listTitle, + filterByPackageNames($games, $listPackageNames) + ); + } + addDiscoverRow( $data, 'Special', [ 'Best rated', + 'Best rated games', 'Most rated', 'Random', + 'Last updated', ] ); writeJson( 'api/v1/discover-data/' . categoryPath('Best rated') . '.json', buildSpecialCategory('Best rated', filterBestRated($games, 99)) ); + writeJson( + 'api/v1/discover-data/' . categoryPath('Best rated games') . '.json', + buildSpecialCategory('Best rated games', filterBestRatedGames($games, 99)) + ); writeJson( 'api/v1/discover-data/' . categoryPath('Most rated') . '.json', buildSpecialCategory('Most rated', filterMostDownloaded($games, 99)) @@ -180,6 +216,10 @@ function buildDiscover(array $games) filterRandom($games, 99) ) ); + writeJson( + 'api/v1/discover-data/' . categoryPath('Last updated') . '.json', + buildSpecialCategory('Last updated', filterLastUpdated($games, 99)) + ); $players = [ //1 => '1 player', @@ -191,7 +231,18 @@ function buildDiscover(array $games) foreach ($players as $num => $title) { writeJson( 'api/v1/discover-data/' . categoryPath($title) . '.json', - buildDiscoverCategory($title, filterByPlayers($games, $num)) + buildDiscoverCategory( + $title, + //I do not want emulators here, + // and neither Streaming apps + filterByGenre( + filterByGenre( + filterByPlayers($games, $num), + 'Emulator', true + ), + 'App', true + ) + ) ); } @@ -244,7 +295,8 @@ function buildDiscoverCategory($name, $games) ); addDiscoverRow( $data, 'Best rated', - filterBestRated($games, 10) + filterBestRated($games, 10), + true ); $games = sortByTitle($games); @@ -270,6 +322,11 @@ function buildMakeCategory($name, $games) return $data; } +/** + * Category without the "Last updated" or "Best rated" top rows + * + * Used for "Best rated", "Most rated", "Random" + */ function buildSpecialCategory($name, $games) { $data = [ @@ -291,19 +348,29 @@ function buildSpecialCategory($name, $games) function buildDiscoverHome(array $games) { - //we do not want anything here for now $data = [ 'title' => 'home', 'rows' => [ - [ - 'title' => 'FEATURED', - 'showPrice' => false, - 'ranked' => false, - 'tiles' => [], - ] ], 'tiles' => [], ]; + + if (isset($GLOBALS['home'])) { + reset($GLOBALS['home']); + $title = key($GLOBALS['home']); + addDiscoverRow( + $data, $title, + filterByPackageNames($games, $GLOBALS['home'][$title]) + ); + } else { + $data['rows'][] = [ + 'title' => 'FEATURED', + 'showPrice' => false, + 'ranked' => false, + 'tiles' => [], + ]; + } + return $data; } @@ -380,12 +447,13 @@ function buildProduct($product) return null; } return [ - 'type' => 'entitlement', + 'type' => $product->type ?? 'entitlement', 'identifier' => $product->identifier, 'name' => $product->name, 'description' => $product->description ?? '', 'localPrice' => $product->localPrice, 'originalPrice' => $product->originalPrice, + 'priceInCents' => $product->originalPrice * 100, 'percentOff' => 0, 'currency' => $product->currency, ]; @@ -394,7 +462,7 @@ function buildProduct($product) /** * Build /app/v1/details?app=org.example.game */ -function buildDetails($game) +function buildDetails($game, $linkDeveloperPage = false) { $latestRelease = $game->latestRelease; @@ -413,15 +481,17 @@ function buildDetails($game) $mediaTiles[] = [ 'type' => 'image', 'urls' => [ - 'thumbnail' => $medium->thumb, + 'thumbnail' => $medium->thumb ?? $medium->url, 'full' => $medium->url, ], ]; } else { - $mediaTiles[] = [ - 'type' => 'video', - 'url' => $medium->url, - ]; + if (!isUnsupportedVideoUrl($medium->url)) { + $mediaTiles[] = [ + 'type' => 'video', + 'url' => $medium->url, + ]; + } } } @@ -440,8 +510,15 @@ function buildDetails($game) $product = buildProduct($gamePromoted); } + $iaUrl = null; + if (isset($game->latestRelease->url) + && substr($game->latestRelease->url, 0, 29) == 'https://archive.org/download/' + ) { + $iaUrl = dirname($game->latestRelease->url) . '/'; + } + // http://cweiske.de/ouya-store-api-docs.htm#get-https-devs-ouya-tv-api-v1-details - return [ + $data = [ 'type' => 'Game', 'title' => $game->title, 'description' => $game->description, @@ -498,7 +575,19 @@ function buildDetails($game) 'promotedProduct' => $product, 'buttons' => $buttons, + + 'stouyapi' => [ + 'internet-archive' => $iaUrl, + 'developer-url' => $game->developer->website ?? null, + ] ]; + + if ($linkDeveloperPage) { + $data['developer']['url'] = 'ouya://launcher/discover/dev--' + . categoryPath($game->developer->uuid); + } + + return $data; } function buildDeveloperCurrentGamer() @@ -530,6 +619,9 @@ function buildDeveloperProductOnly($product, $developer) */ function buildDeveloperProducts($products, $developer) { + //remove duplicates + $products = array_values(array_column($products, null, 'identifier')); + $jsonProducts = []; foreach ($products as $product) { $jsonProducts[] = buildProduct($product); @@ -585,11 +677,11 @@ function buildSearch($games) function dummyEncrypt($data) { return [ - 'key' => base64_encode('0123456789abcdef') . "\n", - 'iv' => 't3jir1LHpICunvhlM76edQ==' . "\n",//random bytes + 'key' => base64_encode('0123456789abcdef'), + 'iv' => 't3jir1LHpICunvhlM76edQ==',//random bytes 'blob' => base64_encode( json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) - ) . "\n", + ), ]; } @@ -606,12 +698,12 @@ function addChunkedDiscoverRows(&$data, $games, $title) } } -function addDiscoverRow(&$data, $title, $games) +function addDiscoverRow(&$data, $title, $games, $ranked = false) { $row = [ 'title' => $title, 'showPrice' => true, - 'ranked' => false, + 'ranked' => $ranked, 'tiles' => [], ]; foreach ($games as $game) { @@ -690,11 +782,6 @@ function buildDiscoverGameTile($game) ]; } -function categoryPath($title) -{ - return str_replace(['/', '\\', ' ', '+', '?'], '_', $title); -} - function getAllAges($games) { $ages = []; @@ -753,7 +840,9 @@ function addMissingGameProperties($game) $game->rating->count = 0; } + $game->firstRelease = null; $game->latestRelease = null; + $firstReleaseTimestamp = null; $latestReleaseTimestamp = 0; foreach ($game->releases as $release) { if (!isset($release->publicSize)) { @@ -768,6 +857,15 @@ function addMissingGameProperties($game) $game->latestRelease = $release; $latestReleaseTimestamp = $releaseTimestamp; } + if ($firstReleaseTimestamp === null + || $releaseTimestamp < $firstReleaseTimestamp + ) { + $game->firstRelease = $release; + $firstReleaseTimestamp = $releaseTimestamp; + } + } + if ($game->firstRelease === null) { + error('No first release for ' . $game->packageName); } if ($game->latestRelease === null) { error('No latest release for ' . $game->packageName); @@ -792,6 +890,34 @@ function addMissingGameProperties($game) if (!isset($game->developer->founder)) { $game->developer->founder = false; } + + if ($game->website) { + $qrfileName = preg_replace('#[^\\w\\d._-]#', '_', $game->website) . '.png'; + $qrfilePath = $GLOBALS['qrDir'] . $qrfileName; + if (!file_exists($qrfilePath)) { + $cmd = __DIR__ . '/create-qr.sh' + . ' ' . escapeshellarg($game->website) + . ' ' . escapeshellarg($qrfilePath); + passthru($cmd, $retval); + if ($retval != 0) { + exit(20); + } + } + $qrUrlPath = $GLOBALS['baseUrl'] . 'gen-qr/' . $qrfileName; + $game->media[] = (object) [ + 'type' => 'image', + 'url' => $qrUrlPath, + ]; + } + + //rewrite urls from Internet Archive to our servers + $game->discover = rewriteUrl($game->discover); + foreach ($game->media as $medium) { + $medium->url = rewriteUrl($medium->url); + } + foreach ($game->releases as $release) { + $release->url = rewriteUrl($release->url); + } } /** @@ -851,6 +977,16 @@ function getPromotedProduct($game) return null; } +/** + * vimeo only work with HTTPS now, + * and the OUYA does not support SNI. + * We get SSL errors and no video for them :/ + */ +function isUnsupportedVideoUrl($url) +{ + return strpos($url, '://vimeo.com/') !== false; +} + function removeMakeGames(array $games) { return filterByGenre($games, 'Tutorials', true); @@ -867,6 +1003,14 @@ function removeMakeGenres($genres) return $filtered; } +function rewriteUrl($url) +{ + foreach ($GLOBALS['urlRewrites'] as $pattern => $replacement) { + $url = preg_replace($pattern, $replacement, $url); + } + return $url; +} + function writeJson($path, $data) { global $wwwDir;