Большое обновление
- проект переписан на flight + twig, laravel-like хелперы - docker-окружение - новая страница с подробностями о плейлисте - улучшен json о плейлисте - нормальный роутинг - нормальная статусная система - попытка перекодировки при не utf-8 + предупреждение об этом - дополнены FAQ + README
This commit is contained in:
7
src/.env.example
Normal file
7
src/.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
APP_TITLE=
|
||||
APP_URL=
|
||||
TWIG_CACHE=1
|
||||
TWIG_DEBUG=0
|
||||
FLIGHT_CASE_SENSITIVE=0
|
||||
FLIGHT_HANDLE_ERRORS=1
|
||||
FLIGHT_LOG_ERRORS=1
|
||||
9
src/app/Controllers/Controller.php
Normal file
9
src/app/Controllers/Controller.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
}
|
||||
76
src/app/Controllers/HomeController.php
Normal file
76
src/app/Controllers/HomeController.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\PlaylistProcessor;
|
||||
use App\Core\RedirectedPlaylist;
|
||||
use Exception;
|
||||
use Flight;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
protected PlaylistProcessor $ini;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->ini = new PlaylistProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (Flight::request()->query->count() > 0) {
|
||||
$id = Flight::request()->query->keys()[0];
|
||||
Flight::redirect(base_url("$id"));
|
||||
die;
|
||||
}
|
||||
view('list', [
|
||||
'updated_at' => $this->ini->updatedAt(),
|
||||
'count' => $this->ini->playlists->count(),
|
||||
'playlists' => $this->ini->playlists->where('redirect_id', null)->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function faq()
|
||||
{
|
||||
view('faq');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function details(string $id): void
|
||||
{
|
||||
$playlist = $this->ini->playlist($id);
|
||||
if ($playlist instanceof RedirectedPlaylist) {
|
||||
Flight::redirect(base_url($playlist->redirect_id . '/info'));
|
||||
}
|
||||
view('details', [
|
||||
'id' => $id,
|
||||
'playlist' => $playlist->toArray(),
|
||||
'info' => $this->ini->parse($id),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ajax(string $id): void
|
||||
{
|
||||
$playlist = $this->ini->playlist($id);
|
||||
if ($playlist instanceof RedirectedPlaylist) {
|
||||
Flight::redirect(base_url($playlist->redirect_id . '/getInfo'));
|
||||
}
|
||||
Flight::json([
|
||||
'playlist' => $playlist->toArray(),
|
||||
'info' => $this->ini->parse($id),
|
||||
]);
|
||||
}
|
||||
}
|
||||
65
src/app/Controllers/PlaylistController.php
Normal file
65
src/app/Controllers/PlaylistController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\PlaylistProcessor;
|
||||
use App\Core\RedirectedPlaylist;
|
||||
use Exception;
|
||||
use Flight;
|
||||
|
||||
class PlaylistController extends Controller
|
||||
{
|
||||
protected PlaylistProcessor $ini;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->ini = new PlaylistProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function download($id)
|
||||
{
|
||||
$playlist = $this->ini->playlist($id);
|
||||
if ($playlist instanceof RedirectedPlaylist) {
|
||||
Flight::redirect(base_url($playlist->redirect_id));
|
||||
die;
|
||||
}
|
||||
Flight::redirect($playlist->pls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function details(string $id): void
|
||||
{
|
||||
$playlist = $this->ini->playlist($id);
|
||||
if ($playlist instanceof RedirectedPlaylist) {
|
||||
Flight::redirect(base_url($playlist->redirect_id . '/details'));
|
||||
die;
|
||||
}
|
||||
view('details', [
|
||||
...$playlist->toArray(),
|
||||
...$this->ini->parse($id),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function json(string $id): void
|
||||
{
|
||||
$playlist = $this->ini->playlist($id);
|
||||
if ($playlist instanceof RedirectedPlaylist) {
|
||||
Flight::redirect(base_url($playlist->redirect_id . '/json'));
|
||||
die;
|
||||
}
|
||||
Flight::json([
|
||||
...$playlist->toArray(),
|
||||
...$this->ini->parse($id),
|
||||
]);
|
||||
}
|
||||
}
|
||||
17
src/app/Core/BasicPlaylist.php
Normal file
17
src/app/Core/BasicPlaylist.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
||||
abstract class BasicPlaylist implements Arrayable
|
||||
{
|
||||
public string $id;
|
||||
|
||||
public function url(): string
|
||||
{
|
||||
return sprintf('%s/%s', base_url(), $this->id);
|
||||
}
|
||||
}
|
||||
77
src/app/Core/Bootstrapper.php
Normal file
77
src/app/Core/Bootstrapper.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use App\Controllers\AjaxController;
|
||||
use App\Controllers\HomeController;
|
||||
use App\Controllers\PlaylistController;
|
||||
use App\Extensions\TwigFunctions;
|
||||
use Flight;
|
||||
use Illuminate\Support\Arr;
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\DebugExtension;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
|
||||
final class Bootstrapper
|
||||
{
|
||||
public static function bootEnv(): void
|
||||
{
|
||||
(new Dotenv())->loadEnv(root_path() . '/.env');
|
||||
}
|
||||
|
||||
public static function bootSettings(): void
|
||||
{
|
||||
$settings = Arr::dot(require_once config_path('app.php'));
|
||||
Arr::map($settings, function ($value, $key) {
|
||||
Flight::set("flight.$key", $value);
|
||||
});
|
||||
Flight::set('config', $settings);
|
||||
}
|
||||
|
||||
public static function bootTwig(): void
|
||||
{
|
||||
$filesystemLoader = new FilesystemLoader(config('views.path'));
|
||||
Flight::register(
|
||||
'view',
|
||||
Environment::class,
|
||||
[$filesystemLoader, config('twig')],
|
||||
function ($twig) {
|
||||
/** @var Environment $twig */
|
||||
Flight::set('twig', $twig);
|
||||
$twig->addExtension(new TwigFunctions());
|
||||
$twig->addExtension(new DebugExtension());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static function bootRoutes(): void
|
||||
{
|
||||
Flight::route(
|
||||
'GET /',
|
||||
fn() => (new HomeController())->index()
|
||||
);
|
||||
Flight::route(
|
||||
'GET /faq',
|
||||
fn() => (new HomeController())->faq()
|
||||
);
|
||||
Flight::route(
|
||||
'GET /@id:[a-zA-Z0-9_-]+',
|
||||
fn($id) => (new PlaylistController())->download($id)
|
||||
);
|
||||
Flight::route(
|
||||
'GET /?[a-zA-Z0-9_-]+',
|
||||
fn($id) => (new PlaylistController())->download($id)
|
||||
);
|
||||
Flight::route(
|
||||
'GET /@id:[a-zA-Z0-9_-]+/details',
|
||||
fn($id) => (new PlaylistController())->details($id)
|
||||
);
|
||||
Flight::route(
|
||||
'GET /@id:[a-zA-Z0-9_-]+/json',
|
||||
fn($id) => (new PlaylistController())->json($id)
|
||||
);
|
||||
}
|
||||
}
|
||||
45
src/app/Core/Playlist.php
Normal file
45
src/app/Core/Playlist.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Playlist extends BasicPlaylist
|
||||
{
|
||||
public ?string $name;
|
||||
|
||||
public ?string $desc;
|
||||
|
||||
public string $pls;
|
||||
|
||||
public ?string $src;
|
||||
|
||||
public string $url;
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(public string $id, array $params)
|
||||
{
|
||||
empty($params['pls']) && throw new \Exception(
|
||||
"Плейлист с ID=$id обязан иметь параметр pls или redirect"
|
||||
);
|
||||
$this->url = str_replace(['http://', 'https://'], '', base_url($id));
|
||||
$this->name = $params['name'] ?? "Плейлист #$id";
|
||||
$this->desc = $params['desc'] ?? null;
|
||||
$this->pls = $params['pls'];
|
||||
$this->src = $params['src'] ?? null;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'url' => $this->url,
|
||||
'name' => $this->name,
|
||||
'desc' => $this->desc,
|
||||
'pls' => $this->pls,
|
||||
'src' => $this->src,
|
||||
];
|
||||
}
|
||||
}
|
||||
119
src/app/Core/PlaylistProcessor.php
Normal file
119
src/app/Core/PlaylistProcessor.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
final class PlaylistProcessor
|
||||
{
|
||||
public Collection $playlists;
|
||||
|
||||
protected string $updated_at;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$filepath = config_path('playlists.ini');
|
||||
$this->updated_at = date('d.m.Y h:i', filemtime($filepath));
|
||||
$this->playlists = collect(parse_ini_file($filepath, true))
|
||||
->transform(function ($playlist, $id) {
|
||||
return empty($playlist['redirect'])
|
||||
? new Playlist((string)$id, $playlist)
|
||||
: new RedirectedPlaylist((string)$id, $playlist['redirect']);
|
||||
});
|
||||
}
|
||||
|
||||
public function hasId(string $id): bool
|
||||
{
|
||||
return in_array($id, $this->playlists->keys()->toArray());
|
||||
}
|
||||
|
||||
public function playlist(string $id): Playlist|RedirectedPlaylist
|
||||
{
|
||||
!$this->hasId($id) && throw new \InvalidArgumentException("Плейлист с ID=$id не найден");
|
||||
return $this->playlists[$id];
|
||||
}
|
||||
|
||||
public function check(string $id): bool
|
||||
{
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $this->playlist($id)['pls']);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_HEADER, 0);
|
||||
curl_setopt($curl, CURLOPT_NOBODY, 1);
|
||||
curl_exec($curl);
|
||||
$code = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||
curl_close($curl);
|
||||
return $code < 400;
|
||||
}
|
||||
|
||||
protected function fetch(string $id)
|
||||
{
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $this->playlist($id)->pls,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
]);
|
||||
$content = curl_exec($curl);
|
||||
$http_code = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||
$err_code = curl_errno($curl);
|
||||
$err_text = curl_error($curl);
|
||||
curl_close($curl);
|
||||
return [
|
||||
'content' => $content,
|
||||
'http_code' => $http_code,
|
||||
'err_code' => $err_code,
|
||||
'err_text' => $err_text,
|
||||
];
|
||||
}
|
||||
|
||||
protected function guessStatus(int $curl_err_code): string
|
||||
{
|
||||
return match ($curl_err_code) {
|
||||
0 => 'online',
|
||||
28 => 'timeout',
|
||||
5, 6, 7, 22, 35 => 'offline',
|
||||
default => 'error',
|
||||
};
|
||||
}
|
||||
|
||||
public function parse(string $id): array
|
||||
{
|
||||
$fetched = $this->fetch($id);
|
||||
if ($fetched['err_code'] > 0) {
|
||||
return [
|
||||
'status' => $this->guessStatus($fetched['err_code']),
|
||||
'error' => [
|
||||
'code' => $fetched['err_code'],
|
||||
'message' => $fetched['err_text'],
|
||||
],
|
||||
];
|
||||
}
|
||||
$result['status'] = $this->guessStatus($fetched['err_code']);
|
||||
$result['encoding']['name'] = 'UTF-8';
|
||||
$result['encoding']['alert'] = false;
|
||||
if (($enc = mb_detect_encoding($fetched['content'], config('app.pls_encodings'))) !== 'UTF-8') {
|
||||
$fetched['content'] = mb_convert_encoding($fetched['content'], 'UTF-8', $enc);
|
||||
$result['encoding']['name'] = $enc;
|
||||
$result['encoding']['alert'] = true;
|
||||
}
|
||||
$matches = [];
|
||||
preg_match_all("/^#EXTINF:-?\d.*,\s*(.*)/m", $fetched['content'], $matches);
|
||||
$result['channels'] = array_map('trim', $matches[1]);
|
||||
$result['count'] = $fetched['http_code'] < 400 ? count($result['channels']) : 0;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function updatedAt(): string
|
||||
{
|
||||
return $this->updated_at;
|
||||
}
|
||||
}
|
||||
25
src/app/Core/RedirectedPlaylist.php
Normal file
25
src/app/Core/RedirectedPlaylist.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class RedirectedPlaylist extends BasicPlaylist
|
||||
{
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $redirect_id,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'redirect_id' => $this->redirect_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
29
src/app/Extensions/TwigFunctions.php
Normal file
29
src/app/Extensions/TwigFunctions.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Extensions;
|
||||
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class TwigFunctions extends AbstractExtension
|
||||
{
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('config', [$this, 'config']),
|
||||
new TwigFunction('base_url', [$this, 'base_url']),
|
||||
];
|
||||
}
|
||||
|
||||
public function config(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return config($key, $default);
|
||||
}
|
||||
|
||||
public function base_url(string $path = ''): string
|
||||
{
|
||||
return base_url($path);
|
||||
}
|
||||
}
|
||||
20
src/bootstrap.php
Normal file
20
src/bootstrap.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
use App\Core\Bootstrapper;
|
||||
|
||||
// autoload composer packages
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
// load .env parameters
|
||||
Bootstrapper::bootEnv();
|
||||
|
||||
// set up framework according to its config
|
||||
Bootstrapper::bootSettings();
|
||||
|
||||
// set up Twig template engine
|
||||
Bootstrapper::bootTwig();
|
||||
|
||||
// set up routes defined in config file
|
||||
Bootstrapper::bootRoutes();
|
||||
0
src/cache/.gitkeep
vendored
Normal file
0
src/cache/.gitkeep
vendored
Normal file
20
src/composer.json
Normal file
20
src/composer.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"require": {
|
||||
"illuminate/collections": "^9.26",
|
||||
"mikecao/flight": "^2.0",
|
||||
"symfony/dotenv": "^6.1",
|
||||
"twig/twig": "^3.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
},
|
||||
"files": [
|
||||
"helpers.php"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
680
src/composer.lock
generated
Normal file
680
src/composer.lock
generated
Normal file
@@ -0,0 +1,680 @@
|
||||
{
|
||||
"_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": "d8a1bc42a20f2a843ee133cd33f44fd4",
|
||||
"packages": [
|
||||
{
|
||||
"name": "illuminate/collections",
|
||||
"version": "v9.26.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/collections.git",
|
||||
"reference": "3bda212d2c245b3261cd9af690dfd47d9878cebf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/collections/zipball/3bda212d2c245b3261cd9af690dfd47d9878cebf",
|
||||
"reference": "3bda212d2c245b3261cd9af690dfd47d9878cebf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/conditionable": "^9.0",
|
||||
"illuminate/contracts": "^9.0",
|
||||
"illuminate/macroable": "^9.0",
|
||||
"php": "^8.0.2"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/var-dumper": "Required to use the dump method (^6.0)."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Illuminate\\Support\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Collections package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2022-08-22T14:29:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/conditionable",
|
||||
"version": "v9.26.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/conditionable.git",
|
||||
"reference": "5b40f51ccb07e0e7b1ec5559d8db9e0e2dc51883"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/conditionable/zipball/5b40f51ccb07e0e7b1ec5559d8db9e0e2dc51883",
|
||||
"reference": "5b40f51ccb07e0e7b1ec5559d8db9e0e2dc51883",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Support\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Conditionable package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2022-07-29T19:44:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/contracts",
|
||||
"version": "v9.26.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/contracts.git",
|
||||
"reference": "0d1dd1a7e947072319f2e641cc50081219606502"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/contracts/zipball/0d1dd1a7e947072319f2e641cc50081219606502",
|
||||
"reference": "0d1dd1a7e947072319f2e641cc50081219606502",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0.2",
|
||||
"psr/container": "^1.1.1|^2.0.1",
|
||||
"psr/simple-cache": "^1.0|^2.0|^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Contracts\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Contracts package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2022-08-18T14:18:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/macroable",
|
||||
"version": "v9.26.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/macroable.git",
|
||||
"reference": "e3bfaf6401742a9c6abca61b9b10e998e5b6449a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/macroable/zipball/e3bfaf6401742a9c6abca61b9b10e998e5b6449a",
|
||||
"reference": "e3bfaf6401742a9c6abca61b9b10e998e5b6449a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Support\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Macroable package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2022-08-09T13:29:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mikecao/flight",
|
||||
"version": "v2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mikecao/flight.git",
|
||||
"reference": "a130231646e6c7a9e2504a9025f851e9a3bf1975"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mikecao/flight/zipball/a130231646e6c7a9e2504a9025f851e9a3bf1975",
|
||||
"reference": "a130231646e6c7a9e2504a9025f851e9a3bf1975",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^7.4|^8.0|^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"flight/autoload.php",
|
||||
"flight/Flight.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mike Cao",
|
||||
"email": "mike@mikecao.com",
|
||||
"homepage": "http://www.mikecao.com/",
|
||||
"role": "Original Developer"
|
||||
}
|
||||
],
|
||||
"description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.",
|
||||
"homepage": "http://flightphp.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/mikecao/flight/issues",
|
||||
"source": "https://github.com/mikecao/flight/tree/v2.0.1"
|
||||
},
|
||||
"time": "2021-12-19T03:03:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
"version": "2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/container.git",
|
||||
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
|
||||
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Container\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common Container Interface (PHP FIG PSR-11)",
|
||||
"homepage": "https://github.com/php-fig/container",
|
||||
"keywords": [
|
||||
"PSR-11",
|
||||
"container",
|
||||
"container-interface",
|
||||
"container-interop",
|
||||
"psr"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/container/issues",
|
||||
"source": "https://github.com/php-fig/container/tree/2.0.2"
|
||||
},
|
||||
"time": "2021-11-05T16:47:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/simple-cache",
|
||||
"version": "3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/simple-cache.git",
|
||||
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
|
||||
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\SimpleCache\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interfaces for simple caching",
|
||||
"keywords": [
|
||||
"cache",
|
||||
"caching",
|
||||
"psr",
|
||||
"psr-16",
|
||||
"simple-cache"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
|
||||
},
|
||||
"time": "2021-10-29T13:26:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dotenv",
|
||||
"version": "v6.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dotenv.git",
|
||||
"reference": "568c11bcedf419e7e61f663912c3547b54de51df"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dotenv/zipball/568c11bcedf419e7e61f663912c3547b54de51df",
|
||||
"reference": "568c11bcedf419e7e61f663912c3547b54de51df",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/console": "<5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^5.4|^6.0",
|
||||
"symfony/process": "^5.4|^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Dotenv\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Registers environment variables from a .env file",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"dotenv",
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/dotenv/tree/v6.1.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-01T07:15:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.26.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
|
||||
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.26.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
|
||||
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077",
|
||||
"reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-ctype": "^1.8",
|
||||
"symfony/polyfill-mbstring": "^1.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"psr/container": "^1.0",
|
||||
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Twig\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com",
|
||||
"homepage": "http://fabien.potencier.org",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Twig Team",
|
||||
"role": "Contributors"
|
||||
},
|
||||
{
|
||||
"name": "Armin Ronacher",
|
||||
"email": "armin.ronacher@active-4.com",
|
||||
"role": "Project Founder"
|
||||
}
|
||||
],
|
||||
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||
"homepage": "https://twig.symfony.com",
|
||||
"keywords": [
|
||||
"templating"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.4.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-12T06:47:24+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
30
src/config/app.php
Normal file
30
src/config/app.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
return [
|
||||
'flight' => [
|
||||
// https://flightphp.com/learn#configuration
|
||||
'base_url' => env('APP_URL', 'http://localhost:8080'),
|
||||
'case_sensitive' => bool(env('FLIGHT_CASE_SENSITIVE', false)),
|
||||
'handle_errors' => bool(env('FLIGHT_HANDLE_ERRORS', true)),
|
||||
'log_errors' => bool(env('FLIGHT_LOG_ERRORS', true)),
|
||||
'views' => [
|
||||
'path' => views_path(),
|
||||
'extension' => '.twig',
|
||||
],
|
||||
],
|
||||
'twig' => [
|
||||
'cache' => bool(env('TWIG_CACHE', true)) ? cache_path() . '/views' : false,
|
||||
'debug' => bool(env('TWIG_DEBUG', false)),
|
||||
],
|
||||
'app' => [
|
||||
'title' => env('APP_TITLE', 'IPTV Playlists'),
|
||||
'pls_encodings' => [
|
||||
'UTF-8',
|
||||
'CP1251',
|
||||
// 'CP866',
|
||||
// 'ISO-8859-5',
|
||||
],
|
||||
],
|
||||
];
|
||||
452
src/config/playlists.ini
Normal file
452
src/config/playlists.ini
Normal file
@@ -0,0 +1,452 @@
|
||||
[1]
|
||||
name = 'Рабочий и актуальный IPTV плейлист M3U (smarttvapp.ru)'
|
||||
desc = 'В этом IPTV плейлисте формата m3u вы найдете очень много каналов в HD качестве. Познавательные: Discovery HD, Discovery Science, Nat Geo, Nat Geo WILD, TLC HD. Детские: Nickelodeon HD. Спортивные, много каналов с фильмами: Дом Кино Премиум HD, Кинопремьера HD. Плейлист актуален на: 3.02.22'
|
||||
pls = 'https://smarttvapp.ru/app/iptvfull.m3u'
|
||||
src = 'https://smarttvapp.ru/aktualnyiy-i-rabochiy-iptv-pleylist-m3u/'
|
||||
|
||||
[2]
|
||||
name = 'Самообновляемый IPTV плейлист — июнь 2022 (prodigtv.ru)'
|
||||
desc = 'Возможно, дублирует какой-то от smarttvnews'
|
||||
pls = 'https://prodigtv.ru/play/iptv.m3u'
|
||||
src = 'https://prodigtv.ru/iptv/playlist/samoobnovlyaemyj'
|
||||
|
||||
[3]
|
||||
name = 'IPTV каналы плейлист m3u без тормозов (poiskpmr)'
|
||||
desc = 'Самые популярные и актуальные жанры iptv каналов m3u в 2022 году'
|
||||
pls = 'https://iptvmaster.ru/december.m3u'
|
||||
src = 'https://poiskpmr.ru/blog/ip-kanaly-plejlist-m3u-bez-tormozov-b256'
|
||||
|
||||
[4]
|
||||
name = 'Самообновляемый IPTV плейлист 2022 на июнь (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/iptv-playlist.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/iptv-playlist/'
|
||||
|
||||
[5]
|
||||
name = 'IPTV плейлист с миксом ТВ каналов 2022 на июнь (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/mix.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/mix/'
|
||||
|
||||
[p1]
|
||||
name = 'Каналы в SD и HD качестве (smarttvnews.ru)'
|
||||
desc = 'Рабочий и актуальный IPTV плейлист M3U — на июнь 2022 года'
|
||||
pls = 'https://smarttvnews.ru/apps/iptvchannels.m3u'
|
||||
src = 'https://smarttvnews.ru/rabochiy-i-aktualnyiy-iptv-pleylist-m3u-kanalyi-v-sd-i-hd-kachestve/'
|
||||
|
||||
[p2]
|
||||
name = 'Самообновляемый iptv плейлист 2022 июнь (smarttvnews.ru)'
|
||||
desc = 'Лучший самообновляемый IPTV плейлист в 2022 году'
|
||||
pls = 'https://smarttvnews.ru/apps/freeiptv.m3u'
|
||||
src = 'https://smarttvnews.ru/samoobnovlyaemyj-iptv-plejlist/'
|
||||
|
||||
[p4]
|
||||
name = 'IPTV плейлист на июль 2020 (iptvm3u.ru)'
|
||||
desc = 'Плейлист содержит 1200+ ТВ каналов всех категорий (музыка, спорт, детские, образовательные, взрослые). Так же в файле есть каналы Украины, Белоруссии, Молдовы. Для удобства каналы других стран расположены в низу списка.'
|
||||
pls = 'https://iptvm3u.ru/0720.m3u'
|
||||
src = 'https://iptvm3u.ru/iptv-plejlist-na-ijul-2/'
|
||||
|
||||
[p5]
|
||||
name = 'Плейлист 2020 от iptv-playlisty.ru'
|
||||
desc = 'Трансляции для детей и подростков. Сериалы и Премьеры кино. Каналы для женщин и мужских развлечений. Документалистика и исторические лента о событиях прошлого.'
|
||||
pls = 'https://iptv-playlisty.ru/wp-content/uploads/m3u/2020.m3u'
|
||||
src = 'https://iptv-playlisty.ru/collection/samyj-svezheobnovlennyj-plejlist-iptv-na-2020-god/'
|
||||
|
||||
[p6]
|
||||
redirect = 'p1'
|
||||
|
||||
[kid1]
|
||||
name = 'Детский IPTV «Kids»'
|
||||
desc = ''
|
||||
pls = 'https://webhalpme.ru/kids.m3u'
|
||||
src = 'https://webhalpme.ru/samoobnovljaemye-plejlisty-iptv-2019/'
|
||||
|
||||
[kid2]
|
||||
name = 'Плейлист детских каналов iptvmaster.ru'
|
||||
desc = '02.08.2020 Среди детских каналов есть и отечественные, и зарубежные, большинство из них в HD.'
|
||||
pls = 'https://iptvmaster.ru/kids-all.m3u'
|
||||
src = 'https://iptvmaster.ru/detskie-kanaly-playlist/'
|
||||
|
||||
[np]
|
||||
name = 'Плейлист newplay (iptv-playlisty.ru)'
|
||||
desc = 'Общефедеральные. Каналы фильмов. Все на русском. Имеются с зарубежными лентами. Спортивные. Как трансляции, так и кино данной тематики. Детские. Мультфильмы и передачи.'
|
||||
pls = 'https://iptv-playlisty.ru/wp-content/uploads/m3u/newplay.m3u'
|
||||
src = 'https://iptv-playlisty.ru/collection/besplatnyj-iptv-plejlist-formata-m3u/'
|
||||
|
||||
[his]
|
||||
name = 'IPTV плейлист телеканала History (iptv-playlisty.ru)'
|
||||
desc =
|
||||
pls = 'https://iptv-playlisty.ru/wp-content/uploads/m3u/history.m3u'
|
||||
src = 'https://iptv-playlisty.ru/iptv-kanaly/iptv-plejlist-telekanala-history/'
|
||||
|
||||
[dis]
|
||||
name = 'IPTV плейлист телеканала Discovery (iptv-playlisty.ru)'
|
||||
desc =
|
||||
pls = 'https://iptv-playlisty.ru/wp-content/uploads/m3u/discovery.m3u'
|
||||
src = 'https://iptv-playlisty.ru/iptv-kanaly/iptv-plejlist-telekanala-discovery/'
|
||||
|
||||
[ngeo]
|
||||
name = 'IPTV плейлист канала national geographic (iptv-playlisty.ru)'
|
||||
desc =
|
||||
pls = 'https://iptv-playlisty.ru/wp-content/uploads/m3u/ngeografik.m3u'
|
||||
src = 'https://iptv-playlisty.ru/iptv-kanaly/iptv-plejlist-kanala-national-geographic/'
|
||||
|
||||
[news]
|
||||
name = 'Новости'
|
||||
desc =
|
||||
pls = 'https://iptvmaster.ru/news.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[mus]
|
||||
name = 'Музыкальные 1'
|
||||
desc =
|
||||
pls = 'https://iptvmaster.ru/music.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[mus1]
|
||||
name = 'Музыкальные 2 (smarttvnews.ru)'
|
||||
desc = 'IPTV плейлист музыкальных каналов 2022'
|
||||
pls = 'https://smarttvnews.ru/apps/music.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[mus2]
|
||||
name = 'IPTV плейлист с музыкальными каналами (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/music.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/music/'
|
||||
|
||||
[ser]
|
||||
name = 'Сериалы'
|
||||
desc =
|
||||
pls = 'http://bluecrabstv.do.am/serial.m3u'
|
||||
src = 'https://iptvsensei.ru/samoobnovlyayemyye-pleylisty-iptv'
|
||||
|
||||
[kino1]
|
||||
name = 'Фильмы 1'
|
||||
desc =
|
||||
pls = 'https://smarttvnews.ru/apps/Films.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[kino2]
|
||||
name = 'Фильмы 2'
|
||||
desc =
|
||||
pls = 'http://iptvm3u.ru/500newFilms.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[kino3]
|
||||
name = 'Фильмы 3'
|
||||
desc =
|
||||
pls = 'http://iptvm3u.ru/film1.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[kino4]
|
||||
name = 'Фильмы 4'
|
||||
desc =
|
||||
pls = 'http://iptvm3u.ru/film4.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[kino5]
|
||||
name = 'Фильмы 5'
|
||||
desc =
|
||||
pls = 'https://pastebin.com/raw/jLaRge54'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[kino6]
|
||||
name = 'IPTV плейлист с кино, сериалами и мультфильмами 2022 на июнь (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/cinematic.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/cinematic/'
|
||||
|
||||
[ru1]
|
||||
name = 'Русские 1'
|
||||
desc =
|
||||
pls = 'https://webhalpme.ru/RussiaIPTV.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[ru3]
|
||||
name = 'Русские 3'
|
||||
desc =
|
||||
pls = 'https://getsapp.ru/IPTV/Auto_IPTV.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[ru4]
|
||||
name = 'Русские 4'
|
||||
desc =
|
||||
pls = 'https://iptvm3u.ru/list2511.m3u8'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[ru5]
|
||||
name = 'Русские 5'
|
||||
desc =
|
||||
pls = 'https://avdmono.do.am/film/natgeo.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[ru6]
|
||||
name = 'Русские 6'
|
||||
desc =
|
||||
pls = 'http://iptv.ktkru.ru/playlist.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[ru7]
|
||||
name = 'IPTV плейлист с ТВ каналами России 2022 на июнь (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/ru-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/ru-all/'
|
||||
|
||||
[reg]
|
||||
name = 'IPTV Плейлист — Региональные каналы России (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/ru-regional.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/ru-regional/'
|
||||
|
||||
[ua1]
|
||||
name = 'Украинские IPTV каналы (smarttvnews.ru)'
|
||||
desc = ''
|
||||
pls = 'https://smarttvnews.ru/apps/ukraine.m3u'
|
||||
src = 'https://smarttvnews.ru/iptv-plejlist-ukrainskih-kanalov/'
|
||||
|
||||
[ua2]
|
||||
name = 'Украинские 2'
|
||||
desc = ''
|
||||
pls = 'https://iptvmaster.ru/ukraine.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[ua3]
|
||||
name = 'IPTV плейлист с ТВ каналами Украины 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/ua-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/ua-all/'
|
||||
|
||||
[ua4]
|
||||
name = 'IPTV m3u плейлист Украина самообновляемый 2022 (tva.org.ua)'
|
||||
desc = 'IPTV плейлист m3u бесплатных украинских каналов на 29 мая 2022 року'
|
||||
pls = 'https://tva.org.ua/ip/u/iptv_ukr.m3u'
|
||||
src = 'https://tva.org.ua/iptv-m3u-plejlist-ukraina-samoobnovlyaemyj.html'
|
||||
|
||||
[by]
|
||||
name = 'IPTV плейлист с ТВ каналами Беларуси 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/by-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/by-all/'
|
||||
|
||||
[arm]
|
||||
name = 'IPTV плейлист с ТВ каналами Армении 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/arm-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/all-arm/'
|
||||
|
||||
[uz]
|
||||
name = 'IPTV плейлист с ТВ каналами Узбекистана 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/uz-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/uz-all/'
|
||||
|
||||
[uz]
|
||||
name = 'IPTV плейлист с ТВ каналами Казахстана 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/kz-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/kz-all/'
|
||||
|
||||
[tr]
|
||||
name = 'IPTV плейлист с ТВ каналами Турции и Азербайджана 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/tr-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/tr-all/'
|
||||
|
||||
[usa]
|
||||
name = 'IPTV плейлист с ТВ каналами США 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/usa-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/usa-all/'
|
||||
|
||||
[ita]
|
||||
name = 'IPTV плейлист с ТВ каналами Италии 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/ita-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/ita-all/'
|
||||
|
||||
[m2]
|
||||
name = 'Мультфильмы 2'
|
||||
desc =
|
||||
pls = 'https://iptvmaster.ru/kids.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[m3]
|
||||
name = 'Мультфильмы 3'
|
||||
desc =
|
||||
pls = 'https://iptvmaster.ru/multfilm.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[m4]
|
||||
name = 'Мультфильмы 4'
|
||||
desc =
|
||||
pls = 'https://iptvmaster.ru/kids-all.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[m5]
|
||||
name = 'Мультфильмы 5'
|
||||
desc =
|
||||
pls = 'https://smarttvnews.ru/apps/Films.m3u'
|
||||
src = 'https://iptvsensei.ru/samoobnovlyayemyye-pleylisty-iptv'
|
||||
|
||||
[m6]
|
||||
name = 'Мультфильмы 6'
|
||||
desc =
|
||||
pls = 'http://iptvm3u.ru/film4.m3u'
|
||||
src = 'https://iptvsensei.ru/samoobnovlyayemyye-pleylisty-iptv'
|
||||
|
||||
[m7]
|
||||
name = 'Мультфильмы 7'
|
||||
desc =
|
||||
pls = 'http://iptvm3u.ru/film2.m3u'
|
||||
src = 'https://iptvsensei.ru/samoobnovlyayemyye-pleylisty-iptv'
|
||||
|
||||
[m8]
|
||||
name = 'Мультфильмы 8'
|
||||
desc =
|
||||
pls = 'http://iptvm3u.ru/film1.m3u'
|
||||
src = 'https://iptvsensei.ru/samoobnovlyayemyye-pleylisty-iptv'
|
||||
|
||||
[m9]
|
||||
name = 'Мультфильмы 9'
|
||||
desc =
|
||||
pls = 'http://iptvm3u.ru/500newFilms.m3u'
|
||||
src = 'https://iptvsensei.ru/samoobnovlyayemyye-pleylisty-iptv'
|
||||
|
||||
[m10]
|
||||
name = 'Детский Iptv плейлист с каналами и мультфильмами (smarttvnews.ru)'
|
||||
desc = ''
|
||||
pls = 'https://smarttvnews.ru/apps/mult.m3u'
|
||||
src = 'https://smarttvnews.ru/samoobnovlyaemyie-iptv-pleylistyi/'
|
||||
|
||||
[sci]
|
||||
name = 'Познавательные'
|
||||
desc = ''
|
||||
pls = 'https://iptvmaster.ru/poznavatelnoe.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sci2]
|
||||
name = 'IPTV плейлист с познавательными ТВ каналами 2022 на июнь (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/sci-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/sci-all/'
|
||||
|
||||
[sp]
|
||||
name = 'IPTV плейлист со спортивными каналами 2022 на июнь (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/sport-all.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/sports-all/'
|
||||
|
||||
[cam]
|
||||
name = 'IPTV плейлист с вебкамерами России и мира 2022 на июнь (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/webcams.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/webcams/'
|
||||
|
||||
[cam2]
|
||||
name = 'Веб камеры онлайн всего мира m3u (tva.org.ua)'
|
||||
desc = 'Веб камеры со всего мира онлайн в формате m3u плейлиста iptv.'
|
||||
pls = 'https://tva.org.ua/ip/web/web-kam-14.12.2021.m3u'
|
||||
src = 'https://tva.org.ua/veb-kamery-onlayn-vsego-mira-m3u.html'
|
||||
|
||||
[r1]
|
||||
name = 'Радио каналы 1'
|
||||
desc =
|
||||
pls = 'http://lradio.c1.biz/ltradio.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[r3]
|
||||
name = 'Радио каналы 3'
|
||||
desc =
|
||||
pls = 'https://iptvmaster.ru/radio.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng1]
|
||||
name = 'Каналы СНГ 1'
|
||||
desc =
|
||||
pls = 'https://iptvm3u.ru/iptv1218.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng2]
|
||||
name = 'Каналы СНГ 2'
|
||||
desc =
|
||||
pls = 'https://iptvm3u.ru/0119.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng3]
|
||||
name = 'Каналы СНГ 3'
|
||||
desc =
|
||||
pls = 'https://iptvm3u.ru/0219.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng4]
|
||||
name = 'Каналы СНГ 4'
|
||||
desc =
|
||||
pls = 'http://iptvm3u.ru/iptv082018.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng5]
|
||||
name = 'Каналы СНГ 5'
|
||||
desc =
|
||||
pls = 'https://iptvm3u.ru/0919.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng6]
|
||||
name = 'Каналы СНГ 6'
|
||||
desc =
|
||||
pls = 'https://iptvm3u.ru/0819.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng7]
|
||||
name = 'Каналы СНГ 7'
|
||||
desc =
|
||||
pls = 'https://iptvm3u.ru/1019.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng8]
|
||||
name = 'Каналы СНГ 8'
|
||||
desc =
|
||||
pls = 'https://iptvm3u.ru/1119.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng10]
|
||||
name = 'Каналы СНГ 10'
|
||||
desc =
|
||||
pls = 'https://webhalpme.ru/donwhm.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng11]
|
||||
name = 'Каналы СНГ 11'
|
||||
desc =
|
||||
pls = 'https://iptvmaster.ru/hd.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng12]
|
||||
name = 'Каналы Армении'
|
||||
desc =
|
||||
pls = 'https://iptvmaster.ru/armenia.m3u'
|
||||
src = 'https://iptvsensei.ru/novye-samoobnovlyaemye-plejlisty'
|
||||
|
||||
[sng13]
|
||||
name = 'Каналы СНГ 13'
|
||||
desc =
|
||||
pls = 'https://dl.dropboxusercontent.com/s/iw9v57cln6dfkpu/Vinnitsa.m3u'
|
||||
src = 'https://iptvsensei.ru/samoobnovlyayemyye-pleylisty-iptv'
|
||||
|
||||
[sng14]
|
||||
name = 'Каналы СНГ 14'
|
||||
desc =
|
||||
pls = 'http://gorod.tv/iptv.m3u'
|
||||
src = 'https://iptvsensei.ru/samoobnovlyayemyye-pleylisty-iptv'
|
||||
|
||||
[x]
|
||||
name = 'IPTV плейлист для взрослых 2022 (smarttvnews.ru)'
|
||||
desc = 'Рабочий IPTV плейлист с каналами и фильмами для взрослых'
|
||||
pls = 'https://smarttvnews.ru/apps/xxx.m3u'
|
||||
src = 'https://smarttvnews.ru/iptv-plejlist-dlya-vzroslyh/'
|
||||
|
||||
[x2]
|
||||
name = 'IPTV плейлист для взрослых 2022 (iptv-russia.ru)'
|
||||
desc = ''
|
||||
pls = 'https://iptv-russia.ru/list/xxx.m3u'
|
||||
src = 'https://iptv-russia.ru/playlists/xxx/'
|
||||
4
src/config/routes.php
Normal file
4
src/config/routes.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
180
src/helpers.php
Normal file
180
src/helpers.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
use flight\Engine;
|
||||
use flight\net\Response;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
/**
|
||||
* Returns path to root application directory
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
function root_path(string $path = ''): string
|
||||
{
|
||||
return rtrim(sprintf('%s/%s', dirname($_SERVER['DOCUMENT_ROOT']), $path), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to app
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
function app_path(string $path = ''): string
|
||||
{
|
||||
return root_path("app/$path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return path to application configuration directory
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
function config_path(string $path = ''): string
|
||||
{
|
||||
return root_path("config/$path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to app cache
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
function cache_path(string $path = ''): string
|
||||
{
|
||||
return root_path("cache/$path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return path to public part of application
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
function public_path(string $path = ''): string
|
||||
{
|
||||
return root_path("public/$path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to app views
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
function views_path(string $path = ''): string
|
||||
{
|
||||
return root_path("views/$path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns base URL
|
||||
*
|
||||
* @param string $route
|
||||
* @return string
|
||||
*/
|
||||
function base_url(string $route = ''): string
|
||||
{
|
||||
return rtrim(sprintf('%s/%s', config('flight.base_url'), $route), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value of environment var
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed|null $default
|
||||
* @return mixed
|
||||
*/
|
||||
function env(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $_ENV[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders template
|
||||
*
|
||||
* @param mixed $template
|
||||
* @param array $data
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
function view(mixed $template, array $data = []): void
|
||||
{
|
||||
$template = str_contains($template, '.twig') ? $template : "$template.twig";
|
||||
echo Flight::view()->render($template, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns response object
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
function response(): Response
|
||||
{
|
||||
return Flight::response();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns app object
|
||||
*
|
||||
* @return Engine
|
||||
*/
|
||||
function app(): Engine
|
||||
{
|
||||
return Flight::app();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any value as boolean
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
function bool(mixed $value): bool
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_object($value)) {
|
||||
return true;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
return match ($value = trim($value)) {
|
||||
'1', 'yes', 'true' => true,
|
||||
'0', 'no', 'false' => false,
|
||||
default => empty($value),
|
||||
};
|
||||
}
|
||||
if ($is_resource = is_resource($value)) {
|
||||
return $is_resource; // false if closed
|
||||
}
|
||||
return !empty($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config values
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed|null $default
|
||||
* @return mixed
|
||||
*/
|
||||
function config(string $key, mixed $default = null): mixed
|
||||
{
|
||||
$config = Flight::get('config');
|
||||
if (isset($config["flight.$key"])) {
|
||||
return $config["flight.$key"];
|
||||
}
|
||||
if (isset($config[$key])) {
|
||||
return $config[$key];
|
||||
}
|
||||
$config = Arr::undot($config);
|
||||
if (Arr::has($config, $key)) {
|
||||
return Arr::get($config, $key);
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
7
src/public/css/bootstrap.min.css
vendored
Normal file
7
src/public/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
17
src/public/index.php
Normal file
17
src/public/index.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bootstrap all classes, settings, etc.
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
require '../bootstrap.php';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Start application
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
Flight::start();
|
||||
7
src/public/js/bootstrap.bundle.min.js
vendored
Normal file
7
src/public/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
46
src/public/js/checker.js
Normal file
46
src/public/js/checker.js
Normal file
@@ -0,0 +1,46 @@
|
||||
document.querySelectorAll('tr.pls').forEach((tr) => {
|
||||
const id = tr.attributes['data-playlist-id'].value
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.responseType = 'json'
|
||||
xhr.timeout = 60000 // ms = 1 min
|
||||
let el_status = tr.querySelector('span.status')
|
||||
let el_count = tr.querySelector('td.count')
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
console.log('[' + id + '] DONE', xhr.response)
|
||||
el_status.classList.remove('bg-secondary')
|
||||
el_status.innerText = xhr.response.status
|
||||
el_count.innerText = xhr.response.count
|
||||
switch (xhr.response.status) {
|
||||
case 'online':
|
||||
el_status.classList.add('bg-success')
|
||||
break
|
||||
case 'timeout':
|
||||
el_status.classList.add('bg-warning')
|
||||
break
|
||||
default:
|
||||
el_status.classList.add('bg-danger')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr.onerror = () => {
|
||||
console.log('[' + id + '] ERROR', xhr.response)
|
||||
el_status.classList.add('bg-danger')
|
||||
el_status.innerText = 'error'
|
||||
el_count.innerText = '-'
|
||||
}
|
||||
xhr.onabort = () => {
|
||||
console.log('[' + id + '] ABORTED', xhr.response)
|
||||
el_status.classList.add('bg-secondary')
|
||||
el_count.innerText = '-'
|
||||
}
|
||||
xhr.ontimeout = () => {
|
||||
console.log('[' + id + '] TIMEOUT', xhr.response)
|
||||
el_status.classList.add('bg-secondary')
|
||||
el_status.innerText = 'timeout'
|
||||
el_count.innerText = '-'
|
||||
}
|
||||
xhr.open('GET', '/' + id + '/json')
|
||||
xhr.send()
|
||||
})
|
||||
76
src/views/details.twig
Normal file
76
src/views/details.twig
Normal file
@@ -0,0 +1,76 @@
|
||||
{% extends "layouts/default.twig" %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<a href="{{ base_url() }}" class="btn btn-outline-light mb-3"><< Назад</a>
|
||||
<h2>{{ name }}</h2>
|
||||
{% if (encoding.alert) %}
|
||||
<div class="alert alert-warning small" role="alert">
|
||||
Кодировка исходного плейлиста отличается от UTF-8.
|
||||
Он был автоматически с конвертирован из {{ encoding.name }}, чтобы отобразить здесь список каналов.
|
||||
Однако названия каналов могут отображаться некорректно, причём не только здесь, но и в плеере.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h4>О плейлисте</h4>
|
||||
<table class="table table-dark table-hover small">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="w-25">ID</td>
|
||||
<td>
|
||||
{{ id }} {% if status == 'online' %}
|
||||
<span class="badge small text-dark bg-success">online</span>
|
||||
{% elseif status == 'offline' %}
|
||||
<span class="badge small text-dark bg-danger">offline</span>
|
||||
{% elseif status == 'timeout' %}
|
||||
<span class="badge small text-dark bg-warning">timeout</span>
|
||||
{% elseif status == 'error' %}
|
||||
<span class="badge small text-dark bg-danger">error</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Описание</td>
|
||||
<td><p>{{ desc }}</p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td title="Нажми на ссылку, чтобы скопировать её в буфер обмена"><b>Ccылка для ТВ</b></td>
|
||||
<td><b onclick="prompt('Скопируй адрес плейлиста', '{{ url }}')"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Нажми на ссылку, чтобы скопировать её в буфер обмена"
|
||||
class="font-monospace cursor-pointer">{{ url }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>M3U</td>
|
||||
<td>{{ pls }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Источник</td>
|
||||
<td>{{ src }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4>Список каналов ({{ count }})</h4>
|
||||
<div class="overflow-auto" style="max-height: 350px;">
|
||||
<table class="table table-dark table-hover small">
|
||||
<tbody>
|
||||
{% for channel in channels %}
|
||||
<tr>
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ channel }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
149
src/views/faq.twig
Normal file
149
src/views/faq.twig
Normal file
@@ -0,0 +1,149 @@
|
||||
{% extends "layouts/default.twig" %}
|
||||
|
||||
{% block header %}
|
||||
<a href="{{ base_url() }}" class="btn btn-outline-light mb-3"><< Назад</a>
|
||||
<h2>FAQ</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
На этой странице собраны ссылки на IPTV-плейлисты, которые находятся в открытом доступе.
|
||||
Они отбираются вручную и постоянно проверяются здесь автоматически.
|
||||
</p>
|
||||
<p>
|
||||
Сервис "{{ config('app.title') }}" ({{ base_url() }}) не предназначен для хранения или трансляции
|
||||
видео/аудио потоков, программ телепередач, плейлистов и их поддержки. Этим занимаются администраторы
|
||||
ресурсов, указанные как источник, и те, с чьих ресурсов ведётся трансляция.
|
||||
</p>
|
||||
<p class="mb-5">
|
||||
Сервис "{{ config('app.title') }}" ({{ base_url() }}) предоставляет только информацию об активности
|
||||
плейлистов, найденных в открытом доступе, и короткие ссылки на них для удобства использования в ПО.
|
||||
Вопросы по содержанию и работоспособности плейлистов, а также вопросы юридического характера, адресуйте
|
||||
тем, кто несёт за них ответственность (см. источники плейлистов).
|
||||
</p>
|
||||
|
||||
<h3>Как пользоваться сервисом?</h3>
|
||||
<p class="mb-5">
|
||||
На главной странице отображается список доступных в плейлистов, их идентификаторы, статусы,
|
||||
количество каналов и короткие ссылки.
|
||||
Для просмотра списка каналов следует нажать на ссылку <b>"Подробнее..."</b> под интересующим плейлистом.
|
||||
Для добавления плейлиста в свой медиаплеер удобно использовать <b>"Ссылку для ТВ"</b>.
|
||||
Это делается для удобства ввода, например, на телевизоре с пульта.
|
||||
На странице детальной информации также есть прямая ссылка на сам плейлист от источника.
|
||||
Можно использовать и её.
|
||||
</p>
|
||||
|
||||
<h3>Эти плейлисты и каналы в них -- бесплатны?</h3>
|
||||
<p class="mb-5">Возможно. По крайней мере, так утверждают источники. Но гарантий никаких никто не даёт.</p>
|
||||
|
||||
<h3>Как подключить плейлист?</h3>
|
||||
<p class="mb-5">
|
||||
<a href="https://www.google.com/search?q=%D0%BA%D0%B0%D0%BA%20%D0%BF%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B8%D1%82%D1%8C%20iptv%20%D0%BF%D0%BB%D0%B5%D0%B9%D0%BB%D0%B8%D1%81%D1%82%20%D0%BF%D0%BE%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B5">
|
||||
Добавь в свой медиаплеер</a> "Ссылку для ТВ".
|
||||
</p>
|
||||
|
||||
<h3>Какие плейлисты попадают сюда?</h3>
|
||||
<p>Есть некоторые критерии, по которым плейлисты отбираются в этот список:</p>
|
||||
<ul>
|
||||
<li>Прежде всего -- каналы РФ и бывшего СНГ, но не только</li>
|
||||
<li>Открытый источник</li>
|
||||
<li>Прямая ссылка на плейлист</li>
|
||||
<li>Автообновление плейлиста</li>
|
||||
</ul>
|
||||
<p>
|
||||
В основном, в плейлистах именно трансляции телеканалов, но могут быть просто список каких-то
|
||||
(мульт)фильмов и передач, находящихся на чужих дисках (как если бы вы сами составили плейлист с музыкой,
|
||||
например).
|
||||
</p>
|
||||
|
||||
<h3>Что означают статусы плейлистов?</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="badge small text-dark bg-secondary">?</span>
|
||||
Загрузка данных, нужно немного подождать.
|
||||
</li>
|
||||
<li>
|
||||
<span class="badge small text-dark bg-success">online</span>
|
||||
Плейлист, возможно, активен.
|
||||
</li>
|
||||
<li>
|
||||
<span class="badge small text-dark bg-warning">timeout</span>
|
||||
Не удалось вовремя проверить плейлист.
|
||||
</li>
|
||||
<li>
|
||||
<span class="badge small text-dark bg-danger">offline</span>
|
||||
Плейлист недоступен.
|
||||
</li>
|
||||
<li>
|
||||
<span class="badge small text-dark bg-danger">error</span>
|
||||
Ошибка при проверке плейлиста.
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mb-5">
|
||||
На странице детального описания статус может отображаться только online/offline.
|
||||
Это временно. В некоем скором времени это будет доработано.
|
||||
</p>
|
||||
|
||||
<h3>Почему нельзя доверять результатам проверки?</h3>
|
||||
<p>
|
||||
Я не гарантирую корректность и актуальность информации, которую ты увидишь здесь.
|
||||
Хотя я и стараюсь улучшать качество проверок, но всё же рекомендую проверять желаемые
|
||||
плейлисты самостоятельно вручную, ибо нет никаких гарантий:
|
||||
</p>
|
||||
<ul class="mb-5">
|
||||
<li>
|
||||
что это вообще плейлисты, а не чьи-то архивы с мокрыми кисками;
|
||||
</li>
|
||||
<li>
|
||||
что плейлисты по разным ссылкам не дублируют друг друга и отличаются каналами хотя бы на четверть;
|
||||
</li>
|
||||
<li>
|
||||
что плейлист работоспособен (каналы работают, корректно названы, имеют аудио, etc.);
|
||||
</li>
|
||||
<li>
|
||||
что подгрузится корректное количество каналов и их список (хотя на это я ещё могу влиять и
|
||||
стараюсь как-то улучшить).
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Какова гарантия, что я добавлю себе плейлист отсюда и он работать хоть сколько-нибудь долго?</h3>
|
||||
<p class="mb-5">
|
||||
Никакова.
|
||||
Мёртвые плейлисты я периодически вычищаю, реже -- добавляю новые.
|
||||
ID плейлистов могут меняться, поэтому вполне может произойти внезапная подмена одного другим, однако
|
||||
намеренно я так не делаю.
|
||||
Если один плейлист переезжает на новый адрес, то я ставлю временное перенаправление со старого ID на
|
||||
новый.
|
||||
Плюс читай выше про доверие результатам проверки (проблема может быть не стороне сервиса).
|
||||
</p>
|
||||
|
||||
<h3>Где взять программу передач (EPG)?</h3>
|
||||
<ul class="mb-5">
|
||||
<li><b>https://iptvx.one/viewtopic.php?f=12&t=4</b></li>
|
||||
<li>https://iptvmaster.ru/epg-for-iptv</li>
|
||||
<li>https://google.com</li>
|
||||
</ul>
|
||||
|
||||
<h3>Как часто обновляется этот список?</h3>
|
||||
<p class="mb-5">
|
||||
Время от времени.
|
||||
Иногда я захожу сюда и проверяю всё ли на месте, иногда занимаюсь какими-то доработками.
|
||||
Если есть кандидаты на добавление, то читай ниже.
|
||||
</p>
|
||||
|
||||
<h3>Как часто обновляется содержимое плейлистов?</h3>
|
||||
<p class="mb-5">Зависит от источника. Я этим не занимаюсь.</p>
|
||||
|
||||
<h3>Есть ли API? Как им пользоваться?</h3>
|
||||
<p class="mb-5">Есть, подробности <a href="https://github.com/anthonyaxenov/iptv2#api">здесь</a>.</p>
|
||||
|
||||
<h3>Как пополнить этот список?</h3>
|
||||
<p class="mb-5">
|
||||
Сделать pull-request в <a href="https://github.com/anthonyaxenov/iptv">репозиторий</a>.
|
||||
Я проверю плейлист и добавлю его в общий список, если всё ок.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
39
src/views/layouts/default.twig
Normal file
39
src/views/layouts/default.twig
Normal file
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<title>{{ config('app.title') }}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<link href="{{ base_url('css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<style>.cursor-pointer {
|
||||
cursor: pointer
|
||||
}</style>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="col-lg-8 mx-auto p-3 pt-md-5 pb-0">
|
||||
<header class="pb-3 mb-3">
|
||||
<a href="/" class="text-light text-decoration-none">
|
||||
<h1>{{ config('app.title') }}</h1>
|
||||
</a>
|
||||
<p class="small text-muted">
|
||||
<a class="small" href="{{ base_url('faq') }}">FAQ</a> | <a
|
||||
class="small" href="https://github.com/anthonyaxenov/iptv">GitHub</a> | <a
|
||||
class="small" href="https://axenov.dev">axenov.dev</a>
|
||||
</p>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="py-4 text-center">
|
||||
<a href="https://github.com/anthonyaxenov/iptv">GitHub</a> | <a href="https://axenov.dev">axenov.dev</a>
|
||||
<script src="{{ base_url('js/bootstrap.bundle.min.js') }}"></script>
|
||||
{% block footer %}{% endblock %}
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
63
src/views/list.twig
Normal file
63
src/views/list.twig
Normal file
@@ -0,0 +1,63 @@
|
||||
{% extends "layouts/default.twig" %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<p class="text-muted small">
|
||||
Обновлено: {{ updated_at }} МСК<br/>
|
||||
Плейлистов в списке: <strong>{{ count }}</strong>
|
||||
</p>
|
||||
<hr/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<table class="table table-dark table-hover small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Информация о плейлисте</th>
|
||||
<th>Каналов</th>
|
||||
<th title="Нажми на ссылку, чтобы скопировать её в буфер обмена">Ссылка для ТВ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for id, playlist in playlists %}
|
||||
<tr class="pls" data-playlist-id="{{ id }}">
|
||||
<td class="text-center id">
|
||||
<strong>{{ id }}</strong>
|
||||
</td>
|
||||
<td class="info">
|
||||
<strong>{{ playlist.name }}</strong>
|
||||
<span class="badge small bg-secondary text-dark status">?</span>
|
||||
<div class="small">
|
||||
{% if playlist.desc|length > 0 %}
|
||||
<p class="my-1">{{ playlist.desc }}</p>
|
||||
{% endif %}
|
||||
<a href="{{ base_url(id ~ '/details') }}"
|
||||
target="_blank"
|
||||
rel="noopener nofollow">Подробнее...</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center count">
|
||||
<div class="spinner-border text-success" role="status">
|
||||
<span class="visually-hidden">загрузка...</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="col-3">
|
||||
<span onclick="prompt('Скопируй адрес плейлиста', '{{ playlist.url }}')"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Нажми на ссылку, чтобы скопировать её в буфер обмена"
|
||||
class="font-monospace cursor-pointer">
|
||||
{{ playlist.url }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
<script src="{{ base_url('js/checker.js') }}"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user