@recursiv/sdk 0.7.58 → 0.7.62

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.
@@ -1,5 +1,5 @@
1
1
  import type { HttpClient } from '../client.js';
2
- import type { DiscoveryLayout, PaginatedResponse, PaginationParams, SingleResponse } from '../types.js';
2
+ import type { CompassFilterMeta, DiscoveryLayout, PaginatedResponse, PaginationParams, SingleResponse } from '../types.js';
3
3
  /** Canonical protocol operation names returned by the adapter registry. */
4
4
  export declare const PROTOCOL_CAPABILITY_OPERATIONS: readonly ["discover", "stream", "backfill", "search", "resolveIdentity", "fetchProfile", "fetchThread", "publish", "reply", "react", "repost", "follow", "delete", "health", "checkpoint"];
5
5
  export declare const PROTOCOL_CAPABILITY_STATUSES: readonly ["supported", "planned", "unsupported"];
@@ -906,7 +906,8 @@ export interface SocialNetwork {
906
906
  url: string;
907
907
  };
908
908
  coverage: string;
909
- capabilities: Array<'profile_search' | 'profile_read' | 'original_author_posts' | 'minds_subscription' | 'following_feed' | 'conversation_read' | 'public_trends' | 'trend_feed' | 'popular_accounts' | 'post_search'>;
909
+ /** `compass_filter`: the Politics filter is served to this viewer (an app offers it only then); listed on every network. */
910
+ capabilities: Array<'profile_search' | 'profile_read' | 'original_author_posts' | 'minds_subscription' | 'following_feed' | 'conversation_read' | 'public_trends' | 'trend_feed' | 'popular_accounts' | 'post_search' | 'compass_filter'>;
910
911
  profileSearchPagination: boolean;
911
912
  postSearchAccountRequired?: boolean;
912
913
  /** Categories available from the current app configuration. */
@@ -930,8 +931,22 @@ export interface SocialProfile {
930
931
  postsCount: number | null;
931
932
  /** Nostr only: the NIP-05 name its domain has confirmed for this key ("lyn@primal.net"); `handle` stays the key. */
932
933
  nip05?: string;
934
+ /** A bridge (Bridgy Fed, Mostr) made this account from one on another network (SocialBridge). Present only on a bridged account. */
935
+ bridge?: SocialBridge;
933
936
  subscription: SocialSubscription | null;
934
937
  }
938
+ /**
939
+ * A post or account a bridge copied from another network: Bridgy Fed (`*.brid.gy`), Mostr (`mostr.pub`), or a Nostr
940
+ * event's NIP-48 `proxy` tag naming no bridge the server knows (`proxy`). `originId` names the original, normalised (a
941
+ * Bluesky at-uri or DID, an ActivityPub object URL or `user@host`, a Nostr event id or key, a web URL), when the bridge
942
+ * says. Where the server reads both, feeds, search and the radar already fold a copy into its original; an app that
943
+ * merges several networks' pages itself can fold the same way: a copy whose original is shown is the same post.
944
+ */
945
+ export interface SocialBridge {
946
+ via: 'bridgy_fed' | 'mostr' | 'proxy';
947
+ protocol: 'bluesky' | 'activitypub' | 'nostr' | 'web';
948
+ originId?: string;
949
+ }
935
950
  export interface SocialSubscription {
936
951
  id: string;
937
952
  protocol: string;
@@ -1028,6 +1043,8 @@ export interface SocialPost {
1028
1043
  * to signed-out visitors.
1029
1044
  */
1030
1045
  authorOptOut?: true;
1046
+ /** A bridge (Bridgy Fed, Mostr, a NIP-48 proxy tag) made this post from one on another network (SocialBridge). Present only on a copy read live. */
1047
+ bridge?: SocialBridge;
1031
1048
  }
1032
1049
  export interface SocialPostSearch {
1033
1050
  items: SocialPost[];
@@ -1103,7 +1120,12 @@ export type SocialTrends = SocialTrendProvenance & ({
1103
1120
  safetyFilter?: {
1104
1121
  withheld: number;
1105
1122
  };
1106
- });
1123
+ }) & {
1124
+ /** Present when `getPublicTrends` named a filtering `compass`: what Hide politics did to this page (counts only). */
1125
+ meta?: {
1126
+ compass_filter?: CompassFilterMeta;
1127
+ };
1128
+ };
1107
1129
  export interface SocialTrendFeed extends SocialTrendProvenance {
1108
1130
  trend: SocialTrend;
1109
1131
  items: SocialPost[];
@@ -1229,10 +1251,20 @@ export interface SocialRadar {
1229
1251
  selected: string;
1230
1252
  status: 'complete' | 'partial';
1231
1253
  };
1254
+ /** Present when the read named a filtering `compass`: what Hide politics did to the stories listed (counts only). */
1255
+ meta?: {
1256
+ compass_filter?: CompassFilterMeta;
1257
+ };
1232
1258
  }
1233
1259
  export interface SocialRadarParams {
1234
1260
  category?: string;
1235
1261
  limit?: number;
1262
+ /**
1263
+ * A Politics choice. Hide politics (`hide`) leaves out the stories whose theme is politics, and the ones not themed yet
1264
+ * or themed `mixed` (a story has no compass placement, so it fails closed); the stories after them fill the list.
1265
+ * A side or Mix is answered unfiltered, marked `unavailable`; the keyless `getSharedRadar` takes only `all` or `hide`.
1266
+ */
1267
+ compass?: string;
1236
1268
  }
1237
1269
  /** One radar story's social member posts, in the cluster's member order. Empty once the story leaves the snapshot. */
1238
1270
  export interface SocialRadarPosts {
@@ -1290,6 +1322,182 @@ export interface SocialRadarStory {
1290
1322
  */
1291
1323
  editionStory?: string;
1292
1324
  }
1325
+ /**
1326
+ * The Viral read (`getPublicViral`, `getSharedViral`): what is moving fastest on Bluesky, the Fediverse, Nostr and
1327
+ * Minds right now, each measured against its own network's pace, from a snapshot the server's rank tick rebuilds about
1328
+ * every five minutes. A read never reaches a provider or the model. These types mirror the server's
1329
+ * ViralSnapshotContract; a contract test compares them key by key.
1330
+ */
1331
+ export type ViralNetwork = 'all' | 'minds' | 'bluesky' | 'activitypub' | 'nostr';
1332
+ export type ViralProtocol = 'bluesky' | 'activitypub' | 'nostr' | 'minds';
1333
+ /** The networks whose posts the read serves as a `SocialPost`. */
1334
+ export type ViralOpenProtocol = 'bluesky' | 'activitypub' | 'nostr';
1335
+ /**
1336
+ * `viral`: a measured rate well above its network's baseline (z of 1 or more) with two or more kinds of engagement;
1337
+ * `rising`: a measured rate speeding up (accel of 0.7 or more) on a post under six hours old; `hot`: everything else,
1338
+ * including every post without a measured rate.
1339
+ */
1340
+ export type ViralLabel = 'viral' | 'rising' | 'hot';
1341
+ /**
1342
+ * `ok`: a build served. `building`: no build yet. `screening`: served, but fewer than 30% of the top 200 posts have
1343
+ * a safety verdict yet. `off`: the read is not enabled for this app (or, on `getSharedViral` only, the API predates it):
1344
+ * no items, and nothing to retry until it is turned on.
1345
+ */
1346
+ export type ViralReadStatus = 'ok' | 'building' | 'screening' | 'off';
1347
+ /** Every parameter is optional. The server rejects any other parameter with a 400, so only these travel. */
1348
+ export interface ViralReadParams {
1349
+ /** Default `all`: every network, with a per-network quota so no one network fills the page. */
1350
+ network?: ViralNetwork;
1351
+ /** A Discover category id: only posts judged into it. */
1352
+ category?: string;
1353
+ /**
1354
+ * `video`: only posts whose own media includes a video. A member's read serves every such video. A visitor's read
1355
+ * (`getSharedViral`) still serves a post's media only where the server cleared it, so some items can arrive with
1356
+ * the video withheld (the card's `media` empty): keep `items.filter(viralItemHasVideo)` for a video-only player.
1357
+ */
1358
+ media?: 'video';
1359
+ /** Page size, default 20. */
1360
+ limit?: 5 | 10 | 20 | 40;
1361
+ /** The previous page's `cursor`. A cursor names its build; one whose build expired (30 minutes) ends the list. */
1362
+ cursor?: string;
1363
+ /** A Politics choice. A visitor (`getSharedViral`) may send only `all` or `hide`; anything else is a 400. */
1364
+ compass?: string;
1365
+ }
1366
+ /** How fast a post is gathering reactions, reposts and replies (per hour), against its network's baseline. */
1367
+ export interface ViralItemVelocity {
1368
+ perHour1h: number;
1369
+ perHour6h: number;
1370
+ /** The 1-hour rate against its network's trailing baseline, from -2 to 4 (1 or more is well above). */
1371
+ z: number;
1372
+ /** ln((1 + perHour1h) / (1 + perHour6h)), from -1 to 2: above 0 is speeding up. */
1373
+ accel: number;
1374
+ /** Readings behind the rate; null for a Minds post. */
1375
+ samples: number | null;
1376
+ spanHours: number;
1377
+ /** The rate comes from two or more readings over time (a Minds post's always does). An unmeasured post is never viral or rising. */
1378
+ measured: boolean;
1379
+ /** Who counted: Bluesky's AppView, one Fediverse server, a third party's report (Primal, for Nostr), or Minds itself. */
1380
+ countsOrigin: 'appview' | 'instance_local' | 'provider_reported' | 'minds';
1381
+ /** Minutes since the counts were last read, when known. */
1382
+ countsAgeMin?: number;
1383
+ /** Present when a third party reported the counts (`Primal`): show them as reported by it, never as verified. */
1384
+ countsReportedBy?: string;
1385
+ }
1386
+ /** Another post in the same spread group: the same link posted on another network. */
1387
+ export interface ViralRef {
1388
+ protocol: ViralProtocol;
1389
+ nativeId: string;
1390
+ }
1391
+ export interface ViralItemSpread {
1392
+ /** The networks sharing the post's link (its own alone when no other network carries it). */
1393
+ protocols: ViralProtocol[];
1394
+ /** Fediverse: how many servers' trends lists carried the post. */
1395
+ instances?: number;
1396
+ clusterId: null;
1397
+ alsoOn: ViralRef[];
1398
+ }
1399
+ /** A Minds post's card as the read serves it; open it at /post/:id. A visitor's carries media only where the server cleared it. */
1400
+ export interface ViralMindsCard {
1401
+ id: string;
1402
+ title: string | null;
1403
+ text: string;
1404
+ createdAt: string;
1405
+ author: {
1406
+ name: string | null;
1407
+ username: string | null;
1408
+ } | null;
1409
+ media: Array<{
1410
+ type: string;
1411
+ url: string;
1412
+ width: number | null;
1413
+ height: number | null;
1414
+ thumbnailUrl: string | null;
1415
+ hlsUrl: string | null;
1416
+ }>;
1417
+ likeCount: number;
1418
+ repostCount: number;
1419
+ replyCount: number;
1420
+ }
1421
+ /** What every ranked post carries, whichever network it comes from. */
1422
+ export interface ViralItemFields {
1423
+ /** Stable across builds: use it to drop a post already shown. */
1424
+ key: string;
1425
+ label: ViralLabel;
1426
+ score: number;
1427
+ /** 1-based position in this read's ordering, across pages. */
1428
+ rank: number;
1429
+ category: string | null;
1430
+ velocity: ViralItemVelocity;
1431
+ spread: ViralItemSpread;
1432
+ }
1433
+ /** A ranked Minds post; open it at /post/:id. */
1434
+ export interface ViralMindsItem extends ViralItemFields {
1435
+ protocol: 'minds';
1436
+ post: ViralMindsCard;
1437
+ }
1438
+ /** A ranked Bluesky, Fediverse or Nostr post. */
1439
+ export interface ViralSocialItem extends ViralItemFields {
1440
+ protocol: ViralOpenProtocol;
1441
+ post: SocialPost;
1442
+ }
1443
+ /**
1444
+ * One ranked post: check `protocol` and TypeScript narrows `post` (a `ViralMindsCard` for `minds`, a `SocialPost`
1445
+ * otherwise). No item carries a compass value, an author id, a safety flag or its media kind. A visitor's card keeps
1446
+ * its own images and video only where the server cleared them; a withheld one has empty `media` (a `SocialPost` also
1447
+ * has `hasAdditionalContent: true`). Even under `media: 'video'` a visitor's item can arrive without its video, so
1448
+ * test each one with `viralItemHasVideo` before a video player.
1449
+ */
1450
+ export type ViralItem = ViralMindsItem | ViralSocialItem;
1451
+ /**
1452
+ * Whether the item's own card carries a playable video. Use it on a `media: 'video'` read before a Watch-style
1453
+ * player: a visitor's read can serve an item whose video the server withheld (its card's `media` is then empty).
1454
+ * A video only in a quoted post does not count, as the server never counts a quote's media.
1455
+ */
1456
+ export declare function viralItemHasVideo(item: ViralItem): boolean;
1457
+ /** A network's trailing pace that velocity is measured against. */
1458
+ export interface ViralBaseline {
1459
+ median: number;
1460
+ p90: number;
1461
+ n: number;
1462
+ source: 'trailing' | 'tick' | 'prior';
1463
+ }
1464
+ export interface ViralProvider {
1465
+ name: string;
1466
+ ok: boolean;
1467
+ lastOkAt: string | null;
1468
+ observedAt: string;
1469
+ }
1470
+ export interface ViralReadMeta {
1471
+ network: ViralNetwork;
1472
+ category_filter: {
1473
+ selected: string;
1474
+ } | null;
1475
+ media_filter: 'video' | null;
1476
+ /** Null unless the read named a filtering `compass`: what Hide politics left out (sides and Mix are not applied here). */
1477
+ compass_filter: CompassFilterMeta | null;
1478
+ safety_filter: {
1479
+ withheld: number;
1480
+ pending: number;
1481
+ };
1482
+ }
1483
+ export interface ViralReadResponse {
1484
+ items: ViralItem[];
1485
+ /** Pass as `cursor` for the next page; null at the end of this build's list. */
1486
+ cursor: string | null;
1487
+ builtAt: string | null;
1488
+ status: ViralReadStatus;
1489
+ window: {
1490
+ primary: '1h';
1491
+ context: '6h';
1492
+ };
1493
+ baselines: Record<string, ViralBaseline>;
1494
+ provenance: {
1495
+ providers: ViralProvider[];
1496
+ };
1497
+ meta: ViralReadMeta;
1498
+ /** True when no further page exists in this build (always on `off` and `building`). */
1499
+ exhausted: boolean;
1500
+ }
1293
1501
  /** Read options for callers that hold their own deadline, such as a discovery page. */
1294
1502
  export interface SocialReadOptions {
1295
1503
  retry?: boolean;
@@ -1409,6 +1617,14 @@ export declare class ProtocolsResource {
1409
1617
  getSharedRadarStory(projectId: string, params: {
1410
1618
  cluster: string;
1411
1619
  }): Promise<SingleResponse<SocialRadarStory>>;
1620
+ /**
1621
+ * The Viral read without a key, for a signed-out visitor: posts whose authors allow wide reach only, and media only
1622
+ * where the server cleared it, so a `media: 'video'` read can carry items whose video was withheld (filter with
1623
+ * `viralItemHasVideo`). `status: 'off'` (no items, never thrown) until the server enables the read for the app;
1624
+ * an API that predates the read answers this path 401 `missing_api_key`, which resolves as off too. Every other
1625
+ * failure throws. Cacheable for a minute by a shared cache.
1626
+ */
1627
+ getSharedViral(projectId: string, params?: ViralReadParams, options?: SocialReadOptions): Promise<SingleResponse<ViralReadResponse>>;
1412
1628
  publicNetworks(): Promise<SingleResponse<SocialNetwork[]>>;
1413
1629
  /** Query the live public network. It does not import users or posts. */
1414
1630
  searchPublicPosts(params: SearchPublicPostsParams): Promise<SingleResponse<SocialPostSearch>>;
@@ -1442,7 +1658,10 @@ export declare class ProtocolsResource {
1442
1658
  }): Promise<SingleResponse<SocialConversation>>;
1443
1659
  /**
1444
1660
  * Current provider-ranked topics or posts, with explicit source and coverage. Does not ingest content.
1445
- * Every page, previews included, gets the safety floor; `discovery` only asks for a layout.
1661
+ * Every page, previews included, gets the safety floor; `discovery` only asks for a layout. `compass` (a Politics
1662
+ * choice, spelled as `posts.list` takes it): Hide politics (`hide`) leaves out the topics and posts whose cached
1663
+ * browse category is politics, where the filter is served to the member; a side or Mix is answered unfiltered. The
1664
+ * page then says what it did in `meta.compass_filter`.
1446
1665
  */
1447
1666
  getPublicTrends(params: {
1448
1667
  protocol: string;
@@ -1451,6 +1670,7 @@ export declare class ProtocolsResource {
1451
1670
  includePreviews?: boolean;
1452
1671
  category?: string;
1453
1672
  discovery?: boolean;
1673
+ compass?: string;
1454
1674
  }, options?: SocialReadOptions): Promise<SingleResponse<SocialTrends>>;
1455
1675
  /** Open a topic currently listed by the provider. Use its nativeId as topic and preserve the opaque cursor. Screened like `getPublicTrends`. */
1456
1676
  getPublicTrendFeed(params: {
@@ -1477,6 +1697,12 @@ export declare class ProtocolsResource {
1477
1697
  getPublicRadarStory(params: {
1478
1698
  cluster: string;
1479
1699
  }, options?: SocialReadOptions): Promise<SingleResponse<SocialRadarStory>>;
1700
+ /**
1701
+ * The Viral read under the signed-in member's app key (scopes `users:read` and `posts:read`): every network's posts
1702
+ * the build keeps, less the member's own blocks, mutes and hidden posts. Never cached. `status: 'off'` resolves with
1703
+ * no items; a 401 or 403 always throws (a session or key failure is never reported as off).
1704
+ */
1705
+ getPublicViral(params?: ViralReadParams, options?: SocialReadOptions): Promise<SingleResponse<ViralReadResponse>>;
1480
1706
  /** Provider-ranked public accounts with explicit provenance; served from the app's index when a fresh snapshot exists. */
1481
1707
  getPublicPopularProfiles(params: {
1482
1708
  protocol: string;
@@ -1,4 +1,4 @@
1
- import { ValidationError } from '../errors.js';
1
+ import { AuthenticationError, ValidationError } from '../errors.js';
2
2
  /** Canonical protocol operation names returned by the adapter registry. */
3
3
  export const PROTOCOL_CAPABILITY_OPERATIONS = Object.freeze([
4
4
  'discover',
@@ -34,11 +34,43 @@ function hostedIdentityPath(id) {
34
34
  }
35
35
  return `/protocols/identities/${encodeURIComponent(id)}`;
36
36
  }
37
- /** Only the two radar fields travel; caller scope or destination extras never reach the query string. */
37
+ /**
38
+ * Whether the item's own card carries a playable video. Use it on a `media: 'video'` read before a Watch-style
39
+ * player: a visitor's read can serve an item whose video the server withheld (its card's `media` is then empty).
40
+ * A video only in a quoted post does not count, as the server never counts a quote's media.
41
+ */
42
+ export function viralItemHasVideo(item) {
43
+ return item.post.media.some(media => media.type === 'video');
44
+ }
45
+ /** Only the radar's own fields travel; caller scope or destination extras never reach the query string. */
38
46
  /** Forwarded only when given, so a default read makes exactly the call it always made. */
39
47
  const readOptions = (options) => options ? [options] : [];
40
48
  function radarQuery(params) {
41
- return { ...(params.category !== undefined ? { category: params.category } : {}), ...(params.limit !== undefined ? { limit: params.limit } : {}) };
49
+ return { ...(params.category !== undefined ? { category: params.category } : {}), ...(params.limit !== undefined ? { limit: params.limit } : {}),
50
+ ...(params.compass !== undefined ? { compass: params.compass } : {}) };
51
+ }
52
+ /** Only the six Viral read fields travel, each only when given: the read answers any other parameter with a 400. */
53
+ function viralQuery(params) {
54
+ const query = {};
55
+ for (const key of ['network', 'category', 'media', 'limit', 'cursor', 'compass'])
56
+ if (params[key] !== undefined)
57
+ query[key] = params[key];
58
+ return query;
59
+ }
60
+ /** An off read is empty whatever else its body carried. Every other status is passed through as served. */
61
+ function viralRead(response) {
62
+ if (response.data?.status !== 'off')
63
+ return response;
64
+ return { ...response, data: { ...response.data, items: [], cursor: null, exhausted: true } };
65
+ }
66
+ /** The server's own off answer (ViralReadService), built here for an API that predates the read. */
67
+ function viralOff(params) {
68
+ return { data: {
69
+ items: [], cursor: null, builtAt: null, status: 'off', window: { primary: '1h', context: '6h' }, baselines: {}, provenance: { providers: [] },
70
+ meta: { network: params.network ?? 'all', category_filter: params.category !== undefined ? { selected: params.category } : null,
71
+ media_filter: params.media ?? null, compass_filter: null, safety_filter: { withheld: 0, pending: 0 } },
72
+ exhausted: true,
73
+ } };
42
74
  }
43
75
  /** Forward only the media contract fields; nothing else from the app's own media rows. */
44
76
  const nativeMediaInput = (media) => media.map(item => ({ url: item.url,
@@ -117,6 +149,23 @@ export class ProtocolsResource {
117
149
  getSharedRadarStory(projectId, params) {
118
150
  return this.client.get(`/protocols/public/${encodeURIComponent(projectId)}/radar/story`, { cluster: params.cluster });
119
151
  }
152
+ /**
153
+ * The Viral read without a key, for a signed-out visitor: posts whose authors allow wide reach only, and media only
154
+ * where the server cleared it, so a `media: 'video'` read can carry items whose video was withheld (filter with
155
+ * `viralItemHasVideo`). `status: 'off'` (no items, never thrown) until the server enables the read for the app;
156
+ * an API that predates the read answers this path 401 `missing_api_key`, which resolves as off too. Every other
157
+ * failure throws. Cacheable for a minute by a shared cache.
158
+ */
159
+ async getSharedViral(projectId, params = {}, options) {
160
+ try {
161
+ return viralRead(await this.client.get(`/protocols/public/${encodeURIComponent(projectId)}/viral`, viralQuery(params), ...readOptions(options)));
162
+ }
163
+ catch (error) {
164
+ if (error instanceof AuthenticationError && error.code === 'missing_api_key')
165
+ return viralOff(params);
166
+ throw error;
167
+ }
168
+ }
120
169
  publicNetworks() {
121
170
  return this.client.get('/protocols/social/networks');
122
171
  }
@@ -156,13 +205,17 @@ export class ProtocolsResource {
156
205
  }
157
206
  /**
158
207
  * Current provider-ranked topics or posts, with explicit source and coverage. Does not ingest content.
159
- * Every page, previews included, gets the safety floor; `discovery` only asks for a layout.
208
+ * Every page, previews included, gets the safety floor; `discovery` only asks for a layout. `compass` (a Politics
209
+ * choice, spelled as `posts.list` takes it): Hide politics (`hide`) leaves out the topics and posts whose cached
210
+ * browse category is politics, where the filter is served to the member; a side or Mix is answered unfiltered. The
211
+ * page then says what it did in `meta.compass_filter`.
160
212
  */
161
213
  getPublicTrends(params, options) {
162
214
  return this.client.get('/protocols/social/trends', { protocol: params.protocol, limit: params.limit, cursor: params.cursor,
163
215
  ...(params.includePreviews !== undefined ? { includePreviews: params.includePreviews } : {}),
164
216
  ...(params.category !== undefined ? { category: params.category } : {}),
165
- ...(params.discovery !== undefined ? { discovery: params.discovery } : {}) }, ...readOptions(options));
217
+ ...(params.discovery !== undefined ? { discovery: params.discovery } : {}),
218
+ ...(params.compass !== undefined ? { compass: params.compass } : {}) }, ...readOptions(options));
166
219
  }
167
220
  /** Open a topic currently listed by the provider. Use its nativeId as topic and preserve the opaque cursor. Screened like `getPublicTrends`. */
168
221
  getPublicTrendFeed(params, options) {
@@ -188,6 +241,14 @@ export class ProtocolsResource {
188
241
  getPublicRadarStory(params, options) {
189
242
  return this.client.get('/protocols/social/radar/story', { cluster: params.cluster }, ...readOptions(options));
190
243
  }
244
+ /**
245
+ * The Viral read under the signed-in member's app key (scopes `users:read` and `posts:read`): every network's posts
246
+ * the build keeps, less the member's own blocks, mutes and hidden posts. Never cached. `status: 'off'` resolves with
247
+ * no items; a 401 or 403 always throws (a session or key failure is never reported as off).
248
+ */
249
+ async getPublicViral(params = {}, options) {
250
+ return viralRead(await this.client.get('/protocols/social/viral', viralQuery(params), ...readOptions(options)));
251
+ }
191
252
  /** Provider-ranked public accounts with explicit provenance; served from the app's index when a fresh snapshot exists. */
192
253
  getPublicPopularProfiles(params) {
193
254
  return this.client.get('/protocols/social/popular-profiles', { protocol: params.protocol, limit: params.limit, cursor: params.cursor });
package/dist/types.d.ts CHANGED
@@ -37,6 +37,10 @@ export type FeedNext = {
37
37
  * cursor; the same continuation `next` carries), the same category on the open networks, the member's
38
38
  * next categories (`via`: the order they came from, the one `posts.categories()` serves), For you
39
39
  * (signed in), All.
40
+ *
41
+ * `feed` (0.7.60) names the next feed by its ref (e.g. `builtin:category:science`, or a member's `feed:<id>`). No
42
+ * server sends it yet: it comes with feeds. Skip a step whose kind your app cannot open yet; a feed session
43
+ * (`discovery.createFeed`) already leaves out any step this SDK does not know, or a malformed one.
40
44
  */
41
45
  export type FalloverStep = {
42
46
  kind: 'category_older';
@@ -53,9 +57,45 @@ export type FalloverStep = {
53
57
  kind: 'for_you';
54
58
  } | {
55
59
  kind: 'all';
60
+ } | {
61
+ kind: 'feed';
62
+ ref: string;
56
63
  };
64
+ /**
65
+ * What a Politics choice (`compass`) did to a page, on the feeds that take one (`posts.list` on discovery, category
66
+ * and Minds' own lists, `curator.forYou`). Counts only: never a post's placement, never the cells asked for.
67
+ * `unavailable` means nothing was applied (the filter is not served to this caller yet, or the judgments are not
68
+ * there). Under Choose sides every post counts once as kept or left out; `non_political`, `pending` and `unplaced`
69
+ * (with posts judged maybe) are the not-placed rest. `mix` is Mix sides' band counts over the page served.
70
+ */
71
+ export interface CompassFilterMeta {
72
+ status: 'applied' | 'unavailable';
73
+ mode: 'all' | 'mix' | 'hide' | 'cells';
74
+ kept: number;
75
+ left_out: number;
76
+ unplaced: number;
77
+ pending: number;
78
+ non_political: number;
79
+ uncertain_shown: number;
80
+ truncated: boolean;
81
+ rubric?: string;
82
+ mix?: {
83
+ econ: {
84
+ l: number;
85
+ c: number;
86
+ r: number;
87
+ };
88
+ power: {
89
+ l: number;
90
+ m: number;
91
+ o: number;
92
+ };
93
+ };
94
+ }
57
95
  export interface PaginationMeta {
58
96
  discovery?: DiscoveryLayout;
97
+ /** Present when the request named `compass`: what the Politics choice did to this page. */
98
+ compass_filter?: CompassFilterMeta;
59
99
  scanned?: number;
60
100
  category_filter?: {
61
101
  selected: string;
@@ -348,6 +388,21 @@ export interface ListPostsParams extends PaginationParams {
348
388
  discovery?: boolean;
349
389
  /** Public-only broad category. Requires an enabled app experiment; limit <=25. */
350
390
  category?: string;
391
+ /**
392
+ * A Politics choice on a discovery, category or Minds list read: `all`, `mix`, `hide`, or cells in the canonical
393
+ * order `lo,co,ro,lm,cm,rm,ll,cl,rl`, optionally followed by `np`. Any other spelling is 400 `invalid_compass`; on a
394
+ * profile, group, Following, liked or saved read it is 400 `compass_not_supported`. Mix sides and Choose sides
395
+ * spend the member's hourly budget (429 `compass_rate_limited`). The page says what it did in
396
+ * `meta.compass_filter`. Only Minds posts are filtered.
397
+ */
398
+ compass?: string;
399
+ /**
400
+ * `false` asks once: the first 5xx or 429 is thrown, with no retry and no Retry-After sleep. For a read that holds its
401
+ * own deadline, such as a Discover feed session's Minds read. Never sent to the server. It rides in the params, not
402
+ * in a second argument, so a reader wrapped by a function that forwards only its params (as the Minds app wraps the
403
+ * `posts.list` it hands a feed session, to add a Politics choice) still asks once.
404
+ */
405
+ retry?: boolean;
351
406
  community_id?: string;
352
407
  author_id?: string;
353
408
  organization_id?: string;
@@ -355,6 +410,8 @@ export interface ListPostsParams extends PaginationParams {
355
410
  tag_ids?: string;
356
411
  /** Pass 'me' to return only posts where audience_user_id matches the caller (personal agent feed). */
357
412
  audience_user_id?: 'me' | string;
413
+ /** Return only posts by accounts the authenticated caller follows, newest first (the Following feed). */
414
+ following?: boolean;
358
415
  /** Return only posts the authenticated caller has upvoted. */
359
416
  liked?: boolean;
360
417
  /**
@@ -584,6 +641,19 @@ export interface ConversationMember {
584
641
  username: string;
585
642
  image: string | null;
586
643
  is_ai: boolean;
644
+ /**
645
+ * `admin` for a private group's admin: the one person who adds, removes and
646
+ * renames, and whose device sets up an encrypted group's keys. The creator,
647
+ * then, once they leave, the longest-standing person left. `member` for
648
+ * everyone else. Omitted by APIs before SDK 0.7.59's group routes.
649
+ */
650
+ role?: ChatMemberRole;
651
+ /**
652
+ * In a private group, every member's request: `pending` until an invitee
653
+ * accepts. Declining stays private, so to anyone else a member who declined
654
+ * still reads `pending`. Omitted outside private groups and by older APIs.
655
+ */
656
+ request_status?: ChatRequestStatus;
587
657
  }
588
658
  export interface ConversationMemberDetail extends ConversationMember {
589
659
  joined_at: string;
@@ -633,6 +703,12 @@ export interface LeaveConversationResult {
633
703
  left: true;
634
704
  /** True when the caller was the last person in it, so the room was removed. */
635
705
  conversation_deleted: boolean;
706
+ /**
707
+ * When the admin of an encrypted group leaves: the invitees who had not
708
+ * accepted, whose invites were cancelled with them (the next admin
709
+ * re-invites). Omitted when there were none.
710
+ */
711
+ invites_cancelled?: string[];
636
712
  }
637
713
  export interface ConversationDetail {
638
714
  /** Missing on older APIs: treat as unknown, never as plaintext permission. */
@@ -652,6 +728,67 @@ export interface ConversationDetail {
652
728
  can_leave?: boolean;
653
729
  created_at: string;
654
730
  }
731
+ export type ChatMemberRole = 'admin' | 'member';
732
+ /**
733
+ * Why an encrypted group refused someone: `details.reason` of a 409
734
+ * `conversation_not_eligible` (`RecursivError`) from
735
+ * `chat.createGroup({ encrypted: true })` or `chat.addMembers`, beside
736
+ * `details.max_people`. The group must be a private group (not a DM, not a
737
+ * community room) of people only (no agents), all inside the encryption
738
+ * pilot, and at most `max_people` (8 at launch) counting pending invitees.
739
+ */
740
+ export type EncryptedGroupRefusalReason = 'not_a_private_group' | 'community' | 'too_many_people' | 'agent' | 'outside_pilot';
741
+ export interface CreateGroupInput {
742
+ name: string;
743
+ /** Up to 100 people, invited in this order. */
744
+ member_ids: string[];
745
+ /**
746
+ * End-to-end encrypted. The group and its encryption record are created
747
+ * together or not at all: a refusal never leaves a plaintext group behind.
748
+ */
749
+ encrypted?: boolean;
750
+ /** With `encrypted`: the creator's active encrypted-chat device, when they have one. */
751
+ device_id?: string;
752
+ }
753
+ export interface AddGroupMembersResult {
754
+ id: string;
755
+ /** Who was added, in the order asked; `pending` for someone who does not follow you (a message request). */
756
+ added: Array<{
757
+ id: string;
758
+ request_status: ChatRequestStatus;
759
+ }>;
760
+ /** Asked for but already in the group: nothing changed for them. */
761
+ already_members: string[];
762
+ }
763
+ export interface RemoveGroupMemberResult {
764
+ id: string;
765
+ user_id: string;
766
+ removed: true;
767
+ }
768
+ export interface UpdateGroupResult {
769
+ id: string;
770
+ name: string;
771
+ }
772
+ /**
773
+ * Realtime `chat:members`: a private group's people changed. Ids only: read
774
+ * the conversation again (and, in an encrypted group, check its keys).
775
+ * `actorId` is the admin for `added` and `removed`, and the leaver for `left`
776
+ * and `invites_cancelled` (the invites an encrypted group's admin sent, which
777
+ * went with them).
778
+ */
779
+ export interface ChatMembersEvent {
780
+ conversationId: string;
781
+ change: 'added' | 'removed' | 'left' | 'invites_cancelled';
782
+ userIds: string[];
783
+ actorId: string;
784
+ }
785
+ /** Realtime `chat:conversation`: a private group was renamed by its admin. */
786
+ export interface ChatConversationEvent {
787
+ conversationId: string;
788
+ change: 'renamed';
789
+ name: string;
790
+ actorId: string;
791
+ }
655
792
  export interface MessageReaction {
656
793
  type: string;
657
794
  user_id: string;