mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at static-click 173 lines 4.3 kB view raw
1import {Platform} from 'react-native' 2import {getLocales} from 'expo-localization' 3import {keepPreviousData, useInfiniteQuery} from '@tanstack/react-query' 4 5import {GIF_FEATURED, GIF_SEARCH} from '#/lib/constants' 6 7export const RQKEY_ROOT = 'gif-service' 8export const RQKEY_FEATURED = [RQKEY_ROOT, 'featured'] 9export const RQKEY_SEARCH = (query: string) => [RQKEY_ROOT, 'search', query] 10 11const getTrendingGifs = createTenorApi(GIF_FEATURED) 12 13const searchGifs = createTenorApi<{q: string}>(GIF_SEARCH) 14 15export function useFeaturedGifsQuery() { 16 return useInfiniteQuery({ 17 queryKey: RQKEY_FEATURED, 18 queryFn: ({pageParam}) => getTrendingGifs({pos: pageParam}), 19 initialPageParam: undefined as string | undefined, 20 getNextPageParam: lastPage => lastPage.next, 21 }) 22} 23 24export function useGifSearchQuery(query: string) { 25 return useInfiniteQuery({ 26 queryKey: RQKEY_SEARCH(query), 27 queryFn: ({pageParam}) => searchGifs({q: query, pos: pageParam}), 28 initialPageParam: undefined as string | undefined, 29 getNextPageParam: lastPage => lastPage.next, 30 enabled: !!query, 31 placeholderData: keepPreviousData, 32 }) 33} 34 35function createTenorApi<Input extends object>( 36 urlFn: (params: string) => string, 37): (input: Input & {pos?: string}) => Promise<{ 38 next: string 39 results: Gif[] 40}> { 41 return async input => { 42 const params = new URLSearchParams() 43 44 // set client key based on platform 45 params.set( 46 'client_key', 47 Platform.select({ 48 ios: 'bluesky-ios', 49 android: 'bluesky-android', 50 default: 'bluesky-web', 51 }), 52 ) 53 54 // 30 is divisible by 2 and 3, so both 2 and 3 column layouts can be used 55 params.set('limit', '30') 56 57 params.set('contentfilter', 'high') 58 59 params.set( 60 'media_filter', 61 (['preview', 'gif', 'tinygif'] satisfies ContentFormats[]).join(','), 62 ) 63 64 const locale = getLocales?.()?.[0] 65 66 if (locale) { 67 params.set('locale', locale.languageTag.replace('-', '_')) 68 } 69 70 for (const [key, value] of Object.entries(input)) { 71 if (value !== undefined) { 72 params.set(key, String(value)) 73 } 74 } 75 76 const res = await fetch(urlFn(params.toString()), { 77 method: 'GET', 78 headers: { 79 'Content-Type': 'application/json', 80 }, 81 }) 82 if (!res.ok) { 83 throw new Error('Failed to fetch Tenor API') 84 } 85 return res.json() 86 } 87} 88 89export type Gif = { 90 /** 91 * A Unix timestamp that represents when this post was created. 92 */ 93 created: number 94 /** 95 * Returns true if this post contains audio. 96 * Note: Only video formats support audio. The GIF image file format can't contain audio information. 97 */ 98 hasaudio: boolean 99 /** 100 * Tenor result identifier 101 */ 102 id: string 103 /** 104 * A dictionary with a content format as the key and a Media Object as the value. 105 */ 106 media_formats: Record<ContentFormats, MediaObject> 107 /** 108 * An array of tags for the post 109 */ 110 tags: string[] 111 /** 112 * The title of the post 113 */ 114 title: string 115 /** 116 * A textual description of the content. 117 * We recommend that you use content_description for user accessibility features. 118 */ 119 content_description: string 120 /** 121 * The full URL to view the post on tenor.com. 122 */ 123 itemurl: string 124 /** 125 * Returns true if this post contains captions. 126 */ 127 hascaption: boolean 128 /** 129 * Comma-separated list to signify whether the content is a sticker or static image, has audio, or is any combination of these. If sticker and static aren't present, then the content is a GIF. A blank flags field signifies a GIF without audio. 130 */ 131 flags: string 132 /** 133 * The most common background pixel color of the content 134 */ 135 bg_color?: string 136 /** 137 * A short URL to view the post on tenor.com. 138 */ 139 url: string 140} 141 142type MediaObject = { 143 /** 144 * A URL to the media source 145 */ 146 url: string 147 /** 148 * Width and height of the media in pixels 149 */ 150 dims: [number, number] 151 /** 152 * Represents the time in seconds for one loop of the content. If the content is static, the duration is set to 0. 153 */ 154 duration: number 155 /** 156 * Size of the file in bytes 157 */ 158 size: number 159} 160 161type ContentFormats = 162 | 'preview' 163 | 'gif' 164 // | 'mediumgif' 165 | 'tinygif' 166// | 'nanogif' 167// | 'mp4' 168// | 'loopedmp4' 169// | 'tinymp4' 170// | 'nanomp4' 171// | 'webm' 172// | 'tinywebm' 173// | 'nanowebm'