From f99b21547439b5a807ee1db9236566ced3f1bd13 Mon Sep 17 00:00:00 2001 From: RieN 7z Date: Mon, 30 Sep 2024 23:47:37 +0800 Subject: [PATCH 01/23] [feat(routes)] Add ipsw.dev support --- lib/routes/ipswdev/index.ts | 77 +++++++++++++++++++++++++++++++++ lib/routes/ipswdev/namespace.ts | 7 +++ 2 files changed, 84 insertions(+) create mode 100644 lib/routes/ipswdev/index.ts create mode 100644 lib/routes/ipswdev/namespace.ts diff --git a/lib/routes/ipswdev/index.ts b/lib/routes/ipswdev/index.ts new file mode 100644 index 00000000000000..19f1a5b1ba75ef --- /dev/null +++ b/lib/routes/ipswdev/index.ts @@ -0,0 +1,77 @@ +import { Data, Route } from '@/types'; +import got from '@/utils/got'; +import { load } from 'cheerio'; + +const host = 'https://ipsw.dev/'; + +export const route: Route = { + path: '/index/:productID', + categories: ['program-update'], + example: '/ipswdev/index/iPhone16,1', + parameters: { + productID: 'Product ID', + }, + name: 'Apple latest beta firmware', + maintainers: ['RieN7'], + handler, +}; + +async function handler(ctx) { + const { productID } = ctx.req.param(); + const link = `https://ipsw.dev/product/version/${productID}`; + + const resp = await got({ + method: 'get', + url: link, + headers: { + Referer: host, + }, + }); + + const $ = load(resp.data); + + const productName = $('#IdentifierModal > div > div > div.modal-body > p:nth-child(1) > em').text(); + + // eslint-disable-next-line no-restricted-syntax + const list: Data[] = $('.firmware') + .map((index, element) => { + const ele = $(element); + const version = ele.find('td:nth-child(1) > div > div > strong').text(); + const build = ele.find('td:nth-child(1) > div > div > div > code').text(); + const date = ele.find('td:nth-child(3)').text(); + const size = ele.find('td:nth-child(4)').text(); + return { + title: `${productName} - ${version}`, + link: `https://ipsw.dev/download/${productID}/${build}`, + pubDate: new Date(date).toLocaleDateString(), + guid: build, + description: ` + + + + + + + + + + + + + + + + + + +
Version${version}
Build${build}
Released${date}
Size${size}
`, + }; + }) + .get(); + + return { + title: `${productName} Released`, + link, + item: list, + }; +} diff --git a/lib/routes/ipswdev/namespace.ts b/lib/routes/ipswdev/namespace.ts new file mode 100644 index 00000000000000..e6c6cb1ff9b805 --- /dev/null +++ b/lib/routes/ipswdev/namespace.ts @@ -0,0 +1,7 @@ +import { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'IPSW.dev', + url: 'ipsw.dev', + description: 'Download the latest beta firmware for iPhone, iPad, Mac, Apple Vision Pro, and Apple TV. Check the signing status of the beta firmware.', +}; From eab98051398952f2a140ab9711ee219ca36ce64f Mon Sep 17 00:00:00 2001 From: RieN7 Date: Wed, 9 Oct 2024 22:56:45 +0800 Subject: [PATCH 02/23] fix(xiaohongshu): fix note fulltext --- lib/routes/xiaohongshu/notes.ts | 89 +++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/lib/routes/xiaohongshu/notes.ts b/lib/routes/xiaohongshu/notes.ts index a4d85a5d563425..b04884b6c1aee0 100644 --- a/lib/routes/xiaohongshu/notes.ts +++ b/lib/routes/xiaohongshu/notes.ts @@ -1,6 +1,8 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; -import { getNotes, formatText, formatNote } from './util'; +import { config } from '@/config'; +import * as cheerio from 'cheerio'; +import got from '@/utils/got'; export const route: Route = { path: '/user/:user_id/notes/fulltext', @@ -15,11 +17,22 @@ export const route: Route = { handler, example: '/xiaohongshu/user/52d8c541b4c4d60e6c867480/notes/fulltext', features: { + requireConfig: [ + { + name: 'XIAOHONGSHU_COOKIE', + optional: true, + description: '小红书 cookie 值,可在浏览器控制台通过`document.cookie`获取。', + }, + ], antiCrawler: true, requirePuppeteer: true, }, parameters: { user_id: 'user id, length 24 characters', + fulltext: { + description: '是否获取全文', + default: '', + }, }, }; @@ -27,13 +40,81 @@ async function handler(ctx) { const userId = ctx.req.param('user_id'); const url = `https://www.xiaohongshu.com/user/profile/${userId}`; - const { user, notes } = await getNotes(url, cache); + const user = await getUser(url, config.xiaohongshu.cookie); + const notes = await renderNotesFulltext(user.notes, url); return { title: `${user.nickname} - 笔记 • 小红书 / RED`, - description: formatText(user.desc), + description: user.desc, image: user.imageb || user.images, link: url, - item: notes.map((item) => formatNote(url, item)), + item: notes, }; } + +async function getUser(url, cookie) { + const res = await got(url, { + headers: { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Cookie": cookie, + } + }); + const $ = cheerio.load(res.data); + + let script = $('script').filter((i, script) => { + const text = script.children[0]?.data; + return text?.startsWith('window.__INITIAL_STATE__='); + }).text(); + script = script.slice('window.__INITIAL_STATE__='.length); + script = script.replace(/undefined/g, 'null'); + const state = JSON.parse(script); + return state.user; +} + +async function renderNotesFulltext(notes, url) { + const data: any[] = []; + for (const note of notes) { + for (const {noteCard} of note) { + const link = `${url}/${noteCard.noteId}`; + const {title, description} = await getNote(link); + data.push({ + title, + link, + description, + author: noteCard.user.nickname, + guid: noteCard.noteId, + }); + } + } + return data; +} + +async function getNote(link) { + const cookie = config.xiaohongshu.cookie; + const data = await cache.tryGet(link, async () => { + const res = await got(link, { + headers: { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Cookie": cookie, + } as any + }); + const $ = cheerio.load(res.data); + let script = $('script').filter((i, script) => { + const text = script.children[0]?.data; + return text?.startsWith('window.__INITIAL_STATE__='); + }).text(); + script = script.slice('window.__INITIAL_STATE__='.length); + script = script.replace(/undefined/g, 'null'); + const state = JSON.parse(script); + const note = state.note.noteDetailMap[state.note.firstNoteId].note; + const images = note.imageList.map((image) => image.urlDefault); + const title = note.title; + const desc = note.desc.replaceAll('#(.*?)\[话题\]#', '#$1'); + const description = `${images.map((image) => ``).join('')}
${title}
${desc}`; + return { + title, + description, + }; + }) as Promise<{title: string, description: string}>; + return data; +} \ No newline at end of file From 797f6e5b0ba7ae0820dcb3f4cced71b5a74c6a0f Mon Sep 17 00:00:00 2001 From: RieN7 Date: Wed, 9 Oct 2024 23:59:17 +0800 Subject: [PATCH 03/23] add config --- lib/config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/config.ts b/lib/config.ts index e2e0e162f8794f..10d89d3e3c7819 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -309,6 +309,9 @@ export type Config = { device_id?: string; refresh_token?: string; }; + xiaohongshu: { + cookie?: string; + }; ximalaya: { token?: string; }; @@ -702,6 +705,9 @@ const calculateValue = () => { device_id: envs.XIAOYUZHOU_ID, refresh_token: envs.XIAOYUZHOU_TOKEN, }, + xiaohongshu: { + cookie: envs.XIAOHONGSHU_COOKIE, + }, ximalaya: { token: envs.XIMALAYA_TOKEN, }, From a5ee72094d61e0e8e3672df7d18f24079b6ee066 Mon Sep 17 00:00:00 2001 From: RieN7 Date: Thu, 10 Oct 2024 22:11:55 +0800 Subject: [PATCH 04/23] feat: add fulltext with cookie --- lib/config.ts | 6 ++ lib/routes/xiaohongshu/notes.ts | 118 +++++++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 9 deletions(-) diff --git a/lib/config.ts b/lib/config.ts index e2e0e162f8794f..10d89d3e3c7819 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -309,6 +309,9 @@ export type Config = { device_id?: string; refresh_token?: string; }; + xiaohongshu: { + cookie?: string; + }; ximalaya: { token?: string; }; @@ -702,6 +705,9 @@ const calculateValue = () => { device_id: envs.XIAOYUZHOU_ID, refresh_token: envs.XIAOYUZHOU_TOKEN, }, + xiaohongshu: { + cookie: envs.XIAOHONGSHU_COOKIE, + }, ximalaya: { token: envs.XIMALAYA_TOKEN, }, diff --git a/lib/routes/xiaohongshu/notes.ts b/lib/routes/xiaohongshu/notes.ts index a4d85a5d563425..9cd5428d4e3974 100644 --- a/lib/routes/xiaohongshu/notes.ts +++ b/lib/routes/xiaohongshu/notes.ts @@ -1,6 +1,9 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; -import { getNotes, formatText, formatNote } from './util'; +import { config } from '@/config'; +import * as cheerio from 'cheerio'; +import got from '@/utils/got'; +import { formatNote, formatText, getNotes } from './util'; export const route: Route = { path: '/user/:user_id/notes/fulltext', @@ -15,11 +18,22 @@ export const route: Route = { handler, example: '/xiaohongshu/user/52d8c541b4c4d60e6c867480/notes/fulltext', features: { + requireConfig: [ + { + name: 'XIAOHONGSHU_COOKIE', + optional: true, + description: '小红书 cookie 值,可在浏览器控制台通过`document.cookie`获取。', + }, + ], antiCrawler: true, requirePuppeteer: true, }, parameters: { user_id: 'user id, length 24 characters', + fulltext: { + description: '是否获取全文', + default: '', + }, }, }; @@ -27,13 +41,99 @@ async function handler(ctx) { const userId = ctx.req.param('user_id'); const url = `https://www.xiaohongshu.com/user/profile/${userId}`; - const { user, notes } = await getNotes(url, cache); + if (config.xiaohongshu.cookie && ctx.req.param('fulltext')) { + const user = await getUser(url, config.xiaohongshu.cookie); + const notes = await renderNotesFulltext(user.notes, url); + return { + title: `${user.userPageData.basicInfo.nickname} - 笔记 • 小红书 / RED`, + description: user.userPageData.basicInfo.desc, + image: user.userPageData.basicInfo.imageb || user.userPageData.basicInfo.images, + link: url, + item: notes, + }; + } else { + const { user, notes } = await getNotes(url, cache); + return { + title: `${user.nickname} - 笔记 • 小红书 / RED`, + description: formatText(user.desc), + image: user.imageb || user.images, + link: url, + item: notes.map((item) => formatNote(url, item)), + }; + } +} + +async function getUser(url, cookie) { + const res = await got(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + Cookie: cookie, + }, + }); + const $ = cheerio.load(res.data); + + let script = $('script') + .filter((i, script) => { + const text = script.children[0]?.data; + return text?.startsWith('window.__INITIAL_STATE__='); + }) + .text(); + script = script.slice('window.__INITIAL_STATE__='.length); + script = script.replaceAll('undefined', 'null'); + const state = JSON.parse(script); + return state.user; +} + +async function renderNotesFulltext(notes, url) { + const data: any[] = []; + for (const note of notes) { + for (const { noteCard } of note) { + const link = `${url}/${noteCard.noteId}`; + // eslint-disable-next-line no-await-in-loop + const { title, description } = await getFullNote(link); + data.push({ + title, + link, + description, + author: noteCard.user.nickName, + guid: noteCard.noteId, + }); + } + } + return data; +} - return { - title: `${user.nickname} - 笔记 • 小红书 / RED`, - description: formatText(user.desc), - image: user.imageb || user.images, - link: url, - item: notes.map((item) => formatNote(url, item)), - }; +async function getFullNote(link) { + const cookie = config.xiaohongshu.cookie; + const data = (await cache.tryGet(link, async () => { + const res = await got(link, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + Cookie: cookie, + } as any, + }); + const $ = cheerio.load(res.data); + let script = $('script') + .filter((i, script) => { + const text = script.children[0]?.data; + return text?.startsWith('window.__INITIAL_STATE__='); + }) + .text(); + script = script.slice('window.__INITIAL_STATE__='.length); + script = script.replaceAll('undefined', 'null'); + const state = JSON.parse(script); + const note = state.note.noteDetailMap[state.note.firstNoteId].note; + const images = note.imageList.map((image) => image.urlDefault); + const title = note.title; + let desc = note.desc; + desc = desc.replaceAll(/\[.*?\]/g, ''); + desc = desc.replaceAll(/#(.*?)#/g, '#$1'); + desc = desc.replaceAll('\n', '
'); + const description = `${images.map((image) => ``).join('')}
${title}
${desc}`; + return { + title, + description, + }; + })) as Promise<{ title: string; description: string }>; + return data; } From 25ba2e4c3f7d43efe265c0e4f36c196494f2f118 Mon Sep 17 00:00:00 2001 From: RieN7 Date: Thu, 10 Oct 2024 22:11:55 +0800 Subject: [PATCH 05/23] feat: add fulltext with cookie --- lib/config.ts | 6 ++ lib/routes/xiaohongshu/notes.ts | 118 +++++++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 9 deletions(-) diff --git a/lib/config.ts b/lib/config.ts index e2e0e162f8794f..10d89d3e3c7819 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -309,6 +309,9 @@ export type Config = { device_id?: string; refresh_token?: string; }; + xiaohongshu: { + cookie?: string; + }; ximalaya: { token?: string; }; @@ -702,6 +705,9 @@ const calculateValue = () => { device_id: envs.XIAOYUZHOU_ID, refresh_token: envs.XIAOYUZHOU_TOKEN, }, + xiaohongshu: { + cookie: envs.XIAOHONGSHU_COOKIE, + }, ximalaya: { token: envs.XIMALAYA_TOKEN, }, diff --git a/lib/routes/xiaohongshu/notes.ts b/lib/routes/xiaohongshu/notes.ts index a4d85a5d563425..9cd5428d4e3974 100644 --- a/lib/routes/xiaohongshu/notes.ts +++ b/lib/routes/xiaohongshu/notes.ts @@ -1,6 +1,9 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; -import { getNotes, formatText, formatNote } from './util'; +import { config } from '@/config'; +import * as cheerio from 'cheerio'; +import got from '@/utils/got'; +import { formatNote, formatText, getNotes } from './util'; export const route: Route = { path: '/user/:user_id/notes/fulltext', @@ -15,11 +18,22 @@ export const route: Route = { handler, example: '/xiaohongshu/user/52d8c541b4c4d60e6c867480/notes/fulltext', features: { + requireConfig: [ + { + name: 'XIAOHONGSHU_COOKIE', + optional: true, + description: '小红书 cookie 值,可在浏览器控制台通过`document.cookie`获取。', + }, + ], antiCrawler: true, requirePuppeteer: true, }, parameters: { user_id: 'user id, length 24 characters', + fulltext: { + description: '是否获取全文', + default: '', + }, }, }; @@ -27,13 +41,99 @@ async function handler(ctx) { const userId = ctx.req.param('user_id'); const url = `https://www.xiaohongshu.com/user/profile/${userId}`; - const { user, notes } = await getNotes(url, cache); + if (config.xiaohongshu.cookie && ctx.req.param('fulltext')) { + const user = await getUser(url, config.xiaohongshu.cookie); + const notes = await renderNotesFulltext(user.notes, url); + return { + title: `${user.userPageData.basicInfo.nickname} - 笔记 • 小红书 / RED`, + description: user.userPageData.basicInfo.desc, + image: user.userPageData.basicInfo.imageb || user.userPageData.basicInfo.images, + link: url, + item: notes, + }; + } else { + const { user, notes } = await getNotes(url, cache); + return { + title: `${user.nickname} - 笔记 • 小红书 / RED`, + description: formatText(user.desc), + image: user.imageb || user.images, + link: url, + item: notes.map((item) => formatNote(url, item)), + }; + } +} + +async function getUser(url, cookie) { + const res = await got(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + Cookie: cookie, + }, + }); + const $ = cheerio.load(res.data); + + let script = $('script') + .filter((i, script) => { + const text = script.children[0]?.data; + return text?.startsWith('window.__INITIAL_STATE__='); + }) + .text(); + script = script.slice('window.__INITIAL_STATE__='.length); + script = script.replaceAll('undefined', 'null'); + const state = JSON.parse(script); + return state.user; +} + +async function renderNotesFulltext(notes, url) { + const data: any[] = []; + for (const note of notes) { + for (const { noteCard } of note) { + const link = `${url}/${noteCard.noteId}`; + // eslint-disable-next-line no-await-in-loop + const { title, description } = await getFullNote(link); + data.push({ + title, + link, + description, + author: noteCard.user.nickName, + guid: noteCard.noteId, + }); + } + } + return data; +} - return { - title: `${user.nickname} - 笔记 • 小红书 / RED`, - description: formatText(user.desc), - image: user.imageb || user.images, - link: url, - item: notes.map((item) => formatNote(url, item)), - }; +async function getFullNote(link) { + const cookie = config.xiaohongshu.cookie; + const data = (await cache.tryGet(link, async () => { + const res = await got(link, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + Cookie: cookie, + } as any, + }); + const $ = cheerio.load(res.data); + let script = $('script') + .filter((i, script) => { + const text = script.children[0]?.data; + return text?.startsWith('window.__INITIAL_STATE__='); + }) + .text(); + script = script.slice('window.__INITIAL_STATE__='.length); + script = script.replaceAll('undefined', 'null'); + const state = JSON.parse(script); + const note = state.note.noteDetailMap[state.note.firstNoteId].note; + const images = note.imageList.map((image) => image.urlDefault); + const title = note.title; + let desc = note.desc; + desc = desc.replaceAll(/\[.*?\]/g, ''); + desc = desc.replaceAll(/#(.*?)#/g, '#$1'); + desc = desc.replaceAll('\n', '
'); + const description = `${images.map((image) => ``).join('')}
${title}
${desc}`; + return { + title, + description, + }; + })) as Promise<{ title: string; description: string }>; + return data; } From 1018ff245197abba713704117551a92fbee7d53e Mon Sep 17 00:00:00 2001 From: RieN 7z Date: Mon, 30 Sep 2024 23:47:37 +0800 Subject: [PATCH 06/23] [feat(routes)] Add ipsw.dev support --- lib/routes/ipswdev/index.ts | 77 +++++++++++++++++++++++++++++++++ lib/routes/ipswdev/namespace.ts | 7 +++ 2 files changed, 84 insertions(+) create mode 100644 lib/routes/ipswdev/index.ts create mode 100644 lib/routes/ipswdev/namespace.ts diff --git a/lib/routes/ipswdev/index.ts b/lib/routes/ipswdev/index.ts new file mode 100644 index 00000000000000..19f1a5b1ba75ef --- /dev/null +++ b/lib/routes/ipswdev/index.ts @@ -0,0 +1,77 @@ +import { Data, Route } from '@/types'; +import got from '@/utils/got'; +import { load } from 'cheerio'; + +const host = 'https://ipsw.dev/'; + +export const route: Route = { + path: '/index/:productID', + categories: ['program-update'], + example: '/ipswdev/index/iPhone16,1', + parameters: { + productID: 'Product ID', + }, + name: 'Apple latest beta firmware', + maintainers: ['RieN7'], + handler, +}; + +async function handler(ctx) { + const { productID } = ctx.req.param(); + const link = `https://ipsw.dev/product/version/${productID}`; + + const resp = await got({ + method: 'get', + url: link, + headers: { + Referer: host, + }, + }); + + const $ = load(resp.data); + + const productName = $('#IdentifierModal > div > div > div.modal-body > p:nth-child(1) > em').text(); + + // eslint-disable-next-line no-restricted-syntax + const list: Data[] = $('.firmware') + .map((index, element) => { + const ele = $(element); + const version = ele.find('td:nth-child(1) > div > div > strong').text(); + const build = ele.find('td:nth-child(1) > div > div > div > code').text(); + const date = ele.find('td:nth-child(3)').text(); + const size = ele.find('td:nth-child(4)').text(); + return { + title: `${productName} - ${version}`, + link: `https://ipsw.dev/download/${productID}/${build}`, + pubDate: new Date(date).toLocaleDateString(), + guid: build, + description: ` + + + + + + + + + + + + + + + + + + +
Version${version}
Build${build}
Released${date}
Size${size}
`, + }; + }) + .get(); + + return { + title: `${productName} Released`, + link, + item: list, + }; +} diff --git a/lib/routes/ipswdev/namespace.ts b/lib/routes/ipswdev/namespace.ts new file mode 100644 index 00000000000000..e6c6cb1ff9b805 --- /dev/null +++ b/lib/routes/ipswdev/namespace.ts @@ -0,0 +1,7 @@ +import { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'IPSW.dev', + url: 'ipsw.dev', + description: 'Download the latest beta firmware for iPhone, iPad, Mac, Apple Vision Pro, and Apple TV. Check the signing status of the beta firmware.', +}; From 637c1ec9bca49909b6145a29132b0b76d5fd2eaa Mon Sep 17 00:00:00 2001 From: RieN7 Date: Thu, 10 Oct 2024 22:11:55 +0800 Subject: [PATCH 07/23] feat: add fulltext with cookie --- lib/routes/xiaohongshu/notes.ts | 100 +++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 35 deletions(-) diff --git a/lib/routes/xiaohongshu/notes.ts b/lib/routes/xiaohongshu/notes.ts index b04884b6c1aee0..5196a948d3a568 100644 --- a/lib/routes/xiaohongshu/notes.ts +++ b/lib/routes/xiaohongshu/notes.ts @@ -3,6 +3,7 @@ import cache from '@/utils/cache'; import { config } from '@/config'; import * as cheerio from 'cheerio'; import got from '@/utils/got'; +import { formatNote, formatText, getNotes } from './util'; export const route: Route = { path: '/user/:user_id/notes/fulltext', @@ -17,6 +18,13 @@ export const route: Route = { handler, example: '/xiaohongshu/user/52d8c541b4c4d60e6c867480/notes/fulltext', features: { + requireConfig: [ + { + name: 'XIAOHONGSHU_COOKIE', + optional: true, + description: '小红书 cookie 值,可在浏览器控制台通过`document.cookie`获取。', + }, + ], requireConfig: [ { name: 'XIAOHONGSHU_COOKIE', @@ -33,6 +41,10 @@ export const route: Route = { description: '是否获取全文', default: '', }, + fulltext: { + description: '是否获取全文', + default: '', + }, }, }; @@ -40,33 +52,45 @@ async function handler(ctx) { const userId = ctx.req.param('user_id'); const url = `https://www.xiaohongshu.com/user/profile/${userId}`; - const user = await getUser(url, config.xiaohongshu.cookie); - const notes = await renderNotesFulltext(user.notes, url); - - return { - title: `${user.nickname} - 笔记 • 小红书 / RED`, - description: user.desc, - image: user.imageb || user.images, - link: url, - item: notes, - }; + if (config.xiaohongshu.cookie && ctx.req.param('fulltext')) { + const user = await getUser(url, config.xiaohongshu.cookie); + const notes = await renderNotesFulltext(user.notes, url); + return { + title: `${user.userPageData.basicInfo.nickname} - 笔记 • 小红书 / RED`, + description: user.userPageData.basicInfo.desc, + image: user.userPageData.basicInfo.imageb || user.userPageData.basicInfo.images, + link: url, + item: notes, + }; + } else { + const { user, notes } = await getNotes(url, cache); + return { + title: `${user.nickname} - 笔记 • 小红书 / RED`, + description: formatText(user.desc), + image: user.imageb || user.images, + link: url, + item: notes.map((item) => formatNote(url, item)), + }; + } } async function getUser(url, cookie) { const res = await got(url, { headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - "Cookie": cookie, - } + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + Cookie: cookie, + }, }); const $ = cheerio.load(res.data); - - let script = $('script').filter((i, script) => { - const text = script.children[0]?.data; - return text?.startsWith('window.__INITIAL_STATE__='); - }).text(); + + let script = $('script') + .filter((i, script) => { + const text = script.children[0]?.data; + return text?.startsWith('window.__INITIAL_STATE__='); + }) + .text(); script = script.slice('window.__INITIAL_STATE__='.length); - script = script.replace(/undefined/g, 'null'); + script = script.replaceAll('undefined', 'null'); const state = JSON.parse(script); return state.user; } @@ -74,14 +98,15 @@ async function getUser(url, cookie) { async function renderNotesFulltext(notes, url) { const data: any[] = []; for (const note of notes) { - for (const {noteCard} of note) { + for (const { noteCard } of note) { const link = `${url}/${noteCard.noteId}`; - const {title, description} = await getNote(link); + // eslint-disable-next-line no-await-in-loop + const { title, description } = await getFullNote(link); data.push({ title, link, description, - author: noteCard.user.nickname, + author: noteCard.user.nickName, guid: noteCard.noteId, }); } @@ -89,32 +114,37 @@ async function renderNotesFulltext(notes, url) { return data; } -async function getNote(link) { +async function getFullNote(link) { const cookie = config.xiaohongshu.cookie; - const data = await cache.tryGet(link, async () => { + const data = (await cache.tryGet(link, async () => { const res = await got(link, { headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - "Cookie": cookie, - } as any + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + Cookie: cookie, + } as any, }); const $ = cheerio.load(res.data); - let script = $('script').filter((i, script) => { - const text = script.children[0]?.data; - return text?.startsWith('window.__INITIAL_STATE__='); - }).text(); + let script = $('script') + .filter((i, script) => { + const text = script.children[0]?.data; + return text?.startsWith('window.__INITIAL_STATE__='); + }) + .text(); script = script.slice('window.__INITIAL_STATE__='.length); - script = script.replace(/undefined/g, 'null'); + script = script.replaceAll('undefined', 'null'); const state = JSON.parse(script); const note = state.note.noteDetailMap[state.note.firstNoteId].note; const images = note.imageList.map((image) => image.urlDefault); const title = note.title; - const desc = note.desc.replaceAll('#(.*?)\[话题\]#', '#$1'); + let desc = note.desc; + desc = desc.replaceAll(/\[.*?\]/g, ''); + desc = desc.replaceAll(/#(.*?)#/g, '#$1'); + desc = desc.replaceAll('\n', '
'); const description = `${images.map((image) => ``).join('')}
${title}
${desc}`; return { title, description, }; - }) as Promise<{title: string, description: string}>; + })) as Promise<{ title: string; description: string }>; return data; -} \ No newline at end of file +} From 3434cd447cb8693b3117107c8e0d1d06a3ccfb4f Mon Sep 17 00:00:00 2001 From: RieN7 Date: Thu, 10 Oct 2024 22:50:50 +0800 Subject: [PATCH 08/23] fix: config --- lib/routes/xiaohongshu/notes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/xiaohongshu/notes.ts b/lib/routes/xiaohongshu/notes.ts index 9cd5428d4e3974..053ab690e4cb81 100644 --- a/lib/routes/xiaohongshu/notes.ts +++ b/lib/routes/xiaohongshu/notes.ts @@ -6,7 +6,7 @@ import got from '@/utils/got'; import { formatNote, formatText, getNotes } from './util'; export const route: Route = { - path: '/user/:user_id/notes/fulltext', + path: '/user/:user_id/notes/:fulltext', radar: [ { source: ['xiaohongshu.com/user/profile/:user_id'], From 5399b18d1f56695cc55e657930e4f63f6cb3d00c Mon Sep 17 00:00:00 2001 From: RieN7 Date: Thu, 10 Oct 2024 23:03:48 +0800 Subject: [PATCH 09/23] fix: await in loop --- lib/routes/xiaohongshu/notes.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/routes/xiaohongshu/notes.ts b/lib/routes/xiaohongshu/notes.ts index 053ab690e4cb81..3bde514d2d860d 100644 --- a/lib/routes/xiaohongshu/notes.ts +++ b/lib/routes/xiaohongshu/notes.ts @@ -86,20 +86,20 @@ async function getUser(url, cookie) { async function renderNotesFulltext(notes, url) { const data: any[] = []; - for (const note of notes) { - for (const { noteCard } of note) { + const promises = notes.flatMap((note) => + note.map(async ({ noteCard }) => { const link = `${url}/${noteCard.noteId}`; - // eslint-disable-next-line no-await-in-loop const { title, description } = await getFullNote(link); - data.push({ + return { title, link, description, author: noteCard.user.nickName, guid: noteCard.noteId, - }); - } - } + }; + }) + ); + data.push(...(await Promise.all(promises))); return data; } From 735da7c7470e9154bc0ecf33bffdc60163285271 Mon Sep 17 00:00:00 2001 From: RieN7 Date: Fri, 11 Oct 2024 13:10:45 +0800 Subject: [PATCH 10/23] fix: use art-template --- lib/routes/ipswdev/index.ts | 31 +++++--------------- lib/routes/ipswdev/templates/description.art | 20 +++++++++++++ 2 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 lib/routes/ipswdev/templates/description.art diff --git a/lib/routes/ipswdev/index.ts b/lib/routes/ipswdev/index.ts index 19f1a5b1ba75ef..5c566a24e54920 100644 --- a/lib/routes/ipswdev/index.ts +++ b/lib/routes/ipswdev/index.ts @@ -1,8 +1,8 @@ import { Data, Route } from '@/types'; import got from '@/utils/got'; import { load } from 'cheerio'; - -const host = 'https://ipsw.dev/'; +import { art } from '@/utils/render'; +import path from 'node:path'; export const route: Route = { path: '/index/:productID', @@ -32,7 +32,6 @@ async function handler(ctx) { const productName = $('#IdentifierModal > div > div > div.modal-body > p:nth-child(1) > em').text(); - // eslint-disable-next-line no-restricted-syntax const list: Data[] = $('.firmware') .map((index, element) => { const ele = $(element); @@ -45,26 +44,12 @@ async function handler(ctx) { link: `https://ipsw.dev/download/${productID}/${build}`, pubDate: new Date(date).toLocaleDateString(), guid: build, - description: ` - - - - - - - - - - - - - - - - - - -
Version${version}
Build${build}
Released${date}
Size${size}
`, + description: art(path.join(__dirname, 'templates/description.art'), { + version, + build, + date, + size, + }), }; }) .get(); diff --git a/lib/routes/ipswdev/templates/description.art b/lib/routes/ipswdev/templates/description.art new file mode 100644 index 00000000000000..c0faef7bfac60d --- /dev/null +++ b/lib/routes/ipswdev/templates/description.art @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + +
Version{{ version }}
Build{{ build }}
Released{{ released }}
Size{{ size }}
\ No newline at end of file From 650c85838839c2f626066d984e5336af2f578c66 Mon Sep 17 00:00:00 2001 From: RieN7 Date: Fri, 11 Oct 2024 13:11:56 +0800 Subject: [PATCH 11/23] fix: remove ipswdev in other branch --- lib/routes/ipswdev/index.ts | 77 --------------------------------- lib/routes/ipswdev/namespace.ts | 7 --- 2 files changed, 84 deletions(-) delete mode 100644 lib/routes/ipswdev/index.ts delete mode 100644 lib/routes/ipswdev/namespace.ts diff --git a/lib/routes/ipswdev/index.ts b/lib/routes/ipswdev/index.ts deleted file mode 100644 index 19f1a5b1ba75ef..00000000000000 --- a/lib/routes/ipswdev/index.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Data, Route } from '@/types'; -import got from '@/utils/got'; -import { load } from 'cheerio'; - -const host = 'https://ipsw.dev/'; - -export const route: Route = { - path: '/index/:productID', - categories: ['program-update'], - example: '/ipswdev/index/iPhone16,1', - parameters: { - productID: 'Product ID', - }, - name: 'Apple latest beta firmware', - maintainers: ['RieN7'], - handler, -}; - -async function handler(ctx) { - const { productID } = ctx.req.param(); - const link = `https://ipsw.dev/product/version/${productID}`; - - const resp = await got({ - method: 'get', - url: link, - headers: { - Referer: host, - }, - }); - - const $ = load(resp.data); - - const productName = $('#IdentifierModal > div > div > div.modal-body > p:nth-child(1) > em').text(); - - // eslint-disable-next-line no-restricted-syntax - const list: Data[] = $('.firmware') - .map((index, element) => { - const ele = $(element); - const version = ele.find('td:nth-child(1) > div > div > strong').text(); - const build = ele.find('td:nth-child(1) > div > div > div > code').text(); - const date = ele.find('td:nth-child(3)').text(); - const size = ele.find('td:nth-child(4)').text(); - return { - title: `${productName} - ${version}`, - link: `https://ipsw.dev/download/${productID}/${build}`, - pubDate: new Date(date).toLocaleDateString(), - guid: build, - description: ` - - - - - - - - - - - - - - - - - - -
Version${version}
Build${build}
Released${date}
Size${size}
`, - }; - }) - .get(); - - return { - title: `${productName} Released`, - link, - item: list, - }; -} diff --git a/lib/routes/ipswdev/namespace.ts b/lib/routes/ipswdev/namespace.ts deleted file mode 100644 index e6c6cb1ff9b805..00000000000000 --- a/lib/routes/ipswdev/namespace.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Namespace } from '@/types'; - -export const namespace: Namespace = { - name: 'IPSW.dev', - url: 'ipsw.dev', - description: 'Download the latest beta firmware for iPhone, iPad, Mac, Apple Vision Pro, and Apple TV. Check the signing status of the beta firmware.', -}; From 09cfceeb6b836ab480aaeb5454998235b7bc6a3b Mon Sep 17 00:00:00 2001 From: RieN7 Date: Mon, 14 Oct 2024 14:59:09 +0800 Subject: [PATCH 12/23] fix: add pubDate --- lib/routes/xiaohongshu/notes.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/routes/xiaohongshu/notes.ts b/lib/routes/xiaohongshu/notes.ts index 3bde514d2d860d..2f58aa75a5bb44 100644 --- a/lib/routes/xiaohongshu/notes.ts +++ b/lib/routes/xiaohongshu/notes.ts @@ -89,13 +89,14 @@ async function renderNotesFulltext(notes, url) { const promises = notes.flatMap((note) => note.map(async ({ noteCard }) => { const link = `${url}/${noteCard.noteId}`; - const { title, description } = await getFullNote(link); + const { title, description, pubDate } = await getFullNote(link); return { title, link, description, author: noteCard.user.nickName, guid: noteCard.noteId, + pubDate, }; }) ); @@ -129,11 +130,13 @@ async function getFullNote(link) { desc = desc.replaceAll(/\[.*?\]/g, ''); desc = desc.replaceAll(/#(.*?)#/g, '#$1'); desc = desc.replaceAll('\n', '
'); + const pubDate = new Date(note.time); const description = `${images.map((image) => ``).join('')}
${title}
${desc}`; return { title, description, + pubDate, }; - })) as Promise<{ title: string; description: string }>; + })) as Promise<{ title: string; description: string; pubDate: Date }>; return data; } From db4d9d4bb40e6a30ae7604304af88b71d39f7388 Mon Sep 17 00:00:00 2001 From: daniel <861397272@qq.com> Date: Sun, 24 Nov 2024 16:29:36 +0800 Subject: [PATCH 13/23] fix(route) ikea/cn/low-price --- lib/routes/ikea/cn/low-price.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/routes/ikea/cn/low-price.ts b/lib/routes/ikea/cn/low-price.ts index d1c5d1a56d71df..994466f348258a 100644 --- a/lib/routes/ikea/cn/low-price.ts +++ b/lib/routes/ikea/cn/low-price.ts @@ -32,7 +32,7 @@ async function handler() { headers: generateRequestHeaders(), searchParams: { processOutOfStock: 'SORT', - groupId: 'cms_低价好物_cms-商品列表-_0', + groupId: 'cms_product_cn--zh--8b08af400ac511ec909ec36c6e99b004_0_0', page: 1, size: 200, }, @@ -42,6 +42,7 @@ async function handler() { title: 'IKEA 宜家 - 低价优选', link: 'https://www.ikea.cn/cn/zh/campaigns/wo3-men2-de-chao1-zhi2-di1-jia4-pub8b08af40', description: '低价优选', + allowEmpty: true, item: response.data.products.map((element) => generateProductItem(element)), }; } From 35dc219646ab41b704b33da62c2c3aeb4dd9636e Mon Sep 17 00:00:00 2001 From: daniel <861397272@qq.com> Date: Fri, 13 Dec 2024 22:56:00 +0800 Subject: [PATCH 14/23] =?UTF-8?q?feat(route/aves-art):=20Add=20=E5=B0=8F?= =?UTF-8?q?=E9=B8=9F=E6=96=87=E5=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves-art/index.ts | 80 ++++++++++++++++++++ lib/routes/aves-art/namespace.ts | 6 ++ lib/routes/ipswdev/index.ts | 62 --------------- lib/routes/ipswdev/namespace.ts | 7 -- lib/routes/ipswdev/templates/description.art | 20 ----- 5 files changed, 86 insertions(+), 89 deletions(-) create mode 100644 lib/routes/aves-art/index.ts create mode 100644 lib/routes/aves-art/namespace.ts delete mode 100644 lib/routes/ipswdev/index.ts delete mode 100644 lib/routes/ipswdev/namespace.ts delete mode 100644 lib/routes/ipswdev/templates/description.art diff --git a/lib/routes/aves-art/index.ts b/lib/routes/aves-art/index.ts new file mode 100644 index 00000000000000..6674310f08c501 --- /dev/null +++ b/lib/routes/aves-art/index.ts @@ -0,0 +1,80 @@ +import { Route } from '@/types'; +import got from '@/utils/got'; + +export const route: Route = { + path: '/:category?', + categories: ['other'], + example: '/aves-art', + parameters: { category: '分类,见下表,默认为信息快递' }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + name: '分类', + maintainers: ['ddddanielx'], + handler, + description: `| 小说 | 非虚构 | 档案 | 专栏 | 诗歌 | + | -------- | -------- | -------- | -------- | -------- | + | 2 | 5 | 4 | 3 | 1 |`, +}; + +async function handler(ctx) { + const { category } = ctx.req.param(); + const currentUrl = 'http://aves.art/'; + const rootUrl = 'https://app.aves.art'; + const categoryPath = '/api/lb_catalog/get_catalog_post'; + const detailPath = '/api/lb_post/detail'; + const cateMapping = new Map([ + ['1', '诗歌'], + ['2', '小说'], + ['3', '档案'], + ['4', '专栏'], + ['5', '非虚构'], + ['all', 'all'], + ]); + const title = `小鸟文学 - ${cateMapping.get(category)}`; + const description = + '小鸟文学是个独立 App,它的表达在不停变化,认识它的人都有不同的机缘。此前你可能会从各种短篇小说、长篇访谈,人类学田野笔记或者和它的前身《好奇心日报》的联系认识到它,如今它还在持续作出调整。不过它的价值观一以贯之:和我们所处的世界保持距离,与此同时又不会袖手旁观。'; + let articleIds: any[] = []; + if (category === 'all') { + const allCategoryId = ['1', '2', '3', '4', '5']; + const allCategory = await Promise.all(allCategoryId.map((item) => got.post(`${rootUrl}${categoryPath}`, { json: { catalogId: item } }))); + for (const item of allCategory) { + if (item?.data.code === 200) { + articleIds.push(...item.data.result); + } + } + articleIds = articleIds.flat().map((item) => item.id); + } else { + const { data } = await got.post(`${rootUrl}${categoryPath}`, { json: { catalogId: category } }); + if (data.code === 200) { + articleIds.push(...data.result); + } + articleIds = articleIds.map((item) => item.id); + } + let articles: any[] = await Promise.all(articleIds.map((id) => got.post(`${rootUrl}${detailPath}`, { json: { id } }))); + articles = articles.map((item) => { + if (item.data.code === 200) { + return { + title: item.data.result.title, + description: item.data.result.content, + author: item.data.result.author, + pubDate: item.data.result.publishedAt, + }; + } + return {}; + }); + return { + item: articles, + title, + link: currentUrl, + description, + language: 'zh', + image: 'https://imagedelivery.net/kDRCweMmqLnTPNlbum-pYA/prod/avatar/2798eec0-9a13-4a2f-85cf-27d00832aee3.jpeg/public', + allowEmpty: true, + }; +} diff --git a/lib/routes/aves-art/namespace.ts b/lib/routes/aves-art/namespace.ts new file mode 100644 index 00000000000000..34d8b8051af82c --- /dev/null +++ b/lib/routes/aves-art/namespace.ts @@ -0,0 +1,6 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: '小鸟文学', + url: 'http://aves.art/', +}; diff --git a/lib/routes/ipswdev/index.ts b/lib/routes/ipswdev/index.ts deleted file mode 100644 index 5c566a24e54920..00000000000000 --- a/lib/routes/ipswdev/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Data, Route } from '@/types'; -import got from '@/utils/got'; -import { load } from 'cheerio'; -import { art } from '@/utils/render'; -import path from 'node:path'; - -export const route: Route = { - path: '/index/:productID', - categories: ['program-update'], - example: '/ipswdev/index/iPhone16,1', - parameters: { - productID: 'Product ID', - }, - name: 'Apple latest beta firmware', - maintainers: ['RieN7'], - handler, -}; - -async function handler(ctx) { - const { productID } = ctx.req.param(); - const link = `https://ipsw.dev/product/version/${productID}`; - - const resp = await got({ - method: 'get', - url: link, - headers: { - Referer: host, - }, - }); - - const $ = load(resp.data); - - const productName = $('#IdentifierModal > div > div > div.modal-body > p:nth-child(1) > em').text(); - - const list: Data[] = $('.firmware') - .map((index, element) => { - const ele = $(element); - const version = ele.find('td:nth-child(1) > div > div > strong').text(); - const build = ele.find('td:nth-child(1) > div > div > div > code').text(); - const date = ele.find('td:nth-child(3)').text(); - const size = ele.find('td:nth-child(4)').text(); - return { - title: `${productName} - ${version}`, - link: `https://ipsw.dev/download/${productID}/${build}`, - pubDate: new Date(date).toLocaleDateString(), - guid: build, - description: art(path.join(__dirname, 'templates/description.art'), { - version, - build, - date, - size, - }), - }; - }) - .get(); - - return { - title: `${productName} Released`, - link, - item: list, - }; -} diff --git a/lib/routes/ipswdev/namespace.ts b/lib/routes/ipswdev/namespace.ts deleted file mode 100644 index e6c6cb1ff9b805..00000000000000 --- a/lib/routes/ipswdev/namespace.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Namespace } from '@/types'; - -export const namespace: Namespace = { - name: 'IPSW.dev', - url: 'ipsw.dev', - description: 'Download the latest beta firmware for iPhone, iPad, Mac, Apple Vision Pro, and Apple TV. Check the signing status of the beta firmware.', -}; diff --git a/lib/routes/ipswdev/templates/description.art b/lib/routes/ipswdev/templates/description.art deleted file mode 100644 index c0faef7bfac60d..00000000000000 --- a/lib/routes/ipswdev/templates/description.art +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - -
Version{{ version }}
Build{{ build }}
Released{{ released }}
Size{{ size }}
\ No newline at end of file From 378237eeb192c3c7c3846e57cc15ba9f023b6c80 Mon Sep 17 00:00:00 2001 From: daniel <861397272@qq.com> Date: Sat, 14 Dec 2024 09:12:21 +0800 Subject: [PATCH 15/23] =?UTF-8?q?feat(route/aves-art):=20Add=20=E5=B0=8F?= =?UTF-8?q?=E9=B8=9F=E6=96=87=E5=AD=A6=20(fix=20code=20review=20problem)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/{aves-art => aves}/index.ts | 43 ++++++++++++---------- lib/routes/{aves-art => aves}/namespace.ts | 0 2 files changed, 24 insertions(+), 19 deletions(-) rename lib/routes/{aves-art => aves}/index.ts (71%) rename lib/routes/{aves-art => aves}/namespace.ts (100%) diff --git a/lib/routes/aves-art/index.ts b/lib/routes/aves/index.ts similarity index 71% rename from lib/routes/aves-art/index.ts rename to lib/routes/aves/index.ts index 6674310f08c501..8ebd3740000f25 100644 --- a/lib/routes/aves-art/index.ts +++ b/lib/routes/aves/index.ts @@ -1,11 +1,12 @@ import { Route } from '@/types'; import got from '@/utils/got'; +import cache from '@/utils/cache'; export const route: Route = { path: '/:category?', categories: ['other'], - example: '/aves-art', - parameters: { category: '分类,见下表,默认为信息快递' }, + example: '/aves', + parameters: { category: '分类,见下表' }, features: { requireConfig: false, requirePuppeteer: false, @@ -15,11 +16,11 @@ export const route: Route = { supportScihub: false, }, name: '分类', - maintainers: ['ddddanielx'], + maintainers: ['dddaniel1'], handler, - description: `| 小说 | 非虚构 | 档案 | 专栏 | 诗歌 | + description: `| 诗歌 | 小说 | 专栏 | 档案 | 非虚构 | all | -------- | -------- | -------- | -------- | -------- | - | 2 | 5 | 4 | 3 | 1 |`, + | 1 | 2 | 3 | 4 | 5 | all |`, }; async function handler(ctx) { @@ -31,8 +32,8 @@ async function handler(ctx) { const cateMapping = new Map([ ['1', '诗歌'], ['2', '小说'], - ['3', '档案'], - ['4', '专栏'], + ['3', '专栏'], + ['4', '档案'], ['5', '非虚构'], ['all', 'all'], ]); @@ -56,18 +57,22 @@ async function handler(ctx) { } articleIds = articleIds.map((item) => item.id); } - let articles: any[] = await Promise.all(articleIds.map((id) => got.post(`${rootUrl}${detailPath}`, { json: { id } }))); - articles = articles.map((item) => { - if (item.data.code === 200) { - return { - title: item.data.result.title, - description: item.data.result.content, - author: item.data.result.author, - pubDate: item.data.result.publishedAt, - }; - } - return {}; - }); + const articles: any[] = await Promise.all( + articleIds.map((id) => + cache.tryGet(String(id), async () => { + const res: any = await got.post(`${rootUrl}${detailPath}`, { json: { id } }); + if (res.data.code === 200) { + return { + title: res.data.result.title, + description: res.data.result.content, + author: res.data.result.author, + pubDate: res.data.result.publishedAt, + }; + } + return {}; + }) + ) + ); return { item: articles, title, diff --git a/lib/routes/aves-art/namespace.ts b/lib/routes/aves/namespace.ts similarity index 100% rename from lib/routes/aves-art/namespace.ts rename to lib/routes/aves/namespace.ts From b8f10aa73788fd64499809396564a1a570730a43 Mon Sep 17 00:00:00 2001 From: daniel <861397272@qq.com> Date: Sat, 14 Dec 2024 18:08:31 +0800 Subject: [PATCH 16/23] =?UTF-8?q?feat(route/aves-art):=20Add=20=E5=B0=8F?= =?UTF-8?q?=E9=B8=9F=E6=96=87=E5=AD=A6=20(fix=20code=20review=20problem)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves/namespace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/aves/namespace.ts b/lib/routes/aves/namespace.ts index 34d8b8051af82c..73abf940c3ef43 100644 --- a/lib/routes/aves/namespace.ts +++ b/lib/routes/aves/namespace.ts @@ -2,5 +2,5 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: '小鸟文学', - url: 'http://aves.art/', + url: 'aves.art', }; From c958b0531e94d87e9c33b6aec625bda21f47128d Mon Sep 17 00:00:00 2001 From: daniel <861397272@qq.com> Date: Sat, 14 Dec 2024 18:24:23 +0800 Subject: [PATCH 17/23] =?UTF-8?q?feat(route/aves-art):=20Add=20=E5=B0=8F?= =?UTF-8?q?=E9=B8=9F=E6=96=87=E5=AD=A6=20(fix=20author=20display=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/aves/index.ts b/lib/routes/aves/index.ts index 8ebd3740000f25..4e991bc6ea8f86 100644 --- a/lib/routes/aves/index.ts +++ b/lib/routes/aves/index.ts @@ -65,7 +65,7 @@ async function handler(ctx) { return { title: res.data.result.title, description: res.data.result.content, - author: res.data.result.author, + author: JSON.parse(res.data.result.author)[0].name, pubDate: res.data.result.publishedAt, }; } From 47634f7fdec1d0fa6368083f08194a23bdebe615 Mon Sep 17 00:00:00 2001 From: daniel <861397272@qq.com> Date: Sat, 14 Dec 2024 21:41:18 +0800 Subject: [PATCH 18/23] =?UTF-8?q?feat(route/aves-art):=20Add=20=E5=B0=8F?= =?UTF-8?q?=E9=B8=9F=E6=96=87=E5=AD=A6=20(=E6=B7=BB=E5=8A=A0=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E6=96=87=E7=AB=A0=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves/index.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/routes/aves/index.ts b/lib/routes/aves/index.ts index 4e991bc6ea8f86..70f3e078919b1a 100644 --- a/lib/routes/aves/index.ts +++ b/lib/routes/aves/index.ts @@ -18,9 +18,9 @@ export const route: Route = { name: '分类', maintainers: ['dddaniel1'], handler, - description: `| 诗歌 | 小说 | 专栏 | 档案 | 非虚构 | all + description: `| 诗歌 | 小说 | 专栏 | 档案 | 非虚构 | all | current | -------- | -------- | -------- | -------- | -------- | - | 1 | 2 | 3 | 4 | 5 | all |`, + | 1 | 2 | 3 | 4 | 5 | all | 最新文章 |`, }; async function handler(ctx) { @@ -28,6 +28,7 @@ async function handler(ctx) { const currentUrl = 'http://aves.art/'; const rootUrl = 'https://app.aves.art'; const categoryPath = '/api/lb_catalog/get_catalog_post'; + const currentPath = '/api/lb_periodical/current'; const detailPath = '/api/lb_post/detail'; const cateMapping = new Map([ ['1', '诗歌'], @@ -36,8 +37,9 @@ async function handler(ctx) { ['4', '档案'], ['5', '非虚构'], ['all', 'all'], + ['current', '最新文章'], ]); - const title = `小鸟文学 - ${cateMapping.get(category)}`; + const title = `小鸟文学(好奇心日报) - ${cateMapping.get(category)}`; const description = '小鸟文学是个独立 App,它的表达在不停变化,认识它的人都有不同的机缘。此前你可能会从各种短篇小说、长篇访谈,人类学田野笔记或者和它的前身《好奇心日报》的联系认识到它,如今它还在持续作出调整。不过它的价值观一以贯之:和我们所处的世界保持距离,与此同时又不会袖手旁观。'; let articleIds: any[] = []; @@ -50,6 +52,12 @@ async function handler(ctx) { } } articleIds = articleIds.flat().map((item) => item.id); + } else if (category === 'current') { + const { data } = await got.post(`${rootUrl}${currentPath}`); + if (data.code === 200) { + articleIds.push(...data.result.lbPostList); + } + articleIds = articleIds.map((item) => item.id); } else { const { data } = await got.post(`${rootUrl}${categoryPath}`, { json: { catalogId: category } }); if (data.code === 200) { From 084eb9a6040dfa0eb094d9f33da4ed5e1915a056 Mon Sep 17 00:00:00 2001 From: dddaniel1 <61337842+dddaniel1@users.noreply.github.com> Date: Tue, 17 Dec 2024 07:39:22 +0800 Subject: [PATCH 19/23] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20index.ts=20=20add=20?= =?UTF-8?q?pipe=20in=20header,fix=20description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/routes/aves/index.ts b/lib/routes/aves/index.ts index 70f3e078919b1a..7ba427a2ade44e 100644 --- a/lib/routes/aves/index.ts +++ b/lib/routes/aves/index.ts @@ -18,9 +18,9 @@ export const route: Route = { name: '分类', maintainers: ['dddaniel1'], handler, - description: `| 诗歌 | 小说 | 专栏 | 档案 | 非虚构 | all | current + description: `| 诗歌 | 小说 | 专栏 | 档案 | 非虚构 | all | 最新文章 | | -------- | -------- | -------- | -------- | -------- | - | 1 | 2 | 3 | 4 | 5 | all | 最新文章 |`, + | 1 | 2 | 3 | 4 | 5 | all | current |`, }; async function handler(ctx) { From 97009bb80a1025454382a9615de82bac42ae3904 Mon Sep 17 00:00:00 2001 From: dddaniel1 <61337842+dddaniel1@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:45:00 +0800 Subject: [PATCH 20/23] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20index.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/aves/index.ts b/lib/routes/aves/index.ts index 7ba427a2ade44e..a14370ce977db0 100644 --- a/lib/routes/aves/index.ts +++ b/lib/routes/aves/index.ts @@ -19,7 +19,7 @@ export const route: Route = { maintainers: ['dddaniel1'], handler, description: `| 诗歌 | 小说 | 专栏 | 档案 | 非虚构 | all | 最新文章 | - | -------- | -------- | -------- | -------- | -------- | + | -------- | -------- | -------- | -------- | -------- | -------- | -------- | | 1 | 2 | 3 | 4 | 5 | all | current |`, }; From b2f9c671cdc3e568865c8446c60f092f595c9905 Mon Sep 17 00:00:00 2001 From: dddaniel1 <61337842+dddaniel1@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:59:09 +0800 Subject: [PATCH 21/23] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20index.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/aves/index.ts b/lib/routes/aves/index.ts index a14370ce977db0..8e62721169648b 100644 --- a/lib/routes/aves/index.ts +++ b/lib/routes/aves/index.ts @@ -5,7 +5,7 @@ import cache from '@/utils/cache'; export const route: Route = { path: '/:category?', categories: ['other'], - example: '/aves', + example: '/aves/current', parameters: { category: '分类,见下表' }, features: { requireConfig: false, From 47ee28d9567a316312af0ba92d78da238da36b62 Mon Sep 17 00:00:00 2001 From: dddaniel1 <61337842+dddaniel1@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:14:10 +0800 Subject: [PATCH 22/23] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20index.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/routes/aves/index.ts b/lib/routes/aves/index.ts index 8e62721169648b..8a9e35201c190c 100644 --- a/lib/routes/aves/index.ts +++ b/lib/routes/aves/index.ts @@ -3,8 +3,8 @@ import got from '@/utils/got'; import cache from '@/utils/cache'; export const route: Route = { - path: '/:category?', - categories: ['other'], + path: '/:category', + categories: ['reading'], example: '/aves/current', parameters: { category: '分类,见下表' }, features: { From f8aaee5a64c2a07ecd3e8b695d1557e69ff00f4a Mon Sep 17 00:00:00 2001 From: daniel <861397272@qq.com> Date: Thu, 26 Dec 2024 20:57:23 +0800 Subject: [PATCH 23/23] =?UTF-8?q?feat(route/aves-art):=20Add=20=E5=B0=8F?= =?UTF-8?q?=E9=B8=9F=E6=96=87=E5=AD=A6=20(=E4=BF=AE=E6=94=B9=E7=B7=A9?= =?UTF-8?q?=E5=AD=98Key=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/aves/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/aves/index.ts b/lib/routes/aves/index.ts index 8a9e35201c190c..d10b3e6c46803c 100644 --- a/lib/routes/aves/index.ts +++ b/lib/routes/aves/index.ts @@ -67,7 +67,7 @@ async function handler(ctx) { } const articles: any[] = await Promise.all( articleIds.map((id) => - cache.tryGet(String(id), async () => { + cache.tryGet('articleId:' + id, async () => { const res: any = await got.post(`${rootUrl}${detailPath}`, { json: { id } }); if (res.data.code === 200) { return {