mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Merge branch 'main' into candidate
This commit is contained in:
@ -2,7 +2,7 @@ import { mapEnvironment } from '@utils/helpers/checkEnvironment'
|
||||
import { GLOBAL } from '@utils/storage'
|
||||
import { setGlobalStorage } from '@utils/storage/actions'
|
||||
import axios from 'axios'
|
||||
import parse from 'url-parse'
|
||||
import * as Linking from 'expo-linking'
|
||||
import { userAgent } from '.'
|
||||
|
||||
const list = [
|
||||
@ -86,21 +86,23 @@ export const connectMedia = (args?: {
|
||||
}): { uri?: string; headers?: { 'x-tooot-domain': string } } => {
|
||||
if (GLOBAL.connect) {
|
||||
if (args?.uri) {
|
||||
const host = parse(args.uri).host
|
||||
return {
|
||||
...args,
|
||||
uri: args.uri.replace(
|
||||
host,
|
||||
CONNECT_DOMAIN(
|
||||
args.uri
|
||||
.split('')
|
||||
.map(i => i.charCodeAt(0))
|
||||
.reduce((a, b) => a + b, 0) %
|
||||
(list.length + 1)
|
||||
)
|
||||
),
|
||||
headers: { 'x-tooot-domain': host }
|
||||
}
|
||||
const host = Linking.parse(args.uri).hostname
|
||||
return host
|
||||
? {
|
||||
...args,
|
||||
uri: args.uri.replace(
|
||||
host,
|
||||
CONNECT_DOMAIN(
|
||||
args.uri
|
||||
.split('')
|
||||
.map(i => i.charCodeAt(0))
|
||||
.reduce((a, b) => a + b, 0) %
|
||||
(list.length + 1)
|
||||
)
|
||||
),
|
||||
headers: { 'x-tooot-domain': host }
|
||||
}
|
||||
: { ...args }
|
||||
} else {
|
||||
return { ...args }
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ import { GLOBAL } from '@utils/storage'
|
||||
import { setGlobalStorage } from '@utils/storage/actions'
|
||||
import chalk from 'chalk'
|
||||
import Constants from 'expo-constants'
|
||||
import * as Linking from 'expo-linking'
|
||||
import { Platform } from 'react-native'
|
||||
import parse from 'url-parse'
|
||||
|
||||
const userAgent = {
|
||||
'User-Agent': `tooot/${Constants.expoConfig?.version} ${Platform.OS}/${Platform.Version}`
|
||||
@ -80,17 +80,22 @@ export const parseHeaderLinks = (headerLink?: string): PagedResponse['links'] =>
|
||||
|
||||
const linkParsed = [...headerLink.matchAll(/<(\S+?)>; *rel="(next|prev)"/gi)]
|
||||
for (const link of linkParsed) {
|
||||
const queries = parse(link[1], true).query
|
||||
const queries = Linking.parse(link[1]).queryParams
|
||||
if (!queries) return
|
||||
|
||||
const isOffset = !!queries.offset?.length
|
||||
const unwrapArray = (value: any | any[]) => (Array.isArray(value) ? value[0] : value)
|
||||
|
||||
switch (link[2]) {
|
||||
case 'prev':
|
||||
const prevId = isOffset ? queries.offset : queries.min_id
|
||||
if (prevId) links.prev = isOffset ? { offset: prevId } : { min_id: prevId }
|
||||
if (prevId)
|
||||
links.prev = isOffset ? { offset: unwrapArray(prevId) } : { min_id: unwrapArray(prevId) }
|
||||
break
|
||||
case 'next':
|
||||
const nextId = isOffset ? queries.offset : queries.max_id
|
||||
if (nextId) links.next = isOffset ? { offset: nextId } : { max_id: nextId }
|
||||
if (nextId)
|
||||
links.next = isOffset ? { offset: unwrapArray(nextId) } : { max_id: unwrapArray(nextId) }
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import parse from 'url-parse'
|
||||
import * as Linking from 'expo-linking'
|
||||
|
||||
// Would mess with the /@username format
|
||||
const BLACK_LIST = ['matters.news', 'medium.com']
|
||||
@ -13,8 +13,8 @@ export const urlMatcher = (
|
||||
status?: Partial<Pick<Mastodon.Status, 'id' | '_remote'>>
|
||||
}
|
||||
| undefined => {
|
||||
const parsed = parse(url)
|
||||
if (!parsed.hostname.length || !parsed.pathname.length) return undefined
|
||||
const parsed = Linking.parse(url)
|
||||
if (!parsed.hostname?.length || !parsed.path?.length) return undefined
|
||||
|
||||
const domain = parsed.hostname
|
||||
if (BLACK_LIST.includes(domain)) {
|
||||
@ -26,8 +26,8 @@ export const urlMatcher = (
|
||||
let statusId: string | undefined
|
||||
let accountAcct: string | undefined
|
||||
|
||||
const segments = parsed.pathname.split('/')
|
||||
const last = segments[segments.length - 1]
|
||||
const segments = parsed.path.split('/')
|
||||
const last = segments.at(-1)
|
||||
const length = segments.length // there is a starting slash
|
||||
|
||||
const testAndAssignStatusId = (id: string) => {
|
||||
@ -38,7 +38,7 @@ export const urlMatcher = (
|
||||
|
||||
switch (last?.startsWith('@')) {
|
||||
case true:
|
||||
if (length === 2 || (length === 3 && segments[length - 2] === 'web')) {
|
||||
if (length === 1 || (length === 2 && segments.at(-2) === 'web')) {
|
||||
// https://social.xmflsct.com/@tooot <- Mastodon v4.0 and above
|
||||
// https://social.xmflsct.com/web/@tooot <- Mastodon v3.5 and below ! cannot be searched on the same instance
|
||||
accountAcct = `${last}@${domain}`
|
||||
@ -48,13 +48,13 @@ export const urlMatcher = (
|
||||
const nextToLast = segments[length - 2]
|
||||
if (nextToLast) {
|
||||
if (nextToLast === 'statuses') {
|
||||
if (length === 4 && segments[length - 3] === 'web') {
|
||||
if (length === 3 && segments.at(-3) === 'web') {
|
||||
// https://social.xmflsct.com/web/statuses/105590085754428765 <- old
|
||||
testAndAssignStatusId(last)
|
||||
} else if (
|
||||
length === 5 &&
|
||||
segments[length - 2] === 'statuses' &&
|
||||
segments[length - 4] === 'users'
|
||||
length === 4 &&
|
||||
segments.at(-2) === 'statuses' &&
|
||||
segments.at(-4) === 'users'
|
||||
) {
|
||||
// https://social.xmflsct.com/users/tooot/statuses/105590085754428765 <- default Mastodon
|
||||
testAndAssignStatusId(last)
|
||||
@ -62,7 +62,7 @@ export const urlMatcher = (
|
||||
}
|
||||
} else if (
|
||||
nextToLast.startsWith('@') &&
|
||||
(length === 3 || (length === 4 && segments[length - 3] === 'web'))
|
||||
(length === 2 || (length === 3 && segments.at(-3) === 'web'))
|
||||
) {
|
||||
// https://social.xmflsct.com/web/@tooot/105590085754428765 <- pretty Mastodon v3.5 and below
|
||||
// https://social.xmflsct.com/@tooot/105590085754428765 <- pretty Mastodon v4.0 and above
|
||||
|
43
src/utils/linking/index.ts
Normal file
43
src/utils/linking/index.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import openLink from '@components/openLink'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { getReadableAccounts, setAccount } from '@utils/storage/actions'
|
||||
import * as Linking from 'expo-linking'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
// /compose OR /compose/@username@example.com
|
||||
|
||||
export const useLinking = () => {
|
||||
const parseLink = async (link: string | null) => {
|
||||
if (!link) return
|
||||
|
||||
const parsed = Linking.parse(link)
|
||||
|
||||
switch (parsed.scheme) {
|
||||
case 'tooot':
|
||||
if (parsed.hostname === 'compose') {
|
||||
if (parsed.path?.length) {
|
||||
const accounts = getReadableAccounts()
|
||||
const foundNotActiveAccount = accounts.find(
|
||||
account => account.acct === parsed.path && !account.active
|
||||
)
|
||||
if (foundNotActiveAccount) {
|
||||
await setAccount(foundNotActiveAccount.key)
|
||||
}
|
||||
}
|
||||
navigationRef.navigate('Screen-Compose')
|
||||
}
|
||||
break
|
||||
case 'https':
|
||||
case 'http':
|
||||
await openLink(link)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
Linking.getInitialURL().then(parseLink)
|
||||
|
||||
const listener = Linking.addEventListener('url', ({ url }) => parseLink(url))
|
||||
return () => listener.remove()
|
||||
}, [])
|
||||
}
|
@ -9,13 +9,22 @@ export type QueryKeyInstance = ['Instance'] | ['Instance', { domain?: string }]
|
||||
|
||||
const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyInstance>) => {
|
||||
const domain = queryKey[1]?.domain
|
||||
|
||||
const checkV2Format = (body: Mastodon.Instance_V2) => {
|
||||
if (body.version) {
|
||||
return body
|
||||
} else {
|
||||
throw new Error('Instance v2 format error')
|
||||
}
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
return await apiGeneral<Mastodon.Instance<'v2'>>({
|
||||
method: 'get',
|
||||
domain,
|
||||
url: 'api/v2/instance'
|
||||
})
|
||||
.then(res => res.body)
|
||||
.then(res => checkV2Format(res.body))
|
||||
.catch(
|
||||
async () =>
|
||||
await apiGeneral<Mastodon.Instance<'v1'>>({
|
||||
@ -32,7 +41,7 @@ const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyInstance
|
||||
version: 'v2',
|
||||
url: 'instance'
|
||||
})
|
||||
.then(res => res.body)
|
||||
.then(res => checkV2Format(res.body))
|
||||
.catch(
|
||||
async () =>
|
||||
await apiInstance<Mastodon.Instance<'v1'>>({
|
||||
|
40
src/utils/queryHooks/neodb.ts
Normal file
40
src/utils/queryHooks/neodb.ts
Normal file
@ -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<QueryKeyNeodb>) => {
|
||||
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<any, AxiosError>
|
||||
}
|
||||
) => {
|
||||
const queryKey: QueryKeyNeodb = ['Neodb', { path: params.path }]
|
||||
return useQuery(queryKey, queryFunction, {
|
||||
...params?.options,
|
||||
staleTime: Infinity,
|
||||
cacheTime: Infinity
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user