diff --git a/docs/en/traditional-media.md b/docs/en/traditional-media.md
index 983211fe8..4f942f500 100644
--- a/docs/en/traditional-media.md
+++ b/docs/en/traditional-media.md
@@ -149,6 +149,18 @@ Generates full-text feeds that the official feed doesn't provide.
+## Radio Free Asia (RFA)
+
+
+
+Delivers a better experience by supporting parameter specification.
+
+Parameters can be obtained from the official website, for instance:
+
+`https://www.rfa.org/cantonese/news` corresponds to `/rfa/cantonese/news`
+
+`https://www.rfa.org/cantonese/news/htm` corresponds to `/rfa/cantonese/news/htm`
+
## Reuters
### Channel
diff --git a/docs/traditional-media.md b/docs/traditional-media.md
index 94db156ae..6281d1bee 100644
--- a/docs/traditional-media.md
+++ b/docs/traditional-media.md
@@ -965,3 +965,15 @@ category 对应的关键词有
### 九江新闻
+
+## 自由亚洲电台
+
+
+
+通过指定频道参数,提供比官方源更佳的阅读体验。
+
+参数均可在官网获取,如:
+
+`https://www.rfa.org/cantonese/news` 对应 `/rfa/cantonese/news`
+
+`https://www.rfa.org/cantonese/news/htm` 对应 `/rfa/cantonese/news/htm`
diff --git a/lib/router.js b/lib/router.js
index 5bb0f656c..7ca17dea0 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -3780,4 +3780,7 @@ router.get('/nace/blog/:sort?', require('./routes/nace/blog'));
// Caixin Latest
router.get('/caixin/latest', require('./routes/caixin/latest'));
+// Radio Free Asia
+router.get('/rfa/:language?/:channel?/:subChannel?', require('./routes/rfa/index'));
+
module.exports = router;
diff --git a/lib/routes/rfa/index.js b/lib/routes/rfa/index.js
new file mode 100644
index 000000000..90791eb2f
--- /dev/null
+++ b/lib/routes/rfa/index.js
@@ -0,0 +1,47 @@
+const cheerio = require('cheerio');
+const got = require('@/utils/got');
+
+module.exports = async (ctx) => {
+ let url = 'https://www.rfa.org/' + (ctx.params.language || 'english');
+
+ if (ctx.params.channel) {
+ url += '/' + ctx.params.channel;
+ }
+ if (ctx.params.subChannel) {
+ url += '/' + ctx.params.subChannel;
+ }
+
+ const response = await got.get(url);
+ const $ = cheerio.load(response.data);
+
+ const selectors = ['div[id=topstorywidefulltease]', 'div.two_featured', 'div.three_featured', 'div.single_column_teaser', 'div.sectionteaser', 'div.specialwrap'];
+ const list = [];
+ selectors.forEach(function (selector) {
+ $(selector).each(function (_, e) {
+ const item = {};
+ item.title = $(e).find('h2 a span').first().text();
+ item.link = $(e).find('h2 a').first().attr('href');
+ list.push(item);
+ });
+ });
+
+ const result = await Promise.all(
+ list.map(
+ async (item) =>
+ await ctx.cache.tryGet(item.link, async () => {
+ const content = await got.get(item.link);
+
+ const description = cheerio.load(content.data);
+ item.description = description('div[id=storytext]').html();
+ item.pubDate = new Date(description('span[id=story_date]').text()).toUTCString();
+ return item;
+ })
+ )
+ );
+
+ ctx.state.data = {
+ title: 'RFA',
+ link: 'https://www.rfa.org/',
+ item: result,
+ };
+};