0
0
mirror of https://github.com/anthonyaxenov/iptv.git synced 2024-11-22 05:24:45 +00:00
This commit is contained in:
Anthony Axenov 2022-02-06 00:05:47 +08:00
parent da54db9c50
commit fb69c92ead
Signed by: anthony
GPG Key ID: EA9EC32FF7CCD4EC
4 changed files with 231 additions and 97 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/.idea

7
css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

313
index.php
View File

@ -1,114 +1,233 @@
<?php <?php
ini_set('display_errors', 1); //ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); //ini_set('display_startup_errors', 1);
error_reporting(E_ALL); //error_reporting(E_ALL);
/** $updated_at = date('d.m.Y h:i', filemtime('playlists.ini'));
* Возвращает количество каналов в плейлисте $my_url = $_SERVER['SERVER_NAME'] . '?';
* $ini = parse_ini_file('playlists.ini', true);
* @param string $pls_url URL плейлиста
* @return int // получение инфы о плейлисте
*/ if (!empty($_GET['getinfo'])) {
function getChannelCount($pls_url) { $ch = curl_init();
$content = file_get_contents($pls_url); curl_setopt($ch, CURLOPT_URL, $ini[$_GET['getinfo']]['pls']);
unset($ini);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
$response = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = explode("\r\n", substr($response, 0, $header_size));
$content = substr($response, $header_size);
unset($response);
unset($header_size);
curl_close($ch);
unset($ch);
$matches = []; $matches = [];
preg_match_all('[EXTINF]', $content, $matches); preg_match_all("/^#EXTINF:-?\d[\s]?,[\s]?(.*$)/m", $content, $matches);
return count($matches[0]); unset($content);
$channels = $matches[1];
unset($matches);
$is_online = is_array($headers) && !empty($headers) && strpos($headers[0], ' 200') !== false;
unset($headers);
array_walk($channels, function (&$str) { $str = trim($str); });
die(json_encode([
'is_online' => $is_online,
'count' => $is_online ? count($channels) : '-',
'channels' => $channels,
]));
} }
// Шаблон короткой ссылки на плейлист if (array_intersect(array_keys($_GET), array_keys($ini))) {
// $my_url = $_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'].'?s='; $id = array_keys($_GET)[0];
$my_url = $_SERVER['SERVER_NAME'].'/iptv?s='; if (!empty($ini[$id]['redirect'])) {
header('Location: ' . $ini[$ini[$id]['redirect']]['pls']);
// Чтение списка плейлистов из ini-файла die;
$data = parse_ini_file('playlists.ini', true); } elseif (!empty($ini[$id]['pls'])) {
header('Location: ' . $ini[$id]['pls']);
if (!empty($_GET['s'])) { die;
if (!empty($data[$_GET['s']]['redirect'])) { }
header('Location: '.$data[$data[$_GET['s']]['redirect']]['pls']); }
} elseif (!empty($data[$_GET['s']]['pls'])) {
header('Location: '.$data[$_GET['s']]['pls']);
}
} else {
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="ru">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IPTV Playlists</title> <title>IPTV Playlists</title>
<style> <link href="css/bootstrap.min.css" rel="stylesheet">
.myurl { <script src="js/bootstrap.bundle.min.js"></script>
font-weight: bold; </head>
line-height: 2em; <body class="bg-dark text-light">
display: block; <div class="col-lg-8 mx-auto p-3 py-md-5">
} <header class="pb-3 mb-5 border-bottom">
.center { <a href="/" class="text-light text-decoration-none">
text-align: center; <h1>Самообновляемые плейлисты IPTV</h1>
} </a>
.pointer { <p class="small text-muted">
cursor:pointer; Обновлено: <?=$updated_at?>&nbsp;МСК<br/>
} Плейлистов в списке:&nbsp;<strong><?=count($ini)?></strong>
</style>
</head>
<body>
<h1>Список самообновляемых плейлистов для IPTV</h1>
<p>
Дата обновления списка: <strong>
<?=date('d-m-Y h:i:s', filemtime('playlists.ini'))?>&nbsp;МСК<br>
<a href="https://github.com/anthonyaxenov/iptv">github.com/anthonyaxenov/iptv</a>
</strong>
<p>
Поддержкой этих плейлистов занимаются сервисы и ресурсы, указанные как источник (если таковые имеются).<br>
Эти плейлисты собраны здесь вручную и бесплатны.
</p> </p>
<p>Чтобы подключить плейлист, нужно в настройках IPTV-плеера добавить ссылку в следующем формате:</p> </header>
<pre><?=$my_url?><strong>ID</strong></pre>
<p>где <strong>ID</strong> - один из указанных ниже идентификаторов.</p> <main>
<table width="100%" border="1" cellpadding="1"> <div class="container">
<thead> <p>
На этой странице собраны ссылки на IPTV-плейлисты, которые находятся в открытом доступе.<br/>
Они бесплатны для использования. Список проверяется и обновляется мной вручную.<br/>
Поддержкой этих плейлистов занимаются сервисы и ресурсы, указанные как источник.<br/>
Вопросы работоспособности плейлистов адресуйте тем, кто несёт за них ответственность.
</p>
<p>Чтобы подключить плейлист, нужно в настройках IPTV-плеера указать ссылку из последней колонки.</p>
</div>
<div class="container py-5">
<h2>Пояснение статусов проверки плейлистов</h2>
<ui>
<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>
</ui>
</div>
<div class="container py-5">
<h2>Список плейлистов</h2>
<table class="table table-dark table-hover">
<thead>
<tr> <tr>
<td class="center">ID</td> <th>ID</th>
<td>Информация о плейлисте</td> <th>Информация о плейлисте</th>
<td class="center">Каналов</td> <th>Каналов</th>
<td title="Нажмите на ссылку, чтобы скопировать адрес">Ссылка на плейлист</td> <th title="Нажмите на ссылку, чтобы скопировать её в буфер обмена">Ссылка</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($data as $id => $element) { <?php
foreach ($ini as $id => $element) {
if (empty($element['pls'])) { if (empty($element['pls'])) {
continue; continue;
} ?> }
<tr> ?>
<td class="center"> <tr class="pls" data-playlist-id="<?=$id?>">
<td class="text-center id">
<strong><?=$id?></strong> <strong><?=$id?></strong>
</td> </td>
<td> <td class="info">
<strong><?=$element['name'] ?: "Плейлист #".$id?></strong> <strong><?=$element['name'] ?: "Плейлист #" . $id?></strong>
<?php if (!empty($element['src'])) { ?> <span class="badge small bg-warning text-dark status">?</span>
<br><a href="<?=$element['src']?>" target="_blank" rel="noopener nofollow">Источник</a> <div class="small">
<?php } ?> <a href="<?=$element['pls']?>"
<?php if (!empty($element['desc'])) { ?> target="_blank"
<br><?=$element['desc']?> rel="noopener nofollow">M3U</a>
<?php } ?> <?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>
<td class="center"><?=getChannelCount($element['pls'])?></td> <td class="text-center count">
<td width="250"> <div class="spinner-border text-success" role="status">
<span onclick="prompt('Скопируйте адрес плейлиста', '<?=$my_url?><?=$id?>')" <span class="visually-hidden">загрузка...</span>
title="Нажмите, чтобы скопировать адрес" </div>
class="pointer myurl"> </td>
<?=$my_url?><?=$id?> <td class="col-3">
</span> <span onclick="prompt('Скопируйте адрес плейлиста', '<?=$my_url?><?=$id?>')"
<span> data-bs-toggle="tooltip"
Прямая ссылка: <a href="<?=$element['pls']?>">M3U</a> data-bs-placement="top"
</span> title="Нажмите на ссылку, чтобы скопировать её в буфер обмена"
class="font-monospace">
<?=$my_url?><?=$id?>
</span>
</td> </td>
</tr> </tr>
<?php } ?> <?php
</tbody> } ?>
</table> </tbody>
</body> </table>
</html> </div>
<?php </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 = 10000 // ms
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')
tr.querySelector('td.info').innerHTML += '<a class="small" ' +
'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>

7
js/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long