UNPKG

rsshub

Version:
431 lines (405 loc) 18.8 kB
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, };