ai/at
1
0

Compare commits

..

27 Commits

Author SHA1 Message Date
7518cd7d06 fix social-app patch 2026-03-01 17:06:52 +09:00
f130c8d731 fix social-app ios icon 2026-03-01 13:00:39 +09:00
511527abfd fix bgs 2026-02-28 21:12:35 +09:00
31ee74bb66 fix ozone-web patch 2026-02-28 19:43:45 +09:00
acd41f8ece fix social-app ios 2025 2026-02-26 11:09:42 +09:00
34a51a2198 fix readme 2026-02-25 12:43:47 +09:00
966bfc7c2f fix install.zsh 2026-02-25 12:35:14 +09:00
25e7927c70 fix dockerfile copy patch 2026-02-25 08:07:34 +09:00
6d89ea4481 fix patch social-app ios 2026-02-24 20:30:45 +09:00
1690aa4c12 fix install.zsh 2026-02-20 08:43:30 +09:00
c9dd10d6f3 fix dockerfile patch 2026-02-20 07:23:58 +09:00
b6f7d8bed1 fix install.zsh 2026-02-20 07:08:04 +09:00
f4180e0c54 rm atproto 2026-02-19 16:56:10 +09:00
9157948d93 fix social-app ios patch langs 2026-02-19 08:24:32 +09:00
8db064d8c0 fix social-app service link pds 2026-02-16 14:36:12 +09:00
c09738f342 fix readme 2026-02-16 07:33:02 +09:00
0ef8f22dcb fix social-app web support link 2026-02-16 06:50:44 +09:00
0506a32fd8 add social-app service ai.syui.at 2026-02-16 04:05:12 +09:00
a49a2ff61f fix install.zsh 2026-02-16 04:04:58 +09:00
9644e9151b custom social-app service view 2026-02-16 03:07:08 +09:00
8b26cf9452 fix patch 2026-02-16 01:23:52 +09:00
718820daec add k8s 2026-02-04 22:59:18 +09:00
88cb429951 fix social-app patch support page 2026-02-03 14:48:11 +09:00
f27cae0669 fix feed docker 2026-02-03 00:28:58 +09:00
bfa82b1880 fix social-app patch 2026-02-02 22:55:33 +09:00
d157199dd1 fix bgs docker 2026-02-02 22:10:44 +09:00
9a55cf01b5 fix patch bgs lexicon null 2026-02-02 22:07:16 +09:00
103 changed files with 14426 additions and 946 deletions

View File

@@ -33,11 +33,8 @@ jobs:
working-directory: web working-directory: web
- name: Deploy to Cloudflare Pages - name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1 uses: cloudflare/wrangler-action@v3
with: with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }} command: pages deploy web/dist/aiat --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}
directory: web/dist/aiat
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
wranglerVersion: '3'

3
.gitignore vendored
View File

@@ -4,6 +4,9 @@ deploy.yml
claude.md claude.md
embedded.mobileprovision embedded.mobileprovision
.env .env
k8s/secrets.env
k8s/deploy.yml
web/dist web/dist
node_modules node_modules
package-lock.json package-lock.json
/tmp

View File

@@ -1,18 +1,15 @@
# at # at
- https://github.com/bluesky-social/atproto - https://github.com/bluesky-social/atproto
- https://github.com/bluesky-social/atproto/discussions/2026 - https://github.com/bluesky-social/social-app
|word|name|example| |name|type|example|
|---|---|---| |---|---|---|
|at|uri|at://example.com| |at|uri|at://example.com|
|@|user|@example.com| |@|user|@example.com|
|[at]proto|repo|`git@github.com:bluesky-social/atproto`|
|[at]mosphere|system|pds, bsky(appview), ozone, bgs, plc| |[at]mosphere|system|pds, bsky(appview), ozone, bgs, plc|
|[a]uthenticated [t]ransfer|protocol|[did](https://www.w3.org/TR/did-core/)| |[a]uthenticated [t]ransfer|protocol|[did](https://www.w3.org/TR/did-core/)|
- https://atproto.com/guides/glossary
## account ## account
- [ai@syu.is](https://syu.is/profile/did:plc:6qyecktefllvenje24fcxnie) - [ai@syu.is](https://syu.is/profile/did:plc:6qyecktefllvenje24fcxnie)
@@ -22,19 +19,6 @@
```sh ```sh
$ curl -sL syu.is/xrpc/_health $ curl -sL syu.is/xrpc/_health
# latest
# https://github.com/bluesky-social/atproto/blob/main/packages/pds/package.json
$ curl -sL https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/main/packages/pds/package.json |jq -r .version
```
```sh
$ handle=ai.syui.ai
$ curl -sL "syu.is/xrpc/com.atproto.repo.describeRepo?repo=${handle}" |jq -r .did
did:plc:6qyecktefllvenje24fcxnie
$ 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"}
``` ```
## feed ## feed
@@ -85,4 +69,3 @@ $ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=a
./ios/build.zsh ./ios/build.zsh
``` ```

View File

@@ -77,7 +77,7 @@ services:
- 2470:2470 - 2470:2470
build: build:
context: ./repos/indigo/ context: ./repos/indigo/
dockerfile: cmd/bigsky/Dockerfile dockerfile: cmd/relay/Dockerfile
restart: always restart: always
env_file: env_file:
- ./envs/bgs - ./envs/bgs
@@ -155,3 +155,5 @@ services:
- ./envs/feed - ./envs/feed
volumes: volumes:
- ./data/feed:/data/ - ./data/feed:/data/
depends_on:
- jetstream

20
docker/feed/Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM node:20-alpine
WORKDIR /app
# Install dependencies for better-sqlite3
RUN apk add --no-cache python3 make g++
# Copy package files and install
COPY package.json yarn.lock ./
RUN yarn install
# Copy source
COPY . .
# Build TypeScript
RUN yarn build
EXPOSE 3000
CMD ["node", "dist/index.js"]

View File

@@ -1,32 +0,0 @@
<svg
viewBox="0 0 2821.6379 794.29016"
preserveAspectRatio="xMidYMid"
xmlns:svg="http://www.w3.org/2000/svg">
<g
transform="matrix(0.1,0,0,-0.1,-282.80153,1445)"
fill="#000000"
stroke="none"
>
<path
d="m 24787,14443 c -4,-3 -7,-224 -7,-490 v -483 h 545 545 v 490 490 h -538 c -296,0 -542,-3 -545,-7 z"
/>
<path
d="m 5190,13285 c -8,-3 -96,-12 -195,-20 -199,-16 -296,-32 -430,-70 -49,-14 -115,-32 -145,-40 -153,-39 -504,-198 -662,-301 -21,-13 -57,-36 -80,-51 -24,-16 -72,-50 -109,-78 -241,-182 -377,-315 -528,-517 -119,-158 -120,-160 -106,-188 23,-45 140,-140 560,-457 110,-84 319,-242 465,-353 146,-110 369,-279 495,-375 791,-600 723,-549 1049,-799 269,-207 398,-307 524,-403 l 114,-86 -49,-49 c -128,-131 -378,-258 -588,-299 -66,-13 -357,-13 -420,0 -115,23 -172,39 -202,54 -18,10 -37,17 -43,17 -24,0 -171,81 -255,141 -50,35 -146,121 -215,192 -69,70 -133,127 -142,127 -19,0 -153,-63 -177,-83 -9,-7 -54,-35 -101,-62 -47,-26 -110,-64 -140,-85 -30,-20 -97,-61 -148,-91 -51,-30 -107,-62 -124,-72 -18,-10 -57,-38 -87,-61 -70,-53 -252,-168 -446,-281 -82,-49 -158,-95 -168,-104 -16,-16 -14,-21 34,-106 165,-289 544,-666 867,-860 9,-6 35,-22 58,-37 36,-23 267,-138 349,-173 108,-47 160,-67 240,-93 150,-48 201,-62 228,-62 14,0 64,-9 109,-19 197,-46 302,-56 573,-56 197,1 281,5 345,17 47,9 110,19 141,22 31,3 75,13 99,21 23,8 56,15 72,15 17,0 51,7 77,15 25,8 80,24 121,36 41,12 125,41 185,66 61,25 124,50 140,56 17,7 89,42 160,78 113,58 177,98 395,246 82,56 273,232 403,371 127,136 237,271 237,291 0,12 -208,179 -425,342 -186,140 -1121,843 -1720,1294 -264,199 -589,444 -723,546 -134,101 -274,208 -312,237 -39,29 -70,58 -70,66 0,21 107,115 203,179 95,64 237,133 295,143 20,4 49,12 63,20 96,48 519,48 619,-1 14,-7 41,-16 60,-20 65,-13 262,-118 360,-191 99,-74 250,-230 372,-384 34,-44 70,-80 80,-80 9,0 39,15 67,33 28,17 70,44 94,58 23,15 121,76 217,136 96,60 254,156 350,213 96,56 272,160 390,230 118,70 230,135 248,144 41,21 41,45 -3,116 -18,30 -46,75 -61,100 -64,106 -252,348 -352,454 -106,112 -169,171 -293,274 -197,164 -310,235 -594,376 -215,106 -519,205 -735,240 -137,22 -574,51 -610,41 z"
/>
<path
d="m 29095,12736 c -421,-44 -744,-157 -975,-342 -259,-207 -396,-446 -465,-809 -16,-84 -23,-301 -14,-425 36,-518 257,-859 694,-1070 166,-80 284,-116 716,-215 449,-103 514,-121 646,-186 218,-107 308,-253 306,-494 -2,-303 -188,-477 -588,-551 -140,-26 -640,-27 -825,-1 -275,38 -486,94 -776,203 -94,35 -174,64 -178,64 -3,0 -6,-175 -6,-389 v -388 l 88,-41 c 404,-187 905,-282 1486,-282 675,0 1154,150 1465,459 196,194 311,434 362,756 20,121 17,476 -5,600 -89,517 -358,800 -945,994 -130,43 -241,71 -616,156 -137,31 -299,73 -360,92 -331,106 -455,246 -455,511 1,249 127,412 387,501 272,92 801,76 1269,-39 116,-28 326,-96 432,-138 l 52,-22 -2,406 -3,406 -66,28 c -154,66 -413,140 -604,174 -279,49 -756,69 -1020,42 z"
/>
<path
d="m 9630,12676 c 0,-2 403,-996 896,-2208 493,-1211 898,-2211 901,-2220 6,-21 -135,-386 -193,-499 -104,-202 -256,-324 -471,-380 -118,-30 -340,-43 -516,-29 -84,6 -185,17 -225,24 -40,7 -75,10 -77,7 -3,-2 -5,-163 -5,-357 v -353 l 63,-30 c 194,-93 493,-138 801,-120 414,23 683,115 937,319 174,140 357,402 474,680 26,62 1945,5159 1945,5167 0,2 -232,2 -516,1 l -517,-3 -556,-1550 c -485,-1350 -560,-1550 -578,-1553 -18,-3 -26,11 -62,105 -23,59 -295,758 -605,1553 l -562,1445 -567,3 c -312,1 -567,0 -567,-2 z"
/>
<path
d="m 15690,10867 c 0,-1970 -1,-1936 56,-2153 128,-497 461,-787 1014,-885 147,-26 440,-36 605,-20 413,40 834,200 1181,447 35,25 73,44 88,44 14,0 26,-1 26,-3 0,-2 20,-94 45,-205 25,-111 45,-204 45,-207 0,-3 209,-5 465,-5 h 465 v 2400 2400 h -535 -535 l -2,-1866 -3,-1866 -115,-50 c -425,-185 -743,-252 -1088,-227 -302,21 -472,109 -562,293 -79,161 -74,11 -77,1969 l -3,1747 h -535 -535 z"
/>
<path
d="m 24785,12668 c -3,-7 -4,-1086 -3,-2398 l 3,-2385 538,-3 537,-2 v 2400 2400 h -535 c -419,0 -537,-3 -540,-12 z"
/>
<path
d="m 21660,8275 v -545 h 565 565 v 545 545 h -565 -565 z"
/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -72,11 +72,8 @@ typeset -a PATCH_FILES
PATCH_FILES=( PATCH_FILES=(
"170-pds-oauth-same-site-fix.patch" "170-pds-oauth-same-site-fix.patch"
"8980-social-app-disable-proxy.diff" "8980-social-app-disable-proxy.diff"
"disable-statsig-sdk.diff"
"140-social-app-yarn-network-timeout.patch" "140-social-app-yarn-network-timeout.patch"
"130-atproto-ozone-enable-daemon-v2.patch" "130-atproto-ozone-enable-daemon-v2.patch"
"152-indigo-newpds-dayper-limit-pr707.diff"
"190-bgs-disable-ratelimit.patch"
"200-feed-generator-custom.patch" "200-feed-generator-custom.patch"
"210-bgs-since-empty-fix.patch" "210-bgs-since-empty-fix.patch"
) )
@@ -123,6 +120,10 @@ function at-repos-pull() {
cd .. cd ..
fi fi
done done
# Copy feed-generator Dockerfile if missing (removed by git checkout)
if [ ! -f $d/repos/feed-generator/Dockerfile ] && [ -f $d/docker/feed/Dockerfile ];then
cp -rf $d/docker/feed/Dockerfile $d/repos/feed-generator/
fi
cd $d cd $d
} }
@@ -333,6 +334,41 @@ export const SOCIAL_APP_DOMAIN =\
}' lib/constants.ts 2>/dev/null || true }' lib/constants.ts 2>/dev/null || true
# Fix parseInt() to handle undefined by adding || '' # Fix parseInt() to handle undefined by adding || ''
sediment "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true sediment "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
# Add next-runtime-env to package.json if missing (used by lib/constants.ts)
if ! grep -q 'next-runtime-env' package.json 2>/dev/null; then
echo "📦 Adding next-runtime-env to package.json..."
python3 -c "
import json
with open('package.json') as f:
d = json.load(f)
d.setdefault('dependencies', {})['next-runtime-env'] = '^1.6.2'
with open('package.json', 'w') as f:
json.dump(d, f, indent=2)
f.write('\n')
"
echo "✅ Added next-runtime-env"
fi
# Update @atproto/ozone in service/package.json to latest
echo "📦 Updating @atproto/ozone in service/package.json..."
local latest_ozone_ver
latest_ozone_ver=$(npm view @atproto/ozone version 2>/dev/null)
if [ -n "$latest_ozone_ver" ] && [ -f service/package.json ]; then
python3 -c "
import json, sys
ver = sys.argv[1]
with open('service/package.json') as f:
d = json.load(f)
old = d.get('dependencies', {}).get('@atproto/ozone', '')
d.setdefault('dependencies', {})['@atproto/ozone'] = ver
with open('service/package.json', 'w') as f:
json.dump(d, f, indent=2)
f.write('\n')
print(f'✅ @atproto/ozone: {old} -> {ver}')
" "$latest_ozone_ver"
fi
popd > /dev/null popd > /dev/null
} }
@@ -381,23 +417,86 @@ export const handler = async (ctx: AppContext, params: QueryParams) => {
EOF EOF
echo "✅ Created src/algos/app.ts" echo "✅ Created src/algos/app.ts"
# Restore Dockerfile (removed during patch apply to avoid conflicts)
if [ ! -f $d/repos/feed-generator/Dockerfile ] && [ -f $d/docker/feed/Dockerfile ];then
cp -rf $d/docker/feed/Dockerfile $d/repos/feed-generator/
echo "✅ Restored Dockerfile"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
} }
function at-repos-docker-verify() {
local service=$1
local image="at-${service}:latest"
local check_file=""
case $service in
pds) check_file="/app/services/pds/index.js" ;;
bsky) check_file="/app/services/bsky/api.js" ;;
ozone) check_file="/app/services/ozone/api.js" ;;
bgs) check_file="/relay" ;;
plc) check_file="/app/packages/server/dist/index.js" ;;
*) return 0 ;;
esac
local cid
cid=$(docker create --entrypoint "" "$image" true 2>&1)
if [ $? -ne 0 ]; then
echo " ❌ FAILED: cannot create container from $image"
echo " $cid"
return 1
fi
if docker cp "$cid:$check_file" /tmp/.docker-verify-tmp 2>/dev/null; then
rm -f /tmp/.docker-verify-tmp
docker rm "$cid" > /dev/null 2>&1
echo " ✅ Verified: $check_file exists"
return 0
else
rm -f /tmp/.docker-verify-tmp
docker rm "$cid" > /dev/null 2>&1
echo " ❌ FAILED: $check_file not found in $image"
return 1
fi
}
function at-repos-build-docker-atproto() { function at-repos-build-docker-atproto() {
cd $d cd $d
docker image prune -a local failed=()
if [ -z "$1" ];then if [ -z "$1" ];then
for ((i=1; i<=${#services}; i++)); do for ((i=1; i<=${#services}; i++)); do
service=${services[$i]} service=${services[$i]}
docker compose build --no-cache $service echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔨 Building: $service"
if ! docker compose build --no-cache $service; then
echo " ❌ Build failed: $service"
failed+=($service)
continue
fi
if ! at-repos-docker-verify $service; then
failed+=($service)
continue
fi
if [ "$service" = "ozone" ]; then if [ "$service" = "ozone" ]; then
docker compose build --no-cache ${service}-web docker compose build --no-cache ${service}-web
fi fi
done done
else else
docker compose build --no-cache $1 echo "🔨 Building: $1"
if ! docker compose build --no-cache $1; then
echo "❌ Build failed: $1"
return 1
fi fi
if ! at-repos-docker-verify $1; then
return 1
fi
fi
if [ ${#failed[@]} -gt 0 ]; then
echo ""
echo "❌ Failed builds: ${failed[*]}"
echo "⚠️ Do NOT push these images."
return 1
fi
echo ""
echo "✅ All builds verified successfully."
} }
function at-repos-push-reset() { function at-repos-push-reset() {
@@ -415,25 +514,41 @@ function at-repos-push-reset() {
} }
function at-repos-push-docker() { function at-repos-push-docker() {
local dtag=$(date +%Y%m%d)
if [ -z "$1" ] || [ "$1" = "push" ]; then if [ -z "$1" ] || [ "$1" = "push" ]; then
for service in "${services[@]}"; do for service in "${services[@]}"; do
if ! at-repos-docker-verify $service; then
echo "⚠️ Skipping push: $service (verification failed)"
continue
fi
docker tag at-${service}:latest localhost:${dport}/${service}:latest docker tag at-${service}:latest localhost:${dport}/${service}:latest
docker tag at-${service}:latest localhost:${dport}/${service}:${dtag}
docker push localhost:${dport}/${service}:latest docker push localhost:${dport}/${service}:latest
docker push localhost:${dport}/${service}:${dtag}
if [ "$service" = "ozone" ]; then if [ "$service" = "ozone" ]; then
docker tag at-${service}-web:latest localhost:${dport}/${service}-web:latest docker tag at-${service}-web:latest localhost:${dport}/${service}-web:latest
docker tag at-${service}-web:latest localhost:${dport}/${service}-web:${dtag}
docker push localhost:${dport}/${service}-web:latest docker push localhost:${dport}/${service}-web:latest
docker push localhost:${dport}/${service}-web:${dtag}
fi fi
done done
else else
docker tag at-${1}:latest localhost:${dport}/${1}:latest if ! at-repos-docker-verify $1; then
docker push localhost:${dport}/${1}:latest echo "❌ Push aborted: $1 (verification failed)"
return 1
fi fi
docker tag at-${1}:latest localhost:${dport}/${1}:latest
docker tag at-${1}:latest localhost:${dport}/${1}:${dtag}
docker push localhost:${dport}/${1}:latest
docker push localhost:${dport}/${1}:${dtag}
fi
echo "📦 Pushed with tags: latest, ${dtag}"
} }
function at-repos-pull-docker() { function at-repos-pull-docker() {
cd $d cd $d
docker image prune -a
docker compose up -d --pull always docker compose up -d --pull always
echo "💡 Run 'docker image prune' manually to clean up old images."
} }
function at-repos-reset-bgs-db() { function at-repos-reset-bgs-db() {
@@ -575,6 +690,177 @@ curl -sL -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $
https://${host}/xrpc/com.atproto.repo.putRecord https://${host}/xrpc/com.atproto.repo.putRecord
} }
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Patch creation helpers
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Find existing patches that modify the same file
# Usage: at-patch-find-conflicts <filepath> <patch-dir>
function at-patch-find-conflicts() {
local filepath="$1"
local patch_dir="$2"
local conflicts=()
if [ ! -d "$patch_dir" ]; then
return
fi
for pf in "$patch_dir"/*.patch(N) "$patch_dir"/*.diff(N); do
[ -f "$pf" ] || continue
if grep -q "^--- a/$filepath" "$pf" 2>/dev/null || grep -q "^+++ b/$filepath" "$pf" 2>/dev/null; then
conflicts+=("$(basename "$pf")")
fi
done
if [ ${#conflicts[@]} -gt 0 ]; then
echo "⚠️ This file is also modified by:"
for c in "${conflicts[@]}"; do
echo " - $c"
done
echo ""
echo " Ensure patches are applied in order before patch-begin."
echo " Baseline must reflect the post-earlier-patches state."
fi
}
# Save current file state as baseline for patch creation
# Usage: ./install.zsh patch-begin <repo> <file-path> [--ios]
# Example: ./install.zsh patch-begin social-app "src/screens/Profile/Header/ProfileHeaderStandard.tsx" --ios
function at-patch-begin() {
local repo="$1"
local filepath="$2"
local flag="$3"
if [ -z "$repo" ] || [ -z "$filepath" ]; then
echo "Usage: ./install.zsh patch-begin <repo> <file-path> [--ios]"
echo "Example: ./install.zsh patch-begin social-app \"src/screens/Profile/Header/ProfileHeaderStandard.tsx\" --ios"
return 1
fi
local full_path="$d/repos/$repo/$filepath"
if [ ! -f "$full_path" ]; then
echo "❌ File not found: $full_path"
return 1
fi
local tmp_file="/tmp/patch-base--$(echo "$filepath" | tr '/' '-')"
cp "$full_path" "$tmp_file"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Baseline saved: $tmp_file"
echo " File: $full_path"
echo ""
# Check for conflicting patches
if [ "$flag" = "--ios" ]; then
at-patch-find-conflicts "$filepath" "$d/ios/patching"
else
at-patch-find-conflicts "$filepath" "$d/patching"
fi
echo "Next steps:"
echo " 1. Edit: $full_path"
echo " 2. Save: ./install.zsh patch-save <NNN-name.patch> $repo \"$filepath\" ${flag:---ios}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
# Generate patch from baseline diff
# Usage: ./install.zsh patch-save <patch-filename> <repo> <file-path> [--ios]
# Example: ./install.zsh patch-save 042-social-app-ios-feature.patch social-app "src/path/to/file.tsx" --ios
function at-patch-save() {
local patch_filename="$1"
local repo="$2"
local filepath="$3"
local flag="$4"
if [ -z "$patch_filename" ] || [ -z "$repo" ] || [ -z "$filepath" ]; then
echo "Usage: ./install.zsh patch-save <patch-filename> <repo> <file-path> [--ios]"
echo "Example: ./install.zsh patch-save 042-social-app-ios-feature.patch social-app \"src/file.tsx\" --ios"
return 1
fi
local full_path="$d/repos/$repo/$filepath"
local tmp_file="/tmp/patch-base--$(echo "$filepath" | tr '/' '-')"
if [ ! -f "$tmp_file" ]; then
echo "❌ No baseline found. Run 'patch-begin' first."
return 1
fi
if [ ! -f "$full_path" ]; then
echo "❌ File not found: $full_path"
return 1
fi
# Determine output directory
local patch_dir="$d/patching"
if [ "$flag" = "--ios" ]; then
patch_dir="$d/ios/patching"
fi
# Generate diff with proper a/b paths
diff -u "$tmp_file" "$full_path" \
| sed "1s|--- .*|--- a/$filepath|" \
| sed "2s|+++ .*|+++ b/$filepath|" \
> "$patch_dir/$patch_filename"
local line_count
line_count=$(wc -l < "$patch_dir/$patch_filename" | tr -d ' ')
if [ "$line_count" -eq 0 ]; then
echo "⚠️ No differences found. Patch file is empty."
rm "$patch_dir/$patch_filename"
return 1
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Patch: $patch_dir/$patch_filename ($line_count lines)"
# Dry-run verify: restore baseline, test patch, restore edit
pushd "$d/repos/$repo" > /dev/null
cp "$full_path" /tmp/patch-edited-tmp
cp "$tmp_file" "$full_path"
if patch --dry-run -p1 < "$patch_dir/$patch_filename" > /dev/null 2>&1; then
echo "✅ Dry-run: OK"
else
echo "❌ Dry-run: FAILED"
patch --dry-run -p1 < "$patch_dir/$patch_filename" 2>&1 | head -5
fi
cp /tmp/patch-edited-tmp "$full_path"
rm -f /tmp/patch-edited-tmp
popd > /dev/null
rm -f "$tmp_file"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
# Verify all patches can be applied (full dry-run)
# Usage: ./install.zsh patch-check [--ios]
function at-patch-check() {
local flag="$1"
if [ "$flag" = "--ios" ]; then
echo "Checking iOS patches against: $d/repos/social-app"
pushd "$d/repos/social-app" > /dev/null
for pf in "$d/ios/patching"/*.patch; do
[ -f "$pf" ] || continue
local name="$(basename "$pf")"
if patch --dry-run -p1 < "$pf" > /dev/null 2>&1; then
echo "$name"
else
echo "$name"
fi
done
popd > /dev/null
else
echo "Checking server patches..."
for pf in "$d/patching"/*.patch "$d/patching"/*.diff; do
[ -f "$pf" ] || continue
echo " $(basename "$pf")"
done
fi
}
at-repos-env at-repos-env
case "$1" in case "$1" in
pull) pull)
@@ -590,6 +876,18 @@ case "$1" in
show-failed-patches show-failed-patches
exit exit
;; ;;
patch-begin)
at-patch-begin "$2" "$3" "$4"
exit
;;
patch-save)
at-patch-save "$2" "$3" "$4" "$5"
exit
;;
patch-check)
at-patch-check "$2"
exit
;;
build) build)
at-repos-build-docker-atproto $2 at-repos-build-docker-atproto $2
exit exit

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M237.9 461.4C237.9 463.4 235.6 465 232.7 465C229.4 465.3 227.1 463.7 227.1 461.4C227.1 459.4 229.4 457.8 232.3 457.8C235.3 457.5 237.9 459.1 237.9 461.4zM206.8 456.9C206.1 458.9 208.1 461.2 211.1 461.8C213.7 462.8 216.7 461.8 217.3 459.8C217.9 457.8 216 455.5 213 454.6C210.4 453.9 207.5 454.9 206.8 456.9zM251 455.2C248.1 455.9 246.1 457.8 246.4 460.1C246.7 462.1 249.3 463.4 252.3 462.7C255.2 462 257.2 460.1 256.9 458.1C256.6 456.2 253.9 454.9 251 455.2zM316.8 72C178.1 72 72 177.3 72 316C72 426.9 141.8 521.8 241.5 555.2C254.3 557.5 258.8 549.6 258.8 543.1C258.8 536.9 258.5 502.7 258.5 481.7C258.5 481.7 188.5 496.7 173.8 451.9C173.8 451.9 162.4 422.8 146 415.3C146 415.3 123.1 399.6 147.6 399.9C147.6 399.9 172.5 401.9 186.2 425.7C208.1 464.3 244.8 453.2 259.1 446.6C261.4 430.6 267.9 419.5 275.1 412.9C219.2 406.7 162.8 398.6 162.8 302.4C162.8 274.9 170.4 261.1 186.4 243.5C183.8 237 175.3 210.2 189 175.6C209.9 169.1 258 202.6 258 202.6C278 197 299.5 194.1 320.8 194.1C342.1 194.1 363.6 197 383.6 202.6C383.6 202.6 431.7 169 452.6 175.6C466.3 210.3 457.8 237 455.2 243.5C471.2 261.2 481 275 481 302.4C481 398.9 422.1 406.6 366.2 412.9C375.4 420.8 383.2 435.8 383.2 459.3C383.2 493 382.9 534.7 382.9 542.9C382.9 549.4 387.5 557.3 400.2 555C500.2 521.8 568 426.9 568 316C568 177.3 455.5 72 316.8 72zM169.2 416.9C167.9 417.9 168.2 420.2 169.9 422.1C171.5 423.7 173.8 424.4 175.1 423.1C176.4 422.1 176.1 419.8 174.4 417.9C172.8 416.3 170.5 415.6 169.2 416.9zM158.4 408.8C157.7 410.1 158.7 411.7 160.7 412.7C162.3 413.7 164.3 413.4 165 412C165.7 410.7 164.7 409.1 162.7 408.1C160.7 407.5 159.1 407.8 158.4 408.8zM190.8 444.4C189.2 445.7 189.8 448.7 192.1 450.6C194.4 452.9 197.3 453.2 198.6 451.6C199.9 450.3 199.3 447.3 197.3 445.4C195.1 443.1 192.1 442.8 190.8 444.4zM179.4 429.7C177.8 430.7 177.8 433.3 179.4 435.6C181 437.9 183.7 438.9 185 437.9C186.6 436.6 186.6 434 185 431.7C183.6 429.4 181 428.4 179.4 429.7z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

1
ios/assets/icons/x.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M453.2 112L523.8 112L369.6 288.2L551 528L409 528L297.7 382.6L170.5 528L99.8 528L264.7 339.5L90.8 112L236.4 112L336.9 244.9L453.2 112zM428.4 485.8L467.5 485.8L215.1 152L173.1 152L428.4 485.8z"/></svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M581.7 188.1C575.5 164.4 556.9 145.8 533.4 139.5C490.9 128 320.1 128 320.1 128C320.1 128 149.3 128 106.7 139.5C83.2 145.8 64.7 164.4 58.4 188.1C47 231 47 320.4 47 320.4C47 320.4 47 409.8 58.4 452.7C64.7 476.3 83.2 494.2 106.7 500.5C149.3 512 320.1 512 320.1 512C320.1 512 490.9 512 533.5 500.5C557 494.2 575.5 476.3 581.8 452.7C593.2 409.8 593.2 320.4 593.2 320.4C593.2 320.4 593.2 231 581.8 188.1zM264.2 401.6L264.2 239.2L406.9 320.4L264.2 401.6z"/></svg>

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
ios/assets/logo_old.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
ios/assets/logo_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,9 +1,7 @@
diff --git a/app.config.js b/app.config.js
index ca417206b..0807d7cd3 100644
--- a/app.config.js --- a/app.config.js
+++ b/app.config.js +++ b/app.config.js
@@ -18,10 +18,7 @@ module.exports = function (_config) { @@ -23,10 +23,7 @@
const IS_DEV = !IS_TESTFLIGHT || !IS_PRODUCTION const IS_DEV = !IS_TESTFLIGHT && !IS_PRODUCTION
const ASSOCIATED_DOMAINS = [ const ASSOCIATED_DOMAINS = [
- 'applinks:bsky.app', - 'applinks:bsky.app',
@@ -14,7 +12,7 @@ index ca417206b..0807d7cd3 100644
// When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port. // When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port.
...(IS_DEV || IS_TESTFLIGHT ? [] : []), ...(IS_DEV || IS_TESTFLIGHT ? [] : []),
] ]
@@ -33,27 +30,25 @@ module.exports = function (_config) { @@ -38,27 +35,25 @@
return { return {
expo: { expo: {
version: VERSION, version: VERSION,
@@ -29,7 +27,7 @@ index ca417206b..0807d7cd3 100644
policy: 'appVersion', policy: 'appVersion',
}, },
- icon: './assets/app-icons/ios_icon_default_next.png', - icon: './assets/app-icons/ios_icon_default_next.png',
+ icon: './assets/logo.png', + icon: './assets/app-icon.png',
userInterfaceStyle: 'automatic', userInterfaceStyle: 'automatic',
primaryColor: '#1083fe', primaryColor: '#1083fe',
newArchEnabled: false, newArchEnabled: false,
@@ -45,25 +43,25 @@ index ca417206b..0807d7cd3 100644
- PLATFORM === 'web' // web build doesn't like .icon files - PLATFORM === 'web' // web build doesn't like .icon files
- ? './assets/app-icons/ios_icon_default_next.png' - ? './assets/app-icons/ios_icon_default_next.png'
- : './assets/app-icons/ios_icon_default.icon', - : './assets/app-icons/ios_icon_default.icon',
+ icon: './assets/logo.png', + icon: './assets/app-icon.png',
infoPlist: { infoPlist: {
UIBackgroundModes: ['remote-notification'], UIBackgroundModes: ['remote-notification'],
NSCameraUsageDescription: NSCameraUsageDescription:
@@ -113,7 +108,7 @@ module.exports = function (_config) { @@ -118,7 +113,7 @@
entitlements: { entitlements: {
'com.apple.developer.kernel.increased-memory-limit': true, 'com.apple.developer.kernel.increased-memory-limit': true,
'com.apple.developer.kernel.extended-virtual-addressing': true, 'com.apple.developer.kernel.extended-virtual-addressing': true,
- 'com.apple.security.application-groups': 'group.app.bsky', - 'com.apple.security.application-groups': 'group.app.bsky',
+ 'com.apple.security.application-groups': 'group.ai.syui.at', + 'com.apple.security.application-groups': 'group.ai.syui.at',
// 'com.apple.developer.device-information.user-assigned-device-name': true,
}, },
privacyManifests: { privacyManifests: {
NSPrivacyCollectedDataTypes: [ @@ -181,14 +176,14 @@
@@ -175,14 +170,14 @@ module.exports = function (_config) {
barStyle: 'light-content', barStyle: 'light-content',
}, },
android: { android: {
- icon: './assets/app-icons/android_icon_default_next.png', - icon: './assets/app-icons/android_icon_default_next.png',
+ icon: './assets/logo.png', + icon: './assets/app-icon.png',
adaptiveIcon: { adaptiveIcon: {
foregroundImage: './assets/icon-android-foreground.png', foregroundImage: './assets/icon-android-foreground.png',
monochromeImage: './assets/icon-android-monochrome.png', monochromeImage: './assets/icon-android-monochrome.png',
@@ -75,38 +73,16 @@ index ca417206b..0807d7cd3 100644
intentFilters: [ intentFilters: [
{ {
action: 'VIEW', action: 'VIEW',
@@ -190,7 +185,7 @@ module.exports = function (_config) { @@ -196,7 +191,7 @@
data: [ data: [
{ {
scheme: 'https', scheme: 'https',
- host: 'bsky.app', - host: 'bsky.app',
+ host: 'syu.is', + host: 'syu.is',
}, },
IS_DEV && { ...(IS_DEV
scheme: 'http', ? [
@@ -213,9 +208,9 @@ module.exports = function (_config) { @@ -280,7 +275,6 @@
: undefined,
codeSigningMetadata: UPDATES_ENABLED
? {
- keyid: 'main',
- alg: 'rsa-v1_5-sha256',
- }
+ keyid: 'main',
+ alg: 'rsa-v1_5-sha256',
+ }
: undefined,
checkAutomatically: 'NEVER',
},
@@ -225,7 +220,7 @@ module.exports = function (_config) {
'expo-web-browser',
[
'react-native-edge-to-edge',
- {android: {enforceNavigationBarContrast: false}},
+ { android: { enforceNavigationBarContrast: false } },
],
USE_SENTRY && [
'@sentry/react-native/expo',
@@ -265,7 +260,6 @@ module.exports = function (_config) {
networkInstrumentation: true, networkInstrumentation: true,
}, },
], ],
@@ -114,7 +90,7 @@ index ca417206b..0807d7cd3 100644
'./plugins/withGradleJVMHeapSizeIncrease.js', './plugins/withGradleJVMHeapSizeIncrease.js',
'./plugins/withAndroidManifestLargeHeapPlugin.js', './plugins/withAndroidManifestLargeHeapPlugin.js',
'./plugins/withAndroidManifestFCMIconPlugin.js', './plugins/withAndroidManifestFCMIconPlugin.js',
@@ -273,8 +267,6 @@ module.exports = function (_config) { @@ -288,8 +282,6 @@
'./plugins/withAndroidStylesAccentColorPlugin.js', './plugins/withAndroidStylesAccentColorPlugin.js',
'./plugins/withAndroidDayNightThemePlugin.js', './plugins/withAndroidDayNightThemePlugin.js',
'./plugins/withAndroidNoJitpackPlugin.js', './plugins/withAndroidNoJitpackPlugin.js',
@@ -123,16 +99,7 @@ index ca417206b..0807d7cd3 100644
[ [
'expo-font', 'expo-font',
{ {
@@ -387,7 +379,7 @@ module.exports = function (_config) { @@ -417,30 +409,7 @@
},
},
],
- ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}],
+ ['expo-screen-orientation', { initialOrientation: 'PORTRAIT_UP' }],
['expo-location'],
[
'expo-contacts',
@@ -402,30 +394,7 @@ module.exports = function (_config) {
build: { build: {
experimental: { experimental: {
ios: { ios: {

View File

@@ -38,7 +38,7 @@ index 231447b4f..a44b3da05 100644
-const HELP_DESK_LANG = 'en-us' -const HELP_DESK_LANG = 'en-us'
-export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}` -export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
+const HELP_DESK_LANG = 'ja-jp' +const HELP_DESK_LANG = 'ja-jp'
+export const HELP_DESK_URL = 'https://syu.is/about/support/help' +export const HELP_DESK_URL = '/support/license'
export const EMBED_SERVICE = 'https://embed.bsky.app' export const EMBED_SERVICE = 'https://embed.bsky.app'
export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download' export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download'
@@ -102,8 +102,8 @@ index 231447b4f..a44b3da05 100644
export const webLinks = { export const webLinks = {
- tos: `https://bsky.social/about/support/tos`, - tos: `https://bsky.social/about/support/tos`,
- privacy: `https://bsky.social/about/support/privacy-policy`, - privacy: `https://bsky.social/about/support/privacy-policy`,
+ tos: `https://syu.is/about/support/tos`, + tos: `/support/tos`,
+ privacy: `https://syu.is/about/support/privacy-policy`, + privacy: `/support/privacy-policy`,
community: `https://bsky.social/about/support/community-guidelines`, community: `https://bsky.social/about/support/community-guidelines`,
communityDeprecated: `https://bsky.social/about/support/community-guidelines-deprecated`, communityDeprecated: `https://bsky.social/about/support/community-guidelines-deprecated`,
} }

View File

@@ -21,11 +21,11 @@ index f325539c7..3e2c7b3eb 100644
Support: '/support', Support: '/support',
- PrivacyPolicy: '/support/privacy', - PrivacyPolicy: '/support/privacy',
- TermsOfService: '/support/tos', - TermsOfService: '/support/tos',
+ PrivacyPolicy: 'https://syu.is/about/support/privacy-policy', + PrivacyPolicy: ['/support/privacy-policy', '/about/support/privacy-policy'],
+ TermsOfService: 'https://syu.is/about/support/tos', + TermsOfService: ['/support/tos', '/about/support/tos'],
CommunityGuidelines: '/support/community-guidelines', CommunityGuidelines: '/support/community-guidelines',
+ License: 'https://syu.is/about/support/license', + License: ['/support/license', '/about/support/license'],
+ AppInfo: 'https://syu.is/about/support/app', + AppInfo: '/support/app',
CopyrightPolicy: '/support/copyright', CopyrightPolicy: '/support/copyright',
// hashtags // hashtags
Hashtag: '/hashtag/:tag', Hashtag: '/hashtag/:tag',

View File

@@ -1,483 +1,47 @@
diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
index 6b8257b91..48ba7909e 100644
--- a/src/screens/Settings/AboutSettings.tsx --- a/src/screens/Settings/AboutSettings.tsx
+++ b/src/screens/Settings/AboutSettings.tsx +++ b/src/screens/Settings/AboutSettings.tsx
@@ -80,7 +80,7 @@ export function AboutSettingsScreen({}: Props) { @@ -79,7 +79,7 @@
<Layout.Content> <Layout.Content>
<SettingsList.Container> <SettingsList.Container>
<SettingsList.LinkItem <SettingsList.LinkItem
- to="https://bsky.social/about/support/tos" - to="https://bsky.social/about/support/tos"
+ to="https://syu.is/about/support/tos" + to="/support/tos"
label={_(msg`Terms of Service`)}> label={_(msg`Terms of Service`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} /> <SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText> <SettingsList.ItemText>
@@ -88,7 +88,7 @@ export function AboutSettingsScreen({}: Props) { @@ -87,7 +87,7 @@
</SettingsList.ItemText> </SettingsList.ItemText>
</SettingsList.LinkItem> </SettingsList.LinkItem>
<SettingsList.LinkItem <SettingsList.LinkItem
- to="https://bsky.social/about/support/privacy-policy" - to="https://bsky.social/about/support/privacy-policy"
+ to="https://syu.is/about/support/privacy-policy" + to="/support/privacy-policy"
label={_(msg`Privacy Policy`)}> label={_(msg`Privacy Policy`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} /> <SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText> <SettingsList.ItemText>
diff --git a/src/screens/Takendown.tsx b/src/screens/Takendown.tsx
index 77f219e55..53f5e0cc0 100644
--- a/src/screens/Takendown.tsx --- a/src/screens/Takendown.tsx
+++ b/src/screens/Takendown.tsx +++ b/src/screens/Takendown.tsx
@@ -217,10 +217,10 @@ export function Takendown() { @@ -210,10 +210,10 @@
<Trans> <Trans>
Your account was found to be in violation of the{' '} Your account was found to be in violation of the{' '}
<SimpleInlineLinkText <SimpleInlineLinkText
- label={_(msg`Bluesky Social Terms of Service`)} - label={_(msg`Bluesky Social Terms of Service`)}
- to="https://bsky.social/about/support/tos" - to="https://bsky.social/about/support/tos"
+ label={_(msg`syu.is Terms of Service`)} + label={_(msg`syu.is Terms of Service`)}
+ to="https://syu.is/about/support/tos" + to="/support/tos"
style={[a.text_md, a.leading_snug]}> style={[a.text_md, a.leading_snug]}>
- Bluesky Social Terms of Service - Bluesky Social Terms of Service
+ syu.is Terms of Service + syu.is Terms of Service
</SimpleInlineLinkText> </SimpleInlineLinkText>
. You have been sent an email outlining the specific violation . You have been sent an email outlining the specific violation
and suspension period, if applicable. You can appeal this and suspension period, if applicable. You can appeal this
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index e058e2883..8daf41089 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -1,23 +1,16 @@
import React from 'react'
import {ActivityIndicator, StyleSheet} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
-
import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useOTAUpdates} from '#/lib/hooks/useOTAUpdates'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
-import {
- type HomeTabNavigatorParams,
- type NativeStackScreenProps,
-} from '#/lib/routes/types'
+import {type HomeTabNavigatorParams, type NativeStackScreenProps} from '#/lib/routes/types'
import {logEvent} from '#/lib/statsig/statsig'
import {isWeb} from '#/platform/detection'
import {emitSoftReset} from '#/state/events'
-import {
- type SavedFeedSourceInfo,
- usePinnedFeedsInfos,
-} from '#/state/queries/feed'
+import {type SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed'
import {type FeedDescriptor, type FeedParams} from '#/state/queries/post-feed'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
@@ -27,11 +20,7 @@ import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
import {FeedPage} from '#/view/com/feeds/FeedPage'
import {HomeHeader} from '#/view/com/home/HomeHeader'
-import {
- Pager,
- type PagerRef,
- type RenderTabBarFnProps,
-} from '#/view/com/pager/Pager'
+import {Pager, type PagerRef, type RenderTabBarFnProps} from '#/view/com/pager/Pager'
import {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState'
import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState'
import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed'
@@ -39,97 +28,90 @@ import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned'
import * as Layout from '#/components/Layout'
import {useDemoMode} from '#/storage/hooks/demo-mode'
+const SYU_IS_FEED_URI = 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app'
+
+const DEFAULT_PINNED_FEEDS: any[] = [{
+ feedDescriptor: 'following',
+ displayName: 'Following',
+ id: 'following',
+ uri: 'following',
+ type: 'feed',
+ savedFeed: undefined,
+ pinned: true,
+ route: { href: '/', name: 'Home', params: {} },
+ cid: '',
+ avatar: '',
+ creatorDid: '',
+ creatorHandle: '',
+}, {
+ feedDescriptor: `feedgen|${SYU_IS_FEED_URI}`,
+ displayName: 'Feeds',
+ id: SYU_IS_FEED_URI,
+ uri: SYU_IS_FEED_URI,
+ type: 'feed',
+ savedFeed: {
+ type: 'feed',
+ value: SYU_IS_FEED_URI,
+ pinned: true,
+ },
+ pinned: true,
+ route: { href: '/', name: 'Home', params: {} },
+ cid: '',
+ avatar: '',
+ creatorDid: '',
+ creatorHandle: '',
+}]
+
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'>
export function HomeScreen(props: Props) {
const {setShowLoggedOut} = useLoggedOutViewControls()
const {data: preferences} = usePreferencesQuery()
const {currentAccount} = useSession()
- const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
- usePinnedFeedsInfos()
+ const {data: pinnedFeedInfos} = usePinnedFeedsInfos()
+
+ const safePreferences = preferences || { feedViewPrefs: { lab_mergeFeedEnabled: false }, savedFeeds: [] } as any
+ // Use user's pinned feeds when logged in and available, otherwise use defaults
+ const safePinnedFeedInfos = !currentAccount
+ ? DEFAULT_PINNED_FEEDS.filter(f => f.feedDescriptor !== 'following')
+ : (pinnedFeedInfos && pinnedFeedInfos.length > 0)
+ ? pinnedFeedInfos
+ : DEFAULT_PINNED_FEEDS
React.useEffect(() => {
if (isWeb && !currentAccount) {
const getParams = new URLSearchParams(window.location.search)
const splash = getParams.get('splash')
- if (splash === 'true') {
- setShowLoggedOut(true)
- return
- }
+ if (splash === 'true') { setShowLoggedOut(true); return }
}
-
const params = props.route.params
- if (
- currentAccount &&
- props.route.name === 'Start' &&
- params?.name &&
- params?.rkey
- ) {
- props.navigation.navigate('StarterPack', {
- rkey: params.rkey,
- name: params.name,
- })
+ if (currentAccount && props.route.name === 'Start' && params?.name && params?.rkey) {
+ props.navigation.navigate('StarterPack', { rkey: params.rkey, name: params.name })
}
- }, [
- currentAccount,
- props.navigation,
- props.route.name,
- props.route.params,
- setShowLoggedOut,
- ])
+ }, [currentAccount, props.navigation, props.route.name, props.route.params, setShowLoggedOut])
- if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
- return (
- <Layout.Screen testID="HomeScreen">
- <HomeScreenReady
- {...props}
- preferences={preferences}
- pinnedFeedInfos={pinnedFeedInfos}
- />
- </Layout.Screen>
- )
- } else {
- return (
- <Layout.Screen>
- <Layout.Center style={styles.loading}>
- <ActivityIndicator size="large" />
- </Layout.Center>
- </Layout.Screen>
- )
- }
+ return (
+ <Layout.Screen testID="HomeScreen">
+ <HomeScreenReady {...props} preferences={safePreferences} pinnedFeedInfos={safePinnedFeedInfos as any} />
+ </Layout.Screen>
+ )
}
-function HomeScreenReady({
- preferences,
- pinnedFeedInfos,
-}: Props & {
- preferences: UsePreferencesQueryResponse
- pinnedFeedInfos: SavedFeedSourceInfo[]
-}) {
- const allFeeds = React.useMemo(
- () => pinnedFeedInfos.map(f => f.feedDescriptor),
- [pinnedFeedInfos],
- )
- const maybeRawSelectedFeed: FeedDescriptor | undefined =
- useSelectedFeed() ?? allFeeds[0]
+function HomeScreenReady({preferences, pinnedFeedInfos}: any) {
+ const allFeeds = React.useMemo(() => pinnedFeedInfos.map(f => f.feedDescriptor), [pinnedFeedInfos])
+ const maybeRawSelectedFeed = useSelectedFeed() ?? allFeeds[0]
const setSelectedFeed = useSetSelectedFeed()
const maybeFoundIndex = allFeeds.indexOf(maybeRawSelectedFeed)
const selectedIndex = Math.max(0, maybeFoundIndex)
- const maybeSelectedFeed: FeedDescriptor | undefined = allFeeds[selectedIndex]
+ const maybeSelectedFeed = allFeeds[selectedIndex]
const requestNotificationsPermission = useRequestNotificationsPermission()
useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName)
useOTAUpdates()
-
- React.useEffect(() => {
- requestNotificationsPermission('Home')
- }, [requestNotificationsPermission])
+ React.useEffect(() => { requestNotificationsPermission('Home') }, [requestNotificationsPermission])
const pagerRef = React.useRef<PagerRef>(null)
const lastPagerReportedIndexRef = React.useRef(selectedIndex)
React.useLayoutEffect(() => {
- // Since the pager is not a controlled component, adjust it imperatively
- // if the selected index gets out of sync with what it last reported.
- // This is supposed to only happen on the web when you use the right nav.
if (selectedIndex !== lastPagerReportedIndexRef.current) {
lastPagerReportedIndexRef.current = selectedIndex
pagerRef.current?.setPage(selectedIndex)
@@ -138,205 +120,43 @@ function HomeScreenReady({
const {hasSession} = useSession()
const setMinimalShellMode = useSetMinimalShellMode()
- useFocusEffect(
- React.useCallback(() => {
- setMinimalShellMode(false)
- }, [setMinimalShellMode]),
- )
+ useFocusEffect(React.useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode]))
- useFocusEffect(
- useNonReactiveCallback(() => {
- if (maybeSelectedFeed) {
- logEvent('home:feedDisplayed', {
- index: selectedIndex,
- feedType: maybeSelectedFeed.split('|')[0],
- feedUrl: maybeSelectedFeed,
- reason: 'focus',
- })
- }
- }),
- )
-
- const onPageSelected = React.useCallback(
- (index: number) => {
- setMinimalShellMode(false)
- const maybeFeed = allFeeds[index]
+ const onPageSelected = React.useCallback((index) => {
+ setMinimalShellMode(false)
+ const maybeFeed = allFeeds[index]
+ lastPagerReportedIndexRef.current = index
+ setSelectedFeed(maybeFeed)
+ }, [setSelectedFeed, setMinimalShellMode, allFeeds])
- // Mutate the ref before setting state to avoid the imperative syncing effect
- // above from starting a loop on Android when swiping back and forth.
- lastPagerReportedIndexRef.current = index
- setSelectedFeed(maybeFeed)
-
- if (maybeFeed) {
- logEvent('home:feedDisplayed', {
- index,
- feedType: maybeFeed.split('|')[0],
- feedUrl: maybeFeed,
- })
- }
- },
- [setSelectedFeed, setMinimalShellMode, allFeeds],
- )
-
- const onPressSelected = React.useCallback(() => {
- emitSoftReset()
- }, [])
-
- const onPageScrollStateChanged = React.useCallback(
- (state: 'idle' | 'dragging' | 'settling') => {
- 'worklet'
- if (state === 'dragging') {
- setMinimalShellMode(false)
- }
- },
- [setMinimalShellMode],
- )
+ const onPressSelected = React.useCallback(() => { emitSoftReset() }, [])
+ const onPageScrollStateChanged = React.useCallback((state) => {
+ 'worklet'
+ if (state === 'dragging') setMinimalShellMode(false)
+ }, [setMinimalShellMode])
const [demoMode] = useDemoMode()
+ const renderTabBar = React.useCallback((props) => {
+ return <HomeHeader key="FEEDS_TAB_BAR" {...props} testID="homeScreenFeedTabs" onPressSelected={onPressSelected} feeds={pinnedFeedInfos} />
+ }, [onPressSelected, pinnedFeedInfos])
- const renderTabBar = React.useCallback(
- (props: RenderTabBarFnProps) => {
- if (demoMode) {
- return (
- <HomeHeader
- key="FEEDS_TAB_BAR"
- {...props}
- testID="homeScreenFeedTabs"
- onPressSelected={onPressSelected}
- // @ts-ignore
- feeds={[{displayName: 'Following'}, {displayName: 'Discover'}]}
- />
- )
- }
- return (
- <HomeHeader
- key="FEEDS_TAB_BAR"
- {...props}
- testID="homeScreenFeedTabs"
- onPressSelected={onPressSelected}
- feeds={pinnedFeedInfos}
- />
- )
- },
- [onPressSelected, pinnedFeedInfos, demoMode],
- )
-
- const renderFollowingEmptyState = React.useCallback(() => {
- return <FollowingEmptyState />
- }, [])
+ const renderFollowingEmptyState = React.useCallback(() => <FollowingEmptyState />, [])
+ const renderCustomFeedEmptyState = React.useCallback(() => <CustomFeedEmptyState />, [])
- const renderCustomFeedEmptyState = React.useCallback(() => {
- return <CustomFeedEmptyState />
- }, [])
+ const homeFeedParams = React.useMemo(() => ({
+ mergeFeedEnabled: false, mergeFeedSources: []
+ }), [preferences])
- const homeFeedParams = React.useMemo<FeedParams>(() => {
- return {
- mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled),
- mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled
- ? preferences.savedFeeds
- .filter(f => f.type === 'feed' || f.type === 'list')
- .map(f => f.value)
- : [],
- }
- }, [preferences])
-
- if (demoMode) {
- return (
- <Pager
- ref={pagerRef}
- testID="homeScreen"
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}
- initialPage={selectedIndex}>
- <FeedPage
- testID="demoFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed="demo"
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
- <FeedPage
- testID="customFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
- </Pager>
- )
- }
-
- return hasSession ? (
- <Pager
- key={allFeeds.join(',')}
- ref={pagerRef}
- testID="homeScreen"
- initialPage={selectedIndex}
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}>
- {pinnedFeedInfos.length ? (
- pinnedFeedInfos.map((feedInfo, index) => {
+ return (
+ <Pager ref={pagerRef} testID="homeScreen" initialPage={selectedIndex} onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} renderTabBar={renderTabBar}>
+ {pinnedFeedInfos.map((feedInfo, index) => {
const feed = feedInfo.feedDescriptor
if (feed === 'following') {
- return (
- <FeedPage
- key={feed}
- testID="followingFeedPage"
- isPageFocused={maybeSelectedFeed === feed}
- isPageAdjacent={Math.abs(selectedIndex - index) === 1}
- feed={feed}
- feedParams={homeFeedParams}
- renderEmptyState={renderFollowingEmptyState}
- renderEndOfFeed={FollowingEndOfFeed}
- feedInfo={feedInfo}
- />
- )
+ return <FeedPage key={feed} testID="followingFeedPage" isPageFocused={maybeSelectedFeed === feed} isPageAdjacent={Math.abs(selectedIndex - index) === 1} feed={feed} feedParams={homeFeedParams} renderEmptyState={renderFollowingEmptyState} renderEndOfFeed={FollowingEndOfFeed} feedInfo={feedInfo} />
}
- const savedFeedConfig = feedInfo.savedFeed
- return (
- <FeedPage
- key={feed}
- testID="customFeedPage"
- isPageFocused={maybeSelectedFeed === feed}
- isPageAdjacent={Math.abs(selectedIndex - index) === 1}
- feed={feed}
- renderEmptyState={renderCustomFeedEmptyState}
- savedFeedConfig={savedFeedConfig}
- feedInfo={feedInfo}
- />
- )
- })
- ) : (
- <NoFeedsPinned preferences={preferences} />
- )}
- </Pager>
- ) : (
- <Pager
- testID="homeScreen"
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}>
- <FeedPage
- testID="customFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
+ return <FeedPage key={feed} testID="customFeedPage" isPageFocused={maybeSelectedFeed === feed} isPageAdjacent={Math.abs(selectedIndex - index) === 1} feed={feed} renderEmptyState={renderCustomFeedEmptyState} savedFeedConfig={feedInfo.savedFeed} feedInfo={feedInfo} />
+ })}
</Pager>
)
}
-
-const styles = StyleSheet.create({
- loading: {
- height: '100%',
- alignContent: 'center',
- justifyContent: 'center',
- paddingBottom: 100,
- },
-})
+const styles = StyleSheet.create({ loading: { height: '100%', alignContent: 'center', justifyContent: 'center', paddingBottom: 100 } })
diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx
index a89eaadc4..1da393f03 100644
--- a/src/view/screens/PrivacyPolicy.tsx --- a/src/view/screens/PrivacyPolicy.tsx
+++ b/src/view/screens/PrivacyPolicy.tsx +++ b/src/view/screens/PrivacyPolicy.tsx
@@ -1,52 +1,13 @@ @@ -1,52 +1,49 @@
import React from 'react' import React from 'react'
-import {View} from 'react-native' -import {View} from 'react-native'
-import {msg, Trans} from '@lingui/macro' -import {msg} from '@lingui/core/macro'
-import {useLingui} from '@lingui/react' -import {useLingui} from '@lingui/react'
-import {Trans} from '@lingui/react/macro'
-import {useFocusEffect} from '@react-navigation/native' -import {useFocusEffect} from '@react-navigation/native'
- -
-import {usePalette} from '#/lib/hooks/usePalette' -import {usePalette} from '#/lib/hooks/usePalette'
@@ -490,25 +54,28 @@ index a89eaadc4..1da393f03 100644
-import {TextLink} from '#/view/com/util/Link' -import {TextLink} from '#/view/com/util/Link'
-import {Text} from '#/view/com/util/text/Text' -import {Text} from '#/view/com/util/text/Text'
-import {ScrollView} from '#/view/com/util/Views' -import {ScrollView} from '#/view/com/util/Views'
+import { WebView } from 'react-native-webview' +import {ScrollView} from 'react-native'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
-import {ViewHeader} from '../com/util/ViewHeader' -import {ViewHeader} from '../com/util/ViewHeader'
- +import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'> -type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'>
-export const PrivacyPolicyScreen = (_props: Props) => { -export const PrivacyPolicyScreen = (_props: Props) => {
- const pal = usePalette('default') - const pal = usePalette('default')
- const {_} = useLingui() - const {_} = useLingui()
- const setMinimalShellMode = useSetMinimalShellMode() - const setMinimalShellMode = useSetMinimalShellMode()
- +export function PrivacyPolicyScreen() {
+ useSetTitle('Privacy Policy')
+ const t = useTheme()
- useFocusEffect( - useFocusEffect(
- React.useCallback(() => { - React.useCallback(() => {
- setMinimalShellMode(false) - setMinimalShellMode(false)
- }, [setMinimalShellMode]), - }, [setMinimalShellMode]),
- ) - )
+import {useSetTitle} from '#/lib/hooks/useSetTitle' -
+export function PrivacyPolicyScreen() {
+ useSetTitle('Privacy Policy')
return ( return (
<Layout.Screen> <Layout.Screen>
- <ViewHeader title={_(msg`Privacy Policy`)} /> - <ViewHeader title={_(msg`Privacy Policy`)} />
@@ -526,20 +93,50 @@ index a89eaadc4..1da393f03 100644
- </Text> - </Text>
- </View> - </View>
- <View style={s.footerSpacer} /> - <View style={s.footerSpacer} />
- </ScrollView> + <ScrollView
+ <WebView source={{ uri: 'https://syu.is/about/support/privacy-policy' }} style={{ flex: 1 }} /> + style={[a.flex_1, {backgroundColor: t.palette.white}]}
+ contentContainerStyle={[a.p_lg, a.pt_5xl, a.pb_5xl]}>
+ <Text style={[a.text_2xl, a.font_bold, a.mb_lg]}>Privacy Policy</Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Data Collection</Text>
+ <Text style={[a.mb_md]}>
+ syu.is collects minimal data necessary to provide the service. This includes your account information, posts, and interactions on the AT Protocol network.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Data Storage</Text>
+ <Text style={[a.mb_md]}>
+ Your data is stored on the AT Protocol network. Posts and profile information are public by default as part of the decentralized social network.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Third Parties</Text>
+ <Text style={[a.mb_md]}>
+ We do not sell your personal information to third parties. Your data may be visible to other users and services on the AT Protocol network.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Contact</Text>
+ <Text style={[a.mb_md]}>
+ For privacy-related questions, please contact the administrator.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_xl, a.mb_md]}>日本語</Text>
+ <Text style={[a.mb_md]}>
+ syu.isはサービス提供に必要な最小限のデータのみを収集します。投稿やプロフィール情報はAT Protocolネットワーク上で公開されます。個人情報を第三者に販売することはありません。
+ </Text>
+
+ <Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
+ Last updated: 2026
+ </Text>
</ScrollView>
</Layout.Screen> </Layout.Screen>
) )
}
diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx
index d843c713c..b81767bd5 100644
--- a/src/view/screens/TermsOfService.tsx --- a/src/view/screens/TermsOfService.tsx
+++ b/src/view/screens/TermsOfService.tsx +++ b/src/view/screens/TermsOfService.tsx
@@ -1,50 +1,13 @@ @@ -1,50 +1,49 @@
import React from 'react' import React from 'react'
-import {View} from 'react-native' -import {View} from 'react-native'
-import {msg, Trans} from '@lingui/macro' -import {msg} from '@lingui/core/macro'
-import {useLingui} from '@lingui/react' -import {useLingui} from '@lingui/react'
-import {Trans} from '@lingui/react/macro'
-import {useFocusEffect} from '@react-navigation/native' -import {useFocusEffect} from '@react-navigation/native'
- -
-import {usePalette} from '#/lib/hooks/usePalette' -import {usePalette} from '#/lib/hooks/usePalette'
@@ -552,25 +149,28 @@ index d843c713c..b81767bd5 100644
-import {TextLink} from '#/view/com/util/Link' -import {TextLink} from '#/view/com/util/Link'
-import {Text} from '#/view/com/util/text/Text' -import {Text} from '#/view/com/util/text/Text'
-import {ScrollView} from '#/view/com/util/Views' -import {ScrollView} from '#/view/com/util/Views'
+import { WebView } from 'react-native-webview' +import {ScrollView} from 'react-native'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
-import {ViewHeader} from '../com/util/ViewHeader' -import {ViewHeader} from '../com/util/ViewHeader'
- +import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'TermsOfService'> -type Props = NativeStackScreenProps<CommonNavigatorParams, 'TermsOfService'>
-export const TermsOfServiceScreen = (_props: Props) => { -export const TermsOfServiceScreen = (_props: Props) => {
- const pal = usePalette('default') - const pal = usePalette('default')
- const setMinimalShellMode = useSetMinimalShellMode() - const setMinimalShellMode = useSetMinimalShellMode()
- const {_} = useLingui() - const {_} = useLingui()
- +export function TermsOfServiceScreen() {
+ useSetTitle('Terms of Service')
+ const t = useTheme()
- useFocusEffect( - useFocusEffect(
- React.useCallback(() => { - React.useCallback(() => {
- setMinimalShellMode(false) - setMinimalShellMode(false)
- }, [setMinimalShellMode]), - }, [setMinimalShellMode]),
- ) - )
+import {useSetTitle} from '#/lib/hooks/useSetTitle' -
+export function TermsOfServiceScreen() {
+ useSetTitle('Terms of Service')
return ( return (
<Layout.Screen> <Layout.Screen>
- <ViewHeader title={_(msg`Terms of Service`)} /> - <ViewHeader title={_(msg`Terms of Service`)} />
@@ -586,8 +186,39 @@ index d843c713c..b81767bd5 100644
- </Text> - </Text>
- </View> - </View>
- <View style={s.footerSpacer} /> - <View style={s.footerSpacer} />
- </ScrollView> + <ScrollView
+ <WebView source={{ uri: 'https://syu.is/about/support/tos' }} style={{ flex: 1 }} /> + style={[a.flex_1, {backgroundColor: t.palette.white}]}
+ contentContainerStyle={[a.p_lg, a.pt_5xl, a.pb_5xl]}>
+ <Text style={[a.text_2xl, a.font_bold, a.mb_lg]}>Terms of Service</Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Acceptance</Text>
+ <Text style={[a.mb_md]}>
+ By using syu.is, you agree to these terms. If you do not agree, please do not use the service.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Prohibited Content</Text>
+ <Text style={[a.mb_md]}>
+ Do not post illegal content, spam, or harass others. Do not impersonate others or spread misinformation.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Account Termination</Text>
+ <Text style={[a.mb_md]}>
+ The administrator reserves the right to suspend or terminate accounts that violate these terms.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Disclaimer</Text>
+ <Text style={[a.mb_md]}>
+ This service is provided "as is" without warranty of any kind.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_xl, a.mb_md]}>日本語</Text>
+ <Text style={[a.mb_md]}>
+ syu.isを利用することで、これらの利用規約に同意したものとみなします。違法なコンテンツの投稿、スパム、他者への嫌がらせは禁止です。管理者は規約違反のアカウントを停止する権利を有します。本サービスは現状のまま提供され、いかなる保証もありません。
+ </Text>
+
+ <Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
+ Last updated: 2026
+ </Text>
</ScrollView>
</Layout.Screen> </Layout.Screen>
) )
}

View File

@@ -1,8 +1,8 @@
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
index f76147ccf..36b4d7de1 100644 index 42a5fe417..8e7963512 100644
--- a/src/view/shell/Drawer.tsx --- a/src/view/shell/Drawer.tsx
+++ b/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx
@@ -292,17 +292,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { @@ -294,17 +294,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<> <>
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} /> <SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} /> <HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
@@ -20,7 +20,7 @@ index f76147ccf..36b4d7de1 100644
<ProfileMenuItem <ProfileMenuItem
isActive={isAtMyProfile} isActive={isAtMyProfile}
onPress={onPressProfile} onPress={onPressProfile}
@@ -357,17 +351,7 @@ let DrawerFooter = ({ @@ -359,17 +353,7 @@ let DrawerFooter = ({
), ),
}, },
]}> ]}>
@@ -39,18 +39,27 @@ index f76147ccf..36b4d7de1 100644
<Button <Button
label={_(msg`Get help`)} label={_(msg`Get help`)}
size="small" size="small"
@@ -695,12 +679,12 @@ function ExtraLinks() { @@ -697,15 +681,21 @@ function ExtraLinks() {
<InlineLinkText <InlineLinkText
style={[a.text_md]} style={[a.text_md]}
label={_(msg`Terms of Service`)} label={_(msg`Terms of Service`)}
- to="https://bsky.social/about/support/tos"> - to="https://bsky.social/about/support/tos">
+ to="https://syu.is/about/support/tos"> + to="/support/tos">
<Trans>Terms of Service</Trans> <Trans>Terms of Service</Trans>
</InlineLinkText> </InlineLinkText>
<InlineLinkText <InlineLinkText
style={[a.text_md]} style={[a.text_md]}
- to="https://bsky.social/about/support/privacy-policy" - to="https://bsky.social/about/support/privacy-policy"
+ to="https://syu.is/about/support/privacy-policy" + to="/support/privacy-policy"
label={_(msg`Privacy Policy`)}> label={_(msg`Privacy Policy`)}>
<Trans>Privacy Policy</Trans> <Trans>Privacy Policy</Trans>
</InlineLinkText> </InlineLinkText>
+ <InlineLinkText
+ style={[a.text_md]}
+ to="/support/license"
+ label="License">
+ License
+ </InlineLinkText>
{kawaii && (
<Text style={t.atoms.text_contrast_medium}>
<Trans>

View File

@@ -20,13 +20,13 @@ index 8365057e8..59c8506a2 100644
terms: { terms: {
overridePresentation: false, overridePresentation: false,
- to: `https://bsky.social/about/support/tos`, - to: `https://bsky.social/about/support/tos`,
+ to: `https://syu.is/about/support/tos`, + to: `/support/tos`,
label: _(msg`Terms of Service`), label: _(msg`Terms of Service`),
}, },
privacy: { privacy: {
overridePresentation: false, overridePresentation: false,
- to: `https://bsky.social/about/support/privacy-policy`, - to: `https://bsky.social/about/support/privacy-policy`,
+ to: `https://syu.is/about/support/privacy-policy`, + to: `/support/privacy-policy`,
label: _(msg`Privacy Policy`), label: _(msg`Privacy Policy`),
}, },
copyright: { copyright: {

View File

@@ -1,54 +1,5 @@
diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
index 3442d1bdf..8ed9e3d0d 100644
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -1,4 +1,5 @@
import {View} from 'react-native'
+import {Pressable, Linking} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg, Trans} from '@lingui/macro'
@@ -40,16 +41,6 @@ export const SplashScreen = ({
<View style={[a.pb_sm, a.pt_5xl]}>
<Logotype width={161} fill={t.atoms.text.color} />
</View>
-
- <Text
- style={[
- a.text_md,
- a.font_semi_bold,
- t.atoms.text_contrast_medium,
- a.text_center,
- ]}>
- <Trans>What's up?</Trans>
- </Text>
</View>
<View
@@ -102,6 +93,21 @@ export const SplashScreen = ({
<AppLanguageDropdown />
</View>
</View>
+ <View style={[a.pb_sm, a.justify_center, a.align_center]}>
+ <Pressable onPress={() => Linking.openURL('https://syu.is/about/support/license')}>
+ <Text
+ style={[
+ a.text_xs,
+ t.atoms.text_contrast_low,
+ {textDecorationLine: 'underline'},
+ ]}>
+ License
+ </Text>
+ </Pressable>
+ </View>
+ <View style={[a.pb_xl, a.justify_center, a.align_center]}>
+ <Text style={[a.text_xs, t.atoms.text_contrast_low]}>© syui</Text>
+ </View>
<View style={{height: insets.bottom}} />
</ErrorBoundary>
</Animated.View>
diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx
index 22dd23d7f..7ceb3800e 100644 index d9185d778..504f521a4 100644
--- a/src/view/com/auth/SplashScreen.web.tsx --- a/src/view/com/auth/SplashScreen.web.tsx
+++ b/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx
@@ -94,14 +94,6 @@ export const SplashScreen = ({ @@ -94,14 +94,6 @@ export const SplashScreen = ({

View File

@@ -2,7 +2,7 @@ diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.t
index 2fa5aa7de..3faf6a7b3 100644 index 2fa5aa7de..3faf6a7b3 100644
--- a/src/screens/Settings/Settings.tsx --- a/src/screens/Settings/Settings.tsx
+++ b/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx
@@ -205,26 +205,8 @@ export function SettingsScreen({}: Props) { @@ -203,26 +203,8 @@ export function SettingsScreen({}: Props) {
<Trans>Notifications</Trans> <Trans>Notifications</Trans>
</SettingsList.ItemText> </SettingsList.ItemText>
</SettingsList.LinkItem> </SettingsList.LinkItem>
@@ -14,9 +14,9 @@ index 2fa5aa7de..3faf6a7b3 100644
- <Trans>Content and media</Trans> - <Trans>Content and media</Trans>
- </SettingsList.ItemText> - </SettingsList.ItemText>
- </SettingsList.LinkItem> - </SettingsList.LinkItem>
- {isNative && - {IS_NATIVE &&
- findContactsEnabled && - findContactsEnabled &&
- !gate('disable_settings_find_contacts') && ( - !ax.features.enabled(ax.features.ImportContactsSettingsDisable) && (
- <SettingsList.LinkItem - <SettingsList.LinkItem
- to="/settings/find-contacts" - to="/settings/find-contacts"
- label={_(msg`Find friends from contacts`)}> - label={_(msg`Find friends from contacts`)}>
@@ -31,7 +31,7 @@ index 2fa5aa7de..3faf6a7b3 100644
<SettingsList.LinkItem <SettingsList.LinkItem
to="/settings/appearance" to="/settings/appearance"
label={_(msg`Appearance`)}> label={_(msg`Appearance`)}>
@@ -249,16 +231,6 @@ export function SettingsScreen({}: Props) { @@ -247,16 +229,6 @@ export function SettingsScreen({}: Props) {
<Trans>Languages</Trans> <Trans>Languages</Trans>
</SettingsList.ItemText> </SettingsList.ItemText>
</SettingsList.LinkItem> </SettingsList.LinkItem>

View File

@@ -1,8 +1,6 @@
diff --git a/src/view/com/posts/DiscoverFallbackHeader.tsx b/src/view/com/posts/DiscoverFallbackHeader.tsx
index e35a33aaf..a36f84ae0 100644
--- a/src/view/com/posts/DiscoverFallbackHeader.tsx --- a/src/view/com/posts/DiscoverFallbackHeader.tsx
+++ b/src/view/com/posts/DiscoverFallbackHeader.tsx +++ b/src/view/com/posts/DiscoverFallbackHeader.tsx
@@ -7,37 +7,5 @@ import {TextLink} from '../util/Link' @@ -7,37 +7,5 @@
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
export function DiscoverFallbackHeader() { export function DiscoverFallbackHeader() {
@@ -41,25 +39,23 @@ index e35a33aaf..a36f84ae0 100644
- ) - )
+ return null + return null
} }
diff --git a/src/view/com/posts/FollowingEmptyState.tsx b/src/view/com/posts/FollowingEmptyState.tsx
index 352cc1dc0..f477521af 100644
--- a/src/view/com/posts/FollowingEmptyState.tsx --- a/src/view/com/posts/FollowingEmptyState.tsx
+++ b/src/view/com/posts/FollowingEmptyState.tsx +++ b/src/view/com/posts/FollowingEmptyState.tsx
@@ -1,37 +1,14 @@ @@ -1,38 +1,15 @@
import React from 'react' import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
-import { -import {
- FontAwesomeIcon, - FontAwesomeIcon,
- type FontAwesomeIconStyle, - type FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome' -} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/react/macro'
-import {useNavigation} from '@react-navigation/native' -import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {MagnifyingGlassIcon} from '#/lib/icons' import {MagnifyingGlassIcon} from '#/lib/icons'
-import {type NavigationProp} from '#/lib/routes/types' -import {type NavigationProp} from '#/lib/routes/types'
import {s} from '#/lib/styles' import {s} from '#/lib/styles'
-import {isWeb} from '#/platform/detection' -import {IS_WEB} from '#/env'
-import {Button} from '../util/forms/Button' -import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
@@ -67,9 +63,9 @@ index 352cc1dc0..f477521af 100644
const pal = usePalette('default') const pal = usePalette('default')
- const palInverted = usePalette('inverted') - const palInverted = usePalette('inverted')
- const navigation = useNavigation<NavigationProp>() - const navigation = useNavigation<NavigationProp>()
-
- const onPressFindAccounts = React.useCallback(() => { - const onPressFindAccounts = React.useCallback(() => {
- if (isWeb) { - if (IS_WEB) {
- navigation.navigate('Search', {}) - navigation.navigate('Search', {})
- } else { - } else {
- navigation.navigate('SearchTab') - navigation.navigate('SearchTab')
@@ -80,10 +76,11 @@ index 352cc1dc0..f477521af 100644
- const onPressDiscoverFeeds = React.useCallback(() => { - const onPressDiscoverFeeds = React.useCallback(() => {
- navigation.navigate('Feeds') - navigation.navigate('Feeds')
- }, [navigation]) - }, [navigation])
-
return ( return (
<View style={styles.container}> <View style={styles.container}>
@@ -45,36 +22,6 @@ export function FollowingEmptyState() { <View style={styles.inner}>
@@ -45,36 +22,6 @@
happening. happening.
</Trans> </Trans>
</Text> </Text>
@@ -120,7 +117,7 @@ index 352cc1dc0..f477521af 100644
</View> </View>
</View> </View>
) )
@@ -98,13 +45,4 @@ const styles = StyleSheet.create({ @@ -98,13 +45,4 @@
marginLeft: 'auto', marginLeft: 'auto',
marginRight: 'auto', marginRight: 'auto',
}, },
@@ -134,24 +131,22 @@ index 352cc1dc0..f477521af 100644
- borderRadius: 30, - borderRadius: 30,
- }, - },
}) })
diff --git a/src/view/com/posts/FollowingEndOfFeed.tsx b/src/view/com/posts/FollowingEndOfFeed.tsx
index e3c84d782..efb55d406 100644
--- a/src/view/com/posts/FollowingEndOfFeed.tsx --- a/src/view/com/posts/FollowingEndOfFeed.tsx
+++ b/src/view/com/posts/FollowingEndOfFeed.tsx +++ b/src/view/com/posts/FollowingEndOfFeed.tsx
@@ -1,36 +1,13 @@ @@ -1,37 +1,14 @@
import React from 'react' import React from 'react'
import {Dimensions, StyleSheet, View} from 'react-native' import {Dimensions, StyleSheet, View} from 'react-native'
-import { -import {
- FontAwesomeIcon, - FontAwesomeIcon,
- type FontAwesomeIconStyle, - type FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome' -} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/react/macro'
-import {useNavigation} from '@react-navigation/native' -import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
-import {type NavigationProp} from '#/lib/routes/types' -import {type NavigationProp} from '#/lib/routes/types'
import {s} from '#/lib/styles' import {s} from '#/lib/styles'
-import {isWeb} from '#/platform/detection' -import {IS_WEB} from '#/env'
-import {Button} from '../util/forms/Button' -import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
@@ -159,9 +154,9 @@ index e3c84d782..efb55d406 100644
const pal = usePalette('default') const pal = usePalette('default')
- const palInverted = usePalette('inverted') - const palInverted = usePalette('inverted')
- const navigation = useNavigation<NavigationProp>() - const navigation = useNavigation<NavigationProp>()
-
- const onPressFindAccounts = React.useCallback(() => { - const onPressFindAccounts = React.useCallback(() => {
- if (isWeb) { - if (IS_WEB) {
- navigation.navigate('Search', {}) - navigation.navigate('Search', {})
- } else { - } else {
- navigation.navigate('SearchTab') - navigation.navigate('SearchTab')
@@ -172,10 +167,11 @@ index e3c84d782..efb55d406 100644
- const onPressDiscoverFeeds = React.useCallback(() => { - const onPressDiscoverFeeds = React.useCallback(() => {
- navigation.navigate('Feeds') - navigation.navigate('Feeds')
- }, [navigation]) - }, [navigation])
-
return ( return (
<View <View
@@ -41,41 +18,8 @@ export function FollowingEndOfFeed() { style={[
@@ -41,41 +18,8 @@
]}> ]}>
<View style={styles.inner}> <View style={styles.inner}>
<Text type="xl-medium" style={[s.textCenter, pal.text]}> <Text type="xl-medium" style={[s.textCenter, pal.text]}>
@@ -183,7 +179,8 @@ index e3c84d782..efb55d406 100644
- You've reached the end of your feed! Find some more accounts to - You've reached the end of your feed! Find some more accounts to
- follow. - follow.
- </Trans> - </Trans>
- </Text> + <Trans>You've reached the end of your feed!</Trans>
</Text>
- <Button - <Button
- type="inverted" - type="inverted"
- style={styles.emptyBtn} - style={styles.emptyBtn}
@@ -200,8 +197,7 @@ index e3c84d782..efb55d406 100644
- -
- <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}> - <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
- <Trans>You can also discover new Custom Feeds to follow.</Trans> - <Trans>You can also discover new Custom Feeds to follow.</Trans>
+ <Trans>You've reached the end of your feed!</Trans> - </Text>
</Text>
- <Button - <Button
- type="inverted" - type="inverted"
- style={[styles.emptyBtn, s.mt10]} - style={[styles.emptyBtn, s.mt10]}
@@ -218,7 +214,7 @@ index e3c84d782..efb55d406 100644
</View> </View>
</View> </View>
) )
@@ -93,13 +37,4 @@ const styles = StyleSheet.create({ @@ -93,13 +37,4 @@
width: '100%', width: '100%',
maxWidth: 460, maxWidth: 460,
}, },
@@ -232,11 +228,9 @@ index e3c84d782..efb55d406 100644
- borderRadius: 30, - borderRadius: 30,
- }, - },
}) })
diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx
index 4f25468c9..a72a10b80 100644
--- a/src/view/com/posts/PostFeed.tsx --- a/src/view/com/posts/PostFeed.tsx
+++ b/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx
@@ -766,7 +766,7 @@ let PostFeed = ({ @@ -765,7 +765,7 @@
} else if (row.type === 'feedShutdownMsg') { } else if (row.type === 'feedShutdownMsg') {
return <FeedShutdownMsg feedUri={feedUriOrActorDid} /> return <FeedShutdownMsg feedUri={feedUriOrActorDid} />
} else if (row.type === 'interstitialFollows') { } else if (row.type === 'interstitialFollows') {

View File

@@ -1,51 +1,17 @@
diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go
index 790f211ee..ec05a8bcd 100644
--- a/bskyweb/cmd/bskyweb/server.go --- a/bskyweb/cmd/bskyweb/server.go
+++ b/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go
@@ -317,6 +317,12 @@ func serve(cctx *cli.Context) error { @@ -317,6 +317,14 @@ func serve(cctx *cli.Context) error {
e.GET("/support/tos", server.WebGeneric) e.GET("/support/tos", server.WebGeneric)
e.GET("/support/community-guidelines", server.WebGeneric) e.GET("/support/community-guidelines", server.WebGeneric)
e.GET("/support/copyright", server.WebGeneric) e.GET("/support/copyright", server.WebGeneric)
+ // about/support pages (syu.is specific) + e.GET("/support/privacy-policy", server.WebGeneric)
+ e.GET("/about/support/tos", server.WebAboutTOS) + e.GET("/support/license", server.WebGeneric)
+ e.GET("/about/support/privacy-policy", server.WebAboutPrivacy) + e.GET("/support/app", server.WebGeneric)
+ e.GET("/about/support/help", server.WebAboutHelp) + e.GET("/support/help", server.WebGeneric)
+ e.GET("/about/support/license", server.WebAboutLicense) + // /about/support/ paths (for web compatibility)
+ e.GET("/about/support/app", server.WebAboutApp) + e.GET("/about/support/tos", server.WebGeneric)
+ e.GET("/about/support/privacy-policy", server.WebGeneric)
+ e.GET("/about/support/license", server.WebGeneric)
e.GET("/intent/compose", server.WebGeneric) e.GET("/intent/compose", server.WebGeneric)
e.GET("/intent/verify-email", server.WebGeneric) e.GET("/intent/verify-email", server.WebGeneric)
e.GET("/intent/age-assurance", server.WebGeneric) e.GET("/intent/age-assurance", server.WebGeneric)
@@ -825,3 +831,33 @@ func (srv *Server) serveSitemapRequest(c echo.Context, url, sitemapType string)
return nil
}
+
+// Handler for About TOS page (syu.is specific)
+func (srv *Server) WebAboutTOS(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-tos.html", data)
+}
+
+// Handler for About Privacy Policy page (syu.is specific)
+func (srv *Server) WebAboutPrivacy(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-privacy.html", data)
+}
+
+// Handler for About Help page (syu.is specific)
+func (srv *Server) WebAboutHelp(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-help.html", data)
+}
+
+// Handler for About License page (syu.is specific)
+func (srv *Server) WebAboutLicense(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-license.html", data)
+}
+
+// Handler for About App page (syu.is specific)
+func (srv *Server) WebAboutApp(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-app.html", data)
+}

View File

@@ -1,6 +1,6 @@
--- a/src/view/shell/bottom-bar/BottomBar.tsx --- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -198,38 +198,40 @@ @@ -196,38 +196,40 @@
accessibilityLabel={_(msg`Search`)} accessibilityLabel={_(msg`Search`)}
accessibilityHint="" accessibilityHint=""
/> />
@@ -10,12 +10,12 @@
- isAtMessages ? ( - isAtMessages ? (
- <MessageFilled - <MessageFilled
- width={iconWidth - 1} - width={iconWidth - 1}
- style={[styles.ctrlIcon, pal.text, styles.feedsIcon]} - style={[styles.ctrlIcon, t.atoms.text, styles.feedsIcon]}
- /> - />
- ) : ( - ) : (
- <Message - <Message
- width={iconWidth - 1} - width={iconWidth - 1}
- style={[styles.ctrlIcon, pal.text, styles.feedsIcon]} - style={[styles.ctrlIcon, t.atoms.text, styles.feedsIcon]}
- /> - />
- ) - )
- } - }
@@ -28,10 +28,10 @@
- accessibilityHint={ - accessibilityHint={
- numUnreadMessages.count > 0 - numUnreadMessages.count > 0
- ? _( - ? _(
- msg`${plural(numUnreadMessages.numUnread ?? 0, { - plural(numUnreadMessages.numUnread ?? 0, {
- one: '# unread item', - one: '# unread item',
- other: '# unread items', - other: '# unread items',
- })}` || '', - }),
- ) - )
- : '' - : ''
- } - }
@@ -43,12 +43,12 @@
+ isAtMessages ? ( + isAtMessages ? (
+ <MessageFilled + <MessageFilled
+ width={iconWidth - 1} + width={iconWidth - 1}
+ style={[styles.ctrlIcon, pal.text, styles.feedsIcon]} + style={[styles.ctrlIcon, t.atoms.text, styles.feedsIcon]}
+ /> + />
+ ) : ( + ) : (
+ <Message + <Message
+ width={iconWidth - 1} + width={iconWidth - 1}
+ style={[styles.ctrlIcon, pal.text, styles.feedsIcon]} + style={[styles.ctrlIcon, t.atoms.text, styles.feedsIcon]}
+ /> + />
+ ) + )
+ } + }
@@ -61,10 +61,10 @@
+ accessibilityHint={ + accessibilityHint={
+ numUnreadMessages.count > 0 + numUnreadMessages.count > 0
+ ? _( + ? _(
+ msg`${plural(numUnreadMessages.numUnread ?? 0, { + plural(numUnreadMessages.numUnread ?? 0, {
+ one: '# unread item', + one: '# unread item',
+ other: '# unread items', + other: '# unread items',
+ })}` || '', + }),
+ ) + )
+ : '' + : ''
+ } + }

View File

@@ -1,12 +1,15 @@
diff --git a/src/env/common.ts b/src/env/common.ts
index 04e98c49c..a4ee47932 100644
--- a/src/env/common.ts --- a/src/env/common.ts
+++ b/src/env/common.ts +++ b/src/env/common.ts
@@ -107,19 +107,17 @@ export const GCP_PROJECT_ID: number = @@ -88,7 +88,7 @@
* Metrics API host
*/
export const METRICS_API_HOST: string =
- process.env.EXPO_PUBLIC_METRICS_API_HOST || 'https://events.bsky.app'
+ process.env.EXPO_PUBLIC_METRICS_API_HOST || ''
/** /**
* URLs for the app config web worker. Can be a * Growthbook API host
* locally running server, see `env.example` for more. @@ -128,9 +128,7 @@
+ * Disabled for self-hosted environment to avoid CORS errors
*/ */
export const GEOLOCATION_DEV_URL = process.env.GEOLOCATION_DEV_URL export const GEOLOCATION_DEV_URL = process.env.GEOLOCATION_DEV_URL
export const GEOLOCATION_PROD_URL = `https://ip.bsky.app` export const GEOLOCATION_PROD_URL = `https://ip.bsky.app`
@@ -17,8 +20,7 @@ index 04e98c49c..a4ee47932 100644
/** /**
* URLs for the live-event config web worker. Can be a * URLs for the live-event config web worker. Can be a
* locally running server, see `env.example` for more. @@ -138,9 +136,7 @@
+ * Disabled for self-hosted environment
*/ */
export const LIVE_EVENTS_DEV_URL = process.env.LIVE_EVENTS_DEV_URL export const LIVE_EVENTS_DEV_URL = process.env.LIVE_EVENTS_DEV_URL
export const LIVE_EVENTS_PROD_URL = `https://live-events.workers.bsky.app` export const LIVE_EVENTS_PROD_URL = `https://live-events.workers.bsky.app`
@@ -26,3 +28,14 @@ index 04e98c49c..a4ee47932 100644
- ? (LIVE_EVENTS_DEV_URL ?? LIVE_EVENTS_PROD_URL) - ? (LIVE_EVENTS_DEV_URL ?? LIVE_EVENTS_PROD_URL)
- : LIVE_EVENTS_PROD_URL - : LIVE_EVENTS_PROD_URL
+export const LIVE_EVENTS_URL = null +export const LIVE_EVENTS_URL = null
/**
* URLs for the app-config web worker. Can be a
@@ -148,6 +144,4 @@
*/
export const APP_CONFIG_DEV_URL = process.env.APP_CONFIG_DEV_URL
export const APP_CONFIG_PROD_URL = `https://app-config.workers.bsky.app`
-export const APP_CONFIG_URL = IS_DEV
- ? (APP_CONFIG_DEV_URL ?? APP_CONFIG_PROD_URL)
- : APP_CONFIG_PROD_URL
+export const APP_CONFIG_URL = null

View File

@@ -1,8 +1,8 @@
--- a/src/screens/Signup/StepInfo/index.tsx --- a/src/screens/Signup/StepInfo/index.tsx
+++ b/src/screens/Signup/StepInfo/index.tsx +++ b/src/screens/Signup/StepInfo/index.tsx
@@ -9,44 +9,17 @@ @@ -8,46 +8,18 @@
import {isEmailMaybeInvalid} from '#/lib/strings/email'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {useSignupContext} from '#/screens/Signup/state' import {useSignupContext} from '#/screens/Signup/state'
-import {Policies} from '#/screens/Signup/StepInfo/Policies' -import {Policies} from '#/screens/Signup/StepInfo/Policies'
import {atoms as a, native} from '#/alf' import {atoms as a, native} from '#/alf'
@@ -26,6 +26,8 @@
- MIN_ACCESS_AGE, - MIN_ACCESS_AGE,
- useAgeAssuranceRegionConfigWithFallback, - useAgeAssuranceRegionConfigWithFallback,
-} from '#/ageAssurance/util' -} from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics'
-import {IS_NATIVE} from '#/env'
-import { -import {
- useDeviceGeolocationApi, - useDeviceGeolocationApi,
- useIsDeviceGeolocationGranted, - useIsDeviceGeolocationGranted,
@@ -45,12 +47,12 @@
export function StepInfo({ export function StepInfo({
onPressBack, onPressBack,
isServerError, isServerError,
@@ -70,21 +43,6 @@ @@ -72,22 +44,7 @@
const emailInputRef = useRef<TextInput>(null) const emailInputRef = useRef<TextInput>(null)
const passwordInputRef = useRef<TextInput>(null) const passwordInputRef = useRef<TextInput>(null)
- const birthdateInputRef = useRef<DateFieldRef>(null) - const birthdateInputRef = useRef<DateFieldRef>(null)
-
- const aaRegionConfig = useAgeAssuranceRegionConfigWithFallback() - const aaRegionConfig = useAgeAssuranceRegionConfigWithFallback()
- const {setDeviceGeolocation} = useDeviceGeolocationApi() - const {setDeviceGeolocation} = useDeviceGeolocationApi()
- const locationControl = Dialog.useDialogControl() - const locationControl = Dialog.useDialogControl()
@@ -64,10 +66,11 @@
- ? !isUnderAge(state.dateOfBirth.toISOString(), 18) - ? !isUnderAge(state.dateOfBirth.toISOString(), 18)
- : true - : true
- const isDeviceGeolocationGranted = useIsDeviceGeolocationGranted() - const isDeviceGeolocationGranted = useIsDeviceGeolocationGranted()
-
const [hasWarnedEmail, setHasWarnedEmail] = React.useState<boolean>(false) const [hasWarnedEmail, setHasWarnedEmail] = React.useState<boolean>(false)
@@ -105,10 +63,6 @@ const tldtsRef = React.useRef<typeof tldts>(undefined)
@@ -107,10 +64,6 @@
const emailChanged = prevEmailValueRef.current !== email const emailChanged = prevEmailValueRef.current !== email
const password = passwordValueRef.current const password = passwordValueRef.current
@@ -78,7 +81,7 @@
if (state.serviceDescription?.inviteCodeRequired && !inviteCode) { if (state.serviceDescription?.inviteCodeRequired && !inviteCode) {
return dispatch({ return dispatch({
type: 'setError', type: 'setError',
@@ -275,107 +229,16 @@ @@ -273,107 +226,16 @@
secureTextEntry secureTextEntry
autoComplete="new-password" autoComplete="new-password"
autoCapitalize="none" autoCapitalize="none"
@@ -91,7 +94,7 @@
passwordRules="minlength: 8;" passwordRules="minlength: 8;"
/> />
</TextField.Root> </TextField.Root>
</View> - </View>
- <View> - <View>
- <DateField.LabelText> - <DateField.LabelText>
- <Trans>Your birth date</Trans> - <Trans>Your birth date</Trans>
@@ -110,7 +113,7 @@
- accessibilityHint={_(msg`Select your date of birth`)} - accessibilityHint={_(msg`Select your date of birth`)}
- maximumDate={new Date()} - maximumDate={new Date()}
- /> - />
- </View> </View>
- -
- <View style={[a.gap_sm]}> - <View style={[a.gap_sm]}>
- <Policies serviceDescription={state.serviceDescription} /> - <Policies serviceDescription={state.serviceDescription} />
@@ -122,18 +125,18 @@
- <Admonition.Content> - <Admonition.Content>
- <Admonition.Text> - <Admonition.Text>
- {!isOverAppMinAccessAge ? ( - {!isOverAppMinAccessAge ? (
- <Trans> - <Plural
- You must be {MIN_ACCESS_AGE} years of age or older - value={MIN_ACCESS_AGE}
- to create an account. - other="You must be # years of age or older to create an account."
- </Trans> - />
- ) : ( - ) : (
- <Trans> - <Plural
- You must be {aaRegionConfig.minAccessAge} years of - value={aaRegionConfig.minAccessAge}
- age or older to create an account in your region. - other="You must be # years of age or older to create an account in your region."
- </Trans> - />
- )} - )}
- </Admonition.Text> - </Admonition.Text>
- {isNative && - {IS_NATIVE &&
- !isDeviceGeolocationGranted && - !isDeviceGeolocationGranted &&
- isOverAppMinAccessAge && ( - isOverAppMinAccessAge && (
- <Admonition.Text> - <Admonition.Text>
@@ -165,7 +168,7 @@
- ) : undefined} - ) : undefined}
- </View> - </View>
- -
- {isNative && ( - {IS_NATIVE && (
- <DeviceLocationRequestDialog - <DeviceLocationRequestDialog
- control={locationControl} - control={locationControl}
- onLocationAcquired={props => { - onLocationAcquired={props => {

View File

@@ -0,0 +1,132 @@
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -1,5 +1,5 @@
import {memo, useMemo, useState} from 'react'
-import {View} from 'react-native'
+import {Image, Pressable, View} from 'react-native'
import {
type AppBskyActorDefs,
moderateProfile,
@@ -11,7 +11,10 @@
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
+import {useQuery} from '@tanstack/react-query'
+
import {useHaptics} from '#/lib/haptics'
+import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
@@ -47,6 +50,103 @@
import {ProfileHeaderShell} from './Shell'
import {ProfileHeaderSuggestedFollows} from './SuggestedFollows'
+const SERVICE_FAVICONS: Record<string, any> = {
+ 'syui.ai': require('../../../../assets/favicons/syui.ai.png'),
+ 'bsky.app': require('../../../../assets/favicons/bsky.app.png'),
+ 'atproto.com': require('../../../../assets/favicons/atproto.com.png'),
+}
+
+async function resolvePds(did: string): Promise<string> {
+ if (did.startsWith('did:web:')) {
+ const host = did.split(':').slice(2).join(':')
+ const res = await fetch(`https://${host}/.well-known/did.json`)
+ if (!res.ok) throw new Error('failed to resolve did:web')
+ const doc = await res.json()
+ const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
+ if (pds) return pds
+ return `https://${host}`
+ }
+ const res = await fetch(`https://plc.directory/${did}`)
+ if (!res.ok) throw new Error('failed to resolve DID')
+ const doc = await res.json()
+ const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
+ if (!pds) throw new Error('no PDS found')
+ return pds
+}
+
+function ProfileServiceLinks({
+ profile,
+}: {
+ profile: AppBskyActorDefs.ProfileViewDetailed
+}) {
+ const t = useTheme()
+ const openLink = useOpenLink()
+
+ const {data: services} = useQuery({
+ queryKey: ['profile-services', profile.did],
+ queryFn: async () => {
+ const pds = await resolvePds(profile.did)
+ const res = await fetch(
+ `${pds}/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(profile.did)}`,
+ )
+ if (!res.ok) throw new Error('failed')
+ const data = await res.json()
+ const collections: string[] = data.collections || []
+ const serviceSet = new Set<string>()
+ for (const nsid of collections) {
+ const parts = nsid.split('.')
+ if (parts.length >= 2) {
+ const domain = parts.slice(0, 2).reverse().join('.')
+ serviceSet.add(domain)
+ }
+ }
+ return Array.from(serviceSet)
+ },
+ })
+
+ if (!services || services.length === 0) return null
+
+ return (
+ <View style={[a.flex_row, a.flex_wrap, a.gap_sm, a.pt_xs]}>
+ {services.map(service => (
+ <Pressable
+ key={service}
+ onPress={() =>
+ openLink(
+ `https://at.syu.is/@${profile.handle}/at/service/${service}`,
+ )
+ }
+ style={[
+ a.flex_row,
+ a.align_center,
+ a.gap_xs,
+ a.rounded_full,
+ t.atoms.bg_contrast_50,
+ {paddingVertical: 6, paddingHorizontal: 10},
+ ]}>
+ <Image
+ source={
+ SERVICE_FAVICONS[service] || {
+ uri: `https://www.google.com/s2/favicons?domain=${service}&sz=32`,
+ }
+ }
+ style={{width: 14, height: 14, borderRadius: 3}}
+ accessibilityIgnoresInvertColors
+ />
+ <Text
+ style={[
+ a.text_xs,
+ a.font_medium,
+ t.atoms.text_contrast_medium,
+ ]}>
+ {service}
+ </Text>
+ </Pressable>
+ ))}
+ </View>
+ )
+}
+
interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed
descriptionRT: RichTextAPI | null
@@ -152,6 +252,7 @@
{!isPlaceholderProfile && !isBlockedUser && (
<View style={a.gap_md}>
<ProfileHeaderMetrics profile={profile} />
+ <ProfileServiceLinks profile={profile} />
{descriptionRT && !moderation.ui('profileView').blur ? (
<View pointerEvents="auto">
<RichText

View File

@@ -0,0 +1,35 @@
diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx
--- a/src/view/com/posts/PostFeedItem.tsx
+++ b/src/view/com/posts/PostFeedItem.tsx
@@ -385,17 +385,19 @@
threadgateRecord={threadgateRecord}
/>
- <PostControls
- post={post}
- record={record}
- richText={richText}
- onPressReply={onPressReply}
- logContext="FeedItem"
- feedContext={feedContext}
- reqId={reqId}
- threadgateRecord={threadgateRecord}
- onShowLess={onShowLess}
- viaRepost={viaRepost}
- />
+ {false && (
+ <PostControls
+ post={post}
+ record={record}
+ richText={richText}
+ onPressReply={onPressReply}
+ logContext="FeedItem"
+ feedContext={feedContext}
+ reqId={reqId}
+ threadgateRecord={threadgateRecord}
+ onShowLess={onShowLess}
+ viaRepost={viaRepost}
+ />
+ )}
</View>
<DiscoverDebug feedContext={feedContext} />

View File

@@ -0,0 +1,50 @@
--- a/src/view/com/posts/PostFeed.tsx 2026-02-16 02:19:59
+++ b/src/view/com/posts/PostFeed.tsx 2026-02-16 02:20:13
@@ -519,16 +519,17 @@
key: 'liveEventFeedsAndTrendingBanner-' + sliceIndex,
})
// Show composer prompt for Discover and Following feeds
- if (
- hasSession &&
- (feedUriOrActorDid === DISCOVER_FEED_URI ||
- feed === 'following')
- ) {
- arr.push({
- type: 'composerPrompt',
- key: 'composerPrompt-' + sliceIndex,
- })
- }
+ // Disabled: hide composer prompt
+ // if (
+ // hasSession &&
+ // (feedUriOrActorDid === DISCOVER_FEED_URI ||
+ // feed === 'following')
+ // ) {
+ // arr.push({
+ // type: 'composerPrompt',
+ // key: 'composerPrompt-' + sliceIndex,
+ // })
+ // }
} else if (sliceIndex === 15) {
if (areVideoFeedsEnabled && !trendingVideoDisabled) {
arr.push({
@@ -545,12 +546,13 @@
} else if (feedKind === 'following') {
if (sliceIndex === 0) {
// Show composer prompt for Following feed
- if (hasSession) {
- arr.push({
- type: 'composerPrompt',
- key: 'composerPrompt-' + sliceIndex,
- })
- }
+ // Disabled: hide composer prompt
+ // if (hasSession) {
+ // arr.push({
+ // type: 'composerPrompt',
+ // key: 'composerPrompt-' + sliceIndex,
+ // })
+ // }
}
} else if (feedKind === 'profile') {
if (sliceIndex === 5) {

View File

@@ -0,0 +1,14 @@
--- a/src/view/com/auth/SplashScreen.tsx 2026-02-16 02:55:08
+++ b/src/view/com/auth/SplashScreen.tsx 2026-02-16 02:55:16
@@ -118,8 +118,9 @@
accessibilityHint={_(
msg`Opens flow to sign in to your existing Bluesky account`,
)}
- size="large">
- <ButtonText style={{color: 'white'}}>
+ size="large"
+ color="primary">
+ <ButtonText>
<Trans>Sign in</Trans>
</ButtonText>
</Button>

View File

@@ -0,0 +1,18 @@
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -48,6 +48,7 @@
import {ProfileHeaderHandle} from './Handle'
import {ProfileHeaderMetrics} from './Metrics'
import {ProfileHeaderShell} from './Shell'
+import {ProfileAtLinks} from './ProfileAtLinks'
import {ProfileHeaderSuggestedFollows} from './SuggestedFollows'
const SERVICE_FAVICONS: Record<string, any> = {
@@ -253,6 +254,7 @@
<View style={a.gap_md}>
<ProfileHeaderMetrics profile={profile} />
<ProfileServiceLinks profile={profile} />
+ <ProfileAtLinks profile={profile} />
{descriptionRT && !moderation.ui('profileView').blur ? (
<View pointerEvents="auto">
<RichText

View File

@@ -0,0 +1,19 @@
--- a/src/view/shell/desktop/RightNav.tsx
+++ b/src/view/shell/desktop/RightNav.tsx
@@ -111,14 +111,14 @@
</>
)}
<InlineLinkText
- to="https://bsky.social/about/support/privacy-policy"
+ to="/support/privacy-policy"
style={[t.atoms.text_contrast_medium]}
label={_(msg`Privacy`)}>
{_(msg`Privacy`)}
</InlineLinkText>
<Text style={[t.atoms.text_contrast_low]}>{' ∙ '}</Text>
<InlineLinkText
- to="https://bsky.social/about/support/tos"
+ to="/support/tos"
style={[t.atoms.text_contrast_medium]}
label={_(msg`Terms`)}>
{_(msg`Terms`)}

View File

@@ -0,0 +1,60 @@
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -2,6 +2,7 @@
import {Image as RNImage, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {Image} from 'expo-image'
+import {useVideoPlayer, VideoView} from 'expo-video'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -15,10 +16,13 @@
import splashImagePointer from '../../../../assets/splash/illustration-mobile.png'
// @ts-ignore
import darkSplashImagePointer from '../../../../assets/splash/illustration-mobile-dark.png'
+// @ts-ignore
+import splashVideoPointer from '../../../../assets/splash/illustration-mobile.mp4'
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri
const darkSplashImageUri = RNImage.resolveAssetSource(
darkSplashImagePointer,
).uri
+const splashVideoUri = RNImage.resolveAssetSource(splashVideoPointer).uri
export const SplashScreen = ({
onPressSignin,
@@ -53,13 +57,30 @@
}
}, [t, isDarkMode])
+ const player = useVideoPlayer(splashVideoUri, p => {
+ p.loop = true
+ p.muted = true
+ p.play()
+ })
+
return (
<>
- <Image
- accessibilityIgnoresInvertColors
- source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}}
- style={[a.absolute, a.inset_0]}
- />
+ {isDarkMode ? (
+ <Image
+ accessibilityIgnoresInvertColors
+ source={{uri: darkSplashImageUri}}
+ style={[a.absolute, a.inset_0]}
+ />
+ ) : (
+ <VideoView
+ player={player}
+ style={[a.absolute, a.inset_0]}
+ contentFit="cover"
+ nativeControls={false}
+ allowsFullscreen={false}
+ allowsPictureInPicture={false}
+ />
+ )}
<Animated.View
entering={FadeIn.duration(90)}

View File

@@ -0,0 +1,40 @@
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -717,16 +717,7 @@
post.shortenedGraphemeLength > 0 || post.embed.media || post.embed.link,
)
- // Show discard prompt if there's content AND either:
- // - No draft is loaded (new composition)
- // - Draft is loaded but has been modified
- if (hasContent && (!composerState.draftId || composerState.isDirty)) {
- closeAllDialogs()
- Keyboard.dismiss()
- discardPromptControl.open()
- } else {
- onClose()
- }
+ onClose()
}, [
thread,
composerState.draftId,
@@ -1551,18 +1542,7 @@
</>
) : (
<>
- {!isReply && (
- <DraftsButton
- onSelectDraft={onSelectDraft}
- onSaveDraft={onSaveDraft}
- onDiscard={onDiscard}
- isEmpty={isEmpty}
- isDirty={isDirty}
- isEditingDraft={isEditingDraft}
- canSaveDraft={canSaveDraft}
- textLength={textLength}
- />
- )}
+ {/* DraftsButton removed */}
<Button
testID="composerPublishBtn"
label={

View File

@@ -0,0 +1,55 @@
diff --git a/src/features/liveEvents/context.tsx b/src/features/liveEvents/context.tsx
index 3de2534a8..e11955f57 100644
--- a/src/features/liveEvents/context.tsx
+++ b/src/features/liveEvents/context.tsx
@@ -24,6 +24,7 @@ export const DEFAULT_LIVE_EVENTS = {
async function fetchLiveEvents(): Promise<LiveEventsWorkerResponse | null> {
try {
+ if (!LIVE_EVENTS_URL) return null
const res = await fetch(`${LIVE_EVENTS_URL}/config`)
if (!res.ok) return null
const data = await res.json()
diff --git a/src/geolocation/const.ts b/src/geolocation/const.ts
index 653e829ba..4992cc870 100644
--- a/src/geolocation/const.ts
+++ b/src/geolocation/const.ts
@@ -1,7 +1,9 @@
import {GEOLOCATION_URL} from '#/env'
import {type Geolocation} from '#/geolocation/types'
-export const GEOLOCATION_SERVICE_URL = `${GEOLOCATION_URL}/geolocation`
+export const GEOLOCATION_SERVICE_URL = GEOLOCATION_URL
+ ? `${GEOLOCATION_URL}/geolocation`
+ : null
/**
* Default geolocation config.
diff --git a/src/geolocation/service.ts b/src/geolocation/service.ts
index 2d9285b67..c7c3d02c0 100644
--- a/src/geolocation/service.ts
+++ b/src/geolocation/service.ts
@@ -26,9 +26,10 @@ const onGeolocationServiceResponseUpdate = (
}
async function fetchGeolocationServiceData(
- url: string,
+ url: string | null,
): Promise<Geolocation | undefined> {
if (debug.enabled) return debug.resolve(debug.geolocation)
+ if (!url) throw new Error('geolocation service disabled')
const res = await fetch(url)
if (!res.ok) {
throw new Error(`fetchGeolocationServiceData failed ${res.status}`)
diff --git a/src/state/appConfig.tsx b/src/state/appConfig.tsx
index 67b0e553e..9eacf7ead 100644
--- a/src/state/appConfig.tsx
+++ b/src/state/appConfig.tsx
@@ -30,6 +30,7 @@ let fetchAppConfigPromise: Promise<AppConfigResponse> | undefined
async function fetchAppConfig(): Promise<AppConfigResponse | null> {
try {
+ if (!APP_CONFIG_URL) return null
if (!fetchAppConfigPromise) {
fetchAppConfigPromise = (async () => {
const r = await fetch(`${APP_CONFIG_URL}/config`)

View File

@@ -27,7 +27,7 @@ export function LicenseScreen() {
<Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>MIT License</Text> <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>MIT License</Text>
<Text style={[a.mb_md, {fontFamily: 'monospace'}]}> <Text style={[a.mb_md, {fontFamily: 'monospace'}]}>
Copyright (c) 2022-2025 Bluesky PBC Copyright (c) 2022-2026 Bluesky PBC
</Text> </Text>
<Text style={[a.mb_md]}> <Text style={[a.mb_md]}>

View File

@@ -0,0 +1,150 @@
import React from 'react'
import {Pressable, View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {useQuery} from '@tanstack/react-query'
import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
import {createSinglePathSVG} from '#/components/icons/TEMPLATE'
// --- SVG Icons (viewBox 0 0 640 640, Font Awesome) ---
const GithubIcon = createSinglePathSVG({
path: 'M237.9 461.4C237.9 463.4 235.6 465 232.7 465C229.4 465.3 227.1 463.7 227.1 461.4C227.1 459.4 229.4 457.8 232.3 457.8C235.3 457.5 237.9 459.1 237.9 461.4zM206.8 456.9C206.1 458.9 208.1 461.2 211.1 461.8C213.7 462.8 216.7 461.8 217.3 459.8C217.9 457.8 216 455.5 213 454.6C210.4 453.9 207.5 454.9 206.8 456.9zM251 455.2C248.1 455.9 246.1 457.8 246.4 460.1C246.7 462.1 249.3 463.4 252.3 462.7C255.2 462 257.2 460.1 256.9 458.1C256.6 456.2 253.9 454.9 251 455.2zM316.8 72C178.1 72 72 177.3 72 316C72 426.9 141.8 521.8 241.5 555.2C254.3 557.5 258.8 549.6 258.8 543.1C258.8 536.9 258.5 502.7 258.5 481.7C258.5 481.7 188.5 496.7 173.8 451.9C173.8 451.9 162.4 422.8 146 415.3C146 415.3 123.1 399.6 147.6 399.9C147.6 399.9 172.5 401.9 186.2 425.7C208.1 464.3 244.8 453.2 259.1 446.6C261.4 430.6 267.9 419.5 275.1 412.9C219.2 406.7 162.8 398.6 162.8 302.4C162.8 274.9 170.4 261.1 186.4 243.5C183.8 237 175.3 210.2 189 175.6C209.9 169.1 258 202.6 258 202.6C278 197 299.5 194.1 320.8 194.1C342.1 194.1 363.6 197 383.6 202.6C383.6 202.6 431.7 169 452.6 175.6C466.3 210.3 457.8 237 455.2 243.5C471.2 261.2 481 275 481 302.4C481 398.9 422.1 406.6 366.2 412.9C375.4 420.8 383.2 435.8 383.2 459.3C383.2 493 382.9 534.7 382.9 542.9C382.9 549.4 387.5 557.3 400.2 555C500.2 521.8 568 426.9 568 316C568 177.3 455.5 72 316.8 72zM169.2 416.9C167.9 417.9 168.2 420.2 169.9 422.1C171.5 423.7 173.8 424.4 175.1 423.1C176.4 422.1 176.1 419.8 174.4 417.9C172.8 416.3 170.5 415.6 169.2 416.9zM158.4 408.8C157.7 410.1 158.7 411.7 160.7 412.7C162.3 413.7 164.3 413.4 165 412C165.7 410.7 164.7 409.1 162.7 408.1C160.7 407.5 159.1 407.8 158.4 408.8zM190.8 444.4C189.2 445.7 189.8 448.7 192.1 450.6C194.4 452.9 197.3 453.2 198.6 451.6C199.9 450.3 199.3 447.3 197.3 445.4C195.1 443.1 192.1 442.8 190.8 444.4zM179.4 429.7C177.8 430.7 177.8 433.3 179.4 435.6C181 437.9 183.7 438.9 185 437.9C186.6 436.6 186.6 434 185 431.7C183.6 429.4 181 428.4 179.4 429.7z',
viewBox: '0 0 640 640',
})
const XIcon = createSinglePathSVG({
path: 'M453.2 112L523.8 112L369.6 288.2L551 528L409 528L297.7 382.6L170.5 528L99.8 528L264.7 339.5L90.8 112L236.4 112L336.9 244.9L453.2 112zM428.4 485.8L467.5 485.8L215.1 152L173.1 152L428.4 485.8z',
viewBox: '0 0 640 640',
})
const YoutubeIcon = createSinglePathSVG({
path: 'M581.7 188.1C575.5 164.4 556.9 145.8 533.4 139.5C490.9 128 320.1 128 320.1 128C320.1 128 149.3 128 106.7 139.5C83.2 145.8 64.7 164.4 58.4 188.1C47 231 47 320.4 47 320.4C47 320.4 47 409.8 58.4 452.7C64.7 476.3 83.2 494.2 106.7 500.5C149.3 512 320.1 512 320.1 512C320.1 512 490.9 512 533.5 500.5C557 494.2 575.5 476.3 581.8 452.7C593.2 409.8 593.2 320.4 593.2 320.4C593.2 320.4 593.2 231 581.8 188.1zM264.2 401.6L264.2 239.2L406.9 320.4L264.2 401.6z',
viewBox: '0 0 640 640',
})
// --- Types ---
interface LinkItem {
service: string
username: string
}
interface LinkCollection {
links: LinkItem[]
createdAt: string
updatedAt?: string
}
// --- Service Config ---
const SERVICE_CONFIG: Record<
string,
{
name: string
urlTemplate: string
icon: React.ComponentType<{size?: 'xs' | 'sm' | 'md' | 'lg'; fill?: string}>
}
> = {
github: {
name: 'GitHub',
urlTemplate: 'https://github.com/{username}',
icon: GithubIcon,
},
x: {
name: 'X',
urlTemplate: 'https://x.com/{username}',
icon: XIcon,
},
youtube: {
name: 'YouTube',
urlTemplate: 'https://youtube.com/@{username}',
icon: YoutubeIcon,
},
}
// --- PDS Resolution ---
async function resolvePds(did: string): Promise<string> {
if (did.startsWith('did:web:')) {
const host = did.split(':').slice(2).join(':')
const res = await fetch(`https://${host}/.well-known/did.json`)
if (!res.ok) throw new Error('failed to resolve did:web')
const doc = await res.json()
const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
if (pds) return pds
return `https://${host}`
}
const res = await fetch(`https://plc.directory/${did}`)
if (!res.ok) throw new Error('failed to resolve DID')
const doc = await res.json()
const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
if (!pds) throw new Error('no PDS found')
return pds
}
// --- Component ---
export function ProfileAtLinks({
profile,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
}) {
const t = useTheme()
const openLink = useOpenLink()
const {data: linkData} = useQuery({
queryKey: ['at-links', profile.did],
queryFn: async () => {
const pds = await resolvePds(profile.did)
const res = await fetch(
`${pds}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(profile.did)}&collection=ai.syui.at.link&rkey=self`,
)
if (!res.ok) throw new Error('not found')
const json = await res.json()
return json.value as LinkCollection
},
retry: false,
staleTime: 1000 * 60 * 5,
})
if (!linkData?.links?.length) return null
return (
<View style={[a.flex_row, a.flex_wrap, a.gap_sm, a.pt_xs]}>
{linkData.links.map(link => {
const config = SERVICE_CONFIG[link.service]
if (!config) return null
const url = config.urlTemplate.replace('{username}', link.username)
const Icon = config.icon
return (
<Pressable
key={link.service}
onPress={() => openLink(url)}
accessibilityRole="link"
accessibilityLabel={`${config.name}: ${link.username}`}
style={[
a.flex_row,
a.align_center,
a.gap_xs,
a.rounded_full,
t.atoms.bg_contrast_50,
{paddingVertical: 6, paddingHorizontal: 10},
]}>
<Icon size="xs" fill={t.atoms.text_contrast_medium.color} />
<Text
style={[
a.text_xs,
a.font_medium,
t.atoms.text_contrast_medium,
]}>
{link.username}
</Text>
</Pressable>
)
})}
</View>
)
}

12162
ios/patching/ja-messages.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,15 @@ PATCH_FILES_IOS=(
"033-social-app-ios-hide-profile-tabs.patch" "033-social-app-ios-hide-profile-tabs.patch"
"036-social-app-ios-homeheader-loggedout.patch" "036-social-app-ios-homeheader-loggedout.patch"
"037-social-app-ios-disable-contacts-nux.patch" "037-social-app-ios-disable-contacts-nux.patch"
"038-social-app-ios-profile-services.patch"
"039-social-app-ios-hide-feed-controls.patch"
"040-social-app-ios-hide-composer-prompt.patch"
"041-social-app-ios-splash-signin-button.patch"
"042-social-app-ios-at-links.patch"
"043-social-app-ios-rightnav-links.patch"
"044-social-app-ios-splash-video.patch"
"045-social-app-ios-composer-cancel.patch"
"046-social-app-ios-null-url-guards.patch"
) )
function ios-env() { function ios-env() {
@@ -166,6 +175,17 @@ function ios-copy-new-files() {
echo "✅ Copied all assets (including logo.png, app-icons)" echo "✅ Copied all assets (including logo.png, app-icons)"
fi fi
# Generate app-icon.png (logo on #333 background) for app icon
if [ -f "$d/ios/assets/logo.png" ]; then
if command -v magick >/dev/null 2>&1; then
magick -size 1024x1024 "xc:#333333" "$d/ios/assets/logo.png" -gravity center -composite "$target_dir/assets/app-icon.png"
echo "✅ Generated app-icon.png (logo + #333 bg)"
else
echo "⚠️ ImageMagick not found, copying logo.png as app-icon.png fallback"
cp "$d/ios/assets/logo.png" "$target_dir/assets/app-icon.png"
fi
fi
# Copy License.tsx # Copy License.tsx
if [ -f "$patching_dir/License.tsx" ]; then if [ -f "$patching_dir/License.tsx" ]; then
mkdir -p "$target_dir/src/view/screens" mkdir -p "$target_dir/src/view/screens"
@@ -180,6 +200,24 @@ function ios-copy-new-files() {
echo "✅ Copied AppInfo.tsx" echo "✅ Copied AppInfo.tsx"
fi fi
# Copy Japanese locale (translation overrides)
if [ -f "$patching_dir/ja-messages.po" ]; then
cp "$patching_dir/ja-messages.po" "$target_dir/src/locale/locales/ja/messages.po"
echo "✅ Copied ja-messages.po"
fi
# Copy splash video
if [ -f "$d/ios/assets/splash/illustration-mobile.mp4" ]; then
cp "$d/ios/assets/splash/illustration-mobile.mp4" "$target_dir/assets/splash/illustration-mobile.mp4"
echo "✅ Copied splash video (illustration-mobile.mp4)"
fi
# Copy ProfileAtLinks.tsx
if [ -f "$patching_dir/ProfileAtLinks.tsx" ]; then
cp "$patching_dir/ProfileAtLinks.tsx" "$target_dir/src/screens/Profile/Header/ProfileAtLinks.tsx"
echo "✅ Copied ProfileAtLinks.tsx"
fi
# Copy pre-generated favicons for bskyweb # Copy pre-generated favicons for bskyweb
local favicon_src="$d/ios/assets/favicons" local favicon_src="$d/ios/assets/favicons"
local bskyweb_static="$target_dir/bskyweb/static" local bskyweb_static="$target_dir/bskyweb/static"
@@ -192,6 +230,14 @@ function ios-copy-new-files() {
echo "✅ Copied favicons to bskyweb/static" echo "✅ Copied favicons to bskyweb/static"
fi fi
# Compile locale if messages.po was copied
if [ -f "$patching_dir/ja-messages.po" ]; then
echo "🌐 Compiling locale..."
pushd "$target_dir" > /dev/null
yarn intl:compile 2>/dev/null && echo "✅ Compiled locale" || echo "⚠️ Locale compile skipped (yarn not ready)"
popd > /dev/null
fi
echo "" echo ""
} }

78
k8s/bgs.yaml Normal file
View File

@@ -0,0 +1,78 @@
apiVersion: v1
kind: Service
metadata:
name: bgs
namespace: atproto
spec:
selector:
app: bgs
ports:
- port: 2470
targetPort: 2470
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: bgs-data
namespace: atproto
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bgs
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: bgs
template:
metadata:
labels:
app: bgs
spec:
containers:
- name: bgs
image: registry/bgs
ports:
- containerPort: 2470
env:
- name: DATABASE_URL
value: "postgres://postgres:postgres@database/bgs"
- name: CARSTORE_DATABASE_URL
value: "postgres://postgres:postgres@database/carstore"
- name: DATA_DIR
value: "/data"
- name: ATP_PLC_HOST
value: "https://plc.syu.is"
- name: BGS_NEW_PDS_PER_DAY_LIMIT
value: "1000"
- name: BGS_ADMIN_KEY
valueFrom:
secretKeyRef:
name: atproto-secrets
key: bgs-admin-key
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /xrpc/_health
port: 2470
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /xrpc/_health
port: 2470
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: bgs-data

199
k8s/bsky.yaml Normal file
View File

@@ -0,0 +1,199 @@
apiVersion: v1
kind: Service
metadata:
name: bsky
namespace: atproto
spec:
selector:
app: bsky
ports:
- name: api
port: 2584
targetPort: 2584
- name: dataplane
port: 3001
targetPort: 3001
- name: bsync
port: 3002
targetPort: 3002
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: bsky-data
namespace: atproto
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bsky
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: bsky
template:
metadata:
labels:
app: bsky
spec:
securityContext:
runAsUser: 0
containers:
- name: bsky
image: registry/bsky
command: ["node", "--enable-source-maps", "api.js"]
ports:
- containerPort: 2584
- containerPort: 3001
- containerPort: 3002
env:
- name: BSKY_PORT
value: "2584"
- name: BSKY_BLOB_CACHE_LOC
value: "/data/"
- name: BSKY_BSYNC_HTTP_VERSION
value: "1.1"
- name: BSKY_BSYNC_PORT
value: "3002"
- name: BSKY_BSYNC_URL
value: "http://localhost:3002"
- name: BSKY_COURIER_URL
value: "http://fake-courier.example.invalid/"
- name: BSKY_DATAPLANE_HTTP_VERSION
value: "1.1"
- name: BSKY_DATAPLANE_PORT
value: "3001"
- name: BSKY_DATAPLANE_URLS
value: "http://localhost:3001"
- name: BSKY_DB_POSTGRES_URL
value: "postgres://postgres:postgres@database/bsky"
- name: BSKY_DID_PLC_URL
value: "https://plc.syu.is"
- name: BSKY_PUBLIC_URL
value: "https://bsky.syu.is"
- name: BSKY_REPO_PROVIDER
value: "ws://bgs:2470"
- name: BSKY_SERVER_DID
value: "did:web:bsky.syu.is"
- name: MOD_SERVICE_DID
value: "did:web:ozone.syu.is"
- name: BSKY_ADMIN_PASSWORDS
valueFrom:
secretKeyRef:
name: atproto-secrets
key: bsky-admin-passwords
- name: BSKY_SERVICE_SIGNING_KEY
valueFrom:
secretKeyRef:
name: atproto-secrets
key: bsky-service-signing-key
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /xrpc/_health
port: 2584
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /xrpc/_health
port: 2584
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: bsky-data
---
## bsky subscription monitor
## subscriptionが停止していたらPodを再起動する
apiVersion: batch/v1
kind: CronJob
metadata:
name: bsky-subscription-watchdog
namespace: atproto
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: watchdog
image: postgres:16-alpine
command:
- /bin/sh
- -c
- |
# BGSの最新seqを取得
LATEST_SEQ=$(psql -t -A "$DB_URL" -c "SELECT COALESCE(MAX(seq),0) FROM repo_event_records")
# bskyのsubscription cursorを取得
BSKY_CURSOR=$(psql -t -A "$BSKY_DB_URL" -c "SELECT COALESCE(state,0) FROM subscription WHERE service='ws://bgs:2470' LIMIT 1")
LAG=$((LATEST_SEQ - BSKY_CURSOR))
echo "BGS seq=$LATEST_SEQ, bsky cursor=$BSKY_CURSOR, lag=$LAG"
if [ "$LAG" -gt 50 ]; then
echo "WARN: bsky subscription lag=$LAG, restarting bsky pod"
# Podを削除すればDeploymentが再作成する
apk add --no-cache curl > /dev/null 2>&1
APISERVER=https://kubernetes.default.svc
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
# bsky podを取得して削除
POD=$(curl -s -k -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/$NAMESPACE/pods?labelSelector=app=bsky" \
| grep -o '"name":"bsky-[^"]*"' | head -1 | cut -d'"' -f4)
if [ -n "$POD" ]; then
curl -s -k -X DELETE -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/$NAMESPACE/pods/$POD"
echo "Deleted pod $POD"
fi
else
echo "OK: subscription is healthy"
fi
env:
- name: DB_URL
value: "postgres://postgres:postgres@database/bgs"
- name: BSKY_DB_URL
value: "postgres://postgres:postgres@database/bsky"
restartPolicy: OnFailure
serviceAccountName: bsky-watchdog
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: bsky-watchdog
namespace: atproto
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: bsky-watchdog
namespace: atproto
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: bsky-watchdog
namespace: atproto
subjects:
- kind: ServiceAccount
name: bsky-watchdog
namespace: atproto
roleRef:
kind: Role
name: bsky-watchdog
apiGroup: rbac.authorization.k8s.io

71
k8s/feed.yaml Normal file
View File

@@ -0,0 +1,71 @@
apiVersion: v1
kind: Service
metadata:
name: feed
namespace: atproto
spec:
selector:
app: feed
ports:
- port: 3000
targetPort: 3000
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: feed-data
namespace: atproto
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: feed
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: feed
template:
metadata:
labels:
app: feed
spec:
containers:
- name: feed
image: registry/feed
ports:
- containerPort: 3000
env:
- name: FEEDGEN_PORT
value: "3000"
- name: FEEDGEN_LISTENHOST
value: "0.0.0.0"
- name: FEEDGEN_SQLITE_LOCATION
value: "/data/db.sqlite"
- name: FEEDGEN_HOSTNAME
value: "feed.syu.is"
- name: FEEDGEN_PUBLISHER_DID
value: "did:plc:6qyecktefllvenje24fcxnie"
- name: FEEDGEN_SERVICE_DID
value: "did:web:feed.syu.is"
- name: FEEDGEN_JETSTREAM_URL
value: "ws://jetstream:6008/subscribe"
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: feed-data

26
k8s/gen-secrets.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
## envs/ から k8s/secrets.env を生成する
## usage: cd k8s && bash gen-secrets.sh
ENVS_DIR="${1:-../envs}"
OUT="secrets.env"
get_val() {
local file="$1" key="$2"
grep "^${key}=" "$file" 2>/dev/null | head -1 | cut -d'=' -f2-
}
cat > "$OUT" <<EOF
pds-admin-password=$(get_val "$ENVS_DIR/pds" PDS_ADMIN_PASSWORD)
pds-plc-rotation-key=$(get_val "$ENVS_DIR/pds" PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX)
pds-repo-signing-key=$(get_val "$ENVS_DIR/pds" PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX)
pds-jwt-secret=$(get_val "$ENVS_DIR/pds" PDS_JWT_SECRET)
pds-email-smtp-url=$(get_val "$ENVS_DIR/pds" PDS_EMAIL_SMTP_URL)
bsky-admin-passwords=$(get_val "$ENVS_DIR/bsky" BSKY_ADMIN_PASSWORDS)
bsky-service-signing-key=$(get_val "$ENVS_DIR/bsky" BSKY_SERVICE_SIGNING_KEY)
bgs-admin-key=$(get_val "$ENVS_DIR/bgs" BGS_ADMIN_KEY)
ozone-admin-password=$(get_val "$ENVS_DIR/ozone" OZONE_ADMIN_PASSWORD)
ozone-signing-key-hex=$(get_val "$ENVS_DIR/ozone" OZONE_SIGNING_KEY_HEX)
EOF
echo "generated $OUT"

67
k8s/jetstream.yaml Normal file
View File

@@ -0,0 +1,67 @@
apiVersion: v1
kind: Service
metadata:
name: jetstream
namespace: atproto
spec:
selector:
app: jetstream
ports:
- port: 6008
targetPort: 6008
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jetstream-data
namespace: atproto
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jetstream
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: jetstream
template:
metadata:
labels:
app: jetstream
spec:
containers:
- name: jetstream
image: registry/jetstream
ports:
- containerPort: 6008
env:
- name: JETSTREAM_WS_URL
value: "ws://bgs:2470/xrpc/com.atproto.sync.subscribeRepos"
- name: JETSTREAM_DATA_DIR
value: "/data"
- name: JETSTREAM_LISTEN_ADDR
value: ":6008"
- name: JETSTREAM_METRICS_LISTEN_ADDR
value: ":6009"
- name: JETSTREAM_LIVENESS_TTL
value: "96h"
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /
port: 6009
initialDelaySeconds: 10
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: jetstream-data

27
k8s/kustomization.yaml Normal file
View File

@@ -0,0 +1,27 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: atproto
resources:
- namespace.yaml
- postgres.yaml
- redis.yaml
- plc.yaml
- pds.yaml
- bgs.yaml
- bsky.yaml
- social-app.yaml
- ozone.yaml
- ozone-web.yaml
- jetstream.yaml
- feed.yaml
## deploy.yml に images / secretGenerator を設定
## デプロイ: cp deploy.yml kustomization.yaml && kubectl apply -k .
secretGenerator:
- name: atproto-secrets
envs:
- secrets.env
options:
disableNameSuffixHash: true

4
k8s/namespace.yaml Normal file
View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: atproto

47
k8s/ozone-web.yaml Normal file
View File

@@ -0,0 +1,47 @@
apiVersion: v1
kind: Service
metadata:
name: ozone-web
namespace: atproto
spec:
selector:
app: ozone-web
ports:
- port: 3000
targetPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ozone-web
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: ozone-web
template:
metadata:
labels:
app: ozone-web
spec:
containers:
- name: ozone-web
image: registry/ozone-web
ports:
- containerPort: 3000
env:
- name: NEXT_PUBLIC_PLC_DIRECTORY_URL
value: "https://plc.syu.is"
- name: NEXT_PUBLIC_OZONE_SERVICE_DID
value: "did:web:ozone.syu.is"
- name: NEXT_PUBLIC_SOCIAL_APP_DOMAIN
value: "syu.is"
- name: NEXT_PUBLIC_SOCIAL_APP_URL
value: "https://syu.is"
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 10
periodSeconds: 10

96
k8s/ozone.yaml Normal file
View File

@@ -0,0 +1,96 @@
apiVersion: v1
kind: Service
metadata:
name: ozone
namespace: atproto
spec:
selector:
app: ozone
ports:
- port: 3000
targetPort: 3000
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ozone-data
namespace: atproto
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ozone
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: ozone
template:
metadata:
labels:
app: ozone
spec:
containers:
- name: ozone
image: registry/ozone
command: ["node", "--enable-source-maps", "api.js"]
ports:
- containerPort: 3000
env:
- name: OZONE_SERVER_DID
value: "did:web:ozone.syu.is"
- name: OZONE_PUBLIC_URL
value: "https://ozone.syu.is"
- name: OZONE_DB_POSTGRES_URL
value: "postgres://postgres:postgres@database/ozone"
- name: OZONE_DID_PLC_URL
value: "https://plc.syu.is"
- name: OZONE_APPVIEW_DID
value: "did:web:bsky.syu.is"
- name: OZONE_APPVIEW_URL
value: "https://bsky.syu.is"
- name: OZONE_APPVIEW_PUSH_EVENTS
value: "true"
- name: OZONE_PDS_DID
value: "did:web:syu.is"
- name: OZONE_PDS_URL
value: "https://syu.is"
- name: OZONE_DEV_MODE
value: "true"
- name: OZONE_DB_MIGRATE
value: "1"
- name: OZONE_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: atproto-secrets
key: ozone-admin-password
- name: OZONE_SIGNING_KEY_HEX
valueFrom:
secretKeyRef:
name: atproto-secrets
key: ozone-signing-key-hex
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /xrpc/_health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /xrpc/_health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: ozone-data

121
k8s/pds.yaml Normal file
View File

@@ -0,0 +1,121 @@
apiVersion: v1
kind: Service
metadata:
name: pds
namespace: atproto
spec:
selector:
app: pds
ports:
- port: 3000
targetPort: 3000
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pds-data
namespace: atproto
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pds
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: pds
template:
metadata:
labels:
app: pds
spec:
containers:
- name: pds
image: registry/pds
command: ["node", "--enable-source-maps", "index.js"]
ports:
- containerPort: 3000
env:
- name: PDS_HOSTNAME
value: "syu.is"
- name: PDS_DB_POSTGRES_URL
value: "postgres://postgres:postgres@database/pds"
- name: PDS_DATA_DIRECTORY
value: "/data"
- name: PDS_BLOBSTORE_DISK_LOCATION
value: "/data/img/static"
- name: PDS_BSKY_APP_VIEW_DID
value: "did:web:bsky.syu.is"
- name: PDS_BSKY_APP_VIEW_URL
value: "https://bsky.syu.is"
- name: PDS_CRAWLERS
value: "https://bgs.syu.is"
- name: PDS_SEQUENCER_ENABLED
value: "true"
- name: PDS_SEQUENCER_DB_LOCATION
value: "/data/sequencer.sqlite"
- name: PDS_DEV_MODE
value: "true"
- name: PDS_DID_PLC_URL
value: "https://plc.syu.is"
- name: PDS_ENABLE_DID_DOC_WITH_SESSION
value: "true"
- name: PDS_INVITE_INTERVAL
value: "604800000"
- name: PDS_SERVICE_DID
value: "did:web:syu.is"
- name: PDS_EMAIL_FROM_ADDRESS
value: "no-reply@syu.is"
- name: PDS_INVITE_REQUIRED
value: "true"
- name: PDS_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: atproto-secrets
key: pds-admin-password
- name: PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX
valueFrom:
secretKeyRef:
name: atproto-secrets
key: pds-plc-rotation-key
- name: PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX
valueFrom:
secretKeyRef:
name: atproto-secrets
key: pds-repo-signing-key
- name: PDS_JWT_SECRET
valueFrom:
secretKeyRef:
name: atproto-secrets
key: pds-jwt-secret
- name: PDS_EMAIL_SMTP_URL
valueFrom:
secretKeyRef:
name: atproto-secrets
key: pds-email-smtp-url
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /xrpc/_health
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /xrpc/_health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: pds-data

53
k8s/plc.yaml Normal file
View File

@@ -0,0 +1,53 @@
apiVersion: v1
kind: Service
metadata:
name: plc
namespace: atproto
spec:
selector:
app: plc
ports:
- port: 3000
targetPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: plc
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: plc
template:
metadata:
labels:
app: plc
spec:
containers:
- name: plc
image: registry/plc
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
value: "postgres://postgres:postgres@database/plc"
- name: DB_CREDS_JSON
value: '{"username":"postgres","password":"postgres","host":"database","port":"5432","database":"plc"}'
- name: ENABLE_MIGRATIONS
value: "true"
- name: DB_MIGRATE_CREDS_JSON
value: '{"username":"postgres","password":"postgres","host":"database","port":"5432","database":"plc"}'
livenessProbe:
httpGet:
path: /_health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /_health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5

89
k8s/postgres.yaml Normal file
View File

@@ -0,0 +1,89 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-init
namespace: atproto
data:
init.sql: |
CREATE DATABASE plc;
GRANT ALL PRIVILEGES ON DATABASE plc TO postgres;
CREATE DATABASE bgs;
CREATE DATABASE carstore;
GRANT ALL PRIVILEGES ON DATABASE bgs TO postgres;
GRANT ALL PRIVILEGES ON DATABASE carstore TO postgres;
CREATE DATABASE bsky;
GRANT ALL PRIVILEGES ON DATABASE bsky TO postgres;
CREATE DATABASE ozone;
GRANT ALL PRIVILEGES ON DATABASE ozone TO postgres;
CREATE DATABASE pds;
GRANT ALL PRIVILEGES ON DATABASE pds TO postgres;
CREATE DATABASE bsync;
GRANT ALL PRIVILEGES ON DATABASE bsync TO postgres;
---
apiVersion: v1
kind: Service
metadata:
name: database
namespace: atproto
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: atproto
spec:
serviceName: database
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
env:
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: postgres
- name: POSTGRES_DB
value: healthcheck
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
- name: init
mountPath: /docker-entrypoint-initdb.d
livenessProbe:
exec:
command: ["pg_isready", "-U", "postgres", "-d", "healthcheck"]
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
exec:
command: ["pg_isready", "-U", "postgres", "-d", "healthcheck"]
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: init
configMap:
name: postgres-init
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi

58
k8s/redis.yaml Normal file
View File

@@ -0,0 +1,58 @@
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: atproto
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
exec:
command: ["redis-cli", "ping"]
periodSeconds: 5
readinessProbe:
exec:
command: ["redis-cli", "ping"]
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: redis-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-data
namespace: atproto
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi

10
k8s/secrets.env.example Normal file
View File

@@ -0,0 +1,10 @@
pds-admin-password=
pds-plc-rotation-key=
pds-repo-signing-key=
pds-jwt-secret=
pds-email-smtp-url=
bsky-admin-passwords=
bsky-service-signing-key=
bgs-admin-key=
ozone-admin-password=
ozone-signing-key-hex=

52
k8s/social-app.yaml Normal file
View File

@@ -0,0 +1,52 @@
apiVersion: v1
kind: Service
metadata:
name: social-app
namespace: atproto
spec:
selector:
app: social-app
ports:
- port: 8100
targetPort: 8100
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: social-app
namespace: atproto
spec:
replicas: 1
selector:
matchLabels:
app: social-app
template:
metadata:
labels:
app: social-app
spec:
containers:
- name: social-app
image: registry/social-app
command: ["/usr/bin/bskyweb", "serve"]
ports:
- containerPort: 8100
env:
- name: ATP_APPVIEW_HOST
value: "https://public.api.bsky.app"
- name: EXPO_PUBLIC_BLUESKY_PROXY_DID
value: "did:web:api.bsky.app"
- name: EXPO_PUBLIC_ENV
value: "production"
livenessProbe:
httpGet:
path: /
port: 8100
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8100
initialDelaySeconds: 5
periodSeconds: 5

View File

@@ -1,106 +0,0 @@
diff --git a/bgs/bgs.go b/bgs/bgs.go
index 35dfab9d..7e225f6c 100644
--- a/bgs/bgs.go
+++ b/bgs/bgs.go
@@ -112,6 +112,7 @@ type BGSConfig struct {
DefaultRepoLimit int64
ConcurrencyPerPDS int64
MaxQueuePerPDS int64
+ InitialNewPDSPerDayLimit int64
NumCompactionWorkers int
}
@@ -122,6 +123,7 @@ func DefaultBGSConfig() *BGSConfig {
DefaultRepoLimit: 100,
ConcurrencyPerPDS: 100,
MaxQueuePerPDS: 1_000,
+ InitialNewPDSPerDayLimit: 10,
NumCompactionWorkers: 2,
}
}
@@ -159,6 +161,7 @@ func NewBGS(db *gorm.DB, ix *indexer.Indexer, repoman *repomgr.RepoManager, evtm
slOpts.DefaultRepoLimit = config.DefaultRepoLimit
slOpts.ConcurrencyPerPDS = config.ConcurrencyPerPDS
slOpts.MaxQueuePerPDS = config.MaxQueuePerPDS
+ slOpts.DefaultNewPDSPerDayLimit = config.InitialNewPDSPerDayLimit
s, err := NewSlurper(db, bgs.handleFedEvent, slOpts)
if err != nil {
return nil, err
diff --git a/bgs/fedmgr.go b/bgs/fedmgr.go
index 42ce7407..9072ba05 100644
--- a/bgs/fedmgr.go
+++ b/bgs/fedmgr.go
@@ -45,6 +45,7 @@ type Slurper struct {
MaxQueuePerPDS int64
NewPDSPerDayLimiter *slidingwindow.Limiter
+ initialNewPDSPerDayLimit int64
newSubsDisabled bool
trustedDomains []string
@@ -70,6 +71,7 @@ type SlurperOptions struct {
DefaultRepoLimit int64
ConcurrencyPerPDS int64
MaxQueuePerPDS int64
+ DefaultNewPDSPerDayLimit int64
}
func DefaultSlurperOptions() *SlurperOptions {
@@ -82,6 +84,7 @@ func DefaultSlurperOptions() *SlurperOptions {
DefaultRepoLimit: 100,
ConcurrencyPerPDS: 100,
MaxQueuePerPDS: 1_000,
+ DefaultNewPDSPerDayLimit: 10,
}
}
@@ -112,6 +115,7 @@ func NewSlurper(db *gorm.DB, cb IndexCallback, opts *SlurperOptions) (*Slurper,
ssl: opts.SSL,
shutdownChan: make(chan bool),
shutdownResult: make(chan []error),
+ initialNewPDSPerDayLimit: opts.DefaultNewPDSPerDayLimit,
}
if err := s.loadConfig(); err != nil {
return nil, err
@@ -224,13 +228,15 @@ func (s *Slurper) loadConfig() error {
}
if sc.ID == 0 {
- if err := s.db.Create(&SlurpConfig{}).Error; err != nil {
+ sc.NewPDSPerDayLimit = s.initialNewPDSPerDayLimit
+ if err := s.db.Create(&SlurpConfig{ NewPDSPerDayLimit: s.initialNewPDSPerDayLimit, }).Error; err != nil {
return err
}
}
s.newSubsDisabled = sc.NewSubsDisabled
s.trustedDomains = sc.TrustedDomains
+ s.initialNewPDSPerDayLimit = sc.NewPDSPerDayLimit
s.NewPDSPerDayLimiter, _ = slidingwindow.NewLimiter(time.Hour*24, sc.NewPDSPerDayLimit, windowFunc)
diff --git a/cmd/bigsky/main.go b/cmd/bigsky/main.go
index 540796f5..cb946aed 100644
--- a/cmd/bigsky/main.go
+++ b/cmd/bigsky/main.go
@@ -195,6 +195,12 @@ func run(args []string) error {
EnvVars: []string{"RELAY_EVENT_PLAYBACK_TTL"},
Value: 72 * time.Hour,
},
+ &cli.Int64Flag{
+ Name: "newpds-perday-limit",
+ EnvVars: []string{"RELAY_NEWPDS_PERDAY_LIMIT"},
+ Value: 10,
+ Usage: "initial value for NewPDSPerDayLimit",
+ },
&cli.IntFlag{
Name: "num-compaction-workers",
EnvVars: []string{"RELAY_NUM_COMPACTION_WORKERS"},
@@ -418,6 +424,7 @@ func runBigsky(cctx *cli.Context) error {
bgsConfig.ConcurrencyPerPDS = cctx.Int64("concurrency-per-pds")
bgsConfig.MaxQueuePerPDS = cctx.Int64("max-queue-per-pds")
bgsConfig.DefaultRepoLimit = cctx.Int64("default-repo-limit")
+ bgsConfig.InitialNewPDSPerDayLimit = cctx.Int64("newpds-perday-limit")
bgsConfig.NumCompactionWorkers = cctx.Int("num-compaction-workers")
bgs, err := libbgs.NewBGS(db, ix, repoman, evtman, cachedidr, rf, hr, bgsConfig)
if err != nil {

View File

@@ -1,13 +1,11 @@
diff --git a/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts b/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts diff --git a/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts b/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts
index f653b0353..45c45fac1 100644
--- a/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts --- a/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts
+++ b/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts +++ b/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts
@@ -53,7 +53,7 @@ export function createAuthorizationPageMiddleware< @@ -74,7 +74,7 @@
res.setHeader('Cache-Control', 'no-store') // @TODO Consider removing this altogether to allow hosting PDS and app on
res.setHeader('Pragma', 'no-cache') // the same site but different origins (different subdomains).
- validateFetchSite(req, ['same-origin', 'cross-site', 'none'])
- validateFetchSite(req, ['cross-site', 'none']) + validateFetchSite(req, ['same-origin', 'same-site', 'cross-site', 'none'])
+ validateFetchSite(req, ['cross-site', 'same-site', 'none'])
validateFetchMode(req, ['navigate']) validateFetchMode(req, ['navigate'])
validateFetchDest(req, ['document']) validateFetchDest(req, ['document'])
validateOrigin(req, issuerOrigin) validateOrigin(req, issuerOrigin)

View File

@@ -1,12 +0,0 @@
diff --git a/bgs/fedmgr.go b/bgs/fedmgr.go
index 2235c6e..e69de29 100644
--- a/bgs/fedmgr.go
+++ b/bgs/fedmgr.go
@@ -342,6 +342,7 @@ var ErrNewSubsDisabled = fmt.Errorf("new subscriptions temporarily disabled")
// Checks whether a host is allowed to be subscribed to
// must be called with the slurper lock held
func (s *Slurper) canSlurpHost(host string) bool {
+ return true
// Check if we're over the limit for new PDSs today
if !s.NewPDSPerDayLimiter.Allow() {
return false

View File

@@ -1,26 +1,61 @@
diff --git a/indexer/indexer.go b/indexer/indexer.go diff --git a/events/dbpersist/dbpersist.go b/events/dbpersist/dbpersist.go
index e3c28ec1..66663de0 100644 index 04e9fb87..5e47218e 100644
--- a/indexer/indexer.go --- a/events/dbpersist/dbpersist.go
+++ b/indexer/indexer.go +++ b/events/dbpersist/dbpersist.go
@@ -104,13 +104,20 @@ func (ix *Indexer) HandleRepoEvent(ctx context.Context, evt *repomgr.RepoEvent) @@ -306,6 +306,12 @@ func (p *DbPersistence) RecordFromRepoCommit(ctx context.Context, evt *comatprot
toobig = true return nil, err
} }
+ // Normalize empty string to nil for since field + // Normalize empty string to nil for since field
+ // Empty string fails TID validation on consumers
+ var since *string + var since *string
+ if evt.Since != nil && *evt.Since != "" { + if evt.Since != nil && *evt.Since != "" {
+ since = evt.Since + since = evt.Since
+ } + }
+ +
ix.log.Debug("Sending event", "did", did) rer := RepoEventRecord{
if err := ix.events.AddEvent(ctx, &events.XRPCStreamEvent{ Commit: &models.DbCID{CID: cid.Cid(evt.Commit)},
RepoCommit: &comatproto.SyncSubscribeRepos_Commit{ //Prev
Repo: did, @@ -315,7 +321,7 @@ func (p *DbPersistence) RecordFromRepoCommit(ctx context.Context, evt *comatprot
Blocks: slice, Time: t,
Rebase: evt.Rebase,
Rev: evt.Rev, Rev: evt.Rev,
- Since: evt.Since, - Since: evt.Since,
+ Since: since, + Since: since,
Commit: lexutil.LexLink(evt.NewRoot), }
Time: time.Now().Format(util.ISO8601),
Ops: outops, opsb, err := json.Marshal(evt.Ops)
@@ -339,6 +345,12 @@ func (p *DbPersistence) RecordFromRepoSync(ctx context.Context, evt *comatproto.
return nil, err
}
+ // Normalize empty string to nil for since field
+ var since *string
+ if evt.Since != nil && *evt.Since != "" {
+ since = evt.Since
+ }
+
rer := RepoEventRecord{
Repo: uid,
Type: "repo_sync",
@@ -555,6 +567,12 @@ func (p *DbPersistence) hydrateCommit(ctx context.Context, rer *RepoEventRecord)
return nil, err
}
+ // Normalize empty string to nil for since field
+ var since *string
+ if rer.Since != nil && *rer.Since != "" {
+ since = rer.Since
+ }
+
out := &comatproto.SyncSubscribeRepos_Commit{
Seq: int64(rer.Seq),
Repo: did,
@@ -564,7 +582,7 @@ func (p *DbPersistence) hydrateCommit(ctx context.Context, rer *RepoEventRecord)
Rebase: rer.Rebase,
Ops: ops,
Rev: rer.Rev,
- Since: rer.Since,
+ Since: since,
}
cs, err := p.readCarSlice(ctx, rer)

View File

@@ -1,22 +0,0 @@
diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx
index 1234567..89abcdef 100644
--- a/src/lib/statsig/statsig.tsx
+++ b/src/lib/statsig/statsig.tsx
@@ -266,6 +266,7 @@ export async function tryFetchGates(
}
export function initialize() {
+ if (!SDK_KEY) return Promise.resolve()
return Statsig.initialize(SDK_KEY, null, createStatsigOptions([]))
}
@@ -310,6 +311,9 @@ export function Provider({children}: {children: React.ReactNode}) {
return () => clearInterval(id)
}, [handleIntervalTick])
+ if (!SDK_KEY) {
+ return <GateCache.Provider value={gateCache}>{children}</GateCache.Provider>
+ }
return (
<GateCache.Provider value={gateCache}>
<StatsigProvider

View File

@@ -7,12 +7,12 @@ path: help/index.html
## Getting Started ## Getting Started
1. Download the app from the App Store 1. Download the app from the App Store
2. Sign in with your AT Protocol handle 2. Sign in with your handle
3. Start connecting with your community 3. Start connecting with your community
## Authentication ## Authentication
This app uses AT Protocol OAuth for secure authentication. Your credentials are never stored on our servers. This app uses OAuth for secure authentication. Your credentials are never stored on our servers.
## Features ## Features
@@ -36,4 +36,4 @@ This app uses AT Protocol OAuth for secure authentication. Your credentials are
## Contact Support ## Contact Support
For additional help, please visit our Git repository or contact us through AT Protocol. For additional help, please visit our Git repository or contact us through our social account.

View File

@@ -12,7 +12,7 @@ This application is open source software.
This app uses the following open source libraries: This app uses the following open source libraries:
- **AT Protocol** - MIT License - **Decentralized Social Protocol** - MIT License
- **React Native** - MIT License - **React Native** - MIT License
- **Expo** - MIT License - **Expo** - MIT License
@@ -22,7 +22,7 @@ App icons and graphics are proprietary unless otherwise noted.
## Attribution ## Attribution
Built with AT Protocol by syui. Built by syui.
## Contact ## Contact

View File

@@ -8,7 +8,7 @@ path: privacy/index.html
This application collects minimal information necessary for its operation: This application collects minimal information necessary for its operation:
- **Account Information**: Your AT Protocol handle and DID for authentication - **Account Information**: Your handle and DID for authentication
- **Usage Data**: Basic app usage statistics to improve the service - **Usage Data**: Basic app usage statistics to improve the service
## How We Use Your Information ## How We Use Your Information
@@ -19,7 +19,7 @@ This application collects minimal information necessary for its operation:
## Data Storage ## Data Storage
Your data is stored securely on AT Protocol compatible servers. We do not sell or share your personal information with third parties. Your data is stored securely on decentralized social servers. We do not sell or share your personal information with third parties.
## Your Rights ## Your Rights

Some files were not shown because too many files have changed in this diff Show More