-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.php
375 lines (328 loc) · 12.6 KB
/
index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
<?php
/**
* @file index.php
* @brief 主页处理脚本
*
* 该脚本处理来自客户端的请求,根据查询参数获取指定日期和频道的节目信息,
* 并从 SQLite 数据库中提取或返回默认数据。
*
* 作者: Tak
* GitHub: https://github.com/taksssss/EPG-Server
* 二次开发: mxdabc
* Github: https://github.com/mxdabc/epgphp
*/
// 引入公共脚本
require_once 'public.php';
// 获取当前完整的 URL
$requestUrl = $_SERVER['REQUEST_URI'];
// 修正 URL 格式:如果存在多个 `?`,将后续的 `?` 替换为 `&`
if (substr_count($requestUrl, '?') > 1) {
$requestUrl = preg_replace('/&/', '?', preg_replace('/\?/', '&', $requestUrl), 1);
}
// 解析 URL 中的查询参数
parse_str(str_replace('+', '%2B', parse_url($requestUrl, PHP_URL_QUERY)), $query_params);
// 获取 URL 中的 token 参数并验证
$tokenRange = $Config['token_range'] ?? 1;
$live = $query_params['live'] ?? '';
if ($tokenRange !== 0) {
$allowedTokens = array_map('trim', explode(',', $Config['token'] ?? ''));
$token = $query_params['token'] ?? '';
if (!in_array($token, $allowedTokens) && (($tokenRange !== 2 && $live) ||
($tokenRange !== 1 && !$live))) {
http_response_code(403);
echo '访问被拒绝:无效的 Token。';
exit;
}
}
// 获取请求的 User-Agent 并验证
$userAgentRange = $Config['user_agent_range'] ?? 0;
if ($userAgentRange !== 0) {
$allowedUserAgents = array_map('trim', explode(',', $Config['user_agent'] ?? ''));
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (!in_array($userAgent, $allowedUserAgents) && (($userAgentRange !== 2 && $live) ||
($userAgentRange !== 1 && !$live))) {
http_response_code(403);
echo '访问被拒绝:无效的 User-Agent。';
exit;
}
}
// 禁止输出错误提示
error_reporting(0);
// 初始化响应头信息
$init = [
'status' => 200,
'headers' => [
'content-type' => 'application/json'
]
];
// 生成响应
function makeRes($body, $status = 200, $headers = []) {
$headers['Access-Control-Allow-Origin'] = '*';
http_response_code($status);
foreach ($headers as $key => $value) {
header("$key: $value");
}
echo $body;
}
// 获取当前日期
function getNowDate() {
return date('Y-m-d');
}
// 格式化时间
function getFormatTime($time) {
if (strlen($time) < 8) {
return ['date' => getNowDate(), 'time' => ''];
}
$date = substr($time, 0, 4) . '-' . substr($time, 4, 2) . '-' . substr($time, 6, 2);
$time = strlen($time) >= 12 ? substr($time, 8, 2) . ':' . substr($time, 10, 2) : '';
return ['date' => $date, 'time' => $time];
}
// 检查 Memcached 状态
$memcached_enabled = class_exists('Memcached') && ($memcached = new Memcached())->addServer('127.0.0.1', 11211);
// 奇怪了,FreeBSD 上我的 Memcached 监听不了 localhost,127.0.0.1就可以。
// 检查 Redis 状态
$redis_enabled = class_exists('Redis') && ($redis = new Redis())->connect('127.0.0.1', 6379);
if ($redis_enabled && isset($Config['redis_password'])) {
$redis->auth($Config['redis_password']);
}
// 从数据库读取 diyp、lovetv 数据,兼容未安装 memcached 的情况
function readEPGData($date, $oriChannelName, $cleanChannelName, $db, $type) {
// 默认缓存 24 小时,更新数据时清空
// 按需更改,如 12 小时就是 12 * 3600
$cache_time = 24 * 3600;
// 检查 Memcached 和 Redis 状态
global $memcached_enabled, $redis_enabled, $memcached, $redis;
$cache_key = base64_encode("{$date}_{$cleanChannelName}_{$type}");
if ($memcached_enabled) {
// 从 Memcached 缓存中读取数据
$cached_data = $memcached->get($cache_key);
if ($cached_data) {
return $cached_data;
}
}
if ($redis_enabled) {
// 从 Redis 缓存中读取数据
$cached_data = $redis->get($cache_key);
if ($cached_data) {
return $cached_data;
}
}
// 获取数据库类型(mysql 或 sqlite)
$concat = $db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql'
? "CONCAT('%', channel, '%')"
: "'%' || channel || '%'";
// 优先精准匹配,其次正向模糊匹配,最后反向模糊匹配
$stmt = $db->prepare("
SELECT epg_diyp
FROM epg_data
WHERE (
(channel = :channel
OR channel LIKE :like_channel
OR :channel LIKE $concat)
AND date = :date
)
ORDER BY
CASE
WHEN channel = :channel THEN 1
WHEN channel LIKE :like_channel THEN 2
ELSE 3
END,
CASE
WHEN channel = :channel THEN NULL
WHEN channel LIKE :like_channel THEN LENGTH(channel)
ELSE -LENGTH(channel)
END
LIMIT 1
");
$stmt->execute([
':date' => $date,
':channel' => $cleanChannelName,
':like_channel' => $cleanChannelName . '%'
]);
$row = $stmt->fetchColumn();
if (!$row) {
return false;
}
// 在解码和添加 icon 后再编码为 JSON
$rowArray = json_decode($row, true);
$iconUrl = iconUrlMatch($rowArray['channel_name']) ?? iconUrlMatch($cleanChannelName) ?? iconUrlMatch($oriChannelName);
$rowArray = array_merge(
array_slice($rowArray, 0, array_search('source', array_keys($rowArray)) + 1),
['icon' => $iconUrl],
array_slice($rowArray, array_search('source', array_keys($rowArray)) + 1)
);
$row = json_encode($rowArray, JSON_UNESCAPED_UNICODE);
if ($type === 'diyp') {
// 如果 Memcached 或 Redis 可用,将结果存储到缓存中
if ($memcached_enabled) {
$memcached->set($cache_key, $row, $cache_time);
}
if ($redis_enabled) {
$redis->set($cache_key, $row, $cache_time);
}
return $row;
}
if ($type === 'lovetv') {
$diyp_data = $rowArray;
$date = $diyp_data['date'];
$program = array_map(function($epg) use ($date) {
$start_time = strtotime($date . ' ' . $epg['start']);
$end_time = strtotime($date . ' ' . $epg['end']);
$duration = $end_time - $start_time;
return [
'st' => $start_time,
'et' => $end_time,
'eventType' => '',
'eventId' => '',
't' => $epg['title'],
'showTime' => gmdate('H:i', $duration),
'duration' => $duration
];
}, $diyp_data['epg_data']);
// 查找当前节目
$current_programme = $date === date('Y-m-d') ? findCurrentProgramme($program) : null;
// 生成 lovetv 数据
$lovetv_data = [
$oriChannelName => [
'isLive' => $current_programme ? $current_programme['t'] : '',
'liveSt' => $current_programme ? $current_programme['st'] : 0,
'channelName' => $diyp_data['channel_name'],
'lvUrl' => $diyp_data['url'],
'srcUrl' => $diyp_data['source'],
'icon' => $diyp_data['icon'],
'program' => $program
]
];
$response = json_encode($lovetv_data, JSON_UNESCAPED_UNICODE);
// 如果 Memcached 或 Redis 可用,将结果存储到缓存中
if ($memcached_enabled) {
$memcached->set($cache_key, $response, $cache_time);
}
if ($redis_enabled) {
$redis->set($cache_key, $response, $cache_time);
}
return $response;
}
return false;
}
// 查找当前节目
function findCurrentProgramme($programmes) {
$now = time();
foreach ($programmes as $programme) {
if ($programme['st'] <= $now && $programme['et'] >= $now) {
return $programme;
}
}
return null;
}
// 处理直播源请求
function liveFetchHandler($query_params) {
global $Config, $liveDir, $serverUrl, $liveFileDir;
header('Content-Type: text/plain');
// 计算文件路径
$isValidFile = false;
if (!empty($query_params['url'])) {
$url = $query_params['url'];
$filePath = sprintf('%s/%s.%s', $liveFileDir, md5(urlencode($url)), $query_params['live']);
if (($query_params['latest'] === '1' && doParseSourceInfo($url)) === true ||
file_exists($filePath) || doParseSourceInfo($url) === true) { // 判断是否需要获取最新文件
$isValidFile = true;
}
} else {
$filePath = $liveDir . ($query_params['live'] === 'txt' ? 'tv.txt' : ($query_params['live'] === 'm3u' ? 'tv.m3u' : ''));
$isValidFile = file_exists($filePath);
}
// 如果文件存在或成功解析了源数据
if ($isValidFile) {
$content = file_get_contents($filePath);
} else {
echo "文件不存在或无效的 live 类型";
exit;
}
// 处理 TVG URL 替换
$tvgUrl = $serverUrl . ($query_params['live'] === 'm3u' ? '/t.xml.gz' : '/');
if ($query_params['live'] === 'm3u') {
$content = preg_replace('/(#EXTM3U x-tvg-url=")(.*?)(")/', '$1' . $tvgUrl . '$3', $content, 1);
}
echo $content;
exit;
}
// 处理请求
function fetchHandler($query_params) {
global $init, $db, $Config;
// 处理直播源请求
if (isset($query_params['live'])) {
liveFetchHandler($query_params);
}
// 获取并清理频道名称,繁体转换成简体
$oriChannelName = $query_params['ch'] ?? $query_params['channel'] ?? '';
$cleanChannelName = cleanChannelName($oriChannelName, $t2s = true);
$date = isset($query_params['date']) ? getFormatTime(preg_replace('/\D+/', '', $query_params['date']))['date'] : getNowDate();
// 频道参数为空时,直接返回 t.xml 文件数据
if (empty($cleanChannelName)) {
if ($Config['gen_xml'] === 1) {
header('Content-Type: application/xml');
header('Content-Disposition: attachment; filename="t.xml"');
readfile('./t.xml');
} else {
// 输出消息并设置404状态码
echo "404 Not Found. <br>未生成 xmltv 文件";
http_response_code(404);
}
exit;
}
// 返回 diyp、lovetv 数据
if (isset($query_params['ch']) || isset($query_params['channel'])) {
$type = isset($query_params['ch']) ? 'diyp' : 'lovetv';
$response = readEPGData($date, $oriChannelName, $cleanChannelName, $db, $type);
if ($response) {
makeRes($response, $init['status'], $init['headers']);
exit;
}
// 无法获取到数据时返回默认数据
$ret_default = !isset($Config['ret_default']) || $Config['ret_default'];
$iconUrl = iconUrlMatch($cleanChannelName) ?? iconUrlMatch($oriChannelName);
if ($type === 'diyp') {
// 返回默认 diyp 数据
$default_diyp_program_info = [
'channel_name' => $cleanChannelName,
'date' => $date,
'url' => "https://github.com/mxdabc/epgphp",
'icon' => $iconUrl,
'epg_data' => !$ret_default ? '' : array_map(function($hour) {
return [
'start' => sprintf('%02d:00', $hour),
'end' => sprintf('%02d:00', ($hour + 1) % 24),
'title' => '此频道暂无预告或者您配置有误。',
'desc' => ''
];
}, range(0, 23, 1))
];
$response = json_encode($default_diyp_program_info, JSON_UNESCAPED_UNICODE);
} else {
// 返回默认 lovetv 数据
$default_lovetv_program_info = [
$cleanChannelName => [
'isLive' => '',
'liveSt' => 0,
'channelName' => $cleanChannelName,
'lvUrl' => 'https://github.com/mxdabc/epgphp',
'icon' => $iconUrl,
'program' => !$ret_default ? '' : array_map(function($hour) {
return [
'st' => strtotime(sprintf('%02d:00', $hour)),
'et' => strtotime(sprintf('%02d:00', ($hour + 1) % 24)),
't' => '此频道暂无预告或者您配置有误。',
'd' => ''
];
}, range(0, 23, 1))
]
];
$response = json_encode($default_lovetv_program_info, JSON_UNESCAPED_UNICODE);
}
makeRes($response, $init['status'], $init['headers']);
}
}
// 执行请求处理
fetchHandler($query_params);
?>