@recursiv/sdk 0.7.51 → 0.7.53
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.
- package/README.md +43 -5
- package/dist/client.d.ts +6 -2
- package/dist/client.js +10 -9
- package/dist/index.d.ts +15 -4
- package/dist/index.js +11 -1
- package/dist/resources/admin.d.ts +13 -0
- package/dist/resources/agents.d.ts +13 -2
- package/dist/resources/agents.js +7 -1
- package/dist/resources/app-subscriptions.d.ts +48 -2
- package/dist/resources/app-subscriptions.js +18 -2
- package/dist/resources/bookmarks.d.ts +172 -0
- package/dist/resources/bookmarks.js +44 -0
- package/dist/resources/boosts.d.ts +23 -1
- package/dist/resources/boosts.js +6 -1
- package/dist/resources/communities.d.ts +12 -2
- package/dist/resources/communities.js +15 -1
- package/dist/resources/curator.d.ts +24 -6
- package/dist/resources/curator.js +2 -1
- package/dist/resources/discovery.d.ts +104 -2
- package/dist/resources/discovery.js +111 -20
- package/dist/resources/invite-codes-admin.d.ts +13 -0
- package/dist/resources/invite-codes.d.ts +10 -0
- package/dist/resources/invite-codes.js +6 -6
- package/dist/resources/link-preview.d.ts +11 -4
- package/dist/resources/link-preview.js +4 -3
- package/dist/resources/notifications.d.ts +26 -0
- package/dist/resources/posts.d.ts +45 -6
- package/dist/resources/posts.js +29 -4
- package/dist/resources/profiles.d.ts +23 -14
- package/dist/resources/profiles.js +18 -5
- package/dist/resources/protocols.d.ts +54 -7
- package/dist/resources/protocols.js +10 -4
- package/dist/resources/search.d.ts +159 -0
- package/dist/resources/search.js +21 -0
- package/dist/types.d.ts +146 -5
- package/llm.md +41 -8
- package/package.json +2 -2
|
@@ -8,6 +8,20 @@ const RADAR_MAX_MEMBERS = 12;
|
|
|
8
8
|
const RADAR_MAX_MINDS_READS = 4;
|
|
9
9
|
/** A discovery page holds its own deadline: a retried 5xx from one provider only makes the whole page wait. */
|
|
10
10
|
const NO_RETRY = { retry: false };
|
|
11
|
+
/**
|
|
12
|
+
* The provider no longer lists the topic (`trend_unavailable`): that topic's end, never a network
|
|
13
|
+
* failure. Matched on the server's code, which every SDK error carries: any other 404 on the route
|
|
14
|
+
* (`unsupported_protocol`, a missing route) is the network failing, and still fails the source.
|
|
15
|
+
*/
|
|
16
|
+
const isGone = (error) => typeof error === 'object' && error !== null && error.code === 'trend_unavailable';
|
|
17
|
+
/**
|
|
18
|
+
* Topic pages one read of a topics network walks past when each is gone or carries nothing in the
|
|
19
|
+
* category, so a page reaches a topic that carries posts. Live (2026-09-26, Bluesky, news) 21 of 25
|
|
20
|
+
* topics answered no posts in the category; one topic a read left the network out of most pages.
|
|
21
|
+
*/
|
|
22
|
+
const TOPIC_WALK = 5;
|
|
23
|
+
/** The walk starts no topic read after this long: well inside the page deadline, so it commits its progress rather than timing the network out. */
|
|
24
|
+
const TOPIC_WALK_MS = 8000;
|
|
11
25
|
const RECOMMENDATION_KINDS = ['groups', 'people', 'tags'];
|
|
12
26
|
const nonEmpty = (value) => typeof value === 'string' && value.trim().length > 0 && value.length <= 512;
|
|
13
27
|
function recommendationItem(kind, candidate) {
|
|
@@ -116,7 +130,7 @@ function windowLayout(layout, index) {
|
|
|
116
130
|
}
|
|
117
131
|
/** Per-viewer feed session. Create a new session when user, app, category or
|
|
118
132
|
* source changes. All reads retain existing server authorization/moderation.
|
|
119
|
-
* At most four
|
|
133
|
+
* At most four sources per page (a topics network's read walks up to TOPIC_WALK topic pages); providers fail independently. */
|
|
120
134
|
export class DiscoveryFeedSession {
|
|
121
135
|
posts;
|
|
122
136
|
protocols;
|
|
@@ -160,6 +174,9 @@ export class DiscoveryFeedSession {
|
|
|
160
174
|
return this.pending;
|
|
161
175
|
}
|
|
162
176
|
async read(stream) {
|
|
177
|
+
// `discovery` asks only for the layout judgment (and, for Minds posts, the public-only floor). The
|
|
178
|
+
// server's safety floor does not depend on it, so a reader that drops it to skip layout work (a clip
|
|
179
|
+
// pool) is screened exactly like this session.
|
|
163
180
|
const params = { limit: 25, ...(this.category ? { category: this.category } : {}), discovery: true };
|
|
164
181
|
if (stream.protocol === 'minds') {
|
|
165
182
|
// Discover leads with what is moving now: the REST hot order (engagement
|
|
@@ -178,23 +195,9 @@ export class DiscoveryFeedSession {
|
|
|
178
195
|
if (data.coverage !== 'provider_ranked_window' || data.items.length > 25 || data.items.some(post => post.protocol !== stream.protocol || post.author.protocol !== stream.protocol))
|
|
179
196
|
throw new Error('Invalid discovery source response');
|
|
180
197
|
};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
stream.done = true;
|
|
185
|
-
return { items: [], layout: ordinary, partial: false };
|
|
186
|
-
}
|
|
187
|
-
const { data } = await this.protocols.getPublicTrendFeed({ ...params, protocol: stream.protocol, topic: topic.topic, cursor: topic.cursor }, NO_RETRY);
|
|
188
|
-
validatePosts(data);
|
|
189
|
-
if (data.trend.protocol !== stream.protocol || data.trend.nativeId !== topic.topic)
|
|
190
|
-
throw new Error('Invalid discovery topic');
|
|
191
|
-
const next = data.cursor;
|
|
192
|
-
stream.topics.shift();
|
|
193
|
-
if (next && next !== topic.cursor && !topic.used.includes(next))
|
|
194
|
-
stream.topics.push({ topic: topic.topic, cursor: next, used: [...topic.used, ...(topic.cursor ? [topic.cursor] : [])].slice(-32) });
|
|
195
|
-
stream.done = !stream.topics.length;
|
|
196
|
-
return { items: data.items.map(post => ({ key: key(post.protocol, post.nativeId), kind: 'social', post })), layout: data.discovery ?? ordinary, partial: data.categoryFilter?.status === 'partial' };
|
|
197
|
-
}
|
|
198
|
+
const started = Date.now();
|
|
199
|
+
if (stream.topics)
|
|
200
|
+
return this.walkTopics(stream, params, validatePosts, false, started);
|
|
198
201
|
const { data } = await this.protocols.getPublicTrends({ ...params, protocol: stream.protocol, cursor: stream.cursor, includePreviews: true }, NO_RETRY);
|
|
199
202
|
if (data.coverage !== 'provider_ranked_window' || data.items.length > 25 || data.items.some(item => item.protocol !== stream.protocol))
|
|
200
203
|
throw new Error('Invalid discovery source response');
|
|
@@ -203,7 +206,14 @@ export class DiscoveryFeedSession {
|
|
|
203
206
|
throw new Error('Invalid discovery preview');
|
|
204
207
|
stream.topics = [...new Set(data.items.map(topic => topic.nativeId))].map(topic => ({ topic, used: [] }));
|
|
205
208
|
stream.done = !stream.topics.length;
|
|
206
|
-
|
|
209
|
+
const partial = data.categoryFilter?.status === 'partial';
|
|
210
|
+
// Outside a category the catalog's previews are this page. In a category a preview is not
|
|
211
|
+
// judged to be in it, so the catalog carries no card: the read walks into its topics now,
|
|
212
|
+
// not a round later, so the network's first page in a category is not empty by design.
|
|
213
|
+
const previews = data.items.flatMap(topic => !this.category && topic.preview ? [{ key: key(topic.preview.protocol, topic.preview.nativeId), kind: 'social', post: topic.preview }] : []);
|
|
214
|
+
if (previews.length || stream.done)
|
|
215
|
+
return { items: previews, layout: ordinary, partial };
|
|
216
|
+
return this.walkTopics(stream, params, validatePosts, partial, started);
|
|
207
217
|
}
|
|
208
218
|
validatePosts(data);
|
|
209
219
|
const next = data.cursor ?? undefined;
|
|
@@ -213,6 +223,42 @@ export class DiscoveryFeedSession {
|
|
|
213
223
|
stream.cursor = next;
|
|
214
224
|
return { items: data.items.map(post => ({ key: key(post.protocol, post.nativeId), kind: 'social', post })), layout: data.discovery ?? ordinary, partial: data.categoryFilter?.status === 'partial' };
|
|
215
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* One read of a topics network: the next topic page that carries posts. A topic the provider no
|
|
228
|
+
* longer lists (Bluesky rotates its trends between the catalog read and the walk through it) is
|
|
229
|
+
* that topic's end, not the network's failure; a topic page with nothing in the category is
|
|
230
|
+
* walked past too, and its continuation kept, so the page reaches a topic that carries posts
|
|
231
|
+
* instead of spending a round on each empty one. Bounded by TOPIC_WALK reads and TOPIC_WALK_MS;
|
|
232
|
+
* a walk that stops short has moved the stream on, and the next read continues from there.
|
|
233
|
+
*/
|
|
234
|
+
async walkTopics(stream, params, validatePosts, partial, started) {
|
|
235
|
+
const topics = stream.topics;
|
|
236
|
+
for (let reads = 0; reads < TOPIC_WALK && Date.now() - started < TOPIC_WALK_MS; reads++) {
|
|
237
|
+
const topic = topics[0];
|
|
238
|
+
if (!topic)
|
|
239
|
+
break;
|
|
240
|
+
const response = await this.protocols.getPublicTrendFeed({ ...params, protocol: stream.protocol, topic: topic.topic, cursor: topic.cursor }, NO_RETRY)
|
|
241
|
+
.catch((error) => { if (isGone(error))
|
|
242
|
+
return null; throw error; });
|
|
243
|
+
topics.shift();
|
|
244
|
+
if (!response)
|
|
245
|
+
continue;
|
|
246
|
+
const { data } = response;
|
|
247
|
+
validatePosts(data);
|
|
248
|
+
if (data.trend.protocol !== stream.protocol || data.trend.nativeId !== topic.topic)
|
|
249
|
+
throw new Error('Invalid discovery topic');
|
|
250
|
+
const next = data.cursor;
|
|
251
|
+
if (next && next !== topic.cursor && !topic.used.includes(next))
|
|
252
|
+
topics.push({ topic: topic.topic, cursor: next, used: [...topic.used, ...(topic.cursor ? [topic.cursor] : [])].slice(-32) });
|
|
253
|
+
partial ||= data.categoryFilter?.status === 'partial';
|
|
254
|
+
if (data.items.length) {
|
|
255
|
+
stream.done = !topics.length;
|
|
256
|
+
return { items: data.items.map(post => ({ key: key(post.protocol, post.nativeId), kind: 'social', post })), layout: data.discovery ?? ordinary, partial };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
stream.done = !topics.length;
|
|
260
|
+
return { items: [], layout: ordinary, partial };
|
|
261
|
+
}
|
|
216
262
|
async readPage() {
|
|
217
263
|
// The radar is one small snapshot read: start it with the page, not after the slowest provider,
|
|
218
264
|
// and start the lead story's member reads the moment the snapshot names it.
|
|
@@ -462,9 +508,54 @@ export class DiscoveryFeedSession {
|
|
|
462
508
|
export class DiscoveryResource {
|
|
463
509
|
posts;
|
|
464
510
|
protocols;
|
|
465
|
-
|
|
511
|
+
curator;
|
|
512
|
+
http;
|
|
513
|
+
constructor(posts, protocols, curator, http) {
|
|
466
514
|
this.posts = posts;
|
|
467
515
|
this.protocols = protocols;
|
|
516
|
+
this.curator = curator;
|
|
517
|
+
this.http = http;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Suggestions for the signed-in viewer from the app's one recommender: active or warm members, groups
|
|
521
|
+
* with several posters, tags used by several people, open-network accounts or stories, overall or in one
|
|
522
|
+
* browse category. What the viewer follows, joined, blocked or muted is left out, and so is anything
|
|
523
|
+
* resting for them. Call `recordImpressions` when suggestions are actually on screen and when one is tapped.
|
|
524
|
+
*/
|
|
525
|
+
recommendations(params, options) {
|
|
526
|
+
if (!this.http)
|
|
527
|
+
throw new Error('recommendations needs the HTTP client');
|
|
528
|
+
const { exclude, ...rest } = params;
|
|
529
|
+
return this.http.get('/discover/recommendations', { ...rest, exclude: exclude?.length ? exclude.join(',') : undefined }, options);
|
|
530
|
+
}
|
|
531
|
+
/** Records that suggestions were shown (at most once per half hour counts) or engaged with. */
|
|
532
|
+
recordImpressions(input) {
|
|
533
|
+
if (!this.http)
|
|
534
|
+
throw new Error('recordImpressions needs the HTTP client');
|
|
535
|
+
return this.http.post('/discover/impressions', input);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* A For you Discover feed: the member's For You ranking (asked for Discover's surface, with layouts and a
|
|
539
|
+
* snapshot cursor) in a session's Minds slot. With a category, the session also reads open-network posts
|
|
540
|
+
* from that category (the member's top one); without, it is Minds only. Never the radar. The page-0
|
|
541
|
+
* arrangement and when to use which are the app's choice.
|
|
542
|
+
*/
|
|
543
|
+
createPersonalFeed(options) {
|
|
544
|
+
const curator = this.curator;
|
|
545
|
+
if (!curator)
|
|
546
|
+
throw new Error('createPersonalFeed needs the curator resource');
|
|
547
|
+
const list = {
|
|
548
|
+
// The session's `sort` and `category` are Discover's hot-order knobs; the ranker ignores them.
|
|
549
|
+
list: async (params) => {
|
|
550
|
+
const response = await curator.forYou({ limit: params?.limit, offset: params?.offset, cursor: params?.cursor, discovery: true, surface: 'discover' });
|
|
551
|
+
// A server that predates the cursor says has_more: false on a partial ranked page although its
|
|
552
|
+
// continuation serves the next offset. A cursor-aware server always says `personalized`, and its
|
|
553
|
+
// has_more is the end of the feed.
|
|
554
|
+
const legacy = typeof response.meta.personalized !== 'boolean';
|
|
555
|
+
return { ...response, meta: { ...response.meta, has_more: response.meta.has_more || (legacy && response.data.length > 0) } };
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
return new DiscoveryFeedSession(list, this.protocols, options.category ? options.networks : [], options.category, options.category ? 'all' : 'minds', options.learning, options.recommend);
|
|
468
559
|
}
|
|
469
560
|
createFeed(options) {
|
|
470
561
|
// The radar rides along by default; an older server answers 404 and the session simply shows no story module.
|
|
@@ -18,12 +18,25 @@ export interface InviteCodeStats {
|
|
|
18
18
|
used: number;
|
|
19
19
|
expired: number;
|
|
20
20
|
revoked: number;
|
|
21
|
+
/** @deprecated Use `total`. */
|
|
22
|
+
total_codes: number;
|
|
23
|
+
/** @deprecated Use `active`. */
|
|
24
|
+
active_codes: number;
|
|
25
|
+
/** @deprecated Use `used`. */
|
|
26
|
+
used_codes: number;
|
|
27
|
+
/** @deprecated Use `expired`. */
|
|
28
|
+
expired_codes: number;
|
|
29
|
+
/** @deprecated Use `revoked`. */
|
|
30
|
+
revoked_codes: number;
|
|
21
31
|
};
|
|
22
32
|
waitlist: Record<string, number>;
|
|
23
33
|
current_wave: {
|
|
24
34
|
id: string;
|
|
25
35
|
wave: number;
|
|
26
36
|
name: string;
|
|
37
|
+
max_codes: number;
|
|
38
|
+
invites_per_user: number;
|
|
39
|
+
reward_tokens: number;
|
|
27
40
|
is_active: boolean;
|
|
28
41
|
} | null;
|
|
29
42
|
}
|
|
@@ -90,10 +90,20 @@ export interface BulkGenerateResult {
|
|
|
90
90
|
}
|
|
91
91
|
export interface InviteStats {
|
|
92
92
|
codes: {
|
|
93
|
+
total: number;
|
|
94
|
+
active: number;
|
|
95
|
+
used: number;
|
|
96
|
+
expired: number;
|
|
97
|
+
revoked: number;
|
|
98
|
+
/** @deprecated Use `total`. Retained for legacy admin clients. */
|
|
93
99
|
total_codes: number;
|
|
100
|
+
/** @deprecated Use `active`. Retained for legacy admin clients. */
|
|
94
101
|
active_codes: number;
|
|
102
|
+
/** @deprecated Use `used`. Retained for legacy admin clients. */
|
|
95
103
|
used_codes: number;
|
|
104
|
+
/** @deprecated Use `expired`. Retained for legacy admin clients. */
|
|
96
105
|
expired_codes: number;
|
|
106
|
+
/** @deprecated Use `revoked`. Retained for legacy admin clients. */
|
|
97
107
|
revoked_codes: number;
|
|
98
108
|
};
|
|
99
109
|
waitlist: Record<string, number>;
|
|
@@ -43,26 +43,26 @@ export class InviteCodesResource {
|
|
|
43
43
|
// ── Admin Methods ──
|
|
44
44
|
/** Admin: Initialize wave configs for the network */
|
|
45
45
|
initializeWaves() {
|
|
46
|
-
return this.client.post('/invite-codes
|
|
46
|
+
return this.client.post('/invite-codes-admin/waves/initialize');
|
|
47
47
|
}
|
|
48
48
|
/** Admin: Get all wave configs */
|
|
49
49
|
getWaves() {
|
|
50
|
-
return this.client.get('/invite-codes
|
|
50
|
+
return this.client.get('/invite-codes-admin/waves');
|
|
51
51
|
}
|
|
52
52
|
/** Admin: Activate a specific wave */
|
|
53
53
|
activateWave(wave) {
|
|
54
|
-
return this.client.post('/invite-codes
|
|
54
|
+
return this.client.post('/invite-codes-admin/waves/activate', { wave });
|
|
55
55
|
}
|
|
56
56
|
/** Admin: Bulk generate invite codes */
|
|
57
57
|
bulkGenerate(input) {
|
|
58
|
-
return this.client.post('/invite-codes
|
|
58
|
+
return this.client.post('/invite-codes-admin/bulk-generate', input);
|
|
59
59
|
}
|
|
60
60
|
/** Admin: Get invite system stats */
|
|
61
61
|
getStats() {
|
|
62
|
-
return this.client.get('/invite-codes
|
|
62
|
+
return this.client.get('/invite-codes-admin/stats');
|
|
63
63
|
}
|
|
64
64
|
/** Admin: Get waitlist entries */
|
|
65
65
|
getWaitlist(params) {
|
|
66
|
-
return this.client.get('/invite-codes
|
|
66
|
+
return this.client.get('/invite-codes-admin/waitlist', params);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -9,7 +9,13 @@ export interface LinkPreview {
|
|
|
9
9
|
siteName: string | null;
|
|
10
10
|
}
|
|
11
11
|
export interface LinkPreviewResponse {
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The preview, or null when the link cannot be previewed: the page refused a
|
|
14
|
+
* server (a private repository, a paywall or bot wall), is not HTML, or could
|
|
15
|
+
* not be reached. Show the plain link. Servers before this change answered
|
|
16
|
+
* 422 for these links instead, so treat that rejection the same way.
|
|
17
|
+
*/
|
|
18
|
+
data: LinkPreview | null;
|
|
13
19
|
cached?: boolean;
|
|
14
20
|
}
|
|
15
21
|
export declare class LinkPreviewResource {
|
|
@@ -17,9 +23,10 @@ export declare class LinkPreviewResource {
|
|
|
17
23
|
constructor(client: HttpClient);
|
|
18
24
|
/**
|
|
19
25
|
* Fetch Open Graph / Twitter card metadata for a public URL. Cached
|
|
20
|
-
* server-side so repeated lookups are cheap
|
|
21
|
-
*
|
|
22
|
-
* link previews.
|
|
26
|
+
* server-side so repeated lookups are cheap, including links that have no
|
|
27
|
+
* preview (`data: null`). No auth required — the endpoint is intentionally
|
|
28
|
+
* public so unauthenticated UIs can render link previews. A URL that is not
|
|
29
|
+
* http(s), or points at a private or internal address, still rejects (422).
|
|
23
30
|
*/
|
|
24
31
|
get(url: string): Promise<LinkPreviewResponse>;
|
|
25
32
|
}
|
|
@@ -5,9 +5,10 @@ export class LinkPreviewResource {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Fetch Open Graph / Twitter card metadata for a public URL. Cached
|
|
8
|
-
* server-side so repeated lookups are cheap
|
|
9
|
-
*
|
|
10
|
-
* link previews.
|
|
8
|
+
* server-side so repeated lookups are cheap, including links that have no
|
|
9
|
+
* preview (`data: null`). No auth required — the endpoint is intentionally
|
|
10
|
+
* public so unauthenticated UIs can render link previews. A URL that is not
|
|
11
|
+
* http(s), or points at a private or internal address, still rejects (422).
|
|
11
12
|
*/
|
|
12
13
|
get(url) {
|
|
13
14
|
return this.client.get('/link-preview', { url });
|
|
@@ -7,6 +7,12 @@ export interface NotificationActor {
|
|
|
7
7
|
username: string | null;
|
|
8
8
|
image: string | null;
|
|
9
9
|
is_ai: boolean | null;
|
|
10
|
+
/**
|
|
11
|
+
* Follow rows only: whether the viewer already follows this person, so
|
|
12
|
+
* Follow back can render without reading the follow set. Absent on other
|
|
13
|
+
* rows and from servers older than this field.
|
|
14
|
+
*/
|
|
15
|
+
viewer_follows?: boolean;
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
12
18
|
* The post a notification references, as it reads NOW, with the viewer's own
|
|
@@ -15,7 +21,18 @@ export interface NotificationActor {
|
|
|
15
21
|
*/
|
|
16
22
|
export interface NotificationPostPreview {
|
|
17
23
|
id: string;
|
|
24
|
+
/**
|
|
25
|
+
* The post's words as plain text (no Markdown or HTML marks; a link reads as
|
|
26
|
+
* its label and a bare URL as its site), up to 140 characters. A longer post
|
|
27
|
+
* is cut at the last whole word and ends in "…". Show it as it is.
|
|
28
|
+
*/
|
|
18
29
|
excerpt: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* True when `excerpt` is a cut of a longer post. Use this rather than the
|
|
32
|
+
* excerpt's length to decide whether there is more to read. Absent from
|
|
33
|
+
* servers older than this field.
|
|
34
|
+
*/
|
|
35
|
+
excerpt_truncated?: boolean;
|
|
19
36
|
media_url: string | null;
|
|
20
37
|
media_type: string | null;
|
|
21
38
|
reactions_count?: number;
|
|
@@ -24,7 +41,10 @@ export interface NotificationPostPreview {
|
|
|
24
41
|
downvote_count?: number;
|
|
25
42
|
/** The viewer's own reaction on this post ('upvote', 'downvote', ...), if any. */
|
|
26
43
|
user_reaction?: string | null;
|
|
44
|
+
/** Stored replies a reader can open. */
|
|
27
45
|
reply_count?: number;
|
|
46
|
+
/** A legacy import's comment count; none of those comments are stored as replies. */
|
|
47
|
+
reply_count_external?: number;
|
|
28
48
|
unique_commenters?: number;
|
|
29
49
|
reposts_count?: number;
|
|
30
50
|
/** Id of the viewer's own repost of this post; delete it to undo the repost. */
|
|
@@ -43,6 +63,12 @@ export interface Notification {
|
|
|
43
63
|
read_at: string | null;
|
|
44
64
|
/** For `post_reaction` rows: which reaction ('upvote', 'downvote', ...). Null otherwise or on older rows. */
|
|
45
65
|
reaction_type?: string | null;
|
|
66
|
+
/**
|
|
67
|
+
* For `agent_needs_input` rows: whether you have written in the agent's
|
|
68
|
+
* thread since it asked, which is what answers an ask. The title then reads
|
|
69
|
+
* "You answered <agent>". Null on other rows; absent from older servers.
|
|
70
|
+
*/
|
|
71
|
+
answered?: boolean | null;
|
|
46
72
|
actor?: NotificationActor | null;
|
|
47
73
|
post_preview?: NotificationPostPreview | null;
|
|
48
74
|
}
|
|
@@ -1,17 +1,52 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
-
import type { PaginatedResponse, SingleResponse, DeleteResponse, Post, PostDetail, CreatePostInput, CreatedPost, UpdatePostInput, ListPostsParams, ListPostRepliesParams, PostRepliesPage, SearchPostsParams, ReactionType } from '../types.js';
|
|
2
|
+
import type { PaginatedResponse, SingleResponse, DeleteResponse, Post, PostDetail, CreatePostInput, CreatedPost, UpdatePostInput, ListPostsParams, ListPostRepliesParams, PostRepliesPage, SearchPostsParams, SearchPostsResponse, ReactionType } from '../types.js';
|
|
3
|
+
import type { BookmarkedPost, BookmarksPage, SavedBookmark, UnsavedBookmark } from './bookmarks.js';
|
|
4
|
+
/** How GET /posts/categories ordered its answer: the member's own order, the app's popularity order, or the fixed order. */
|
|
5
|
+
export type CategoryOrder = 'personal' | 'popular' | 'static';
|
|
6
|
+
/** Why an answer is not `personal`. */
|
|
7
|
+
export type CategoryOrderReason = 'low_signal' | 'deadline' | 'requested' | 'experiment_off';
|
|
8
|
+
export interface CategoryOrderMeta {
|
|
9
|
+
order: CategoryOrder;
|
|
10
|
+
/** 16 hex; equal ids mean the same order. */
|
|
11
|
+
order_id: string;
|
|
12
|
+
as_of: string;
|
|
13
|
+
reason?: CategoryOrderReason;
|
|
14
|
+
/** Whether For You can personalize for this member yet; absent when the member's order was not read. */
|
|
15
|
+
personalized?: boolean;
|
|
16
|
+
}
|
|
3
17
|
export declare class PostsResource {
|
|
4
18
|
private client;
|
|
5
19
|
constructor(client: HttpClient);
|
|
6
20
|
/** List posts with optional filters */
|
|
7
21
|
list(params?: ListPostsParams): Promise<PaginatedResponse<Post>>;
|
|
8
|
-
/**
|
|
9
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Broad categories currently enabled for public posts in this app, in the order this member should see
|
|
24
|
+
* them: their own order once the app serves personal orders, otherwise the app's popularity order. The
|
|
25
|
+
* order is private to the member. `order` asks for a specific one (default `personal`).
|
|
26
|
+
*/
|
|
27
|
+
categories(params?: {
|
|
28
|
+
order?: CategoryOrder;
|
|
29
|
+
}): Promise<SingleResponse<Array<{
|
|
10
30
|
id: string;
|
|
11
31
|
name: string;
|
|
12
|
-
}
|
|
32
|
+
}>> & {
|
|
33
|
+
meta?: CategoryOrderMeta;
|
|
34
|
+
}>;
|
|
13
35
|
/** List posts the authenticated caller has upvoted. */
|
|
14
36
|
liked(params?: Pick<ListPostsParams, 'limit' | 'offset' | 'cursor'>): Promise<PaginatedResponse<Post>>;
|
|
37
|
+
/**
|
|
38
|
+
* The caller's saved posts in this app, newest saved first, each with `bookmarked_at`. Page on
|
|
39
|
+
* `meta.next_cursor` (a page can be short while it skips posts the member can no longer read).
|
|
40
|
+
* Same as `bookmarks.posts`; 503 `bookmarks_unavailable` where synced bookmarks are not available.
|
|
41
|
+
*/
|
|
42
|
+
bookmarked(params?: {
|
|
43
|
+
limit?: number;
|
|
44
|
+
cursor?: string;
|
|
45
|
+
}): Promise<BookmarksPage<BookmarkedPost>>;
|
|
46
|
+
/** Save a post to the caller's synced bookmarks (idempotent). A bare repost is saved as its original: see `post_id`. */
|
|
47
|
+
bookmark(postId: string): Promise<SingleResponse<SavedBookmark>>;
|
|
48
|
+
/** Remove a post from the caller's synced bookmarks (idempotent). Pass the `post_id` its save returned. */
|
|
49
|
+
unbookmark(postId: string): Promise<SingleResponse<UnsavedBookmark>>;
|
|
15
50
|
/** Posts similar to this one (embedding neighbours), in the app's scope. */
|
|
16
51
|
similar(id: string, params?: {
|
|
17
52
|
limit?: number;
|
|
@@ -27,8 +62,12 @@ export declare class PostsResource {
|
|
|
27
62
|
update(id: string, input: UpdatePostInput): Promise<SingleResponse<CreatedPost>>;
|
|
28
63
|
/** Delete a post */
|
|
29
64
|
delete(id: string): Promise<DeleteResponse>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Search posts by text query. Relevance (the default) ranks relevance x recency x engagement through
|
|
67
|
+
* the promotion safety floor; `sort: 'new'` lists newest first. Page with `meta.next_cursor` as
|
|
68
|
+
* `cursor`; the first relevance page carries `latest`, the last page `fall_over` (SearchPostsResponse).
|
|
69
|
+
*/
|
|
70
|
+
search(params: SearchPostsParams): Promise<SearchPostsResponse>;
|
|
32
71
|
/** Add or update a reaction on a post */
|
|
33
72
|
react(postId: string, type: ReactionType): Promise<SingleResponse<{
|
|
34
73
|
success: true;
|
package/dist/resources/posts.js
CHANGED
|
@@ -7,14 +7,35 @@ export class PostsResource {
|
|
|
7
7
|
list(params) {
|
|
8
8
|
return this.client.get('/posts', params);
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Broad categories currently enabled for public posts in this app, in the order this member should see
|
|
12
|
+
* them: their own order once the app serves personal orders, otherwise the app's popularity order. The
|
|
13
|
+
* order is private to the member. `order` asks for a specific one (default `personal`).
|
|
14
|
+
*/
|
|
15
|
+
categories(params) {
|
|
16
|
+
// No params, no query: the same request older SDKs make.
|
|
17
|
+
return params ? this.client.get('/posts/categories', params) : this.client.get('/posts/categories');
|
|
13
18
|
}
|
|
14
19
|
/** List posts the authenticated caller has upvoted. */
|
|
15
20
|
liked(params) {
|
|
16
21
|
return this.list({ ...params, liked: true });
|
|
17
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* The caller's saved posts in this app, newest saved first, each with `bookmarked_at`. Page on
|
|
25
|
+
* `meta.next_cursor` (a page can be short while it skips posts the member can no longer read).
|
|
26
|
+
* Same as `bookmarks.posts`; 503 `bookmarks_unavailable` where synced bookmarks are not available.
|
|
27
|
+
*/
|
|
28
|
+
bookmarked(params) {
|
|
29
|
+
return this.client.get('/posts', { bookmarked: true, ...params });
|
|
30
|
+
}
|
|
31
|
+
/** Save a post to the caller's synced bookmarks (idempotent). A bare repost is saved as its original: see `post_id`. */
|
|
32
|
+
bookmark(postId) {
|
|
33
|
+
return this.client.put('/bookmarks', { kind: 'post', post_id: postId });
|
|
34
|
+
}
|
|
35
|
+
/** Remove a post from the caller's synced bookmarks (idempotent). Pass the `post_id` its save returned. */
|
|
36
|
+
unbookmark(postId) {
|
|
37
|
+
return this.client.delete(`/bookmarks/${encodeURIComponent(`post:${postId}`)}`);
|
|
38
|
+
}
|
|
18
39
|
/** Posts similar to this one (embedding neighbours), in the app's scope. */
|
|
19
40
|
similar(id, params) {
|
|
20
41
|
return this.client.get(`/posts/${encodeURIComponent(id)}/similar`, params);
|
|
@@ -39,7 +60,11 @@ export class PostsResource {
|
|
|
39
60
|
delete(id) {
|
|
40
61
|
return this.client.delete(`/posts/${id}`);
|
|
41
62
|
}
|
|
42
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Search posts by text query. Relevance (the default) ranks relevance x recency x engagement through
|
|
65
|
+
* the promotion safety floor; `sort: 'new'` lists newest first. Page with `meta.next_cursor` as
|
|
66
|
+
* `cursor`; the first relevance page carries `latest`, the last page `fall_over` (SearchPostsResponse).
|
|
67
|
+
*/
|
|
43
68
|
search(params) {
|
|
44
69
|
return this.client.get('/posts/search', params);
|
|
45
70
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
-
import type { PaginatedResponse, SingleResponse, SuccessResponse, Profile, ProfileDetail, ProfileMe, LeaderboardEntry, UpdateProfileInput, SearchProfilesParams, SearchMentionsParams, MentionCandidate, FollowListProfile, BlockedProfile, MutedProfile, ModerationListParams, ViewerRelationship } from '../types.js';
|
|
2
|
+
import type { PaginatedResponse, SingleResponse, SuccessResponse, Profile, ProfileDetail, ProfileMe, LeaderboardEntry, UpdateProfileInput, SearchProfilesParams, ProfileSearchResult, SearchMentionsParams, MentionCandidate, FollowListParams, FollowListProfile, BlockedProfile, MutedProfile, ModerationListParams, ViewerRelationship } from '../types.js';
|
|
3
3
|
export declare class ProfilesResource {
|
|
4
4
|
private client;
|
|
5
5
|
constructor(client: HttpClient);
|
|
@@ -15,14 +15,25 @@ export declare class ProfilesResource {
|
|
|
15
15
|
/** Get a user by username */
|
|
16
16
|
getByUsername(username: string): Promise<SingleResponse<ProfileDetail>>;
|
|
17
17
|
/** Search users by name or username */
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Search accounts by name or handle, ranked by match x ln(1 + followers + min(posts, 100)) (doubled for an
|
|
20
|
+
* official account): empties last and marked `empty`, the official ones `verified`. Every page is
|
|
21
|
+
* a slice of one ranked pool of 200; past it `has_more` is false.
|
|
22
|
+
*/
|
|
23
|
+
search(params: SearchProfilesParams): Promise<PaginatedResponse<ProfileSearchResult>>;
|
|
19
24
|
/**
|
|
20
25
|
* Accounts to @mention: people and agents on the caller's network, the
|
|
21
26
|
* caller's follows first. Unlike `search`, this includes agents the app may
|
|
22
27
|
* discover (an agent answers when mentioned); another member's personal AI
|
|
23
28
|
* is never offered.
|
|
29
|
+
*
|
|
30
|
+
* A search that runs over its 2 s budget answers 503 with Retry-After. A
|
|
31
|
+
* type-ahead has its own deadline, so pass `{ retry: false }` to get that
|
|
32
|
+
* answer at once instead of waiting through retries.
|
|
24
33
|
*/
|
|
25
|
-
mentions(params: SearchMentionsParams
|
|
34
|
+
mentions(params: SearchMentionsParams, options?: {
|
|
35
|
+
retry?: boolean;
|
|
36
|
+
}): Promise<PaginatedResponse<MentionCandidate>>;
|
|
26
37
|
/** Update own profile */
|
|
27
38
|
update(input: UpdateProfileInput): Promise<SingleResponse<Profile>>;
|
|
28
39
|
/** Follow a user */
|
|
@@ -30,18 +41,16 @@ export declare class ProfilesResource {
|
|
|
30
41
|
/** Unfollow a user */
|
|
31
42
|
unfollow(userId: string): Promise<SuccessResponse>;
|
|
32
43
|
/**
|
|
33
|
-
* List a user's followers
|
|
34
|
-
*
|
|
44
|
+
* List a user's followers, newest follow first once the server's database
|
|
45
|
+
* has the follow-list order indexes (until then, in the order it reads
|
|
46
|
+
* them). Each row carries the caller's relationship with it (`is_following`,
|
|
47
|
+
* `follows_you`), so a Follow button needs no second read. `search` narrows
|
|
48
|
+
* the whole list to accounts whose name or handle contains it (at most 200
|
|
49
|
+
* characters); page the matches with `limit` and `offset`.
|
|
35
50
|
*/
|
|
36
|
-
followers(userId: string, params?:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}): Promise<PaginatedResponse<FollowListProfile>>;
|
|
40
|
-
/** List who a user follows, each row with the caller's relationship with it. */
|
|
41
|
-
following(userId: string, params?: {
|
|
42
|
-
limit?: number;
|
|
43
|
-
offset?: number;
|
|
44
|
-
}): Promise<PaginatedResponse<FollowListProfile>>;
|
|
51
|
+
followers(userId: string, params?: FollowListParams): Promise<PaginatedResponse<FollowListProfile>>;
|
|
52
|
+
/** List who a user follows, in the order `followers` uses, each row with the caller's relationship with it; `search` as on `followers`. */
|
|
53
|
+
following(userId: string, params?: FollowListParams): Promise<PaginatedResponse<FollowListProfile>>;
|
|
45
54
|
/**
|
|
46
55
|
* The caller's whole follow set as ids, up to 5000 per page (default 1000).
|
|
47
56
|
* Answers what `isFollowing` answers, for every account at once. Pass
|
|
@@ -20,6 +20,11 @@ export class ProfilesResource {
|
|
|
20
20
|
return this.client.get(`/profiles/by-username/${username}`);
|
|
21
21
|
}
|
|
22
22
|
/** Search users by name or username */
|
|
23
|
+
/**
|
|
24
|
+
* Search accounts by name or handle, ranked by match x ln(1 + followers + min(posts, 100)) (doubled for an
|
|
25
|
+
* official account): empties last and marked `empty`, the official ones `verified`. Every page is
|
|
26
|
+
* a slice of one ranked pool of 200; past it `has_more` is false.
|
|
27
|
+
*/
|
|
23
28
|
search(params) {
|
|
24
29
|
return this.client.get('/profiles/search', params);
|
|
25
30
|
}
|
|
@@ -28,9 +33,13 @@ export class ProfilesResource {
|
|
|
28
33
|
* caller's follows first. Unlike `search`, this includes agents the app may
|
|
29
34
|
* discover (an agent answers when mentioned); another member's personal AI
|
|
30
35
|
* is never offered.
|
|
36
|
+
*
|
|
37
|
+
* A search that runs over its 2 s budget answers 503 with Retry-After. A
|
|
38
|
+
* type-ahead has its own deadline, so pass `{ retry: false }` to get that
|
|
39
|
+
* answer at once instead of waiting through retries.
|
|
31
40
|
*/
|
|
32
|
-
mentions(params) {
|
|
33
|
-
return this.client.get('/profiles/mentions', params);
|
|
41
|
+
mentions(params, options) {
|
|
42
|
+
return this.client.get('/profiles/mentions', params, options);
|
|
34
43
|
}
|
|
35
44
|
/** Update own profile */
|
|
36
45
|
update(input) {
|
|
@@ -45,13 +54,17 @@ export class ProfilesResource {
|
|
|
45
54
|
return this.client.delete(`/profiles/${userId}/follow`);
|
|
46
55
|
}
|
|
47
56
|
/**
|
|
48
|
-
* List a user's followers
|
|
49
|
-
*
|
|
57
|
+
* List a user's followers, newest follow first once the server's database
|
|
58
|
+
* has the follow-list order indexes (until then, in the order it reads
|
|
59
|
+
* them). Each row carries the caller's relationship with it (`is_following`,
|
|
60
|
+
* `follows_you`), so a Follow button needs no second read. `search` narrows
|
|
61
|
+
* the whole list to accounts whose name or handle contains it (at most 200
|
|
62
|
+
* characters); page the matches with `limit` and `offset`.
|
|
50
63
|
*/
|
|
51
64
|
followers(userId, params) {
|
|
52
65
|
return this.client.get(`/profiles/${userId}/followers`, params);
|
|
53
66
|
}
|
|
54
|
-
/** List who a user follows, each row with the caller's relationship with it
|
|
67
|
+
/** List who a user follows, in the order `followers` uses, each row with the caller's relationship with it; `search` as on `followers`. */
|
|
55
68
|
following(userId, params) {
|
|
56
69
|
return this.client.get(`/profiles/${userId}/following`, params);
|
|
57
70
|
}
|