deer social fork for personal usage. but you might see a use idk. github mirror

change defaults

Signed-off-by: ayla <ayla-git.barcode041@silomails.com>

aylac.top a02ab233 c8d9d234

verified
+258 -258
+258 -258
src/state/persisted/schema.ts
··· 1 - import {z} from 'zod' 1 + import { z } from "zod"; 2 2 3 - import {deviceLanguageCodes, deviceLocales} from '#/locale/deviceLocales' 4 - import {findSupportedAppLanguage} from '#/locale/helpers' 5 - import {logger} from '#/logger' 6 - import {PlatformInfo} from '../../../modules/expo-bluesky-swiss-army' 3 + import { deviceLanguageCodes, deviceLocales } from "#/locale/deviceLocales"; 4 + import { findSupportedAppLanguage } from "#/locale/helpers"; 5 + import { logger } from "#/logger"; 6 + import { PlatformInfo } from "../../../modules/expo-bluesky-swiss-army"; 7 7 8 - const externalEmbedOptions = ['show', 'hide'] as const 8 + const externalEmbedOptions = ["show", "hide"] as const; 9 9 10 10 /** 11 11 * A account persisted to storage. Stored in the `accounts[]` array. Contains 12 12 * base account info and access tokens. 13 13 */ 14 14 const accountSchema = z.object({ 15 - service: z.string(), 16 - did: z.string(), 17 - handle: z.string(), 18 - email: z.string().optional(), 19 - emailConfirmed: z.boolean().optional(), 20 - emailAuthFactor: z.boolean().optional(), 21 - refreshJwt: z.string().optional(), // optional because it can expire 22 - accessJwt: z.string().optional(), // optional because it can expire 23 - signupQueued: z.boolean().optional(), 24 - active: z.boolean().optional(), // optional for backwards compat 25 - /** 26 - * Known values: takendown, suspended, deactivated 27 - * @see https://github.com/bluesky-social/atproto/blob/5441fbde9ed3b22463e91481ec80cb095643e141/lexicons/com/atproto/server/getSession.json 28 - */ 29 - status: z.string().optional(), 30 - pdsUrl: z.string().optional(), 31 - isSelfHosted: z.boolean().optional(), 32 - }) 33 - export type PersistedAccount = z.infer<typeof accountSchema> 15 + service: z.string(), 16 + did: z.string(), 17 + handle: z.string(), 18 + email: z.string().optional(), 19 + emailConfirmed: z.boolean().optional(), 20 + emailAuthFactor: z.boolean().optional(), 21 + refreshJwt: z.string().optional(), // optional because it can expire 22 + accessJwt: z.string().optional(), // optional because it can expire 23 + signupQueued: z.boolean().optional(), 24 + active: z.boolean().optional(), // optional for backwards compat 25 + /** 26 + * Known values: takendown, suspended, deactivated 27 + * @see https://github.com/bluesky-social/atproto/blob/5441fbde9ed3b22463e91481ec80cb095643e141/lexicons/com/atproto/server/getSession.json 28 + */ 29 + status: z.string().optional(), 30 + pdsUrl: z.string().optional(), 31 + isSelfHosted: z.boolean().optional(), 32 + }); 33 + export type PersistedAccount = z.infer<typeof accountSchema>; 34 34 35 35 /** 36 36 * The current account. Stored in the `currentAccount` field. ··· 41 41 * here for backwards compat. 42 42 */ 43 43 const currentAccountSchema = accountSchema.extend({ 44 - service: z.string().optional(), 45 - handle: z.string().optional(), 46 - }) 47 - export type PersistedCurrentAccount = z.infer<typeof currentAccountSchema> 44 + service: z.string().optional(), 45 + handle: z.string().optional(), 46 + }); 47 + export type PersistedCurrentAccount = z.infer<typeof currentAccountSchema>; 48 48 49 49 const schema = z.object({ 50 - colorMode: z.enum(['system', 'light', 'dark']), 51 - darkTheme: z.enum(['dim', 'dark']).optional(), 52 - session: z.object({ 53 - accounts: z.array(accountSchema), 54 - currentAccount: currentAccountSchema.optional(), 55 - }), 56 - reminders: z.object({ 57 - lastEmailConfirm: z.string().optional(), 58 - }), 59 - languagePrefs: z.object({ 60 - /** 61 - * The target language for translating posts. 62 - * 63 - * BCP-47 2-letter language code without region. 64 - */ 65 - primaryLanguage: z.string(), 66 - /** 67 - * The languages the user can read, passed to feeds. 68 - * 69 - * BCP-47 2-letter language codes without region. 70 - */ 71 - contentLanguages: z.array(z.string()), 72 - /** 73 - * The language(s) the user is currently posting in, configured within the 74 - * composer. Multiple languages are separated by commas. 75 - * 76 - * BCP-47 2-letter language code without region. 77 - */ 78 - postLanguage: z.string(), 79 - /** 80 - * The user's post language history, used to pre-populate the post language 81 - * selector in the composer. Within each value, multiple languages are separated 82 - * by commas. 83 - * 84 - * BCP-47 2-letter language codes without region. 85 - */ 86 - postLanguageHistory: z.array(z.string()), 87 - /** 88 - * The language for UI translations in the app. 89 - * 90 - * BCP-47 2-letter language code with or without region, 91 - * to match with {@link AppLanguage}. 92 - */ 93 - appLanguage: z.string(), 94 - }), 95 - requireAltTextEnabled: z.boolean(), // should move to server 96 - largeAltBadgeEnabled: z.boolean().optional(), 97 - externalEmbeds: z 98 - .object({ 99 - giphy: z.enum(externalEmbedOptions).optional(), 100 - tenor: z.enum(externalEmbedOptions).optional(), 101 - youtube: z.enum(externalEmbedOptions).optional(), 102 - youtubeShorts: z.enum(externalEmbedOptions).optional(), 103 - twitch: z.enum(externalEmbedOptions).optional(), 104 - vimeo: z.enum(externalEmbedOptions).optional(), 105 - spotify: z.enum(externalEmbedOptions).optional(), 106 - appleMusic: z.enum(externalEmbedOptions).optional(), 107 - soundcloud: z.enum(externalEmbedOptions).optional(), 108 - flickr: z.enum(externalEmbedOptions).optional(), 109 - }) 110 - .optional(), 111 - invites: z.object({ 112 - copiedInvites: z.array(z.string()), 113 - }), 114 - onboarding: z.object({ 115 - step: z.string(), 116 - }), 117 - hiddenPosts: z.array(z.string()).optional(), // should move to server 118 - useInAppBrowser: z.boolean().optional(), 119 - lastSelectedHomeFeed: z.string().optional(), 120 - pdsAddressHistory: z.array(z.string()).optional(), 121 - disableHaptics: z.boolean().optional(), 122 - disableAutoplay: z.boolean().optional(), 123 - kawaii: z.boolean().optional(), 124 - hasCheckedForStarterPack: z.boolean().optional(), 125 - subtitlesEnabled: z.boolean().optional(), 50 + colorMode: z.enum(["system", "light", "dark"]), 51 + darkTheme: z.enum(["dim", "dark"]).optional(), 52 + session: z.object({ 53 + accounts: z.array(accountSchema), 54 + currentAccount: currentAccountSchema.optional(), 55 + }), 56 + reminders: z.object({ 57 + lastEmailConfirm: z.string().optional(), 58 + }), 59 + languagePrefs: z.object({ 60 + /** 61 + * The target language for translating posts. 62 + * 63 + * BCP-47 2-letter language code without region. 64 + */ 65 + primaryLanguage: z.string(), 66 + /** 67 + * The languages the user can read, passed to feeds. 68 + * 69 + * BCP-47 2-letter language codes without region. 70 + */ 71 + contentLanguages: z.array(z.string()), 72 + /** 73 + * The language(s) the user is currently posting in, configured within the 74 + * composer. Multiple languages are separated by commas. 75 + * 76 + * BCP-47 2-letter language code without region. 77 + */ 78 + postLanguage: z.string(), 79 + /** 80 + * The user's post language history, used to pre-populate the post language 81 + * selector in the composer. Within each value, multiple languages are separated 82 + * by commas. 83 + * 84 + * BCP-47 2-letter language codes without region. 85 + */ 86 + postLanguageHistory: z.array(z.string()), 87 + /** 88 + * The language for UI translations in the app. 89 + * 90 + * BCP-47 2-letter language code with or without region, 91 + * to match with {@link AppLanguage}. 92 + */ 93 + appLanguage: z.string(), 94 + }), 95 + requireAltTextEnabled: z.boolean(), // should move to server 96 + largeAltBadgeEnabled: z.boolean().optional(), 97 + externalEmbeds: z 98 + .object({ 99 + giphy: z.enum(externalEmbedOptions).optional(), 100 + tenor: z.enum(externalEmbedOptions).optional(), 101 + youtube: z.enum(externalEmbedOptions).optional(), 102 + youtubeShorts: z.enum(externalEmbedOptions).optional(), 103 + twitch: z.enum(externalEmbedOptions).optional(), 104 + vimeo: z.enum(externalEmbedOptions).optional(), 105 + spotify: z.enum(externalEmbedOptions).optional(), 106 + appleMusic: z.enum(externalEmbedOptions).optional(), 107 + soundcloud: z.enum(externalEmbedOptions).optional(), 108 + flickr: z.enum(externalEmbedOptions).optional(), 109 + }) 110 + .optional(), 111 + invites: z.object({ 112 + copiedInvites: z.array(z.string()), 113 + }), 114 + onboarding: z.object({ 115 + step: z.string(), 116 + }), 117 + hiddenPosts: z.array(z.string()).optional(), // should move to server 118 + useInAppBrowser: z.boolean().optional(), 119 + lastSelectedHomeFeed: z.string().optional(), 120 + pdsAddressHistory: z.array(z.string()).optional(), 121 + disableHaptics: z.boolean().optional(), 122 + disableAutoplay: z.boolean().optional(), 123 + kawaii: z.boolean().optional(), 124 + hasCheckedForStarterPack: z.boolean().optional(), 125 + subtitlesEnabled: z.boolean().optional(), 126 126 127 - // deer 128 - goLinksEnabled: z.boolean().optional(), 129 - constellationEnabled: z.boolean().optional(), 130 - directFetchRecords: z.boolean().optional(), 131 - noAppLabelers: z.boolean().optional(), 132 - noDiscoverFallback: z.boolean().optional(), 133 - repostCarouselEnabled: z.boolean().optional(), 134 - constellationInstance: z.string().optional(), 135 - showLinkInHandle: z.boolean().optional(), 136 - hideFeedsPromoTab: z.boolean().optional(), 137 - disableViaRepostNotification: z.boolean().optional(), 138 - disableLikesMetrics: z.boolean().optional(), 139 - disableRepostsMetrics: z.boolean().optional(), 140 - disableQuotesMetrics: z.boolean().optional(), 141 - disableSavesMetrics: z.boolean().optional(), 142 - disableReplyMetrics: z.boolean().optional(), 143 - hideSimilarAccountsRecomm: z.boolean().optional(), 144 - hideStarterPackStuff: z.boolean().optional(), 145 - deerVerification: z 146 - .object({ 147 - enabled: z.boolean(), 148 - trusted: z.array(z.string()), 149 - }) 150 - .optional(), 151 - fullsizeFormat: z.string(), 152 - thumbnailFormat: z.string(), 153 - loadAsPngs: z.boolean(), 127 + // deer 128 + goLinksEnabled: z.boolean().optional(), 129 + constellationEnabled: z.boolean().optional(), 130 + directFetchRecords: z.boolean().optional(), 131 + noAppLabelers: z.boolean().optional(), 132 + noDiscoverFallback: z.boolean().optional(), 133 + repostCarouselEnabled: z.boolean().optional(), 134 + constellationInstance: z.string().optional(), 135 + showLinkInHandle: z.boolean().optional(), 136 + hideFeedsPromoTab: z.boolean().optional(), 137 + disableViaRepostNotification: z.boolean().optional(), 138 + disableLikesMetrics: z.boolean().optional(), 139 + disableRepostsMetrics: z.boolean().optional(), 140 + disableQuotesMetrics: z.boolean().optional(), 141 + disableSavesMetrics: z.boolean().optional(), 142 + disableReplyMetrics: z.boolean().optional(), 143 + hideSimilarAccountsRecomm: z.boolean().optional(), 144 + hideStarterPackStuff: z.boolean().optional(), 145 + deerVerification: z 146 + .object({ 147 + enabled: z.boolean(), 148 + trusted: z.array(z.string()), 149 + }) 150 + .optional(), 151 + fullsizeFormat: z.string(), 152 + thumbnailFormat: z.string(), 153 + loadAsPngs: z.boolean(), 154 154 155 - /** @deprecated */ 156 - mutedThreads: z.array(z.string()), 157 - trendingDisabled: z.boolean().optional(), 158 - trendingVideoDisabled: z.boolean().optional(), 159 - }) 160 - export type Schema = z.infer<typeof schema> 155 + /** @deprecated */ 156 + mutedThreads: z.array(z.string()), 157 + trendingDisabled: z.boolean().optional(), 158 + trendingVideoDisabled: z.boolean().optional(), 159 + }); 160 + export type Schema = z.infer<typeof schema>; 161 161 162 162 export const defaults: Schema = { 163 - colorMode: 'system', 164 - darkTheme: 'dim', 165 - session: { 166 - accounts: [], 167 - currentAccount: undefined, 168 - }, 169 - reminders: { 170 - lastEmailConfirm: undefined, 171 - }, 172 - languagePrefs: { 173 - primaryLanguage: deviceLanguageCodes[0] || 'en', 174 - contentLanguages: [], 175 - postLanguage: deviceLanguageCodes[0] || 'en', 176 - postLanguageHistory: (deviceLanguageCodes || []) 177 - .concat(['en', 'ja', 'pt', 'de']) 178 - .slice(0, 6), 179 - // try full language tag first, then fallback to language code 180 - appLanguage: findSupportedAppLanguage([ 181 - deviceLocales.at(0)?.languageTag, 182 - deviceLanguageCodes[0], 183 - ]), 184 - }, 185 - requireAltTextEnabled: false, 186 - largeAltBadgeEnabled: false, 187 - externalEmbeds: {}, 188 - mutedThreads: [], 189 - invites: { 190 - copiedInvites: [], 191 - }, 192 - onboarding: { 193 - step: 'Home', 194 - }, 195 - hiddenPosts: [], 196 - useInAppBrowser: undefined, 197 - lastSelectedHomeFeed: undefined, 198 - pdsAddressHistory: [], 199 - disableHaptics: false, 200 - disableAutoplay: PlatformInfo.getIsReducedMotionEnabled(), 201 - kawaii: false, 202 - hasCheckedForStarterPack: true, 203 - subtitlesEnabled: true, 204 - trendingDisabled: true, 205 - trendingVideoDisabled: true, 163 + colorMode: "system", 164 + darkTheme: "dim", 165 + session: { 166 + accounts: [], 167 + currentAccount: undefined, 168 + }, 169 + reminders: { 170 + lastEmailConfirm: undefined, 171 + }, 172 + languagePrefs: { 173 + primaryLanguage: deviceLanguageCodes[0] || "en", 174 + contentLanguages: [], 175 + postLanguage: deviceLanguageCodes[0] || "en", 176 + postLanguageHistory: (deviceLanguageCodes || []) 177 + .concat(["en", "ja", "pt", "de"]) 178 + .slice(0, 6), 179 + // try full language tag first, then fallback to language code 180 + appLanguage: findSupportedAppLanguage([ 181 + deviceLocales.at(0)?.languageTag, 182 + deviceLanguageCodes[0], 183 + ]), 184 + }, 185 + requireAltTextEnabled: false, 186 + largeAltBadgeEnabled: false, 187 + externalEmbeds: {}, 188 + mutedThreads: [], 189 + invites: { 190 + copiedInvites: [], 191 + }, 192 + onboarding: { 193 + step: "Home", 194 + }, 195 + hiddenPosts: [], 196 + useInAppBrowser: undefined, 197 + lastSelectedHomeFeed: undefined, 198 + pdsAddressHistory: [], 199 + disableHaptics: false, 200 + disableAutoplay: PlatformInfo.getIsReducedMotionEnabled(), 201 + kawaii: false, 202 + hasCheckedForStarterPack: true, 203 + subtitlesEnabled: true, 204 + trendingDisabled: true, 205 + trendingVideoDisabled: true, 206 206 207 - // deer 208 - goLinksEnabled: false, 209 - constellationEnabled: true, 210 - directFetchRecords: true, 211 - noAppLabelers: true, 212 - noDiscoverFallback: false, 213 - repostCarouselEnabled: false, 214 - constellationInstance: 'https://constellation.microcosm.blue/', 215 - showLinkInHandle: true, 216 - hideFeedsPromoTab: false, 217 - disableViaRepostNotification: false, 218 - disableLikesMetrics: false, 219 - disableRepostsMetrics: false, 220 - disableQuotesMetrics: false, 221 - disableSavesMetrics: false, 222 - disableReplyMetrics: false, 223 - hideSimilarAccountsRecomm: false, 224 - hideStarterPackStuff: true, 225 - deerVerification: { 226 - enabled: false, 227 - // https://deer.aylac.top/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep/post/3lndyqyyr4k2k 228 - // using https://bverified.vercel.app/ as a source 229 - trusted: [ 230 - 'did:plc:z72i7hdynmk6r22z27h6tvur', 231 - 'did:plc:b2kutgxqlltwc6lhs724cfwr', 232 - 'did:plc:inz4fkbbp7ms3ixufw6xuvdi', 233 - 'did:plc:eclio37ymobqex2ncko63h4r', 234 - 'did:plc:dzezcmpb3fhcpns4n4xm4ur5', 235 - 'did:plc:5u54z2qgkq43dh2nzwzdbbhb', 236 - 'did:plc:wmho6q2uiyktkam3jsvrms3s', 237 - 'did:plc:sqbswn3lalcc2dlh2k7zdpuw', 238 - 'did:plc:k5nskatzhyxersjilvtnz4lh', 239 - 'did:plc:d2jith367s6ybc3ldsusgdae', 240 - 'did:plc:y3xrmnwvkvsq4tqcsgwch4na', 241 - 'did:plc:i3fhjvvkbmirhyu4aeihhrnv', 242 - 'did:plc:fivojrvylkim4nuo3pfqcf3k', 243 - 'did:plc:ofbkqcjzvm6gtwuufsubnkaf', 244 - ], 245 - }, 246 - thumbnailFormat: 'jpeg', 247 - fullsizeFormat: 'jpeg', 248 - loadAsPngs: true, 249 - } 207 + // deer 208 + goLinksEnabled: false, 209 + constellationEnabled: true, 210 + directFetchRecords: true, 211 + noAppLabelers: true, 212 + noDiscoverFallback: false, 213 + repostCarouselEnabled: false, 214 + constellationInstance: "https://constellation.microcosm.blue/", 215 + showLinkInHandle: true, 216 + hideFeedsPromoTab: false, 217 + disableViaRepostNotification: false, 218 + disableLikesMetrics: false, 219 + disableRepostsMetrics: false, 220 + disableQuotesMetrics: false, 221 + disableSavesMetrics: false, 222 + disableReplyMetrics: false, 223 + hideSimilarAccountsRecomm: false, 224 + hideStarterPackStuff: true, 225 + deerVerification: { 226 + enabled: false, 227 + // https://deer.aylac.top/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep/post/3lndyqyyr4k2k 228 + // using https://bverified.vercel.app/ as a source 229 + trusted: [ 230 + "did:plc:z72i7hdynmk6r22z27h6tvur", 231 + "did:plc:b2kutgxqlltwc6lhs724cfwr", 232 + "did:plc:inz4fkbbp7ms3ixufw6xuvdi", 233 + "did:plc:eclio37ymobqex2ncko63h4r", 234 + "did:plc:dzezcmpb3fhcpns4n4xm4ur5", 235 + "did:plc:5u54z2qgkq43dh2nzwzdbbhb", 236 + "did:plc:wmho6q2uiyktkam3jsvrms3s", 237 + "did:plc:sqbswn3lalcc2dlh2k7zdpuw", 238 + "did:plc:k5nskatzhyxersjilvtnz4lh", 239 + "did:plc:d2jith367s6ybc3ldsusgdae", 240 + "did:plc:y3xrmnwvkvsq4tqcsgwch4na", 241 + "did:plc:i3fhjvvkbmirhyu4aeihhrnv", 242 + "did:plc:fivojrvylkim4nuo3pfqcf3k", 243 + "did:plc:ofbkqcjzvm6gtwuufsubnkaf", 244 + ], 245 + }, 246 + thumbnailFormat: "webp", 247 + fullsizeFormat: "webp", 248 + loadAsPngs: true, 249 + }; 250 250 251 251 export function tryParse(rawData: string): Schema | undefined { 252 - let objData 253 - try { 254 - objData = JSON.parse(rawData) 255 - } catch (e) { 256 - logger.error('persisted state: failed to parse root state from storage', { 257 - message: e, 258 - }) 259 - } 260 - if (!objData) { 261 - return undefined 262 - } 263 - const parsed = schema.safeParse(objData) 264 - if (parsed.success) { 265 - return objData 266 - } else { 267 - const errors = 268 - parsed.error?.errors?.map(e => ({ 269 - code: e.code, 270 - // @ts-ignore exists on some types 271 - expected: e?.expected, 272 - path: e.path?.join('.'), 273 - })) || [] 274 - logger.error(`persisted store: data failed validation on read`, {errors}) 275 - return undefined 276 - } 252 + let objData; 253 + try { 254 + objData = JSON.parse(rawData); 255 + } catch (e) { 256 + logger.error("persisted state: failed to parse root state from storage", { 257 + message: e, 258 + }); 259 + } 260 + if (!objData) { 261 + return undefined; 262 + } 263 + const parsed = schema.safeParse(objData); 264 + if (parsed.success) { 265 + return objData; 266 + } else { 267 + const errors = 268 + parsed.error?.errors?.map((e) => ({ 269 + code: e.code, 270 + // @ts-ignore exists on some types 271 + expected: e?.expected, 272 + path: e.path?.join("."), 273 + })) || []; 274 + logger.error(`persisted store: data failed validation on read`, { errors }); 275 + return undefined; 276 + } 277 277 } 278 278 279 279 export function tryStringify(value: Schema): string | undefined { 280 - try { 281 - schema.parse(value) 282 - return JSON.stringify(value) 283 - } catch (e) { 284 - logger.error(`persisted state: failed stringifying root state`, { 285 - message: e, 286 - }) 287 - return undefined 288 - } 280 + try { 281 + schema.parse(value); 282 + return JSON.stringify(value); 283 + } catch (e) { 284 + logger.error(`persisted state: failed stringifying root state`, { 285 + message: e, 286 + }); 287 + return undefined; 288 + } 289 289 }