feat(route): add Amazfitwatchfaces (#19032)
* feat(route): add Amazfitwatchfaces * fix: missing description for searchParams * refactor: fix Element type import ---------
This commit is contained in:
parent
c540575892
commit
be620d2348
|
|
@ -0,0 +1,430 @@
|
|||
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,
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'Amazfitwatchfaces',
|
||||
url: 'amazfitwatchfaces.com',
|
||||
categories: ['program-update'],
|
||||
description:
|
||||
"amazfitwatchfaces.com is the world's largest collection of watch faces for Amazfit, Zepp, Bip, Pace, Stratos, Cor, Verge, Verge Lite, GTR, GTS, T-Rex, watches. Here you can find everything you need to customize & personalize your device! The website also has catalogs of watch faces for Xiaomi, Haylou, Honor and Huawei watches.",
|
||||
lang: 'en',
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{{ if images }}
|
||||
{{ each images image }}
|
||||
{{ if image?.src }}
|
||||
<figure>
|
||||
<img
|
||||
{{ if image.alt }}
|
||||
alt="{{ image.alt }}"
|
||||
{{ /if }}
|
||||
src="{{ image.src }}">
|
||||
</figure>
|
||||
{{ /if }}
|
||||
{{ /each }}
|
||||
{{ /if }}
|
||||
|
||||
{{ if description }}
|
||||
{{@ description }}
|
||||
{{ /if }}
|
||||
Loading…
Reference in New Issue