Skip to content

Commit e7db3ad

Browse files
feat: v0.1 (#47)
1 parent aff7012 commit e7db3ad

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1120
-132
lines changed

packages/client/package.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,24 @@
2121
"main": "dist/index.cjs",
2222
"module": "dist/index.js",
2323
"types": "dist/index.d.ts",
24-
"files": ["dist"],
24+
"files": [
25+
"dist"
26+
],
2527
"scripts": {
2628
"dev": "pkgroll --watch",
27-
"build": "pkgroll",
29+
"build": "pkgroll --minify",
2830
"test": "vitest",
2931
"test:typescript": "tsc --noEmit"
3032
},
3133
"dependencies": {
32-
"@atcute/client": "^2.0.6",
33-
"@tsky/lexicons": "workspace:*"
34+
"@atcute/client": "^2.0.6"
3435
},
3536
"devDependencies": {
37+
"@tsky/lexicons": "workspace:*",
3638
"globals": "^15.12.0",
3739
"pkgroll": "^2.5.1",
3840
"tsx": "^4.19.2",
3941
"typescript": "^5.7.2",
4042
"vitest": "^2.1.6"
4143
}
42-
}
44+
}

packages/client/src/auth/auth.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
type AtpSessionData,
3+
CredentialManager,
4+
type CredentialManagerOptions,
5+
} from '@atcute/client';
6+
7+
export class Auth {
8+
manager: CredentialManager;
9+
sessions: Map<string, AtpSessionData> = new Map();
10+
11+
constructor(options?: CredentialManagerOptions) {
12+
this.manager = new CredentialManager(
13+
options ?? { service: 'https://bsky.social' },
14+
);
15+
}
16+
17+
async login(identifier: string, password: string) {
18+
const session = await this.manager.login({
19+
identifier,
20+
password,
21+
});
22+
23+
this.sessions.set(session.did, session);
24+
25+
return session;
26+
}
27+
28+
async switch(did: string) {
29+
const session = this.sessions.get(did);
30+
31+
if (!session) {
32+
throw new Error('Session not found');
33+
}
34+
35+
return await this.manager.resume(session);
36+
}
37+
38+
logout(did: string) {
39+
this.sessions.delete(did);
40+
}
41+
42+
get currentSession() {
43+
return this.manager.session;
44+
}
45+
}

packages/client/src/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './auth';
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import type {
2+
AppBskyActorDefs,
3+
AppBskyFeedGetAuthorFeed,
4+
} from '@tsky/lexicons';
5+
import type { Client } from '~/tsky/client';
6+
import type { RPCOptions } from '~/types';
7+
import { Paginator } from '~/utils';
8+
9+
export class Actor {
10+
client: Client;
11+
identifier: string;
12+
13+
constructor(client: Client, identifier: string) {
14+
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;
27+
}
28+
29+
/**
30+
* Get a list of starter packs created by the actor.
31+
*/
32+
starterPacks(limit?: number, options: RPCOptions = {}) {
33+
return Paginator.init(async (cursor) => {
34+
const res = await this.client.get('app.bsky.graph.getActorStarterPacks', {
35+
params: { cursor, actor: this.identifier, limit },
36+
...options,
37+
});
38+
39+
return res.data;
40+
});
41+
}
42+
43+
/**
44+
* Enumerates accounts which follow a specified account (actor).
45+
*/
46+
followers(limit?: number, options: RPCOptions = {}) {
47+
return Paginator.init(async (cursor) => {
48+
const res = await this.client.get('app.bsky.graph.getFollowers', {
49+
params: {
50+
cursor,
51+
actor: this.identifier,
52+
limit,
53+
},
54+
...options,
55+
});
56+
57+
return res.data;
58+
});
59+
}
60+
61+
/**
62+
* Enumerates accounts which a specified account (actor) follows.
63+
*/
64+
follows(limit?: number, options: RPCOptions = {}) {
65+
return Paginator.init(async (cursor) => {
66+
const res = await this.client.get('app.bsky.graph.getFollows', {
67+
params: {
68+
cursor,
69+
actor: this.identifier,
70+
limit,
71+
},
72+
...options,
73+
});
74+
75+
return res.data;
76+
});
77+
}
78+
79+
/**
80+
* Enumerates the lists created by a specified account (actor).
81+
*/
82+
lists(limit?: number, options: RPCOptions = {}) {
83+
return Paginator.init(async (cursor) => {
84+
const res = await this.client.get('app.bsky.graph.getLists', {
85+
params: {
86+
cursor,
87+
actor: this.identifier,
88+
limit,
89+
},
90+
...options,
91+
});
92+
93+
return res.data;
94+
});
95+
}
96+
97+
/**
98+
* Enumerates public relationships between one account, and a list of other accounts. Does not require auth.
99+
*/
100+
async relationships(others?: string[], options?: RPCOptions) {
101+
const res = await this.client.get('app.bsky.graph.getRelationships', {
102+
params: {
103+
actor: this.identifier,
104+
others,
105+
},
106+
...options,
107+
});
108+
109+
return res.data;
110+
}
111+
112+
/**
113+
* Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth.
114+
*/
115+
feeds(limit?: number, options?: RPCOptions) {
116+
return Paginator.init(async (cursor) => {
117+
const res = await this.client.get('app.bsky.feed.getActorFeeds', {
118+
params: { cursor, actor: this.identifier, limit },
119+
...options,
120+
});
121+
122+
return res.data;
123+
});
124+
}
125+
126+
/**
127+
* Get a list of feeds (feed generator records) created by the actor (in the actor's repo).
128+
*/
129+
feed(
130+
params?: Omit<AppBskyFeedGetAuthorFeed.Params, 'actor'>,
131+
options?: RPCOptions,
132+
) {
133+
return Paginator.init(async (cursor) => {
134+
const res = await this.client.get('app.bsky.feed.getAuthorFeed', {
135+
params: { cursor, ...params, actor: this.identifier },
136+
...options,
137+
});
138+
139+
return res.data;
140+
});
141+
}
142+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './actor';

packages/client/src/bsky/bsky.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Feed } from '~/bsky/feed';
2+
import type { Client } from '~/tsky/client';
3+
import { Actor } from './actor';
4+
import { List } from './list';
5+
6+
export class Bsky {
7+
constructor(private client: Client) {}
8+
9+
actor(identifier: string) {
10+
return new Actor(this.client, identifier);
11+
}
12+
13+
list(uri: string) {
14+
return new List(this.client, uri);
15+
}
16+
17+
get feed() {
18+
return new Feed(this.client);
19+
}
20+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { Tsky } from '~/index';
3+
4+
const TEST_CREDENTIALS = {
5+
alice: {
6+
handle: 'alice.tsky.dev',
7+
did: 'did:plc:jguhdmnjclquqf5lsvkyxqy3',
8+
password: 'alice_and_bob',
9+
},
10+
bob: {
11+
handle: 'bob.tsky.dev',
12+
did: 'did:plc:2ig7akkyfq256j42uxvc4g2h',
13+
password: 'alice_and_bob',
14+
},
15+
};
16+
17+
async function getAliceTsky() {
18+
const tsky = new Tsky();
19+
20+
await tsky.auth.login(
21+
TEST_CREDENTIALS.alice.handle,
22+
TEST_CREDENTIALS.alice.password,
23+
);
24+
25+
return tsky;
26+
}
27+
28+
describe('feed', () => {
29+
it('.getFeed()', async () => {
30+
const tsky = await getAliceTsky();
31+
const paginator = await tsky.bsky.feed.get({
32+
// "Birds! 🦉" custom feed
33+
// - https://bsky.app/profile/daryllmarie.bsky.social/feed/aaagllxbcbsje
34+
feed: 'at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.generator/aaagllxbcbsje',
35+
limit: 30,
36+
});
37+
expect(paginator).toBeDefined();
38+
expect(paginator.values).toBeDefined();
39+
expect(paginator.values).toBeInstanceOf(Array);
40+
expect(paginator.values.length).toBe(1); // we should get the first page from the paginator
41+
expect(paginator.values[0].feed.length).toBeGreaterThan(0); // we found some birds posts ;)
42+
expect(paginator.values[0].feed[0]).toHaveProperty('post');
43+
});
44+
});
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import type {
22
AppBskyFeedGetFeed,
3-
AppBskyFeedGetTimeline,
3+
AppBskyFeedSendInteractions,
44
} from '@tsky/lexicons';
55
import type { Client } from '~/tsky/client';
6-
import { Paginator } from '~/tsky/paginator';
6+
import type { RPCOptions } from '~/types';
7+
import { Paginator } from '~/utils';
8+
import { FeedGenerator } from './generator';
79

810
export class Feed {
911
constructor(private client: Client) {}
1012

1113
/**
1214
* Get a hydrated feed from an actor's selected feed generator. Implemented by App View.
1315
*/
14-
async getFeed(
16+
async get(
1517
params: AppBskyFeedGetFeed.Params,
1618
options?: AppBskyFeedGetFeed.Input,
1719
): Promise<Paginator<AppBskyFeedGetFeed.Output>> {
@@ -29,22 +31,21 @@ export class Feed {
2931
}
3032

3133
/**
32-
* Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed.
34+
* Send information about interactions with feed items back to the feed generator that served them.
3335
*/
34-
getTimeline(
35-
params: AppBskyFeedGetTimeline.Params,
36-
options?: AppBskyFeedGetTimeline.Input,
37-
): Promise<Paginator<AppBskyFeedGetTimeline.Output>> {
38-
return Paginator.init(async (cursor) => {
39-
const res = await this.client.get('app.bsky.feed.getTimeline', {
40-
...(options ?? {}),
41-
params: {
42-
cursor,
43-
...params,
44-
},
45-
});
46-
47-
return res.data;
36+
async sendInteractions(
37+
interactions: AppBskyFeedSendInteractions.Input['interactions'],
38+
options: RPCOptions = {},
39+
) {
40+
const res = await this.client.call('app.bsky.feed.sendInteractions', {
41+
data: { interactions },
42+
...options,
4843
});
44+
45+
return res.data;
46+
}
47+
48+
generator() {
49+
return new FeedGenerator(this.client);
4950
}
5051
}

0 commit comments

Comments
 (0)