diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000000..cafe685a112d33 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/lib/routes/18comic/templates/description.art b/lib/routes/18comic/templates/description.art index ffe0935976d0a0..1d69859b42fe56 100644 --- a/lib/routes/18comic/templates/description.art +++ b/lib/routes/18comic/templates/description.art @@ -1,6 +1,11 @@ {{ if cover }} {{ /if }} +

+{{each category}} +{{ $value }} +{{/each}} +

{{ introduction }}

{{ each images image }} diff --git a/lib/routes/18comic/utils.ts b/lib/routes/18comic/utils.ts index e90e55077c9e42..4f11fdd2ba65d9 100644 --- a/lib/routes/18comic/utils.ts +++ b/lib/routes/18comic/utils.ts @@ -67,6 +67,7 @@ const ProcessItems = async (ctx, currentUrl, rootUrl) => { .toArray() .map((image) => content(image).attr('data-original')), cover: content('.thumb-overlay img').first().attr('src'), + category: item.category, }); return item; diff --git a/lib/routes/aeon/utils.ts b/lib/routes/aeon/utils.ts index 5afcb4f57f3520..346ebe4df0bece 100644 --- a/lib/routes/aeon/utils.ts +++ b/lib/routes/aeon/utils.ts @@ -36,7 +36,7 @@ const getData = async (ctx, list) => { const article = data.props.pageProps.article; const capture = load(article.body); - const banner = article.thumbnail?.urls?.header; + const banner = article.image?.url; capture('p.pullquote').remove(); const authorsBio = article.authors.map((author) => '

' + author.name + author.authorBio.replaceAll(/^

/g, ' ')).join(''); diff --git a/lib/routes/docschina/weekly.ts b/lib/routes/docschina/weekly.ts index 84369ae44ba6e5..a3486580716e52 100644 --- a/lib/routes/docschina/weekly.ts +++ b/lib/routes/docschina/weekly.ts @@ -18,8 +18,8 @@ export const route: Route = { maintainers: ['daijinru', 'hestudy'], handler, description: `| javascript | node | react | - | ---------- | ---- | ----- | - | js | node | react |`, + | ---------- | ---- | ----- | + | js | node | react |`, radar: [ { source: ['docschina.org/news/weekly/js/*', 'docschina.org/news/weekly/js', 'docschina.org/'], diff --git a/lib/routes/github/file.ts b/lib/routes/github/file.ts index f44113890e448b..b1c192a0e945ca 100644 --- a/lib/routes/github/file.ts +++ b/lib/routes/github/file.ts @@ -5,14 +5,21 @@ import queryString from 'query-string'; export const route: Route = { path: '/file/:user/:repo/:branch/:filepath{.+}', + example: '/github/file/DIYgod/RSSHub/master/README.md', + parameters: { + user: 'GitHub user or org name', + repo: 'repository name', + branch: 'branch name', + filepath: 'path of target file', + }, radar: [ { source: ['github.com/:user/:repo/blob/:branch/*filepath'], target: '/file/:user/:repo/:branch/:filepath', }, ], - name: 'Unknown', - maintainers: [], + name: 'File Commits', + maintainers: ['zengxs'], handler, }; diff --git a/lib/routes/junhe/legal-updates.ts b/lib/routes/junhe/legal-updates.ts new file mode 100644 index 00000000000000..70f5e4a9182c33 --- /dev/null +++ b/lib/routes/junhe/legal-updates.ts @@ -0,0 +1,98 @@ +import { Route } from '@/types'; + +import cache from '@/utils/cache'; +import got from '@/utils/got'; +import { load } from 'cheerio'; +import { parseDate } from '@/utils/parse-date'; + +export const handler = async (ctx) => { + const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10; + + const rootUrl = 'https://junhe.com'; + const currentUrl = new URL('legal-updates', rootUrl).href; + + const { data: response } = await got(currentUrl); + + const $ = load(response); + + const language = $('html').prop('lang'); + + let items = $('a.content-wrap') + .slice(0, limit) + .toArray() + .map((item) => { + item = $(item); + + return { + title: item.find('h1.news.detail').text(), + pubDate: parseDate(item.find('p.date').text(), 'YYYY.MM.DD'), + link: new URL(item.prop('href'), rootUrl).href, + }; + }); + + items = await Promise.all( + items.map((item) => + cache.tryGet(item.link, async () => { + const { data: detailResponse } = await got(item.link); + + const $$ = load(detailResponse); + + const title = $$('h1.d-title').text(); + const description = $$('div.d-content').html(); + const infos = $$('p.d-pub-date').text().split(/\s/); + + item.title = title; + item.description = description; + item.pubDate = parseDate(infos[0], 'YYYY.MM.DD'); + item.author = infos.slice(1).join('/'); + item.content = { + html: description, + text: $$('div.d-content').text(), + }; + item.language = language; + + return item; + }) + ) + ); + + const image = new URL($('a.site-logo img').prop('src'), rootUrl).href; + + return { + title: $('title').text(), + description: $('meta[name="description"]').prop('content'), + link: currentUrl, + item: items, + allowEmpty: true, + image, + author: '君合律师事务所', + language, + }; +}; + +export const route: Route = { + path: '/legal-updates', + name: '君合法评', + url: 'junhe.com', + maintainers: ['nczitzk'], + handler, + example: '/junhe/legal-updates', + description: '', + categories: ['new-media'], + + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportRadar: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['/legal-updates'], + target: '/legal-updates', + }, + ], +}; diff --git a/lib/routes/junhe/namespace.ts b/lib/routes/junhe/namespace.ts new file mode 100644 index 00000000000000..d4f55988191e91 --- /dev/null +++ b/lib/routes/junhe/namespace.ts @@ -0,0 +1,8 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: '君合律师事务所', + url: 'junhe.com', + categories: ['new-media'], + description: '', +}; diff --git a/lib/routes/miui/community/user.ts b/lib/routes/miui/community/user.ts new file mode 100644 index 00000000000000..49f47bb755de74 --- /dev/null +++ b/lib/routes/miui/community/user.ts @@ -0,0 +1,69 @@ +import { Route, Data } from '@/types'; +import got from '@/utils/got'; + +export const route: Route = { + path: '/community/user/:uid', + categories: ['bbs'], + example: '/miui/community/user/1200057564', + parameters: { + uid: '小米用户 UID,可于网页版用户主页链接中 `uid` 项获取', + }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['web.vip.miui.com/page/info/mio/mio/homePage'], + target: (_, url) => `/miui/community/user/${new URL(url).searchParams.get('uid')}`, + }, + ], + name: '小米社区用户发帖', + maintainers: ['abc1763613206'], + handler, +}; + +const userRoot = 'https://web.vip.miui.com/page/info/mio/mio/homePage'; +const apiRoot = 'https://api.vip.miui.com/api/community/user/announce/list'; +const pageRoot = 'https://web.vip.miui.com/page/info/mio/mio/detail'; + +async function handler(ctx): Promise { + const uid = ctx.req.param('uid'); + const apiLink = `${apiRoot}?uid=${uid}&limit=10`; + const userLink = `${userRoot}?uid=${uid}`; + const { data } = await got({ + method: 'get', + url: apiLink, + headers: { + Referer: userLink, + }, + }); + if (data.code === 200) { + let authorName = ''; + const records = data.entity.records; + const items = records.map((item) => { + authorName = item.author.name; + return { + title: item.title || `${authorName} 的动态`, + description: item.textContent, + pubDate: new Date(item.createTime).toUTCString(), + author: item.author.name, + link: `${pageRoot}?postId=${item.id}`, + image: item.pic || item.cover || '', + }; + }); + return { + title: `小米社区 - ${authorName} 的发帖`, + link: userLink, + description: `${authorName} 的发帖`, + item: items, + language: 'zh-cn', + }; + } else { + throw new Error(data.message); + } +} diff --git a/lib/routes/miui/index.ts b/lib/routes/miui/firmware/index.ts similarity index 95% rename from lib/routes/miui/index.ts rename to lib/routes/miui/firmware/index.ts index ceb1ed8a76d69a..ffdeb734f61e6a 100644 --- a/lib/routes/miui/index.ts +++ b/lib/routes/miui/firmware/index.ts @@ -3,9 +3,9 @@ import got from '@/utils/got'; import queryString from 'query-string'; export const route: Route = { - path: '/:device/:type?/:region?', + path: '/firmware/:device/:type?/:region?', categories: ['program-update'], - example: '/miui/aries', + example: '/miui/firmware/aries', parameters: { device: 'the device `codename` eg. `aries` for Mi 2S', type: 'type', region: 'Region, default to `cn`' }, name: 'New firmware', maintainers: ['Indexyz'], diff --git a/lib/routes/pts/index.ts b/lib/routes/pts/index.ts index b64162ea67fdfc..c98290cbe29477 100644 --- a/lib/routes/pts/index.ts +++ b/lib/routes/pts/index.ts @@ -29,7 +29,7 @@ async function handler(ctx) { const $ = load(response.data); - let items = $('h2 a') + let items = $('h1 a,h2 a') .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 30) .toArray() .map((item) => { diff --git a/package.json b/package.json index 1fd6485821b8e1..969610d08045b1 100644 --- a/package.json +++ b/package.json @@ -23,19 +23,19 @@ "scripts": { "build": "tsx scripts/workflow/build-routes.ts", "build:docs": "tsx scripts/workflow/build-docs.ts", - "dev": "NODE_ENV=dev tsx watch --no-cache lib/index.ts", - "dev:cache": "NODE_ENV=production tsx watch lib/index.ts", + "dev": "cross-env NODE_ENV=dev tsx watch --no-cache lib/index.ts", + "dev:cache": "cross-env NODE_ENV=production tsx watch lib/index.ts", "format": "eslint --cache --fix \"**/*.{ts,js,yml}\" && prettier \"**/*.{ts,js,json}\" --write", "format:check": "eslint --cache \"**/*.{ts,js,yml}\" && prettier \"**/*.{ts,js,json}\" --check", "format:staged": "lint-staged", - "vitest": "NODE_ENV=test vitest", - "vitest:fullroutes": "NODE_ENV=test FULL_ROUTES_TEST=true vitest --reporter=json --reporter=default --outputFile=\"./assets/build/test-full-routes.json\" routes", - "vitest:coverage": "NODE_ENV=test vitest --coverage.enabled --reporter=junit", - "vitest:watch": "NODE_ENV=test vitest --watch", + "vitest": "cross-env NODE_ENV=test vitest", + "vitest:fullroutes": "cross-env NODE_ENV=test FULL_ROUTES_TEST=true vitest --reporter=json --reporter=default --outputFile=\"./assets/build/test-full-routes.json\" routes", + "vitest:coverage": "cross-env NODE_ENV=test vitest --coverage.enabled --reporter=junit", + "vitest:watch": "cross-env NODE_ENV=test vitest --watch", "lint": "eslint --cache .", "prepare": "husky || true", - "profiling": "NODE_ENV=production tsx --prof lib/index.ts", - "start": "NODE_ENV=production tsx lib/index.ts", + "profiling": "cross-env NODE_ENV=production tsx --prof lib/index.ts", + "start": "cross-env NODE_ENV=production tsx lib/index.ts", "test": "npm run format:check && npm run vitest:coverage" }, "lint-staged": { @@ -63,14 +63,16 @@ "cheerio": "1.0.0-rc.12", "chrono-node": "2.7.5", "city-timezones": "1.2.1", + "cross-env": "7.0.3", "crypto-js": "4.2.0", "currency-symbol-map": "5.1.0", "dayjs": "1.11.8", - "directory-import": "3.2.1", + "directory-import": "3.3.1", "dotenv": "16.4.5", "entities": "4.5.0", "etag": "1.8.1", "fanfou-sdk": "5.0.0", + "form-data": "4.0.0", "git-rev-sync": "3.0.2", "googleapis": "134.0.0", "got": "14.2.1", @@ -111,7 +113,7 @@ "telegram": "2.20.2", "tiny-async-pool": "2.1.0", "title": "3.5.3", - "tldts": "6.1.14", + "tldts": "6.1.15", "tough-cookie": "4.1.3", "tsx": "4.7.1", "twitter-api-v2": "1.16.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db69f0c9f479bc..9d80c751bf6b9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ dependencies: city-timezones: specifier: 1.2.1 version: 1.2.1 + cross-env: + specifier: 7.0.3 + version: 7.0.3 crypto-js: specifier: 4.2.0 version: 4.2.0 @@ -54,8 +57,8 @@ dependencies: specifier: 1.11.8 version: 1.11.8 directory-import: - specifier: 3.2.1 - version: 3.2.1 + specifier: 3.3.1 + version: 3.3.1 dotenv: specifier: 16.4.5 version: 16.4.5 @@ -68,6 +71,9 @@ dependencies: fanfou-sdk: specifier: 5.0.0 version: 5.0.0 + form-data: + specifier: 4.0.0 + version: 4.0.0 git-rev-sync: specifier: 3.0.2 version: 3.0.2 @@ -189,8 +195,8 @@ dependencies: specifier: 3.5.3 version: 3.5.3 tldts: - specifier: 6.1.14 - version: 6.1.14 + specifier: 6.1.15 + version: 6.1.15 tough-cookie: specifier: 4.1.3 version: 4.1.3 @@ -3881,6 +3887,14 @@ packages: typescript: 5.4.3 dev: false + /cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + dev: false + /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -3896,7 +3910,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} @@ -4126,8 +4139,8 @@ packages: path-type: 4.0.0 dev: true - /directory-import@3.2.1: - resolution: {integrity: sha512-hIMc9WJHs4HRZbBiNAvKGxkhjaJ2wOaFcFA0K+yj16GyBJDg28e6QgQ3hFuFB0ZRETNYZu3u85rmxIxoxWEwbA==} + /directory-import@3.3.1: + resolution: {integrity: sha512-d9paCbverdqmuwR+B40phSqiHhgPKiP8dpsMz5WT9U6ug2VVQ3tqXNCedpa6iGHg6mgv9lHaoq5DJUu2IXMjsQ==} engines: {node: '>=18.17.0'} dev: false @@ -7236,7 +7249,6 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} @@ -8092,7 +8104,6 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@1.0.0: resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} @@ -8102,7 +8113,6 @@ packages: /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /shelljs@0.8.5: resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} @@ -8624,15 +8634,15 @@ packages: hasBin: true dev: false - /tldts-core@6.1.14: - resolution: {integrity: sha512-McyMQkkIUFYhfs3FPTxTn+5mewxERhfwy2x7TWHkBPb1poKaTBJhXehtuMg0FrhXp53J5eXRfvSD/oH/3mk/2A==} + /tldts-core@6.1.15: + resolution: {integrity: sha512-E/gy+y6I53cZcqbHllhS5Aak6DmMZSO9tX6DYw3NXZk+f1yoFKGLiojQ8ww39G5QAupKKZL+vehNVqzSS0SQgQ==} dev: false - /tldts@6.1.14: - resolution: {integrity: sha512-zGbimRt9fHP68Gbj5fGWmR90xiuRDK/iYukHevT9mcG6Job+HfR119D9DSr6voFWjpStJwOtVfWGpbhNYVwt3A==} + /tldts@6.1.15: + resolution: {integrity: sha512-0/2FOdXnP5ON6PTQfE7B4X1ymqa1Bs6oG/yHKtVMxLSlUPlb/1AdWGzz2Co3P/uM0eUPWOt61L7cayyyNUSh7A==} hasBin: true dependencies: - tldts-core: 6.1.14 + tldts-core: 6.1.15 dev: false /tmp@0.0.33: @@ -9255,7 +9265,6 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: true /why-is-node-running@2.2.2: resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}