0
0
mirror of https://github.com/anthonyaxenov/iptv.git synced 2024-11-23 14:00:41 +00:00

Большое обновление

- проект переписан на flight + twig, laravel-like хелперы
- docker-окружение
- новая страница с подробностями о плейлисте
- улучшен json о плейлисте
- нормальный роутинг
- нормальная статусная система
- попытка перекодировки при не utf-8 + предупреждение об этом
- дополнены FAQ + README
This commit is contained in:
Anthony Axenov 2022-09-01 19:54:43 +08:00
parent 649ab85d79
commit c43439b9cc
Signed by: anthony
GPG Key ID: EA9EC32FF7CCD4EC
37 changed files with 2566 additions and 860 deletions

5
.gitignore vendored
View File

@ -1,7 +1,12 @@
/.idea
/.vscode
/downloaded
/src/cache/*
/src/vendor
*.log
.env
*.m3u
*.m3u.*
*.m3u8
*.m3u8.*
!/**/.gitkeep

245
README.md
View File

@ -1,97 +1,200 @@
# Список самообновляемых плейлистов для IPTV
# Автообновляемые IPTV-плейлисты
- [Список самообновляемых плейлистов для IPTV](#список-самообновляемых-плейлистов-для-iptv)
- [Как использовать этот список?](#как-использовать-этот-список)
- [Как добавить плейлист в этот список?](#как-добавить-плейлист-в-этот-список)
- [API](#api)
- [Формат `playlists.ini`](#формат-playlistsini)
- [Дополнительные инструменты (`./tools`)](#дополнительные-инструменты-tools)
- [Как создать свой собственный плейлист](#как-создать-свой-собственный-плейлист)
- [Лицензия](#лицензия)
> **Web-версия**: [https://iptv.axenov.dev/](https://iptv.axenov.dev/)
> **FAQ**: [https://iptv.axenov.dev/faq](https://iptv.axenov.dev/faq)
> **Зеркало репозитория**: https://git.axenov.dev/anthony/iptv
Проект, содержащий в себе инструменты для работы с IPTV-плейлистами:
* список автообновляемых плейлистов, которые найдены в открытых источниках;
* скрипты для поиска каналов в этом списке, создания своего плейлиста;
* веб-сервис, предоставляющий короткие ссылки на эти плейлисты и отображающий список каналов.
Плейлисты подбираются преимущественно для РФ и любых стран бывшего СНГ, но этими странами список не ограничивается.
Поддержкой этих плейлистов занимаются сервисы и ресурсы, указанные как источник.
Вопросы работоспособности плейлистов адресуйте тем, кто несёт за них ответственность.
Они бесплатны для использования.
Список проверяется и обновляется мной вручную.
Гарантию работоспособности никто не даёт.
## English description
> **Mirrored repo**: https://git.axenov.dev/anthony/iptv
This repo contains IPTV-playlists free to use with your media-player.
Most of them are in russian or CIS languages but you can find something interesting here for yourself.
Also there are some handy tools to make your own playlist or find channels you want in playlists listed here.
You can use this repo according to [LICENSE](LICENSE) conditions.
I'm too lazy to translate and support the whole project in ru and en, sorry, guys.
---
> **[Перейти на актуальную сраницу](https://iptv.axenov.dev/)**
## Содержание
Здесь собраны ссылки на IPTV-плейлисты, которые находятся в открытом доступе.
- [Как использовать этот список?](#howtouse)
- [Формат playlists.ini](#iniformat)
- [API](#api)
- [Развёртывание проекта](#deploy)
- [Дополнительные инструменты](#tools)
- [Как создать свой собственный плейлист](#howtomake)
- [Использованный стек](#stack)
- [Лицензия](#license)
Они бесплатны для использования. Список проверяется и обновляется мной вручную.
---
Поддержкой этих плейлистов занимаются сервисы и ресурсы, указанные как источник.
Вопросы работоспособности плейлистов адресуйте тем, кто несёт за них ответственность.
<a id="howtouse"></a>
## Как использовать этот список?
Чтобы подключить плейлист, нужно в настройках IPTV-плеера указать ссылку в следующем формате:
Чтобы подключить плейлист, нужно в настройках медиаплеера указать ссылку в следующем формате:
```
iptv.axenov.dev?ID
iptv.axenov.dev/<ID>
iptv.axenov.dev?<ID> (устаревший формат)
iptv.axenov.dev/?<ID> (устаревший формат)
```
где `ID` - один из идентификаторов, указанных в [`playlists.ini`](playlists.ini) в квадратных скобках.
## Как добавить плейлист в этот список?
1) Склонировать себе репозиторий, создать ветку
2) Внести изменения в файл [`playlists.ini`](playlists.ini) как описано ниже
3) Сделать коммит, отправить изменения в свой репозиторий и создать merge-request
где `<ID>` - один из идентификаторов, указанных в [`playlists.ini`](playlists.ini) в квадратных скобках.
Либо провернуть всё то же самое через браузер.
<a id="iniformat"></a>
## Формат `playlists.ini`
```ini
# В квадратных скобках - ID плейлиста в рамках этого
# конфига (обязателен). Для удобства ввода с пульта,
# для ID рекомендуется число или короткая строка без
# пробелов и др. спецсимволов.
[1]
# Название плейлиста (необязательно)
name = 'Рабочий и актуальный IPTV плейлист M3U'
# Краткое описание из источника или от себя (необязательно)
desc = 'В этом IPTV плейлисте вы найдете очень много каналов в HD качестве'
# Прямая ссылка на m3u/m3u8 плейлист (обязательно)
pls = 'https://example.com/pls.m3u'
# Ссылка на источник, откуда взят плейлист (необязательно)
src = 'https://example.com/super-duper-playlist'
[2]
# ID другого плейлиста в этом списке, на который
# произойдёт редирект. Нужен для мягкой смены ID.
redirect = 1
```
В описании плейлиста обязательны:
* любой желаемый ID в квадратных скобках;
* либо `pls`, либо `redirect` (если указаны оба, то `redirect` приоритетен).
Плейлистов с редиректами может быть сколько угодно, но они не должны быть цикличными.
<a id="api"></a>
## API
Можно получать состояние плейлистов из этого сборника при помощи метода:
```
GET https://iptv.axenov.dev/?getinfo=<ID>
GET https://iptv.axenov.dev/<ID>/json
```
где `ID` - один из идентификаторов, указанных в [`playlists.ini`](playlists.ini) в квадратных скобках.
где `ID` -- один из идентификаторов, указанных в [`playlists.ini`](playlists.ini) в квадратных скобках.
Ответом может быть JSON следующего содержания:
В случае успеха вернётся JSON следующего содержания:
```json
{
"is_online": true,
"count": 123,
"channels": [ ... ]
"id": "p1",
"url": "localhost:8080/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/",
"status": "online",
"encoding": {
"name": "UTF-8",
"alert": false
},
"channels": [
"Channel1",
"Channel2",
"ChannelX"
],
"count": 3
}
```
где:
* `is_online` - `bool`, доступность плейлиста
* `count` - `uint|char[1]`, количество каналов >=0 либо `'-'` при `is_online === false`
* `channels` - `string[]`, массив строк с названиями каналов, может быть пустым.
Также ответ может быть пустым (вообще пустым, даже не `null`).
Такое я встречал с одним конкретном плейлисте с поехавшей кодировкой.
Лень разбираться, пофиг.
* `id` -- название плейлиста
* `name` -- краткое описание из источника или от себя
* `url` -- короткая ссылка, которую можно использовать для добавления плейлиста в плеер
* `desc` -- подробное описание плейлиста
* `pls` -- прямая ссылка на m3u/m3u8 плейлист
* `src` -- ссылка на источник, откуда взят плейлист
* `status` -- признак доступности плейлиста (`online`, `timeout`, `offline`, `unknown`)
* `encoding` -- данные о кодировке файла плейлиста
* `name` -- название кодировки (на данный момент определяются только `UTF-8` или `Windows-1251`)
* `alert` -- признак отличия кодировки от `UTF-8`, названия каналов сконвертированы в `UTF-8`
* `channels` -- массив названий каналов
* `count` -- количество каналов >= 0
## Формат `playlists.ini`
> Название кодировки `encoding.name` может определяться неточно!
```ini
; В квадратных скобках - ID плейлиста в рамках этого
; конфига (обязателен). Для удобства ввода с пульта,
; для ID рекомендуется число или короткая строка без
; пробелов и др. спецсимволов.
[p1]
; Название плейлиста (необязательно)
name='webarmen.com 18+'
; Краткое описание из источника или от себя (необязательно)
desc=''
; Прямая ссылка на m3u/m3u8 плейлист (обязательно)
pls='https://webarmen.com/my/iptv/auto.xxx.m3u'
; Ссылка на источник, откуда взят плейлист (необязательно)
src='https://webarmen.com/my/iptv/xxx.php'
В случае ошибки вернётся JSON в следующем формате:
[p2]
; ID другого плейлиста в этом списке, на который
; произойдёт редирект (нужно для мягкой смены ID).
; Необязателен, но если указан, то приоритетнее, чем pls.
redirect=p1
```json
{
"id": "p1",
"url": "localhost:8080/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/",
"status": "offline",
"error": {
"code": 22,
"message": "The requested URL returned error: 404 Not Found"
}
}
```
где:
* `id` -- название плейлиста
* `name` -- краткое описание из источника или от себя
* `url` -- короткая ссылка, которую можно использовать для добавления плейлиста в плеер
* `desc` -- подробное описание плейлиста
* `pls` -- прямая ссылка на m3u/m3u8 плейлист
* `src` -- ссылка на источник, откуда взят плейлист
* `status` -- признак доступности плейлиста (`online`, `timeout`, `offline`, `error`)
* `error` -- данные о кодировке файла плейлиста
* `code` -- [код ошибки curl](https://curl.se/libcurl/c/libcurl-errors.html)
* `message` -- текст ошибки curl
<a id="deploy"></a>
## Развёртывание проекта
1. Выполнить `cp .env.example .env`, установить необходимые параметры
2. Выполнить `docker compose up -d --build`
3. Открыть `https://<APP_URL>:8080`
<a id="tools"></a>
## Дополнительные инструменты (`./tools`)
### `download-all.sh`
@ -102,7 +205,7 @@ redirect=p1
Проверяет каждый канал в плейлисте на доступность и выводит результат проверки.
Поддерживаются \*.m3u и \*.m3u8; как локальные файлы, так по прямым ссылкам.
Поддерживаются `*.m3u` и `*.m3u8`; как локальные файлы, так по прямым ссылкам.
Коды ошибок доступны [здесь](https://everything.curl.dev/usingcurl/returns).
@ -139,9 +242,9 @@ Check stats
### `find-in-pls.sh`
Находит каналы по заданному регулярному выражению в указанном плейлисте.
Находит каналы по заданному регулярному выражению в одном указанном плейлисте.
Поддерживаются \*.m3u и \*.m3u8; как локальные файлы, так по прямым ссылкам.
Поддерживаются `*.m3u` и `*.m3u8`; как локальные файлы, так по прямым ссылкам.
Пример:
@ -165,7 +268,7 @@ Found: 2
### `find-in-all.sh`
Находит каналы по заданному регулярному выражению в плейлистах, скачанных через `download-all.sh`.
Находит каналы по заданному регулярному выражению во всех плейлистах, скачанных через `download-all.sh`.
Пример:
@ -199,9 +302,10 @@ Nothing found
### `make-pls.sh`
Находит каналы по заданному регулярному выражению в плейлистах, скачанных через `download-all.sh`.
Находит каналы по заданному регулярному выражению во всех плейлистах, скачанных через `download-all.sh`.
Отличается от `find-in-all.sh` тем, что тот выводит результат в человекочитаемом формате, а этот -- в готовом m3u формате для сохранения в файл.
Отличается от `find-in-all.sh` тем, что тот выводит результат в человекочитаемом формате, а этот -- в готовом m3u
формате для сохранения в файл.
Пример:
@ -222,6 +326,7 @@ http://live02-cdn.tv.ti.ru:80/dtv/id376_NBN_SG--Fox_HD/04/plst.m3u8
...
```
<a id="howtomake"></a>
## Как создать свой собственный плейлист?
1. Скачать все плейлисты, указанные в [`playlists.ini`](playlists.ini):
@ -232,7 +337,8 @@ http://live02-cdn.tv.ti.ru:80/dtv/id376_NBN_SG--Fox_HD/04/plst.m3u8
```
$ ./tools/make-pls.sh "(fox|disney)" > my.m3u8
```
Так в плейлисте `./my.m3u8` окажутся все каналы из скачанных плейлистов, в названиях которых встрелись `fox` или `disney`.
Так в плейлисте `./my.m3u8` окажутся все каналы из скачанных плейлистов, в названиях которых встретились `fox`
или `disney`.
3. Проверить доступность каналов в полученном плейлисте:
```
$ ./tools/check-pls.sh my.m3u8
@ -242,6 +348,19 @@ http://live02-cdn.tv.ti.ru:80/dtv/id376_NBN_SG--Fox_HD/04/plst.m3u8
4. Вручную: удалить нерабочие, мусорные и продублировавшиеся (по ссылкам) каналы.
5. Вручную: добавить плейлист в IPTV-плеер и перепроверить результат.
<a id="stack"></a>
## Использованный стек
* [docker compose](https://docs.docker.com/compose/)
* [php8.1-fpm](https://www.php.net/releases/8.1/en.php)
* [FlightPHP](https://flightphp.com/learn)
* [Bootstrap 5](https://getbootstrap.com/docs/5.0/getting-started/introduction/)
* [nginx](https://nginx.org/ru/)
* bash
<a id="license"></a>
## Лицензия
[The MIT License](LICENSE)

39
docker-compose.yml Normal file
View File

@ -0,0 +1,39 @@
version: '3'
networks:
iptv:
driver: bridge
services:
php:
container_name: iptv-php
build: 'docker/php'
restart: unless-stopped
networks:
- iptv
volumes:
- /etc/localtime:/etc/localtime:ro
- ./docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro
- ./docker/php/php.ini:/usr/local/etc/php/conf.d/php.ini:ro
- ./log/php:/var/log/php:rw
- ./src:/var/www:rw
- ./playlists.ini:/var/www/config/playlists.ini:ro
nginx:
container_name: iptv-nginx
image: nginx:latest
restart: unless-stopped
networks:
- iptv
volumes:
- /etc/localtime:/etc/localtime:ro
- ./docker/nginx/vhost.conf:/etc/nginx/conf.d/default.conf:ro
- ./log/nginx:/var/log/nginx:rw
- ./src:/var/www:ro
ports:
- 8080:80
links:
- php
depends_on:
- php

30
docker/nginx/vhost.conf Normal file
View File

@ -0,0 +1,30 @@
server {
server_name iptv.local;
listen 80;
root /var/www/public;
index index.php;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
location ~* \.(jpg|jpeg|gif|css|png|js|ico|html)$ {
access_log off;
expires max;
log_not_found off;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_pass php:9000;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
include fastcgi_params;
}
}

16
docker/php/dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM php:8.1-fpm
RUN apt update && \
apt upgrade -y && \
apt install -y git && \
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# https://pecl.php.net/package/xdebug
RUN pecl channel-update pecl.php.net && \
pecl install xdebug-3.1.5 && \
mkdir -p /var/log/php
EXPOSE 9000
WORKDIR /var/www
CMD composer install
CMD php-fpm

27
docker/php/php.ini Normal file
View File

@ -0,0 +1,27 @@
[PHP]
file_uploads = Off
; upload_max_filesize=256M
; post_max_size=256M
error_reporting = E_ALL
;& ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE & ~E_WARNING
[opcache]
opcache.enable = 1
opcache.enable_cli = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 30000
opcache.revalidate_freq = 0
opcache.jit_buffer_size = 64M
opcache.jit = tracing
[xdebug]
; https://xdebug.org/docs/all_settings
zend_extension = xdebug.so
xdebug.mode = develop,debug
xdebug.REQUEST = *
xdebug.SESSION = *
xdebug.SERVER = *
xdebug.client_host = 172.17.0.1
;xdebug.start_with_request=yes
xdebug.start_with_request = trigger
xdebug.trigger_value = go

21
docker/php/www.conf Normal file
View File

@ -0,0 +1,21 @@
[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 50
pm.status_path = /status
ping.path = /ping
ping.response = pong
access.log = /var/log/php/$pool.access.log
;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{milli}d %{kilo}M %C%%"
; chroot = /var/www
; chdir = /var/www
php_flag[display_errors] = on
php_admin_value[error_log] = /var/log/php/www.error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 32M

345
index.php
View File

@ -1,345 +0,0 @@
<?php
/**
* Anthony Axenov (c) 2022
* The MIT License:
* https://github.com/anthonyaxenov/iptv/blob/master/LICENSE
*/
//ini_set('display_errors', 1);
//ini_set('display_startup_errors', 1);
//error_reporting(E_ALL);
function response(array $content): void
{
header("Content-Type: application/json; charset=utf-8");
die(json_encode($content));
}
$updated_at = date('d.m.Y h:i', filemtime('playlists.ini'));
$my_url = $_SERVER['SERVER_NAME'] . '?';
$ini = parse_ini_file('playlists.ini', true);
// get playlist info (ajax)
if (!empty($_GET['getinfo'])) {
$pls = $ini[$_GET['getinfo']];
if (!empty($pls['redirect'])) {
$pls = $ini[$pls['redirect']];
}
unset($ini);
if (empty($pls)) { // no playlist in ini
response([
'is_online' => false,
'count' => 0,
'channels' => [],
]);
}
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $pls['pls']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
curl_setopt($curl, CURLOPT_HEADER, 0);
$response = curl_exec($curl);
$code = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
unset($curl);
if ($response === false) { // timed out
response([
'is_online' => false,
'count' => '-',
'channels' => [],
]);
}
$matches = [];
preg_match_all("/^#EXTINF:-?\d.*,\s*(.*)/m", $response, $matches);
$channels = array_map('trim', $matches[1]);
unset($response, $matches);
response([
'is_online' => $is_online = $code < 400,
'count' => $is_online ? count($channels) : '-',
'channels' => $channels,
]);
}
// redirect to playlist
if (array_intersect(array_keys($_GET), array_keys($ini))) {
$id = array_keys($_GET)[0];
if (!empty($ini[$id]['redirect'])) {
header('Location: ' . $ini[$ini[$id]['redirect']]['pls']);
die;
} elseif (!empty($ini[$id]['pls'])) {
header('Location: ' . $ini[$id]['pls']);
die;
}
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IPTV Playlists</title>
<link href="css/bootstrap.min.css" rel="stylesheet">
<style>.cursor-pointer {cursor: pointer} </style>
<script src="js/bootstrap.bundle.min.js"></script>
</head>
<body class="bg-dark text-light">
<div class="col-lg-8 mx-auto p-3 py-md-5">
<header class="pb-3 mb-3">
<a href="/" class="text-light text-decoration-none">
<h1>Самообновляемые плейлисты IPTV</h1>
</a>
<p class="small text-muted">
<a href="https://github.com/anthonyaxenov/iptv">GitHub</a> | <a href="https://axenov.dev">axenov.dev</a><br/>
Обновлено:&nbsp;<?=$updated_at?>МСК<br/>
Плейлистов в списке:&nbsp;<strong><?=count($ini)?></strong>
</p>
</header>
<main>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="list-tab" data-bs-toggle="tab" data-bs-target="#list" type="button"
role="tab" aria-controls="list" aria-selected="true">Список</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="faq-tab" data-bs-toggle="tab" data-bs-target="#faq" type="button"
role="tab" aria-controls="faq" aria-selected="false">FAQ</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active pt-5" id="list" role="tabpanel" aria-labelledby="list-tab">
<table class="table table-dark table-hover small">
<thead>
<tr>
<th>ID</th>
<th>Информация о плейлисте</th>
<th>Каналов</th>
<th title="Нажми на ссылку, чтобы скопировать её в буфер обмена">Ссылка</th>
</tr>
</thead>
<tbody>
<?php
foreach ($ini as $id => $element) {
if (empty($element['pls'])) {
continue;
}
?>
<tr class="pls" data-playlist-id="<?=$id?>">
<td class="text-center id">
<strong><?=$id?></strong>
</td>
<td class="info">
<strong><?=$element['name'] ?: "Плейлист #" . $id?></strong>
<span class="badge small bg-warning text-dark status">?</span>
<div class="small">
<a href="<?=$element['pls']?>"
target="_blank"
rel="noopener nofollow">M3U</a>
<?php
if (!empty($element['src'])) { ?>
| <a href="<?=$element['src']?>"
target="_blank"
rel="noopener nofollow">Источник</a>
<?php
} ?>
<?php
if (!empty($element['desc'])) { ?>
<br/><p class="my-1"><?=$element['desc']?></p>
<?php
} ?>
</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('Скопируй адрес плейлиста', '<?=$my_url?><?=$id?>')"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Нажми на ссылку, чтобы скопировать её в буфер обмена"
class="font-monospace cursor-pointer">
<?=$my_url?><?=$id?>
</span>
</td>
</tr>
<?php
} ?>
</tbody>
</table>
</div>
<div class="tab-pane fade p-3 pt-5" id="faq" role="tabpanel" aria-labelledby="profile-tab">
<h2>Что здесь происходит?</h2>
<p>
На этой странице собраны ссылки на IPTV-плейлисты, которые находятся в открытом доступе.
Они отбираются мной вручную и проверяются здесь автоматически.
</p>
<p>
Ресурс <?=$_SERVER['SERVER_NAME']?> не занимается трансляцией видео- и аудиопотоков,
администрированием конечных плейлистов и программ телепередач или хранением всего указанного.
Подобными вопросами занимаются администраторы ресурсов, указанные как источник, и те, с чьих ресурсов
ведётся трансляция.
</p>
<p class="mb-5">
Ресурс <?=$_SERVER['SERVER_NAME']?> предоставляет только информацию об активности плейлистов, найденных
в открытом доступе, и короткие ссылки на них для удобства ввода с пульта на телевизоре.
Вопросы работоспособности плейлистов и каналов адресуйте тем, кто несёт за них ответственность.
</p>
<h2>Что означают статусы плейлистов?</h2>
<ul class="mb-5">
<li>
<span class="badge small bg-warning text-dark">?</span> Загрузка данных, нужно немного подождать.
</li>
<li>
<span class="badge small text-dark bg-success">online</span> Плейлист, возможно, активен.
</li>
<li>
<span class="badge small text-dark bg-secondary">unknown</span> Состояние неизвестно.
Возможно, плейлист активен, но корректно его проверить не удалось.
</li>
<li>
<span class="badge small text-dark bg-secondary">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>
<h2>Почему нельзя доверять результатам проверки?</h2>
<p>
Я не гарантирую корректность и актуальность информации, которую ты увидишь здесь.
Хотя я и стараюсь улучшать качество проверок, но всё же рекомендую проверять желаемые
плейлисты самостоятельно вручную, ибо нет никаких гарантий:
</p>
<ul class="mb-5">
<li>
что это вообще плейлисты, а не чьи-то архивы с мокрыми кисками;
</li>
<li>
что плейлисты по разным ссылкам не дублируют друг друга и отличаются каналами хотя бы на четверть;
</li>
<li>
что плейлист работоспособен (каналы работают, корректно названы, имеют аудио, etc.);
</li>
<li>
что подгрузится корректное количество каналов и их список (хотя на это я ещё могу влиять и
стараюсь как-то улучшить).
</li>
</ul>
<h2>Эти плейлисты и каналы в них -- бесплатны?</h2>
<p class="mb-5">Возможно. По крайней мере, так утверждают источники.</p>
<h2>Как подключить плейлист?</h2>
<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">
Добавь в свой IPTV-плеер</a> ссылку из последней колонки.
</p>
<h2>Какова гарантия, что я добавлю себе плейлист отсюда и он работать хоть сколько-нибудь долго?</h2>
<p class="mb-5">
Никакова.
Мёртвые плейлисты я периодически вычищаю, реже -- добавляю новые.
ID плейлистов могут меняться, поэтому вполне может произойти внезапная подмена одного другим, однако намеренно я так не делаю.
Если один плейлист переезжает на новый адрес, то я ставлю временное перенаправление со старого ID на новый.
Плюс читай выше про доверие результатам проверки (проблема может быть не на этой стороне).
</p>
<h2>Где взять программу передач (EPG)?</h2>
<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>
<h2>Как часто обновляется этот список?</h2>
<p>
Время от времени.
Иногда я захожу сюда и проверяю всё ли на месте, иногда занимаюсь какими-то доработками.
</p>
<p class="mb-5">
Если есть кандидаты на добавление, то читай ниже.
</p>
<h2>Как часто обновляются сами плейлисты (каналы)?</h2>
<p class="mb-5">
Зависит от источника. Я этим не занимаюсь.
</p>
<h2>Как пополнить этот список?</h2>
<p class="mb-5">
Сделать pull-request в <a href="https://github.com/anthonyaxenov/iptv">репозиторий</a>.
Я проверю плейлист и добавлю его в общий список, если всё ок.
</p>
</div>
</div>
</main>
<footer class="py-4 text-center">
<a href="https://github.com/anthonyaxenov/iptv">GitHub</a> | <a href="https://axenov.dev">axenov.dev</a>
</footer>
</div>
<script>
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 st_el = tr.querySelector('span.status')
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log('[' + id + '] DONE', xhr.response)
st_el.classList.remove('bg-warning')
if (xhr.response) {
tr.querySelector('td.count').innerHTML = xhr.response.count
if (xhr.response.is_online === true) {
st_el.innerHTML = 'online'
st_el.classList.add('bg-success')
if (xhr.response.channels.length > 0) {
tr.querySelector('td.info').innerHTML += '<a class="small cursor-pointer" ' +
'data-bs-toggle="collapse" data-bs-target="#channels-' + id + '" aria-expanded="false" ' +
'aria-controls="channels-' + id + '">Список каналов</a><div class="collapse" id="channels-' + id +
'"><p class="card card-body bg-dark small" style="max-height:250px;overflow-y:auto;">' +
xhr.response.channels.join('<br />') + '</p></div>'
}
} else {
st_el.innerHTML = 'offline'
st_el.classList.add('bg-danger')
}
} else {
tr.querySelector('td.count').innerHTML = '-'
st_el.classList.add('bg-secondary')
st_el.innerHTML = 'unknown'
}
}
}
xhr.onerror = () => {
console.log('[' + id + '] ERROR', xhr.response)
st_el.classList.add('bg-danger')
st_el.innerHTML = 'error'
tr.querySelector('td.count').innerHTML = '-'
}
xhr.onabort = () => {
console.log('[' + id + '] ABORTED', xhr.response)
st_el.classList.add('bg-secondary')
tr.querySelector('td.count').innerHTML = '-'
}
xhr.ontimeout = () => {
console.log('[' + id + '] TIMEOUT', xhr.response)
st_el.classList.add('bg-secondary')
st_el.innerHTML = 'timeout'
tr.querySelector('td.count').innerHTML = '-'
}
xhr.open('GET', '/?getinfo=' + id)
xhr.send()
})
</script>
</body>
</html>

0
log/nginx/.gitkeep Normal file
View File

0
log/php/.gitkeep Normal file
View File

View File

@ -1,452 +0,0 @@
[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='Каналы СНГ 12'
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/'

1
playlists.ini Symbolic link
View File

@ -0,0 +1 @@
src/config/playlists.ini

7
src/.env.example Normal file
View 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

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types = 1);
namespace App\Controllers;
abstract class Controller
{
}

View 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),
]);
}
}

View 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),
]);
}
}

View 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);
}
}

View 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
View 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,
];
}
}

View 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;
}
}

View 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,
];
}
}

View 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
View 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
View File

20
src/composer.json Normal file
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
<?php
declare(strict_types = 1);

180
src/helpers.php Normal file
View 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;
}

17
src/public/index.php Normal file
View File

@ -0,0 +1,17 @@
<?php
declare(strict_types = 1);
/*
|--------------------------------------------------------------------------
| Bootstrap all classes, settings, etc.
|--------------------------------------------------------------------------
*/
require '../bootstrap.php';
/*
|--------------------------------------------------------------------------
| Start application
|--------------------------------------------------------------------------
*/
Flight::start();

46
src/public/js/checker.js Normal file
View 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
View 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 }}&nbsp;{% 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
View 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 %}

View 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>&nbsp;|&nbsp;<a
class="small" href="https://github.com/anthonyaxenov/iptv">GitHub</a>&nbsp;|&nbsp;<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
View File

@ -0,0 +1,63 @@
{% extends "layouts/default.twig" %}
{% block title %}{{ title }}{% endblock %}
{% block header %}
<p class="text-muted small">
Обновлено:&nbsp;{{ updated_at }}&nbsp;МСК<br/>
Плейлистов в списке:&nbsp;<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 %}