Skip to content

Commit a0c67dc

Browse files
ubuntuubuntu
authored andcommitted
feat(route): add Fars News showcase
1 parent 12a7918 commit a0c67dc

2 files changed

Lines changed: 108 additions & 0 deletions

File tree

lib/routes/farsnews/namespace.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Namespace } from '@/types';
2+
3+
export const namespace: Namespace = {
4+
name: 'Fars News',
5+
url: 'farsnews.ir',
6+
lang: 'fa',
7+
};

lib/routes/farsnews/showcase.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { load } from 'cheerio';
2+
import type { Route } from '@/types';
3+
import cache from '@/utils/cache';
4+
import got from '@/utils/got';
5+
import { parseDate } from '@/utils/parse-date';
6+
import puppeteer from '@/utils/puppeteer';
7+
8+
export const route: Route = {
9+
path: '/showcase/:category?',
10+
categories: ['traditional-media'],
11+
example: '/farsnews/showcase',
12+
parameters: { category: 'Category slug from farsnews.ir/showcase URL' },
13+
features: {
14+
requireConfig: false,
15+
requirePuppeteer: true,
16+
antiCrawler: true,
17+
supportBT: false,
18+
supportPodcast: false,
19+
supportScihub: false,
20+
},
21+
radar: [{
22+
source: ['farsnews.ir/showcase'],
23+
target: '/showcase',
24+
}],
25+
name: 'Showcase',
26+
maintainers: [],
27+
handler,
28+
description: 'Fars News showcase articles. Persian news agency.',
29+
};
30+
31+
async function handler(ctx) {
32+
const category = ctx.req.param('category') ?? '';
33+
const baseUrl = 'https://farsnews.ir';
34+
const currentUrl = category ? `${baseUrl}/showcase/${category}` : `${baseUrl}/showcase`;
35+
36+
const browser = await puppeteer();
37+
const page = await browser.newPage();
38+
39+
try {
40+
await page.setRequestInterception(true);
41+
page.on('request', (req) => {
42+
if (req.resourceType() === 'document') {
43+
req.continue();
44+
} else {
45+
req.abort();
46+
}
47+
});
48+
49+
await page.goto(currentUrl, {
50+
waitUntil: 'domcontentloaded',
51+
});
52+
53+
const response = await page.content();
54+
const $ = load(response);
55+
56+
const items = $('.rounded-2xl.border.border-gray-200')
57+
.toArray()
58+
.map((item) => {
59+
item = $(item);
60+
const a = item.find('a[href^="/"]').first();
61+
const href = a.attr('href');
62+
63+
return {
64+
title: a.find('h2').text() || a.text(),
65+
link: href ? (href.startsWith('http') ? href : `${baseUrl}${href}`) : undefined,
66+
};
67+
})
68+
.filter((item) => item.title && item.link);
69+
70+
const processedItems = await Promise.all(
71+
items.map((item) =>
72+
cache.tryGet(item.link, async () => {
73+
try {
74+
const detailResponse = await got({ method: 'get', url: item.link });
75+
const detail$ = load(detailResponse.data);
76+
77+
const desc = detail$('meta[name="description"]').attr('content') || '';
78+
item.description = desc;
79+
80+
const timeText = detail$('time').attr('datetime') || detail$('.text-gray-400').first().text();
81+
if (timeText) {
82+
item.pubDate = parseDate(timeText);
83+
}
84+
} catch {
85+
// Silently continue if detail fetch fails
86+
}
87+
return item;
88+
})
89+
)
90+
);
91+
92+
return {
93+
title: 'Fars News - Showcase',
94+
link: currentUrl,
95+
item: processedItems,
96+
};
97+
} finally {
98+
await page.close();
99+
await browser.close();
100+
}
101+
}

0 commit comments

Comments
 (0)