diff --git a/lib/v2/tvtropes/featured.js b/lib/v2/tvtropes/featured.js
new file mode 100644
index 000000000..d765e2d50
--- /dev/null
+++ b/lib/v2/tvtropes/featured.js
@@ -0,0 +1,73 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const { art } = require('@/utils/render');
+const path = require('path');
+
+const categories = {
+ today: 'left',
+ newest: 'right',
+};
+
+module.exports = async (ctx) => {
+ const { category = 'today' } = ctx.params;
+
+ const rootUrl = 'https://tvtropes.org';
+
+ const { data: response } = await got(rootUrl);
+
+ const $ = cheerio.load(response);
+
+ const item = $(`div#featured-tropes div.${categories[category]}`);
+
+ const link = new URL(item.find('h2.entry-title a').prop('href'), rootUrl).href;
+
+ const { data: detailResponse } = await got(link);
+
+ const content = cheerio.load(detailResponse);
+
+ content('div.folderlabel').remove();
+
+ content('div.lazy_load_img_box').each((_, el) => {
+ el = content(el);
+
+ const image = el.find('img');
+
+ el.replaceWith(
+ art(path.join(__dirname, 'templates/description.art'), {
+ images: [
+ {
+ src: image.prop('src'),
+ alt: image.prop('alt'),
+ width: image.prop('width'),
+ height: image.prop('height'),
+ },
+ ],
+ })
+ );
+ });
+
+ const items = [
+ {
+ title: item.find('h2.entry-title').text(),
+ link,
+ description: content('div#main-article').html(),
+ },
+ ];
+
+ const image = new URL($('img.logo-big').prop('src'), rootUrl).href;
+ const icon = $('link[rel="shortcut icon"]').prop('href');
+
+ ctx.state.data = {
+ item: items,
+ title: `${$('title').text()} - ${item.find('span.box-title').text()}`,
+ link: rootUrl,
+ description: $('meta[name="description"]').prop('content'),
+ language: $('html').prop('lang'),
+ image,
+ icon,
+ logo: icon,
+ subtitle: $('meta[property="og:title"]').prop('content'),
+ author: $('meta[property="og:site_name"]').prop('content'),
+ allowEmpty: true,
+ };
+};
diff --git a/lib/v2/tvtropes/maintainer.js b/lib/v2/tvtropes/maintainer.js
new file mode 100644
index 000000000..d5f298526
--- /dev/null
+++ b/lib/v2/tvtropes/maintainer.js
@@ -0,0 +1,3 @@
+module.exports = {
+ '/featured/:category?': ['nczitzk'],
+};
diff --git a/lib/v2/tvtropes/radar.js b/lib/v2/tvtropes/radar.js
new file mode 100644
index 000000000..85e853eab
--- /dev/null
+++ b/lib/v2/tvtropes/radar.js
@@ -0,0 +1,19 @@
+module.exports = {
+ 'tvtropes.org': {
+ _name: 'TV Tropes',
+ '.': [
+ {
+ title: "Featured - Today's Featured Trope",
+ docs: 'https://docs.rsshub.app/routes/other#tvtropes-featured',
+ source: ['/'],
+ target: '/tvtropes/featured/today',
+ },
+ {
+ title: 'Featured - Newest Trope',
+ docs: 'https://docs.rsshub.app/routes/other#tvtropes-featured',
+ source: ['/'],
+ target: '/tvtropes/featured/newest',
+ },
+ ],
+ },
+};
diff --git a/lib/v2/tvtropes/router.js b/lib/v2/tvtropes/router.js
new file mode 100644
index 000000000..6c4235863
--- /dev/null
+++ b/lib/v2/tvtropes/router.js
@@ -0,0 +1,3 @@
+module.exports = (router) => {
+ router.get('/featured/:category?', require('./featured'));
+};
diff --git a/lib/v2/tvtropes/templates/description.art b/lib/v2/tvtropes/templates/description.art
new file mode 100644
index 000000000..48df396d7
--- /dev/null
+++ b/lib/v2/tvtropes/templates/description.art
@@ -0,0 +1,23 @@
+{{ if images }}
+ {{ each images image }}
+ {{ if image?.src }}
+
+
+
+ {{ /if }}
+ {{ /each }}
+{{ /if }}
+
+{{ if description }}
+ {{@ description }}
+{{ /if }}
\ No newline at end of file
diff --git a/website/docs/routes/other.mdx b/website/docs/routes/other.mdx
index 9979244a9..53d4a990d 100644
--- a/website/docs/routes/other.mdx
+++ b/website/docs/routes/other.mdx
@@ -528,6 +528,16 @@ Specify options (in the format of query string) in parameter `routeParams` param
| `routeParams` | `title=Example` |
+## TV Tropes {#tv-tropes}
+
+### Featured {#tv-tropes-featured}
+
+
+ | Today's Featured Trope | Newest Trope |
+ | ---------------------- | ------------ |
+ | today | newest |
+
+
## Urban Dictionary {#urban-dictionary}
### Random words {#urban-dictionary-random-words}