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',