* @author Christian Weiske <cweiske@cweiske.de>
*/
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])) {
+
+//command line option parsing
+$optind = null;
+$opts = getopt('h', ['help', 'noqr'], $optind);
+$args = array_slice($argv, $optind);
+
+if (isset($opts['help']) || isset($opts['h'])) {
+ echo "Import games from a OUYA game data repository\n";
+ echo "\n";
+ echo "Usage: import-game-data.php [--noqr] [--help|-h]\n";
+ echo " --noqr Do not generate and link QR code images\n";
+ exit(0);
+}
+
+if (!isset($args[0])) {
error('Pass the path to a "folders" file with game data json files folder names');
}
-$foldersFile = $argv[1];
+$foldersFile = $args[0];
if (!is_file($foldersFile)) {
error('Given path is not a file: ' . $foldersFile);
}
+$cfgEnableQr = !isset($opts['noqr']);
+
+
//default configuration values
+$GLOBALS['baseUrl'] = 'http://ouya.cweiske.de/';
+$GLOBALS['categorySubtitles'] = [];
$GLOBALS['packagelists'] = [];
$GLOBALS['urlRewrites'] = [];
$cfgFile = __DIR__ . '/../config.php';
$wwwDir = __DIR__ . '/../www/';
+if ($cfgEnableQr) {
+ $qrDir = $wwwDir . 'gen-qr/';
+ if (!is_dir($qrDir)) {
+ mkdir($qrDir, 0775);
+ }
+}
+
$baseDir = dirname($foldersFile);
$gameFiles = [];
foreach (file($foldersFile) as $line) {
}
}
+//store git repository version of last folder
+$workdir = getcwd();
+chdir($folder);
+$gitDate = `git log --max-count=1 --format="%h %cI"`;
+chdir($workdir);
+file_put_contents($wwwDir . '/game-data-version', $gitDate);
+
$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) {
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(
$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)
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));
];
addDiscoverRow(
- $data, 'Last Updated',
- filterLastUpdated($games, 10)
+ $data, 'New games',
+ filterLastAdded($games, 10)
);
addDiscoverRow(
- $data, 'Best rated',
- filterBestRated($games, 10)
+ $data, 'Best rated games',
+ filterBestRatedGames($games, 10),
+ true
);
foreach ($GLOBALS['packagelists'] as $listTitle => $listPackageNames) {
$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))
filterRandom($games, 99)
)
);
+ writeJson(
+ 'api/v1/discover-data/' . categoryPath('Last updated') . '.json',
+ buildSpecialCategory('Last updated', filterLastUpdated($games, 99))
+ );
$players = [
//1 => '1 player',
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
+ )
+ )
);
}
'rows' => [],
'tiles' => [],
];
- addDiscoverRow(
- $data, 'Last Updated',
- filterLastUpdated($games, 10)
- );
- addDiscoverRow(
- $data, 'Best rated',
- filterBestRated($games, 10)
- );
+ if (isset($GLOBALS['categorySubtitles'][$name])) {
+ $data['stouyapi']['subtitle'] = $GLOBALS['categorySubtitles'][$name];
+ }
+
+ if (count($games) >= 20) {
+ addDiscoverRow(
+ $data, 'Last Updated',
+ filterLastUpdated($games, 10)
+ );
+ addDiscoverRow(
+ $data, 'Best rated',
+ filterBestRated($games, 10),
+ true
+ );
+ }
$games = sortByTitle($games);
$chunks = array_chunk($games, 4);
return $data;
}
+/**
+ * Category without the "Last updated" or "Best rated" top rows
+ *
+ * Used for "Best rated", "Most rated", "Random"
+ */
function buildSpecialCategory($name, $games)
{
$data = [
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;
}
'fileSize' => $release->size,
'version' => $release->uuid,
'contentRating' => $game->contentRating,
- 'downloadLink' => rewriteUrl($release->url),
+ 'downloadLink' => $release->url,
]
];
}
/**
* Build /app/v1/details?app=org.example.game
*/
-function buildDetails($game)
+function buildDetails($game, $linkDeveloperPage = false)
{
$latestRelease = $game->latestRelease;
$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) . '/';
+ }
+
+ $description = $game->description;
+ if (isset($game->notes) && trim($game->notes)) {
+ $description = "Technical notes:\r\n" . $game->notes
+ . "\r\n----\r\n"
+ . $description;
+ }
+
// 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,
+ 'description' => $description,
'gamerNumbers' => $game->players,
'genres' => $game->genres,
'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()
*/
function buildDeveloperProducts($products, $developer)
{
+ //remove duplicates
+ $products = array_values(array_column($products, null, 'identifier'));
+
$jsonProducts = [];
foreach ($products as $product) {
$jsonProducts[] = buildProduct($product);
}
}
-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) {
];
}
-function categoryPath($title)
-{
- return str_replace(['/', '\\', ' ', '+', '?'], '_', $title);
-}
-
function getAllAges($games)
{
$ages = [];
function addMissingGameProperties($game)
{
+ global $cfgEnableQr;
+
if (!isset($game->overview)) {
$game->overview = null;
}
$game->rating->count = 0;
}
+ $game->firstRelease = null;
$game->latestRelease = null;
+ $firstReleaseTimestamp = null;
$latestReleaseTimestamp = 0;
foreach ($game->releases as $release) {
if (!isset($release->publicSize)) {
$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);
if (!isset($game->developer->founder)) {
$game->developer->founder = false;
}
+
+ if ($cfgEnableQr && $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);
+ }
}
/**