diff --git a/src/app/api/tmdb/[...all]/route.ts b/src/app/api/tmdb/[...all]/route.ts index 0ecbc0ba4a8..39406dfeca3 100644 --- a/src/app/api/tmdb/[...all]/route.ts +++ b/src/app/api/tmdb/[...all]/route.ts @@ -26,7 +26,9 @@ export const GET = async (req: NextRequest) => { const searchString = query.toString() const url = `https://api.themoviedb.org/3/${pathname.join('/')}${ - searchString ? `?${searchString}` : '' + searchString + ? `?${searchString}&api_key=${process.env.TMDB_API_KEY}` + : `?api_key=${process.env.TMDB_API_KEY}` }` const headers = new Headers() diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index 1c259fbcf2a..bf1b32cabd8 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -28,7 +28,9 @@ export default function GlobalError({

禁止访问或者 API 服务出现问题

- 重试 + location.reload()}> + 重试 +
diff --git a/src/components/ui/link-card/LinkCard.tsx b/src/components/ui/link-card/LinkCard.tsx index 1add07014e1..6cd0216cfd6 100644 --- a/src/components/ui/link-card/LinkCard.tsx +++ b/src/components/ui/link-card/LinkCard.tsx @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/switch-case-braces */ import { simpleCamelcaseKeys as camelcaseKeys } from '@mx-space/api-client' import { m, useMotionTemplate, useMotionValue } from 'framer-motion' import Link from 'next/link' @@ -11,6 +12,7 @@ import uniqolor from 'uniqolor' import { LazyLoad } from '~/components/common/Lazyload' import { MingcuteStarHalfFill } from '~/components/icons/star' import { usePeek } from '~/components/modules/peek/usePeek' +import { API_URL } from '~/constants/env' import { LanguageToColorMap } from '~/constants/language' import { useIsClientTransition } from '~/hooks/common/use-is-client' import useIsCommandOrControlPressed from '~/hooks/common/use-is-command-or-control-pressed' @@ -93,6 +95,7 @@ const LinkCardImpl: FC = (props) => { [LinkCardSource.GHCommit]: fetchGitHubCommitData, [LinkCardSource.GHPr]: fetchGitHubPRData, [LinkCardSource.Self]: fetchMxSpaceData, + [LinkCardSource.LEETCODE]: fetchLeetCodeQuestionData, } as Record if (tmdbEnabled) fetchDataFunctions[LinkCardSource.TMDB] = fetchTheMovieDBData @@ -508,3 +511,113 @@ const fetchTheMovieDBData: FetchObject = { json.homepage && setFullUrl(json.homepage) }, } + +const fetchLeetCodeQuestionData: FetchObject = { + isValid: (id) => { + // 检查 titleSlug 是否是一个有效的字符串 + return typeof id === 'string' && id.length > 0 + }, + fetch: async (id, setCardInfo, setFullUrl) => { + try { + //获取题目信息 + const body = { + query: `query questionData($titleSlug: String!) {\n question(titleSlug: $titleSlug) {translatedTitle\n difficulty\n likes\n topicTags { translatedName\n }\n stats\n }\n}\n`, + variables: { titleSlug: id }, + } + const questionData = await fetch(`${API_URL}/fn/leetcode/shiro`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }).then(async (res) => { + if (!res.ok) { + throw new Error('Failed to fetch LeetCode question title') + } + return res.json() + }) + const questionTitleData = camelcaseKeys(questionData.data.question) + const stats = JSON.parse(questionTitleData.stats) + // 设置卡片信息 + setCardInfo({ + title: ( + <> + + + {questionTitleData.translatedTitle} + + + {questionTitleData.likes > 0 && ( + + + + {questionTitleData.likes} + + + )} + + + + ), + desc: ( + <> + + {questionTitleData.difficulty} + + + {questionTitleData.topicTags + .map((tag: any) => tag.translatedName) + .join(' / ')} + + + AR: {stats.acRate} + + + ), + image: + 'https://upload.wikimedia.org/wikipedia/commons/1/19/LeetCode_logo_black.png', + color: getDifficultyColor(questionTitleData.difficulty), + }) + + setFullUrl(`https://leetcode.cn/problems/${id}/description`) + } catch (err) { + console.error('Error fetching LeetCode question data:', err) + throw err + } + }, +} + +// 映射难度到颜色的函数 +function getDifficultyColor(difficulty: string) { + switch (difficulty) { + case 'Easy': + return '#00BFA5' + case 'Medium': + return '#FFA726' + case 'Hard': + return '#F44336' + default: + return '#757575' + } +} + +// 难度字体颜色className +function getDifficultyColorClass(difficulty: string) { + switch (difficulty) { + case 'Easy': + return 'text-green-500' + case 'Medium': + return 'text-yellow-500' + case 'Hard': + return 'text-red-500' + default: + return 'text-gray-500' + } +} + +interface LeetCodeResponse { + query: string + variables: Record +} diff --git a/src/components/ui/link-card/enums.tsx b/src/components/ui/link-card/enums.tsx index 52ea50bee6d..041116b8ef4 100644 --- a/src/components/ui/link-card/enums.tsx +++ b/src/components/ui/link-card/enums.tsx @@ -7,4 +7,5 @@ export enum LinkCardSource { GHCommit = 'gh-commit', GHPr = 'gh-pr', TMDB = 'tmdb', + LEETCODE = 'leetcode', } diff --git a/src/components/ui/markdown/renderers/LinkRenderer.tsx b/src/components/ui/markdown/renderers/LinkRenderer.tsx index 37803f55db1..365215c8dbc 100644 --- a/src/components/ui/markdown/renderers/LinkRenderer.tsx +++ b/src/components/ui/markdown/renderers/LinkRenderer.tsx @@ -18,6 +18,7 @@ import { isGithubPrUrl, isGithubRepoUrl, isGithubUrl, + isLeetCodeUrl, isSelfArticleUrl, isSelfThinkingUrl, isTMDBUrl, @@ -144,6 +145,17 @@ export const BlockLinkRenderer = ({ return fallbackElement } + + case isLeetCodeUrl(url): { + return ( + + ) + } + case isBilibiliVideoUrl(url): { const { id } = parseBilibiliVideoUrl(url) diff --git a/src/lib/link-parser.ts b/src/lib/link-parser.ts index 5e45c4d15aa..c2d9201cd34 100644 --- a/src/lib/link-parser.ts +++ b/src/lib/link-parser.ts @@ -5,7 +5,9 @@ import { isClientSide, isDev } from './env' export const getTweetId = (url: URL) => url.pathname.split('/').pop()! const GITHUB_HOST = 'github.com' - +export const isLeetCodeUrl = (url: URL) => { + return url.hostname === 'leetcode.cn' || url.hostname === 'leetcode.com' +} export const isGithubRepoUrl = (url: URL) => url.hostname === GITHUB_HOST && url.pathname.startsWith('/') &&