deeme commited on
Commit
bab9c3b
·
verified ·
1 Parent(s): e3e5333

Upload 26 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . /app
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ EXPOSE 5100
10
+
11
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,10 +1,11 @@
1
  ---
2
- title: Cs
3
- emoji: 🐢
4
- colorFrom: green
5
- colorTo: gray
6
  sdk: docker
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: cs
3
+ emoji: 👀
4
+ colorFrom: purple
5
+ colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
+ license: gpl-3.0
9
+ app_port: 5100
10
  ---
11
 
 
app.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+ import utils.configs as configs
3
+
4
+ app = Flask(__name__)
5
+ app.secret_key = configs.authorization # 用于加密 session
6
+
7
+ from gateway.index import *
8
+ from gateway.geteway import *
9
+ from gateway.chatgpt import *
10
+ from gateway.login import *
11
+ from gateway.timing import *
12
+ from gateway.user import *
13
+ from gateway.admin import *
14
+ from gateway.claude import *
15
+
16
+ # 启动 Flask 应用
17
+ if __name__ == '__main__':
18
+ app.run(host='0.0.0.0', port=5100)
gateway/__init__.py ADDED
File without changes
gateway/admin.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import app
2
+ from flask import render_template, request, jsonify
3
+ import utils.globals as globals
4
+ from utils.globals import *
5
+ from utils.tools import *
6
+
7
+
8
+
9
+
10
+
11
+
12
+ # GPT 主页路由
13
+ @app.route('/chatgpt', methods=['GET', 'POST'])
14
+ @admin_required
15
+ def chatgpt():
16
+
17
+ if request.method == 'GET':
18
+ # 加载并显示 chatToken.json 文件中的内容
19
+ return render_template('GPT.html', retokens=globals.chatToken)
20
+
21
+ if request.method == 'POST':
22
+ # 获取更新后的 retoken 数据
23
+ globals.chatToken = request.json.get('retokens')
24
+
25
+ # 如果数据格式有效,保存到文件
26
+ if globals.chatToken:
27
+ save_retoken(globals.chatToken)
28
+ return jsonify({"status": "success", "message": "chatToken.json 已更新!"}), 200
29
+ else:
30
+ return jsonify({"status": "error", "message": "无效的数据格式!"}), 400
31
+
32
+ # Claude 主页路由
33
+ @app.route('/claude', methods=['GET', 'POST'])
34
+ @admin_required
35
+ def claude():
36
+
37
+ if request.method == 'GET':
38
+ # 加载并显示 chatToken.json 文件中的内容
39
+ return render_template('claude.html', retokens=globals.cluadeToken)
40
+
41
+ if request.method == 'POST':
42
+ # 获取更新后的 cltoken 数据
43
+ globals.cluadeToken = request.json.get('cltokens')
44
+
45
+ # 如果数据格式有效,保存到文件
46
+ if globals.cluadeToken:
47
+ save_cltoken(globals.cluadeToken)
48
+ return jsonify({"status": "success", "message": "claudeToken.json 已更新!"}), 200
49
+ else:
50
+ return jsonify({"status": "error", "message": "无效的数据格式!"}), 400
gateway/chatgpt.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import app
2
+ import json
3
+ from flask import request, jsonify
4
+ import utils.globals as globals
5
+ from utils.globals import *
6
+ from utils.tools import *
7
+
8
+
9
+
10
+ # 加载刷新历史
11
+ @app.route('/refresh_history', methods=['GET'])
12
+ @admin_required
13
+ def get_refresh_history():
14
+ return jsonify({
15
+ "status": "success",
16
+ "history": globals.refresh_history
17
+ }), 200
18
+
19
+ # 加载失败Refresh Token
20
+ @app.route('/get_failed_tokens')
21
+ @admin_required
22
+ def get_failed_tokens():
23
+ try:
24
+ return jsonify(globals.failed_tokens), 200
25
+ except json.JSONDecodeError:
26
+ return jsonify({"error": "Invalid JSON in failed_tokens.json"}), 500
27
+ except Exception as e:
28
+ return jsonify({"error": str(e)}), 500
29
+
30
+
31
+ # 加载Refresh Token
32
+ @app.route('/get_tokens')
33
+ @admin_required
34
+ def get_tokens():
35
+ try:
36
+ return jsonify(globals.chatToken), 200
37
+ except json.JSONDecodeError:
38
+ return jsonify({"error": "Invalid JSON in tokens.json"}), 500
39
+ except Exception as e:
40
+ return jsonify({"error": str(e)}), 500
41
+
42
+
43
+ # 添加新账号
44
+ @app.route('/api/tokens', methods=['POST'])
45
+ @admin_required
46
+ def create_tokens():
47
+ data = request.get_json()
48
+
49
+ # 检查账号是否已存在
50
+ if any(token['email'] == data['email'] for token in globals.chatToken):
51
+ return jsonify({'success': False, 'message': '该账号已存在'}), 400
52
+
53
+ new_token = {
54
+ 'email': data['email'],
55
+ 'refresh_token': data['ReToken'],
56
+ 'access_token': data['AcToken'],
57
+ 'status': True,
58
+ 'type':"/static/gpt.png",
59
+ 'PLUS': data['PLUS']
60
+ }
61
+
62
+ globals.chatToken.append(new_token)
63
+ save_retoken(globals.chatToken)
64
+
65
+ return jsonify({'success': True, 'message': '用户创建成功'})
66
+
67
+ # 更新账号信息
68
+ @app.route('/api/tokens/<email>', methods=['PUT'])
69
+ @admin_required
70
+ def update_token(email):
71
+ data = request.get_json()
72
+
73
+ token_index = next((i for i, token in enumerate(globals.chatToken) if token['email'] == email), None)
74
+ if token_index is None:
75
+ return jsonify({'success': False, 'message': '账号不存在'}), 400
76
+
77
+ # 如果提供了邮箱,则更新邮箱
78
+ new_email = data.get('email')
79
+ if new_email:
80
+ # 检查是否已有重复的邮箱
81
+ if any(token['email'] == new_email for i, token in enumerate(globals.chatToken) if i != token_index):
82
+ return jsonify({'success': False, 'message': '邮箱已存在'}), 400
83
+ globals.chatToken[token_index]['email'] = new_email
84
+
85
+ # 如果提供了ReToken,则更新ReToken
86
+ if data.get('ReToken'):
87
+ globals.chatToken[token_index]['refresh_token'] = data['ReToken']
88
+ else:
89
+ globals.chatToken[token_index]['refresh_token'] = ''
90
+
91
+ # 如果提供了AcToken,则更新AcToken
92
+ if data.get('AcToken'):
93
+ globals.chatToken[token_index]['access_token'] = data['AcToken']
94
+ globals.chatToken[token_index]['status'] = True
95
+ for i, user in enumerate(globals.users):
96
+ if user['bind_email'] == email:
97
+ globals.users[i]['bind_token'] = data['AcToken']
98
+ set_seedmap(globals.users[i]['id'],data['AcToken'])
99
+ save_users(globals.users)
100
+ else:
101
+ globals.chatToken[token_index]['access_token'] = ''
102
+ for i, user in enumerate(globals.users):
103
+ if user['bind_email'] == email:
104
+ globals.users[i]['bind_token'] = ''
105
+ del_seedmap(globals.users[i]['id'])
106
+ save_users(globals.users)
107
+
108
+
109
+ if data.get('PLUS'):
110
+ globals.chatToken[token_index]['PLUS'] = data['PLUS']
111
+
112
+ save_retoken(globals.chatToken)
113
+ return jsonify({'success': True, 'message': '账号更新成功'})
114
+
115
+ # 删除账号
116
+ @app.route('/api/tokens/<email>', methods=['DELETE'])
117
+ @admin_required
118
+ def delete_token(email):
119
+
120
+ # 过滤掉要删除的账号
121
+ updated_email = [token for token in globals.chatToken if token['email'] != email]
122
+
123
+ if len(updated_email) == len(globals.chatToken):
124
+ return jsonify({'success': False, 'message': '账号不存在'}), 404
125
+
126
+ for i, user in enumerate(globals.users):
127
+ if user['bind_email'] == email:
128
+ globals.users[i]['bind_email'] = ''
129
+ globals.users[i]['bind_token'] = ''
130
+ del_seedmap(globals.users[i]['id'])
131
+ save_users(globals.users)
132
+ globals.chatToken=updated_email
133
+ save_retoken(globals.chatToken)
134
+ return jsonify({'success': True, 'message': '账号删除成功'})
gateway/claude.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import app
2
+ import json
3
+ from flask import request, jsonify
4
+ import utils.globals as globals
5
+ from utils.globals import *
6
+ from utils.tools import *
7
+
8
+ # 加载Claude Token
9
+ @app.route('/get_Claude')
10
+ @admin_required
11
+ def get_Claude():
12
+ try:
13
+ return jsonify(globals.cluadeToken), 200
14
+ except json.JSONDecodeError:
15
+ return jsonify({"error": "Invalid JSON in tokens.json"}), 500
16
+ except Exception as e:
17
+ return jsonify({"error": str(e)}), 500
18
+
19
+ # 添加新账号
20
+ @app.route('/api/Claude', methods=['POST'])
21
+ @admin_required
22
+ def create_Claude():
23
+ data = request.get_json()
24
+
25
+ # 检查账号是否已存在
26
+ if any(token['email'] == data['email'] for token in globals.cluadeToken):
27
+ return jsonify({'success': False, 'message': '该账号已存在'}), 400
28
+
29
+ new_token = {
30
+ 'email': data['email'],
31
+ 'skToken': data['SkToken'],
32
+ 'status': True,
33
+ 'type':"/static/claude.png",
34
+ 'PLUS': data['PLUS']
35
+ }
36
+
37
+ globals.cluadeToken.append(new_token)
38
+ save_cltoken(globals.cluadeToken)
39
+
40
+ return jsonify({'success': True, 'message': '用户创建成功'})
41
+
42
+ # 更新账号信息
43
+ @app.route('/api/Claude/<email>', methods=['PUT'])
44
+ @admin_required
45
+ def update_Claude(email):
46
+ data = request.get_json()
47
+
48
+ token_index = next((i for i, token in enumerate(globals.cluadeToken) if token['email'] == email), None)
49
+ if token_index is None:
50
+ return jsonify({'success': False, 'message': '账号不存在'}), 400
51
+
52
+
53
+ # 如果提供了邮箱,则更新邮箱
54
+ if data.get('email'):
55
+ globals.cluadeToken[token_index]['email'] = data['email']
56
+
57
+ # 如果提供了ReToken,则更新ReToken
58
+ if data.get('SkToken'):
59
+ globals.cluadeToken[token_index]['skToken'] = data['SkToken']
60
+ globals.cluadeToken[token_index]['status'] = True
61
+
62
+ if data.get('PLUS'):
63
+ globals.cluadeToken[token_index]['PLUS'] = data['PLUS']
64
+
65
+ save_cltoken(globals.cluadeToken)
66
+ return jsonify({'success': True, 'message': '账号更新成功'})
67
+
68
+ # 删除用户
69
+ @app.route('/api/Claude/<email>', methods=['DELETE'])
70
+ @admin_required
71
+ def delete_Claude(email):
72
+
73
+ # 过滤掉要删除的用户
74
+ updated_email = [token for token in globals.cluadeToken if token['email'] != email]
75
+
76
+ if len(updated_email) == len(globals.cluadeToken):
77
+ return jsonify({'success': False, 'message': '账号不存在'}), 404
78
+ globals.cluadeToken = updated_email
79
+ save_cltoken(globals.cluadeToken)
80
+ return jsonify({'success': True, 'message': '账号删除成功'})
gateway/geteway.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import app
2
+ from flask import jsonify
3
+ import utils.globals as globals
4
+ from utils.globals import *
5
+ from utils.tools import *
6
+
7
+
8
+
9
+
10
+
11
+ # 一键同步网关seedmap
12
+ @app.route('/api/syc', methods=['GET'])
13
+ @admin_required
14
+ def syc_seedmap():
15
+ del_seedmap("clear")
16
+
17
+ for user in globals.users:
18
+ if user['bind_email'] != '' and user['bind_token'] != '':
19
+ set_seedmap(user['id'],user['bind_token'])
20
+ return jsonify({'success': True, 'message': '同步成功'})
gateway/index.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import app
2
+ from flask import redirect, url_for, session
3
+ from utils.globals import *
4
+ from utils.tools import *
5
+
6
+
7
+
8
+ # 主页路由
9
+ @app.route('/')
10
+ @login_required
11
+ def index():
12
+
13
+ if session.get('logged_in'):
14
+ session.clear()
15
+ return redirect(url_for('login'))
gateway/login.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import app
2
+ from flask import render_template, request, redirect, url_for, session, flash
3
+ import utils.globals as globals
4
+ from utils.globals import *
5
+ from utils.tools import *
6
+ from werkzeug.security import check_password_hash
7
+ from gateway.index import *
8
+
9
+
10
+ # 登录页面
11
+ @app.route('/login', methods=['GET', 'POST'])
12
+ def login():
13
+ if request.method == 'POST':
14
+ username = request.form['username']
15
+ password = request.form['password']
16
+ loginTarget = request.form['login_target']
17
+ user = next((user for user in globals.users if user['username'] == username), None)
18
+
19
+ if user and check_password_hash(user['password'], password):
20
+ # 登录成功,存储用户信息到session
21
+ session['logged_in'] = True
22
+ session['user_id'] = user['id']
23
+ session['username'] = user['username']
24
+ session['role'] = user['role']
25
+
26
+ flash('登录成功!', 'success')
27
+
28
+ # 如果是管理员,跳转到管理页面,否则跳转到ChatGPT共享页面
29
+ if user['role'] == 'admin' and loginTarget == 'manage':
30
+ return redirect(url_for('chatgpt'))
31
+ elif user['role'] == 'user' and loginTarget == 'manage':
32
+ flash('你没有管理员权限。', 'danger')
33
+ elif loginTarget == 'gpt':
34
+ logurl = getoauth(session.get('user_id'))
35
+ session.clear()
36
+ return redirect(logurl)
37
+ elif loginTarget == 'claude':
38
+ logurl = get_claude_login_url(user['bind_claude_token'],session.get('user_id'))
39
+ if logurl == None:
40
+ flash('你没有此权限。', 'danger')
41
+ else:
42
+ session.clear()
43
+ return redirect(logurl)
44
+ else:
45
+ flash('用户名或密码错误,请重试。', 'danger')
46
+
47
+ return render_template('login.html')
48
+
49
+ # 登出路由
50
+ @app.route('/logout')
51
+ def logout():
52
+ session.clear()
53
+ flash('已成功登出。', 'success')
54
+ return redirect(url_for('login'))
55
+
56
+
gateway/timing.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import app
2
+ from datetime import datetime, timedelta
3
+ import time
4
+ import threading
5
+ from flask import request, jsonify
6
+ import utils.globals as globals
7
+ from utils.globals import *
8
+ from utils.tools import *
9
+
10
+
11
+
12
+ def is_main_process():
13
+ import os
14
+ return os.environ.get('WERKZEUG_RUN_MAIN') != 'true'
15
+
16
+ current_timer = None
17
+ timer_lock = threading.Lock()
18
+
19
+ def schedule_next_refresh():
20
+ if not is_main_process():
21
+ print("在 reloader 进程中,跳过定时器设置")
22
+ return
23
+
24
+ global current_timer
25
+
26
+ with timer_lock:
27
+ if globals.auto_refresh_config['auto_refresh_enabled']:
28
+ if current_timer:
29
+ current_timer.cancel()
30
+
31
+ next_refresh = datetime.now() + timedelta(days=globals.auto_refresh_config['refresh_interval_days'])
32
+ globals.auto_refresh_config['next_refresh_time'] = next_refresh.isoformat()
33
+ save_auto_refresh_config(globals.auto_refresh_config)
34
+
35
+ current_timer = threading.Timer(
36
+ (next_refresh - datetime.now()).total_seconds(),
37
+ auto_refresh_tokens
38
+ )
39
+ current_timer.start()
40
+
41
+ def auto_refresh_tokens():
42
+
43
+ print('开始自动刷新')
44
+ new_access_tokens = refresh_access_tokens()
45
+
46
+ # 更新刷新历史
47
+ update_refresh_history(len(new_access_tokens))
48
+
49
+ # 添加延时,确保两次刷新之间有足够间隔
50
+ time.sleep(2) # 等待1秒
51
+
52
+ # 刷新完成后,调度下一次刷新
53
+ schedule_next_refresh()
54
+
55
+ # 更新刷新历史
56
+ def update_refresh_history(token_count):
57
+
58
+ globals.refresh_history.append({
59
+ "timestamp": datetime.now().isoformat(),
60
+ "token_count": token_count
61
+ })
62
+
63
+ # 保留最近的 5 条记录
64
+ globals.refresh_history = globals.refresh_history[-5:]
65
+
66
+ save_refresh_history(globals.refresh_history)
67
+
68
+ # 设定定时任务
69
+ @app.route('/set_auto_refresh', methods=['POST'])
70
+ @admin_required
71
+ def set_auto_refresh():
72
+ data = request.json
73
+
74
+ # 取消现有的定时任务
75
+ globals.auto_refresh_config['auto_refresh_enabled'] = data['enabled']
76
+ globals.auto_refresh_config['refresh_interval_days'] = data['interval']
77
+ save_auto_refresh_config(globals.auto_refresh_config)
78
+
79
+ if globals.auto_refresh_config['auto_refresh_enabled']:
80
+ schedule_next_refresh()
81
+
82
+ return jsonify({"status": "success", "message": "自动刷新设置已更新"})
83
+
84
+ # 加载定时任务配置信息
85
+ @app.route('/get_auto_refresh_config', methods=['GET'])
86
+ def get_auto_refresh_config():
87
+ return jsonify(globals.auto_refresh_config)
88
+
89
+ # 在应用启动时调用这个函数
90
+ def init_auto_refresh():
91
+ if not is_main_process():
92
+ print("在 reloader 进程中,跳过定时器初始化")
93
+ return
94
+
95
+ print(f"在主进程中初始化自动刷新, 当前时间: {datetime.now()}")
96
+
97
+ if globals.auto_refresh_config['auto_refresh_enabled'] and globals.auto_refresh_config['next_refresh_time']:
98
+ next_refresh = datetime.fromisoformat(globals.auto_refresh_config['next_refresh_time'])
99
+
100
+ if next_refresh > datetime.now():
101
+ delay_seconds = (next_refresh - datetime.now()).total_seconds()
102
+ print(f"设置初始定时器, 延迟秒数: {delay_seconds}")
103
+
104
+ global current_timer
105
+ with timer_lock:
106
+ current_timer = threading.Timer(delay_seconds, auto_refresh_tokens)
107
+ current_timer.start()
108
+ else:
109
+ schedule_next_refresh()
110
+
111
+ # 在应用启动时调用
112
+ init_auto_refresh()
113
+
114
+
115
+ # 删除gpt过期用户
116
+ def delete_expired_users():
117
+ print('开始检查并清理过期用户bind_token')
118
+
119
+ # 获取当前时间
120
+ now = datetime.now()
121
+ active_users = []
122
+ # 遍历 globals.users 来检查用户的过期时间
123
+ for user in globals.users:
124
+ # 如果 expiration_time 字段为空,跳过此用户
125
+ if user.get('expiration_time')=="":
126
+ active_users.append(user)
127
+ continue
128
+
129
+ try:
130
+ # 转换 expiration_time 为 datetime 对象
131
+ expiration_time = datetime.fromisoformat(user['expiration_time'])
132
+ except ValueError:
133
+ print(f"无效的过期时间格式,跳过用户 {user['username']}")
134
+ active_users.append(user)
135
+ continue
136
+
137
+
138
+ # 如果过期时间小于当前时间,设置 expiration_time 为空
139
+ if expiration_time < now:
140
+ user['expiration_time'] = ""
141
+ user['bind_email'] = ''
142
+ user['bind_token'] = ''
143
+ del_seedmap(user['id'])
144
+ active_users.append(user)
145
+ print(f"用户 {user['username']} 的过期时间已到,清除该用户绑定的token")
146
+ else:
147
+ active_users.append(user)
148
+
149
+
150
+ globals.users = active_users
151
+ save_users(globals.users)
152
+ # 打印处理结果
153
+ print(f"过期用户的bind_token已设置为空")
154
+
155
+ # 调度下一次检查
156
+ schedule_next_user_cleanup()
157
+
158
+ # 删除claude过期用户
159
+ def delete_expired_users_claude():
160
+ print('开始检查并清理过期用户bind_token')
161
+
162
+ # 获取当前时间
163
+ now = datetime.now()
164
+ active_users = []
165
+ # 遍历 globals.users 来检查用户的过期时间
166
+ for user in globals.users:
167
+ # 如果 claude_expiration_time 字段为空,跳过此用户
168
+ if user.get('claude_expiration_time')=="":
169
+ active_users.append(user)
170
+ continue
171
+
172
+ try:
173
+ # 转换 claude_expiration_time 为 datetime 对象
174
+ claude_expiration_time = datetime.fromisoformat(user['claude_expiration_time'])
175
+ except ValueError:
176
+ print(f"无效的过期时间格式,跳过用户 {user['username']}")
177
+ active_users.append(user)
178
+ continue
179
+
180
+
181
+ # 如果过期时间小于当前时间,设置 claude_expiration_time 为空
182
+ if claude_expiration_time < now:
183
+ user['claude_expiration_time'] = ""
184
+ user['bind_claude_email'] = ''
185
+ user['bind_claude_token'] = ''
186
+ active_users.append(user)
187
+ print(f"用户 {user['username']} 的过期时间已到,清除该用户绑定的token")
188
+ else:
189
+ active_users.append(user)
190
+
191
+
192
+ globals.users = active_users
193
+ save_users(globals.users)
194
+ # 打印处理结果
195
+ print(f"过期用户的bind_token已设置为空")
196
+
197
+ # 调度下一次检查
198
+ schedule_next_user_cleanup_claude()
199
+
200
+
201
+ # 设定gpt定时任务
202
+ def schedule_next_user_cleanup():
203
+ # 设定检查过期用户的时间间隔,例如每天检查一次
204
+ next_check = datetime.now() + timedelta(days=1)
205
+ delay_seconds = (next_check - datetime.now()).total_seconds()
206
+
207
+ # 使用 threading.Timer 启动定时器
208
+ threading.Timer(delay_seconds, delete_expired_users).start()
209
+
210
+ # 设定claude定时任务
211
+ def schedule_next_user_cleanup_claude():
212
+ # 设定检查过期用户的时间间隔,例如每23小时检查一次
213
+ next_check = datetime.now() + timedelta(minutes=1380)
214
+ delay_seconds = (next_check - datetime.now()).total_seconds()
215
+
216
+ # 使用 threading.Timer 启动定时器
217
+ threading.Timer(delay_seconds, delete_expired_users_claude).start()
218
+
219
+
220
+ # 在应用启动时调用
221
+ def init_user_cleanup():
222
+ print(f"在应用启动时初始化用户清理任务, 当前时间: {datetime.now()}")
223
+ # 启动定时器,开始检查过期用户
224
+ schedule_next_user_cleanup()
225
+ schedule_next_user_cleanup_claude()
226
+
227
+
228
+ # 初始化用户清理任务
229
+ init_user_cleanup()
230
+
231
+
232
+ # 手动刷新access token
233
+ @app.route('/refresh_tokens', methods=['POST'])
234
+ @admin_required
235
+ def refresh_tokens():
236
+ try:
237
+ # 调用刷新 access_token 的函数
238
+ new_access_tokens = refresh_access_tokens()
239
+ update_refresh_history(len(new_access_tokens))
240
+
241
+ return jsonify({
242
+ "status": "success",
243
+ "access_tokens": new_access_tokens
244
+ }), 200
245
+ except Exception as e:
246
+ return jsonify({
247
+ "status": "error",
248
+ "message": str(e)
249
+ }), 500
gateway/user.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import app
2
+ from flask import render_template, request, jsonify
3
+ import uuid
4
+ import utils.globals as globals
5
+ from utils.globals import *
6
+ from utils.tools import *
7
+ from werkzeug.security import generate_password_hash
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+ @app.route('/user')
16
+ @admin_required
17
+ def user_management():
18
+ return render_template('user_management.html')
19
+
20
+
21
+
22
+ # 获取所有用户
23
+ @app.route('/api/users', methods=['GET'])
24
+ @admin_required
25
+ def get_users():
26
+ # 返回用户列表时不包含密码信息
27
+ return jsonify([{k: v for k, v in user.items() if k != 'password' and k != 'bind_token'} for user in globals.users])
28
+
29
+ # 创建新用户
30
+ @app.route('/api/users', methods=['POST'])
31
+ @admin_required
32
+ def create_user():
33
+ data = request.get_json()
34
+
35
+ # 检查用户名是否已存在
36
+ if any(user['username'] == data['username'] for user in globals.users):
37
+ return jsonify({'success': False, 'message': '用户名已存在'}), 400
38
+
39
+ new_user = {
40
+ 'id': str(uuid.uuid4()),
41
+ 'username': data['username'],
42
+ 'password': generate_password_hash(data['password']),
43
+ 'role': data['role'],
44
+ 'bind_token': '',
45
+ 'bind_email': '',
46
+ 'expiration_time': data['expiration_time'],
47
+ 'bind_claude_token': '',
48
+ 'bind_claude_email': '',
49
+ 'claude_expiration_time': data['claude_expiration_time']
50
+ }
51
+
52
+ globals.users.append(new_user)
53
+ save_users(globals.users)
54
+
55
+ return jsonify({'success': True, 'message': '用户创建成功'})
56
+
57
+ # 更新用户信息
58
+ @app.route('/api/users/<user_id>', methods=['PUT'])
59
+ @admin_required
60
+ def update_user(user_id):
61
+ data = request.get_json()
62
+
63
+ user_index = next((i for i, user in enumerate(globals.users) if user['id'] == user_id), None)
64
+ if user_index is None:
65
+ return jsonify({'success': False, 'message': '用户不存在'}), 404
66
+
67
+ # 检查用户名是否与其他用户冲突
68
+ if any(user['username'] == data['username'] and user['id'] != user_id for user in globals.users):
69
+ return jsonify({'success': False, 'message': '用户名已存在'}), 400
70
+
71
+ # 更新用户信息
72
+ globals.users[user_index]['username'] = data['username']
73
+ globals.users[user_index]['role'] = data['role']
74
+
75
+ # 如果提供了新密码,则更新密码
76
+ if data.get('password'):
77
+ globals.users[user_index]['password'] = generate_password_hash(data['password'])
78
+
79
+ globals.users[user_index]['expiration_time'] = data['expiration_time']
80
+ globals.users[user_index]['claude_expiration_time'] = data['claude_expiration_time']
81
+
82
+ save_users(globals.users)
83
+ return jsonify({'success': True, 'message': '用户更新成功'})
84
+
85
+ # 绑定ChatGPT账号
86
+ @app.route('/api/bind/<user_id>', methods=['PUT'])
87
+ @admin_required
88
+ def bind_account(user_id):
89
+ data = request.get_json()
90
+ user_index = next((i for i, user in enumerate(globals.users) if user['id'] == user_id), None)
91
+ token_index = next((i for i, token in enumerate(globals.chatToken) if token['email'] == data['email']), None)
92
+ res = set_seedmap(user_id,globals.chatToken[token_index]['access_token'])
93
+ if res == 200:
94
+ globals.users[user_index]['bind_email'] = data['email']
95
+ globals.users[user_index]['bind_token'] = globals.chatToken[token_index]['access_token']
96
+ save_users(globals.users)
97
+ return jsonify({'success': True, 'message': '账号绑定成功'})
98
+ else:
99
+ return jsonify({'success': False, 'message': '账号绑定失败'})
100
+
101
+ # 解绑ChatGPT账号
102
+ @app.route('/api/del_bind/<user_id>', methods=['DELETE'])
103
+ @admin_required
104
+ def del_bind_account(user_id):
105
+ res = del_seedmap(user_id)
106
+ if res == 200:
107
+ user_index = next((i for i, user in enumerate(globals.users) if user['id'] == user_id), None)
108
+ globals.users[user_index]['bind_email'] = ''
109
+ globals.users[user_index]['bind_token'] = ''
110
+ save_users(globals.users)
111
+ return jsonify({'success': True, 'message': '账号解绑成功'})
112
+ else:
113
+ return jsonify({'success': False, 'message': '账号解绑失败'})
114
+
115
+
116
+ # 获取全部ChatGPT账号的email
117
+ @app.route('/api/all_email', methods=['GET'])
118
+ @admin_required
119
+ def all_email():
120
+ # 返回账号的全部email
121
+ return jsonify([token['email'] for token in globals.chatToken if 'email' in token])
122
+
123
+ # 绑定Claude账号
124
+ @app.route('/api/bindClaude/<user_id>', methods=['PUT'])
125
+ @admin_required
126
+ def bind_claude_account(user_id):
127
+ data = request.get_json()
128
+ user_index = next((i for i, user in enumerate(globals.users) if user['id'] == user_id), None)
129
+ token_index = next((i for i, token in enumerate(globals.cluadeToken) if token['email'] == data['email']), None)
130
+ globals.users[user_index]['bind_claude_email'] = data['email']
131
+ globals.users[user_index]['bind_claude_token'] = globals.cluadeToken[token_index]['skToken']
132
+ save_users(globals.users)
133
+ return jsonify({'success': True, 'message': '账号绑定成功'})
134
+
135
+ # 解绑Claude账号
136
+ @app.route('/api/del_bindClaude/<user_id>', methods=['DELETE'])
137
+ @admin_required
138
+ def del_bind_claude_account(user_id):
139
+
140
+ user_index = next((i for i, user in enumerate(globals.users) if user['id'] == user_id), None)
141
+ globals.users[user_index]['bind_claude_email'] = ''
142
+ globals.users[user_index]['bind_claude_token'] = ''
143
+ save_users(globals.users)
144
+ return jsonify({'success': True, 'message': '账号解绑成功'})
145
+
146
+
147
+ # 获取全部Claude账号的email
148
+ @app.route('/api/all_claude_email', methods=['GET'])
149
+ @admin_required
150
+ def all_claude_email():
151
+ # 返回账号的全部email
152
+ return jsonify([token['email'] for token in globals.cluadeToken if 'email' in token])
153
+
154
+ # 删除用户
155
+ @app.route('/api/users/<user_id>', methods=['DELETE'])
156
+ @admin_required
157
+ def delete_user(user_id):
158
+
159
+ # 过滤掉要删除的用户
160
+ updated_users = [user for user in globals.users if user['id'] != user_id]
161
+
162
+ user = next((user for user in globals.users if user['id'] == user_id), None)
163
+
164
+ if len(updated_users) == len(globals.users):
165
+ return jsonify({'success': False, 'message': '用户不存在'}), 404
166
+
167
+ globals.users = updated_users
168
+ save_users(globals.users)
169
+ if user['bind_token'] != '':
170
+ del_seedmap(user_id)
171
+ return jsonify({'success': True, 'message': '用户删除成功'})
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ flask
2
+ requests
3
+ werkzeug
4
+ gunicorn
5
+ python-dotenv
static/claude.png ADDED
static/favicon.png ADDED
static/gpt.png ADDED
static/login.css ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: Arial, sans-serif;
3
+ background-color: #f0f2f5;
4
+ display: flex;
5
+ justify-content: center;
6
+ align-items: center;
7
+ height: 100vh;
8
+ margin: 0;
9
+ }
10
+
11
+ .login-container {
12
+ background-color: white;
13
+ padding: 40px;
14
+ border-radius: 10px;
15
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
16
+ width: 300px;
17
+ text-align: center;
18
+ }
19
+
20
+ .login-container h1 {
21
+ margin-bottom: 20px;
22
+ font-size: 24px;
23
+ color: #333;
24
+ }
25
+
26
+ .login-container input[type="text"],
27
+ .login-container input[type="password"] {
28
+ width: 300px;
29
+ padding: 10px;
30
+ margin: 10px 0;
31
+ border: 1px solid #ddd;
32
+ border-radius: 5px;
33
+ font-size: 16px;
34
+ box-sizing: border-box;
35
+ }
36
+
37
+ .login-container button {
38
+ width: 300px;
39
+ padding: 10px;
40
+ background-color: #007bff;
41
+ border: none;
42
+ border-radius: 5px;
43
+ color: white;
44
+ font-size: 16px;
45
+ cursor: pointer;
46
+ }
47
+
48
+ .login-container button:hover {
49
+ background-color: #0056b3;
50
+ }
51
+
52
+ .message {
53
+ margin-bottom: 15px;
54
+ color: red;
55
+ font-size: 14px;
56
+ }
57
+
58
+ .success {
59
+ color: green;
60
+ }
61
+
62
+ .danger {
63
+ color: red;
64
+ }
templates/GPT.html ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Token管理{% endblock %}
4
+
5
+ {% block extra_css %}
6
+ <style>
7
+ .token-list {
8
+ width: 100%;
9
+ border-collapse: collapse;
10
+ }
11
+
12
+ .token-list th,
13
+ .token-list td {
14
+ border: 1px solid #e2e8f0;
15
+ padding: 8px;
16
+ text-align: left;
17
+ }
18
+
19
+ .token-list th {
20
+ background-color: #f8fafc;
21
+ }
22
+
23
+ .token-list tr:nth-child(even) {
24
+ background-color: #f1f5f9;
25
+ }
26
+
27
+ .truncate {
28
+ max-width: 200px;
29
+ white-space: nowrap;
30
+ overflow: hidden;
31
+ text-overflow: ellipsis;
32
+ }
33
+
34
+ h2 {
35
+ position: relative;
36
+ }
37
+
38
+ #addTokenBtn {
39
+ position: absolute;
40
+ font-size: 1rem;
41
+ right: 0;
42
+ top: 50%;
43
+ transform: translateY(-50%);
44
+ }
45
+
46
+ button:disabled {
47
+ color: #ccc;
48
+ background-color: #eee;
49
+ border: none;
50
+ cursor: not-allowed;
51
+ }
52
+
53
+ button:disabled:hover {
54
+ color: #ccc;
55
+ background-color: #eee;
56
+ border: none;
57
+ cursor: not-allowed;
58
+ }
59
+
60
+ @media (max-width: 770px) {
61
+ body {
62
+ font-size: 2.3vw;
63
+ }
64
+
65
+ h2 {
66
+ font-size: 3.5vw !important;
67
+ }
68
+
69
+ #addTokenBtn {
70
+ font-size: 2vw;
71
+ line-height: 2vw;
72
+ }
73
+
74
+ .token-list {
75
+ flex: 1;
76
+ }
77
+
78
+ .token-list thead {
79
+ display: flex;
80
+ flex-wrap: wrap;
81
+ }
82
+
83
+ .token-list #tokenTableBody {
84
+ display: flex;
85
+ flex-wrap: wrap;
86
+ }
87
+
88
+ .token-list tr {
89
+ display: flex;
90
+ }
91
+
92
+ .token-list tr,
93
+ .token-list th,
94
+ .token-list td {
95
+ flex: 1;
96
+ }
97
+ }
98
+ </style>
99
+ {% endblock %}
100
+
101
+ {% block content %}
102
+ <div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
103
+ <h2 class="text-2xl font-bold text-gray-800 mb-4">账号列表
104
+ <button id="addTokenBtn"
105
+ class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition duration-300 ease-in-out mr-2">
106
+ 添加账号
107
+ </button>
108
+ </h2>
109
+ <table class="token-list">
110
+ <thead>
111
+ <tr>
112
+ <th>Email</th>
113
+ <th>Re Token</th>
114
+ <th>状态</th>
115
+ <th>操作</th>
116
+ </tr>
117
+ </thead>
118
+ <tbody id="tokenTableBody">
119
+ <!-- Token 数据将在这里动态添加 -->
120
+ </tbody>
121
+ </table>
122
+ <div class="flex items-center justify-between mt-4">
123
+ <div>
124
+ <button id="refreshBtn"
125
+ class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition duration-300 ease-in-out mr-2">
126
+ 立即刷新 Tokens
127
+ </button>
128
+ </div>
129
+ <p id="statusMessage" class="italic"></p>
130
+ </div>
131
+ </div>
132
+
133
+ <div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
134
+ <h2 class="text-2xl font-bold text-gray-800 mb-4">Access Token刷新设置</h2>
135
+ <div class="flex items-center mb-4">
136
+ <input type="checkbox" id="autoRefreshToggle" class="mr-2">
137
+ <label for="autoRefreshToggle" class="mr-4">启用自动刷新</label>
138
+ <input type="number" id="refreshInterval" min="1" value="1" class="border rounded px-2 py-1 w-20 mr-2">
139
+ <span>天</span>
140
+ </div>
141
+ <p id="nextRefreshTime" class="text-gray-600"></p>
142
+ </div>
143
+
144
+ <div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
145
+ <h2 class="text-2xl font-bold text-gray-800 mb-4">刷新Refresh Token失败的账号</h2>
146
+ <details class="mb-4">
147
+ <summary class="cursor-pointer text-blue-600 hover:text-blue-800">点击查看</summary>
148
+ <div id="failedTokens" class="mt-2 space-y-1">
149
+ <!-- 失败的 tokens 将在这里动态添加 -->
150
+ </div>
151
+ </details>
152
+ </div>
153
+
154
+ <div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
155
+ <h2 class="text-2xl font-bold text-gray-800 mb-4">刷新历史</h2>
156
+ <details class="mb-4">
157
+ <summary class="cursor-pointer text-blue-600 hover:text-blue-800">点击查看</summary>
158
+ <div id="refreshHistory" class="space-y-2">
159
+ <!-- 刷新历史将在这里动态添加 -->
160
+ </div>
161
+ </details>
162
+
163
+ </div>
164
+
165
+ <div id="tokenModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden">
166
+ <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
167
+ <div class="mt-3">
168
+ <h3 class="text-lg font-medium text-gray-900 mb-4">账号信息</h3>
169
+ <form id="tokenForm">
170
+ <input type="hidden" id="id">
171
+ <div class="mb-4">
172
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="email">
173
+ 邮箱
174
+ </label>
175
+ <input
176
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
177
+ id="email" type="text" required placeholder="仅用于标识 之后不得修改">
178
+ </div>
179
+ <div class="mb-4">
180
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="ReToken">
181
+ Refresh Token
182
+ </label>
183
+ <input
184
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
185
+ id="ReToken" type="text" placeholder="可为空">
186
+ </div>
187
+ <div class="mb-4">
188
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="acToken">
189
+ Access Token
190
+ </label>
191
+ <input
192
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
193
+ id="AcToken" type="text" placeholder="无Refresh Token时必填">
194
+ </div>
195
+ <div class="mb-4">
196
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="PLUS">
197
+ PLUS
198
+ </label>
199
+ <select
200
+ class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
201
+ id="PLUS" required>
202
+ <option value='false'>false</option>
203
+ <option value='true'>true</option>
204
+ </select>
205
+ </div>
206
+ <div class="flex items-center justify-end">
207
+ <button type="button" onclick="closeModal()"
208
+ class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mr-2">
209
+ 取消
210
+ </button>
211
+ <button type="submit"
212
+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
213
+ 保存
214
+ </button>
215
+ </div>
216
+ </form>
217
+ </div>
218
+ </div>
219
+ </div>
220
+
221
+ </div>
222
+ {% endblock %}
223
+ {% block extra_js %}
224
+
225
+ <script>
226
+
227
+ function loadTokens() {
228
+ fetch('/get_tokens')
229
+ .then(response => response.json())
230
+ .then(data => {
231
+ tokens = data
232
+ tokenTableBody.innerHTML = '';
233
+ data.forEach(token => {
234
+ let rt = '有'
235
+ if (!token.refresh_token) {
236
+ rt = '无'
237
+ }
238
+ let recolor = token.refresh_token ? ' style="color: rgb(6, 161, 6);"' : ' style="color: rgb(204, 28, 28);"'
239
+ let color = token.status ? ' style="color: rgb(6, 161, 6);"' : ' style="color: rgb(204, 28, 28);"'
240
+ const row = document.createElement('tr');
241
+ row.innerHTML = `
242
+ <td class="truncate">${token.email}</td>
243
+ <td class="truncate" ${recolor}>${rt}</td>
244
+ <td class="truncate" ${color}>${token.status ? '有效' : '失效'}</td>
245
+ <td>
246
+ <button onclick="editUser('${token.email}')" class="text-blue-600 hover:text-blue-900 mr-4">编辑</button>
247
+ <button onclick="deleteUser('${token.email}')" class="text-red-600 hover:text-red-900">删除</button>
248
+ </td>
249
+ `;
250
+ tokenTableBody.appendChild(row);
251
+ });
252
+ })
253
+ .catch(error => {
254
+ console.error('加载 Tokens 失败:', error);
255
+ showStatus('加载 Tokens 失败', 'error');
256
+ });
257
+ }
258
+
259
+ let tokens = []
260
+ const modal = document.getElementById('tokenModal');
261
+ const tokenForm = document.getElementById('tokenForm');
262
+ const addTokenBtn = document.getElementById('addTokenBtn');
263
+
264
+ // 打开模态框
265
+ function openModal(email = null) {
266
+ const form = document.getElementById('tokenForm');
267
+ if (email) {
268
+ document.getElementById('id').value = 'id'
269
+ document.getElementById('email').value = email.email;
270
+ document.getElementById('ReToken').value = email.refresh_token;
271
+ document.getElementById('AcToken').value = email.access_token;
272
+ document.getElementById('PLUS').value = email.PLUS;
273
+ } else {
274
+ form.reset();
275
+ document.getElementById('id').value = ''
276
+ }
277
+ modal.classList.remove('hidden');
278
+ }
279
+
280
+ // 关闭模态框
281
+ function closeModal() {
282
+ modal.classList.add('hidden');
283
+ }
284
+
285
+ // 编辑用户
286
+ function editUser(email) {
287
+ const tkemail = tokens.find(u => u.email === email);
288
+ if (tkemail) {
289
+ openModal(tkemail);
290
+ }
291
+ }
292
+
293
+ // 删除用户
294
+ function deleteUser(email) {
295
+ if (confirm('确定要删除这个账户吗?')) {
296
+ fetch(`/api/tokens/${email}`, {
297
+ method: 'DELETE'
298
+ })
299
+ .then(response => response.json())
300
+ .then(data => {
301
+ if (data.success) {
302
+ loadTokens();
303
+ }
304
+ })
305
+ .catch(error => console.error('Error:', error));
306
+ }
307
+ }
308
+
309
+ // 表单提交处理
310
+ tokenForm.addEventListener('submit', function (e) {
311
+ e.preventDefault();
312
+ const Id = document.getElementById('id').value;
313
+ const tkemail = document.getElementById('email').value
314
+ const retoken = document.getElementById('ReToken').value
315
+ const actoken = document.getElementById('AcToken').value
316
+ const plus = document.getElementById('PLUS').value
317
+ if(!(retoken || actoken)){
318
+ alert('必须有一个Refresh Token或Access Token')
319
+ return
320
+ }
321
+ const tkData = {
322
+ email: document.getElementById('email').value,
323
+ ReToken: retoken,
324
+ AcToken: actoken,
325
+ PLUS: plus
326
+ };
327
+
328
+ const url = Id ? `/api/tokens/${tkemail}` : '/api/tokens';
329
+ const method = Id ? 'PUT' : 'POST';
330
+
331
+ fetch(url, {
332
+ method: method,
333
+ headers: {
334
+ 'Content-Type': 'application/json'
335
+ },
336
+ body: JSON.stringify(tkData)
337
+ })
338
+ .then(response => response.json())
339
+ .then(data => {
340
+ if (data.success) {
341
+ closeModal();
342
+ loadTokens();
343
+ }
344
+ })
345
+ .catch(error => {
346
+ console.error('Error:', error);
347
+ });
348
+
349
+ });
350
+
351
+ // 添加用户按钮点击事件
352
+ addTokenBtn.addEventListener('click', () => openModal());
353
+ </script>
354
+ <script>
355
+ document.addEventListener('DOMContentLoaded', function () {
356
+ const tokenTableBody = document.getElementById('tokenTableBody');
357
+ const refreshBtn = document.getElementById('refreshBtn');
358
+ const statusMessage = document.getElementById('statusMessage');
359
+ const autoRefreshToggle = document.getElementById('autoRefreshToggle');
360
+ const refreshInterval = document.getElementById('refreshInterval');
361
+ const nextRefreshTime = document.getElementById('nextRefreshTime');
362
+ const refreshHistory = document.getElementById('refreshHistory');
363
+
364
+ function loadTokens() {
365
+ fetch('/get_tokens')
366
+ .then(response => response.json())
367
+ .then(data => {
368
+ tokens = data
369
+ tokenTableBody.innerHTML = '';
370
+ data.forEach(token => {
371
+ let rt = '有'
372
+ if (!token.refresh_token) {
373
+ rt = '无'
374
+ }
375
+ let recolor = token.refresh_token ? ' style="color: rgb(6, 161, 6);"' : ' style="color: rgb(204, 28, 28);"'
376
+ let color = token.status ? ' style="color: rgb(6, 161, 6);"' : ' style="color: rgb(204, 28, 28);"'
377
+ const row = document.createElement('tr');
378
+ row.innerHTML = `
379
+ <td class="truncate">${token.email}</td>
380
+ <td class="truncate" ${recolor}>${rt}</td>
381
+ <td class="truncate" ${color}>${token.status ? '有效' : '失效'}</td>
382
+ <td>
383
+ <button onclick="editUser('${token.email}')" class="text-blue-600 hover:text-blue-900 mr-4">编辑</button>
384
+ <button onclick="deleteUser('${token.email}')" class="text-red-600 hover:text-red-900">删除</button>
385
+ </td>
386
+ `;
387
+ tokenTableBody.appendChild(row);
388
+ });
389
+ })
390
+ .catch(error => {
391
+ console.error('加载 Tokens 失败:', error);
392
+ showStatus('加载 Tokens 失败', 'error');
393
+ });
394
+ }
395
+
396
+ function loadFailedTokens() {
397
+ fetch('/get_failed_tokens')
398
+ .then(response => response.json())
399
+ .then(data => {
400
+ const failedTokensDiv = document.getElementById('failedTokens');
401
+ failedTokensDiv.innerHTML = '';
402
+ data.forEach(item => {
403
+ const email = item.email;
404
+ const tokenItem = document.createElement('div');
405
+ tokenItem.className = 'bg-red-100 p-2 rounded';
406
+ tokenItem.textContent = email;
407
+ failedTokensDiv.appendChild(tokenItem);
408
+ });
409
+ })
410
+ .catch(error => {
411
+ console.error('加载失败Refresh Token 失败:', error);
412
+ showStatus('加载失败的 Tokens 失败', 'error');
413
+ });
414
+ }
415
+
416
+ function updateAutoRefreshUI(config) {
417
+ autoRefreshToggle.checked = config.auto_refresh_enabled;
418
+ refreshInterval.value = config.refresh_interval_days;
419
+ if (config.next_refresh_time) {
420
+ nextRefreshTime.textContent = `下次刷新时间: ${new Date(config.next_refresh_time).toLocaleString()}`;
421
+ } else {
422
+ nextRefreshTime.textContent = '自动刷新已关闭';
423
+ }
424
+ }
425
+
426
+ function loadAutoRefreshConfig() {
427
+ fetch('/get_auto_refresh_config')
428
+ .then(response => response.json())
429
+ .then(config => {
430
+ updateAutoRefreshUI(config);
431
+ })
432
+ .catch(error => {
433
+ console.error('加载自动刷新配置失败:', error);
434
+ showStatus('加载自动刷新配置失败', 'error');
435
+ });
436
+ }
437
+
438
+ function saveAutoRefreshConfig() {
439
+ fetch('/set_auto_refresh', {
440
+ method: 'POST',
441
+ headers: {
442
+ 'Content-Type': 'application/json'
443
+ },
444
+ body: JSON.stringify({
445
+ enabled: autoRefreshToggle.checked,
446
+ interval: parseInt(refreshInterval.value)
447
+ })
448
+ })
449
+ .then(response => response.json())
450
+ .then(data => {
451
+ if (data.status === 'success') {
452
+ showStatus('自动刷新设置已更新', 'success');
453
+ loadAutoRefreshConfig();
454
+ } else {
455
+ showStatus('更新自动刷新设置失败', 'error');
456
+ }
457
+ })
458
+ .catch(error => {
459
+ showStatus('更新自动刷新设置失败', 'error');
460
+ });
461
+ }
462
+
463
+ function refreshTokens() {
464
+ refreshBtn.disabled = true;
465
+ fetch('/refresh_tokens', {
466
+ method: 'POST'
467
+ })
468
+ .then(response => response.json())
469
+ .then(data => {
470
+ if (data.status === 'success') {
471
+ refreshBtn.disabled = false;
472
+ showStatus('Tokens 刷新成功', 'success');
473
+ loadTokens();
474
+ loadRefreshHistory();
475
+ loadAutoRefreshConfig();
476
+ loadFailedTokens();
477
+ } else {
478
+ refreshBtn.disabled = false;
479
+ showStatus('Tokens 刷新失败: ' + data.message, 'error');
480
+ }
481
+ })
482
+ .catch(error => {
483
+ refreshBtn.disabled = false;
484
+ showStatus('Tokens 刷新失败', 'error');
485
+ });
486
+ }
487
+
488
+ function loadRefreshHistory() {
489
+ fetch('/refresh_history')
490
+ .then(response => response.json())
491
+ .then(data => {
492
+ if (data.status === 'success') {
493
+ refreshHistory.innerHTML = '';
494
+ data.history.forEach(item => {
495
+ const historyItem = document.createElement('div');
496
+ historyItem.className = 'bg-gray-100 p-2 rounded';
497
+ historyItem.innerHTML = `
498
+ <p><strong>刷新时间:</strong> ${new Date(item.timestamp).toLocaleString()}</p>
499
+ <p><strong>Token 数量:</strong> ${item.token_count}</p>
500
+ `;
501
+ refreshHistory.appendChild(historyItem);
502
+ });
503
+ }
504
+ })
505
+ .catch(error => {
506
+ console.error('加载刷新历史失败:', error);
507
+ showStatus('加载刷新历史失败', 'error');
508
+ });
509
+ }
510
+
511
+ function showStatus(message, status) {
512
+ statusMessage.textContent = message;
513
+ statusMessage.className = status === 'success' ? 'text-green-600' : 'text-red-600';
514
+ setTimeout(() => {
515
+ statusMessage.textContent = '';
516
+ }, 3000);
517
+ }
518
+
519
+
520
+ refreshBtn.addEventListener('click', refreshTokens);
521
+ autoRefreshToggle.addEventListener('change', saveAutoRefreshConfig);
522
+ refreshInterval.addEventListener('change', saveAutoRefreshConfig);
523
+
524
+
525
+ // 初始化
526
+ loadTokens();
527
+ loadAutoRefreshConfig();
528
+ loadRefreshHistory();
529
+ loadFailedTokens();
530
+ });
531
+ </script>
532
+ {% endblock %}
templates/base.html ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>{% block title %}管理页面{% endblock %}</title>
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
9
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css" rel="stylesheet">
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script>
12
+ <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.png') }}">
13
+ {% block extra_css %}{% endblock %}
14
+ </head>
15
+
16
+ <body class="bg-gray-100 min-h-screen flex flex-col">
17
+ <!-- 顶部导航栏 -->
18
+ <nav class="bg-white shadow-lg">
19
+ <div class="max-w-7xl mx-auto px-4">
20
+ <div class="flex justify-between h-16">
21
+ <!-- 左侧 Logo 和导航链接 -->
22
+ <div class="flex">
23
+ <div class="flex-shrink-0 flex items-center">
24
+ <span class="text-xl font-bold text-gray-800">后台管理</span>
25
+ </div>
26
+
27
+ <!-- 导航链接 -->
28
+ <div class="hidden md:ml-6 md:flex md:space-x-4">
29
+ {% if session.get('role') == 'admin' %}
30
+ <a href="{{ url_for('chatgpt') }}"
31
+ class="{% if request.endpoint == 'chatgpt' %}bg-gray-900 text-white{% else %}text-gray-900 hover:bg-gray-700 hover:text-white{% endif %} px-3 py-2 rounded-md text-sm font-medium flex items-center">
32
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20"
33
+ fill="currentColor">
34
+ <path
35
+ d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z" />
36
+ </svg>
37
+ ChatGPT
38
+ </a>
39
+ <a href="{{ url_for('claude') }}"
40
+ class="{% if request.endpoint == 'claude' %}bg-gray-900 text-white{% else %}text-gray-900 hover:bg-gray-700 hover:text-white{% endif %} px-3 py-2 rounded-md text-sm font-medium flex items-center">
41
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20"
42
+ fill="currentColor">
43
+ <path
44
+ d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z" />
45
+ </svg>
46
+ Claude
47
+ </a>
48
+ <a href="{{ url_for('user_management') }}"
49
+ class="{% if request.endpoint == 'user_management' %}bg-gray-900 text-white{% else %}text-gray-900 hover:bg-gray-700 hover:text-white{% endif %} px-3 py-2 rounded-md text-sm font-medium flex items-center">
50
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20"
51
+ fill="currentColor">
52
+ <path
53
+ d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
54
+ </svg>
55
+ 用户管理
56
+ </a>
57
+
58
+ {% endif %}
59
+ </div>
60
+ </div>
61
+
62
+ <!-- 右侧用户信息和退出按钮 -->
63
+ <div class="flex items-center">
64
+ {% if session.get('logged_in') %}
65
+ <div class="flex items-center">
66
+ <span class="text-gray-700 mr-4">
67
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block mr-1"
68
+ viewBox="0 0 20 20" fill="currentColor">
69
+ <path fill-rule="evenodd"
70
+ d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z"
71
+ clip-rule="evenodd" />
72
+ </svg>
73
+ {{ session.get('username') }}
74
+ {% if session.get('role') == 'admin' %}
75
+ <span
76
+ class="bg-blue-100 text-blue-800 text-xs font-medium mr-2 px-2 py-0.5 rounded">管理员</span>
77
+ {% endif %}
78
+ </span>
79
+ <a href="{{ url_for('logout') }}"
80
+ class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded flex items-center">
81
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20"
82
+ fill="currentColor">
83
+ <path fill-rule="evenodd"
84
+ d="M3 3a1 1 0 011 1v12a1 1 0 11-2 0V4a1 1 0 011-1zm7.707 3.293a1 1 0 010 1.414L9.414 9H17a1 1 0 110 2H9.414l1.293 1.293a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0z"
85
+ clip-rule="evenodd" />
86
+ </svg>
87
+ 退出
88
+ </a>
89
+ </div>
90
+ {% endif %}
91
+ </div>
92
+ </div>
93
+ </div>
94
+
95
+ <!-- 移动端菜单按钮 -->
96
+ <div class="md:hidden">
97
+ <button type="button"
98
+ class="mobile-menu-button inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
99
+ aria-expanded="false">
100
+ <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
101
+ stroke="currentColor">
102
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
103
+ </svg>
104
+ </button>
105
+ </div>
106
+ </nav>
107
+
108
+ <!-- 移动端导航菜单 -->
109
+ <div class="mobile-menu hidden md:hidden">
110
+ <div class="px-2 pt-2 pb-3 space-y-1">
111
+ {% if session.get('role') == 'admin' %}
112
+ <a href="{{ url_for('chatgpt') }}"
113
+ class="{% if request.endpoint == 'chatgpt' %}bg-gray-900 text-white{% else %}text-gray-900 hover:bg-gray-700 hover:text-white{% endif %} block px-3 py-2 rounded-md text-base font-medium">
114
+ ChatGPT
115
+ </a>
116
+ <a href="{{ url_for('claude') }}"
117
+ class="{% if request.endpoint == 'claude' %}bg-gray-900 text-white{% else %}text-gray-900 hover:bg-gray-700 hover:text-white{% endif %} block px-3 py-2 rounded-md text-base font-medium">
118
+ Claude
119
+ </a>
120
+ <a href="{{ url_for('user_management') }}"
121
+ class="{% if request.endpoint == 'user_management' %}bg-gray-900 text-white{% else %}text-gray-900 hover:bg-gray-700 hover:text-white{% endif %} block px-3 py-2 rounded-md text-base font-medium">
122
+ 用户管理
123
+ </a>
124
+ {% endif %}
125
+ </div>
126
+ </div>
127
+
128
+ <!-- 主要内容区域 -->
129
+ <main class="flex-grow container mx-auto px-4 py-8">
130
+ {% with messages = get_flashed_messages(with_categories=true) %}
131
+ {% if messages %}
132
+ {% for category, message in messages %}
133
+ <div class="mb-4 p-4 rounded
134
+ {% if category == 'success' %}bg-green-100 text-green-700
135
+ {% elif category == 'danger' %}bg-red-100 text-red-700
136
+ {% else %}bg-blue-100 text-blue-700{% endif %}">
137
+ {{ message }}
138
+ </div>
139
+ {% endfor %}
140
+ {% endif %}
141
+ {% endwith %}
142
+
143
+ {% block content %}{% endblock %}
144
+ </main>
145
+
146
+ {% block extra_js %}{% endblock %}
147
+
148
+ <script>
149
+ // 移动端菜单切换
150
+ document.querySelector('.mobile-menu-button').addEventListener('click', function () {
151
+ document.querySelector('.mobile-menu').classList.toggle('hidden');
152
+ });
153
+ </script>
154
+ </body>
155
+
156
+ </html>
templates/claude.html ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Token管理{% endblock %}
4
+
5
+ {% block extra_css %}
6
+ <style>
7
+ .token-list {
8
+ width: 100%;
9
+ border-collapse: collapse;
10
+ }
11
+
12
+ .token-list th,
13
+ .token-list td {
14
+ border: 1px solid #e2e8f0;
15
+ padding: 8px;
16
+ text-align: left;
17
+ }
18
+
19
+ .token-list th {
20
+ background-color: #f8fafc;
21
+ }
22
+
23
+ .token-list tr:nth-child(even) {
24
+ background-color: #f1f5f9;
25
+ }
26
+
27
+ .truncate {
28
+ max-width: 200px;
29
+ white-space: nowrap;
30
+ overflow: hidden;
31
+ text-overflow: ellipsis;
32
+ }
33
+
34
+ h2 {
35
+ position: relative;
36
+ }
37
+
38
+ #addTokenBtn {
39
+ position: absolute;
40
+ font-size: 1rem;
41
+ right: 0;
42
+ top: 50%;
43
+ transform: translateY(-50%);
44
+ }
45
+
46
+ button:disabled {
47
+ color: #ccc;
48
+ background-color: #eee;
49
+ border: none;
50
+ cursor: not-allowed;
51
+ }
52
+
53
+ button:disabled:hover {
54
+ color: #ccc;
55
+ background-color: #eee;
56
+ border: none;
57
+ cursor: not-allowed;
58
+ }
59
+
60
+ @media (max-width: 770px) {
61
+ body {
62
+ font-size: 2.3vw;
63
+ }
64
+
65
+ h2 {
66
+ font-size: 3.5vw !important;
67
+ }
68
+
69
+ #addTokenBtn {
70
+ font-size: 2vw;
71
+ line-height: 2vw;
72
+ }
73
+
74
+ .token-list {
75
+ flex: 1;
76
+ }
77
+
78
+ .token-list thead {
79
+ display: flex;
80
+ flex-wrap: wrap;
81
+ }
82
+
83
+ .token-list #tokenTableBody {
84
+ display: flex;
85
+ flex-wrap: wrap;
86
+ }
87
+
88
+ .token-list tr {
89
+ display: flex;
90
+ }
91
+
92
+ .token-list tr,
93
+ .token-list th,
94
+ .token-list td {
95
+ flex: 1;
96
+ }
97
+ }
98
+ </style>
99
+ {% endblock %}
100
+
101
+ {% block content %}
102
+ <div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
103
+ <h2 class="text-2xl font-bold text-gray-800 mb-4">账号列表
104
+ <button id="addTokenBtn"
105
+ class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition duration-300 ease-in-out mr-2">
106
+ 添加账号
107
+ </button>
108
+ </h2>
109
+ <table class="token-list">
110
+ <thead>
111
+ <tr>
112
+ <th>Email</th>
113
+ <th>Sk Token</th>
114
+ <th>状态</th>
115
+ <th>操作</th>
116
+ </tr>
117
+ </thead>
118
+ <tbody id="tokenTableBody">
119
+ <!-- Token 数据将在这里动态添加 -->
120
+ </tbody>
121
+ </table>
122
+ <!-- <div class="flex items-center justify-between mt-4">
123
+ <div>
124
+ <button id="refreshBtn"
125
+ class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition duration-300 ease-in-out mr-2">
126
+ 立即刷新 Tokens
127
+ </button>
128
+ </div>
129
+ <p id="statusMessage" class="italic"></p>
130
+ </div> -->
131
+ </div>
132
+
133
+ <div id="tokenModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden">
134
+ <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
135
+ <div class="mt-3">
136
+ <h3 class="text-lg font-medium text-gray-900 mb-4">账号信息</h3>
137
+ <form id="tokenForm">
138
+ <input type="hidden" id="id">
139
+ <div class="mb-4">
140
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="email">
141
+ 邮箱
142
+ </label>
143
+ <input
144
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
145
+ id="email" type="text" required placeholder="仅用于标识">
146
+ </div>
147
+ <div class="mb-4">
148
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="skToken">
149
+ skToken
150
+ </label>
151
+ <input
152
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
153
+ id="skToken" type="text" placeholder="必填" required>
154
+ </div>
155
+ <div class="mb-4">
156
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="PLUS">
157
+ PLUS
158
+ </label>
159
+ <select
160
+ class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
161
+ id="PLUS" required>
162
+ <option value='false'>false</option>
163
+ <option value='true'>true</option>
164
+ </select>
165
+ </div>
166
+ <div class="flex items-center justify-end">
167
+ <button type="button" onclick="closeModal()"
168
+ class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mr-2">
169
+ 取消
170
+ </button>
171
+ <button type="submit"
172
+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
173
+ 保存
174
+ </button>
175
+ </div>
176
+ </form>
177
+ </div>
178
+ </div>
179
+ </div>
180
+
181
+ </div>
182
+ {% endblock %}
183
+ {% block extra_js %}
184
+
185
+ <script>
186
+
187
+ function loadTokens() {
188
+ fetch('/get_Claude')
189
+ .then(response => response.json())
190
+ .then(data => {
191
+ tokens = data
192
+ tokenTableBody.innerHTML = '';
193
+ data.forEach(token => {
194
+ let rt = '有'
195
+ if (!token.skToken) {
196
+ rt = '无'
197
+ }
198
+ let recolor = token.skToken ? ' style="color: rgb(6, 161, 6);"' : ' style="color: rgb(204, 28, 28);"'
199
+ let color = token.status ? ' style="color: rgb(6, 161, 6);"' : ' style="color: rgb(204, 28, 28);"'
200
+ const row = document.createElement('tr');
201
+ row.innerHTML = `
202
+ <td class="truncate">${token.email}</td>
203
+ <td class="truncate" ${recolor}>${rt}</td>
204
+ <td class="truncate" ${color}>${token.status ? '有效' : '失效'}</td>
205
+ <td>
206
+ <button onclick="editUser('${token.email}')" class="text-blue-600 hover:text-blue-900 mr-4">编辑</button>
207
+ <button onclick="deleteUser('${token.email}')" class="text-red-600 hover:text-red-900">删除</button>
208
+ </td>
209
+ `;
210
+ tokenTableBody.appendChild(row);
211
+ });
212
+ })
213
+ .catch(error => {
214
+ console.error('加载 Tokens 失败:', error);
215
+ showStatus('加载 Tokens 失败', 'error');
216
+ });
217
+ }
218
+
219
+ let tokens = []
220
+ const modal = document.getElementById('tokenModal');
221
+ const tokenForm = document.getElementById('tokenForm');
222
+ const addTokenBtn = document.getElementById('addTokenBtn');
223
+
224
+ // 打开模态框
225
+ function openModal(email = null) {
226
+ const form = document.getElementById('tokenForm');
227
+ if (email) {
228
+ document.getElementById('id').value = 'id'
229
+ document.getElementById('email').value = email.email;
230
+ document.getElementById('skToken').value = email.skToken;
231
+ document.getElementById('PLUS').value = email.PLUS;
232
+ } else {
233
+ form.reset();
234
+ document.getElementById('id').value = ''
235
+ }
236
+ modal.classList.remove('hidden');
237
+ }
238
+
239
+ // 关闭模态框
240
+ function closeModal() {
241
+ modal.classList.add('hidden');
242
+ }
243
+
244
+ // 编辑用户
245
+ function editUser(email) {
246
+ const tkemail = tokens.find(u => u.email === email);
247
+ if (tkemail) {
248
+ openModal(tkemail);
249
+ }
250
+ }
251
+
252
+ // 删除用户
253
+ function deleteUser(email) {
254
+ if (confirm('确定要删除这个账户吗?')) {
255
+ fetch(`/api/Claude/${email}`, {
256
+ method: 'DELETE'
257
+ })
258
+ .then(response => response.json())
259
+ .then(data => {
260
+ if (data.success) {
261
+ loadTokens();
262
+ }
263
+ })
264
+ .catch(error => console.error('Error:', error));
265
+ }
266
+ }
267
+
268
+ // 表单提交处理
269
+ tokenForm.addEventListener('submit', function (e) {
270
+ e.preventDefault();
271
+ const Id = document.getElementById('id').value;
272
+ const tkemail = document.getElementById('email').value
273
+ const skToken = document.getElementById('skToken').value
274
+ const plus = document.getElementById('PLUS').value
275
+ const tkData = {
276
+ email: document.getElementById('email').value,
277
+ SkToken: skToken,
278
+ PLUS: plus
279
+ };
280
+
281
+ const url = Id ? `/api/Claude/${tkemail}` : '/api/Claude';
282
+ const method = Id ? 'PUT' : 'POST';
283
+
284
+ fetch(url, {
285
+ method: method,
286
+ headers: {
287
+ 'Content-Type': 'application/json'
288
+ },
289
+ body: JSON.stringify(tkData)
290
+ })
291
+ .then(response => response.json())
292
+ .then(data => {
293
+ if (data.success) {
294
+ closeModal();
295
+ loadTokens();
296
+ }
297
+ })
298
+ .catch(error => console.error('Error:', error));
299
+ });
300
+
301
+ // 添加用户按钮点击事件
302
+ addTokenBtn.addEventListener('click', () => openModal());
303
+ </script>
304
+ <script>
305
+ document.addEventListener('DOMContentLoaded', function () {
306
+ const tokenTableBody = document.getElementById('tokenTableBody');
307
+ const refreshBtn = document.getElementById('refreshBtn');
308
+ const statusMessage = document.getElementById('statusMessage');
309
+ const autoRefreshToggle = document.getElementById('autoRefreshToggle');
310
+ const refreshInterval = document.getElementById('refreshInterval');
311
+ const nextRefreshTime = document.getElementById('nextRefreshTime');
312
+ const refreshHistory = document.getElementById('refreshHistory');
313
+
314
+ function loadTokens() {
315
+ fetch('/get_Claude')
316
+ .then(response => response.json())
317
+ .then(data => {
318
+ tokens = data
319
+ tokenTableBody.innerHTML = '';
320
+ data.forEach(token => {
321
+ let rt = '有'
322
+ if (!token.skToken) {
323
+ rt = '无'
324
+ }
325
+ let recolor = token.skToken ? ' style="color: rgb(6, 161, 6);"' : ' style="color: rgb(204, 28, 28);"'
326
+ let color = token.status ? ' style="color: rgb(6, 161, 6);"' : ' style="color: rgb(204, 28, 28);"'
327
+ const row = document.createElement('tr');
328
+ row.innerHTML = `
329
+ <td class="truncate">${token.email}</td>
330
+ <td class="truncate" ${recolor}>${rt}</td>
331
+ <td class="truncate" ${color}>${token.status ? '有效' : '失效'}</td>
332
+ <td>
333
+ <button onclick="editUser('${token.email}')" class="text-blue-600 hover:text-blue-900 mr-4">编辑</button>
334
+ <button onclick="deleteUser('${token.email}')" class="text-red-600 hover:text-red-900">删除</button>
335
+ </td>
336
+ `;
337
+ tokenTableBody.appendChild(row);
338
+ });
339
+ })
340
+ .catch(error => {
341
+ console.error('加载 Tokens 失败:', error);
342
+ showStatus('加载 Tokens 失败', 'error');
343
+ });
344
+ }
345
+
346
+ // 初始化
347
+ loadTokens();
348
+ });
349
+ </script>
350
+ {% endblock %}
templates/login.html ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>登录 | GPT共享</title>
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
9
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
10
+ <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.png') }}">
11
+ <style>
12
+ @keyframes fadeIn {
13
+ from { opacity: 0; transform: translateY(-20px); }
14
+ to { opacity: 1; transform: translateY(0); }
15
+ }
16
+
17
+ .animate-fade-in {
18
+ animation: fadeIn 0.5s ease-out forwards;
19
+ }
20
+
21
+ .form-input {
22
+ transition: all 0.3s ease;
23
+ }
24
+
25
+ .form-input:focus {
26
+ transform: translateY(-2px);
27
+ }
28
+
29
+ .login-button {
30
+ transition: all 0.3s ease;
31
+ }
32
+
33
+ .login-button:hover {
34
+ transform: translateY(-2px);
35
+ box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
36
+ }
37
+
38
+ .alert {
39
+ animation: slideIn 0.5s ease-out forwards;
40
+ }
41
+
42
+ @keyframes slideIn {
43
+ from { transform: translateX(-100%); opacity: 0; }
44
+ to { transform: translateX(0); opacity: 1; }
45
+ }
46
+ </style>
47
+ </head>
48
+ <body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen flex items-center justify-center p-4">
49
+ <!-- 登录卡片 -->
50
+ <div class="bg-white rounded-2xl shadow-xl w-full max-w-md p-8 animate-fade-in">
51
+ <!-- Logo和标题 -->
52
+ <div class="text-center mb-8">
53
+ <div class="inline-block p-3 rounded-full bg-blue-50 mb-4">
54
+ <i class="fas fa-user-circle text-4xl text-blue-500"></i>
55
+ </div>
56
+ <h1 class="text-2xl font-bold text-gray-800">欢迎回来</h1>
57
+ <p class="text-gray-500 mt-2">请登录您的账号</p>
58
+ </div>
59
+
60
+ <!-- Flash messages -->
61
+ {% with messages = get_flashed_messages(with_categories=true) %}
62
+ {% if messages %}
63
+ {% for category, message in messages %}
64
+ <div class="alert mb-4 p-4 rounded-lg {% if category == 'error' %}bg-red-100 text-red-700 border-l-4 border-red-500{% else %}bg-green-100 text-green-700 border-l-4 border-green-500{% endif %}">
65
+ <div class="flex items-center">
66
+ <div class="flex-shrink-0">
67
+ {% if category == 'error' %}
68
+ <i class="fas fa-exclamation-circle"></i>
69
+ {% else %}
70
+ <i class="fas fa-check-circle"></i>
71
+ {% endif %}
72
+ </div>
73
+ <div class="ml-3">
74
+ <p class="text-sm">{{ message }}</p>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ {% endfor %}
79
+ {% endif %}
80
+ {% endwith %}
81
+
82
+ <!-- 登录表单 -->
83
+ <form action="{{ url_for('login') }}" method="post" class="space-y-6">
84
+ <div>
85
+ <div class="relative">
86
+ <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
87
+ <i class="fas fa-user text-gray-400"></i>
88
+ </div>
89
+ <input
90
+ type="text"
91
+ name="username"
92
+ placeholder="用户名"
93
+ required
94
+ class="form-input w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-gray-700"
95
+ >
96
+ </div>
97
+ </div>
98
+
99
+ <div>
100
+ <div class="relative">
101
+ <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
102
+ <i class="fas fa-lock text-gray-400"></i>
103
+ </div>
104
+ <input
105
+ type="password"
106
+ name="password"
107
+ placeholder="密码"
108
+ required
109
+ class="form-input w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-gray-700"
110
+ >
111
+ </div>
112
+ </div>
113
+ <!-- 记住我选项 -->
114
+ <div class="flex items-center">
115
+ <input
116
+ type="checkbox"
117
+ id="remember_me"
118
+ name="remember_me"
119
+ class="h-4 w-4 text-blue-500 focus:ring-blue-500 border-gray-300 rounded"
120
+ >
121
+ <label for="remember_me" class="ml-2 block text-sm text-gray-700">
122
+ 记住我
123
+ </label>
124
+ </div>
125
+ <!-- 下拉菜单:选择登录到 GPT 或者 Claude -->
126
+ <div class="relative">
127
+ <select
128
+ name="login_target"
129
+ required
130
+ class="form-input w-full py-3 pl-4 pr-4 rounded-lg border border-gray-300 focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-gray-700"
131
+ >
132
+ <option value="" disabled selected>请选择登录目标</option>
133
+ <option value="manage">登录到管理端</option>
134
+ <option value="gpt">登录到 GPT</option>
135
+ <option value="claude">登录到 Claude</option>
136
+
137
+ </select>
138
+ </div>
139
+ <button
140
+ type="submit"
141
+ class="login-button w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-3 px-6 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
142
+ >
143
+ 登录
144
+ <i class="fas fa-arrow-right ml-2"></i>
145
+ </button>
146
+ </form>
147
+
148
+ <!-- 额外链接 -->
149
+ <div class="mt-6 text-center text-sm">
150
+ <a href="mailto:your@email.com" class="text-blue-500 hover:text-blue-600 font-medium">联系管理员</a>
151
+ </div>
152
+
153
+ <!-- 页脚 -->
154
+ <div class="mt-8 text-center text-sm text-gray-500">
155
+ <p>© 2024 ChatGPT共享. Created by <a style="color: rgb(106, 149, 228);" href="https://github.com/h88782481/Chat-Share" target="_blank" rel="noopener noreferrer">Baimo</a></p>
156
+ </div>
157
+ </div>
158
+ <!-- JavaScript for Remember Me functionality -->
159
+ <script>
160
+ // 页面加载时检查并填充保存的用户信息
161
+ window.onload = function() {
162
+ const savedUsername = localStorage.getItem('rememberedUsername');
163
+ const savedLoginTarget = localStorage.getItem('rememberedLoginTarget');
164
+ const rememberMe = localStorage.getItem('rememberMe') === 'true';
165
+
166
+ if (savedUsername && rememberMe) {
167
+ document.getElementById('username').value = savedUsername;
168
+ document.getElementById('remember_me').checked = true;
169
+ }
170
+
171
+ if (savedLoginTarget && rememberMe) {
172
+ document.getElementById('login_target').value = savedLoginTarget;
173
+ }
174
+ };
175
+
176
+ // 表单提交时保存用户信息
177
+ document.getElementById('loginForm').addEventListener('submit', function() {
178
+ const rememberMe = document.getElementById('remember_me').checked;
179
+ const username = document.getElementById('username').value;
180
+ const loginTarget = document.getElementById('login_target').value;
181
+
182
+ if (rememberMe) {
183
+ localStorage.setItem('rememberedUsername', username);
184
+ localStorage.setItem('rememberedLoginTarget', loginTarget);
185
+ localStorage.setItem('rememberMe', 'true');
186
+ } else {
187
+ localStorage.removeItem('rememberedUsername');
188
+ localStorage.removeItem('rememberedLoginTarget');
189
+ localStorage.setItem('rememberMe', 'false');
190
+ }
191
+ });
192
+ </script>
193
+ </body>
194
+ </html>
templates/user_management.html ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}用户管理{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
7
+ <div class="flex justify-between items-center mb-6">
8
+ <h2 class="text-2xl font-bold text-gray-800">用户列表</h2>
9
+ <button id="sycGatewayBtn" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">
10
+ 一键同步GPT网关
11
+ </button>
12
+ <button id="addUserBtn" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">
13
+ 添加用户
14
+ </button>
15
+ </div>
16
+
17
+ <!-- 用户列表 -->
18
+ <table class="min-w-full">
19
+ <thead>
20
+ <tr>
21
+ <th
22
+ class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
23
+ 用户名
24
+ </th>
25
+ <th
26
+ class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
27
+ 角色
28
+ </th>
29
+ <th
30
+ class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
31
+ 绑定GPT账号
32
+ </th>
33
+ <th
34
+ class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
35
+ GPT过期时间
36
+ </th>
37
+ <th
38
+ class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
39
+ 绑定Claude账号
40
+ </th>
41
+ <th
42
+ class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
43
+ Claude过期时间
44
+ </th>
45
+ <th
46
+ class="px-6 py-3 border-b-2 border-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
47
+ 操作
48
+ </th>
49
+ </tr>
50
+ </thead>
51
+ <tbody id="userTableBody">
52
+ <!-- 用户数据将动态插入这里 -->
53
+ </tbody>
54
+ </table>
55
+
56
+ <!-- 用户表单模态框 -->
57
+ <div id="userModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden">
58
+ <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
59
+ <div class="mt-3">
60
+ <h3 class="text-lg font-medium text-gray-900 mb-4">用户信息</h3>
61
+ <form id="userForm">
62
+ <input type="hidden" id="userId">
63
+ <div class="mb-4">
64
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="username">
65
+ 用户名
66
+ </label>
67
+ <input
68
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
69
+ id="username" type="text" required>
70
+ </div>
71
+ <div class="mb-4">
72
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="password">
73
+ 密码
74
+ </label>
75
+ <input
76
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
77
+ id="password" type="password">
78
+ </div>
79
+ <div class="mb-4">
80
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="role">
81
+ 角色
82
+ </label>
83
+ <select
84
+ class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
85
+ id="role" required>
86
+ <option value="admin">管理员</option>
87
+ <option value="user">普通用户</option>
88
+ </select>
89
+ </div>
90
+ <!-- 添加GPT过期时间选择 -->
91
+ <div class="mb-4">
92
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="expirationDate">
93
+ GPT过期时间
94
+ </label>
95
+ <input
96
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
97
+ id="expirationDate" type="datetime-local">
98
+ </div>
99
+ <!-- 添加Claude过期时间选择 -->
100
+ <div class="mb-4">
101
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="expirationClaude">
102
+ Claude过期时间
103
+ </label>
104
+ <input
105
+ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
106
+ id="expirationClaude" type="datetime-local">
107
+ </div>
108
+ <div class="flex items-center justify-end">
109
+ <button type="button" onclick="closeModal()"
110
+ class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mr-2">
111
+ 取消
112
+ </button>
113
+ <button type="submit"
114
+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
115
+ 保存
116
+ </button>
117
+ </div>
118
+ </form>
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- 绑定账号模态框 -->
124
+ <div id="bindModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden">
125
+ <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
126
+ <div class="mt-3">
127
+ <h3 class="text-lg font-medium text-gray-900 mb-4">请选择要绑定的账号</h3>
128
+ <form id="bindForm">
129
+ <input type="hidden" id="userId">
130
+ <div class="mb-4">
131
+ <select
132
+ class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
133
+ id="email">
134
+ </select>
135
+ </div>
136
+ <div class="mb-4">
137
+ <select
138
+ class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
139
+ id="claudeEmail">
140
+ </select>
141
+ </div>
142
+ <div class="flex items-center justify-end">
143
+ <button type="button" onclick="closeBindModal()"
144
+ class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mr-2">
145
+ 取消
146
+ </button>
147
+ <button type="submit"
148
+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
149
+ 保存
150
+ </button>
151
+ </div>
152
+ </form>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+
158
+ </div>
159
+ {% endblock %}
160
+ {% block extra_js %}
161
+ <script>
162
+ let users = [];
163
+ const bindModal = document.getElementById('bindModal');
164
+ const modal = document.getElementById('userModal');
165
+ const userForm = document.getElementById('userForm');
166
+ const bindForm = document.getElementById('bindForm');
167
+ const addUserBtn = document.getElementById('addUserBtn');
168
+ const sycGateway = document.getElementById('sycGatewayBtn');
169
+ // 加载用户列表
170
+ function loadUsers() {
171
+ fetch('/api/users')
172
+ .then(response => response.json())
173
+ .then(data => {
174
+ users = data;
175
+ renderUsers();
176
+ })
177
+ .catch(error => console.error('Error:', error));
178
+ }
179
+
180
+ // 渲染用户列表
181
+ function renderUsers() {
182
+ const tbody = document.getElementById('userTableBody');
183
+ tbody.innerHTML = '';
184
+ users.forEach(user => {
185
+ const tr = document.createElement('tr');
186
+ tr.innerHTML = `
187
+ <td class="px-6 py-4 whitespace-nowrap">${user.username}</td>
188
+ <td class="px-6 py-4 whitespace-nowrap">${user.role}</td>
189
+ <td class="px-6 py-4 whitespace-nowrap">${user.bind_email}</td>
190
+ <td class="px-6 py-4 whitespace-nowrap">${user.expiration_time}</td>
191
+ <td class="px-6 py-4 whitespace-nowrap">${user.bind_claude_email}</td>
192
+ <td class="px-6 py-4 whitespace-nowrap">${user.claude_expiration_time}</td>
193
+ <td class="px-6 py-4 whitespace-nowrap">
194
+ <button onclick="bindChatGPT('${user.id}')" class="text-blue-600 hover:text-blue-900 mr-4">绑定GPT</button>
195
+ <button onclick="bindClaude('${user.id}')" class="text-blue-600 hover:text-blue-900 mr-4">绑定Claude</button>
196
+ <button onclick="editUser('${user.id}')" class="text-blue-600 hover:text-blue-900 mr-4">编辑</button>
197
+ <button onclick="deleteUser('${user.id}')" class="text-red-600 hover:text-red-900">删除</button>
198
+ <button onclick="deleteBind('${user.id}')" class="text-red-600 hover:text-red-900">解绑GPT</button>
199
+ <button onclick="deleteClaudeBind('${user.id}')" class="text-red-600 hover:text-red-900">解绑Claude</button>
200
+ </td>
201
+ `;
202
+ tbody.appendChild(tr);
203
+ });
204
+ }
205
+
206
+ // 打开用户模态框
207
+ function openModal(user = null) {
208
+ const form = document.getElementById('userForm');
209
+ if (user) {
210
+ document.getElementById('userId').value = user.id;
211
+ document.getElementById('username').value = user.username;
212
+ document.getElementById('password').value = '';
213
+ document.getElementById('role').value = user.role;
214
+ document.getElementById('expirationDate').value = user.expiration_time;
215
+ document.getElementById('expirationClaude').value = user.claude_expiration_time;
216
+ } else {
217
+ form.reset();
218
+ document.getElementById('userId').value = '';
219
+ }
220
+ modal.classList.remove('hidden');
221
+ }
222
+
223
+ // 关闭用户模态框
224
+ function closeModal() {
225
+ modal.classList.add('hidden');
226
+ }
227
+
228
+ // 编辑用户
229
+ function editUser(userId) {
230
+ const user = users.find(u => u.id === userId);
231
+ if (user) {
232
+ openModal(user);
233
+ }
234
+ }
235
+
236
+ // 打开绑定模态框
237
+ function openBindModal(user = null,type) {
238
+ if(type == 1){
239
+ url = "/api/all_email"
240
+ fetch(url)
241
+ .then(response => response.json())
242
+ .then(data => {
243
+ const emailSelect = document.getElementById('email');
244
+ document.getElementById('email').classList.remove('hidden')
245
+ document.getElementById('claudeEmail').classList.add('hidden')
246
+ emailSelect.innerHTML = '';
247
+ document.getElementById('userId').value = user.id;
248
+ data.forEach(email => {
249
+ const option = document.createElement('option');
250
+ option.value = email;
251
+ option.textContent = email;
252
+ emailSelect.appendChild(option);
253
+ });
254
+
255
+ if (user && user.email) {
256
+ emailSelect.value = user.email;
257
+ }
258
+ })
259
+ .catch(error => console.error('Error:', error));
260
+ }else{
261
+ url = "/api/all_claude_email"
262
+ fetch(url)
263
+ .then(response => response.json())
264
+ .then(data => {
265
+ const emailSelect = document.getElementById('claudeEmail');
266
+ document.getElementById('claudeEmail').classList.remove('hidden')
267
+ document.getElementById('email').classList.add('hidden')
268
+ emailSelect.innerHTML = '';
269
+ document.getElementById('userId').value = user.id;
270
+ data.forEach(email => {
271
+ const option = document.createElement('option');
272
+ option.value = email;
273
+ option.textContent = email;
274
+ emailSelect.appendChild(option);
275
+ });
276
+
277
+ if (user && user.email) {
278
+ emailSelect.value = user.email;
279
+ }
280
+ })
281
+ .catch(error => console.error('Error:', error));
282
+ }
283
+
284
+ bindModal.classList.remove('hidden');
285
+ }
286
+
287
+ // 关闭绑定模态框
288
+ function closeBindModal() {
289
+ bindModal.classList.add('hidden');
290
+ }
291
+
292
+ // 绑定GPT账号
293
+ function bindChatGPT(userId) {
294
+ const user = users.find(u => u.id === userId);
295
+ if (user) {
296
+ openBindModal(user,1);
297
+ }
298
+ }
299
+ // 绑定Claude账号
300
+ function bindClaude(userId) {
301
+ const user = users.find(u => u.id === userId);
302
+ if (user) {
303
+ openBindModal(user,0);
304
+ }
305
+ }
306
+
307
+
308
+ // 删除用户
309
+ function deleteUser(userId) {
310
+ if (confirm('确定要删除这个用户吗?')) {
311
+ fetch(`/api/users/${userId}`, {
312
+ method: 'DELETE'
313
+ })
314
+ .then(response => response.json())
315
+ .then(data => {
316
+ if (data.success) {
317
+ loadUsers();
318
+ }
319
+ })
320
+ .catch(error => console.error('Error:', error));
321
+ }
322
+ }
323
+
324
+ // 解绑GPT账号
325
+ function deleteBind(userId) {
326
+ if (confirm('确定要解除这个用户绑定的ChatGPT账号吗?')) {
327
+ fetch(`/api/del_bind/${userId}`, {
328
+ method: 'DELETE'
329
+ })
330
+ .then(response => response.json())
331
+ .then(data => {
332
+ if (data.success) {
333
+ loadUsers();
334
+ }
335
+ })
336
+ .catch(error => console.error('Error:', error));
337
+ }
338
+ }
339
+
340
+ // 解绑Claude账号
341
+ function deleteClaudeBind(userId) {
342
+ if (confirm('确定要解除这个用户绑定的Claude账号吗?')) {
343
+ fetch(`/api/del_bindClaude/${userId}`, {
344
+ method: 'DELETE'
345
+ })
346
+ .then(response => response.json())
347
+ .then(data => {
348
+ if (data.success) {
349
+ loadUsers();
350
+ }
351
+ })
352
+ .catch(error => console.error('Error:', error));
353
+ }
354
+ }
355
+
356
+ // 表单提交处理
357
+ userForm.addEventListener('submit', function (e) {
358
+ e.preventDefault();
359
+ const userId = document.getElementById('userId').value;
360
+ const userData = {
361
+ username: document.getElementById('username').value,
362
+ password: document.getElementById('password').value,
363
+ expiration_time: document.getElementById('expirationDate').value,
364
+ claude_expiration_time: document.getElementById('expirationClaude').value,
365
+ role: document.getElementById('role').value
366
+ };
367
+
368
+ const url = userId ? `/api/users/${userId}` : '/api/users';
369
+ const method = userId ? 'PUT' : 'POST';
370
+
371
+ fetch(url, {
372
+ method: method,
373
+ headers: {
374
+ 'Content-Type': 'application/json'
375
+ },
376
+ body: JSON.stringify(userData)
377
+ })
378
+ .then(response => response.json())
379
+ .then(data => {
380
+ if (data.success) {
381
+ closeModal();
382
+ loadUsers();
383
+ }
384
+ })
385
+ .catch(error => console.error('Error:', error));
386
+ });
387
+
388
+
389
+ // 表单提交处理
390
+ bindForm.addEventListener('submit', function (e) {
391
+ e.preventDefault();
392
+ const userId = document.getElementById('userId').value;
393
+ const emailSelect = document.getElementById('email');
394
+ const emailSelectClaude = document.getElementById('claudeEmail');
395
+ const bindEmail = {email: emailSelect.value};
396
+ const bindClaudeEmail = {email: emailSelectClaude.value};
397
+
398
+ const method = 'PUT';
399
+ if(document.getElementById('claudeEmail').classList.contains('hidden')){
400
+ url = `/api/bind/${userId}`;
401
+ fetch(url, {
402
+ method: method,
403
+ headers: {
404
+ 'Content-Type': 'application/json'
405
+ },
406
+ body: JSON.stringify(bindEmail)
407
+ })
408
+ .then(response => response.json())
409
+ .then(data => {
410
+ if (data.success) {
411
+ closeBindModal();
412
+ loadUsers();
413
+ }
414
+ })
415
+ .catch(error => console.error('Error:', error));
416
+ }else{
417
+ url = `/api/bindClaude/${userId}`;
418
+ fetch(url, {
419
+ method: method,
420
+ headers: {
421
+ 'Content-Type': 'application/json'
422
+ },
423
+ body: JSON.stringify(bindClaudeEmail)
424
+ })
425
+ .then(response => response.json())
426
+ .then(data => {
427
+ if (data.success) {
428
+ closeBindModal();
429
+ loadUsers();
430
+ }
431
+ })
432
+ .catch(error => console.error('Error:', error));
433
+ }
434
+
435
+ });
436
+
437
+ // 添加用户按钮点击事件
438
+ addUserBtn.addEventListener('click', () => openModal());
439
+
440
+ // 一键同步
441
+ function syc() {
442
+ if (confirm('确定是否同步网关?')) {
443
+ fetch(`/api/syc`, {
444
+ method: 'GET'
445
+ })
446
+ .then(response => response.json())
447
+ .then(data => {
448
+ if (data.success) {
449
+ loadUsers();
450
+ alert('同步成功!'); // 添加弹窗
451
+ } else {
452
+ alert('同步失败!'); // 可选,处理失败情况
453
+ }
454
+ })
455
+ .catch(error => console.error('Error:', error));
456
+ }
457
+ }
458
+
459
+
460
+ // 一键同步按钮点击事件
461
+ sycGateway.addEventListener('click', () => syc());
462
+
463
+
464
+ // 初始加载用户列表
465
+ loadUsers();
466
+ </script>
467
+ {% endblock %}
utils/Logger.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+
3
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)s | %(message)s')
4
+
5
+
6
+ class Logger:
7
+ @staticmethod
8
+ def info(message):
9
+ logging.info(str(message))
10
+
11
+ @staticmethod
12
+ def warning(message):
13
+ logging.warning("\033[0;33m" + str(message) + "\033[0m")
14
+
15
+ @staticmethod
16
+ def error(message):
17
+ logging.error("\033[0;31m" + "-" * 50 + '\n| ' + str(message) + "\033[0m" + "\n" + "└" + "-" * 80)
18
+
19
+ @staticmethod
20
+ def debug(message):
21
+ logging.debug("\033[0;37m" + str(message) + "\033[0m")
22
+
23
+
24
+ logger = Logger()
utils/configs.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from utils.Logger import logger
4
+
5
+ load_dotenv(encoding="ascii")
6
+
7
+
8
+
9
+ secret_key = os.getenv('SECRET_KEY', None)
10
+ authorization = os.getenv('AUTHORIZATION', False)
11
+ domain_chatgpt = os.getenv('DOMAIN_CHATGPT', '')
12
+ domain_claude = os.getenv('DOMAIN_CLAUDE', '')
13
+
14
+
15
+ logger.info("-" * 60)
16
+ logger.info(f"Chat-Share | https://github.com/h88782481/Chat-Share")
17
+ logger.info("-" * 60)
18
+ logger.info("Environment variables:")
19
+ logger.info("SECRET_KEY: " + str(secret_key))
20
+ logger.info("AUTHORIZATION: " + str(authorization))
21
+ logger.info("DOMAIN_CHATGPT: " + str(domain_chatgpt))
22
+ logger.info("DOMAIN_CLAUDE: " + str(domain_claude))
23
+ logger.info("-" * 60)
utils/globals.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from datetime import datetime, timedelta
4
+
5
+ DATA_FOLDER = "data"
6
+ AUTO_REFRESH_CONFIG = os.path.join(DATA_FOLDER, "auto_refresh_config.json")
7
+ CHAT_TOKEN = os.path.join(DATA_FOLDER, "chatToken.json")
8
+ FAILED_TOKENS = os.path.join(DATA_FOLDER, "failed_tokens.json")
9
+ REFRESH_HISTORY = os.path.join(DATA_FOLDER, "refresh_history.json")
10
+ CLAUDE_TOKEN = os.path.join(DATA_FOLDER, "claudeToken.json")
11
+ USERS = os.path.join(DATA_FOLDER, "users.json")
12
+
13
+ auto_refresh_config = {}
14
+ chatToken = []
15
+ failed_tokens = []
16
+ refresh_history = []
17
+ users = []
18
+ cluadeToken = []
19
+
20
+ # 上一级目录
21
+ if not os.path.exists(DATA_FOLDER):
22
+ os.makedirs(DATA_FOLDER)
23
+
24
+ # 读取 auto_refresh_config.json 文件
25
+ if os.path.exists(AUTO_REFRESH_CONFIG):
26
+ with open(AUTO_REFRESH_CONFIG, "r") as f:
27
+ try:
28
+ auto_refresh_config = json.load(f)
29
+ except:
30
+ auto_refresh_config = {"auto_refresh_enabled": False, "refresh_interval_days": 9, "next_refresh_time": None}
31
+ else:
32
+ auto_refresh_config = {"auto_refresh_enabled": False, "refresh_interval_days": 9, "next_refresh_time": None}
33
+
34
+ # 如果 next_refresh_time 为 None,则设置为当前时间加 9 天
35
+ if auto_refresh_config["next_refresh_time"] is None:
36
+ next_refresh_time = datetime.now() + timedelta(days=9)
37
+ auto_refresh_config["next_refresh_time"] = next_refresh_time.isoformat() # 转换为 ISO 8601 格式字符串
38
+
39
+
40
+ # 读取 chatToken.json 文件
41
+ if os.path.exists(CHAT_TOKEN):
42
+ with open(CHAT_TOKEN, "r") as f:
43
+ try:
44
+ chatToken = json.load(f)
45
+ except:
46
+ chatToken = []
47
+ else:
48
+ chatToken = []
49
+
50
+
51
+ # 读取 claudeToken.json 文件
52
+ if os.path.exists(CLAUDE_TOKEN):
53
+ with open(CLAUDE_TOKEN, "r") as f:
54
+ try:
55
+ cluadeToken = json.load(f)
56
+ except:
57
+ cluadeToken = []
58
+ else:
59
+ cluadeToken = []
60
+
61
+
62
+ # 读取 failed_tokens.json 文件
63
+ if os.path.exists(FAILED_TOKENS):
64
+ with open(FAILED_TOKENS, "r") as f:
65
+ try:
66
+ failed_tokens = json.load(f)
67
+ except:
68
+ failed_tokens = []
69
+ else:
70
+ failed_tokens = []
71
+
72
+ # 读取刷新历史
73
+ if os.path.exists(REFRESH_HISTORY):
74
+ with open(REFRESH_HISTORY, "r") as f:
75
+ try:
76
+ refresh_history = json.load(f)
77
+ except:
78
+ refresh_history = []
79
+ else:
80
+ refresh_history = []
81
+
82
+ # 加载用户表
83
+ if os.path.exists(USERS):
84
+ with open(USERS, "r", encoding="utf-8") as f:
85
+ try:
86
+ users = json.load(f)
87
+ except:
88
+ users = []
89
+ else:
90
+ users = []
91
+
92
+ # 如果 users 为空,设置为默认用户
93
+ if not users:
94
+ users = [
95
+ {
96
+ "id": "d87a64eb-3eda-4f87-9268-d79387d1dfe6",
97
+ "username": "admin",
98
+ "password": "pbkdf2:sha256:260000$CqvLDzNaezUTatZ2$61963529d02a0c0eb74212775872a910a1315d160c4df11da528ad3c03a5ea85",
99
+ "role": "admin",
100
+ "bind_token": "",
101
+ "bind_email": "",
102
+ "expiration_time": "",
103
+ "bind_claude_token": "",
104
+ "bind_claude_email": "",
105
+ "claude_expiration_time": ""
106
+ }
107
+ ]
108
+
109
+ # 保存更新后的 chatToken.json 文件
110
+ def save_retoken(updated_tokens):
111
+ with open('data/chatToken.json', 'w') as f:
112
+ json.dump(updated_tokens, f, indent=4)
113
+
114
+ # 保存更新后的 claudeToken.json 文件
115
+ def save_cltoken(updated_tokens):
116
+ with open('data/claudeToken.json', 'w') as f:
117
+ json.dump(updated_tokens, f, indent=4)
118
+
119
+ # 写入 failed_tokens.json 文件
120
+ def save_failed_tokens(failed_tokens):
121
+ with open('data/failed_tokens.json', 'w') as f:
122
+ json.dump(failed_tokens, f, indent=4)
123
+
124
+ # 保存定时任务信息
125
+ def save_auto_refresh_config(config):
126
+ with open('data/auto_refresh_config.json', 'w') as f:
127
+ json.dump(config, f)
128
+
129
+
130
+ # 保存刷新历史
131
+ def save_refresh_history(history):
132
+ with open('data/refresh_history.json', 'w') as f:
133
+ json.dump(history, f, indent=4)
134
+
135
+
136
+ # 保存用户信息
137
+ def save_users(users):
138
+ with open('data/users.json', 'w', encoding='utf-8') as f:
139
+ json.dump(users, f, ensure_ascii=False, indent=2)
140
+
141
+ save_users(users)
utils/tools.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import requests
3
+ from flask import redirect, url_for, session, flash
4
+ from functools import wraps
5
+ import utils.configs as configs
6
+ import utils.globals as globals
7
+ from utils.globals import *
8
+ from utils.tools import *
9
+
10
+
11
+ # 刷新 access_token 的主函数
12
+ def refresh_access_tokens():
13
+ globals.failed_tokens = []
14
+ # 遍历 refresh_token 列表
15
+ for token_info in globals.chatToken:
16
+ email = token_info['email']
17
+ refresh_token = token_info['refresh_token']
18
+
19
+ # 如果 refresh_token 为空,跳过这一行
20
+ if not refresh_token:
21
+ continue
22
+
23
+ try:
24
+
25
+ # 使用 POST 请求通过 refresh_token 获取 access_token
26
+ url = "https://auth0.openai.com/oauth/token"
27
+ headers = {"Content-Type": "application/json"}
28
+ data = {
29
+ "redirect_uri": "com.openai.chat://auth0.openai.com/ios/com.openai.chat/callback",
30
+ "grant_type": "refresh_token",
31
+ "client_id": "pdlLIX2Y72MIl2rhLhTE9VV9bN905kBh",
32
+ "refresh_token": refresh_token
33
+ }
34
+ response = requests.post(url, headers=headers, data=json.dumps(data))
35
+ response_data = response.json()
36
+
37
+ access_token = response_data.get("access_token")
38
+ if access_token: # 如果成功获取到 access_token
39
+ # 更新 access_token 和状态为 True
40
+ for i, user in enumerate(globals.users):
41
+ if user['bind_email'] == email:
42
+ globals.users[i]['bind_token'] = access_token
43
+ set_seedmap(globals.users[i]['id'],access_token)
44
+ save_users(globals.users)
45
+ token_info['access_token'] = access_token
46
+ token_info['status'] = True
47
+ else:
48
+ # 如果获取失败,设置状态为 False
49
+ token_info['status'] = False
50
+ globals.failed_tokens.append(token_info)
51
+
52
+ except Exception as e:
53
+ # 捕获请求错误并记录失败的 token,状态为 False
54
+ token_info['status'] = False
55
+ globals.failed_tokens.append(token_info)
56
+
57
+ # 保存更新后的 retoken 数据
58
+ save_retoken(globals.chatToken)
59
+
60
+ # 如果有失败的 token,记录到 failed_tokens.json
61
+ save_failed_tokens(globals.failed_tokens)
62
+
63
+ return globals.chatToken
64
+
65
+
66
+
67
+ # 获取登陆链接
68
+ def getoauth(seed_token):
69
+ domain = configs.domain_chatgpt
70
+
71
+ url = f'{domain}/?token={seed_token}'
72
+ try:
73
+ return url
74
+ except requests.RequestException as e:
75
+ return None
76
+
77
+ # 验证是否登录的装饰器
78
+ def login_required(f):
79
+ @wraps(f)
80
+ def decorated_function(*args, **kwargs):
81
+ if not session.get('logged_in'):
82
+ flash('请先登录。', 'warning')
83
+ return redirect(url_for('login'))
84
+ return f(*args, **kwargs)
85
+ return decorated_function
86
+
87
+ # 验证是否为管理员的装饰器
88
+ def admin_required(f):
89
+ @wraps(f)
90
+ def decorated_function(*args, **kwargs):
91
+ if not session.get('logged_in'):
92
+ flash('请先登录。', 'warning')
93
+ return redirect(url_for('login'))
94
+ if session.get('role') != 'admin':
95
+ flash('需要管理员权限。', 'danger')
96
+ return redirect(url_for('index'))
97
+ return f(*args, **kwargs)
98
+ return decorated_function
99
+
100
+ # 设置网关seedmap
101
+ def set_seedmap(user_id,token):
102
+
103
+ domain = configs.domain_chatgpt
104
+
105
+ url = f'{domain}/seedtoken'
106
+ headers = {
107
+ "Authorization": f"Bearer {configs.authorization}",
108
+ "Content-Type": "application/json"
109
+ }
110
+ data = {
111
+ "seed": user_id,
112
+ "token": token,
113
+ }
114
+ response = requests.post(url, headers=headers, data=json.dumps(data))
115
+ return response.status_code
116
+
117
+
118
+ # 删除网关seedmap
119
+ def del_seedmap(user_id):
120
+
121
+ domain = configs.domain_chatgpt
122
+
123
+ url = f'{domain}/seedtoken'
124
+ headers = {
125
+ "Authorization": f"Bearer {configs.authorization}",
126
+ "Content-Type": "application/json"
127
+ }
128
+ data = {
129
+ "seed": user_id
130
+ }
131
+ response = requests.delete(url, headers=headers, data=json.dumps(data))
132
+ return response.status_code
133
+
134
+ # 获取Claude登陆链接
135
+ def get_claude_login_url(session_key,uname):
136
+ domain = configs.domain_claude
137
+ url = f'{domain}/manage-api/auth/oauth_token'
138
+
139
+ # 请求体参数
140
+ data = {
141
+ 'session_key': session_key,
142
+ 'unique_name': uname, # 生成唯一标识符
143
+ "expires_in": 3600 #过期时间1小时
144
+ }
145
+
146
+ # 设置请求头
147
+ headers = {'Content-Type': 'application/json'}
148
+
149
+ try:
150
+ # 发送 POST 请求
151
+ response = requests.post(url, headers=headers, data=json.dumps(data))
152
+
153
+ # 检查响应状态码是否为200
154
+ if response.status_code == 200:
155
+ response_data = response.json()
156
+
157
+ # 检查 'login_url' 是否存在
158
+ if 'login_url' in response_data:
159
+ login_url = response_data['login_url']
160
+
161
+ # 如果URL没有以http开头,拼接基础URL
162
+ if not login_url.startswith('http'):
163
+ return f'{domain}' + login_url
164
+ return login_url
165
+
166
+ # 如果状态码不是200或login_url不存在,返回None
167
+ return None
168
+
169
+ except requests.RequestException as e:
170
+ # 捕获异常并返回错误信息
171
+ return None