+25
deno.lock
+25
deno.lock
···
3
3
"specifiers": {
4
4
"npm:@atcute/bluesky@^2.0.2": "2.0.2_@atcute+client@3.0.1",
5
5
"npm:@atcute/client@^3.0.1": "3.0.1",
6
+
"npm:@atcute/identity-resolver@~0.1.2": "0.1.2_@atcute+identity@0.1.3",
6
7
"npm:@sveltejs/vite-plugin-svelte@^5.0.3": "5.0.3_svelte@5.28.1__acorn@8.14.1_vite@6.3.2__picomatch@4.0.2",
7
8
"npm:@tsconfig/svelte@^5.0.4": "5.0.4",
8
9
"npm:svelte-check@^4.1.5": "4.1.6_svelte@5.28.1__acorn@8.14.1_typescript@5.7.3",
···
26
27
},
27
28
"@atcute/client@3.0.1": {
28
29
"integrity": "sha512-j51SuQYQj5jeKrx1DCXx+Q3fpVvO0JYGnKnJAdDSlesSLjPXjvnx1abC+hkuro58KRHHJvRA6P1MC0pmJsWfcg=="
30
+
},
31
+
"@atcute/identity-resolver@0.1.2_@atcute+identity@0.1.3": {
32
+
"integrity": "sha512-fP2VbHD04kVcCdNi/Kszo6jFzqM7Pg3p33oGhfp2zVkwFKaVBlwCaFRWEga/Xvu/IDLwNdASGWnLqoA34SFeSg==",
33
+
"dependencies": [
34
+
"@atcute/identity",
35
+
"@atcute/util-fetch",
36
+
"@badrap/valita"
37
+
]
38
+
},
39
+
"@atcute/identity@0.1.3": {
40
+
"integrity": "sha512-ndlD8nypHt8G00wixbozKdSNL0O8HTzBjFGEXeAcBUCXSZPBjRWbqtgyJxhgUWnr7swgxgw1mSbZwRB5b7xCiQ==",
41
+
"dependencies": [
42
+
"@badrap/valita"
43
+
]
44
+
},
45
+
"@atcute/util-fetch@1.0.1": {
46
+
"integrity": "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==",
47
+
"dependencies": [
48
+
"@badrap/valita"
49
+
]
50
+
},
51
+
"@badrap/valita@0.4.4": {
52
+
"integrity": "sha512-GEhUCk9c4XbNxi+0YZHZsV4fYNd6HejfWuN4Ti4c02DauX+LyX5WY1Y3WfyZ8Pxxl0zqhs+MLtW98cMh86vv6g=="
29
53
},
30
54
"@esbuild/aix-ppc64@0.25.2": {
31
55
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="
···
443
467
"dependencies": [
444
468
"npm:@atcute/bluesky@^2.0.2",
445
469
"npm:@atcute/client@^3.0.1",
470
+
"npm:@atcute/identity-resolver@~0.1.2",
446
471
"npm:@sveltejs/vite-plugin-svelte@^5.0.3",
447
472
"npm:@tsconfig/svelte@^5.0.4",
448
473
"npm:svelte-check@^4.1.5",
+2
-1
package.json
+2
-1
package.json
+16
-5
src/lib/PostComponent.svelte
+16
-5
src/lib/PostComponent.svelte
···
13
13
alt="avatar of {post.displayName}"
14
14
/>
15
15
{/if}
16
-
<div id="headerText">{post.displayName} | {post.timenotstamp}</div>
16
+
<div id="headerText">{post.displayName} | {post.authorHandle} | {post.timenotstamp}</div>
17
17
</div>
18
18
<div id="postContent">
19
-
{#if post.replyingDid}
20
-
<p id="replyingText">replying to: {post.replyingDid}</p>
21
-
{/if}
22
-
<p id="postText">{post.text}</p>
19
+
<p>{post.text}</p>
20
+
{#if post.replyingUri}
21
+
<a
22
+
id="replyingText"
23
+
href="https://deer.social/profile/{post.replyingUri.repo}/post/{post
24
+
.replyingUri.rkey}">replying to {post.replyingUri.repo}</a
25
+
>
26
+
{/if}
27
+
{#if post.quotingUri}
28
+
<a
29
+
id="quotingText"
30
+
href="https://deer.social/profile/{post.quotingUri.repo}/post/{post
31
+
.quotingUri.rkey}">quoting {post.quotingUri.repo}</a
32
+
>
33
+
{/if}
23
34
{#if post.imagesCid}
24
35
<div id="imagesContainer">
25
36
{#each post.imagesCid as imageLink}
+66
-10
src/lib/pdsfetch.ts
+66
-10
src/lib/pdsfetch.ts
···
2
2
import "@atcute/bluesky/lexicons";
3
3
import type {
4
4
AppBskyActorDefs,
5
+
AppBskyActorProfile,
5
6
AppBskyFeedPost,
7
+
At,
6
8
ComAtprotoRepoListRecords,
7
9
} from "@atcute/client/lexicons";
10
+
import {
11
+
CompositeDidDocumentResolver,
12
+
PlcDidDocumentResolver,
13
+
WebDidDocumentResolver,
14
+
} from "@atcute/identity-resolver";
8
15
import { Config } from "../../config";
9
16
// import { ComAtprotoRepoListRecords.Record } from "@atcute/client/lexicons";
10
17
// import { AppBskyFeedPost } from "@atcute/client/lexicons";
···
13
20
interface AccountMetadata {
14
21
did: string;
15
22
displayName: string;
23
+
handle: string;
16
24
avatarCid: string | null;
17
25
}
26
+
interface atUriObject {
27
+
repo: string;
28
+
collection: string;
29
+
rkey: string;
30
+
}
18
31
class Post {
19
32
authorDid: string;
20
33
authorAvatarCid: string | null;
34
+
authorHandle: string;
21
35
displayName: string;
22
36
text: string;
23
37
timestamp: number;
24
38
timenotstamp: string;
25
-
quotingDid: string | null;
26
-
replyingDid: string | null;
39
+
quotingUri: atUriObject | null;
40
+
replyingUri: atUriObject | null;
27
41
imagesCid: string[] | null;
28
42
videosLinkCid: string | null;
29
43
···
33
47
) {
34
48
this.authorDid = account.did;
35
49
this.authorAvatarCid = account.avatarCid;
50
+
this.authorHandle = account.handle;
36
51
this.displayName = account.displayName;
37
52
const post = record.value as AppBskyFeedPost.Record;
38
53
this.timenotstamp = post.createdAt;
39
54
this.text = post.text;
40
55
this.timestamp = Date.parse(post.createdAt);
41
56
if (post.reply) {
42
-
this.replyingDid = didFromATuri(post.reply.parent.uri).repo;
57
+
this.replyingUri = processAtUri(post.reply.parent.uri);
43
58
} else {
44
-
this.replyingDid = null;
59
+
this.replyingUri = null;
45
60
}
46
-
this.quotingDid = null;
61
+
this.quotingUri = null;
47
62
this.imagesCid = null;
48
63
this.videosLinkCid = null;
49
64
switch (post.embed?.$type) {
···
56
71
this.videosLinkCid = post.embed.video.ref.$link;
57
72
break;
58
73
case "app.bsky.embed.record":
59
-
this.quotingDid = didFromATuri(post.embed.record.uri).repo;
74
+
this.quotingUri = processAtUri(post.embed.record.uri);
60
75
break;
61
76
case "app.bsky.embed.recordWithMedia":
62
-
this.quotingDid = didFromATuri(post.embed.record.record.uri).repo;
77
+
this.quotingUri = processAtUri(post.embed.record.record.uri);
63
78
switch (post.embed.media.$type) {
64
79
case "app.bsky.embed.images":
65
80
this.imagesCid = post.embed.media.images.map((imageRecord) =>
···
77
92
}
78
93
}
79
94
80
-
const didFromATuri = (aturi: string) => {
95
+
const processAtUri = (aturi: string): atUriObject => {
81
96
const parts = aturi.split("/");
82
97
return {
83
98
repo: parts[2],
···
108
123
rkey: "self",
109
124
},
110
125
});
111
-
const value = data.value as AppBskyActorDefs.ProfileView;
126
+
const value = data.value as AppBskyActorProfile.Record;
127
+
const handle = await blueskyHandleFromDid(did);
112
128
const account: AccountMetadata = {
113
129
did: did,
130
+
handle: handle,
114
131
displayName: value.displayName || "",
115
132
avatarCid: null,
116
133
};
···
143
160
try {
144
161
const { data } = await rpc.get("com.atproto.repo.listRecords", {
145
162
params: {
146
-
repo: did,
163
+
repo: did as At.Identifier,
147
164
collection: "app.bsky.feed.post",
148
165
limit: 5,
149
166
},
···
163
180
}
164
181
};
165
182
183
+
const identityResolve = async (did: At.Did) => {
184
+
const resolver = new CompositeDidDocumentResolver({
185
+
methods: {
186
+
plc: new PlcDidDocumentResolver(),
187
+
web: new WebDidDocumentResolver(),
188
+
},
189
+
});
190
+
191
+
if (did.startsWith("did:plc:") || did.startsWith("did:web:")) {
192
+
const doc = await resolver.resolve(
193
+
did as `did:plc:${string}` | `did:web:${string}`,
194
+
);
195
+
return doc;
196
+
} else {
197
+
throw new Error(`Unsupported DID type: ${did}`);
198
+
}
199
+
};
200
+
201
+
const blueskyHandleFromDid = async (did: At.Did) => {
202
+
const doc = await identityResolve(did);
203
+
if (doc.alsoKnownAs) {
204
+
const handleAtUri = doc.alsoKnownAs.find((url) => url.startsWith("at://"));
205
+
const handle = handleAtUri?.split("/")[2];
206
+
if (!handle) {
207
+
return "Handle not found";
208
+
} else {
209
+
return handle;
210
+
}
211
+
} else {
212
+
return "Handle not found";
213
+
}
214
+
};
215
+
166
216
const fetchAllPosts = async () => {
167
217
const users: AccountMetadata[] = await getAllMetadataFromPds();
168
218
const postRecords = await Promise.all(
···
186
236
return posts;
187
237
};
188
238
239
+
const testApiCall = async () => {
240
+
const { data } = await rpc.get("com.atproto.sync.listRepos", {
241
+
params: {},
242
+
});
243
+
console.log(data);
244
+
};
189
245
export { fetchAllPosts, getAllMetadataFromPds, Post };
190
246
export type { AccountMetadata };