Переработка под iptvc
This commit is contained in:
@@ -1,36 +1,117 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2025, Антон Аксенов
|
||||
* This file is part of iptv.axenov.dev web interface
|
||||
* MIT License: https://git.axenov.dev/IPTV/web/src/branch/master/LICENSE
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Errors\PlaylistNotFoundException;
|
||||
use App\Playlists\ChannelLogo;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Random\RandomException;
|
||||
use Twig\Error\LoaderError;
|
||||
|
||||
/**
|
||||
*
|
||||
* Контроллер методов API
|
||||
*/
|
||||
class ApiController extends BasicController
|
||||
{
|
||||
/**
|
||||
* Возвращает информацию о каналов плейлиста
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
* @throws RandomException
|
||||
* @throws LoaderError
|
||||
*/
|
||||
public function json(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
public function makeQrCode(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$code = $request->getAttribute('code');
|
||||
$codes = array_keys(ini()->getPlaylists());
|
||||
if (!in_array($code, $codes, true)) {
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$filePath = cache_path("qr-codes/$code.jpg");
|
||||
if (file_exists($filePath)) {
|
||||
$raw = file_get_contents($filePath);
|
||||
} else {
|
||||
$options = new QROptions([
|
||||
'version' => 5,
|
||||
'outputType' => QRCode::OUTPUT_IMAGE_JPG,
|
||||
'eccLevel' => QRCode::ECC_L,
|
||||
]);
|
||||
$data = base_url("$code");
|
||||
$raw = (new QRCode($options))->render($data, $filePath);
|
||||
$raw = base64_decode(str_replace('data:image/jpg;base64,', '', $raw));
|
||||
}
|
||||
|
||||
$mime = mime_content_type($filePath);
|
||||
$response->getBody()->write($raw);
|
||||
return $response->withStatus(200)
|
||||
->withHeader('Content-Type', $mime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает информацию о плейлисте
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
* @throws LoaderError
|
||||
*/
|
||||
public function getPlaylist(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$code = $request->getAttributes()['code'];
|
||||
$playlist = $this->getPlaylist($code, true);
|
||||
$playlist->fetchContent();
|
||||
$playlist->parse();
|
||||
|
||||
$json = json_encode($playlist->toArray(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$response->getBody()->write($json);
|
||||
try {
|
||||
$playlist = ini()->getPlaylist($code);
|
||||
return $this->responseJson($response, 200, $playlist);
|
||||
} catch (PlaylistNotFoundException $e) {
|
||||
return $this->responseJsonError($response, 404, $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withHeader('Content-Length', strlen($json));
|
||||
/**
|
||||
* Возвращает логотип канала
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
* @throws LoaderError
|
||||
* @throws PlaylistNotFoundException
|
||||
* @todo логотипы каналов
|
||||
*/
|
||||
public function logo(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$code = $request->getAttributes()['code'];
|
||||
$playlist = ini()->getPlaylist($code);
|
||||
$channelHash = $request->getAttributes()['hash'];
|
||||
$channel = $playlist['channels'][$channelHash];
|
||||
$url = $channel['attributes']['tvg-logo'] ?? '';
|
||||
|
||||
$logo = new ChannelLogo($url);
|
||||
if (!$logo->readFile()) {
|
||||
$logo->fetch();
|
||||
if ($logo->size() === 0) {
|
||||
$logo->setDefault();
|
||||
} else {
|
||||
$logo->store();
|
||||
}
|
||||
}
|
||||
|
||||
$body = $logo->raw();
|
||||
$size = $logo->size();
|
||||
$mime = $logo->mimeType();
|
||||
|
||||
$response->getBody()->write($body);
|
||||
return $response->withHeader('Content-Type', $mime)
|
||||
->withHeader('Content-Length', $size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2025, Антон Аксенов
|
||||
* This file is part of iptv.axenov.dev web interface
|
||||
* MIT License: https://git.axenov.dev/IPTV/web/src/branch/master/LICENSE
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Playlist;
|
||||
use App\Errors\PlaylistNotFoundException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Slim\Views\Twig;
|
||||
use Throwable;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
|
||||
/**
|
||||
*
|
||||
* Базовый класс контроллера
|
||||
*/
|
||||
class BasicController
|
||||
{
|
||||
/**
|
||||
* Отправляет сообщение о том, что метод не найден с кодом страницы 404
|
||||
* Отображает страницу 404
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
@@ -37,6 +41,46 @@ class BasicController
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает ответ в формате json
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param int $status
|
||||
* @param array $data
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function responseJson(ResponseInterface $response, int $status, array $data): ResponseInterface
|
||||
{
|
||||
$data = array_merge(['timestamp' => time()], $data);
|
||||
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$response->getBody()->write($json);
|
||||
return $response->withStatus($status)
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает ответ с ошибкой в формате json
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param int $status
|
||||
* @param Throwable $t
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function responseJsonError(ResponseInterface $response, int $status, Throwable $t): ResponseInterface
|
||||
{
|
||||
$data = [
|
||||
'error' => [
|
||||
'code' => array_last(explode('\\', $t::class)),
|
||||
'message' => $t->getMessage(),
|
||||
],
|
||||
];
|
||||
|
||||
return $this->responseJson($response, $status, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает ответ в формате html
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param string $template
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2025, Антон Аксенов
|
||||
* This file is part of iptv.axenov.dev web interface
|
||||
* MIT License: https://git.axenov.dev/IPTV/web/src/branch/master/LICENSE
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\ChannelLogo;
|
||||
use App\Errors\PlaylistNotFoundException;
|
||||
use Exception;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Throwable;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
|
||||
/**
|
||||
*
|
||||
* Контроллер маршрутов web
|
||||
*/
|
||||
class WebController extends BasicController
|
||||
{
|
||||
/**
|
||||
* Возвращает главную страницу со списком плейлистов
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
@@ -29,26 +36,36 @@ class WebController extends BasicController
|
||||
*/
|
||||
public function home(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
ini()->load();
|
||||
$playlists = ini()->getPlaylists();
|
||||
|
||||
$playlists = ini()->playlists(false);
|
||||
$count = count($playlists);
|
||||
$page = (int)($request->getAttributes()['page'] ?? $request->getQueryParams()['page'] ?? 1);
|
||||
$onlineCount = count(array_filter($playlists, static fn (array $playlist) => $playlist['isOnline'] === true));
|
||||
$uncheckedCount = count(array_filter($playlists, static fn (array $playlist) => $playlist['isOnline'] === null));
|
||||
$offlineCount = $count - $onlineCount - $uncheckedCount;
|
||||
|
||||
$pageSize = config('app.page_size');
|
||||
$pageCount = ceil($count / $pageSize);
|
||||
$offset = max(0, ($page - 1) * $pageSize);
|
||||
$list = array_slice($playlists, $offset, $pageSize, true);
|
||||
if ($pageSize > 0) {
|
||||
$pageCurrent = (int)($request->getAttributes()['page'] ?? $request->getQueryParams()['page'] ?? 1);
|
||||
$pageCount = ceil($count / $pageSize);
|
||||
$offset = max(0, ($pageCurrent - 1) * $pageSize);
|
||||
$playlists = array_slice($playlists, $offset, $pageSize, true);
|
||||
}
|
||||
|
||||
return $this->view($request, $response, 'list.twig', [
|
||||
'updated_at' => ini()->updatedAt(),
|
||||
'playlists' => $list,
|
||||
'updatedAt' => ini()->updatedAt(),
|
||||
'playlists' => $playlists,
|
||||
'count' => $count,
|
||||
'pageCount' => $pageCount,
|
||||
'pageCurrent' => $page,
|
||||
'onlineCount' => $onlineCount,
|
||||
'uncheckedCount' => $uncheckedCount,
|
||||
'offlineCount' => $offlineCount,
|
||||
'pageCount' => $pageCount ?? 1,
|
||||
'pageCurrent' => $pageCurrent ?? 1,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает страницу FAQ
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
@@ -62,67 +79,46 @@ class WebController extends BasicController
|
||||
}
|
||||
|
||||
/**
|
||||
* Переадресует запрос на прямую ссылку плейлиста
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
* @throws LoaderError
|
||||
* @throws RuntimeError
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
public function redirect(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
ini()->load();
|
||||
$code = $request->getAttributes()['code'];
|
||||
|
||||
try {
|
||||
$playlist = ini()->getPlaylist($code);
|
||||
return $response->withHeader('Location', $playlist->pls);
|
||||
} catch (PlaylistNotFoundException) {
|
||||
return $response->withHeader('Location', $playlist['url']);
|
||||
} catch (Throwable) {
|
||||
return $this->notFound($request, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает страницу с описанием плейлиста
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
* @throws \Random\RandomException
|
||||
* @throws LoaderError
|
||||
* @throws RuntimeError
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
public function details(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
ini()->load();
|
||||
$code = $request->getAttributes()['code'];
|
||||
|
||||
try {
|
||||
$playlist = ini()->getPlaylist($code);
|
||||
$response->withHeader('Location', $playlist->pls);
|
||||
return $this->view($request, $response, 'details.twig', ['playlist' => $playlist]);
|
||||
} catch (PlaylistNotFoundException) {
|
||||
return $this->notFound($request, $response);
|
||||
}
|
||||
|
||||
$playlist->fetchContent();
|
||||
$playlist->parse();
|
||||
|
||||
return $this->view($request, $response, 'details.twig', $playlist->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function logo(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$input = $request->getQueryParams()['url'] ?? null;
|
||||
|
||||
$logo = new ChannelLogo($input);
|
||||
$logo->readFile() || $logo->fetch();
|
||||
$logo->size() === 0 && $logo->setDefault();
|
||||
$logo->store();
|
||||
$body = $logo->raw();
|
||||
$size = $logo->size();
|
||||
$mime = $logo->mimeType();
|
||||
|
||||
$response->getBody()->write($body);
|
||||
return $response->withHeader('Content-Type', $mime)
|
||||
->withHeader('Content-Length', $size);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user