|
import os
|
|
from flask import Flask, request, jsonify, render_template_string, redirect, url_for, session, render_template
|
|
import config
|
|
import data_manager
|
|
import scheduler_jobs
|
|
import api_client
|
|
import requests
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = config.SECRET_KEY
|
|
app.permanent_session_lifetime = config.PERMANENT_SESSION_LIFETIME
|
|
|
|
LOGIN_FORM_HTML = """
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>访问授权</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100vh;
|
|
margin: 0;
|
|
background-color: #fff;
|
|
color: #000;
|
|
}
|
|
.login-container {
|
|
background-color: #fff;
|
|
padding: 40px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 8px 30px rgba(0,0,0,0.1);
|
|
width: 100%;
|
|
max-width: 360px;
|
|
border: 1px solid #eaeaea;
|
|
}
|
|
h2 {
|
|
text-align: center;
|
|
color: #000;
|
|
font-weight: 600;
|
|
margin-bottom: 30px;
|
|
}
|
|
label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
color: #444;
|
|
font-size: 14px;
|
|
}
|
|
input[type="password"] {
|
|
width: 100%;
|
|
padding: 12px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 6px;
|
|
box-sizing: border-box;
|
|
background-color: #fff;
|
|
color: #000;
|
|
font-size: 16px;
|
|
}
|
|
input[type="password"]:focus {
|
|
border-color: #999;
|
|
outline: none;
|
|
box-shadow: 0 0 0 2px rgba(0,0,0,0.05);
|
|
}
|
|
input[type="submit"] {
|
|
width: 100%;
|
|
padding: 12px;
|
|
background-color: #000;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
input[type="submit"]:hover {
|
|
background-color: #333;
|
|
}
|
|
.error {
|
|
color: #e53e3e;
|
|
text-align: center;
|
|
margin-bottom: 15px;
|
|
font-size: 14px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="login-container">
|
|
<h2>授权访问</h2>
|
|
{% if error %}
|
|
<p class="error">{{ error }}</p>
|
|
{% endif %}
|
|
<form method="post">
|
|
<label for="password">访问密码:</label>
|
|
<input type="password" id="password" name="password" required autofocus>
|
|
<input type="submit" value="继续">
|
|
</form>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
@app.route('/', methods=['GET', 'POST'])
|
|
def login_frontend():
|
|
if not config.FRONTEND_PASSWORD:
|
|
return "错误: 前端密码 (PASSWORD 环境变量) 未设置。", 500
|
|
|
|
if 'logged_in' in session and session['logged_in']:
|
|
return redirect(url_for('dashboard'))
|
|
|
|
error = None
|
|
if request.method == 'POST':
|
|
entered_password = request.form.get('password')
|
|
if entered_password == config.FRONTEND_PASSWORD:
|
|
accounts_list = data_manager.get_accounts_from_config()
|
|
if not accounts_list:
|
|
error = "配置错误:未找到账户信息。"
|
|
return render_template_string(LOGIN_FORM_HTML, error=error)
|
|
|
|
first_account_email = accounts_list[0]['email']
|
|
first_account_password = accounts_list[0]['password']
|
|
|
|
jwt_token, login_api_error = api_client.api_login(first_account_email, first_account_password)
|
|
|
|
if login_api_error:
|
|
error = f"API 登录失败: {login_api_error}"
|
|
return render_template_string(LOGIN_FORM_HTML, error=error)
|
|
|
|
if not jwt_token:
|
|
error = "API 登录成功但未能获取 Token。"
|
|
return render_template_string(LOGIN_FORM_HTML, error=error)
|
|
|
|
session['logged_in'] = True
|
|
session['jwt_token'] = jwt_token
|
|
session.permanent = True
|
|
return redirect(url_for('dashboard'))
|
|
else:
|
|
error = "密码错误!"
|
|
return render_template_string(LOGIN_FORM_HTML, error=error)
|
|
|
|
@app.route('/dashboard')
|
|
def dashboard():
|
|
if not ('logged_in' in session and session['logged_in']):
|
|
return redirect(url_for('login_frontend'))
|
|
return render_template_string(open('templates/dashboard.html', encoding='utf-8').read())
|
|
|
|
@app.route('/dashboard/statements')
|
|
def account_statements_page():
|
|
if not ('logged_in' in session and session['logged_in']):
|
|
return redirect(url_for('login_frontend'))
|
|
|
|
account_email_param = request.args.get('email')
|
|
if not account_email_param:
|
|
return redirect(url_for('dashboard'))
|
|
|
|
return render_template('account_statements.html', account_email=account_email_param)
|
|
|
|
@app.route('/logout')
|
|
def logout():
|
|
session.pop('logged_in', None)
|
|
return redirect(url_for('login_frontend'))
|
|
|
|
@app.route('/api/data', methods=['GET'])
|
|
def get_all_data():
|
|
if not ('logged_in' in session and session['logged_in']):
|
|
return jsonify({"error": "未授权访问"}), 401
|
|
|
|
display_data = data_manager.get_accounts_data_for_display()
|
|
return jsonify(display_data)
|
|
|
|
@app.route('/api/account_statements', methods=['GET'])
|
|
def get_account_statements():
|
|
if not ('logged_in' in session and session['logged_in']):
|
|
return jsonify({"error": "未授权访问"}), 401
|
|
|
|
email_param = request.args.get('email')
|
|
if not email_param:
|
|
return jsonify({"error": "缺少 'email' 参数"}), 400
|
|
|
|
page = request.args.get('page', 1, type=int)
|
|
size = request.args.get('size', 10000, type=int)
|
|
|
|
token_to_use = None
|
|
target_account_password = None
|
|
|
|
all_accounts = data_manager.get_accounts_from_config()
|
|
for acc in all_accounts:
|
|
if acc.get('email') == email_param:
|
|
target_account_password = acc.get('password')
|
|
break
|
|
|
|
if not target_account_password:
|
|
return jsonify({"error": f"未找到账户 {email_param} 的配置信息或密码。"}), 404
|
|
|
|
print(f"为特定账户 {email_param} 获取账单,尝试重新登录获取专属 token...")
|
|
specific_token, login_error = api_client.api_login(email_param, target_account_password)
|
|
|
|
if login_error:
|
|
print(f"为账户 {email_param} 获取专属 token 失败: {login_error}")
|
|
return jsonify({"error": f"为账户 {email_param} 获取账单时登录失败: {login_error}"}), 500
|
|
|
|
if not specific_token:
|
|
return jsonify({"error": f"为账户 {email_param} 获取账单时未能获取专属 API token。"}), 500
|
|
|
|
token_to_use = specific_token
|
|
print(f"成功获取账户 {email_param} 的专属 token,将用于获取账单。")
|
|
|
|
statement_data, error = api_client.get_statement_records(token_to_use, page, size)
|
|
|
|
if error:
|
|
return jsonify({"error": f"获取账户 {email_param} 的账单失败: {error}"}), 500
|
|
|
|
return jsonify(statement_data)
|
|
|
|
@app.route('/api/account_summary_statistics', methods=['GET'])
|
|
def get_account_summary_statistics():
|
|
if not ('logged_in' in session and session['logged_in']):
|
|
return jsonify({"error": "未授权访问"}), 401
|
|
|
|
email_param = request.args.get('email')
|
|
if not email_param:
|
|
return jsonify({"error": "缺少 'email' 参数"}), 400
|
|
|
|
target_account_password = None
|
|
all_accounts = data_manager.get_accounts_from_config()
|
|
for acc in all_accounts:
|
|
if acc.get('email') == email_param:
|
|
target_account_password = acc.get('password')
|
|
break
|
|
|
|
if not target_account_password:
|
|
return jsonify({"error": f"未找到账户 {email_param} 的配置信息或密码。"}), 404
|
|
|
|
print(f"为特定账户 {email_param} 获取统计摘要,尝试重新登录获取专属 token...")
|
|
specific_token, login_error = api_client.api_login(email_param, target_account_password)
|
|
|
|
if login_error:
|
|
print(f"为账户 {email_param} 获取专属 token 失败: {login_error}")
|
|
return jsonify({"error": f"为账户 {email_param} 获取统计摘要时登录失败: {login_error}"}), 500
|
|
|
|
if not specific_token:
|
|
return jsonify({"error": f"为账户 {email_param} 获取统计摘要时未能获取专属 API token。"}), 500
|
|
|
|
token_to_use = specific_token
|
|
print(f"成功获取账户 {email_param} 的专属 token,将用于获取统计摘要。")
|
|
|
|
external_api_url = "https://api-card.infini.money/user/statement/statistics"
|
|
headers = {
|
|
"Authorization": f"Bearer {token_to_use}",
|
|
"User-Agent": "InfiniDashboard/1.0"
|
|
}
|
|
|
|
try:
|
|
response = requests.get(external_api_url, headers=headers, timeout=10)
|
|
response.raise_for_status()
|
|
|
|
api_response_data = response.json()
|
|
|
|
return jsonify(api_response_data)
|
|
|
|
except requests.exceptions.HTTPError as http_err:
|
|
print(f"HTTP error occurred while fetching statistics for {email_param}: {http_err}")
|
|
try:
|
|
error_content = http_err.response.json()
|
|
return jsonify({"error": f"获取统计摘要失败 (API错误): {error_content.get('message', str(http_err))}", "details": error_content}), http_err.response.status_code
|
|
except:
|
|
return jsonify({"error": f"获取统计摘要失败: {str(http_err)}"}), getattr(http_err.response, 'status_code', 500)
|
|
except requests.exceptions.RequestException as req_err:
|
|
print(f"Request error occurred while fetching statistics for {email_param}: {req_err}")
|
|
return jsonify({"error": f"获取统计摘要时发生网络请求错误: {str(req_err)}"}), 503
|
|
except Exception as e:
|
|
print(f"An unexpected error occurred while fetching statistics for {email_param}: {e}")
|
|
return jsonify({"error": f"获取统计摘要时发生未知错误: {str(e)}"}), 500
|
|
|
|
|
|
@app.route('/api/refresh', methods=['POST'])
|
|
def manual_refresh_all_data():
|
|
if not ('logged_in' in session and session['logged_in']):
|
|
return jsonify({"error": "未授权访问"}), 401
|
|
|
|
scheduler_jobs.manual_refresh_all_data_job()
|
|
return jsonify({"message": "刷新任务已启动,请稍后查看数据。"}), 202
|
|
|
|
if __name__ == '__main__':
|
|
if not config.FRONTEND_PASSWORD:
|
|
print("警告: PASSWORD 环境变量未设置,前端将无法登录。程序将尝试继续,但部分功能可能受限。")
|
|
|
|
if scheduler_jobs.initialize_scheduler(is_gunicorn=False):
|
|
is_hf_space = os.getenv("SPACE_ID") is not None
|
|
if not is_hf_space:
|
|
print("在本地环境运行 Flask 开发服务器...")
|
|
app.run(host='0.0.0.0', port=int(os.getenv("PORT", 7860)), debug=False)
|
|
else:
|
|
print("检测到在 Hugging Face Space 环境中运行。假定由 Gunicorn 等 WSGI 服务器启动。")
|
|
else:
|
|
print("定时任务初始化失败,程序退出。")
|
|
|
|
|
|
else:
|
|
print("在 Gunicorn 环境下初始化应用...")
|
|
if not config.FRONTEND_PASSWORD:
|
|
print("Gunicorn警告: PASSWORD 环境变量未设置,前端将无法登录。")
|
|
|
|
if not scheduler_jobs.initialize_scheduler(is_gunicorn=True):
|
|
print("Gunicorn: 定时任务初始化失败。")
|
|
|