personal fork of bluesky app bsky.kelinci.net
0

Configure Feed

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

refactor: clean up moderation authorities and labelers

+44 -153
-1
messages/en/screens.moderation.json
··· 1 1 { 2 2 "$schema": "https://inlang.com/schema/inlang-message-format", 3 3 "screens.moderation.adultContent.enable": "Enable adult content", 4 - "screens.moderation.adultContent.requiredInRegion": "Required in your region", 5 4 "screens.moderation.adultContent.title": "Content filters", 6 5 "screens.moderation.adultContent.toggleA11y": "Toggle to enable or disable adult content", 7 6 "screens.moderation.advanced": "Advanced",
+1 -3
src/components/moderation/ReportDialog/ReportDialogContent.tsx
··· 94 94 error: labelersError, 95 95 isLoading: labelersLoading, 96 96 refetch, 97 - } = useMyLabelersQuery({ 98 - excludeNonConfigurableLabelers: true, 99 - }); 97 + } = useMyLabelersQuery(); 100 98 const copy = useCopyForSubject(subject); 101 99 const { categories, getCategory } = useReportOptions(); 102 100 const [state, dispatch] = useReducer(reducer, initialState);
+3 -5
src/lib/moderation.ts
··· 10 10 } from '@atcute/bluesky-moderation'; 11 11 import type { Did, Handle } from '@atcute/lexicons'; 12 12 13 - import { getAppLabelers } from '#/lib/moderation/app-labelers'; 13 + import { BSKY_LABELER_DID } from '#/lib/moderation/const'; 14 14 import type { AppModerationCause } from '#/lib/moderation/types'; 15 15 import { sanitizeDisplayName } from '#/lib/strings/display-names'; 16 16 ··· 84 84 export function isAppLabeler( 85 85 labeler: Did | AppBskyLabelerDefs.LabelerView | AppBskyLabelerDefs.LabelerViewDetailed, 86 86 ): boolean { 87 - if (typeof labeler === 'string') { 88 - return getAppLabelers().includes(labeler); 89 - } 90 - return getAppLabelers().includes(labeler.creator.did); 87 + const did = typeof labeler === 'string' ? labeler : labeler.creator.did; 88 + return did === BSKY_LABELER_DID; 91 89 } 92 90 93 91 export function isLabelerSubscribed(
-23
src/lib/moderation/app-labelers.ts
··· 1 - import type { Did } from '@atcute/lexicons'; 2 - 3 - import { BSKY_LABELER_DID } from '#/lib/moderation/const'; 4 - 5 - /** app-level labeler configuration */ 6 - 7 - let appLabelers: readonly Did[] = [BSKY_LABELER_DID]; 8 - 9 - /** 10 - * Returns the configured app-level labeler DIDs. 11 - * 12 - * @returns the app labeler DIDs. 13 - */ 14 - export const getAppLabelers = (): readonly Did[] => appLabelers; 15 - 16 - /** 17 - * Sets the app-level labeler DIDs. 18 - * 19 - * @param dids the app labeler DIDs. 20 - */ 21 - export const configureAppLabelers = (dids: Did[]): void => { 22 - appLabelers = dids; 23 - };
+5
src/lib/moderation/const.ts
··· 1 + import type { Did } from '@atcute/lexicons'; 2 + 1 3 import type { LabelVisibility } from '#/lib/moderation/preferences-types'; 2 4 3 5 /** DID of the Bluesky-operated moderation labeler, applied as an app labeler. */ 4 6 export const BSKY_LABELER_DID = 'did:plc:ar7c4by46qjdydhdevvrndac'; 7 + 8 + /** App-level moderation labeler DIDs. */ 9 + export const APP_LABELERS: readonly Did[] = [BSKY_LABELER_DID]; 5 10 6 11 /** Default visibility for the self-applied adult/graphic labels when the user has no stored preference. */ 7 12 export const DEFAULT_LABEL_SETTINGS: Record<string, LabelVisibility> = {
-10
src/screens/Moderation/index.css.ts
··· 1 1 import { style } from '@vanilla-extract/css'; 2 2 3 - import { vars } from '#/styles/contract.css'; 4 3 import { space } from '#/styles/tokens.css'; 5 4 6 5 export const labelerAvatar = style({ ··· 18 17 flexDirection: 'column', 19 18 gap: space._2xs, 20 19 minWidth: 0, 21 - }); 22 - 23 - export const regionalNotice = style({ 24 - display: 'flex', 25 - gap: space.xs, 26 - alignItems: 'center', 27 - paddingTop: space._2xs, 28 - color: vars.palette.contrast_500, 29 - fontStyle: 'italic', 30 20 }); 31 21 32 22 export const cleanup = style({
+1 -11
src/screens/Moderation/index.tsx
··· 21 21 usePreferencesSetAdultContentMutation, 22 22 usePreferencesSetContentLabelMutation, 23 23 } from '#/state/queries/preferences'; 24 - import { isNonConfigurableModerationAuthority } from '#/state/session/additional-moderation-authorities'; 25 24 26 25 import { logger } from '#/logger'; 27 26 ··· 30 29 import { CircleCheck_Stroke2_Corner0_Rounded as CircleCheck } from '#/components/icons/CircleCheck'; 31 30 import { EditBig_Stroke2_Corner2_Rounded as EditBig } from '#/components/icons/EditBig'; 32 31 import { Filter_Stroke2_Corner0_Rounded as Filter } from '#/components/icons/Filter'; 33 - import { Flag_Stroke2_Corner0_Rounded as Flag } from '#/components/icons/Flag'; 34 32 import { Group3_Stroke2_Corner0_Rounded as Group } from '#/components/icons/Group'; 35 33 import { Person_Stroke2_Corner0_Rounded as Person } from '#/components/icons/Person'; 36 34 import * as Settings from '#/components/SettingsCards'; ··· 94 92 95 93 const subscribedDids = preferences.moderationPrefs.labelers.map((labeler) => labeler.did); 96 94 const returnedDids = new Set<string>(labelers?.map((labeler) => labeler.creator.did)); 97 - const unavailableDids = subscribedDids.filter( 98 - (did) => !returnedDids.has(did) && !isAppLabeler(did) && !isNonConfigurableModerationAuthority(did), 99 - ); 95 + const unavailableDids = subscribedDids.filter((did) => !returnedDids.has(did) && !isAppLabeler(did)); 100 96 101 97 const adultContentEnabled = 102 98 optimisticAdultContent?.enabled || ··· 278 274 <Text color="textContrastMedium" size="md_sub"> 279 275 {m['screens.moderation.labeler.byCreator']({ handle: creator.handle })} 280 276 </Text> 281 - )} 282 - {isNonConfigurableModerationAuthority(creator.did) && ( 283 - <span className={styles.regionalNotice}> 284 - <Flag fill="currentColor" size="sm" /> 285 - <Text size="sm">{m['screens.moderation.adultContent.requiredInRegion']()}</Text> 286 - </span> 287 277 )} 288 278 </div> 289 279 <span className={clsx(cardStyles.chevron, styles.labelerChevron)}>
+2 -2
src/state/preferences/moderation-opts.tsx
··· 2 2 3 3 import type { ModerationOptions } from '@atcute/bluesky-moderation'; 4 4 5 - import { getAppLabelers } from '#/lib/moderation/app-labelers'; 5 + import { APP_LABELERS } from '#/lib/moderation/const'; 6 6 import { toModerationPreferences } from '#/lib/moderation/prefs'; 7 7 8 8 import { useLabelDefinitions } from '#/state/preferences'; ··· 31 31 } 32 32 const labelers = moderationPrefs.labelers.length 33 33 ? moderationPrefs.labelers 34 - : getAppLabelers().map((did) => ({ 34 + : APP_LABELERS.map((did) => ({ 35 35 did, 36 36 labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, 37 37 }));
+3 -5
src/state/queries/preferences/index.ts
··· 1 1 import type { LabelPreference } from '@atcute/bluesky-moderation'; 2 2 import type { Did } from '@atcute/lexicons'; 3 3 4 - import { difference } from '@mary/array-fns'; 5 - 6 4 import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 7 5 8 6 import { PROD_DEFAULT_FEED } from '#/lib/constants'; 9 7 import { replaceEqualDeep } from '#/lib/functions'; 10 - import { getAppLabelers } from '#/lib/moderation/app-labelers'; 8 + import { APP_LABELERS } from '#/lib/moderation/const'; 11 9 import type { AppBskyActorDefs, BskyFeedViewPreference } from '#/lib/moderation/preferences-types'; 12 10 13 11 import { GCTIME, STALE } from '#/state/queries'; ··· 64 62 if (!pds || !currentAccount) { 65 63 return DEFAULT_LOGGED_OUT_PREFERENCES; 66 64 } else { 67 - const res = await getPreferences(pds, getAppLabelers()); 65 + const res = await getPreferences(pds, APP_LABELERS); 68 66 69 67 const labelerDids = res.moderationPrefs.labelers.map((l) => l.did); 70 68 // save to local storage to ensure there are labels on initial requests 71 69 saveLabelers(currentAccount.did, labelerDids); 72 70 // keep the appview client's labeler header in sync with the freshly fetched prefs, as 73 71 // `agent.getPreferences()` used to do internally 74 - setSubscribedLabelers(difference(labelerDids, getAppLabelers())); 72 + setSubscribedLabelers(labelerDids); 75 73 76 74 const preferences: UsePreferencesQueryResponse = { 77 75 ...res,
+4 -14
src/state/queries/preferences/moderation.ts
··· 1 1 import { interpretLabelerDefinition } from '@atcute/bluesky-moderation'; 2 2 3 - import { getAppLabelers } from '#/lib/moderation/app-labelers'; 4 - import { DEFAULT_LABEL_SETTINGS } from '#/lib/moderation/const'; 5 - 6 - import { isNonConfigurableModerationAuthority } from '#/state/session/additional-moderation-authorities'; 3 + import { APP_LABELERS, DEFAULT_LABEL_SETTINGS } from '#/lib/moderation/const'; 7 4 8 5 import { useLabelersDetailedInfoQuery } from '../labeler'; 9 6 import { usePreferencesQuery } from './index'; ··· 13 10 Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']), 14 11 ); 15 12 16 - export function useMyLabelersQuery({ 17 - excludeNonConfigurableLabelers = false, 18 - }: { 19 - excludeNonConfigurableLabelers?: boolean; 20 - } = {}) { 13 + export function useMyLabelersQuery() { 21 14 const prefs = usePreferencesQuery(); 22 - let dids = Array.from( 23 - new Set(getAppLabelers().concat(prefs.data?.moderationPrefs.labelers.map((l) => l.did) || [])), 15 + const dids = Array.from( 16 + new Set(APP_LABELERS.concat(prefs.data?.moderationPrefs.labelers.map((l) => l.did) || [])), 24 17 ); 25 - if (excludeNonConfigurableLabelers) { 26 - dids = dids.filter((did) => !isNonConfigurableModerationAuthority(did)); 27 - } 28 18 const labelers = useLabelersDetailedInfoQuery({ dids }); 29 19 const isLoading = prefs.isLoading || labelers.isLoading; 30 20 const error = prefs.error || labelers.error;
-24
src/state/session/additional-moderation-authorities.ts
··· 1 - import type { Did } from '@atcute/lexicons'; 2 - 3 - import { unique } from '@mary/array-fns'; 4 - 5 - import { configureAppLabelers, getAppLabelers } from '#/lib/moderation/app-labelers'; 6 - 7 - import { logger } from '#/logger'; 8 - 9 - export function isNonConfigurableModerationAuthority(_did: string) { 10 - return false; 11 - } 12 - 13 - export function configureAdditionalModerationAuthorities() { 14 - const additionalLabelers: Did[] = []; 15 - 16 - const appLabelers = unique([...getAppLabelers(), ...additionalLabelers]); 17 - 18 - logger.info(`applying mod authorities`, { 19 - additionalLabelers, 20 - appLabelers, 21 - }); 22 - 23 - configureAppLabelers(appLabelers); 24 - }
+1 -1
src/state/session/agent.ts
··· 3 3 import { networkRetry } from '#/lib/async/retry'; 4 4 5 5 import { type Clients, createOAuthClients, createPublicClients } from './clients'; 6 - import { configureModerationForAccount, configureModerationForGuest } from './moderation'; 6 + import { configureModerationForAccount, configureModerationForGuest } from './labelers'; 7 7 import { finalizeAuthorization, getSession, OAuthUserAgent, type Session } from './oauth'; 8 8 import type { SessionAccount } from './types'; 9 9
+24 -21
src/state/session/labelers.ts
··· 1 1 import type { Did } from '@atcute/lexicons'; 2 2 3 - import { BSKY_LABELER_DID } from '#/lib/moderation/const'; 3 + import { difference } from '@mary/array-fns'; 4 4 5 - /** 6 - * labelers the AppView and chat clients advertise via the `atproto-accept-labelers` header. kept as mutable 7 - * state so subscription changes are reflected on the next request. 8 - */ 5 + import { APP_LABELERS, BSKY_LABELER_DID } from '#/lib/moderation/const'; 9 6 10 - // App labelers are sent with `;redact`; subscribed labelers are sent plain. 11 - let appLabelers: Did[] = [BSKY_LABELER_DID]; 7 + import { readLabelers } from './agent-config'; 8 + import type { SessionAccount } from './types'; 9 + 12 10 let subscribedLabelers: Did[] = []; 13 11 14 12 /** 15 - * Sets the app-level labelers, sent with the `;redact` directive. 13 + * Sets the user's subscribed labelers. 16 14 * 17 - * @param dids the app labeler DIDs. 15 + * @param dids the subscribed labeler DIDs. 18 16 */ 19 - export function setAppLabelers(dids: Did[]): void { 20 - appLabelers = dids; 17 + export function setSubscribedLabelers(dids: Did[]): void { 18 + subscribedLabelers = difference(dids, APP_LABELERS); 19 + } 20 + 21 + /** Configures moderation labelers for a guest session. */ 22 + export function configureModerationForGuest(): void { 23 + setSubscribedLabelers([]); 21 24 } 22 25 23 26 /** 24 - * Sets the user's subscribed labelers, sent without a directive. 27 + * Configures moderation labelers for an account session. 25 28 * 26 - * @param dids the subscribed labeler DIDs. 29 + * @param account session account info. 27 30 */ 28 - export function setSubscribedLabelers(dids: Did[]): void { 29 - subscribedLabelers = dids; 31 + export function configureModerationForAccount(account: SessionAccount): void { 32 + const labelerDids = readLabelers(account.did); 33 + setSubscribedLabelers(labelerDids ?? []); 30 34 } 31 35 32 - /** 33 - * Builds the current `atproto-accept-labelers` header value from the configured labelers. 34 - * 35 - * @returns the comma-separated header value. 36 - */ 37 36 export function acceptLabelersHeaderValue(): string { 38 - return [...appLabelers.map((did) => `${did};redact`), ...subscribedLabelers].join(', '); 37 + if (subscribedLabelers.length === 0) { 38 + return `${BSKY_LABELER_DID};redact`; 39 + } 40 + 41 + return `${BSKY_LABELER_DID};redact, ${subscribedLabelers.join(', ')}`; 39 42 }
-33
src/state/session/moderation.ts
··· 1 - import { configureAppLabelers } from '#/lib/moderation/app-labelers'; 2 - import { BSKY_LABELER_DID } from '#/lib/moderation/const'; 3 - 4 - import { configureAdditionalModerationAuthorities } from './additional-moderation-authorities'; 5 - import { readLabelers } from './agent-config'; 6 - import { setAppLabelers, setSubscribedLabelers } from './labelers'; 7 - import type { SessionAccount } from './types'; 8 - 9 - export function configureModerationForGuest() { 10 - switchToBskyAppLabeler(); 11 - configureAdditionalModerationAuthorities(); 12 - } 13 - 14 - export function configureModerationForAccount(account: SessionAccount) { 15 - switchToBskyAppLabeler(); 16 - 17 - const labelerDids = readLabelers(account.did); 18 - if (labelerDids) { 19 - const subscribed = labelerDids.filter((did) => did !== BSKY_LABELER_DID); 20 - // the @atcute appview client injects this header on every request, reading it fresh from here 21 - setSubscribedLabelers(subscribed); 22 - } else { 23 - // If there are no headers in the storage, we'll not send them on the initial requests. 24 - // If we wanted to fix this, we could block on the preferences query here. 25 - } 26 - 27 - configureAdditionalModerationAuthorities(); 28 - } 29 - 30 - function switchToBskyAppLabeler() { 31 - configureAppLabelers([BSKY_LABELER_DID]); 32 - setAppLabelers([BSKY_LABELER_DID]); 33 - }