Infini / app.py
yangtb24's picture
Upload 7 files
931e053 verified
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: 定时任务初始化失败。")