From 6bab45b6d846c0ed295c30f4a46a9c25c1c43807 Mon Sep 17 00:00:00 2001 From: shinya Date: Sun, 17 Aug 2025 17:48:37 +0800 Subject: [PATCH] feat: add melody-cdn-sharon and make it default --- README.md | 2 ++ src/app/admin/page.tsx | 16 +++++++++++----- src/app/layout.tsx | 4 ++-- src/components/UserMenu.tsx | 20 +++++++++++++------- src/lib/config.ts | 4 ++-- src/lib/douban.client.ts | 37 ++++++++++++++++++++++--------------- src/lib/utils.ts | 36 +++++++++++++++++++----------------- 7 files changed, 71 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 57eb4a8..57816fc 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,7 @@ networks: NEXT_PUBLIC_DOUBAN_PROXY_TYPE 选项解释: - direct: 由服务器直接请求豆瓣源站 +- melody-cdn-sharon: 浏览器向豆瓣 CDN 请求数据,该 CDN 由 [旋律](https://github.com/JohnsonRan) 搭建,并由 Sharon cdn 提供加速 - cors-proxy-zwei: 浏览器向 cors proxy 请求豆瓣数据,该 cors proxy 由 [Zwei](https://github.com/bestzwei) 搭建 - cmliussss-cdn-tencent: 浏览器向豆瓣 CDN 请求数据,该 CDN 由 [CMLiussss](https://github.com/cmliu) 搭建,并由腾讯云 cdn 提供加速 - cmliussss-cdn-ali: 浏览器向豆瓣 CDN 请求数据,该 CDN 由 [CMLiussss](https://github.com/cmliu) 搭建,并由阿里云 cdn 提供加速 @@ -272,6 +273,7 @@ NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE 选项解释: - direct:由浏览器直接请求豆瓣分配的默认图片域名 - server:由服务器代理请求豆瓣分配的默认图片域名 - img3:由浏览器请求豆瓣官方的精品 cdn(阿里云) +- melody-cdn-sharon: 由浏览器请求豆瓣 CDN,该 CDN 由 [旋律](https://github.com/JohnsonRan) 搭建,并由 Sharon cdn 提供加速 - cmliussss-cdn-tencent:由浏览器请求豆瓣 CDN,该 CDN 由 [CMLiussss](https://github.com/cmliu) 搭建,并由腾讯云 cdn 提供加速 - cmliussss-cdn-ali:由浏览器请求豆瓣 CDN,该 CDN 由 [CMLiussss](https://github.com/cmliu) 搭建,并由阿里云 cdn 提供加速 - custom: 用户自定义 proxy,由 NEXT_PUBLIC_DOUBAN_IMAGE_PROXY 定义 diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 4dcbffa..5816a48 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1512,9 +1512,9 @@ const SiteConfigComponent = ({ config, refreshConfig }: { config: AdminConfig | Announcement: '', SearchDownstreamMaxPage: 1, SiteInterfaceCacheTime: 7200, - DoubanProxyType: 'direct', + DoubanProxyType: 'melody-cdn-sharon', DoubanProxy: '', - DoubanImageProxyType: 'direct', + DoubanImageProxyType: 'melody-cdn-sharon', DoubanImageProxy: '', DisableYellowFilter: false, FluidSearch: true, @@ -1530,13 +1530,13 @@ const SiteConfigComponent = ({ config, refreshConfig }: { config: AdminConfig | // 豆瓣数据源选项 const doubanDataSourceOptions = [ { value: 'direct', label: '直连(服务器直接请求豆瓣)' }, + { value: 'melody-cdn-sharon', label: '豆瓣 CDN By 旋律(Sharon CDN)' }, { value: 'cors-proxy-zwei', label: 'Cors Proxy By Zwei' }, { value: 'cmliussss-cdn-tencent', label: '豆瓣 CDN By CMLiussss(腾讯云)', }, { value: 'cmliussss-cdn-ali', label: '豆瓣 CDN By CMLiussss(阿里云)' }, - { value: 'cors-anywhere', label: 'Cors Anywhere(20 qpm)' }, { value: 'custom', label: '自定义代理' }, ]; @@ -1545,6 +1545,7 @@ const SiteConfigComponent = ({ config, refreshConfig }: { config: AdminConfig | { value: 'direct', label: '直连(浏览器直接请求豆瓣)' }, { value: 'server', label: '服务器代理(由服务器代理请求豆瓣)' }, { value: 'img3', label: '豆瓣官方精品 CDN(阿里云)' }, + { value: 'melody-cdn-sharon', label: '豆瓣 CDN By 旋律(Sharon CDN)' }, { value: 'cmliussss-cdn-tencent', label: '豆瓣 CDN By CMLiussss(腾讯云)', @@ -1556,6 +1557,11 @@ const SiteConfigComponent = ({ config, refreshConfig }: { config: AdminConfig | // 获取感谢信息 const getThanksInfo = (dataSource: string) => { switch (dataSource) { + case 'melody-cdn-sharon': + return { + text: 'Thanks to @JohnsonRan', + url: 'https://github.com/JohnsonRan', + }; case 'cors-proxy-zwei': return { text: 'Thanks to @Zwei', @@ -1576,10 +1582,10 @@ const SiteConfigComponent = ({ config, refreshConfig }: { config: AdminConfig | if (config?.SiteConfig) { setSiteSettings({ ...config.SiteConfig, - DoubanProxyType: config.SiteConfig.DoubanProxyType || 'direct', + DoubanProxyType: config.SiteConfig.DoubanProxyType || 'melody-cdn-sharon', DoubanProxy: config.SiteConfig.DoubanProxy || '', DoubanImageProxyType: - config.SiteConfig.DoubanImageProxyType || 'direct', + config.SiteConfig.DoubanImageProxyType || 'melody-cdn-sharon', DoubanImageProxy: config.SiteConfig.DoubanImageProxy || '', DisableYellowFilter: config.SiteConfig.DisableYellowFilter || false, FluidSearch: config.SiteConfig.FluidSearch || true, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 07fe0a4..eb408db 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -46,10 +46,10 @@ export default async function RootLayout({ process.env.ANNOUNCEMENT || '本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。'; let enableRegister = process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true'; - let doubanProxyType = process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'direct'; + let doubanProxyType = process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'melody-cdn-sharon'; let doubanProxy = process.env.NEXT_PUBLIC_DOUBAN_PROXY || ''; let doubanImageProxyType = - process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE || 'direct'; + process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE || 'melody-cdn-sharon'; let doubanImageProxy = process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY || ''; let disableYellowFilter = process.env.NEXT_PUBLIC_DISABLE_YELLOW_FILTER === 'true'; diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index ab466d4..68b2612 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -43,8 +43,8 @@ export const UserMenu: React.FC = () => { const [doubanProxyUrl, setDoubanProxyUrl] = useState(''); const [enableOptimization, setEnableOptimization] = useState(true); const [fluidSearch, setFluidSearch] = useState(true); - const [doubanDataSource, setDoubanDataSource] = useState('direct'); - const [doubanImageProxyType, setDoubanImageProxyType] = useState('direct'); + const [doubanDataSource, setDoubanDataSource] = useState('melody-cdn-sharon'); + const [doubanImageProxyType, setDoubanImageProxyType] = useState('melody-cdn-sharon'); const [doubanImageProxyUrl, setDoubanImageProxyUrl] = useState(''); const [isDoubanDropdownOpen, setIsDoubanDropdownOpen] = useState(false); const [isDoubanImageProxyDropdownOpen, setIsDoubanImageProxyDropdownOpen] = @@ -53,13 +53,13 @@ export const UserMenu: React.FC = () => { // 豆瓣数据源选项 const doubanDataSourceOptions = [ { value: 'direct', label: '直连(服务器直接请求豆瓣)' }, + { value: 'melody-cdn-sharon', label: '豆瓣 CDN By 旋律(Sharon CDN)' }, { value: 'cors-proxy-zwei', label: 'Cors Proxy By Zwei' }, { value: 'cmliussss-cdn-tencent', label: '豆瓣 CDN By CMLiussss(腾讯云)', }, { value: 'cmliussss-cdn-ali', label: '豆瓣 CDN By CMLiussss(阿里云)' }, - { value: 'cors-anywhere', label: 'Cors Anywhere(20 qpm)' }, { value: 'custom', label: '自定义代理' }, ]; @@ -68,6 +68,7 @@ export const UserMenu: React.FC = () => { { value: 'direct', label: '直连(浏览器直接请求豆瓣)' }, { value: 'server', label: '服务器代理(由服务器代理请求豆瓣)' }, { value: 'img3', label: '豆瓣官方精品 CDN(阿里云)' }, + { value: 'melody-cdn-sharon', label: '豆瓣 CDN By 旋律(Sharon CDN)' }, { value: 'cmliussss-cdn-tencent', label: '豆瓣 CDN By CMLiussss(腾讯云)', @@ -115,7 +116,7 @@ export const UserMenu: React.FC = () => { const savedDoubanDataSource = localStorage.getItem('doubanDataSource'); const defaultDoubanProxyType = - (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY_TYPE || 'direct'; + (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY_TYPE || 'melody-cdn-sharon'; if (savedDoubanDataSource !== null) { setDoubanDataSource(savedDoubanDataSource); } else if (defaultDoubanProxyType) { @@ -135,7 +136,7 @@ export const UserMenu: React.FC = () => { 'doubanImageProxyType' ); const defaultDoubanImageProxyType = - (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY_TYPE || 'direct'; + (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY_TYPE || 'melody-cdn-sharon'; if (savedDoubanImageProxyType !== null) { setDoubanImageProxyType(savedDoubanImageProxyType); } else if (defaultDoubanImageProxyType) { @@ -366,6 +367,11 @@ export const UserMenu: React.FC = () => { // 获取感谢信息 const getThanksInfo = (dataSource: string) => { switch (dataSource) { + case 'melody-cdn-sharon': + return { + text: 'Thanks to @JohnsonRan', + url: 'https://github.com/JohnsonRan', + }; case 'cors-proxy-zwei': return { text: 'Thanks to @Zwei', @@ -384,11 +390,11 @@ export const UserMenu: React.FC = () => { const handleResetSettings = () => { const defaultDoubanProxyType = - (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY_TYPE || 'direct'; + (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY_TYPE || 'melody-cdn-sharon'; const defaultDoubanProxy = (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY || ''; const defaultDoubanImageProxyType = - (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY_TYPE || 'direct'; + (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY_TYPE || 'melody-cdn-sharon'; const defaultDoubanImageProxyUrl = (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY || ''; const defaultFluidSearch = diff --git a/src/lib/config.ts b/src/lib/config.ts index 7abc88d..521981b 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -161,10 +161,10 @@ async function getInitConfig(configFile: string, subConfig: { Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5, SiteInterfaceCacheTime: cfgFile.cache_time || 7200, DoubanProxyType: - process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'direct', + process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'melody-cdn-sharon', DoubanProxy: process.env.NEXT_PUBLIC_DOUBAN_PROXY || '', DoubanImageProxyType: - process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE || 'direct', + process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE || 'melody-cdn-sharon', DoubanImageProxy: process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY || '', DisableYellowFilter: process.env.NEXT_PUBLIC_DISABLE_YELLOW_FILTER === 'true', diff --git a/src/lib/douban.client.ts b/src/lib/douban.client.ts index ca0cfd8..3bc1561 100644 --- a/src/lib/douban.client.ts +++ b/src/lib/douban.client.ts @@ -69,8 +69,8 @@ async function fetchWithTimeout( proxyUrl === 'https://cors-anywhere.com/' ? `${proxyUrl}${url}` : proxyUrl - ? `${proxyUrl}${encodeURIComponent(url)}` - : url; + ? `${proxyUrl}${encodeURIComponent(url)}` + : url; const fetchOptions: RequestInit = { signal: controller.signal, @@ -94,18 +94,19 @@ async function fetchWithTimeout( function getDoubanProxyConfig(): { proxyType: - | 'direct' - | 'cors-proxy-zwei' - | 'cmliussss-cdn-tencent' - | 'cmliussss-cdn-ali' - | 'cors-anywhere' - | 'custom'; + | 'direct' + | 'melody-cdn-sharon' + | 'cors-proxy-zwei' + | 'cmliussss-cdn-tencent' + | 'cmliussss-cdn-ali' + | 'cors-anywhere' + | 'custom'; proxyUrl: string; } { const doubanProxyType = localStorage.getItem('doubanDataSource') || (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY_TYPE || - 'direct'; + 'melody-cdn-sharon'; const doubanProxy = localStorage.getItem('doubanProxyUrl') || (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY || @@ -147,8 +148,8 @@ export async function fetchDoubanCategories( const target = useTencentCDN ? `https://m.douban.cmliussss.net/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}` : useAliCDN - ? `https://m.douban.cmliussss.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}` - : `https://m.douban.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}`; + ? `https://m.douban.cmliussss.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}` + : `https://m.douban.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}`; try { const response = await fetchWithTimeout( @@ -198,6 +199,8 @@ export async function getDoubanCategories( const { kind, category, type, pageLimit = 20, pageStart = 0 } = params; const { proxyType, proxyUrl } = getDoubanProxyConfig(); switch (proxyType) { + case 'melody-cdn-sharon': + return fetchDoubanCategories(params, 'https://douban.ihtw.moe/'); case 'cors-proxy-zwei': return fetchDoubanCategories(params, 'https://ciao-cors.is-an.org/'); case 'cmliussss-cdn-tencent': @@ -231,6 +234,8 @@ export async function getDoubanList( const { tag, type, pageLimit = 20, pageStart = 0 } = params; const { proxyType, proxyUrl } = getDoubanProxyConfig(); switch (proxyType) { + case 'melody-cdn-sharon': + return fetchDoubanList(params, 'https://douban.ihtw.moe/'); case 'cors-proxy-zwei': return fetchDoubanList(params, 'https://ciao-cors.is-an.org/'); case 'cmliussss-cdn-tencent': @@ -279,8 +284,8 @@ export async function fetchDoubanList( const target = useTencentCDN ? `https://movie.douban.cmliussss.net/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}` : useAliCDN - ? `https://movie.douban.cmliussss.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}` - : `https://movie.douban.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}`; + ? `https://movie.douban.cmliussss.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}` + : `https://movie.douban.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}`; try { const response = await fetchWithTimeout( @@ -351,6 +356,8 @@ export async function getDoubanRecommends( } = params; const { proxyType, proxyUrl } = getDoubanProxyConfig(); switch (proxyType) { + case 'melody-cdn-sharon': + return fetchDoubanRecommends(params, 'https://douban.ihtw.moe/'); case 'cors-proxy-zwei': return fetchDoubanRecommends(params, 'https://ciao-cors.is-an.org/'); case 'cmliussss-cdn-tencent': @@ -432,8 +439,8 @@ async function fetchDoubanRecommends( const baseUrl = useTencentCDN ? `https://m.douban.cmliussss.net/rexxar/api/v2/${kind}/recommend` : useAliCDN - ? `https://m.douban.cmliussss.com/rexxar/api/v2/${kind}/recommend` - : `https://m.douban.com/rexxar/api/v2/${kind}/recommend`; + ? `https://m.douban.cmliussss.com/rexxar/api/v2/${kind}/recommend` + : `https://m.douban.com/rexxar/api/v2/${kind}/recommend`; const reqParams = new URLSearchParams(); reqParams.append('refresh', '0'); reqParams.append('start', pageStart.toString()); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 8b9a3d4..d376f8a 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,18 +4,19 @@ import Hls from 'hls.js'; function getDoubanImageProxyConfig(): { proxyType: - | 'direct' - | 'server' - | 'img3' - | 'cmliussss-cdn-tencent' - | 'cmliussss-cdn-ali' - | 'custom'; + | 'direct' + | 'server' + | 'img3' + | 'melody-cdn-sharon' + | 'cmliussss-cdn-tencent' + | 'cmliussss-cdn-ali' + | 'custom'; proxyUrl: string; } { const doubanImageProxyType = localStorage.getItem('doubanImageProxyType') || (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY_TYPE || - 'direct'; + 'melody-cdn-sharon'; const doubanImageProxy = localStorage.getItem('doubanImageProxyUrl') || (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY || @@ -41,6 +42,8 @@ export function processImageUrl(originalUrl: string): string { switch (proxyType) { case 'server': return `/api/image-proxy?url=${encodeURIComponent(originalUrl)}`; + case 'melody-cdn-sharon': + return `https://douban.ihtw.moe/${encodeURIComponent(originalUrl)}`; case 'img3': return originalUrl.replace(/img\d+\.doubanio\.com/g, 'img3.doubanio.com'); case 'cmliussss-cdn-tencent': @@ -131,14 +134,14 @@ export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{ width >= 3840 ? '4K' // 4K: 3840x2160 : width >= 2560 - ? '2K' // 2K: 2560x1440 - : width >= 1920 - ? '1080p' // 1080p: 1920x1080 - : width >= 1280 - ? '720p' // 720p: 1280x720 - : width >= 854 - ? '480p' - : 'SD'; // 480p: 854x480 + ? '2K' // 2K: 2560x1440 + : width >= 1920 + ? '1080p' // 1080p: 1920x1080 + : width >= 1280 + ? '720p' // 720p: 1280x720 + : width >= 854 + ? '480p' + : 'SD'; // 480p: 854x480 resolve({ quality, @@ -211,8 +214,7 @@ export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{ }); } catch (error) { throw new Error( - `Error getting video resolution: ${ - error instanceof Error ? error.message : String(error) + `Error getting video resolution: ${error instanceof Error ? error.message : String(error) }` ); }