ai/at
1
0

add social-app-custom

This commit is contained in:
2025-12-03 12:38:37 +09:00
parent 647dd5978c
commit c3e99d26ae
15 changed files with 739 additions and 55 deletions

View File

@@ -1,6 +1,7 @@
# at # at
https://github.com/bluesky-social/atproto - https://github.com/bluesky-social/atproto
- https://github.com/bluesky-social/atproto/discussions/2026
|word|name|example| |word|name|example|
|---|---|---| |---|---|---|
@@ -14,16 +15,13 @@ https://github.com/bluesky-social/atproto
## account ## account
[@ai.syu.is](https://web.syu.is/profile/ai.syu.is) - [ai@syu.is](https://syu.is/profile/did:plc:6qyecktefllvenje24fcxnie)
- [ai@bsky.app](https://bsky.app/profile/did:plc:6qyecktefllvenje24fcxnie)
- https://plc.syu.is/did:plc:6qyecktefllvenje24fcxnie - https://plc.syu.is/did:plc:6qyecktefllvenje24fcxnie
- https://plc.directory/did:plc:ytvoptig4ddshmwdsjmhtcym - https://plc.directory/did:plc:6qyecktefllvenje24fcxnie
[@yui.syui.ai](https://bsky.app/profile/did:plc:4hqjfn7m6n5hno3doamuhgef)
```sh ```sh
$ curl -sL syu.is/xrpc/_health $ curl -sL syu.is/xrpc/_health
{"version":"0.4.65"}
# latest # latest
# https://github.com/bluesky-social/atproto/blob/main/packages/pds/package.json # https://github.com/bluesky-social/atproto/blob/main/packages/pds/package.json
@@ -31,10 +29,11 @@ $ curl -sL https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/m
``` ```
```sh ```sh
$ curl -sL "syu.is/xrpc/com.atproto.repo.describeRepo?repo=ai.syu.is" |jq -r .did $ handle=ai.syui.ai
$ curl -sL "syu.is/xrpc/com.atproto.repo.describeRepo?repo=${handle}" |jq -r .did
did:plc:6qyecktefllvenje24fcxnie did:plc:6qyecktefllvenje24fcxnie
$ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=ai.syu.is&collection=app.bsky.feed.post&reverse=true&limit=1" $ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=app.bsky.feed.post&reverse=true&limit=1"
{"records":[{"uri":"at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.post/3l6s2riuouk2j","cid":"bafyreibjohl7va4upkibw5twaxdd4jg3l6rmfatu4dpjjfd5xkb2ijtlx4","value":{"text":"hello","$type":"app.bsky.feed.post","langs":["ja"],"createdAt":"2024-10-18T13:21:39.809Z"}}],"cursor":"3l6s2riuouk2j"} {"records":[{"uri":"at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.post/3l6s2riuouk2j","cid":"bafyreibjohl7va4upkibw5twaxdd4jg3l6rmfatu4dpjjfd5xkb2ijtlx4","value":{"text":"hello","$type":"app.bsky.feed.post","langs":["ja"],"createdAt":"2024-10-18T13:21:39.809Z"}}],"cursor":"3l6s2riuouk2j"}
``` ```
@@ -46,12 +45,6 @@ $ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=ai.syu.is&collection=a
- https://feed.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/cmd - https://feed.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/cmd
- https://desc.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/cmd - https://desc.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/cmd
## link
- https://github.com/bluesky-social/atproto
- https://github.com/itaru2622/bluesky-selfhost-env
- https://github.com/bluesky-social/atproto/discussions/2026
## self-host ## self-host
currently, bsky and bsync require patches to function properly. additionally, social-app is not displaying avatars. for components that are not working, it's recommended to use [itaru2622/bluesky-selfhost-env](https://github.com/itaru2622/bluesky-selfhost-env). this repository provides an environment for self-hosting bluesky. currently, bsky and bsync require patches to function properly. additionally, social-app is not displaying avatars. for components that are not working, it's recommended to use [itaru2622/bluesky-selfhost-env](https://github.com/itaru2622/bluesky-selfhost-env). this repository provides an environment for self-hosting bluesky.

View File

@@ -48,8 +48,7 @@ services:
- ./envs/pds - ./envs/pds
volumes: volumes:
- ./data/pds/:/data/ - ./data/pds/:/data/
- ./repos/atproto/services/pds/index.js:/app/run-pds.js command: node --enable-source-maps index.js
command: ['node', '--enable-source-maps', '/app/run-pds.js']
depends_on: depends_on:
database: database:
condition: service_healthy condition: service_healthy
@@ -66,8 +65,7 @@ services:
user: root user: root
volumes: volumes:
- ./data/bsky/:/data/ - ./data/bsky/:/data/
- ./repos/atproto/services/bsky/api.js:/app/run-bsky.js command: node --enable-source-maps api.js
command: ['node', '--enable-source-maps', '/app/run-bsky.js']
depends_on: depends_on:
database: database:
condition: service_healthy condition: service_healthy

View File

@@ -2,5 +2,5 @@ DATABASE_URL=postgres://postgres:postgres@database/bgs
CARSTORE_DATABASE_URL=postgres://postgres:postgres@database/carstore CARSTORE_DATABASE_URL=postgres://postgres:postgres@database/carstore
DATA_DIR=/data DATA_DIR=/data
ATP_PLC_HOST=https://plc.${host} ATP_PLC_HOST=https://plc.${host}
BGS_NEW_PDS_PER_DAY_LIMIT=1000
BGS_ADMIN_KEY BGS_ADMIN_KEY=

View File

@@ -6,6 +6,8 @@ PDS_BLOBSTORE_DISK_LOCATION=/data/img/static
PDS_BSKY_APP_VIEW_DID=did:web:bsky.${host} PDS_BSKY_APP_VIEW_DID=did:web:bsky.${host}
PDS_BSKY_APP_VIEW_URL=https://bsky.${host} PDS_BSKY_APP_VIEW_URL=https://bsky.${host}
PDS_CRAWLERS=https://bgs.${host} PDS_CRAWLERS=https://bgs.${host}
PDS_SEQUENCER_ENABLED=true
PDS_SEQUENCER_DB_LOCATION=/data/sequencer.sqlite
PDS_DEV_MODE=true PDS_DEV_MODE=true
PDS_DID_PLC_URL=https://plc.${host} PDS_DID_PLC_URL=https://plc.${host}
PDS_ENABLE_DID_DOC_WITH_SESSION=true PDS_ENABLE_DID_DOC_WITH_SESSION=true

View File

@@ -18,6 +18,8 @@ function at-repos-env() {
https://github.com/bluesky-social/ozone https://github.com/bluesky-social/ozone
https://github.com/bluesky-social/jetstream https://github.com/bluesky-social/jetstream
) )
services=( bsky plc pds jetstream bgs ozone social-app )
img=( bsky plc pds jetstream bgs ozone social-app ozone-web ozone-daemon )
d=${0:a:h} d=${0:a:h}
dh=${0:a:h:h} dh=${0:a:h:h}
name=${host%%.*} name=${host%%.*}
@@ -108,7 +110,53 @@ function at-repos-social-app-avatar-write() {
grep -R syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/syu.is/${host}/g" grep -R syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/syu.is/${host}/g"
grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/web.syu.is/web.${host}/g" grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/web.syu.is/web.${host}/g"
f=$dt/lib/constants.ts f=$dt/lib/constants.ts
sed -i "s/public.api.web/bsky/g" $f sed -i "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f
sed -i "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f
sed -i "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f
sed -i "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f
sed -i "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f
# Disable external services (CORS fix)
f=$dt/state/geolocation/const.ts
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/state/geolocation/const.ts -o $f
cat > $f << 'GEOEOF'
import {type GeolocationStatus} from '#/state/geolocation/types'
import {BAPP_CONFIG_DEV_URL, IS_DEV} from '#/env'
import {type Device} from '#/storage'
export const IPCC_URL = `https://bsky.app/ipcc`
// Disabled for self-hosted environment to avoid CORS errors
export const BAPP_CONFIG_URL_PROD = null
export const BAPP_CONFIG_URL = null
export const GEOLOCATION_CONFIG_URL = BAPP_CONFIG_URL
export const DEFAULT_GEOLOCATION_CONFIG: Device['geolocation'] = {
countryCode: undefined,
regionCode: undefined,
ageRestrictedGeos: [],
ageBlockedGeos: [],
}
export const DEFAULT_GEOLOCATION_STATUS: GeolocationStatus = {
countryCode: undefined,
regionCode: undefined,
isAgeRestrictedGeo: false,
isAgeBlockedGeo: false,
}
GEOEOF
# Add null check to geolocation config.ts to prevent fetch(null) errors
f=$dt/state/geolocation/config.ts
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/state/geolocation/config.ts -o $f
# Add null check at the beginning of getGeolocationConfig function (after line with 'url: string,')
sed -i "s/): Promise<Device\['geolocation'\]> {/): Promise<Device['geolocation']> {\n if (!url) return undefined/" $f
# Disable Statsig (CORS fix)
f=$dt/lib/statsig/statsig.tsx
sed -i "s#api: 'https://events.bsky.app/v2'#api: '' // Disabled for self-hosted#g" $f
# Disable SDK initialization to prevent statsigapi.net connections
sed -i "s#const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV'#const SDK_KEY = '' // Disabled for self-hosted#g" $f
f=$dt/view/icons/Logotype.tsx f=$dt/view/icons/Logotype.tsx
o=$d/icons/Logotype.tsx o=$d/icons/Logotype.tsx
cp -rf $o $f cp -rf $o $f
@@ -156,6 +204,16 @@ function at-repos-social-app-agent-patch() {
popd popd
} }
function at-repos-social-app-disable-external-services-patch() {
f=$d/repos/social-app/src/state/geolocation/const.ts
p_=$d/patching/8980-social-app-disable-external-services.diff
d_=$d/repos/social-app
echo "applying patch: under ${f} for ${p_}"
pushd ${d_}
patch -p1 < ${p_}
popd
}
function at-repos-ozone-patch() { function at-repos-ozone-patch() {
#DOMAIN=syu.is #DOMAIN=syu.is
cd $d/repos cd $d/repos
@@ -180,48 +238,40 @@ function at-repos-ozone-patch() {
function at-repos-build-docker-atproto() { function at-repos-build-docker-atproto() {
cd $d cd $d
docker image prune -a docker image prune -a
docker compose build --no-cache bsky plc pds jetstream bgs ozone if [ -z "$1" ];then
for ((i=1; i<=${#services}; i++)); do
service=${services[$i]}
docker compose build --no-cache $service
done
else
docker compose build --no-cache $1
fi
} }
function at-repos-build-docker-social() { function at-repos-push-reset() {
cd $d
docker compose build --no-cache social-app
}
function at-repos-build-docker-tag() {
docker restart registry docker restart registry
docker stop registry docker stop registry
docker rm registry docker rm registry
docker volume rm registry-data 2>/dev/null || true docker volume rm registry-data 2>/dev/null || true
docker run -d -p 5000:5000 --name registry \ docker run -d -p ${dport}:${dport} --name registry \
--restart=always \ --restart=always \
-v registry-data:/var/lib/registry \ -v registry-data:/var/lib/registry \
registry:2 registry:2
sleep 3 sleep 3
docker run -d -p ${dport}:${dport} --name registry --restart=always registry:2 docker run -d -p ${dport}:${dport} --name registry --restart=always registry:2
docker tag at-pds:latest localhost:${dport}/pds:latest }
docker tag at-ozone-web:latest localhost:${dport}/ozone-web:latest
docker tag at-bgs:latest localhost:${dport}/bgs:latest
docker tag at-jetstream:latest localhost:${dport}/jetstream:latest
docker tag at-bsky:latest localhost:${dport}/bsky:latest
docker tag at-ozone-daemon:latest localhost:${dport}/ozone-daemon:latest
docker tag at-ozone:latest localhost:${dport}/ozone:latest
docker tag at-plc:latest localhost:${dport}/plc:latest
docker tag at-social-app:latest localhost:${dport}/social-app:latest
docker push localhost:${dport}/pds:latest function at-repos-push-docker() {
docker push localhost:${dport}/ozone-web:latest if [ -z "$1" ];then
docker push localhost:${dport}/bgs:latest for ((i=1; i<=${#img}; i++)); do
docker push localhost:${dport}/jetstream:latest img=${imgs[$i]}
docker push localhost:${dport}/bsky:latest docker tag at-${img}:latest localhost:${dport}/${img}:latest
docker push localhost:${dport}/ozone-daemon:latest docker push localhost:${dport}/${img}:latest
docker push localhost:${dport}/ozone:latest done
docker push localhost:${dport}/plc:latest else
docker push localhost:${dport}/social-app:latest docker tag at-${1}:latest localhost:${dport}/${1}:latest
docker push localhost:${dport}/${1}:latest
cd $d fi
docker compose down
} }
function at-repos-pull-docker() { function at-repos-pull-docker() {
@@ -230,24 +280,37 @@ function at-repos-pull-docker() {
docker compose up -d --pull always docker compose up -d --pull always
} }
function at-origin-social-app() {
cp -rf $d/social-app-custom $d/repos/social-app
}
at-repos-env at-repos-env
case "`cat /etc/hostname`" in case "`cat /etc/hostname`" in
at) at)
at-repos-pull-docker at-repos-pull-docker
exit
;; ;;
*) *)
at-repos-push-reset
at-repos-clone at-repos-clone
at-repos-pull at-repos-pull
at-repos-social-app-icon at-repos-social-app-icon
at-repos-social-app-icon-origin at-repos-social-app-icon-origin
at-repos-social-app-avatar-write at-repos-social-app-avatar-write
at-repos-social-app-agent-patch
at-repos-social-app-disable-external-services-patch
at-repos-atproto-service-bsky-api-patch at-repos-atproto-service-bsky-api-patch
at-repos-atproto-service-pds-index-patch at-repos-atproto-service-pds-index-patch
at-repos-social-app-agent-patch
at-repos-ozone-patch at-repos-ozone-patch
if [ -n "$1" ];then
at-repos-build-docker-atproto $1
at-repos-push-docker $1
exit
fi
at-repos-build-docker-atproto at-repos-build-docker-atproto
at-repos-build-docker-social at-repos-push-docker
cd $d; docker compose down
;; ;;
esac esac

View File

@@ -0,0 +1,17 @@
--- a/src/state/geolocation/const.ts
+++ b/src/state/geolocation/const.ts
@@ -3,9 +3,10 @@ import {BAPP_CONFIG_DEV_URL, IS_DEV} from '#/env'
import {type Device} from '#/storage'
export const IPCC_URL = `https://bsky.app/ipcc`
-export const BAPP_CONFIG_URL_PROD = `https://ip.bsky.app/config`
-export const BAPP_CONFIG_URL = IS_DEV
- ? (BAPP_CONFIG_DEV_URL ?? BAPP_CONFIG_URL_PROD)
- : BAPP_CONFIG_URL_PROD
+// Disabled for self-hosted environment to avoid CORS errors
+// export const BAPP_CONFIG_URL_PROD = `https://ip.bsky.app/config`
+// export const BAPP_CONFIG_URL = IS_DEV
+// ? (BAPP_CONFIG_DEV_URL ?? BAPP_CONFIG_URL_PROD)
+// : BAPP_CONFIG_URL_PROD
+export const BAPP_CONFIG_URL = null
export const GEOLOCATION_CONFIG_URL = BAPP_CONFIG_URL

0
social-app-custom/.keep Normal file
View File

View File

@@ -0,0 +1,134 @@
import React from 'react'
import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
interface AppInfoProps {
onLinkPress?: (url: string) => void
}
export default function AppInfo({onLinkPress}: AppInfoProps) {
const handleLinkPress = (url: string) => {
if (onLinkPress) {
onLinkPress(url)
} else {
Linking.openURL(url)
}
}
return (
<View style={styles.container}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>About This App</Text>
<Text style={styles.paragraph}>
This is a customized AT Protocol social networking client. It allows you to
connect to any Personal Data Server (PDS) and participate in the decentralized
social network.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Key Features</Text>
<View style={styles.list}>
<Text style={styles.listItem}> Connect to any AT Protocol PDS</Text>
<Text style={styles.listItem}> Post text, images, and videos</Text>
<Text style={styles.listItem}> Follow users and view timelines</Text>
<Text style={styles.listItem}> Customize feeds and moderation settings</Text>
<Text style={styles.listItem}> Direct messaging support</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Open Source</Text>
<Text style={styles.paragraph}>
This application is based on the Bluesky social-app, licensed under the MIT
License. The original source code is available at:
</Text>
<Pressable
onPress={() =>
handleLinkPress('https://github.com/bluesky-social/social-app')
}>
<Text style={styles.link}>github.com/bluesky-social/social-app</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>AT Protocol</Text>
<Text style={styles.paragraph}>
This app uses the AT Protocol (Authenticated Transfer Protocol), an open and
decentralized standard for social applications.
</Text>
<Pressable onPress={() => handleLinkPress('https://atproto.com')}>
<Text style={styles.link}>atproto.com</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>License</Text>
<Text style={styles.paragraph}>
Copyright 20232025 Bluesky Social PBC
</Text>
<Text style={styles.paragraph}>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software.
</Text>
<Text style={styles.paragraph}>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Contact</Text>
<Pressable onPress={() => handleLinkPress('https://syu.is')}>
<Text style={styles.link}>https://syu.is</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.versionText}>Version 1.0.0</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
section: {
marginBottom: 24,
},
sectionTitle: {
fontSize: 20,
fontWeight: '600',
color: '#1d1d1f',
marginBottom: 12,
},
paragraph: {
fontSize: 15,
lineHeight: 22,
color: '#3a3a3c',
marginBottom: 8,
},
list: {
marginLeft: 8,
marginTop: 8,
},
listItem: {
fontSize: 15,
lineHeight: 24,
color: '#3a3a3c',
},
link: {
fontSize: 15,
color: '#007aff',
textDecorationLine: 'underline',
marginTop: 8,
},
versionText: {
fontSize: 13,
color: '#8e8e93',
fontStyle: 'italic',
},
})

View File

@@ -0,0 +1,95 @@
import React from 'react'
import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
export default function LicenseNotice() {
return (
<View style={styles.container}>
<Text style={styles.title}>Open Source Licenses</Text>
<View style={styles.section}>
<Text style={styles.projectName}>Bluesky Social App</Text>
<Text style={styles.license}>MIT License</Text>
<Text style={styles.copyright}>Copyright 20232025 Bluesky Social PBC</Text>
<Text style={styles.licenseText}>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
</Text>
<Text style={styles.licenseText}>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
</Text>
<Text style={styles.licenseText}>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</Text>
<Pressable
onPress={() =>
Linking.openURL('https://github.com/bluesky-social/social-app')
}>
<Text style={styles.link}>View Source Code</Text>
</Pressable>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
color: '#1d1d1f',
},
section: {
marginBottom: 24,
padding: 16,
backgroundColor: '#f5f5f7',
borderRadius: 8,
},
projectName: {
fontSize: 18,
fontWeight: '600',
marginBottom: 8,
color: '#1d1d1f',
},
license: {
fontSize: 14,
fontWeight: '500',
color: '#007aff',
marginBottom: 4,
},
copyright: {
fontSize: 13,
color: '#3a3a3c',
marginBottom: 12,
},
licenseText: {
fontSize: 12,
lineHeight: 18,
color: '#3a3a3c',
marginBottom: 12,
},
link: {
fontSize: 14,
color: '#007aff',
textDecorationLine: 'underline',
marginTop: 8,
},
})

View File

@@ -0,0 +1,163 @@
import React from 'react'
import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
interface PrivacyContentProps {
onLinkPress?: (url: string) => void
}
export default function PrivacyContent({onLinkPress}: PrivacyContentProps) {
const handleLinkPress = (url: string) => {
if (onLinkPress) {
onLinkPress(url)
} else {
Linking.openURL(url)
}
}
return (
<View style={styles.container}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Introduction</Text>
<Text style={styles.paragraph}>
This Privacy Policy explains how this AT Protocol client application
(hereinafter referred to as "the App") handles personal information.
Please read this policy carefully before using the App.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Information We Collect</Text>
<Text style={styles.paragraph}>
The App may collect and use the following information:
</Text>
<Text style={styles.subTitle}>1. Information Collected Automatically</Text>
<View style={styles.list}>
<Text style={styles.listItem}> Device information (model, OS version)</Text>
<Text style={styles.listItem}> App usage data (sessions, features used)</Text>
<Text style={styles.listItem}> Crash logs and performance data</Text>
</View>
<Text style={styles.subTitle}>2. Information Provided by Users</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
DID (Decentralized Identifier) and handle for authentication
</Text>
<Text style={styles.listItem}> Posts, media, and social interactions</Text>
<Text style={styles.listItem}> Profile information (avatar, display name, bio)</Text>
</View>
<View style={styles.highlight}>
<Text style={styles.highlightText}>
Important: Your data is stored on your chosen PDS (Personal Data Server).
This app does not store your content on our servers.
</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>How We Use Your Information</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
To provide AT Protocol social networking features
</Text>
<Text style={styles.listItem}> To improve app performance and user experience</Text>
<Text style={styles.listItem}> To diagnose and fix technical issues</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Data Sharing</Text>
<Text style={styles.paragraph}>
The App interacts with your chosen PDS and AppView services. Your posts and
profile information are shared according to the AT Protocol specification and
your privacy settings.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Your Rights</Text>
<Text style={styles.paragraph}>
You have the right to access, modify, or delete your data through your PDS.
You can also switch to a different PDS at any time while maintaining your
identity.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Contact</Text>
<Text style={styles.paragraph}>
For questions about this Privacy Policy, please contact:
</Text>
<Pressable onPress={() => handleLinkPress('https://syu.is')}>
<Text style={styles.link}>https://syu.is</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.lastUpdated}>Last Updated: December 3, 2025</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
section: {
marginBottom: 24,
},
sectionTitle: {
fontSize: 20,
fontWeight: '600',
color: '#1d1d1f',
marginBottom: 12,
},
subTitle: {
fontSize: 16,
fontWeight: '500',
color: '#1d1d1f',
marginTop: 12,
marginBottom: 8,
},
paragraph: {
fontSize: 15,
lineHeight: 22,
color: '#3a3a3c',
marginBottom: 8,
},
list: {
marginLeft: 8,
marginTop: 8,
},
listItem: {
fontSize: 15,
lineHeight: 24,
color: '#3a3a3c',
},
highlight: {
backgroundColor: '#fff3cd',
borderLeftWidth: 4,
borderLeftColor: '#ffc107',
padding: 12,
marginTop: 12,
borderRadius: 4,
},
highlightText: {
fontSize: 14,
lineHeight: 20,
color: '#856404',
},
link: {
fontSize: 15,
color: '#007aff',
textDecorationLine: 'underline',
marginTop: 8,
},
lastUpdated: {
fontSize: 13,
color: '#8e8e93',
fontStyle: 'italic',
},
})

View File

@@ -0,0 +1,42 @@
import React from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {
type CommonNavigatorParams,
type NativeStackScreenProps,
} from '#/lib/routes/types'
import {s} from '#/lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {ScrollView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import {ViewHeader} from '../com/util/ViewHeader'
import PrivacyContent from '#/components/custom/PrivacyContent'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'>
export const PrivacyPolicyScreen = (_props: Props) => {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
return (
<Layout.Screen>
<ViewHeader title={_(msg`Privacy Policy`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<View style={[s.p20]}>
<PrivacyContent />
</View>
<View style={s.footerSpacer} />
</ScrollView>
</Layout.Screen>
)
}

View File

@@ -0,0 +1,38 @@
import React from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {
type CommonNavigatorParams,
type NativeStackScreenProps,
} from '#/lib/routes/types'
import {s} from '#/lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {ScrollView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import AppInfo from '#/components/custom/AppInfo'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Support'>
export const SupportScreen = (_props: Props) => {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
return (
<Layout.Screen>
<ViewHeader title={_(msg`App Info`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<AppInfo />
</ScrollView>
</Layout.Screen>
)
}

View File

@@ -0,0 +1,9 @@
// Aiat app configuration overrides
module.exports = {
name: 'Aiat',
slug: 'aiat',
scheme: 'aiat',
owner: 'syui', // Your Expo account
bundleIdentifier: 'ai.syui.at',
// Icon will be set separately
}

44
social-app-custom/bin/build.zsh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/zsh
set -e
d=~/ai/at/repos/social-app
APP_NAME=Aiat
PKG=aiat
TEAM_NAME=
TEAM_ID=
CERT="Apple Distribution: ${TEAM_NAME} (${TEAM_ID})"
MAIL=user@example.com
KEY_CHAIN=EXAMPLE
cd $d
# npx expo prebuild --clean
# cd ios && pod install && cd ..
## アーカイブ
xcodebuild -workspace ios/${PKG}.xcworkspace \
-scheme ${PKG} \
-configuration Release \
-archivePath build/${APP_NAME}.xcarchive \
-allowProvisioningUpdates \
archive
cd build
# IPA作成
rm -rf Payload ${APP_NAME}.ipa
mkdir -p Payload
cp -R ${APP_NAME}.xcarchive/Products/Applications/${PKG}.app Payload/
cp ../store.mobileprovision Payload/${PKG}.app/embedded.mobileprovision
# entitlements抽出
security cms -D -i Payload/${PKG}.app/embedded.mobileprovision > /tmp/profile.plist
/usr/libexec/PlistBuddy -x -c "Print :Entitlements" /tmp/profile.plist > /tmp/entitlements.plist
codesign -f -s "$CERT" Payload/${PKG}.app/Frameworks/*.framework 2>/dev/null || true
codesign -f -s "$CERT" --entitlements /tmp/entitlements.plist Payload/${PKG}.app
zip -r ${APP_NAME}.ipa Payload
xcrun altool --upload-app -f ${APP_NAME}.ipa -t ios -u "${MAIL}" -p "@keychain:${KEY_CHAIN}"
echo "Upload complete"

View File

@@ -0,0 +1,86 @@
#!/bin/zsh
if [ "$1" = "social-app-custom" ];then
at-social-app-custom-pages
at-social-app-custom-screens
at-social-app-aiat-config
at-social-app-aiat-logo
at-origin-social-app
exit
fi
function at-social-app-custom-pages() {
d_=$d/repos/social-app
custom=$d/social-app-custom
echo "copying custom components to social-app"
# Create components directory if not exists
mkdir -p ${d_}/src/components/custom
# Copy custom components
cp ${custom}/PrivacyContent.tsx ${d_}/src/components/custom/
cp ${custom}/AppInfo.tsx ${d_}/src/components/custom/
echo "custom components copied successfully"
}
function at-social-app-aiat-config() {
d_=$d/repos/social-app
custom=$d/social-app-custom
echo "applying Aiat configuration"
# Update app.config.js
cd ${d_}
# Backup original
cp app.config.js app.config.js.orig
# Apply changes using sed
sed -i "s/name: 'Bluesky'/name: 'Aiat'/g" app.config.js
sed -i "s/slug: 'bluesky'/slug: 'aiat'/g" app.config.js
sed -i "s/scheme: 'bluesky'/scheme: 'aiat'/g" app.config.js
sed -i "s/owner: 'blueskysocial'/owner: 'syui'/g" app.config.js
sed -i "s/bundleIdentifier: 'xyz.blueskyweb.app'/bundleIdentifier: 'ai.syui.at'/g" app.config.js
# Update package.json name
sed -i 's/"name": "bsky.app"/"name": "aiat"/g' package.json
echo "Aiat configuration applied"
}
function at-social-app-aiat-logo() {
d_=$d/repos/social-app
custom=$d/social-app-custom
echo "applying Aiat logo"
# Create logo directory if not exists
mkdir -p ${custom}/assets
# Copy logo if exists in custom folder
if [ -f ${custom}/assets/icon.png ]; then
cp ${custom}/assets/icon.png ${d_}/assets/app-icons/ios_icon_default_next.png
echo "Aiat logo applied"
else
echo "Warning: Logo file not found at ${custom}/assets/icon.png"
echo "Please add your logo file there"
fi
}
function at-social-app-custom-screens() {
d_=$d/repos/social-app
custom=$d/social-app-custom
echo "applying custom screens"
# Copy custom screen files
cp ${custom}/PrivacyPolicy.screen.tsx ${d_}/src/view/screens/PrivacyPolicy.tsx
cp ${custom}/Support.screen.tsx ${d_}/src/view/screens/Support.tsx
cp ${custom}/LicenseNotice.tsx ${d_}/src/components/custom/
echo "custom screens applied"
}