@recursiv/sdk 0.7.122 → 0.7.123

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/dist/index.d.ts CHANGED
@@ -227,8 +227,8 @@ export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus
227
227
  export { TrustResource } from './resources/trust.js';
228
228
  export type { TrustEnvelope, OwnTrustEvent, TrustPublicKey, OwnTrustEvents } from './resources/trust.js';
229
229
  export { LinkPreviewResource } from './resources/link-preview.js';
230
- export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
231
- export type { PublicSearchPage, PublicSearchTab, SearchAnswer, SearchGroup, SearchLadder, SearchOmitReason, SearchParams, SearchPerson, SearchPost, SearchRelated, SearchResponse, SearchSection, SearchStage, SearchStory, SearchTopic, SearchVia, } from './resources/search.js';
230
+ export { SEARCH_MEDIA_KINDS, SEARCH_SECTIONS, SearchResource } from './resources/search.js';
231
+ export type { PublicSearchPage, PublicSearchTab, SearchAnswer, SearchGroup, SearchLadder, SearchOmitReason, SearchParams, SearchPerson, SearchPost, SearchRelated, SearchResponse, SearchSection, SearchStage, SearchStory, SearchTopic, SearchVia, SearchMedia, SearchMediaEmbed, SearchMediaItem, SearchMediaKind, SearchMediaProvider, SearchMediaProviderStatus, SearchMediaSource, } from './resources/search.js';
232
232
  export { BOOKMARK_MERGE_MAX_ITEMS, BookmarkCollectionsResource, BookmarksResource } from './resources/bookmarks.js';
233
233
  export { FEED_TOP_NETWORKS, FeedsResource } from './resources/feeds.js';
234
234
  export type { FeedStoriesParams, FeedStoriesResponse, FeedStory, FeedTopExplain, FeedTopItem, FeedTopMeta, FeedTopNetwork, FeedTopNetworkCard, FeedTopNetworkEntry, FeedTopNetworkMap, FeedTopParams, FeedTopResponse, FeedTopTarget, PublicFeedTopMeta, PublicFeedTopParams, PublicFeedTopResponse, } from './resources/feeds.js';
package/dist/index.js CHANGED
@@ -284,7 +284,7 @@ export { JudgmentsResource, isJudgmentSharedWordsError } from './resources/judgm
284
284
  export { ModerationResource } from './resources/moderation.js';
285
285
  export { TrustResource } from './resources/trust.js';
286
286
  export { LinkPreviewResource } from './resources/link-preview.js';
287
- export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
287
+ export { SEARCH_MEDIA_KINDS, SEARCH_SECTIONS, SearchResource } from './resources/search.js';
288
288
  export { BOOKMARK_MERGE_MAX_ITEMS, BookmarkCollectionsResource, BookmarksResource } from './resources/bookmarks.js';
289
289
  export { FEED_TOP_NETWORKS, FeedsResource } from './resources/feeds.js';
290
290
  export { AccountsResource } from './resources/accounts.js';
@@ -2,7 +2,7 @@ import type { HttpClient } from '../client.js';
2
2
  import type { Post, StoryOutlets, ViewerModeration } from '../types.js';
3
3
  import type { SocialPost } from './protocols.js';
4
4
  /** The sections a blended search can answer, in no particular order (see `SearchAnswer.order`). */
5
- export declare const SEARCH_SECTIONS: readonly ["top_posts", "latest_posts", "open_network", "people", "groups", "topics", "stories", "related", "trending"];
5
+ export declare const SEARCH_SECTIONS: readonly ["top_posts", "latest_posts", "open_network", "people", "groups", "topics", "stories", "related", "trending", "media"];
6
6
  export type SearchSection = (typeof SEARCH_SECTIONS)[number];
7
7
  /** Why a section is absent: out of time, the server was busy, not available for this app, not allowed for this key, or it failed. */
8
8
  export type SearchOmitReason = 'timeout' | 'saturated' | 'unavailable' | 'scope' | 'error';
@@ -39,8 +39,11 @@ export interface SearchLadder {
39
39
  export type SearchPost = Post & {
40
40
  via?: SearchVia;
41
41
  };
42
- /** A visitor's search tabs (tRPC `publicDiscovery.search`, read keyless through the app's public client). */
43
- export type PublicSearchTab = 'top' | 'latest' | 'people' | 'groups' | 'videos' | 'news' | 'tag';
42
+ /**
43
+ * A visitor's search tabs (tRPC `publicDiscovery.search`, read keyless through the app's public client). `media` (server
44
+ * 0.7.123): no items, the page's `media` section instead.
45
+ */
46
+ export type PublicSearchTab = 'top' | 'latest' | 'people' | 'groups' | 'videos' | 'news' | 'tag' | 'media';
44
47
  /** `fast` answers within about a second; `deep` waits for the reads `fast` left running. */
45
48
  export type SearchStage = 'fast' | 'deep';
46
49
  /** One page of a visitor's search: every item labelled `via`, never empty while the app has anything popular. */
@@ -57,15 +60,75 @@ export interface PublicSearchPage<T = Record<string, unknown>> {
57
60
  next_cursor: string | null;
58
61
  has_more: boolean;
59
62
  };
63
+ /** The `media` tab only: what the web has for the words. Absent where the app does not get it (`ladder.unavailable` names `media`). */
64
+ media?: SearchMedia;
65
+ }
66
+ /** The kinds of a search's media items, in the order the server lists them. */
67
+ export declare const SEARCH_MEDIA_KINDS: readonly ["about", "video", "article", "image", "audio"];
68
+ export type SearchMediaKind = (typeof SEARCH_MEDIA_KINDS)[number];
69
+ /** Where a media item was found. */
70
+ export type SearchMediaSource = 'brave' | 'wikipedia' | 'peertube' | 'openverse';
71
+ /**
72
+ * How a video may play in place: YouTube's own player for `id` (use youtube-nocookie.com and follow its player rules:
73
+ * at least 200x200, nothing drawn over it, YouTube shown as the source), or a PeerTube instance's own embed page.
74
+ */
75
+ export type SearchMediaEmbed = {
76
+ provider: 'youtube';
77
+ id: string;
78
+ } | {
79
+ provider: 'peertube';
80
+ url: string;
81
+ };
82
+ /** One item of the media section: a video, a picture, a news article, a sound or the about card. */
83
+ export interface SearchMediaItem {
84
+ kind: SearchMediaKind;
85
+ source: SearchMediaSource;
86
+ title: string;
87
+ /** The page it lives on: open it when the item is tapped. */
88
+ url: string;
89
+ /** The picture to draw, always on the API's signed media proxy; null when there is none. A picture item always has one. */
90
+ thumbnail: string | null;
91
+ width: number | null;
92
+ height: number | null;
93
+ duration_s: number | null;
94
+ /** The channel, outlet or creator. */
95
+ publisher: string | null;
96
+ /** ISO 8601. */
97
+ published_at: string | null;
98
+ /** A news description, or the about card's extract. */
99
+ description: string | null;
100
+ embed: SearchMediaEmbed | null;
101
+ /** The credit a licensed work requires: show it with the work. */
102
+ attribution: string | null;
103
+ license: string | null;
104
+ /**
105
+ * Its picture was judged suggestive: present only for a member who turned Show NSFW on, who is shown it blurred.
106
+ * Visitors and members on safe defaults are never served such an item.
107
+ */
108
+ sensitive?: true;
109
+ }
110
+ export type SearchMediaProvider = 'brave_web' | 'brave_images' | 'wikipedia' | 'peertube' | 'openverse_images' | 'openverse_audio';
111
+ /**
112
+ * What each provider did for this answer; `timeout`: it is still reading, ask again shortly for more; `budget`: a
113
+ * spending limit held it back (the day's Brave calls, or this caller's own calls a day or fresh answers an hour, past
114
+ * which only what is already cached is served).
115
+ */
116
+ export type SearchMediaProviderStatus = 'ok' | 'cached' | 'empty' | 'skipped' | 'budget' | 'unconfigured' | 'timeout' | 'error';
117
+ /** The media section: what the web has for the words (server 0.7.123). */
118
+ export interface SearchMedia {
119
+ items: SearchMediaItem[];
120
+ providers: Partial<Record<SearchMediaProvider, SearchMediaProviderStatus>>;
60
121
  }
61
122
  export interface SearchParams {
62
123
  q: string;
63
124
  /** Items per section, 1-20 (default 6). */
64
125
  limit?: number;
65
- /** Only these sections (default: all). */
126
+ /** Only these sections (default: all but `media`, which answers only when named: each may spend a paid web search). */
66
127
  sections?: readonly SearchSection[];
67
128
  /** For organization-bound keys, as on `posts.search`. */
68
129
  organization_id?: string;
130
+ /** How many pictures the app already shows for the query: the media section asks the web for pictures only when fewer than six. */
131
+ own_images?: number;
69
132
  }
70
133
  export interface SearchPerson {
71
134
  id: string;
@@ -205,6 +268,11 @@ export interface SearchAnswer {
205
268
  } | null;
206
269
  items: SearchPost[];
207
270
  };
271
+ /**
272
+ * What the web has for the words (server 0.7.123): videos, news, pictures, sound and an about card, only on the
273
+ * networks the server offers it to (others list it in `omitted` as `unavailable`).
274
+ */
275
+ media?: SearchMedia;
208
276
  };
209
277
  /** Answered, non-empty sections in the order to render them. */
210
278
  order: SearchSection[];
@@ -1,5 +1,7 @@
1
1
  /** The sections a blended search can answer, in no particular order (see `SearchAnswer.order`). */
2
- export const SEARCH_SECTIONS = ['top_posts', 'latest_posts', 'open_network', 'people', 'groups', 'topics', 'stories', 'related', 'trending'];
2
+ export const SEARCH_SECTIONS = ['top_posts', 'latest_posts', 'open_network', 'people', 'groups', 'topics', 'stories', 'related', 'trending', 'media'];
3
+ /** The kinds of a search's media items, in the order the server lists them. */
4
+ export const SEARCH_MEDIA_KINDS = ['about', 'video', 'article', 'image', 'audio'];
3
5
  export class SearchResource {
4
6
  client;
5
7
  constructor(client) {
package/llm.md CHANGED
@@ -739,9 +739,13 @@ Each answers 404 while the app's operator has it off; `feeds.top` is private to
739
739
  // One query, answered as sections. Each has its own time budget; a slow one is listed in `omitted`.
740
740
  const { data: answer } = await r.search.query({ q: 'apple', limit: 6 })
741
741
  for (const section of answer.order) render(section, answer.sections[section])
742
- // top_posts, latest_posts, open_network, people, groups, topics, stories, related, trending
742
+ // top_posts, latest_posts, open_network, people, groups, topics, stories, related, trending, media
743
+ // What the web has for the words, alone, so it paints beside the rest (0.7.123):
744
+ const { data: web } = await r.search.query({ q: 'hockey', sections: ['media'], own_images: shownPictures })
743
745
  ```
744
746
 
747
+ `media` (0.7.123): videos (YouTube ids as `embed`, PeerTube embed pages), news articles, pictures, sound and one `about` card (Wikipedia), from Brave Search, Wikipedia, PeerTube and Openverse with strict safe search. It answers only when `sections` names it (the default sections leave it out: each answer may spend a paid web search). Every picture passes the same vision check a post's picture does: a visitor sees only pictures judged safe, and a member who turned Show NSFW on gets a suggestive one marked `sensitive` (blur it). Every `thumbnail` is on the API's signed media proxy; play a YouTube embed through youtube-nocookie.com by its player's rules and show each `attribution`. `own_images` (how many pictures the page already shows) lets the server ask for the web's pictures only when fewer than six. Only the networks the server offers it to get it (others: `omitted` `unavailable`); `providers` says what each source did (`timeout`: ask again shortly). Visitors read it as `publicDiscovery.search` `tab: 'media'` (`PublicSearchPage.media`).
748
+
745
749
  `trending` joins when the answer is thin or old, so a search never dead-ends. A thin answer steps down the search ladder (0.7.79): the word a typo meant, the known words with the rest dropped ("wiggly memes" searches "memes"), what is popular in the category the words are about, then on the app. Every item says how in `via` (`SearchVia`) and `answer.ladder` (`SearchLadder`) says what happened: label it honestly ("No exact matches for X", "Did you mean Y?", "Popular in Comedy right now"); `ladder.unavailable` names sections the key cannot read. Visitors read the same ladder keyless through tRPC `publicDiscovery.search` (`PublicSearchPage`). `top_posts` is floored as `r.posts.search`'s relevance page is; `people` rows carry `followers_count`, `posts_count`, `empty` and `verified` (see `r.profiles.search`); `groups` rows carry `description` (one line) and `last_active_at`, to tell groups of one name apart. Page further with `r.posts.search`. Needs `posts:read`; `people` needs `users:read`, `groups` `communities:read`; `open_network` and `stories` need `users:read` plus the caller's own app key on its network, `trending` that app key. What the key cannot have is in `omitted` (`scope` / `unavailable`).
746
750
 
747
751
  ### r.curator — World Today and its story pages
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recursiv/sdk",
3
- "version": "0.7.122",
3
+ "version": "0.7.123",
4
4
  "description": "TypeScript SDK for the Recursiv API",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "type": "module",