Satori API scripts overhaul!
This commit is contained in:
parent
59dba92418
commit
f491be9eba
99 changed files with 3867 additions and 1704 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -1,2 +1,8 @@
|
|||
/config/git-broadcast.ini
|
||||
/config/flashii.ini
|
||||
[Tt]humbs.db
|
||||
[Dd]esktop.ini
|
||||
.DS_Store
|
||||
/.debug
|
||||
/vendor
|
||||
/config.cfg
|
||||
/config.ini
|
||||
/.migrating
|
||||
|
|
13
composer.json
Normal file
13
composer.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Satori\\": "src"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"flashwave/index": "dev-master",
|
||||
"flashwave/syokuhou": "dev-master"
|
||||
}
|
||||
}
|
107
composer.lock
generated
Normal file
107
composer.lock
generated
Normal file
|
@ -0,0 +1,107 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d909826a501788db19148054e389ec5f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "flashwave/index",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.flash.moe/flash/index.git",
|
||||
"reference": "82a350a5c719cc83aa22382201683a68a2629f2a"
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mysqli": "Support for the Index\\Data\\MariaDB namespace (both mysqlnd and libmysql are supported).",
|
||||
"ext-sqlite3": "Support for the Index\\Data\\SQLite namespace."
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"index.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Index\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"bsd-3-clause-clear"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "flashwave",
|
||||
"email": "packagist@flash.moe",
|
||||
"homepage": "https://flash.moe",
|
||||
"role": "mom"
|
||||
}
|
||||
],
|
||||
"description": "Composer package for the common library for my projects.",
|
||||
"homepage": "https://railgun.sh/index",
|
||||
"time": "2023-09-15T22:44:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "flashwave/syokuhou",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.flash.moe/flash/syokuhou.git",
|
||||
"reference": "b3470ad8605b0484294c73cd95be6e7ba4551e5a"
|
||||
},
|
||||
"require": {
|
||||
"flashwave/index": "dev-master",
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.4"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Syokuhou\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"bsd-3-clause-clear"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "flashwave",
|
||||
"email": "packagist@flash.moe",
|
||||
"homepage": "https://flash.moe",
|
||||
"role": "mom"
|
||||
}
|
||||
],
|
||||
"description": "Configuration library for PHP.",
|
||||
"homepage": "https://railgun.sh/syokuhou",
|
||||
"time": "2023-10-20T21:26:38+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"flashwave/index": 20,
|
||||
"flashwave/syokuhou": 20
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
24
database/2023_11_06_004121_initial_structure.php
Normal file
24
database/2023_11_06_004121_initial_structure.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\Migration\IDbMigration;
|
||||
|
||||
final class InitialStructure_20231106_004121 implements IDbMigration {
|
||||
public function migrate(IDbConnection $conn): void {
|
||||
$existingTables = [];
|
||||
$result = $conn->query('SHOW TABLES');
|
||||
while($result->next())
|
||||
$existingTables[] = $result->getString(0);
|
||||
|
||||
if(!in_array('exchange-rates', $existingTables))
|
||||
$conn->execute('
|
||||
CREATE TABLE `exchange-rates` (
|
||||
rate_from BINARY(3) NOT NULL,
|
||||
rate_to BINARY(3) NOT NULL,
|
||||
rate_value DECIMAL(20,6) NOT NULL,
|
||||
rate_stored TIMESTAMP NOT NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (rate_from, rate_to),
|
||||
KEY rate_stored (rate_stored)
|
||||
) ENGINE=InnoDB COLLATE="utf8mb4_bin"
|
||||
');
|
||||
}
|
||||
}
|
47
public/404.html
Normal file
47
public/404.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cannot find page</title>
|
||||
<link rel="stylesheet" type="text/css" href="/satori.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap">
|
||||
<h1>
|
||||
<img src="//static.flash.moe/images/404-info.gif">
|
||||
The page cannot be found
|
||||
</h1>
|
||||
<p>
|
||||
The page you are looking for might have been removed, had its
|
||||
name changed, or is temporarily unavailable.
|
||||
</p>
|
||||
<hr>
|
||||
<h2>
|
||||
Please try the following:
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
If you typed the page address in the Address bar, make
|
||||
sure that it is spelled correctly.
|
||||
</li>
|
||||
<li>
|
||||
Open the <a href="//flashii.net">flashii.net</a>
|
||||
home page, and then look for links to the information you want.
|
||||
</li>
|
||||
<li>
|
||||
Click the <img src="//static.flash.moe/images/404-back.gif"><a href="javascript:void(0);" onclick="history.go(-1);">Back</a>
|
||||
button to try another link.
|
||||
</li>
|
||||
<li>
|
||||
Click <img src="//static.flash.moe/images/404-search.gif"><a href="//flashii.net/search.php">Search</a>
|
||||
to look for information on the Internet.
|
||||
</li>
|
||||
</ul>
|
||||
<h3>
|
||||
HTTP 404 - File not found
|
||||
<br>
|
||||
Internet Explorer
|
||||
</h3>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
186
public/booru.php
186
public/booru.php
|
@ -1,186 +0,0 @@
|
|||
<?php
|
||||
define('BOORUS', [
|
||||
'danbooru',
|
||||
'gelbooru',
|
||||
'yandere',
|
||||
]);
|
||||
|
||||
define('BOORU_INFOS', [
|
||||
'danbooru' => [
|
||||
'title' => 'Danbooru',
|
||||
'type' => 'danbooru',
|
||||
'randomUrlFormat' => 'https://danbooru.donmai.us/posts.json?limit=20&tags=order:random+limit:20+%s',
|
||||
'postUrlFormat' => 'https://danbooru.donmai.us/posts/%s',
|
||||
'implicitTags' => [
|
||||
'misaka_mikoto' => ['-shokuhou_misaki'],
|
||||
'shokuhou_misaki' => ['-misaka_mikoto'],
|
||||
'kagari_(rewrite)' => ['-screencap'],
|
||||
'kasuga_ayumu' => ['-rating:explicit'],
|
||||
'osaka_(azumanga_daioh)' => ['-rating:explicit'],
|
||||
'yazawa_nico' => ['-nishikino_maki'],
|
||||
'nishikino_maki' => ['-yazawa_nico'],
|
||||
'heanna_sumire' => ['-tang_keke'],
|
||||
'tang_keke' => ['-heanna_sumire'],
|
||||
],
|
||||
],
|
||||
'gelbooru' => [
|
||||
'title' => 'Gelbooru',
|
||||
'type' => 'gelbooru',
|
||||
'randomUrlFormat' => 'https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1&limit=20&tags=sort:random+%s',
|
||||
'postUrlFormat' => 'https://gelbooru.com/index.php?page=post&s=view&id=%s',
|
||||
'implicitTags' => [
|
||||
'misaka_mikoto' => ['-shokuhou_misaki'],
|
||||
'shokuhou_misaki' => ['-misaka_mikoto'],
|
||||
'kagari_(rewrite)' => ['-screencap'],
|
||||
'kasuga_ayumu' => ['-rating:explicit'],
|
||||
'yazawa_nico' => ['-nishikino_maki'],
|
||||
'nishikino_maki' => ['-yazawa_nico'],
|
||||
'heanna_sumire' => ['-tang_keke'],
|
||||
'tang_keke' => ['-heanna_sumire'],
|
||||
],
|
||||
],
|
||||
'yandere' => [
|
||||
'title' => 'Yande.re',
|
||||
'type' => 'yandere',
|
||||
'randomUrlFormat' => 'https://yande.re/post.json?api_version=2&limit=20&tags=order:random+%s',
|
||||
'postUrlFormat' => 'https://yande.re/post/show/%s',
|
||||
'implicitTags' => [
|
||||
'misaka_mikoto' => ['-shokuhou_misaki'],
|
||||
'shokuhou_misaki' => ['-misaka_mikoto'],
|
||||
'kagari_(rewrite)' => ['-cap'],
|
||||
'kasuga_ayumu' => ['-rating:explicit'],
|
||||
'yazawa_nico' => ['-nishikino_maki'],
|
||||
'nishikino_maki' => ['-yazawa_nico'],
|
||||
'heanna_sumire' => ['-tang_keke'],
|
||||
'tang_keke' => ['-heanna_sumire'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$config = parse_ini_file(__DIR__ . '/../config/flashii.ini');
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$booru = (string)filter_input(INPUT_GET, 'b');
|
||||
$tags = (string)filter_input(INPUT_GET, 't');
|
||||
$tags = array_filter(explode(' ', trim($tags)));
|
||||
|
||||
$randomSource = empty($booru);
|
||||
|
||||
if($randomSource)
|
||||
$booru = BOORUS[0];
|
||||
elseif(!in_array($booru, BOORUS))
|
||||
die('{"error":"booru"}');
|
||||
|
||||
if(!array_key_exists($booru, BOORU_INFOS))
|
||||
die('{"error":"booru-info"}');
|
||||
|
||||
$booruInfo = BOORU_INFOS[$booru];
|
||||
|
||||
if(empty($booruInfo['randomUrlFormat']))
|
||||
die('{"error":"random"}');
|
||||
if(empty($booruInfo['postUrlFormat']))
|
||||
die('{"error":"post"}');
|
||||
if(empty($booruInfo['type']))
|
||||
die('{"error":"type"}');
|
||||
|
||||
$isDanbooru = $booruInfo['type'] === 'danbooru';
|
||||
$isGelbooru = $booruInfo['type'] === 'gelbooru';
|
||||
$isYandere = $booruInfo['type'] === 'yandere';
|
||||
|
||||
if(!empty($booruInfo['implicitTags'])) {
|
||||
$originalTags = $tags;
|
||||
foreach($booruInfo['implicitTags'] as $targetTag => $addTags) {
|
||||
if(!in_array($targetTag, $originalTags))
|
||||
continue;
|
||||
|
||||
foreach($addTags as $tag)
|
||||
if(!in_array($tag, $tags))
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$tagString = implode('+', array_map(fn($x) => urlencode($x), $tags));
|
||||
|
||||
$curl = curl_init(sprintf($booruInfo['randomUrlFormat'], $tagString));
|
||||
|
||||
$headers = ['Accept: application/json'];
|
||||
if($isDanbooru)
|
||||
$headers[] = 'Authorization: Basic ' . base64_encode($config['danbooru-token']);
|
||||
|
||||
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/20230202 (+https://fii.moe/satori)',
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
]);
|
||||
$response = json_decode(curl_exec($curl));
|
||||
curl_close($curl);
|
||||
|
||||
$posts = [];
|
||||
|
||||
// gelbooru and yandere block remote embedding, so not even going to bother with a file_url for those
|
||||
|
||||
if($isDanbooru) {
|
||||
if(!empty($response) && is_array($response)) {
|
||||
foreach($response as $raw) {
|
||||
$posts[] = $post = new stdClass;
|
||||
$post->id = $raw->id;
|
||||
$post->rating = $raw->rating;
|
||||
if(!empty($raw->file_url))
|
||||
$post->file_url = $raw->file_url;
|
||||
$post->post_url = sprintf($booruInfo['postUrlFormat'], $raw->id);
|
||||
$post->tags = explode(' ', $raw->tag_string);
|
||||
$post->tags_general = explode(' ', $raw->tag_string_general);
|
||||
$post->tags_character = explode(' ', $raw->tag_string_character);
|
||||
$post->tags_copyright = explode(' ', $raw->tag_string_copyright);
|
||||
$post->tags_artist = explode(' ', $raw->tag_string_artist);
|
||||
$post->tags_meta = explode(' ', $raw->tag_string_meta);
|
||||
}
|
||||
}
|
||||
} elseif($isGelbooru) {
|
||||
if(!empty($response) && !empty($response->post) && is_array($response->post)) {
|
||||
foreach($response->post as $raw) {
|
||||
$posts[] = $post = new stdClass;
|
||||
$post->id = $raw->id;
|
||||
$post->rating = $raw->rating[0];
|
||||
//if(!empty($raw->file_url))
|
||||
// $post->file_url = $raw->file_url;
|
||||
$post->post_url = sprintf($booruInfo['postUrlFormat'], $raw->id);
|
||||
$post->tags = explode(' ', $raw->tags);
|
||||
}
|
||||
}
|
||||
} elseif($isYandere) {
|
||||
if(!empty($response) && !empty($response->posts) && is_array($response->posts)) {
|
||||
foreach($response->posts as $raw) {
|
||||
$posts[] = $post = new stdClass;
|
||||
$post->id = $raw->id;
|
||||
$post->rating = $raw->rating;
|
||||
//if(!empty($raw->file_url))
|
||||
// $post->file_url = $raw->file_url;
|
||||
$post->post_url = sprintf($booruInfo['postUrlFormat'], $raw->id);
|
||||
$post->tags = explode(' ', $raw->tags);
|
||||
}
|
||||
}
|
||||
} else
|
||||
die('{"error":"support"}');
|
||||
|
||||
echo json_encode([
|
||||
'source' => [
|
||||
'name' => $booru,
|
||||
'type' => $booruInfo['type'],
|
||||
'title' => $booruInfo['title'] ?? $booru,
|
||||
],
|
||||
'tags' => $tags,
|
||||
'posts' => $posts,
|
||||
]);
|
|
@ -1,93 +0,0 @@
|
|||
<?php
|
||||
$config = parse_ini_file(__DIR__ . '/../config/flashii.ini');
|
||||
require_once $config['msz-path'] . '/vendor/flashwave/index/index.php';
|
||||
|
||||
try {
|
||||
$db = \Index\Data\DbTools::create($config['exrate-dsn2']);
|
||||
$db->execute('SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';');
|
||||
} catch(Exception $ex) {
|
||||
die((string)$ex);
|
||||
}
|
||||
|
||||
define('EXRATE_INTER', 'EUR');
|
||||
define('EXRATE_COMMON', [
|
||||
'EUR', 'AUD', 'GBP', 'CAD', 'USD', 'JPY', 'PLN', 'SGD', 'RUB', 'ILS',
|
||||
]);
|
||||
|
||||
$from = strtoupper((string)filter_input(INPUT_GET, 'from'));
|
||||
$to = strtoupper((string)filter_input(INPUT_GET, 'to'));
|
||||
$amount = (string)(filter_input(INPUT_GET, 'amount', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) ?? '1');
|
||||
|
||||
if((!empty($to) && strlen($to) !== 3) || strlen($from) !== 3) {
|
||||
http_response_code(400);
|
||||
die('Invalid currency specified.');
|
||||
}
|
||||
|
||||
$needsRefresh = $db->query('SELECT MAX(rate_stored) > NOW() - INTERVAL 1 DAY FROM `exchange-rates` LIMIT 1');
|
||||
$needsRefresh->next();
|
||||
$needsRefresh = $needsRefresh->isNull(0) || $needsRefresh->getInteger(0) < 1;
|
||||
|
||||
if($needsRefresh) {
|
||||
$data = json_decode(file_get_contents('https://api.exchangerate.host/latest?base=' . EXRATE_INTER), true);
|
||||
if($data !== null) {
|
||||
$db->execute('TRUNCATE `exchange-rates`');
|
||||
$insertCurrency = $db->prepare('INSERT INTO `exchange-rates` (rate_from, rate_to, rate_value) VALUES (?, ?, ?)');
|
||||
foreach($data['rates'] as $currency => $rate) {
|
||||
$insertCurrency->reset();
|
||||
$insertCurrency->addParameter(1, $data['base']);
|
||||
$insertCurrency->addParameter(2, $currency);
|
||||
$insertCurrency->addParameter(3, $rate);
|
||||
$insertCurrency->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = new stdClass;
|
||||
$result->from = $from;
|
||||
$result->to = $to;
|
||||
$result->amount = (float)$amount;
|
||||
|
||||
if($from === $to) {
|
||||
$result->result = $result->amount;
|
||||
} else {
|
||||
$convertCurrency = $db->prepare(sprintf(
|
||||
'SELECT (SELECT (? / rate_value) FROM `exchange-rates` WHERE rate_from = "%1$s" AND rate_to = ?) * rate_value FROM `exchange-rates` WHERE rate_from = "%1$s" AND rate_to = ?',
|
||||
EXRATE_INTER
|
||||
));
|
||||
|
||||
if(empty($to)) {
|
||||
$result->results = [];
|
||||
foreach(EXRATE_COMMON as $commonCurrency) {
|
||||
if($commonCurrency === $from)
|
||||
continue;
|
||||
|
||||
$result->results[] = $current = new stdClass;
|
||||
$current->to = $commonCurrency;
|
||||
|
||||
$convertCurrency->reset();
|
||||
$convertCurrency->addParameter(1, $amount);
|
||||
$convertCurrency->addParameter(2, $from);
|
||||
$convertCurrency->addParameter(3, $commonCurrency);
|
||||
$convertCurrency->execute();
|
||||
|
||||
$convertResult = $convertCurrency->getResult();
|
||||
$convertResult->next();
|
||||
|
||||
$current->result = $convertResult->getFloat(0);
|
||||
}
|
||||
} else {
|
||||
$convertCurrency->addParameter(1, $amount);
|
||||
$convertCurrency->addParameter(2, $from);
|
||||
$convertCurrency->addParameter(3, $to);
|
||||
$convertCurrency->execute();
|
||||
|
||||
$convertResult = $convertCurrency->getResult();
|
||||
$convertResult->next();
|
||||
|
||||
$result->result = $convertResult->getFloat(0);
|
||||
}
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($result);
|
|
@ -1,167 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* (21:42:47) malloc_CDLVII: i think that's one of the two rules of Flash Wave Development
|
||||
* (21:42:57) malloc_CDLVII: 1. if it is written, it will be rewritten
|
||||
* (21:43:04) malloc_CDLVII: 2. it will have automatic updates
|
||||
*/
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
ini_set('display_errors', 'on');
|
||||
error_reporting(-1);
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] !== 'POST')
|
||||
die('no');
|
||||
|
||||
$config = __DIR__ . '/../config/git-broadcast.ini';
|
||||
if(!is_file($config))
|
||||
die('config missing');
|
||||
|
||||
$config = parse_ini_file($config, true);
|
||||
if(empty($config['tokens']['token']))
|
||||
die('config invalid');
|
||||
|
||||
$isGitea = isset($_SERVER['HTTP_X_GITEA_DELIVERY']) && isset($_SERVER['HTTP_X_GITEA_EVENT']);
|
||||
|
||||
$rawData = file_get_contents('php://input');
|
||||
$sigParts = $isGitea
|
||||
? ['sha256', $_SERVER['HTTP_X_GITEA_SIGNATURE']]
|
||||
: explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE'] ?? '', 2);
|
||||
|
||||
if(empty($sigParts[1]))
|
||||
die('invalid signature');
|
||||
|
||||
$repoAuthenticated = false;
|
||||
foreach($config['tokens']['token'] as $repoName => $repoToken) {
|
||||
if(hash_equals(hash_hmac($sigParts[0], $rawData, $repoToken), $sigParts[1])) {
|
||||
$repoAuthenticated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$repoAuthenticated)
|
||||
die('signature check failed');
|
||||
|
||||
$data = json_decode($_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded'
|
||||
? $_POST['payload']
|
||||
: $rawData);
|
||||
|
||||
if(empty($data))
|
||||
die('body is corrupt');
|
||||
|
||||
if(empty($data->repository->full_name))
|
||||
die('body is corrupt');
|
||||
|
||||
if($data->repository->full_name !== $repoName)
|
||||
die('invalid repository token');
|
||||
|
||||
$message = '';
|
||||
|
||||
switch($_SERVER['HTTP_X_GITHUB_EVENT']) {
|
||||
case 'ping':
|
||||
$message = "ping received from {$data->repository->full_name}!";
|
||||
break;
|
||||
|
||||
case 'create':
|
||||
switch ($data->ref_type) {
|
||||
case 'tag':
|
||||
$message = "[b][url={$data->repository->html_url}]{$data->repository->full_name}[/url][/b]: [url={$data->sender->html_url}]{$data->sender->login}[/url] created tag [url={$data->repository->html_url}/releases/tag/{$data->ref}]{$data->ref}[/url]";
|
||||
break;
|
||||
|
||||
case 'branch':
|
||||
$message = "[b][url={$data->repository->html_url}]{$data->repository->full_name}[/url][/b]: [url={$data->sender->html_url}]{$data->sender->login}[/url] created branch [url={$data->repository->html_url}/tree/{$data->ref}]{$data->ref}[/url]";
|
||||
break;
|
||||
|
||||
case 'repository':
|
||||
// doubt we'll ever get here
|
||||
// TODO: test organisation level webhooks?
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
switch($data->ref_type) {
|
||||
case 'tag':
|
||||
$message = "[b][url={$data->repository->html_url}]{$data->repository->full_name}[/url][/b]: [url={$data->sender->html_url}]{$data->sender->login}[/url] deleted tag {$data->ref}";
|
||||
break;
|
||||
|
||||
case 'branch':
|
||||
$message = "[b][url={$data->repository->html_url}]{$data->repository->full_name}[/url][/b]: [url={$data->sender->html_url}]{$data->sender->login}[/url] deleted branch {$data->ref}";
|
||||
break;
|
||||
|
||||
case 'repository':
|
||||
$message = "[b]{$data->repository->full_name}[/b]: [url={$data->sender->html_url}]{$data->sender->login}[/url] deleted the repository :crying:";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'push':
|
||||
$commitCount = count($data->commits);
|
||||
if($commitCount < 1)
|
||||
break;
|
||||
|
||||
$message .= "[b]{$data->repository->full_name}:" . substr($data->ref, strrpos($data->ref, '/') + 1) . "[/b] {$commitCount} new commit" . ($commitCount === 1 ? '' : 's') . "\r\n";
|
||||
|
||||
foreach($data->commits as $commit) {
|
||||
$timestamp = strtotime($commit->timestamp);
|
||||
$commit->message = trim($commit->message);
|
||||
$message .= "[b][url={$commit->url}][code]" . substr($commit->id, 0, 7) . "[/code][/url][/b] {$commit->message} - [url=" . ($isGitea ? 'https://git.flash.moe/' : 'https://github.com/') . "{$commit->author->username}]{$commit->author->username}[/url]\r\n";
|
||||
}
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
$status_colour['failure'] = '#cb2431';
|
||||
//$status_colour['pending'] = '#b5ab86';
|
||||
$status_colour['success'] = '#28a745';
|
||||
$status_colour['error'] = '#586069';
|
||||
|
||||
$status_emotes['failure'] = [':angry:', ':angrier:', ':angriest:', ':sad:', ':ouch:', ':dizzy:', ':sweat:', ':fat:', ':jew:'];
|
||||
$status_emotes['success'] = [':happy:', ':lol:', ':yay:'];
|
||||
$status_emotes['error'] = [':blank:'];
|
||||
|
||||
$status_name['continuous-integration/travis-ci/push'] = 'Unit Tests';
|
||||
$status_name['continuous-integration/styleci/push'] = 'Style Check';
|
||||
|
||||
if (!array_key_exists($data->context, $status_name) || !array_key_exists($data->state, $status_colour)) {
|
||||
//$message = "/msg flash {$data->context}: {$data->state}";
|
||||
break;
|
||||
}
|
||||
|
||||
$message = "[b][url={$data->commit->html_url}]{$data->repository->full_name}[/url] [url={$data->target_url}]" . $status_name[$data->context] . '[/url][/b]: [b][i][color=' . $status_colour[$data->state] . ']' . ucfirst($data->state) . ' ' . $status_emotes[$data->state][array_rand($status_emotes[$data->state])] . '[/color][/i][/b]';
|
||||
break;
|
||||
|
||||
case 'watch':
|
||||
switch($data->action) {
|
||||
case 'started':
|
||||
$message = "[url={$data->sender->html_url}]{$data->sender->login}[/url] starred [url={$data->repository->html_url}]{$data->repository->full_name}[/url] :love:";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'fork':
|
||||
$message = "[url={$data->forkee->html_url}]{$data->forkee->owner->login}[/url] forked [url={$data->repository->html_url}]{$data->repository->full_name}[/url] :omg:";
|
||||
break;
|
||||
|
||||
case 'issues':
|
||||
if(!in_array($data->action, ['opened', /*'edited',*/ 'closed', 'reopened']))
|
||||
break;
|
||||
|
||||
$message = "[b][url={$data->repository->html_url}]{$data->repository->full_name}[/url] [url={$data->sender->html_url}]{$data->sender->login}[/url] {$data->action} issue [/b][url={$data->issue->html_url}][b]#{$data->issue->number}[/b]: {$data->issue->title}[/url]";
|
||||
break;
|
||||
|
||||
case 'pull_request':
|
||||
if(!in_array($data->action, ['opened', /*'edited',*/ 'closed', 'reopened']))
|
||||
break;
|
||||
|
||||
$message = "[b][url={$data->repository->html_url}]{$data->repository->full_name}[/url]: [url={$data->sender->html_url}]{$data->sender->login}[/url] {$data->action} pull request [/b][url={$data->pull_request->html_url}][b]#{$data->pull_request->number}[/b]: {$data->pull_request->title}[/url]";
|
||||
break;
|
||||
}
|
||||
|
||||
if(!empty($message)) {
|
||||
// the
|
||||
var_dump($message);
|
||||
$sock = fsockopen($config['boat']['host'], $config['boat']['port'], $errno, $errstr, 5);
|
||||
$message = chr(1) . '/msg flash ' . $message;
|
||||
$message = hash_hmac('sha256', $message, $config['boat']['secret'], true) . $message;
|
||||
fwrite($sock, $message);
|
||||
}
|
|
@ -1,50 +1,21 @@
|
|||
<?php
|
||||
http_response_code(404);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Cannot find page</title>
|
||||
<link rel="stylesheet" type="text/css" href="/satori.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap">
|
||||
<h1>
|
||||
<img src="//static.flash.moe/images/404-info.gif"/>
|
||||
The page cannot be found
|
||||
</h1>
|
||||
<p>
|
||||
The page you are looking for might have been removed, had its
|
||||
name changed, or is temporarily unavailable.
|
||||
</p>
|
||||
<hr>
|
||||
<h2>
|
||||
Please try the following:
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
If you typed the page address in the Address bar, make
|
||||
sure that it is spelled correctly.
|
||||
</li>
|
||||
<li>
|
||||
Open the <a href="//flashii.net">flashii.net</a>
|
||||
home page, and then look for links to the information you want.
|
||||
</li>
|
||||
<li>
|
||||
Click the <img src="//static.flash.moe/images/404-back.gif"/><a href="javascript:void(0);" onclick="history.go(-1);">Back</a>
|
||||
button to try another link.
|
||||
</li>
|
||||
<li>
|
||||
Click <img src="//static.flash.moe/images/404-search.gif"/><a href="//flashii.net/search.php">Search</a>
|
||||
to look for information on the Internet.
|
||||
</li>
|
||||
</ul>
|
||||
<h3>
|
||||
HTTP 404 - File not found
|
||||
<br/>
|
||||
Internet Explorer
|
||||
</h3>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
namespace Satori;
|
||||
|
||||
require_once __DIR__ . '/../satori.php';
|
||||
|
||||
set_exception_handler(function(\Throwable $ex) {
|
||||
http_response_code(500);
|
||||
ob_clean();
|
||||
|
||||
if(SAT_DEBUG) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo (string)$ex;
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo file_get_contents(SAT_DIR_PUBLIC . '/404.html');
|
||||
exit;
|
||||
});
|
||||
|
||||
$sat->createRouting()->dispatch();
|
||||
|
|
1018
public/splatoon.php
1018
public/splatoon.php
File diff suppressed because it is too large
Load diff
|
@ -1,150 +0,0 @@
|
|||
<?php
|
||||
define('TL_FORMAT', strtolower($_GET['fmt'] ?? 'json'));
|
||||
|
||||
if (isset($_SERVER['HTTP_USER_AGENT'], $_GET['pretty']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Mozilla/') !== false)
|
||||
define('JSON_ARGS', JSON_PRETTY_PRINT);
|
||||
else
|
||||
define('JSON_ARGS', 0);
|
||||
|
||||
function out($arr, $mode) {
|
||||
$output = '';
|
||||
$mimeType = 'text/plain';
|
||||
|
||||
switch (TL_FORMAT) {
|
||||
case 'xml':
|
||||
$mimeType = 'application/xml';
|
||||
$output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
|
||||
|
||||
switch ($mode) {
|
||||
case 'list':
|
||||
$type = get_class($arr[0]);
|
||||
$output .= "<ArrayOf{$type}>";
|
||||
foreach ($arr as $part) {
|
||||
$output .= "<{$type} ";
|
||||
|
||||
foreach (get_object_vars($part) as $name => $value) {
|
||||
$output .= "{$name}=\"{$value}\" ";
|
||||
}
|
||||
|
||||
$output .= '/>';
|
||||
}
|
||||
$output .= "</ArrayOf{$type}>";
|
||||
break;
|
||||
|
||||
case 'do':
|
||||
$output .= "<Translation ";
|
||||
|
||||
foreach ($arr as $name => $value) {
|
||||
$value = htmlspecialchars($value);
|
||||
$output .= "{$name}=\"{$value}\" ";
|
||||
}
|
||||
|
||||
$output .= '/>';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
default:
|
||||
$mimeType = 'application/json';
|
||||
$output = json_encode($arr, JSON_ARGS);
|
||||
break;
|
||||
}
|
||||
|
||||
header("Content-Type: {$mimeType}; charset=utf-8");
|
||||
die($output);
|
||||
}
|
||||
|
||||
define('TRANSLATE_URL', 'https://translate.google.com/translate_a/single?ie=UTF-8&oe=UTF-8&multires=1&client=gtx&sl=%s&tl=%s&dt=t&q=%s');
|
||||
define('CURL_USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0');
|
||||
define('MAX_TRANSL_LENGTH', 630);
|
||||
|
||||
function curl_req($url) {
|
||||
$curl = curl_init($url);
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
|
||||
CURLOPT_USERAGENT => CURL_USER_AGENT,
|
||||
]);
|
||||
|
||||
return curl_exec($curl);
|
||||
}
|
||||
|
||||
class Language {
|
||||
public $code;
|
||||
public $name;
|
||||
|
||||
public function __construct($code, $name) {
|
||||
$this->code = $code;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public static function fromCode($code) {
|
||||
return new Language($code, locale_get_display_name($code));
|
||||
}
|
||||
}
|
||||
|
||||
$languages = [
|
||||
'af', 'sq', 'ar', 'hy', 'az', 'eu', 'be', 'bn', 'bs',
|
||||
'bg', 'ca', 'ny', 'co', 'hr', 'cs', 'da', 'nl', 'en',
|
||||
'eo', 'tl', 'fi', 'fr', 'fy', 'gl', 'ka', 'de', 'el',
|
||||
'gu', 'ht', 'ha', 'iw', 'hi', 'hu', 'is', 'ig', 'id',
|
||||
'ga', 'ja', 'jw', 'kn', 'kk', 'km', 'ko', 'ku', 'ky',
|
||||
'lo', 'la', 'lv', 'lt', 'lb', 'mk', 'mg', 'ms', 'ml',
|
||||
'mt', 'mi', 'mr', 'mn', 'my', 'ne', 'no', 'ps', 'fa',
|
||||
'pl', 'pt', 'ma', 'ro', 'ru', 'sm', 'gd', 'sr', 'st',
|
||||
'sn', 'sd', 'si', 'sk', 'sl', 'so', 'es', 'su', 'sw',
|
||||
'sv', 'tg', 'ta', 'te', 'th', 'tr', 'uk', 'ur', 'uz',
|
||||
'vi', 'cy', 'xh', 'yi', 'yo', 'zu', 'it',
|
||||
'ceb', 'haw', 'hmn',
|
||||
'zh-cn', 'zh-tw',
|
||||
];
|
||||
|
||||
$mode = $_GET['m'] ?? null;
|
||||
|
||||
switch ($mode) {
|
||||
case 'list':
|
||||
$out = [];
|
||||
|
||||
foreach ($languages as $lang)
|
||||
$out[] = Language::fromCode($lang);
|
||||
|
||||
out($out, $mode);
|
||||
|
||||
case 'do':
|
||||
$from = strtolower($_GET['f'] ?? 'auto');
|
||||
$to = strtolower($_GET['t'] ?? 'en');
|
||||
|
||||
if ($from !== 'auto' && !in_array($from, $languages))
|
||||
out(['error' => 'The given origin language is invalid.'], $mode);
|
||||
|
||||
if (!in_array($to, $languages))
|
||||
out(['error' => 'The given destination language is invalid.'], $mode);
|
||||
|
||||
$text = isset($_GET['z']) ? $_GET['z'] : file_get_contents('php://input');
|
||||
$t_len = strlen($text);
|
||||
|
||||
if ($t_len < 1)
|
||||
out(['error' => 'Nothing to translate.'], $mode);
|
||||
|
||||
if ($t_len > MAX_TRANSL_LENGTH)
|
||||
out(['error' => 'Too much text, please split some up.'], $mode);
|
||||
|
||||
$google_txt = curl_req(sprintf(TRANSLATE_URL, $from, $to, rawurlencode($text)));
|
||||
|
||||
$google = json_decode($google_txt);
|
||||
|
||||
if (strlen($google_txt) < 1 || !$google)
|
||||
out(['error' => 'Something happened.'], $mode);
|
||||
|
||||
$result = $google[0][0][0];
|
||||
$original = $google[0][0][1];
|
||||
$from = $google[2];
|
||||
$from_text = locale_get_display_name($from);
|
||||
$to_text = locale_get_display_name($to);
|
||||
|
||||
out(compact('from', 'from_text', 'to', 'to_text', 'original', 'result'), $mode);
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
$config = parse_ini_file(__DIR__ . '/../config/flashii.ini');
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$word = (string)filter_input(INPUT_GET, 'word');
|
||||
if(empty($word))
|
||||
die('{"error":"word"}');
|
||||
|
||||
$response = json_decode(file_get_contents(sprintf(
|
||||
'https://api.wordnik.com/v4/word.json/%s/definitions?limit=5&includeRelated=false&sourceDictionaries=ahd-5%%2Cwordnet&useCanonical=true&includeTags=false&api_key=%s',
|
||||
rawurlencode($word),
|
||||
$config['wordnik-token']
|
||||
)));
|
||||
|
||||
if(empty($response))
|
||||
die('{"error":"response"}');
|
||||
|
||||
$defs = [];
|
||||
|
||||
foreach($response as $def) {
|
||||
if(empty($def->text))
|
||||
continue;
|
||||
|
||||
$defs[] = [
|
||||
'word' => $def->word ?? '',
|
||||
'pos' => $def->partOfSpeech ?? '',
|
||||
'attr' => $def->attributionText ?? '',
|
||||
'attr_url' => $def->attributionUrl ?? '',
|
||||
'dict' => $def->sourceDictionary ?? '',
|
||||
'text' => strtr($def->text, [
|
||||
'<em>' => '[i]',
|
||||
'</em>' => '[/i]',
|
||||
]),
|
||||
'url' => $def->wordnikUrl ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode($defs);
|
25
satori.php
Normal file
25
satori.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace Satori;
|
||||
|
||||
use Index\Environment;
|
||||
use Index\Data\DbTools;
|
||||
use Syokuhou\SharpConfig;
|
||||
|
||||
define('SAT_ROOT', __DIR__);
|
||||
define('SAT_DIR_MIGRATIONS', SAT_ROOT . '/database');
|
||||
define('SAT_DIR_PUBLIC', SAT_ROOT . '/public');
|
||||
define('SAT_DEBUG', is_file(SAT_ROOT . '/.debug'));
|
||||
define('SAT_VERSION', '20231029');
|
||||
|
||||
require_once SAT_ROOT . '/vendor/autoload.php';
|
||||
|
||||
Environment::setDebug(SAT_DEBUG);
|
||||
mb_internal_encoding('utf-8');
|
||||
date_default_timezone_set('utc');
|
||||
|
||||
$cfg = SharpConfig::fromFile(SAT_ROOT . '/config.cfg');
|
||||
|
||||
$dbc = DbTools::create($cfg->getString('database:dsn', 'null'));
|
||||
$dbc->execute('SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';');
|
||||
|
||||
$sat = new SatoriContext($cfg, $dbc);
|
30
src/Booru/BooruContext.php
Normal file
30
src/Booru/BooruContext.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
namespace Satori\Booru;
|
||||
|
||||
use Syokuhou\IConfig;
|
||||
|
||||
class BooruContext {
|
||||
private array $sources = [];
|
||||
|
||||
public function __construct(private IConfig $config) {
|
||||
$this->sources[] = new Danbooru\DanbooruSource($config->scopeTo('danbooru'));
|
||||
$this->sources[] = new Gelbooru\GelbooruSource($config->scopeTo('gelbooru'));
|
||||
$this->sources[] = new Konachan\KonachanSource($config->scopeTo('konachan'));
|
||||
$this->sources[] = new Yandere\YandereSource($config->scopeTo('yandere'));
|
||||
}
|
||||
|
||||
public function getConfig(): IConfig {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function getSources(): array {
|
||||
return $this->sources;
|
||||
}
|
||||
|
||||
public function getSourceByName(string $name): ?IBooruSource {
|
||||
foreach($this->sources as $source)
|
||||
if($source->getInfo()->getName() === $name)
|
||||
return $source;
|
||||
return null;
|
||||
}
|
||||
}
|
16
src/Booru/BooruErrorResponse.php
Normal file
16
src/Booru/BooruErrorResponse.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Satori\Booru;
|
||||
|
||||
class BooruErrorResponse implements \JsonSerializable {
|
||||
public function __construct(
|
||||
private string $code
|
||||
) {}
|
||||
|
||||
public function getCode(): string {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed {
|
||||
return ['error' => $this->code];
|
||||
}
|
||||
}
|
24
src/Booru/BooruQueryResponse.php
Normal file
24
src/Booru/BooruQueryResponse.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
namespace Satori\Booru;
|
||||
|
||||
class BooruQueryResponse implements \JsonSerializable {
|
||||
public function __construct(
|
||||
private BooruSourceInfo $source,
|
||||
private array $posts
|
||||
) {}
|
||||
|
||||
public function getSource(): BooruSourceInfo {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
public function getPosts(): array {
|
||||
return $this->posts;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed {
|
||||
return [
|
||||
'source' => $this->source,
|
||||
'posts' => $this->posts,
|
||||
];
|
||||
}
|
||||
}
|
60
src/Booru/BooruRoutes.php
Normal file
60
src/Booru/BooruRoutes.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
namespace Satori\Booru;
|
||||
|
||||
use stdClass;
|
||||
use Index\XArray;
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
|
||||
class BooruRoutes extends RouteHandler {
|
||||
public function __construct(
|
||||
private BooruContext $context
|
||||
) {}
|
||||
|
||||
private static function makePostsJsonReady(array $posts): array {
|
||||
return XArray::select($posts, function($post) {
|
||||
$output = new stdClass;
|
||||
|
||||
if($post instanceof IBooruPost) {
|
||||
$output->id = (int)$post->getId();
|
||||
$output->rating = $post->getRating();
|
||||
if($post->hasFileUrl())
|
||||
$output->file_url = $post->getFileUrl();
|
||||
$output->post_url = $post->getPostUrl();
|
||||
$output->tags = $post->getTags();
|
||||
|
||||
if($post instanceof IBooruPostWithSpecificTags) {
|
||||
$output->tags_general = $post->getGeneralTags();
|
||||
$output->tags_character = $post->getCharacterTags();
|
||||
$output->tags_copyright = $post->getCopyrightTags();
|
||||
$output->tags_artist = $post->getArtistTags();
|
||||
$output->tags_meta = $post->getMetaTags();
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
});
|
||||
}
|
||||
|
||||
#[Route('GET', '/booru.php')]
|
||||
public function getPHP($response, $request) {
|
||||
$sourceName = (string)$request->getParam('b');
|
||||
$tags = (string)$request->getParam('t');
|
||||
|
||||
if($sourceName === '') {
|
||||
$sources = $this->context->getSources();
|
||||
if(empty($sources))
|
||||
return new BooruErrorResponse('none');
|
||||
$source = $sources[0];
|
||||
} else
|
||||
$source = $this->context->getSourceByName($sourceName);
|
||||
|
||||
if($source === null)
|
||||
return new BooruErrorResponse('booru');
|
||||
|
||||
return new BooruQueryResponse(
|
||||
$source->getInfo(),
|
||||
self::makePostsJsonReady($source->getPosts($tags))
|
||||
);
|
||||
}
|
||||
}
|
25
src/Booru/BooruSourceInfo.php
Normal file
25
src/Booru/BooruSourceInfo.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace Satori\Booru;
|
||||
|
||||
class BooruSourceInfo implements \JsonSerializable {
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $title
|
||||
) {}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed {
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'type' => $this->name,
|
||||
'title' => $this->title,
|
||||
];
|
||||
}
|
||||
}
|
52
src/Booru/Danbooru/DanbooruPost.php
Normal file
52
src/Booru/Danbooru/DanbooruPost.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
namespace Satori\Booru\Danbooru;
|
||||
|
||||
use Satori\Booru\IBooruPostWithSpecificTags;
|
||||
|
||||
class DanbooruPost implements IBooruPostWithSpecificTags {
|
||||
public function __construct(private array $info) {}
|
||||
|
||||
public function getId(): string {
|
||||
return (string)$this->info['id'];
|
||||
}
|
||||
|
||||
public function getRating(): string {
|
||||
return $this->info['rating'];
|
||||
}
|
||||
|
||||
public function getPostUrl(): string {
|
||||
return sprintf('https://danbooru.donmai.us/posts/%d', $this->info['id']);
|
||||
}
|
||||
|
||||
public function getTags(): array {
|
||||
return explode(' ', $this->info['tag_string']);
|
||||
}
|
||||
|
||||
public function hasFileUrl(): bool {
|
||||
return array_key_exists('file_url', $this->info);
|
||||
}
|
||||
|
||||
public function getFileUrl(): string {
|
||||
return $this->info['file_url'];
|
||||
}
|
||||
|
||||
public function getGeneralTags(): array {
|
||||
return explode(' ', $this->info['tag_string_general']);
|
||||
}
|
||||
|
||||
public function getCharacterTags(): array {
|
||||
return explode(' ', $this->info['tag_string_character']);
|
||||
}
|
||||
|
||||
public function getCopyrightTags(): array {
|
||||
return explode(' ', $this->info['tag_string_copyright']);
|
||||
}
|
||||
|
||||
public function getArtistTags(): array {
|
||||
return explode(' ', $this->info['tag_string_artist']);
|
||||
}
|
||||
|
||||
public function getMetaTags(): array {
|
||||
return explode(' ', $this->info['tag_string_meta']);
|
||||
}
|
||||
}
|
62
src/Booru/Danbooru/DanbooruSource.php
Normal file
62
src/Booru/Danbooru/DanbooruSource.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
namespace Satori\Booru\Danbooru;
|
||||
|
||||
use Index\XArray;
|
||||
use Syokuhou\IConfig;
|
||||
use Satori\Booru\BooruSourceInfo;
|
||||
use Satori\Booru\IBooruSource;
|
||||
|
||||
class DanbooruSource implements IBooruSource {
|
||||
private const POSTS_URL = 'https://danbooru.donmai.us/posts.json';
|
||||
|
||||
private BooruSourceInfo $info;
|
||||
|
||||
public function __construct(private IConfig $config) {
|
||||
$this->info = new BooruSourceInfo('danbooru', 'Danbooru');
|
||||
}
|
||||
|
||||
public function getInfo(): BooruSourceInfo {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
public function getPosts(string $tags, bool $random = true, int $limit = 20): array {
|
||||
$params = [];
|
||||
if($tags !== '')
|
||||
$params['tags'] = $tags;
|
||||
if($limit > 0)
|
||||
$params['limit'] = $limit;
|
||||
if($random)
|
||||
$params['random'] = '1';
|
||||
|
||||
$url = self::POSTS_URL;
|
||||
if(!empty($params))
|
||||
$url .= '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
|
||||
|
||||
$curl = curl_init($url);
|
||||
|
||||
$headers = ['Accept: application/json'];
|
||||
if($this->config->hasValues('token'))
|
||||
$headers[] = sprintf('Authorization: Basic %s', base64_encode($this->config->getString('token')));
|
||||
|
||||
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 => sprintf('Satori/%s (+https://fii.moe/satori)', SAT_VERSION),
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
]);
|
||||
$response = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
|
||||
return empty($response) ? [] : XArray::select($response, fn($post) => new DanbooruPost($post));
|
||||
}
|
||||
}
|
32
src/Booru/Gelbooru/GelbooruPost.php
Normal file
32
src/Booru/Gelbooru/GelbooruPost.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
namespace Satori\Booru\Gelbooru;
|
||||
|
||||
use Satori\Booru\IBooruPost;
|
||||
|
||||
class GelbooruPost implements IBooruPost {
|
||||
public function __construct(private array $info) {}
|
||||
|
||||
public function getId(): string {
|
||||
return (string)$this->info['id'];
|
||||
}
|
||||
|
||||
public function getRating(): string {
|
||||
return $this->info['rating'][0];
|
||||
}
|
||||
|
||||
public function getPostUrl(): string {
|
||||
return sprintf('https://gelbooru.com/index.php?page=post&s=view&id=%d', $this->info['id']);
|
||||
}
|
||||
|
||||
public function getTags(): array {
|
||||
return explode(' ', $this->info['tags']);
|
||||
}
|
||||
|
||||
public function hasFileUrl(): bool {
|
||||
return false; // remote embedding is blocked
|
||||
}
|
||||
|
||||
public function getFileUrl(): string {
|
||||
return $this->info['file_url'];
|
||||
}
|
||||
}
|
59
src/Booru/Gelbooru/GelbooruSource.php
Normal file
59
src/Booru/Gelbooru/GelbooruSource.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
namespace Satori\Booru\Gelbooru;
|
||||
|
||||
use Index\XArray;
|
||||
use Syokuhou\IConfig;
|
||||
use Satori\Booru\BooruSourceInfo;
|
||||
use Satori\Booru\IBooruSource;
|
||||
|
||||
class GelbooruSource implements IBooruSource {
|
||||
private const POSTS_URL = 'https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1';
|
||||
|
||||
private BooruSourceInfo $info;
|
||||
|
||||
public function __construct(private IConfig $config) {
|
||||
$this->info = new BooruSourceInfo('gelbooru', 'Gelbooru');
|
||||
}
|
||||
|
||||
public function getInfo(): BooruSourceInfo {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
public function getPosts(string $tags, bool $random = true, int $limit = 20): array {
|
||||
if($random)
|
||||
$tags .= ' sort:random';
|
||||
|
||||
$params = [];
|
||||
if($tags !== '')
|
||||
$params['tags'] = $tags;
|
||||
if($limit > 0)
|
||||
$params['limit'] = $limit;
|
||||
|
||||
$url = self::POSTS_URL;
|
||||
if(!empty($params))
|
||||
$url .= '&' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
|
||||
|
||||
$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 => sprintf('Satori/%s (+https://fii.moe/satori)', SAT_VERSION),
|
||||
CURLOPT_HTTPHEADER => ['Accept: application/json'],
|
||||
]);
|
||||
$response = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
|
||||
return empty($response) || empty($response['post']) || !is_array($response['post'])
|
||||
? [] : XArray::select($response['post'], fn($post) => new GelbooruPost($post));
|
||||
}
|
||||
}
|
12
src/Booru/IBooruPost.php
Normal file
12
src/Booru/IBooruPost.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace Satori\Booru;
|
||||
|
||||
interface IBooruPost {
|
||||
public function getId(): string;
|
||||
public function getRating(): string;
|
||||
public function getPostUrl(): string;
|
||||
public function getTags(): array;
|
||||
|
||||
public function hasFileUrl(): bool;
|
||||
public function getFileUrl(): string;
|
||||
}
|
10
src/Booru/IBooruPostWithSpecificTags.php
Normal file
10
src/Booru/IBooruPostWithSpecificTags.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace Satori\Booru;
|
||||
|
||||
interface IBooruPostWithSpecificTags extends IBooruPost {
|
||||
public function getGeneralTags(): array;
|
||||
public function getCharacterTags(): array;
|
||||
public function getCopyrightTags(): array;
|
||||
public function getArtistTags(): array;
|
||||
public function getMetaTags(): array;
|
||||
}
|
7
src/Booru/IBooruSource.php
Normal file
7
src/Booru/IBooruSource.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
namespace Satori\Booru;
|
||||
|
||||
interface IBooruSource {
|
||||
public function getInfo(): BooruSourceInfo;
|
||||
public function getPosts(string $tags, bool $random = true, int $limit = 20): array;
|
||||
}
|
37
src/Booru/Konachan/KonachanPost.php
Normal file
37
src/Booru/Konachan/KonachanPost.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
namespace Satori\Booru\Konachan;
|
||||
|
||||
use Satori\Booru\IBooruPost;
|
||||
|
||||
class KonachanPost implements IBooruPost {
|
||||
public function __construct(private array $info) {}
|
||||
|
||||
public function getId(): string {
|
||||
return (string)$this->info['id'];
|
||||
}
|
||||
|
||||
public function getRating(): string {
|
||||
return $this->info['rating'];
|
||||
}
|
||||
|
||||
public function isSafe(): bool {
|
||||
return $this->info['rating'] === 'g'
|
||||
|| $this->info['rating'] === 's';
|
||||
}
|
||||
|
||||
public function getPostUrl(): string {
|
||||
return sprintf('https://konachan.%s/post/show/%d', $this->isSafe() ? 'net' : 'com', $this->info['id']);
|
||||
}
|
||||
|
||||
public function getTags(): array {
|
||||
return explode(' ', $this->info['tags']);
|
||||
}
|
||||
|
||||
public function hasFileUrl(): bool {
|
||||
return false; // remote embedding is blocked
|
||||
}
|
||||
|
||||
public function getFileUrl(): string {
|
||||
return $this->info['file_url'];
|
||||
}
|
||||
}
|
59
src/Booru/Konachan/KonachanSource.php
Normal file
59
src/Booru/Konachan/KonachanSource.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
namespace Satori\Booru\Konachan;
|
||||
|
||||
use Index\XArray;
|
||||
use Syokuhou\IConfig;
|
||||
use Satori\Booru\BooruSourceInfo;
|
||||
use Satori\Booru\IBooruSource;
|
||||
|
||||
class KonachanSource implements IBooruSource {
|
||||
private const POSTS_URL = 'https://konachan.com/post.json?api_version=2';
|
||||
|
||||
private BooruSourceInfo $info;
|
||||
|
||||
public function __construct(private IConfig $config) {
|
||||
$this->info = new BooruSourceInfo('konachan', 'Konachan');
|
||||
}
|
||||
|
||||
public function getInfo(): BooruSourceInfo {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
public function getPosts(string $tags, bool $random = true, int $limit = 20): array {
|
||||
if($random)
|
||||
$tags .= ' order:random';
|
||||
|
||||
$params = [];
|
||||
if($tags !== '')
|
||||
$params['tags'] = $tags;
|
||||
if($limit > 0)
|
||||
$params['limit'] = $limit;
|
||||
|
||||
$url = self::POSTS_URL;
|
||||
if(!empty($params))
|
||||
$url .= '&' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
|
||||
|
||||
$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 => sprintf('Satori/%s (+https://fii.moe/satori)', SAT_VERSION),
|
||||
CURLOPT_HTTPHEADER => ['Accept: application/json'],
|
||||
]);
|
||||
$response = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
|
||||
return empty($response) || empty($response['posts']) || !is_array($response['posts'])
|
||||
? [] : XArray::select($response['posts'], fn($post) => new KonachanPost($post));
|
||||
}
|
||||
}
|
32
src/Booru/Yandere/YanderePost.php
Normal file
32
src/Booru/Yandere/YanderePost.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
namespace Satori\Booru\Yandere;
|
||||
|
||||
use Satori\Booru\IBooruPost;
|
||||
|
||||
class YanderePost implements IBooruPost {
|
||||
public function __construct(private array $info) {}
|
||||
|
||||
public function getId(): string {
|
||||
return (string)$this->info['id'];
|
||||
}
|
||||
|
||||
public function getRating(): string {
|
||||
return $this->info['rating'];
|
||||
}
|
||||
|
||||
public function getPostUrl(): string {
|
||||
return sprintf('https://yande.re/post/show/%d', $this->info['id']);
|
||||
}
|
||||
|
||||
public function getTags(): array {
|
||||
return explode(' ', $this->info['tags']);
|
||||
}
|
||||
|
||||
public function hasFileUrl(): bool {
|
||||
return false; // remote embedding is blocked
|
||||
}
|
||||
|
||||
public function getFileUrl(): string {
|
||||
return $this->info['file_url'];
|
||||
}
|
||||
}
|
78
src/Booru/Yandere/YandereSource.php
Normal file
78
src/Booru/Yandere/YandereSource.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
namespace Satori\Booru\Yandere;
|
||||
|
||||
use Index\XArray;
|
||||
use Syokuhou\IConfig;
|
||||
use Satori\Booru\BooruSourceInfo;
|
||||
use Satori\Booru\IBooruSource;
|
||||
|
||||
class YandereSource implements IBooruSource {
|
||||
private const POSTS_URL = 'https://yande.re/post.json?api_version=2';
|
||||
|
||||
private BooruSourceInfo $info;
|
||||
|
||||
public function __construct(private IConfig $config) {
|
||||
$this->info = new BooruSourceInfo('yandere', 'Yandere');
|
||||
}
|
||||
|
||||
public function getInfo(): BooruSourceInfo {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
public function getPosts(string $tags, bool $random = true, int $limit = 20): array {
|
||||
$localRandom = false;
|
||||
$localLimit = $limit;
|
||||
|
||||
if($random) {
|
||||
$localRandom = trim($tags) !== '';
|
||||
|
||||
if($localRandom)
|
||||
$limit = max($limit, 200);
|
||||
else
|
||||
$tags = 'order:random';
|
||||
}
|
||||
|
||||
$params = [];
|
||||
if($tags !== '')
|
||||
$params['tags'] = $tags;
|
||||
if($limit > 0)
|
||||
$params['limit'] = $limit;
|
||||
|
||||
$url = self::POSTS_URL;
|
||||
if(!empty($params))
|
||||
$url .= '&' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
|
||||
|
||||
$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 => sprintf('Satori/%s (+https://fii.moe/satori)', SAT_VERSION),
|
||||
CURLOPT_HTTPHEADER => ['Accept: application/json'],
|
||||
]);
|
||||
$response = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
|
||||
if(empty($response) || empty($response['posts']) || !is_array($response['posts']))
|
||||
return [];
|
||||
|
||||
$posts = $response['posts'];
|
||||
|
||||
if($localRandom) {
|
||||
shuffle($posts);
|
||||
if($localLimit > 0)
|
||||
$posts = array_slice($posts, 0, $localLimit);
|
||||
}
|
||||
|
||||
return XArray::select($posts, fn($post) => new YanderePost($post));
|
||||
}
|
||||
}
|
30
src/DatabaseContext.php
Normal file
30
src/DatabaseContext.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
namespace Satori;
|
||||
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\Migration\IDbMigrationRepo;
|
||||
use Index\Data\Migration\DbMigrationManager;
|
||||
use Index\Data\Migration\FsDbMigrationRepo;
|
||||
|
||||
class DatabaseContext {
|
||||
public function __construct(
|
||||
private IDbConnection $connection
|
||||
) {}
|
||||
|
||||
public function getConnection(): IDbConnection {
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
public function getQueryCount(): int {
|
||||
$result = $this->connection->query('SHOW SESSION STATUS LIKE "Questions"');
|
||||
return $result->next() ? $result->getInteger(1) : 0;
|
||||
}
|
||||
|
||||
public function createMigrationManager(): DbMigrationManager {
|
||||
return new DbMigrationManager($this->connection);
|
||||
}
|
||||
|
||||
public function createMigrationRepo(): IDbMigrationRepo {
|
||||
return new FsDbMigrationRepo(SAT_DIR_MIGRATIONS);
|
||||
}
|
||||
}
|
34
src/Dictionary/DictionaryContext.php
Normal file
34
src/Dictionary/DictionaryContext.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
namespace Satori\Dictionary;
|
||||
|
||||
use Syokuhou\IConfig;
|
||||
|
||||
class DictionaryContext {
|
||||
private IConfig $config;
|
||||
|
||||
public function __construct(IConfig $config) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getConfig(): IConfig {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function defineWord(string $word): array {
|
||||
$data = json_decode(file_get_contents(sprintf(
|
||||
'https://api.wordnik.com/v4/word.json/%s/definitions?limit=5&includeRelated=false&sourceDictionaries=ahd-5%%2Cwordnet&useCanonical=true&includeTags=false&api_key=%s',
|
||||
rawurlencode($word),
|
||||
$this->config->getString('token')
|
||||
)), true);
|
||||
|
||||
if(empty($data))
|
||||
return [];
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach($data as $result)
|
||||
$results[] = new DictionaryResult($result);
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
42
src/Dictionary/DictionaryResult.php
Normal file
42
src/Dictionary/DictionaryResult.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
namespace Satori\Dictionary;
|
||||
|
||||
class DictionaryResult {
|
||||
public function __construct(private array $info) {}
|
||||
|
||||
public function getWord(): string {
|
||||
return $this->info['word'] ?? '';
|
||||
}
|
||||
|
||||
public function getPartOfSpeech(): string {
|
||||
return $this->info['partOfSpeech'] ?? '';
|
||||
}
|
||||
|
||||
public function getAttributionText(): string {
|
||||
return $this->info['attributionText'] ?? '';
|
||||
}
|
||||
|
||||
public function getAttributionUrl(): string {
|
||||
return $this->info['attributionUrl'] ?? '';
|
||||
}
|
||||
|
||||
public function getSourceDictionary(): string {
|
||||
return $this->info['sourceDictionary'] ?? '';
|
||||
}
|
||||
|
||||
public function getWordnikUrl(): string {
|
||||
return $this->info['wordnikUrl'] ?? '';
|
||||
}
|
||||
|
||||
public function hasText(): bool {
|
||||
return array_key_exists('text', $this->info)
|
||||
&& $this->info['text'] !== '';
|
||||
}
|
||||
|
||||
public function getText(): string {
|
||||
return strtr($this->info['text'] ?? '', [
|
||||
'<em>' => '[i]',
|
||||
'</em>' => '[/i]',
|
||||
]);
|
||||
}
|
||||
}
|
65
src/Dictionary/DictionaryRoutes.php
Normal file
65
src/Dictionary/DictionaryRoutes.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
namespace Satori\Dictionary;
|
||||
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
|
||||
class DictionaryRoutes extends RouteHandler {
|
||||
public function __construct(
|
||||
private DictionaryContext $context
|
||||
) {}
|
||||
|
||||
#[Route('GET', '/dictionary/define')]
|
||||
public function getDefine($response, $request) {
|
||||
$word = trim((string)$request->getParam('word'));
|
||||
if($word === '')
|
||||
return ['error' => 'word'];
|
||||
|
||||
$results = $this->context->defineWord($word);
|
||||
$response = [];
|
||||
|
||||
foreach($results as $result) {
|
||||
if(!$result->hasText())
|
||||
continue;
|
||||
|
||||
$response[] = [
|
||||
'word' => $result->getWord(),
|
||||
'part_of_speech' => $result->getPartOfSpeech(),
|
||||
'attribution_text' => $result->getAttributionText(),
|
||||
'attribution_url' => $result->getAttributionUrl(),
|
||||
'source_dictionary' => $result->getSourceDictionary(),
|
||||
'text' => $result->getText(),
|
||||
'wordnik_url' => $result->getWordnikUrl(),
|
||||
];
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
#[Route('GET', '/word-define.php')]
|
||||
public function getPHP($response, $request) {
|
||||
$word = trim((string)$request->getParam('word'));
|
||||
if($word === '')
|
||||
return ['error' => 'word'];
|
||||
|
||||
$results = $this->context->defineWord($word);
|
||||
$response = [];
|
||||
|
||||
foreach($results as $result) {
|
||||
if(!$result->hasText())
|
||||
continue;
|
||||
|
||||
$response[] = [
|
||||
'word' => $result->getWord(),
|
||||
'pos' => $result->getPartOfSpeech(),
|
||||
'attr' => $result->getAttributionText(),
|
||||
'attr_url' => $result->getAttributionUrl(),
|
||||
'dict' => $result->getSourceDictionary(),
|
||||
'text' => $result->getText(),
|
||||
'url' => $result->getWordnikUrl(),
|
||||
];
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
68
src/ExRate/ExRateContext.php
Normal file
68
src/ExRate/ExRateContext.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
namespace Satori\ExRate;
|
||||
|
||||
use Index\Data\IDbConnection;
|
||||
use Syokuhou\IConfig;
|
||||
|
||||
class ExRateContext {
|
||||
private const BASE_CURRENCY = 'EUR';
|
||||
private const COMMON_CURRENCIES = [
|
||||
'EUR', 'AUD', 'GBP', 'CAD', 'USD',
|
||||
'JPY', 'PLN', 'SGD', 'RUB', 'ILS',
|
||||
];
|
||||
|
||||
private IConfig $config;
|
||||
private ExRateDatabase $database;
|
||||
|
||||
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||
$this->config = $config;
|
||||
$this->database = new ExRateDatabase(self::BASE_CURRENCY, $dbConn);
|
||||
}
|
||||
|
||||
public function getConfig(): IConfig {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function getDatabase(): ExRateDatabase {
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
public function getBaseCurrency(): string {
|
||||
return self::BASE_CURRENCY;
|
||||
}
|
||||
|
||||
public function getCommonCurrencies(): array {
|
||||
return self::COMMON_CURRENCIES;
|
||||
}
|
||||
|
||||
public function ratesNeedRefresh(): bool {
|
||||
return $this->database->ratesNeedRefresh();
|
||||
}
|
||||
|
||||
public function refreshRates(): void {
|
||||
// HTTPS is a paid feature?????????
|
||||
$data = json_decode(file_get_contents(sprintf(
|
||||
'http://api.exchangerate.host/live?source=%s&access_key=%s',
|
||||
self::BASE_CURRENCY,
|
||||
$this->config->getString('token')
|
||||
)), true);
|
||||
|
||||
if(empty($data) || !$data['success'])
|
||||
return;
|
||||
|
||||
$this->database->clearRates();
|
||||
$this->database->insertRate(self::BASE_CURRENCY, 1.0);
|
||||
|
||||
foreach($data['quotes'] as $names => $value) {
|
||||
$to = substr($names, 3, 3);
|
||||
if(strlen($to) !== 3)
|
||||
continue;
|
||||
|
||||
$this->database->insertRate($to, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function convertRate(string $from, string $to, float $value): float {
|
||||
return $this->database->convertRate($from, $to, $value);
|
||||
}
|
||||
}
|
49
src/ExRate/ExRateDatabase.php
Normal file
49
src/ExRate/ExRateDatabase.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
namespace Satori\ExRate;
|
||||
|
||||
use Index\Data\DbStatementCache;
|
||||
use Index\Data\IDbConnection;
|
||||
|
||||
class ExRateDatabase {
|
||||
private DbStatementCache $cache;
|
||||
|
||||
public function __construct(
|
||||
private string $baseCurrency,
|
||||
private IDbConnection $dbConn
|
||||
) {
|
||||
$this->cache = new DbStatementCache($dbConn);
|
||||
}
|
||||
|
||||
public function ratesNeedRefresh(): bool {
|
||||
$result = $this->dbConn->query('SELECT MAX(rate_stored) > NOW() - INTERVAL 1 DAY FROM `exchange-rates` LIMIT 1');
|
||||
return !$result->next() || $result->isNull(0) || $result->getInteger(0) < 1;
|
||||
}
|
||||
|
||||
public function clearRates(): void {
|
||||
$this->dbConn->execute('TRUNCATE `exchange-rates`');
|
||||
}
|
||||
|
||||
public function insertRate(string $to, float $rate): void {
|
||||
$stmt = $this->cache->get(sprintf(
|
||||
'INSERT INTO `exchange-rates` (rate_from, rate_to, rate_value) VALUES ("%s", ?, ?)',
|
||||
$this->baseCurrency
|
||||
));
|
||||
$stmt->addParameter(1, $to);
|
||||
$stmt->addParameter(2, $rate);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function convertRate(string $from, string $to, float $value): float {
|
||||
$stmt = $this->cache->get(sprintf(
|
||||
'SELECT (SELECT (? / rate_value) FROM `exchange-rates` WHERE rate_from = "%1$s" AND rate_to = ?) * rate_value FROM `exchange-rates` WHERE rate_from = "%1$s" AND rate_to = ?',
|
||||
$this->baseCurrency
|
||||
));
|
||||
$stmt->addParameter(1, $value);
|
||||
$stmt->addParameter(2, $from);
|
||||
$stmt->addParameter(3, $to);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
return $result->next() ? $result->getFloat(0) : 0;
|
||||
}
|
||||
}
|
93
src/ExRate/ExRateRoutes.php
Normal file
93
src/ExRate/ExRateRoutes.php
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
namespace Satori\ExRate;
|
||||
|
||||
use stdClass;
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
|
||||
class ExRateRoutes extends RouteHandler {
|
||||
public function __construct(
|
||||
private ExRateContext $context
|
||||
) {}
|
||||
|
||||
#[Route('GET', '/exrate/convert')]
|
||||
public function getConvert($response, $request) {
|
||||
$from = strtoupper((string)$request->getParam('from'));
|
||||
$targets = explode(',', strtoupper((string)$request->getParam('to')));
|
||||
$amount = (string)($request->getParam('amount', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) ?? '1');
|
||||
|
||||
if(strlen($from) !== 3)
|
||||
return 400;
|
||||
|
||||
if(empty($targets) || empty($targets[0]))
|
||||
$targets = $this->context->getCommonCurrencies();
|
||||
else
|
||||
foreach($targets as $target)
|
||||
if(strlen($target) !== 3)
|
||||
return 400;
|
||||
|
||||
if($this->context->ratesNeedRefresh())
|
||||
$this->context->refreshRates();
|
||||
|
||||
$response = new stdClass;
|
||||
$response->from = $from;
|
||||
$response->amount = (float)$amount;
|
||||
$response->results = [];
|
||||
|
||||
foreach($targets as $target) {
|
||||
if($target === $from)
|
||||
continue;
|
||||
|
||||
$response->results[] = [
|
||||
'to' => $target,
|
||||
'result' => $this->context->convertRate($from, $target, $amount),
|
||||
];
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
#[Route('GET', '/exrate.php')]
|
||||
public function getPHP($response, $request) {
|
||||
$from = strtoupper((string)$request->getParam('from'));
|
||||
$to = strtoupper((string)$request->getParam('to'));
|
||||
$amount = (string)($request->getParam('amount', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) ?? '1');
|
||||
|
||||
if((!empty($to) && strlen($to) !== 3) || strlen($from) !== 3) {
|
||||
$response->setStatusCode(404);
|
||||
return 'Invalid currency specified.';
|
||||
}
|
||||
|
||||
if($this->context->ratesNeedRefresh())
|
||||
$this->context->refreshRates();
|
||||
|
||||
|
||||
$response = new stdClass;
|
||||
$response->from = $from;
|
||||
$response->to = $to;
|
||||
$response->amount = (float)$amount;
|
||||
|
||||
if($from === $to) {
|
||||
$response->result = $response->amount;
|
||||
return $response;
|
||||
}
|
||||
|
||||
$singleResult = !empty($to);
|
||||
$targets = $singleResult ? [$to] : $this->context->getCommonCurrencies();
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach($targets as $to)
|
||||
$results[] = [
|
||||
'to' => $to,
|
||||
'result' => $this->context->convertRate($from, $to, $amount),
|
||||
];
|
||||
|
||||
if($singleResult)
|
||||
$response->result = $results[0]['result'];
|
||||
else
|
||||
$response->results = $results;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
36
src/RoutingContext.php
Normal file
36
src/RoutingContext.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
namespace Satori;
|
||||
|
||||
use Index\Http\HttpFx;
|
||||
use Index\Http\HttpRequest;
|
||||
use Index\Routing\IRouter;
|
||||
use Index\Routing\IRouteHandler;
|
||||
|
||||
class RoutingContext {
|
||||
private HttpFx $router;
|
||||
|
||||
public function __construct() {
|
||||
$this->router = new HttpFx;
|
||||
$this->router->use('/', fn($resp) => $resp->setPoweredBy('Satori'));
|
||||
}
|
||||
|
||||
public function getRouter(): IRouter {
|
||||
return $this->router;
|
||||
}
|
||||
|
||||
public function registerDefaultErrorPages(): void {
|
||||
$this->router->addErrorHandler(401, fn($resp) => $resp->setContent(file_get_contents(SAT_DIR_PUBLIC . '/404.html')));
|
||||
$this->router->addErrorHandler(403, fn($resp) => $resp->setContent(file_get_contents(SAT_DIR_PUBLIC . '/404.html')));
|
||||
$this->router->addErrorHandler(404, fn($resp) => $resp->setContent(file_get_contents(SAT_DIR_PUBLIC . '/404.html')));
|
||||
$this->router->addErrorHandler(500, fn($resp) => $resp->setContent(file_get_contents(SAT_DIR_PUBLIC . '/404.html')));
|
||||
$this->router->addErrorHandler(503, fn($resp) => $resp->setContent(file_get_contents(SAT_DIR_PUBLIC . '/404.html')));
|
||||
}
|
||||
|
||||
public function register(IRouteHandler $handler): void {
|
||||
$this->router->register($handler);
|
||||
}
|
||||
|
||||
public function dispatch(?HttpRequest $request = null): void {
|
||||
$this->router->dispatch($request);
|
||||
}
|
||||
}
|
23
src/SFileCache.php
Normal file
23
src/SFileCache.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
namespace Satori;
|
||||
|
||||
final class SFileCache {
|
||||
public static function filePath(string $name): string {
|
||||
return sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'satori-' . hash('xxh3', $name) . '.tmp';
|
||||
}
|
||||
|
||||
public static function retrieve(string $name, callable $match, callable $create): mixed {
|
||||
$fileName = self::filePath($name);
|
||||
|
||||
if(is_file($fileName) && $match(filemtime($fileName), $fileName))
|
||||
return unserialize(file_get_contents($fileName));
|
||||
|
||||
$output = $create();
|
||||
file_put_contents($fileName, serialize($output));
|
||||
return $output;
|
||||
}
|
||||
|
||||
public static function for15mins(string $name, callable $create): mixed {
|
||||
return self::retrieve($name, fn($mtime) => is_int($mtime) && floor($mtime / 900) >= floor(time() / 900), $create);
|
||||
}
|
||||
}
|
32
src/SHttp.php
Normal file
32
src/SHttp.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
namespace Satori;
|
||||
|
||||
final class SHttp {
|
||||
public static function getJson(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 => sprintf('Satori/%s (+https://fii.moe/satori)', SAT_VERSION),
|
||||
CURLOPT_HTTPHEADER => ['Accept: application/json'],
|
||||
]);
|
||||
$response = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function getJsonCached(string $url): mixed {
|
||||
return SFileCache::for15mins($url, fn() => self::getJson($url));
|
||||
}
|
||||
}
|
67
src/SatoriContext.php
Normal file
67
src/SatoriContext.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
namespace Satori;
|
||||
|
||||
use Index\Data\IDbConnection;
|
||||
use Syokuhou\IConfig;
|
||||
|
||||
final class SatoriContext {
|
||||
private IConfig $config;
|
||||
private DatabaseContext $dbCtx;
|
||||
|
||||
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||
$this->config = $config;
|
||||
$this->dbCtx = new DatabaseContext($dbConn);
|
||||
}
|
||||
|
||||
public function getConfig(): IConfig {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function getDatabase(): DatabaseContext {
|
||||
return $this->dbCtx;
|
||||
}
|
||||
|
||||
public function createExRates(): ExRate\ExRateContext {
|
||||
return new ExRate\ExRateContext(
|
||||
$this->config->scopeTo('exrate'),
|
||||
$this->dbCtx->getConnection()
|
||||
);
|
||||
}
|
||||
|
||||
public function createDictionary(): Dictionary\DictionaryContext {
|
||||
return new Dictionary\DictionaryContext(
|
||||
$this->config->scopeTo('dictionary')
|
||||
);
|
||||
}
|
||||
|
||||
public function createTranslation(): Translation\TranslateContext {
|
||||
return new Translation\TranslateContext(
|
||||
$this->config->scopeTo('translate')
|
||||
);
|
||||
}
|
||||
|
||||
public function createBooru(): Booru\BooruContext {
|
||||
return new Booru\BooruContext(
|
||||
$this->config->scopeTo('booru')
|
||||
);
|
||||
}
|
||||
|
||||
public function createSplatoon(): Splatoon\SplatoonContext {
|
||||
return new Splatoon\SplatoonContext(
|
||||
$this->config->scopeTo('splatoon')
|
||||
);
|
||||
}
|
||||
|
||||
public function createRouting(): RoutingContext {
|
||||
$routingCtx = new RoutingContext;
|
||||
$routingCtx->registerDefaultErrorPages();
|
||||
|
||||
$routingCtx->register(new ExRate\ExRateRoutes($this->createExRates()));
|
||||
$routingCtx->register(new Dictionary\DictionaryRoutes($this->createDictionary()));
|
||||
$routingCtx->register(new Translation\TranslateRoutes($this->createTranslation()));
|
||||
$routingCtx->register(new Booru\BooruRoutes($this->createBooru()));
|
||||
$routingCtx->register(new Splatoon\SplatoonRoutes($this->createSplatoon()));
|
||||
|
||||
return $routingCtx;
|
||||
}
|
||||
}
|
20
src/Splatoon/ISplatoonFestival.php
Normal file
20
src/Splatoon/ISplatoonFestival.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonFestival {
|
||||
public function getId(): string;
|
||||
public function getLocalId(): string;
|
||||
public function getTitle(): string;
|
||||
public function getImage(): string;
|
||||
public function getRegion(): string;
|
||||
|
||||
public function getStartTime(): int;
|
||||
public function getEndTime(): int;
|
||||
|
||||
public function getState(): string;
|
||||
public function canVote(): bool;
|
||||
public function getTeams(): array;
|
||||
|
||||
public function hasSpecialStages(): bool;
|
||||
public function getSpecialStages(): array;
|
||||
}
|
14
src/Splatoon/ISplatoonFestivalTeam.php
Normal file
14
src/Splatoon/ISplatoonFestivalTeam.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonFestivalTeam {
|
||||
public function getId(): string;
|
||||
public function getLocalId(): string;
|
||||
public function getName(): string;
|
||||
public function getImage(): string;
|
||||
public function getColour(): int;
|
||||
|
||||
public function hasResults(): bool;
|
||||
public function isWinner(): bool;
|
||||
public function getResults(): array;
|
||||
}
|
8
src/Splatoon/ISplatoonFestivalTeamResult.php
Normal file
8
src/Splatoon/ISplatoonFestivalTeamResult.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonFestivalTeamResult {
|
||||
public function getCategory(): string;
|
||||
public function getRatio(): float;
|
||||
public function isTop(): bool;
|
||||
}
|
14
src/Splatoon/ISplatoonGame.php
Normal file
14
src/Splatoon/ISplatoonGame.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonGame {
|
||||
public function getName(): string;
|
||||
public function getTitle(): string;
|
||||
public function getColour(): int;
|
||||
|
||||
public function getDefaultLocale(): string;
|
||||
public function isSupportedLocale(string $locale): bool;
|
||||
public function getLocales(): array;
|
||||
public function getLocaleAlias(string $locale): string;
|
||||
public function createLocale(string $locale): ISplatoonLocale;
|
||||
}
|
10
src/Splatoon/ISplatoonGameMode.php
Normal file
10
src/Splatoon/ISplatoonGameMode.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonGameMode {
|
||||
public function getName(): string;
|
||||
public function getTitle(): string;
|
||||
public function getColour(): int;
|
||||
public function hasVariants(): bool;
|
||||
public function getVariants(): array;
|
||||
}
|
8
src/Splatoon/ISplatoonGameModeVariant.php
Normal file
8
src/Splatoon/ISplatoonGameModeVariant.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonGameModeVariant {
|
||||
public function getName(): string;
|
||||
public function getTitle(): string;
|
||||
public function getColour(): int;
|
||||
}
|
9
src/Splatoon/ISplatoonHasFestivals.php
Normal file
9
src/Splatoon/ISplatoonHasFestivals.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonHasFestivals extends ISplatoonGame {
|
||||
public function isSupportedFestivalRegion(string $region): bool;
|
||||
public function getFestivalRegions(): array;
|
||||
public function getFestivalRegionAlias(string $region): string;
|
||||
public function getFestivals(ISplatoonLocale $locale, array $regions = []): array;
|
||||
}
|
11
src/Splatoon/ISplatoonHasSchedules.php
Normal file
11
src/Splatoon/ISplatoonHasSchedules.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonHasSchedules extends ISplatoonGame {
|
||||
public function isValidScheduleFilter(string $filter): bool;
|
||||
public function getScheduleFestival(): ?ISplatoonScheduleFestival;
|
||||
public function getScheduleFilters(): array;
|
||||
public function getScheduleFilterAlias(string $filter): string;
|
||||
public function getScheduleModes(ISplatoonLocale $locale, array $filters): array;
|
||||
public function getSchedules(ISplatoonLocale $locale, array $filters): array;
|
||||
}
|
10
src/Splatoon/ISplatoonLeague.php
Normal file
10
src/Splatoon/ISplatoonLeague.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonLeague {
|
||||
public function getId(): string;
|
||||
public function getName(): string;
|
||||
public function getDescription(): string;
|
||||
public function getRulesLines(): array;
|
||||
public function getRulesUrl(): string;
|
||||
}
|
6
src/Splatoon/ISplatoonLocale.php
Normal file
6
src/Splatoon/ISplatoonLocale.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonLocale {
|
||||
public function getString(string $name, string $fallback): string;
|
||||
}
|
9
src/Splatoon/ISplatoonScheduleEntry.php
Normal file
9
src/Splatoon/ISplatoonScheduleEntry.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonScheduleEntry {
|
||||
public function getGameMode(): string;
|
||||
public function getGameModeVariant(): string;
|
||||
public function getTimePeriods(): array;
|
||||
public function getStages(): array;
|
||||
}
|
6
src/Splatoon/ISplatoonScheduleEntryCoop.php
Normal file
6
src/Splatoon/ISplatoonScheduleEntryCoop.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonScheduleEntryCoop extends ISplatoonScheduleEntry {
|
||||
public function getWeapons(): array;
|
||||
}
|
6
src/Splatoon/ISplatoonScheduleEntryVs.php
Normal file
6
src/Splatoon/ISplatoonScheduleEntryVs.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonScheduleEntryVs extends ISplatoonScheduleEntry {
|
||||
public function getRuleset(): ISplatoonVsRuleset;
|
||||
}
|
6
src/Splatoon/ISplatoonScheduleEntryVsLeague.php
Normal file
6
src/Splatoon/ISplatoonScheduleEntryVsLeague.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonScheduleEntryVsLeague extends ISplatoonScheduleEntryVs {
|
||||
public function getLeagueInfo(): ISplatoonLeague;
|
||||
}
|
14
src/Splatoon/ISplatoonScheduleFestival.php
Normal file
14
src/Splatoon/ISplatoonScheduleFestival.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonScheduleFestival {
|
||||
public function getId(): string;
|
||||
public function getTitle(): string;
|
||||
|
||||
public function getStartTime(): int;
|
||||
public function getMidTermTime(): int;
|
||||
public function getEndTime(): int;
|
||||
|
||||
public function getState(): string;
|
||||
public function getTeams(): array;
|
||||
}
|
7
src/Splatoon/ISplatoonScheduleFestivalTeam.php
Normal file
7
src/Splatoon/ISplatoonScheduleFestivalTeam.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonScheduleFestivalTeam {
|
||||
public function getId(): string;
|
||||
public function getColour(): int;
|
||||
}
|
8
src/Splatoon/ISplatoonStage.php
Normal file
8
src/Splatoon/ISplatoonStage.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonStage {
|
||||
public function getId(): string;
|
||||
public function getName(): string;
|
||||
public function getImage(): string;
|
||||
}
|
7
src/Splatoon/ISplatoonTimePeriod.php
Normal file
7
src/Splatoon/ISplatoonTimePeriod.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonTimePeriod {
|
||||
public function getStartTime(): int;
|
||||
public function getEndTime(): int;
|
||||
}
|
8
src/Splatoon/ISplatoonVsRuleset.php
Normal file
8
src/Splatoon/ISplatoonVsRuleset.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonVsRuleset {
|
||||
public function getId(): string;
|
||||
public function getName(): string;
|
||||
public function getShortName(): string;
|
||||
}
|
8
src/Splatoon/ISplatoonWeapon.php
Normal file
8
src/Splatoon/ISplatoonWeapon.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
interface ISplatoonWeapon {
|
||||
public function getId(): string;
|
||||
public function getName(): string;
|
||||
public function getImage(): string;
|
||||
}
|
26
src/Splatoon/Splatoon2/Splatoon2CoopStage.php
Normal file
26
src/Splatoon/Splatoon2/Splatoon2CoopStage.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonStage;
|
||||
|
||||
class Splatoon2CoopStage implements ISplatoonStage {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private array $stageInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->stageInfo['image'];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('stages:%s:name', $this->stageInfo['image']),
|
||||
$this->stageInfo['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return Splatoon2Game::asset($this->stageInfo['image']);
|
||||
}
|
||||
}
|
122
src/Splatoon/Splatoon2/Splatoon2Festival.php
Normal file
122
src/Splatoon/Splatoon2/Splatoon2Festival.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonFestival;
|
||||
|
||||
class Splatoon2Festival implements ISplatoonFestival {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private string $region,
|
||||
private array $festInfo,
|
||||
private array $results
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return (string)$this->festInfo['festival_id'];
|
||||
}
|
||||
|
||||
public function getLocalId(): string {
|
||||
return base64_encode(sprintf('Fest-%s:JEA-%05d', $this->region, $this->festInfo['festival_id']));
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
$prefix = sprintf('festivals:%s:', $this->festInfo['festival_id']);
|
||||
|
||||
return sprintf(
|
||||
'%s vs %s',
|
||||
$this->locale->getString($prefix . 'alpha_short', 'ALPHA'),
|
||||
$this->locale->getString($prefix . 'bravo_short', 'BRAVO')
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return Splatoon2Game::asset($this->festInfo['images']['panel']);
|
||||
}
|
||||
|
||||
public function getRegion(): string {
|
||||
return $this->region;
|
||||
}
|
||||
|
||||
public function getStartTime(): int {
|
||||
return $this->festInfo['times']['start'];
|
||||
}
|
||||
|
||||
public function getEndTime(): int {
|
||||
return $this->festInfo['times']['end'];
|
||||
}
|
||||
|
||||
public function getState(): string {
|
||||
$time = time();
|
||||
if($time > $this->getEndTime())
|
||||
return 'CLOSED';
|
||||
if($time > $this->getStartTime())
|
||||
return 'OPEN'; // this is an assumption
|
||||
|
||||
return 'SCHEDULED';
|
||||
}
|
||||
|
||||
public function canVote(): bool {
|
||||
return $this->getEndTime() > time();
|
||||
}
|
||||
|
||||
public function getTeams(): array {
|
||||
$teams = [];
|
||||
$tmpResults = [];
|
||||
|
||||
foreach(['alpha', 'bravo'] as $teamNo => $teamId) {
|
||||
$rawColour = $this->festInfo['colors'][$teamId];
|
||||
$teamColour = (round($rawColour['a'] * 0xFF) << 24)
|
||||
| (round($rawColour['r'] * 0xFF) << 16)
|
||||
| (round($rawColour['g'] * 0xFF) << 8)
|
||||
| round($rawColour['b'] * 0xFF);
|
||||
|
||||
$results = [];
|
||||
$isWinner = false;
|
||||
|
||||
// god i hope this works
|
||||
if(!empty($this->results)) {
|
||||
$isWinner = $this->results['summary']['total'] == $teamNo;
|
||||
|
||||
foreach(['challenge', 'vote', 'regular', 'solo', 'team'] as $rateType) {
|
||||
if(!array_key_exists($rateType, $this->results['rates']))
|
||||
continue;
|
||||
|
||||
$rate = $this->results['rates'][$rateType][$teamId];
|
||||
|
||||
if(!isset($tmpResults[$rateType]))
|
||||
$tmpResults[$rateType] = $rate > $this->results['rates'][$rateType]['bravo'];
|
||||
|
||||
$results[] = new Splatoon2FestivalTeamResult(
|
||||
$rateType,
|
||||
$rate / 10000,
|
||||
$tmpResults[$rateType],
|
||||
);
|
||||
|
||||
$tmpResults[$rateType] = !$tmpResults[$rateType];
|
||||
}
|
||||
}
|
||||
|
||||
$teams[] = new Splatoon2FestivalTeam(
|
||||
$this->locale,
|
||||
$this->region,
|
||||
$this->festInfo['festival_id'],
|
||||
$teamId,
|
||||
$this->festInfo['names'][$teamId . '_long'],
|
||||
$this->festInfo['images'][$teamId],
|
||||
$teamColour,
|
||||
$isWinner,
|
||||
$results
|
||||
);
|
||||
}
|
||||
|
||||
return $teams;
|
||||
}
|
||||
|
||||
public function hasSpecialStages(): bool {
|
||||
return array_key_exists('special_stage', $this->festInfo);
|
||||
}
|
||||
|
||||
public function getSpecialStages(): array {
|
||||
return [new Splatoon2Stage($this->locale, $this->festInfo['special_stage'])];
|
||||
}
|
||||
}
|
58
src/Splatoon/Splatoon2/Splatoon2FestivalTeam.php
Normal file
58
src/Splatoon/Splatoon2/Splatoon2FestivalTeam.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonFestivalTeam;
|
||||
|
||||
class Splatoon2FestivalTeam implements ISplatoonFestivalTeam {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private string $region,
|
||||
private string $festId,
|
||||
private string $teamId,
|
||||
private string $teamName,
|
||||
private string $teamImage,
|
||||
private int $teamColour,
|
||||
private bool $isWinner,
|
||||
private array $results
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->teamId;
|
||||
}
|
||||
|
||||
public function getLocalId(): string {
|
||||
return base64_encode(sprintf(
|
||||
'FestTeam-%s:JEA-%05d:%s',
|
||||
$this->region,
|
||||
$this->festId,
|
||||
ucfirst($this->teamId)
|
||||
));
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('festivals:%s:names:%s_long', $this->festId, $this->teamId),
|
||||
$this->teamName
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return Splatoon2Game::asset($this->teamImage);
|
||||
}
|
||||
|
||||
public function getColour(): int {
|
||||
return $this->teamColour;
|
||||
}
|
||||
|
||||
public function hasResults(): bool {
|
||||
return !empty($this->results);
|
||||
}
|
||||
|
||||
public function isWinner(): bool {
|
||||
return $this->isWinner;
|
||||
}
|
||||
|
||||
public function getResults(): array {
|
||||
return $this->results;
|
||||
}
|
||||
}
|
24
src/Splatoon/Splatoon2/Splatoon2FestivalTeamResult.php
Normal file
24
src/Splatoon/Splatoon2/Splatoon2FestivalTeamResult.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonFestivalTeamResult;
|
||||
|
||||
class Splatoon2FestivalTeamResult implements ISplatoonFestivalTeamResult {
|
||||
public function __construct(
|
||||
private string $category,
|
||||
private float $ratio,
|
||||
private bool $isTop
|
||||
) {}
|
||||
|
||||
public function getCategory(): string {
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
public function getRatio(): float {
|
||||
return $this->ratio;
|
||||
}
|
||||
|
||||
public function isTop(): bool {
|
||||
return $this->isTop;
|
||||
}
|
||||
}
|
243
src/Splatoon/Splatoon2/Splatoon2Game.php
Normal file
243
src/Splatoon/Splatoon2/Splatoon2Game.php
Normal file
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\SHttp;
|
||||
use Satori\Splatoon\ISplatoonScheduleFestival;
|
||||
use Satori\Splatoon\ISplatoonGame;
|
||||
use Satori\Splatoon\ISplatoonHasFestivals;
|
||||
use Satori\Splatoon\ISplatoonHasSchedules;
|
||||
use Satori\Splatoon\ISplatoonLocale;
|
||||
|
||||
class Splatoon2Game implements ISplatoonGame, ISplatoonHasFestivals, ISplatoonHasSchedules {
|
||||
private const URL = 'https://splatoon2.ink';
|
||||
private const ASSET_URL = self::URL . '/assets/splatnet%s';
|
||||
|
||||
public static function asset(string $path): string {
|
||||
return sprintf(self::ASSET_URL, $path);
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 's2';
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return 'Splatoon 2';
|
||||
}
|
||||
|
||||
public function getColour(): int {
|
||||
return 0xFFFF4506;
|
||||
}
|
||||
|
||||
private const LOCALE_URL = self::URL . '/data/locale/%s.json';
|
||||
private const DEFAULT_LOCALE = 'en';
|
||||
private const LOCALES = ['en', 'es', 'es-MX', 'fr', 'fr-CA', 'de', 'nl', 'it', 'ru', 'ja'];
|
||||
private const LOCALE_ALIASES = [
|
||||
'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',
|
||||
];
|
||||
|
||||
public function getDefaultLocale(): string {
|
||||
return self::DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
public function isSupportedLocale(string $locale): bool {
|
||||
return in_array($locale, self::LOCALES);
|
||||
}
|
||||
|
||||
public function getLocales(): array {
|
||||
return self::LOCALES;
|
||||
}
|
||||
|
||||
public function getLocaleAlias(string $locale): string {
|
||||
if($locale === '')
|
||||
return self::DEFAULT_LOCALE;
|
||||
|
||||
if(array_key_exists($locale, self::LOCALE_ALIASES))
|
||||
return self::LOCALE_ALIASES[$locale];
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
public function createLocale(string $locale): ISplatoonLocale {
|
||||
return new Splatoon2Locale(
|
||||
SHttp::getJsonCached(sprintf(self::LOCALE_URL, $locale))
|
||||
);
|
||||
}
|
||||
|
||||
private const FEST_URL = self::URL . '/data/festivals.json';
|
||||
private const FEST_REGIONS = ['eu', 'jp', 'na'];
|
||||
private const FEST_REGIONS_ALIAS = [
|
||||
'EU' => 'eu', 'JP' => 'jp', 'US' => 'na',
|
||||
];
|
||||
|
||||
public function isSupportedFestivalRegion(string $region): bool {
|
||||
return in_array($region, self::FEST_REGIONS);
|
||||
}
|
||||
|
||||
public function getFestivalRegions(): array {
|
||||
return self::FEST_REGIONS;
|
||||
}
|
||||
|
||||
public function getFestivalRegionAlias(string $region): string {
|
||||
if(array_key_exists($region, self::FEST_REGIONS_ALIAS))
|
||||
return self::FEST_REGIONS_ALIAS[$region];
|
||||
|
||||
return $region;
|
||||
}
|
||||
|
||||
public function getFestivals(ISplatoonLocale $locale, array $regions = []): array {
|
||||
if(empty($regions))
|
||||
$regions = self::FEST_REGIONS;
|
||||
|
||||
$raw = SHttp::getJsonCached(self::FEST_URL);
|
||||
$fests = [];
|
||||
$regionsReverse = array_flip(self::FEST_REGIONS_ALIAS);
|
||||
|
||||
foreach($regions as $region) {
|
||||
if(!array_key_exists($region, $raw) || !array_key_exists('festivals', $raw[$region]))
|
||||
continue;
|
||||
|
||||
$festivals = $raw[$region]['festivals'];
|
||||
$resultsRaw = $raw[$region]['results'];
|
||||
$results = [];
|
||||
|
||||
if(array_key_exists($region, $regionsReverse))
|
||||
$region = $regionsReverse[$region];
|
||||
|
||||
foreach($resultsRaw as $result)
|
||||
$results[$result['festival_id']] = $result;
|
||||
|
||||
foreach($festivals as $festId => $festRaw)
|
||||
$fests[] = new Splatoon2Festival($locale, $region, $festRaw, $results[$festRaw['festival_id']]);
|
||||
}
|
||||
|
||||
return $fests;
|
||||
}
|
||||
|
||||
private const SCHED_URL = self::URL . '/data/schedules.json';
|
||||
private const SCHED_COOP_URL = self::URL . '/data/coop-schedules.json';
|
||||
private const SCHED_FILTERS = [
|
||||
'regular' => 'regular',
|
||||
'turf' => 'regular',
|
||||
'series' => 'gachi',
|
||||
'open' => 'gachi',
|
||||
'ranked' => 'gachi',
|
||||
'bankara' => 'gachi',
|
||||
'gachi' => 'gachi',
|
||||
'x' => 'gachi',
|
||||
'challenge' => 'league',
|
||||
'league' => 'league',
|
||||
'salmon' => 'coop',
|
||||
'coop' => 'coop',
|
||||
'fest' => 'fest',
|
||||
'splatfest' => 'fest',
|
||||
'festival' => 'fest',
|
||||
];
|
||||
|
||||
public function isValidScheduleFilter(string $filter): bool {
|
||||
return array_key_exists($filter, self::SCHED_FILTERS);
|
||||
}
|
||||
|
||||
public function getScheduleFilters(): array {
|
||||
return array_unique(array_values(self::SCHED_FILTERS));
|
||||
}
|
||||
|
||||
public function getScheduleFilterAlias(string $filter): string {
|
||||
if(array_key_exists($filter, self::SCHED_FILTERS))
|
||||
return self::SCHED_FILTERS[$filter];
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
private ?array $rawSchedule = null;
|
||||
private function getRawScheduleData(): array {
|
||||
if($this->rawSchedule === null)
|
||||
$this->rawSchedule = SHttp::getJsonCached(self::SCHED_URL);
|
||||
|
||||
return $this->rawSchedule;
|
||||
}
|
||||
|
||||
private ?array $rawCoopSchedule = null;
|
||||
private function getRawCoopScheduleData(): array {
|
||||
if($this->rawCoopSchedule === null)
|
||||
$this->rawCoopSchedule = SHttp::getJsonCached(self::SCHED_COOP_URL);
|
||||
|
||||
return $this->rawCoopSchedule;
|
||||
}
|
||||
|
||||
public function getScheduleModes(ISplatoonLocale $locale, array $filters): array {
|
||||
$modes = [];
|
||||
|
||||
$includeRegular = in_array('regular', $filters);
|
||||
$includeGachi = in_array('gachi', $filters);
|
||||
$includeLeague = in_array('league', $filters);
|
||||
$includeCoop = in_array('coop', $filters);
|
||||
|
||||
if($includeRegular || $includeGachi || $includeLeague) {
|
||||
$regularSchedule = $this->getRawScheduleData();
|
||||
|
||||
if($includeRegular && !empty($regularSchedule['regular']))
|
||||
$modes[] = new Splatoon2GameMode($locale, 'regular', 'Regular Battle', 0xFF19D719);
|
||||
|
||||
if($includeGachi && !empty($regularSchedule['gachi']))
|
||||
$modes[] = new Splatoon2GameMode($locale, 'gachi', 'Ranked Battle', 0xFFF54910);
|
||||
|
||||
if($includeLeague && !empty($regularSchedule['league']))
|
||||
$modes[] = new Splatoon2GameMode($locale, 'league', 'League Battle', 0xFFF02D7D);
|
||||
}
|
||||
|
||||
if($includeCoop) {
|
||||
$coopSchedule = $this->getRawCoopScheduleData();
|
||||
if(!empty($coopSchedule['details']))
|
||||
$modes[] = new Splatoon2GameMode($locale, 'coop', 'Salmon Run', 0xFFFF5600);
|
||||
}
|
||||
|
||||
return $modes;
|
||||
}
|
||||
|
||||
public function getScheduleFestival(): ?ISplatoonScheduleFestival {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getSchedules(ISplatoonLocale $locale, array $filters): array {
|
||||
$schedules = [];
|
||||
|
||||
$includeRegular = in_array('regular', $filters);
|
||||
$includeGachi = in_array('gachi', $filters);
|
||||
$includeLeague = in_array('league', $filters);
|
||||
$includeCoop = in_array('coop', $filters);
|
||||
|
||||
if($includeRegular || $includeGachi || $includeLeague) {
|
||||
$regularSchedule = $this->getRawScheduleData();
|
||||
|
||||
if($includeRegular && !empty($regularSchedule['regular']))
|
||||
foreach($regularSchedule['regular'] as $entryInfo)
|
||||
$schedules[] = new Splatoon2ScheduleEntryVs($locale, $entryInfo);
|
||||
|
||||
if($includeGachi && !empty($regularSchedule['gachi']))
|
||||
foreach($regularSchedule['gachi'] as $entryInfo)
|
||||
$schedules[] = new Splatoon2ScheduleEntryVs($locale, $entryInfo);
|
||||
|
||||
if($includeLeague && !empty($regularSchedule['league']))
|
||||
foreach($regularSchedule['league'] as $entryInfo)
|
||||
$schedules[] = new Splatoon2ScheduleEntryVs($locale, $entryInfo);
|
||||
}
|
||||
|
||||
if($includeCoop) {
|
||||
$coopSchedule = $this->getRawCoopScheduleData();
|
||||
if(!empty($coopSchedule['schedules']))
|
||||
foreach($coopSchedule['schedules'] as $key => $schedule) {
|
||||
if(array_key_exists($key, $coopSchedule['details']))
|
||||
$schedule = $coopSchedule['details'][$key];
|
||||
|
||||
$schedules[] = new Splatoon2ScheduleEntryCoop($locale, $schedule);
|
||||
}
|
||||
}
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
}
|
36
src/Splatoon/Splatoon2/Splatoon2GameMode.php
Normal file
36
src/Splatoon/Splatoon2/Splatoon2GameMode.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonGameMode;
|
||||
|
||||
class Splatoon2GameMode implements ISplatoonGameMode {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private string $name,
|
||||
private string $title,
|
||||
private int $colour
|
||||
) {}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('game_modes:%s:name', $this->name),
|
||||
$this->title
|
||||
);
|
||||
}
|
||||
|
||||
public function getColour(): int {
|
||||
return $this->colour;
|
||||
}
|
||||
|
||||
public function hasVariants(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getVariants(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
42
src/Splatoon/Splatoon2/Splatoon2Locale.php
Normal file
42
src/Splatoon/Splatoon2/Splatoon2Locale.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonLocale;
|
||||
|
||||
class Splatoon2Locale implements ISplatoonLocale {
|
||||
public function __construct(
|
||||
private array $strings
|
||||
) {}
|
||||
|
||||
public function getString(string $name, string $fallback): string {
|
||||
$name = explode(':', $name);
|
||||
$nameCount = count($name);
|
||||
if($nameCount > 1 && array_key_exists($name[0], $this->strings) && array_key_exists($name[1], $this->strings[$name[0]])) {
|
||||
$stringInfo = $this->strings[$name[0]][$name[1]];
|
||||
|
||||
if($nameCount > 2) {
|
||||
if(array_key_exists($name[2], $stringInfo)) {
|
||||
if(is_array($stringInfo[$name[2]]) && $nameCount > 3) {
|
||||
if(array_key_exists($name[3], $stringInfo[$name[2]]))
|
||||
return $stringInfo[$name[2]][$name[3]];
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
return $stringInfo[$name[2]];
|
||||
}
|
||||
|
||||
if(array_key_exists('names', $stringInfo) && array_key_exists($name[2], $stringInfo['names']))
|
||||
return $stringInfo['names'][$name[2]];
|
||||
}
|
||||
|
||||
if(array_key_exists('name', $stringInfo))
|
||||
return $stringInfo['name'];
|
||||
|
||||
if(!empty($stringInfo))
|
||||
return $stringInfo[array_key_first($stringInfo)];
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
}
|
42
src/Splatoon/Splatoon2/Splatoon2ScheduleEntryCoop.php
Normal file
42
src/Splatoon/Splatoon2/Splatoon2ScheduleEntryCoop.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Index\XArray;
|
||||
use Satori\Splatoon\ISplatoonScheduleEntryCoop;
|
||||
|
||||
class Splatoon2ScheduleEntryCoop implements ISplatoonScheduleEntryCoop {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private array $entryInfo
|
||||
) {}
|
||||
|
||||
public function getGameMode(): string {
|
||||
return 'coop';
|
||||
}
|
||||
|
||||
public function getGameModeVariant(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTimePeriods(): array {
|
||||
return [
|
||||
new Splatoon2TimePeriod($this->entryInfo['start_time'], $this->entryInfo['end_time']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getStages(): array {
|
||||
if(!array_key_exists('stage', $this->entryInfo))
|
||||
return [];
|
||||
|
||||
return [
|
||||
new Splatoon2CoopStage($this->locale, $this->entryInfo['stage']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getWeapons(): array {
|
||||
if(!array_key_exists('weapons', $this->entryInfo))
|
||||
return [];
|
||||
|
||||
return XArray::select($this->entryInfo['weapons'], fn($weapon) => new Splatoon2Weapon($this->locale, $weapon));
|
||||
}
|
||||
}
|
43
src/Splatoon/Splatoon2/Splatoon2ScheduleEntryVs.php
Normal file
43
src/Splatoon/Splatoon2/Splatoon2ScheduleEntryVs.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonScheduleEntryVs;
|
||||
use Satori\Splatoon\ISplatoonVsRuleset;
|
||||
|
||||
class Splatoon2ScheduleEntryVs implements ISplatoonScheduleEntryVs {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private array $entryInfo
|
||||
) {}
|
||||
|
||||
public function getGameMode(): string {
|
||||
return $this->entryInfo['game_mode']['key'];
|
||||
}
|
||||
|
||||
public function getGameModeVariant(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTimePeriods(): array {
|
||||
return [
|
||||
new Splatoon2TimePeriod($this->entryInfo['start_time'], $this->entryInfo['end_time']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getStages(): array {
|
||||
$stages = [];
|
||||
|
||||
foreach(['stage_a', 'stage_b', 'stage_c'] as $stage) {
|
||||
if(!array_key_exists($stage, $this->entryInfo))
|
||||
continue;
|
||||
|
||||
$stages[] = new Splatoon2Stage($this->locale, $this->entryInfo[$stage]);
|
||||
}
|
||||
|
||||
return $stages;
|
||||
}
|
||||
|
||||
public function getRuleset(): ISplatoonVsRuleset {
|
||||
return new Splatoon2VsRuleset($this->locale, $this->entryInfo['rule']);
|
||||
}
|
||||
}
|
26
src/Splatoon/Splatoon2/Splatoon2Stage.php
Normal file
26
src/Splatoon/Splatoon2/Splatoon2Stage.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonStage;
|
||||
|
||||
class Splatoon2Stage implements ISplatoonStage {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private array $stageInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->stageInfo['id'];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('stages:%s:name', $this->stageInfo['id']),
|
||||
$this->stageInfo['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return Splatoon2Game::asset($this->stageInfo['image']);
|
||||
}
|
||||
}
|
19
src/Splatoon/Splatoon2/Splatoon2TimePeriod.php
Normal file
19
src/Splatoon/Splatoon2/Splatoon2TimePeriod.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonTimePeriod;
|
||||
|
||||
class Splatoon2TimePeriod implements ISplatoonTimePeriod {
|
||||
public function __construct(
|
||||
private int $startTime,
|
||||
private int $endTime
|
||||
) {}
|
||||
|
||||
public function getStartTime(): int {
|
||||
return $this->startTime;
|
||||
}
|
||||
|
||||
public function getEndTime(): int {
|
||||
return $this->endTime;
|
||||
}
|
||||
}
|
48
src/Splatoon/Splatoon2/Splatoon2VsRuleset.php
Normal file
48
src/Splatoon/Splatoon2/Splatoon2VsRuleset.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonVsRuleset;
|
||||
|
||||
class Splatoon2VsRuleset implements ISplatoonVsRuleset {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private array $rulesetInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
$id = $this->rulesetInfo['key'];
|
||||
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;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('rules:%s:name', $this->rulesetInfo['key']),
|
||||
$this->rulesetInfo['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getShortName(): string {
|
||||
$id = $this->rulesetInfo['key'];
|
||||
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';
|
||||
return $id;
|
||||
}
|
||||
}
|
26
src/Splatoon/Splatoon2/Splatoon2Weapon.php
Normal file
26
src/Splatoon/Splatoon2/Splatoon2Weapon.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon2;
|
||||
|
||||
use Satori\Splatoon\ISplatoonWeapon;
|
||||
|
||||
class Splatoon2Weapon implements ISplatoonWeapon {
|
||||
public function __construct(
|
||||
private Splatoon2Locale $locale,
|
||||
private array $weaponInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->weaponInfo['id'];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('weapons:%s:name', $this->weaponInfo['id']),
|
||||
$this->weaponInfo['weapon']['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return $this->weaponInfo['weapon']['image'];
|
||||
}
|
||||
}
|
76
src/Splatoon/Splatoon3/Splatoon3Festival.php
Normal file
76
src/Splatoon/Splatoon3/Splatoon3Festival.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Index\XArray;
|
||||
use Satori\Splatoon\ISplatoonFestival;
|
||||
|
||||
class Splatoon3Festival implements ISplatoonFestival {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private string $region,
|
||||
private array $festInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->festInfo['__splatoon3ink_id'];
|
||||
}
|
||||
|
||||
public function getLocalId(): string {
|
||||
return $this->festInfo['id'];
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('festivals:%s:title', $this->festInfo['__splatoon3ink_id']),
|
||||
$this->festInfo['title']
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return $this->festInfo['image']['url'];
|
||||
}
|
||||
|
||||
public function getRegion(): string {
|
||||
return $this->region;
|
||||
}
|
||||
|
||||
public function getStartTime(): int {
|
||||
return strtotime($this->festInfo['startTime']);
|
||||
}
|
||||
|
||||
public function getEndTime(): int {
|
||||
return strtotime($this->festInfo['endTime']);
|
||||
}
|
||||
|
||||
public function getState(): string {
|
||||
return $this->festInfo['state'];
|
||||
}
|
||||
|
||||
public function canVote(): bool {
|
||||
return $this->festInfo['isVotable'];
|
||||
}
|
||||
|
||||
public function getTeams(): array {
|
||||
$teamNo = -1;
|
||||
$teams = [];
|
||||
|
||||
foreach($this->festInfo['teams'] as $teamInfo)
|
||||
$teams[] = new Splatoon3FestivalTeam(
|
||||
$this->locale,
|
||||
$this->region,
|
||||
$this->festInfo['__splatoon3ink_id'],
|
||||
++$teamNo,
|
||||
$teamInfo
|
||||
);
|
||||
|
||||
return $teams;
|
||||
}
|
||||
|
||||
public function hasSpecialStages(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getSpecialStages(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
71
src/Splatoon/Splatoon3/Splatoon3FestivalTeam.php
Normal file
71
src/Splatoon/Splatoon3/Splatoon3FestivalTeam.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonFestivalTeam;
|
||||
|
||||
class Splatoon3FestivalTeam implements ISplatoonFestivalTeam {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private string $region,
|
||||
private string $festId,
|
||||
private int $teamNo,
|
||||
private array $teamInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->teamInfo['id'];
|
||||
}
|
||||
|
||||
public function getLocalId(): string {
|
||||
return $this->teamInfo['id'];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('festivals:%s:teams:%s', $this->festId, $this->teamNo),
|
||||
$this->teamInfo['teamName']
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return $this->teamInfo['image']['url'];
|
||||
}
|
||||
|
||||
public function getColour(): int {
|
||||
$colourInfo = $this->teamInfo['color'];
|
||||
return (round($colourInfo['a'] * 0xFF) << 24)
|
||||
| (round($colourInfo['r'] * 0xFF) << 16)
|
||||
| (round($colourInfo['g'] * 0xFF) << 8)
|
||||
| round($colourInfo['b'] * 0xFF);
|
||||
}
|
||||
|
||||
public function hasResults(): bool {
|
||||
return array_key_exists('result', $this->teamInfo);
|
||||
}
|
||||
|
||||
public function isWinner(): bool {
|
||||
return $this->teamInfo['result']['isWinner'];
|
||||
}
|
||||
|
||||
public function getResults(): array {
|
||||
$results = [];
|
||||
|
||||
foreach(['horagai', 'vote', 'regularContribution', 'challengeContribution', 'tricolorContribution'] as $rateType) {
|
||||
$rateName = $rateType . 'Ratio';
|
||||
if(!array_key_exists($rateName, $this->teamInfo['result'])
|
||||
|| $this->teamInfo['result'][$rateName] === null)
|
||||
continue;
|
||||
|
||||
if(str_ends_with($rateType, 'Contribution'))
|
||||
$rateType = substr($rateType, 0, -12);
|
||||
|
||||
$results[] = new Splatoon3FestivalTeamResult(
|
||||
$rateType,
|
||||
$this->teamInfo['result'][$rateName],
|
||||
$this->teamInfo['result']['is' . ucfirst($rateName) . 'Top']
|
||||
);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
24
src/Splatoon/Splatoon3/Splatoon3FestivalTeamResult.php
Normal file
24
src/Splatoon/Splatoon3/Splatoon3FestivalTeamResult.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonFestivalTeamResult;
|
||||
|
||||
class Splatoon3FestivalTeamResult implements ISplatoonFestivalTeamResult {
|
||||
public function __construct(
|
||||
private string $category,
|
||||
private float $ratio,
|
||||
private bool $isTop
|
||||
) {}
|
||||
|
||||
public function getCategory(): string {
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
public function getRatio(): float {
|
||||
return $this->ratio;
|
||||
}
|
||||
|
||||
public function isTop(): bool {
|
||||
return $this->isTop;
|
||||
}
|
||||
}
|
342
src/Splatoon/Splatoon3/Splatoon3Game.php
Normal file
342
src/Splatoon/Splatoon3/Splatoon3Game.php
Normal file
|
@ -0,0 +1,342 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\SHttp;
|
||||
use Satori\Splatoon\ISplatoonScheduleFestival;
|
||||
use Satori\Splatoon\ISplatoonGame;
|
||||
use Satori\Splatoon\ISplatoonHasFestivals;
|
||||
use Satori\Splatoon\ISplatoonHasSchedules;
|
||||
use Satori\Splatoon\ISplatoonLocale;
|
||||
|
||||
class Splatoon3Game implements ISplatoonGame, ISplatoonHasFestivals, ISplatoonHasSchedules {
|
||||
private const URL = 'https://splatoon3.ink';
|
||||
|
||||
public function getName(): string {
|
||||
return 's3';
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return 'Splatoon 3';
|
||||
}
|
||||
|
||||
public function getColour(): int {
|
||||
return 0xFFEFFE65;
|
||||
}
|
||||
|
||||
private const LOCALE_URL = self::URL . '/data/locale/%s.json';
|
||||
private const DEFAULT_LOCALE = 'en-US';
|
||||
private const 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'];
|
||||
|
||||
public function getDefaultLocale(): string {
|
||||
return self::DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
public function isSupportedLocale(string $locale): bool {
|
||||
return in_array($locale, self::LOCALES);
|
||||
}
|
||||
|
||||
public function getLocales(): array {
|
||||
return self::LOCALES;
|
||||
}
|
||||
|
||||
public function getLocaleAlias(string $locale): string {
|
||||
if($locale === '')
|
||||
return self::DEFAULT_LOCALE;
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
public function createLocale(string $locale): ISplatoonLocale {
|
||||
return new Splatoon3Locale(
|
||||
SHttp::getJsonCached(sprintf(self::LOCALE_URL, $locale))
|
||||
);
|
||||
}
|
||||
|
||||
private const FEST_URL = self::URL . '/data/festivals.json';
|
||||
private const FEST_REGIONS = ['US', 'EU', 'JP', 'AP'];
|
||||
|
||||
public function isSupportedFestivalRegion(string $region): bool {
|
||||
return in_array($region, self::FEST_REGIONS);
|
||||
}
|
||||
|
||||
public function getFestivalRegions(): array {
|
||||
return self::FEST_REGIONS;
|
||||
}
|
||||
|
||||
public function getFestivalRegionAlias(string $region): string {
|
||||
return $region;
|
||||
}
|
||||
|
||||
public function getFestivals(ISplatoonLocale $locale, array $regions = []): array {
|
||||
if(empty($regions))
|
||||
$regions = self::FEST_REGIONS;
|
||||
|
||||
$raw = SHttp::getJsonCached(self::FEST_URL);
|
||||
$fests = [];
|
||||
|
||||
foreach($regions as $region) {
|
||||
if(!array_key_exists($region, $raw)
|
||||
|| !array_key_exists('data', $raw[$region])
|
||||
|| !array_key_exists('festRecords', $raw[$region]['data'])
|
||||
|| !array_key_exists('nodes', $raw[$region]['data']['festRecords']))
|
||||
continue;
|
||||
|
||||
foreach($raw[$region]['data']['festRecords']['nodes'] as $festival)
|
||||
$fests[] = new Splatoon3Festival($locale, $region, $festival);
|
||||
}
|
||||
|
||||
return $fests;
|
||||
}
|
||||
|
||||
private const SCHED_URL = self::URL . '/data/schedules.json';
|
||||
private const SCHED_FILTERS = [
|
||||
'regular' => 'regular',
|
||||
'turf' => 'regular',
|
||||
'series' => 'series',
|
||||
'open' => 'open',
|
||||
'ranked' => 'bankara',
|
||||
'bankara' => 'bankara',
|
||||
'gachi' => 'bankara',
|
||||
'x' => 'x',
|
||||
'challenge' => 'league',
|
||||
'league' => 'league',
|
||||
'salmon' => 'coop',
|
||||
'coop' => 'coop',
|
||||
'eggstra' => 'eggstra',
|
||||
'bigrun' => 'bigrun',
|
||||
'fest' => 'fest',
|
||||
'splatfest' => 'fest',
|
||||
'festival' => 'fest',
|
||||
];
|
||||
|
||||
public function isValidScheduleFilter(string $filter): bool {
|
||||
return array_key_exists($filter, self::SCHED_FILTERS);
|
||||
}
|
||||
|
||||
public function getScheduleFilters(): array {
|
||||
return array_unique(array_values(self::SCHED_FILTERS));
|
||||
}
|
||||
|
||||
public function getScheduleFilterAlias(string $filter): string {
|
||||
if(array_key_exists($filter, self::SCHED_FILTERS))
|
||||
return self::SCHED_FILTERS[$filter];
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
private ?array $rawSchedule = null;
|
||||
private function getRawScheduleData(): array {
|
||||
if($this->rawSchedule === null)
|
||||
$this->rawSchedule = SHttp::getJsonCached(self::SCHED_URL);
|
||||
|
||||
return $this->rawSchedule;
|
||||
}
|
||||
|
||||
public function getScheduleModes(ISplatoonLocale $locale, array $filters): array {
|
||||
$raw = $this->getRawScheduleData();
|
||||
if(!array_key_exists('data', $raw))
|
||||
return [];
|
||||
|
||||
$raw = $raw['data'];
|
||||
$modes = [];
|
||||
|
||||
if(in_array('regular', $filters) && array_key_exists('regularSchedules', $raw) && !empty($raw['regularSchedules']['nodes']))
|
||||
foreach($raw['regularSchedules']['nodes'] as $info) {
|
||||
if(!empty($info['regularMatchSetting'])) {
|
||||
$modes[] = new Splatoon3GameMode('regular', 'Regular Battle', 0xFF19D719);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$includeRankedSeries = $includeRankedOpen = $includeRanked = in_array('bankara', $filters);
|
||||
if(!$includeRanked) {
|
||||
$includeRankedSeries = in_array('series', $filters);
|
||||
$includeRankedOpen = in_array('open', $filters);
|
||||
$includeRanked = $includeRankedSeries || $includeRankedOpen;
|
||||
}
|
||||
|
||||
if($includeRanked && array_key_exists('bankaraSchedules', $raw) && !empty($raw['bankaraSchedules']['nodes'])) {
|
||||
$variants = [];
|
||||
foreach($raw['bankaraSchedules']['nodes'] as $info) {
|
||||
if(empty($info['bankaraMatchSettings']))
|
||||
continue;
|
||||
|
||||
if($includeRankedSeries && !empty($info['bankaraMatchSettings'][0])) {
|
||||
$includeRankedSeries = false;
|
||||
$variants[] = new Splatoon3GameModeVariant('CHALLENGE', 'Series', 0xFF603BFF);
|
||||
}
|
||||
|
||||
if($includeRankedOpen && !empty($info['bankaraMatchSettings'][1])) {
|
||||
$includeRankedOpen = false;
|
||||
$variants[] = new Splatoon3GameModeVariant('OPEN', 'Open', 0xFF603BFF);
|
||||
}
|
||||
|
||||
if(!$includeRankedSeries && !$includeRankedOpen)
|
||||
break;
|
||||
}
|
||||
|
||||
if(!empty($variants))
|
||||
$modes[] = new Splatoon3GameMode('bankara', 'Anarchy Battle', 0xFFF54910, $variants);
|
||||
}
|
||||
|
||||
if(in_array('x', $filters) && array_key_exists('xSchedules', $raw) && !empty($raw['xSchedules']['nodes']))
|
||||
foreach($raw['xSchedules']['nodes'] as $info) {
|
||||
if(!empty($info['xMatchSetting'])) {
|
||||
$modes[] = new Splatoon3GameMode('x', 'X Battle', 0xFF0FDB9B);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(in_array('league', $filters) && array_key_exists('eventSchedules', $raw) && !empty($raw['eventSchedules']['nodes']))
|
||||
foreach($raw['eventSchedules']['nodes'] as $info) {
|
||||
if(!empty($info['leagueMatchSetting'])) {
|
||||
$modes[] = new Splatoon3GameMode('league', 'Challenge Battle', 0xFFF02D7D);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(in_array('fest', $filters) && array_key_exists('festSchedules', $raw) && !empty($raw['festSchedules']['nodes'])) {
|
||||
foreach($raw['festSchedules']['nodes'] as $info) {
|
||||
if(!empty($info['festMatchSettings'])) {
|
||||
$modes[] = new Splatoon3GameMode('fest', 'Splatfest Battle', 0xFF71717A);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($raw['currentFest']['tricolorStage']))
|
||||
$modes[] = new Splatoon3GameMode('tricolor', 'Tricolor Battle', 0xFF71717A);
|
||||
}
|
||||
|
||||
if(array_key_exists('coopGroupingSchedule', $raw)) {
|
||||
$coop = $raw['coopGroupingSchedule'];
|
||||
|
||||
if(in_array('coop', $filters) && array_key_exists('regularSchedules', $coop) && !empty($coop['regularSchedules']['nodes']))
|
||||
foreach($coop['regularSchedules']['nodes'] as $info) {
|
||||
if(!empty($info['setting'])) {
|
||||
$modes[] = new Splatoon3GameMode('coop', 'Salmon Run', 0xFFFF5600);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(in_array('eggstra', $filters) && array_key_exists('teamContestSchedules', $coop) && !empty($coop['teamContestSchedules']['nodes']))
|
||||
foreach($coop['teamContestSchedules']['nodes'] as $info) {
|
||||
if(!empty($info['setting'])) {
|
||||
$modes[] = new Splatoon3GameMode('eggstra', 'Eggstra Work', 0xFFBE8800);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(in_array('bigrun', $filters) && array_key_exists('bigRunSchedules', $coop) && !empty($coop['bigRunSchedules']['nodes']))
|
||||
foreach($coop['bigRunSchedules']['nodes'] as $info) {
|
||||
if(!empty($info['setting'])) {
|
||||
$modes[] = new Splatoon3GameMode('bigrun', 'Big Run', 0xFFB322FF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $modes;
|
||||
}
|
||||
|
||||
public function getScheduleFestival(): ?ISplatoonScheduleFestival {
|
||||
$raw = $this->getRawScheduleData();
|
||||
if(!array_key_exists('data', $raw) || empty($raw['data']['currentFest']))
|
||||
return null;
|
||||
|
||||
return new Splatoon3ScheduleFestival($this->locale, $raw['data']['currentFest']);
|
||||
}
|
||||
|
||||
public function getSchedules(ISplatoonLocale $locale, array $filters): array {
|
||||
$raw = $this->getRawScheduleData();
|
||||
if(!array_key_exists('data', $raw))
|
||||
return [];
|
||||
|
||||
$raw = $raw['data'];
|
||||
$schedules = [];
|
||||
|
||||
if(in_array('regular', $filters) && array_key_exists('regularSchedules', $raw) && !empty($raw['regularSchedules']['nodes']))
|
||||
foreach($raw['regularSchedules']['nodes'] as $info) {
|
||||
if(empty($info['regularMatchSetting']))
|
||||
continue;
|
||||
|
||||
$schedules[] = new Splatoon3ScheduleEntryVs($locale, 'regular', '', $info, $info['regularMatchSetting']);
|
||||
}
|
||||
|
||||
$includeRankedSeries = $includeRankedOpen = $includeRanked = in_array('bankara', $filters);
|
||||
if(!$includeRanked) {
|
||||
$includeRankedSeries = in_array('series', $filters);
|
||||
$includeRankedOpen = in_array('open', $filters);
|
||||
$includeRanked = $includeRankedSeries || $includeRankedOpen;
|
||||
}
|
||||
|
||||
if($includeRanked && array_key_exists('bankaraSchedules', $raw) && !empty($raw['bankaraSchedules']['nodes']))
|
||||
foreach($raw['bankaraSchedules']['nodes'] as $info) {
|
||||
if(empty($info['bankaraMatchSettings']))
|
||||
continue;
|
||||
|
||||
if($includeRankedSeries && !empty($info['bankaraMatchSettings'][0]))
|
||||
$schedules[] = new Splatoon3ScheduleEntryVs($locale, 'bankara', 'CHALLENGE', $info, $info['bankaraMatchSettings'][0]);
|
||||
|
||||
if($includeRankedOpen && !empty($info['bankaraMatchSettings'][1]))
|
||||
$schedules[] = new Splatoon3ScheduleEntryVs($locale, 'bankara', 'OPEN', $info, $info['bankaraMatchSettings'][1]);
|
||||
}
|
||||
|
||||
if(in_array('x', $filters) && array_key_exists('xSchedules', $raw) && !empty($raw['xSchedules']['nodes']))
|
||||
foreach($raw['xSchedules']['nodes'] as $info) {
|
||||
if(empty($info['xMatchSetting']))
|
||||
continue;
|
||||
|
||||
$schedules[] = new Splatoon3ScheduleEntryVs($locale, 'x', '', $info, $info['xMatchSetting']);
|
||||
}
|
||||
|
||||
if(in_array('league', $filters) && array_key_exists('eventSchedules', $raw) && !empty($raw['eventSchedules']['nodes']))
|
||||
foreach($raw['eventSchedules']['nodes'] as $info) {
|
||||
if(empty($info['leagueMatchSetting']))
|
||||
continue;
|
||||
|
||||
$schedules[] = new Splatoon3ScheduleEntryVsLeague($locale, $info);
|
||||
}
|
||||
|
||||
if(in_array('fest', $filters) && array_key_exists('festSchedules', $raw) && !empty($raw['festSchedules']['nodes'])) {
|
||||
foreach($raw['festSchedules']['nodes'] as $info) {
|
||||
if(empty($info['festMatchSettings']))
|
||||
continue;
|
||||
|
||||
$schedules[] = new Splatoon3ScheduleEntryVs($locale, 'fest', '', $info, $info['festMatchSettings']);
|
||||
}
|
||||
|
||||
if(!empty($raw['currentFest']['tricolorStage']))
|
||||
$schedules[] = new Splatoon3ScheduleEntryVsTricolor($locale, $raw['currentFest']['tricolorStage']);
|
||||
}
|
||||
|
||||
if(array_key_exists('coopGroupingSchedule', $raw)) {
|
||||
$coop = $raw['coopGroupingSchedule'];
|
||||
|
||||
if(in_array('coop', $filters) && array_key_exists('regularSchedules', $coop) && !empty($coop['regularSchedules']['nodes']))
|
||||
foreach($coop['regularSchedules']['nodes'] as $info) {
|
||||
if(empty($info['setting']))
|
||||
continue;
|
||||
|
||||
$schedules[] = new Splatoon3ScheduleEntryCoop($locale, 'coop', $info);
|
||||
}
|
||||
|
||||
if(in_array('eggstra', $filters) && array_key_exists('teamContestSchedules', $coop) && !empty($coop['teamContestSchedules']['nodes']))
|
||||
foreach($coop['teamContestSchedules']['nodes'] as $info) {
|
||||
if(empty($info['setting']))
|
||||
continue;
|
||||
|
||||
$schedules[] = new Splatoon3ScheduleEntryCoop($locale, 'eggstra', $info);
|
||||
}
|
||||
|
||||
if(in_array('bigrun', $filters) && array_key_exists('bigRunSchedules', $coop) && !empty($coop['bigRunSchedules']['nodes']))
|
||||
foreach($coop['bigRunSchedules']['nodes'] as $info) {
|
||||
if(empty($info['setting']))
|
||||
continue;
|
||||
|
||||
$schedules[] = new Splatoon3ScheduleEntryCoop($locale, 'bigrun', $info);
|
||||
}
|
||||
}
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
}
|
33
src/Splatoon/Splatoon3/Splatoon3GameMode.php
Normal file
33
src/Splatoon/Splatoon3/Splatoon3GameMode.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonGameMode;
|
||||
|
||||
class Splatoon3GameMode implements ISplatoonGameMode {
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $title,
|
||||
private int $colour,
|
||||
private array $variants = []
|
||||
) {}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getColour(): int {
|
||||
return $this->colour;
|
||||
}
|
||||
|
||||
public function hasVariants(): bool {
|
||||
return !empty($this->variants);
|
||||
}
|
||||
|
||||
public function getVariants(): array {
|
||||
return $this->variants;
|
||||
}
|
||||
}
|
24
src/Splatoon/Splatoon3/Splatoon3GameModeVariant.php
Normal file
24
src/Splatoon/Splatoon3/Splatoon3GameModeVariant.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonGameModeVariant;
|
||||
|
||||
class Splatoon3GameModeVariant implements ISplatoonGameModeVariant {
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $title,
|
||||
private int $colour
|
||||
) {}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getColour(): int {
|
||||
return $this->colour;
|
||||
}
|
||||
}
|
40
src/Splatoon/Splatoon3/Splatoon3League.php
Normal file
40
src/Splatoon/Splatoon3/Splatoon3League.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonLeague;
|
||||
|
||||
class Splatoon3League implements ISplatoonLeague {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private $eventInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->eventInfo['leagueMatchEventId'];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('events:%s:name', $this->eventInfo['id']),
|
||||
$this->eventInfo['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getDescription(): string {
|
||||
return str_replace('<br />', "\n", $this->locale->getString(
|
||||
sprintf('events:%s:desc', $this->eventInfo['id']),
|
||||
$this->eventInfo['desc']
|
||||
));
|
||||
}
|
||||
|
||||
public function getRulesLines(): array {
|
||||
return explode("\n", str_replace('<br />', "\n", $this->locale->getString(
|
||||
sprintf('events:%s:regulation', $this->eventInfo['id']),
|
||||
$this->eventInfo['regulation']
|
||||
)));
|
||||
}
|
||||
|
||||
public function getRulesUrl(): string {
|
||||
return $this->eventInfo['regulationUrl'] ?? '';
|
||||
}
|
||||
}
|
64
src/Splatoon/Splatoon3/Splatoon3Locale.php
Normal file
64
src/Splatoon/Splatoon3/Splatoon3Locale.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonLocale;
|
||||
|
||||
class Splatoon3Locale implements ISplatoonLocale {
|
||||
public function __construct(
|
||||
private array $strings
|
||||
) {}
|
||||
|
||||
public function getString(string $name, string $fallback): string {
|
||||
$name = explode(':', $name);
|
||||
$nameCount = count($name);
|
||||
if($nameCount > 1 && array_key_exists($name[0], $this->strings) && array_key_exists($name[1], $this->strings[$name[0]])) {
|
||||
$stringInfo = $this->strings[$name[0]][$name[1]];
|
||||
|
||||
if($nameCount > 2) {
|
||||
if(array_key_exists($name[2], $stringInfo)) {
|
||||
$stringDeepInfo = $stringInfo[$name[2]];
|
||||
|
||||
if(is_string($stringDeepInfo))
|
||||
return str_replace('<br />', "\n", $stringInfo[$name[2]]);
|
||||
|
||||
if(is_array($stringDeepInfo) && $nameCount > 3) {
|
||||
if(array_key_exists($name[3], $stringDeepInfo)) {
|
||||
$stringDeepInfo = $stringDeepInfo[$name[3]];
|
||||
|
||||
if(is_string($stringDeepInfo))
|
||||
return $stringDeepInfo;
|
||||
|
||||
if(is_array($stringDeepInfo)) {
|
||||
if($nameCount > 4) {
|
||||
if(array_key_exists($name[4], $stringDeepInfo)) {
|
||||
$stringDeepInfo = $stringDeepInfo[$name[4]];
|
||||
|
||||
if(is_string($stringDeepInfo))
|
||||
return $stringDeepInfo;
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
return $stringDeepInfo[array_key_first($stringDeepInfo)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
if(array_key_exists('names', $stringInfo) && array_key_exists($name[2], $stringInfo['names']))
|
||||
return $stringInfo['names'][$name[2]];
|
||||
}
|
||||
|
||||
if(array_key_exists('name', $stringInfo))
|
||||
return $stringInfo['name'];
|
||||
|
||||
if(!empty($stringInfo))
|
||||
return $stringInfo[array_key_first($stringInfo)];
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
}
|
37
src/Splatoon/Splatoon3/Splatoon3ScheduleEntryCoop.php
Normal file
37
src/Splatoon/Splatoon3/Splatoon3ScheduleEntryCoop.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Index\XArray;
|
||||
use Satori\Splatoon\ISplatoonScheduleEntryCoop;
|
||||
|
||||
class Splatoon3ScheduleEntryCoop implements ISplatoonScheduleEntryCoop {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private string $gameMode,
|
||||
private array $entryInfo
|
||||
) {}
|
||||
|
||||
public function getGameMode(): string {
|
||||
return $this->gameMode;
|
||||
}
|
||||
|
||||
public function getGameModeVariant(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTimePeriods(): array {
|
||||
return [
|
||||
new Splatoon3TimePeriod($this->entryInfo['startTime'], $this->entryInfo['endTime']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getStages(): array {
|
||||
return [
|
||||
new Splatoon3Stage($this->locale, $this->entryInfo['setting']['coopStage']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getWeapons(): array {
|
||||
return XArray::select($this->entryInfo['setting']['weapons'], fn($weapon) => new Splatoon3Weapon($this->locale, $weapon));
|
||||
}
|
||||
}
|
38
src/Splatoon/Splatoon3/Splatoon3ScheduleEntryVs.php
Normal file
38
src/Splatoon/Splatoon3/Splatoon3ScheduleEntryVs.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Index\XArray;
|
||||
use Satori\Splatoon\ISplatoonScheduleEntryVs;
|
||||
use Satori\Splatoon\ISplatoonVsRuleset;
|
||||
|
||||
class Splatoon3ScheduleEntryVs implements ISplatoonScheduleEntryVs {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private string $gameMode,
|
||||
private string $gameModeVariant,
|
||||
private array $entryInfo,
|
||||
private array $matchSetting
|
||||
) {}
|
||||
|
||||
public function getGameMode(): string {
|
||||
return $this->gameMode;
|
||||
}
|
||||
|
||||
public function getGameModeVariant(): string {
|
||||
return $this->gameModeVariant;
|
||||
}
|
||||
|
||||
public function getTimePeriods(): array {
|
||||
return [
|
||||
new Splatoon3TimePeriod($this->entryInfo['startTime'], $this->entryInfo['endTime']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getStages(): array {
|
||||
return XArray::select($this->matchSetting['vsStages'], fn($stage) => new Splatoon3Stage($this->locale, $stage));
|
||||
}
|
||||
|
||||
public function getRuleset(): ISplatoonVsRuleset {
|
||||
return new Splatoon3VsRuleset($this->locale, $this->matchSetting['vsRule']);
|
||||
}
|
||||
}
|
38
src/Splatoon/Splatoon3/Splatoon3ScheduleEntryVsLeague.php
Normal file
38
src/Splatoon/Splatoon3/Splatoon3ScheduleEntryVsLeague.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Index\XArray;
|
||||
use Satori\Splatoon\ISplatoonLeague;
|
||||
use Satori\Splatoon\ISplatoonScheduleEntryVsLeague;
|
||||
use Satori\Splatoon\ISplatoonVsRuleset;
|
||||
|
||||
class Splatoon3ScheduleEntryVsLeague implements ISplatoonScheduleEntryVsLeague {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private array $entryInfo
|
||||
) {}
|
||||
|
||||
public function getGameMode(): string {
|
||||
return 'league';
|
||||
}
|
||||
|
||||
public function getGameModeVariant(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTimePeriods(): array {
|
||||
return XArray::select($this->entryInfo['timePeriods'], fn($time) => new Splatoon3TimePeriod($time['startTime'], $time['endTime']));
|
||||
}
|
||||
|
||||
public function getStages(): array {
|
||||
return XArray::select($this->entryInfo['leagueMatchSetting']['vsStages'], fn($stage) => new Splatoon3Stage($this->locale, $stage));
|
||||
}
|
||||
|
||||
public function getRuleset(): ISplatoonVsRuleset {
|
||||
return new Splatoon3VsRuleset($this->locale, $this->entryInfo['leagueMatchSetting']['vsRule']);
|
||||
}
|
||||
|
||||
public function getLeagueInfo(): ISplatoonLeague {
|
||||
return new Splatoon3League($this->locale, $this->entryInfo['leagueMatchSetting']['leagueMatchEvent']);
|
||||
}
|
||||
}
|
36
src/Splatoon/Splatoon3/Splatoon3ScheduleEntryVsTricolor.php
Normal file
36
src/Splatoon/Splatoon3/Splatoon3ScheduleEntryVsTricolor.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonScheduleEntryVs;
|
||||
use Satori\Splatoon\ISplatoonVsRuleset;
|
||||
|
||||
class Splatoon3ScheduleEntryVsTricolor implements ISplatoonScheduleEntryVs {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private array $tricolorStage
|
||||
) {}
|
||||
|
||||
public function getGameMode(): string {
|
||||
return 'tricolor';
|
||||
}
|
||||
|
||||
public function getGameModeVariant(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTimePeriods(): array {
|
||||
return [
|
||||
new Splatoon3TimePeriod(strtotime('today'), strtotime('tomorrow')),
|
||||
];
|
||||
}
|
||||
|
||||
public function getStages(): array {
|
||||
return [
|
||||
new Splatoon3Stage($this->locale, $this->tricolorStage),
|
||||
];
|
||||
}
|
||||
|
||||
public function getRuleset(): ISplatoonVsRuleset {
|
||||
return new Splatoon3VsRulesetTricolor;
|
||||
}
|
||||
}
|
40
src/Splatoon/Splatoon3/Splatoon3ScheduleFestival.php
Normal file
40
src/Splatoon/Splatoon3/Splatoon3ScheduleFestival.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Index\XArray;
|
||||
use Satori\Splatoon\ISplatoonScheduleFestival;
|
||||
|
||||
class Splatoon3ScheduleFestival implements ISplatoonScheduleFestival {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private array $festInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->festInfo['id'];
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->festInfo['title'];
|
||||
}
|
||||
|
||||
public function getStartTime(): int {
|
||||
return $this->festInfo['startTime'];
|
||||
}
|
||||
|
||||
public function getMidTermTime(): int {
|
||||
return $this->festInfo['endTime'];
|
||||
}
|
||||
|
||||
public function getEndTime(): int {
|
||||
return $this->festInfo['midtermTime'];
|
||||
}
|
||||
|
||||
public function getState(): string {
|
||||
return $this->festInfo['state'];
|
||||
}
|
||||
|
||||
public function getTeams(): array {
|
||||
return XArray::select($this->festInfo['teams'], fn($team) => new Splatoon3ScheduleFestivalTeam($team));
|
||||
}
|
||||
}
|
21
src/Splatoon/Splatoon3/Splatoon3ScheduleFestivalTeam.php
Normal file
21
src/Splatoon/Splatoon3/Splatoon3ScheduleFestivalTeam.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonScheduleFestivalTeam;
|
||||
|
||||
class Splatoon3ScheduleFestivalTeam implements ISplatoonScheduleFestivalTeam {
|
||||
public function __construct(
|
||||
private array $teamInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->teamInfo['id'];
|
||||
}
|
||||
|
||||
public function getColour(): int {
|
||||
return (round($this->teamInfo['color']['a'] * 0xFF) << 24)
|
||||
| (round($this->teamInfo['color']['r'] * 0xFF) << 16)
|
||||
| (round($this->teamInfo['color']['g'] * 0xFF) << 8)
|
||||
| round($this->teamInfo['color']['b'] * 0xFF);
|
||||
}
|
||||
}
|
27
src/Splatoon/Splatoon3/Splatoon3Stage.php
Normal file
27
src/Splatoon/Splatoon3/Splatoon3Stage.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonStage;
|
||||
|
||||
class Splatoon3Stage implements ISplatoonStage {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private array $stageInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->stageInfo['vsStageId']
|
||||
?? $this->stageInfo['id'];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('stages:%s:name', $this->stageInfo['id']),
|
||||
$this->stageInfo['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return $this->stageInfo['image']['url'];
|
||||
}
|
||||
}
|
22
src/Splatoon/Splatoon3/Splatoon3TimePeriod.php
Normal file
22
src/Splatoon/Splatoon3/Splatoon3TimePeriod.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonTimePeriod;
|
||||
|
||||
class Splatoon3TimePeriod implements ISplatoonTimePeriod {
|
||||
private int $startTime;
|
||||
private int $endTime;
|
||||
|
||||
public function __construct(string $startTime, string $endTime) {
|
||||
$this->startTime = strtotime($startTime);
|
||||
$this->endTime = strtotime($endTime);
|
||||
}
|
||||
|
||||
public function getStartTime(): int {
|
||||
return $this->startTime;
|
||||
}
|
||||
|
||||
public function getEndTime(): int {
|
||||
return $this->endTime;
|
||||
}
|
||||
}
|
39
src/Splatoon/Splatoon3/Splatoon3VsRuleset.php
Normal file
39
src/Splatoon/Splatoon3/Splatoon3VsRuleset.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonVsRuleset;
|
||||
|
||||
class Splatoon3VsRuleset implements ISplatoonVsRuleset {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private array $rulesetInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->rulesetInfo['rule'];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('rules:%s:name', $this->rulesetInfo['id']),
|
||||
$this->rulesetInfo['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getShortName(): string {
|
||||
$id = $this->rulesetInfo['rule'];
|
||||
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 === 'TEAM_CONTEST')
|
||||
return 'EW';
|
||||
return $id;
|
||||
}
|
||||
}
|
18
src/Splatoon/Splatoon3/Splatoon3VsRulesetTricolor.php
Normal file
18
src/Splatoon/Splatoon3/Splatoon3VsRulesetTricolor.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonVsRuleset;
|
||||
|
||||
class Splatoon3VsRulesetTricolor implements ISplatoonVsRuleset {
|
||||
public function getId(): string {
|
||||
return 'TRICOLOR_TURF_WAR';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'Tricolor Turf War';
|
||||
}
|
||||
|
||||
public function getShortName(): string {
|
||||
return 'TTW';
|
||||
}
|
||||
}
|
26
src/Splatoon/Splatoon3/Splatoon3Weapon.php
Normal file
26
src/Splatoon/Splatoon3/Splatoon3Weapon.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon\Splatoon3;
|
||||
|
||||
use Satori\Splatoon\ISplatoonWeapon;
|
||||
|
||||
class Splatoon3Weapon implements ISplatoonWeapon {
|
||||
public function __construct(
|
||||
private Splatoon3Locale $locale,
|
||||
private array $weaponInfo
|
||||
) {}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->weaponInfo['__splatoon3ink_id'];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->locale->getString(
|
||||
sprintf('weapons:%s:name', $this->weaponInfo['__splatoon3ink_id']),
|
||||
$this->weaponInfo['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getImage(): string {
|
||||
return $this->weaponInfo['image']['url'];
|
||||
}
|
||||
}
|
28
src/Splatoon/SplatoonContext.php
Normal file
28
src/Splatoon/SplatoonContext.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
use Syokuhou\IConfig;
|
||||
|
||||
class SplatoonContext {
|
||||
private array $games = [];
|
||||
|
||||
public function __construct(private IConfig $config) {
|
||||
$this->games[] = new Splatoon3\Splatoon3Game;
|
||||
$this->games[] = new Splatoon2\Splatoon2Game;
|
||||
}
|
||||
|
||||
public function getConfig(): IConfig {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function getGames(): array {
|
||||
return $this->games;
|
||||
}
|
||||
|
||||
public function getGameByName(string $name): ?ISplatoonGame {
|
||||
foreach($this->games as $game)
|
||||
if($game->getName() === $name)
|
||||
return $game;
|
||||
return null;
|
||||
}
|
||||
}
|
16
src/Splatoon/SplatoonErrorResponse.php
Normal file
16
src/Splatoon/SplatoonErrorResponse.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
class SplatoonErrorResponse implements \JsonSerializable {
|
||||
public function __construct(
|
||||
private string $code
|
||||
) {}
|
||||
|
||||
public function getCode(): string {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed {
|
||||
return ['error' => $this->code];
|
||||
}
|
||||
}
|
241
src/Splatoon/SplatoonRoutes.php
Normal file
241
src/Splatoon/SplatoonRoutes.php
Normal file
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
namespace Satori\Splatoon;
|
||||
|
||||
use stdClass;
|
||||
use Index\XArray;
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
|
||||
class SplatoonRoutes extends RouteHandler {
|
||||
public function __construct(
|
||||
private SplatoonContext $context
|
||||
) {}
|
||||
|
||||
private static function encodeGameInfo(ISplatoonGame $gameInfo): array {
|
||||
return [
|
||||
'id' => $gameInfo->getName(),
|
||||
'name' => $gameInfo->getTitle(),
|
||||
'colour' => $gameInfo->getColour(),
|
||||
'variant' => $gameInfo->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
private static function encodeStageInfo(ISplatoonStage $stageInfo): array {
|
||||
return [
|
||||
'id' => $stageInfo->getId(),
|
||||
'name' => $stageInfo->getName(),
|
||||
'image' => $stageInfo->getImage(),
|
||||
];
|
||||
}
|
||||
|
||||
#[Route('GET', '/splatoon.php')]
|
||||
public function getPHP($response, $request) {
|
||||
$gameInfo = $this->context->getGameByName((string)$request->getParam('g'));
|
||||
if($gameInfo === null)
|
||||
return new SplatoonErrorResponse('game:id');
|
||||
|
||||
$localeId = $gameInfo->getLocaleAlias((string)$request->getParam('l'));
|
||||
if(!$gameInfo->isSupportedLocale($localeId))
|
||||
return new SplatoonErrorResponse('locale:id');
|
||||
|
||||
$localeInfo = $gameInfo->createLocale($localeId);
|
||||
$typeId = (string)$request->getParam('t');
|
||||
|
||||
if($typeId === 'schedules' && $gameInfo instanceof ISplatoonHasSchedules) {
|
||||
$filters = array_filter(explode(',', (string)$request->getParam('sf')));
|
||||
$filterAll = empty($filters);
|
||||
|
||||
if($filterAll)
|
||||
$filters = $gameInfo->getScheduleFilters();
|
||||
else
|
||||
foreach($filters as &$filter) {
|
||||
$filter = $gameInfo->getScheduleFilterAlias($filter);
|
||||
if(!$gameInfo->isValidScheduleFilter($filter))
|
||||
return new SplatoonErrorResponse('schedules:filter');
|
||||
}
|
||||
|
||||
// can't really test this while there's no splatfest going on, test it later
|
||||
$fest = null;
|
||||
$festInfo = $gameInfo->getScheduleFestival();
|
||||
if($festInfo !== null) {
|
||||
$fest = new stdClass;
|
||||
$fest->id = $festInfo->getId();
|
||||
$fest->title = $festInfo->getTitle();
|
||||
$fest->start = $festInfo->getStartTime();
|
||||
$fest->midterm = $festInfo->getMidTermTime();
|
||||
$fest->end = $festInfo->getEndTime();
|
||||
$fest->state = $festInfo->getState();
|
||||
$fest->teams = XArray::select($festInfo->getTeams(), function($teamInfo) {
|
||||
$team = new stdClass;
|
||||
$team->id = $teamInfo->getId();
|
||||
$team->colour = $teamInfo->getColour();
|
||||
|
||||
return $team;
|
||||
});
|
||||
}
|
||||
|
||||
$modes = XArray::select($gameInfo->getScheduleModes($localeInfo, $filters), function($modeInfo) {
|
||||
$mode = new stdClass;
|
||||
$mode->id = $modeInfo->getName();
|
||||
$mode->name = $modeInfo->getTitle();
|
||||
$mode->colour = $modeInfo->getColour();
|
||||
|
||||
if($modeInfo->hasVariants())
|
||||
$mode->variants = XArray::select($modeInfo->getVariants(), function($variantInfo) {
|
||||
$variant = new stdClass;
|
||||
$variant->id = $variantInfo->getName();
|
||||
$variant->name = $variantInfo->getTitle();
|
||||
$variant->colour = $variantInfo->getColour();
|
||||
|
||||
return $variant;
|
||||
});
|
||||
|
||||
return $mode;
|
||||
});
|
||||
|
||||
$schedules = [];
|
||||
$scheduleInfos = $gameInfo->getSchedules($localeInfo, $filters);
|
||||
foreach($scheduleInfos as $scheduleInfo) {
|
||||
if($scheduleInfo instanceof ISplatoonScheduleEntry) {
|
||||
$gameMode = $scheduleInfo->getGameMode() . ':' . $scheduleInfo->getGameModeVariant();
|
||||
|
||||
if(!array_key_exists($gameMode, $schedules)) {
|
||||
$schedules[$gameMode] = $scheduleMode = new stdClass;
|
||||
$scheduleMode->mode = $scheduleInfo->getGameMode();
|
||||
|
||||
$variant = $scheduleInfo->getGameModeVariant();
|
||||
if($variant !== '')
|
||||
$scheduleMode->variant = $variant;
|
||||
|
||||
$scheduleMode->schedule = [];
|
||||
}
|
||||
|
||||
$schedules[$gameMode]->schedule[] = $schedule = new stdClass;
|
||||
$schedule->periods = XArray::select($scheduleInfo->getTimePeriods(), function($periodInfo) {
|
||||
$period = new stdClass;
|
||||
$period->start = str_replace('+00:00', 'Z', date(\DateTime::ATOM, $periodInfo->getStartTime()));
|
||||
$period->end = str_replace('+00:00', 'Z', date(\DateTime::ATOM, $periodInfo->getEndTime()));
|
||||
|
||||
return $period;
|
||||
});
|
||||
|
||||
$stagesInfo = $scheduleInfo->getStages();
|
||||
if(!empty($stagesInfo))
|
||||
$schedule->stages = XArray::select($stagesInfo, fn($item) => self::encodeStageInfo($item));
|
||||
$schedule->flags = [];
|
||||
|
||||
if($scheduleInfo instanceof ISplatoonScheduleEntryVs) {
|
||||
$schedule->variant = 'vs';
|
||||
|
||||
$rulesetInfo = $scheduleInfo->getRuleset();
|
||||
$schedule->ruleset = new stdClass;
|
||||
$schedule->ruleset->id = $rulesetInfo->getId();
|
||||
$schedule->ruleset->name = $rulesetInfo->getName();
|
||||
$schedule->ruleset->short = $rulesetInfo->getShortName();
|
||||
|
||||
if($scheduleInfo instanceof ISplatoonScheduleEntryVsLeague) {
|
||||
$schedule->flags[] = 'league';
|
||||
|
||||
$leagueInfo = $scheduleInfo->getLeagueInfo();
|
||||
$schedule->league = new stdClass;
|
||||
$schedule->league->id = $leagueInfo->getId();
|
||||
$schedule->league->name = $leagueInfo->getName();
|
||||
$schedule->league->desc = $leagueInfo->getDescription();
|
||||
$schedule->league->rules = new stdClass;
|
||||
$schedule->league->rules->lines = $leagueInfo->getRulesLines();
|
||||
$schedule->league->rules->url = $leagueInfo->getRulesUrl();
|
||||
}
|
||||
} elseif($scheduleInfo instanceof ISplatoonScheduleEntryCoop) {
|
||||
$schedule->variant = 'coop';
|
||||
|
||||
$weaponsInfo = $scheduleInfo->getWeapons();
|
||||
if(!empty($weaponsInfo))
|
||||
$schedule->weapons = XArray::select($weaponsInfo, function($weaponInfo) {
|
||||
$weapon = new stdClass;
|
||||
$weapon->id = $weaponInfo->getId();
|
||||
$weapon->name = $weaponInfo->getName();
|
||||
$weapon->image = $weaponInfo->getImage();
|
||||
|
||||
return $weapon;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'game' => self::encodeGameInfo($gameInfo),
|
||||
'fest' => $fest,
|
||||
'modes' => $modes,
|
||||
'schedules' => array_values($schedules),
|
||||
];
|
||||
}
|
||||
|
||||
if($typeId === 'fests' && $gameInfo instanceof ISplatoonHasFestivals) {
|
||||
$regions = array_filter(explode(',', (string)$request->getParam('fr')));
|
||||
$regionsAll = empty($regions);
|
||||
|
||||
if($regionsAll)
|
||||
$regions = $gameInfo->getFestivalRegions();
|
||||
else
|
||||
foreach($regions as &$region) {
|
||||
$region = $gameInfo->getFestivalRegionAlias($region);
|
||||
if(!$gameInfo->isSupportedFestivalRegion($region))
|
||||
return new SplatoonErrorResponse('fests:region');
|
||||
}
|
||||
|
||||
$fests = [];
|
||||
$festInfos = $gameInfo->getFestivals($localeInfo, $regions);
|
||||
|
||||
foreach($festInfos as $festInfo) {
|
||||
if(array_key_exists($festInfo->getId(), $fests)) {
|
||||
$group = $fests[$festInfo->getId()];
|
||||
} else {
|
||||
$fests[$festInfo->getId()] = $group = new stdClass;
|
||||
$group->regions = [];
|
||||
}
|
||||
|
||||
$group->regions[] = $fest = new stdClass;
|
||||
$fest->id = $festInfo->getLocalId();
|
||||
$fest->region = $festInfo->getRegion();
|
||||
$fest->state = $festInfo->getState();
|
||||
$fest->start = str_replace('+00:00', 'Z', date(\DateTime::ATOM, $festInfo->getStartTime()));
|
||||
$fest->end = str_replace('+00:00', 'Z', date(\DateTime::ATOM, $festInfo->getEndTime()));
|
||||
$fest->title = $festInfo->getTitle();
|
||||
$fest->image = $festInfo->getImage();
|
||||
$fest->can_vote = $festInfo->canVote();
|
||||
$fest->teams = XArray::select($festInfo->getTeams(), function($teamInfo) {
|
||||
$team = new stdClass;
|
||||
$team->id = $teamInfo->getLocalId();
|
||||
$team->name = $teamInfo->getName();
|
||||
$team->image = $teamInfo->getImage();
|
||||
$team->colour = $teamInfo->getColour();
|
||||
$team->has_results = $teamInfo->hasResults();
|
||||
|
||||
if($team->has_results) {
|
||||
$team->is_winner = $teamInfo->isWinner();
|
||||
$team->results = XArray::select($teamInfo->getResults(), function($resultInfo) {
|
||||
$result = new stdClass;
|
||||
$result->category = $resultInfo->getCategory();
|
||||
$result->ratio = $resultInfo->getRatio();
|
||||
$result->is_top = $resultInfo->isTop();
|
||||
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
return $team;
|
||||
});
|
||||
|
||||
if($festInfo->hasSpecialStages())
|
||||
$fest->special_stages = XArray::select($festInfo->getSpecialStages(), fn($item) => self::encodeStageInfo($item));
|
||||
}
|
||||
|
||||
return [
|
||||
'game' => self::encodeGameInfo($gameInfo),
|
||||
'fests' => array_values($fests),
|
||||
];
|
||||
}
|
||||
|
||||
return new SplatoonErrorResponse('type:id');
|
||||
}
|
||||
}
|
85
src/Translation/TranslateContext.php
Normal file
85
src/Translation/TranslateContext.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
namespace Satori\Translation;
|
||||
|
||||
use stdClass;
|
||||
use Syokuhou\IConfig;
|
||||
|
||||
class TranslateContext {
|
||||
public const GOOGLE_TRANSLATE = 'https://translate.google.com/translate_a/single?ie=UTF-8&oe=UTF-8&multires=1&client=gtx&sl=%s&tl=%s&dt=t&q=%s';
|
||||
public const GOOGLE_USERAGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36';
|
||||
public const GOOGLE_LANGS = [
|
||||
'af', 'sq', 'ar', 'hy', 'az', 'eu', 'be', 'bn', 'bs',
|
||||
'bg', 'ca', 'ny', 'co', 'hr', 'cs', 'da', 'nl', 'en',
|
||||
'eo', 'tl', 'fi', 'fr', 'fy', 'gl', 'ka', 'de', 'el',
|
||||
'gu', 'ht', 'ha', 'iw', 'hi', 'hu', 'is', 'ig', 'id',
|
||||
'ga', 'ja', 'jw', 'kn', 'kk', 'km', 'ko', 'ku', 'ky',
|
||||
'lo', 'la', 'lv', 'lt', 'lb', 'mk', 'mg', 'ms', 'ml',
|
||||
'mt', 'mi', 'mr', 'mn', 'my', 'ne', 'no', 'ps', 'fa',
|
||||
'pl', 'pt', 'ma', 'ro', 'ru', 'sm', 'gd', 'sr', 'st',
|
||||
'sn', 'sd', 'si', 'sk', 'sl', 'so', 'es', 'su', 'sw',
|
||||
'sv', 'tg', 'ta', 'te', 'th', 'tr', 'uk', 'ur', 'uz',
|
||||
'vi', 'cy', 'xh', 'yi', 'yo', 'zu', 'it',
|
||||
'ceb', 'haw', 'hmn',
|
||||
'zh-cn', 'zh-tw',
|
||||
];
|
||||
|
||||
private IConfig $config;
|
||||
|
||||
public function __construct(IConfig $config) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getConfig(): IConfig {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function getGoogleLanguageCodes(): array {
|
||||
return self::GOOGLE_LANGS;
|
||||
}
|
||||
|
||||
public function getGoogleLanguages(): array {
|
||||
$langs = [];
|
||||
|
||||
foreach(self::GOOGLE_LANGS as $langId)
|
||||
$langs[] = [
|
||||
'code' => $langId,
|
||||
'name' => locale_get_display_name($langId),
|
||||
];
|
||||
|
||||
return $langs;
|
||||
}
|
||||
|
||||
public function googleTranslate(string $from, string $to, string $text): ?object {
|
||||
$curl = curl_init(sprintf(
|
||||
self::GOOGLE_TRANSLATE,
|
||||
rawurlencode($from),
|
||||
rawurlencode($to),
|
||||
rawurlencode($text)
|
||||
));
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_USERAGENT => self::GOOGLE_USERAGENT,
|
||||
]);
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if(empty($response))
|
||||
return null;
|
||||
|
||||
$response = json_decode($response);
|
||||
if(empty($response))
|
||||
return null;
|
||||
|
||||
$result = new stdClass;
|
||||
$result->translatedText = $response[0][0][0] ?? '';
|
||||
$result->originalText = $response[0][0][1] ?? '';
|
||||
$result->fromLangCode = $response[2] ?? '';
|
||||
$result->fromLangName = locale_get_display_name($result->fromLangCode);
|
||||
$result->toLangCode = $to;
|
||||
$result->toLangName = locale_get_display_name($to);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
99
src/Translation/TranslateRoutes.php
Normal file
99
src/Translation/TranslateRoutes.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
namespace Satori\Translation;
|
||||
|
||||
use DOMDocument;
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
|
||||
class TranslateRoutes extends RouteHandler {
|
||||
public function __construct(
|
||||
private TranslateContext $context
|
||||
) {}
|
||||
|
||||
#[Route('GET', '/translate.php')]
|
||||
public function getPHP($response, $request) {
|
||||
$format = (string)($request->getParam('fmt') ?? 'json');
|
||||
if($format !== 'json' && $format !== 'xml')
|
||||
return '';
|
||||
|
||||
$mode = (string)$request->getParam('m');
|
||||
|
||||
if($mode === 'list') {
|
||||
$langs = $this->context->getGoogleLanguages();
|
||||
|
||||
if($format === 'xml') {
|
||||
$document = new DOMDocument('1.0', 'UTF-8');
|
||||
|
||||
$array = $document->createElement('ArrayOfLanguage');
|
||||
$document->appendChild($array);
|
||||
|
||||
foreach($langs as $langInfo) {
|
||||
$lang = $document->createElement('Language');
|
||||
|
||||
foreach($langInfo as $name => $value)
|
||||
$lang->setAttribute($name, $value);
|
||||
|
||||
$array->appendChild($lang);
|
||||
}
|
||||
|
||||
return $document->saveXML();
|
||||
}
|
||||
|
||||
return $langs;
|
||||
}
|
||||
|
||||
if($mode === 'do') {
|
||||
$from = (string)($request->getParam('f') ?? 'auto');
|
||||
$to = (string)($request->getParam('t') ?? 'en');
|
||||
$langs = $this->context->getGoogleLanguageCodes();
|
||||
|
||||
$encodeOutput = function(array $info) use ($format) {
|
||||
if($format === 'json')
|
||||
return $info;
|
||||
|
||||
if($format === 'xml') {
|
||||
$document = new DOMDocument('1.0', 'UTF-8');
|
||||
$translation = $document->createElement('Translation');
|
||||
|
||||
foreach($info as $name => $value)
|
||||
$translation->setAttribute($name, $value);
|
||||
|
||||
$document->appendChild($translation);
|
||||
|
||||
return $document->saveXML();
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
if($from !== 'auto' && !in_array($from, $langs))
|
||||
return $encodeOutput(['error' => 'Source language is not supported.']);
|
||||
if(!in_array($to, $langs))
|
||||
return $encodeOutput(['error' => 'Target language is not supported.']);
|
||||
|
||||
$text = '';
|
||||
if($request->hasParam('z'))
|
||||
$text = trim((string)$request->getParam('z'));
|
||||
elseif($request->hasContent())
|
||||
$text = trim((string)$request->getContent());
|
||||
|
||||
if($text === '')
|
||||
return $encodeOutput(['error' => 'Nothing to translate.']);
|
||||
|
||||
$result = $this->context->googleTranslate($from, $to, $text);
|
||||
if($result === null)
|
||||
return $encodeOutput(['error' => 'Translation failed.']);
|
||||
|
||||
return $encodeOutput([
|
||||
'from' => $result->fromLangCode,
|
||||
'from_text' => $result->fromLangName,
|
||||
'to' => $result->toLangCode,
|
||||
'to_text' => $result->toLangName,
|
||||
'original' => $result->originalText,
|
||||
'result' => $result->translatedText,
|
||||
]);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
34
tools/migrate
Executable file
34
tools/migrate
Executable file
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
require_once __DIR__ . '/../satori.php';
|
||||
|
||||
try {
|
||||
touch(SAT_ROOT . '/.migrating');
|
||||
chmod(SAT_ROOT . '/.migrating', 0777);
|
||||
|
||||
$db = $sat->getDatabase();
|
||||
|
||||
echo 'Creating migration manager...' . PHP_EOL;
|
||||
$manager = $db->createMigrationManager();
|
||||
|
||||
echo 'Preparing to run migrations...' . PHP_EOL;
|
||||
$manager->init();
|
||||
|
||||
echo 'Creating migration repository...' . PHP_EOL;
|
||||
$repo = $db->createMigrationRepo();
|
||||
|
||||
echo 'Running migrations...' . PHP_EOL;
|
||||
$completed = $manager->processMigrations($repo);
|
||||
|
||||
if(empty($completed)) {
|
||||
echo 'There were no migrations to run!' . PHP_EOL;
|
||||
} else {
|
||||
echo 'The following migrations have been completed:' . PHP_EOL;
|
||||
foreach($completed as $migration)
|
||||
echo ' - ' . $migration . PHP_EOL;
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
} finally {
|
||||
unlink(SAT_ROOT . '/.migrating');
|
||||
}
|
26
tools/new-migration
Executable file
26
tools/new-migration
Executable file
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
use Index\Data\Migration\FsDbMigrationRepo;
|
||||
|
||||
require_once __DIR__ . '/../satori.php';
|
||||
|
||||
$db = $sat->getDatabase();
|
||||
$repo = $db->createMigrationRepo();
|
||||
if(!($repo instanceof FsDbMigrationRepo)) {
|
||||
echo 'Migration repository type does not support creation of templates.' . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
|
||||
$baseName = implode(' ', array_slice($argv, 1));
|
||||
$manager = $db->createMigrationManager();
|
||||
|
||||
try {
|
||||
$names = $manager->createNames($baseName);
|
||||
} catch(InvalidArgumentException $ex) {
|
||||
echo $ex->getMessage() . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
|
||||
$repo->saveMigrationTemplate($names->name, $manager->template($names->className));
|
||||
|
||||
echo "Template for '{$names->className}' has been saved to {$names->name}.php." . PHP_EOL;
|
Reference in a new issue