rsshub
Version:
Make RSS Great Again!
431 lines (405 loc) • 18.8 kB
text/typescript
import { type Data, type DataItem, type Route, ViewType } from '@/types';
import { art } from '@/utils/render';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import { type CheerioAPI, type Cheerio, load } from 'cheerio';
import type { Element } from 'domhandler';
import { type Context } from 'hono';
import path from 'node:path';
export const handler = async (ctx: Context): Promise<Data> => {
const { device, sort, searchParams } = ctx.req.param();
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
const baseUrl: string = 'https://amazfitwatchfaces.com';
const targetUrl: string = new URL(`${device}/${sort}${searchParams ? `?${searchParams}` : ''}`, baseUrl).href;
const response = await ofetch(targetUrl);
const $: CheerioAPI = load(response);
const language = $('html').attr('lang') ?? 'en';
let items: DataItem[] = [];
items = $('div.wf-panel')
.slice(0, limit)
.toArray()
.map((el): Element => {
const $el: Cheerio<Element> = $(el);
const title: string = $el.prop('title');
const image: string | undefined = $el.find('img.wf-img').attr('src');
const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), {
images: image
? [
{
src: image,
alt: title,
},
]
: undefined,
});
const linkUrl: string | undefined = $el.find('a.wf-act').attr('href');
const categoryEls: Element[] = $el.find('div.wf-comp code').toArray();
const categories: string[] = [...new Set(categoryEls.map((el) => $(el).text()).filter(Boolean))];
const authorEls: Element[] = $el.find('div.wf-user a').toArray();
const authors: DataItem['author'] = authorEls.map((authorEl) => {
const $authorEl: Cheerio<Element> = $(authorEl);
return {
name: $authorEl.text(),
url: $authorEl.attr('href') ? new URL($authorEl.attr('href') as string, baseUrl).href : undefined,
avatar: undefined,
};
});
const processedItem: DataItem = {
title,
description,
link: linkUrl ? new URL(linkUrl, baseUrl).href : undefined,
category: categories,
author: authors,
content: {
html: description,
text: description,
},
image,
banner: image,
language,
};
return processedItem;
});
items = (
await Promise.all(
items.map((item) => {
if (!item.link) {
return item;
}
return cache.tryGet(item.link, async (): Promise<DataItem> => {
const detailResponse = await ofetch(item.link);
const $$: CheerioAPI = load(detailResponse);
const title: string = $$('div.page-title h1').text();
const image: string | undefined = $$('img#watchface-preview').attr('src');
const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), {
images: image
? [
{
src: image,
alt: title,
},
]
: undefined,
description: $$('div.unicodebidi').html(),
});
const pubDateStr: string | undefined = $$('i.fa-calendar').parent().find('span').text();
const linkUrl: string | undefined = $$('.title').attr('href');
const categoryEls: Element[] = $$('div.mdesc a.btn-sm').toArray();
const categories: string[] = [...new Set(categoryEls.map((el) => $$(el).text()).filter(Boolean))];
const authorEls: Element[] = $$('div.wf-userinfo-name').toArray();
const authors: DataItem['author'] = authorEls.map((authorEl) => {
const $$authorEl: Cheerio<Element> = $$(authorEl).find('a.wf-author-h');
return {
name: $$authorEl.text(),
url: $$authorEl.attr('href') ? new URL($$authorEl.attr('href') as string, baseUrl).href : undefined,
avatar: $$authorEl.find('img.wf-userpic').attr('src'),
};
});
const upDatedStr: string | undefined = $$('i.fa-clock-o').parent().find('span').text();
const processedItem: DataItem = {
title,
description,
pubDate: pubDateStr ? parseDate(pubDateStr, 'DD.MM.YYYY HH:mm') : item.pubDate,
link: linkUrl ? new URL(linkUrl, baseUrl).href : item.link,
category: categories,
author: authors,
content: {
html: description,
text: description,
},
image,
banner: image,
updated: upDatedStr ? parseDate(upDatedStr, 'DD.MM.YYYY HH:mm') : item.updated,
language,
};
return {
...item,
...processedItem,
};
});
})
)
).filter((_): _ is DataItem => true);
return {
title: $('title').text(),
description: $('meta[property="og:description"]').attr('content'),
link: targetUrl,
item: items,
allowEmpty: true,
image: $('img.mainlogolg').attr('src') ? new URL($('img.mainlogolg').attr('src') as string, baseUrl).href : undefined,
author: $('meta[property="og:site_name"]').attr('content'),
language,
id: targetUrl,
};
};
export const route: Route = {
path: '/:device/:sort/:searchParams?',
name: 'Watch Faces',
url: 'amazfitwatchfaces.com',
maintainers: ['nczitzk'],
handler,
example: '/amazfitwatchfaces/amazfit-x/fresh',
parameters: {
device: {
description: 'Device Id',
options: [
{
label: 'Amazfit X',
value: 'amazfit-x',
},
{
label: 'Amazfit Band',
value: 'amazfit-band',
},
{
label: 'Amazfit Bip',
value: 'bip',
},
{
label: 'Amazfit Active',
value: 'active',
},
{
label: 'Amazfit Balance',
value: 'balance',
},
{
label: 'Amazfit Cheetah',
value: 'cheetah',
},
{
label: 'Amazfit Falcon',
value: 'falcon',
},
{
label: 'Amazfit GTR',
value: 'gtr',
},
{
label: 'Amazfit GTS',
value: 'gts',
},
{
label: 'Amazfit T-Rex',
value: 't-rex',
},
{
label: 'Amazfit Stratos',
value: 'pace',
},
{
label: 'Amazfit Verge Lite',
value: 'verge-lite',
},
{
label: 'Haylou Watches',
value: 'haylou',
},
{
label: 'Huawei Watches',
value: 'huawei-watch-gt',
},
{
label: 'Xiaomi Mi Band 4',
value: 'mi-band-4',
},
{
label: 'Xiaomi Mi Band 5',
value: 'mi-band-5',
},
{
label: 'Xiaomi Mi Band 6',
value: 'mi-band-6',
},
{
label: 'Xiaomi Mi Band 7',
value: 'mi-band-7',
},
{
label: 'Xiaomi Smart Band 8',
value: 'mi-band',
},
{
label: 'Xiaomi Smart Band 9',
value: 'mi-band',
},
],
},
sort: {
description: 'Sort By',
options: [
{
label: 'Fresh',
value: 'fresh',
},
{
label: 'Updated',
value: 'updated',
},
{
label: 'Random',
value: 'random',
},
{
label: 'Top',
value: 'top',
},
],
},
searchParams: {
description: 'Search Params',
},
},
description: `:::tip
If you subscribe to [Updated watch faces for Amazfit X](https://amazfitwatchfaces.com/amazfit-x/updated),where the URL is \`https://amazfitwatchfaces.com/amazfit-x/updated\`, extract the part \`https://amazfitwatchfaces.com/\` to the end, which is \`amazfit-x/updated\`, and use it as the parameter to fill in. Therefore, the route will be [\`/amazfitwatchfaces/amazfit-x/updated\`](https://rsshub.app/amazfitwatchfaces/amazfit-x/updated).
If you subscribe to [TOP for the last 6 months (Only new) - Xiaomi Smart Band 9](https://amazfitwatchfaces.com/mi-band/top?compatible=Smart_Band_9&topof=6months),where the URL is \`https://amazfitwatchfaces.com/mi-band/top?compatible=Smart_Band_9&topof=6months\`, extract the part \`https://amazfitwatchfaces.com/\` to the end, which is \`mi-band/top\`, and use it as the parameter to fill in. Therefore, the route will be [\`/amazfitwatchfaces/mi-band/top/compatible=Smart_Band_9&topof=6months\`](https://rsshub.app/amazfitwatchfaces/mi-band/top/compatible=Smart_Band_9&topof=6months).
:::
<details>
<summary>More devices</summary>
| Device Name | Device Id |
| ------------------------------------------------------------------------------------------ | --------------- |
| [Amazfit X](https://amazfitwatchfaces.com/amazfit-x/fresh) | [amazfit-x](https://rsshub.app/amazfitwatchfaces/amazfit-x/fresh) |
| [Amazfit Band](https://amazfitwatchfaces.com/amazfit-band/fresh) | [amazfit-band](https://rsshub.app/amazfitwatchfaces/amazfit-band/fresh) |
| [Amazfit Bip](https://amazfitwatchfaces.com/bip/fresh) | [bip](https://rsshub.app/amazfitwatchfaces/bip/fresh) |
| [Amazfit Active](https://amazfitwatchfaces.com/active/fresh) | [active](https://rsshub.app/amazfitwatchfaces/active/fresh) |
| [Amazfit Balance](https://amazfitwatchfaces.com/balance/fresh) | [balance](https://rsshub.app/amazfitwatchfaces/balance/fresh) |
| [Amazfit Cheetah](https://amazfitwatchfaces.com/cheetah/fresh) | [cheetah](https://rsshub.app/amazfitwatchfaces/cheetah/fresh) |
| [Amazfit Falcon](https://amazfitwatchfaces.com/falcon/fresh) | [falcon](https://rsshub.app/amazfitwatchfaces/falcon/fresh) |
| [Amazfit GTR](https://amazfitwatchfaces.com/gtr/fresh) | [gtr](https://rsshub.app/amazfitwatchfaces/gtr/fresh) |
| [Amazfit GTS](https://amazfitwatchfaces.com/gts/fresh) | [gts](https://rsshub.app/amazfitwatchfaces/gts/fresh) |
| [Amazfit T-Rex](https://amazfitwatchfaces.com/t-rex/fresh) | [t-rex](https://rsshub.app/amazfitwatchfaces/t-rex/fresh) |
| [Amazfit Stratos](https://amazfitwatchfaces.com/pace/fresh) | [pace](https://rsshub.app/amazfitwatchfaces/pace/fresh) |
| [Amazfit Verge Lite](https://amazfitwatchfaces.com/verge-lite/fresh) | [verge-lite](https://rsshub.app/amazfitwatchfaces/verge-lite/fresh) |
| [Haylou Watches](https://amazfitwatchfaces.com/haylou/fresh) | [haylou](https://rsshub.app/amazfitwatchfaces/haylou/fresh) |
| [Huawei Watches](https://amazfitwatchfaces.com/huawei-watch-gt/fresh) | [huawei-watch-gt](https://rsshub.app/amazfitwatchfaces/huawei-watch-gt/fresh) |
| [Xiaomi Mi Band 4](https://amazfitwatchfaces.com/mi-band-4/fresh) | [mi-band-4](https://rsshub.app/amazfitwatchfaces/mi-band-4/fresh) |
| [Xiaomi Mi Band 5](https://amazfitwatchfaces.com/mi-band-5/fresh) | [mi-band-5](https://rsshub.app/amazfitwatchfaces/mi-band-5/fresh) |
| [Xiaomi Mi Band 6](https://amazfitwatchfaces.com/mi-band-6/fresh) | [mi-band-6](https://rsshub.app/amazfitwatchfaces/mi-band-6/fresh) |
| [Xiaomi Mi Band 7](https://amazfitwatchfaces.com/mi-band-7/fresh) | [mi-band-7](https://rsshub.app/amazfitwatchfaces/mi-band-7/fresh) |
| [Xiaomi Smart Band 8](https://amazfitwatchfaces.com/mi-band/fresh?compatible=Smart_Band_8) | [mi-band](https://rsshub.app/amazfitwatchfaces/mi-band/fresh/compatible=Smart_Band_8) |
| [Xiaomi Smart Band 9](https://amazfitwatchfaces.com/mi-band/fresh?compatible=Smart_Band_9) | [mi-band](https://rsshub.app/amazfitwatchfaces/mi-band/fresh/compatible=Smart_Band_9) |
</details>
`,
categories: ['program-update'],
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportRadar: true,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['amazfitwatchfaces.com/:device/:sort'],
target: (params) => {
const device: string = params.device;
const sort: string = params.sort;
return `/amazfitwatchfaces${device ? `/${device}${sort ? `/${sort}` : ''}` : ''}`;
},
},
{
title: 'Fresh watch faces for Amazfit X',
source: ['amazfitwatchfaces.com/amazfit-x/fresh'],
target: '/amazfit-x/fresh',
},
{
title: 'Fresh watch faces for Amazfit Band',
source: ['amazfitwatchfaces.com/amazfit-band/fresh'],
target: '/amazfit-band/fresh',
},
{
title: 'Fresh watch faces for Amazfit Bip',
source: ['amazfitwatchfaces.com/bip/fresh'],
target: '/bip/fresh',
},
{
title: 'Fresh watch faces for Amazfit Active',
source: ['amazfitwatchfaces.com/active/fresh'],
target: '/active/fresh',
},
{
title: 'Fresh watch faces for Amazfit Balance',
source: ['amazfitwatchfaces.com/balance/fresh'],
target: '/balance/fresh',
},
{
title: 'Fresh watch faces for Amazfit Cheetah',
source: ['amazfitwatchfaces.com/cheetah/fresh'],
target: '/cheetah/fresh',
},
{
title: 'Fresh watch faces for Amazfit Falcon',
source: ['amazfitwatchfaces.com/falcon/fresh'],
target: '/falcon/fresh',
},
{
title: 'Fresh watch faces for Amazfit GTR',
source: ['amazfitwatchfaces.com/gtr/fresh'],
target: '/gtr/fresh',
},
{
title: 'Fresh watch faces for Amazfit GTS',
source: ['amazfitwatchfaces.com/gts/fresh'],
target: '/gts/fresh',
},
{
title: 'Fresh watch faces for Amazfit T-Rex',
source: ['amazfitwatchfaces.com/t-rex/fresh'],
target: '/t-rex/fresh',
},
{
title: 'Fresh watch faces for Amazfit Stratos',
source: ['amazfitwatchfaces.com/pace/fresh'],
target: '/pace/fresh',
},
{
title: 'Fresh watch faces for Amazfit Verge Lite',
source: ['amazfitwatchfaces.com/verge-lite/fresh'],
target: '/verge-lite/fresh',
},
{
title: 'Fresh watch faces for Haylou Watches',
source: ['amazfitwatchfaces.com/haylou/fresh'],
target: '/haylou/fresh',
},
{
title: 'Fresh watch faces for Huawei Watches',
source: ['amazfitwatchfaces.com/huawei-watch-gt/fresh'],
target: '/huawei-watch-gt/fresh',
},
{
title: 'Fresh watch faces for Xiaomi Mi Band 4',
source: ['amazfitwatchfaces.com/mi-band-4/fresh'],
target: '/mi-band-4/fresh',
},
{
title: 'Fresh watch faces for Xiaomi Mi Band 5',
source: ['amazfitwatchfaces.com/mi-band-5/fresh'],
target: '/mi-band-5/fresh',
},
{
title: 'Fresh watch faces for Xiaomi Mi Band 6',
source: ['amazfitwatchfaces.com/mi-band-6/fresh'],
target: '/mi-band-6/fresh',
},
{
title: 'Fresh watch faces for Xiaomi Mi Band 7',
source: ['amazfitwatchfaces.com/mi-band-7/fresh'],
target: '/mi-band-7/fresh',
},
{
title: 'Fresh watch faces for Xiaomi Smart Band 8',
source: ['amazfitwatchfaces.com/mi-band/fresh'],
target: '/mi-band/fresh/compatible=Smart_Band_8',
},
{
title: 'Fresh watch faces for Xiaomi Smart Band 9',
source: ['amazfitwatchfaces.com/mi-band/fresh'],
target: '/mi-band/fresh/compatible=Smart_Band_9',
},
],
view: ViewType.Articles,
};