samlax12 commited on
Commit
49fc5e8
·
verified ·
1 Parent(s): 438ef36

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +194 -127
server.js CHANGED
@@ -1,128 +1,195 @@
1
- const express = require('express');
2
- const { createProxyMiddleware } = require('http-proxy-middleware');
3
- const path = require('path');
4
- const crypto = require('crypto');
5
- const fs = require('fs');
6
-
7
- const app = express();
8
- const PORT = process.env.PORT || 8080;
9
-
10
- // 中间件:解析请求体
11
- app.use(express.json());
12
- app.use(express.urlencoded({ extended: true }));
13
-
14
- // 中间件:处理HTML文件中的环境变量注入
15
- app.use((req, res, next) => {
16
- if (req.path.endsWith('.html') || req.path === '/' || req.path.endsWith('/')) {
17
- const filePath = req.path === '/' || req.path.endsWith('/')
18
- ? path.join(__dirname, 'index.html')
19
- : path.join(__dirname, req.path);
20
-
21
- if (fs.existsSync(filePath)) {
22
- let content = fs.readFileSync(filePath, 'utf8');
23
-
24
- // 替换密码占位符
25
- const password = process.env.PASSWORD || '';
26
- let passwordHash = '';
27
-
28
- if (password) {
29
- const hash = crypto.createHash('sha256');
30
- hash.update(password);
31
- passwordHash = hash.digest('hex');
32
- }
33
-
34
- content = content.replace(
35
- 'window.__ENV__.PASSWORD = "{{PASSWORD}}";',
36
- `window.__ENV__.PASSWORD = "${passwordHash}"; // SHA-256 hash`
37
- );
38
-
39
- res.setHeader('Content-Type', 'text/html');
40
- return res.send(content);
41
- }
42
- }
43
- next();
44
- });
45
-
46
- // 创建代理中间件函数
47
- function createDynamicProxy(req, res, next) {
48
- // 从URL参数获取目标URL
49
- const targetUrl = decodeURIComponent(req.params.url);
50
-
51
- if (!targetUrl || !targetUrl.match(/^https?:\/\/.+/i)) {
52
- return res.status(400).json({
53
- success: false,
54
- error: '无效的目标URL'
55
- });
56
- }
57
-
58
- // 提取主机和协议
59
- try {
60
- const urlObj = new URL(targetUrl);
61
- const target = `${urlObj.protocol}//${urlObj.host}`;
62
- const pathToProxy = urlObj.pathname + urlObj.search;
63
-
64
- // 创建代理
65
- const proxy = createProxyMiddleware({
66
- target,
67
- changeOrigin: true,
68
- pathRewrite: () => pathToProxy,
69
- onProxyReq: (proxyReq, req, res) => {
70
- // 设置请求头
71
- proxyReq.setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36');
72
- proxyReq.setHeader('Accept', req.headers.accept || '*/*');
73
- proxyReq.setHeader('Accept-Language', req.headers['accept-language'] || 'zh-CN,zh;q=0.9');
74
- proxyReq.setHeader('Referer', req.headers.referer || target);
75
- },
76
- onProxyRes: (proxyRes, req, res) => {
77
- // 设置CORS头
78
- proxyRes.headers['Access-Control-Allow-Origin'] = '*';
79
- proxyRes.headers['Access-Control-Allow-Methods'] = 'GET, HEAD, OPTIONS';
80
- proxyRes.headers['Access-Control-Allow-Headers'] = '*';
81
-
82
- // 设置缓存策略
83
- proxyRes.headers['Cache-Control'] = 'public, max-age=86400';
84
-
85
- // 移除可能导致问题的头部
86
- delete proxyRes.headers['content-length'];
87
- delete proxyRes.headers['content-encoding'];
88
- }
89
- });
90
-
91
- proxy(req, res, next);
92
- } catch (error) {
93
- console.error(`代理错误: ${error.message}`);
94
- return res.status(500).json({
95
- success: false,
96
- error: `代理请求失败: ${error.message}`
97
- });
98
- }
99
- }
100
-
101
- // 设置代理路由
102
- app.use('/proxy/:url(*)', createDynamicProxy);
103
-
104
- // OPTIONS请求处理
105
- app.options('/proxy/:url(*)', (req, res) => {
106
- res.setHeader('Access-Control-Allow-Origin', '*');
107
- res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
108
- res.setHeader('Access-Control-Allow-Headers', '*');
109
- res.setHeader('Access-Control-Max-Age', '86400');
110
- res.status(204).end();
111
- });
112
-
113
- // 静态文件服务 - 所有其他请求
114
- app.use(express.static(path.join(__dirname), {
115
- maxAge: '1d'
116
- }));
117
-
118
- // 错误处理中间件
119
- app.use((err, req, res, next) => {
120
- console.error(`服务器错误: ${err.stack}`);
121
- res.status(500).send('服务器内部错误');
122
- });
123
-
124
- // 启动服务器
125
- app.listen(PORT, () => {
126
- console.log(`LibreTV 服务器已启动,运行在 http://localhost:${PORT}`);
127
- console.log(`代理服务可通过 http://localhost:${PORT}/proxy/{URL} 访问`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  });
 
1
+ const express = require('express');
2
+ const http = require('http');
3
+ const https = require('https');
4
+ const zlib = require('zlib');
5
+ const path = require('path');
6
+ const crypto = require('crypto');
7
+ const fs = require('fs');
8
+
9
+ const app = express();
10
+ const PORT = process.env.PORT || 8080;
11
+
12
+ // 中间件:解析请求体
13
+ app.use(express.json());
14
+ app.use(express.urlencoded({ extended: true }));
15
+
16
+ // 中间件:处理HTML文件中的环境变量注入
17
+ app.use((req, res, next) => {
18
+ if (req.path.endsWith('.html') || req.path === '/' || req.path.endsWith('/')) {
19
+ const filePath = req.path === '/' || req.path.endsWith('/')
20
+ ? path.join(__dirname, 'index.html')
21
+ : path.join(__dirname, req.path);
22
+
23
+ if (fs.existsSync(filePath)) {
24
+ let content = fs.readFileSync(filePath, 'utf8');
25
+
26
+ // 替换密码占位符
27
+ const password = process.env.PASSWORD || '';
28
+ let passwordHash = '';
29
+
30
+ if (password) {
31
+ const hash = crypto.createHash('sha256');
32
+ hash.update(password);
33
+ passwordHash = hash.digest('hex');
34
+ }
35
+
36
+ content = content.replace(
37
+ 'window.__ENV__.PASSWORD = "{{PASSWORD}}";',
38
+ `window.__ENV__.PASSWORD = "${passwordHash}"; // SHA-256 hash`
39
+ );
40
+
41
+ res.setHeader('Content-Type', 'text/html');
42
+ return res.send(content);
43
+ }
44
+ }
45
+ next();
46
+ });
47
+
48
+ // 代理处理函数
49
+ app.use('/proxy/:url(*)', async (req, res) => {
50
+ try {
51
+ // 获取目标 URL
52
+ const targetUrl = decodeURIComponent(req.params.url);
53
+ if (!targetUrl || !targetUrl.match(/^https?:\/\/.+/i)) {
54
+ return res.status(400).json({ error: '无效的目标URL' });
55
+ }
56
+
57
+ console.log(`处理代理请求: ${targetUrl}`);
58
+
59
+ // 选择合适的协议
60
+ const protocol = targetUrl.startsWith('https') ? https : http;
61
+
62
+ // 设置请求选项
63
+ const options = {
64
+ headers: {
65
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
66
+ 'Accept': req.headers.accept || '*/*',
67
+ 'Accept-Language': req.headers['accept-language'] || 'zh-CN,zh;q=0.9',
68
+ 'Accept-Encoding': 'gzip, deflate',
69
+ 'Referer': req.headers.referer || new URL(targetUrl).origin
70
+ },
71
+ timeout: 10000 // 10秒超时
72
+ };
73
+
74
+ // 创建请求
75
+ const proxyReq = protocol.get(targetUrl, options, (proxyRes) => {
76
+ // 设置CORS头
77
+ res.setHeader('Access-Control-Allow-Origin', '*');
78
+ res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
79
+ res.setHeader('Access-Control-Allow-Headers', '*');
80
+
81
+ // 设置缓存头
82
+ res.setHeader('Cache-Control', 'public, max-age=86400');
83
+
84
+ // 如果是JSON内容,确保设置正确的编码
85
+ if (proxyRes.headers['content-type'] &&
86
+ proxyRes.headers['content-type'].includes('application/json')) {
87
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
88
+ } else {
89
+ // 复制其他响应头
90
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
91
+ if (!['content-length', 'transfer-encoding', 'content-encoding'].includes(key.toLowerCase())) {
92
+ res.setHeader(key, value);
93
+ }
94
+ }
95
+ }
96
+
97
+ // 设置状态码
98
+ res.status(proxyRes.statusCode);
99
+
100
+ // 处理内容编码
101
+ let responseStream = proxyRes;
102
+ const contentEncoding = proxyRes.headers['content-encoding']?.toLowerCase();
103
+
104
+ if (contentEncoding === 'gzip') {
105
+ responseStream = proxyRes.pipe(zlib.createGunzip());
106
+ } else if (contentEncoding === 'deflate') {
107
+ responseStream = proxyRes.pipe(zlib.createInflate());
108
+ }
109
+
110
+ // 收集响应数据
111
+ const chunks = [];
112
+ responseStream.on('data', (chunk) => chunks.push(chunk));
113
+
114
+ responseStream.on('end', () => {
115
+ try {
116
+ const buffer = Buffer.concat(chunks);
117
+
118
+ // 检查是否是M3U8内容
119
+ const isM3u8 = (proxyRes.headers['content-type'] &&
120
+ (proxyRes.headers['content-type'].includes('application/vnd.apple.mpegurl') ||
121
+ proxyRes.headers['content-type'].includes('application/x-mpegurl'))) ||
122
+ buffer.toString('utf8', 0, 7) === '#EXTM3U';
123
+
124
+ if (isM3u8) {
125
+ // 针对M3U8内容的特殊处理
126
+ const content = buffer.toString('utf8');
127
+ // 这里可以添加M3U8处理逻辑,如果需要的话
128
+ res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
129
+ res.end(content);
130
+ return;
131
+ }
132
+
133
+ // 二进制内容,如视频片段,直接传递
134
+ if (proxyRes.headers['content-type'] &&
135
+ (proxyRes.headers['content-type'].includes('video/') ||
136
+ proxyRes.headers['content-type'].includes('audio/') ||
137
+ proxyRes.headers['content-type'].includes('application/octet-stream'))) {
138
+ res.end(buffer);
139
+ return;
140
+ }
141
+
142
+ // 文本内容(包括JSON)
143
+ const body = buffer.toString('utf8');
144
+ res.end(body);
145
+ } catch (error) {
146
+ console.error('处理响应数据错误:', error);
147
+ res.status(502).json({ error: '处理API响应失败' });
148
+ }
149
+ });
150
+ });
151
+
152
+ // 处理请求错误
153
+ proxyReq.on('error', (err) => {
154
+ console.error(`代理请求错误: ${err.message} (${targetUrl})`);
155
+ res.status(502).json({ error: `代理请求失败: ${err.message}` });
156
+ });
157
+
158
+ // 处���超时
159
+ proxyReq.on('timeout', () => {
160
+ proxyReq.destroy();
161
+ console.error(`代理请求超时: ${targetUrl}`);
162
+ res.status(504).json({ error: '代理请求超时' });
163
+ });
164
+
165
+ } catch (error) {
166
+ console.error(`代理处理错误: ${error.message}`);
167
+ res.status(500).json({ error: `代理服务器错误: ${error.message}` });
168
+ }
169
+ });
170
+
171
+ // 处理OPTIONS请求
172
+ app.options('/proxy/:url(*)', (req, res) => {
173
+ res.setHeader('Access-Control-Allow-Origin', '*');
174
+ res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
175
+ res.setHeader('Access-Control-Allow-Headers', '*');
176
+ res.setHeader('Access-Control-Max-Age', '86400');
177
+ res.status(204).end();
178
+ });
179
+
180
+ // 静态文件服务 - 所有其他请求
181
+ app.use(express.static(path.join(__dirname), {
182
+ maxAge: '1d'
183
+ }));
184
+
185
+ // 错误处理中间件
186
+ app.use((err, req, res, next) => {
187
+ console.error(`服务器错误: ${err.stack}`);
188
+ res.status(500).send('服务器内部错误');
189
+ });
190
+
191
+ // 启动服务器
192
+ app.listen(PORT, () => {
193
+ console.log(`LibreTV 服务器已启动,运行在 http://localhost:${PORT}`);
194
+ console.log(`代理服务可通过 http://localhost:${PORT}/proxy/{URL} 访问`);
195
  });