chore(deps-dev): bump eslint-plugin-unicorn from 60.0.0 to 61.0.2 (#19998)

* chore(deps-dev): bump eslint-plugin-unicorn from 60.0.0 to 61.0.1

Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 60.0.0 to 61.0.1.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v60.0.0...v61.0.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-unicorn
  dependency-version: 61.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* refactor: replace `replace(/regex/g)` with replaceAll

* refactor: replace sort() with toSorted() for immutability in various routes and utilities

* refactor: fix unicorn/prefer-array-find

* refactor: fix unicorn/prefer-at

* refactor: add unicorn/no-array-sort rule to ESLint configuration

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
dependabot[bot] 2025-09-08 23:03:48 +08:00 committed by GitHub
parent ad1bfc539c
commit 802cbf95d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 130 additions and 92 deletions

View File

@ -141,6 +141,7 @@ unicorn.configs.recommended,
'unicorn/no-array-callback-reference': 'warn',
'unicorn/no-array-reduce': 'warn',
'unicorn/no-array-sort': 'warn',
'unicorn/no-await-expression-member': 'off',
'unicorn/no-empty-file': 'warn',
'unicorn/no-hex-escape': 'warn',

View File

@ -80,7 +80,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
// sort items
if (ctx.req.query('sorted') !== 'false') {
data.item = data.item.sort((a: DataItem, b: DataItem) => +new Date(b.pubDate || 0) - +new Date(a.pubDate || 0));
data.item = data.item.toSorted((a: DataItem, b: DataItem) => +new Date(b.pubDate || 0) - +new Date(a.pubDate || 0));
}
const handleItem = (item: DataItem) => {

View File

@ -155,7 +155,7 @@ const sortRoutes = (
}
>
) =>
Object.entries(routes).sort(([pathA], [pathB]) => {
Object.entries(routes).toSorted(([pathA], [pathB]) => {
const segmentsA = pathA.split('/');
const segmentsB = pathB.split('/');
const lenA = segmentsA.length;

View File

@ -16,6 +16,6 @@ export const getToken = () =>
export const sign = (requestPath: string, payload: Record<string, any> = {}, timestamp: number, token: string) => {
payload.timestamp = timestamp;
payload.token = token;
const sortedParams = Object.keys(payload).sort();
const sortedParams = Object.keys(payload).toSorted();
return md5(md5(requestPath) + md5(sortedParams + md5(token) + timestamp));
};

View File

@ -132,7 +132,7 @@ async function handler(ctx) {
if (enclosureMatches) {
const enclosureMatch = enclosureMatches
.map((e) => e.match(new RegExp(enclosurePattern)))
.sort((a, b) => Number.parseInt(a[2], 10) - Number.parseInt(b[2], 10))
.toSorted((a, b) => Number.parseInt(a[2], 10) - Number.parseInt(b[2], 10))
.pop();
item.enclosure_url = enclosureMatch[3];

View File

@ -86,7 +86,7 @@ async function handler(ctx) {
uniqueItems.push(item!);
}
}
items = uniqueItems.sort((a, b) => b.pubDate - a.pubDate).slice(0, ctx.req.query('limit') || 20);
items = uniqueItems.toSorted((a, b) => b.pubDate - a.pubDate).slice(0, ctx.req.query('limit') || 20);
items = await Promise.all(
items.map((item) =>

View File

@ -81,7 +81,7 @@ async function handler(ctx) {
}
})
.filter(Boolean)
.sort((a, b) => b.pubDate - a.pubDate)
.toSorted((a, b) => b.pubDate - a.pubDate)
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20);
const items = ctx.req.query('fulltext') === 'true' ? await pMap(list, (item) => fetchArticle(item), { concurrency: 10 }) : list;

View File

@ -79,7 +79,7 @@ async function handler(ctx) {
return res;
})
.filter((e) => Boolean(e.link) && !new URL(e.link).pathname.split('/').includes('hub'))
.sort((a, b) => (a.pubDate && b.pubDate ? b.pubDate - a.pubDate : b.lastmod - a.lastmod))
.toSorted((a, b) => (a.pubDate && b.pubDate ? b.pubDate - a.pubDate : b.lastmod - a.lastmod))
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20);
const items = ctx.req.query('fulltext') === 'true' ? await pMap(list, (item) => fetchArticle(item), { concurrency: 20 }) : list;

View File

@ -130,7 +130,7 @@ async function handler(ctx) {
return {
title: `${appName} ${item.versionDisplay} for ${p}`,
link: currentUrl,
description: item.releaseNotes?.replace(/\n/g, '<br>'),
description: item.releaseNotes?.replaceAll('\n', '<br>'),
category: [p],
guid: `apple/apps/${country}/${id}/${pid}#${item.versionDisplay}`,
pubDate: parseDate(item.releaseTimestamp),
@ -147,7 +147,7 @@ async function handler(ctx) {
item: items,
title: `${title} - Apple App Store`,
link: currentUrl,
description: description?.replace(/\n/g, ' '),
description: description?.replaceAll('\n', ' '),
language: $('html').prop('lang'),
image: $('meta[property="og:image"]').prop('content'),
icon,

View File

@ -56,7 +56,7 @@ async function handler(ctx) {
date: $el.children().first().find('small').children().remove().end().text().slice(3),
};
});
const finalLatestReplies = [...latestReplies, ...latestSubReplies].sort((a, b) => (a.id < b.id ? 1 : -1));
const finalLatestReplies = [...latestReplies, ...latestSubReplies].toSorted((a, b) => (a.id < b.id ? 1 : -1));
const postTopic = {
title,

View File

@ -45,7 +45,7 @@ async function handler(ctx) {
link,
description: `视频 ${name} 的视频选集列表`,
item: data
.sort((a, b) => b.page - a.page)
.toSorted((a, b) => b.page - a.page)
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10)
.map((item) => ({
title: item.part,

View File

@ -595,7 +595,7 @@
}
argvPtrs.push(0);
const keys = Object.keys(this.env).sort();
const keys = Object.keys(this.env).toSorted();
for (const key of keys) {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
}

View File

@ -48,7 +48,7 @@ async function handler(ctx) {
pubDate: timezone(parseDate(e.find('span').text().replace('', ''), 'YYYY-MM-DD'), 8),
};
})
.sort((a, b) => b.pubDate - a.pubDate)
.toSorted((a, b) => b.pubDate - a.pubDate)
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10);
const items = await Promise.all(

View File

@ -115,7 +115,7 @@ function getItems(list) {
const { data: descrptionResponse } = await got(item.link);
const $descrption = load(descrptionResponse);
const desc = $descrption('#main > div.content > div.search_height > div.search_con:has(p)').html();
item.description = desc?.replace(/(\r|\n)+/g, '<br />');
item.description = desc?.replaceAll(/(\r|\n)+/g, '<br />');
item.author = $descrption('#main > div.content > div.search_height > span.search_con').text().split('发布者:').at(-1) || '教务部';
return item;
})

View File

@ -94,7 +94,7 @@ async function handler(ctx) {
} else {
const buildUnitScript = $('script[parseType="bulidstatic"]');
const queryUrl = `${baseUrl}${buildUnitScript.attr('url')}`;
const queryData = JSON.parse(buildUnitScript.attr('querydata')?.replace(/'/g, '"') ?? '{}');
const queryData = JSON.parse(buildUnitScript.attr('querydata')?.replaceAll("'", '"') ?? '{}');
queryData.paramJson = `{"pageNo":1,"pageSize":${limit}}`;
const { data } = await got.get<{ data: { html: string } }>(queryUrl, {

View File

@ -82,7 +82,7 @@ async function handler(ctx) {
ordered,
// index,
}))
.sort((a, b) => b.ordered - a.ordered);
.toSorted((a, b) => b.ordered - a.ordered);
return chapters;
},

View File

@ -50,7 +50,7 @@ const handler: Route['handler'] = async (ctx) => {
link: absoluteLink,
};
})
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
.toSorted((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
.slice(0, 10);
const fetchDataItem = (item: { title: string; date: string; link: string }) =>

View File

@ -42,7 +42,7 @@ async function handler(ctx) {
const items = await Promise.all(
chapters.chapters
.sort((a, b) => b.idx - a.idx)
.toSorted((a, b) => b.idx - a.idx)
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 3)
.map(async (c) => {
let pages;

View File

@ -26,7 +26,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
while ((match = updateRegex.exec(response)) !== null && items.length < limit) {
const headerLine: string | undefined = match[2].trim();
const description: string | undefined = match[4].trim()?.replace(/(\s[+-])/g, '<br>$1');
const description: string | undefined = match[4].trim()?.replaceAll(/(\s[+-])/g, '<br>$1');
let version: string = 'N/A';
let pubDateStr: string | undefined = undefined;

View File

@ -3,7 +3,7 @@ import md5 from '@/utils/md5';
function hash(images) {
const entries = Object.entries(images)
.map((x) => `${x[1].os}/${x[1].architecture},${x[1].digest}`)
.sort((a, b) => a.localeCompare(b));
.toSorted((a, b) => a.localeCompare(b));
const text = entries.join('|');
return md5(text);
}

View File

@ -82,7 +82,7 @@ async function getContent(nextBuildId: string, contentId: string) {
const description =
content
.html()
?.replace(rubyRegex, '$1$2')
?.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]/gm, '') ?? '';
?.replaceAll(rubyRegex, '$1$2')
?.replaceAll(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]/gm, '') ?? '';
return description;
}

View File

@ -45,7 +45,7 @@ export const route: Route = {
item: feed.items.map((item) => ({
title: item.title ?? '',
link: item.link,
description: sanitizeHtml(item.content?.replace(/href="\/(.+?)"/g, `href="https://github.com/$1"`) ?? '', { allowedTags: [...sanitizeHtml.defaults.allowedTags, 'img'] }),
description: sanitizeHtml(item.content?.replaceAll(/href="\/(.+?)"/g, `href="https://github.com/$1"`) ?? '', { allowedTags: [...sanitizeHtml.defaults.allowedTags, 'img'] }),
pubDate: item.pubDate ? parseDate(item.pubDate) : undefined,
author: item.author,
guid: item.id,

View File

@ -49,7 +49,7 @@ async function handler(ctx) {
pubDate: timezone(parseDate($_chapter.find('nobr').text(), 'YYYYMMDD HH:mm'), +9),
};
})
.sort((a, b) => (a.pubDate <= b.pubDate ? 1 : -1))
.toSorted((a, b) => (a.pubDate <= b.pubDate ? 1 : -1))
.slice(0, limit);
const item_list = await Promise.all(

View File

@ -63,7 +63,7 @@ async function handler(ctx) {
gkdiy: 'GK/其他',
};
const filterArr = catType.split('|').sort();
const filterArr = catType.split('|').toSorted();
const filterSet = new Set(filterArr.map((e: string) => classMap[e]));
if (catType.includes('all')) {

View File

@ -77,7 +77,7 @@ async function handler(ctx) {
author: item.paper.authors.map((author) => author.name).join(', '),
upvotes: item.paper.upvotes,
}))
.sort((a, b) => b.upvotes - a.upvotes);
.toSorted((a, b) => b.upvotes - a.upvotes);
return {
title: 'Huggingface Daily Papers',

View File

@ -57,7 +57,7 @@ export const handler = async (ctx) => {
)
);
const title = $('meta[name="keywords"]').prop('content')?.replace(/,/g, ' - ') ?? $('title').text();
const title = $('meta[name="keywords"]').prop('content')?.replaceAll(',', ' - ') ?? $('title').text();
const image = new URL($('div.logo img').prop('src'), rootUrl).href;
return {

View File

@ -75,7 +75,7 @@ export const handler = async (ctx) => {
)
);
const title = $('meta[name="keywords"]').prop('content')?.replace(/,/g, ' - ') ?? $('title').text();
const title = $('meta[name="keywords"]').prop('content')?.replaceAll(',', ' - ') ?? $('title').text();
const image = new URL($('div.logo img').prop('src'), rootUrl).href;
return {

View File

@ -257,7 +257,7 @@ const generateSignature = () => {
const appSecret = 'hUzaABtNfDE-6UiyaYhfsmjW-8dnoyVc';
const nonce = generateNonce();
const r = [appSecret, timestamp, nonce].sort();
const r = [appSecret, timestamp, nonce].toSorted();
return {
nonce,
timestamp,
@ -363,7 +363,7 @@ const processItems = async (items, limit, tryGet) => {
return {
...audioItem,
...videoItem,
title: (item.title ?? item.summary ?? item.content)?.replace(/<\/?(?:em|br)?>/g, ''),
title: (item.title ?? item.summary ?? item.content)?.replaceAll(/<\/?(?:em|br)?>/g, ''),
link,
description: art(path.join(__dirname, 'templates/description.art'), {
image: {

View File

@ -90,7 +90,7 @@ export const handler = async (ctx) => {
const script = $$('script[type="text/javascript"]').text();
const videoSrc = script.match(/P\.s\s=\s'(.*?)';/)?.[1] ?? undefined;
const poster = script.match(/P\.c\(.*?isWideScreen,\s'(.*?)',\s/)?.[1] ?? undefined;
const topicsStr = script.match(/var\stopicsInPage\s=\sJSON\.parse\('(.*?)'\);/)?.[1]?.replace(/\\/g, '') ?? undefined;
const topicsStr = script.match(/var\stopicsInPage\s=\sJSON\.parse\('(.*?)'\);/)?.[1]?.replaceAll('\\', '') ?? undefined;
if (videoSrc) {
$$('div.player').replaceWith(

View File

@ -12,7 +12,7 @@ const renderItems = (items) =>
switch (productType) {
case 'carousel_container': {
const images = item.carousel_media.map((i) => ({
...i.image_versions2.candidates.sort((a, b) => b.width - a.width)[0],
...i.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0],
alt: item.accessibility_caption,
}));
description = art(path.join(__dirname, 'templates/images.art'), {
@ -25,12 +25,12 @@ const renderItems = (items) =>
case 'igtv':
description = art(path.join(__dirname, 'templates/video.art'), {
summary,
image: item.image_versions2.candidates.sort((a, b) => b.width - a.width)[0],
image: item.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0],
video: item.video_versions[0],
});
break;
case 'feed': {
const images = [{ ...item.image_versions2.candidates.sort((a, b) => b.width - a.width)[0], alt: item.accessibility_caption }];
const images = [{ ...item.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0], alt: item.accessibility_caption }];
description = art(path.join(__dirname, 'templates/images.art'), {
summary,
images,

View File

@ -158,7 +158,7 @@ async function handler(ctx) {
});
if (magnets) {
item.enclosure_url = magnets.sort((a, b) => b.score - a.score)[0].link;
item.enclosure_url = magnets.toSorted((a, b) => b.score - a.score)[0].link;
item.enclosure_type = 'application/x-bittorrent';
}
} catch {

View File

@ -68,7 +68,7 @@ const ProcessItems = async (language, currentUrl, tryGet) => {
item.description = art(path.join(__dirname, 'templates/description.art'), {
cover: content('#video_jacket_img').attr('src'),
info: content('#video_info').html().replaceAll('span><span', 'span>,&nbsp;<span'),
comment: item.description?.replace(/\[img]/g, '<img src="')?.replace(/\[\/img]/g, '"/>'),
comment: item.description?.replaceAll('[img]', '<img src="')?.replaceAll('[/img]', '"/>'),
thumbs: content('.previewthumbs img')
.toArray()
.map((img) => content(img).attr('src').replaceAll('-', 'jp-')),

View File

@ -87,7 +87,7 @@ async function handler(ctx) {
const items = response.activities
.map((activity) => processActivity(activity, issueId))
.filter((item) => item !== null)
.sort((a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime())
.toSorted((a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime())
.slice(0, limit);
return {

View File

@ -23,7 +23,7 @@ export function sign(payload: Map<string, any>) {
}
const sortedString = lowerCaseKeys
.sort()
.toSorted()
.map((key) => key + '=' + map.get(key))
.join('');
const linkedString = link('--'.slice(0, 1), '#CEAIWER', '892F', 'KB97', 'JKB6', 'HJ7OC7C8', 'GJZG');

View File

@ -49,7 +49,7 @@ async function handler(ctx: Context): Promise<Data> {
const episodes = values.filter((value) => value.__typename === 'Episode') as NextDataEpisode[];
const items = (await Promise.all(
episodes
.sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))
.toSorted((a, b) => b.publishedAt.localeCompare(a.publishedAt))
.slice(0, limit)
.map((item) => {
const episodeUrl = `https://kakuyomu.jp/works/${id}/episodes/${item.id}`;

View File

@ -63,7 +63,7 @@ export const route: Route = {
::: tip
When \`posts\` is selected as the value of the parameter **source**, the parameter **id** does not take effect.
There is an optinal parameter **limit** which controls the number of posts to fetch, default value is 25.
Support for announcements and fancards:
- Use \`/:source/:id/announcements\` to get announcements
- Use \`/:source/:id/fancards\` to get fancards
@ -201,7 +201,7 @@ async function processDiscordMessages(channels: any[], limit: number) {
return channelResponse.data
.filter((message: DiscordMessage) => message.content || message.attachments)
.sort((a, b) => b.id.localeCompare(a.id))
.toSorted((a, b) => b.id.localeCompare(a.id))
.slice(0, limit)
.map((message: DiscordMessage) => ({
title: message.content || 'Discord Message',

View File

@ -62,7 +62,7 @@ async function handler(ctx) {
},
});
const sortedChapters = chapterData.data.chaptersByComicId.sort((a, b) => Date.parse(b.dateUpdated) - Date.parse(a.dateUpdated));
const sortedChapters = chapterData.data.chaptersByComicId.toSorted((a, b) => Date.parse(b.dateUpdated) - Date.parse(a.dateUpdated));
const chapterLimit = Number(limit) || sortedChapters.length;
const filteredChapters = sortedChapters.slice(0, chapterLimit);

View File

@ -53,7 +53,7 @@ export const route: Route = {
title: each.vod_name,
image: each.vod_pic,
link: `https://${domain}/vod/${each.vod_id}/`,
guid: each.vod_play_url?.match(/https:\/\/.+?\.m3u8/g)?.slice(-1)[0],
guid: each.vod_play_url?.match(/https:\/\/.+?\.m3u8/g)?.at(-1),
pubDate: timezone(parseDate(each.vod_time, 'YYYY-MM-DD HH:mm:ss'), +8),
category: [each.type_name, ...each.vod_class!.split(',')],
description: render(each, `https://${domain}/vod/${each.vod_id}/`) + each.vod_content,

View File

@ -137,7 +137,7 @@ async function handler(ctx) {
? $(script)
.text()
?.match(/\$\('#lower'\)\.prepend\('(.*)'\);/)?.[1]
?.replaceAll(/\\"/g, '"')
?.replaceAll(String.raw`\"`, '"')
: '';
if (lowerContent) {
const $ = cheerio.load(lowerContent, null, false);

View File

@ -49,7 +49,7 @@ async function handler(ctx) {
}))
);
items = items.sort((a, b) => b.pubDate - a.pubDate).slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30);
items = items.toSorted((a, b) => b.pubDate - a.pubDate).slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30);
items = await Promise.all(
items.map((item) =>

View File

@ -136,10 +136,7 @@ async function handler(ctx) {
item.author = content('meta[name="author"]').attr('content');
item.title = item.title ?? content('meta[name="twitter:title"]').attr('content');
item.description = content('#contentDiv')
.html()
?.replace(/&nbsp;/g, '')
.replaceAll('<p></p>', '');
item.description = content('#contentDiv').html()?.replaceAll('&nbsp;', '').replaceAll('<p></p>', '');
return item;
})

View File

@ -100,7 +100,7 @@ async function handler(ctx) {
link: `https://news.now.com/home/${category}/player?newsId=${item.newsId}`,
pubDate: parseDate(item.publishDate, 'x'),
category: [...item.sportTypes.map((t) => t.sportTypeNameChi), ...item.players.map((p) => p.playerFullNameChi), ...item.teams.map((t) => t.teamCodeChi)],
image: item.newsPhotos?.filter((p) => p.sizeType === '3')?.[0]?.imageUrl,
image: item.newsPhotos?.find((p) => p.sizeType === '3')?.imageUrl,
newsId: item.newsId,
};
})

View File

@ -136,7 +136,7 @@ async function handler(ctx) {
// Match 感谢|謝.*?cn.letters@nytimes.com。
const ending = /&#x611F;(&#x8C22|&#x8B1D);.*?cn\.letters@nytimes\.com&#x3002;/g;
single.description = result.description?.replace(ending, '');
single.description = result.description?.replaceAll(ending, '');
if (hasEnVersion) {
single.title = result.title;

View File

@ -15,7 +15,7 @@ export async function getSFWUserNovels(id: string, fullContent: boolean = false,
});
const novels = Object.keys(allData.body.novels)
.sort((a, b) => Number(b) - Number(a))
.toSorted((a, b) => Number(b) - Number(a))
.slice(0, Number.parseInt(String(limit), 10));
if (novels.length === 0) {

View File

@ -81,6 +81,6 @@ export function processContent($: CheerioAPI, lang: string): string {
return (
$('.am__body')
.html()
?.replace(/https:\/\/i\.pximg\.net/g, config.pixiv.imgProxy || '') || ''
?.replaceAll('https://i.pximg.net', config.pixiv.imgProxy || '') || ''
);
}

View File

@ -34,7 +34,7 @@ export const route: Route = {
};
async function handler(ctx) {
const category = ctx.req.param('category')?.replace(/-/g, '/') ?? 'zxgg';
const category = ctx.req.param('category')?.replaceAll('-', '/') ?? 'zxgg';
const rootUrl = 'https://hr.pku.edu.cn/';
const currentUrl = `${rootUrl}/${category}/index.htm`;

View File

@ -56,7 +56,7 @@ async function handler(ctx) {
};
});
const sorted = list.sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime()).slice(0, 10);
const sorted = list.toSorted((a, b) => b.pubDate.getTime() - a.pubDate.getTime()).slice(0, 10);
return {
title: `北京大学学生就业指导服务中心 - ${feed_title}`,

View File

@ -87,7 +87,7 @@ async function handler(ctx) {
for (const item of items) {
result = [...result, ...item];
}
result = result.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
result = result.toSorted((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
return {
title: `${id} 的 PSN 奖杯`,

View File

@ -26,7 +26,7 @@ const generateNonce = (length: number): string => {
export const getSignedHeaders = () => {
const nonce = generateNonce(6);
const timestamp = Date.now().toString();
const signature = sha1([salt, timestamp, nonce].sort().join(''));
const signature = sha1([salt, timestamp, nonce].toSorted().join(''));
return {
nonce,
timestamp,

View File

@ -26,7 +26,7 @@ export const route: Route = {
name: '高瓴人工智能学院',
maintainers: ['yinhanyan'],
handler: async (ctx) => {
const category = ctx.req.param('category')?.replace(/-/g, '/') ?? 'newslist/notice';
const category = ctx.req.param('category')?.replaceAll('-', '/') ?? 'newslist/notice';
const baseURL = `http://ai.ruc.edu.cn/${category}/`;
const indexUrl = baseURL + 'index.htm';
const response = await ofetch(indexUrl);

View File

@ -34,7 +34,7 @@ export const route: Route = {
};
async function handler(ctx) {
const category = ctx.req.param('category')?.replace(/-/g, '/') ?? 'tzgg';
const category = ctx.req.param('category')?.replaceAll('-', '/') ?? 'tzgg';
const rootUrl = 'http://hr.ruc.edu.cn';
const currentUrl = `${rootUrl}/${category}/index.htm`;

View File

@ -62,7 +62,7 @@ const post = async (requestPath: string, accessToken = md5(Date.now().toString()
};
function sortBy(items: any[], key: string) {
return items.sort((a, b) => {
return items.toSorted((a, b) => {
if (a[key] < b[key]) {
return -1;
}

View File

@ -128,7 +128,7 @@ async function handler(ctx) {
.text()
.match(/contentData = (.*)/)?.[1]
)
.sort((a: any, b: any) => b.length - a.length)[0] || '{}'
.toSorted((a: any, b: any) => b.length - a.length)[0] || '{}'
);
const blockRenderData = JSON.parse(
$('script:contains("column_2_text")')

View File

@ -41,7 +41,7 @@ const renderMedia = (mediaEntities) =>
case 'photo':
return `<img src="${e.mediaURL}">`;
case 'video': {
const video = e.videoInfo.variants.filter((v) => v.type === 'video/mp4').sort((a, b) => b.bitrate - a.bitrate)[0];
const video = e.videoInfo.variants.filter((v) => v.type === 'video/mp4').toSorted((a, b) => b.bitrate - a.bitrate)[0];
return `<video controls preload="metadata" poster="${e.mediaURL}"><source src="${video.url}" type="video/mp4"></video>`;
}
default:

View File

@ -31,7 +31,7 @@ export const route: Route = {
const findLargestImgKey = (images) =>
Object.keys(images)
.filter((key) => key.startsWith('inline_free_') || key.startsWith('hero_landscape_'))
.sort((a, b) => Number.parseInt(b.split('_')[2]) - Number.parseInt(a.split('_')[2]))[0];
.toSorted((a, b) => Number.parseInt(b.split('_')[2]) - Number.parseInt(a.split('_')[2]))[0];
const renderFigure = (url, caption) => `<figure><img src="${url}" alt="${caption}" /><figcaption>${caption}</figcaption></figure>`;

View File

@ -49,7 +49,7 @@ async function handler(): Promise<Data> {
const updates = dates
.map((date, index) => ({
date,
content: contents[index]?.replace(/\n/g, '<br>') ?? '',
content: contents[index]?.replaceAll('\n', '<br>') ?? '',
}))
.filter((update) => update.content);

View File

@ -70,7 +70,7 @@ export async function handleIsekaiRanking(type: string, limit: number): Promise<
}
const items = uniqueNovels
.sort((a, b) => (b[pointField] || 0) - (a[pointField] || 0))
.toSorted((a, b) => (b[pointField] || 0) - (a[pointField] || 0))
.map((novel, index) => ({
title: `#${index + 1} ${novel.title}`,
link: `https://ncode.syosetu.com/${String(novel.ncode).toLowerCase()}`,

View File

@ -147,7 +147,7 @@ async function handler(ctx) {
alt: item.venueName ?? item.title,
}
: undefined,
description: item.description?.replace(/\["|"]/g, '') ?? undefined,
description: item.description?.replaceAll(/\["|"]/g, '') ?? undefined,
data: item.parkingLocationId
? {
title: item.venueName ?? item.title,

View File

@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date';
import { type CheerioAPI, load } from 'cheerio';
import { type Context } from 'hono';
const escapeHtml = (text: string): string => text?.replace(/&/g, '&amp;')?.replace(/</g, '&lt;')?.replace(/>/g, '&gt;')?.replace(/'/g, '&quot;')?.replace(/'/g, '&#039;') ?? text;
const escapeHtml = (text: string): string => text?.replaceAll('&', '&amp;')?.replaceAll('<', '&lt;')?.replaceAll('>', '&gt;')?.replaceAll("'", '&quot;')?.replaceAll("'", '&#039;') ?? text;
const parseTextChildren = (children: any[]): string => children.map((child: any) => escapeHtml(child.text)).join('');

View File

@ -57,12 +57,12 @@ async function handler(ctx) {
switch (item.cell_type) {
case 0:
case 49: {
const video = item.video.play_addr_list.sort((a, b) => b.bitrate - a.bitrate)[0];
const video = item.video.play_addr_list.toSorted((a, b) => b.bitrate - a.bitrate)[0];
return {
title: item.title,
description: art(path.join(__dirname, 'templates/video.art'), {
poster: item.video.origin_cover.url_list[0],
url: item.video.play_addr_list.sort((a, b) => b.bitrate - a.bitrate)[0].play_url_list[0],
url: item.video.play_addr_list.toSorted((a, b) => b.bitrate - a.bitrate)[0].play_url_list[0],
}),
link: `https://www.toutiao.com/video/${item.id}/`,
pubDate: parseDate(item.publish_time, 'X'),

View File

@ -280,7 +280,7 @@ const getUserTweets = async (id, params = {}) => {
return !idSet.has(id_str) && idSet.add(id_str) && tweet;
}) // deduplicate
.filter(Boolean) // remove null
.sort((a, b) => (b.id_str || b.conversation_id_str) - (a.id_str || a.conversation_id_str)) // desc
.toSorted((a, b) => (b.id_str || b.conversation_id_str) - (a.id_str || a.conversation_id_str)) // desc
.slice(0, 20);
cache.set(cacheKey, JSON.stringify(tweets));
return tweets;

View File

@ -71,12 +71,12 @@ async function handler(ctx) {
.remove()
.end()
.html()
?.replace(/(<img.*?) src=".*?"(.*?>)/g, '$1$2')
?.replaceAll(/(<img.*?) src=".*?"(.*?>)/g, '$1$2')
.replaceAll(/(<img.*?)zoomfile(.*?>)/g, '$1src$2'),
pattl: content(item)
.find('div.pattl')
.html()
?.replace(/(<img.*?) src=".*?"(.*?>)/g, '$1$2')
?.replaceAll(/(<img.*?) src=".*?"(.*?>)/g, '$1$2')
.replaceAll(/(<img.*?)zoomfile(.*?>)/g, '$1src$2'),
author: content(item).find('a.xw1').text().trim(),
})

View File

@ -77,7 +77,7 @@ export const route: Route = {
displayErrors: true,
});
const md = markdownit('commonmark');
const items = context.an.exports.sort((a, b) => a.updatedAt - b.updatedAt);
const items = context.an.exports.toSorted((a, b) => a.updatedAt - b.updatedAt);
const groups = new Map(items.map((it) => [it.name, it]));
const pkgs = [...groups.values()].map((item) => {
const $ = load(md.render(item.readme));

View File

@ -77,7 +77,7 @@ async function handler(ctx) {
.find('ul li') // 定位到附件列表项
.each(function () {
const $li = $(this);
const newText = $li.html()?.replace(/已下载[\s\S]*?<\/span>次/g, '') ?? '';
const newText = $li.html()?.replaceAll(/已下载[\s\S]*?<\/span>次/g, '') ?? '';
$li.html(newText.replace(/<\/a>\s*$/, '</a>'));
})
.end()

View File

@ -45,7 +45,7 @@ async function handler(ctx) {
data.push(...response.data.data);
})
);
data = data.sort((a, b) => b.publishTime - a.publishTime).slice(0, 10);
data = data.toSorted((a, b) => b.publishTime - a.publishTime).slice(0, 10);
} else {
const response = await got(link);
data = response.data.data;

View File

@ -42,7 +42,7 @@ async function handler() {
const data = Object.values(parseJSONP(response.data).items)
.flat()
.sort((a, b) => new Date(b.date) - new Date(a.date))
.toSorted((a, b) => new Date(b.date) - new Date(a.date))
.map((item) => ({
date: item.date,
weekDay: item.youbi,

View File

@ -84,7 +84,7 @@ export const getSafeLineCookieWithData = async (link): Promise<{ cookie: string;
query: {
once_id: onceId,
v: '1.0.0',
hints: hints.sort(() => Math.random() - 0.5).join(','),
hints: hints.toSorted(() => Math.random() - 0.5).join(','),
},
});

View File

@ -119,7 +119,7 @@ export const orderContent = (parent) => {
for (const [i, e] of parent
.children()
.toArray()
.sort((a, b) => {
.toSorted((a, b) => {
const index = Buffer.from(base32.parse('GM======')).toString(); // substring(3)
a = Buffer.from(
base32.parse(

View File

@ -73,7 +73,7 @@ describe('puppeteer-utils', () => {
await page.goto('https://httpbingo.org/cookies/set?foo=bar&baz=qux', {
waitUntil: 'domcontentloaded',
});
expect((await getCookies(page, 'httpbingo.org')).split('; ').sort()).toEqual(['foo=bar', 'baz=qux'].sort());
expect((await getCookies(page, 'httpbingo.org')).split('; ').toSorted()).toEqual(['foo=bar', 'baz=qux'].toSorted());
}, 45000);
it('setCookies httpbingo', async () => {
@ -96,6 +96,6 @@ describe('puppeteer-utils', () => {
await page.goto('https://example.org', {
waitUntil: 'domcontentloaded',
});
expect((await getCookies(page, 'example.org')).split('; ').sort()).toEqual(cookieStrAll.split('; ').sort());
expect((await getCookies(page, 'example.org')).split('; ').toSorted()).toEqual(cookieStrAll.split('; ').toSorted());
}, 45000);
});

View File

@ -78,7 +78,7 @@ const Index: FC<{ debugQuery: string | undefined }> = ({ debugQuery }) => {
{
name: 'Hot Routes',
value: Object.keys(debug.routes)
.sort((a, b) => debug.routes[b] - debug.routes[a])
.toSorted((a, b) => debug.routes[b] - debug.routes[a])
.slice(0, 30)
.map((route) => (
<>
@ -90,7 +90,7 @@ const Index: FC<{ debugQuery: string | undefined }> = ({ debugQuery }) => {
{
name: 'Hot Paths',
value: Object.keys(debug.paths)
.sort((a, b) => debug.paths[b] - debug.paths[a])
.toSorted((a, b) => debug.paths[b] - debug.paths[a])
.slice(0, 30)
.map((path) => (
<>
@ -102,7 +102,7 @@ const Index: FC<{ debugQuery: string | undefined }> = ({ debugQuery }) => {
{
name: 'Hot Error Routes',
value: Object.keys(debug.errorRoutes)
.sort((a, b) => debug.errorRoutes[b] - debug.errorRoutes[a])
.toSorted((a, b) => debug.errorRoutes[b] - debug.errorRoutes[a])
.slice(0, 30)
.map((route) => (
<>
@ -114,7 +114,7 @@ const Index: FC<{ debugQuery: string | undefined }> = ({ debugQuery }) => {
{
name: 'Hot Error Paths',
value: Object.keys(debug.errorPaths)
.sort((a, b) => debug.errorPaths[b] - debug.errorPaths[a])
.toSorted((a, b) => debug.errorPaths[b] - debug.errorPaths[a])
.slice(0, 30)
.map((path) => (
<>

View File

@ -177,7 +177,7 @@
"eslint-nibble": "9.0.0",
"eslint-plugin-n": "17.21.3",
"eslint-plugin-prettier": "5.5.4",
"eslint-plugin-unicorn": "60.0.0",
"eslint-plugin-unicorn": "61.0.2",
"eslint-plugin-yml": "1.18.0",
"fs-extra": "11.3.1",
"globals": "16.3.0",

View File

@ -409,8 +409,8 @@ importers:
specifier: 5.5.4
version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(prettier@3.6.2)
eslint-plugin-unicorn:
specifier: 60.0.0
version: 60.0.0(eslint@9.35.0(jiti@2.5.1))
specifier: 61.0.2
version: 61.0.2(eslint@9.35.0(jiti@2.5.1))
eslint-plugin-yml:
specifier: 1.18.0
version: 1.18.0(eslint@9.35.0(jiti@2.5.1))
@ -2868,6 +2868,11 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
browserslist@4.25.4:
resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
@ -2922,6 +2927,9 @@ packages:
caniuse-lite@1.0.30001735:
resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==}
caniuse-lite@1.0.30001741:
resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==}
caseless@0.12.0:
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
@ -3096,6 +3104,9 @@ packages:
core-js-compat@3.45.0:
resolution: {integrity: sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==}
core-js-compat@3.45.1:
resolution: {integrity: sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==}
core-js@2.6.12:
resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==}
deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
@ -3355,6 +3366,9 @@ packages:
electron-to-chromium@1.5.202:
resolution: {integrity: sha512-NxbYjRmiHcHXV1Ws3fWUW+SLb62isauajk45LUJ/HgIOkUA7jLZu/X2Iif+X9FBNK8QkF9Zb4Q2mcwXCcY30mg==}
electron-to-chromium@1.5.214:
resolution: {integrity: sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==}
ellipsize@0.1.0:
resolution: {integrity: sha512-5gxbEjcb/Z2n6TTmXZx9wVi3N/DOzE7RXY3Xg9dakDuhX/izwumB9rGjeWUV6dTA0D0+juvo+JonZgNR9sgA5A==}
@ -3527,8 +3541,8 @@ packages:
eslint-config-prettier:
optional: true
eslint-plugin-unicorn@60.0.0:
resolution: {integrity: sha512-QUzTefvP8stfSXsqKQ+vBQSEsXIlAiCduS/V1Em+FKgL9c21U/IIm20/e3MFy1jyCf14tHAhqC1sX8OTy6VUCg==}
eslint-plugin-unicorn@61.0.2:
resolution: {integrity: sha512-zLihukvneYT7f74GNbVJXfWIiNQmkc/a9vYBTE4qPkQZswolWNdu+Wsp9sIXno1JOzdn6OUwLPd19ekXVkahRA==}
engines: {node: ^20.10.0 || >=21.0.0}
peerDependencies:
eslint: '>=9.29.0'
@ -4767,6 +4781,9 @@ packages:
node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
node-releases@2.0.20:
resolution: {integrity: sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==}
nodemailer@7.0.3:
resolution: {integrity: sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==}
engines: {node: '>=6.0.0'}
@ -8757,6 +8774,13 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.2)
browserslist@4.25.4:
dependencies:
caniuse-lite: 1.0.30001741
electron-to-chromium: 1.5.214
node-releases: 2.0.20
update-browserslist-db: 1.1.3(browserslist@4.25.4)
buffer-crc32@0.2.13: {}
buffer-equal-constant-time@1.0.1: {}
@ -8812,6 +8836,8 @@ snapshots:
caniuse-lite@1.0.30001735: {}
caniuse-lite@1.0.30001741: {}
caseless@0.12.0: {}
chai@5.2.1:
@ -9004,6 +9030,10 @@ snapshots:
dependencies:
browserslist: 4.25.2
core-js-compat@3.45.1:
dependencies:
browserslist: 4.25.4
core-js@2.6.12: {}
core-util-is@1.0.2: {}
@ -9241,6 +9271,8 @@ snapshots:
electron-to-chromium@1.5.202: {}
electron-to-chromium@1.5.214: {}
ellipsize@0.1.0: {}
emoji-regex@10.5.0: {}
@ -9461,15 +9493,15 @@ snapshots:
'@types/eslint': 9.6.1
eslint-config-prettier: 10.1.8(eslint@9.35.0(jiti@2.5.1))
eslint-plugin-unicorn@60.0.0(eslint@9.35.0(jiti@2.5.1)):
eslint-plugin-unicorn@61.0.2(eslint@9.35.0(jiti@2.5.1)):
dependencies:
'@babel/helper-validator-identifier': 7.27.1
'@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.5.1))
'@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1))
'@eslint/plugin-kit': 0.3.5
change-case: 5.4.4
ci-info: 4.3.0
clean-regexp: 1.0.0
core-js-compat: 3.45.0
core-js-compat: 3.45.1
eslint: 9.35.0(jiti@2.5.1)
esquery: 1.6.0
find-up-simple: 1.0.1
@ -10922,6 +10954,8 @@ snapshots:
node-releases@2.0.19: {}
node-releases@2.0.20: {}
nodemailer@7.0.3: {}
nodemailer@7.0.4: {}
@ -12079,6 +12113,12 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
update-browserslist-db@1.1.3(browserslist@4.25.4):
dependencies:
browserslist: 4.25.4
escalade: 3.2.0
picocolors: 1.1.1
upper-case@1.1.3: {}
uri-js@4.4.1: