142 lines
5.1 KiB
PHP
142 lines
5.1 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||
|
||
/* === API: upload skin === */
|
||
if ($method === 'POST' && $path === '/upload') {
|
||
$name = $_POST['username'] ?? '';
|
||
if (!preg_match('/^[A-Za-z0-9_]{2,16}$/', $name)) return err('Неправильний нік (2–16, латиниця/цифри/_).');
|
||
$tmp = require_png_upload('file');
|
||
|
||
[$w, $h] = must_image_size($tmp);
|
||
if (!($w === 64 && ($h === 64 || $h === 128))) return err('Скін має бути 64×64 або 64×128.');
|
||
$img = @imagecreatefrompng($tmp) ?: err('Пошкоджений PNG.');
|
||
|
||
if (!is_dir(__DIR__ . '/skins')) @mkdir(__DIR__ . '/skins', 0755, true);
|
||
$dest = __DIR__ . "/skins/{$name}.png";
|
||
@imagepng($img, $dest) ?: err('Не вдалося зберегти (права на ./skins).');
|
||
imagedestroy($img);
|
||
|
||
json_ok(['skin_url' => base() . "/skins/{$name}.png", 'username' => $name]);
|
||
}
|
||
|
||
/* === API: upload cape === */
|
||
if ($method === 'POST' && $path === '/upload-cape') {
|
||
$name = $_POST['username'] ?? '';
|
||
if (!preg_match('/^[A-Za-z0-9_]{2,16}$/', $name)) return err('Неправильний нік (2–16, латиниця/цифри/_).');
|
||
$tmp = require_png_upload('file');
|
||
|
||
[$w, $h] = must_image_size($tmp);
|
||
// Плащ: співвідношення 2:1, мінімум 64×32, кратні двійці (типові 64×32, 128×64, 256×128, …)
|
||
if (!($w === 2 * $h && $w >= 64 && $h >= 32)) {
|
||
return err("Плащ має бути зі співвідношенням 2:1 (64×32, 128×64, …). Отримано {$w}×{$h}.");
|
||
}
|
||
$img = @imagecreatefrompng($tmp) ?: err('Пошкоджений PNG.');
|
||
|
||
if (!is_dir(__DIR__ . '/capes')) @mkdir(__DIR__ . '/capes', 0755, true);
|
||
$dest = __DIR__ . "/capes/{$name}.png";
|
||
@imagepng($img, $dest) ?: err('Не вдалося зберегти (права на ./capes).');
|
||
imagedestroy($img);
|
||
|
||
json_ok(['cape_url' => base() . "/capes/{$name}.png", 'username' => $name]);
|
||
}
|
||
|
||
/* === UI === */
|
||
if ($method === 'GET' && $path === '/') {
|
||
header('Content-Type: text/html; charset=utf-8');
|
||
echo <<<HTML
|
||
<!doctype html>
|
||
<html lang="uk">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
||
<title>MSS - MrAkells Skin Server</title>
|
||
|
||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||
<link rel="shortcut icon" href="/favicon.ico">
|
||
|
||
<link rel="stylesheet" href="assets/style.css">
|
||
|
||
</head>
|
||
<body>
|
||
<div class="shell">
|
||
<div class="card">
|
||
<form id="form" class="form" autocomplete="off">
|
||
<div class="segment" role="tablist" aria-label="Тип">
|
||
<button type="button" class="segbtn active" id="seg-skin" aria-selected="true">Скін</button>
|
||
<button type="button" class="segbtn" id="seg-cape" aria-selected="false">Плащ</button>
|
||
</div>
|
||
|
||
<div>
|
||
<div class="lbl">Нікнейм</div>
|
||
<input class="input" name="username" id="username" placeholder="Наприклад, Notch" minlength="2" maxlength="16" pattern="[A-Za-z0-9_]{2,16}" required>
|
||
</div>
|
||
<div>
|
||
<div class="lbl">Файл PNG</div>
|
||
<input class="input" type="file" id="file" name="file" accept="image/png" required>
|
||
</div>
|
||
<button class="btn" id="submit" type="submit">Завантажити</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toasts"></div>
|
||
|
||
<a href="https://t.me/mrakells" class="credit" target="_blank" rel="noopener">by MrAkells</a>
|
||
|
||
<script src="assets/app.js"></script>
|
||
</body>
|
||
</html>
|
||
HTML;
|
||
exit;
|
||
}
|
||
|
||
/* === 404 === */
|
||
http_response_code(404);
|
||
header('Content-Type: text/plain; charset=utf-8');
|
||
echo "Not Found";
|
||
exit;
|
||
|
||
/* === helpers === */
|
||
function err(string $m): void
|
||
{
|
||
header('Content-Type: application/json; charset=utf-8', true, 400);
|
||
echo json_encode(['status' => 'error', 'error' => $m], JSON_UNESCAPED_UNICODE);
|
||
exit;
|
||
}
|
||
function json_ok(array $extra): void
|
||
{
|
||
header('Content-Type: application/json; charset=utf-8');
|
||
echo json_encode(['status' => 'ok'] + $extra, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||
exit;
|
||
}
|
||
function require_png_upload(string $key): string
|
||
{
|
||
if (!isset($_FILES[$key]) || $_FILES[$key]['error'] !== UPLOAD_ERR_OK) err('Файл не завантажено.');
|
||
$tmp = $_FILES[$key]['tmp_name'];
|
||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||
$mime = finfo_file($finfo, $tmp);
|
||
finfo_close($finfo);
|
||
if ($mime !== 'image/png') err('Лише PNG.');
|
||
return $tmp;
|
||
}
|
||
function must_image_size(string $tmp): array
|
||
{
|
||
$info = @getimagesize($tmp);
|
||
if (!$info) err('Не PNG.');
|
||
return [$info[0], $info[1]];
|
||
}
|
||
function base(): string
|
||
{
|
||
$https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['SERVER_PORT'] ?? '') === '443');
|
||
$scheme = $https ? 'https' : 'http';
|
||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||
return $scheme . '://' . $host;
|
||
}
|