mirror of
https://github.com/rekryt/iplist.git
synced 2025-10-13 00:49:36 +03:00
feat: new portal selection and favicons
This commit is contained in:
123
src/App/Controller/FaviconController.php
Normal file
123
src/App/Controller/FaviconController.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\App\Controller;
|
||||
|
||||
use Amp\Http\HttpStatus;
|
||||
use Amp\Http\Server\Driver\SocketClientFactory;
|
||||
use Amp\Http\Server\Request;
|
||||
use OpenCCK\Infrastructure\Storage\IconsStorage;
|
||||
use function Amp\File\isFile;
|
||||
use function Amp\File\read;
|
||||
use function finfo_close;
|
||||
use function finfo_file;
|
||||
use function finfo_open;
|
||||
|
||||
class FaviconController extends AbstractIPListController {
|
||||
private IconsStorage $storage;
|
||||
|
||||
public function __construct(Request $request, array $headers = []) {
|
||||
parent::__construct($request, $headers);
|
||||
|
||||
$this->storage = IconsStorage::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBody(): string {
|
||||
$site = $this->request->getQueryParameter('site');
|
||||
|
||||
if ($this->storage->has($site)) {
|
||||
return $this->output($this->storage->get($site));
|
||||
}
|
||||
|
||||
if (in_array($site, array_keys($this->service->sites))) {
|
||||
$content = file_get_contents('https://' . $site . '/favicon.ico');
|
||||
if ($content) {
|
||||
$filename = $site . '.ico';
|
||||
$this->saveIcon($site, $filename, $content);
|
||||
return $this->output($filename);
|
||||
}
|
||||
|
||||
$siteUrl = 'https://' . $site . '/';
|
||||
$indexBody = file_get_contents('https://' . $site . '/');
|
||||
if ($indexBody) {
|
||||
$rawUrl = $this->extractFaviconHref($indexBody);
|
||||
$url = parse_url($rawUrl);
|
||||
$parts = explode('.', $url['path']);
|
||||
$ext = end($parts);
|
||||
|
||||
$content = file_get_contents($siteUrl . $url['path']);
|
||||
if ($content) {
|
||||
$filename = $site . '.' . $ext;
|
||||
$this->saveIcon($site, $filename, $content);
|
||||
return $this->output($filename);
|
||||
}
|
||||
}
|
||||
|
||||
$filename = 'blank.png';
|
||||
$this->saveIcon($site, $filename);
|
||||
return $this->output($filename);
|
||||
}
|
||||
|
||||
$this->setHttpStatus(HttpStatus::NOT_FOUND);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $html
|
||||
* @return ?string
|
||||
*/
|
||||
private function extractFaviconHref(string $html): ?string {
|
||||
$dom = new \DOMDocument();
|
||||
|
||||
// Заглушка для подавления ошибок из-за невалидного HTML
|
||||
\libxml_use_internal_errors(true);
|
||||
$dom->loadHTML($html);
|
||||
\libxml_clear_errors();
|
||||
|
||||
$links = $dom->getElementsByTagName('link');
|
||||
|
||||
foreach ($links as $link) {
|
||||
$rel = strtolower($link->getAttribute('rel'));
|
||||
if (in_array($rel, ['icon', 'shortcut icon', 'alternate icon'])) {
|
||||
$href = $link->getAttribute('href');
|
||||
return $href !== '' ? $href : null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $site
|
||||
* @param string $filename
|
||||
* @param ?string $content
|
||||
* @return void
|
||||
*/
|
||||
private function saveIcon(string $site, string $filename, ?string $content = null): void {
|
||||
$this->storage->set($site, $filename);
|
||||
if ($content) {
|
||||
file_put_contents(PATH_ROOT . '/storage/icons/' . $filename, $content);
|
||||
}
|
||||
$this->storage->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
private function output(string $filename): string {
|
||||
$path = PATH_ROOT . '/storage/icons/' . $filename;
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$contentType = finfo_file($finfo, $path);
|
||||
finfo_close($finfo);
|
||||
|
||||
$this->setHeaders([
|
||||
'Content-Type' => $contentType,
|
||||
'Cache-Control' => 'public, max-age=31536000, immutable',
|
||||
'Expires' => gmdate('D, d M Y H:i:s', strtotime('+1 year')) . ' GMT',
|
||||
]);
|
||||
return read($path);
|
||||
}
|
||||
}
|
@@ -98,9 +98,10 @@ final class Server implements AppModuleInterface {
|
||||
// $this->bindContext
|
||||
//);
|
||||
$router = new Router($this->httpServer, $this->logger, $this->errorHandler);
|
||||
$httpHandler = HTTPHandler::getInstance($this->logger)->getHandler();
|
||||
$router->addRoute('GET', '/', $httpHandler);
|
||||
$router->addRoute('GET', '/{name:.+}', $httpHandler);
|
||||
$httpHandlerInstance = HTTPHandler::getInstance($this->logger);
|
||||
$router->addRoute('GET', '/', $httpHandlerInstance->getHandler('main'));
|
||||
$router->addRoute('GET', '/favicon', $httpHandlerInstance->getHandler('favicon'));
|
||||
$router->addRoute('GET', '/{name:.+}', $httpHandlerInstance->getHandler('main'));
|
||||
$router->setFallback(new DocumentRoot($this->httpServer, $this->errorHandler, PATH_ROOT . '/public'));
|
||||
|
||||
$this->httpServer->start($router, $this->errorHandler);
|
||||
|
42
src/Infrastructure/Storage/IconsStorage.php
Normal file
42
src/Infrastructure/Storage/IconsStorage.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Infrastructure\Storage;
|
||||
|
||||
use OpenCCK\Infrastructure\API\App;
|
||||
use Revolt\EventLoop;
|
||||
|
||||
class IconsStorage implements StorageInterface {
|
||||
const FILENAME = 'icons.json';
|
||||
|
||||
private static IconsStorage $_instance;
|
||||
private array $data = [];
|
||||
|
||||
private function __construct() {
|
||||
$path = PATH_ROOT . '/storage/' . self::FILENAME;
|
||||
if (is_file($path)) {
|
||||
$this->data = (array) json_decode(file_get_contents($path)) ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
public static function getInstance(): IconsStorage {
|
||||
return self::$_instance ??= new self();
|
||||
}
|
||||
|
||||
public function get(string $key): ?string {
|
||||
return $this->data[$key] ?? null;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): bool {
|
||||
$this->data[$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function has(string $key): bool {
|
||||
return isset($this->data[$key]);
|
||||
}
|
||||
|
||||
public function save(): void {
|
||||
file_put_contents(PATH_ROOT . '/storage/' . self::FILENAME, json_encode($this->data, JSON_PRETTY_PRINT));
|
||||
App::getLogger()->notice('Icons storage saved', [count($this->data) . ' items']);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user