From ed8b868432b54c2ffdda0bd4dad684ab1161328d Mon Sep 17 00:00:00 2001
From: CaoMeiYouRen <40430746+CaoMeiYouRen@users.noreply.github.com>
Date: Thu, 5 Sep 2024 20:47:53 +0800
Subject: [PATCH] =?UTF-8?q?feat(middleware):=20=E4=BC=98=E5=8C=96=20?=
=?UTF-8?q?=E5=A4=9A=E5=AA=92=E4=BD=93=E5=A4=84=E7=90=86=E5=A2=9E=E5=8A=A0?=
=?UTF-8?q?=20data.image/item.enclosure=5Furl/item.image=E2=80=A6=20(#1661?=
=?UTF-8?q?0)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(middleware): 优化 多媒体处理增加 data.image/item.enclosure_url/item.image/item.itunes_item_image 等字段
* test(middleware): 补充 anti-hotlink 缺失的测试用例
---
lib/middleware/anti-hotlink.test.ts | 197 +++++++++++++++++++++++++++-
lib/middleware/anti-hotlink.ts | 28 ++++
lib/routes/test/index.ts | 51 ++++++-
3 files changed, 263 insertions(+), 13 deletions(-)
diff --git a/lib/middleware/anti-hotlink.test.ts b/lib/middleware/anti-hotlink.test.ts
index 081aa470a7a9c8..70c30f1343b2ea 100644
--- a/lib/middleware/anti-hotlink.test.ts
+++ b/lib/middleware/anti-hotlink.test.ts
@@ -116,6 +116,148 @@ const expects = {
desc: ' - Powered by RSSHub',
},
},
+ extraComplicated: {
+ origin: {
+ items: [
+ {
+ content:
+ '\n\n\n\n\n\n\n\n\n\n',
+ itunes: {},
+ },
+ {
+ content: '\n',
+ itunes: {},
+ },
+ {
+ content:
+ '\n\n',
+ enclosure: {
+ url: 'https://mock.com/DIYgod/RSSHub.png',
+ type: 'image/png',
+ },
+ itunes: {
+ image: 'https://mock.com/DIYgod/RSSHub.gif',
+ },
+ },
+ ],
+ image: {
+ link: 'https://github.com/DIYgod/RSSHub',
+ url: 'https://mock.com/DIYgod/RSSHub.png',
+ title: 'Test complicated',
+ },
+ description: ' - Powered by RSSHub',
+ },
+ processed: {
+ items: [
+ {
+ content:
+ '\n\n\n\n\n\n\n\n\n\n',
+ itunes: {},
+ },
+ {
+ content: '\n',
+ itunes: {},
+ },
+ {
+ content:
+ '\n\n',
+ enclosure: {
+ url: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.png',
+ type: 'image/png',
+ },
+ itunes: {
+ image: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.gif',
+ },
+ },
+ ],
+ image: {
+ link: 'https://github.com/DIYgod/RSSHub',
+ url: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.png',
+ title: 'Test complicated',
+ },
+ description: ' - Powered by RSSHub',
+ },
+ urlencoded: {
+ items: [
+ {
+ content:
+ '\n\n\n\n\n\n\n\n\n\n',
+ itunes: {},
+ },
+ {
+ content: '\n',
+ itunes: {},
+ },
+ {
+ content:
+ '\n\n',
+ enclosure: {
+ url: 'https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.png',
+ type: 'image/png',
+ },
+ itunes: {
+ image: 'https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.gif',
+ },
+ },
+ ],
+ image: {
+ link: 'https://github.com/DIYgod/RSSHub',
+ url: 'https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.png',
+ title: 'Test complicated',
+ },
+ description: ' - Powered by RSSHub',
+ },
+ },
+ extraMultimedia: {
+ origin: {
+ items: [
+ {
+ content:
+ '\n\n\n\n',
+ },
+ {
+ content: '\n',
+ enclosure: {
+ url: 'https://mock.com/DIYgod/RSSHub.mp4',
+ type: 'video/mp4',
+ },
+ },
+ ],
+ description: ' - Powered by RSSHub',
+ },
+ relayed: {
+ items: [
+ {
+ content:
+ '\n\n\n\n',
+ },
+ {
+ content: '\n',
+ enclosure: {
+ url: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.mp4',
+ type: 'video/mp4',
+ },
+ },
+ ],
+ description: ' - Powered by RSSHub',
+ },
+ partlyRelayed: {
+ items: [
+ {
+ content:
+ '\n\n\n\n',
+ },
+ {
+ content: '\n',
+ enclosure: {
+ url: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.mp4',
+ type: 'video/mp4',
+ },
+ },
+ ],
+ description: ' - Powered by RSSHub',
+ },
+ },
};
const testAntiHotlink = async (path, expectObj, query?: string | Record) => {
@@ -142,12 +284,55 @@ const testAntiHotlink = async (path, expectObj, query?: string | Record) => testAntiHotlink('/test/complicated', expects.complicated.origin, query);
-const expectImgProcessed = (query?: string | Record) => testAntiHotlink('/test/complicated', expects.complicated.processed, query);
-const expectImgUrlencoded = (query?: string | Record) => testAntiHotlink('/test/complicated', expects.complicated.urlencoded, query);
-const expectMultimediaOrigin = (query?: string | Record) => testAntiHotlink('/test/multimedia', expects.multimedia.origin, query);
-const expectMultimediaRelayed = (query?: string | Record) => testAntiHotlink('/test/multimedia', expects.multimedia.relayed, query);
-const expectMultimediaPartlyRelayed = (query?: string | Record) => testAntiHotlink('/test/multimedia', expects.multimedia.partlyRelayed, query);
+const testAntiHotlinkExtra = async (path, expectObj, query?: string | Record) => {
+ const app = (await import('@/app')).default;
+
+ path += query ? `?${new URLSearchParams(query).toString()}` : '';
+
+ const response = await app.request(path);
+ const parsed = await parser.parseString(await response.text());
+ const obj = {
+ description: parsed.description,
+ image: parsed.image,
+ items: parsed.items.slice(0, expectObj.items.length).map((e) => ({
+ content: e.content,
+ enclosure: e.enclosure,
+ itunes: e.itunes,
+ })),
+ };
+ expect(obj).toEqual(expectObj);
+
+ return parsed;
+};
+
+const expectImgOrigin = async (query?: string | Record) => {
+ await testAntiHotlink('/test/complicated', expects.complicated.origin, query);
+ await testAntiHotlinkExtra('/test/complicated', expects.extraComplicated.origin, query);
+};
+const expectImgProcessed = async (query?: string | Record) => {
+ await testAntiHotlink('/test/complicated', expects.complicated.processed, query);
+ await testAntiHotlinkExtra('/test/complicated', expects.extraComplicated.processed, query);
+};
+
+const expectImgUrlencoded = async (query?: string | Record) => {
+ await testAntiHotlink('/test/complicated', expects.complicated.urlencoded, query);
+ await testAntiHotlinkExtra('/test/complicated', expects.extraComplicated.urlencoded, query);
+};
+
+const expectMultimediaOrigin = async (query?: string | Record) => {
+ await testAntiHotlink('/test/multimedia', expects.multimedia.origin, query);
+ await testAntiHotlinkExtra('/test/multimedia', expects.extraMultimedia.origin, query);
+};
+
+const expectMultimediaRelayed = async (query?: string | Record) => {
+ await testAntiHotlink('/test/multimedia', expects.multimedia.relayed, query);
+ await testAntiHotlinkExtra('/test/multimedia', expects.extraMultimedia.relayed, query);
+};
+
+const expectMultimediaPartlyRelayed = async (query?: string | Record) => {
+ await testAntiHotlink('/test/multimedia', expects.multimedia.partlyRelayed, query);
+ await testAntiHotlinkExtra('/test/multimedia', expects.extraMultimedia.partlyRelayed, query);
+};
describe('anti-hotlink', () => {
it('template-legacy', async () => {
diff --git a/lib/middleware/anti-hotlink.ts b/lib/middleware/anti-hotlink.ts
index dd8b289d5564f0..ea8f077b0301c5 100644
--- a/lib/middleware/anti-hotlink.ts
+++ b/lib/middleware/anti-hotlink.ts
@@ -44,6 +44,18 @@ const parseUrl = (str: string) => {
return url;
};
+
+const replaceUrl = (template?: string, url?: string) => {
+ if (!template || !url) {
+ return url;
+ }
+ const oldUrl = parseUrl(url);
+ if (oldUrl && oldUrl.protocol !== 'data:') {
+ return interpolate(template, oldUrl);
+ }
+ return url;
+};
+
const replaceUrls = ($: CheerioAPI, selector: string, template: string, attribute = 'src') => {
$(selector).each(function () {
const oldSrc = $(this).attr(attribute);
@@ -120,6 +132,9 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
// image link
const data: Data = ctx.get('data');
if (data) {
+ if (data.image) {
+ data.image = replaceUrl(imageHotlinkTemplate, data.image);
+ }
if (data.description) {
data.description = process(data.description, imageHotlinkTemplate, multimediaHotlinkTemplate);
}
@@ -129,6 +144,19 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
if (item.description) {
item.description = process(item.description, imageHotlinkTemplate, multimediaHotlinkTemplate);
}
+ if (item.enclosure_url && item.enclosure_type) {
+ if (item.enclosure_type.startsWith('image/')) {
+ item.enclosure_url = replaceUrl(imageHotlinkTemplate, item.enclosure_url);
+ } else if (/^(video|audio)\//.test(item.enclosure_type)) {
+ item.enclosure_url = replaceUrl(multimediaHotlinkTemplate, item.enclosure_url);
+ }
+ }
+ if (item.image) {
+ item.image = replaceUrl(imageHotlinkTemplate, item.image);
+ }
+ if (item.itunes_item_image) {
+ item.itunes_item_image = replaceUrl(imageHotlinkTemplate, item.itunes_item_image);
+ }
}
}
diff --git a/lib/routes/test/index.ts b/lib/routes/test/index.ts
index 668709f6da3a63..70c88815b7e1e9 100644
--- a/lib/routes/test/index.ts
+++ b/lib/routes/test/index.ts
@@ -33,6 +33,7 @@ async function handler(ctx) {
throw new InvalidParameterError('Test invalid parameter error');
}
let item: DataItem[] = [];
+ let image: string | null = null;
switch (ctx.req.param('id')) {
case 'filter':
item = [
@@ -141,6 +142,7 @@ async function handler(ctx) {
break;
}
case 'complicated':
+ image = 'https://mock.com/DIYgod/RSSHub.png';
item.push(
{
title: `Complicated Title`,
@@ -166,15 +168,38 @@ async function handler(ctx) {
pubDate: new Date(`2019-3-1`).toUTCString(),
link: `https://mock.com/DIYgod/RSSHub`,
author: `DIYgod`,
+ },
+ {
+ title: `Complicated Title`,
+ description: `
+
+`,
+ pubDate: new Date(`2019-3-1`).toUTCString(),
+ link: `//mock.com/DIYgod/RSSHub`,
+ author: `DIYgod`,
+ enclosure_url: 'https://mock.com/DIYgod/RSSHub.png',
+ enclosure_type: 'image/png',
+ itunes_item_image: 'https://mock.com/DIYgod/RSSHub.gif',
+ },
+ {
+ title: `Complicated Title`,
+ description: `
+
+`,
+ pubDate: new Date(`2019-3-1`).toUTCString(),
+ link: `//mock.com/DIYgod/RSSHub`,
+ author: `DIYgod`,
+ image: 'https://mock.com/DIYgod/RSSHub.jpg',
}
);
break;
case 'multimedia':
- item.push({
- title: `Multimedia Title`,
- description: `
+ item.push(
+ {
+ title: `Multimedia Title`,
+ description: `
`,
- pubDate: new Date(`2019-3-1`).toUTCString(),
- link: `https://mock.com/DIYgod/RSSHub`,
- author: `DIYgod`,
- });
+ pubDate: new Date(`2019-3-1`).toUTCString(),
+ link: `https://mock.com/DIYgod/RSSHub`,
+ author: `DIYgod`,
+ },
+ {
+ title: `Multimedia Title`,
+ description: `
+`,
+ pubDate: new Date(`2019-3-1`).toUTCString(),
+ link: `https://mock.com/DIYgod/RSSHub`,
+ author: `DIYgod`,
+ enclosure_url: 'https://mock.com/DIYgod/RSSHub.mp4',
+ enclosure_type: 'video/mp4',
+ }
+ );
break;
@@ -369,6 +405,7 @@ async function handler(ctx) {
}
return {
+ image,
title: `Test ${ctx.req.param('id')}`,
itunes_author: ctx.req.param('id') === 'enclosure' ? 'DIYgod' : null,
link: 'https://github.com/DIYgod/RSSHub',