diff --git a/src/components/Timeline/Shared/Card/Neodb.tsx b/src/components/Timeline/Shared/Card/Neodb.tsx
new file mode 100644
index 00000000..98bffedd
--- /dev/null
+++ b/src/components/Timeline/Shared/Card/Neodb.tsx
@@ -0,0 +1,125 @@
+import GracefullyImage from '@components/GracefullyImage'
+import openLink from '@components/openLink'
+import CustomText from '@components/Text'
+import { useNeodbQuery } from '@utils/queryHooks/neodb'
+import { StyleConstants } from '@utils/styles/constants'
+import { useTheme } from '@utils/styles/ThemeManager'
+import * as Linking from 'expo-linking'
+import { Pressable, View } from 'react-native'
+
+export const CardNeodb = ({ card }: { card: Mastodon.Card }) => {
+ const { colors } = useTheme()
+
+ const segments = Linking.parse(card.url).path?.split('/')
+ if (!segments || !(segments[0] === 'movie' || segments[0] === 'book')) return null
+
+ const { data } = useNeodbQuery({ path: `${segments[0]}/${segments[1]}` })
+
+ if (!data) return null
+
+ switch (segments[0]) {
+ case 'movie':
+ return (
+ openLink(card.url)}
+ >
+
+
+
+ {[
+ data.data.title,
+ data.data.orig_title,
+ data.data.year ? `(${data.data.year})` : null
+ ]
+ .filter(d => d)
+ .join(' ')}
+
+
+ {[
+ data.data.duration ? `${data.data.duration}分钟` : null,
+ data.data.area?.join(' '),
+ data.data.genre?.join(' '),
+ data.data.director?.join(' ')
+ ]
+ .filter(d => d)
+ .join(' / ')}
+
+
+
+ )
+ case 'book':
+ return (
+ openLink(card.url)}
+ >
+
+
+
+ {[
+ data.data.title,
+ data.data.pub_year && data.data.pub_month
+ ? `(${data.data.pub_year}年${data.data.pub_month}月)`
+ : null
+ ]
+ .filter(d => d)
+ .join(' ')}
+
+
+ {[
+ data.data.author?.join(' '),
+ data.data.language,
+ data.data.pages ? `${data.data.pages}页` : null,
+ data.data.pub_house
+ ]
+ .filter(d => d)
+ .join(' / ')}
+
+
+
+ )
+ default:
+ return null
+ }
+}
diff --git a/src/components/Timeline/Shared/Card.tsx b/src/components/Timeline/Shared/Card/index.tsx
similarity index 93%
rename from src/components/Timeline/Shared/Card.tsx
rename to src/components/Timeline/Shared/Card/index.tsx
index d77ae525..254787f3 100644
--- a/src/components/Timeline/Shared/Card.tsx
+++ b/src/components/Timeline/Shared/Card/index.tsx
@@ -11,14 +11,21 @@ import { useStatusQuery } from '@utils/queryHooks/status'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext, useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
import { Pressable, View } from 'react-native'
-import TimelineDefault from '../Default'
-import StatusContext from './Context'
+import TimelineDefault from '../../Default'
+import StatusContext from '../Context'
+import { CardNeodb } from './Neodb'
const TimelineCard: React.FC = () => {
const { status, spoilerHidden, disableDetails, inThread } = useContext(StatusContext)
if (!status || !status.card) return null
+ const { i18n } = useTranslation()
+ if (status.card.url.includes('://neodb.social/') && i18n.language === 'zh-hans') {
+ return
+ }
+
const { colors } = useTheme()
const navigation = useNavigation>()
diff --git a/src/utils/queryHooks/neodb.ts b/src/utils/queryHooks/neodb.ts
new file mode 100644
index 00000000..fce9cfdf
--- /dev/null
+++ b/src/utils/queryHooks/neodb.ts
@@ -0,0 +1,40 @@
+import { QueryFunctionContext, useQuery, UseQueryOptions } from '@tanstack/react-query'
+import apiGeneral from '@utils/api/general'
+import { AxiosError } from 'axios'
+
+export type QueryKeyNeodb = ['Neodb', { path: string }]
+
+const queryFunction = async ({ queryKey }: QueryFunctionContext) => {
+ const data: any = {}
+
+ await Promise.all([
+ apiGeneral({
+ method: 'get',
+ domain: 'neodb.social',
+ url: `/${queryKey[1].path}`
+ }).then(res => {
+ const matches = (res.body as string).match(/"(\/media\/.+autocrop.+?)"/)
+ data.image = matches?.[1]
+ }),
+ apiGeneral({
+ method: 'get',
+ domain: 'neodb.social',
+ url: `/api/${queryKey[1].path}`
+ }).then(res => (data.data = res.body))
+ ])
+
+ return data
+}
+
+export const useNeodbQuery = (
+ params: QueryKeyNeodb[1] & {
+ options?: UseQueryOptions
+ }
+) => {
+ const queryKey: QueryKeyNeodb = ['Neodb', { path: params.path }]
+ return useQuery(queryKey, queryFunction, {
+ ...params?.options,
+ staleTime: Infinity,
+ cacheTime: Infinity
+ })
+}