Skip to content

Commit ccabf3a

Browse files
tidy
1 parent 1af6a92 commit ccabf3a

File tree

8 files changed

+135
-28
lines changed

8 files changed

+135
-28
lines changed

lexicons.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"version": 1,
33
"lexicons": [
4+
"app.bsky.actor.getProfiles",
45
"app.bsky.actor.profile",
56
"app.bsky.embed.external",
67
"app.bsky.embed.images",
@@ -9,13 +10,18 @@
910
"app.bsky.feed.getPostThread",
1011
"app.bsky.feed.getPosts",
1112
"app.bsky.feed.post",
13+
"com.bad-example.identity.resolveMiniDoc",
1214
"site.standard.document"
1315
],
1416
"resolutions": {
1517
"app.bsky.actor.defs": {
1618
"uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.defs",
1719
"cid": "bafyreigwqwhe2jxohagozazfbrf6dxgzphvkg3d3lg7uxdvepsimqyclka"
1820
},
21+
"app.bsky.actor.getProfiles": {
22+
"uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.getProfiles",
23+
"cid": "bafyreihphrndnt2cvnv3nk442f6kohdnpagk4xip74f2jn36uoal25vhta"
24+
},
1925
"app.bsky.actor.profile": {
2026
"uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.profile",
2127
"cid": "bafyreia6umzg3a6d7mjbow4p57tviey45muohklhgsvjoamcctoiusr4pe"
@@ -104,6 +110,10 @@
104110
"uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.strongRef",
105111
"cid": "bafyreifrkdbnkvfjujntdaeigolnrjj3srrs53tfixjhmacclps72qlov4"
106112
},
113+
"com.bad-example.identity.resolveMiniDoc": {
114+
"uri": "at://did:plc:hdhoaan3xa3jiuq4fg4mefid/com.atproto.lexicon.schema/com.bad-example.identity.resolveMiniDoc",
115+
"cid": "bafyreihqq4ud3ihj63op2ety24nziyyh7axibm65mnde5bdeai2z6adety"
116+
},
107117
"site.standard.document": {
108118
"uri": "at://did:plc:re3ebnp5v7ffagz6rb6xfei4/com.atproto.lexicon.schema/site.standard.document",
109119
"cid": "bafyreigdukg62hmel4jbdvghdsoaphslhrdktmahzdyokomuka6yejetwa"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"id": "app.bsky.actor.getProfiles",
3+
"defs": {
4+
"main": {
5+
"type": "query",
6+
"output": {
7+
"schema": {
8+
"type": "object",
9+
"required": ["profiles"],
10+
"properties": {
11+
"profiles": {
12+
"type": "array",
13+
"items": {
14+
"ref": "app.bsky.actor.defs#profileViewDetailed",
15+
"type": "ref"
16+
}
17+
}
18+
}
19+
},
20+
"encoding": "application/json"
21+
},
22+
"parameters": {
23+
"type": "params",
24+
"required": ["actors"],
25+
"properties": {
26+
"actors": {
27+
"type": "array",
28+
"items": {
29+
"type": "string",
30+
"format": "at-identifier"
31+
},
32+
"maxLength": 25
33+
}
34+
}
35+
},
36+
"description": "Get detailed profile views of multiple actors."
37+
}
38+
},
39+
"$type": "com.atproto.lexicon.schema",
40+
"lexicon": 1
41+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"id": "com.bad-example.identity.resolveMiniDoc",
3+
"defs": {
4+
"main": {
5+
"type": "query",
6+
"output": {
7+
"schema": {
8+
"type": "object",
9+
"required": ["did", "handle", "pds", "signing_key"],
10+
"properties": {
11+
"did": {
12+
"type": "string",
13+
"format": "did",
14+
"description": "DID, bi-directionally verified if a handle was provided in the query."
15+
},
16+
"pds": {
17+
"type": "string",
18+
"format": "uri",
19+
"description": "The identity's PDS URL"
20+
},
21+
"handle": {
22+
"type": "string",
23+
"format": "handle",
24+
"description": "The validated handle of the account or `handle.invalid` if the handle\ndid not bi-directionally match the DID document."
25+
},
26+
"signing_key": {
27+
"type": "string",
28+
"description": "The atproto signing key publicKeyMultibase\n\nLegacy key encoding not supported. the key is returned directly; `id`,\n`type`, and `controller` are omitted."
29+
}
30+
}
31+
},
32+
"encoding": "application/json"
33+
},
34+
"parameters": {
35+
"type": "params",
36+
"required": ["identifier"],
37+
"properties": {
38+
"identifier": {
39+
"type": "string",
40+
"format": "at-identifier",
41+
"description": "Handle or DID to resolve"
42+
}
43+
}
44+
},
45+
"description": "Like [com.atproto.identity.resolveIdentity](https://docs.bsky.app/docs/api/com-atproto-identity-resolve-identity) but instead of the full `didDoc` it returns an atproto-relevant subset."
46+
}
47+
},
48+
"$type": "com.atproto.lexicon.schema",
49+
"lexicon": 1
50+
}

server/api/atproto/author-profiles.get.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
import * as v from 'valibot'
22
import { CACHE_MAX_AGE_ONE_DAY, BLUESKY_API } from '#shared/utils/constants'
33
import { AuthorSchema } from '#shared/schemas/blog'
4+
import { Client } from '@atproto/lex'
45
import type { Author, ResolvedAuthor } from '#shared/schemas/blog'
5-
6-
type ProfilesResponse = {
7-
profiles: Array<{
8-
did: string
9-
handle: string
10-
displayName?: string
11-
avatar?: string
12-
}>
13-
}
6+
import * as app from '#shared/types/lexicons/app'
147

158
export default defineCachedEventHandler(
169
async event => {
@@ -45,17 +38,18 @@ export default defineCachedEventHandler(
4538
return { authors: [] }
4639
}
4740

48-
const handles = authors.filter(a => a.blueskyHandle).map(a => a.blueskyHandle as string)
41+
const handles = authors.map(a => a.blueskyHandle).filter(v => v != null)
4942

5043
if (handles.length === 0) {
5144
return {
5245
authors: authors.map(author => Object.assign(author, { avatar: null, profileUrl: null })),
5346
}
5447
}
5548

56-
const response = await $fetch<ProfilesResponse>(`${BLUESKY_API}app.bsky.actor.getProfiles`, {
57-
query: { actors: handles },
58-
}).catch(() => ({ profiles: [] }))
49+
const client = new Client({ service: BLUESKY_API })
50+
const response = await client
51+
.call(app.bsky.actor.getProfiles, { actors: handles })
52+
.catch(() => ({ profiles: [] }))
5953

6054
const avatarMap = new Map<string, string>()
6155
for (const profile of response.profiles) {

server/api/atproto/bluesky-comments.get.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import type { $Typed, AtUriString, Unknown$TypedObject } from '@atproto/lex'
22
import { Client, isAtUriString } from '@atproto/lex'
33
import type { Comment, CommentEmbed } from '#shared/types/blog-post'
44
import * as app from '#shared/types/lexicons/app'
5-
import { CACHE_MAX_AGE_ONE_MINUTE, BLUESKY_API, AT_URI_REGEX } from '#shared/utils/constants'
5+
import {
6+
CACHE_MAX_AGE_ONE_MINUTE,
7+
BLUESKY_API,
8+
BSKY_POST_AT_URI_REGEX,
9+
} from '#shared/utils/constants'
610

711
const blueskyClient = new Client({ service: BLUESKY_API })
812

@@ -78,9 +82,9 @@ export default defineCachedEventHandler(
7882

7983
// Helper to convert AT URI to web URL
8084
function atUriToWebUrl(uri: AtUriString): string | null {
81-
const match = uri.match(AT_URI_REGEX)
85+
const match = uri.match(BSKY_POST_AT_URI_REGEX)
8286
if (!match) return null
83-
const [, did, rkey] = match
87+
const [, did, rkey] = match as [string, `did:plc:${string}`, string]
8488
return `https://bsky.app/profile/${did}/post/${rkey}`
8589
}
8690

server/api/auth/atproto.get.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { getOAuthLock } from '#server/utils/atproto/lock'
44
import { useOAuthStorage } from '#server/utils/atproto/storage'
55
import { SLINGSHOT_HOST } from '#shared/utils/constants'
66
import { useServerSession } from '#server/utils/server-session'
7-
import type { PublicUserSession } from '#shared/schemas/publicUserSession'
87
import { handleResolver } from '#server/utils/atproto/oauth'
98
import { Client } from '@atproto/lex'
9+
import * as com from '#shared/types/lexicons/com'
1010
import * as app from '#shared/types/lexicons/app'
1111
import { isAtIdentifierString } from '@atproto/lex'
1212
// @ts-expect-error virtual file from oauth module
@@ -30,7 +30,7 @@ async function getAvatar(did: string, pds: string) {
3030
try {
3131
const pdsUrl = new URL(pds)
3232
// Only fetch from HTTPS PDS endpoints to prevent SSRF
33-
if (did && pdsUrl.protocol === 'https:') {
33+
if (pdsUrl.protocol === 'https:') {
3434
const client = new Client(pdsUrl)
3535
const profileResponse = await client.get(app.bsky.actor.profile, {
3636
repo: did,
@@ -136,12 +136,13 @@ export default defineEventHandler(async event => {
136136
new URLSearchParams(query as Record<string, string>),
137137
)
138138

139-
const response = await fetch(
140-
`https://${SLINGSHOT_HOST}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${encodeURIComponent(authSession.did)}`,
141-
{ headers: { 'User-Agent': 'npmx' } },
142-
)
143-
if (response.ok) {
144-
const miniDoc: PublicUserSession = await response.json()
139+
const client = new Client({ service: SLINGSHOT_HOST })
140+
const response = await client.xrpcSafe(com['bad-example'].identity.resolveMiniDoc, {
141+
headers: { 'User-Agent': 'npmx' },
142+
params: { identifier: authSession.did },
143+
})
144+
if (response.success) {
145+
const miniDoc = response.body
145146

146147
let avatar: string | undefined = await getAvatar(authSession.did, miniDoc.pds)
147148

shared/schemas/blog.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
import { object, string, optional, array, boolean, pipe, isoDate } from 'valibot'
1+
import { isAtIdentifierString, type AtIdentifierString } from '@atproto/lex'
2+
import { custom, object, string, optional, array, boolean, pipe, isoDate } from 'valibot'
23
import type { InferOutput } from 'valibot'
34

45
export const AuthorSchema = object({
56
name: string(),
6-
blueskyHandle: optional(string()),
7+
blueskyHandle: optional(
8+
pipe(
9+
string(),
10+
custom<AtIdentifierString>(v => typeof v === 'string' && isAtIdentifierString(v)),
11+
),
12+
),
713
})
814

915
export const BlogPostSchema = object({

shared/utils/constants.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const CACHE_MAX_AGE_ONE_YEAR = 60 * 60 * 24 * 365
99

1010
// API Strings
1111
export const NPMX_SITE = 'https://npmx.dev'
12-
export const BLUESKY_API = 'https://public.api.bsky.app/xrpc/'
12+
export const BLUESKY_API = 'https://public.api.bsky.app'
1313
export const BLUESKY_COMMENTS_REQUEST = '/api/atproto/bluesky-comments'
1414
export const NPM_REGISTRY = 'https://registry.npmjs.org'
1515
export const NPM_API = 'https://api.npmjs.org'
@@ -74,4 +74,5 @@ export const BACKGROUND_THEMES = {
7474
} as const
7575

7676
// Regex
77-
export const AT_URI_REGEX = /^at:\/\/(did:plc:[a-z0-9]+)\/app\.bsky\.feed\.post\/([a-z0-9]+)$/
77+
export const BSKY_POST_AT_URI_REGEX =
78+
/^at:\/\/(did:plc:[a-z0-9]+)\/app\.bsky\.feed\.post\/([a-z0-9]+)$/

0 commit comments

Comments
 (0)