Files
web/app/Core/Playlist.php
2026-01-01 21:10:46 +08:00

217 lines
6.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
* Copyright (c) 2025, Антон Аксенов
* This file is part of m3u.su project
* MIT License: https://git.axenov.dev/IPTV/web/src/branch/master/LICENSE
*/
declare(strict_types=1);
namespace App\Core;
use App\Exceptions\PlaylistNotFoundException;
use App\Exceptions\PlaylistWithoutUrlException;
/**
* @phpstan-import-type TPlaylistDefinition from IniFile
*/
class Playlist
{
/**
* @var non-falsy-string
*/
public readonly string $code;
/**
* @var non-falsy-string|null
*/
public readonly ?string $desc;
/**
* @var non-falsy-string|null
*/
public readonly ?string $name;
/**
* @var non-falsy-string
*/
public readonly string $url;
/**
* @var non-falsy-string|null
*/
public readonly ?string $src;
protected string $text;
/**
* @param non-falsy-string $code
* @param TPlaylistDefinition $params
*/
public function __construct(string $code, array $params)
{
$this->code = $code;
$this->name = isset($params['name']) ? trim($params['name']) : null;
$this->desc = isset($params['desc']) ? trim($params['desc']) : null;
$this->url = isset($params['url']) ? trim($params['url']) : throw new PlaylistWithoutUrlException($code);
$this->src = isset($params['src']) ? trim($params['src']) : null;
}
public function getCheckResult(\Redis $redis)
{
$this->text = $redis->get($this->code);
$stop = 1;
}
private array $definition;
private array $cached;
/**
* Возвращает плейлисты
*
* @return array[]
* @throws Exception
*/
// public function getCachedPlaylists(array $plsCodes = []): array
// {
// $playlists = [];
// empty($plsCodes) && $plsCodes = array_keys($this->playlists);
// $cached = array_combine($plsCodes, redis()->mget($plsCodes));
// foreach ($cached as $code => $data) {
// $playlists[$code] = $this->initPlaylist($code, $data);
// }
//
// return $playlists;
// }
/**
* Возвращает плейлист по его коду
*
* @param string $code Код плейлиста
* @return array|null
* @throws PlaylistNotFoundException
* @throws Exception
*/
// public function getCachedPlaylist(string $code): ?array
// {
// $data = redis()->get($code);
//
// return $this->initPlaylist($code, $data);
// }
/**
* Подготавливает данные о плейлисте в расширенном формате
*
* @param string $code
* @param array|false $data
* @return array
* @throws PlaylistNotFoundException
*/
protected function initPlaylist(string $code, array|false $data): array
{
if ($data === false) {
$raw = $this->playlists[$code]
?? throw new PlaylistNotFoundException($code);
$data = [
'code' => $code,
'name' => $raw['name'] ?? "Плейлист #{$code}",
'description' => $raw['desc'] ?? null,
'url' => $raw['url'],
'source' => $raw['src'] ?? null,
'content' => null,
'isOnline' => null,
'attributes' => [],
'groups' => [],
'channels' => [],
'checkedAt' => null,
];
}
// приколы golang
$data['attributes'] === null && $data['attributes'] = [];
$data['groups'] === null && $data['groups'] = [];
$data['channels'] === null && $data['channels'] = [];
$data['onlinePercent'] = 0;
$data['offlinePercent'] = 0;
if ($data['isOnline'] === true && count($data['channels']) > 0) {
$data['onlinePercent'] = round($data['onlineCount'] / count($data['channels']) * 100);
$data['offlinePercent'] = round($data['offlineCount'] / count($data['channels']) * 100);
}
$data['hasCatchup'] = str_contains($data['content'] ?? '', 'catchup');
$data['hasTvg'] = !empty($data['attributes']['url-tvg']) || !empty($data['attributes']['x-tvg-url']);
$data['hasTokens'] = $this->hasTokens($data);
$data['tags'] = [];
foreach ($data['channels'] as &$channel) {
$data['tags'] = array_merge($data['tags'], $channel['tags']);
$channel['hasToken'] = $this->hasTokens($channel);
}
$data['tags'] = array_values(array_unique($data['tags']));
sort($data['tags']);
return $data;
}
/**
* Проверяет наличие токенов в плейлисте
*
* Сделано именно так, а не через тег unstable, чтобы разделить логику: есть заведомо нестабильные каналы,
* которые могут не транслироваться круглосуточно, а есть платные круглосуточные, которые могут оборваться
* в любой момент.
*
* @param array $data
* @return bool
*/
protected function hasTokens(array $data): bool
{
$string = ($data['url'] ?? '') . ($data['content'] ?? '');
if (empty($string)) {
return false;
}
$badAttributes = [
// токены и ключи
'[?&]token=',
'[?&]drmreq=',
// логины
'[?&]u=',
'[?&]user=',
'[?&]username=',
// пароли
'[?&]p=',
'[?&]pwd=',
'[?&]password=',
// неизвестные
// 'free=true',
// 'uid=',
// 'c_uniq_tag=',
// 'rlkey=',
// '?s=',
// '&s=',
// '?q=',
// '&q=',
];
return array_any(
$badAttributes,
static fn (string $badAttribute) => preg_match_all("/{$badAttribute}/", $string) >= 1,
);
}
}