itd-api 0.0.7 → 0.0.9

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.cts CHANGED
@@ -1 +1,4206 @@
1
- export { A as ALLOWED_MIME_TYPES, c as AUDIO_MIME_TYPES, d as AUTH_FLAG_COOKIE, e as AUTH_PATHS, f as AccessType, g as Actor, h as AllowedMimeType, i as Announcement, j as AnnouncementButton, k as Attachment, l as AttachmentType, m as AudioMimeType, n as AuthInput, o as AuthResource, p as Author, B as BuilderInput, C as CaptchaCredentials, q as ChangelogEntry, r as Clan, s as ClientHooks, t as Comment, u as CommentBuilder, v as CommentInput, w as CommentReplyTo, x as CommentSort, y as CommentsParams, z as CommentsResource, D as CreateCommentInput, E as CreatePollInput, G as CreatePostInput, H as CreateReportInput, J as Credentials, K as CredentialsAuth, L as DEFAULT_BASE_URL, M as DEFAULT_TIMEOUT, N as DEFAULT_USER_AGENT, O as DEVICE_ID_HEADER, P as DetectedRuntime, Q as DwellEntry, R as ErrorContextHook, S as FeedParams, U as FeedTab, V as FileInput, W as FilesResource, X as FollowResult, Y as ForgotPasswordInput, Z as Hashtag, _ as HashtagPostsParams, $ as HashtagsResource, a0 as IMAGE_MIME_TYPES, a1 as ImageMimeType, a2 as InteractionEntry, a3 as InteractionType, a4 as IsoDate, a5 as ItdAbortError, a6 as ItdApiError, a7 as ItdApiErrorInit, a8 as ItdApiErrorKind, a9 as ItdAuthError, aa as ItdBuilder, a as ItdClient, b as ItdClientOptions, ab as ItdConfigError, ac as ItdConflictError, ad as ItdError, ae as ItdErrorCode, af as ItdErrorKind, ag as ItdFieldErrors, ah as ItdForbiddenError, ai as ItdNetworkError, aj as ItdNotFoundError, ak as ItdPhoneVerificationError, al as ItdPlugin, am as ItdRateLimitError, an as ItdRealtime, ao as ItdServerError, I as ItdSession, ap as ItdTimeoutError, aq as ItdValidationError, ar as LIBRARY_VERSION, as as LikeResult, at as LikesVisibility, au as Listener, av as LocalStorageTokenStorage, aw as Logger, ax as Loose, ay as MAX_RECONNECT_ATTEMPTS, az as MemoryTokenStorage, aA as MyProfile, aB as NOTIFICATION_TYPE_ALIASES, aC as Notification, aD as NotificationEvent, aE as NotificationListParams, aF as NotificationSettings, aG as NotificationType, aH as NotificationsResource, aI as OAuthProvider, aJ as Page, aK as PageState, aL as PaginationMode, aM as Paginator, aN as PaymentMethod, aO as Pin, aP as PinPostResult, aQ as PinsResult, aR as PlatformResource, aS as PluginContext, aT as Poll, aU as PollBuilder, aV as PollInput, aW as PollOption, aX as PollTransportOptions, aY as Portal, aZ as Post, a_ as PostBuilder, a$ as PostInput, b0 as PostStats, b1 as PostsResource, b2 as PrivacySettings, b3 as Profile, b4 as PublicProfile, b5 as RECONNECT_BACKOFF, b6 as RECONNECT_JITTER, b7 as REFRESH_COOKIE, b8 as REFRESH_COOKIE_PATH, b9 as RateLimitOptions, ba as RawRequestOptions, bb as RealtimeEvents, bc as RealtimeOptions, bd as RealtimeStatus, be as RealtimeTransport, bf as RealtimeTransportKind, bg as ReconnectOptions, bh as RepliesParams, bi as Report, bj as ReportBuilder, bk as ReportInput, bl as ReportReason, bm as ReportTargetType, bn as ReportsResource, bo as RequestContext, bp as RequestOptions, bq as ResetPasswordInput, br as ResponseContext, bs as RetryContext, bt as RetryOptions, bu as RuntimeMode, bv as STREAM_PATH, bw as SearchResource, bx as SearchResult, by as Session, bz as SignInResult, bA as SignInStatus, bB as Span, bC as SpanType, bD as SseTransportOptions, bE as Subscription, bF as SubscriptionResource, bG as SubscriptionState, bH as TURNSTILE_SITE_KEY, bI as TelemetryOptions, bJ as TelemetryResource, T as TokenStorage, bK as Transformer, bL as TransportContext, bM as TransportEvent, bN as UnauthorizedStreamError, bO as Unsubscribe, bP as UpdateNotificationSettingsInput, bQ as UpdatePrivacyInput, bR as UpdateProfileInput, bS as UploadOptions, bT as UploadedFile, bU as UserId, bV as UserListParams, bW as UserPostsParams, bX as UserRef, bY as UserSummary, bZ as UsersResource, b_ as VIDEO_MIME_TYPES, b$ as VerificationResource, c0 as VerificationStatus, c1 as VideoMimeType, c2 as ViewReason, c3 as ViewSource, c4 as WallAccess, c5 as canonicalNotificationType, c6 as comment, cu as createClient, c7 as createTokenStorage, c8 as formatNotificationText, c9 as isBuilder, ca as isItdApiError, cb as isItdAuthError, cc as isItdConflictError, cd as isItdError, ce as isItdForbiddenError, cf as isItdNotFoundError, cg as isItdPhoneVerificationError, ch as isItdRateLimitError, ci as isItdServerError, cj as isItdValidationError, ck as isKnownNotificationType, cl as isMyProfile, cm as normalizeNotification, cn as poll, co as post, cp as readNotificationEvent, cq as readUnreadCountEvent, cr as report, cs as resolveNotificationUrl, ct as toDate } from './index-DNFPX_Z1.cjs';
1
+ /** Метка билдера. Через `Symbol.for` чтобы распознавание переживало смешивание ESM и CJS. */
2
+ declare const BUILDER: unique symbol;
3
+ /**
4
+ * Билдер входных данных.
5
+ *
6
+ * Билдеры необязательны: любой метод, принимающий билдер, принимает и обычный объект.
7
+ * Проверки одинаковы в обоих случаях.
8
+ */
9
+ interface ItdBuilder<T> {
10
+ /** @internal */
11
+ readonly [BUILDER]: true;
12
+ /**
13
+ * Собирает и проверяет результат.
14
+ *
15
+ * @throws {ItdConfigError} если нарушены требования к данным
16
+ */
17
+ build(): T;
18
+ /** Чтобы билдер корректно вёл себя внутри `JSON.stringify`. */
19
+ toJSON(): T;
20
+ }
21
+ /**
22
+ * Три равноправные формы входа: обычный объект, готовый билдер или функция-настройщик.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * itd.posts.create({ content: 'привет' }); // объект
27
+ * itd.posts.create(post().content('привет')); // билдер
28
+ * itd.posts.create((p) => p.content('привет')); // функция
29
+ * ```
30
+ */
31
+ type BuilderInput<T, B extends ItdBuilder<T>> = T | B | ((builder: B) => B | T);
32
+ /** Является ли значение билдером. */
33
+ declare function isBuilder<T>(value: unknown): value is ItdBuilder<T>;
34
+
35
+ /**
36
+ * Перечисления API итд.com.
37
+ *
38
+ * Здесь намеренно не используется `enum` из TypeScript. Вместо него — пара «замороженный
39
+ * объект + одноимённый тип». Такой приём даёт всё, ради чего берут `enum`
40
+ * (`FeedTab.Popular`, перебор значений в рантайме), и при этом:
41
+ *
42
+ * - **стирается без остатка** — `enum` порождает рантайм-код и отвергается средами,
43
+ * которые просто срезают типы (`node --experimental-strip-types`);
44
+ * - **не запрещает обычные строки** — `itd.posts.list({ tab: 'popular' })` остаётся валидным,
45
+ * тогда как строковый `enum` считает это ошибкой типа и вынуждает всех импортировать себя;
46
+ * - **позволяет открытые множества** — там, где документация перечисляет значения не полностью,
47
+ * тип расширяется через {@link Loose}, а объект остаётся справочником известных значений.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { FeedTab } from 'itd-api';
52
+ *
53
+ * await itd.posts.list({ tab: FeedTab.Popular }); // без магических строк
54
+ * await itd.posts.list({ tab: 'popular' }); // и так тоже можно
55
+ *
56
+ * Object.values(FeedTab); // ['popular', 'following', 'clan']
57
+ * ```
58
+ *
59
+ * @packageDocumentation
60
+ */
61
+ /**
62
+ * Открытое строковое перечисление.
63
+ *
64
+ * Даёт автодополнение известных значений, но не ломается, если сервер пришлёт новое.
65
+ * Используется там, где документация API перечисляет значения не полностью («`everyone` и др.»).
66
+ */
67
+ type Loose<T extends string> = T | (string & {});
68
+ /**
69
+ * Вкладка ленты `GET /api/posts`.
70
+ *
71
+ * Множество закрытое: неизвестное значение сервер отвергнет.
72
+ */
73
+ declare const FeedTab: Readonly<{
74
+ /** Популярное. Курсор здесь — номер страницы в виде строки (`"2"`, `"6"`…). */
75
+ readonly Popular: "popular";
76
+ /** Записи тех, на кого вы подписаны. Курсор — отметка времени последнего поста. */
77
+ readonly Following: "following";
78
+ /** Лента клана. Курсор, как и в подписках, — отметка времени. */
79
+ readonly Clan: "clan";
80
+ }>;
81
+ type FeedTab = (typeof FeedTab)[keyof typeof FeedTab];
82
+ /** Порядок комментариев к посту. */
83
+ declare const CommentSort: Readonly<{
84
+ /** Сначала новые. */
85
+ readonly Newest: "newest";
86
+ /** Сначала старые. */
87
+ readonly Oldest: "oldest";
88
+ /** Сначала популярные. */
89
+ readonly Popular: "popular";
90
+ }>;
91
+ type CommentSort = (typeof CommentSort)[keyof typeof CommentSort];
92
+ /** Тип вложения. */
93
+ declare const AttachmentType: Readonly<{
94
+ readonly Image: "image";
95
+ readonly Video: "video";
96
+ /** Голосовые комментарии: `audio/ogg`, с полем `duration`. */
97
+ readonly Audio: "audio";
98
+ }>;
99
+ type AttachmentType = (typeof AttachmentType)[keyof typeof AttachmentType];
100
+ /**
101
+ * Тип фрагмента разметки в тексте поста или комментария.
102
+ *
103
+ * Первые два сервер расставляет сам при разборе текста, остальные приходят от редактора.
104
+ * Тип открытый: набор может пополниться.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * await itd.posts.update(postId, {
109
+ * content: 'жирное слово',
110
+ * spans: [{ type: SpanType.Bold, offset: 0, length: 6 }],
111
+ * });
112
+ * ```
113
+ */
114
+ declare const SpanType: Readonly<{
115
+ /** Хэштег. Название без решётки лежит в `tag`. */
116
+ readonly Hashtag: "hashtag";
117
+ /** Упоминание. Имя пользователя лежит в `tag`. */
118
+ readonly Mention: "mention";
119
+ /** Ссылка. Адрес лежит в `url`, а не в `tag`. */
120
+ readonly Link: "link";
121
+ readonly Bold: "bold";
122
+ readonly Italic: "italic";
123
+ readonly Underline: "underline";
124
+ /** Зачёркнутый. */
125
+ readonly Strike: "strike";
126
+ /** Спойлер: текст скрыт до нажатия. */
127
+ readonly Spoiler: "spoiler";
128
+ /** Моноширинный. */
129
+ readonly Monospace: "monospace";
130
+ readonly Quote: "quote";
131
+ }>;
132
+ type SpanType = Loose<(typeof SpanType)[keyof typeof SpanType]>;
133
+ /** На что подаётся жалоба. */
134
+ declare const ReportTargetType: Readonly<{
135
+ readonly Post: "post";
136
+ readonly Comment: "comment";
137
+ readonly User: "user";
138
+ }>;
139
+ type ReportTargetType = (typeof ReportTargetType)[keyof typeof ReportTargetType];
140
+ /** Причина жалобы. Множество закрытое. */
141
+ declare const ReportReason: Readonly<{
142
+ readonly Spam: "spam";
143
+ readonly Violence: "violence";
144
+ readonly Hate: "hate";
145
+ readonly Adult: "adult";
146
+ readonly Fraud: "fraud";
147
+ readonly Other: "other";
148
+ }>;
149
+ type ReportReason = (typeof ReportReason)[keyof typeof ReportReason];
150
+ /** Состояние realtime-соединения. */
151
+ declare const RealtimeStatus: Readonly<{
152
+ readonly Connecting: "connecting";
153
+ readonly Connected: "connected";
154
+ readonly Error: "error";
155
+ readonly Disconnected: "disconnected";
156
+ }>;
157
+ type RealtimeStatus = (typeof RealtimeStatus)[keyof typeof RealtimeStatus];
158
+ /** Состояние сервиса платформы. Тип открытый. */
159
+ declare const ServiceState: Readonly<{
160
+ /** Работает штатно. */
161
+ readonly Operational: "operational";
162
+ /** Работает с деградацией. */
163
+ readonly Degraded: "degraded";
164
+ /** Недоступен. */
165
+ readonly Downtime: "downtime";
166
+ }>;
167
+ type ServiceState = Loose<(typeof ServiceState)[keyof typeof ServiceState]>;
168
+ /** Вид происшествия в истории сервиса. Тип открытый. */
169
+ declare const IncidentKind: Readonly<{
170
+ /** Недоступен. */
171
+ readonly Down: "down";
172
+ /** Деградация. */
173
+ readonly Degraded: "deg";
174
+ }>;
175
+ type IncidentKind = Loose<(typeof IncidentKind)[keyof typeof IncidentKind]>;
176
+ /**
177
+ * Уровень доступа к разделу профиля.
178
+ *
179
+ * Общий набор значений для полей `wallAccess` и `likesVisibility` настроек приватности.
180
+ * Тип открытый: сервер может прислать значение вне этого перечня.
181
+ */
182
+ declare const AccessType: Readonly<{
183
+ /** Никто. */
184
+ readonly Nobody: "nobody";
185
+ /** Только взаимные подписки. */
186
+ readonly Mutual: "mutual";
187
+ /** Подписчики. */
188
+ readonly Followers: "followers";
189
+ /** Все. */
190
+ readonly Everyone: "everyone";
191
+ }>;
192
+ type AccessType = Loose<(typeof AccessType)[keyof typeof AccessType]>;
193
+ /** Кто может писать на стену профиля. Псевдоним {@link AccessType}. */
194
+ declare const WallAccess: Readonly<{
195
+ /** Никто. */
196
+ readonly Nobody: "nobody";
197
+ /** Только взаимные подписки. */
198
+ readonly Mutual: "mutual";
199
+ /** Подписчики. */
200
+ readonly Followers: "followers";
201
+ /** Все. */
202
+ readonly Everyone: "everyone";
203
+ }>;
204
+ type WallAccess = AccessType;
205
+ /** Кто видит реакции пользователя. Псевдоним {@link AccessType}. */
206
+ declare const LikesVisibility: Readonly<{
207
+ /** Никто. */
208
+ readonly Nobody: "nobody";
209
+ /** Только взаимные подписки. */
210
+ readonly Mutual: "mutual";
211
+ /** Подписчики. */
212
+ readonly Followers: "followers";
213
+ /** Все. */
214
+ readonly Everyone: "everyone";
215
+ }>;
216
+ type LikesVisibility = AccessType;
217
+ /**
218
+ * Канонический тип уведомления (новое поколение имён).
219
+ *
220
+ * REST-эндпоинт `/api/notifications/` отдаёт старые имена (`like`, `comment`, `reply`,
221
+ * `repost`, `mention`), SSE-поток — новые. Библиотека приводит их к этому набору,
222
+ * сохраняя исходное значение в поле `rawType`.
223
+ */
224
+ declare const NotificationType: Readonly<{
225
+ /** Реакция на пост. Старое имя — `like`. */
226
+ readonly PostReaction: "post_reaction";
227
+ /** Комментарий к посту. Старое имя — `comment`. */
228
+ readonly PostComment: "post_comment";
229
+ /** Ответ на комментарий. Старое имя — `reply`. */
230
+ readonly CommentReply: "comment_reply";
231
+ /** Репост. Старое имя — `repost`. */
232
+ readonly PostRepost: "post_repost";
233
+ /** Упоминание в посте. Старое имя — `mention`. */
234
+ readonly PostMention: "post_mention";
235
+ /** Реакция на комментарий. */
236
+ readonly CommentReaction: "comment_reaction";
237
+ /** Упоминание в комментарии. */
238
+ readonly CommentMention: "comment_mention";
239
+ /** Запись на вашей стене. */
240
+ readonly WallPost: "wall_post";
241
+ /** На вас подписались. */
242
+ readonly Follow: "follow";
243
+ /** Заявка на подписку (закрытый профиль). */
244
+ readonly FollowRequest: "follow_request";
245
+ /** Заявка на подписку принята. */
246
+ readonly FollowAccepted: "follow_accepted";
247
+ /** Верификация одобрена. Приходит только по REST. */
248
+ readonly VerificationApproved: "verification_approved";
249
+ /** Верификация отклонена. Приходит только по REST. */
250
+ readonly VerificationRejected: "verification_rejected";
251
+ }>;
252
+ type NotificationType = Loose<(typeof NotificationType)[keyof typeof NotificationType]>;
253
+ /**
254
+ * Тип взаимодействия с контентом в телеметрии (`POST /api/v1/x`, поле `t`).
255
+ *
256
+ * Кодируется числом.
257
+ */
258
+ declare const InteractionType: Readonly<{
259
+ /** Открытие фотографии. */
260
+ readonly PhotoOpen: 1;
261
+ /** Прогресс просмотра видео. Несёт поля `pm`/`dm`. */
262
+ readonly VideoProgress: 2;
263
+ }>;
264
+ type InteractionType = (typeof InteractionType)[keyof typeof InteractionType];
265
+ /**
266
+ * Источник показа поста в телеметрии (поле `s`).
267
+ *
268
+ * Кодируется числом. Поле применимо к источникам `PostPage` и `Link`; для лент источник
269
+ * передаётся контекстом `sc`.
270
+ */
271
+ declare const ViewSource: Readonly<{
272
+ readonly FeedGlobal: 1;
273
+ readonly FeedFollowing: 2;
274
+ readonly FeedClan: 3;
275
+ readonly Profile: 4;
276
+ readonly Hashtag: 5;
277
+ readonly PostPage: 6;
278
+ readonly Link: 7;
279
+ readonly Search: 8;
280
+ }>;
281
+ type ViewSource = (typeof ViewSource)[keyof typeof ViewSource];
282
+ /**
283
+ * Причина завершения просмотра поста в телеметрии (`POST /api/v1/i`, поле `r`).
284
+ *
285
+ * Кодируется числом.
286
+ */
287
+ declare const ViewReason: Readonly<{
288
+ /** Пост ушёл из зоны видимости при обычной прокрутке. */
289
+ readonly Normal: 0;
290
+ /** Потеря фокуса окна. */
291
+ readonly Blur: 1;
292
+ /** Вкладка скрыта. */
293
+ readonly Hidden: 2;
294
+ /** Уход со страницы (`pagehide`). */
295
+ readonly PageHide: 3;
296
+ /** Элемент перестал наблюдаться. */
297
+ readonly Unobserve: 4;
298
+ /** Достигнут порог времени просмотра. */
299
+ readonly ThresholdMet: 5;
300
+ }>;
301
+ type ViewReason = (typeof ViewReason)[keyof typeof ViewReason];
302
+ /**
303
+ * Строковые коды ошибок из поля `code`.
304
+ *
305
+ * Ключи намеренно повторяют написание сервера: код из ответа API можно найти здесь
306
+ * поиском один в один, без мысленного перевода регистра.
307
+ *
308
+ * Список открыт — сервер может добавить новый код, и это не должно ломать типизацию.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * if (err.hasCode(ItdErrorCode.OTP_INVALID)) await restartOtpFlow();
313
+ * ```
314
+ */
315
+ declare const ItdErrorCode: Readonly<{
316
+ readonly BAD_REQUEST: "BAD_REQUEST";
317
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
318
+ readonly ACCESS_DENIED: "ACCESS_DENIED";
319
+ readonly ENTITY_NOT_FOUND: "ENTITY_NOT_FOUND";
320
+ readonly ENTITY_ALREADY_EXISTS: "ENTITY_ALREADY_EXISTS";
321
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
322
+ readonly BUSINESS_RULE_VIOLATION: "BUSINESS_RULE_VIOLATION";
323
+ readonly RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED";
324
+ readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
325
+ /** Сервер отвечает так на `404`, `ENTITY_NOT_FOUND` в этом случае не приходит. */
326
+ readonly NOT_FOUND: "NOT_FOUND";
327
+ /** На практике не приходит: вместо него сервер шлёт `TURNSTILE_VERIFICATION_FAILED`. */
328
+ readonly CAPTCHA_FAILED: "CAPTCHA_FAILED";
329
+ /** Капча не пройдена: токен Turnstile недействителен, просрочен или уже использован. */
330
+ readonly TURNSTILE_VERIFICATION_FAILED: "TURNSTILE_VERIFICATION_FAILED";
331
+ readonly OTP_INVALID: "OTP_INVALID";
332
+ /** `flowToken` неизвестен или просрочен — поток подтверждения нужно начинать заново. */
333
+ readonly INVALID_FLOW_TOKEN: "INVALID_FLOW_TOKEN";
334
+ readonly ACCOUNT_DEACTIVATED: "ACCOUNT_DEACTIVATED";
335
+ readonly ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED: "ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED";
336
+ readonly ACCOUNT_INVALID_CREDENTIALS: "ACCOUNT_INVALID_CREDENTIALS";
337
+ readonly ACCOUNT_TEMPORARILY_LOCKED: "ACCOUNT_TEMPORARILY_LOCKED";
338
+ readonly ACCOUNT_CURRENT_PASSWORD_INCORRECT: "ACCOUNT_CURRENT_PASSWORD_INCORRECT";
339
+ readonly SESSION_EXPIRED: "SESSION_EXPIRED";
340
+ readonly SESSION_REVOKED: "SESSION_REVOKED";
341
+ readonly SESSION_INVALID_REFRESH_TOKEN: "SESSION_INVALID_REFRESH_TOKEN";
342
+ /** Запрос обновления пришёл без cookie `refresh_token` — продлевать нечего. */
343
+ readonly REFRESH_TOKEN_MISSING: "REFRESH_TOKEN_MISSING";
344
+ /** Cookie `refresh_token` есть, но сессии за ней уже нет: отозвана или истекла. */
345
+ readonly SESSION_NOT_FOUND: "SESSION_NOT_FOUND";
346
+ readonly MISSING_FLOW_TOKEN: "MISSING_FLOW_TOKEN";
347
+ readonly PROFILE_USERNAME_TAKEN: "PROFILE_USERNAME_TAKEN";
348
+ readonly PROFILE_RESTRICTION_ACTIVE: "PROFILE_RESTRICTION_ACTIVE";
349
+ readonly PROFILE_MODIFICATION_RESTRICTED: "PROFILE_MODIFICATION_RESTRICTED";
350
+ readonly CONTENT_MODERATION_FAILED: "CONTENT_MODERATION_FAILED";
351
+ readonly FILE_TOO_LARGE: "FILE_TOO_LARGE";
352
+ readonly UNSUPPORTED_FILE_TYPE: "UNSUPPORTED_FILE_TYPE";
353
+ readonly UPLOAD_FAILED: "UPLOAD_FAILED";
354
+ readonly VIDEO_REQUIRES_VERIFICATION: "VIDEO_REQUIRES_VERIFICATION";
355
+ readonly PHONE_VERIFICATION_REQUIRED: "PHONE_VERIFICATION_REQUIRED";
356
+ readonly WRITE_ACCESS_RESTRICTED: "WRITE_ACCESS_RESTRICTED";
357
+ }>;
358
+ type ItdErrorCode = Loose<(typeof ItdErrorCode)[keyof typeof ItdErrorCode]>;
359
+
360
+ /**
361
+ * Дата и время в формате ISO-8601, например `2026-07-21T14:30:00.000Z`.
362
+ *
363
+ * Библиотека не превращает такие поля в `Date`: строку проще сравнивать, логировать
364
+ * и передавать дальше без потерь. Для разбора есть {@link toDate}.
365
+ */
366
+ type IsoDate = string;
367
+ /**
368
+ * Идентификатор пользователя — **строго UUID**.
369
+ *
370
+ * Отличается от {@link UserRef} тем, что имя пользователя здесь не подойдёт. Так помечены
371
+ * места, где API принимает только UUID: например `wallRecipientId` при постинге на чужую стену.
372
+ */
373
+ type UserId = string;
374
+ /**
375
+ * Ссылка на пользователя: **UUID либо имя пользователя**.
376
+ *
377
+ * Пути вида `/api/users/{id}` принимают оба варианта, поэтому `itd.users.get('durov')`
378
+ * работает так же, как `itd.users.get('9f1c…')`.
379
+ */
380
+ type UserRef = string;
381
+ /**
382
+ * Разметка в тексте поста.
383
+ *
384
+ * Приходит от сервера и отправляется обратно как есть — библиотека разметку не генерирует
385
+ * и не пересчитывает.
386
+ *
387
+ * Единицы `offset` и `length` в документации API не уточнены (UTF-16 или кодовые точки),
388
+ * поэтому при работе с эмодзи проверяйте результат.
389
+ */
390
+ interface Span {
391
+ /** Тип фрагмента — см. {@link SpanType}. */
392
+ type: SpanType;
393
+ /** Смещение от начала текста. */
394
+ offset: number;
395
+ /** Длина фрагмента. */
396
+ length: number;
397
+ /** Имя хэштега без решётки либо имя пользователя. */
398
+ tag?: string;
399
+ /** Адрес ссылки. Только у `link`: у него вместо `tag` отдельное поле. */
400
+ url?: string;
401
+ }
402
+ /**
403
+ * Значок-«пин» в профиле — награда или отметка платформы.
404
+ */
405
+ interface Pin {
406
+ /** Постоянный идентификатор, например `epepuy_202605_59`. */
407
+ slug: string;
408
+ /** Отображаемое название. */
409
+ name: string;
410
+ /** Описание, за что выдан. */
411
+ description: string;
412
+ /** Адрес изображения. */
413
+ url: string;
414
+ /** Когда выдан. Приходит только в списке своих пинов. */
415
+ grantedAt?: IsoDate;
416
+ }
417
+ /**
418
+ * Автор поста или комментария.
419
+ *
420
+ * Встречается внутри `post.author` и `comment.author`.
421
+ */
422
+ interface Author {
423
+ id: UserId;
424
+ username: string;
425
+ displayName: string;
426
+ /**
427
+ * **Эмодзи, а не картинка.**
428
+ *
429
+ * На итд.com аватар — это символ клана (`🩵`, `🦎`), а не адрес изображения.
430
+ * Отрисовывать его нужно как текст.
431
+ */
432
+ avatar: string;
433
+ /** Пройдена ли верификация. */
434
+ verified: boolean;
435
+ /** Активный значок профиля. Может отсутствовать. */
436
+ pin?: Pin | null;
437
+ /** Есть ли премиум-подписка (значок NUKSTA). */
438
+ hasNuksta?: boolean;
439
+ }
440
+ /**
441
+ * Участник события в уведомлении.
442
+ *
443
+ * Отличается от {@link Author} набором полей: вместо значков приходит связь с вами.
444
+ */
445
+ interface Actor {
446
+ id: UserId;
447
+ username: string;
448
+ displayName: string;
449
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
450
+ avatar: string;
451
+ /** Подписаны ли вы на этого пользователя. */
452
+ isFollowing?: boolean;
453
+ /** Подписан ли он на вас. */
454
+ isFollowedBy?: boolean;
455
+ }
456
+ /**
457
+ * Пользователь в списках.
458
+ *
459
+ * Набор полей зависит от эндпоинта: подписчики и подписки приносят `isFollowing`,
460
+ * поиск и рекомендации — `followersCount` и `hasNuksta`. Необязательные поля отражают
461
+ * это различие.
462
+ */
463
+ interface UserSummary {
464
+ id: UserId;
465
+ username: string;
466
+ displayName: string;
467
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
468
+ avatar: string;
469
+ verified: boolean;
470
+ /** Подписаны ли вы. Приходит в списках подписчиков и подписок. */
471
+ isFollowing?: boolean;
472
+ /** Есть ли премиум. Приходит в поиске и рекомендациях. */
473
+ hasNuksta?: boolean;
474
+ /** Число подписчиков. Приходит в поиске и рекомендациях. */
475
+ followersCount?: number;
476
+ }
477
+ /** Поля профиля, общие для своего и чужого. */
478
+ interface ProfileBase {
479
+ id: UserId;
480
+ username: string;
481
+ displayName: string;
482
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
483
+ avatar: string;
484
+ /** Адрес изображения-шапки либо `null`. В отличие от аватара это настоящий URL. */
485
+ banner: string | null;
486
+ /** Описание профиля. */
487
+ bio: string;
488
+ verified: boolean;
489
+ pin?: Pin | null;
490
+ /** Кто может писать на стену. */
491
+ wallAccess: WallAccess;
492
+ /** Кто видит реакции. */
493
+ likesVisibility: LikesVisibility;
494
+ followersCount: number;
495
+ followingCount: number;
496
+ postsCount: number;
497
+ createdAt: IsoDate;
498
+ }
499
+ /** Состояние подписки на премиум. */
500
+ interface SubscriptionState {
501
+ isActive: boolean;
502
+ expiresAt: IsoDate | null;
503
+ autoRenewal: boolean;
504
+ }
505
+ /**
506
+ * Свой профиль — ответ `GET /api/users/me`.
507
+ *
508
+ * Отличается от чужого наличием {@link subscription} и {@link isPhoneVerified}
509
+ * и отсутствием полей связи (`isFollowing`, `online`).
510
+ */
511
+ interface MyProfile extends ProfileBase {
512
+ /** Закрыт ли профиль. */
513
+ isPrivate: boolean;
514
+ /** Подтверждён ли телефон. Без него часть действий недоступна. */
515
+ isPhoneVerified: boolean;
516
+ /** Своя премиум-подписка. */
517
+ subscription: SubscriptionState;
518
+ }
519
+ /**
520
+ * Чужой профиль — ответ `GET /api/users/{id|username}`.
521
+ *
522
+ * Вместо своей подписки содержит связь с вами и присутствие.
523
+ */
524
+ interface PublicProfile extends ProfileBase {
525
+ hasNuksta?: boolean;
526
+ /** Закреплённый пост, если он есть. */
527
+ pinnedPostId: string | null;
528
+ /** Подписаны ли вы на него. */
529
+ isFollowing: boolean;
530
+ /** Подписан ли он на вас. */
531
+ isFollowedBy: boolean;
532
+ /** Сейчас ли пользователь в сети. */
533
+ online: boolean;
534
+ /** Когда был в сети. `null`, если скрыто настройками приватности. */
535
+ lastSeen: IsoDate | null;
536
+ }
537
+ /** Профиль: свой либо чужой. Различаются функцией {@link isMyProfile}. */
538
+ type Profile = MyProfile | PublicProfile;
539
+ /**
540
+ * Свой ли это профиль.
541
+ *
542
+ * @example
543
+ * ```ts
544
+ * if (isMyProfile(profile)) console.log(profile.subscription.isActive);
545
+ * ```
546
+ */
547
+ declare function isMyProfile(profile: Profile): profile is MyProfile;
548
+ /** Вложение поста или комментария. */
549
+ interface Attachment {
550
+ id: string;
551
+ type: AttachmentType;
552
+ /** Адрес файла на CDN. */
553
+ url: string;
554
+ /** Ширина изображения или видео в пикселях. */
555
+ width?: number;
556
+ /** Высота изображения или видео в пикселях. */
557
+ height?: number;
558
+ mimeType: string;
559
+ /** Исходное имя файла. Приходит не всегда. */
560
+ filename?: string;
561
+ /** Размер в байтах. Приходит не всегда. */
562
+ size?: number;
563
+ /** Длительность аудио или видео в секундах. */
564
+ duration?: number | null;
565
+ /** Порядковый номер во вложениях поста. */
566
+ order?: number;
567
+ }
568
+ /** Вариант ответа в опросе. */
569
+ interface PollOption {
570
+ id: string;
571
+ text: string;
572
+ /** Сколько голосов отдано за этот вариант. */
573
+ votesCount: number;
574
+ /** Порядковый номер варианта, начиная с нуля. */
575
+ position: number;
576
+ }
577
+ /** Опрос внутри поста. */
578
+ interface Poll {
579
+ id: string;
580
+ /** Пост, которому принадлежит опрос. */
581
+ postId: string;
582
+ question: string;
583
+ /** Можно ли выбрать несколько вариантов. */
584
+ multipleChoice: boolean;
585
+ options: PollOption[];
586
+ totalVotes: number;
587
+ /** Голосовали ли вы. */
588
+ hasVoted: boolean;
589
+ /** За что проголосовали вы. Пустой массив, если голоса не было. */
590
+ votedOptionIds: string[];
591
+ createdAt: IsoDate;
592
+ }
593
+ /** Пост ленты, стены или профиля. */
594
+ interface Post {
595
+ id: string;
596
+ content: string;
597
+ /** Разметка текста. Передаётся без изменений, см. {@link Span}. */
598
+ spans: Span[];
599
+ author: Author;
600
+ attachments: Attachment[];
601
+ likesCount: number;
602
+ commentsCount: number;
603
+ repostsCount: number;
604
+ viewsCount: number;
605
+ /** Чья это стена, если пост опубликован не у себя. */
606
+ wallRecipientId: UserId | null;
607
+ /** Владелец стены. Приходит не во всех ответах. */
608
+ wallRecipient?: Author | null;
609
+ /** Поставили ли вы реакцию. */
610
+ isLiked: boolean;
611
+ /** Делали ли вы репост. */
612
+ isReposted: boolean;
613
+ /** Засчитан ли просмотр. */
614
+ isViewed: boolean;
615
+ /** Ваш ли это пост. */
616
+ isOwner: boolean;
617
+ /** Исходный пост, если это репост. */
618
+ originalPost?: Post | null;
619
+ poll?: Poll | null;
620
+ /** Преобладающая реакция — эмодзи либо `null`. */
621
+ dominantEmoji?: string | null;
622
+ /** Когда пост отредактировали. `null`, если не редактировали. */
623
+ editedAt: IsoDate | null;
624
+ createdAt: IsoDate;
625
+ /**
626
+ * Служебная метка показа для телеметрии.
627
+ *
628
+ * Нужна только эндпоинтам `itd.telemetry.*`. В остальных случаях игнорируйте.
629
+ */
630
+ vs?: string;
631
+ /**
632
+ * Топовые комментарии. Приходят только в ответе `GET /api/posts/{id}`.
633
+ *
634
+ * В списках постов поле отсутствует.
635
+ */
636
+ comments?: Comment[];
637
+ }
638
+ /** На чей комментарий дан ответ. */
639
+ interface CommentReplyTo {
640
+ id: string;
641
+ username: string;
642
+ displayName: string;
643
+ }
644
+ /** Комментарий к посту или ответ на комментарий. */
645
+ interface Comment {
646
+ id: string;
647
+ /** Текст. У голосового комментария пустой. */
648
+ content: string;
649
+ author: Author;
650
+ likesCount: number;
651
+ repliesCount: number;
652
+ isLiked: boolean;
653
+ createdAt: IsoDate;
654
+ /** Вложения. У голосового — одно аудио с `mimeType: 'audio/ogg'`. */
655
+ attachments?: Attachment[];
656
+ /** Вложенные ответы. В списках приходит превью, полный список — через `itd.comments.replies()`. */
657
+ replies?: Comment[];
658
+ /** Заполнено только у ответов. */
659
+ replyTo?: CommentReplyTo;
660
+ }
661
+ /**
662
+ * Уведомление в единой форме.
663
+ *
664
+ * REST-список и SSE-поток отдают уведомления по-разному — разные имена типов, разные имена
665
+ * полей, один участник против массива. Библиотека приводит оба вида к этой структуре,
666
+ * поэтому объекты из `itd.notifications.list()` и из потока можно складывать в один список.
667
+ *
668
+ * Исходные данные не теряются: сервeрное имя типа остаётся в {@link rawType},
669
+ * а весь необработанный объект — в {@link raw}.
670
+ */
671
+ interface Notification {
672
+ id: string;
673
+ /** Канонический тип. Старые имена (`like`, `comment`) приведены к новым. */
674
+ type: NotificationType;
675
+ /** Имя типа в том виде, в каком его прислал сервер. */
676
+ rawType: string;
677
+ /** Объект события: пост, комментарий, пользователь. */
678
+ entityId: string | null;
679
+ /** Пост, которому принадлежит комментарий, если событие о комментарии. */
680
+ parentEntityId: string | null;
681
+ /** Прочитано ли уведомление. */
682
+ isRead: boolean;
683
+ /** Кто совершил действие. Для схлопнутых уведомлений — несколько человек. */
684
+ actors: Actor[];
685
+ /** Сколько участников схлопнуто в одно уведомление. Минимум 1. */
686
+ count: number;
687
+ /** Текст или заголовок объекта события. */
688
+ preview: string | null;
689
+ /** Ссылка перехода, предложенная сервером. Обычно точнее её `resolveNotificationUrl()`. */
690
+ clickUrl?: string;
691
+ createdAt: IsoDate;
692
+ /** Когда уведомление изменилось — например было прочитано. */
693
+ updatedAt: IsoDate;
694
+ /** Исходный объект как он пришёл от сервера. */
695
+ raw: unknown;
696
+ }
697
+ /** Настройки приватности профиля. */
698
+ interface PrivacySettings {
699
+ /** Закрыт ли профиль: подписка требует одобрения. */
700
+ isPrivate: boolean;
701
+ wallAccess: WallAccess;
702
+ likesVisibility: LikesVisibility;
703
+ /** Показывать ли время последнего посещения. */
704
+ showLastSeen: boolean;
705
+ }
706
+ /**
707
+ * Настройки уведомлений.
708
+ *
709
+ * Сервер отдаёт плоский объект, но исторически знает два набора имён для одних и тех же
710
+ * настроек (`likes` и `reactions`, `comments` и `replies`). При сохранении библиотека
711
+ * отправляет оба, при чтении принимает любой.
712
+ */
713
+ interface NotificationSettings {
714
+ /** Общий выключатель доставки. */
715
+ enabled: boolean;
716
+ /** Звук уведомления. */
717
+ sound: boolean;
718
+ /** Новые подписчики. */
719
+ follows: boolean;
720
+ /** Записи на вашей стене. */
721
+ wallPosts: boolean;
722
+ /** Реакции на ваши записи. */
723
+ likes: boolean;
724
+ /** Комментарии и ответы. */
725
+ comments: boolean;
726
+ /** Упоминания. */
727
+ mentions: boolean;
728
+ }
729
+ /** Активная сессия входа. */
730
+ interface Session {
731
+ id: string;
732
+ /** Та ли это сессия, из которой выполнен запрос. */
733
+ isCurrent: boolean;
734
+ createdAt: IsoDate;
735
+ lastUsedAt: IsoDate;
736
+ expiresAt: IsoDate;
737
+ ipAddress: string;
738
+ /** Код страны по IP, например `RU`. */
739
+ ipCountry: string | null;
740
+ ipCity: string | null;
741
+ deviceType: Loose<'desktop' | 'mobile'>;
742
+ osName: string | null;
743
+ osVersion: string | null;
744
+ /** Название браузера или приложения. */
745
+ clientName: string | null;
746
+ clientVersion: string | null;
747
+ deviceModel: string | null;
748
+ }
749
+ /** Состояние платной подписки и её цена. */
750
+ interface Subscription {
751
+ /** Активна ли подписка сейчас. */
752
+ active: boolean;
753
+ /** Включено ли автопродление. */
754
+ recurringEnabled: boolean;
755
+ /** Цена в рублях. */
756
+ price: number;
757
+ }
758
+ /** Сохранённый способ оплаты. */
759
+ interface PaymentMethod {
760
+ id: string;
761
+ /** Последние четыре цифры карты. */
762
+ last4?: string;
763
+ /** Платёжная система: `visa`, `mastercard`, `mir`. */
764
+ brand?: string;
765
+ /** Основной ли это способ оплаты. */
766
+ isDefault?: boolean;
767
+ expiresAt?: IsoDate | null;
768
+ }
769
+ /** Хэштег. */
770
+ interface Hashtag {
771
+ id: string;
772
+ /** Название без решётки. */
773
+ name: string;
774
+ /** Сколько постов с этим хэштегом. */
775
+ postsCount: number;
776
+ }
777
+ /** Клан в рейтинге. */
778
+ interface Clan {
779
+ /** Эмодзи клана — оно же аватар его участников. */
780
+ avatar: string;
781
+ memberCount: number;
782
+ }
783
+ /**
784
+ * Результат подписки на пользователя.
785
+ *
786
+ * @example
787
+ * ```ts
788
+ * const result = await itd.users.follow('durov');
789
+ * // { following: true, followersCount: 11 }
790
+ * ```
791
+ */
792
+ interface FollowResult {
793
+ /** Подписка оформлена. У закрытого профиля отправляется заявка, и здесь будет `false`. */
794
+ following: boolean;
795
+ /** Сколько подписчиков стало у пользователя после действия. */
796
+ followersCount?: number;
797
+ /** Статус заявки, если профиль закрыт. */
798
+ status?: Loose<'following' | 'requested'>;
799
+ }
800
+ /** Запись журнала изменений платформы. */
801
+ interface ChangelogEntry {
802
+ version: string;
803
+ date: string;
804
+ changes: string[];
805
+ }
806
+ /** Кнопка в анонсе платформы. */
807
+ interface AnnouncementButton {
808
+ title: string;
809
+ /** Оформление: `primary`, `secondary` и другие. */
810
+ style: string;
811
+ action: {
812
+ type: string;
813
+ [key: string]: unknown;
814
+ };
815
+ }
816
+ /** Анонс на главной странице платформы. */
817
+ interface Announcement {
818
+ id: string;
819
+ image: {
820
+ url: string;
821
+ width: number;
822
+ height: number;
823
+ };
824
+ title: string;
825
+ description: string;
826
+ /** Дополнительный текст мелким шрифтом. */
827
+ additional_text?: string;
828
+ buttons: AnnouncementButton[];
829
+ }
830
+ /** Баннер текущего события — виджет «портал». */
831
+ interface Portal {
832
+ active: boolean;
833
+ title: string;
834
+ url: string;
835
+ }
836
+ /** Происшествие в истории сервиса. */
837
+ interface StatusIncidentLine {
838
+ /** Вид происшествия. */
839
+ t: IncidentKind;
840
+ /**
841
+ * Готовая строка для показа: `недоступен 6 мин (12:00–12:06)`. Время московское.
842
+ * Длительность и границы интервала отдельными полями не приходят.
843
+ */
844
+ text: string;
845
+ }
846
+ /** Одни сутки в истории сервиса. */
847
+ interface StatusDay {
848
+ /** Худшее состояние за сутки. */
849
+ type: ServiceState;
850
+ /** Дата суток, `YYYY-MM-DD`. Сутки нарезаны по UTC. */
851
+ date_key: string;
852
+ /** Доступность за сутки в процентах. */
853
+ uptime: number;
854
+ /** Происшествия за сутки. */
855
+ lines: StatusIncidentLine[];
856
+ }
857
+ /** Сервис платформы и его история доступности. */
858
+ interface ServiceStatus {
859
+ /** Идентификатор: `auth`, `main`, `media` и прочие. */
860
+ id: string;
861
+ /** Отображаемое название. */
862
+ name: string;
863
+ current_status: ServiceState;
864
+ /** Пояснение к текущему состоянию, например `No downtime`. */
865
+ current_message: string;
866
+ /** Задержка последней проверки в миллисекундах. */
867
+ latency_ms: number;
868
+ /**
869
+ * Момент последней проверки. Сервер отдаёт `YYYY-MM-DD HH:mm:ss` в UTC, библиотека
870
+ * приводит значение к ISO.
871
+ */
872
+ last_checked: IsoDate;
873
+ /** Доступность за 90 суток в процентах. */
874
+ uptime_90d: number;
875
+ /**
876
+ * История по суткам. Ключ — сколько суток назад, `'0'` — сегодня.
877
+ *
878
+ * Объект разреженный: сутки без данных сервер пропускает. Ровный массив даёт
879
+ * {@link statusDays}.
880
+ */
881
+ days: Record<string, StatusDay | undefined>;
882
+ }
883
+ /** Состояние платформы — ответ `itd.platform.status()`. */
884
+ interface PlatformStatus {
885
+ /** Худшее состояние среди сервисов. */
886
+ overall_status: ServiceState;
887
+ /** Когда данные последний раз пересчитаны. */
888
+ updated_at: IsoDate;
889
+ services: ServiceStatus[];
890
+ }
891
+ /** Статус заявки на верификацию. `none` означает, что заявка не подавалась. */
892
+ interface VerificationStatus {
893
+ status: Loose<'none' | 'pending' | 'approved' | 'rejected'>;
894
+ }
895
+ /** Созданная жалоба. */
896
+ interface Report {
897
+ id: string;
898
+ createdAt: IsoDate;
899
+ }
900
+ /** Счётчики поста из `itd.posts.stats()`. */
901
+ interface PostStats {
902
+ id: string;
903
+ likesCount: number;
904
+ commentsCount: number;
905
+ repostsCount: number;
906
+ viewsCount: number;
907
+ /** Преобладающая реакция — эмодзи либо `null`. */
908
+ dominantEmoji: string | null;
909
+ }
910
+ /** Результат реакции на пост. */
911
+ interface LikeResult {
912
+ liked: boolean;
913
+ likesCount: number;
914
+ }
915
+ /** Результат закрепления поста в профиле. */
916
+ interface PinPostResult {
917
+ success: boolean;
918
+ pinnedPostId: string | null;
919
+ }
920
+ /** Закреплённые значки профиля и выбранный из них. */
921
+ interface PinsResult {
922
+ pins: Pin[];
923
+ /** Идентификатор активного значка — строка, а не объект. */
924
+ activePin: string | null;
925
+ }
926
+ /**
927
+ * Разбирает дату API в объект `Date`.
928
+ *
929
+ * @returns `null`, если строки нет или она не разбирается
930
+ *
931
+ * @example
932
+ * ```ts
933
+ * const created = toDate(post.createdAt);
934
+ * ```
935
+ */
936
+ declare function toDate(value: IsoDate | null | undefined): Date | null;
937
+ /**
938
+ * Разворачивает историю сервиса в массив на 90 суток.
939
+ * Сутки без данных становятся `null`.
940
+ *
941
+ * @returns массив, где индекс — сколько суток назад: `[0]` — сегодня
942
+ *
943
+ * @example
944
+ * ```ts
945
+ * const status = await itd.platform.status();
946
+ * const days = statusDays(status.services[0]);
947
+ *
948
+ * days[0]?.uptime; // доступность за сегодня
949
+ * days.filter((day) => day === null).length; // за сколько суток данных нет
950
+ * ```
951
+ */
952
+ declare function statusDays(service: ServiceStatus): (StatusDay | null)[];
953
+
954
+ /**
955
+ * Билдер опроса.
956
+ *
957
+ * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно
958
+ * переиспользовать, не боясь её испортить. Создаётся функцией {@link poll}.
959
+ */
960
+ declare class PollBuilder implements ItdBuilder<CreatePollInput> {
961
+ #private;
962
+ /** @internal */
963
+ readonly [BUILDER]: true;
964
+ /** @internal Создавайте билдер функцией {@link poll}. */
965
+ constructor(state: CreatePollInput);
966
+ /** Задаёт вопрос. */
967
+ question(text: string): PollBuilder;
968
+ /** Добавляет один вариант ответа. */
969
+ option(text: string): PollBuilder;
970
+ /**
971
+ * Добавляет несколько вариантов сразу.
972
+ *
973
+ * @example
974
+ * ```ts
975
+ * poll('ну как?').options('да', 'нет', 'не знаю');
976
+ * ```
977
+ */
978
+ options(...texts: string[]): PollBuilder;
979
+ /** Разрешает выбор нескольких вариантов. */
980
+ multipleChoice(enabled?: boolean): PollBuilder;
981
+ build(): CreatePollInput;
982
+ toJSON(): CreatePollInput;
983
+ }
984
+ /**
985
+ * Начинает сборку опроса.
986
+ *
987
+ * @param question вопрос; можно задать позже методом {@link PollBuilder.question}
988
+ *
989
+ * @example
990
+ * ```ts
991
+ * import { poll } from 'itd-api';
992
+ *
993
+ * const q = poll('Какой язык лучше?')
994
+ * .options('TypeScript', 'JavaScript')
995
+ * .multipleChoice();
996
+ *
997
+ * await itd.posts.create({ content: 'голосуем', poll: q });
998
+ * ```
999
+ */
1000
+ declare function poll(question?: string): PollBuilder;
1001
+ /** Что принимает параметр опроса: объект, билдер или функция-настройщик. */
1002
+ type PollInput = BuilderInput<CreatePollInput, PollBuilder>;
1003
+
1004
+ /**
1005
+ * Файл для загрузки.
1006
+ *
1007
+ * Строка означает **путь на диске** и работает только в Node, Bun и Deno — для этого
1008
+ * подключите `itd-api/node`. В браузере и React Native передавайте `File` или `Blob`.
1009
+ */
1010
+ type FileInput = Blob | ArrayBuffer | Uint8Array | string | {
1011
+ /** Содержимое файла. */
1012
+ data: Blob | ArrayBuffer | Uint8Array;
1013
+ /** Имя файла. Влияет на определение типа, если `contentType` не задан. */
1014
+ filename?: string;
1015
+ /** MIME-тип. Если не указан, определяется по расширению или по самому `Blob`. */
1016
+ contentType?: string;
1017
+ };
1018
+ /** Данные для создания опроса. */
1019
+ interface CreatePollInput {
1020
+ /** Вопрос. Не может быть пустым. */
1021
+ question: string;
1022
+ /** Варианты ответа. Требуется минимум два. */
1023
+ options: {
1024
+ text: string;
1025
+ }[];
1026
+ /** Разрешить выбор нескольких вариантов. По умолчанию `false`. */
1027
+ multipleChoice?: boolean;
1028
+ }
1029
+ /** Данные для создания поста. */
1030
+ interface CreatePostInput {
1031
+ /** Текст поста. */
1032
+ content?: string;
1033
+ /** Разметка текста. Передаётся серверу без изменений — библиотека её не генерирует. */
1034
+ spans?: Span[];
1035
+ /**
1036
+ * Чья стена, если пост публикуется не у себя.
1037
+ *
1038
+ * Требуется **UUID**: имя пользователя здесь не работает, его можно получить
1039
+ * из профиля через `itd.users.get(username)`.
1040
+ */
1041
+ wallRecipientId?: UserId | null;
1042
+ /** Идентификаторы заранее загруженных вложений. */
1043
+ attachmentIds?: string[];
1044
+ /** Файлы, которые нужно загрузить перед публикацией. Порядок сохраняется. */
1045
+ files?: FileInput[];
1046
+ /** Опрос: обычный объект, {@link PollBuilder} или функция-настройщик. */
1047
+ poll?: PollInput;
1048
+ }
1049
+ /** Данные для создания комментария или ответа. */
1050
+ interface CreateCommentInput {
1051
+ /** Текст. У голосового комментария должен быть пустым. */
1052
+ content?: string;
1053
+ /** Идентификаторы заранее загруженных вложений. */
1054
+ attachmentIds?: string[];
1055
+ /** Файлы, которые нужно загрузить перед отправкой. */
1056
+ files?: FileInput[];
1057
+ /**
1058
+ * Кому адресован ответ.
1059
+ *
1060
+ * Применимо только в `itd.comments.reply()`; в комментарии к посту поле не имеет смысла.
1061
+ */
1062
+ replyToUserId?: UserId;
1063
+ }
1064
+ /** Данные для создания жалобы. */
1065
+ interface CreateReportInput {
1066
+ /** На что жалоба. */
1067
+ targetType: ReportTargetType;
1068
+ /** Идентификатор объекта жалобы. */
1069
+ targetId: string;
1070
+ /** Причина. */
1071
+ reason: ReportReason;
1072
+ /** Пояснение в свободной форме. */
1073
+ description?: string;
1074
+ }
1075
+
1076
+ /** Внутреннее состояние {@link CommentBuilder}. */
1077
+ interface CommentState extends CreateCommentInput {
1078
+ content: string;
1079
+ attachmentIds: string[];
1080
+ files: FileInput[];
1081
+ /** Голосовой комментарий: текста быть не должно, вложение ровно одно. */
1082
+ voice: boolean;
1083
+ }
1084
+ /**
1085
+ * Билдер комментария и ответа на комментарий.
1086
+ *
1087
+ * Неизменяемый: каждый вызов возвращает новый экземпляр. Создаётся функцией {@link comment}.
1088
+ */
1089
+ declare class CommentBuilder implements ItdBuilder<CreateCommentInput> {
1090
+ #private;
1091
+ /** @internal */
1092
+ readonly [BUILDER]: true;
1093
+ /** @internal Создавайте билдер функцией {@link comment}. */
1094
+ constructor(state: CommentState);
1095
+ /** Задаёт текст комментария. */
1096
+ content(text: string): CommentBuilder;
1097
+ /** Прикладывает файл — он будет загружен перед отправкой. */
1098
+ attach(file: FileInput): CommentBuilder;
1099
+ /** Прикладывает уже загруженное вложение. */
1100
+ attachId(attachmentId: string): CommentBuilder;
1101
+ /**
1102
+ * Делает комментарий голосовым.
1103
+ *
1104
+ * Текста у такого комментария быть не должно, а вложение ровно одно — аудио в формате
1105
+ * `audio/ogg`. Так его принимает API.
1106
+ *
1107
+ * @example
1108
+ * ```ts
1109
+ * await itd.posts.comment(postId, (c) => c.voice('./answer.ogg'));
1110
+ * ```
1111
+ */
1112
+ voice(audio: FileInput): CommentBuilder;
1113
+ /**
1114
+ * Кому адресован ответ.
1115
+ *
1116
+ * Имеет смысл только в `itd.comments.reply()`; при отправке комментария к посту
1117
+ * это поле вызовет ошибку.
1118
+ */
1119
+ replyTo(userId: UserId): CommentBuilder;
1120
+ build(): CreateCommentInput;
1121
+ toJSON(): CreateCommentInput;
1122
+ }
1123
+ /**
1124
+ * Начинает сборку комментария.
1125
+ *
1126
+ * @param content текст; можно задать позже методом {@link CommentBuilder.content}
1127
+ *
1128
+ * @example
1129
+ * ```ts
1130
+ * import { comment } from 'itd-api';
1131
+ *
1132
+ * await itd.posts.comment(postId, comment('согласен').attach('./meme.png'));
1133
+ * ```
1134
+ */
1135
+ declare function comment(content?: string): CommentBuilder;
1136
+ /** Что принимает параметр комментария: объект, билдер или функция-настройщик. */
1137
+ type CommentInput = BuilderInput<CreateCommentInput, CommentBuilder>;
1138
+
1139
+ /** Внутреннее состояние {@link PostBuilder}. */
1140
+ interface PostState extends CreatePostInput {
1141
+ content: string;
1142
+ attachmentIds: string[];
1143
+ files: FileInput[];
1144
+ }
1145
+ /**
1146
+ * Билдер поста.
1147
+ *
1148
+ * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно
1149
+ * переиспользовать. Создаётся функцией {@link post}.
1150
+ *
1151
+ * @example Заготовка для нескольких постов
1152
+ * ```ts
1153
+ * const onWall = post().onWall(userId);
1154
+ *
1155
+ * await itd.posts.create(onWall.content('первый'));
1156
+ * await itd.posts.create(onWall.content('второй')); // заготовка не испорчена
1157
+ * ```
1158
+ */
1159
+ declare class PostBuilder implements ItdBuilder<CreatePostInput> {
1160
+ #private;
1161
+ /** @internal */
1162
+ readonly [BUILDER]: true;
1163
+ /** @internal Создавайте билдер функцией {@link post}. */
1164
+ constructor(state: PostState);
1165
+ /** Задаёт текст поста, заменяя прежний. */
1166
+ content(text: string): PostBuilder;
1167
+ /** Дописывает текст к уже заданному. */
1168
+ append(text: string): PostBuilder;
1169
+ /**
1170
+ * Задаёт разметку текста.
1171
+ *
1172
+ * Библиотека разметку не генерирует: хэштеги и упоминания нужно размечать самостоятельно
1173
+ * либо не размечать вовсе.
1174
+ */
1175
+ spans(spans: Span[]): PostBuilder;
1176
+ /**
1177
+ * Публикует пост на стене другого пользователя.
1178
+ *
1179
+ * @param userId **UUID** пользователя; имя пользователя не подойдёт
1180
+ */
1181
+ onWall(userId: UserId): PostBuilder;
1182
+ /**
1183
+ * Прикладывает файл — он будет загружен перед публикацией.
1184
+ *
1185
+ * Порядок вызовов сохраняется в порядке вложений.
1186
+ */
1187
+ attach(file: FileInput): PostBuilder;
1188
+ /** Прикладывает уже загруженное вложение по его идентификатору. */
1189
+ attachId(attachmentId: string): PostBuilder;
1190
+ /**
1191
+ * Добавляет опрос.
1192
+ *
1193
+ * Принимает объект, {@link PollBuilder} или функцию-настройщик.
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * post('голосуем').poll((q) => q.question('ну как?').options('да', 'нет'));
1198
+ * ```
1199
+ */
1200
+ poll(input: PollInput): PostBuilder;
1201
+ build(): CreatePostInput;
1202
+ toJSON(): CreatePostInput;
1203
+ }
1204
+ /**
1205
+ * Начинает сборку поста.
1206
+ *
1207
+ * @param content текст; можно задать позже методом {@link PostBuilder.content}
1208
+ *
1209
+ * @example
1210
+ * ```ts
1211
+ * import { post } from 'itd-api';
1212
+ *
1213
+ * await itd.posts.create(
1214
+ * post('смотрите что нашёл')
1215
+ * .attach('./photo.jpg')
1216
+ * .poll((q) => q.question('нравится?').options('да', 'нет')),
1217
+ * );
1218
+ * ```
1219
+ */
1220
+ declare function post(content?: string): PostBuilder;
1221
+ /** Что принимает параметр поста: объект, билдер или функция-настройщик. */
1222
+ type PostInput = BuilderInput<CreatePostInput, PostBuilder>;
1223
+
1224
+ /**
1225
+ * Билдер жалобы.
1226
+ *
1227
+ * Точка входа задаёт объект жалобы и его тип одновременно, поэтому рассогласовать
1228
+ * `targetType` и `targetId` невозможно. Создаётся объектом {@link report}.
1229
+ */
1230
+ declare class ReportBuilder implements ItdBuilder<CreateReportInput> {
1231
+ #private;
1232
+ /** @internal */
1233
+ readonly [BUILDER]: true;
1234
+ /** @internal Создавайте билдер через {@link report}. */
1235
+ constructor(state: Partial<CreateReportInput>);
1236
+ /** Указывает причину жалобы. */
1237
+ reason(reason: ReportReason): ReportBuilder;
1238
+ /** Добавляет пояснение в свободной форме. */
1239
+ description(text: string): ReportBuilder;
1240
+ build(): CreateReportInput;
1241
+ toJSON(): CreateReportInput;
1242
+ }
1243
+ /**
1244
+ * Начинает сборку жалобы.
1245
+ *
1246
+ * Тип объекта выбирается точкой входа, так что указать идентификатор комментария
1247
+ * с типом «пост» нельзя в принципе.
1248
+ *
1249
+ * @example
1250
+ * ```ts
1251
+ * import { report, ReportReason } from 'itd-api';
1252
+ *
1253
+ * await itd.reports.create(report.post(postId).reason(ReportReason.Spam));
1254
+ * await itd.reports.create(report.user(userId).reason('fraud').description('пишет в личку'));
1255
+ * ```
1256
+ */
1257
+ declare const report: Readonly<{
1258
+ /** Жалоба на пост. */
1259
+ post: (postId: string) => ReportBuilder;
1260
+ /** Жалоба на комментарий. */
1261
+ comment: (commentId: string) => ReportBuilder;
1262
+ /** Жалоба на пользователя. */
1263
+ user: (userId: string) => ReportBuilder;
1264
+ }>;
1265
+ /** Что принимает параметр жалобы: объект, билдер или функция-настройщик. */
1266
+ type ReportInput = BuilderInput<CreateReportInput, ReportBuilder>;
1267
+
1268
+ /**
1269
+ * Как библиотека обращается с cookie.
1270
+ *
1271
+ * - `browser` — cookie ведёт браузер, запросы уходят с `credentials: 'include'`;
1272
+ * - `server` — cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную;
1273
+ * - `auto` — определяется по среде исполнения (значение по умолчанию).
1274
+ */
1275
+ declare const RuntimeMode: Readonly<{
1276
+ /** Определяется по среде исполнения. Значение по умолчанию. */
1277
+ readonly Auto: "auto";
1278
+ /** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */
1279
+ readonly Browser: "browser";
1280
+ /** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */
1281
+ readonly Server: "server";
1282
+ }>;
1283
+ type RuntimeMode = (typeof RuntimeMode)[keyof typeof RuntimeMode];
1284
+ /** Распознанная среда исполнения. */
1285
+ declare const DetectedRuntime: Readonly<{
1286
+ readonly Browser: "browser";
1287
+ /** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */
1288
+ readonly ReactNative: "react-native";
1289
+ readonly Server: "server";
1290
+ }>;
1291
+ type DetectedRuntime = (typeof DetectedRuntime)[keyof typeof DetectedRuntime];
1292
+
1293
+ /** Сервис платформы на отдельном домене. */
1294
+ interface ServiceDefinition {
1295
+ /** Имя, по которому запрос выбирает сервис: `{ service: 'status' }`. */
1296
+ name: string;
1297
+ /** Базовый URL сервиса. */
1298
+ baseUrl: string;
1299
+ /** Заголовки, добавляемые к каждому запросу сервиса. Заголовки вызова важнее. */
1300
+ headers?: Record<string, string> | undefined;
1301
+ /**
1302
+ * Слать ли заголовок авторизации.
1303
+ *
1304
+ * По умолчанию включено для основного хоста и его поддоменов. Для остальных хостов
1305
+ * авторизацию нужно разрешить явно: `auth: true`.
1306
+ */
1307
+ auth?: boolean | undefined;
1308
+ }
1309
+ /** Именованные сервисы клиента. */
1310
+ declare class ServiceRegistry {
1311
+ #private;
1312
+ /** @param primaryBaseUrl базовый URL клиента */
1313
+ constructor(primaryBaseUrl?: string);
1314
+ /**
1315
+ * Регистрирует сервис. Имя очищается от краевых пробелов, базовый URL приводится
1316
+ * к каноничному виду, а незаданный `auth` выводится из хоста.
1317
+ *
1318
+ * @throws {ItdConfigError} если имя пустое, имя занято или `baseUrl` не абсолютный URL
1319
+ */
1320
+ define(definition: ServiceDefinition): void;
1321
+ /** Определение сервиса либо `undefined`, если такого нет. */
1322
+ get(name: string): ServiceDefinition | undefined;
1323
+ /** Зарегистрирован ли сервис с таким именем. */
1324
+ has(name: string): boolean;
1325
+ /**
1326
+ * Определение сервиса.
1327
+ *
1328
+ * @throws {ItdConfigError} если сервис не зарегистрирован
1329
+ */
1330
+ require(name: string): ServiceDefinition;
1331
+ /**
1332
+ * Базовый URL сервиса.
1333
+ *
1334
+ * @throws {ItdConfigError} если сервис не зарегистрирован
1335
+ */
1336
+ resolveBaseUrl(name: string): string;
1337
+ }
1338
+
1339
+ /**
1340
+ * Сохранённая сессия.
1341
+ *
1342
+ * Кроме токенов сюда попадают cookie: refresh-токен итд.com живёт именно в cookie, и без них
1343
+ * восстановить сессию после перезапуска процесса невозможно.
1344
+ */
1345
+ interface ItdSession {
1346
+ /** Токен доступа для заголовка `Authorization: Bearer`. */
1347
+ accessToken?: string | undefined;
1348
+ /**
1349
+ * Refresh-токен, если удалось получить его явным значением.
1350
+ *
1351
+ * Обычно сервер держит его в httpOnly-cookie и наружу не отдаёт — тогда поле останется
1352
+ * пустым, а обновление пойдёт через {@link ItdSession.cookies}.
1353
+ */
1354
+ refreshToken?: string | undefined;
1355
+ /** Сырые cookie в форме `имя=значение`, привязанные к origin API. */
1356
+ cookies?: string[] | undefined;
1357
+ /**
1358
+ * Идентификатор устройства для заголовка `X-Device-Id`.
1359
+ *
1360
+ * Сервер различает по нему записи в списке сессий, поэтому значение должно пережить
1361
+ * перезапуск процесса — иначе каждый старт бота порождает новую сессию. Библиотека
1362
+ * заводит его сама при первом запросе и хранит здесь.
1363
+ */
1364
+ deviceId?: string | undefined;
1365
+ /** Когда сессия получена, мс с начала эпохи. Нужно для диагностики. */
1366
+ obtainedAt?: number | undefined;
1367
+ }
1368
+ /**
1369
+ * Хранилище сессии.
1370
+ *
1371
+ * Подключаемый компонент: библиотека не знает, где вы держите токены, и обращается к ним
1372
+ * только через этот интерфейс. Все методы могут быть как синхронными, так и асинхронными.
1373
+ *
1374
+ * @example Своё хранилище поверх AsyncStorage в React Native
1375
+ * ```ts
1376
+ * const storage = createTokenStorage({
1377
+ * get: async () => JSON.parse((await AsyncStorage.getItem('itd')) ?? 'null'),
1378
+ * set: (session) => AsyncStorage.setItem('itd', JSON.stringify(session)),
1379
+ * clear: () => AsyncStorage.removeItem('itd'),
1380
+ * });
1381
+ * ```
1382
+ */
1383
+ interface TokenStorage {
1384
+ /** Прочитать сессию. `null`, если её нет. */
1385
+ get(): ItdSession | null | Promise<ItdSession | null>;
1386
+ /** Сохранить сессию целиком. */
1387
+ set(session: ItdSession): void | Promise<void>;
1388
+ /** Удалить сессию. Вызывается при выходе и при неудачном обновлении токена. */
1389
+ clear(): void | Promise<void>;
1390
+ }
1391
+ /**
1392
+ * Хранилище в памяти процесса — вариант по умолчанию.
1393
+ *
1394
+ * Сессия теряется при перезапуске. Для долгоживущих ботов возьмите `FileTokenStorage`
1395
+ * из `itd-api/node`, для браузера — {@link LocalStorageTokenStorage}.
1396
+ */
1397
+ declare class MemoryTokenStorage implements TokenStorage {
1398
+ #private;
1399
+ constructor(initial?: ItdSession | null);
1400
+ get(): ItdSession | null;
1401
+ set(session: ItdSession): void;
1402
+ clear(): void;
1403
+ }
1404
+ /**
1405
+ * Хранилище поверх `localStorage` браузера.
1406
+ *
1407
+ * Если `localStorage` недоступен (приватный режим, серверный рендеринг), молча работает
1408
+ * как хранилище в памяти — библиотека не должна падать из-за настроек браузера.
1409
+ *
1410
+ * Помните, что `localStorage` доступен любому скрипту на странице: не используйте его,
1411
+ * если для вашего приложения это неприемлемый риск.
1412
+ *
1413
+ * После ошибки записи или удаления хранилище переключается на память до конца
1414
+ * своего жизненного цикла.
1415
+ */
1416
+ declare class LocalStorageTokenStorage implements TokenStorage {
1417
+ #private;
1418
+ /** @param key ключ в `localStorage`. По умолчанию `itd-api:session`. */
1419
+ constructor(key?: string);
1420
+ get(): ItdSession | null;
1421
+ set(session: ItdSession): void;
1422
+ clear(): void;
1423
+ }
1424
+ /**
1425
+ * Собирает {@link TokenStorage} из трёх функций — когда заводить класс избыточно.
1426
+ *
1427
+ * @example
1428
+ * ```ts
1429
+ * const storage = createTokenStorage({
1430
+ * get: () => db.getSession(userId),
1431
+ * set: (session) => db.saveSession(userId, session),
1432
+ * clear: () => db.deleteSession(userId),
1433
+ * });
1434
+ * ```
1435
+ */
1436
+ declare function createTokenStorage(handlers: TokenStorage): TokenStorage;
1437
+
1438
+ /** Значение параметра запроса. `undefined` и `null` в строку не попадают. */
1439
+ type QueryValue = string | number | boolean | null | undefined | readonly (string | number | boolean)[];
1440
+ /** Параметры строки запроса. */
1441
+ type QueryParams = Record<string, QueryValue>;
1442
+
1443
+ /**
1444
+ * Вход по логину и паролю.
1445
+ *
1446
+ * Вход требует токен капчи Cloudflare Turnstile, поэтому полностью автоматическим он быть
1447
+ * не может: капчу должен решить кто-то снаружи. Токен одноразовый и живёт несколько минут,
1448
+ * так что долгоживущему клиенту нужен `getTurnstileToken` — он спрашивается заново перед
1449
+ * каждой попыткой входа. Одиночный `turnstileToken` годится для разового скрипта.
1450
+ */
1451
+ interface CredentialsAuth {
1452
+ email: string;
1453
+ password: string;
1454
+ /** Разовый токен капчи. Для повторного входа после истечения сессии не подойдёт. */
1455
+ turnstileToken?: string | undefined;
1456
+ /** Источник свежего токена капчи. Спрашивается перед каждым входом. */
1457
+ getTurnstileToken?: (() => string | Promise<string>) | undefined;
1458
+ }
1459
+ /**
1460
+ * Как клиент получает доступ к API.
1461
+ *
1462
+ * Четыре формы — от разового вызова с готовым токеном до полноценной сессии, которую
1463
+ * библиотека заводит и продлевает сама.
1464
+ *
1465
+ * Опция необязательна: если {@link ItdClientOptions.storage} уже содержит сессию, доступ
1466
+ * берётся оттуда. Когда заданы обе, хранилище главнее — оно отражает текущее состояние
1467
+ * сессии, — а недостающие поля берутся отсюда.
1468
+ *
1469
+ * @example
1470
+ * ```ts
1471
+ * new ItdClient({ auth: '<accessToken>' }); // разовый вызов
1472
+ * new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию
1473
+ * new ItdClient({ auth: { email, password, getTurnstileToken } }); // залогиниться самому
1474
+ * new ItdClient({ auth: { getToken: () => vault.read() } }); // токен из внешнего источника
1475
+ * ```
1476
+ */
1477
+ type AuthInput = string | {
1478
+ accessToken: string;
1479
+ refreshToken?: string | undefined;
1480
+ } | CredentialsAuth | {
1481
+ getToken: () => string | null | Promise<string | null>;
1482
+ };
1483
+ /** Куда библиотека пишет отладочные сообщения. Совместим с `console`. */
1484
+ interface Logger {
1485
+ debug(message: string, ...args: unknown[]): void;
1486
+ info(message: string, ...args: unknown[]): void;
1487
+ warn(message: string, ...args: unknown[]): void;
1488
+ error(message: string, ...args: unknown[]): void;
1489
+ }
1490
+ /** Настройки повторных попыток. */
1491
+ interface RetryOptions {
1492
+ /** Сколько всего попыток, включая первую. По умолчанию 3. */
1493
+ attempts?: number | undefined;
1494
+ /** Базовая пауза в мс, удваивается с каждой попыткой. По умолчанию 500. */
1495
+ baseDelay?: number | undefined;
1496
+ /** Верхняя граница паузы в мс. По умолчанию 30000. */
1497
+ maxDelay?: number | undefined;
1498
+ /** Доля случайного разброса паузы, 0…1. По умолчанию 0.3. */
1499
+ jitter?: number | undefined;
1500
+ /**
1501
+ * Повторять ли запись (`POST`, `PUT`, `PATCH`, `DELETE`) при сетевых сбоях и `5xx`.
1502
+ *
1503
+ * По умолчанию `false`: сервер мог успеть выполнить операцию до обрыва, и повтор
1504
+ * создаст дубль поста или лишнюю жалобу. Ответ `429` повторяется всегда — он гарантирует,
1505
+ * что запрос не был обработан.
1506
+ */
1507
+ retryWrites?: boolean | undefined;
1508
+ /** Своя логика: вернуть `true`, чтобы повторить. Заменяет правила по умолчанию. */
1509
+ shouldRetry?: ((error: unknown, attempt: number) => boolean) | undefined;
1510
+ }
1511
+ /** Настройки ограничения нагрузки на API. */
1512
+ interface RateLimitOptions {
1513
+ /** Сколько запросов выполняется одновременно. По умолчанию 6. */
1514
+ concurrency?: number | undefined;
1515
+ /** Верхняя граница запросов в секунду. По умолчанию без ограничения. */
1516
+ rps?: number | undefined;
1517
+ /**
1518
+ * Паузы перед повторами при ответе `429`, мс.
1519
+ * По умолчанию `[1000, 5000, 30000, 60000, 90000]`.
1520
+ *
1521
+ * Сервер не сообщает, когда сбросится окно лимита, поэтому паузу приходится подбирать.
1522
+ * Лестница начинается с секунды: если окно почти истекло, работа продолжится почти
1523
+ * сразу, а если лимит исчерпан всерьёз — паузы дорастут до полутора минут.
1524
+ * Когда лестница закончилась, {@link ItdRateLimitError} пробрасывается вызывающему коду.
1525
+ *
1526
+ * Этот список не зависит от `retry.attempts`: тот управляет повторами при обрывах
1527
+ * сети и ошибках сервера, где уместен совсем другой темп.
1528
+ */
1529
+ retryDelays?: readonly number[] | undefined;
1530
+ /**
1531
+ * Тормозить ли очередь по заголовкам ответа. По умолчанию `true`.
1532
+ *
1533
+ * Выключите, если управляете темпом сами.
1534
+ */
1535
+ respectHeaders?: boolean | undefined;
1536
+ }
1537
+ /** Данные о запросе, доступные хукам. */
1538
+ interface RequestContext {
1539
+ method: string;
1540
+ /** Путь без базового URL, например `/api/posts`. */
1541
+ path: string;
1542
+ /** Итоговый URL со строкой запроса. */
1543
+ url: string;
1544
+ headers: Headers;
1545
+ /** Номер попытки, начиная с 1. */
1546
+ attempt: number;
1547
+ }
1548
+ /** Данные об успешном ответе. */
1549
+ interface ResponseContext extends RequestContext {
1550
+ status: number;
1551
+ /** Длительность запроса в мс. */
1552
+ duration: number;
1553
+ response: Response;
1554
+ }
1555
+ /** Данные об ошибке запроса. */
1556
+ interface ErrorContextHook extends RequestContext {
1557
+ duration: number;
1558
+ error: unknown;
1559
+ }
1560
+ /** Данные о предстоящем повторе. */
1561
+ interface RetryContext extends RequestContext {
1562
+ error: unknown;
1563
+ /** Пауза перед следующей попыткой в мс. */
1564
+ delay: number;
1565
+ }
1566
+ /**
1567
+ * Перехватчики жизненного цикла запроса.
1568
+ *
1569
+ * Вызываются последовательно; исключение внутри хука прервёт запрос, поэтому свою логику
1570
+ * лучше оборачивать в `try`.
1571
+ */
1572
+ interface ClientHooks {
1573
+ /** Перед отправкой. Можно дописать заголовки — объект `headers` изменяемый. */
1574
+ onRequest?(context: RequestContext): void | Promise<void>;
1575
+ /** После успешного ответа, до разбора тела. */
1576
+ onResponse?(context: ResponseContext): void | Promise<void>;
1577
+ /** При любой ошибке запроса, включая те, что будут повторены. */
1578
+ onError?(context: ErrorContextHook): void | Promise<void>;
1579
+ /** Перед паузой между попытками. */
1580
+ onRetry?(context: RetryContext): void | Promise<void>;
1581
+ }
1582
+ /**
1583
+ * Опции конструктора `ItdClient`.
1584
+ *
1585
+ * Все поля допускают явный `undefined`, чтобы можно было передавать значения, которых
1586
+ * может не быть, — например `new ItdClient({ auth: process.env.ITD_TOKEN })`.
1587
+ */
1588
+ interface ItdClientOptions {
1589
+ /**
1590
+ * Базовый URL API. По умолчанию `https://xn--d1ah4a.com`.
1591
+ *
1592
+ * Укажите здесь адрес своего прокси, если работаете из браузера: CORS для сторонних
1593
+ * источников на итд.com, скорее всего, не настроен.
1594
+ */
1595
+ baseUrl?: string | undefined;
1596
+ /**
1597
+ * Сервисы платформы на отдельных доменах.
1598
+ *
1599
+ * Ключ — имя сервиса, значение — базовый URL или определение целиком. Имя встроенного
1600
+ * сервиса задаёт его хост. Встроен один: `status` — хост `itd.platform.status()`.
1601
+ *
1602
+ * @example
1603
+ * ```ts
1604
+ * const itd = new ItdClient({
1605
+ * services: {
1606
+ * status: 'https://my-proxy.example/status',
1607
+ * pb: { baseUrl: 'https://pbapi.xn--d1ah4a.com', headers: { Referer: 'https://pixel.xn--d1ah4a.com/' } },
1608
+ * },
1609
+ * });
1610
+ * ```
1611
+ */
1612
+ services?: Record<string, string | Omit<ServiceDefinition, 'name'>> | undefined;
1613
+ /** Авторизация. Без неё доступны только публичные эндпоинты. */
1614
+ auth?: AuthInput | undefined;
1615
+ /** Где хранить сессию. По умолчанию {@link MemoryTokenStorage}. */
1616
+ storage?: TokenStorage | undefined;
1617
+ /**
1618
+ * Обновлять токен автоматически при ответе `401`. По умолчанию `true`.
1619
+ *
1620
+ * При `false` библиотека просто пробросит {@link ItdAuthError}, а обновлением
1621
+ * вы управляете сами через `itd.auth.refresh()`.
1622
+ */
1623
+ autoRefresh?: boolean | undefined;
1624
+ /**
1625
+ * Пытаться ли войти заново, если обновление токена не удалось.
1626
+ *
1627
+ * Работает, только когда в `auth` переданы email и пароль. По умолчанию `true`.
1628
+ */
1629
+ reloginOnRefreshFailure?: boolean | undefined;
1630
+ /** Своя реализация `fetch`: для Deno, React Native, тестов или прокси. */
1631
+ fetch?: typeof fetch | undefined;
1632
+ /** Таймаут запроса в мс. По умолчанию 30000 — столько же использует сайт итд.com. `0` снимает ограничение. */
1633
+ timeout?: number | undefined;
1634
+ /** Повторные попытки. `false` отключает их полностью. */
1635
+ retry?: RetryOptions | false | undefined;
1636
+ /** Ограничение нагрузки. `false` отключает очередь. */
1637
+ rateLimit?: RateLimitOptions | false | undefined;
1638
+ /** Перехватчики запросов. */
1639
+ hooks?: ClientHooks | undefined;
1640
+ /** Отладочный вывод. `true` — писать в `console`. */
1641
+ logger?: Logger | boolean | undefined;
1642
+ /** Заголовки, добавляемые ко всем запросам, — например `User-Agent` для бота. */
1643
+ headers?: Record<string, string> | undefined;
1644
+ /**
1645
+ * Значение заголовка `X-Device-Id`, который уходит с каждым запросом.
1646
+ *
1647
+ * Сервер различает по нему записи в списке сессий, поэтому значение должно быть стабильным.
1648
+ * Если не задать, библиотека заведёт идентификатор сама и сохранит его в {@link ItdSession},
1649
+ * так что при постоянном хранилище он переживёт перезапуск процесса.
1650
+ */
1651
+ deviceId?: string | undefined;
1652
+ /**
1653
+ * Значение заголовка `User-Agent`. `false` — не отправлять его вовсе.
1654
+ *
1655
+ * По умолчанию `Mozilla/5.0 (compatible; itd-api/<версия>; …)`: `fetch` в Node не шлёт
1656
+ * `User-Agent` сам, а сайт стоит за DDoS-Guard, который такие запросы может не пропустить.
1657
+ * В браузере опция не действует — там заголовок менять запрещено.
1658
+ */
1659
+ userAgent?: string | false | undefined;
1660
+ /** Как обращаться с cookie. По умолчанию определяется по среде исполнения. */
1661
+ mode?: RuntimeMode | undefined;
1662
+ }
1663
+ /** Опции отдельного запроса. Доступны в каждом методе ресурсов. */
1664
+ interface RequestOptions {
1665
+ /** Отмена запроса извне. */
1666
+ signal?: AbortSignal | undefined;
1667
+ /** Таймаут только для этого запроса, мс. */
1668
+ timeout?: number | undefined;
1669
+ /** Дополнительные заголовки. */
1670
+ headers?: Record<string, string> | undefined;
1671
+ /** Повторы только для этого запроса. Переопределяют глобальную настройку `retry`. */
1672
+ retry?: RetryOptions | false | undefined;
1673
+ }
1674
+ /**
1675
+ * Имена полей {@link RequestOptions} — единственный источник истины.
1676
+ *
1677
+ * Ресурсы переносят в описание запроса только эти поля (плюс заявленные плагинами),
1678
+ * потому что параметры методов подмешивают к ним `limit`, `cursor` и прочее, чему
1679
+ * в транспорте делать нечего. Список стоит рядом с интерфейсом, чтобы новое поле нельзя
1680
+ * было забыть.
1681
+ *
1682
+ * `satisfies` гарантирует, что каждое имя в списке — действительно поле `RequestOptions`.
1683
+ * Обратную полноту (не забыто ли новое поле) проверяет тип {@link RequestOptionKeysComplete}
1684
+ * в тесте: здесь её проверять нельзя — плагины расширяют `RequestOptions` своими опциями
1685
+ * (`encrypt`, `decrypt` и подобными), которых в этом списке быть и не должно.
1686
+ */
1687
+ declare const REQUEST_OPTION_KEYS: readonly ["signal", "timeout", "headers", "retry"];
1688
+ /** Полное описание запроса для низкоуровневого `itd.request()`. */
1689
+ interface RawRequestOptions extends RequestOptions {
1690
+ method: string;
1691
+ /** Путь с ведущим слэшем, например `/api/posts`. Завершающий слэш значим. */
1692
+ path: string;
1693
+ /**
1694
+ * Имя сервиса, на хост которого уйдёт запрос. Без него запрос идёт на основной `baseUrl`
1695
+ * клиента. Сервисы задаются опцией {@link ItdClientOptions.services}.
1696
+ */
1697
+ service?: string | undefined;
1698
+ /** Хост этого запроса. Важнее, чем {@link RawRequestOptions.service}. */
1699
+ baseUrl?: string | undefined;
1700
+ query?: QueryParams | undefined;
1701
+ /** Тело: будет отправлено как JSON. Для загрузки файлов передайте `FormData`. */
1702
+ body?: unknown;
1703
+ /** Не подставлять заголовок авторизации. */
1704
+ skipAuth?: boolean | undefined;
1705
+ /** Не пытаться обновить токен при `401` — используется самими эндпоинтами авторизации. */
1706
+ skipAuthRefresh?: boolean | undefined;
1707
+ /**
1708
+ * Выполнить запрос мимо очереди.
1709
+ *
1710
+ * Нужно запросам, которые слой авторизации делает **изнутри** другого запроса: продление
1711
+ * токена и отложенный вход. Такой запрос обязан стартовать, пока исходный держит место
1712
+ * в очереди и ждёт его результата, — иначе оба ждут друг друга и не завершатся никогда.
1713
+ */
1714
+ skipQueue?: boolean | undefined;
1715
+ /** Вернуть тело ответа без снятия обёртки `{ data: … }`. */
1716
+ raw?: boolean | undefined;
1717
+ }
1718
+
1719
+ /** Версия библиотеки. Попадает в `User-Agent`. */
1720
+ declare const LIBRARY_VERSION = "0.0.9";
1721
+
1722
+ /** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
1723
+ declare const DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
1724
+ /** Базовый URL страницы статуса. Домен записан в punycode: `статус.итд.com`. */
1725
+ declare const DEFAULT_STATUS_BASE_URL = "https://xn--80a7abcbg.xn--d1ah4a.com";
1726
+ /** Имя встроенного сервиса статуса. */
1727
+ declare const STATUS_SERVICE = "status";
1728
+ /** Сервисы, зарегистрированные у любого клиента. */
1729
+ declare const BUILT_IN_SERVICES: readonly ServiceDefinition[];
1730
+ /** Таймаут запроса по умолчанию. Столько же использует официальный клиент итд.com. */
1731
+ declare const DEFAULT_TIMEOUT = 30000;
1732
+
1733
+ /**
1734
+ * `User-Agent` по умолчанию.
1735
+ *
1736
+ * Сайт стоит за DDoS-Guard, и запросы вовсе без `User-Agent` (так делает `fetch` в Node)
1737
+ * имеют шанс не пройти фильтр. Префикс `Mozilla/5.0` — дань традиции таких фильтров,
1738
+ * дальше идёт честное имя библиотеки: подделываться под браузер она не должна.
1739
+ *
1740
+ * В браузере заголовок не выставляется — `User-Agent` там запрещён к изменению, и среда
1741
+ * молча его игнорирует.
1742
+ */
1743
+ declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; itd-api/0.0.9; +https://github.com/KiowDev/itd-api)";
1744
+ /**
1745
+ * Срез конфигурации, нужный слою авторизации.
1746
+ *
1747
+ * Выделен явно, чтобы {@link AuthManager} не получал целиком `ResolvedConfig` со всеми
1748
+ * настройками транспорта, повторов и очереди, к которым он отношения не имеет.
1749
+ * `ResolvedConfig` содержит все эти поля, поэтому подходит везде, где ждут `AuthConfig`.
1750
+ */
1751
+ interface AuthConfig {
1752
+ baseUrl: string;
1753
+ auth: AuthInput | undefined;
1754
+ storage: TokenStorage;
1755
+ useCookieJar: boolean;
1756
+ deviceId: string | undefined;
1757
+ reloginOnRefreshFailure: boolean;
1758
+ logger: Logger | undefined;
1759
+ }
1760
+
1761
+ /** Имя cookie-флага «есть refresh-сессия». Ставится сайтом итд.com рядом с refresh-токеном. */
1762
+ declare const AUTH_FLAG_COOKIE = "is_auth";
1763
+ /**
1764
+ * Имя cookie с refresh-токеном.
1765
+ *
1766
+ * `POST /api/v1/auth/refresh` читает токен **только отсюда** — тело запроса сервер игнорирует.
1767
+ * Cookie помечена `HttpOnly`, поэтому в браузере её не прочитать и не выставить; вне браузера
1768
+ * она проходит через jar, и туда же кладётся токен, переданный строкой.
1769
+ */
1770
+ declare const REFRESH_COOKIE = "refresh_token";
1771
+ /** Путь, которым сервер ограничивает {@link REFRESH_COOKIE}. */
1772
+ declare const REFRESH_COOKIE_PATH = "/api/v1/auth";
1773
+ /**
1774
+ * Минимальное хранилище cookie для сред без своего.
1775
+ *
1776
+ * Refresh-токен итд.com приходит в `Set-Cookie`, а `fetch` вне браузера cookie не хранит —
1777
+ * без jar сессию не продлить. В браузере и React Native не используется: там cookie ведёт
1778
+ * сама среда.
1779
+ *
1780
+ * Реализована практическая часть RFC 6265: origin, путь, срок жизни, флаг `Secure`.
1781
+ * Доменные cookie для поддоменов намеренно не поддерживаются — API работает с одного хоста.
1782
+ */
1783
+ declare class CookieJar {
1784
+ #private;
1785
+ /**
1786
+ * Забирает `Set-Cookie` из ответа.
1787
+ *
1788
+ * Использует `Headers.getSetCookie()`, где он есть (Node 20+, undici). В остальных средах
1789
+ * заголовки склеены в одну строку, и её нельзя резать по запятой напрямую: запятая есть
1790
+ * внутри `Expires=Wed, 09 Jun 2027 …`. Разделением занимается `set-cookie-parser`.
1791
+ */
1792
+ setFromResponse(url: string, response: Response): void;
1793
+ /**
1794
+ * Кладёт cookie напрямую, минуя `Set-Cookie`.
1795
+ *
1796
+ * Нужно ровно в одном случае: пользователь передал refresh-токен строкой, а сервер читает
1797
+ * его только из cookie. Значение не кодируется — оно уходит в заголовок как есть.
1798
+ */
1799
+ set(url: string, name: string, value: string, path?: string): void;
1800
+ /** Сохраняет cookie из готовых строк `Set-Cookie`. */
1801
+ setFromStrings(url: string, setCookieStrings: string[]): void;
1802
+ /**
1803
+ * Собирает значение заголовка `Cookie` для запроса.
1804
+ *
1805
+ * @returns строка вида `a=1; b=2` либо `undefined`, если подходящих cookie нет
1806
+ */
1807
+ getHeader(url: string): string | undefined;
1808
+ /**
1809
+ * Значение действующей cookie.
1810
+ *
1811
+ * Нужно, чтобы забрать обновлённый refresh-токен: сервер ротирует его при каждом
1812
+ * продлении сессии, и сохранять надо именно новое значение.
1813
+ *
1814
+ * @param url если указан, учитываются origin, путь и флаг `Secure`
1815
+ */
1816
+ getValue(name: string, url?: string): string | undefined;
1817
+ /**
1818
+ * Есть ли действующая cookie с таким именем.
1819
+ *
1820
+ * Используется для проверки флага {@link AUTH_FLAG_COOKIE} перед запросом обновления
1821
+ * токена: у анонима refresh-сессии нет, и дёргать API незачем.
1822
+ */
1823
+ has(name: string, url?: string): boolean;
1824
+ /** Сохраняет содержимое jar для записи в {@link TokenStorage}. */
1825
+ serialize(): string[];
1826
+ /** Восстанавливает jar из результата {@link serialize}. Некорректные записи молча пропускаются. */
1827
+ deserialize(entries: readonly string[] | undefined): void;
1828
+ /** Удаляет все cookie. */
1829
+ clear(): void;
1830
+ }
1831
+
1832
+ /** Обработчик события. */
1833
+ type Listener<T> = (payload: T) => void;
1834
+ /** Функция отписки, которую возвращает подписка на событие. */
1835
+ type Unsubscribe = () => void;
1836
+ /**
1837
+ * Минимальный типизированный источник событий.
1838
+ *
1839
+ * Своя реализация вместо `EventTarget` и `EventEmitter`: первый есть не везде и требует
1840
+ * обёрток `CustomEvent`, второй существует только в Node. Нужны ровно подписка и рассылка.
1841
+ *
1842
+ * Исключение в обработчике не прерывает рассылку остальным и не роняет библиотеку.
1843
+ *
1844
+ * @typeParam Events карта «имя события → тип полезной нагрузки». Задаётся интерфейсом,
1845
+ * поэтому ограничение на индексную сигнатуру намеренно не накладывается.
1846
+ */
1847
+ declare class Emitter<Events> {
1848
+ #private;
1849
+ constructor(onListenerError?: (error: unknown) => void);
1850
+ /**
1851
+ * Подписывается на событие.
1852
+ *
1853
+ * @returns функция отписки
1854
+ *
1855
+ * @example
1856
+ * ```ts
1857
+ * const off = realtime.on('notification', (event) => console.log(event));
1858
+ * off();
1859
+ * ```
1860
+ */
1861
+ on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe;
1862
+ /** Подписывается на одно срабатывание. */
1863
+ once<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe;
1864
+ /** Отписывается от события. */
1865
+ off<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void;
1866
+ /** Рассылает событие подписчикам. */
1867
+ emit<K extends keyof Events>(event: K, payload: Events[K]): void;
1868
+ /** Сколько подписчиков у события. */
1869
+ listenerCount(event: keyof Events): number;
1870
+ /** Снимает все подписки. */
1871
+ removeAllListeners(): void;
1872
+ }
1873
+
1874
+ /**
1875
+ * Описание запроса внутри конвейера.
1876
+ *
1877
+ * Отличается от публичного {@link RawRequestOptions} одним служебным полем: слои конвейера
1878
+ * должны уметь дописать заголовки так, чтобы пользовательские `headers` всё равно остались
1879
+ * важнее. Смешивать их в одном объекте нельзя — тогда слой авторизации перебивал бы
1880
+ * `Authorization`, заданный вызывающим кодом вручную.
1881
+ */
1882
+ interface PipelineRequest extends RawRequestOptions {
1883
+ /**
1884
+ * Заголовки, добавленные слоями конвейера.
1885
+ *
1886
+ * Ставятся до пользовательских `headers` и потому могут быть ими переопределены.
1887
+ *
1888
+ * @internal
1889
+ */
1890
+ layerHeaders?: Record<string, string> | undefined;
1891
+ /**
1892
+ * Номер попытки, начиная с 1. Проставляет слой повторов, читают хуки.
1893
+ *
1894
+ * @internal
1895
+ */
1896
+ attempt?: number | undefined;
1897
+ }
1898
+ /** Обработчик запроса. Самый внутренний в цепочке — транспорт. */
1899
+ type RequestHandler = (request: PipelineRequest) => Promise<unknown>;
1900
+
1901
+ /** Пути эндпоинтов авторизации. */
1902
+ declare const AUTH_PATHS: {
1903
+ readonly signUp: "/api/v1/auth/sign-up";
1904
+ readonly signIn: "/api/v1/auth/sign-in";
1905
+ readonly verifyOtp: "/api/v1/auth/verify-otp";
1906
+ readonly resendOtp: "/api/v1/auth/resend-otp";
1907
+ readonly refresh: "/api/v1/auth/refresh";
1908
+ readonly logout: "/api/v1/auth/logout";
1909
+ readonly forgotPassword: "/api/v1/auth/forgot-password";
1910
+ readonly resetPassword: "/api/v1/auth/reset-password";
1911
+ readonly changePassword: "/api/v1/auth/change-password";
1912
+ readonly sessions: "/api/v1/auth/sessions";
1913
+ /** Префикс внешнего входа: к нему дописывается имя провайдера. */
1914
+ readonly oauthLogin: "/api/v1/auth/login";
1915
+ };
1916
+ /**
1917
+ * Публичный ключ Cloudflare Turnstile платформы итд.com.
1918
+ *
1919
+ * Нужен, чтобы отрисовать виджет капчи и получить токен для `signIn`, `signUp`
1920
+ * и `forgotPassword`.
1921
+ *
1922
+ * @example
1923
+ * ```ts
1924
+ * turnstile.render('#captcha', {
1925
+ * sitekey: TURNSTILE_SITE_KEY,
1926
+ * callback: (turnstileToken) => itd.auth.signIn({ email, password, turnstileToken }),
1927
+ * });
1928
+ * ```
1929
+ */
1930
+ declare const TURNSTILE_SITE_KEY = "0x4AAAAAACHhxczw6fJGwPBg";
1931
+ /** Заголовок с идентификатором устройства. Сервер связывает с ним запись в списке сессий. */
1932
+ declare const DEVICE_ID_HEADER = "X-Device-Id";
1933
+ /** События слоя авторизации. */
1934
+ interface AuthEvents {
1935
+ /** Токен получен или обновлён. */
1936
+ tokens: {
1937
+ accessToken: string;
1938
+ };
1939
+ /** Выполнен вход. */
1940
+ signIn: {
1941
+ accessToken: string;
1942
+ };
1943
+ /** Сессия очищена — вручную или из-за неудачного обновления. */
1944
+ signOut: undefined;
1945
+ /** Обновить сессию не удалось; дальнейшие запросы будут падать с 401. */
1946
+ authError: {
1947
+ error: unknown;
1948
+ };
1949
+ }
1950
+ /**
1951
+ * Хранит сессию и продлевает её.
1952
+ *
1953
+ * Главное здесь — **дедупликация обновления**. Когда десять параллельных запросов
1954
+ * одновременно получают `401`, обновление должно произойти один раз, а остальные обязаны
1955
+ * дождаться его результата. Иначе сервер увидит десять параллельных `refresh`, и все,
1956
+ * кроме первого, скорее всего получат отказ по уже использованному токену.
1957
+ */
1958
+ declare class AuthManager {
1959
+ #private;
1960
+ constructor(config: AuthConfig, send: RequestHandler, jar: CookieJar);
1961
+ /** Подписка на события авторизации. */
1962
+ get on(): Emitter<AuthEvents>['on'];
1963
+ /** Подписка на одно срабатывание. */
1964
+ get once(): Emitter<AuthEvents>['once'];
1965
+ /**
1966
+ * Есть ли признак живой refresh-сессии.
1967
+ *
1968
+ * Рядом с refresh-токеном сервер ставит незакрытую cookie `is_auth` — по ней видно,
1969
+ * что продлевать сессию вообще есть смысл, и API не дёргается у анонимов.
1970
+ * В браузере cookie ведёт сама среда, поэтому там ответ всегда `true`.
1971
+ *
1972
+ * Асинхронный, потому что признак может лежать в {@link TokenStorage}: до чтения оттуда
1973
+ * ответ был бы `false` даже при полностью рабочей сохранённой сессии.
1974
+ */
1975
+ hasRefreshSession(): Promise<boolean>;
1976
+ /** Заголовки авторизации для очередного запроса. Пустой объект, если токена нет. */
1977
+ getAuthHeaders(): Promise<Record<string, string>>;
1978
+ /**
1979
+ * Идентификатор устройства для заголовка `X-Device-Id`.
1980
+ *
1981
+ * Заводится один раз и сохраняется в сессии, чтобы пережить перезапуск процесса:
1982
+ * сервер связывает с ним запись в списке сессий, и плавающее значение плодило бы
1983
+ * по новой сессии на каждый старт.
1984
+ */
1985
+ getDeviceId(): Promise<string>;
1986
+ /**
1987
+ * Текущий токен доступа.
1988
+ *
1989
+ * При необходимости выполняет отложенный вход: если в конфигурации переданы логин
1990
+ * и пароль, первый же запрос сам заведёт сессию.
1991
+ */
1992
+ getAccessToken(): Promise<string | null>;
1993
+ /**
1994
+ * Реакция транспорта на ответ `401`.
1995
+ *
1996
+ * @returns `true`, если токен обновлён и запрос имеет смысл повторить
1997
+ */
1998
+ onUnauthorized(): Promise<boolean>;
1999
+ /**
2000
+ * Обновляет токен доступа.
2001
+ *
2002
+ * Параллельные вызовы объединяются в один сетевой запрос.
2003
+ *
2004
+ * @throws {ItdAuthError} если обновить сессию не удалось
2005
+ */
2006
+ refresh(): Promise<string>;
2007
+ /** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
2008
+ setAccessToken(accessToken: string): Promise<void>;
2009
+ /** Текущая сессия целиком. Полезно, чтобы сохранить её самому. */
2010
+ getSession(): Promise<ItdSession | null>;
2011
+ /** Заменяет сессию и связанные с ней cookie целиком. */
2012
+ setSession(session: ItdSession): Promise<void>;
2013
+ /**
2014
+ * Забывает сессию и cookie. Сетевой запрос не выполняется.
2015
+ *
2016
+ * Идентификатор устройства выход переживает: иначе каждая пара «выход — вход» плодила бы
2017
+ * новую запись в списке сессий.
2018
+ */
2019
+ clear(): Promise<void>;
2020
+ }
2021
+
2022
+ /**
2023
+ * Обёртка вокруг запроса.
2024
+ *
2025
+ * Получает описание запроса и продолжение цепочки. Может изменить запрос перед отправкой,
2026
+ * посмотреть и подменить разобранный ответ или вовсе не вызывать `next` и вернуть своё.
2027
+ *
2028
+ * @param request что уходит на сервер; изменять сам объект не нужно — передайте копию в `next`
2029
+ * @param next продолжение: либо следующая обёртка, либо настоящий запрос
2030
+ * @returns тело ответа в том виде, в каком его получит вызывающий код
2031
+ *
2032
+ * @example Дописать заголовок ко всем запросам
2033
+ * ```ts
2034
+ * const transformer: Transformer = (request, next) =>
2035
+ * next({ ...request, headers: { ...request.headers, 'X-Trace': trace() } });
2036
+ * ```
2037
+ */
2038
+ type Transformer = (request: RawRequestOptions, next: (request: RawRequestOptions) => Promise<unknown>) => Promise<unknown>;
2039
+ /** Что плагин получает при подключении. */
2040
+ interface PluginContext {
2041
+ /** Базовый URL клиента — например чтобы разобрать абсолютные ссылки из ответа. */
2042
+ baseUrl: string;
2043
+ /** Отладочный вывод клиента, если он включён. */
2044
+ logger: Logger | undefined;
2045
+ /** Добавляет обёртку запроса. Подключённые раньше оказываются снаружи. */
2046
+ use(transformer: Transformer): void;
2047
+ }
2048
+ /**
2049
+ * Плагин клиента.
2050
+ *
2051
+ * Подключается через `itd.use(plugin)` и работает на уровне транспорта: видит запрос
2052
+ * до отправки и разобранный ответ. Библиотека не знает, что именно делает плагин, —
2053
+ * ей достаточно списка обёрток и имён опций, которые он читает.
2054
+ *
2055
+ * @example
2056
+ * ```ts
2057
+ * const logging: ItdPlugin = {
2058
+ * name: 'logging',
2059
+ * install({ use, logger }) {
2060
+ * use(async (request, next) => {
2061
+ * logger?.info(`${request.method} ${request.path}`);
2062
+ * return next(request);
2063
+ * });
2064
+ * },
2065
+ * };
2066
+ *
2067
+ * itd.use(logging);
2068
+ * ```
2069
+ */
2070
+ interface ItdPlugin {
2071
+ /** Имя плагина. Должно быть уникальным: повторное подключение — ошибка. */
2072
+ name: string;
2073
+ /**
2074
+ * Имена опций запроса, которые плагин читает у методов ресурсов.
2075
+ *
2076
+ * Библиотека этих опций не понимает и ничего с ними не делает — только доносит
2077
+ * от вызова метода до обёртки нетронутыми. Без такого списка чужие поля отсеиваются,
2078
+ * чтобы случайная опечатка в параметрах не уезжала на сервер.
2079
+ *
2080
+ * Имена полей самого запроса (`path`, `body`, `headers`, `signal` и прочие из
2081
+ * `RawRequestOptions`) заявить нельзя: подключение такого плагина завершится ошибкой.
2082
+ *
2083
+ * Типы для них плагин объявляет сам, дополняя `RequestOptions`:
2084
+ * ```ts
2085
+ * declare module 'itd-api' {
2086
+ * interface RequestOptions { encrypt?: string | undefined }
2087
+ * }
2088
+ * ```
2089
+ */
2090
+ optionKeys?: readonly string[];
2091
+ /** Вызывается один раз при подключении. */
2092
+ install(context: PluginContext): void;
2093
+ }
2094
+ /**
2095
+ * Список подключённых плагинов и собранная из них цепочка обёрток.
2096
+ *
2097
+ * Живёт в клиенте, а работает в транспорте: {@link HttpClient} прогоняет через `run`
2098
+ * каждый запрос, если плагины есть.
2099
+ */
2100
+ declare class PluginRegistry {
2101
+ #private;
2102
+ /** Сколько обёрток подключено. Ноль означает, что запрос идёт прежним путём. */
2103
+ get size(): number;
2104
+ /** Имена опций запроса, заявленные плагинами. */
2105
+ get optionKeys(): ReadonlySet<string>;
2106
+ /**
2107
+ * Подключает плагин.
2108
+ *
2109
+ * @throws {ItdConfigError} если плагин задан неверно, уже подключён или заявил занятое
2110
+ * имя опции
2111
+ */
2112
+ add(plugin: ItdPlugin, context: Omit<PluginContext, 'use'>): void;
2113
+ /**
2114
+ * Прогоняет запрос через цепочку обёрток.
2115
+ *
2116
+ * Цепочка собирается на каждый запрос заново: плагин можно подключить в любой момент,
2117
+ * а обёрток единицы — экономить тут не на чем.
2118
+ *
2119
+ * @param execute настоящий запрос, вызывается самой внутренней обёрткой
2120
+ */
2121
+ run(request: RawRequestOptions, execute: (request: RawRequestOptions) => Promise<unknown>): Promise<unknown>;
2122
+ }
2123
+
2124
+ /** Событие потока уведомлений после разбора. */
2125
+ interface NotificationEvent {
2126
+ /** Само уведомление в единой форме. */
2127
+ notification: Notification;
2128
+ /**
2129
+ * Актуальное число непрочитанных, если сервер его сообщил.
2130
+ *
2131
+ * Клиент не увеличивает счётчик сам: значение приходит с сервера.
2132
+ */
2133
+ unreadCount: number | undefined;
2134
+ /** Нужно ли проиграть звук. */
2135
+ sound: boolean;
2136
+ }
2137
+ /**
2138
+ * Приводит уведомление к единой форме.
2139
+ *
2140
+ * Нужна потому, что REST-список и поток событий описывают одно и то же событие по-разному:
2141
+ * различаются имена типов (`like` против `post_reaction`), имена полей
2142
+ * (`targetId`/`entityId`, `read`/`isRead`, `preview`/`entityPreview`) и число участников
2143
+ * (`actor` против массива `actors`). После приведения объекты из обоих источников
2144
+ * можно складывать в один список.
2145
+ *
2146
+ * Исходные данные не теряются: имя типа с сервера остаётся в `rawType`,
2147
+ * весь объект целиком — в `raw`.
2148
+ *
2149
+ * @param input уведомление из REST-ответа либо полезная нагрузка события потока
2150
+ *
2151
+ * @example
2152
+ * ```ts
2153
+ * const fromRest = normalizeNotification(restItem);
2154
+ * const fromStream = normalizeNotification(event.payload);
2155
+ * // одинаковая форма — можно объединять
2156
+ * ```
2157
+ */
2158
+ declare function normalizeNotification(input: unknown): Notification;
2159
+ /**
2160
+ * Разбирает событие `notification` из потока.
2161
+ *
2162
+ * Кроме самого уведомления событие несёт служебные поля уровня конверта: актуальный
2163
+ * счётчик непрочитанных и признак звука.
2164
+ */
2165
+ declare function readNotificationEvent(data: unknown): NotificationEvent;
2166
+ /**
2167
+ * Разбирает событие `unread_count` из потока.
2168
+ *
2169
+ * Возвращает `undefined`, если сервер прислал событие без вложенного `payload`.
2170
+ * Официальный клиент в этом случае **обнуляет** счётчик — это ошибка, из-за которой
2171
+ * непрочитанные пропадают из интерфейса.
2172
+ */
2173
+ declare function readUnreadCountEvent(data: unknown): number | undefined;
2174
+
2175
+ /**
2176
+ * Паузы перед попытками переподключения, мс.
2177
+ *
2178
+ * Значения совпадают с теми, что использует сайт итд.com, — поведение библиотеки
2179
+ * не отличается от привычного пользователю.
2180
+ */
2181
+ declare const RECONNECT_BACKOFF: readonly number[];
2182
+ /** Доля случайного разброса паузы. */
2183
+ declare const RECONNECT_JITTER = 0.3;
2184
+ /**
2185
+ * Сколько раз пытаться переподключиться подряд.
2186
+ *
2187
+ * После исчерпания поток сообщает `giveup` и ждёт ручного `connect()`.
2188
+ */
2189
+ declare const MAX_RECONNECT_ATTEMPTS = 15;
2190
+ /** Настройки переподключения. */
2191
+ interface ReconnectOptions {
2192
+ /** Таблица пауз. Последнее значение действует для всех дальнейших попыток. */
2193
+ backoff?: readonly number[];
2194
+ /** Доля разброса, 0…1. */
2195
+ jitter?: number;
2196
+ /** Предел числа попыток. */
2197
+ maxAttempts?: number;
2198
+ }
2199
+
2200
+ /** Событие, пришедшее по каналу реального времени. */
2201
+ interface TransportEvent {
2202
+ /** Имя события: `notification`, `unread_count` и другие. */
2203
+ name: string;
2204
+ /** Полезная нагрузка, уже разобранная из JSON. */
2205
+ data: unknown;
2206
+ }
2207
+ /** Что транспорт получает от клиента при подключении. */
2208
+ interface TransportContext {
2209
+ /** Базовый URL API. */
2210
+ baseUrl: string;
2211
+ /** Реализация `fetch`. */
2212
+ fetch: typeof fetch;
2213
+ /**
2214
+ * Общие заголовки клиента: `User-Agent`, `X-Device-Id`, заголовки конфигурации
2215
+ * и cookie для указанного адреса.
2216
+ */
2217
+ baseHeaders: (url: string) => Promise<Headers>;
2218
+ /** Текущий токен доступа. */
2219
+ getToken: () => Promise<string | null>;
2220
+ /** Отмена подключения. */
2221
+ signal: AbortSignal;
2222
+ /** Сообщает о полученном событии. */
2223
+ onEvent: (event: TransportEvent) => void;
2224
+ /** Сообщает о разобранном, но некорректном сообщении. Соединение при этом живёт. */
2225
+ onParseError: (error: unknown, raw: string) => void;
2226
+ /** Вызывается, когда соединение установлено. */
2227
+ onOpen: () => void;
2228
+ }
2229
+ /**
2230
+ * Канал получения событий в реальном времени.
2231
+ *
2232
+ * Сейчас у платформы один такой канал — поток `text/event-stream`. Абстракция нужна
2233
+ * на будущее: политика безопасности сайта уже разрешает `wss://*.xn--d1ah4a.com`,
2234
+ * и когда появится WebSocket, достаточно будет добавить ещё одну реализацию этого
2235
+ * интерфейса. Переподключение, обновление токена и разбор уведомлений от транспорта
2236
+ * не зависят.
2237
+ */
2238
+ interface RealtimeTransport {
2239
+ /** Понятное имя для логов и диагностики. */
2240
+ readonly name: string;
2241
+ /**
2242
+ * Держит соединение, пока оно живо.
2243
+ *
2244
+ * Должен завершиться, когда поток закрылся, и бросить исключение при ошибке.
2245
+ * Отмена через `context.signal` должна приводить к `AbortError`.
2246
+ */
2247
+ connect(context: TransportContext): Promise<void>;
2248
+ }
2249
+ /** Ошибка, по которой видно, что сервер отверг авторизацию потока. */
2250
+ declare class UnauthorizedStreamError extends Error {
2251
+ constructor();
2252
+ }
2253
+
2254
+ /** События потока уведомлений. */
2255
+ interface RealtimeEvents {
2256
+ /** Пришло новое уведомление. */
2257
+ notification: NotificationEvent;
2258
+ /**
2259
+ * Сервер подтвердил подключение и назвал получателя событий.
2260
+ *
2261
+ * Приходит первым кадром сразу после установки соединения.
2262
+ */
2263
+ ready: {
2264
+ userId: string | undefined;
2265
+ };
2266
+ /**
2267
+ * Сервер сообщил актуальное число непрочитанных.
2268
+ *
2269
+ * На практике сервер этого не делает: за всё наблюдение он не прислал ни одного
2270
+ * такого кадра, а в уведомлениях нет поля со счётчиком. Держите счётчик сами
2271
+ * либо запрашивайте `itd.notifications.count()`.
2272
+ */
2273
+ unreadCount: number;
2274
+ /** Изменилось состояние соединения. */
2275
+ status: RealtimeStatus;
2276
+ /** Соединение оборвалось; будет предпринята попытка переподключения. */
2277
+ error: {
2278
+ error: unknown;
2279
+ willReconnect: boolean;
2280
+ };
2281
+ /** Сообщение не удалось разобрать. Соединение при этом продолжает работать. */
2282
+ parseError: {
2283
+ error: unknown;
2284
+ raw: string;
2285
+ };
2286
+ /** Запланировано переподключение. */
2287
+ reconnect: {
2288
+ attempt: number;
2289
+ delay: number;
2290
+ };
2291
+ /** Попытки исчерпаны — соединение восстановится только ручным `connect()`. */
2292
+ giveup: undefined;
2293
+ /** Любое событие потока в необработанном виде, включая неизвестные библиотеке. */
2294
+ message: {
2295
+ name: string;
2296
+ data: unknown;
2297
+ };
2298
+ }
2299
+ /** Способ получения событий. */
2300
+ declare const RealtimeTransportKind: Readonly<{
2301
+ /** Поток событий, если среда умеет читать тело по частям, иначе опрос. */
2302
+ readonly Auto: "auto";
2303
+ /** Поток `text/event-stream`. */
2304
+ readonly Sse: "sse";
2305
+ /** Периодический опрос REST. */
2306
+ readonly Poll: "poll";
2307
+ }>;
2308
+ type RealtimeTransportKind = (typeof RealtimeTransportKind)[keyof typeof RealtimeTransportKind];
2309
+ /** Настройки потока уведомлений. */
2310
+ interface RealtimeOptions extends ReconnectOptions {
2311
+ /**
2312
+ * Транспорт. По умолчанию `auto`: поток событий, если среда умеет читать тело ответа
2313
+ * по частям, иначе опрос.
2314
+ *
2315
+ * Можно передать и свою реализацию {@link RealtimeTransport} — это пригодится, если
2316
+ * у платформы появится WebSocket либо нужен нестандартный способ доставки.
2317
+ */
2318
+ transport?: RealtimeTransportKind | RealtimeTransport;
2319
+ /**
2320
+ * Молчание сервера, после которого соединение считается мёртвым, мс. По умолчанию 90 000.
2321
+ *
2322
+ * Сервер не присылает keep-alive, поэтому без этой проверки оборванное соединение
2323
+ * может незаметно «зависнуть».
2324
+ */
2325
+ idleTimeout?: number;
2326
+ /** Как часто опрашивать сервер, если используется запасной транспорт. */
2327
+ pollInterval?: number;
2328
+ /**
2329
+ * Запрашивать число непрочитанных при подключении. По умолчанию `true`.
2330
+ *
2331
+ * Так поступает сайт итд.com: поток присылает только новые события, а начальное
2332
+ * значение счётчика нужно получить отдельно.
2333
+ */
2334
+ syncCount?: boolean;
2335
+ /**
2336
+ * Переподключаться, когда вкладка снова становится видимой. По умолчанию `true`.
2337
+ *
2338
+ * Только в браузере. У сайта итд.com такой обработки нет, из-за чего вкладка,
2339
+ * пролежавшая в фоне, может остаться без соединения.
2340
+ */
2341
+ reconnectOnVisible?: boolean;
2342
+ /** Переподключаться при восстановлении сети. По умолчанию `true`. Только в браузере. */
2343
+ reconnectOnOnline?: boolean;
2344
+ }
2345
+ /** Что поток получает от клиента. */
2346
+ interface RealtimeDeps {
2347
+ baseUrl: string;
2348
+ fetch: typeof fetch;
2349
+ /** Общие заголовки клиента для адреса — см. {@link TransportContext.baseHeaders}. */
2350
+ baseHeaders: (url: string) => Promise<Headers>;
2351
+ getToken: () => Promise<string | null>;
2352
+ /** Обновляет токен после отказа авторизации. Возвращает `true`, если удалось. */
2353
+ refresh: () => Promise<boolean>;
2354
+ /** Загружает начальное число непрочитанных. */
2355
+ fetchUnreadCount: () => Promise<number>;
2356
+ /** Вызывается при явном закрытии потока. */
2357
+ onClose?: (() => void) | undefined;
2358
+ logger?: Logger | undefined;
2359
+ }
2360
+ /**
2361
+ * Поток уведомлений в реальном времени.
2362
+ *
2363
+ * Получается вызовом `itd.realtime()`. Соединение поднимается методом {@link connect}
2364
+ * и держится само: обрывы, обновление токена и повторные попытки библиотека берёт на себя.
2365
+ *
2366
+ * @example
2367
+ * ```ts
2368
+ * const stream = itd.realtime();
2369
+ *
2370
+ * stream.on('notification', ({ notification, unreadCount }) => {
2371
+ * console.log(formatNotificationText(notification), unreadCount);
2372
+ * });
2373
+ * stream.on('status', (status) => console.log('соединение:', status));
2374
+ *
2375
+ * await stream.connect();
2376
+ * // …позже
2377
+ * stream.disconnect();
2378
+ * ```
2379
+ */
2380
+ declare class ItdRealtime {
2381
+ #private;
2382
+ constructor(deps: RealtimeDeps, options?: RealtimeOptions);
2383
+ /** Текущее состояние соединения. */
2384
+ get status(): RealtimeStatus;
2385
+ /** Какой транспорт используется: `sse` или `poll`. */
2386
+ get transport(): string;
2387
+ /** Подписывается на событие потока. @returns функция отписки */
2388
+ on<K extends keyof RealtimeEvents>(event: K, listener: Listener<RealtimeEvents[K]>): Unsubscribe;
2389
+ /** Подписывается на одно срабатывание. */
2390
+ once<K extends keyof RealtimeEvents>(event: K, listener: Listener<RealtimeEvents[K]>): Unsubscribe;
2391
+ /**
2392
+ * Поднимает соединение.
2393
+ *
2394
+ * Повторный вызов при уже живом соединении ничего не делает — это защита от двойного
2395
+ * подключения при перерисовке интерфейса.
2396
+ *
2397
+ * Возвращает управление сразу после запуска: соединение живёт в фоне.
2398
+ */
2399
+ connect(): Promise<void>;
2400
+ /** Закрывает соединение и отменяет запланированные попытки. */
2401
+ disconnect(): void;
2402
+ /** Снимает все подписки. Соединение при этом не закрывается. */
2403
+ removeAllListeners(): void;
2404
+ }
2405
+
2406
+ /** Что нужно фасаду для работы. */
2407
+ interface HttpClientDeps {
2408
+ /** Готовый обработчик — вся цепочка слоёв поверх транспорта. */
2409
+ handler: RequestHandler;
2410
+ /** Реестр плагинов: у него ресурсы спрашивают имена заявленных опций. */
2411
+ plugins: PluginRegistry;
2412
+ baseUrl: string;
2413
+ }
2414
+ /**
2415
+ * Точка входа ресурсов в конвейер запросов.
2416
+ *
2417
+ * Принимает готовый обработчик — цепочку слоёв поверх транспорта, собранную
2418
+ * в {@link ItdClient}, — и отдаёт ресурсам метод `request` и имена опций плагинов.
2419
+ * О слоях и их порядке ресурсы не знают.
2420
+ */
2421
+ declare class HttpClient {
2422
+ #private;
2423
+ constructor(deps: HttpClientDeps);
2424
+ /** Базовый URL, к которому обращается клиент. */
2425
+ get baseUrl(): string;
2426
+ /**
2427
+ * Имена опций запроса, заявленные плагинами.
2428
+ *
2429
+ * Читается ресурсами: они переносят в транспорт только известные поля, а чужие,
2430
+ * если их никто не заявил, отсеивают.
2431
+ */
2432
+ get pluginOptionKeys(): ReadonlySet<string>;
2433
+ /**
2434
+ * Выполняет запрос к API через собранный конвейер.
2435
+ *
2436
+ * @typeParam T ожидаемая форма ответа после снятия обёртки `{ data: … }`
2437
+ * @throws {ItdApiError} если сервер ответил статусом ≥ 400
2438
+ * @throws {ItdTimeoutError} если истёк таймаут
2439
+ * @throws {ItdAbortError} если запрос отменён через `signal`
2440
+ * @throws {ItdNetworkError} если запрос не дошёл до сервера
2441
+ */
2442
+ request<T = unknown>(options: RawRequestOptions): Promise<T>;
2443
+ }
2444
+
2445
+ /**
2446
+ * Страница списка — единая форма для всех трёх схем пагинации API.
2447
+ *
2448
+ * Какие необязательные поля заполнены, зависит от эндпоинта: у ленты это `nextCursor`,
2449
+ * у подписчиков — `page` и `total`, у уведомлений — `nextOffset`. Обычно они не нужны:
2450
+ * перебор берёт на себя {@link Paginator}.
2451
+ */
2452
+ interface Page<T> {
2453
+ /** Элементы страницы. */
2454
+ items: T[];
2455
+ /** Есть ли следующая страница. */
2456
+ hasMore: boolean;
2457
+ /**
2458
+ * Курсор следующей страницы.
2459
+ *
2460
+ * Непрозрачен: у вкладки `popular` это номер страницы, у `following` — отметка времени.
2461
+ * Передавайте его обратно как есть и не пытайтесь разобрать.
2462
+ */
2463
+ nextCursor?: string | null | undefined;
2464
+ /** Номер текущей страницы при постраничной схеме. */
2465
+ page?: number | undefined;
2466
+ /** Запрошенный размер страницы. */
2467
+ limit?: number | undefined;
2468
+ /** Общее число элементов, если сервер его сообщил. */
2469
+ total?: number | undefined;
2470
+ /** Смещение для следующего запроса при схеме со смещением. */
2471
+ nextOffset?: number | undefined;
2472
+ /** Исходный ответ — на случай, если документация разошлась с реальностью. */
2473
+ raw: unknown;
2474
+ }
2475
+ /** Схема пагинации эндпоинта. */
2476
+ declare const PaginationMode: Readonly<{
2477
+ /** Следующая страница запрашивается непрозрачным курсором. */
2478
+ readonly Cursor: "cursor";
2479
+ /** Следующая страница запрашивается номером. */
2480
+ readonly Page: "page";
2481
+ /** Следующая страница запрашивается смещением от начала списка. */
2482
+ readonly Offset: "offset";
2483
+ }>;
2484
+ type PaginationMode = (typeof PaginationMode)[keyof typeof PaginationMode];
2485
+ /** Применяет преобразование к элементам страницы, сохраняя сведения о пагинации. */
2486
+ declare function mapPage<T, R>(page: Page<T>, map: (item: T) => R): Page<R>;
2487
+ /** Позиция, с которой запрашивается очередная страница. */
2488
+ interface PageState {
2489
+ cursor?: string | undefined;
2490
+ page?: number | undefined;
2491
+ offset?: number | undefined;
2492
+ }
2493
+ /** Настройки перебора страниц. */
2494
+ interface PaginatorOptions<T> {
2495
+ mode: PaginationMode;
2496
+ /** Загружает одну страницу для указанной позиции. */
2497
+ load: (state: PageState) => Promise<Page<T>>;
2498
+ /**
2499
+ * С какой позиции начать. По умолчанию с начала списка.
2500
+ *
2501
+ * Нужно, чтобы перебор можно было продолжить с сохранённого курсора, а не только
2502
+ * начать заново.
2503
+ */
2504
+ start?: PageState | undefined;
2505
+ /**
2506
+ * Предохранитель от бесконечного перебора. По умолчанию 1000.
2507
+ *
2508
+ * Сработает, только если сервер бесконечно сообщает `hasMore` — при нормальной работе
2509
+ * перебор останавливается сам.
2510
+ */
2511
+ maxPages?: number | undefined;
2512
+ /** Отмена перебора. */
2513
+ signal?: AbortSignal | undefined;
2514
+ }
2515
+ /**
2516
+ * Перебор страниц списка.
2517
+ *
2518
+ * Скрывает различия трёх схем пагинации: перебор элементов, страниц и сбор в массив
2519
+ * выглядят одинаково независимо от эндпоинта.
2520
+ *
2521
+ * **Одноразовый.** Позиция хранится внутри, поэтому повторный перебор того же объекта
2522
+ * ничего не вернёт: он продолжится с места, где закончился прошлый. Нужен второй проход —
2523
+ * возьмите новый перебор у того же метода ресурса.
2524
+ *
2525
+ * @example Перебор элементов
2526
+ * ```ts
2527
+ * for await (const post of itd.posts.iterate({ tab: 'following' })) {
2528
+ * console.log(post.content);
2529
+ * }
2530
+ * ```
2531
+ *
2532
+ * @example Первые сто элементов
2533
+ * ```ts
2534
+ * const posts = await itd.posts.iterate({ tab: 'popular' }).collect(100);
2535
+ * ```
2536
+ *
2537
+ * @example Постранично
2538
+ * ```ts
2539
+ * for await (const page of itd.users.followers('durov').pages()) {
2540
+ * console.log(page.items.length, 'из', page.total);
2541
+ * }
2542
+ * ```
2543
+ */
2544
+ declare class Paginator<T> implements AsyncIterable<T> {
2545
+ #private;
2546
+ constructor(options: PaginatorOptions<T>);
2547
+ /**
2548
+ * Загружает следующую страницу.
2549
+ *
2550
+ * @returns страница либо `null`, если перебор закончен
2551
+ */
2552
+ next(): Promise<Page<T> | null>;
2553
+ /**
2554
+ * Перебирает страницы целиком.
2555
+ *
2556
+ * Полезно, когда нужны сведения о самой странице — например `total`.
2557
+ */
2558
+ pages(): AsyncGenerator<Page<T>, void, undefined>;
2559
+ /** Перебирает элементы всех страниц подряд. */
2560
+ [Symbol.asyncIterator](): AsyncGenerator<T, void, undefined>;
2561
+ /**
2562
+ * Собирает элементы в массив.
2563
+ *
2564
+ * @param max сколько элементов достаточно; без него перебираются все страницы
2565
+ */
2566
+ collect(max?: number): Promise<T[]>;
2567
+ }
2568
+
2569
+ /** Параметры перебираемого списка: опции запроса плюс предел числа страниц. */
2570
+ interface ListParams extends RequestOptions {
2571
+ /** Ограничение числа страниц при переборе. */
2572
+ maxPages?: number | undefined;
2573
+ }
2574
+ /**
2575
+ * Описание перебираемого эндпоинта.
2576
+ *
2577
+ * Одно место, где заданы путь, параметры запроса, чтение страницы и схема пагинации.
2578
+ * {@link BaseResource.paginated} строит из него и разовую загрузку, и перебор.
2579
+ *
2580
+ * @typeParam T тип элемента списка
2581
+ * @typeParam P тип параметров метода
2582
+ */
2583
+ interface ListingSpec<T, P extends ListParams> {
2584
+ /** Путь эндпоинта. */
2585
+ path: (params: P) => string;
2586
+ /** Параметры запроса без полей пагинации — их добавит перебор. */
2587
+ query: (params: P) => QueryParams;
2588
+ /** Читает страницу из ответа. Получает позицию — она нужна схеме со смещением. */
2589
+ read: (body: unknown, state: PageState) => Page<T>;
2590
+ /** Схема пагинации эндпоинта. */
2591
+ mode: PaginationMode;
2592
+ /** Начальная позиция, вычисленная из параметров (курсор, номер или смещение). */
2593
+ start: (params: P) => PageState;
2594
+ }
2595
+ /** Пара методов, собранная из {@link ListingSpec}: разовая загрузка и перебор. */
2596
+ interface Listing<T, P extends ListParams> {
2597
+ /** Загружает одну страницу с позиции, заданной параметрами. */
2598
+ list(params: P): Promise<Page<T>>;
2599
+ /** Перебирает страницы, сама подставляя позиции. */
2600
+ iterate(params: P): Paginator<T>;
2601
+ }
2602
+ /** Общая основа всех групп методов клиента. */
2603
+ declare class BaseResource {
2604
+ /** @internal */
2605
+ protected readonly http: HttpClient;
2606
+ constructor(http: HttpClient);
2607
+ /**
2608
+ * Переносит опции запроса в описание транспорта.
2609
+ *
2610
+ * Копируются только поля {@link REQUEST_OPTION_KEYS} и опции, заявленные плагинами:
2611
+ * параметры методов наследуют {@link RequestOptions} и приносят с собой `limit`, `cursor`
2612
+ * и прочее, чему в описании запроса делать нечего. Чужие опции плагинов библиотека
2613
+ * не понимает, но обязана донести до обёрток нетронутыми.
2614
+ */
2615
+ protected requestOptions(options: RequestOptions | undefined): Partial<RequestOptions>;
2616
+ /**
2617
+ * Собирает перебор страниц.
2618
+ *
2619
+ * @param mode схема пагинации эндпоинта
2620
+ * @param load загружает одну страницу для указанной позиции
2621
+ * @param options `maxPages` и `signal`, а также `start` — позиция, с которой продолжить
2622
+ */
2623
+ protected paginate<T>(mode: PaginationMode, load: (state: PageState) => Promise<Page<T>>, options?: RequestOptions & {
2624
+ maxPages?: number;
2625
+ start?: PageState;
2626
+ }): Paginator<T>;
2627
+ /**
2628
+ * Собирает пару «загрузка страницы + перебор» из одного описания.
2629
+ *
2630
+ * Путь, параметры запроса и разбор ответа задаются один раз; `list` и `iterate`
2631
+ * строятся из них.
2632
+ *
2633
+ * @example
2634
+ * ```ts
2635
+ * #feed = this.paginated<Post, FeedParams>({
2636
+ * path: () => '/api/posts',
2637
+ * query: (p) => ({ tab: p.tab, limit: p.limit }),
2638
+ * start: (p) => (p.cursor ? { cursor: p.cursor } : {}),
2639
+ * read: (body) => readCursorPage<Post>(body, 'posts'),
2640
+ * mode: PaginationMode.Cursor,
2641
+ * });
2642
+ * ```
2643
+ */
2644
+ protected paginated<T, P extends ListParams>(spec: ListingSpec<T, P>): Listing<T, P>;
2645
+ }
2646
+
2647
+ /** Учётные данные для входа. */
2648
+ interface Credentials {
2649
+ email: string;
2650
+ password: string;
2651
+ }
2652
+ /**
2653
+ * Учётные данные вместе с токеном капчи.
2654
+ *
2655
+ * `turnstileToken` обязателен: без него сервер отвечает `VALIDATION_ERROR`, с недействительным —
2656
+ * `TURNSTILE_VERIFICATION_FAILED`. Токен даёт виджет Cloudflare Turnstile с ключом
2657
+ * {@link TURNSTILE_SITE_KEY}; он одноразовый и живёт несколько минут.
2658
+ */
2659
+ interface CaptchaCredentials extends Credentials {
2660
+ turnstileToken: string;
2661
+ }
2662
+ /** Запрос письма для сброса пароля. Капча обязательна, как и при входе. */
2663
+ interface ForgotPasswordInput {
2664
+ email: string;
2665
+ turnstileToken: string;
2666
+ }
2667
+ /**
2668
+ * Установка нового пароля.
2669
+ *
2670
+ * Сброс идёт тем же потоком с кодом, что и вход: {@link AuthResource.forgotPassword} выдаёт
2671
+ * `flowToken`, письмо приносит `otp`, и всё это вместе с новым паролем уходит сюда.
2672
+ */
2673
+ interface ResetPasswordInput {
2674
+ email: string;
2675
+ otp: string;
2676
+ flowToken: string;
2677
+ newPassword: string;
2678
+ }
2679
+ /** Провайдер внешнего входа. */
2680
+ declare const OAuthProvider: Readonly<{
2681
+ readonly Yandex: "yandex";
2682
+ readonly Google: "google";
2683
+ }>;
2684
+ type OAuthProvider = (typeof OAuthProvider)[keyof typeof OAuthProvider];
2685
+ /** Чем закончился вход: сразу токеном или запросом кода подтверждения. */
2686
+ declare const SignInStatus: Readonly<{
2687
+ readonly Authenticated: "authenticated";
2688
+ readonly OtpRequired: "otp_required";
2689
+ }>;
2690
+ type SignInStatus = (typeof SignInStatus)[keyof typeof SignInStatus];
2691
+ /**
2692
+ * Результат входа.
2693
+ *
2694
+ * Сервер может как сразу выдать токен, так и потребовать код подтверждения — размеченное
2695
+ * объединение делает оба случая явными.
2696
+ */
2697
+ type SignInResult = {
2698
+ status: 'authenticated';
2699
+ accessToken: string;
2700
+ } | {
2701
+ status: 'otp_required';
2702
+ flowToken: string | undefined;
2703
+ };
2704
+ /**
2705
+ * Авторизация, сессии и пароли.
2706
+ *
2707
+ * Доступна как `itd.auth`.
2708
+ */
2709
+ declare class AuthResource extends BaseResource {
2710
+ #private;
2711
+ constructor(http: HttpClient, deps: {
2712
+ auth: AuthManager;
2713
+ });
2714
+ /**
2715
+ * Регистрирует аккаунт и запускает подтверждение по коду.
2716
+ *
2717
+ * @returns `flowToken`, который нужно передать в {@link verifyOtp}
2718
+ */
2719
+ signUp(credentials: CaptchaCredentials, options?: RequestOptions): Promise<string>;
2720
+ /**
2721
+ * Выполняет вход.
2722
+ *
2723
+ * Если сервер потребовал код подтверждения, вернётся `status: 'otp_required'` —
2724
+ * тогда продолжайте через {@link verifyOtp} либо воспользуйтесь {@link signInWithOtp}.
2725
+ *
2726
+ * При успешном входе токен сохраняется в клиенте автоматически.
2727
+ *
2728
+ * @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
2729
+ */
2730
+ signIn(credentials: CaptchaCredentials, options?: RequestOptions): Promise<SignInResult>;
2731
+ /**
2732
+ * Подтверждает вход кодом из письма.
2733
+ *
2734
+ * Полученный токен сохраняется в клиенте автоматически.
2735
+ */
2736
+ verifyOtp(input: Credentials & {
2737
+ otp: string;
2738
+ flowToken: string;
2739
+ }, options?: RequestOptions): Promise<string>;
2740
+ /** Отправляет код подтверждения повторно. */
2741
+ resendOtp(input: {
2742
+ email: string;
2743
+ flowToken: string;
2744
+ }, options?: RequestOptions): Promise<void>;
2745
+ /**
2746
+ * Полный вход с подтверждением по коду.
2747
+ *
2748
+ * Удобно для скриптов и ботов: код запрашивается функцией `getOtp`, а всё остальное
2749
+ * библиотека делает сама.
2750
+ *
2751
+ * @example
2752
+ * ```ts
2753
+ * import { createInterface } from 'node:readline/promises';
2754
+ *
2755
+ * const rl = createInterface({ input: process.stdin, output: process.stdout });
2756
+ *
2757
+ * const token = await itd.auth.signInWithOtp({
2758
+ * email, password,
2759
+ * getOtp: () => rl.question('Код из письма: '),
2760
+ * });
2761
+ * ```
2762
+ */
2763
+ signInWithOtp(input: CaptchaCredentials & {
2764
+ getOtp: () => string | Promise<string>;
2765
+ }, options?: RequestOptions): Promise<string>;
2766
+ /**
2767
+ * Обновляет токен доступа.
2768
+ *
2769
+ * Параллельные вызовы объединяются в один сетевой запрос. При включённом `autoRefresh`
2770
+ * вызывать вручную обычно не нужно.
2771
+ */
2772
+ refresh(): Promise<string>;
2773
+ /**
2774
+ * Есть ли признак живой сессии обновления.
2775
+ *
2776
+ * Проверяет cookie `is_auth`, которую сервер ставит рядом с refresh-токеном, а также
2777
+ * refresh-токен, переданный строкой. Позволяет не дёргать API у неавторизованного
2778
+ * пользователя. В браузере всегда `true`: cookie ведёт сама среда, и прочитать её
2779
+ * из JS нельзя.
2780
+ *
2781
+ * Читает {@link TokenStorage}, поэтому результат верен и до первого запроса.
2782
+ *
2783
+ * @example
2784
+ * ```ts
2785
+ * if (await itd.auth.hasRefreshSession()) await itd.auth.refresh();
2786
+ * else redirectToLogin();
2787
+ * ```
2788
+ */
2789
+ hasRefreshSession(): Promise<boolean>;
2790
+ /** Завершает текущую сессию на сервере и очищает локальную. */
2791
+ logout(options?: RequestOptions): Promise<void>;
2792
+ /**
2793
+ * Завершает все сессии пользователя и очищает локальную.
2794
+ *
2795
+ * Собран из двух запросов, потому что единого эндпоинта на сервере нет:
2796
+ * `POST /api/v1/auth/logout-all` отвечает `404`. Сначала отзываются все прочие сессии
2797
+ * (`DELETE /api/v1/auth/sessions`), затем завершается текущая — в обратном порядке
2798
+ * отзывать было бы уже нечем.
2799
+ */
2800
+ logoutAll(options?: RequestOptions): Promise<void>;
2801
+ /** Забывает сессию локально, не обращаясь к серверу. */
2802
+ signOut(): Promise<void>;
2803
+ /**
2804
+ * Запрашивает письмо с кодом для сброса пароля.
2805
+ *
2806
+ * @returns `flowToken`, который нужно передать в {@link resetPassword}
2807
+ */
2808
+ forgotPassword(input: ForgotPasswordInput, options?: RequestOptions): Promise<string>;
2809
+ /**
2810
+ * Устанавливает новый пароль по коду из письма.
2811
+ *
2812
+ * Сервер ждёт все четыре поля сразу — `email`, `otp`, `flowToken` и `newPassword`;
2813
+ * при нехватке любого отвечает `422`.
2814
+ */
2815
+ resetPassword(input: ResetPasswordInput, options?: RequestOptions): Promise<void>;
2816
+ /**
2817
+ * Полный сброс пароля с кодом из письма.
2818
+ *
2819
+ * Тот же приём, что и {@link signInWithOtp}: код запрашивается функцией `getOtp`,
2820
+ * остальное библиотека делает сама.
2821
+ *
2822
+ * @example
2823
+ * ```ts
2824
+ * await itd.auth.resetPasswordWithOtp({
2825
+ * email,
2826
+ * turnstileToken,
2827
+ * newPassword,
2828
+ * getOtp: () => rl.question('Код из письма: '),
2829
+ * });
2830
+ * ```
2831
+ */
2832
+ resetPasswordWithOtp(input: ForgotPasswordInput & {
2833
+ newPassword: string;
2834
+ getOtp: () => string | Promise<string>;
2835
+ }, options?: RequestOptions): Promise<void>;
2836
+ /**
2837
+ * Меняет пароль. Требует действующей сессии.
2838
+ *
2839
+ * При неверном текущем пароле сервер отвечает `ACCOUNT_CURRENT_PASSWORD_INCORRECT`.
2840
+ *
2841
+ * @example
2842
+ * ```ts
2843
+ * await itd.auth.changePassword({ currentPassword, newPassword });
2844
+ * ```
2845
+ */
2846
+ changePassword(input: {
2847
+ currentPassword: string;
2848
+ newPassword: string;
2849
+ }, options?: RequestOptions): Promise<void>;
2850
+ /**
2851
+ * Возвращает адрес для входа через внешнего провайдера.
2852
+ *
2853
+ * Сам переход выполняет приложение: в браузере — редиректом, в приложении — открытием
2854
+ * системного браузера.
2855
+ *
2856
+ * @example
2857
+ * ```ts
2858
+ * window.location.href = itd.auth.oauthUrl('yandex');
2859
+ * ```
2860
+ */
2861
+ oauthUrl(provider: OAuthProvider): string;
2862
+ /** Загружает список активных сессий. У текущей поле `isCurrent` равно `true`. */
2863
+ sessions(options?: RequestOptions): Promise<Session[]>;
2864
+ /** Завершает указанную сессию. */
2865
+ revokeSession(sessionId: string, options?: RequestOptions): Promise<void>;
2866
+ /** Завершает все сессии, кроме текущей. */
2867
+ revokeOtherSessions(options?: RequestOptions): Promise<void>;
2868
+ }
2869
+
2870
+ /** Параметры запроса ответов на комментарий. */
2871
+ interface RepliesParams extends RequestOptions {
2872
+ limit?: number;
2873
+ page?: number;
2874
+ maxPages?: number;
2875
+ }
2876
+ /**
2877
+ * Комментарии и ответы на них.
2878
+ *
2879
+ * Доступна как `itd.comments`. Комментарии **к посту** живут в `itd.posts`:
2880
+ * `itd.posts.comments()` и `itd.posts.comment()`.
2881
+ */
2882
+ declare class CommentsResource extends BaseResource {
2883
+ #private;
2884
+ constructor(http: HttpClient, deps: {
2885
+ uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]>;
2886
+ });
2887
+ /**
2888
+ * Загружает страницу ответов на комментарий.
2889
+ *
2890
+ * Здесь пагинация **постраничная**, в отличие от комментариев к посту, где курсорная.
2891
+ */
2892
+ replies(commentId: string, params?: RepliesParams): Promise<Page<Comment>>;
2893
+ /** Перебирает ответы на комментарий. */
2894
+ iterateReplies(commentId: string, params?: RepliesParams): Paginator<Comment>;
2895
+ /**
2896
+ * Отвечает на комментарий.
2897
+ *
2898
+ * @example
2899
+ * ```ts
2900
+ * await itd.comments.reply(commentId, 'согласен');
2901
+ * await itd.comments.reply(commentId, (c) => c.content('и вот почему').replyTo(userId));
2902
+ * ```
2903
+ */
2904
+ reply(commentId: string, input: CommentInput | string, options?: RequestOptions): Promise<Comment>;
2905
+ /** Редактирует текст комментария. */
2906
+ update(commentId: string, content: string, options?: RequestOptions): Promise<Comment>;
2907
+ /** Удаляет комментарий. Восстановить его можно через {@link restore}. */
2908
+ remove(commentId: string, options?: RequestOptions): Promise<void>;
2909
+ /** Восстанавливает удалённый комментарий. */
2910
+ restore(commentId: string, options?: RequestOptions): Promise<Comment>;
2911
+ /** Ставит реакцию на комментарий. */
2912
+ like(commentId: string, options?: RequestOptions): Promise<LikeResult>;
2913
+ /** Убирает реакцию с комментария. */
2914
+ unlike(commentId: string, options?: RequestOptions): Promise<LikeResult>;
2915
+ }
2916
+
2917
+ /** Ответ загрузки файла. */
2918
+ interface UploadedFile {
2919
+ /** Идентификатор вложения — его передают в `attachmentIds`. */
2920
+ id: string;
2921
+ /** Адрес файла на CDN. */
2922
+ url: string;
2923
+ }
2924
+ /** Настройки загрузки. */
2925
+ interface UploadOptions extends RequestOptions {
2926
+ /** Имя файла. По нему определяется тип, если он не задан явно. */
2927
+ filename?: string;
2928
+ /** MIME-тип. Если не указан, определяется по имени файла или по самому `Blob`. */
2929
+ contentType?: string;
2930
+ /**
2931
+ * Проверять тип файла до отправки. По умолчанию `true`.
2932
+ *
2933
+ * Отключайте, если API начал принимать формат, которого ещё нет в списке библиотеки.
2934
+ */
2935
+ validateMime?: boolean;
2936
+ }
2937
+ /**
2938
+ * Таймаут загрузки файла по умолчанию.
2939
+ *
2940
+ * Заметно больше обычного: видео на несколько десятков мегабайт не укладывается
2941
+ * в стандартные 30 секунд, и запрос обрывался бы на середине.
2942
+ */
2943
+ declare const DEFAULT_UPLOAD_TIMEOUT = 300000;
2944
+ /** Чтение файла по пути — подставляется точкой входа `itd-api/node`. */
2945
+ type FileReader = (path: string) => Promise<{
2946
+ data: Uint8Array;
2947
+ filename: string;
2948
+ }>;
2949
+ /**
2950
+ * Файлы и медиа.
2951
+ *
2952
+ * Доступна как `itd.files`. Обычно вызывать её напрямую не нужно: `itd.posts.create()`
2953
+ * и `itd.posts.comment()` загружают файлы сами.
2954
+ */
2955
+ declare class FilesResource extends BaseResource {
2956
+ #private;
2957
+ /**
2958
+ * @param deps.readFile чтение файлов с диска. Передаёт точка входа `itd-api/node`;
2959
+ * в основном бандле его нет, чтобы браузерные сборщики не пытались разрешить `node:fs`.
2960
+ */
2961
+ constructor(http: HttpClient, deps?: {
2962
+ readFile?: FileReader;
2963
+ });
2964
+ /**
2965
+ * Загружает файл и возвращает его идентификатор.
2966
+ *
2967
+ * @remarks
2968
+ * Кроме типа сервер проверяет и само изображение: слишком маленькие картинки
2969
+ * он отклоняет сообщением «Не удалось проверить изображение». Точный порог
2970
+ * неизвестен, но 64×64 проходит.
2971
+ *
2972
+ * @example
2973
+ * ```ts
2974
+ * const file = await itd.files.upload(blob, { filename: 'photo.jpg' });
2975
+ * await itd.posts.create({ content: 'смотрите', attachmentIds: [file.id] });
2976
+ * ```
2977
+ */
2978
+ upload(input: FileInput, options?: UploadOptions): Promise<UploadedFile>;
2979
+ /**
2980
+ * Загружает несколько файлов, сохраняя порядок.
2981
+ *
2982
+ * Файлы отправляются последовательно: параллельная загрузка нескольких видео легко
2983
+ * упирается в ограничение частоты, а порядок вложений в посте важен.
2984
+ *
2985
+ * @returns идентификаторы вложений в том же порядке, что и входные файлы
2986
+ */
2987
+ uploadMany(files: FileInput[], options?: UploadOptions): Promise<string[]>;
2988
+ /**
2989
+ * Загружает сведения о файле.
2990
+ *
2991
+ * @remarks
2992
+ * Сервер отвечает `404` даже на только что загруженный файл, который ещё никуда
2993
+ * не прикреплён, — проверено на боевом API. Практической пользы у метода пока нет,
2994
+ * он оставлен для полноты.
2995
+ */
2996
+ get(fileId: string, options?: RequestOptions): Promise<unknown>;
2997
+ /** Удаляет загруженный файл. */
2998
+ remove(fileId: string, options?: RequestOptions): Promise<void>;
2999
+ }
3000
+
3001
+ /** Параметры запроса постов по хэштегу. */
3002
+ interface HashtagPostsParams extends RequestOptions {
3003
+ limit?: number;
3004
+ cursor?: string;
3005
+ maxPages?: number;
3006
+ }
3007
+ /**
3008
+ * Хэштеги.
3009
+ *
3010
+ * Доступна как `itd.hashtags`.
3011
+ */
3012
+ declare class HashtagsResource extends BaseResource {
3013
+ #private;
3014
+ /**
3015
+ * Ищет хэштеги.
3016
+ *
3017
+ * Без строки запроса возвращает общий список.
3018
+ */
3019
+ search(query?: string, params?: {
3020
+ limit?: number;
3021
+ } & RequestOptions): Promise<Hashtag[]>;
3022
+ /** Загружает трендовые хэштеги. */
3023
+ trending(params?: {
3024
+ limit?: number;
3025
+ } & RequestOptions): Promise<Hashtag[]>;
3026
+ /**
3027
+ * Загружает страницу постов по хэштегу.
3028
+ *
3029
+ * @param tag название без решётки; кодируется автоматически, поэтому кириллица
3030
+ * и пробелы допустимы
3031
+ */
3032
+ posts(tag: string, params?: HashtagPostsParams): Promise<Page<Post>>;
3033
+ /** Перебирает посты по хэштегу. */
3034
+ iteratePosts(tag: string, params?: HashtagPostsParams): Paginator<Post>;
3035
+ }
3036
+
3037
+ /** Параметры запроса списка уведомлений. */
3038
+ interface NotificationListParams extends RequestOptions {
3039
+ limit?: number;
3040
+ /** Смещение от начала списка. */
3041
+ offset?: number;
3042
+ maxPages?: number;
3043
+ }
3044
+ /** Изменяемые настройки уведомлений. */
3045
+ type UpdateNotificationSettingsInput = Partial<NotificationSettings>;
3046
+ /**
3047
+ * Уведомления: список, счётчик, отметки о прочтении, настройки.
3048
+ *
3049
+ * Доступна как `itd.notifications`. Все уведомления приведены к единой форме, поэтому
3050
+ * объекты отсюда и из потока событий можно складывать в один список.
3051
+ */
3052
+ declare class NotificationsResource extends BaseResource {
3053
+ #private;
3054
+ /**
3055
+ * Загружает страницу уведомлений.
3056
+ *
3057
+ * Пагинация здесь основана на смещении.
3058
+ *
3059
+ * @example
3060
+ * ```ts
3061
+ * const page = await itd.notifications.list({ limit: 20 });
3062
+ * const next = await itd.notifications.list({ limit: 20, offset: page.nextOffset });
3063
+ * ```
3064
+ */
3065
+ list(params?: NotificationListParams): Promise<Page<Notification>>;
3066
+ /**
3067
+ * Перебирает уведомления.
3068
+ *
3069
+ * @example
3070
+ * ```ts
3071
+ * for await (const notification of itd.notifications.iterate()) {
3072
+ * console.log(formatNotificationText(notification));
3073
+ * }
3074
+ * ```
3075
+ */
3076
+ iterate(params?: NotificationListParams): Paginator<Notification>;
3077
+ /** Загружает число непрочитанных уведомлений. */
3078
+ count(options?: RequestOptions): Promise<number>;
3079
+ /**
3080
+ * Отмечает уведомление прочитанным.
3081
+ *
3082
+ * @returns сколько записей отметил сервер
3083
+ */
3084
+ markRead(notificationId: string, options?: RequestOptions): Promise<number>;
3085
+ /**
3086
+ * Отмечает прочитанными сразу несколько уведомлений.
3087
+ *
3088
+ * Список автоматически режется на части по 20 идентификаторов — столько же отправляет
3089
+ * сайт итд.com, поэтому на сервере вероятен предел. Части уходят последовательно,
3090
+ * результат суммируется.
3091
+ *
3092
+ * @returns сколько записей отметил сервер суммарно
3093
+ */
3094
+ markReadBatch(ids: string[], options?: RequestOptions): Promise<number>;
3095
+ /** Отмечает прочитанными все уведомления. */
3096
+ markAllRead(options?: RequestOptions): Promise<number>;
3097
+ /** Загружает настройки уведомлений. */
3098
+ getSettings(options?: RequestOptions): Promise<NotificationSettings>;
3099
+ /**
3100
+ * Обновляет настройки уведомлений.
3101
+ *
3102
+ * Отправляются только изменяемые поля, в том же виде, в каком сервер их возвращает.
3103
+ */
3104
+ updateSettings(input: UpdateNotificationSettingsInput, options?: RequestOptions): Promise<NotificationSettings>;
3105
+ }
3106
+
3107
+ /**
3108
+ * Сведения о платформе: изменения, анонсы, баннер события.
3109
+ *
3110
+ * Доступна как `itd.platform`.
3111
+ */
3112
+ declare class PlatformResource extends BaseResource {
3113
+ /** Загружает журнал изменений. */
3114
+ changelog(options?: RequestOptions): Promise<ChangelogEntry[]>;
3115
+ /** Загружает анонсы платформы. */
3116
+ announcements(options?: RequestOptions): Promise<Announcement[]>;
3117
+ /** Загружает баннер текущего события — виджет «портал». */
3118
+ portal(options?: RequestOptions): Promise<Portal>;
3119
+ /**
3120
+ * Загружает состояние сервисов платформы за последние 90 суток.
3121
+ *
3122
+ * Идёт на хост `статус.итд.com` без авторизации. Ответ кэшируется сервером на минуту.
3123
+ * История по суткам приходит разреженной, ровный массив даёт `statusDays`.
3124
+ *
3125
+ * @example
3126
+ * ```ts
3127
+ * const status = await itd.platform.status();
3128
+ *
3129
+ * if (status.overall_status !== 'operational') {
3130
+ * const broken = status.services.filter((s) => s.current_status !== 'operational');
3131
+ * console.log('лежит:', broken.map((s) => s.name).join(', '));
3132
+ * }
3133
+ * ```
3134
+ */
3135
+ status(options?: RequestOptions): Promise<PlatformStatus>;
3136
+ }
3137
+
3138
+ /** Параметры запроса ленты. */
3139
+ interface FeedParams extends RequestOptions {
3140
+ /** Вкладка ленты. По умолчанию сервер отдаёт популярное. */
3141
+ tab?: FeedTab;
3142
+ /** Сколько постов на страницу. */
3143
+ limit?: number;
3144
+ /**
3145
+ * Курсор следующей страницы из предыдущего ответа.
3146
+ *
3147
+ * Передавайте значение как есть: его формат зависит от вкладки и может измениться.
3148
+ */
3149
+ cursor?: string;
3150
+ /** Ограничение числа страниц при переборе. */
3151
+ maxPages?: number;
3152
+ }
3153
+ /** Параметры запроса постов пользователя. */
3154
+ interface UserPostsParams extends RequestOptions {
3155
+ limit?: number;
3156
+ cursor?: string;
3157
+ /** Порядок сортировки. */
3158
+ sort?: string;
3159
+ /** Закреплённый пост, чтобы сервер поднял его наверх. */
3160
+ pinnedPostId?: string;
3161
+ maxPages?: number;
3162
+ }
3163
+ /** Параметры запроса комментариев к посту. */
3164
+ interface CommentsParams extends RequestOptions {
3165
+ limit?: number;
3166
+ /**
3167
+ * Курсор следующей страницы: идентификатор последнего полученного комментария.
3168
+ *
3169
+ * Передавайте значение из `nextCursor` предыдущего ответа как есть.
3170
+ */
3171
+ cursor?: string;
3172
+ sort?: CommentSort;
3173
+ maxPages?: number;
3174
+ }
3175
+ /**
3176
+ * Посты: лента, публикация, реакции, репосты, комментарии.
3177
+ *
3178
+ * Доступна как `itd.posts`.
3179
+ */
3180
+ declare class PostsResource extends BaseResource {
3181
+ #private;
3182
+ constructor(http: HttpClient, deps: {
3183
+ uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]>;
3184
+ });
3185
+ /**
3186
+ * Загружает страницу ленты.
3187
+ *
3188
+ * @example
3189
+ * ```ts
3190
+ * const page = await itd.posts.list({ tab: FeedTab.Following, limit: 20 });
3191
+ * const next = await itd.posts.list({ tab: FeedTab.Following, cursor: page.nextCursor ?? undefined });
3192
+ * ```
3193
+ */
3194
+ list(params?: FeedParams): Promise<Page<Post>>;
3195
+ /**
3196
+ * Перебирает ленту, сама подставляя курсоры.
3197
+ *
3198
+ * @example
3199
+ * ```ts
3200
+ * for await (const post of itd.posts.iterate({ tab: 'following' })) {
3201
+ * console.log(post.author.username, post.content);
3202
+ * }
3203
+ * ```
3204
+ */
3205
+ iterate(params?: FeedParams): Paginator<Post>;
3206
+ /**
3207
+ * Публикует пост.
3208
+ *
3209
+ * Принимает обычный объект, {@link PostBuilder} или функцию-настройщик. Файлы из поля
3210
+ * `files` загружаются автоматически, порядок вложений сохраняется.
3211
+ *
3212
+ * @example
3213
+ * ```ts
3214
+ * await itd.posts.create({ content: 'привет' });
3215
+ * await itd.posts.create((p) => p.content('привет').attach('./photo.jpg'));
3216
+ * ```
3217
+ */
3218
+ create(input: PostInput, options?: RequestOptions): Promise<Post>;
3219
+ /**
3220
+ * Загружает один пост вместе с топовыми комментариями.
3221
+ *
3222
+ * В отличие от списков, здесь у поста заполнено поле `comments`.
3223
+ */
3224
+ get(postId: string, options?: RequestOptions): Promise<Post>;
3225
+ /** Редактирует текст поста. */
3226
+ update(postId: string, input: Pick<CreatePostInput, 'content' | 'spans'>, options?: RequestOptions): Promise<Post>;
3227
+ /** Удаляет пост. Восстановить его можно через {@link restore}. */
3228
+ remove(postId: string, options?: RequestOptions): Promise<void>;
3229
+ /** Восстанавливает удалённый пост. */
3230
+ restore(postId: string, options?: RequestOptions): Promise<Post>;
3231
+ /** Ставит реакцию на пост. */
3232
+ like(postId: string, options?: RequestOptions): Promise<LikeResult>;
3233
+ /** Убирает реакцию с поста. */
3234
+ unlike(postId: string, options?: RequestOptions): Promise<LikeResult>;
3235
+ /**
3236
+ * Делает репост с необязательным комментарием.
3237
+ *
3238
+ * Вложения к репосту не поддерживаются: сервер их игнорирует, поэтому параметров
3239
+ * для файлов здесь нет.
3240
+ */
3241
+ repost(postId: string, content?: string, options?: RequestOptions): Promise<Post>;
3242
+ /** Отменяет репост. */
3243
+ unrepost(postId: string, options?: RequestOptions): Promise<void>;
3244
+ /** Закрепляет пост в профиле. */
3245
+ pin(postId: string, options?: RequestOptions): Promise<PinPostResult>;
3246
+ /** Открепляет пост. */
3247
+ unpin(postId: string, options?: RequestOptions): Promise<PinPostResult>;
3248
+ /**
3249
+ * Голосует в опросе.
3250
+ *
3251
+ * @param optionIds выбранные варианты; несколько допустимы только при `multipleChoice`
3252
+ */
3253
+ vote(postId: string, optionIds: string[], options?: RequestOptions): Promise<Poll>;
3254
+ /** Запрашивает счётчики сразу для нескольких постов. */
3255
+ stats(ids: string[], options?: RequestOptions): Promise<PostStats[]>;
3256
+ /**
3257
+ * Загружает страницу стены пользователя.
3258
+ *
3259
+ * Это **не только его собственные посты**: сюда попадают и записи, которые другие
3260
+ * оставили на его стене — у них `author` чужой, а `wallRecipient` указывает на владельца
3261
+ * стены. Поэтому число записей обычно больше, чем `postsCount` из профиля; чтобы
3262
+ * получить только авторские посты, отфильтруйте по `post.author.id`.
3263
+ *
3264
+ * Принимает и UUID, и имя пользователя.
3265
+ */
3266
+ byUser(user: UserRef, params?: UserPostsParams): Promise<Page<Post>>;
3267
+ /** Перебирает стену пользователя. Что именно в неё входит — см. {@link byUser}. */
3268
+ iterateByUser(user: UserRef, params?: UserPostsParams): Paginator<Post>;
3269
+ /** Загружает страницу постов, которые пользователь отметил реакцией. */
3270
+ likedByUser(user: UserRef, params?: UserPostsParams): Promise<Page<Post>>;
3271
+ /** Перебирает посты, которые пользователь отметил реакцией. */
3272
+ iterateLikedByUser(user: UserRef, params?: UserPostsParams): Paginator<Post>;
3273
+ /**
3274
+ * Загружает страницу комментариев к посту.
3275
+ *
3276
+ * У этого эндпоинта курсор и признак продолжения лежат рядом со списком, а не внутри
3277
+ * объекта `pagination`, как у остальных, — разница скрыта внутри.
3278
+ */
3279
+ comments(postId: string, params?: CommentsParams): Promise<Page<Comment>>;
3280
+ /** Перебирает комментарии к посту. */
3281
+ iterateComments(postId: string, params?: CommentsParams): Paginator<Comment>;
3282
+ /**
3283
+ * Комментирует пост.
3284
+ *
3285
+ * @example
3286
+ * ```ts
3287
+ * await itd.posts.comment(postId, 'согласен');
3288
+ * await itd.posts.comment(postId, (c) => c.content('смотри').attach('./meme.png'));
3289
+ * ```
3290
+ */
3291
+ comment(postId: string, input: CommentInput | string, options?: RequestOptions): Promise<Comment>;
3292
+ /**
3293
+ * Отправляет голосовой комментарий.
3294
+ *
3295
+ * Текста у такого комментария нет: сервер ждёт пустой `content` и одно аудиовложение
3296
+ * в формате `audio/ogg`.
3297
+ *
3298
+ * @example
3299
+ * ```ts
3300
+ * await itd.posts.voiceComment(postId, './answer.ogg');
3301
+ * ```
3302
+ */
3303
+ voiceComment(postId: string, audio: FileInput, options?: RequestOptions): Promise<Comment>;
3304
+ }
3305
+
3306
+ /**
3307
+ * Жалобы на контент и пользователей.
3308
+ *
3309
+ * Доступна как `itd.reports`.
3310
+ */
3311
+ declare class ReportsResource extends BaseResource {
3312
+ /**
3313
+ * Отправляет жалобу.
3314
+ *
3315
+ * Повторная жалоба на тот же объект отклоняется сервером с сообщением
3316
+ * «Вы уже отправляли жалобу на этот контент».
3317
+ *
3318
+ * @example
3319
+ * ```ts
3320
+ * await itd.reports.create(report.post(postId).reason('spam'));
3321
+ * await itd.reports.create({ targetType: 'user', targetId, reason: 'fraud' });
3322
+ * ```
3323
+ */
3324
+ create(input: ReportInput, options?: RequestOptions): Promise<Report>;
3325
+ }
3326
+
3327
+ /** Результат глобального поиска. */
3328
+ interface SearchResult {
3329
+ users: UserSummary[];
3330
+ hashtags: Hashtag[];
3331
+ }
3332
+ /**
3333
+ * Глобальный поиск.
3334
+ *
3335
+ * Доступна как `itd.search`.
3336
+ */
3337
+ declare class SearchResource extends BaseResource {
3338
+ /**
3339
+ * Ищет пользователей и хэштеги одним запросом.
3340
+ *
3341
+ * @example
3342
+ * ```ts
3343
+ * const { users, hashtags } = await itd.search.all('арт');
3344
+ * ```
3345
+ */
3346
+ all(query: string, options?: RequestOptions): Promise<SearchResult>;
3347
+ }
3348
+
3349
+ /**
3350
+ * Подписка и способы оплаты.
3351
+ *
3352
+ * Доступна как `itd.subscription`.
3353
+ */
3354
+ declare class SubscriptionResource extends BaseResource {
3355
+ /** Загружает состояние подписки и её цену. */
3356
+ status(options?: RequestOptions): Promise<Subscription>;
3357
+ /**
3358
+ * Запускает оплату подписки.
3359
+ *
3360
+ * Форма ответа в документации API не описана, поэтому тип результата не уточняется.
3361
+ */
3362
+ pay(options?: RequestOptions): Promise<unknown>;
3363
+ /** Включает или отключает автопродление. */
3364
+ setAutoRenewal(enabled: boolean, options?: RequestOptions): Promise<unknown>;
3365
+ /** Запускает привязку карты. */
3366
+ bindCard(options?: RequestOptions): Promise<unknown>;
3367
+ /** Загружает список способов оплаты. Пустой массив, если карт нет. */
3368
+ methods(options?: RequestOptions): Promise<PaymentMethod[]>;
3369
+ /** Делает способ оплаты основным. */
3370
+ setDefaultMethod(methodId: string, options?: RequestOptions): Promise<unknown>;
3371
+ /** Удаляет способ оплаты. */
3372
+ removeMethod(methodId: string, options?: RequestOptions): Promise<void>;
3373
+ }
3374
+
3375
+ /**
3376
+ * Общие опции запроса телеметрии.
3377
+ *
3378
+ * Помимо {@link RequestOptions} позволяет задать `sid` — идентификатор сессии телеметрии.
3379
+ * По умолчанию он заводится один на объект {@link TelemetryResource}.
3380
+ */
3381
+ interface TelemetryOptions extends RequestOptions {
3382
+ /** Переопределяет идентификатор сессии телеметрии (`sid`) для этого запроса. */
3383
+ sid?: string;
3384
+ }
3385
+ /**
3386
+ * Событие просмотра поста для {@link TelemetryResource.dwell}.
3387
+ *
3388
+ * Пост определяется меткой показа `vs` и, при наличии, контекстом источника `sc`; поля
3389
+ * `postId` эндпоинт `/api/v1/i` не принимает.
3390
+ */
3391
+ interface DwellEntry {
3392
+ /** Метка показа — поле `vs` объекта поста. Уходит в поле `v`. */
3393
+ vs: string;
3394
+ /** Время появления поста в зоне видимости, epoch-мс. Поле `et`. */
3395
+ enterAt: number;
3396
+ /** Время ухода из зоны видимости, epoch-мс. Поле `xt`. */
3397
+ exitAt: number;
3398
+ /** Причина завершения просмотра. Поле `r`. */
3399
+ reason: ViewReason;
3400
+ /**
3401
+ * Длительность просмотра в мс. Поле `md`.
3402
+ *
3403
+ * Если не задано, вычисляется как `exitAt − enterAt`.
3404
+ */
3405
+ durationMs?: number;
3406
+ /** Контекст источника показа. Поле `sc`. */
3407
+ sourceContext?: string;
3408
+ /** Источник показа. Применим к `PostPage`/`Link`. Поле `s`. */
3409
+ source?: ViewSource;
3410
+ /** Повторный просмотр: пост уже встречался в этой сессии. Уходит как `b: 1`. */
3411
+ repeat?: boolean;
3412
+ }
3413
+ /** Событие взаимодействия с контентом для {@link TelemetryResource.interaction}. */
3414
+ interface InteractionEntry {
3415
+ /** Тип взаимодействия. Поле `t`. */
3416
+ type: InteractionType;
3417
+ /** Метка показа — поле `vs` объекта поста. Поле `v`. */
3418
+ vs: string;
3419
+ /** Идентификатор поста. Поле `ai`. */
3420
+ postId: string;
3421
+ /** Индекс вложения (с нуля) — для `InteractionType.PhotoOpen` ({@link InteractionType}). Поле `mi`. */
3422
+ mediaIndex?: number;
3423
+ /** Источник показа. Поле `s`. */
3424
+ source?: ViewSource;
3425
+ /** Просмотрено мс — для `InteractionType.VideoProgress` ({@link InteractionType}). Поле `pm`. */
3426
+ positionMs?: number;
3427
+ /** Длительность видео в мс — для `InteractionType.VideoProgress` ({@link InteractionType}). Поле `dm`. */
3428
+ durationMs?: number;
3429
+ }
3430
+ /**
3431
+ * Телеметрия просмотров.
3432
+ *
3433
+ * @experimental Недокументированные эндпоинты `/api/v1/i` (просмотры) и `/api/v1/x`
3434
+ * (взаимодействия); формат полей может измениться без предупреждения.
3435
+ *
3436
+ * Методы не вызываются автоматически — телеметрия отправляется только явным вызовом.
3437
+ *
3438
+ * Оба эндпоинта принимают конверт `{ sid, e }`, где `sid` — идентификатор сессии
3439
+ * телеметрии: по умолчанию один на объект, переопределяется опцией `sid`.
3440
+ *
3441
+ * Доступна как `itd.telemetry`.
3442
+ */
3443
+ declare class TelemetryResource extends BaseResource {
3444
+ #private;
3445
+ /**
3446
+ * Идентификатор сессии телеметрии (`sid`).
3447
+ *
3448
+ * Создаётся лениво при первом обращении и далее неизменен.
3449
+ */
3450
+ get sessionId(): string;
3451
+ /**
3452
+ * Отправляет события просмотра постов (`POST /api/v1/i`).
3453
+ *
3454
+ * @experimental См. предупреждение у {@link TelemetryResource}.
3455
+ */
3456
+ dwell(entries: DwellEntry[], options?: TelemetryOptions): Promise<unknown>;
3457
+ /**
3458
+ * Отправляет события взаимодействия с контентом (`POST /api/v1/x`).
3459
+ *
3460
+ * @experimental См. предупреждение у {@link TelemetryResource}.
3461
+ */
3462
+ interaction(entries: InteractionEntry[], options?: TelemetryOptions): Promise<unknown>;
3463
+ }
3464
+
3465
+ /**
3466
+ * Параметры списков пользователей.
3467
+ *
3468
+ * ⚠️ Списки подписчиков, подписок и заблокированных на сервере **не листаются**:
3469
+ * `page` он игнорирует, а `limit` зажимает на 20. Подробности — в {@link UsersResource.followers}.
3470
+ */
3471
+ interface UserListParams extends RequestOptions {
3472
+ /** Сколько записей вернуть. Значения больше 20 сервер молча уменьшает до 20. */
3473
+ limit?: number;
3474
+ /** Номер страницы. Сервер его игнорирует — оставлен на случай, если пагинацию починят. */
3475
+ page?: number;
3476
+ maxPages?: number;
3477
+ }
3478
+ /** Изменяемые поля своего профиля. */
3479
+ interface UpdateProfileInput {
3480
+ displayName?: string;
3481
+ username?: string;
3482
+ /** Эмодзи-аватар: символ клана, а не адрес картинки. */
3483
+ avatar?: string;
3484
+ bio?: string;
3485
+ /** Адрес изображения-шапки. */
3486
+ banner?: string;
3487
+ }
3488
+ /** Изменяемые настройки приватности. */
3489
+ type UpdatePrivacyInput = Partial<PrivacySettings>;
3490
+ /**
3491
+ * Пользователи: профили, подписки, блокировки, приватность.
3492
+ *
3493
+ * Доступна как `itd.users`.
3494
+ */
3495
+ declare class UsersResource extends BaseResource {
3496
+ #private;
3497
+ /** Загружает свой профиль — с подпиской и признаком подтверждённого телефона. */
3498
+ me(options?: RequestOptions): Promise<MyProfile>;
3499
+ /** Обновляет свой профиль. Передавайте только изменяемые поля. */
3500
+ updateMe(input: UpdateProfileInput, options?: RequestOptions): Promise<MyProfile>;
3501
+ /** Деактивирует аккаунт. Вернуть его можно через {@link restore}. */
3502
+ deactivate(options?: RequestOptions): Promise<void>;
3503
+ /** Восстанавливает деактивированный аккаунт. */
3504
+ restore(options?: RequestOptions): Promise<void>;
3505
+ /** Создаёт профиль после регистрации. */
3506
+ createProfile(input: {
3507
+ username: string;
3508
+ displayName: string;
3509
+ avatar?: string;
3510
+ }, options?: RequestOptions): Promise<MyProfile>;
3511
+ /**
3512
+ * Загружает профиль пользователя.
3513
+ *
3514
+ * @param user UUID **или** имя пользователя — подходит и то, и другое
3515
+ *
3516
+ * @example
3517
+ * ```ts
3518
+ * const profile = await itd.users.get('durov');
3519
+ * await itd.posts.create({ content: 'привет', wallRecipientId: profile.id });
3520
+ * ```
3521
+ */
3522
+ get(user: UserRef, options?: RequestOptions): Promise<PublicProfile>;
3523
+ /** Проверяет, свободно ли имя пользователя. */
3524
+ checkUsername(username: string, options?: RequestOptions): Promise<boolean>;
3525
+ /** Ищет пользователей по строке запроса. */
3526
+ search(query: string, params?: {
3527
+ limit?: number;
3528
+ } & RequestOptions): Promise<UserSummary[]>;
3529
+ /** Загружает рекомендации, на кого подписаться. */
3530
+ whoToFollow(options?: RequestOptions): Promise<UserSummary[]>;
3531
+ /** Загружает рейтинг кланов. */
3532
+ topClans(options?: RequestOptions): Promise<Clan[]>;
3533
+ /**
3534
+ * Подписывается на пользователя.
3535
+ *
3536
+ * У закрытого профиля вместо подписки отправляется заявка — это видно по полю `status`.
3537
+ */
3538
+ follow(user: UserRef, options?: RequestOptions): Promise<FollowResult>;
3539
+ /** Отписывается от пользователя. */
3540
+ unfollow(user: UserRef, options?: RequestOptions): Promise<void>;
3541
+ /**
3542
+ * Загружает подписчиков пользователя.
3543
+ *
3544
+ * ⚠️ **Сервер этот список не листает.** Возвращаются первые 20 записей и только они:
3545
+ * параметр `page` игнорируется (любая страница отдаёт те же записи и `pagination.page: 1`),
3546
+ * `limit` больше 20 молча уменьшается, а `hasMore` всегда `false`. Последнее честно —
3547
+ * получить продолжение нечем.
3548
+ *
3549
+ * Числу `total` доверять тоже не стоит: оно расходится с `followersCount` из профиля —
3550
+ * на проверенных аккаунтах занижено примерно на 1–4%.
3551
+ */
3552
+ followers(user: UserRef, params?: UserListParams): Promise<Page<UserSummary>>;
3553
+ /**
3554
+ * Перебирает подписчиков.
3555
+ *
3556
+ * ⚠️ Перебор закончится после первых 20 записей: сервер список не листает —
3557
+ * см. {@link followers}. Метод оставлен на случай, если пагинацию починят.
3558
+ */
3559
+ iterateFollowers(user: UserRef, params?: UserListParams): Paginator<UserSummary>;
3560
+ /** Загружает подписки пользователя. Ограничения те же, что у {@link followers}. */
3561
+ following(user: UserRef, params?: UserListParams): Promise<Page<UserSummary>>;
3562
+ /** Перебирает подписки. Закончится после первых 20 записей — см. {@link followers}. */
3563
+ iterateFollowing(user: UserRef, params?: UserListParams): Paginator<UserSummary>;
3564
+ /**
3565
+ * Проверяет, подписаны ли вы, сразу для нескольких пользователей.
3566
+ *
3567
+ * @returns объект «идентификатор пользователя → подписаны ли вы»
3568
+ *
3569
+ * @example
3570
+ * ```ts
3571
+ * const statuses = await itd.users.followStatus([userA, userB]);
3572
+ * // { 'b89dee4f-…': true, '35ea3059-…': false }
3573
+ * ```
3574
+ */
3575
+ followStatus(userIds: UserId[], options?: RequestOptions): Promise<Record<string, boolean>>;
3576
+ /** Блокирует пользователя. */
3577
+ block(user: UserRef, options?: RequestOptions): Promise<void>;
3578
+ /** Снимает блокировку. */
3579
+ unblock(user: UserRef, options?: RequestOptions): Promise<void>;
3580
+ /** Загружает заблокированных пользователей. Ограничения те же, что у {@link followers}. */
3581
+ blocked(params?: UserListParams): Promise<Page<UserSummary>>;
3582
+ /** Перебирает заблокированных. Закончится после первых 20 записей — см. {@link followers}. */
3583
+ iterateBlocked(params?: UserListParams): Paginator<UserSummary>;
3584
+ /** Загружает настройки приватности. */
3585
+ getPrivacy(options?: RequestOptions): Promise<PrivacySettings>;
3586
+ /** Обновляет настройки приватности. Передавайте только изменяемые поля. */
3587
+ updatePrivacy(input: UpdatePrivacyInput, options?: RequestOptions): Promise<PrivacySettings>;
3588
+ /**
3589
+ * Загружает значки профиля и выбранный из них.
3590
+ *
3591
+ * `activePin` — строка-идентификатор, а не объект.
3592
+ */
3593
+ pins(options?: RequestOptions): Promise<PinsResult>;
3594
+ /** Выбирает активный значок профиля. */
3595
+ setPin(slug: string, options?: RequestOptions): Promise<void>;
3596
+ /** Снимает активный значок. */
3597
+ removePin(options?: RequestOptions): Promise<void>;
3598
+ }
3599
+
3600
+ /**
3601
+ * Верификация профиля.
3602
+ *
3603
+ * Доступна как `itd.verification`.
3604
+ */
3605
+ declare class VerificationResource extends BaseResource {
3606
+ /** Загружает статус заявки. Значение `none` означает, что заявка не подавалась. */
3607
+ status(options?: RequestOptions): Promise<VerificationStatus>;
3608
+ /** Подаёт заявку на верификацию с видео. */
3609
+ submit(videoUrl: string, options?: RequestOptions): Promise<unknown>;
3610
+ }
3611
+
3612
+ declare global {
3613
+ interface SymbolConstructor {
3614
+ readonly asyncDispose: unique symbol;
3615
+ }
3616
+ }
3617
+ /**
3618
+ * Скрытые параметры конструктора — не часть публичного API.
3619
+ *
3620
+ * Через них точка входа `itd-api/node` передаёт чтение файлов с диска, не мутируя уже
3621
+ * созданный объект.
3622
+ *
3623
+ * @internal
3624
+ */
3625
+ interface ItdClientInternals {
3626
+ fileReader?: FileReader | undefined;
3627
+ }
3628
+ /**
3629
+ * Клиент API итд.com.
3630
+ *
3631
+ * Методы сгруппированы по разделам: `itd.posts`, `itd.users`, `itd.comments`, `itd.auth`,
3632
+ * `itd.files`. Авторизация, обновление токена, повторы и очередь запросов работают сами.
3633
+ *
3634
+ * @example Готовый токен — для разового вызова
3635
+ * ```ts
3636
+ * const itd = new ItdClient({ auth: '<accessToken>' });
3637
+ * const me = await itd.users.me();
3638
+ * ```
3639
+ *
3640
+ * @example Полноценная сессия для бота
3641
+ * ```ts
3642
+ * import { ItdClient } from 'itd-api';
3643
+ * import { FileTokenStorage } from 'itd-api/node';
3644
+ *
3645
+ * const itd = new ItdClient({
3646
+ * // `auth` не обязателен: когда хранилище уже содержит сессию, токен берётся оттуда,
3647
+ * // а истёкший продлевается сам. Здесь он нужен на первый запуск.
3648
+ * // Вход по паролю требует токена капчи — см. AuthInput и TURNSTILE_SITE_KEY.
3649
+ * auth: { email, password, getTurnstileToken },
3650
+ * storage: new FileTokenStorage('./.itd-session.json'),
3651
+ * rateLimit: { concurrency: 4, rps: 8 },
3652
+ * });
3653
+ *
3654
+ * for await (const post of itd.posts.iterate({ tab: 'following' })) {
3655
+ * if (!post.isLiked) await itd.posts.like(post.id);
3656
+ * }
3657
+ * ```
3658
+ */
3659
+ declare class ItdClient {
3660
+ #private;
3661
+ /** Авторизация, сессии и пароли. */
3662
+ readonly auth: AuthResource;
3663
+ /** Профили, подписки, блокировки, приватность. */
3664
+ readonly users: UsersResource;
3665
+ /** Лента, публикация, реакции, репосты, комментарии к постам. */
3666
+ readonly posts: PostsResource;
3667
+ /** Ответы на комментарии и действия над ними. */
3668
+ readonly comments: CommentsResource;
3669
+ /** Загрузка файлов и медиа. */
3670
+ readonly files: FilesResource;
3671
+ /** Уведомления: список, счётчик, отметки о прочтении, настройки. */
3672
+ readonly notifications: NotificationsResource;
3673
+ /** Хэштеги и посты по ним. */
3674
+ readonly hashtags: HashtagsResource;
3675
+ /** Глобальный поиск по пользователям и хэштегам. */
3676
+ readonly search: SearchResource;
3677
+ /** Жалобы на контент и пользователей. */
3678
+ readonly reports: ReportsResource;
3679
+ /** Верификация профиля. */
3680
+ readonly verification: VerificationResource;
3681
+ /** Подписка и способы оплаты. */
3682
+ readonly subscription: SubscriptionResource;
3683
+ /** Сведения о платформе: изменения, анонсы, баннер события. */
3684
+ readonly platform: PlatformResource;
3685
+ /**
3686
+ * Телеметрия просмотров.
3687
+ *
3688
+ * @experimental Недокументированные эндпоинты. Библиотека никогда не отправляет их сама.
3689
+ */
3690
+ readonly telemetry: TelemetryResource;
3691
+ constructor(options?: ItdClientOptions, internals?: ItdClientInternals);
3692
+ /** Базовый URL, к которому обращается клиент. */
3693
+ get baseUrl(): string;
3694
+ /**
3695
+ * Выполняет произвольный запрос к API.
3696
+ *
3697
+ * Запасной путь для случаев, когда нужного метода ещё нет или ответ сервера разошёлся
3698
+ * с документацией. Проходит через ту же авторизацию, очередь и обработку ошибок.
3699
+ *
3700
+ * @example
3701
+ * ```ts
3702
+ * const raw = await itd.request({ method: 'GET', path: '/api/posts', raw: true });
3703
+ * ```
3704
+ */
3705
+ request<T = unknown>(options: RawRequestOptions): Promise<T>;
3706
+ /**
3707
+ * Подключает плагин.
3708
+ *
3709
+ * Плагин работает на уровне транспорта: видит запрос до отправки и разобранный ответ,
3710
+ * поэтому одна обёртка охватывает сразу все методы клиента. Подключать можно в любой
3711
+ * момент, но обычно это делают сразу после создания клиента.
3712
+ *
3713
+ * @throws {ItdConfigError} если плагин задан неверно или уже подключён
3714
+ *
3715
+ * @example
3716
+ * ```ts
3717
+ * import { crypt } from '@itd-api/crypto';
3718
+ *
3719
+ * itd.use(crypt());
3720
+ * await itd.posts.create({ content: 'секрет' }, { encrypt: 'invis' });
3721
+ * ```
3722
+ */
3723
+ use(plugin: ItdPlugin): this;
3724
+ /**
3725
+ * Регистрирует сервис платформы — домен, отличный от основного.
3726
+ *
3727
+ * Запросы с `{ service: 'имя' }` уходят на его хост с его заголовками. То же самое умеет
3728
+ * опция `services` конструктора. Занятое имя не переопределяется — ни своё, ни встроенное:
3729
+ * разовому запросу хост задаётся полем `baseUrl`.
3730
+ *
3731
+ * Заголовок авторизации по умолчанию уходит только своим — домену клиента и его
3732
+ * поддоменам. Стороннему хосту токен нужно разрешить явно: `auth: true`.
3733
+ *
3734
+ * @throws {ItdConfigError} если определение неверно или имя уже занято
3735
+ *
3736
+ * @example Сервис платформы на поддомене — токен уходит сам
3737
+ * ```ts
3738
+ * itd.defineService({
3739
+ * name: 'pb',
3740
+ * baseUrl: 'https://pbapi.xn--d1ah4a.com',
3741
+ * headers: { Referer: 'https://pixel.xn--d1ah4a.com/' },
3742
+ * });
3743
+ *
3744
+ * await itd.request({ method: 'GET', service: 'pb', path: '/api/pixel-info' });
3745
+ * ```
3746
+ */
3747
+ defineService(definition: ServiceDefinition): this;
3748
+ /**
3749
+ * Базовый URL зарегистрированного сервиса.
3750
+ *
3751
+ * @throws {ItdConfigError} если сервис не зарегистрирован
3752
+ */
3753
+ serviceBaseUrl(name: string): string;
3754
+ /**
3755
+ * Подписывается на события авторизации.
3756
+ *
3757
+ * Полезно, чтобы сохранять сессию во внешнее хранилище или узнавать, что вход
3758
+ * окончательно потерян.
3759
+ *
3760
+ * @returns функция отписки
3761
+ *
3762
+ * @example
3763
+ * ```ts
3764
+ * itd.on('tokens', ({ accessToken }) => cache.set('itd', accessToken));
3765
+ * itd.on('authError', () => notifyUser('Сессия истекла, войдите заново'));
3766
+ * ```
3767
+ */
3768
+ on<K extends keyof AuthEvents>(event: K, listener: Listener<AuthEvents[K]>): Unsubscribe;
3769
+ /**
3770
+ * Создаёт поток уведомлений в реальном времени.
3771
+ *
3772
+ * Каждый вызов даёт новый независимый поток; обычно он нужен один на приложение.
3773
+ * Соединение поднимается методом `connect()` и держится само.
3774
+ *
3775
+ * @example
3776
+ * ```ts
3777
+ * const stream = itd.realtime();
3778
+ *
3779
+ * stream.on('notification', ({ notification }) => {
3780
+ * console.log(formatNotificationText(notification));
3781
+ * });
3782
+ * stream.on('unreadCount', (count) => setBadge(count));
3783
+ *
3784
+ * await stream.connect();
3785
+ * ```
3786
+ */
3787
+ realtime(options?: RealtimeOptions): ItdRealtime;
3788
+ /**
3789
+ * Освобождает ресурсы клиента: останавливает очередь запросов (снимает отложенные паузы)
3790
+ * и закрывает все потоки уведомлений, созданные через {@link realtime}.
3791
+ *
3792
+ * После вызова клиентом можно пользоваться снова — новые запросы поднимут всё заново,
3793
+ * но уже созданные потоки останутся закрытыми.
3794
+ *
3795
+ * @example
3796
+ * ```ts
3797
+ * await using itd = new ItdClient({ auth: token });
3798
+ * // …работа…
3799
+ * // close() вызовется сам на выходе из блока
3800
+ * ```
3801
+ */
3802
+ close(): Promise<void>;
3803
+ /** Позволяет использовать клиент с `await using`. */
3804
+ [Symbol.asyncDispose](): Promise<void>;
3805
+ /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */
3806
+ getSession(): Promise<ItdSession | null>;
3807
+ /** Восстанавливает сохранённую сессию, включая cookie. */
3808
+ setSession(session: ItdSession): Promise<void>;
3809
+ }
3810
+ /**
3811
+ * Создаёт клиент API итд.com.
3812
+ *
3813
+ * То же, что `new ItdClient(options)`, — для тех, кому привычнее фабрика.
3814
+ *
3815
+ * @example
3816
+ * ```ts
3817
+ * const itd = createClient({ auth: token });
3818
+ * ```
3819
+ */
3820
+ declare function createClient(options?: ItdClientOptions): ItdClient;
3821
+
3822
+ /** Бренд, по которому ошибки библиотеки распознаются надёжнее, чем через `instanceof`. */
3823
+ declare const ITD_ERROR: unique symbol;
3824
+ /**
3825
+ * Категория ошибки. Определяет, какие поля у неё есть.
3826
+ *
3827
+ * Значение, а не только тип: категорию нужно с чем-то сравнивать в рантайме.
3828
+ */
3829
+ declare const ItdErrorKind: Readonly<{
3830
+ /** Сервер ответил статусом ≥ 400. */
3831
+ readonly Api: "api";
3832
+ /** Запрос не дошёл до сервера. */
3833
+ readonly Network: "network";
3834
+ /** Истёк таймаут запроса. */
3835
+ readonly Timeout: "timeout";
3836
+ /** Запрос отменён через `AbortSignal`. */
3837
+ readonly Abort: "abort";
3838
+ /** Некорректная конфигурация или аргументы — обнаружено до обращения к сети. */
3839
+ readonly Config: "config";
3840
+ }>;
3841
+ type ItdErrorKind = (typeof ItdErrorKind)[keyof typeof ItdErrorKind];
3842
+ /**
3843
+ * Разновидность ошибки API — то же, что класс ошибки, но в виде данных.
3844
+ *
3845
+ * Существует потому, что `instanceof` подводит, когда в дереве зависимостей оказались
3846
+ * две копии пакета или смешаны сборки ESM и CJS: классы тогда разные, хотя ошибка та же.
3847
+ * Проверки {@link isItdValidationError} и соседние опираются на это поле, а не на класс.
3848
+ */
3849
+ declare const ItdApiErrorKind: Readonly<{
3850
+ /** Ни одна из специализаций не подошла. */
3851
+ readonly Generic: "generic";
3852
+ readonly Validation: "validation";
3853
+ readonly Auth: "auth";
3854
+ readonly Forbidden: "forbidden";
3855
+ readonly NotFound: "not_found";
3856
+ readonly Conflict: "conflict";
3857
+ readonly RateLimit: "rate_limit";
3858
+ readonly PhoneVerification: "phone_verification";
3859
+ readonly Server: "server";
3860
+ }>;
3861
+ type ItdApiErrorKind = (typeof ItdApiErrorKind)[keyof typeof ItdApiErrorKind];
3862
+ /** Ошибки по полям формы: `{ email: ['уже занят'] }`. */
3863
+ type ItdFieldErrors = Record<string, string[]>;
3864
+ /**
3865
+ * Базовый класс всех ошибок библиотеки.
3866
+ *
3867
+ * Ловить его имеет смысл, чтобы отделить проблемы обращения к итд.com от прочих исключений.
3868
+ * Для разбора конкретной причины используйте {@link isItdApiError} и поле {@link ItdApiError.code}.
3869
+ *
3870
+ * @example
3871
+ * ```ts
3872
+ * try {
3873
+ * await itd.posts.like(id);
3874
+ * } catch (e) {
3875
+ * if (isItdError(e)) console.error('итд.com:', e.message);
3876
+ * else throw e;
3877
+ * }
3878
+ * ```
3879
+ */
3880
+ declare class ItdError extends Error {
3881
+ /** @internal */
3882
+ readonly [ITD_ERROR]: true;
3883
+ /** Категория ошибки. */
3884
+ readonly kind: ItdErrorKind;
3885
+ constructor(kind: ItdErrorKind, message: string, options?: {
3886
+ cause?: unknown;
3887
+ });
3888
+ }
3889
+ /** Параметры конструктора {@link ItdApiError}. */
3890
+ interface ItdApiErrorInit {
3891
+ /** HTTP-статус ответа. */
3892
+ status: number;
3893
+ /** Строковый код ошибки из тела ответа. */
3894
+ code: ItdErrorCode;
3895
+ /** Человекочитаемое сообщение. */
3896
+ message: string;
3897
+ /** Расширенное описание, если сервер его прислал. */
3898
+ detail?: string | undefined;
3899
+ /** Заголовок ошибки, если сервер его прислал. */
3900
+ title?: string | undefined;
3901
+ /** Ошибки по конкретным полям (сведены из `errors` и `violations`). */
3902
+ fieldErrors?: ItdFieldErrors | undefined;
3903
+ /** Идентификатор запроса из заголовков ответа, если есть. */
3904
+ requestId?: string | undefined;
3905
+ /** HTTP-метод запроса. */
3906
+ method: string;
3907
+ /** Путь запроса без базового URL. */
3908
+ path: string;
3909
+ /** Тело ответа как оно пришло — на случай, если документация разошлась с реальностью. */
3910
+ raw: unknown;
3911
+ /** Сам объект ответа. Тело уже прочитано. */
3912
+ response?: Response | undefined;
3913
+ /** Значение `Retry-After` в миллисекундах, если заголовок был. */
3914
+ retryAfter?: number | undefined;
3915
+ /** Сколько запросов разрешено в окне (`x-ratelimit-limit`). */
3916
+ rateLimit?: number | undefined;
3917
+ /** Сколько запросов осталось в окне (`x-ratelimit-remaining`). */
3918
+ rateLimitRemaining?: number | undefined;
3919
+ }
3920
+ /**
3921
+ * Ошибка, возвращённая сервером итд.com (HTTP-статус ≥ 400).
3922
+ *
3923
+ * API отдаёт ошибки в двух разных формах — `{ error: { … } }` и `{ code, message, violations }`.
3924
+ * Библиотека сводит обе к этому классу, поэтому разбирать форму ответа вручную не нужно.
3925
+ *
3926
+ * @example
3927
+ * ```ts
3928
+ * try {
3929
+ * await itd.users.updateMe({ username: 'занятое_имя' });
3930
+ * } catch (e) {
3931
+ * if (e instanceof ItdValidationError) {
3932
+ * console.log(e.fieldErrors.username); // ['Имя уже занято']
3933
+ * }
3934
+ * }
3935
+ * ```
3936
+ */
3937
+ declare class ItdApiError extends ItdError {
3938
+ /**
3939
+ * Разновидность ошибки: та же информация, что и класс, но пригодная для сравнения.
3940
+ *
3941
+ * Позволяет разбирать ошибку через `switch`, а проверкам вроде {@link isItdAuthError} —
3942
+ * работать даже когда в проекте оказались две копии библиотеки.
3943
+ */
3944
+ readonly apiKind: ItdApiErrorKind;
3945
+ /** HTTP-статус ответа. */
3946
+ readonly status: number;
3947
+ /** Строковый код ошибки, например `VALIDATION_ERROR`. */
3948
+ readonly code: ItdErrorCode;
3949
+ /** Расширенное описание, если сервер его прислал. */
3950
+ readonly detail: string | undefined;
3951
+ /** Заголовок ошибки, если сервер его прислал. */
3952
+ readonly title: string | undefined;
3953
+ /** Ошибки по полям. Пустой объект, если сервер их не прислал. */
3954
+ readonly fieldErrors: ItdFieldErrors;
3955
+ /** Идентификатор запроса из заголовков ответа. */
3956
+ readonly requestId: string | undefined;
3957
+ /** HTTP-метод запроса. */
3958
+ readonly method: string;
3959
+ /** Путь запроса без базового URL. */
3960
+ readonly path: string;
3961
+ /** Тело ответа как оно пришло. */
3962
+ readonly raw: unknown;
3963
+ /** Объект ответа. Тело уже прочитано и повторно прочитано быть не может. */
3964
+ readonly response: Response | undefined;
3965
+ /** Пауза из заголовка `Retry-After` в миллисекундах. Сервер итд.com его не присылает. */
3966
+ readonly retryAfter: number | undefined;
3967
+ /**
3968
+ * Сколько запросов разрешено в окне — заголовок `x-ratelimit-limit`.
3969
+ *
3970
+ * Времени сброса окна сервер не сообщает, поэтому точный момент повтора неизвестен.
3971
+ */
3972
+ readonly rateLimit: number | undefined;
3973
+ /** Сколько запросов осталось в окне — заголовок `x-ratelimit-remaining`. */
3974
+ readonly rateLimitRemaining: number | undefined;
3975
+ /**
3976
+ * @param apiKind разновидность; подставляется подклассами, снаружи задавать не нужно
3977
+ */
3978
+ constructor(init: ItdApiErrorInit, apiKind?: ItdApiErrorKind);
3979
+ /**
3980
+ * Проверяет код ошибки. Удобнее, чем сравнивать строки вручную.
3981
+ *
3982
+ * @example
3983
+ * ```ts
3984
+ * if (err.hasCode('OTP_INVALID', 'MISSING_FLOW_TOKEN')) await restartOtpFlow();
3985
+ * ```
3986
+ */
3987
+ hasCode(...codes: ItdErrorCode[]): boolean;
3988
+ /** Имеет ли смысл повторить запрос: `429` и серверные ошибки `5xx`. */
3989
+ get isRetryable(): boolean;
3990
+ }
3991
+ /** `400` / `422` — данные не прошли валидацию. Подробности в {@link ItdApiError.fieldErrors}. */
3992
+ declare class ItdValidationError extends ItdApiError {
3993
+ constructor(init: ItdApiErrorInit);
3994
+ }
3995
+ /** `401` — токен отсутствует, истёк или отозван. */
3996
+ declare class ItdAuthError extends ItdApiError {
3997
+ constructor(init: ItdApiErrorInit);
3998
+ }
3999
+ /** `403` — доступ запрещён либо действие ограничено настройками приватности. */
4000
+ declare class ItdForbiddenError extends ItdApiError {
4001
+ constructor(init: ItdApiErrorInit);
4002
+ }
4003
+ /** `404` — сущность не найдена. */
4004
+ declare class ItdNotFoundError extends ItdApiError {
4005
+ constructor(init: ItdApiErrorInit);
4006
+ }
4007
+ /** `409` — сущность уже существует. */
4008
+ declare class ItdConflictError extends ItdApiError {
4009
+ constructor(init: ItdApiErrorInit);
4010
+ }
4011
+ /**
4012
+ * `429` — превышен лимит запросов.
4013
+ *
4014
+ * Если сервер прислал `Retry-After`, пауза доступна в {@link ItdApiError.retryAfter}
4015
+ * (в миллисекундах). При включённых ретраях библиотека выдерживает её автоматически.
4016
+ */
4017
+ declare class ItdRateLimitError extends ItdApiError {
4018
+ constructor(init: ItdApiErrorInit);
4019
+ }
4020
+ /**
4021
+ * Действие требует подтверждённого телефона (`PHONE_VERIFICATION_REQUIRED`).
4022
+ *
4023
+ * Подтверждение проходит через Telegram-бота: ссылка лежит в {@link verificationUrl}.
4024
+ */
4025
+ declare class ItdPhoneVerificationError extends ItdApiError {
4026
+ /** Ссылка на бота подтверждения, если удалось определить идентификатор пользователя. */
4027
+ readonly verificationUrl: string | undefined;
4028
+ constructor(init: ItdApiErrorInit & {
4029
+ userId?: string | undefined;
4030
+ });
4031
+ }
4032
+ /** `5xx` — ошибка на стороне сервера. */
4033
+ declare class ItdServerError extends ItdApiError {
4034
+ constructor(init: ItdApiErrorInit);
4035
+ }
4036
+ /** Запрос не дошёл до сервера: DNS, обрыв соединения, отсутствие сети. */
4037
+ declare class ItdNetworkError extends ItdError {
4038
+ /** HTTP-метод запроса. */
4039
+ readonly method: string;
4040
+ /** Путь запроса без базового URL. */
4041
+ readonly path: string;
4042
+ constructor(message: string, init: {
4043
+ method: string;
4044
+ path: string;
4045
+ cause?: unknown;
4046
+ });
4047
+ }
4048
+ /** Истёк таймаут запроса, заданный опцией `timeout`. */
4049
+ declare class ItdTimeoutError extends ItdError {
4050
+ /** Значение таймаута в миллисекундах. */
4051
+ readonly timeout: number;
4052
+ /** HTTP-метод запроса. */
4053
+ readonly method: string;
4054
+ /** Путь запроса без базового URL. */
4055
+ readonly path: string;
4056
+ constructor(init: {
4057
+ timeout: number;
4058
+ method: string;
4059
+ path: string;
4060
+ });
4061
+ }
4062
+ /** Запрос отменён через переданный `AbortSignal`. */
4063
+ declare class ItdAbortError extends ItdError {
4064
+ constructor(message?: string);
4065
+ }
4066
+ /**
4067
+ * Некорректная конфигурация или аргументы — обнаружено до обращения к сети.
4068
+ *
4069
+ * Этим же классом сообщают о нарушенных инвариантах билдеры: например, опрос
4070
+ * с одним вариантом ответа.
4071
+ */
4072
+ declare class ItdConfigError extends ItdError {
4073
+ constructor(message: string);
4074
+ }
4075
+ /** Любая ошибка, порождённая этой библиотекой. */
4076
+ declare function isItdError(value: unknown): value is ItdError;
4077
+ /** Ошибка, пришедшая от сервера итд.com (статус ≥ 400). */
4078
+ declare function isItdApiError(value: unknown): value is ItdApiError;
4079
+ /** Ошибка валидации: `VALIDATION_ERROR` либо статус `400`/`422`. */
4080
+ declare function isItdValidationError(value: unknown): value is ItdValidationError;
4081
+ /** Ошибка авторизации: истёкший или отозванный токен. */
4082
+ declare function isItdAuthError(value: unknown): value is ItdAuthError;
4083
+ /** Доступ запрещён либо действие ограничено настройками приватности. */
4084
+ declare function isItdForbiddenError(value: unknown): value is ItdForbiddenError;
4085
+ /** Сущность не найдена. */
4086
+ declare function isItdNotFoundError(value: unknown): value is ItdNotFoundError;
4087
+ /** Сущность уже существует. */
4088
+ declare function isItdConflictError(value: unknown): value is ItdConflictError;
4089
+ /** Превышен лимит запросов. */
4090
+ declare function isItdRateLimitError(value: unknown): value is ItdRateLimitError;
4091
+ /** Действие требует подтверждённого телефона. Ссылка — в `verificationUrl`. */
4092
+ declare function isItdPhoneVerificationError(value: unknown): value is ItdPhoneVerificationError;
4093
+ /** Ошибка на стороне сервера (`5xx`). */
4094
+ declare function isItdServerError(value: unknown): value is ItdServerError;
4095
+
4096
+ /** Изображения, которые принимает `POST /api/files/upload`. */
4097
+ declare const IMAGE_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/gif", "image/webp", "image/avif", "image/heic", "image/heif"];
4098
+ type ImageMimeType = (typeof IMAGE_MIME_TYPES)[number];
4099
+ /** Видео, которые принимает `POST /api/files/upload`. */
4100
+ declare const VIDEO_MIME_TYPES: readonly ["video/mp4", "video/webm", "video/quicktime"];
4101
+ type VideoMimeType = (typeof VIDEO_MIME_TYPES)[number];
4102
+ /** Аудио для голосовых комментариев. */
4103
+ declare const AUDIO_MIME_TYPES: readonly ["audio/ogg"];
4104
+ type AudioMimeType = (typeof AUDIO_MIME_TYPES)[number];
4105
+ /** Все типы, которые принимает загрузка. */
4106
+ declare const ALLOWED_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/gif", "image/webp", "image/avif", "image/heic", "image/heif", "video/mp4", "video/webm", "video/quicktime", "audio/ogg"];
4107
+ type AllowedMimeType = (typeof ALLOWED_MIME_TYPES)[number];
4108
+
4109
+ /**
4110
+ * Приводит отметку времени без часового пояса к ISO-8601, считая её временем UTC.
4111
+ *
4112
+ * Строку другого вида возвращает нетронутой.
4113
+ *
4114
+ * @example
4115
+ * ```ts
4116
+ * utcStampToIso('2026-07-23 23:14:25'); // '2026-07-23T23:14:25Z'
4117
+ * utcStampToIso('2026-07-23T23:14:25Z'); // без изменений
4118
+ * ```
4119
+ */
4120
+ declare function utcStampToIso(value: string): string;
4121
+
4122
+ /**
4123
+ * Собирает текст уведомления на русском.
4124
+ *
4125
+ * Повторяет формулировки сайта итд.com. Для неизвестного типа возвращает
4126
+ * «Новое уведомление» — библиотека не выдумывает текст, которого нет.
4127
+ *
4128
+ * @example
4129
+ * ```ts
4130
+ * formatNotificationText(notification);
4131
+ * // 'Аня и ещё 2 оценили ваш пост'
4132
+ * ```
4133
+ */
4134
+ declare function formatNotificationText(notification: Notification): string;
4135
+
4136
+ /**
4137
+ * Соответствие коротких имён типов уведомлений развёрнутым.
4138
+ *
4139
+ * Сервер — и в списке, и в потоке событий — присылает короткие имена: `like`, `comment`,
4140
+ * `reply`, `repost`, `comment_like`. Развёрнутые (`post_reaction`, `post_comment`)
4141
+ * встречаются в оформлении интерфейса, поэтому библиотека приводит типы к ним:
4142
+ * они однозначно называют и объект, и действие.
4143
+ *
4144
+ * Пришедшее значение всегда остаётся в поле `rawType`.
4145
+ */
4146
+ declare const NOTIFICATION_TYPE_ALIASES: Readonly<Record<string, NotificationType>>;
4147
+ /**
4148
+ * Приводит имя типа к каноническому.
4149
+ *
4150
+ * Неизвестное значение возвращается без изменений. Официальный клиент в этом случае
4151
+ * подставляет `follow`, из-за чего новое уведомление выглядит как подписка, — здесь
4152
+ * такого не происходит.
4153
+ *
4154
+ * @example
4155
+ * ```ts
4156
+ * canonicalNotificationType('like'); // 'post_reaction'
4157
+ * canonicalNotificationType('post_reaction'); // 'post_reaction'
4158
+ * canonicalNotificationType('новое_событие'); // 'новое_событие'
4159
+ * ```
4160
+ */
4161
+ declare function canonicalNotificationType(rawType: string): NotificationType;
4162
+ /**
4163
+ * Известен ли библиотеке этот тип уведомления.
4164
+ *
4165
+ * Полезно, чтобы решить, показывать ли уведомление, для которого нет своего оформления.
4166
+ */
4167
+ declare function isKnownNotificationType(type: string): boolean;
4168
+
4169
+ /**
4170
+ * Вычисляет адрес, на который ведёт уведомление.
4171
+ *
4172
+ * Возвращает путь внутри сайта — без домена, чтобы его можно было передать роутеру
4173
+ * приложения. Поле `clickUrl` от сервера используется только как запасной вариант:
4174
+ * вычисленный путь точнее, поскольку учитывает родительский пост у комментариев.
4175
+ *
4176
+ * @example
4177
+ * ```ts
4178
+ * const url = resolveNotificationUrl(notification);
4179
+ * // '/@durov/post/9f1c…?comment=2b7e…'
4180
+ * ```
4181
+ */
4182
+ declare function resolveNotificationUrl(notification: Notification): string;
4183
+
4184
+ /** Настройки опроса. */
4185
+ interface PollTransportOptions {
4186
+ /** Как часто опрашивать сервер, мс. По умолчанию 15 000. */
4187
+ interval?: number;
4188
+ /** Сколько уведомлений запрашивать за раз. По умолчанию 20. */
4189
+ limit?: number;
4190
+ }
4191
+
4192
+ /** Путь потока уведомлений. */
4193
+ declare const STREAM_PATH = "/api/notifications/stream";
4194
+ /** Настройки SSE-транспорта. */
4195
+ interface SseTransportOptions {
4196
+ /**
4197
+ * Сколько миллисекунд ждать данных, прежде чем считать соединение мёртвым.
4198
+ *
4199
+ * Сервер не присылает keep-alive, а оборванное TCP-соединение может не закрыться
4200
+ * само — без этой проверки поток «тихо умирает» и новых уведомлений не приходит.
4201
+ * По умолчанию 90 000. `0` отключает проверку.
4202
+ */
4203
+ idleTimeout?: number;
4204
+ }
4205
+
4206
+ export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AUTH_FLAG_COOKIE, AUTH_PATHS, AccessType, type Actor, type AllowedMimeType, type Announcement, type AnnouncementButton, type Attachment, AttachmentType, type AudioMimeType, type AuthEvents, type AuthInput, AuthResource, type Author, BUILT_IN_SERVICES, type BuilderInput, type CaptchaCredentials, type ChangelogEntry, type Clan, type ClientHooks, type Comment, CommentBuilder, type CommentInput, type CommentReplyTo, CommentSort, type CommentsParams, CommentsResource, type CreateCommentInput, type CreatePollInput, type CreatePostInput, type CreateReportInput, type Credentials, type CredentialsAuth, DEFAULT_BASE_URL, DEFAULT_STATUS_BASE_URL, DEFAULT_TIMEOUT, DEFAULT_UPLOAD_TIMEOUT, DEFAULT_USER_AGENT, DEVICE_ID_HEADER, DetectedRuntime, type DwellEntry, type ErrorContextHook, type FeedParams, FeedTab, type FileInput, type FileReader, FilesResource, type FollowResult, type ForgotPasswordInput, type Hashtag, type HashtagPostsParams, HashtagsResource, IMAGE_MIME_TYPES, type ImageMimeType, IncidentKind, type InteractionEntry, InteractionType, type IsoDate, ItdAbortError, ItdApiError, type ItdApiErrorInit, ItdApiErrorKind, ItdAuthError, type ItdBuilder, ItdClient, type ItdClientOptions, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdErrorKind, type ItdFieldErrors, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, type ItdPlugin, ItdRateLimitError, ItdRealtime, ItdServerError, type ItdSession, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, type LikeResult, LikesVisibility, type Listener, LocalStorageTokenStorage, type Logger, type Loose, MAX_RECONNECT_ATTEMPTS, MemoryTokenStorage, type MyProfile, NOTIFICATION_TYPE_ALIASES, type Notification, type NotificationEvent, type NotificationListParams, type NotificationSettings, NotificationType, NotificationsResource, OAuthProvider, type Page, type PageState, PaginationMode, Paginator, type PaginatorOptions, type PaymentMethod, type Pin, type PinPostResult, type PinsResult, PlatformResource, type PlatformStatus, type PluginContext, type Poll, PollBuilder, type PollInput, type PollOption, type PollTransportOptions, type Portal, type Post, PostBuilder, type PostInput, type PostStats, PostsResource, type PrivacySettings, type Profile, type PublicProfile, type QueryParams, type QueryValue, RECONNECT_BACKOFF, RECONNECT_JITTER, REFRESH_COOKIE, REFRESH_COOKIE_PATH, REQUEST_OPTION_KEYS, type RateLimitOptions, type RawRequestOptions, type RealtimeDeps, type RealtimeEvents, type RealtimeOptions, RealtimeStatus, type RealtimeTransport, RealtimeTransportKind, type ReconnectOptions, type RepliesParams, type Report, ReportBuilder, type ReportInput, ReportReason, ReportTargetType, ReportsResource, type RequestContext, type RequestOptions, type ResetPasswordInput, type ResponseContext, type RetryContext, type RetryOptions, RuntimeMode, STATUS_SERVICE, STREAM_PATH, SearchResource, type SearchResult, type ServiceDefinition, ServiceRegistry, ServiceState, type ServiceStatus, type Session, type SignInResult, SignInStatus, type Span, SpanType, type SseTransportOptions, type StatusDay, type StatusIncidentLine, type Subscription, SubscriptionResource, type SubscriptionState, TURNSTILE_SITE_KEY, type TelemetryOptions, TelemetryResource, type TokenStorage, type Transformer, type TransportContext, type TransportEvent, UnauthorizedStreamError, type Unsubscribe, type UpdateNotificationSettingsInput, type UpdatePrivacyInput, type UpdateProfileInput, type UploadOptions, type UploadedFile, type UserId, type UserListParams, type UserPostsParams, type UserRef, type UserSummary, UsersResource, VIDEO_MIME_TYPES, VerificationResource, type VerificationStatus, type VideoMimeType, ViewReason, ViewSource, WallAccess, canonicalNotificationType, comment, createClient, createTokenStorage, formatNotificationText, isBuilder, isItdApiError, isItdAuthError, isItdConflictError, isItdError, isItdForbiddenError, isItdNotFoundError, isItdPhoneVerificationError, isItdRateLimitError, isItdServerError, isItdValidationError, isKnownNotificationType, isMyProfile, mapPage, normalizeNotification, poll, post, readNotificationEvent, readUnreadCountEvent, report, resolveNotificationUrl, statusDays, toDate, utcStampToIso };