From e4ffec9ba4421f873e27d0d7f269917731727547 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sat, 10 Jan 2026 14:32:42 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B1=86=E7=93=A3?= =?UTF-8?q?=E6=B5=B7=E6=8A=A5=E6=97=A0=E6=B3=95=E6=98=BE=E7=A4=BA=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增后端图片代理路由 /api/proxy/douban-image,用于代理豆瓣图片请求 - 后端代理会自动设置正确的 Referer 头以绕过豆瓣防盗链限制 - 修改前端 getProxiedImageUrl 函数,自动检测豆瓣图片URL并使用代理 - 支持流式传输和缓存控制,提升加载性能 解决豆瓣新增防盗链限制导致海报无法显示的问题 --- app/run.py | 54 ++++++++++++++++++++++++++++++++++++++++ app/templates/index.html | 14 ++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/app/run.py b/app/run.py index 3f3326d..d17d13d 100644 --- a/app/run.py +++ b/app/run.py @@ -8805,6 +8805,60 @@ def get_tv_list(tv_type, sub_category): '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"]) def update_show_content_type(): """更新节目的内容类型""" diff --git a/app/templates/index.html b/app/templates/index.html index 4f57769..668f239 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -14300,8 +14300,20 @@ event.target.src = '/static/images/no-poster.svg'; }, getProxiedImageUrl(originalUrl) { - // 保持直连,发现页加载更快,且不需要取色跨域处理 + // 检查是否为豆瓣图片URL,如果是则使用后端代理绕过防盗链限制 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; }, createTaskFromDiscovery(item) { From 4bd7d735149eeb2f4861f94bcf3e4a92a52ccb12 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sat, 10 Jan 2026 15:40:21 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20pan.qoark.cn=20?= =?UTF-8?q?=E9=87=8D=E5=AE=9A=E5=90=91=E9=93=BE=E6=8E=A5=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E8=AE=BF=E9=97=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - 搜索结果中的 pan.qoark.cn 链接在 qasx 内无法正常访问 - 这些链接实际会重定向到 pan.quark.cn 的真实地址 修复: - 在 PanSou 搜索结果处理中添加重定向解析,自动获取真实地址 - 在后端 get_share_detail 接口中添加重定向解析 - 前端支持显示和提取 pan.qoark.cn 链接的资源 ID --- app/run.py | 37 ++++++++++++++++++++++++++++++++ app/sdk/pansou.py | 46 +++++++++++++++++++++++++++++++++++++--- app/templates/index.html | 12 ++++++----- 3 files changed, 87 insertions(+), 8 deletions(-) diff --git a/app/run.py b/app/run.py index d17d13d..f8f2be1 100644 --- a/app/run.py +++ b/app/run.py @@ -3521,6 +3521,39 @@ def get_task_suggestions(): 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"]) def get_share_detail(): @@ -3534,6 +3567,10 @@ def get_share_detail(): else: shareurl = request.json.get("shareurl", "") 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的必要属性 diff --git a/app/sdk/pansou.py b/app/sdk/pansou.py index 9184217..43f0a86 100644 --- a/app/sdk/pansou.py +++ b/app/sdk/pansou.py @@ -23,6 +23,38 @@ class PanSou: return resp.json() except Exception as 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: """ @@ -116,6 +148,8 @@ class PanSou: url = link.get("url", "") link_type = link.get("type", "") if url: # 确保有有效链接 + # 解析 pan.qoark.cn 链接的重定向地址 + url = self._resolve_qoark_redirect(url) cleaned.append({ "taskname": title, "content": content, @@ -144,6 +178,8 @@ class PanSou: pansou_source = self._get_pansou_source(None, link) if url: + # 解析 pan.qoark.cn 链接的重定向地址 + url = self._resolve_qoark_redirect(url) cleaned.append({ "taskname": note, "content": note, # 如果没有 content,使用 note @@ -158,10 +194,14 @@ class PanSou: if not cleaned and isinstance(payload, list): for item in payload: if isinstance(item, dict): + url = item.get("url", "") + # 解析 pan.qoark.cn 链接的重定向地址 + if url: + url = self._resolve_qoark_redirect(url) cleaned.append({ "taskname": item.get("title", ""), "content": item.get("content", ""), - "shareurl": item.get("url", ""), + "shareurl": url, "tags": item.get("tags", []) or [], "publish_date": item.get("datetime", ""), # 原始时间 "source": "PanSou", # 添加来源标识 @@ -178,8 +218,8 @@ class PanSou: try: url = item.get("shareurl", "") tags = item.get("tags", []) or [] - # 检查是否为夸克网盘 - is_quark = ("quark" in tags) or ("pan.quark.cn" in url) + # 检查是否为夸克网盘(支持 pan.quark.cn 和已解析的 pan.qoark.cn) + is_quark = ("quark" in tags) or ("pan.quark.cn" in url) or ("pan.qoark.cn" in url) if is_quark: filtered.append(item) except Exception: diff --git a/app/templates/index.html b/app/templates/index.html index 668f239..218c063 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1260,7 +1260,7 @@ @@ -2793,7 +2793,7 @@ @@ -10407,9 +10407,11 @@ if (!shareurl) return null; try { - // 夸克网盘分享链接格式:https://pan.quark.cn/s/资源ID - const match = shareurl.match(/pan\.quark\.cn\/s\/([a-zA-Z0-9]+)/); - return match ? match[1] : null; + // 夸克网盘分享链接格式: + // https://pan.quark.cn/s/资源ID 或 https://pan.qoark.cn/s/资源ID + // 支持 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) { return null; } From edc11aaa929f72b882e525312781076c22c783ef Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sat, 10 Jan 2026 16:03:21 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E4=B8=AD=E8=B5=84=E6=BA=90?= =?UTF-8?q?=20ID=20=E6=98=BE=E7=A4=BA=E5=86=97=E4=BD=99=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复了资源搜索结果显示资源 ID 时,URL 中包含 # 片段标识符(如 #/list/share)也会被显示的问题 - 在移除 URL 前缀后,增加 .split('#')[0] 来移除 # 及其后面的所有内容 - 修改了任务列表和创建任务两个位置的资源 ID 显示逻辑 - 确保资源 ID 只显示纯 ID 部分,例如:2c9b2e6a53ef 而不是 2c9b2e6a53ef#/list/share --- app/templates/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/index.html b/app/templates/index.html index 218c063..fd9053d 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1260,7 +1260,7 @@ @@ -2793,7 +2793,7 @@