app.py
Rama main · réplica de lectura del repositorio original.
import json
import logging
import os
from datetime import datetime
from src.configs.logging_setup import setup_logging
setup_logging()
logger = logging.getLogger(__name__)
from flask import Flask, render_template, jsonify, request, redirect, url_for, send_from_directory
from src.routes.api_config_routes import api_config_bp
from src.routes.search_routes import search_bp
from src.routes.resource_routes import resources_bp
from src.routes.auth_routes import auth_bp
from src.routes.system_config_routes import system_config_bp
from src.routes.plugin_routes import plugin_bp
from src.routes.search_sources_routes import search_sources_bp
from src.routes.dashboard_routes import dashboard_bp
from src.routes.api_v1_routes import api_v1_bp
from src.routes.log_routes import log_bp
from src.configs.app_config import SECRET_KEY
from src.db.connection import init_sqlite_db
from src.services.scheduler_service import start_scheduler
from src.services.system_config_service import (
get_frontend_link_mode,
get_frontend_display_netdisk_config,
get_frontend_link_check_config,
get_dynamic_transfer_netdisk_config,
is_excel_download_enabled,
is_pc_qr_code_enabled,
is_frontend_github_enabled,
get_frontend_github_config,
is_frontend_enabled,
is_api_only_enabled,
get_active_theme,
get_site_logo,
get_site_name,
build_frontend_seo_meta,
is_resource_request_enabled,
is_hot_searches_enabled,
get_hot_search_words,
get_frontend_disclaimer_text,
get_frontend_copyright_text,
get_frontend_seo_config,
is_frontend_footer_text_enabled,
is_frontend_disclaimer_enabled,
is_frontend_copyright_enabled,
)
from src.services.theme_service import theme_static_bp
from src.utils.netdisk_utils import get_netdisk_drive_type
import jinja2
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
# 配置多主题模板加载器支持
themes_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "themes")
app.jinja_loader = jinja2.ChoiceLoader([
app.jinja_loader,
jinja2.FileSystemLoader(themes_path),
])
app.secret_key = SECRET_KEY
FRONTEND_NETDISK_ICONS = {
"夸克网盘": "kuake-dark.svg",
"百度网盘": "icon_baiduwangpan-dark.svg",
"阿里云盘": "ali-dark.svg",
"迅雷网盘": "xunlei1-dark.svg",
"UC网盘": "changyonglogo31-dark.svg",
"光鸭云盘": "guangya-dark.svg",
"悟空网盘": "wukong-dark.svg",
"移动云盘": "yidong-dark.svg",
"磁力链接": "zhongzi-dark.svg",
"迅雷链接": "zhongzi-dark.svg",
"电驴链接": "zhongzi-dark.svg",
}
def get_frontend_theme_netdisks(limit=9, include_icons=True):
"""按后台配置顺序准备前台网盘标签;默认最多展示九项。"""
enabled_netdisks = get_frontend_display_netdisk_config().get("enabled_netdisks", [])
if limit is not None:
enabled_netdisks = enabled_netdisks[:limit]
result = []
for name in enabled_netdisks:
label = name
for suffix in ("网盘", "云盘", "链接"):
if label.endswith(suffix):
label = label[:-len(suffix)]
break
icon = FRONTEND_NETDISK_ICONS.get(name) if include_icons else None
result.append({
"name": name,
"label": label or name,
"drive": get_netdisk_drive_type(name),
"icon": f"/themes/aurora/assets/{icon}" if icon else "",
})
return result
# 初始化 SQLite 数据库与表结构
init_sqlite_db()
# 初始化管理员账号认证配置(初次启动哈希落库)
from src.services.admin_auth_service import init_admin_auth, is_using_default_password, get_admin_username
init_admin_auth()
# 注册蓝图
app.register_blueprint(theme_static_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(dashboard_bp)
app.register_blueprint(api_config_bp)
app.register_blueprint(search_bp)
app.register_blueprint(resources_bp)
app.register_blueprint(system_config_bp)
app.register_blueprint(plugin_bp)
app.register_blueprint(search_sources_bp)
app.register_blueprint(api_v1_bp)
app.register_blueprint(log_bp)
start_scheduler()
# 上下文处理器,将登录状态传递给所有模板
@app.context_processor
def inject_login_status():
token = request.cookies.get('token')
is_logged_in = False
try:
if token:
import jwt
jwt.decode(token, app.secret_key, algorithms=['HS256'])
is_logged_in = True
except Exception:
pass
is_default_pwd = False
admin_name = 'Admin'
if is_logged_in:
try:
is_default_pwd = is_using_default_password()
admin_name = get_admin_username()
except Exception:
pass
frontend_seo_config = get_frontend_seo_config()
return {
'is_logged_in': is_logged_in,
'enable_frontend_github': is_frontend_github_enabled(),
'enable_frontend_footer_text': is_frontend_footer_text_enabled(),
'frontend_footer_text': get_frontend_github_config().get('footer_text', 'Pan-Relay 发现海量网盘资源'),
'enable_frontend_disclaimer': is_frontend_disclaimer_enabled(),
'frontend_disclaimer_text': get_frontend_disclaimer_text(),
'enable_frontend_copyright': is_frontend_copyright_enabled(),
'frontend_copyright_text': get_frontend_copyright_text(),
'frontend_friend_links': frontend_seo_config.get('friend_links', {'enabled': False, 'items': []}),
'frontend_announcement': frontend_seo_config.get('announcement', {'enabled': True, 'text': '每日持续更新,热门影视与网盘资源一站搜索。'}),
'is_default_admin_password': is_default_pwd,
'admin_username': admin_name,
'site_logo': get_site_logo(),
'site_name': get_site_name(),
'current_year': datetime.now().year,
'enable_resource_request': is_resource_request_enabled(),
'enable_hot_searches': is_hot_searches_enabled(),
'hot_search_words': get_hot_search_words(),
}
@app.before_request
def check_ui_access():
path = request.path
# 若试图访问前台根路径但前台 UI 已关闭,自动重定向至后台管理登录页
if path == "/" and not is_frontend_enabled():
return redirect(url_for('auth.login'))
# 首页,支持根据当前激活主题渲染不同模板
@app.route('/')
def search_index():
if not is_frontend_enabled():
return redirect(url_for('auth.login'))
active_theme = get_active_theme()
link_mode = get_frontend_link_mode()
link_check_config = get_frontend_link_check_config()
dynamic_transfer_config = get_dynamic_transfer_netdisk_config()
search_term = request.args.get('keyword') or request.args.get('q') or request.args.get('kw') or ''
ctx = {
'seo_meta': build_frontend_seo_meta('search' if search_term.strip() else 'home', search_term),
'frontend_link_mode': link_mode,
'allow_excel_download': (link_mode != "view") and is_excel_download_enabled(),
'enable_link_check': link_check_config.get("enable_link_check", True),
'enabled_check_pans': link_check_config.get("enabled_check_pans", []),
'enabled_dynamic_transfer_pans': dynamic_transfer_config.get("enabled_netdisks", []),
'enable_pc_qr_code': is_pc_qr_code_enabled(),
'enable_frontend_github': is_frontend_github_enabled(),
'frontend_footer_text': get_frontend_github_config().get('footer_text', 'Pan-Relay 发现海量网盘资源'),
'enable_resource_request': is_resource_request_enabled(),
'enable_hot_searches': is_hot_searches_enabled(),
'hot_search_words': get_hot_search_words(),
'frontend_theme_netdisks': (
get_frontend_theme_netdisks(limit=None, include_icons=False)
if active_theme == "cinema"
else get_frontend_theme_netdisks()
),
}
if active_theme == "cinema":
from src.db.resources import get_resource_hall_items
ctx['resource_hall_items'] = get_resource_hall_items(limit=8)
return render_template('cinema/templates/index.html', **ctx)
if active_theme == "aurora":
return render_template('aurora/templates/index.html', **ctx)
return render_template('classic/templates/index.html', **ctx)
# 搜索结果页 (主要用于 aurora 主题或独立搜索 URL)
@app.route('/search')
def search_page():
if not is_frontend_enabled():
return redirect(url_for('auth.login'))
active_theme = get_active_theme()
q = request.args.get('q') or request.args.get('keyword') or ''
link_mode = get_frontend_link_mode()
link_check_config = get_frontend_link_check_config()
if active_theme == "cinema":
# 模板三的侧栏应完整遵循后台勾选项,不受模板二的九项上限影响。
netdisks = get_frontend_theme_netdisks(limit=None, include_icons=False)
return render_template(
'cinema/templates/search.html',
query=q,
seo_meta=build_frontend_seo_meta('search', q),
frontend_link_mode=link_mode,
enable_link_check=link_check_config.get("enable_link_check", True),
enabled_check_pans=link_check_config.get("enabled_check_pans", []),
enable_pc_qr_code=is_pc_qr_code_enabled(),
enable_resource_request=is_resource_request_enabled(),
enable_hot_searches=is_hot_searches_enabled(),
hot_search_words=get_hot_search_words(),
frontend_theme_netdisks=netdisks,
)
if active_theme == "aurora":
return render_template(
'aurora/templates/search.html',
query=q,
seo_meta=build_frontend_seo_meta('search', q),
frontend_link_mode=link_mode,
enable_link_check=link_check_config.get("enable_link_check", True),
enabled_check_pans=link_check_config.get("enabled_check_pans", []),
enable_pc_qr_code=is_pc_qr_code_enabled(),
enable_resource_request=is_resource_request_enabled(),
enable_hot_searches=is_hot_searches_enabled(),
hot_search_words=get_hot_search_words(),
frontend_theme_netdisks=get_frontend_theme_netdisks(),
)
return redirect(url_for('search_index', keyword=q))
# 独立 API 接入文档网页
@app.route('/docs')
def public_api_docs_page():
return render_template('api_docs.html')
# 根路径 Favicon 路由 (消除浏览器自动请求 /favicon.ico 导致的 404,统一返回系统项目图标)
@app.route('/favicon.ico')
def favicon():
return send_from_directory(
os.path.join(app.root_path, 'static', 'images'),
'favicon.ico',
mimetype='image/vnd.microsoft.icon'
)
if __name__ == '__main__':
port = int(os.getenv("PORT", 5004))
logger.info(f"启动 Flask 应用,监听端口: {port}")
app.run(host='0.0.0.0', port=port)