137 lines
4.5 KiB
Python
137 lines
4.5 KiB
Python
from flask import Blueprint, render_template, redirect, request, Response
|
||
import requests
|
||
|
||
main_bp = Blueprint('main', __name__)
|
||
|
||
@main_bp.route('/')
|
||
def index():
|
||
from models import SiteConfig
|
||
config = {}
|
||
configs = SiteConfig.query.all()
|
||
for c in configs:
|
||
config[c.config_key] = c.config_value
|
||
return render_template('index.html', config=config)
|
||
|
||
@main_bp.route('/api-docs')
|
||
def api_docs():
|
||
"""API 文档页面"""
|
||
return render_template('api_docs.html')
|
||
|
||
|
||
@main_bp.route('/favicon.ico')
|
||
def favicon():
|
||
"""处理 favicon 请求"""
|
||
from models import SiteConfig
|
||
favicon_config = SiteConfig.query.filter_by(config_key='site_favicon').first()
|
||
if favicon_config and favicon_config.config_value:
|
||
return redirect(favicon_config.config_value)
|
||
# 如果没有设置 favicon,尝试使用 logo
|
||
logo_config = SiteConfig.query.filter_by(config_key='site_logo').first()
|
||
if logo_config and logo_config.config_value:
|
||
return redirect(logo_config.config_value)
|
||
# 返回空响应避免 404
|
||
return '', 204
|
||
|
||
|
||
@main_bp.route('/proxy/download')
|
||
def proxy_download():
|
||
"""代理下载视频/图片,绕过防盗链,支持 Range 请求"""
|
||
url = request.args.get('url')
|
||
if not url:
|
||
return 'Missing url parameter', 400
|
||
|
||
# 限制只能代理特定域名
|
||
allowed_domains = [
|
||
'weibocdn.com', 'sinaimg.cn', 'weibo.com',
|
||
'ppxvod.com', 'byteimg.com', 'pipix.com', '365yg.com', 'ixigua.com',
|
||
'kwimgs.com', 'kwaicdn.com', 'kuaishou.com', 'gifshow.com', 'yximgs.com',
|
||
'douyinvod.com', 'douyin.com', 'tiktokcdn.com', 'bytedance.com',
|
||
'bilivideo.com', 'bilibili.com', 'hdslb.com',
|
||
'zjcdn.com', 'douyinpic.com'
|
||
]
|
||
|
||
from urllib.parse import urlparse
|
||
parsed = urlparse(url)
|
||
domain = parsed.netloc.lower()
|
||
|
||
is_allowed = any(d in domain for d in allowed_domains)
|
||
if not is_allowed:
|
||
return f'Domain not allowed: {domain}', 403
|
||
|
||
# 根据域名设置不同的 Referer
|
||
referer_map = {
|
||
'weibo': 'https://weibo.com/',
|
||
'sina': 'https://weibo.com/',
|
||
'pipix': 'https://www.pipix.com/',
|
||
'365yg': 'https://www.pipix.com/',
|
||
'ixigua': 'https://www.ixigua.com/',
|
||
'kuaishou': 'https://www.kuaishou.com/',
|
||
'kwimgs': 'https://www.kuaishou.com/',
|
||
'kwaicdn': 'https://www.kuaishou.com/',
|
||
'yximgs': 'https://www.kuaishou.com/',
|
||
'douyin': 'https://www.douyin.com/',
|
||
'bytedance': 'https://www.douyin.com/',
|
||
'zjcdn': 'https://www.douyin.com/',
|
||
'douyinpic': 'https://www.douyin.com/',
|
||
'bilibili': 'https://www.bilibili.com/',
|
||
'hdslb': 'https://www.bilibili.com/',
|
||
}
|
||
|
||
referer = 'https://www.google.com/'
|
||
for key, ref in referer_map.items():
|
||
if key in domain:
|
||
referer = ref
|
||
break
|
||
|
||
try:
|
||
headers = {
|
||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||
'Referer': referer
|
||
}
|
||
|
||
# 转发 Range 请求头,支持视频进度条拖动
|
||
range_header = request.headers.get('Range')
|
||
if range_header:
|
||
headers['Range'] = range_header
|
||
|
||
resp = requests.get(url, headers=headers, stream=True, timeout=30)
|
||
|
||
# 获取文件类型
|
||
content_type = resp.headers.get('Content-Type', 'application/octet-stream')
|
||
|
||
# 设置下载文件名
|
||
if 'video' in content_type:
|
||
filename = f'video_{int(__import__("time").time())}.mp4'
|
||
elif 'image' in content_type:
|
||
filename = f'image_{int(__import__("time").time())}.jpg'
|
||
else:
|
||
filename = f'file_{int(__import__("time").time())}'
|
||
|
||
def generate():
|
||
for chunk in resp.iter_content(chunk_size=8192):
|
||
yield chunk
|
||
|
||
# 构建响应头
|
||
response_headers = {
|
||
'Content-Type': content_type,
|
||
'Accept-Ranges': 'bytes'
|
||
}
|
||
|
||
# 转发相关响应头
|
||
if resp.headers.get('Content-Length'):
|
||
response_headers['Content-Length'] = resp.headers.get('Content-Length')
|
||
if resp.headers.get('Content-Range'):
|
||
response_headers['Content-Range'] = resp.headers.get('Content-Range')
|
||
|
||
# 根据是否是 Range 请求返回不同状态码
|
||
status_code = resp.status_code
|
||
|
||
return Response(
|
||
generate(),
|
||
status=status_code,
|
||
headers=response_headers
|
||
)
|
||
|
||
except Exception as e:
|
||
return f'Download failed: {str(e)}', 500
|