Scratch space for learning atproto app development

Read profile record directly

Changed files
+200 -11
lexicons
src
lexicon
types
app
bsky
actor
pages
+49
lexicons/profile.json
···
···
+
{
+
"lexicon": 1,
+
"id": "app.bsky.actor.profile",
+
"defs": {
+
"main": {
+
"type": "record",
+
"description": "A declaration of a Bluesky account profile.",
+
"key": "literal:self",
+
"record": {
+
"type": "object",
+
"properties": {
+
"displayName": {
+
"type": "string",
+
"maxGraphemes": 64,
+
"maxLength": 640
+
},
+
"description": {
+
"type": "string",
+
"description": "Free-form profile description text.",
+
"maxGraphemes": 256,
+
"maxLength": 2560
+
},
+
"avatar": {
+
"type": "blob",
+
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'",
+
"accept": ["image/png", "image/jpeg"],
+
"maxSize": 1000000
+
},
+
"banner": {
+
"type": "blob",
+
"description": "Larger horizontal image to display behind profile view.",
+
"accept": ["image/png", "image/jpeg"],
+
"maxSize": 1000000
+
},
+
"labels": {
+
"type": "union",
+
"description": "Self-label values, specific to the Bluesky application, on the overall account.",
+
"refs": ["com.atproto.label.defs#selfLabels"]
+
},
+
"joinedViaStarterPack": {
+
"type": "ref",
+
"ref": "com.atproto.repo.strongRef"
+
},
+
"createdAt": { "type": "string", "format": "datetime" }
+
}
+
}
+
}
+
}
+
}
+30
src/lexicon/index.ts
···
export class Server {
xrpc: XrpcServer
com: ComNS
constructor(options?: XrpcOptions) {
this.xrpc = createXrpcServer(schemas, options)
this.com = new ComNS(this)
}
}
···
export class Server {
xrpc: XrpcServer
+
app: AppNS
com: ComNS
constructor(options?: XrpcOptions) {
this.xrpc = createXrpcServer(schemas, options)
+
this.app = new AppNS(this)
this.com = new ComNS(this)
+
}
+
}
+
+
export class AppNS {
+
_server: Server
+
bsky: AppBskyNS
+
+
constructor(server: Server) {
+
this._server = server
+
this.bsky = new AppBskyNS(server)
+
}
+
}
+
+
export class AppBskyNS {
+
_server: Server
+
actor: AppBskyActorNS
+
+
constructor(server: Server) {
+
this._server = server
+
this.actor = new AppBskyActorNS(server)
+
}
+
}
+
+
export class AppBskyActorNS {
+
_server: Server
+
+
constructor(server: Server) {
+
this._server = server
}
}
+59 -1
src/lexicon/lexicons.ts
···
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
export const schemaDict = {
ComExampleStatus: {
lexicon: 1,
id: 'com.example.status',
···
}
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
export const lexicons: Lexicons = new Lexicons(schemas)
-
export const ids = { ComExampleStatus: 'com.example.status' }
···
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
export const schemaDict = {
+
AppBskyActorProfile: {
+
lexicon: 1,
+
id: 'app.bsky.actor.profile',
+
defs: {
+
main: {
+
type: 'record',
+
description: 'A declaration of a Bluesky account profile.',
+
key: 'literal:self',
+
record: {
+
type: 'object',
+
properties: {
+
displayName: {
+
type: 'string',
+
maxGraphemes: 64,
+
maxLength: 640,
+
},
+
description: {
+
type: 'string',
+
description: 'Free-form profile description text.',
+
maxGraphemes: 256,
+
maxLength: 2560,
+
},
+
avatar: {
+
type: 'blob',
+
description:
+
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
+
accept: ['image/png', 'image/jpeg'],
+
maxSize: 1000000,
+
},
+
banner: {
+
type: 'blob',
+
description:
+
'Larger horizontal image to display behind profile view.',
+
accept: ['image/png', 'image/jpeg'],
+
maxSize: 1000000,
+
},
+
labels: {
+
type: 'union',
+
description:
+
'Self-label values, specific to the Bluesky application, on the overall account.',
+
refs: ['lex:com.atproto.label.defs#selfLabels'],
+
},
+
joinedViaStarterPack: {
+
type: 'ref',
+
ref: 'lex:com.atproto.repo.strongRef',
+
},
+
createdAt: {
+
type: 'string',
+
format: 'datetime',
+
},
+
},
+
},
+
},
+
},
+
},
ComExampleStatus: {
lexicon: 1,
id: 'com.example.status',
···
}
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
export const lexicons: Lexicons = new Lexicons(schemas)
+
export const ids = {
+
AppBskyActorProfile: 'app.bsky.actor.profile',
+
ComExampleStatus: 'com.example.status',
+
}
+38
src/lexicon/types/app/bsky/actor/profile.ts
···
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { lexicons } from '../../../../lexicons'
+
import { isObj, hasProp } from '../../../../util'
+
import { CID } from 'multiformats/cid'
+
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
+
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
+
+
export interface Record {
+
displayName?: string
+
/** Free-form profile description text. */
+
description?: string
+
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
+
avatar?: BlobRef
+
/** Larger horizontal image to display behind profile view. */
+
banner?: BlobRef
+
labels?:
+
| ComAtprotoLabelDefs.SelfLabels
+
| { $type: string; [k: string]: unknown }
+
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main
+
createdAt?: string
+
[k: string]: unknown
+
}
+
+
export function isRecord(v: unknown): v is Record {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
(v.$type === 'app.bsky.actor.profile#main' ||
+
v.$type === 'app.bsky.actor.profile')
+
)
+
}
+
+
export function validateRecord(v: unknown): ValidationResult {
+
return lexicons.validate('app.bsky.actor.profile#main', v)
+
}
+4 -4
src/pages/home.ts
···
-
import type { Status } from '#/db/schema'
import { html } from '../lib/view'
import { shell } from './shell'
···
type Props = {
statuses: Status[]
didHandleMap: Record<string, string>
-
profile?: { displayName?: string; handle: string }
myStatus?: Status
}
···
${profile
? html`<form action="/logout" method="post" class="session-form">
<div>
-
Hi, <strong>${profile.displayName || profile.handle}</strong>.
-
what's your status today?
</div>
<div>
<button type="submit">Log out</button>
···
+
import type { Status } from '#/db'
import { html } from '../lib/view'
import { shell } from './shell'
···
type Props = {
statuses: Status[]
didHandleMap: Record<string, string>
+
profile?: { displayName?: string }
myStatus?: Status
}
···
${profile
? html`<form action="/logout" method="post" class="session-form">
<div>
+
Hi, <strong>${profile.displayName || 'friend'}</strong>. What's
+
your status today?
</div>
<div>
<button type="submit">Log out</button>
+20 -6
src/routes.ts
···
import { env } from '#/lib/env'
import { page } from '#/lib/view'
import * as Status from '#/lexicon/types/com/example/status'
type Session = { did: string }
···
}
// Fetch additional information about the logged-in user
-
const { data: profile } = await agent.getProfile({
-
actor: agent.accountDid,
})
-
didHandleMap[profile.handle] = agent.accountDid
// Serve the logged-in view
-
return res
-
.type('html')
-
.send(page(home({ statuses, didHandleMap, profile, myStatus })))
})
)
···
import { env } from '#/lib/env'
import { page } from '#/lib/view'
import * as Status from '#/lexicon/types/com/example/status'
+
import * as Profile from '#/lexicon/types/app/bsky/actor/profile'
type Session = { did: string }
···
}
// Fetch additional information about the logged-in user
+
const { data: profileRecord } = await agent.com.atproto.repo.getRecord({
+
repo: agent.accountDid,
+
collection: 'app.bsky.actor.profile',
+
rkey: 'self',
})
+
const profile =
+
Profile.isRecord(profileRecord.value) &&
+
Profile.validateRecord(profileRecord.value).success
+
? profileRecord.value
+
: {}
// Serve the logged-in view
+
return res.type('html').send(
+
page(
+
home({
+
statuses,
+
didHandleMap,
+
profile,
+
myStatus,
+
})
+
)
+
)
})
)