From d5d3076ddacf2ee9495cff63811b547f4bfebd28 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Thu, 26 Feb 2026 13:22:34 -0500 Subject: [PATCH] wip --- src/components/BotAccountInfoDialog.tsx | 56 ++++++ src/components/BotBadge.tsx | 26 +++ src/components/icons/Robot.tsx | 9 + src/lib/bots.ts | 9 + .../Settings/AutomationLabelSettings.tsx | 183 ++++++++++++++++++ 5 files changed, 283 insertions(+) create mode 100644 src/components/BotAccountInfoDialog.tsx create mode 100644 src/components/BotBadge.tsx create mode 100644 src/components/icons/Robot.tsx create mode 100644 src/lib/bots.ts create mode 100644 src/screens/Settings/AutomationLabelSettings.tsx diff --git a/src/components/BotAccountInfoDialog.tsx b/src/components/BotAccountInfoDialog.tsx new file mode 100644 index 0000000000..5114fd3078 --- /dev/null +++ b/src/components/BotAccountInfoDialog.tsx @@ -0,0 +1,56 @@ +import {View} from 'react-native' +import {msg} from '@lingui/core/macro' +import {useLingui} from '@lingui/react' +import {Trans} from '@lingui/react/macro' + +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {Robot_Stroke2_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot' +import {Text} from '#/components/Typography' + +export function BotAccountInfoDialog({ + control, +}: { + control: Dialog.DialogControlProps +}) { + return ( + + + + + ) +} + +function BotAccountInfoDialogInner({ + control, +}: { + control: Dialog.DialogControlProps +}) { + const {_} = useLingui() + const t = useTheme() + + return ( + + + + + + This account has been marked as automated by its owner. + + + + + + + ) +} diff --git a/src/components/BotBadge.tsx b/src/components/BotBadge.tsx new file mode 100644 index 0000000000..a0421015ba --- /dev/null +++ b/src/components/BotBadge.tsx @@ -0,0 +1,26 @@ +import {View} from 'react-native' +import {type ComAtprotoLabelDefs} from '@atproto/api' + +import {isBotAccount} from '#/lib/bots' +import {atoms as a, useTheme} from '#/alf' +import {Robot_Stroke2_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot' + +export function BotBadge({ + profile, + size = 12, +}: { + profile: {did: string; labels?: ComAtprotoLabelDefs.Label[]} + size?: number +}) { + const t = useTheme() + + if (!isBotAccount(profile)) { + return null + } + + return ( + + + + ) +} diff --git a/src/components/icons/Robot.tsx b/src/components/icons/Robot.tsx new file mode 100644 index 0000000000..6bd23acb57 --- /dev/null +++ b/src/components/icons/Robot.tsx @@ -0,0 +1,9 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Robot_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M9 2a1 1 0 0 1 1 1v1.07A7.002 7.002 0 0 1 16.93 11H18a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-.174A7.004 7.004 0 0 1 12 21a7.004 7.004 0 0 1-5.826-4H6a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1.07A7.002 7.002 0 0 1 14 4.07V3a1 1 0 0 1 1-1h-6Zm3 4a5 5 0 1 0 0 10 5 5 0 0 0 0-10Zm-2.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm5 0a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z', +}) + +export const Robot_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M12 0C13.1046 0 14 0.89543 14 2C14 2.73976 13.5971 3.3835 13 3.72949V5H17.2002C18.8802 5 19.7206 5.00018 20.3623 5.32715C20.9265 5.61472 21.3853 6.07347 21.6729 6.6377C21.9998 7.27941 22 8.11978 22 9.7998V10.0498C23.1411 10.2814 24 11.2905 24 12.5C24 13.7094 23.141 14.7175 22 14.9492V15C22 17.8 21.9999 19.2 21.4551 20.2695C20.9757 21.2103 20.2103 21.9757 19.2695 22.4551C18.2 22.9999 16.8 23 14 23H10C7.20005 23 5.79998 22.9999 4.73047 22.4551C3.78966 21.9757 3.02429 21.2103 2.54492 20.2695C2.00013 19.2 2 17.8 2 15V14.9492C0.858955 14.7175 0 13.7094 0 12.5C0 11.2905 0.85886 10.2814 2 10.0498V9.7998C2 8.11978 2.00018 7.27941 2.32715 6.6377C2.61472 6.07347 3.07347 5.61472 3.6377 5.32715C4.27941 5.00018 5.11978 5 6.7998 5H11V3.72949C10.4029 3.3835 10 2.73976 10 2C10 0.89543 10.8954 0 12 0ZM8 10C6.89543 10 6 10.8954 6 12V14C6 15.1046 6.89543 16 8 16C9.10457 16 10 15.1046 10 14V12C10 10.8954 9.10457 10 8 10ZM16 10C14.8954 10 14 10.8954 14 12V14C14 15.1046 14.8954 16 16 16C17.1046 16 18 15.1046 18 14V12C18 10.8954 17.1046 10 16 10Z', +}) diff --git a/src/lib/bots.ts b/src/lib/bots.ts new file mode 100644 index 0000000000..7e96659510 --- /dev/null +++ b/src/lib/bots.ts @@ -0,0 +1,9 @@ +import {type ComAtprotoLabelDefs} from '@atproto/api' + +export function isBotAccount( + profile: {did: string; labels?: ComAtprotoLabelDefs.Label[]}, +): boolean { + return ( + profile.labels?.some(l => l.val === 'bot' && l.src === profile.did) ?? false + ) +} diff --git a/src/screens/Settings/AutomationLabelSettings.tsx b/src/screens/Settings/AutomationLabelSettings.tsx new file mode 100644 index 0000000000..7da10e48bf --- /dev/null +++ b/src/screens/Settings/AutomationLabelSettings.tsx @@ -0,0 +1,183 @@ +import React from 'react' +import {View} from 'react-native' +import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api' +import {msg} from '@lingui/core/macro' +import {useLingui} from '@lingui/react' +import {Trans} from '@lingui/react/macro' +import {type NativeStackScreenProps} from '@react-navigation/native-stack' + +import {isBotAccount} from '#/lib/bots' +import {type CommonNavigatorParams} from '#/lib/routes/types' +import { + useProfileQuery, + useProfileUpdateMutation, +} from '#/state/queries/profile' +import {useSession} from '#/state/session' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {atoms as a, useTheme} from '#/alf' +import {BotBadge} from '#/components/BotBadge' +import * as Toggle from '#/components/forms/Toggle' +import {Robot_Stroke2_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot' +import * as Layout from '#/components/Layout' +import {Text} from '#/components/Typography' +import {VerificationCheck} from '#/components/verification/VerificationCheck' +import * as bsky from '#/types/bsky' +import {getVerificationState} from '../../../bskyembed/src/util/verification-state' + +type Props = NativeStackScreenProps< + CommonNavigatorParams, + 'AutomationLabelSettings' +> +export function AutomationLabelSettingsScreen({}: Props) { + const t = useTheme() + const {_} = useLingui() + const {currentAccount} = useSession() + const {data: profile} = useProfileQuery({did: currentAccount?.did}) + const updateProfile = useProfileUpdateMutation() + const verification = getVerificationState({profile: profile}) + + const isBotLabeled = + profile?.labels?.some(l => l.val === 'bot' && l.src === profile.did) ?? + false + const canToggle = profile && !updateProfile.isPending + + const onToggle = React.useCallback(() => { + if (!profile) { + return + } + let wasAdded = false + updateProfile.mutate({ + profile, + updates: existing => { + const labels: $Typed = bsky.validate( + existing.labels, + ComAtprotoLabelDefs.validateSelfLabels, + ) + ? existing.labels + : { + $type: 'com.atproto.label.defs#selfLabels', + values: [], + } + + const hasLabel = labels.values.some(l => l.val === 'bot') + if (hasLabel) { + wasAdded = false + labels.values = labels.values.filter(l => l.val !== 'bot') + } else { + wasAdded = true + labels.values.push({val: 'bot'}) + } + + if (labels.values.length === 0) { + delete existing.labels + } else { + existing.labels = labels + } + + return existing + }, + checkCommitted: res => { + const exists = !!res.data.labels?.some(l => l.val === 'bot') + return exists === wasAdded + }, + }) + }, [updateProfile, profile]) + + return ( + + + + + + Automation Label + + + + + + + {profile && ( + + + + + + + {profile.displayName || profile.handle} + + {verification.isVerified && ( + + )} + {isBotAccount(profile) && ( + + )} + + {/* */} + + + @{profile.handle} + + + + )} + + + Add automation label to account + + + + This label lets the world know that this account is automated. + If turned on, this label appears next to the account's name on + their profile and posts. It can be turned on or off at any time. + + + + + + + + + Show automation label + + + + + + + ) +}