mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-11 22:50:45 +08:00
Compare commits
4 Commits
96aa822673
...
87662309a1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87662309a1 | ||
|
|
edc11aaa92 | ||
|
|
4bd7d73514 | ||
|
|
e4ffec9ba4 |
91
app/run.py
91
app/run.py
@ -3521,6 +3521,39 @@ def get_task_suggestions():
|
|||||||
return jsonify({"success": True, "message": f"error: {str(e)}"})
|
return jsonify({"success": True, "message": f"error: {str(e)}"})
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_qoark_redirect(url: str) -> str:
|
||||||
|
"""
|
||||||
|
解析 pan.qoark.cn 链接的重定向地址
|
||||||
|
如果链接是 pan.qoark.cn,则获取重定向后的真实地址
|
||||||
|
如果不是,则直接返回原链接
|
||||||
|
"""
|
||||||
|
if not isinstance(url, str) or "pan.qoark.cn" not in url:
|
||||||
|
return url
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建新的 session 用于重定向解析
|
||||||
|
redirect_session = requests.Session()
|
||||||
|
redirect_session.max_redirects = 10
|
||||||
|
# 只获取头信息,不获取完整内容(HEAD 请求更快)
|
||||||
|
resp = redirect_session.head(url, allow_redirects=True, timeout=10)
|
||||||
|
|
||||||
|
# 如果成功重定向到 pan.quark.cn,返回重定向后的地址
|
||||||
|
if resp.status_code == 200 and "pan.quark.cn" in resp.url:
|
||||||
|
return resp.url
|
||||||
|
# 如果 HEAD 请求失败,尝试 GET 请求(某些服务器可能不支持 HEAD)
|
||||||
|
elif resp.status_code != 200:
|
||||||
|
resp = redirect_session.get(url, allow_redirects=True, timeout=10)
|
||||||
|
if resp.status_code == 200 and "pan.quark.cn" in resp.url:
|
||||||
|
return resp.url
|
||||||
|
|
||||||
|
# 如果重定向失败或没有重定向到 pan.quark.cn,返回原链接
|
||||||
|
return url
|
||||||
|
except Exception as e:
|
||||||
|
# 如果解析失败,返回原链接(避免影响其他功能)
|
||||||
|
logging.warning(f"解析 pan.qoark.cn 重定向失败: {str(e)}")
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
# 获取分享详情接口
|
# 获取分享详情接口
|
||||||
@app.route("/get_share_detail", methods=["GET", "POST"])
|
@app.route("/get_share_detail", methods=["GET", "POST"])
|
||||||
def get_share_detail():
|
def get_share_detail():
|
||||||
@ -3534,6 +3567,10 @@ def get_share_detail():
|
|||||||
else:
|
else:
|
||||||
shareurl = request.json.get("shareurl", "")
|
shareurl = request.json.get("shareurl", "")
|
||||||
stoken = request.json.get("stoken", "")
|
stoken = request.json.get("stoken", "")
|
||||||
|
|
||||||
|
# 解析 pan.qoark.cn 链接的重定向地址
|
||||||
|
if shareurl and "pan.qoark.cn" in shareurl:
|
||||||
|
shareurl = _resolve_qoark_redirect(shareurl)
|
||||||
|
|
||||||
account = Quark("", 0)
|
account = Quark("", 0)
|
||||||
# 设置account的必要属性
|
# 设置account的必要属性
|
||||||
@ -8805,6 +8842,60 @@ def get_tv_list(tv_type, sub_category):
|
|||||||
'data': {'items': []}
|
'data': {'items': []}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/proxy/douban-image")
|
||||||
|
def proxy_douban_image():
|
||||||
|
"""代理豆瓣图片请求,设置正确的 Referer 头以绕过防盗链限制"""
|
||||||
|
try:
|
||||||
|
image_url = request.args.get('url')
|
||||||
|
if not image_url:
|
||||||
|
return Response('缺少图片URL参数', status=400, mimetype='text/plain')
|
||||||
|
|
||||||
|
# 验证URL是否为豆瓣图片地址
|
||||||
|
if not (image_url.startswith('http://') or image_url.startswith('https://')):
|
||||||
|
return Response('无效的图片URL', status=400, mimetype='text/plain')
|
||||||
|
|
||||||
|
# 检查是否为豆瓣图片域名(douban.com, doubanio.com等)
|
||||||
|
douban_domains = ['douban.com', 'doubanio.com']
|
||||||
|
is_douban_image = any(domain in image_url for domain in douban_domains)
|
||||||
|
|
||||||
|
if not is_douban_image:
|
||||||
|
# 如果不是豆瓣图片,可以选择直接重定向或拒绝
|
||||||
|
# 这里我们选择直接代理,但不设置Referer
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# 豆瓣图片需要设置Referer为豆瓣域名
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1',
|
||||||
|
'Referer': 'https://movie.douban.com/'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 请求图片
|
||||||
|
response = requests.get(image_url, headers=headers, timeout=10, stream=True)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
return Response(f'图片加载失败: {response.status_code}',
|
||||||
|
status=response.status_code,
|
||||||
|
mimetype='text/plain')
|
||||||
|
|
||||||
|
# 获取图片的Content-Type
|
||||||
|
content_type = response.headers.get('Content-Type', 'image/jpeg')
|
||||||
|
|
||||||
|
# 设置响应头,允许跨域(如果需要)
|
||||||
|
resp = Response(
|
||||||
|
stream_with_context(response.iter_content(chunk_size=8192)),
|
||||||
|
content_type=content_type
|
||||||
|
)
|
||||||
|
resp.headers['Cache-Control'] = 'public, max-age=3600'
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"代理豆瓣图片失败: {str(e)}")
|
||||||
|
return Response(f'代理图片失败: {str(e)}', status=500, mimetype='text/plain')
|
||||||
|
|
||||||
@app.route("/api/calendar/update_content_type", methods=["POST"])
|
@app.route("/api/calendar/update_content_type", methods=["POST"])
|
||||||
def update_show_content_type():
|
def update_show_content_type():
|
||||||
"""更新节目的内容类型"""
|
"""更新节目的内容类型"""
|
||||||
|
|||||||
@ -23,6 +23,38 @@ class PanSou:
|
|||||||
return resp.json()
|
return resp.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"success": False, "message": str(e)}
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
def _resolve_qoark_redirect(self, url: str) -> str:
|
||||||
|
"""
|
||||||
|
解析 pan.qoark.cn 链接的重定向地址
|
||||||
|
如果链接是 pan.qoark.cn,则获取重定向后的真实地址
|
||||||
|
如果不是,则直接返回原链接
|
||||||
|
"""
|
||||||
|
if not isinstance(url, str) or "pan.qoark.cn" not in url:
|
||||||
|
return url
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建新的 session 用于重定向解析(避免影响主 session)
|
||||||
|
redirect_session = requests.Session()
|
||||||
|
redirect_session.max_redirects = 10
|
||||||
|
# 只获取头信息,不获取完整内容(HEAD 请求更快)
|
||||||
|
resp = redirect_session.head(url, allow_redirects=True, timeout=10)
|
||||||
|
|
||||||
|
# 如果成功重定向到 pan.quark.cn,返回重定向后的地址
|
||||||
|
if resp.status_code == 200 and "pan.quark.cn" in resp.url:
|
||||||
|
return resp.url
|
||||||
|
# 如果 HEAD 请求失败,尝试 GET 请求(某些服务器可能不支持 HEAD)
|
||||||
|
elif resp.status_code != 200:
|
||||||
|
resp = redirect_session.get(url, allow_redirects=True, timeout=10)
|
||||||
|
if resp.status_code == 200 and "pan.quark.cn" in resp.url:
|
||||||
|
return resp.url
|
||||||
|
|
||||||
|
# 如果重定向失败或没有重定向到 pan.quark.cn,返回原链接
|
||||||
|
return url
|
||||||
|
except Exception as e:
|
||||||
|
# 如果解析失败,返回原链接(避免影响其他功能)
|
||||||
|
# 可以在这里添加日志记录错误
|
||||||
|
return url
|
||||||
|
|
||||||
def _get_pansou_source(self, result_item: dict, merged_item: dict = None) -> str:
|
def _get_pansou_source(self, result_item: dict, merged_item: dict = None) -> str:
|
||||||
"""
|
"""
|
||||||
@ -116,6 +148,8 @@ class PanSou:
|
|||||||
url = link.get("url", "")
|
url = link.get("url", "")
|
||||||
link_type = link.get("type", "")
|
link_type = link.get("type", "")
|
||||||
if url: # 确保有有效链接
|
if url: # 确保有有效链接
|
||||||
|
# 解析 pan.qoark.cn 链接的重定向地址
|
||||||
|
url = self._resolve_qoark_redirect(url)
|
||||||
cleaned.append({
|
cleaned.append({
|
||||||
"taskname": title,
|
"taskname": title,
|
||||||
"content": content,
|
"content": content,
|
||||||
@ -144,6 +178,8 @@ class PanSou:
|
|||||||
pansou_source = self._get_pansou_source(None, link)
|
pansou_source = self._get_pansou_source(None, link)
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
|
# 解析 pan.qoark.cn 链接的重定向地址
|
||||||
|
url = self._resolve_qoark_redirect(url)
|
||||||
cleaned.append({
|
cleaned.append({
|
||||||
"taskname": note,
|
"taskname": note,
|
||||||
"content": note, # 如果没有 content,使用 note
|
"content": note, # 如果没有 content,使用 note
|
||||||
@ -158,10 +194,14 @@ class PanSou:
|
|||||||
if not cleaned and isinstance(payload, list):
|
if not cleaned and isinstance(payload, list):
|
||||||
for item in payload:
|
for item in payload:
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
|
url = item.get("url", "")
|
||||||
|
# 解析 pan.qoark.cn 链接的重定向地址
|
||||||
|
if url:
|
||||||
|
url = self._resolve_qoark_redirect(url)
|
||||||
cleaned.append({
|
cleaned.append({
|
||||||
"taskname": item.get("title", ""),
|
"taskname": item.get("title", ""),
|
||||||
"content": item.get("content", ""),
|
"content": item.get("content", ""),
|
||||||
"shareurl": item.get("url", ""),
|
"shareurl": url,
|
||||||
"tags": item.get("tags", []) or [],
|
"tags": item.get("tags", []) or [],
|
||||||
"publish_date": item.get("datetime", ""), # 原始时间
|
"publish_date": item.get("datetime", ""), # 原始时间
|
||||||
"source": "PanSou", # 添加来源标识
|
"source": "PanSou", # 添加来源标识
|
||||||
@ -178,8 +218,8 @@ class PanSou:
|
|||||||
try:
|
try:
|
||||||
url = item.get("shareurl", "")
|
url = item.get("shareurl", "")
|
||||||
tags = item.get("tags", []) or []
|
tags = item.get("tags", []) or []
|
||||||
# 检查是否为夸克网盘
|
# 检查是否为夸克网盘(支持 pan.quark.cn 和已解析的 pan.qoark.cn)
|
||||||
is_quark = ("quark" in tags) or ("pan.quark.cn" in url)
|
is_quark = ("quark" in tags) or ("pan.quark.cn" in url) or ("pan.qoark.cn" in url)
|
||||||
if is_quark:
|
if is_quark:
|
||||||
filtered.append(item)
|
filtered.append(item)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@ -1260,7 +1260,7 @@
|
|||||||
<div v-for="suggestion in smart_param.taskSuggestions.data || []" :key="(suggestion.shareurl || '') + '_' + (suggestion.taskname || '') + '_' + (suggestion.publish_date || '')" class="dropdown-item cursor-pointer" @click.prevent="selectSuggestion(task.__originalIndex !== undefined ? task.__originalIndex : index, suggestion)" style="font-size: 14px;" :title="getSuggestionHoverTitle(suggestion)">
|
<div v-for="suggestion in smart_param.taskSuggestions.data || []" :key="(suggestion.shareurl || '') + '_' + (suggestion.taskname || '') + '_' + (suggestion.publish_date || '')" class="dropdown-item cursor-pointer" @click.prevent="selectSuggestion(task.__originalIndex !== undefined ? task.__originalIndex : index, suggestion)" style="font-size: 14px;" :title="getSuggestionHoverTitle(suggestion)">
|
||||||
<span v-html="suggestion.verify ? '✅': ''"></span> {{ suggestion.taskname }}
|
<span v-html="suggestion.verify ? '✅': ''"></span> {{ suggestion.taskname }}
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<a :href="suggestion.shareurl" target="_blank" @click.stop> · {{ suggestion.shareurl.replace(/^https?:\/\/pan\.quark\.cn\/s\//, '') }}</a>
|
<a :href="suggestion.shareurl" target="_blank" @click.stop> · {{ suggestion.shareurl.replace(/^https?:\/\/pan\.(quark|qoark)\.cn\/s\//, '').split('#')[0] }}</a>
|
||||||
<template v-if="suggestion.source"><span class="source-badge" :class="suggestion.source.toLowerCase()" :data-publish-date="suggestion.publish_date ? ' · ' + formatPublishDate(suggestion.publish_date, suggestion.pansou_source, suggestion.source) : ''">{{ suggestion.source }}</span></template>
|
<template v-if="suggestion.source"><span class="source-badge" :class="suggestion.source.toLowerCase()" :data-publish-date="suggestion.publish_date ? ' · ' + formatPublishDate(suggestion.publish_date, suggestion.pansou_source, suggestion.source) : ''">{{ suggestion.source }}</span></template>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
@ -2793,7 +2793,7 @@
|
|||||||
<div v-for="suggestion in smart_param.taskSuggestions.data || []" :key="(suggestion.shareurl || '') + '_' + (suggestion.taskname || '') + '_' + (suggestion.publish_date || '')" class="dropdown-item cursor-pointer" @click.prevent="selectSuggestion(-1, suggestion)" style="font-size: 14px;" :title="getSuggestionHoverTitle(suggestion)">
|
<div v-for="suggestion in smart_param.taskSuggestions.data || []" :key="(suggestion.shareurl || '') + '_' + (suggestion.taskname || '') + '_' + (suggestion.publish_date || '')" class="dropdown-item cursor-pointer" @click.prevent="selectSuggestion(-1, suggestion)" style="font-size: 14px;" :title="getSuggestionHoverTitle(suggestion)">
|
||||||
<span v-html="suggestion.verify ? '✅': ''"></span> {{ suggestion.taskname }}
|
<span v-html="suggestion.verify ? '✅': ''"></span> {{ suggestion.taskname }}
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<a :href="suggestion.shareurl" target="_blank" @click.stop> · {{ suggestion.shareurl.replace(/^https?:\/\/pan\.quark\.cn\/s\//, '') }}</a>
|
<a :href="suggestion.shareurl" target="_blank" @click.stop> · {{ suggestion.shareurl.replace(/^https?:\/\/pan\.(quark|qoark)\.cn\/s\//, '').split('#')[0] }}</a>
|
||||||
<template v-if="suggestion.source"><span class="source-badge" :class="suggestion.source.toLowerCase()" :data-publish-date="suggestion.publish_date ? ' · ' + formatPublishDate(suggestion.publish_date, suggestion.pansou_source, suggestion.source) : ''">{{ suggestion.source }}</span></template>
|
<template v-if="suggestion.source"><span class="source-badge" :class="suggestion.source.toLowerCase()" :data-publish-date="suggestion.publish_date ? ' · ' + formatPublishDate(suggestion.publish_date, suggestion.pansou_source, suggestion.source) : ''">{{ suggestion.source }}</span></template>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
@ -10407,9 +10407,11 @@
|
|||||||
if (!shareurl) return null;
|
if (!shareurl) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 夸克网盘分享链接格式:https://pan.quark.cn/s/资源ID
|
// 夸克网盘分享链接格式:
|
||||||
const match = shareurl.match(/pan\.quark\.cn\/s\/([a-zA-Z0-9]+)/);
|
// https://pan.quark.cn/s/资源ID 或 https://pan.qoark.cn/s/资源ID
|
||||||
return match ? match[1] : null;
|
// 支持 pan.quark.cn 和 pan.qoark.cn
|
||||||
|
const match = shareurl.match(/pan\.(quark|qoark)\.cn\/s\/([a-zA-Z0-9]+)/);
|
||||||
|
return match ? match[2] : null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -14300,8 +14302,20 @@
|
|||||||
event.target.src = '/static/images/no-poster.svg';
|
event.target.src = '/static/images/no-poster.svg';
|
||||||
},
|
},
|
||||||
getProxiedImageUrl(originalUrl) {
|
getProxiedImageUrl(originalUrl) {
|
||||||
// 保持直连,发现页加载更快,且不需要取色跨域处理
|
// 检查是否为豆瓣图片URL,如果是则使用后端代理绕过防盗链限制
|
||||||
if (!originalUrl) return '/static/images/no-poster.svg';
|
if (!originalUrl) return '/static/images/no-poster.svg';
|
||||||
|
|
||||||
|
// 检查是否为豆瓣图片地址
|
||||||
|
const doubanDomains = ['douban.com', 'doubanio.com'];
|
||||||
|
const isDoubanImage = doubanDomains.some(domain => originalUrl.includes(domain));
|
||||||
|
|
||||||
|
if (isDoubanImage) {
|
||||||
|
// 使用后端代理接口,通过URL编码传递图片地址
|
||||||
|
const encodedUrl = encodeURIComponent(originalUrl);
|
||||||
|
return `/api/proxy/douban-image?url=${encodedUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非豆瓣图片直接返回原URL
|
||||||
return originalUrl;
|
return originalUrl;
|
||||||
},
|
},
|
||||||
createTaskFromDiscovery(item) {
|
createTaskFromDiscovery(item) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user