Skip to content

Commit

Permalink
feat(route): add 飞客会员说 (DIYgod#17441)
Browse files Browse the repository at this point in the history
  • Loading branch information
nczitzk authored Nov 4, 2024
1 parent 4ffe081 commit 125124c
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 0 deletions.
99 changes: 99 additions & 0 deletions lib/routes/flyert/forum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Route } from '@/types';

import cache from '@/utils/cache';
import got from '@/utils/got';
import iconv from 'iconv-lite';
import { load } from 'cheerio';
import { rootUrl, parseArticleList, parsePostList, parseArticle, parsePost } from './util';

export const handler = async (ctx) => {
const { params } = ctx.req.param();
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 5;

const decodedParams = params
? decodeURIComponent(params)
.split(/&/)
.filter((p) => p.split(/=/).length === 2)
.join('&')
: undefined;

const currentUrl = new URL(`forum.php${decodedParams ? `?${decodedParams}` : ''}`, rootUrl).href;

const { data: response } = await got(currentUrl, {
responseType: 'buffer',
});

const $ = load(iconv.decode(response, 'gbk'));

const language = $('meta[http-equiv="Content-Language"]').prop('content');

let items = $('table#threadlisttableid').length === 0 ? parseArticleList($, limit) : parsePostList($, limit);

items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const { data: detailResponse } = await got(item.link, {
responseType: 'buffer',
});

const $$ = load(iconv.decode(detailResponse, 'gbk').replaceAll(/<\/?ignore_js_op>/g, ''));

item = $$('div.firstpost').length === 0 ? parseArticle($$, item) : parsePost($$, item);

item.language = language;

return item;
})
)
);

const image = `https:${$('div.wp h2 a img').prop('src')}`;

return {
title: `飞客 - ${$('a.forum_name, li.a, li.cur, li.xw1, div.z > a.xw1')
.toArray()
.map((a) => $(a).text())
.join(' - ')}`,
description: $('meta[name="description"]').prop('content'),
link: currentUrl,
item: items,
allowEmpty: true,
image,
author: $('meta[name="application-name"]').prop('content'),
};
};

export const route: Route = {
path: '/forum/:params{.+}?',
name: '会员说',
url: 'www.flyert.com.cn',
maintainers: ['nczitzk'],
handler,
example: '/flyert/forum',
parameters: { params: '参数,默认为空,可在对应分类页 URL 中找到' },
description: `:::tip
若订阅 [酒店集团优惠](https://www.flyert.com.cn/forum.php?mod=forumdisplay&sum=all&fid=all&catid=322&filter=sortid&sortid=144&searchsort=1&youhui_type=19),网址为 \`https://www.flyert.com.cn/forum.php?mod=forumdisplay&sum=all&fid=all&catid=322&filter=sortid&sortid=144&searchsort=1&youhui_type=19\`。截取 \`https://www.flyert.com.cn/forum.php?\` 到末尾的部分 \`mod=forumdisplay&sum=all&fid=all&catid=322&filter=sortid&sortid=144&searchsort=1&youhui_type=19\` **进行 UrlEncode 编码** 后作为参数填入,此时路由为 [\`/flyert/forum/mod%3Dforumdisplay%26sum%3Dall%26fid%3Dall%26catid%3D322%26filter%3Dsortid%26sortid%3D144%26searchsort%3D1%26youhui_type%3D226\`](https://rsshub.app/flyert/forum/mod%3Dforumdisplay%26sum%3Dall%26fid%3Dall%26catid%3D322%26filter%3Dsortid%26sortid%3D144%26searchsort%3D1%26youhui_type%3D226)。
:::
`,
categories: ['bbs'],

features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportRadar: true,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['www.flyert.com.cn/forum.php'],
target: (_, url) => {
const params = [...url.searchParams.entries()].map(([key, value]) => key + '=' + value).join('&');

return `/forum${params ? `/${encodeURIComponent(params)}` : ''}`;
},
},
],
};
1 change: 1 addition & 0 deletions lib/routes/flyert/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: '飞客茶馆',
url: 'flyert.com',
description: '',
lang: 'zh-CN',
};
21 changes: 21 additions & 0 deletions lib/routes/flyert/templates/description.art
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{ 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 intro }}
<blockquote>{{ intro }}</blockquote>
{{ /if }}

{{ if description }}
{{@ description }}
{{ /if }}
188 changes: 188 additions & 0 deletions lib/routes/flyert/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);

import { CheerioAPI } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import path from 'node:path';

const rootUrl = 'https://www.flyert.com.cn';

/**
* Parses a list of articles based on a CheerioAPI object and a limit.
* @param $ The CheerioAPI object.
* @param limit The maximum number of articles to parse.
* @returns An array of parsed article objects.
*/
const parseArticleList = ($: CheerioAPI, limit: number) =>
$('div.comiis_wzli')
.slice(0, limit)
.toArray()
.map((item) => {
item = $(item);

const title = item.find('div.wzbt').text().trim();
const image = item.find('div.wzpic img').prop('src');
const description = art(path.join(__dirname, 'templates/description.art'), {
images: image
? [
{
src: image,
alt: title,
},
]
: undefined,
description: item.find('div.wznr').html(),
});
const pubDate = item.find('div.subcat span.y').contents()?.eq(2)?.text().trim() ?? undefined;
const link = new URL(item.find('div.wzbt a').prop('href'), rootUrl).href;

return {
title,
description,
pubDate: pubDate ? parseDate(pubDate) : undefined,
link,
author: item.find('div.subcat span.y a').first().text(),
content: {
html: description,
text: item.find('div.wznr').text(),
},
image,
banner: image,
};
});

/**
* Parses a list of posts based on a CheerioAPI object and a limit.
* @param $ The CheerioAPI object.
* @param limit The maximum number of posts to parse.
* @returns An array of parsed post objects.
*/
const parsePostList = ($: CheerioAPI, limit: number) =>
$('div.comiis_postlist')
.toArray()
.filter((item) => {
item = $(item);

return item
.find('span.comiis_common a[data-track]')
.toArray()
.some((a) => {
a = $(a);

const dataTrack = a.attr('data-track') || '';
return dataTrack.endsWith('文章');
});
})
.slice(0, limit)
.map((item) => {
item = $(item);

const aEl = $(
item
.find('span.comiis_common a[data-track]')
.toArray()
.find((a) => {
a = $(a);

const dataTrack = a.attr('data-track') || '';
return dataTrack.endsWith('文章');
})
);

const pubDate = item.find('span.author_b span').prop('title') || undefined;

return {
title: aEl.text().trim(),
pubDate: pubDate ? parseDate(pubDate) : undefined,
link: new URL(aEl.prop('href'), rootUrl).href,
author: item.find('a.author_t').text().trim(),
};
});

/**
* Parses an article based on a CheerioAPI object and an item.
* @param $$ The CheerioAPI object.
* @param item The item to parse.
* @returns The parsed article object.
*/
const parseArticle = ($$: CheerioAPI, item) => {
const title = $$('h1.ph').text().trim();
const description = art(path.join(__dirname, 'templates/description.art'), {
intro: $$('div.s').text() || undefined,
description: $$('div#artMain').html(),
});
const pubDate =
$$('p.xg1')
.contents()
.first()
.text()
.trim()
?.match(/(\d{4}-\d{1,2}-\d{1,2}\s\d{2}:\d{2})/)?.[1] ?? undefined;
const guid = `flyert-${item.link.split(/=/).pop()}`;

item.title = title;
item.description = description;
item.pubDate = pubDate ? timezone(parseDate(pubDate), +8) : item.pubDate;
item.author = $$('p.xg1 a').first().text();
item.guid = guid;
item.id = guid;
item.content = {
html: description,
text: $$('div#artMain').text(),
};

return item;
};

/**
* Parses a post based on a CheerioAPI object and an item.
* @param $$ The CheerioAPI object.
* @param item The item to parse.
* @returns The parsed post object.
*/
const parsePost = ($$: CheerioAPI, item) => {
$$('img.zoom').each((_, el) => {
el = $$(el);

el.replaceWith(
art(path.join(__dirname, 'templates/description.art'), {
images:
el.prop('zoomfile') || el.prop('file')
? [
{
src: el.prop('zoomfile') || el.prop('file'),
alt: el.prop('alt') || el.prop('title'),
},
]
: undefined,
})
);
});

$$('i.pstatus').remove();
$$('div.tip').remove();

const title = $$('span#thread_subject').text().trim();
const description = $$('div.post_message').first().html();
const pubDate = $$('span[title]').first().prop('title');

const tid = item.link.match(/tid=(\d+)/)?.[1] ?? undefined;
const guid = tid ? `flyert-${tid}` : undefined;

item.title = title;
item.description = description;
item.pubDate = pubDate ? timezone(parseDate(pubDate), +8) : item.pubDate;
item.author = $$('a.kmxi2').first().text();
item.guid = guid;
item.id = guid;
item.content = {
html: description,
text: $$('div.post_message').first().text(),
};

return item;
};

export { rootUrl, parseArticleList, parsePostList, parseArticle, parsePost };

0 comments on commit 125124c

Please sign in to comment.