。
+:::
+
+### 系列
+
+
+
+### 热门
+
+
+
+### 剧集系列
+
+
+
+### 剧集章节
+
+
+
+### 片单
+
+
+
+当 `mediaType` 为 `tv` 时,`sheet` 的值如下:
+
+| 今日播出 | 电视播出中 | 高分 |
+| ------------ | ---------- | --------- |
+| airing-today | on-the-air | top-rated |
+
+当 `mediaType` 为 `movie` 时,`sheet` 的值如下:
+
+| 正在上映 | 即将上映 | 高分 |
+| ----------- | -------- | --------- |
+| now-playing | upcoming | top-rated |
+
+
+
## Trakt.tv
### 用户收藏
diff --git a/lib/v2/themoviedb/api-key.js b/lib/v2/themoviedb/api-key.js
new file mode 100644
index 000000000..dc7885cf4
--- /dev/null
+++ b/lib/v2/themoviedb/api-key.js
@@ -0,0 +1,23 @@
+// https://github.com/rickylawson/freekeys/blob/master/index.js
+const KEYS = [
+ 'fb7bb23f03b6994dafc674c074d01761',
+ 'e55425032d3d0f371fc776f302e7c09b',
+ '8301a21598f8b45668d5711a814f01f6',
+ '8cf43ad9c085135b9479ad5cf6bbcbda',
+ 'da63548086e399ffc910fbc08526df05',
+ '13e53ff644a8bd4ba37b3e1044ad24f3',
+ '269890f657dddf4635473cf4cf456576',
+ 'a2f888b27315e62e471b2d587048f32e',
+ '8476a7ab80ad76f0936744df0430e67c',
+ '5622cafbfe8f8cfe358a29c53e19bba0',
+ 'ae4bd1b6fce2a5648671bfc171d15ba4',
+ '257654f35e3dff105574f97fb4b97035',
+ '2f4038e83265214a0dcd6ec2eb3276f5',
+ '9e43f45f94705cc8e1d5a0400d19a7b7',
+ 'af6887753365e14160254ac7f4345dd2',
+ '06f10fc8741a672af455421c239a1ffc',
+ 'fb7bb23f03b6994dafc674c074d01761',
+ '09ad8ace66eec34302943272db0e8d2c',
+];
+
+module.exports = () => KEYS[Math.floor(Math.random() * KEYS.length)];
diff --git a/lib/v2/themoviedb/collection.js b/lib/v2/themoviedb/collection.js
new file mode 100644
index 000000000..20ea2e61b
--- /dev/null
+++ b/lib/v2/themoviedb/collection.js
@@ -0,0 +1,21 @@
+const got = require('@/utils/got');
+const apiKey = require('./api-key');
+const { handleMovieItem } = require('./utils');
+
+module.exports = async (ctx) => {
+ const { id, lang } = ctx.params;
+ const { data } = await got(`https://api.themoviedb.org/3/collection/${id}`, {
+ searchParams: {
+ language: lang,
+ api_key: apiKey(),
+ },
+ });
+
+ ctx.state.data = {
+ title: `${data.name} — TMDB`,
+ description: data.overview.trim(),
+ image: `https://image.tmdb.org/t/p/original${data.poster_path}`,
+ link: `https://www.themoviedb.org/collection/${data.id}`,
+ item: data.parts.map((item) => handleMovieItem(item, lang)),
+ };
+};
diff --git a/lib/v2/themoviedb/episodes.js b/lib/v2/themoviedb/episodes.js
new file mode 100644
index 000000000..e1c21927b
--- /dev/null
+++ b/lib/v2/themoviedb/episodes.js
@@ -0,0 +1,27 @@
+const got = require('@/utils/got');
+const { parseDate } = require('@/utils/parse-date');
+const apiKey = require('./api-key');
+const { handleDescription } = require('./utils');
+
+module.exports = async (ctx) => {
+ const { id, seasonNumber, lang } = ctx.params;
+ const searchParams = {
+ language: lang,
+ api_key: apiKey(),
+ };
+ const { data: tvShowDetails } = await got(`https://api.themoviedb.org/3/tv/${id}`, { searchParams });
+ const { data: seasonDetails } = await got(`https://api.themoviedb.org/3/tv/${id}/season/${seasonNumber}`, { searchParams });
+
+ ctx.state.data = {
+ title: `${tvShowDetails.name} ${seasonDetails.name} — TMDB`,
+ description: seasonDetails.overview.trim(),
+ image: `https://image.tmdb.org/t/p/original${seasonDetails.poster_path}`,
+ link: `https://www.themoviedb.org/tv/${tvShowDetails.id}/season/${seasonDetails.season_number}`,
+ item: seasonDetails.episodes.reverse().map((item) => ({
+ title: `${item.episode_number} ${item.name}`,
+ link: `https://www.themoviedb.org/tv/${tvShowDetails.id}/season/${item.season_number}/episode/${item.episode_number}`,
+ description: handleDescription(item),
+ pubDate: parseDate(item.air_date),
+ })),
+ };
+};
diff --git a/lib/v2/themoviedb/maintainer.js b/lib/v2/themoviedb/maintainer.js
new file mode 100644
index 000000000..5d2559b0d
--- /dev/null
+++ b/lib/v2/themoviedb/maintainer.js
@@ -0,0 +1,7 @@
+module.exports = {
+ '/collection/:id/:lang?': ['x2cf'],
+ '/trending/:mediaType/:timeWindow/:lang?': ['x2cf'],
+ '/tv/:id/seasons/:lang?': ['x2cf'],
+ '/tv/:id/seasons/:seasonNumber/episodes/:lang?': ['x2cf'],
+ '/:mediaType/:sheet/:lang?': ['x2cf'],
+};
diff --git a/lib/v2/themoviedb/radar.js b/lib/v2/themoviedb/radar.js
new file mode 100644
index 000000000..374e2a3ea
--- /dev/null
+++ b/lib/v2/themoviedb/radar.js
@@ -0,0 +1,73 @@
+module.exports = {
+ 'themoviedb.org': {
+ _name: 'The Movie Database',
+ '.': [
+ {
+ title: 'Collection',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/collection/:name'],
+ target: (params) => `/themoviedb/collection/${params.name.split('-')[0]}`,
+ },
+ {
+ title: 'Popular Movies',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/movie'],
+ target: '/themoviedb/trending/movie/week',
+ },
+ {
+ title: 'Popular TV Shows',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/tv'],
+ target: '/themoviedb/trending/tv/week',
+ },
+ {
+ title: 'TV Show Seasons',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/tv/:id/seasons', '/tv/:id'],
+ target: (params) => `/themoviedb/tv/${params.id.split('-')[0]}/seasons`,
+ },
+ {
+ title: 'TV Show Episodes',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/tv/:id/season/:seasonNumber'],
+ target: (params) => `/themoviedb/tv/${params.id.split('-')[0]}/seasons/${params.seasonNumber}/episodes`,
+ },
+ {
+ title: 'TV Shows Airing Today',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/tv/airing-today'],
+ target: '/themoviedb/tv/airing-today',
+ },
+ {
+ title: 'Currently Airing TV Shows',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/tv/on-the-air'],
+ target: '/themoviedb/tv/on-the-air',
+ },
+ {
+ title: 'Top Rated TV Shows',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/tv/top-rated'],
+ target: '/themoviedb/tv/top-rated',
+ },
+ {
+ title: 'Now Playing Movies',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/movie/now-playing'],
+ target: '/themoviedb/movie/now-playing',
+ },
+ {
+ title: 'Upcoming Movies',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/movie/upcoming'],
+ target: '/themoviedb/movie/upcoming',
+ },
+ {
+ title: 'Top Rated Movies',
+ docs: 'https://docs.rsshub.app/multimedia.html#the-movie-database',
+ source: ['/movie/top-rated'],
+ target: '/themoviedb/movie/top-rated',
+ },
+ ],
+ },
+};
diff --git a/lib/v2/themoviedb/router.js b/lib/v2/themoviedb/router.js
new file mode 100644
index 000000000..5868dd9ec
--- /dev/null
+++ b/lib/v2/themoviedb/router.js
@@ -0,0 +1,7 @@
+module.exports = (router) => {
+ router.get('/collection/:id/:lang?', require('./collection'));
+ router.get('/trending/:mediaType/:timeWindow/:lang?', require('./trending'));
+ router.get('/tv/:id/seasons/:lang?', require('./seasons'));
+ router.get('/tv/:id/seasons/:seasonNumber/episodes/:lang?', require('./episodes'));
+ router.get('/:mediaType/:sheet/:lang?', require('./sheet'));
+};
diff --git a/lib/v2/themoviedb/seasons.js b/lib/v2/themoviedb/seasons.js
new file mode 100644
index 000000000..9ed0093f1
--- /dev/null
+++ b/lib/v2/themoviedb/seasons.js
@@ -0,0 +1,31 @@
+const got = require('@/utils/got');
+const { parseDate } = require('@/utils/parse-date');
+const apiKey = require('./api-key');
+const { handleDescription } = require('./utils');
+
+module.exports = async (ctx) => {
+ const { id, lang } = ctx.params;
+ const { data } = await got(`https://api.themoviedb.org/3/tv/${id}`, {
+ searchParams: {
+ language: lang,
+ api_key: apiKey(),
+ },
+ });
+
+ ctx.state.data = {
+ title: `${data.name} - Seasons — TMDB`,
+ description: data.overview.trim(),
+ image: `https://image.tmdb.org/t/p/original${data.poster_path}`,
+ link: `https://www.themoviedb.org/tv/${data.id}/seasons`,
+ item: data.seasons.map((item) => {
+ item.vote_average = data.vote_average;
+ item.vote_count = data.vote_count;
+ return {
+ title: item.name,
+ link: `https://www.themoviedb.org/tv/${data.id}/season/${item.season_number}`,
+ description: handleDescription(item),
+ pubDate: parseDate(item.air_date),
+ };
+ }),
+ };
+};
diff --git a/lib/v2/themoviedb/sheet.js b/lib/v2/themoviedb/sheet.js
new file mode 100644
index 000000000..66a93cda3
--- /dev/null
+++ b/lib/v2/themoviedb/sheet.js
@@ -0,0 +1,45 @@
+const got = require('@/utils/got');
+const apiKey = require('./api-key');
+const { MEDIA_TYPE_TO_ITEM_HANDLE } = require('./utils');
+
+const API_PATH = {
+ movie: {
+ 'now-playing': 'movie/now_playing',
+ upcoming: 'movie/upcoming',
+ 'top-rated': 'movie/top_rated',
+ },
+ tv: {
+ 'airing-today': 'tv/airing_today',
+ 'on-the-air': 'tv/on_the_air',
+ 'top-rated': 'tv/top_rated',
+ },
+};
+
+const TITLE = {
+ movie: {
+ 'now-playing': 'Now Playing Movies',
+ upcoming: 'Upcoming Movies',
+ 'top-rated': 'Top Rated Movies',
+ },
+ tv: {
+ 'airing-today': 'TV Shows Airing Today',
+ 'on-the-air': 'Currently Airing TV Shows',
+ 'top-rated': 'Top Rated TV Shows',
+ },
+};
+
+module.exports = async (ctx) => {
+ const { mediaType, sheet, lang } = ctx.params;
+ const { data } = await got(`https://api.themoviedb.org/3/${API_PATH[mediaType][sheet]}`, {
+ searchParams: {
+ language: lang,
+ api_key: apiKey(),
+ },
+ });
+
+ ctx.state.data = {
+ title: `${TITLE[mediaType][sheet]} — TMDB`,
+ link: `https://www.themoviedb.org/${mediaType}/${sheet}`,
+ item: data.results.map((item) => MEDIA_TYPE_TO_ITEM_HANDLE[mediaType](item, lang)),
+ };
+};
diff --git a/lib/v2/themoviedb/templates/description.art b/lib/v2/themoviedb/templates/description.art
new file mode 100644
index 000000000..daf746176
--- /dev/null
+++ b/lib/v2/themoviedb/templates/description.art
@@ -0,0 +1,6 @@
+
+
+ User Score: {{vote_average}}
+ Vote Count: {{vote_count}}
+
+{{description}}
diff --git a/lib/v2/themoviedb/trending.js b/lib/v2/themoviedb/trending.js
new file mode 100644
index 000000000..c5bed3780
--- /dev/null
+++ b/lib/v2/themoviedb/trending.js
@@ -0,0 +1,24 @@
+const got = require('@/utils/got');
+const apiKey = require('./api-key');
+const { MEDIA_TYPE_TO_ITEM_HANDLE } = require('./utils');
+
+const MEDIA_TYPE_TO_TITLE = {
+ tv: 'TV Shows',
+ movie: 'Movies',
+};
+
+module.exports = async (ctx) => {
+ const { mediaType, timeWindow, lang } = ctx.params;
+ const { data } = await got(`https://api.themoviedb.org/3/trending/${mediaType}/${timeWindow}`, {
+ searchParams: {
+ language: lang,
+ api_key: apiKey(),
+ },
+ });
+
+ ctx.state.data = {
+ title: `Popular ${MEDIA_TYPE_TO_TITLE[mediaType]} — TMDB`,
+ link: `https://www.themoviedb.org/${mediaType}`,
+ item: data.results.map((item) => MEDIA_TYPE_TO_ITEM_HANDLE[mediaType](item, lang)),
+ };
+};
diff --git a/lib/v2/themoviedb/utils.js b/lib/v2/themoviedb/utils.js
new file mode 100644
index 000000000..13cf3dcc2
--- /dev/null
+++ b/lib/v2/themoviedb/utils.js
@@ -0,0 +1,32 @@
+const path = require('path');
+const { art } = require('@/utils/render');
+const { parseDate } = require('@/utils/parse-date');
+
+const handleDescription = (item) =>
+ art(path.join(__dirname, 'templates/description.art'), {
+ poster: `https://image.tmdb.org/t/p/original${item.poster_path ?? item.still_path}`,
+ description: item.overview.trim(),
+ vote_average: item.vote_average,
+ vote_count: item.vote_count,
+ });
+
+const handleMovieItem = (item, lang) => ({
+ title: lang ? item.title : item.original_title,
+ link: `https://www.themoviedb.org/movie/${item.id}`,
+ description: handleDescription(item),
+ pubDate: parseDate(item.release_date),
+});
+
+const handleTVShowItem = (item, lang) => ({
+ title: lang ? item.name : item.original_name,
+ link: `https://www.themoviedb.org/tv/${item.id}`,
+ description: handleDescription(item),
+ pubDate: parseDate(item.first_air_date),
+});
+
+const MEDIA_TYPE_TO_ITEM_HANDLE = {
+ tv: handleTVShowItem,
+ movie: handleMovieItem,
+};
+
+module.exports = { handleDescription, handleMovieItem, handleTVShowItem, MEDIA_TYPE_TO_ITEM_HANDLE };