nbugs commited on
Commit
3f31b27
·
verified ·
1 Parent(s): 853a7b8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -52
app.py CHANGED
@@ -1,11 +1,14 @@
1
  import os
2
- import requests
3
  import urllib.parse
 
 
 
 
4
  from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, Response
5
  from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
6
  from werkzeug.security import check_password_hash, generate_password_hash
7
  from dotenv import load_dotenv
8
- import time
9
 
10
  # 加载环境变量
11
  load_dotenv()
@@ -38,24 +41,155 @@ users = {
38
  )
39
  }
40
 
41
- # 检查 subconverter 是否启动
42
- def check_subconverter():
43
- max_retries = 10
44
- for i in range(max_retries):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  try:
46
- response = requests.get('http://localhost:25500/version', timeout=2)
47
- if response.status_code == 200:
48
- print(f"Subconverter 服务已启动: {response.text}")
49
- return True
50
- except:
51
- pass
52
- print(f"等待 Subconverter 服务启动... ({i+1}/{max_retries})")
53
- time.sleep(2)
54
- print("警告: Subconverter 服务可能未正常启动")
55
- return False
56
-
57
- # 应用启动时检查 subconverter 服务
58
- check_subconverter()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  @login_manager.user_loader
61
  def load_user(user_id):
@@ -85,7 +219,16 @@ def logout():
85
  @app.route('/')
86
  @login_required
87
  def index():
88
- return render_template('index.html')
 
 
 
 
 
 
 
 
 
89
 
90
  @app.route('/convert', methods=['POST'])
91
  @login_required
@@ -99,23 +242,13 @@ def convert():
99
  return jsonify({"status": "error", "message": "订阅链接不能为空"})
100
 
101
  try:
102
- # 检查subconverter服务是否可用
103
- try:
104
- version_check = requests.get('http://localhost:25500/version', timeout=2)
105
- if version_check.status_code != 200:
106
- return jsonify({"status": "error", "message": "订阅转换服务暂时不可用,请稍后再试"})
107
- except:
108
- return jsonify({"status": "error", "message": "订阅转换服务暂时不可用,请稍后再试"})
109
-
110
- # 构建转换请求参数
111
  params = {
112
  'target': target,
113
  'url': original_url,
114
- 'insert': 'false',
115
  'config': backend_url,
116
  'emoji': 'true',
117
  'list': 'false',
118
- 'xudp': 'false',
119
  'udp': 'false',
120
  'tfo': 'false',
121
  'expand': 'true',
@@ -124,37 +257,83 @@ def convert():
124
  'new_name': 'true'
125
  }
126
 
127
- # 通过URL参数构建转换链接
128
- base_url = request.host_url.rstrip('/')
129
- convert_url = f"{base_url}/api/sub?{urllib.parse.urlencode(params)}"
130
 
131
- return jsonify({"status": "success", "result": convert_url})
 
 
 
 
132
 
133
  except Exception as e:
134
  return jsonify({"status": "error", "message": f"处理失败: {str(e)}"})
135
 
136
  @app.route('/api/sub')
137
- def subscribe_api():
138
- """代理 subconverter 的转换请求"""
139
  try:
140
- # 获取所有URL参数
141
  params = request.args.to_dict()
142
 
143
- # 调用本地 subconverter 服务
144
- sub_url = f"http://localhost:25500/sub?{urllib.parse.urlencode(params)}"
145
- response = requests.get(sub_url, timeout=30)
146
-
147
- # 返回原始响应
148
- return Response(
149
- response.content,
150
- status=response.status_code,
151
- content_type=response.headers.get('Content-Type', 'text/plain')
152
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  except Exception as e:
154
- return f"转换失败: {str(e)}", 500
155
 
156
- # 不要在这里调用 app.run()
157
- # 让 Hugging Face 的 WSGI 服务器来运行应用
158
- # 仅在本地开发时使用以下代码
159
  if __name__ == '__main__' and os.environ.get('DEVELOPMENT') == 'true':
160
  app.run(host='0.0.0.0', port=7860, debug=True)
 
1
  import os
2
+ import time
3
  import urllib.parse
4
+ import requests
5
+ import random
6
+ import hashlib
7
+ import json
8
  from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, Response
9
  from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
10
  from werkzeug.security import check_password_hash, generate_password_hash
11
  from dotenv import load_dotenv
 
12
 
13
  # 加载环境变量
14
  load_dotenv()
 
41
  )
42
  }
43
 
44
+ # 代理和缓存系统
45
+ class ProxySystem:
46
+ def __init__(self):
47
+ # 可用的API列表
48
+ self.apis = [
49
+ "https://api.v1.mk/sub",
50
+ "https://pub-api-1.bianyuan.xyz/sub"
51
+ ]
52
+
53
+ # 简单缓存系统
54
+ self._cache_file = "cache.json"
55
+ self._cache = {}
56
+ self._cache_time = {}
57
+ self._cache_ttl = 3600 # 1小时缓存
58
+ self._load_cache()
59
+
60
+ # 统计信息
61
+ self.stats = {
62
+ "cache_hits": 0,
63
+ "cache_misses": 0,
64
+ "api_successes": 0,
65
+ "api_failures": 0
66
+ }
67
+
68
+ def _load_cache(self):
69
+ """从磁盘加载缓存"""
70
+ try:
71
+ if os.path.exists(self._cache_file):
72
+ with open(self._cache_file, 'r') as f:
73
+ cache_data = json.load(f)
74
+ self._cache = cache_data.get('cache', {})
75
+ self._cache_time = cache_data.get('cache_time', {})
76
+ print(f"已加载 {len(self._cache)} 条缓存数据")
77
+ except Exception as e:
78
+ print(f"加载缓存失败: {e}")
79
+ self._cache = {}
80
+ self._cache_time = {}
81
+
82
+ def _save_cache(self):
83
+ """保存缓存到磁盘"""
84
  try:
85
+ # 限制缓存大小,最多保存100条
86
+ if len(self._cache) > 100:
87
+ # 移除最旧的缓存
88
+ sorted_keys = sorted(self._cache_time.items(), key=lambda x: x[1])
89
+ keys_to_remove = [k for k, v in sorted_keys[:len(self._cache) - 100]]
90
+ for key in keys_to_remove:
91
+ self._cache.pop(key, None)
92
+ self._cache_time.pop(key, None)
93
+
94
+ cache_data = {
95
+ 'cache': self._cache,
96
+ 'cache_time': self._cache_time
97
+ }
98
+
99
+ with open(self._cache_file, 'w') as f:
100
+ json.dump(cache_data, f)
101
+ except Exception as e:
102
+ print(f"保存缓存失败: {e}")
103
+
104
+ def get_random_api(self):
105
+ """获取随机API"""
106
+ return random.choice(self.apis)
107
+
108
+ def get_cache_key(self, params):
109
+ """生成缓存键"""
110
+ # 排序参数以保持一致性
111
+ sorted_params = sorted(params.items())
112
+ param_str = urllib.parse.urlencode(sorted_params)
113
+ return hashlib.md5(param_str.encode()).hexdigest()
114
+
115
+ def get_from_cache(self, params):
116
+ """从缓存获取结果"""
117
+ key = self.get_cache_key(params)
118
+ current_time = time.time()
119
+
120
+ if key in self._cache and current_time - self._cache_time.get(key, 0) < self._cache_ttl:
121
+ self.stats["cache_hits"] += 1
122
+ return self._cache.get(key)
123
+
124
+ self.stats["cache_misses"] += 1
125
+ return None
126
+
127
+ def save_to_cache(self, params, data):
128
+ """保存到缓存"""
129
+ key = self.get_cache_key(params)
130
+ self._cache[key] = data
131
+ self._cache_time[key] = time.time()
132
+
133
+ # 定期保存缓存
134
+ if self.stats["cache_misses"] % 5 == 0: # 每5次未命中缓存时保存
135
+ self._save_cache()
136
+
137
+ def convert(self, params):
138
+ """执行转换,优先使用缓存"""
139
+ # 尝试从缓存获取
140
+ cache_result = self.get_from_cache(params)
141
+ if cache_result:
142
+ return cache_result
143
+
144
+ # 缓存未命中,请求API
145
+ errors = []
146
+
147
+ # 随机API顺序
148
+ apis = list(self.apis)
149
+ random.shuffle(apis)
150
+
151
+ for api_url in apis:
152
+ try:
153
+ # 发送请求
154
+ response = requests.get(api_url, params=params, timeout=30)
155
+
156
+ if response.status_code == 200:
157
+ # 获取内容和类型
158
+ content = response.content
159
+ content_type = response.headers.get('Content-Type', 'text/plain')
160
+
161
+ # 保存到缓存
162
+ cache_data = {
163
+ 'content': content.decode('utf-8', errors='ignore'), # 保存为字符串
164
+ 'content_type': content_type
165
+ }
166
+ self.save_to_cache(params, cache_data)
167
+
168
+ self.stats["api_successes"] += 1
169
+ return content, content_type
170
+ else:
171
+ errors.append(f"API {api_url} 返回状态码: {response.status_code}")
172
+ except Exception as e:
173
+ errors.append(f"API {api_url} 请求失败: {str(e)}")
174
+ self.stats["api_failures"] += 1
175
+
176
+ # 所有API都失败
177
+ error_message = "所有转换API均不可用:\n" + "\n".join(errors)
178
+ raise Exception(error_message)
179
+
180
+ def get_stats(self):
181
+ """获取统计信息"""
182
+ return {
183
+ "cache_size": len(self._cache),
184
+ "cache_hits": self.stats["cache_hits"],
185
+ "cache_misses": self.stats["cache_misses"],
186
+ "api_successes": self.stats["api_successes"],
187
+ "api_failures": self.stats["api_failures"],
188
+ "cache_hit_ratio": self.stats["cache_hits"] / (self.stats["cache_hits"] + self.stats["cache_misses"]) if (self.stats["cache_hits"] + self.stats["cache_misses"]) > 0 else 0
189
+ }
190
+
191
+ # 初始化代理系统
192
+ proxy_system = ProxySystem()
193
 
194
  @login_manager.user_loader
195
  def load_user(user_id):
 
219
  @app.route('/')
220
  @login_required
221
  def index():
222
+ # 获取缓存统计信息
223
+ try:
224
+ stats = proxy_system.get_stats()
225
+ cache_hit_ratio = f"{stats['cache_hit_ratio']*100:.1f}%" if stats['cache_hit_ratio'] > 0 else "0%"
226
+ except:
227
+ cache_hit_ratio = "计算中"
228
+
229
+ return render_template('index.html',
230
+ conversion_mode="隐私代理+本地缓存",
231
+ cache_hit_ratio=cache_hit_ratio)
232
 
233
  @app.route('/convert', methods=['POST'])
234
  @login_required
 
242
  return jsonify({"status": "error", "message": "订阅链接不能为空"})
243
 
244
  try:
245
+ # 构建参数
 
 
 
 
 
 
 
 
246
  params = {
247
  'target': target,
248
  'url': original_url,
 
249
  'config': backend_url,
250
  'emoji': 'true',
251
  'list': 'false',
 
252
  'udp': 'false',
253
  'tfo': 'false',
254
  'expand': 'true',
 
257
  'new_name': 'true'
258
  }
259
 
260
+ # 构建转换URL (隐私代理URL)
261
+ server_url = request.url_root.rstrip('/')
262
+ proxy_url = f"{server_url}/api/sub?{urllib.parse.urlencode(params)}"
263
 
264
+ return jsonify({
265
+ "status": "success",
266
+ "result": proxy_url,
267
+ "mode": "proxy"
268
+ })
269
 
270
  except Exception as e:
271
  return jsonify({"status": "error", "message": f"处理失败: {str(e)}"})
272
 
273
  @app.route('/api/sub')
274
+ def api_sub_proxy():
275
+ """智能代理API - 带缓存"""
276
  try:
277
+ # 获取所有请求参数
278
  params = request.args.to_dict()
279
 
280
+ # 使用代理系统处理请求
281
+ try:
282
+ # 从缓存中获取
283
+ cache_result = proxy_system.get_from_cache(params)
284
+ if cache_result:
285
+ # 返回缓存内容
286
+ content = cache_result.get('content', '').encode('utf-8')
287
+ content_type = cache_result.get('content_type', 'text/plain')
288
+
289
+ headers = {
290
+ 'Content-Type': content_type,
291
+ 'X-Proxy-By': 'Your Private Space',
292
+ 'X-Cache': 'HIT',
293
+ 'X-Content-Type-Options': 'nosniff',
294
+ 'Cache-Control': 'max-age=3600'
295
+ }
296
+
297
+ return Response(content, headers=headers)
298
+
299
+ # 缓存未命中,使用API
300
+ content, content_type = proxy_system.convert(params)
301
+
302
+ # 构建响应
303
+ headers = {
304
+ 'Content-Type': content_type,
305
+ 'X-Proxy-By': 'Your Private Space',
306
+ 'X-Cache': 'MISS',
307
+ 'X-Content-Type-Options': 'nosniff',
308
+ 'Cache-Control': 'max-age=3600'
309
+ }
310
+
311
+ return Response(content, headers=headers)
312
+ except Exception as e:
313
+ return f"转换失败: {str(e)}", 500
314
+
315
+ except Exception as e:
316
+ return f"请求处理失败: {str(e)}", 500
317
+
318
+ # 添加统计接口
319
+ @app.route('/stats')
320
+ @login_required
321
+ def show_stats():
322
+ stats = proxy_system.get_stats()
323
+ return jsonify(stats)
324
+
325
+ # 添加清除缓存接口
326
+ @app.route('/clear-cache', methods=['POST'])
327
+ @login_required
328
+ def clear_cache():
329
+ try:
330
+ proxy_system._cache = {}
331
+ proxy_system._cache_time = {}
332
+ proxy_system._save_cache()
333
+ return jsonify({"status": "success", "message": "缓存已清除"})
334
  except Exception as e:
335
+ return jsonify({"status": "error", "message": f"清除缓存失败: {str(e)}"})
336
 
337
+ # 仅在本地开发时使用
 
 
338
  if __name__ == '__main__' and os.environ.get('DEVELOPMENT') == 'true':
339
  app.run(host='0.0.0.0', port=7860, debug=True)