This repository has been archived on 2024-08-28. You can view files and clone it, but cannot push or open issues or pull requests.
satori-services/public/splatoon.php

871 lines
31 KiB
PHP

<?php
if(!defined('DATE_ISO8601_EXPANDED')) // added in PHP 8.2...
define('DATE_ISO8601_EXPANDED', 'Y-m-d\\TH:i:sP');
define('SP_TYPE_SCHED', 'schedules');
define('SP_TYPE_FESTS', 'fests');
define('SP3_DEFAULT_LOCALE', 'en-GB');
define('SP3_LOCALES', ['de-DE', 'en-US', 'en-GB', 'es-ES', 'es-MX', 'fr-FR', 'fr-CA', 'it-IT', 'ja-JP', 'ko-KR', 'nl-NL', 'ru-RU', 'zh-CN', 'zh-TW']);
define('SP3_FEST_REGIONS', ['EU', 'JP', 'AP', 'US']);
define('SP3_DOMAIN', 'https://splatoon3.ink');
define('SP3_LOCALE', SP3_DOMAIN . '/data/locale/%s.json');
define('SP3_SCHED', SP3_DOMAIN . '/data/schedules.json');
define('SP3_FESTS', SP3_DOMAIN . '/data/festivals.json');
define('SP3_TYPES', [SP_TYPE_SCHED, SP_TYPE_FESTS]);
define('SP3_SCHED_FILTER', [
'regular', 'bankara', 'series', 'open', 'x',
'league', 'coop', 'bigrun', 'fest',
]);
define('SP2_DEFAULT_LOCALE', 'en');
define('SP2_LOCALES', ['en', 'es', 'es-MX', 'fr', 'fr-CA', 'de', 'nl', 'it', 'ru', 'ja']);
define('SP2_FEST_REGIONS', ['eu', 'jp', 'na']);
define('SP2_DOMAIN', 'https://splatoon2.ink');
define('SP2_LOCALE', SP2_DOMAIN . '/data/locale/%s.json');
define('SP2_SCHED', SP2_DOMAIN . '/data/schedules.json');
define('SP2_COOP_SCHED', SP2_DOMAIN . '/data/coop-schedules.json');
define('SP2_FESTS', SP2_DOMAIN . '/data/festivals.json');
define('SP2_IMAGE_FORMAT', SP2_DOMAIN . '/assets/splatnet%s');
define('SP2_TYPES', [SP_TYPE_SCHED, SP_TYPE_FESTS]);
define('SP2_SCHED_FILTER', [
'regular', 'gachi', 'league', 'coop',
]);
define('SP2_FEST_REGIONS_ALIAS', [
'EU' => 'eu', 'JP' => 'jp', 'US' => 'na',
]);
define('SP2_FEST_REGIONS_ALIAS_REVERSE', [
'eu' => 'EU', 'jp' => 'JP', 'na' => 'US',
]);
define('SP2_LOCALES_ALIAS', [
'en-US' => 'en', 'en-GB' => 'en',
'es-ES' => 'es', 'es-MX' => 'es',
'fr-FR' => 'fr', 'fr-CA' => 'fr',
'de-DE' => 'de', 'nl-NL' => 'nl',
'it-IT' => 'it', 'ru-RU' => 'ru',
'ja-JP' => 'ja',
]);
define('SP_GAME_SP3', 's3');
define('SP_GAME_SP2', 's2');
define('SP_GAMES', [SP_GAME_SP3, SP_GAME_SP2]);
define('SP_GAME_INFOS', [
SP_GAME_SP3 => [
'name' => 'Splatoon 3',
'colour' => 0xFFEFFE65,
'variant' => SP_GAME_SP3,
'types' => SP3_TYPES,
'locales' => SP3_LOCALES,
'locale' => SP3_DEFAULT_LOCALE,
'localeUrlFormat' => SP3_LOCALE,
'schedUrls' => ['all' => SP3_SCHED],
'schedFilters' => SP3_SCHED_FILTER,
'festUrl' => SP3_FESTS,
'festRegions' => SP3_FEST_REGIONS,
],
SP_GAME_SP2 => [
'name' => 'Splatoon 2',
'colour' => 0xFFFF4506,
'variant' => SP_GAME_SP2,
'types' => SP2_TYPES,
'locales' => SP2_LOCALES,
'localeAliases' => SP2_LOCALES_ALIAS,
'locale' => SP2_DEFAULT_LOCALE,
'localeUrlFormat' => SP2_LOCALE,
'schedUrls' => ['vs' => SP2_SCHED, 'co' => SP2_COOP_SCHED],
'schedFilters' => SP2_SCHED_FILTER,
'festUrl' => SP2_FESTS,
'festRegions' => SP2_FEST_REGIONS,
'festRegionAliases' => SP2_FEST_REGIONS_ALIAS,
'festRegionAliasesReverse' => SP2_FEST_REGIONS_ALIAS_REVERSE,
],
]);
function httpRequest(string $url): mixed {
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_AUTOREFERER => true,
CURLOPT_CERTINFO => false,
CURLOPT_FAILONERROR => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_PATH_AS_IS => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TCP_FASTOPEN => true,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_TIMEOUT => 5,
CURLOPT_USERAGENT => 'Satori/20230203 (+https://fii.moe/satori)',
CURLOPT_HTTPHEADER => ['Accept: application/json'],
]);
$response = json_decode(curl_exec($curl));
curl_close($curl);
return $response;
}
function cached(string $name, callable $match, callable $create): mixed {
$fileName = sys_get_temp_dir() . '/satori-splatoon-' . $name . '.tmp';
if(is_file($fileName) && $match(filemtime($fileName), $fileName))
return unserialize(file_get_contents($fileName));
$output = $create();
file_put_contents($fileName, serialize($output));
return $output;
}
function cache_lifetime_15min($mtime): bool {
return is_int($mtime)
&& floor($mtime / 900) >= floor(time() / 900);
}
function splatoon2_format_date(int $time): string {
return str_replace('+00:00', 'Z', date(DATE_ISO8601_EXPANDED, $time));
}
function splatoon2_translate_ruleset(string $id): string {
if($id === 'tower_control')
return 'LOFT';
if($id === 'splat_zones')
return 'AREA';
if($id === 'clam_blitz')
return 'CLAM';
if($id === 'rainmaker')
return 'GOAL';
if($id === 'turf_war')
return 'TURF_WAR';
return $id;
}
function splatoon2_get_mode_colour(string $id): int {
if($id === 'regular')
return 0xFF19D719;
if($id === 'gachi')
return 0xFFF54910;
if($id === 'league')
return 0xFFF02D7D;
if($id === 'coop')
return 0xFFFF5600;
return 0xFFFFFFFF;
}
function splatoon2_ruleset_short(string $id): string {
if($id === 'tower_control')
return 'TC';
if($id === 'splat_zones')
return 'SZ';
if($id === 'clam_blitz')
return 'CB';
if($id === 'rainmaker')
return 'RM';
if($id === 'turf_war')
return 'TW';
if($id === 'coop')
return 'SR';
return $id;
}
function splatoon3_ruleset_short(string $id): string {
if($id === 'LOFT')
return 'TC';
if($id === 'AREA')
return 'SZ';
if($id === 'CLAM')
return 'CB';
if($id === 'GOAL')
return 'RM';
if($id === 'TURF_WAR')
return 'TW';
if($id === 'coop')
return 'SR';
if($id === 'bigrun')
return 'BR';
return $id;
}
date_default_timezone_set('utc');
header('Content-Type: application/json; charset=utf-8');
$gameId = (string)filter_input(INPUT_GET, 'g');
if(!in_array($gameId, SP_GAMES))
die('{"error":"game:id"}');
$gameInfo = SP_GAME_INFOS[$gameId] ?? null;
if(!isset($gameInfo))
die('{"error":"game:info"}');
$typeId = (string)filter_input(INPUT_GET, 't');
if(empty($typeId) || !in_array($typeId, $gameInfo['types']))
die('{"error":"type:id"}');
$localeId = (string)filter_input(INPUT_GET, 'l');
if(empty($localeId))
$localeId = $gameInfo['locale'];
elseif(!in_array($localeId, $gameInfo['locales'])) {
if(isset($gameInfo['localeAliases']) && array_key_exists($localeId, $gameInfo['localeAliases']))
$localeId = $gameInfo['localeAliases'][$localeId];
else die('{"error":"locale:id"}');
}
$localeInfo = cached(sprintf('%s-locale-%s', $gameId, $localeId), 'cache_lifetime_15min', function() use($gameInfo, $localeId) {
if(empty($gameInfo['localeUrlFormat']))
die('{"error":"locale:url"}');
$localeInfo = httpRequest(sprintf($gameInfo['localeUrlFormat'], $localeId));
if(!is_object($localeInfo))
die('{"error":"locale:info"}');
return $localeInfo;
});
if($typeId === SP_TYPE_SCHED) { // SCHEDULE SHIT
$filters = array_filter(explode(',', (string)filter_input(INPUT_GET, 'sf')));
$filterAll = empty($filters);
foreach($filters as $filter)
if(!in_array($filter, $gameInfo['schedFilters']))
die('{"error":"schedules:filter"}');
$schedRaw = (object)cached(sprintf('%s-sched', $gameId), 'cache_lifetime_15min', function() use($gameInfo) {
if(empty($gameInfo['schedUrls']))
die('{"error":"schedules:url"}');
$output = [];
foreach($gameInfo['schedUrls'] as $name => $url)
$output[$name] = httpRequest($url);
return $output;
});
$gameModes = [];
$schedules = [];
if($gameInfo['variant'] === SP_GAME_SP3) {
$data = $schedRaw->all->data;
$includeRegular = $filterAll || in_array('regular', $filters);
$includeSeries = $filterAll || in_array('series', $filters);
$includeOpen = $filterAll || in_array('open', $filters);
$includeRanked = $filterAll || in_array('bankara', $filters);
$includeX = $filterAll || in_array('x', $filters);
$includeLeague = $filterAll || in_array('league', $filters);
$includeSalmon = $filterAll || in_array('coop', $filters);
$includeBigRun = $filterAll || in_array('bigrun', $filters);
$includeFest = $filterAll || in_array('fest', $filters);
$includeSeries = $includeSeries || $includeRanked;
$includeOpen = $includeOpen || $includeRanked;
$includeRanked = $includeSeries || $includeOpen;
function splatoon3_schedule_gen($target, $node): void {
$target->start = $node->startTime;
$target->end = $node->endTime;
}
function splatoon3_schedule_vs($target, $settings): void {
global $localeInfo;
$target->variant = 'vs';
$target->ruleset = [
'id' => $settings->vsRule->rule,
'name' => $localeInfo->rules->{$settings->vsRule->id}?->name ?? $settings->vsRule->name,
'short' => splatoon3_ruleset_short($settings->vsRule->rule),
];
$target->stages = [];
foreach($settings->vsStages as $stage)
$target->stages[] = [
'id' => (string)$stage->vsStageId,
'name' => $localeInfo->stages->{$stage->id}?->name ?? $stage->name,
'image' => $stage->image->url,
];
}
if($includeRegular && !empty($data->regularSchedules->nodes)) {
$schedule = [];
foreach($data->regularSchedules->nodes as $node) {
if(empty($node->regularMatchSetting))
continue;
$schedule[] = $item = new stdClass;
splatoon3_schedule_gen($item, $node);
splatoon3_schedule_vs($item, $node->regularMatchSetting);
}
if(!empty($schedule)) {
$gameModes[] = [
'id' => 'regular',
'name' => 'Regular Battle',
'colour' => 0xFF19D719,
];
$schedules[] = [
'mode' => 'regular',
'schedule' => $schedule,
];
}
}
if($includeRanked && !empty($data->bankaraSchedules->nodes)) {
$series = [];
$open = [];
foreach($data->bankaraSchedules->nodes as $node) {
if(empty($node->bankaraMatchSettings))
continue;
foreach($node->bankaraMatchSettings as $settings) {
if($settings->mode === 'CHALLENGE') {
$series[] = $item = new stdClass;
} elseif($settings->mode === 'OPEN') {
$open[] = $item = new stdClass;
} else continue;
splatoon3_schedule_gen($item, $node);
splatoon3_schedule_vs($item, $settings);
}
}
if($includeSeries && !empty($series)) {
if(empty($gameModes['bankara']))
$gameModes['bankara'] = [
'id' => 'bankara',
'name' => 'Anarchy Battle',
'colour' => 0xFFF54910,
'variants' => [],
];
$gameModes['bankara']['variants'][] = [
'id' => 'CHALLENGE',
'name' => 'Series',
'colour' => 0xFF603BFF,
];
$schedules[] = [
'mode' => 'bankara',
'variant' => 'CHALLENGE',
'schedule' => $series,
];
}
if($includeOpen && !empty($open)) {
if(empty($gameModes['bankara']))
$gameModes['bankara'] = [
'id' => 'bankara',
'name' => 'Anarchy Battle',
'colour' => 0xFFF54910,
'variants' => [],
];
$gameModes['bankara']['variants'][] = [
'id' => 'OPEN',
'name' => 'Open',
'colour' => 0xFF603BFF,
];
$schedules[] = [
'mode' => 'bankara',
'variant' => 'OPEN',
'schedule' => $open,
];
}
}
if($includeX && !empty($data->xSchedules->nodes)) {
$schedule = [];
foreach($data->xSchedules->nodes as $node) {
if(empty($node->xMatchSetting))
continue;
$schedule[] = $item = new stdClass;
splatoon3_schedule_gen($item, $node);
splatoon3_schedule_vs($item, $node->xMatchSetting);
}
if(!empty($schedule)) {
$gameModes[] = [
'id' => 'x',
'name' => 'X Battle',
'colour' => 0xFF0FDB9B,
];
$schedules[] = [
'mode' => 'x',
'schedule' => $schedule,
];
}
}
if($includeLeague && !empty($data->leagueSchedules->nodes)) {
$schedule = [];
foreach($data->leagueSchedules->nodes as $node) {
if(empty($node->leagueMatchSetting))
continue;
$schedule[] = $item = new stdClass;
splatoon3_schedule_gen($item, $node);
splatoon3_schedule_vs($item, $node->leagueMatchSetting);
}
if(!empty($schedule)) {
$gameModes[] = [
'id' => 'league',
'name' => 'League Battle',
'colour' => 0xFFF02D7D,
];
$schedules[] = [
'mode' => 'league',
'schedule' => $schedule,
];
}
}
if($includeFest && !empty($data->festSchedules->nodes)) {
$schedule = [];
foreach($data->festSchedules->nodes as $node) {
if(empty($node->festMatchSetting))
continue;
$schedule[] = $item = new stdClass;
splatoon3_schedule_gen($item, $node);
splatoon3_schedule_vs($item, $node->festMatchSetting);
}
if(!empty($schedule)) {
$gameModes[] = [
'id' => 'fest',
'name' => 'Splatfest Battle',
'colour' => 0xFF71717A,
];
$schedules[] = [
'mode' => 'fest',
'schedule' => $schedule,
];
}
}
if($includeSalmon && !empty($data->coopGroupingSchedule->regularSchedules->nodes)) {
$schedule = [];
$nodes = $data->coopGroupingSchedule->regularSchedules->nodes;
foreach($nodes as $node) {
if(empty($node->setting))
continue;
$schedule[] = $item = new stdClass;
splatoon3_schedule_gen($item, $node);
$item->variant = 'coop';
$item->stages = [
[
'name' => $node->setting->coopStage->name,
'image' => $node->setting->coopStage->image->url,
],
];
$item->weapons = [];
foreach($node->setting->weapons as $weapon)
$item->weapons[] = [
'name' => $localeInfo->weapons->{$weapon->__splatoon3ink_id}?->name ?? $weapon->name,
'image' => $weapon->image->url,
];
}
if(!empty($schedule)) {
$gameModes[] = [
'id' => 'coop',
'name' => 'Salmon Run',
'colour' => 0xFFFF5600,
];
$schedules[] = [
'mode' => 'coop',
'schedule' => $schedule,
];
}
}
if($includeBigRun && !empty($data->coopGroupingSchedule->bigRunSchedules->nodes)) {
$schedule = [];
// i have no idea what the format of this is gonna be yet
// implement this when there's a big run again lol
$nodes = $data->coopGroupingSchedule->bigRunSchedules->nodes;
if(!empty($schedule)) {
$gameModes[] = [
'id' => 'bigrun',
'name' => 'Big Run',
'colour' => 0xFFFF5600, // reinvestigate this
];
$schedule[] = [
'mode' => 'bigrun',
'schedule' => $schedule,
];
}
}
} elseif($gameInfo['variant'] === SP_GAME_SP2) {
$vs = $schedRaw->vs;
$coop = $schedRaw->co;
$includeRegular = $filterAll || in_array('regular', $filters);
$includeRanked = $filterAll || in_array('gachi', $filters);
$includeLeague = $filterAll || in_array('league', $filters);
$includeSalmon = $filterAll || in_array('coop', $filters);
function splatoon2_schedule_gen($target, $node): void {
$target->start = splatoon2_format_date($node->start_time);
$target->end = splatoon2_format_date($node->end_time);
}
function splatoon2_schedule_vs($target, $node): void {
global $localeInfo, $gameModes;
if(!array_key_exists($node->game_mode->key, $gameModes))
$gameModes[$node->game_mode->key] = [
'id' => $node->game_mode->key,
'name' => $localeInfo->game_modes->{$node->game_mode->key}?->name ?? $node->game_mode->name,
'colour' => splatoon2_get_mode_colour($node->game_mode->key),
];
$target->variant = 'vs';
$target->ruleset = [
'id' => splatoon2_translate_ruleset($node->rule->key),
'name' => $localeInfo->rules->{$node->rule->key}?->name ?? $node->rule->name,
'short' => splatoon2_ruleset_short($node->rule->key),
];
$target->stages = [];
foreach(['stage_a', 'stage_b', 'stage_c'] as $stage) {
$stage = $node->{$stage} ?? null;
if(empty($stage))
continue;
$target->stages[] = [
'id' => $stage->id,
'name' => $localeInfo->stages->{$stage->id}?->name ?? $stage->name,
'image' => sprintf(SP2_IMAGE_FORMAT, $stage->image),
];
}
}
if($includeRegular && !empty($vs->regular)) {
$schedule = [];
foreach($vs->regular as $node) {
if(empty($node->rule))
continue;
$schedule[] = $item = new stdClass;
splatoon2_schedule_gen($item, $node);
splatoon2_schedule_vs($item, $node);
}
if(!empty($schedule))
$schedules[] = [
'mode' => 'regular',
'schedule' => $schedule,
];
}
if($includeRanked && !empty($vs->gachi)) {
$schedule = [];
foreach($vs->gachi as $node) {
if(empty($node->rule))
continue;
$schedule[] = $item = new stdClass;
splatoon2_schedule_gen($item, $node);
splatoon2_schedule_vs($item, $node);
}
if(!empty($schedule))
$schedules[] = [
'mode' => 'gachi',
'schedule' => $schedule,
];
}
if($includeLeague && !empty($vs->league)) {
$schedule = [];
foreach($vs->league as $node) {
if(empty($node->rule))
continue;
$schedule[] = $item = new stdClass;
splatoon2_schedule_gen($item, $node);
splatoon2_schedule_vs($item, $node);
}
if(!empty($schedule))
$schedules[] = [
'mode' => 'league',
'schedule' => $schedule,
];
}
if($includeSalmon && !empty($coop->schedules)) {
$schedule = [];
foreach($coop->schedules as $key => $node) {
$schedule[] = $item = new stdClass;
splatoon2_schedule_gen($item, $node);
$item->variant = 'coop';
$details = $coop->details[$key] ?? null;
if(!empty($details)) {
$item->stages = [
[
'name' => $details->stage->name,
'image' => sprintf(SP2_IMAGE_FORMAT, $details->stage->image),
],
];
$item->weapons = [];
foreach($details->weapons as $weapon) {
if($weapon->id === '-1') {
$weaponImage = $weapon->coop_special_weapon->image;
$weaponName = $localeInfo->coop_special_weapons->{$weaponImage}?->name ?? $weapon->coop_special_weapon->name;
} else {
$weaponName = $localeInfo->weapons->{$weapon->weapon->id}?->name ?? $weapon->weapon->name;
$weaponImage = $weapon->weapon->image;
}
$item->weapons[] = [
'id' => $weapon->id,
'name' => $weaponName,
'image' => sprintf(SP2_IMAGE_FORMAT, $weaponImage),
];
}
}
}
if(!empty($schedule)) {
$gameModes[] = [
'id' => 'coop',
'name' => 'Salmon Run',
'colour' => splatoon2_get_mode_colour('coop'),
];
$schedules[] = [
'mode' => 'coop',
'schedule' => $schedule,
];
}
}
} else die('{"error":"schedules:variant"}');
echo json_encode([
'game' => [
'id' => $gameId,
'name' => $gameInfo['name'],
'colour' => $gameInfo['colour'],
'variant' => $gameInfo['variant'],
],
'modes' => array_values($gameModes),
'schedules' => $schedules,
]);
exit;
}
if($typeId === SP_TYPE_FESTS) { // SPLATFEST SHIT
$regions = array_filter(explode(',', (string)filter_input(INPUT_GET, 'fr')));
$regionsAll = empty($regions);
foreach($regions as $key => $region) {
if(!empty($gameInfo['festRegionAliases']) && array_key_exists($region, $gameInfo['festRegionAliases']))
$region = $gameInfo['festRegionAliases'][$region];
if(!in_array($region, $gameInfo['festRegions']))
die('{"error":"fests:region"}');
$regions[$key] = $region;
}
if($regionsAll)
$regions = $gameInfo['festRegions'];
$festsRaw = (object)cached(sprintf('%s-fests', $gameId), 'cache_lifetime_15min', function() use($gameInfo) {
if(empty($gameInfo['festUrl']))
die('{"error":"fests:url"}');
return httpRequest($gameInfo['festUrl']);
});
$fests = [];
if($gameInfo['variant'] === SP_GAME_SP3) {
foreach($regions as $region) {
if(empty($festsRaw->{$region}->data->festRecords->nodes))
continue;
$nodes = $festsRaw->{$region}->data->festRecords->nodes;
if(!empty($gameInfo['festRegionAliasesReverse']) && array_key_exists($region, $gameInfo['festRegionAliasesReverse']))
$region = $gameInfo['festRegionAliasesReverse'][$region];
foreach($nodes as $node) {
$festId = $node->image->url;
if(empty($fests[$festId]))
$fests[$festId] = [
'regions' => [],
];
$fests[$festId]['regions'][] = $festInfo = new stdClass;
$festInfo->id = $node->id;
$festInfo->region = $region;
$festInfo->state = $node->state;
$festInfo->start = $node->startTime;
$festInfo->end = $node->endTime;
$festInfo->title = $node->title;
$festInfo->image = $node->image->url;
$festInfo->can_vote = $node->isVotable;
$festInfo->teams = [];
foreach($node->teams as $rawTeam) {
$festInfo->teams[] = $team = new stdClass;
$team->id = $rawTeam->id;
$team->name = $rawTeam->teamName;
$team->image = $rawTeam->image->url;
$team->colour = (round($rawTeam->color->a * 0xFF) << 24)
| (round($rawTeam->color->r * 0xFF) << 16)
| (round($rawTeam->color->g * 0xFF) << 8)
| round($rawTeam->color->b * 0xFF);
$team->has_results = !empty($rawTeam->result);
if(!$team->has_results)
continue;
$team->is_winner = $rawTeam->result->isWinner;
$team->results = [];
foreach(['horagai', 'vote', 'regularContribution', 'challengeContribution', 'tricolorContribution'] as $rateType) {
if(!isset($rawTeam->result->{$rateType . 'Ratio'}))
continue;
$rateCat = $rateType;
if(str_ends_with($rateCat, 'Contribution'))
$rateCat = substr($rateCat, 0, -12);
$team->results[] = [
'category' => $rateCat,
'ratio' => $rawTeam->result->{$rateType . 'Ratio'},
'is_top' => $rawTeam->result->{'is' . ucfirst($rateType) . 'RatioTop'},
];
}
}
}
}
} elseif($gameInfo['variant'] === SP_GAME_SP2) {
foreach($regions as $region) {
if(empty($festsRaw->{$region}->festivals))
continue;
$festivals = $festsRaw->{$region}->festivals;
$results = $festsRaw->{$region}->results;
if(!empty($gameInfo['festRegionAliasesReverse']) && array_key_exists($region, $gameInfo['festRegionAliasesReverse']))
$region = $gameInfo['festRegionAliasesReverse'][$region];
foreach($festivals as $festId => $festRaw) {
if(empty($fests[$festId]))
$fests[$festId] = [
'regions' => [],
];
$festNames = $localeInfo->festivals->{$festRaw->festival_id}->names ?? null;
if(empty($festNames))
continue;
foreach($results as $findResultRaw)
if($findResultRaw->festival_id === $festRaw->festival_id) {
$resultsRaw = $findResultRaw;
break;
}
$time = time();
$state = '';
if($time > $festRaw->times->end)
$state = 'CLOSED';
elseif($time > $festRaw->times->start)
$state = 'OPEN'; // this is an assumption
else
$state = 'SCHEDULED';
$fests[$festId]['regions'][] = $festInfo = new stdClass;
$festInfo->id = base64_encode(sprintf('FestTeam-%s:JEA-%05d', $region, $festRaw->festival_id));
$festInfo->region = $region;
$festInfo->state = $state;
$festInfo->start = splatoon2_format_date($festRaw->times->start);
$festInfo->end = splatoon2_format_date($festRaw->times->end);
$festInfo->title = sprintf('%s vs %s', $festNames->alpha_short, $festNames->bravo_short);
$festInfo->image = sprintf(SP2_IMAGE_FORMAT, $festRaw->images->panel);
$festInfo->can_vote = $state !== 'CLOSED';
$festInfo->teams = [];
$festInfo->special_stages = [
[
'id' => $festRaw->special_stage->id,
'name' => $localeInfo->stages->{$festRaw->special_stage->id}?->name ?? $festRaw->special_stage->name,
'image' => sprintf(SP2_IMAGE_FORMAT, $festRaw->special_stage->image),
],
];
$tmpResults = [];
foreach(['alpha', 'bravo'] as $teamId => $teamName) {
$festInfo->teams[] = $team = new stdClass;
$team->id = base64_encode(sprintf('FestTeam-%s:JEA-%05d:%s', $region, $festInfo->id, ucfirst($teamName)));
$team->name = $festNames->{$teamName . '_long'};
$team->image = sprintf(SP2_IMAGE_FORMAT, $festRaw->images->{$teamName});
$rawColour = $festRaw->colors->{$teamName};
$team->colour = (round($rawColour->a * 0xFF) << 24)
| (round($rawColour->r * 0xFF) << 16)
| (round($rawColour->g * 0xFF) << 8)
| round($rawColour->b * 0xFF);
$team->has_results = !empty($resultsRaw);
if(!$team->has_results)
continue;
$team->is_winner = $resultsRaw->summary->total === $teamId;
$team->results = [];
foreach(['challenge', 'vote', 'regular', 'solo', 'team'] as $rateType) {
$rawRate = $resultsRaw->rates->{$rateType}->{$teamName} ?? null;
if(empty($rawRate))
continue;
if(!isset($tmpResults[$rateType]))
$tmpResults[$rateType] = $rawRate > $resultsRaw->rates->{$rateType}->{'bravo'} ?? 0;
$team->results[] = [
'category' => $rateType,
'ratio' => $rawRate / 10000,
'is_top' => $tmpResults[$rateType],
];
$tmpResults[$rateType] = !$tmpResults[$rateType];
}
}
unset($tmpResults);
}
}
} else die('{"error":"fests:variant"}');
echo json_encode([
'game' => [
'id' => $gameId,
'name' => $gameInfo['name'],
'colour' => $gameInfo['colour'],
'variant' => $gameInfo['variant'],
],
'fests' => array_values($fests),
]);
exit;
}