Bluesky app fork with some witchin' additions 馃挮
fork

Configure Feed

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

at main 286 lines 8.3 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import { 4 type AppBskyActorDefs, 5 AppBskyFeedGetAuthorFeed, 6 AtUri, 7} from '@atproto/api' 8import {msg as msgLingui, Trans} from '@lingui/macro' 9import {useLingui} from '@lingui/react' 10import {useNavigation} from '@react-navigation/native' 11 12import {usePalette} from '#/lib/hooks/usePalette' 13import {type NavigationProp} from '#/lib/routes/types' 14import {cleanError} from '#/lib/strings/errors' 15import {logger} from '#/logger' 16import {type FeedDescriptor} from '#/state/queries/post-feed' 17import {useRemoveFeedMutation} from '#/state/queries/preferences' 18import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 19import * as Prompt from '#/components/Prompt' 20import {EmptyState} from '../util/EmptyState' 21import {ErrorMessage} from '../util/error/ErrorMessage' 22import {Button} from '../util/forms/Button' 23import {Text} from '../util/text/Text' 24import * as Toast from '../util/Toast' 25 26export enum KnownError { 27 Block = 'Block', 28 FeedgenDoesNotExist = 'FeedgenDoesNotExist', 29 FeedgenMisconfigured = 'FeedgenMisconfigured', 30 FeedgenBadResponse = 'FeedgenBadResponse', 31 FeedgenOffline = 'FeedgenOffline', 32 FeedgenUnknown = 'FeedgenUnknown', 33 FeedSignedInOnly = 'FeedSignedInOnly', 34 FeedTooManyRequests = 'FeedTooManyRequests', 35 Unknown = 'Unknown', 36} 37 38export function PostFeedErrorMessage({ 39 feedDesc, 40 error, 41 onPressTryAgain, 42 savedFeedConfig, 43}: { 44 feedDesc: FeedDescriptor 45 error?: Error 46 onPressTryAgain: () => void 47 savedFeedConfig?: AppBskyActorDefs.SavedFeed 48}) { 49 const {_: _l} = useLingui() 50 const knownError = React.useMemo( 51 () => detectKnownError(feedDesc, error), 52 [feedDesc, error], 53 ) 54 55 if ( 56 typeof knownError !== 'undefined' && 57 knownError !== KnownError.Unknown && 58 feedDesc.startsWith('feedgen') 59 ) { 60 return ( 61 <FeedgenErrorMessage 62 feedDesc={feedDesc} 63 knownError={knownError} 64 rawError={error} 65 savedFeedConfig={savedFeedConfig} 66 /> 67 ) 68 } 69 70 if (knownError === KnownError.Block) { 71 return ( 72 <EmptyState 73 icon={WarningIcon} 74 iconSize="2xl" 75 message={_l(msgLingui`Posts hidden`)} 76 style={{paddingVertical: 40}} 77 /> 78 ) 79 } 80 81 return ( 82 <ErrorMessage 83 message={cleanError(error)} 84 onPressTryAgain={onPressTryAgain} 85 /> 86 ) 87} 88 89function FeedgenErrorMessage({ 90 feedDesc, 91 knownError, 92 rawError, 93 savedFeedConfig, 94}: { 95 feedDesc: FeedDescriptor 96 knownError: KnownError 97 rawError?: Error 98 savedFeedConfig?: AppBskyActorDefs.SavedFeed 99}) { 100 const pal = usePalette('default') 101 const {_: _l} = useLingui() 102 const navigation = useNavigation<NavigationProp>() 103 const msg = React.useMemo( 104 () => 105 ({ 106 [KnownError.Unknown]: '', 107 [KnownError.Block]: '', 108 [KnownError.FeedgenDoesNotExist]: _l( 109 msgLingui`Hmm, we're having trouble finding this feed. It may have been deleted.`, 110 ), 111 [KnownError.FeedgenMisconfigured]: _l( 112 msgLingui`Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue.`, 113 ), 114 [KnownError.FeedgenBadResponse]: _l( 115 msgLingui`Hmm, the feed server gave a bad response. Please let the feed owner know about this issue.`, 116 ), 117 [KnownError.FeedgenOffline]: _l( 118 msgLingui`Hmm, the feed server appears to be offline. Please let the feed owner know about this issue.`, 119 ), 120 [KnownError.FeedSignedInOnly]: _l( 121 msgLingui`This content is not viewable without a Bluesky account.`, 122 ), 123 [KnownError.FeedgenUnknown]: _l( 124 msgLingui`Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue.`, 125 ), 126 [KnownError.FeedTooManyRequests]: _l( 127 msgLingui`This feed is currently receiving high traffic and is temporarily unavailable. Please try again later.`, 128 ), 129 })[knownError], 130 [_l, knownError], 131 ) 132 const [__, uri] = feedDesc.split('|') 133 const [ownerDid] = safeParseFeedgenUri(uri) 134 const removePromptControl = Prompt.usePromptControl() 135 const {mutateAsync: removeFeed} = useRemoveFeedMutation() 136 137 const onViewProfile = React.useCallback(() => { 138 navigation.navigate('Profile', {name: ownerDid}) 139 }, [navigation, ownerDid]) 140 141 const onPressRemoveFeed = React.useCallback(() => { 142 removePromptControl.open() 143 }, [removePromptControl]) 144 145 const onRemoveFeed = React.useCallback(async () => { 146 try { 147 if (!savedFeedConfig) return 148 await removeFeed(savedFeedConfig) 149 } catch (err) { 150 Toast.show( 151 _l( 152 msgLingui`There was an issue removing this feed. Please check your internet connection and try again.`, 153 ), 154 'exclamation-circle', 155 ) 156 logger.error('Failed to remove feed', {message: err}) 157 } 158 }, [removeFeed, _l, savedFeedConfig]) 159 160 const cta = React.useMemo(() => { 161 switch (knownError) { 162 case KnownError.FeedSignedInOnly: { 163 return null 164 } 165 case KnownError.FeedgenDoesNotExist: 166 case KnownError.FeedgenMisconfigured: 167 case KnownError.FeedgenBadResponse: 168 case KnownError.FeedgenOffline: 169 case KnownError.FeedgenUnknown: { 170 return ( 171 <View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}> 172 {knownError === KnownError.FeedgenDoesNotExist && 173 savedFeedConfig && ( 174 <Button 175 type="inverted" 176 label={_l(msgLingui`Remove feed`)} 177 onPress={onRemoveFeed} 178 /> 179 )} 180 <Button 181 type="default-light" 182 label={_l(msgLingui`View profile`)} 183 onPress={onViewProfile} 184 /> 185 </View> 186 ) 187 } 188 } 189 }, [knownError, onViewProfile, onRemoveFeed, _l, savedFeedConfig]) 190 191 return ( 192 <> 193 <View 194 style={[ 195 pal.border, 196 pal.viewLight, 197 { 198 borderTopWidth: 1, 199 paddingHorizontal: 20, 200 paddingVertical: 18, 201 gap: 12, 202 }, 203 ]}> 204 <Text style={pal.text}>{msg}</Text> 205 206 {rawError?.message && ( 207 <Text style={pal.textLight}> 208 <Trans>Message from server: {rawError.message}</Trans> 209 </Text> 210 )} 211 212 {cta} 213 </View> 214 215 <Prompt.Basic 216 control={removePromptControl} 217 title={_l(msgLingui`Remove feed?`)} 218 description={_l(msgLingui`Remove this feed from your saved feeds`)} 219 onConfirm={onPressRemoveFeed} 220 confirmButtonCta={_l(msgLingui`Remove`)} 221 confirmButtonColor="negative" 222 /> 223 </> 224 ) 225} 226 227function safeParseFeedgenUri(uri: string): [string, string] { 228 try { 229 const urip = new AtUri(uri) 230 return [urip.hostname, urip.rkey] 231 } catch { 232 return ['', ''] 233 } 234} 235 236function detectKnownError( 237 feedDesc: FeedDescriptor, 238 error: any, 239): KnownError | undefined { 240 if (!error) { 241 return undefined 242 } 243 if ( 244 error instanceof AppBskyFeedGetAuthorFeed.BlockedActorError || 245 error instanceof AppBskyFeedGetAuthorFeed.BlockedByActorError 246 ) { 247 return KnownError.Block 248 } 249 250 // check status codes 251 if (error?.status === 429) { 252 return KnownError.FeedTooManyRequests 253 } 254 255 // convert error to string and continue 256 if (typeof error !== 'string') { 257 error = error.toString() 258 } 259 if (error.includes(KnownError.FeedSignedInOnly)) { 260 return KnownError.FeedSignedInOnly 261 } 262 if (!feedDesc.startsWith('feedgen')) { 263 return KnownError.Unknown 264 } 265 if (error.includes('could not find feed')) { 266 return KnownError.FeedgenDoesNotExist 267 } 268 if (error.includes('feed unavailable')) { 269 return KnownError.FeedgenOffline 270 } 271 if (error.includes('invalid did document')) { 272 return KnownError.FeedgenMisconfigured 273 } 274 if (error.includes('could not resolve did document')) { 275 return KnownError.FeedgenMisconfigured 276 } 277 if ( 278 error.includes('invalid feed generator service details in did document') 279 ) { 280 return KnownError.FeedgenMisconfigured 281 } 282 if (error.includes('invalid response')) { 283 return KnownError.FeedgenBadResponse 284 } 285 return KnownError.FeedgenUnknown 286}