Skip to content

Commit 3e0f2ef

Browse files
feat: wip linking all classes
1 parent 373d900 commit 3e0f2ef

File tree

11 files changed

+332
-44
lines changed

11 files changed

+332
-44
lines changed

packages/client/src/actor/actor.ts

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
11
import type {
22
AppBskyActorDefs,
33
AppBskyFeedGetAuthorFeed,
4+
AppBskyGraphDefs,
5+
At,
6+
ComAtprotoLabelDefs,
7+
ComAtprotoRepoStrongRef,
48
} from '@tsky/lexicons';
59
import type { Client } from '~/agent/client';
10+
import { List } from '~/list';
611
import type { RPCOptions } from '~/types';
712
import { Paginator } from '~/utils';
813

914
export class Actor {
1015
client: Client;
11-
identifier: string;
16+
did: At.DID;
1217

13-
constructor(client: Client, identifier: string) {
18+
constructor(client: Client, did: At.DID) {
1419
this.client = client;
15-
this.identifier = identifier;
16-
}
17-
18-
/**
19-
* Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.
20-
*/
21-
async profile(): Promise<AppBskyActorDefs.ProfileViewDetailed> {
22-
const res = await this.client.get('app.bsky.actor.getProfile', {
23-
params: { actor: this.identifier },
24-
});
25-
26-
return res.data;
20+
this.did = did;
2721
}
2822

2923
/**
@@ -32,7 +26,7 @@ export class Actor {
3226
starterPacks(limit?: number, options: RPCOptions = {}) {
3327
return Paginator.init(async (cursor) => {
3428
const res = await this.client.get('app.bsky.graph.getActorStarterPacks', {
35-
params: { cursor, actor: this.identifier, limit },
29+
params: { cursor, actor: this.did, limit },
3630
...options,
3731
});
3832

@@ -48,13 +42,19 @@ export class Actor {
4842
const res = await this.client.get('app.bsky.graph.getFollowers', {
4943
params: {
5044
cursor,
51-
actor: this.identifier,
45+
actor: this.did,
5246
limit,
5347
},
5448
...options,
5549
});
5650

57-
return res.data;
51+
return {
52+
...res.data,
53+
subject: new ActorProfile(this.client, res.data.subject),
54+
followers: res.data.followers.map(
55+
(follower) => new ActorProfile(this.client, follower),
56+
),
57+
};
5858
});
5959
}
6060

@@ -66,13 +66,19 @@ export class Actor {
6666
const res = await this.client.get('app.bsky.graph.getFollows', {
6767
params: {
6868
cursor,
69-
actor: this.identifier,
69+
actor: this.did,
7070
limit,
7171
},
7272
...options,
7373
});
7474

75-
return res.data;
75+
return {
76+
...res.data,
77+
subject: new ActorProfile(this.client, res.data.subject),
78+
follows: res.data.follows.map(
79+
(follow) => new ActorProfile(this.client, follow),
80+
),
81+
};
7682
});
7783
}
7884

@@ -84,13 +90,17 @@ export class Actor {
8490
const res = await this.client.get('app.bsky.graph.getLists', {
8591
params: {
8692
cursor,
87-
actor: this.identifier,
93+
actor: this.did,
8894
limit,
8995
},
9096
...options,
9197
});
9298

93-
return res.data;
99+
return {
100+
...res.data,
101+
// TODO: Solve this
102+
// lists: res.data.lists.map((list) => new List(this.client, list)),
103+
};
94104
});
95105
}
96106

@@ -100,13 +110,18 @@ export class Actor {
100110
async relationships(others?: string[], options?: RPCOptions) {
101111
const res = await this.client.get('app.bsky.graph.getRelationships', {
102112
params: {
103-
actor: this.identifier,
113+
actor: this.did,
104114
others,
105115
},
106116
...options,
107117
});
108118

109-
return res.data;
119+
return {
120+
...res.data,
121+
actor: res.data.actor
122+
? new Actor(this.client, res.data.actor)
123+
: undefined,
124+
};
110125
}
111126

112127
/**
@@ -115,7 +130,7 @@ export class Actor {
115130
feeds(limit?: number, options?: RPCOptions) {
116131
return Paginator.init(async (cursor) => {
117132
const res = await this.client.get('app.bsky.feed.getActorFeeds', {
118-
params: { cursor, actor: this.identifier, limit },
133+
params: { cursor, actor: this.did, limit },
119134
...options,
120135
});
121136

@@ -132,11 +147,61 @@ export class Actor {
132147
) {
133148
return Paginator.init(async (cursor) => {
134149
const res = await this.client.get('app.bsky.feed.getAuthorFeed', {
135-
params: { cursor, ...params, actor: this.identifier },
150+
params: { cursor, ...params, actor: this.did },
136151
...options,
137152
});
138153

139154
return res.data;
140155
});
141156
}
142157
}
158+
159+
export class BasicActorProfile
160+
extends Actor
161+
implements AppBskyActorDefs.ProfileViewBasic
162+
{
163+
// @ts-expect-error - We added this property with Object.assign
164+
handle: string;
165+
associated?: AppBskyActorDefs.ProfileAssociated;
166+
avatar?: string;
167+
createdAt?: string;
168+
displayName?: string;
169+
labels?: ComAtprotoLabelDefs.Label[];
170+
viewer?: AppBskyActorDefs.ViewerState;
171+
$type?: string;
172+
173+
constructor(client: Client, actor: AppBskyActorDefs.ProfileViewBasic) {
174+
super(client, actor.did);
175+
Object.assign(this, actor);
176+
}
177+
}
178+
179+
export class ActorProfile
180+
extends BasicActorProfile
181+
implements AppBskyActorDefs.ProfileView
182+
{
183+
description?: string;
184+
indexedAt?: string;
185+
186+
constructor(client: Client, actor: AppBskyActorDefs.ProfileViewDetailed) {
187+
super(client, actor);
188+
Object.assign(this, actor);
189+
}
190+
}
191+
192+
export class DetailedActorProfile
193+
extends ActorProfile
194+
implements AppBskyActorDefs.ProfileViewDetailed
195+
{
196+
banner?: string;
197+
followersCount?: number;
198+
followsCount?: number;
199+
joinedViaStarterPack?: AppBskyGraphDefs.StarterPackViewBasic;
200+
pinnedPost?: ComAtprotoRepoStrongRef.Main;
201+
postsCount?: number;
202+
203+
constructor(client: Client, actor: AppBskyActorDefs.ProfileViewDetailed) {
204+
super(client, actor);
205+
Object.assign(this, actor);
206+
}
207+
}

packages/client/src/agent/agent.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { type CredentialManager, XRPC } from '@atcute/client';
22
import type {
33
AppBskyGraphGetStarterPack,
44
AppBskyGraphGetStarterPacks,
5+
At,
56
Queries,
67
} from '@tsky/lexicons';
7-
import { Actor } from '~/actor';
8+
import { DetailedActorProfile } from '~/actor';
89
import { Feed } from '~/feed';
910
import { List } from '~/list';
1011
import { Search } from '~/search';
@@ -19,15 +20,22 @@ export class Agent {
1920
constructor(private handler: CredentialManager) {
2021
// Initialize the client
2122
const xrpc = new XRPC({ handler: this.handler });
22-
this.client = new Client(xrpc);
23+
this.client = new Client(xrpc, this.handler);
2324
}
2425

2526
get session() {
2627
return this.handler.session;
2728
}
2829

29-
actor(identifier: string) {
30-
return new Actor(this.client, identifier);
30+
/**
31+
* Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.
32+
*/
33+
async actor(identifier: At.DID | At.Handle): Promise<DetailedActorProfile> {
34+
const res = await this.client.get('app.bsky.actor.getProfile', {
35+
params: { actor: identifier },
36+
});
37+
38+
return new DetailedActorProfile(this.client, res.data);
3139
}
3240

3341
list(uri: string) {
@@ -47,7 +55,7 @@ export class Agent {
4755
throw new Error('There is no active session');
4856
}
4957

50-
return new User(this.client, this.session.handle);
58+
return new User(this.client, this.session.did);
5159
}
5260

5361
get video() {

packages/client/src/agent/client.ts

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
import type {
2+
CredentialManager,
23
RPCOptions,
34
XRPC,
45
XRPCRequestOptions,
56
XRPCResponse,
67
} from '@atcute/client';
7-
import type { Procedures, Queries } from '@tsky/lexicons';
8+
import type { At, Procedures, Queries } from '@tsky/lexicons';
9+
import { parseAtUri } from '~/utils';
10+
import type { RPCOptions as GenericReqOptions, StrongRef } from '../types';
811

912
// From @atcute/client
1013
type OutputOf<T> = T extends {
11-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
12-
output: any;
14+
output: unknown;
1315
}
1416
? T['output']
1517
: never;
1618

19+
const NO_SESSION_ERROR =
20+
'No session found. Please login to perform this action.';
21+
1722
export class Client<Q = Queries, P = Procedures> {
1823
xrpc: XRPC;
24+
crenditials: CredentialManager;
1925

20-
constructor(xrpc: XRPC) {
26+
constructor(xrpc: XRPC, crenditials: CredentialManager) {
2127
this.xrpc = xrpc;
28+
this.crenditials = crenditials;
2229
}
2330

2431
/**
@@ -53,4 +60,86 @@ export class Client<Q = Queries, P = Procedures> {
5360
async request(options: XRPCRequestOptions): Promise<XRPCResponse> {
5461
return this.xrpc.request(options);
5562
}
63+
64+
/**
65+
* Create a record.
66+
* @param nsid The collection's NSID.
67+
* @param record The record to create.
68+
* @param rkey The rkey to use.
69+
* @returns The record's AT URI and CID.
70+
*/
71+
async createRecord<K extends keyof P>(
72+
nsid: K,
73+
record: Omit<RPCOptions<P[K]>, '$type' | 'createdAt'>,
74+
rkey?: string,
75+
): Promise<StrongRef> {
76+
if (!this.crenditials.session) throw new Error(NO_SESSION_ERROR);
77+
const response = await this.call(
78+
'com.atproto.repo.createRecord' as keyof P,
79+
{
80+
data: {
81+
collection: nsid,
82+
record: {
83+
$type: nsid,
84+
createdAt: new Date().toISOString(),
85+
...record,
86+
},
87+
repo: this.crenditials.session.did,
88+
...(rkey ? { rkey } : {}),
89+
},
90+
} as unknown as RPCOptions<P[keyof P]>,
91+
);
92+
return response.data;
93+
}
94+
95+
/**
96+
* Put a record in place of an existing record.
97+
* @param nsid The collection's NSID.
98+
* @param record The record to put.
99+
* @param rkey The rkey to use.
100+
* @returns The record's AT URI and CID.
101+
*/
102+
async putRecord(
103+
nsid: string,
104+
record: object,
105+
rkey: string,
106+
): Promise<StrongRef> {
107+
if (!this.crenditials.session) throw new Error(NO_SESSION_ERROR);
108+
const response = await this.call(
109+
'com.atproto.repo.putRecord' as keyof P,
110+
{
111+
data: {
112+
collection: nsid,
113+
record: {
114+
$type: nsid,
115+
createdAt: new Date().toISOString(),
116+
...record,
117+
},
118+
repo: this.crenditials.session.did,
119+
rkey,
120+
},
121+
} as unknown as RPCOptions<P[keyof P]>,
122+
);
123+
return response.data;
124+
}
125+
126+
/**
127+
* Delete a record.
128+
* @param uri The record's AT URI.
129+
*/
130+
async deleteRecord(
131+
uri: At.Uri,
132+
options: GenericReqOptions = {},
133+
): Promise<void> {
134+
const { host: repo, collection, rkey } = parseAtUri(uri);
135+
if (repo !== this.crenditials.session?.did)
136+
throw new Error('Can only delete own record.');
137+
await this.call(
138+
'com.atproto.repo.deleteRecord' as keyof P,
139+
{
140+
data: { collection, repo, rkey },
141+
...options,
142+
} as unknown as RPCOptions<P[keyof P]>,
143+
);
144+
}
56145
}

packages/client/src/list/list.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ActorProfile } from '~/actor';
12
import type { Client } from '~/agent/client';
23
import type { RPCOptions } from '~/types';
34
import { Paginator } from '~/utils';
@@ -22,7 +23,13 @@ export class List {
2223
...options,
2324
});
2425

25-
return res.data;
26+
return {
27+
...res.data,
28+
items: res.data.items.map((item) => ({
29+
...item,
30+
subject: new ActorProfile(this.client, item.subject),
31+
})),
32+
};
2633
});
2734
}
2835

0 commit comments

Comments
 (0)