Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 111 lines 3.6 kB view raw
1import {useMemo} from 'react' 2import { 3 ageAssuranceRuleIDs as ids, 4 type AppBskyAgeassuranceDefs, 5 getAgeAssuranceRegionConfig, 6 type ModerationPrefs, 7} from '@atproto/api' 8 9import {getAge} from '#/lib/strings/time' 10import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' 11import {useAgeAssuranceDataContext} from '#/ageAssurance/data' 12import {AgeAssuranceAccess} from '#/ageAssurance/types' 13import {type Geolocation, useGeolocation} from '#/geolocation' 14 15export const MIN_ACCESS_AGE = 1 16const FALLBACK_REGION_CONFIG: AppBskyAgeassuranceDefs.ConfigRegion = { 17 countryCode: '*', 18 regionCode: undefined, 19 minAccessAge: MIN_ACCESS_AGE, 20 rules: [ 21 { 22 $type: ids.IfDeclaredOverAge, 23 age: MIN_ACCESS_AGE, 24 access: AgeAssuranceAccess.Full, 25 }, 26 { 27 $type: ids.Default, 28 access: AgeAssuranceAccess.None, 29 }, 30 ], 31} 32 33/** 34 * Get age assurance region config based on geolocation, with fallback to 35 * app defaults if no region config is found. 36 * 37 * See {@link getAgeAssuranceRegionConfig} for the generic option, which can 38 * return undefined if the geolocation does not match any AA region. 39 */ 40export function getAgeAssuranceRegionConfigWithFallback( 41 config: AppBskyAgeassuranceDefs.Config, 42 geolocation: Geolocation, 43): AppBskyAgeassuranceDefs.ConfigRegion { 44 const region = getAgeAssuranceRegionConfig(config, { 45 countryCode: geolocation.countryCode ?? '', 46 regionCode: geolocation.regionCode, 47 }) 48 49 return region || FALLBACK_REGION_CONFIG 50} 51 52/** 53 * Hook to get the age assurance region config based on current geolocation. 54 * Does not fall-back to our app defaults. If no config is found, returns 55 * undefined, which indicates no regional age assurance rules apply. 56 */ 57export function useAgeAssuranceRegionConfig() { 58 const geolocation = useGeolocation() 59 const {config} = useAgeAssuranceDataContext() 60 return useMemo(() => { 61 if (!config) return 62 // use generic helper, we want to potentially return undefined 63 return getAgeAssuranceRegionConfig(config, { 64 countryCode: geolocation.countryCode ?? '', 65 regionCode: geolocation.regionCode, 66 }) 67 }, [config, geolocation]) 68} 69 70/** 71 * Hook to get the age assurance region config based on current geolocation. 72 * Falls back to our app defaults if no region config is found. 73 */ 74export function useAgeAssuranceRegionConfigWithFallback() { 75 return useAgeAssuranceRegionConfig() || FALLBACK_REGION_CONFIG 76} 77 78/** 79 * Some users may have erroneously set their birth date to the current date 80 * if one wasn't set on their account. We previously didn't do validation on 81 * the bday dialog, and it defaulted to the current date. This bug _has_ been 82 * seen in production, so we need to check for it where possible. 83 */ 84export function isLegacyBirthdateBug(birthDate: string) { 85 return ['2025', '2024', '2023'].includes((birthDate || '').slice(0, 4)) 86} 87 88/** 89 * Returns whether the date (converted to an age as a whole integer) is under 90 * the provided minimum age. 91 */ 92export function isUnderAge(birthDate: string, age: number) { 93 return getAge(new Date(birthDate)) < age 94} 95 96export function getBirthdateStringFromAge(age: number) { 97 const today = new Date() 98 return new Date( 99 today.getFullYear() - age, 100 today.getMonth(), 101 today.getDate() - 1, // set to day before to ensure age is reached 102 ).toISOString() 103} 104 105export const makeAgeRestrictedModerationPrefs = ( 106 prefs: ModerationPrefs, 107): ModerationPrefs => ({ 108 ...prefs, 109 adultContentEnabled: false, 110 labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, 111})