diff --git a/lib/middleware/template.js b/lib/middleware/template.js index 8598fe5a1253a1..88dfc57961e2bd 100644 --- a/lib/middleware/template.js +++ b/lib/middleware/template.js @@ -2,7 +2,7 @@ const art = require('art-template'); const path = require('path'); const config = require('@/config').value; const typeRegex = /\.(atom|rss|debug\.json)$/; -const { collapseWhitespace } = require('@/utils/common-utils'); +const { collapseWhitespace, convertDateToISO8601 } = require('@/utils/common-utils'); module.exports = async (ctx, next) => { if (ctx.headers['user-agent'] && ctx.headers['user-agent'].includes('Reeder')) { @@ -28,7 +28,8 @@ module.exports = async (ctx, next) => { if (!ctx.body) { let template; - switch (ctx.state.type[1]) { + const outputType = ctx.state.type[1]; + switch (outputType) { case 'atom': template = path.resolve(__dirname, '../views/atom.art'); break; @@ -66,6 +67,11 @@ module.exports = async (ctx, next) => { item.itunes_duration = Math.floor(item.itunes_duration / 3600) + ':' + (Math.floor((item.itunes_duration % 3600) / 60) / 100).toFixed(2).slice(-2) + ':' + (((item.itunes_duration % 3600) % 60) / 100).toFixed(2).slice(-2); } + + if (outputType === 'atom') { + item.pubDate = convertDateToISO8601(item.pubDate); + item.updated = convertDateToISO8601(item.updated); + } }); } diff --git a/lib/utils/common-utils.js b/lib/utils/common-utils.js index 882cccd7c08d2e..4e29e5086046ab 100644 --- a/lib/utils/common-utils.js +++ b/lib/utils/common-utils.js @@ -1,3 +1,5 @@ +const { parseDate } = require('@/utils/parse-date'); + // convert a string into title case const toTitleCase = (str) => str @@ -17,7 +19,19 @@ const collapseWhitespace = (str) => { return str; }; +const convertDateToISO8601 = (date) => { + if (!date) { + return date; + } + if (typeof date !== 'object') { + // some routes may call `.toUTCString()` before passing the date to ctx... + date = parseDate(date); + } + return date.toISOString(); +}; + module.exports = { toTitleCase, collapseWhitespace, + convertDateToISO8601, }; diff --git a/lib/views/atom.art b/lib/views/atom.art index ee0addb78e2c44..b0a754795d0081 100644 --- a/lib/views/atom.art +++ b/lib/views/atom.art @@ -37,8 +37,10 @@ {{ $e.guid || $e.id || $e.link }} <![CDATA[{{@ $e.title }}]]> - {{ $e.pubDate || $e.updated }} - {{ $e.updated }} + {{ if ($e.pubDate && $e.updated) }} + {{ $e.pubDate }} + {{ /if }} + {{ $e.updated || $e.pubDate }} {{ if $e.author }} diff --git a/test/middleware/template.js b/test/middleware/template.js index 4b31e96fff06ba..89b8c90c061a6d 100644 --- a/test/middleware/template.js +++ b/test/middleware/template.js @@ -10,6 +10,8 @@ afterAll(() => { }); describe('template', () => { + const expectPubDate = new Date(1546272000000 - 10 * 1000); + it(`.rss`, async () => { const response1 = await request.get('/test/1.rss'); const parsed1 = await parser.parseString(response1.text); @@ -25,7 +27,7 @@ describe('template', () => { expect(parsed1.items[0]).toEqual(expect.any(Object)); expect(parsed1.items[0].title).toEqual(expect.any(String)); expect(parsed1.items[0].link).toEqual(expect.any(String)); - expect(parsed1.items[0].pubDate).toEqual(expect.any(String)); + expect(parsed1.items[0].pubDate).toBe(expectPubDate.toUTCString()); expect(parsed1.items[0].author).toEqual(expect.any(String)); expect(parsed1.items[0].content).toEqual(expect.any(String)); expect(parsed1.items[0].guid).toEqual(expect.any(String)); @@ -54,7 +56,7 @@ describe('template', () => { expect(parsed.items[0]).toEqual(expect.any(Object)); expect(parsed.items[0].title).toEqual(expect.any(String)); expect(parsed.items[0].link).toEqual(expect.any(String)); - expect(parsed.items[0].pubDate).toEqual(expect.any(String)); + expect(parsed.items[0].pubDate).toBe(expectPubDate.toISOString()); expect(parsed.items[0].author).toEqual(expect.any(String)); expect(parsed.items[0].content).toEqual(expect.any(String)); expect(parsed.items[0].id).toEqual(expect.any(String)); diff --git a/test/utils/common-utils.js b/test/utils/common-utils.js index d0306193c55fb1..e895a485653325 100644 --- a/test/utils/common-utils.js +++ b/test/utils/common-utils.js @@ -4,4 +4,33 @@ describe('common-utils', () => { it('toTitleCase', () => { expect(utils.toTitleCase('RSSHub IS AS aweSOme aS henry')).toBe('Rsshub Is As Awesome As Henry'); }); + + it('convertDateToISO8601', () => { + expect(utils.convertDateToISO8601('')).toBe(''); + expect(utils.convertDateToISO8601(null)).toBe(null); + expect(utils.convertDateToISO8601(undefined)).toBe(undefined); + + const date = new Date('2019-01-01'); + const expected = date.toISOString(); + expect(utils.convertDateToISO8601(date)).toBe(expected); + expect(utils.convertDateToISO8601(date.toISOString())).toBe(expected); + expect(utils.convertDateToISO8601(date.toUTCString())).toBe(expected); + expect(utils.convertDateToISO8601(date.toLocaleString())).toBe(expected); + expect(utils.convertDateToISO8601('Tue, 01 Jan 2019 08:00:00 UTC+8')).toBe(expected); + + expect(utils.convertDateToISO8601('Tue, 01 Jan 2019 00:00:00')).toBe(new Date(date.getTime() + new Date().getTimezoneOffset() * 60 * 1000).toISOString()); + // need to pass a function in order to use `toThrow` + expect(() => { + utils.convertDateToISO8601('something invalid'); + }).toThrow(RangeError); + }); + + it('collapseWhitespace', () => { + expect(utils.collapseWhitespace('')).toBe(''); + expect(utils.collapseWhitespace(null)).toBe(null); + expect(utils.collapseWhitespace(undefined)).toBe(undefined); + expect(utils.collapseWhitespace(' \n\n\n ')).toBe(''); + expect(utils.collapseWhitespace('a string already collapsed')).toBe('a string already collapsed'); + expect(utils.collapseWhitespace(' \n a lot of whitespaces and \n\n\n\n linebreaks \n\n ')).toBe('a lot of whitespaces and linebreaks'); + }); });