deso-ag 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +272 -0
  3. package/dist/fetchers/bluesky.d.ts +3 -0
  4. package/dist/fetchers/bluesky.d.ts.map +1 -0
  5. package/dist/fetchers/bluesky.js +154 -0
  6. package/dist/fetchers/bluesky.js.map +1 -0
  7. package/dist/fetchers/farcaster.d.ts +3 -0
  8. package/dist/fetchers/farcaster.d.ts.map +1 -0
  9. package/dist/fetchers/farcaster.js +149 -0
  10. package/dist/fetchers/farcaster.js.map +1 -0
  11. package/dist/fetchers/lens.d.ts +3 -0
  12. package/dist/fetchers/lens.d.ts.map +1 -0
  13. package/dist/fetchers/lens.js +125 -0
  14. package/dist/fetchers/lens.js.map +1 -0
  15. package/dist/fetchers/nostr.d.ts +3 -0
  16. package/dist/fetchers/nostr.d.ts.map +1 -0
  17. package/dist/fetchers/nostr.js +307 -0
  18. package/dist/fetchers/nostr.js.map +1 -0
  19. package/dist/formatters/output.d.ts +5 -0
  20. package/dist/formatters/output.d.ts.map +1 -0
  21. package/dist/formatters/output.js +225 -0
  22. package/dist/formatters/output.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +193 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lib.d.ts +20 -0
  28. package/dist/lib.d.ts.map +1 -0
  29. package/dist/lib.js +71 -0
  30. package/dist/lib.js.map +1 -0
  31. package/dist/pipeline.d.ts +12 -0
  32. package/dist/pipeline.d.ts.map +1 -0
  33. package/dist/pipeline.js +136 -0
  34. package/dist/pipeline.js.map +1 -0
  35. package/dist/terms.d.ts +20 -0
  36. package/dist/terms.d.ts.map +1 -0
  37. package/dist/terms.js +158 -0
  38. package/dist/terms.js.map +1 -0
  39. package/dist/types.d.ts +67 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/utils/search.d.ts +6 -0
  44. package/dist/utils/search.d.ts.map +1 -0
  45. package/dist/utils/search.js +13 -0
  46. package/dist/utils/search.js.map +1 -0
  47. package/dist/utils/time.d.ts +4 -0
  48. package/dist/utils/time.d.ts.map +1 -0
  49. package/dist/utils/time.js +24 -0
  50. package/dist/utils/time.js.map +1 -0
  51. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Matthew Lee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # deso-ag
2
+
3
+ **Decentralized Social Aggregator** - A CLI tool and library for aggregating posts from decentralized social protocols.
4
+
5
+ Search and view content across **Farcaster**, **Lens**, **Nostr**, and **Bluesky** from your terminal or programmatically from your agent.
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ git clone https://github.com/sphairetra/deso-ag
11
+ cd deso-ag
12
+ pnpm install
13
+ ```
14
+
15
+ ### Environment Variables
16
+
17
+ | Variable | Required | Default | Description |
18
+ |----------|----------|---------|-------------|
19
+ | `NEYNAR_API_KEY` | For Farcaster | None — Farcaster is skipped | Neynar API key. Get one free at [neynar.com](https://neynar.com) |
20
+ | `BLUESKY_IDENTIFIER` | For Bluesky search | None — Bluesky search is skipped, trending still works | Your Bluesky handle (e.g. `user.bsky.social`) |
21
+ | `BLUESKY_APP_PASSWORD` | For Bluesky search | None — Bluesky search is skipped, trending still works | App password from [bsky.app/settings/app-passwords](https://bsky.app/settings/app-passwords) |
22
+
23
+ Lens, Nostr, and Bluesky trending work without any keys. Bluesky search requires authentication.
24
+
25
+ Add keys to your shell profile so they persist across sessions:
26
+
27
+ ```bash
28
+ # Add to ~/.zshrc or ~/.bashrc
29
+ export NEYNAR_API_KEY=your-key-here
30
+ export BLUESKY_IDENTIFIER=your-handle.bsky.social
31
+ export BLUESKY_APP_PASSWORD=your-app-password
32
+ ```
33
+
34
+ Without a key, the respective source/feature is skipped and everything else still works normally.
35
+
36
+ ## Commands
37
+
38
+ ### `search [query]`
39
+
40
+ Search for posts across networks.
41
+
42
+ ```bash
43
+ pnpm dev search "ethereum"
44
+ pnpm dev search "AI" --sources nostr
45
+ pnpm dev search --channel dev --sources farcaster
46
+ ```
47
+
48
+ Multi-word queries use AND semantics (all terms must match):
49
+
50
+ ```bash
51
+ pnpm dev search "AI crypto" # posts must contain both "AI" and "crypto"
52
+ pnpm dev search "ethereum layer2"
53
+ ```
54
+
55
+ ### `trending`
56
+
57
+ Get trending posts from all networks.
58
+
59
+ ```bash
60
+ pnpm dev trending
61
+ pnpm dev trending --sources farcaster,lens
62
+ pnpm dev trending --format json --limit 50
63
+ ```
64
+
65
+ ### `terms`
66
+
67
+ Extract top discussion terms from posts via engagement-weighted frequency analysis.
68
+
69
+ ```bash
70
+ pnpm dev terms # top 3 terms per platform, last 24h
71
+ pnpm dev terms -n 5 -s farcaster -t week # top 5, Farcaster only, last week
72
+ pnpm dev terms -f json # machine-readable output
73
+ ```
74
+
75
+ ### `channels`
76
+
77
+ Browse popular Farcaster channels.
78
+
79
+ ```bash
80
+ pnpm dev channels
81
+ pnpm dev channels --limit 50
82
+ ```
83
+
84
+ ## Options
85
+
86
+ All commands accept the following options (except where noted):
87
+
88
+ | Option | Description | Values | Default |
89
+ |--------|-------------|--------|---------|
90
+ | `-s, --sources` | Networks to query | `farcaster`, `lens`, `nostr`, `bluesky` (comma-separated) | `farcaster,lens,nostr,bluesky` (all) |
91
+ | `-t, --timeframe` | Time range for posts | `24h`, `48h`, `week` | `24h` |
92
+ | `-c, --channel` | Filter by channel | Any channel ID (Farcaster only) | none |
93
+ | `-f, --format` | Output format | `json`, `markdown`, `summary`, `compact` | `markdown` (search), `summary` (trending) |
94
+ | `-l, --limit` | Max posts per source | Any positive integer | `25` |
95
+ | `-o, --sort` | Sort order | `engagement`, `recent`, `relevance` | `relevance` (search), `engagement` (trending) |
96
+ | `-n, --top` | Top terms per source | Any positive integer (terms command only) | `3` |
97
+
98
+ ### Output Formats
99
+
100
+ - **`markdown`** - Human-readable with headers, author info, and engagement stats. Default for `search`.
101
+ - **`summary`** - Condensed overview with post counts and top content. Default for `trending`.
102
+ - **`json`** - Raw JSON array of post objects. Good for piping to other tools.
103
+ - **`compact`** - Single JSON object with metadata envelope, engagement scores, and full content. Designed for AI agents.
104
+
105
+ ### Sort Orders
106
+
107
+ - **`engagement`** - By score (`likes + reposts*2 + replies`). Best for discovering high-signal content. Default for `trending`.
108
+ - **`recent`** - By timestamp descending. Best for monitoring.
109
+ - **`relevance`** - Query-matching posts first, then by engagement. Default for `search`.
110
+
111
+ ## Agent Usage
112
+
113
+ deso-ag is designed for consumption by AI agents doing research across decentralized social networks.
114
+
115
+ ### Compact Output Format
116
+
117
+ The `compact` format returns a single JSON object with a metadata envelope, pre-computed engagement scores, full untruncated content, and source health info:
118
+
119
+ ```bash
120
+ pnpm dev trending -f compact -l 10
121
+ pnpm dev search "AI agents" -f compact -l 10
122
+ ```
123
+
124
+ Output shape:
125
+
126
+ ```json
127
+ {
128
+ "meta": {
129
+ "query": "AI agents",
130
+ "totalPosts": 42,
131
+ "sources": [
132
+ {"name": "farcaster", "count": 15},
133
+ {"name": "lens", "count": 12},
134
+ {"name": "nostr", "count": 15}
135
+ ],
136
+ "timeframe": "24h",
137
+ "fetchedAt": "2025-01-01T00:00:00.000Z"
138
+ },
139
+ "posts": [
140
+ {
141
+ "id": "...",
142
+ "source": "farcaster",
143
+ "author": "dwr",
144
+ "content": "full untruncated content...",
145
+ "timestamp": "2025-01-01T00:00:00.000Z",
146
+ "url": "https://...",
147
+ "score": 523,
148
+ "engagement": {"likes": 400, "reposts": 50, "replies": 23},
149
+ "tags": []
150
+ }
151
+ ]
152
+ }
153
+ ```
154
+
155
+ ### Library Import
156
+
157
+ For agents that run in Node.js, import `aggregate()` directly instead of shelling out:
158
+
159
+ ```typescript
160
+ import { aggregate } from 'deso-ag';
161
+
162
+ const result = await aggregate({
163
+ sources: ['farcaster', 'lens', 'nostr', 'bluesky'],
164
+ timeframe: '24h',
165
+ query: 'AI agents',
166
+ limit: 20,
167
+ sort: 'relevance',
168
+ });
169
+
170
+ console.log(result.meta.totalPosts);
171
+ for (const post of result.posts) {
172
+ console.log(`[${post.source}] @${post.author.username}: ${post.content.slice(0, 100)}`);
173
+ }
174
+ ```
175
+
176
+ The `terms()` function extracts top discussion terms:
177
+
178
+ ```typescript
179
+ import { terms } from 'deso-ag';
180
+
181
+ const result = await terms({
182
+ sources: ['farcaster', 'nostr'],
183
+ timeframe: '24h',
184
+ limit: 20,
185
+ }, 5); // top 5 terms
186
+
187
+ for (const st of result.bySource) {
188
+ console.log(`${st.source}: ${st.terms.map(t => t.token).join(', ')}`);
189
+ }
190
+ ```
191
+
192
+ Individual fetchers and utilities are also exported:
193
+
194
+ ```typescript
195
+ import { fetchFarcaster, fetchLens, fetchNostr, fetchBluesky, computeEngagementScore, matchesQuery, extractTerms } from 'deso-ag';
196
+ ```
197
+
198
+ ## Examples
199
+
200
+ ```bash
201
+ # Get a quick summary of trending content
202
+ pnpm dev trending -f summary -l 20
203
+
204
+ # Agent-optimized compact output sorted by engagement
205
+ pnpm dev trending -f compact -o engagement -l 10
206
+
207
+ # Search for AI discussions on Lens only
208
+ pnpm dev search "AI" -s lens -f json
209
+
210
+ # Multi-word search with compact output
211
+ pnpm dev search "AI crypto" -f compact -l 10
212
+
213
+ # Browse the /dev channel on Farcaster
214
+ pnpm dev search --channel dev -s farcaster
215
+
216
+ # Export trending Nostr posts as JSON
217
+ pnpm dev trending -s nostr -f json > nostr-trending.json
218
+
219
+ # Search Bluesky for discussions
220
+ pnpm dev search "ethereum" -s bluesky -l 5
221
+
222
+ # Trending on Bluesky
223
+ pnpm dev trending -s bluesky -f summary
224
+
225
+ # Sort search results by recency
226
+ pnpm dev search "ethereum" -o recent -f json -l 5
227
+
228
+ # Top 5 terms across all networks this week
229
+ pnpm dev terms -n 5 -t week
230
+
231
+ # Terms from Farcaster and Nostr as JSON
232
+ pnpm dev terms -f json -s farcaster,nostr -l 10
233
+ ```
234
+
235
+ ## Supported Networks
236
+
237
+ | Network | API | Auth |
238
+ |---------|-----|------|
239
+ | **Farcaster** | [Neynar API](https://neynar.com) - trending feed and full-text search | `NEYNAR_API_KEY` required |
240
+ | **Lens** | [Lens V3 GraphQL API](https://api.lens.xyz) - server-side search, recent posts | None |
241
+ | **Nostr** | [nostr.wine trending API](https://docs.nostr.wine/api/trending) + public relays (relay.damus.io, nos.lol, relay.snort.social) | None |
242
+ | **Bluesky** | [AT Protocol API](https://docs.bsky.app) - public "What's Hot" feed for trending, authenticated search via `app.bsky.feed.searchPosts` | None (trending), `BLUESKY_IDENTIFIER` + `BLUESKY_APP_PASSWORD` (search) |
243
+
244
+ All networks return engagement stats (likes, reposts, replies) and support timeframe filtering.
245
+
246
+ ## Limitations
247
+
248
+ - **Farcaster**: Requires `NEYNAR_API_KEY`. Without it, Farcaster is skipped.
249
+ - **Nostr**: Relay responses can be slow or inconsistent depending on network conditions.
250
+ - **Rate limits**: All APIs have rate limits. For heavy usage, consider running your own infrastructure.
251
+
252
+ ## Development
253
+
254
+ ```bash
255
+ pnpm build # Build for production
256
+ pnpm test # Run tests
257
+ ```
258
+
259
+ ## Tech Stack
260
+
261
+ - TypeScript
262
+ - Commander.js for CLI
263
+ - nostr-tools for Nostr protocol
264
+ - Native fetch for HTTP requests
265
+
266
+ ## License
267
+
268
+ MIT
269
+
270
+ ## Author
271
+
272
+ [@mattlee.eth](https://farcaster.xyz/mattlee.eth)
@@ -0,0 +1,3 @@
1
+ import type { SearchOptions, FetchResult } from '../types.js';
2
+ export declare function fetchBluesky(options: SearchOptions): Promise<FetchResult>;
3
+ //# sourceMappingURL=bluesky.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bluesky.d.ts","sourceRoot":"","sources":["../../src/fetchers/bluesky.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyKpE,wBAAsB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAmD/E"}
@@ -0,0 +1,154 @@
1
+ import { getTimeframeCutoff } from '../utils/time.js';
2
+ import { matchesQuery } from '../utils/search.js';
3
+ const BSKY_PUBLIC = 'https://public.api.bsky.app/xrpc';
4
+ const BSKY_PDS = 'https://bsky.social/xrpc';
5
+ const BSKY_APPVIEW = 'https://api.bsky.app/xrpc';
6
+ // Bluesky's official "What's Hot" feed generator
7
+ const WHATS_HOT_URI = 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot';
8
+ function bskyPostToPost(post) {
9
+ // Extract rkey from AT URI: at://did/app.bsky.feed.post/rkey
10
+ const rkey = post.uri.split('/').pop() || '';
11
+ return {
12
+ id: post.uri,
13
+ source: 'bluesky',
14
+ author: {
15
+ username: post.author.handle,
16
+ displayName: post.author.displayName,
17
+ profileUrl: `https://bsky.app/profile/${post.author.handle}`,
18
+ },
19
+ content: post.record.text,
20
+ timestamp: new Date(post.record.createdAt),
21
+ url: `https://bsky.app/profile/${post.author.handle}/post/${rkey}`,
22
+ engagement: {
23
+ likes: post.likeCount || 0,
24
+ reposts: (post.repostCount || 0) + (post.quoteCount || 0),
25
+ replies: post.replyCount || 0,
26
+ },
27
+ tags: post.record.tags,
28
+ };
29
+ }
30
+ /**
31
+ * Fetch trending posts via the public "What's Hot" feed generator.
32
+ * No auth required.
33
+ */
34
+ async function fetchWhatsHot(limit) {
35
+ const params = new URLSearchParams({
36
+ feed: WHATS_HOT_URI,
37
+ limit: String(Math.min(limit, 100)),
38
+ });
39
+ const controller = new AbortController();
40
+ const timeout = setTimeout(() => controller.abort(), 10000);
41
+ try {
42
+ const response = await fetch(`${BSKY_PUBLIC}/app.bsky.feed.getFeed?${params}`, {
43
+ signal: controller.signal,
44
+ headers: { 'Accept': 'application/json' },
45
+ });
46
+ clearTimeout(timeout);
47
+ if (!response.ok) {
48
+ throw new Error(`Bluesky feed API: ${response.status}`);
49
+ }
50
+ const data = await response.json();
51
+ return (data.feed || []).map(item => bskyPostToPost(item.post));
52
+ }
53
+ catch (error) {
54
+ clearTimeout(timeout);
55
+ throw error;
56
+ }
57
+ }
58
+ async function createSession(identifier, password) {
59
+ const controller = new AbortController();
60
+ const timeout = setTimeout(() => controller.abort(), 10000);
61
+ try {
62
+ const response = await fetch(`${BSKY_PDS}/com.atproto.server.createSession`, {
63
+ method: 'POST',
64
+ signal: controller.signal,
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({ identifier, password }),
67
+ });
68
+ clearTimeout(timeout);
69
+ if (!response.ok) {
70
+ throw new Error(`Bluesky auth failed: ${response.status}`);
71
+ }
72
+ const data = await response.json();
73
+ return data;
74
+ }
75
+ catch (error) {
76
+ clearTimeout(timeout);
77
+ throw error;
78
+ }
79
+ }
80
+ async function searchBluesky(accessJwt, query, limit) {
81
+ const params = new URLSearchParams({
82
+ q: query,
83
+ limit: String(Math.min(limit, 100)),
84
+ sort: 'top',
85
+ });
86
+ const controller = new AbortController();
87
+ const timeout = setTimeout(() => controller.abort(), 10000);
88
+ try {
89
+ const response = await fetch(`${BSKY_APPVIEW}/app.bsky.feed.searchPosts?${params}`, {
90
+ signal: controller.signal,
91
+ headers: {
92
+ 'Accept': 'application/json',
93
+ 'Authorization': `Bearer ${accessJwt}`,
94
+ },
95
+ });
96
+ clearTimeout(timeout);
97
+ if (!response.ok) {
98
+ throw new Error(`Bluesky search API: ${response.status}`);
99
+ }
100
+ const data = await response.json();
101
+ const posts = data.posts || [];
102
+ return posts.map(bskyPostToPost);
103
+ }
104
+ catch (error) {
105
+ clearTimeout(timeout);
106
+ throw error;
107
+ }
108
+ }
109
+ export async function fetchBluesky(options) {
110
+ const limit = options.limit || 25;
111
+ const cutoff = getTimeframeCutoff(options.timeframe);
112
+ try {
113
+ let posts;
114
+ if (options.query) {
115
+ // Search requires authentication
116
+ const identifier = process.env.BLUESKY_IDENTIFIER;
117
+ const appPassword = process.env.BLUESKY_APP_PASSWORD;
118
+ if (!identifier || !appPassword) {
119
+ return {
120
+ posts: [],
121
+ source: 'bluesky',
122
+ error: 'Bluesky search requires BLUESKY_IDENTIFIER and BLUESKY_APP_PASSWORD. Create an app password at https://bsky.app/settings/app-passwords',
123
+ };
124
+ }
125
+ const session = await createSession(identifier, appPassword);
126
+ posts = await searchBluesky(session.accessJwt, options.query, limit);
127
+ }
128
+ else {
129
+ // Trending uses the public "What's Hot" feed — no auth needed
130
+ posts = await fetchWhatsHot(limit);
131
+ }
132
+ // Apply timeframe cutoff client-side
133
+ posts = posts.filter(p => p.timestamp >= cutoff);
134
+ // Filter by query if provided (AND semantics for multi-word queries)
135
+ if (options.query) {
136
+ posts = posts.filter(p => matchesQuery(p.content, p.tags || [], options.query));
137
+ }
138
+ // Filter empty content
139
+ posts = posts.filter(p => p.content.trim().length > 0);
140
+ return {
141
+ posts: posts.slice(0, limit),
142
+ source: 'bluesky',
143
+ };
144
+ }
145
+ catch (error) {
146
+ const message = error instanceof Error ? error.message : 'Unknown error';
147
+ return {
148
+ posts: [],
149
+ source: 'bluesky',
150
+ error: `Bluesky: ${message}`,
151
+ };
152
+ }
153
+ }
154
+ //# sourceMappingURL=bluesky.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bluesky.js","sourceRoot":"","sources":["../../src/fetchers/bluesky.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SearchOptions, FetchResult } from '../types.js';
2
+ export declare function fetchFarcaster(options: SearchOptions): Promise<FetchResult>;
3
+ //# sourceMappingURL=farcaster.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"farcaster.d.ts","sourceRoot":"","sources":["../../src/fetchers/farcaster.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiEpE,wBAAsB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CA+CjF"}
@@ -0,0 +1,149 @@
1
+ import { getTimeframeCutoff } from '../utils/time.js';
2
+ import { matchesQuery } from '../utils/search.js';
3
+ const NEYNAR_API = 'https://api.neynar.com/v2/farcaster';
4
+ function neynarCastToPost(cast) {
5
+ return {
6
+ id: cast.hash,
7
+ source: 'farcaster',
8
+ author: {
9
+ username: cast.author.username || `fid:${cast.author.fid}`,
10
+ displayName: cast.author.display_name,
11
+ profileUrl: `https://farcaster.xyz/${cast.author.username}`,
12
+ },
13
+ content: cast.text,
14
+ timestamp: new Date(cast.timestamp),
15
+ url: `https://farcaster.xyz/${cast.author.username}/${cast.hash.slice(0, 10)}`,
16
+ engagement: {
17
+ likes: cast.reactions?.likes_count || 0,
18
+ reposts: cast.reactions?.recasts_count || 0,
19
+ replies: cast.replies?.count || 0,
20
+ },
21
+ };
22
+ }
23
+ function mapTimeframeToWindow(timeframe) {
24
+ switch (timeframe) {
25
+ case '24h': return '24h';
26
+ case '48h': return '24h'; // closest available
27
+ case 'week': return '7d';
28
+ default: return '24h';
29
+ }
30
+ }
31
+ export async function fetchFarcaster(options) {
32
+ const apiKey = process.env.NEYNAR_API_KEY;
33
+ if (!apiKey) {
34
+ return {
35
+ posts: [],
36
+ source: 'farcaster',
37
+ error: 'Farcaster requires NEYNAR_API_KEY. Get one at https://neynar.com',
38
+ };
39
+ }
40
+ const limit = options.limit || 25;
41
+ const cutoff = getTimeframeCutoff(options.timeframe);
42
+ try {
43
+ let posts;
44
+ if (options.query) {
45
+ posts = await fetchNeynarSearch(apiKey, options.query, limit, options.channel);
46
+ }
47
+ else {
48
+ posts = await fetchNeynarTrending(apiKey, limit, options.timeframe, options.channel);
49
+ }
50
+ // Apply timeframe cutoff
51
+ posts = posts.filter(p => p.timestamp >= cutoff);
52
+ // Filter by query if provided (AND semantics for multi-word queries)
53
+ if (options.query) {
54
+ posts = posts.filter(p => matchesQuery(p.content, p.tags || [], options.query));
55
+ }
56
+ // Filter empty content
57
+ posts = posts.filter(p => p.content.trim().length > 0);
58
+ return {
59
+ posts: posts.slice(0, limit),
60
+ source: 'farcaster',
61
+ };
62
+ }
63
+ catch (error) {
64
+ const message = error instanceof Error ? error.message : 'Unknown error';
65
+ return {
66
+ posts: [],
67
+ source: 'farcaster',
68
+ error: `Farcaster: ${message}`,
69
+ };
70
+ }
71
+ }
72
+ async function fetchNeynarTrending(apiKey, limit, timeframe, channel) {
73
+ const timeWindow = mapTimeframeToWindow(timeframe);
74
+ const allPosts = [];
75
+ let cursor;
76
+ // Neynar trending limit is 1-10 per request, so paginate
77
+ while (allPosts.length < limit) {
78
+ const batchSize = Math.min(10, limit - allPosts.length);
79
+ const params = new URLSearchParams({
80
+ limit: String(batchSize),
81
+ time_window: timeWindow,
82
+ });
83
+ if (channel)
84
+ params.set('channel_id', channel);
85
+ if (cursor)
86
+ params.set('cursor', cursor);
87
+ const controller = new AbortController();
88
+ const timeout = setTimeout(() => controller.abort(), 10000);
89
+ try {
90
+ const response = await fetch(`${NEYNAR_API}/feed/trending?${params}`, {
91
+ signal: controller.signal,
92
+ headers: {
93
+ 'Accept': 'application/json',
94
+ 'x-api-key': apiKey,
95
+ },
96
+ });
97
+ clearTimeout(timeout);
98
+ if (!response.ok) {
99
+ throw new Error(`Neynar trending API: ${response.status}`);
100
+ }
101
+ const data = await response.json();
102
+ const casts = data.casts || [];
103
+ if (casts.length === 0)
104
+ break;
105
+ for (const cast of casts) {
106
+ allPosts.push(neynarCastToPost(cast));
107
+ }
108
+ cursor = data.next?.cursor;
109
+ if (!cursor)
110
+ break;
111
+ }
112
+ catch (error) {
113
+ clearTimeout(timeout);
114
+ throw error;
115
+ }
116
+ }
117
+ return allPosts;
118
+ }
119
+ async function fetchNeynarSearch(apiKey, query, limit, channel) {
120
+ const params = new URLSearchParams({
121
+ q: query,
122
+ limit: String(Math.min(limit, 100)),
123
+ });
124
+ if (channel)
125
+ params.set('channel_id', channel);
126
+ const controller = new AbortController();
127
+ const timeout = setTimeout(() => controller.abort(), 10000);
128
+ try {
129
+ const response = await fetch(`${NEYNAR_API}/cast/search?${params}`, {
130
+ signal: controller.signal,
131
+ headers: {
132
+ 'Accept': 'application/json',
133
+ 'x-api-key': apiKey,
134
+ },
135
+ });
136
+ clearTimeout(timeout);
137
+ if (!response.ok) {
138
+ throw new Error(`Neynar search API: ${response.status}`);
139
+ }
140
+ const data = await response.json();
141
+ const casts = data.result?.casts || [];
142
+ return casts.map(neynarCastToPost);
143
+ }
144
+ catch (error) {
145
+ clearTimeout(timeout);
146
+ throw error;
147
+ }
148
+ }
149
+ //# sourceMappingURL=farcaster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"farcaster.js","sourceRoot":"","sources":["../../src/fetchers/farcaster.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SearchOptions, FetchResult } from '../types.js';
2
+ export declare function fetchLens(options: SearchOptions): Promise<FetchResult>;
3
+ //# sourceMappingURL=lens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lens.d.ts","sourceRoot":"","sources":["../../src/fetchers/lens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiCpE,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAqD5E"}