diff --git a/README.md b/README.md index e69de29..d8ea835 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,119 @@ +# atproto + +https://github.com/bluesky-social/atproto + +- @ +- [at]mosphere +- at://proto +- at protocol + +## account + +[@ai.syu.is](https://web.syu.is/profile/ai.syu.is) + +- https://at.syu.is/at/yui.syui.ai +- https://plc.syu.is/did:plc:6qyecktefllvenje24fcxnie +- https://plc.directory/did:plc:ytvoptig4ddshmwdsjmhtcym + +```sh +$ curl -sL syu.is/xrpc/_health +{"version":"0.4.65"} + +# 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 +$ curl -sL "syu.is/xrpc/com.atproto.repo.describeRepo?repo=ai.syu.is" |jq -r .did +did:plc:6qyecktefllvenje24fcxnie + +$ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=ai.syu.is&collection=app.bsky.feed.post&reverse=true&limit=1" +{"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 + +> at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/cmd + +- https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/cmd +- https://feed.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/cmd +- https://desc.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/cmd + +## link + +- https://github.com/bluesky-social/atproto +- https://github.com/itaru2622/bluesky-selfhost-env +- https://github.com/bluesky-social/atproto/discussions/2026 + +## self-host + +currently, bsky and bsync require patches to function properly. additionally, social-app is not displaying avatars. for components that are not working, it's recommended to use [itaru2622/bluesky-selfhost-env](https://github.com/itaru2622/bluesky-selfhost-env). this repository provides an environment for self-hosting bluesky. + +- bsky = appview +- ozone = mod + +|name|service|patch| +|---|---|---| +|pds|https://github.com/bluesky-social/atproto/blob/main/services/pds/Dockerfile|| +|bsky|https://github.com/bluesky-social/atproto/blob/main/services/bsky/Dockerfile|[itaru2622/bluesky-atproto-bsky](https://hub.docker.com/r/itaru2622/bluesky-atproto-bsky)| +|bsync|https://github.com/bluesky-social/atproto/blob/main/services/bsync/Dockerfile|| +|ozone|https://github.com/bluesky-social/atproto/blob/main/services/ozone/Dockerfile|| +|plc|https://github.com/did-method-plc/did-method-plc/tree/main/packages/server|| +|bgs|https://github.com/bluesky-social/indigo/tree/main/cmd/bigsky|| +|feed|https://github.com/bluesky-social/feed-generator|| +|web|https://github.com/bluesky-social/social-app|[bluesky-selfhost-env](https://github.com/itaru2622/bluesky-selfhost-env/blob/master/patching/160-social-app-disable-hackModifyThumbnailPath.diff)| + +```sh +# BSKY_IMG_URI_ENDPOINT, BSKY_BLOB_CACHE_LOC +# avatar link example +1. https://appview.${host}/img/avatar/plain/${did}/${cid}@jpeg +2. https://${host}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid} +``` + +docker compose will not be published unless you write ports. it is only valid internally. add ports only for what you want to publish. + +## api + +```sh +# create account +url=https://${pds}/xrpc/com.atproto.server.createAccount +json="{\"email\": \"$email\", \"handle\": \"$handle\", \"password\": \"$password\"}" +curl -X POST -H "Content-Type: application/json" -d $json -sL $url +``` + +change `src/pds/handle` to use a name of 3 characters or less. also, you cannot create an account with a name of 3 characters or less from social-app (web client). please create it from api. + +- [/atproto/packages/pds/src/handle/index.ts](https://github.com/bluesky-social/atproto/blame/d4d5a6edba972c0e9976289bde8bc0b42ff547ca/packages/pds/src/handle/index.ts#L86-L88) + +```sh +# invite code +admin_password=xxx +url=https://$host/xrpc/com.atproto.server.createInviteCode +json="{\"useCount\":1}" +curl -X POST -u admin:${admin_password} -H "Content-Type: application/json" -d "$json" -sL $url +``` + +## oauth + +```sh +# https://github.com/bluesky-social/cookbook/tree/main/python-oauth-web-app +$ cd ./repos/cookbook/python-oauth-web-app +$ rye sync +$ rye run python3 -c 'import secrets; print(secrets.token_hex())'|xargs echo FLASK_SECRET_KEY|tr -d ' ' >> .env +$ rye run python3 generate_jwk.py |xargs echo FLASK_CLIENT_SECRET_JWK|tr -d ' ' >> .env +$ cat .env +$ rye run flask run +``` + +please access `127.0.0.1:5000`. it may not work if you use localhost. + +also, oauth doesn't work on localhost. use [ngrok](https://ngrok.com/), [tailscale](https://tailscale.com/), [cloudflare](https://github.com/cloudflare/cloudflared). + +```sh +$ ngrok http http://localhost:5000 +``` + +```sh +$ cloudflared tunnel --url http://localhost:5000 +``` diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..8ecc4d0 --- /dev/null +++ b/build.sh @@ -0,0 +1,30 @@ +#!/bin/zsh + +d=${0:a:h} +cd $d/repos + +t=" +https://github.com/bluesky-social/atproto +https://github.com/did-method-plc/did-method-plc +https://github.com/bluesky-social/feed-generator +https://github.com/bluesky-social/social-app +https://github.com/bluesky-social/cookbook +https://github.com/itaru2622/bluesky-selfhost-env +" + +t=`echo $t|grep -v '^$'` +n=`echo $t|wc -l` + +for ((i=1;i<=$n;i++)) +do + tt=`echo $t|awk "NR==$i"` + dd=$d/repos/$tt:t + if [ -d $dd ];then + echo ok + cd $dd + git pull + cd $d/repos + else + git clone $tt + fi +done diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..4679cad --- /dev/null +++ b/compose.yaml @@ -0,0 +1,166 @@ +services: + + database: + image: postgres:16-alpine + restart: always + env_file: + - ./envs/postgres + volumes: + - ./configs/postgres/init/:/docker-entrypoint-initdb.d/ + - ./data/postgres/:/var/lib/postgresql/data/ + healthcheck: + test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" + interval: 5s + retries: 20 + + redis: + image: redis:alpine + restart: always + volumes: + - ./data/redis/:/data/ + healthcheck: + test: ["CMD", "redis-cli", "ping", "|", "grep", "PONG"] + interval: 1s + timeout: 5s + retries: 5 + + pds-fix-permission: + image: alpine:latest + volumes: + - ./data/pds/:/data/ + command: chown 1000.1000 /data + + bsky-fix-permission: + image: alpine:latest + volumes: + - ./data/bsky/:/data/ + command: chown 1000.1000 /data + + plc: + ports: + - 2582:3000 + build: + context: ./repos/did-method-plc/ + dockerfile: packages/server/Dockerfile + restart: always + env_file: + - ./envs/plc + depends_on: + database: + condition: service_healthy + + pds: + ports: + - 2583:3000 + #image: itaru2622/bluesky-atproto-pds + #image: ghcr.io/bluesky-social/pds:latest + build: + context: ./repos/atproto/ + dockerfile: services/pds/Dockerfile + restart: always + env_file: + - ./envs/pds + volumes: + - ./data/pds/:/data/ + depends_on: + database: + condition: service_healthy + pds-fix-permission: + condition: service_completed_successfully + + bgs: + ports: + - 2470:2470 + build: + context: ./repos/indigo/ + dockerfile: cmd/bigsky/Dockerfile + restart: always + env_file: + - ./envs/bgs + volumes: + - ./data/bgs/:/data/ + depends_on: + database: + condition: service_healthy + + ozone: + ports: + - 2585:3000 + build: + context: ./repos/atproto/ + dockerfile: services/ozone/Dockerfile + restart: always + command: node --enable-source-maps api.js + volumes: + - ./data/ozone/:/data/ + - ./repos/ozone.js:/app/services/ozone/api.js:ro + env_file: + - ./envs/ozone + healthcheck: + test: "wget -q --spider http://localhost:3000/xrpc/_health" + interval: 5s + retries: 20 + depends_on: + database: + condition: service_healthy + + ozone-daemon: + build: + context: ./repos/atproto/ + dockerfile: services/ozone/Dockerfile + restart: always + command: node --enable-source-maps daemon.js + env_file: + - ./envs/ozone + depends_on: + ozone: + condition: service_healthy + database: + condition: service_healthy + + social-app: + ports: + - 8100:8100 + #image: itaru2622/bluesky-social-app + build: + context: ./repos/social-app/ + dockerfile: Dockerfile + restart: always + env_file: + - ./envs/social-app + command: "/usr/bin/bskyweb serve" + + bsky: + ports: + - 2584:2584 + image: itaru2622/bluesky-atproto-bsky + #build: + # context: ./repos/atproto/ + # dockerfile: services/bsky/Dockerfile + restart: always + env_file: + - ./envs/bsky + user: root + volumes: + - ./data/bsky/:/data/ + # - ./repos/bsky.js:/app/services/bsky/api.js:ro + command: node --enable-source-maps api.js + depends_on: + database: + condition: service_healthy + redis: + condition: service_healthy + bsky-fix-permission: + condition: service_completed_successfully + + feed: + ports: + - 2586:2586 + build: + context: ./repos/feed-generator/ + restart: always + env_file: + - ./envs/feed + volumes: + - ./data/feed/:/data/ + diff --git a/configs/postgres/init/init.sql b/configs/postgres/init/init.sql new file mode 100644 index 0000000..f86b7c9 --- /dev/null +++ b/configs/postgres/init/init.sql @@ -0,0 +1,35 @@ +-- PLC +CREATE DATABASE plc; +GRANT ALL PRIVILEGES ON DATABASE plc TO postgres; + +-- BGS +CREATE DATABASE bgs; +CREATE DATABASE carstore; +GRANT ALL PRIVILEGES ON DATABASE bgs TO postgres; +GRANT ALL PRIVILEGES ON DATABASE carstore TO postgres; + +-- bsky(AppView) +--CREATE DATABASE appview; +--GRANT ALL PRIVILEGES ON DATABASE appview TO postgres; +CREATE DATABASE bsky; +GRANT ALL PRIVILEGES ON DATABASE bsky TO postgres; + +-- ozone(Moderation) +--CREATE DATABASE moderation; +--GRANT ALL PRIVILEGES ON DATABASE moderation TO postgres; +CREATE DATABASE ozone; +GRANT ALL PRIVILEGES ON DATABASE ozone TO postgres; + +-- search(palomar) +CREATE DATABASE search; +GRANT ALL PRIVILEGES ON DATABASE search TO postgres; +--CREATE DATABASE palomar; +--GRANT ALL PRIVILEGES ON DATABASE palomar TO postgres; + +-- PDS +CREATE DATABASE pds; +GRANT ALL PRIVILEGES ON DATABASE pds TO postgres; + +-- BSYNC +CREATE DATABASE bsync; +GRANT ALL PRIVILEGES ON DATABASE bsync TO postgres; diff --git a/envs/bgs b/envs/bgs new file mode 100644 index 0000000..fa79239 --- /dev/null +++ b/envs/bgs @@ -0,0 +1,6 @@ +DATABASE_URL=postgres://postgres:postgres@database/bgs +CARSTORE_DATABASE_URL=postgres://postgres:postgres@database/carstore +DATA_DIR=/data +ATP_PLC_HOST=https://plc.${host} + +BGS_ADMIN_KEY diff --git a/envs/bsky b/envs/bsky new file mode 100644 index 0000000..5bebd98 --- /dev/null +++ b/envs/bsky @@ -0,0 +1,19 @@ +BSKY_PORT=2584 +BSKY_BLOB_CACHE_LOC=/data/ +BSKY_BSYNC_HTTP_VERSION=1.1 +BSKY_BSYNC_PORT=3002 +BSKY_BSYNC_URL=http://bsky:3002 +BSKY_COURIER_URL=http://fake-courier.example.invalid/ +BSKY_DATAPLANE_HTTP_VERSION=1.1 +BSKY_DATAPLANE_PORT=3001 +BSKY_DATAPLANE_URLS=http://bsky:3001 +BSKY_DB_POSTGRES_URL=postgres://postgres:postgres@database/bsky +BSKY_DID_PLC_URL=https://plc.${host} +BSKY_PUBLIC_URL=https://bsky.${host} +BSKY_REPO_PROVIDER=wss://bgs.${host} +BSKY_SERVER_DID=did:web:bsky.${host} +MOD_SERVICE_DID=did:web:ozone.${host} + +#BSKY_IMG_URI_ENDPOINT=https://bsky.${host}/img +BSKY_ADMIN_PASSWORDS +BSKY_SERVICE_SIGNING_KEY diff --git a/envs/ozone b/envs/ozone new file mode 100644 index 0000000..72b0158 --- /dev/null +++ b/envs/ozone @@ -0,0 +1,21 @@ +OZONE_SERVER_DID=did:web:ozone.${host} +OZONE_PUBLIC_URL=https://ozone.${host} +OZONE_ADMIN_HANDLE=${user}.${host} +OZONE_MODERATOR_DIDS=${did} +OZONE_ADMIN_DIDS=${did} +OZONE_DB_POSTGRES_URL=postgres://postgres:postgres@database/ozone +OZONE_DID_PLC_URL=https://plc.${host} +NEXT_PUBLIC_PLC_DIRECTORY_URL=https://plc.${host} +NEXT_PUBLIC_OZONE_SERVICE_DID=did:web:ozone.${host} +NEXT_PUBLIC_SOCIAL_APP_DOMAIN=mod.${host} +NEXT_PUBLIC_SOCIAL_APP_URL=https://mod.${host} +OZONE_APPVIEW_DID=did:web:bsky.${host} +OZONE_APPVIEW_URL=https://bsky.${host} +OZONE_APPVIEW_PUSH_EVENTS=false +OZONE_PDS_DID=did:web:${host} +OZONE_PDS_URL=https://${host} +OZONE_DEV_MODE=true +OZONE_DB_MIGRATE=1 + +OZONE_ADMIN_PASSWORD +OZONE_SIGNING_KEY_HEX diff --git a/envs/pds b/envs/pds new file mode 100644 index 0000000..fabc2a4 --- /dev/null +++ b/envs/pds @@ -0,0 +1,21 @@ +PDS_HOSTNAME=${host} +PDS_DB_POSTGRES_URL=postgres://postgres:postgres@database/pds +PDS_DATA_DIRECTORY=/data +PDS_BLOBSTORE_DISK_LOCATION=/data/img/static +#PDS_BLOBSTORE_DISK_TMP_LOCATION=/data/img/tmp +PDS_BSKY_APP_VIEW_DID=did:web:bsky.${host} +PDS_BSKY_APP_VIEW_URL=https://bsky.${host} +PDS_CRAWLERS=https://bgs.${host} +PDS_DEV_MODE=true +PDS_DID_PLC_URL=https://plc.${host} +PDS_ENABLE_DID_DOC_WITH_SESSION=true +PDS_INVITE_INTERVAL=604800000 +PDS_SERVICE_DID=did:web:${host} +PDS_EMAIL_FROM_ADDRESS=no-reply@${host} + +PDS_INVITE_REQUIRED=true +PDS_EMAIL_SMTP_URL=smtps://${user}:${app_password}@smtp.gmail.com +PDS_ADMIN_PASSWORD +PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX +PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX +PDS_JWT_SECRET diff --git a/envs/plc b/envs/plc new file mode 100644 index 0000000..f0ccb52 --- /dev/null +++ b/envs/plc @@ -0,0 +1,4 @@ +DATABASE_URL=postgres://postgres:postgres@database/plc +DB_CREDS_JSON='{"username":"postgres","password":"postgres","host":"database","port":"5432","database":"plc"}' +ENABLE_MIGRATIONS=true +DB_MIGRATE_CREDS_JSON='{"username":"postgres","password":"postgres","host":"database","port":"5432","database":"plc"}' diff --git a/envs/postgres b/envs/postgres new file mode 100644 index 0000000..4184165 --- /dev/null +++ b/envs/postgres @@ -0,0 +1,4 @@ +#POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=healthcheck diff --git a/envs/social-app b/envs/social-app new file mode 100644 index 0000000..a6752de --- /dev/null +++ b/envs/social-app @@ -0,0 +1 @@ +ATP_APPVIEW_HOST=https://bsky.${host} diff --git a/icons/Logotype.tsx b/icons/Logotype.tsx new file mode 100644 index 0000000..b087a56 --- /dev/null +++ b/icons/Logotype.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import Svg, {Path, SvgProps, PathProps} from 'react-native-svg' + +import {usePalette} from '#/lib/hooks/usePalette' + +const ratio = 17 / 64 + +export function Logotype({ + fill, + ...rest +}: {fill?: PathProps['fill']} & SvgProps) { + const pal = usePalette('default') + // @ts-ignore it's fiiiiine + const size = parseInt(rest.width || 32) + + return ( + + + + + + + + + + + + ) +} diff --git a/icons/title.svg b/icons/title.svg new file mode 100644 index 0000000..73a0b2f --- /dev/null +++ b/icons/title.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/lexicons/com/atproto/repo/applyWrites.json b/lexicons/com/atproto/repo/applyWrites.json new file mode 100644 index 0000000..26dd96c --- /dev/null +++ b/lexicons/com/atproto/repo/applyWrites.json @@ -0,0 +1,126 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.applyWrites", + "defs": { + "main": { + "type": "procedure", + "description": "Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "writes"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "validate": { + "type": "boolean", + "description": "Can be set to 'false' to skip Lexicon schema validation of record data across all operations, 'true' to require it, or leave unset to validate only for known Lexicons." + }, + "writes": { + "type": "array", + "items": { + "type": "union", + "refs": ["#create", "#update", "#delete"], + "closed": true + } + }, + "swapCommit": { + "type": "string", + "description": "If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.", + "format": "cid" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [], + "properties": { + "commit": { + "type": "ref", + "ref": "com.atproto.repo.defs#commitMeta" + }, + "results": { + "type": "array", + "items": { + "type": "union", + "refs": ["#createResult", "#updateResult", "#deleteResult"], + "closed": true + } + } + } + } + }, + "errors": [ + { + "name": "InvalidSwap", + "description": "Indicates that the 'swapCommit' parameter did not match current commit." + } + ] + }, + "create": { + "type": "object", + "description": "Operation which creates a new record.", + "required": ["collection", "value"], + "properties": { + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string", "maxLength": 15 }, + "value": { "type": "unknown" } + } + }, + "update": { + "type": "object", + "description": "Operation which updates an existing record.", + "required": ["collection", "rkey", "value"], + "properties": { + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string" }, + "value": { "type": "unknown" } + } + }, + "delete": { + "type": "object", + "description": "Operation which deletes an existing record.", + "required": ["collection", "rkey"], + "properties": { + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string" } + } + }, + "createResult": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "validationStatus": { + "type": "string", + "knownValues": ["valid", "unknown"] + } + } + }, + "updateResult": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "validationStatus": { + "type": "string", + "knownValues": ["valid", "unknown"] + } + } + }, + "deleteResult": { + "type": "object", + "required": [], + "properties": {} + } + } +} diff --git a/lexicons/com/atproto/repo/createRecord.json b/lexicons/com/atproto/repo/createRecord.json new file mode 100644 index 0000000..7290085 --- /dev/null +++ b/lexicons/com/atproto/repo/createRecord.json @@ -0,0 +1,72 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.createRecord", + "defs": { + "main": { + "type": "procedure", + "description": "Create a single new repository record. Requires auth, implemented by PDS.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "collection", "record"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "rkey": { + "type": "string", + "description": "The Record Key.", + "maxLength": 15 + }, + "validate": { + "type": "boolean", + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons." + }, + "record": { + "type": "unknown", + "description": "The record itself. Must contain a $type field." + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "commit": { + "type": "ref", + "ref": "com.atproto.repo.defs#commitMeta" + }, + "validationStatus": { + "type": "string", + "knownValues": ["valid", "unknown"] + } + } + } + }, + "errors": [ + { + "name": "InvalidSwap", + "description": "Indicates that 'swapCommit' didn't match current repo commit." + } + ] + } + } +} diff --git a/lexicons/com/atproto/repo/defs.json b/lexicons/com/atproto/repo/defs.json new file mode 100644 index 0000000..0f5128f --- /dev/null +++ b/lexicons/com/atproto/repo/defs.json @@ -0,0 +1,14 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.defs", + "defs": { + "commitMeta": { + "type": "object", + "required": ["cid", "rev"], + "properties": { + "cid": { "type": "string", "format": "cid" }, + "rev": { "type": "string" } + } + } + } +} diff --git a/lexicons/com/atproto/repo/deleteRecord.json b/lexicons/com/atproto/repo/deleteRecord.json new file mode 100644 index 0000000..fb9b90b --- /dev/null +++ b/lexicons/com/atproto/repo/deleteRecord.json @@ -0,0 +1,56 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.deleteRecord", + "defs": { + "main": { + "type": "procedure", + "description": "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "collection", "rkey"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "rkey": { + "type": "string", + "description": "The Record Key." + }, + "swapRecord": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous record by CID." + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "ref", + "ref": "com.atproto.repo.defs#commitMeta" + } + } + } + }, + "errors": [{ "name": "InvalidSwap" }] + } + } +} diff --git a/lexicons/com/atproto/repo/describeRepo.json b/lexicons/com/atproto/repo/describeRepo.json new file mode 100644 index 0000000..b1ce2b6 --- /dev/null +++ b/lexicons/com/atproto/repo/describeRepo.json @@ -0,0 +1,51 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.describeRepo", + "defs": { + "main": { + "type": "query", + "description": "Get information about an account and repository, including the list of collections. Does not require auth.", + "parameters": { + "type": "params", + "required": ["repo"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "handle", + "did", + "didDoc", + "collections", + "handleIsCorrect" + ], + "properties": { + "handle": { "type": "string", "format": "handle" }, + "did": { "type": "string", "format": "did" }, + "didDoc": { + "type": "unknown", + "description": "The complete DID document for this account." + }, + "collections": { + "type": "array", + "description": "List of all the collections (NSIDs) for which this repo contains at least one record.", + "items": { "type": "string", "format": "nsid" } + }, + "handleIsCorrect": { + "type": "boolean", + "description": "Indicates if handle is currently valid (resolves bi-directionally)" + } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/repo/getRecord.json b/lexicons/com/atproto/repo/getRecord.json new file mode 100644 index 0000000..261c1cd --- /dev/null +++ b/lexicons/com/atproto/repo/getRecord.json @@ -0,0 +1,45 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single record from a repository. Does not require auth.", + "parameters": { + "type": "params", + "required": ["repo", "collection", "rkey"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "rkey": { "type": "string", "description": "The Record Key." }, + "cid": { + "type": "string", + "format": "cid", + "description": "The CID of the version of the record. If not specified, then return the most recent version." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "value"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "value": { "type": "unknown" } + } + } + }, + "errors": [{ "name": "RecordNotFound" }] + } + } +} diff --git a/lexicons/com/atproto/repo/importRepo.json b/lexicons/com/atproto/repo/importRepo.json new file mode 100644 index 0000000..fc850b1 --- /dev/null +++ b/lexicons/com/atproto/repo/importRepo.json @@ -0,0 +1,13 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.importRepo", + "defs": { + "main": { + "type": "procedure", + "description": "Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.", + "input": { + "encoding": "application/vnd.ipld.car" + } + } + } +} diff --git a/lexicons/com/atproto/repo/listMissingBlobs.json b/lexicons/com/atproto/repo/listMissingBlobs.json new file mode 100644 index 0000000..c39913d --- /dev/null +++ b/lexicons/com/atproto/repo/listMissingBlobs.json @@ -0,0 +1,44 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.listMissingBlobs", + "defs": { + "main": { + "type": "query", + "description": "Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 500 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["blobs"], + "properties": { + "cursor": { "type": "string" }, + "blobs": { + "type": "array", + "items": { "type": "ref", "ref": "#recordBlob" } + } + } + } + } + }, + "recordBlob": { + "type": "object", + "required": ["cid", "recordUri"], + "properties": { + "cid": { "type": "string", "format": "cid" }, + "recordUri": { "type": "string", "format": "at-uri" } + } + } + } +} diff --git a/lexicons/com/atproto/repo/listRecords.json b/lexicons/com/atproto/repo/listRecords.json new file mode 100644 index 0000000..bc91c95 --- /dev/null +++ b/lexicons/com/atproto/repo/listRecords.json @@ -0,0 +1,69 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.listRecords", + "defs": { + "main": { + "type": "query", + "description": "List a range of records in a repository, matching a specific collection. Does not require auth.", + "parameters": { + "type": "params", + "required": ["repo", "collection"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record type." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50, + "description": "The number of records to return." + }, + "cursor": { "type": "string" }, + "rkeyStart": { + "type": "string", + "description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)" + }, + "rkeyEnd": { + "type": "string", + "description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)" + }, + "reverse": { + "type": "boolean", + "description": "Flag to reverse the order of the returned records." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["records"], + "properties": { + "cursor": { "type": "string" }, + "records": { + "type": "array", + "items": { "type": "ref", "ref": "#record" } + } + } + } + } + }, + "record": { + "type": "object", + "required": ["uri", "cid", "value"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "value": { "type": "unknown" } + } + } + } +} diff --git a/lexicons/com/atproto/repo/putRecord.json b/lexicons/com/atproto/repo/putRecord.json new file mode 100644 index 0000000..9a841f6 --- /dev/null +++ b/lexicons/com/atproto/repo/putRecord.json @@ -0,0 +1,73 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.putRecord", + "defs": { + "main": { + "type": "procedure", + "description": "Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "collection", "rkey", "record"], + "nullable": ["swapRecord"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "rkey": { + "type": "string", + "description": "The Record Key.", + "maxLength": 15 + }, + "validate": { + "type": "boolean", + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons." + }, + "record": { + "type": "unknown", + "description": "The record to write." + }, + "swapRecord": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation" + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "commit": { + "type": "ref", + "ref": "com.atproto.repo.defs#commitMeta" + }, + "validationStatus": { + "type": "string", + "knownValues": ["valid", "unknown"] + } + } + } + }, + "errors": [{ "name": "InvalidSwap" }] + } + } +} diff --git a/lexicons/com/atproto/repo/strongRef.json b/lexicons/com/atproto/repo/strongRef.json new file mode 100644 index 0000000..cb79625 --- /dev/null +++ b/lexicons/com/atproto/repo/strongRef.json @@ -0,0 +1,15 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.strongRef", + "description": "A URI with a content-hash fingerprint.", + "defs": { + "main": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" } + } + } + } +} diff --git a/lexicons/com/atproto/repo/uploadBlob.json b/lexicons/com/atproto/repo/uploadBlob.json new file mode 100644 index 0000000..547a995 --- /dev/null +++ b/lexicons/com/atproto/repo/uploadBlob.json @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.uploadBlob", + "defs": { + "main": { + "type": "procedure", + "description": "Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.", + "input": { + "encoding": "*/*" + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["blob"], + "properties": { + "blob": { "type": "blob" } + } + } + } + } + } +} diff --git a/repos/.keep b/repos/.keep new file mode 100644 index 0000000..e69de29 diff --git a/repos/atproto b/repos/atproto new file mode 160000 index 0000000..8f2b80a --- /dev/null +++ b/repos/atproto @@ -0,0 +1 @@ +Subproject commit 8f2b80a0dcf118652452ea09764a947b09991e0f diff --git a/repos/bluesky-selfhost-env b/repos/bluesky-selfhost-env new file mode 160000 index 0000000..3e384e3 --- /dev/null +++ b/repos/bluesky-selfhost-env @@ -0,0 +1 @@ +Subproject commit 3e384e3b58b8284c68e9562d16920867f8905340 diff --git a/repos/cookbook b/repos/cookbook new file mode 160000 index 0000000..03739f5 --- /dev/null +++ b/repos/cookbook @@ -0,0 +1 @@ +Subproject commit 03739f5268d5915e0dc7e8815723575f4b457bda diff --git a/repos/did-method-plc b/repos/did-method-plc new file mode 160000 index 0000000..13da315 --- /dev/null +++ b/repos/did-method-plc @@ -0,0 +1 @@ +Subproject commit 13da315787e50bd79548d5b695f4f597b43b4015 diff --git a/repos/feed-generator b/repos/feed-generator new file mode 160000 index 0000000..9a887dd --- /dev/null +++ b/repos/feed-generator @@ -0,0 +1 @@ +Subproject commit 9a887dd8f2ee634c5e524cfa802f754878a91e5a diff --git a/repos/social-app b/repos/social-app new file mode 160000 index 0000000..f6649e2 --- /dev/null +++ b/repos/social-app @@ -0,0 +1 @@ +Subproject commit f6649e22a762fa8f4d3060da0a274f3b83ecb06f diff --git a/repos_extra/.keep b/repos_extra/.keep new file mode 100644 index 0000000..e69de29 diff --git a/repos_extra/frontpage/diff.patch b/repos_extra/frontpage/diff.patch new file mode 100644 index 0000000..bd90bf7 --- /dev/null +++ b/repos_extra/frontpage/diff.patch @@ -0,0 +1,499 @@ +diff --git a/packages-rs/drainpipe/docker-compose.yml b/packages-rs/drainpipe/docker-compose.yml +index 0e1d2ca..d5e214a 100644 +--- a/packages-rs/drainpipe/docker-compose.yml ++++ b/packages-rs/drainpipe/docker-compose.yml +@@ -1,4 +1,3 @@ +-version: "3" + services: + drainpipe: + build: +diff --git a/packages-rs/drainpipe/src/main.rs b/packages-rs/drainpipe/src/main.rs +index 21278a5..0a05d06 100644 +--- a/packages-rs/drainpipe/src/main.rs ++++ b/packages-rs/drainpipe/src/main.rs +@@ -76,7 +76,7 @@ async fn process(message: Vec, ctx: &mut Context) -> Result>(); + if !frontpage_ops.is_empty() { + process_frontpage_ops(&frontpage_ops, &commit, &ctx) +diff --git a/packages/frontpage/app/(app)/_components/post-card.tsx b/packages/frontpage/app/(app)/_components/post-card.tsx +index c0720e5..e619a5f 100644 +--- a/packages/frontpage/app/(app)/_components/post-card.tsx ++++ b/packages/frontpage/app/(app)/_components/post-card.tsx +@@ -83,7 +83,7 @@ export async function PostCard({ + votes={votes} + /> + +-
++
+

+ ++
++ {/*
*/} +
+ + {/* eslint-disable-next-line @next/next/no-img-element */} +@@ -52,6 +53,8 @@ export default async function Layout({ + +
{children}
+ ++
++ {/* +
+

+ Made by{" "} +@@ -59,10 +62,11 @@ export default async function Layout({ + href={`https://bsky.app/profile/${FRONTPAGE_ATPROTO_HANDLE}`} + className="font-medium text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300" + > +- @frontpage.fyi ++ @o.syui.ai + +

+
++ */} +
+ ); + } +@@ -141,8 +145,8 @@ async function LoginOrLogout() { + } + + return ( +- + ); + } +diff --git a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/[commentAuthor]/[commentRkey]/page.tsx b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/[commentAuthor]/[commentRkey]/page.tsx +index c9a2883..c50b02f 100644 +--- a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/[commentAuthor]/[commentRkey]/page.tsx ++++ b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/[commentAuthor]/[commentRkey]/page.tsx +@@ -30,7 +30,7 @@ export async function generateMetadata(props: { + description: + comment.status === "live" ? truncateText(comment.body, 47) : null, + alternates: { +- canonical: `https://frontpage.fyi${path}`, ++ canonical: `https://o.syui.ai${path}`, + }, + openGraph: + comment.status === "live" +@@ -40,7 +40,7 @@ export async function generateMetadata(props: { + type: "article", + publishedTime: comment.createdAt.toISOString(), + authors: [`@${handle}`], +- url: `https://frontpage.fyi${path}`, ++ url: `https://o.syui.ai${path}`, + images: [ + { + url: `${path}/og-image`, +diff --git a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/_lib/comment-client.tsx b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/_lib/comment-client.tsx +index 71dd664..954f499 100644 +--- a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/_lib/comment-client.tsx ++++ b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/_lib/comment-client.tsx +@@ -283,16 +283,12 @@ export function NewComment({ + ref={textAreaRef} + placeholder="Write a comment..." + disabled={isPending} +- className="resize-y flex-1" ++ className="" + /> +
+- +-
++
+ {extraButton} +- +
+diff --git a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/layout.tsx b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/layout.tsx +index 517c871..ef73ecb 100644 +--- a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/layout.tsx ++++ b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/layout.tsx +@@ -29,7 +29,7 @@ export default async function Post(props: { + } + + return ( +-
++
+ }) { + + return ( + <> ++ ++ {/* + }) { + /> + ))} +
++ */} ++ ++ {params.postAuthor === "syui.ai" && (post.title.includes("live") || post.title.includes("video")) && ( ++
++ ++
++ ++ ++ {comments.map((comment) => ( ++ ++ ))} ++
++
++ )} ++ ++ {post.status === "live" ? ( ++ ++ ) : ( ++ ++ This post has been deleted ++ ++ Deleted posts cannot receive new comments. ++ ++ ++ )} ++
++ {comments.map((comment) => ( ++ ++ ))} ++ ++
++ + + ); + } +diff --git a/packages/frontpage/app/(auth)/login/page.tsx b/packages/frontpage/app/(auth)/login/page.tsx +index e3165f9..4d661ce 100644 +--- a/packages/frontpage/app/(auth)/login/page.tsx ++++ b/packages/frontpage/app/(auth)/login/page.tsx +@@ -14,17 +14,15 @@ export default async function LoginPage() { +
+
+

+- Sign in to Frontpage ++ Login with atproto +

+

+- Don't have an account?{" "} + +- Sign up on Bluesky +- +- , then return here to login. ++ Bluesky ++ oauth login +

+
+ +diff --git a/packages/frontpage/app/api/receive_hook/route.ts b/packages/frontpage/app/api/receive_hook/route.ts +index 04bda27..9feb136 100644 +--- a/packages/frontpage/app/api/receive_hook/route.ts ++++ b/packages/frontpage/app/api/receive_hook/route.ts +@@ -96,7 +96,7 @@ export async function POST(request: Request) { + }); + } + +- if (collection === "fyi.unravel.frontpage.vote") { ++ if (collection === "ai.syui.o.vote") { + if (op.action === "create") { + const hydratedRecord = await atprotoGetRecord({ + serviceEndpoint: service, +diff --git a/packages/frontpage/app/globals.css b/packages/frontpage/app/globals.css +index 9abc86b..e22dba5 100644 +--- a/packages/frontpage/app/globals.css ++++ b/packages/frontpage/app/globals.css +@@ -13,8 +13,12 @@ + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + ++ /* + --primary: 222.2 47.4% 11.2%; +- --primary-foreground: 210 40% 98%; ++ --primary-foreground: 210 40% 98%; */ ++ ++ --primary: 210 100% 50%; /* HSLカラーで青色を定義 */ ++ --primary-foreground: 0 0% 100%; /* 白色のテキスト */ + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; +@@ -67,8 +71,12 @@ + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + +- --primary: 210 40% 98%; +- --primary-foreground: 222.2 47.4% 11.2%; ++ /*--primary: 210 40% 98%; ++ *--primary-foreground: 222.2 47.4% 11.2%;*/ ++ ++ /* ダークモード用に少し明るい青 */ ++ --primary: 210 100% 60%; ++ --primary-foreground: 0 0% 100%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; +diff --git a/packages/frontpage/app/layout.tsx b/packages/frontpage/app/layout.tsx +index e8b5a39..a9fa945 100644 +--- a/packages/frontpage/app/layout.tsx ++++ b/packages/frontpage/app/layout.tsx +@@ -22,13 +22,13 @@ const sourceSans = Source_Sans_3({ + }); + + export const metadata: Metadata = { +- title: "Frontpage", ++ title: "o.syui.ai", + description: "A federated link aggregator. Your frontpage to the internet.", + openGraph: { + title: "Frontpage", + description: "Your frontpage to the internet.", + type: "website", +- siteName: "frontpage.fyi", ++ siteName: "o.syui.ai", + }, + }; + +@@ -53,11 +53,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { + + + +-