Skip to content

Commit c48ec0c

Browse files
authored
Simplify and refactor. New Agent. ESM-only build. (#5)
- replace BskyTldr class with utility functions that can be used directly. - move to Agent and Session (remove deprecated AtpAgent). - simplify build with ESM-only. Remove CJS.
1 parent 56b2dc2 commit c48ec0c

File tree

10 files changed

+158
-135
lines changed

10 files changed

+158
-135
lines changed

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Bluesky feed overload? Too long, didn't read?
44

55
Skim a daily list of posts from people you follow, or use AI/LLMs to summarize them into text you can scan or feed to an agent.
66

7+
[Run example](#run-example) section below for a demo of integration with this library.
8+
79
## Installation in your application
810

911
```bash
@@ -15,9 +17,16 @@ npm install bsky-tldr
1517

1618
Exports from the library:
1719

18-
`BskyTldr` is a class that contains lower level functions to retrieve Posts and Follows.
19-
`getDailyPostsFromFollows` is a higher level function to retrieve daily posts from follows. See Data Structure Example below.
20-
`uriToUrl` is a utility function to convert a post uri to a public url to view post on the web.
20+
`getDailyPostsFromFollows` is the main function to retrieve daily posts from follows. See Data Structure Example below.
21+
22+
There are also utility functions that wrap the AtProto pagination with JavaScript generators:
23+
24+
- `retrieveAuthorFeedGenerator()` is a generator function to retrieve posts from an author and
25+
- `retrieveFollowsGenerator` is a generator function to retrieve follows from an author.
26+
27+
And if you want to convert post `uri` to a public URL:
28+
29+
- `uriToUrl` is a utility function to convert a post uri to a public url to view post on the web.
2130

2231
## Data Structure Example
2332

@@ -47,8 +56,6 @@ Here's the data structure built with our `getDailyPostsFromFollows` library func
4756
}
4857
```
4958

50-
You can use `uriToUrl()` function to convert the `uri` to a public URL to view the post on the web.
51-
5259
The author's `did` and `handle` are provided, along with posts that include `uri`, `content`, `createdAt`, and `isRepost`.
5360

5461
If you need more information in your app, use `@atproto/api` library directly to retrieve the author's profile using their `did`, or the full post and replies via its `uri`.

package-lock.json

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,12 @@
1919
"/dist"
2020
],
2121
"type": "module",
22-
"main": "./dist/index.cjs",
2322
"module": "./dist/index.js",
2423
"types": "./dist/index.d.ts",
2524
"exports": {
2625
".": {
2726
"types": "./dist/index.d.ts",
28-
"import": "./dist/index.js",
29-
"require": "./dist/index.cjs",
30-
"default": "./dist/index.js"
27+
"import": "./dist/index.js"
3128
}
3229
},
3330
"scripts": {

rollup.config.mjs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import commonjs from '@rollup/plugin-commonjs';
2+
import json from '@rollup/plugin-json';
13
import resolve from '@rollup/plugin-node-resolve';
24
import terser from '@rollup/plugin-terser';
35
import typescript from '@rollup/plugin-typescript';
4-
import commonjs from '@rollup/plugin-commonjs';
5-
import json from '@rollup/plugin-json';
66
import dts from 'rollup-plugin-dts';
77

88
const external = [
@@ -26,13 +26,11 @@ const plugins = [
2626
tsconfig: './tsconfig.json',
2727
sourceMap: true,
2828
declaration: false,
29-
moduleResolution: 'node',
3029
}),
3130
terser(),
3231
];
3332

3433
export default [
35-
// ESM build
3634
{
3735
input: 'src/index.ts',
3836
output: {
@@ -44,18 +42,6 @@ export default [
4442
plugins,
4543
external,
4644
},
47-
// CJS build
48-
{
49-
input: 'src/index.ts',
50-
output: {
51-
file: 'dist/index.cjs',
52-
format: 'cjs',
53-
sourcemap: true,
54-
exports: 'named',
55-
},
56-
plugins,
57-
external,
58-
},
5945
// Single d.ts file
6046
{
6147
input: 'src/index.ts',

src/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,26 @@ import type {
77
} from './lib/getDailyPostsFromFollows';
88

99
// Import functions and classes
10-
import { BskyTldr } from './lib/bsky-tldr';
10+
import {
11+
retrieveAuthorFeedGenerator,
12+
retrieveFollowsGenerator,
13+
} from './lib/bsky-tldr';
1114
import { getDailyPostsFromFollows } from './lib/getDailyPostsFromFollows';
1215
import { uriToUrl } from './lib/uriToUrl';
1316

1417
// Types
1518
export type {
16-
Follow,
17-
Post,
1819
AuthorFeed,
1920
DailyPostsFromFollows,
2021
DailyPostsFromFollowsResponse,
22+
Follow,
23+
Post,
2124
};
2225

2326
// Core functionality
24-
export { BskyTldr, getDailyPostsFromFollows, uriToUrl };
27+
export {
28+
getDailyPostsFromFollows,
29+
retrieveAuthorFeedGenerator,
30+
retrieveFollowsGenerator,
31+
uriToUrl,
32+
};

src/lib/bsky-tldr.ts

Lines changed: 87 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AtpAgent } from '@atproto/api';
1+
import { Agent } from '@atproto/api';
22
import {
33
isReasonRepost,
44
validateFeedViewPost,
@@ -16,102 +16,98 @@ export interface Post {
1616
isRepost: boolean;
1717
}
1818

19-
export class BskyTldr {
20-
private bluesky: AtpAgent;
21-
22-
constructor(agent: AtpAgent) {
23-
this.bluesky = agent;
19+
export async function* retrieveFollowsGenerator({
20+
bluesky,
21+
actor,
22+
batchSize = 50,
23+
}: {
24+
bluesky: Agent;
25+
actor: string;
26+
batchSize?: number;
27+
}): AsyncGenerator<Follow, void, undefined> {
28+
if (!bluesky) {
29+
throw new Error('Bluesky client not initialized');
2430
}
2531

26-
async *retrieveFollowsGenerator({
27-
actor,
28-
batchSize = 50,
29-
}: {
30-
actor: string;
31-
batchSize?: number;
32-
}): AsyncGenerator<Follow, void, undefined> {
33-
if (!this.bluesky) {
34-
throw new Error('Bluesky client not initialized');
35-
}
36-
37-
let cursor: string | undefined = undefined;
38-
39-
try {
40-
do {
41-
const response = await this.bluesky.getFollows({
42-
actor,
43-
limit: batchSize,
44-
cursor,
45-
});
46-
47-
if (!response.success) {
48-
throw new Error('Failed to fetch follows');
49-
}
50-
51-
for (const follow of response.data.follows) {
52-
yield {
53-
did: follow.did,
54-
handle: follow.handle,
55-
};
56-
}
57-
58-
cursor = response.data.cursor;
59-
} while (cursor);
60-
} catch (error) {
61-
throw new Error(
62-
`Failed to retrieve follows: ${
63-
error instanceof Error ? error.message : 'Unknown error'
64-
}`
65-
);
66-
}
32+
let cursor: string | undefined = undefined;
33+
34+
try {
35+
do {
36+
const response = await bluesky.getFollows({
37+
actor,
38+
limit: batchSize,
39+
cursor,
40+
});
41+
42+
if (!response.success) {
43+
throw new Error('Failed to fetch follows');
44+
}
45+
46+
for (const follow of response.data.follows) {
47+
yield {
48+
did: follow.did,
49+
handle: follow.handle,
50+
};
51+
}
52+
53+
cursor = response.data.cursor;
54+
} while (cursor);
55+
} catch (error) {
56+
throw new Error(
57+
`Failed to retrieve follows: ${
58+
error instanceof Error ? error.message : 'Unknown error'
59+
}`
60+
);
6761
}
62+
}
6863

69-
async *retrieveAuthorFeedGenerator({
70-
actor,
71-
batchSize = 5,
72-
}: {
73-
actor: string;
74-
batchSize?: number;
75-
}): AsyncGenerator<Post, void, undefined> {
76-
if (!this.bluesky) {
77-
throw new Error('Bluesky client not initialized');
78-
}
79-
80-
let cursor: string | undefined = undefined;
81-
let count = 0;
82-
83-
try {
84-
do {
85-
const { data } = await this.bluesky.getAuthorFeed({
86-
actor,
87-
limit: batchSize,
88-
cursor,
89-
});
90-
91-
for (const feedViewPost of data.feed) {
92-
if (!validateFeedViewPost(feedViewPost)) {
93-
console.info('Invalid feed view post:', feedViewPost);
94-
continue;
95-
}
64+
export async function* retrieveAuthorFeedGenerator({
65+
bluesky,
66+
actor,
67+
batchSize = 5,
68+
}: {
69+
bluesky: Agent;
70+
actor: string;
71+
batchSize?: number;
72+
}): AsyncGenerator<Post, void, undefined> {
73+
if (!bluesky) {
74+
throw new Error('Bluesky client not initialized');
75+
}
9676

97-
const postView = feedViewPost.post;
98-
yield {
99-
uri: postView.uri,
100-
content: (postView.record.text as string) || '',
101-
createdAt: (postView.record.createdAt as string) || '',
102-
isRepost: isReasonRepost(feedViewPost.reason),
103-
};
104-
count++;
77+
let cursor: string | undefined = undefined;
78+
let count = 0;
79+
80+
try {
81+
do {
82+
const { data } = await bluesky.getAuthorFeed({
83+
actor,
84+
limit: batchSize,
85+
cursor,
86+
});
87+
88+
for (const feedViewPost of data.feed) {
89+
if (!validateFeedViewPost(feedViewPost)) {
90+
console.info('Invalid feed view post:', feedViewPost);
91+
continue;
10592
}
10693

107-
cursor = data.cursor;
108-
} while (cursor);
109-
} catch (error) {
110-
throw new Error(
111-
`Failed to retrieve author feed: ${
112-
error instanceof Error ? error.message : 'Unknown error'
113-
}`
114-
);
115-
}
94+
const postView = feedViewPost.post;
95+
yield {
96+
uri: postView.uri,
97+
content: (postView.record.text as string) || '',
98+
createdAt: (postView.record.createdAt as string) || '',
99+
isRepost: isReasonRepost(feedViewPost.reason),
100+
};
101+
count++;
102+
}
103+
104+
cursor = data.cursor;
105+
} while (cursor);
106+
} catch (error) {
107+
throw new Error(
108+
`Failed to retrieve author feed: ${
109+
error instanceof Error ? error.message : 'Unknown error'
110+
}`
111+
);
116112
}
117113
}

0 commit comments

Comments
 (0)