Fetch remote user's toots

This commit is contained in:
xmflsct 2023-02-25 23:42:04 +01:00
parent f505f78193
commit 702ecef243
16 changed files with 184 additions and 105 deletions

View File

@ -1,3 +1,4 @@
Enjoy toooting! This version includes following improvements and fixes:
- Added following remote instance
- Added set note of followed users
- Added set note of followed users
- Best effort load remote user's toots

View File

@ -1,3 +1,4 @@
toooting愉快此版本包括以下改进和修复
- 新增关注远程实例功能
- 新增关注用户备注功能
- 新增关注用户备注功能
- 加载远程用户的嘟文

View File

@ -33,7 +33,7 @@ declare namespace Mastodon {
role?: Role
// Internal
_remote?: boolean
_remote?: string // domain
}
type Announcement = {
@ -400,7 +400,7 @@ declare namespace Mastodon {
url: string
// Internal
_remote?: boolean
_remote?: string // domain
}
type Notification =

View File

@ -28,7 +28,7 @@ const TabMeRoot: React.FC = () => {
})
return (
<AccountContext.Provider value={{ account: data, pageMe: true }}>
<AccountContext.Provider value={{ account: data, pageMe: true, localInstance: true }}>
{accountActive && data ? <AccountNav scrollY={scrollY} /> : null}
<Animated.ScrollView
ref={scrollRef}

View File

@ -13,7 +13,12 @@ import { Dimensions, Pressable, View } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import AccountContext from './Context'
const AccountAttachments: React.FC = () => {
export type Props = {
remote_id?: Mastodon.Status['id']
remote_domain?: string
}
const AccountAttachments: React.FC<Props> = ({ remote_id, remote_domain }) => {
const { account } = useContext(AccountContext)
if (account?.suspended) return null
@ -32,7 +37,8 @@ const AccountAttachments: React.FC = () => {
id: account?.id,
exclude_reblogs: false,
only_media: true,
options: { enabled: !!account?.id }
...(remote_id && remote_domain && { remote_id, remote_domain }),
options: { enabled: !!account?.id || (!!remote_id && !!remote_domain) }
})
const flattenData = flattenPages(data)
@ -55,7 +61,7 @@ const AccountAttachments: React.FC = () => {
horizontal
data={flattenData}
renderItem={({ item, index }) => {
if (index === DISPLAY_AMOUNT - 1) {
if (index === DISPLAY_AMOUNT - 1 && (!remote_id || !remote_domain)) {
return (
<Pressable
onPress={() => {

View File

@ -4,6 +4,7 @@ type AccountContextType = {
account?: Mastodon.Account
relationship?: Mastodon.Relationship
pageMe?: boolean
localInstance: boolean
}
const AccountContext = createContext<AccountContextType>({} as AccountContextType)

View File

@ -10,7 +10,7 @@ import { PlaceholderLine } from 'rn-placeholder'
import AccountContext from '../Context'
const AccountInformationAccount: React.FC = () => {
const { account, relationship, pageMe } = useContext(AccountContext)
const { account, relationship, pageMe, localInstance } = useContext(AccountContext)
const { t } = useTranslation('screenTabs')
const { colors } = useTheme()
@ -18,10 +18,6 @@ const AccountInformationAccount: React.FC = () => {
const [acct] = useAccountStorage.string('auth.account.acct')
const [domain] = useAccountStorage.string('auth.account.domain')
const localInstance = account?.acct?.includes('@')
? account?.acct?.includes(`@${domain}`)
: !account?._remote
if (account || pageMe) {
return (
<View

View File

@ -35,6 +35,7 @@ const AccountInformationAvatar: React.FC = () => {
}
}}
dim
withoutTransition
/>
)
}

View File

@ -9,6 +9,7 @@ import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
import { useAccountQuery } from '@utils/queryHooks/account'
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { useAccountStorage } from '@utils/storage/actions'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { Fragment, useEffect, useMemo, useState } from 'react'
@ -31,33 +32,19 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
const { t } = useTranslation('screenTabs')
const { colors, mode } = useTheme()
const { data, dataUpdatedAt } = useAccountQuery({
const { data, dataUpdatedAt, isFetched } = useAccountQuery({
account,
_local: true,
options: {
placeholderData: (account._remote
? { ...account, id: undefined }
: account) as Mastodon.Account,
onSuccess: a => {
if (account._remote) {
setQueryKey([
queryKey[0],
{
...queryKey[1],
page: 'Account',
id: a.id,
exclude_reblogs: true,
only_media: false
}
])
}
},
onError: () => navigation.goBack()
}
})
const { data: dataRelationship } = useRelationshipQuery({
id: account._remote ? data?.id : account.id,
options: { enabled: account._remote ? !!data?.id : true }
id: data?.id,
options: { enabled: account._remote ? isFetched : true }
})
const queryClient = useQueryClient()
@ -65,9 +52,10 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
'Timeline',
{
page: 'Account',
id: account._remote ? data?.id : account.id,
id: data?.id,
exclude_reblogs: true,
only_media: false
only_media: false,
...(account._remote && { remote_id: account.id, remote_domain: account._remote })
}
])
@ -76,9 +64,7 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
useEffect(() => {
navigation.setOptions({
headerTransparent: true,
headerStyle: {
backgroundColor: `rgba(255, 255, 255, 0)`
},
headerStyle: { backgroundColor: `rgba(255, 255, 255, 0)` },
title: '',
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} background />,
headerRight: () => {
@ -179,7 +165,9 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
<View style={{ borderBottomWidth: 1, borderBottomColor: colors.border }}>
<AccountHeader />
<AccountInformation />
<AccountAttachments />
<AccountAttachments
{...(account._remote && { remote_id: account.id, remote_domain: account._remote })}
/>
</View>
{!data?.suspended ? (
// @ts-ignore
@ -246,8 +234,18 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
)
}, [segment, dataUpdatedAt, mode])
const [domain] = useAccountStorage.string('auth.account.domain')
return (
<AccountContext.Provider value={{ account: data, relationship: dataRelationship }}>
<AccountContext.Provider
value={{
account: data,
relationship: dataRelationship,
localInstance: account?.acct?.includes('@')
? account?.acct?.includes(`@${domain}`)
: !account?._remote
}}
>
<AccountNav scrollY={scrollY} />
{data?.suspended ? (
@ -256,7 +254,6 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
<Timeline
queryKey={queryKey}
disableRefresh
queryOptions={{ enabled: account._remote ? !!data?.id : true }}
customProps={{
keyboardShouldPersistTaps: 'always',
renderItem: ({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,

View File

@ -206,7 +206,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
if (localMatch) {
return localMatch
} else {
return appendRemote.status(ancestor)
return appendRemote.status(ancestor, domain)
}
})
}
@ -256,7 +256,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
if (localMatch) {
return { ...localMatch, _level: remote._level }
} else {
return appendRemote.status(remote)
return appendRemote.status(remote, match!.domain)
}
})
}

View File

@ -1,23 +1,23 @@
// Central place appending _remote internal prop
export const appendRemote = {
status: (status: Mastodon.Status) => ({
status: (status: Mastodon.Status, domain: string) => ({
...status,
...(status.reblog && {
reblog: {
...status.reblog,
account: appendRemote.account(status.reblog.account),
mentions: appendRemote.mentions(status.reblog.mentions)
account: appendRemote.account(status.reblog.account, domain),
mentions: appendRemote.mentions(status.reblog.mentions, domain)
}
}),
account: appendRemote.account(status.account),
mentions: appendRemote.mentions(status.mentions),
account: appendRemote.account(status.account, domain),
mentions: appendRemote.mentions(status.mentions, domain),
_remote: true
}),
account: (account: Mastodon.Account) => ({
account: (account: Mastodon.Account, domain: string) => ({
...account,
_remote: true
_remote: domain
}),
mentions: (mentions: Mastodon.Mention[]) =>
mentions?.map(mention => ({ ...mention, _remote: true }))
mentions: (mentions: Mastodon.Mention[], domain: string) =>
mentions?.map(mention => ({ ...mention, _remote: domain }))
}

View File

@ -75,7 +75,7 @@ export const urlMatcher = (
return {
domain,
...(accountAcct && { account: { acct: accountAcct, _remote } }),
...(accountAcct && { account: { acct: accountAcct, _remote: domain } }),
...(statusId && { status: { id: statusId, _remote } })
}
}

View File

@ -33,16 +33,16 @@ const accountQueryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyA
if (id) {
matchedAccount = await apiGeneral<Mastodon.Account>({
method: 'get',
domain: domain,
domain,
url: `api/v1/accounts/${id}`
}).then(res => appendRemote.account(res.body))
}).then(res => appendRemote.account(res.body, domain))
} else if (acct) {
matchedAccount = await apiGeneral<Mastodon.Account>({
method: 'get',
domain: domain,
domain,
url: 'api/v1/accounts/lookup',
params: { acct }
}).then(res => appendRemote.account(res.body))
}).then(res => appendRemote.account(res.body, domain))
}
} catch {}
}

View File

@ -27,7 +27,7 @@ const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyStatus>)
method: 'get',
domain,
url: `api/v1/statuses/${id}`
}).then(res => appendRemote.status(res.body))
}).then(res => appendRemote.status(res.body, domain))
} catch {}
}

View File

@ -46,6 +46,9 @@ export type QueryKeyTimeline = [
id?: Mastodon.Account['id']
exclude_reblogs: boolean
only_media: boolean
// remote info
remote_id?: Mastodon.Account['id']
remote_domain?: string
}
| {
page: 'Toot'
@ -107,10 +110,7 @@ export const queryFunctionTimeline = async ({
return apiInstance<Mastodon.Status[]>({
method: 'get',
url: 'timelines/public',
params: {
...params,
local: 'true'
}
params: { ...params, local: true }
})
case 'LocalPublic':
@ -126,11 +126,11 @@ export const queryFunctionTimeline = async ({
method: 'get',
domain: page.domain,
url: 'api/v1/timelines/public',
params: {
...params,
local: 'true'
}
}).then(res => ({ ...res, body: res.body.map(status => appendRemote.status(status)) }))
params: { ...params, local: true }
}).then(res => ({
...res,
body: res.body.map(status => appendRemote.status(status, page.domain!))
}))
} else {
return apiInstance<Mastodon.Status[]>({
method: 'get',
@ -163,61 +163,137 @@ export const queryFunctionTimeline = async ({
})
case 'Account':
if (!page.id) return Promise.reject('Timeline query account id not provided')
const reject = Promise.reject('Timeline query account id not provided')
if (page.only_media) {
return apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: {
only_media: 'true',
...params
}
})
} else if (page.exclude_reblogs) {
if (pageParam && pageParam.hasOwnProperty('max_id')) {
return apiInstance<Mastodon.Status[]>({
let res
if (page.remote_domain && page.remote_id) {
res = await apiGeneral<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
domain: page.remote_domain,
url: `api/v1/accounts/${page.remote_id}/statuses`,
params: {
exclude_replies: 'true',
only_media: true,
...params
}
})
} else {
const res1 = await apiInstance<(Mastodon.Status & { _pinned: boolean })[]>({
.then(res => ({
...res,
body: res.body.map(status => appendRemote.status(status, page.remote_domain!))
}))
.catch(() => {})
}
if (!res && page.id) {
res = await apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: {
pinned: 'true'
only_media: true,
...params
}
})
res1.body = res1.body.map(status => {
status._pinned = true
return status
})
const res2 = await apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: {
exclude_replies: 'true'
}
})
return {
body: uniqBy([...res1.body, ...res2.body], 'id'),
...(res2.links?.next && { links: { next: res2.links.next } })
}
return res || reject
} else if (page.exclude_reblogs) {
if (pageParam && pageParam.hasOwnProperty('max_id')) {
let res
if (page.remote_domain && page.remote_id) {
res = await apiGeneral<Mastodon.Status[]>({
method: 'get',
domain: page.remote_domain,
url: `api/v1/accounts/${page.remote_id}/statuses`,
params: {
exclude_replies: true,
...params
}
})
.then(res => ({
...res,
body: res.body.map(status => appendRemote.status(status, page.remote_domain!))
}))
.catch(() => {})
}
if (!res && page.id) {
res = await apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: {
exclude_replies: true,
...params
}
})
}
return res || reject
} else {
let res
if (page.remote_domain && page.remote_id) {
res = await apiGeneral<Mastodon.Status[]>({
method: 'get',
domain: page.remote_domain,
url: `api/v1/accounts/${page.remote_id}/statuses`,
params: { exclude_replies: true }
})
.then(res => ({
...res,
body: res.body.map(status => appendRemote.status(status, page.remote_domain!))
}))
.catch(() => {})
}
if (!res && page.id) {
const resPinned = await apiInstance<(Mastodon.Status & { _pinned: boolean })[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: { pinned: true }
}).then(res => ({
...res,
body: res.body.map(status => {
status._pinned = true
return status
})
}))
const resDefault = await apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: { exclude_replies: true }
})
return {
body: uniqBy([...resPinned.body, ...resDefault.body], 'id'),
links: resDefault.links
}
}
return res || reject
}
} else {
return apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: {
...params,
exclude_replies: false,
only_media: false
}
})
let res
if (page.remote_domain && page.remote_id) {
res = await apiGeneral<Mastodon.Status[]>({
method: 'get',
domain: page.remote_domain,
url: `api/v1/accounts/${page.remote_id}/statuses`,
params: {
...params,
exclude_replies: false,
only_media: false
}
})
.then(res => ({
...res,
body: res.body.map(status => appendRemote.status(status, page.remote_domain!))
}))
.catch(() => {})
}
if (!res && page.id) {
res = await apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: {
...params,
exclude_replies: false,
only_media: false
}
})
}
return res || reject
}
case 'Hashtag':

View File

@ -57,7 +57,7 @@ const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<Query
})
return {
...res,
body: res.body.map(account => appendRemote.account(account)),
body: res.body.map(account => appendRemote.account(account, domain)),
remoteData: true
}
} else {