2022-09-06 21:37:53 +00:00
|
|
|
<?php
|
|
|
|
define('FTM_ROOT', __DIR__ . '/..');
|
2024-01-28 03:56:22 +00:00
|
|
|
define('FTM_HASH', 'xxh3');
|
2022-09-06 21:37:53 +00:00
|
|
|
define('FTM_PATH_PUB', FTM_ROOT . '/public');
|
|
|
|
define('FTM_PATH_PRV', FTM_ROOT . '/private');
|
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
define('FTM_COMMON_CONFIG', FTM_PATH_PRV . '/common.ini');
|
|
|
|
define('FTM_SOUND_LIBRARY', FTM_PATH_PRV . '/sound-library.ini');
|
|
|
|
define('FTM_SOUND_PACKS', FTM_PATH_PRV . '/sound-packs.ini');
|
|
|
|
define('FTM_TEXT_TRIGGERS', FTM_PATH_PRV . '/text-triggers.ini');
|
|
|
|
|
2022-09-06 21:37:53 +00:00
|
|
|
define('FTM_LEGACY_SOUND_TYPE', [
|
|
|
|
'opus' => 'audio/ogg',
|
|
|
|
'ogg' => 'audio/ogg',
|
|
|
|
'mp3' => 'audio/mpeg',
|
|
|
|
'caf' => 'audio/x-caf',
|
|
|
|
'wav' => 'audio/wav',
|
|
|
|
]);
|
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
define('FTM_FILE_HASH_MAP', [
|
|
|
|
'common' => [FTM_COMMON_CONFIG],
|
|
|
|
'sounds' => [FTM_SOUND_LIBRARY, FTM_SOUND_PACKS],
|
|
|
|
'sounds2' => [FTM_SOUND_LIBRARY, FTM_SOUND_PACKS],
|
|
|
|
'texttriggers' => [FTM_TEXT_TRIGGERS],
|
|
|
|
'soundtriggers' => [FTM_TEXT_TRIGGERS],
|
|
|
|
]);
|
|
|
|
|
2022-09-06 21:37:53 +00:00
|
|
|
header('X-Powered-By: Futami');
|
|
|
|
header('Cache-Control: max-age=86400, must-revalidate');
|
|
|
|
header('Access-Control-Allow-Origin: *');
|
|
|
|
|
|
|
|
$reqMethod = (string)filter_input(INPUT_SERVER, 'REQUEST_METHOD');
|
|
|
|
if($reqMethod === 'OPTIONS') {
|
|
|
|
http_response_code(204);
|
|
|
|
header('Access-Control-Allow-Methods: OPTIONS, GET');
|
|
|
|
header('Access-Control-Allow-Headers: Cache-Control');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if($reqMethod !== 'HEAD' && $reqMethod !== 'GET') {
|
|
|
|
http_response_code(405);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
function json_out($data): void {
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
echo json_encode($data);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
function ftm_hash_str(string $data): string {
|
|
|
|
return hash(FTM_HASH, $data);
|
|
|
|
}
|
|
|
|
function ftm_hash_file(string $path): string {
|
|
|
|
return hash_file(FTM_HASH, $path);
|
|
|
|
}
|
|
|
|
function hash_mapped_file(string $name): string {
|
|
|
|
$files = FTM_FILE_HASH_MAP[$name] ?? [];
|
|
|
|
if(count($files) === 1)
|
|
|
|
return ftm_hash_file($files[0]);
|
|
|
|
|
|
|
|
$data = '';
|
|
|
|
foreach($files as $file)
|
|
|
|
$data .= file_get_contents($file);
|
|
|
|
|
|
|
|
return ftm_hash_str($data);
|
|
|
|
}
|
|
|
|
|
2022-09-06 21:37:53 +00:00
|
|
|
function match_etag($eTag): void {
|
|
|
|
if(filter_input(INPUT_SERVER, 'HTTP_IF_NONE_MATCH') === $eTag) {
|
|
|
|
http_response_code(304);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function gen_etag(string $user, string $data): string {
|
2024-01-28 03:50:24 +00:00
|
|
|
return sprintf('W/"%s-%s"', ftm_hash_str($data), $user);
|
2022-09-06 21:37:53 +00:00
|
|
|
}
|
|
|
|
function gen_etag_file(string $user, string $path): string {
|
2024-01-28 03:50:24 +00:00
|
|
|
return gen_etag($user, ftm_hash_file($path));
|
2022-09-06 21:37:53 +00:00
|
|
|
}
|
|
|
|
function etag(string $etag): void {
|
|
|
|
match_etag($etag);
|
|
|
|
header('ETag: ' . $etag);
|
|
|
|
}
|
|
|
|
function etag_data(string $user, string $data): void {
|
|
|
|
etag(gen_etag($user, $data));
|
|
|
|
}
|
|
|
|
function etag_file(string $user, string $path): void {
|
|
|
|
etag(gen_etag_file($user, $path));
|
|
|
|
}
|
|
|
|
|
|
|
|
$reqPath = '/' . trim(parse_url((string)filter_input(INPUT_SERVER, 'REQUEST_URI'), PHP_URL_PATH), '/');
|
|
|
|
|
|
|
|
if($reqPath === '/common.json') {
|
2024-01-28 03:50:24 +00:00
|
|
|
etag_file('common', FTM_COMMON_CONFIG);
|
|
|
|
|
|
|
|
$config = parse_ini_file(FTM_COMMON_CONFIG, false, INI_SCANNER_TYPED);
|
|
|
|
$common = new stdClass;
|
|
|
|
|
|
|
|
foreach($config as $key => $value) {
|
|
|
|
if($key === 'colours') {
|
|
|
|
$dict = $value;
|
|
|
|
$value = [];
|
|
|
|
foreach($dict as $name => $raw)
|
|
|
|
$value[] = ['n' => $name, 'c' => $raw];
|
|
|
|
} elseif(is_string($value) && str_starts_with($value, '::')) {
|
|
|
|
$parts = explode(':', substr($value, 2));
|
|
|
|
$hash = hash_mapped_file($parts[0]);
|
|
|
|
$value = sprintf($parts[1], $hash);
|
|
|
|
}
|
2022-09-06 21:37:53 +00:00
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
$common->{$key} = $value;
|
|
|
|
}
|
2022-09-06 21:37:53 +00:00
|
|
|
|
|
|
|
json_out($common);
|
|
|
|
}
|
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
if(preg_match('#^/sounds2(\.[a-f0-9]{8})?.json$#', $reqPath)) {
|
|
|
|
$sndLibData = file_get_contents(FTM_SOUND_LIBRARY);
|
|
|
|
$sndPackData = file_get_contents(FTM_SOUND_PACKS);
|
2022-09-08 20:51:49 +00:00
|
|
|
|
|
|
|
etag_data('sounds2', $sndLibData . $sndPackData);
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds = new stdClass;
|
2022-09-08 20:51:49 +00:00
|
|
|
$sndLib = parse_ini_string($sndLibData, true, INI_SCANNER_TYPED);
|
|
|
|
$sndPacks = parse_ini_string($sndPackData, true, INI_SCANNER_TYPED);
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds->library = [];
|
2022-09-08 20:51:49 +00:00
|
|
|
foreach($sndLib as $name => $info)
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds->library[] = [
|
2022-09-08 20:51:49 +00:00
|
|
|
'name' => $name,
|
|
|
|
'title' => $info['title'],
|
|
|
|
'sources' => $info['sources'],
|
|
|
|
];
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds->packs = [];
|
2022-09-08 20:51:49 +00:00
|
|
|
foreach($sndPacks as $name => $info)
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds->packs[] = [
|
2022-09-08 20:51:49 +00:00
|
|
|
'name' => $name,
|
|
|
|
'title' => $info['title'],
|
|
|
|
'events' => $info['events'],
|
|
|
|
];
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
json_out($sounds);
|
2022-09-08 20:51:49 +00:00
|
|
|
}
|
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
if(preg_match('#^/sounds(\.[a-f0-9]{8})?.json$#', $reqPath)) {
|
|
|
|
$sndLibData = file_get_contents(FTM_SOUND_LIBRARY);
|
|
|
|
$sndPackData = file_get_contents(FTM_SOUND_PACKS);
|
2022-09-06 21:37:53 +00:00
|
|
|
|
|
|
|
etag_data('sounds', $sndLibData . $sndPackData);
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds = new stdClass;
|
2022-09-06 21:37:53 +00:00
|
|
|
$sndLib = parse_ini_string($sndLibData, true, INI_SCANNER_TYPED);
|
|
|
|
$sndPacks = parse_ini_string($sndPackData, true, INI_SCANNER_TYPED);
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds->library = [];
|
2022-09-06 21:37:53 +00:00
|
|
|
foreach($sndLib as $name => $info) {
|
|
|
|
$sources = [];
|
|
|
|
foreach($info['sources'] as $type => $path) {
|
|
|
|
$sources[] = [
|
|
|
|
'format' => FTM_LEGACY_SOUND_TYPE[$type],
|
|
|
|
'url' => $path,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds->library[] = [
|
2022-09-06 21:37:53 +00:00
|
|
|
'id' => $name,
|
|
|
|
'name' => $info['title'],
|
|
|
|
'sources' => $sources,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds->packs = [];
|
2022-09-08 20:51:49 +00:00
|
|
|
foreach($sndPacks as $name => $info)
|
2024-01-28 04:03:42 +00:00
|
|
|
$sounds->packs[] = [
|
2022-09-06 21:37:53 +00:00
|
|
|
'id' => $name,
|
|
|
|
'name' => $info['title'],
|
|
|
|
'events' => $info['events'],
|
|
|
|
];
|
|
|
|
|
2024-01-28 04:03:42 +00:00
|
|
|
json_out($sounds);
|
2022-09-06 21:37:53 +00:00
|
|
|
}
|
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
if(preg_match('#^/texttriggers(\.[a-f0-9]{8})?.json$#', $reqPath)) {
|
|
|
|
etag_file('texttriggers', FTM_TEXT_TRIGGERS);
|
2022-09-08 20:51:49 +00:00
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
$triggers = parse_ini_file(FTM_TEXT_TRIGGERS, true, INI_SCANNER_TYPED);
|
2022-09-08 20:51:49 +00:00
|
|
|
|
|
|
|
if($triggers === false)
|
|
|
|
$triggers = [];
|
|
|
|
else
|
|
|
|
$triggers = array_values($triggers);
|
|
|
|
|
|
|
|
json_out($triggers);
|
|
|
|
}
|
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
if(preg_match('#^/soundtriggers(\.[a-f0-9]{8})?.json$#', $reqPath)) {
|
|
|
|
etag_file('soundtriggers', FTM_TEXT_TRIGGERS);
|
2022-09-06 21:37:53 +00:00
|
|
|
|
2024-01-28 03:50:24 +00:00
|
|
|
$textTriggers = parse_ini_file(FTM_TEXT_TRIGGERS, true, INI_SCANNER_TYPED);
|
|
|
|
$sndLib = parse_ini_file(FTM_SOUND_LIBRARY, true, INI_SCANNER_TYPED);
|
2022-09-06 21:37:53 +00:00
|
|
|
$soundTrigs = [];
|
|
|
|
|
|
|
|
foreach($textTriggers as $triggerInfo) {
|
|
|
|
if($triggerInfo['type'] !== 'sound' && $triggerInfo['type'] !== 'alias')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
$soundTrig = [];
|
|
|
|
|
|
|
|
if($triggerInfo['type'] === 'sound') {
|
|
|
|
$sounds = [];
|
|
|
|
|
|
|
|
foreach($triggerInfo['sounds'] as $soundName) {
|
|
|
|
if(!isset($sndLib[$soundName]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
$sound = [];
|
|
|
|
$libSound = $sndLib[$soundName];
|
|
|
|
|
|
|
|
if(isset($libSound['sources']['mp3']))
|
|
|
|
$sound['m'] = $libSound['sources']['mp3'];
|
|
|
|
if(isset($libSound['sources']['ogg']))
|
|
|
|
$sound['o'] = $libSound['sources']['ogg'];
|
|
|
|
if(isset($libSound['sources']['opus']))
|
|
|
|
$sound['o'] = $libSound['sources']['opus'];
|
|
|
|
if(isset($libSound['sources']['caf']))
|
|
|
|
$sound['c'] = $libSound['sources']['caf'];
|
|
|
|
|
|
|
|
if(empty($sound))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(isset($triggerInfo['volume'])) {
|
|
|
|
$sound['v'] = ceil(($triggerInfo['volume'] - 1) * 100);
|
|
|
|
$sound['v2'] = $triggerInfo['volume'];
|
|
|
|
}
|
|
|
|
|
|
|
|
if(isset($triggerInfo['rate']))
|
|
|
|
$sound['r'] = $triggerInfo['rate'];
|
|
|
|
|
|
|
|
$sounds[] = $sound;
|
|
|
|
}
|
|
|
|
|
|
|
|
$soundTrig['s'] = $sounds;
|
|
|
|
} elseif($triggerInfo['type'] === 'alias') {
|
|
|
|
$soundTrig['f'] = $triggerInfo['for'];
|
|
|
|
}
|
|
|
|
|
|
|
|
$matches = [];
|
|
|
|
foreach($triggerInfo['match'] as $match) {
|
|
|
|
$filters = [];
|
|
|
|
$value = null;
|
|
|
|
$notValue = null;
|
|
|
|
|
|
|
|
$parts = explode(';', $match);
|
|
|
|
foreach($parts as $part) {
|
|
|
|
$part = explode(':', trim($part));
|
|
|
|
|
|
|
|
switch($part[0]) {
|
|
|
|
case 'lc':
|
|
|
|
$filters[] = 'lower';
|
|
|
|
break;
|
|
|
|
case 'is':
|
|
|
|
$filters[] = 'exact';
|
|
|
|
$value = trim($part[1]);
|
|
|
|
break;
|
|
|
|
case 'starts':
|
|
|
|
$filters[] = 'starts';
|
|
|
|
$value = trim($part[1]);
|
|
|
|
break;
|
|
|
|
case 'has':
|
|
|
|
$filters[] = 'contains';
|
|
|
|
$value = trim($part[1]);
|
|
|
|
break;
|
|
|
|
case 'hasnot':
|
|
|
|
$notValue = trim($part[1]);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$filters[] = 'missing:' . $part[0];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$matchNew = ['t' => implode(':', $filters)];
|
|
|
|
if($value !== null)
|
|
|
|
$matchNew['m'] = $value;
|
|
|
|
if($notValue !== null)
|
|
|
|
$matchNew['n'] = $notValue;
|
|
|
|
|
|
|
|
$matches[] = $matchNew;
|
|
|
|
}
|
|
|
|
$soundTrig['t'] = $matches;
|
|
|
|
|
|
|
|
$soundTrigs[] = $soundTrig;
|
|
|
|
}
|
|
|
|
|
|
|
|
json_out([
|
|
|
|
'meta' => [
|
|
|
|
'baseUrl' => '',
|
|
|
|
],
|
|
|
|
'triggers' => $soundTrigs,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if($reqPath === '/' || $reqPath === '/index.html' || $reqPath === '/index.php') {
|
|
|
|
header('Content-Type: text/html; charset=utf-8');
|
|
|
|
echo <<<HTML
|
|
|
|
<!doctype html>
|
|
|
|
Data and settings shared between both chat clients is stored on this subdomain.
|
|
|
|
HTML;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
http_response_code(404);
|