tangled
alpha
login
or
join now
danabra.mov
/
statusphere-react
forked from
samuel.fm/statusphere-react
0
fork
atom
the statusphere demo reworked into a vite/react app in a monorepo
0
fork
atom
overview
issues
pulls
pipelines
Read profile record directly
Paul Frazee
2 years ago
fcc7fe61
854f2884
+200
-11
6 changed files
expand all
collapse all
unified
split
lexicons
profile.json
src
lexicon
index.ts
lexicons.ts
types
app
bsky
actor
profile.ts
pages
home.ts
routes.ts
+49
lexicons/profile.json
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "app.bsky.actor.profile",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "record",
7
7
+
"description": "A declaration of a Bluesky account profile.",
8
8
+
"key": "literal:self",
9
9
+
"record": {
10
10
+
"type": "object",
11
11
+
"properties": {
12
12
+
"displayName": {
13
13
+
"type": "string",
14
14
+
"maxGraphemes": 64,
15
15
+
"maxLength": 640
16
16
+
},
17
17
+
"description": {
18
18
+
"type": "string",
19
19
+
"description": "Free-form profile description text.",
20
20
+
"maxGraphemes": 256,
21
21
+
"maxLength": 2560
22
22
+
},
23
23
+
"avatar": {
24
24
+
"type": "blob",
25
25
+
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'",
26
26
+
"accept": ["image/png", "image/jpeg"],
27
27
+
"maxSize": 1000000
28
28
+
},
29
29
+
"banner": {
30
30
+
"type": "blob",
31
31
+
"description": "Larger horizontal image to display behind profile view.",
32
32
+
"accept": ["image/png", "image/jpeg"],
33
33
+
"maxSize": 1000000
34
34
+
},
35
35
+
"labels": {
36
36
+
"type": "union",
37
37
+
"description": "Self-label values, specific to the Bluesky application, on the overall account.",
38
38
+
"refs": ["com.atproto.label.defs#selfLabels"]
39
39
+
},
40
40
+
"joinedViaStarterPack": {
41
41
+
"type": "ref",
42
42
+
"ref": "com.atproto.repo.strongRef"
43
43
+
},
44
44
+
"createdAt": { "type": "string", "format": "datetime" }
45
45
+
}
46
46
+
}
47
47
+
}
48
48
+
}
49
49
+
}
+30
src/lexicon/index.ts
···
16
16
17
17
export class Server {
18
18
xrpc: XrpcServer
19
19
+
app: AppNS
19
20
com: ComNS
20
21
21
22
constructor(options?: XrpcOptions) {
22
23
this.xrpc = createXrpcServer(schemas, options)
24
24
+
this.app = new AppNS(this)
23
25
this.com = new ComNS(this)
26
26
+
}
27
27
+
}
28
28
+
29
29
+
export class AppNS {
30
30
+
_server: Server
31
31
+
bsky: AppBskyNS
32
32
+
33
33
+
constructor(server: Server) {
34
34
+
this._server = server
35
35
+
this.bsky = new AppBskyNS(server)
36
36
+
}
37
37
+
}
38
38
+
39
39
+
export class AppBskyNS {
40
40
+
_server: Server
41
41
+
actor: AppBskyActorNS
42
42
+
43
43
+
constructor(server: Server) {
44
44
+
this._server = server
45
45
+
this.actor = new AppBskyActorNS(server)
46
46
+
}
47
47
+
}
48
48
+
49
49
+
export class AppBskyActorNS {
50
50
+
_server: Server
51
51
+
52
52
+
constructor(server: Server) {
53
53
+
this._server = server
24
54
}
25
55
}
26
56
+59
-1
src/lexicon/lexicons.ts
···
4
4
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
5
5
6
6
export const schemaDict = {
7
7
+
AppBskyActorProfile: {
8
8
+
lexicon: 1,
9
9
+
id: 'app.bsky.actor.profile',
10
10
+
defs: {
11
11
+
main: {
12
12
+
type: 'record',
13
13
+
description: 'A declaration of a Bluesky account profile.',
14
14
+
key: 'literal:self',
15
15
+
record: {
16
16
+
type: 'object',
17
17
+
properties: {
18
18
+
displayName: {
19
19
+
type: 'string',
20
20
+
maxGraphemes: 64,
21
21
+
maxLength: 640,
22
22
+
},
23
23
+
description: {
24
24
+
type: 'string',
25
25
+
description: 'Free-form profile description text.',
26
26
+
maxGraphemes: 256,
27
27
+
maxLength: 2560,
28
28
+
},
29
29
+
avatar: {
30
30
+
type: 'blob',
31
31
+
description:
32
32
+
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
33
33
+
accept: ['image/png', 'image/jpeg'],
34
34
+
maxSize: 1000000,
35
35
+
},
36
36
+
banner: {
37
37
+
type: 'blob',
38
38
+
description:
39
39
+
'Larger horizontal image to display behind profile view.',
40
40
+
accept: ['image/png', 'image/jpeg'],
41
41
+
maxSize: 1000000,
42
42
+
},
43
43
+
labels: {
44
44
+
type: 'union',
45
45
+
description:
46
46
+
'Self-label values, specific to the Bluesky application, on the overall account.',
47
47
+
refs: ['lex:com.atproto.label.defs#selfLabels'],
48
48
+
},
49
49
+
joinedViaStarterPack: {
50
50
+
type: 'ref',
51
51
+
ref: 'lex:com.atproto.repo.strongRef',
52
52
+
},
53
53
+
createdAt: {
54
54
+
type: 'string',
55
55
+
format: 'datetime',
56
56
+
},
57
57
+
},
58
58
+
},
59
59
+
},
60
60
+
},
61
61
+
},
7
62
ComExampleStatus: {
8
63
lexicon: 1,
9
64
id: 'com.example.status',
···
33
88
}
34
89
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
35
90
export const lexicons: Lexicons = new Lexicons(schemas)
36
36
-
export const ids = { ComExampleStatus: 'com.example.status' }
91
91
+
export const ids = {
92
92
+
AppBskyActorProfile: 'app.bsky.actor.profile',
93
93
+
ComExampleStatus: 'com.example.status',
94
94
+
}
+38
src/lexicon/types/app/bsky/actor/profile.ts
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
+
import { lexicons } from '../../../../lexicons'
6
6
+
import { isObj, hasProp } from '../../../../util'
7
7
+
import { CID } from 'multiformats/cid'
8
8
+
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
9
9
+
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
10
10
+
11
11
+
export interface Record {
12
12
+
displayName?: string
13
13
+
/** Free-form profile description text. */
14
14
+
description?: string
15
15
+
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
16
16
+
avatar?: BlobRef
17
17
+
/** Larger horizontal image to display behind profile view. */
18
18
+
banner?: BlobRef
19
19
+
labels?:
20
20
+
| ComAtprotoLabelDefs.SelfLabels
21
21
+
| { $type: string; [k: string]: unknown }
22
22
+
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main
23
23
+
createdAt?: string
24
24
+
[k: string]: unknown
25
25
+
}
26
26
+
27
27
+
export function isRecord(v: unknown): v is Record {
28
28
+
return (
29
29
+
isObj(v) &&
30
30
+
hasProp(v, '$type') &&
31
31
+
(v.$type === 'app.bsky.actor.profile#main' ||
32
32
+
v.$type === 'app.bsky.actor.profile')
33
33
+
)
34
34
+
}
35
35
+
36
36
+
export function validateRecord(v: unknown): ValidationResult {
37
37
+
return lexicons.validate('app.bsky.actor.profile#main', v)
38
38
+
}
+4
-4
src/pages/home.ts
···
1
1
-
import type { Status } from '#/db/schema'
1
1
+
import type { Status } from '#/db'
2
2
import { html } from '../lib/view'
3
3
import { shell } from './shell'
4
4
···
37
37
type Props = {
38
38
statuses: Status[]
39
39
didHandleMap: Record<string, string>
40
40
-
profile?: { displayName?: string; handle: string }
40
40
+
profile?: { displayName?: string }
41
41
myStatus?: Status
42
42
}
43
43
···
60
60
${profile
61
61
? html`<form action="/logout" method="post" class="session-form">
62
62
<div>
63
63
-
Hi, <strong>${profile.displayName || profile.handle}</strong>.
64
64
-
what's your status today?
63
63
+
Hi, <strong>${profile.displayName || 'friend'}</strong>. What's
64
64
+
your status today?
65
65
</div>
66
66
<div>
67
67
<button type="submit">Log out</button>
+20
-6
src/routes.ts
···
11
11
import { env } from '#/lib/env'
12
12
import { page } from '#/lib/view'
13
13
import * as Status from '#/lexicon/types/com/example/status'
14
14
+
import * as Profile from '#/lexicon/types/app/bsky/actor/profile'
14
15
15
16
type Session = { did: string }
16
17
···
167
168
}
168
169
169
170
// Fetch additional information about the logged-in user
170
170
-
const { data: profile } = await agent.getProfile({
171
171
-
actor: agent.accountDid,
171
171
+
const { data: profileRecord } = await agent.com.atproto.repo.getRecord({
172
172
+
repo: agent.accountDid,
173
173
+
collection: 'app.bsky.actor.profile',
174
174
+
rkey: 'self',
172
175
})
173
173
-
didHandleMap[profile.handle] = agent.accountDid
176
176
+
const profile =
177
177
+
Profile.isRecord(profileRecord.value) &&
178
178
+
Profile.validateRecord(profileRecord.value).success
179
179
+
? profileRecord.value
180
180
+
: {}
174
181
175
182
// Serve the logged-in view
176
176
-
return res
177
177
-
.type('html')
178
178
-
.send(page(home({ statuses, didHandleMap, profile, myStatus })))
183
183
+
return res.type('html').send(
184
184
+
page(
185
185
+
home({
186
186
+
statuses,
187
187
+
didHandleMap,
188
188
+
profile,
189
189
+
myStatus,
190
190
+
})
191
191
+
)
192
192
+
)
179
193
})
180
194
)
181
195