itd-api 0.0.1

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.
@@ -0,0 +1,3238 @@
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
+ declare const ReportTargetType: Readonly<{
102
+ readonly Post: "post";
103
+ readonly Comment: "comment";
104
+ readonly User: "user";
105
+ }>;
106
+ type ReportTargetType = (typeof ReportTargetType)[keyof typeof ReportTargetType];
107
+ /** Причина жалобы. Множество закрытое. */
108
+ declare const ReportReason: Readonly<{
109
+ readonly Spam: "spam";
110
+ readonly Violence: "violence";
111
+ readonly Hate: "hate";
112
+ readonly Adult: "adult";
113
+ readonly Fraud: "fraud";
114
+ readonly Other: "other";
115
+ }>;
116
+ type ReportReason = (typeof ReportReason)[keyof typeof ReportReason];
117
+ /** Состояние realtime-соединения. */
118
+ declare const RealtimeStatus: Readonly<{
119
+ readonly Connecting: "connecting";
120
+ readonly Connected: "connected";
121
+ readonly Error: "error";
122
+ readonly Disconnected: "disconnected";
123
+ }>;
124
+ type RealtimeStatus = (typeof RealtimeStatus)[keyof typeof RealtimeStatus];
125
+ /**
126
+ * Кто может писать на стену профиля.
127
+ *
128
+ * Документация перечисляет значения не полностью, поэтому тип открытый: объект ниже —
129
+ * справочник известных значений, но сервер может прислать и другое.
130
+ */
131
+ declare const WallAccess: Readonly<{
132
+ readonly Everyone: "everyone";
133
+ }>;
134
+ type WallAccess = Loose<(typeof WallAccess)[keyof typeof WallAccess]>;
135
+ /** Кто видит реакции пользователя. Список в документации неполный. */
136
+ declare const LikesVisibility: Readonly<{
137
+ readonly Everyone: "everyone";
138
+ /** Только взаимные подписки. */
139
+ readonly Mutual: "mutual";
140
+ }>;
141
+ type LikesVisibility = Loose<(typeof LikesVisibility)[keyof typeof LikesVisibility]>;
142
+ /**
143
+ * Канонический тип уведомления (новое поколение имён).
144
+ *
145
+ * REST-эндпоинт `/api/notifications/` отдаёт старые имена (`like`, `comment`, `reply`,
146
+ * `repost`, `mention`), SSE-поток — новые. Библиотека приводит их к этому набору,
147
+ * сохраняя исходное значение в поле `rawType`.
148
+ */
149
+ declare const NotificationType: Readonly<{
150
+ /** Реакция на пост. Старое имя — `like`. */
151
+ readonly PostReaction: "post_reaction";
152
+ /** Комментарий к посту. Старое имя — `comment`. */
153
+ readonly PostComment: "post_comment";
154
+ /** Ответ на комментарий. Старое имя — `reply`. */
155
+ readonly CommentReply: "comment_reply";
156
+ /** Репост. Старое имя — `repost`. */
157
+ readonly PostRepost: "post_repost";
158
+ /** Упоминание в посте. Старое имя — `mention`. */
159
+ readonly PostMention: "post_mention";
160
+ /** Реакция на комментарий. */
161
+ readonly CommentReaction: "comment_reaction";
162
+ /** Упоминание в комментарии. */
163
+ readonly CommentMention: "comment_mention";
164
+ /** Запись на вашей стене. */
165
+ readonly WallPost: "wall_post";
166
+ /** На вас подписались. */
167
+ readonly Follow: "follow";
168
+ /** Заявка на подписку (закрытый профиль). */
169
+ readonly FollowRequest: "follow_request";
170
+ /** Заявка на подписку принята. */
171
+ readonly FollowAccepted: "follow_accepted";
172
+ /** Верификация одобрена. Приходит только по REST. */
173
+ readonly VerificationApproved: "verification_approved";
174
+ /** Верификация отклонена. Приходит только по REST. */
175
+ readonly VerificationRejected: "verification_rejected";
176
+ }>;
177
+ type NotificationType = Loose<(typeof NotificationType)[keyof typeof NotificationType]>;
178
+ /**
179
+ * Строковые коды ошибок из поля `code`.
180
+ *
181
+ * Ключи намеренно повторяют написание сервера: код из ответа API можно найти здесь
182
+ * поиском один в один, без мысленного перевода регистра.
183
+ *
184
+ * Список открыт — сервер может добавить новый код, и это не должно ломать типизацию.
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * if (err.hasCode(ItdErrorCode.OTP_INVALID)) await restartOtpFlow();
189
+ * ```
190
+ */
191
+ declare const ItdErrorCode: Readonly<{
192
+ readonly BAD_REQUEST: "BAD_REQUEST";
193
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
194
+ readonly ACCESS_DENIED: "ACCESS_DENIED";
195
+ readonly ENTITY_NOT_FOUND: "ENTITY_NOT_FOUND";
196
+ readonly ENTITY_ALREADY_EXISTS: "ENTITY_ALREADY_EXISTS";
197
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
198
+ readonly BUSINESS_RULE_VIOLATION: "BUSINESS_RULE_VIOLATION";
199
+ readonly RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED";
200
+ readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
201
+ readonly CAPTCHA_FAILED: "CAPTCHA_FAILED";
202
+ readonly OTP_INVALID: "OTP_INVALID";
203
+ readonly ACCOUNT_DEACTIVATED: "ACCOUNT_DEACTIVATED";
204
+ readonly ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED: "ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED";
205
+ readonly ACCOUNT_INVALID_CREDENTIALS: "ACCOUNT_INVALID_CREDENTIALS";
206
+ readonly ACCOUNT_TEMPORARILY_LOCKED: "ACCOUNT_TEMPORARILY_LOCKED";
207
+ readonly ACCOUNT_CURRENT_PASSWORD_INCORRECT: "ACCOUNT_CURRENT_PASSWORD_INCORRECT";
208
+ readonly SESSION_EXPIRED: "SESSION_EXPIRED";
209
+ readonly SESSION_REVOKED: "SESSION_REVOKED";
210
+ readonly SESSION_INVALID_REFRESH_TOKEN: "SESSION_INVALID_REFRESH_TOKEN";
211
+ readonly MISSING_FLOW_TOKEN: "MISSING_FLOW_TOKEN";
212
+ readonly PROFILE_USERNAME_TAKEN: "PROFILE_USERNAME_TAKEN";
213
+ readonly PROFILE_RESTRICTION_ACTIVE: "PROFILE_RESTRICTION_ACTIVE";
214
+ readonly PROFILE_MODIFICATION_RESTRICTED: "PROFILE_MODIFICATION_RESTRICTED";
215
+ readonly CONTENT_MODERATION_FAILED: "CONTENT_MODERATION_FAILED";
216
+ readonly FILE_TOO_LARGE: "FILE_TOO_LARGE";
217
+ readonly UNSUPPORTED_FILE_TYPE: "UNSUPPORTED_FILE_TYPE";
218
+ readonly UPLOAD_FAILED: "UPLOAD_FAILED";
219
+ readonly VIDEO_REQUIRES_VERIFICATION: "VIDEO_REQUIRES_VERIFICATION";
220
+ readonly PHONE_VERIFICATION_REQUIRED: "PHONE_VERIFICATION_REQUIRED";
221
+ readonly WRITE_ACCESS_RESTRICTED: "WRITE_ACCESS_RESTRICTED";
222
+ }>;
223
+ type ItdErrorCode = Loose<(typeof ItdErrorCode)[keyof typeof ItdErrorCode]>;
224
+
225
+ /**
226
+ * Дата и время в формате ISO-8601, например `2026-07-21T14:30:00.000Z`.
227
+ *
228
+ * Библиотека не превращает такие поля в `Date`: строку проще сравнивать, логировать
229
+ * и передавать дальше без потерь. Для разбора есть {@link toDate}.
230
+ */
231
+ type IsoDate = string;
232
+ /**
233
+ * Идентификатор пользователя — **строго UUID**.
234
+ *
235
+ * Отличается от {@link UserRef} тем, что имя пользователя здесь не подойдёт. Так помечены
236
+ * места, где API принимает только UUID: например `wallRecipientId` при постинге на чужую стену.
237
+ */
238
+ type UserId = string;
239
+ /**
240
+ * Ссылка на пользователя: **UUID либо имя пользователя**.
241
+ *
242
+ * Пути вида `/api/users/{id}` принимают оба варианта, поэтому `itd.users.get('durov')`
243
+ * работает так же, как `itd.users.get('9f1c…')`.
244
+ */
245
+ type UserRef = string;
246
+ /**
247
+ * Разметка в тексте поста.
248
+ *
249
+ * Приходит от сервера и отправляется обратно как есть — библиотека разметку не генерирует
250
+ * и не пересчитывает.
251
+ *
252
+ * Единицы `offset` и `length` в документации API не уточнены (UTF-16 или кодовые точки),
253
+ * поэтому при работе с эмодзи проверяйте результат.
254
+ */
255
+ interface Span {
256
+ /** Тип фрагмента: `hashtag`, `mention`, `link` и другие. */
257
+ type: Loose<'hashtag' | 'mention' | 'link'>;
258
+ /** Смещение от начала текста. */
259
+ offset: number;
260
+ /** Длина фрагмента. */
261
+ length: number;
262
+ /** Содержимое: имя хэштега без решётки, имя пользователя, адрес ссылки. */
263
+ tag?: string;
264
+ }
265
+ /**
266
+ * Значок-«пин» в профиле — награда или отметка платформы.
267
+ */
268
+ interface Pin {
269
+ /** Постоянный идентификатор, например `epepuy_202605_59`. */
270
+ slug: string;
271
+ /** Отображаемое название. */
272
+ name: string;
273
+ /** Описание, за что выдан. */
274
+ description: string;
275
+ /** Адрес изображения. */
276
+ url: string;
277
+ /** Когда выдан. Приходит только в списке своих пинов. */
278
+ grantedAt?: IsoDate;
279
+ }
280
+ /**
281
+ * Автор поста или комментария.
282
+ *
283
+ * Встречается внутри `post.author` и `comment.author`.
284
+ */
285
+ interface Author {
286
+ id: UserId;
287
+ username: string;
288
+ displayName: string;
289
+ /**
290
+ * **Эмодзи, а не картинка.**
291
+ *
292
+ * На итд.com аватар — это символ клана (`🩵`, `🦎`), а не адрес изображения.
293
+ * Отрисовывать его нужно как текст.
294
+ */
295
+ avatar: string;
296
+ /** Пройдена ли верификация. */
297
+ verified: boolean;
298
+ /** Активный значок профиля. Может отсутствовать. */
299
+ pin?: Pin | null;
300
+ /** Есть ли премиум-подписка (значок NUKSTA). */
301
+ hasNuksta?: boolean;
302
+ }
303
+ /**
304
+ * Участник события в уведомлении.
305
+ *
306
+ * Отличается от {@link Author} набором полей: вместо значков приходит связь с вами.
307
+ */
308
+ interface Actor {
309
+ id: UserId;
310
+ username: string;
311
+ displayName: string;
312
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
313
+ avatar: string;
314
+ /** Подписаны ли вы на этого пользователя. */
315
+ isFollowing?: boolean;
316
+ /** Подписан ли он на вас. */
317
+ isFollowedBy?: boolean;
318
+ }
319
+ /**
320
+ * Пользователь в списках.
321
+ *
322
+ * Набор полей зависит от эндпоинта: подписчики и подписки приносят `isFollowing`,
323
+ * поиск и рекомендации — `followersCount` и `hasNuksta`. Необязательные поля отражают
324
+ * это различие.
325
+ */
326
+ interface UserSummary {
327
+ id: UserId;
328
+ username: string;
329
+ displayName: string;
330
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
331
+ avatar: string;
332
+ verified: boolean;
333
+ /** Подписаны ли вы. Приходит в списках подписчиков и подписок. */
334
+ isFollowing?: boolean;
335
+ /** Есть ли премиум. Приходит в поиске и рекомендациях. */
336
+ hasNuksta?: boolean;
337
+ /** Число подписчиков. Приходит в поиске и рекомендациях. */
338
+ followersCount?: number;
339
+ }
340
+ /** Поля профиля, общие для своего и чужого. */
341
+ interface ProfileBase {
342
+ id: UserId;
343
+ username: string;
344
+ displayName: string;
345
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
346
+ avatar: string;
347
+ /** Адрес изображения-шапки либо `null`. В отличие от аватара это настоящий URL. */
348
+ banner: string | null;
349
+ /** Описание профиля. */
350
+ bio: string;
351
+ verified: boolean;
352
+ pin?: Pin | null;
353
+ /** Кто может писать на стену. */
354
+ wallAccess: WallAccess;
355
+ /** Кто видит реакции. */
356
+ likesVisibility: LikesVisibility;
357
+ followersCount: number;
358
+ followingCount: number;
359
+ postsCount: number;
360
+ createdAt: IsoDate;
361
+ }
362
+ /** Состояние подписки на премиум. */
363
+ interface SubscriptionState {
364
+ isActive: boolean;
365
+ expiresAt: IsoDate | null;
366
+ autoRenewal: boolean;
367
+ }
368
+ /**
369
+ * Свой профиль — ответ `GET /api/users/me`.
370
+ *
371
+ * Отличается от чужого наличием {@link subscription} и {@link isPhoneVerified}
372
+ * и отсутствием полей связи (`isFollowing`, `online`).
373
+ */
374
+ interface MyProfile extends ProfileBase {
375
+ /** Закрыт ли профиль. */
376
+ isPrivate: boolean;
377
+ /** Подтверждён ли телефон. Без него часть действий недоступна. */
378
+ isPhoneVerified: boolean;
379
+ /** Своя премиум-подписка. */
380
+ subscription: SubscriptionState;
381
+ }
382
+ /**
383
+ * Чужой профиль — ответ `GET /api/users/{id|username}`.
384
+ *
385
+ * Вместо своей подписки содержит связь с вами и присутствие.
386
+ */
387
+ interface PublicProfile extends ProfileBase {
388
+ hasNuksta?: boolean;
389
+ /** Закреплённый пост, если он есть. */
390
+ pinnedPostId: string | null;
391
+ /** Подписаны ли вы на него. */
392
+ isFollowing: boolean;
393
+ /** Подписан ли он на вас. */
394
+ isFollowedBy: boolean;
395
+ /** Сейчас ли пользователь в сети. */
396
+ online: boolean;
397
+ /** Когда был в сети. `null`, если скрыто настройками приватности. */
398
+ lastSeen: IsoDate | null;
399
+ }
400
+ /** Профиль: свой либо чужой. Различаются функцией {@link isMyProfile}. */
401
+ type Profile = MyProfile | PublicProfile;
402
+ /**
403
+ * Свой ли это профиль.
404
+ *
405
+ * @example
406
+ * ```ts
407
+ * if (isMyProfile(profile)) console.log(profile.subscription.isActive);
408
+ * ```
409
+ */
410
+ declare function isMyProfile(profile: Profile): profile is MyProfile;
411
+ /** Вложение поста или комментария. */
412
+ interface Attachment {
413
+ id: string;
414
+ type: AttachmentType;
415
+ /** Адрес файла на CDN. */
416
+ url: string;
417
+ /** Ширина изображения или видео в пикселях. */
418
+ width?: number;
419
+ /** Высота изображения или видео в пикселях. */
420
+ height?: number;
421
+ mimeType: string;
422
+ /** Исходное имя файла. Приходит не всегда. */
423
+ filename?: string;
424
+ /** Размер в байтах. Приходит не всегда. */
425
+ size?: number;
426
+ /** Длительность аудио или видео в секундах. */
427
+ duration?: number | null;
428
+ /** Порядковый номер во вложениях поста. */
429
+ order?: number;
430
+ }
431
+ /** Вариант ответа в опросе. */
432
+ interface PollOption {
433
+ id: string;
434
+ text: string;
435
+ /** Сколько голосов отдано за этот вариант. */
436
+ votesCount: number;
437
+ /** Порядковый номер варианта, начиная с нуля. */
438
+ position: number;
439
+ }
440
+ /** Опрос внутри поста. */
441
+ interface Poll {
442
+ id: string;
443
+ /** Пост, которому принадлежит опрос. */
444
+ postId: string;
445
+ question: string;
446
+ /** Можно ли выбрать несколько вариантов. */
447
+ multipleChoice: boolean;
448
+ options: PollOption[];
449
+ totalVotes: number;
450
+ /** Голосовали ли вы. */
451
+ hasVoted: boolean;
452
+ /** За что проголосовали вы. Пустой массив, если голоса не было. */
453
+ votedOptionIds: string[];
454
+ createdAt: IsoDate;
455
+ }
456
+ /** Пост ленты, стены или профиля. */
457
+ interface Post {
458
+ id: string;
459
+ content: string;
460
+ /** Разметка текста. Передаётся без изменений, см. {@link Span}. */
461
+ spans: Span[];
462
+ author: Author;
463
+ attachments: Attachment[];
464
+ likesCount: number;
465
+ commentsCount: number;
466
+ repostsCount: number;
467
+ viewsCount: number;
468
+ /** Чья это стена, если пост опубликован не у себя. */
469
+ wallRecipientId: UserId | null;
470
+ /** Владелец стены. Приходит не во всех ответах. */
471
+ wallRecipient?: Author | null;
472
+ /** Поставили ли вы реакцию. */
473
+ isLiked: boolean;
474
+ /** Делали ли вы репост. */
475
+ isReposted: boolean;
476
+ /** Засчитан ли просмотр. */
477
+ isViewed: boolean;
478
+ /** Ваш ли это пост. */
479
+ isOwner: boolean;
480
+ /** Исходный пост, если это репост. */
481
+ originalPost?: Post | null;
482
+ poll?: Poll | null;
483
+ /** Преобладающая реакция — эмодзи либо `null`. */
484
+ dominantEmoji?: string | null;
485
+ /** Когда пост отредактировали. `null`, если не редактировали. */
486
+ editedAt: IsoDate | null;
487
+ createdAt: IsoDate;
488
+ /**
489
+ * Служебная метка показа для телеметрии.
490
+ *
491
+ * Нужна только эндпоинтам `itd.telemetry.*`. В остальных случаях игнорируйте.
492
+ */
493
+ vs?: string;
494
+ /**
495
+ * Топовые комментарии. Приходят только в ответе `GET /api/posts/{id}`.
496
+ *
497
+ * В списках постов поле отсутствует.
498
+ */
499
+ comments?: Comment[];
500
+ }
501
+ /** На чей комментарий дан ответ. */
502
+ interface CommentReplyTo {
503
+ id: string;
504
+ username: string;
505
+ displayName: string;
506
+ }
507
+ /** Комментарий к посту или ответ на комментарий. */
508
+ interface Comment {
509
+ id: string;
510
+ /** Текст. У голосового комментария пустой. */
511
+ content: string;
512
+ author: Author;
513
+ likesCount: number;
514
+ repliesCount: number;
515
+ isLiked: boolean;
516
+ createdAt: IsoDate;
517
+ /** Вложения. У голосового — одно аудио с `mimeType: 'audio/ogg'`. */
518
+ attachments?: Attachment[];
519
+ /** Вложенные ответы. В списках приходит превью, полный список — через `itd.comments.replies()`. */
520
+ replies?: Comment[];
521
+ /** Заполнено только у ответов. */
522
+ replyTo?: CommentReplyTo;
523
+ }
524
+ /**
525
+ * Уведомление в единой форме.
526
+ *
527
+ * REST-список и SSE-поток отдают уведомления по-разному — разные имена типов, разные имена
528
+ * полей, один участник против массива. Библиотека приводит оба вида к этой структуре,
529
+ * поэтому объекты из `itd.notifications.list()` и из потока можно складывать в один список.
530
+ *
531
+ * Исходные данные не теряются: сервeрное имя типа остаётся в {@link rawType},
532
+ * а весь необработанный объект — в {@link raw}.
533
+ */
534
+ interface Notification {
535
+ id: string;
536
+ /** Канонический тип. Старые имена (`like`, `comment`) приведены к новым. */
537
+ type: NotificationType;
538
+ /** Имя типа в том виде, в каком его прислал сервер. */
539
+ rawType: string;
540
+ /** Объект события: пост, комментарий, пользователь. */
541
+ entityId: string | null;
542
+ /** Пост, которому принадлежит комментарий, если событие о комментарии. */
543
+ parentEntityId: string | null;
544
+ /** Прочитано ли уведомление. */
545
+ isRead: boolean;
546
+ /** Кто совершил действие. Для схлопнутых уведомлений — несколько человек. */
547
+ actors: Actor[];
548
+ /** Сколько участников схлопнуто в одно уведомление. Минимум 1. */
549
+ count: number;
550
+ /** Текст или заголовок объекта события. */
551
+ preview: string | null;
552
+ /** Ссылка перехода, предложенная сервером. Обычно точнее её `resolveNotificationUrl()`. */
553
+ clickUrl?: string;
554
+ createdAt: IsoDate;
555
+ /** Когда уведомление изменилось — например было прочитано. */
556
+ updatedAt: IsoDate;
557
+ /** Исходный объект как он пришёл от сервера. */
558
+ raw: unknown;
559
+ }
560
+ /** Настройки приватности профиля. */
561
+ interface PrivacySettings {
562
+ /** Закрыт ли профиль: подписка требует одобрения. */
563
+ isPrivate: boolean;
564
+ wallAccess: WallAccess;
565
+ likesVisibility: LikesVisibility;
566
+ /** Показывать ли время последнего посещения. */
567
+ showLastSeen: boolean;
568
+ }
569
+ /**
570
+ * Настройки уведомлений.
571
+ *
572
+ * Сервер отдаёт плоский объект, но исторически знает два набора имён для одних и тех же
573
+ * настроек (`likes` и `reactions`, `comments` и `replies`). При сохранении библиотека
574
+ * отправляет оба, при чтении принимает любой.
575
+ */
576
+ interface NotificationSettings {
577
+ /** Общий выключатель доставки. */
578
+ enabled: boolean;
579
+ /** Звук уведомления. */
580
+ sound: boolean;
581
+ /** Новые подписчики. */
582
+ follows: boolean;
583
+ /** Записи на вашей стене. */
584
+ wallPosts: boolean;
585
+ /** Реакции на ваши записи. */
586
+ likes: boolean;
587
+ /** Комментарии и ответы. */
588
+ comments: boolean;
589
+ /** Упоминания. */
590
+ mentions: boolean;
591
+ }
592
+ /** Активная сессия входа. */
593
+ interface Session {
594
+ id: string;
595
+ /** Та ли это сессия, из которой выполнен запрос. */
596
+ isCurrent: boolean;
597
+ createdAt: IsoDate;
598
+ lastUsedAt: IsoDate;
599
+ expiresAt: IsoDate;
600
+ ipAddress: string;
601
+ /** Код страны по IP, например `RU`. */
602
+ ipCountry: string | null;
603
+ ipCity: string | null;
604
+ deviceType: Loose<'desktop' | 'mobile'>;
605
+ osName: string | null;
606
+ osVersion: string | null;
607
+ /** Название браузера или приложения. */
608
+ clientName: string | null;
609
+ clientVersion: string | null;
610
+ deviceModel: string | null;
611
+ }
612
+ /** Состояние платной подписки и её цена. */
613
+ interface Subscription {
614
+ /** Активна ли подписка сейчас. */
615
+ active: boolean;
616
+ /** Включено ли автопродление. */
617
+ recurringEnabled: boolean;
618
+ /** Цена в рублях. */
619
+ price: number;
620
+ }
621
+ /** Сохранённый способ оплаты. */
622
+ interface PaymentMethod {
623
+ id: string;
624
+ /** Последние четыре цифры карты. */
625
+ last4?: string;
626
+ /** Платёжная система: `visa`, `mastercard`, `mir`. */
627
+ brand?: string;
628
+ /** Основной ли это способ оплаты. */
629
+ isDefault?: boolean;
630
+ expiresAt?: IsoDate | null;
631
+ }
632
+ /** Хэштег. */
633
+ interface Hashtag {
634
+ id: string;
635
+ /** Название без решётки. */
636
+ name: string;
637
+ /** Сколько постов с этим хэштегом. */
638
+ postsCount: number;
639
+ }
640
+ /** Клан в рейтинге. */
641
+ interface Clan {
642
+ /** Эмодзи клана — оно же аватар его участников. */
643
+ avatar: string;
644
+ memberCount: number;
645
+ }
646
+ /**
647
+ * Результат подписки на пользователя.
648
+ *
649
+ * @example
650
+ * ```ts
651
+ * const result = await itd.users.follow('durov');
652
+ * // { following: true, followersCount: 11 }
653
+ * ```
654
+ */
655
+ interface FollowResult {
656
+ /** Подписка оформлена. У закрытого профиля отправляется заявка, и здесь будет `false`. */
657
+ following: boolean;
658
+ /** Сколько подписчиков стало у пользователя после действия. */
659
+ followersCount?: number;
660
+ /** Статус заявки, если профиль закрыт. */
661
+ status?: Loose<'following' | 'requested'>;
662
+ }
663
+ /** Запись журнала изменений платформы. */
664
+ interface ChangelogEntry {
665
+ version: string;
666
+ date: string;
667
+ changes: string[];
668
+ }
669
+ /** Кнопка в анонсе платформы. */
670
+ interface AnnouncementButton {
671
+ title: string;
672
+ /** Оформление: `primary`, `secondary` и другие. */
673
+ style: string;
674
+ action: {
675
+ type: string;
676
+ [key: string]: unknown;
677
+ };
678
+ }
679
+ /** Анонс на главной странице платформы. */
680
+ interface Announcement {
681
+ id: string;
682
+ image: {
683
+ url: string;
684
+ width: number;
685
+ height: number;
686
+ };
687
+ title: string;
688
+ description: string;
689
+ /** Дополнительный текст мелким шрифтом. */
690
+ additional_text?: string;
691
+ buttons: AnnouncementButton[];
692
+ }
693
+ /** Баннер текущего события — виджет «портал». */
694
+ interface Portal {
695
+ active: boolean;
696
+ title: string;
697
+ url: string;
698
+ }
699
+ /** Статус заявки на верификацию. `none` означает, что заявка не подавалась. */
700
+ interface VerificationStatus {
701
+ status: Loose<'none' | 'pending' | 'approved' | 'rejected'>;
702
+ }
703
+ /** Созданная жалоба. */
704
+ interface Report {
705
+ id: string;
706
+ createdAt: IsoDate;
707
+ }
708
+ /** Счётчики поста из `itd.posts.stats()`. */
709
+ interface PostStats {
710
+ id: string;
711
+ likesCount: number;
712
+ commentsCount: number;
713
+ repostsCount: number;
714
+ viewsCount: number;
715
+ /** Преобладающая реакция — эмодзи либо `null`. */
716
+ dominantEmoji: string | null;
717
+ }
718
+ /** Результат реакции на пост. */
719
+ interface LikeResult {
720
+ liked: boolean;
721
+ likesCount: number;
722
+ }
723
+ /** Результат закрепления поста в профиле. */
724
+ interface PinPostResult {
725
+ success: boolean;
726
+ pinnedPostId: string | null;
727
+ }
728
+ /** Закреплённые значки профиля и выбранный из них. */
729
+ interface PinsResult {
730
+ pins: Pin[];
731
+ /** Идентификатор активного значка — строка, а не объект. */
732
+ activePin: string | null;
733
+ }
734
+ /**
735
+ * Разбирает дату API в объект `Date`.
736
+ *
737
+ * @returns `null`, если строки нет или она не разбирается
738
+ *
739
+ * @example
740
+ * ```ts
741
+ * const created = toDate(post.createdAt);
742
+ * ```
743
+ */
744
+ declare function toDate(value: IsoDate | null | undefined): Date | null;
745
+
746
+ /**
747
+ * Билдер опроса.
748
+ *
749
+ * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно
750
+ * переиспользовать, не боясь её испортить. Создаётся функцией {@link poll}.
751
+ */
752
+ declare class PollBuilder implements ItdBuilder<CreatePollInput> {
753
+ #private;
754
+ /** @internal */
755
+ readonly [BUILDER]: true;
756
+ /** @internal Создавайте билдер функцией {@link poll}. */
757
+ constructor(state: CreatePollInput);
758
+ /** Задаёт вопрос. */
759
+ question(text: string): PollBuilder;
760
+ /** Добавляет один вариант ответа. */
761
+ option(text: string): PollBuilder;
762
+ /**
763
+ * Добавляет несколько вариантов сразу.
764
+ *
765
+ * @example
766
+ * ```ts
767
+ * poll('ну как?').options('да', 'нет', 'не знаю');
768
+ * ```
769
+ */
770
+ options(...texts: string[]): PollBuilder;
771
+ /** Разрешает выбор нескольких вариантов. */
772
+ multipleChoice(enabled?: boolean): PollBuilder;
773
+ build(): CreatePollInput;
774
+ toJSON(): CreatePollInput;
775
+ }
776
+ /**
777
+ * Начинает сборку опроса.
778
+ *
779
+ * @param question вопрос; можно задать позже методом {@link PollBuilder.question}
780
+ *
781
+ * @example
782
+ * ```ts
783
+ * import { poll } from 'itd-api';
784
+ *
785
+ * const q = poll('Какой язык лучше?')
786
+ * .options('TypeScript', 'JavaScript')
787
+ * .multipleChoice();
788
+ *
789
+ * await itd.posts.create({ content: 'голосуем', poll: q });
790
+ * ```
791
+ */
792
+ declare function poll(question?: string): PollBuilder;
793
+ /** Что принимает параметр опроса: объект, билдер или функция-настройщик. */
794
+ type PollInput = BuilderInput<CreatePollInput, PollBuilder>;
795
+
796
+ /**
797
+ * Файл для загрузки.
798
+ *
799
+ * Строка означает **путь на диске** и работает только в Node, Bun и Deno — для этого
800
+ * подключите `itd-api/node`. В браузере и React Native передавайте `File` или `Blob`.
801
+ */
802
+ type FileInput = Blob | ArrayBuffer | Uint8Array | string | {
803
+ /** Содержимое файла. */
804
+ data: Blob | ArrayBuffer | Uint8Array;
805
+ /** Имя файла. Влияет на определение типа, если `contentType` не задан. */
806
+ filename?: string;
807
+ /** MIME-тип. Если не указан, определяется по расширению или по самому `Blob`. */
808
+ contentType?: string;
809
+ };
810
+ /** Данные для создания опроса. */
811
+ interface CreatePollInput {
812
+ /** Вопрос. Не может быть пустым. */
813
+ question: string;
814
+ /** Варианты ответа. Требуется минимум два. */
815
+ options: {
816
+ text: string;
817
+ }[];
818
+ /** Разрешить выбор нескольких вариантов. По умолчанию `false`. */
819
+ multipleChoice?: boolean;
820
+ }
821
+ /** Данные для создания поста. */
822
+ interface CreatePostInput {
823
+ /** Текст поста. */
824
+ content?: string;
825
+ /** Разметка текста. Передаётся серверу без изменений — библиотека её не генерирует. */
826
+ spans?: Span[];
827
+ /**
828
+ * Чья стена, если пост публикуется не у себя.
829
+ *
830
+ * Требуется **UUID**: имя пользователя здесь не работает, его можно получить
831
+ * из профиля через `itd.users.get(username)`.
832
+ */
833
+ wallRecipientId?: UserId | null;
834
+ /** Идентификаторы заранее загруженных вложений. */
835
+ attachmentIds?: string[];
836
+ /** Файлы, которые нужно загрузить перед публикацией. Порядок сохраняется. */
837
+ files?: FileInput[];
838
+ /** Опрос: обычный объект, {@link PollBuilder} или функция-настройщик. */
839
+ poll?: PollInput;
840
+ }
841
+ /** Данные для создания комментария или ответа. */
842
+ interface CreateCommentInput {
843
+ /** Текст. У голосового комментария должен быть пустым. */
844
+ content?: string;
845
+ /** Идентификаторы заранее загруженных вложений. */
846
+ attachmentIds?: string[];
847
+ /** Файлы, которые нужно загрузить перед отправкой. */
848
+ files?: FileInput[];
849
+ /**
850
+ * Кому адресован ответ.
851
+ *
852
+ * Применимо только в `itd.comments.reply()`; в комментарии к посту поле не имеет смысла.
853
+ */
854
+ replyToUserId?: UserId;
855
+ }
856
+ /** Данные для создания жалобы. */
857
+ interface CreateReportInput {
858
+ /** На что жалоба. */
859
+ targetType: ReportTargetType;
860
+ /** Идентификатор объекта жалобы. */
861
+ targetId: string;
862
+ /** Причина. */
863
+ reason: ReportReason;
864
+ /** Пояснение в свободной форме. */
865
+ description?: string;
866
+ }
867
+
868
+ /** Внутреннее состояние {@link CommentBuilder}. */
869
+ interface CommentState extends CreateCommentInput {
870
+ content: string;
871
+ attachmentIds: string[];
872
+ files: FileInput[];
873
+ /** Голосовой комментарий: текста быть не должно, вложение ровно одно. */
874
+ voice: boolean;
875
+ }
876
+ /**
877
+ * Билдер комментария и ответа на комментарий.
878
+ *
879
+ * Неизменяемый: каждый вызов возвращает новый экземпляр. Создаётся функцией {@link comment}.
880
+ */
881
+ declare class CommentBuilder implements ItdBuilder<CreateCommentInput> {
882
+ #private;
883
+ /** @internal */
884
+ readonly [BUILDER]: true;
885
+ /** @internal Создавайте билдер функцией {@link comment}. */
886
+ constructor(state: CommentState);
887
+ /** Задаёт текст комментария. */
888
+ content(text: string): CommentBuilder;
889
+ /** Прикладывает файл — он будет загружен перед отправкой. */
890
+ attach(file: FileInput): CommentBuilder;
891
+ /** Прикладывает уже загруженное вложение. */
892
+ attachId(attachmentId: string): CommentBuilder;
893
+ /**
894
+ * Делает комментарий голосовым.
895
+ *
896
+ * Текста у такого комментария быть не должно, а вложение ровно одно — аудио в формате
897
+ * `audio/ogg`. Так его принимает API.
898
+ *
899
+ * @example
900
+ * ```ts
901
+ * await itd.posts.comment(postId, (c) => c.voice('./answer.ogg'));
902
+ * ```
903
+ */
904
+ voice(audio: FileInput): CommentBuilder;
905
+ /**
906
+ * Кому адресован ответ.
907
+ *
908
+ * Имеет смысл только в `itd.comments.reply()`; при отправке комментария к посту
909
+ * это поле вызовет ошибку.
910
+ */
911
+ replyTo(userId: UserId): CommentBuilder;
912
+ build(): CreateCommentInput;
913
+ toJSON(): CreateCommentInput;
914
+ }
915
+ /**
916
+ * Начинает сборку комментария.
917
+ *
918
+ * @param content текст; можно задать позже методом {@link CommentBuilder.content}
919
+ *
920
+ * @example
921
+ * ```ts
922
+ * import { comment } from 'itd-api';
923
+ *
924
+ * await itd.posts.comment(postId, comment('согласен').attach('./meme.png'));
925
+ * ```
926
+ */
927
+ declare function comment(content?: string): CommentBuilder;
928
+ /** Что принимает параметр комментария: объект, билдер или функция-настройщик. */
929
+ type CommentInput = BuilderInput<CreateCommentInput, CommentBuilder>;
930
+
931
+ /** Внутреннее состояние {@link PostBuilder}. */
932
+ interface PostState extends CreatePostInput {
933
+ content: string;
934
+ attachmentIds: string[];
935
+ files: FileInput[];
936
+ }
937
+ /**
938
+ * Билдер поста.
939
+ *
940
+ * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно
941
+ * переиспользовать. Создаётся функцией {@link post}.
942
+ *
943
+ * @example Заготовка для нескольких постов
944
+ * ```ts
945
+ * const onWall = post().onWall(userId);
946
+ *
947
+ * await itd.posts.create(onWall.content('первый'));
948
+ * await itd.posts.create(onWall.content('второй')); // заготовка не испорчена
949
+ * ```
950
+ */
951
+ declare class PostBuilder implements ItdBuilder<CreatePostInput> {
952
+ #private;
953
+ /** @internal */
954
+ readonly [BUILDER]: true;
955
+ /** @internal Создавайте билдер функцией {@link post}. */
956
+ constructor(state: PostState);
957
+ /** Задаёт текст поста, заменяя прежний. */
958
+ content(text: string): PostBuilder;
959
+ /** Дописывает текст к уже заданному. */
960
+ append(text: string): PostBuilder;
961
+ /**
962
+ * Задаёт разметку текста.
963
+ *
964
+ * Библиотека разметку не генерирует: хэштеги и упоминания нужно размечать самостоятельно
965
+ * либо не размечать вовсе.
966
+ */
967
+ spans(spans: Span[]): PostBuilder;
968
+ /**
969
+ * Публикует пост на стене другого пользователя.
970
+ *
971
+ * @param userId **UUID** пользователя; имя пользователя не подойдёт
972
+ */
973
+ onWall(userId: UserId): PostBuilder;
974
+ /**
975
+ * Прикладывает файл — он будет загружен перед публикацией.
976
+ *
977
+ * Порядок вызовов сохраняется в порядке вложений.
978
+ */
979
+ attach(file: FileInput): PostBuilder;
980
+ /** Прикладывает уже загруженное вложение по его идентификатору. */
981
+ attachId(attachmentId: string): PostBuilder;
982
+ /**
983
+ * Добавляет опрос.
984
+ *
985
+ * Принимает объект, {@link PollBuilder} или функцию-настройщик.
986
+ *
987
+ * @example
988
+ * ```ts
989
+ * post('голосуем').poll((q) => q.question('ну как?').options('да', 'нет'));
990
+ * ```
991
+ */
992
+ poll(input: PollInput): PostBuilder;
993
+ build(): CreatePostInput;
994
+ toJSON(): CreatePostInput;
995
+ }
996
+ /**
997
+ * Начинает сборку поста.
998
+ *
999
+ * @param content текст; можно задать позже методом {@link PostBuilder.content}
1000
+ *
1001
+ * @example
1002
+ * ```ts
1003
+ * import { post } from 'itd-api';
1004
+ *
1005
+ * await itd.posts.create(
1006
+ * post('смотрите что нашёл')
1007
+ * .attach('./photo.jpg')
1008
+ * .poll((q) => q.question('нравится?').options('да', 'нет')),
1009
+ * );
1010
+ * ```
1011
+ */
1012
+ declare function post(content?: string): PostBuilder;
1013
+ /** Что принимает параметр поста: объект, билдер или функция-настройщик. */
1014
+ type PostInput = BuilderInput<CreatePostInput, PostBuilder>;
1015
+
1016
+ /**
1017
+ * Билдер жалобы.
1018
+ *
1019
+ * Точка входа задаёт объект жалобы и его тип одновременно, поэтому рассогласовать
1020
+ * `targetType` и `targetId` невозможно. Создаётся объектом {@link report}.
1021
+ */
1022
+ declare class ReportBuilder implements ItdBuilder<CreateReportInput> {
1023
+ #private;
1024
+ /** @internal */
1025
+ readonly [BUILDER]: true;
1026
+ /** @internal Создавайте билдер через {@link report}. */
1027
+ constructor(state: Partial<CreateReportInput>);
1028
+ /** Указывает причину жалобы. */
1029
+ reason(reason: ReportReason): ReportBuilder;
1030
+ /** Добавляет пояснение в свободной форме. */
1031
+ description(text: string): ReportBuilder;
1032
+ build(): CreateReportInput;
1033
+ toJSON(): CreateReportInput;
1034
+ }
1035
+ /**
1036
+ * Начинает сборку жалобы.
1037
+ *
1038
+ * Тип объекта выбирается точкой входа, так что указать идентификатор комментария
1039
+ * с типом «пост» нельзя в принципе.
1040
+ *
1041
+ * @example
1042
+ * ```ts
1043
+ * import { report, ReportReason } from 'itd-api';
1044
+ *
1045
+ * await itd.reports.create(report.post(postId).reason(ReportReason.Spam));
1046
+ * await itd.reports.create(report.user(userId).reason('fraud').description('пишет в личку'));
1047
+ * ```
1048
+ */
1049
+ declare const report: Readonly<{
1050
+ /** Жалоба на пост. */
1051
+ post: (postId: string) => ReportBuilder;
1052
+ /** Жалоба на комментарий. */
1053
+ comment: (commentId: string) => ReportBuilder;
1054
+ /** Жалоба на пользователя. */
1055
+ user: (userId: string) => ReportBuilder;
1056
+ }>;
1057
+ /** Что принимает параметр жалобы: объект, билдер или функция-настройщик. */
1058
+ type ReportInput = BuilderInput<CreateReportInput, ReportBuilder>;
1059
+
1060
+ /**
1061
+ * Как библиотека обращается с cookie.
1062
+ *
1063
+ * - `browser` — cookie ведёт браузер, запросы уходят с `credentials: 'include'`;
1064
+ * - `server` — cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную;
1065
+ * - `auto` — определяется по среде исполнения (значение по умолчанию).
1066
+ */
1067
+ type RuntimeMode = 'auto' | 'browser' | 'server';
1068
+
1069
+ /**
1070
+ * Сохранённая сессия.
1071
+ *
1072
+ * Кроме токенов сюда попадают cookie: refresh-токен итд.com живёт именно в cookie, и без них
1073
+ * восстановить сессию после перезапуска процесса невозможно.
1074
+ */
1075
+ interface ItdSession {
1076
+ /** Токен доступа для заголовка `Authorization: Bearer`. */
1077
+ accessToken?: string | undefined;
1078
+ /**
1079
+ * Refresh-токен, если удалось получить его явным значением.
1080
+ *
1081
+ * Обычно сервер держит его в httpOnly-cookie и наружу не отдаёт — тогда поле останется
1082
+ * пустым, а обновление пойдёт через {@link ItdSession.cookies}.
1083
+ */
1084
+ refreshToken?: string | undefined;
1085
+ /** Сырые cookie в форме `имя=значение`, привязанные к origin API. */
1086
+ cookies?: string[] | undefined;
1087
+ /** Когда сессия получена, мс с начала эпохи. Нужно для диагностики. */
1088
+ obtainedAt?: number | undefined;
1089
+ }
1090
+ /**
1091
+ * Хранилище сессии.
1092
+ *
1093
+ * Подключаемый компонент: библиотека не знает, где вы держите токены, и обращается к ним
1094
+ * только через этот интерфейс. Все методы могут быть как синхронными, так и асинхронными.
1095
+ *
1096
+ * @example Своё хранилище поверх AsyncStorage в React Native
1097
+ * ```ts
1098
+ * const storage = createTokenStorage({
1099
+ * get: async () => JSON.parse((await AsyncStorage.getItem('itd')) ?? 'null'),
1100
+ * set: (session) => AsyncStorage.setItem('itd', JSON.stringify(session)),
1101
+ * clear: () => AsyncStorage.removeItem('itd'),
1102
+ * });
1103
+ * ```
1104
+ */
1105
+ interface TokenStorage {
1106
+ /** Прочитать сессию. `null`, если её нет. */
1107
+ get(): ItdSession | null | Promise<ItdSession | null>;
1108
+ /** Сохранить сессию целиком. */
1109
+ set(session: ItdSession): void | Promise<void>;
1110
+ /** Удалить сессию. Вызывается при выходе и при неудачном обновлении токена. */
1111
+ clear(): void | Promise<void>;
1112
+ }
1113
+ /**
1114
+ * Хранилище в памяти процесса — вариант по умолчанию.
1115
+ *
1116
+ * Сессия теряется при перезапуске. Для долгоживущих ботов возьмите `FileTokenStorage`
1117
+ * из `itd-api/node`, для браузера — {@link LocalStorageTokenStorage}.
1118
+ */
1119
+ declare class MemoryTokenStorage implements TokenStorage {
1120
+ #private;
1121
+ constructor(initial?: ItdSession | null);
1122
+ get(): ItdSession | null;
1123
+ set(session: ItdSession): void;
1124
+ clear(): void;
1125
+ }
1126
+ /**
1127
+ * Хранилище поверх `localStorage` браузера.
1128
+ *
1129
+ * Если `localStorage` недоступен (приватный режим, серверный рендеринг), молча работает
1130
+ * как хранилище в памяти — библиотека не должна падать из-за настроек браузера.
1131
+ *
1132
+ * Помните, что `localStorage` доступен любому скрипту на странице: не используйте его,
1133
+ * если для вашего приложения это неприемлемый риск.
1134
+ */
1135
+ declare class LocalStorageTokenStorage implements TokenStorage {
1136
+ #private;
1137
+ /** @param key ключ в `localStorage`. По умолчанию `itd-api:session`. */
1138
+ constructor(key?: string);
1139
+ get(): ItdSession | null;
1140
+ set(session: ItdSession): void;
1141
+ clear(): void;
1142
+ }
1143
+ /**
1144
+ * Собирает {@link TokenStorage} из трёх функций — когда заводить класс избыточно.
1145
+ *
1146
+ * @example
1147
+ * ```ts
1148
+ * const storage = createTokenStorage({
1149
+ * get: () => db.getSession(userId),
1150
+ * set: (session) => db.saveSession(userId, session),
1151
+ * clear: () => db.deleteSession(userId),
1152
+ * });
1153
+ * ```
1154
+ */
1155
+ declare function createTokenStorage(handlers: TokenStorage): TokenStorage;
1156
+
1157
+ /** Значение параметра запроса. `undefined` и `null` в строку не попадают. */
1158
+ type QueryValue = string | number | boolean | null | undefined | readonly (string | number | boolean)[];
1159
+ /** Параметры строки запроса. */
1160
+ type QueryParams = Record<string, QueryValue>;
1161
+
1162
+ /**
1163
+ * Как клиент получает доступ к API.
1164
+ *
1165
+ * Поддерживаются четыре формы — от разового вызова с готовым токеном до полноценной
1166
+ * сессии, которую библиотека заводит и продлевает сама.
1167
+ *
1168
+ * @example
1169
+ * ```ts
1170
+ * new ItdClient({ auth: '<accessToken>' }); // разовый вызов
1171
+ * new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию
1172
+ * new ItdClient({ auth: { email, password } }); // залогиниться самому
1173
+ * new ItdClient({ auth: { getToken: () => vault.read() } }); // токен из внешнего источника
1174
+ * ```
1175
+ */
1176
+ type AuthInput = string | {
1177
+ accessToken: string;
1178
+ refreshToken?: string | undefined;
1179
+ } | {
1180
+ email: string;
1181
+ password: string;
1182
+ } | {
1183
+ getToken: () => string | null | Promise<string | null>;
1184
+ };
1185
+ /** Куда библиотека пишет отладочные сообщения. Совместим с `console`. */
1186
+ interface Logger {
1187
+ debug(message: string, ...args: unknown[]): void;
1188
+ info(message: string, ...args: unknown[]): void;
1189
+ warn(message: string, ...args: unknown[]): void;
1190
+ error(message: string, ...args: unknown[]): void;
1191
+ }
1192
+ /** Настройки повторных попыток. */
1193
+ interface RetryOptions {
1194
+ /** Сколько всего попыток, включая первую. По умолчанию 3. */
1195
+ attempts?: number | undefined;
1196
+ /** Базовая пауза в мс, удваивается с каждой попыткой. По умолчанию 500. */
1197
+ baseDelay?: number | undefined;
1198
+ /** Верхняя граница паузы в мс. По умолчанию 30000. */
1199
+ maxDelay?: number | undefined;
1200
+ /** Доля случайного разброса паузы, 0…1. По умолчанию 0.3. */
1201
+ jitter?: number | undefined;
1202
+ /**
1203
+ * Повторять ли запись (`POST`, `PUT`, `PATCH`, `DELETE`) при сетевых сбоях и `5xx`.
1204
+ *
1205
+ * По умолчанию `false`: сервер мог успеть выполнить операцию до обрыва, и повтор
1206
+ * создаст дубль поста или лишнюю жалобу. Ответ `429` повторяется всегда — он гарантирует,
1207
+ * что запрос не был обработан.
1208
+ */
1209
+ retryWrites?: boolean | undefined;
1210
+ /** Своя логика: вернуть `true`, чтобы повторить. Заменяет правила по умолчанию. */
1211
+ shouldRetry?: ((error: unknown, attempt: number) => boolean) | undefined;
1212
+ }
1213
+ /** Настройки ограничения нагрузки на API. */
1214
+ interface RateLimitOptions {
1215
+ /** Сколько запросов выполняется одновременно. По умолчанию 6. */
1216
+ concurrency?: number | undefined;
1217
+ /** Верхняя граница запросов в секунду. По умолчанию без ограничения. */
1218
+ rps?: number | undefined;
1219
+ /**
1220
+ * Паузы перед повторами при ответе `429`, мс.
1221
+ * По умолчанию `[1000, 5000, 30000, 60000, 90000]`.
1222
+ *
1223
+ * Сервер не сообщает, когда сбросится окно лимита, поэтому паузу приходится подбирать.
1224
+ * Лестница начинается с секунды: если окно почти истекло, работа продолжится почти
1225
+ * сразу, а если лимит исчерпан всерьёз — паузы дорастут до полутора минут.
1226
+ * Когда лестница закончилась, {@link ItdRateLimitError} пробрасывается вызывающему коду.
1227
+ *
1228
+ * Этот список не зависит от `retry.attempts`: тот управляет повторами при обрывах
1229
+ * сети и ошибках сервера, где уместен совсем другой темп.
1230
+ */
1231
+ retryDelays?: readonly number[] | undefined;
1232
+ /**
1233
+ * Тормозить ли очередь по заголовкам ответа. По умолчанию `true`.
1234
+ *
1235
+ * Выключите, если управляете темпом сами.
1236
+ */
1237
+ respectHeaders?: boolean | undefined;
1238
+ }
1239
+ /** Данные о запросе, доступные хукам. */
1240
+ interface RequestContext {
1241
+ method: string;
1242
+ /** Путь без базового URL, например `/api/posts`. */
1243
+ path: string;
1244
+ /** Итоговый URL со строкой запроса. */
1245
+ url: string;
1246
+ headers: Headers;
1247
+ /** Номер попытки, начиная с 1. */
1248
+ attempt: number;
1249
+ }
1250
+ /** Данные об успешном ответе. */
1251
+ interface ResponseContext extends RequestContext {
1252
+ status: number;
1253
+ /** Длительность запроса в мс. */
1254
+ duration: number;
1255
+ response: Response;
1256
+ }
1257
+ /** Данные об ошибке запроса. */
1258
+ interface ErrorContextHook extends RequestContext {
1259
+ duration: number;
1260
+ error: unknown;
1261
+ }
1262
+ /** Данные о предстоящем повторе. */
1263
+ interface RetryContext extends RequestContext {
1264
+ error: unknown;
1265
+ /** Пауза перед следующей попыткой в мс. */
1266
+ delay: number;
1267
+ }
1268
+ /**
1269
+ * Перехватчики жизненного цикла запроса.
1270
+ *
1271
+ * Вызываются последовательно; исключение внутри хука прервёт запрос, поэтому свою логику
1272
+ * лучше оборачивать в `try`.
1273
+ */
1274
+ interface ClientHooks {
1275
+ /** Перед отправкой. Можно дописать заголовки — объект `headers` изменяемый. */
1276
+ onRequest?(context: RequestContext): void | Promise<void>;
1277
+ /** После успешного ответа, до разбора тела. */
1278
+ onResponse?(context: ResponseContext): void | Promise<void>;
1279
+ /** При любой ошибке запроса, включая те, что будут повторены. */
1280
+ onError?(context: ErrorContextHook): void | Promise<void>;
1281
+ /** Перед паузой между попытками. */
1282
+ onRetry?(context: RetryContext): void | Promise<void>;
1283
+ }
1284
+ /**
1285
+ * Опции конструктора `ItdClient`.
1286
+ *
1287
+ * Все поля допускают явный `undefined`, чтобы можно было передавать значения, которых
1288
+ * может не быть, — например `new ItdClient({ auth: process.env.ITD_TOKEN })`.
1289
+ */
1290
+ interface ItdClientOptions {
1291
+ /**
1292
+ * Базовый URL API. По умолчанию `https://xn--d1ah4a.com`.
1293
+ *
1294
+ * Укажите здесь адрес своего прокси, если работаете из браузера: CORS для сторонних
1295
+ * источников на итд.com, скорее всего, не настроен.
1296
+ */
1297
+ baseUrl?: string | undefined;
1298
+ /** Авторизация. Без неё доступны только публичные эндпоинты. */
1299
+ auth?: AuthInput | undefined;
1300
+ /** Где хранить сессию. По умолчанию {@link MemoryTokenStorage}. */
1301
+ storage?: TokenStorage | undefined;
1302
+ /**
1303
+ * Обновлять токен автоматически при ответе `401`. По умолчанию `true`.
1304
+ *
1305
+ * При `false` библиотека просто пробросит {@link ItdAuthError}, а обновлением
1306
+ * вы управляете сами через `itd.auth.refresh()`.
1307
+ */
1308
+ autoRefresh?: boolean | undefined;
1309
+ /**
1310
+ * Пытаться ли войти заново, если обновление токена не удалось.
1311
+ *
1312
+ * Работает, только когда в `auth` переданы email и пароль. По умолчанию `true`.
1313
+ */
1314
+ reloginOnRefreshFailure?: boolean | undefined;
1315
+ /** Своя реализация `fetch`: для Deno, React Native, тестов или прокси. */
1316
+ fetch?: typeof fetch | undefined;
1317
+ /** Таймаут запроса в мс. По умолчанию 30000 — столько же использует сайт итд.com. `0` снимает ограничение. */
1318
+ timeout?: number | undefined;
1319
+ /** Повторные попытки. `false` отключает их полностью. */
1320
+ retry?: RetryOptions | false | undefined;
1321
+ /** Ограничение нагрузки. `false` отключает очередь. */
1322
+ rateLimit?: RateLimitOptions | false | undefined;
1323
+ /** Перехватчики запросов. */
1324
+ hooks?: ClientHooks | undefined;
1325
+ /** Отладочный вывод. `true` — писать в `console`. */
1326
+ logger?: Logger | boolean | undefined;
1327
+ /** Заголовки, добавляемые ко всем запросам, — например `User-Agent` для бота. */
1328
+ headers?: Record<string, string> | undefined;
1329
+ /** Как обращаться с cookie. По умолчанию определяется по среде исполнения. */
1330
+ mode?: RuntimeMode | undefined;
1331
+ }
1332
+ /** Опции отдельного запроса. Доступны в каждом методе ресурсов. */
1333
+ interface RequestOptions {
1334
+ /** Отмена запроса извне. */
1335
+ signal?: AbortSignal | undefined;
1336
+ /** Таймаут только для этого запроса, мс. */
1337
+ timeout?: number | undefined;
1338
+ /** Дополнительные заголовки. */
1339
+ headers?: Record<string, string> | undefined;
1340
+ /** Повторы только для этого запроса. */
1341
+ retry?: RetryOptions | false | undefined;
1342
+ }
1343
+ /** Полное описание запроса для низкоуровневого `itd.request()`. */
1344
+ interface RawRequestOptions extends RequestOptions {
1345
+ method: string;
1346
+ /** Путь с ведущим слэшем, например `/api/posts`. Завершающий слэш значим. */
1347
+ path: string;
1348
+ query?: QueryParams | undefined;
1349
+ /** Тело: будет отправлено как JSON. Для загрузки файлов передайте `FormData`. */
1350
+ body?: unknown;
1351
+ /** Не подставлять заголовок авторизации. */
1352
+ skipAuth?: boolean | undefined;
1353
+ /** Не пытаться обновить токен при `401` — используется самими эндпоинтами авторизации. */
1354
+ skipAuthRefresh?: boolean | undefined;
1355
+ /** Вернуть тело ответа без снятия обёртки `{ data: … }`. */
1356
+ raw?: boolean | undefined;
1357
+ }
1358
+
1359
+ /** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
1360
+ declare const DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
1361
+ /** Таймаут запроса по умолчанию. Столько же использует официальный клиент итд.com. */
1362
+ declare const DEFAULT_TIMEOUT = 30000;
1363
+ /**
1364
+ * Настройки повторов со всеми значениями по умолчанию.
1365
+ *
1366
+ * Поля перечислены явно, а не через `Required<RetryOptions>`: тот снимает необязательность,
1367
+ * но оставляет `| undefined` в типе значения, раз оно указано в исходном интерфейсе.
1368
+ */
1369
+ interface ResolvedRetryOptions {
1370
+ attempts: number;
1371
+ baseDelay: number;
1372
+ maxDelay: number;
1373
+ jitter: number;
1374
+ retryWrites: boolean;
1375
+ shouldRetry: ((error: unknown, attempt: number) => boolean) | undefined;
1376
+ }
1377
+ /** Настройки очереди со всеми значениями по умолчанию. */
1378
+ interface ResolvedRateLimitOptions {
1379
+ concurrency: number;
1380
+ rps: number | undefined;
1381
+ retryDelays: readonly number[];
1382
+ respectHeaders: boolean;
1383
+ }
1384
+ /** Конфигурация клиента после подстановки значений по умолчанию и проверок. */
1385
+ interface ResolvedConfig {
1386
+ baseUrl: string;
1387
+ auth: AuthInput | undefined;
1388
+ storage: TokenStorage;
1389
+ autoRefresh: boolean;
1390
+ reloginOnRefreshFailure: boolean;
1391
+ fetch: typeof fetch;
1392
+ timeout: number;
1393
+ retry: ResolvedRetryOptions | undefined;
1394
+ rateLimit: ResolvedRateLimitOptions | undefined;
1395
+ hooks: ClientHooks;
1396
+ logger: Logger | undefined;
1397
+ headers: Record<string, string>;
1398
+ mode: RuntimeMode;
1399
+ /** Вести ли собственный cookie-jar (вне браузера и React Native). */
1400
+ useCookieJar: boolean;
1401
+ /** Отправлять ли `credentials: 'include'` (в браузере). */
1402
+ sendCredentials: boolean;
1403
+ }
1404
+
1405
+ /**
1406
+ * Минимальное хранилище cookie для сред без своего.
1407
+ *
1408
+ * Refresh-токен итд.com приходит в `Set-Cookie`, а `fetch` вне браузера cookie не хранит —
1409
+ * без jar сессию не продлить. В браузере и React Native не используется: там cookie ведёт
1410
+ * сама среда.
1411
+ *
1412
+ * Реализована практическая часть RFC 6265: origin, путь, срок жизни, флаг `Secure`.
1413
+ * Доменные cookie для поддоменов намеренно не поддерживаются — API работает с одного хоста.
1414
+ */
1415
+ declare class CookieJar {
1416
+ #private;
1417
+ /**
1418
+ * Забирает `Set-Cookie` из ответа.
1419
+ *
1420
+ * Использует `Headers.getSetCookie()`, где он есть (Node 20+, undici). В остальных средах
1421
+ * заголовки склеены в одну строку, и её нельзя резать по запятой напрямую: запятая есть
1422
+ * внутри `Expires=Wed, 09 Jun 2027 …`. Разделением занимается `set-cookie-parser`.
1423
+ */
1424
+ setFromResponse(url: string, response: Response): void;
1425
+ /** Сохраняет cookie из готовых строк `Set-Cookie`. */
1426
+ setFromStrings(url: string, setCookieStrings: string[]): void;
1427
+ /**
1428
+ * Собирает значение заголовка `Cookie` для запроса.
1429
+ *
1430
+ * @returns строка вида `a=1; b=2` либо `undefined`, если подходящих cookie нет
1431
+ */
1432
+ getHeader(url: string): string | undefined;
1433
+ /**
1434
+ * Есть ли действующая cookie с таким именем.
1435
+ *
1436
+ * Используется для проверки флага {@link AUTH_FLAG_COOKIE} перед запросом обновления
1437
+ * токена: у анонима refresh-сессии нет, и дёргать API незачем.
1438
+ */
1439
+ has(name: string, url?: string): boolean;
1440
+ /** Сохраняет содержимое jar для записи в {@link TokenStorage}. */
1441
+ serialize(): string[];
1442
+ /** Восстанавливает jar из результата {@link serialize}. Некорректные записи молча пропускаются. */
1443
+ deserialize(entries: readonly string[] | undefined): void;
1444
+ /** Удаляет все cookie. */
1445
+ clear(): void;
1446
+ }
1447
+
1448
+ /** Обработчик события. */
1449
+ type Listener<T> = (payload: T) => void;
1450
+ /** Функция отписки, которую возвращает {@link Emitter.on}. */
1451
+ type Unsubscribe = () => void;
1452
+ /**
1453
+ * Минимальный типизированный источник событий.
1454
+ *
1455
+ * Своя реализация вместо `EventTarget` и `EventEmitter`: первый есть не везде и требует
1456
+ * обёрток `CustomEvent`, второй существует только в Node. Нужны ровно подписка и рассылка.
1457
+ *
1458
+ * Исключение в обработчике не прерывает рассылку остальным и не роняет библиотеку.
1459
+ *
1460
+ * @typeParam Events карта «имя события → тип полезной нагрузки». Задаётся интерфейсом,
1461
+ * поэтому ограничение на индексную сигнатуру намеренно не накладывается.
1462
+ */
1463
+ declare class Emitter<Events> {
1464
+ #private;
1465
+ constructor(onListenerError?: (error: unknown) => void);
1466
+ /**
1467
+ * Подписывается на событие.
1468
+ *
1469
+ * @returns функция отписки
1470
+ *
1471
+ * @example
1472
+ * ```ts
1473
+ * const off = realtime.on('notification', (event) => console.log(event));
1474
+ * off();
1475
+ * ```
1476
+ */
1477
+ on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe;
1478
+ /** Подписывается на одно срабатывание. */
1479
+ once<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe;
1480
+ /** Отписывается от события. */
1481
+ off<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void;
1482
+ /** Рассылает событие подписчикам. */
1483
+ emit<K extends keyof Events>(event: K, payload: Events[K]): void;
1484
+ /** Сколько подписчиков у события. */
1485
+ listenerCount(event: keyof Events): number;
1486
+ /** Снимает все подписки. */
1487
+ removeAllListeners(): void;
1488
+ }
1489
+
1490
+ /**
1491
+ * Подключаемые части конвейера.
1492
+ *
1493
+ * Авторизация, cookie, очередь и повторы живут в отдельных модулях и подставляются сюда.
1494
+ * Благодаря этому транспорт тестируется изолированно, а `HttpClient` ничего не знает
1495
+ * о том, как именно добывается токен.
1496
+ */
1497
+ interface HttpCollaborators {
1498
+ /** Заголовки авторизации для запроса. Вызывается перед каждой попыткой. */
1499
+ getAuthHeaders?(): Promise<Record<string, string>> | Record<string, string>;
1500
+ /**
1501
+ * Реакция на ответ `401`.
1502
+ *
1503
+ * Должна вернуть `true`, если токен обновлён и запрос имеет смысл повторить.
1504
+ * Повтор выполняется ровно один раз.
1505
+ */
1506
+ onUnauthorized?(): Promise<boolean>;
1507
+ /** Значение заголовка `Cookie` для указанного URL. */
1508
+ getCookieHeader?(url: string): string | undefined;
1509
+ /** Приём `Set-Cookie` из ответа. */
1510
+ saveCookies?(url: string, response: Response): void;
1511
+ /** Очередь запросов: ограничение конкурентности и частоты. */
1512
+ schedule?<T>(task: () => Promise<T>): Promise<T>;
1513
+ /**
1514
+ * Сообщает об остатке лимита из заголовков ответа.
1515
+ *
1516
+ * Вызывается после **каждого** ответа, включая ошибочные, — так очередь узнаёт
1517
+ * об исчерпании лимита заранее и успевает притормозить до отказа сервера.
1518
+ */
1519
+ onRateLimit?(limit: number | undefined, remaining: number | undefined): void;
1520
+ /**
1521
+ * Планировщик повторов.
1522
+ *
1523
+ * Возвращает паузу в мс перед следующей попыткой либо `undefined`, если повторять не нужно.
1524
+ */
1525
+ nextRetryDelay?(error: unknown, attempt: number, method: string): number | undefined;
1526
+ }
1527
+ /**
1528
+ * Транспортный слой: единственное место, откуда библиотека ходит в сеть.
1529
+ *
1530
+ * Отвечает за сборку URL, заголовки, таймауты, разбор ответа и превращение любой неудачи
1531
+ * в типизированную ошибку. Авторизация, cookie, очередь и повторы подключаются извне
1532
+ * через {@link HttpCollaborators}.
1533
+ */
1534
+ declare class HttpClient {
1535
+ #private;
1536
+ constructor(config: ResolvedConfig, collaborators?: HttpCollaborators);
1537
+ /** Базовый URL, к которому обращается клиент. */
1538
+ get baseUrl(): string;
1539
+ /**
1540
+ * Подключает недостающие части конвейера.
1541
+ *
1542
+ * Нужно из-за кольцевой зависимости: слой авторизации сам выполняет запросы, поэтому
1543
+ * не может быть передан в конструктор до создания транспорта.
1544
+ */
1545
+ setCollaborators(collaborators: HttpCollaborators): void;
1546
+ /**
1547
+ * Выполняет запрос к API.
1548
+ *
1549
+ * @typeParam T ожидаемая форма ответа после снятия обёртки `{ data: … }`
1550
+ * @throws {ItdApiError} если сервер ответил статусом ≥ 400
1551
+ * @throws {ItdTimeoutError} если истёк таймаут
1552
+ * @throws {ItdAbortError} если запрос отменён через `signal`
1553
+ * @throws {ItdNetworkError} если запрос не дошёл до сервера
1554
+ */
1555
+ request<T = unknown>(options: RawRequestOptions): Promise<T>;
1556
+ }
1557
+
1558
+ /** События слоя авторизации. */
1559
+ interface AuthEvents {
1560
+ /** Токен получен или обновлён. */
1561
+ tokens: {
1562
+ accessToken: string;
1563
+ };
1564
+ /** Выполнен вход. */
1565
+ signIn: {
1566
+ accessToken: string;
1567
+ };
1568
+ /** Сессия очищена — вручную или из-за неудачного обновления. */
1569
+ signOut: undefined;
1570
+ /** Обновить сессию не удалось; дальнейшие запросы будут падать с 401. */
1571
+ authError: {
1572
+ error: unknown;
1573
+ };
1574
+ }
1575
+ /**
1576
+ * Хранит сессию и продлевает её.
1577
+ *
1578
+ * Главное здесь — **дедупликация обновления**. Когда десять параллельных запросов
1579
+ * одновременно получают `401`, обновление должно произойти один раз, а остальные обязаны
1580
+ * дождаться его результата. Иначе сервер увидит десять параллельных `refresh`, и все,
1581
+ * кроме первого, скорее всего получат отказ по уже использованному токену.
1582
+ */
1583
+ declare class AuthManager {
1584
+ #private;
1585
+ constructor(config: ResolvedConfig, http: HttpClient, jar: CookieJar);
1586
+ /** Подписка на события авторизации. */
1587
+ get on(): Emitter<AuthEvents>['on'];
1588
+ /** Подписка на одно срабатывание. */
1589
+ get once(): Emitter<AuthEvents>['once'];
1590
+ /**
1591
+ * Есть ли признак живой refresh-сессии.
1592
+ *
1593
+ * Сайт итд.com ставит рядом с refresh-токеном незакрытую cookie `is_auth` — по ней клиент
1594
+ * понимает, что обновление вообще имеет смысл, и не дёргает API у анонимов.
1595
+ * В браузере cookie ведёт сама среда, поэтому там ответ всегда `true`.
1596
+ */
1597
+ hasRefreshSession(): boolean;
1598
+ /** Заголовки авторизации для очередного запроса. Пустой объект, если токена нет. */
1599
+ getAuthHeaders(): Promise<Record<string, string>>;
1600
+ /**
1601
+ * Текущий токен доступа.
1602
+ *
1603
+ * При необходимости выполняет отложенный вход: если в конфигурации переданы логин
1604
+ * и пароль, первый же запрос сам заведёт сессию.
1605
+ */
1606
+ getAccessToken(): Promise<string | null>;
1607
+ /**
1608
+ * Реакция транспорта на ответ `401`.
1609
+ *
1610
+ * @returns `true`, если токен обновлён и запрос имеет смысл повторить
1611
+ */
1612
+ onUnauthorized(): Promise<boolean>;
1613
+ /**
1614
+ * Обновляет токен доступа.
1615
+ *
1616
+ * Параллельные вызовы объединяются в один сетевой запрос.
1617
+ *
1618
+ * @throws {ItdAuthError} если обновить сессию не удалось
1619
+ */
1620
+ refresh(): Promise<string>;
1621
+ /** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
1622
+ setAccessToken(accessToken: string): Promise<void>;
1623
+ /** Текущая сессия целиком. Полезно, чтобы сохранить её самому. */
1624
+ getSession(): Promise<ItdSession | null>;
1625
+ /** Заменяет сессию целиком. */
1626
+ setSession(session: ItdSession): Promise<void>;
1627
+ /** Забывает сессию и cookie. Сетевой запрос не выполняется. */
1628
+ clear(): Promise<void>;
1629
+ }
1630
+
1631
+ /** Событие потока уведомлений после разбора. */
1632
+ interface NotificationEvent {
1633
+ /** Само уведомление в единой форме. */
1634
+ notification: Notification;
1635
+ /**
1636
+ * Актуальное число непрочитанных, если сервер его сообщил.
1637
+ *
1638
+ * Клиент не увеличивает счётчик сам: значение приходит с сервера.
1639
+ */
1640
+ unreadCount: number | undefined;
1641
+ /** Нужно ли проиграть звук. */
1642
+ sound: boolean;
1643
+ }
1644
+ /**
1645
+ * Приводит уведомление к единой форме.
1646
+ *
1647
+ * Нужна потому, что REST-список и поток событий описывают одно и то же событие по-разному:
1648
+ * различаются имена типов (`like` против `post_reaction`), имена полей
1649
+ * (`targetId`/`entityId`, `read`/`isRead`, `preview`/`entityPreview`) и число участников
1650
+ * (`actor` против массива `actors`). После приведения объекты из обоих источников
1651
+ * можно складывать в один список.
1652
+ *
1653
+ * Исходные данные не теряются: имя типа с сервера остаётся в `rawType`,
1654
+ * весь объект целиком — в `raw`.
1655
+ *
1656
+ * @param input уведомление из REST-ответа либо полезная нагрузка события потока
1657
+ *
1658
+ * @example
1659
+ * ```ts
1660
+ * const fromRest = normalizeNotification(restItem);
1661
+ * const fromStream = normalizeNotification(event.payload);
1662
+ * // одинаковая форма — можно объединять
1663
+ * ```
1664
+ */
1665
+ declare function normalizeNotification(input: unknown): Notification;
1666
+ /**
1667
+ * Разбирает событие `notification` из потока.
1668
+ *
1669
+ * Кроме самого уведомления событие несёт служебные поля уровня конверта: актуальный
1670
+ * счётчик непрочитанных и признак звука.
1671
+ */
1672
+ declare function readNotificationEvent(data: unknown): NotificationEvent;
1673
+ /**
1674
+ * Разбирает событие `unread_count` из потока.
1675
+ *
1676
+ * Возвращает `undefined`, если сервер прислал событие без вложенного `payload`.
1677
+ * Официальный клиент в этом случае **обнуляет** счётчик — это ошибка, из-за которой
1678
+ * непрочитанные пропадают из интерфейса.
1679
+ */
1680
+ declare function readUnreadCountEvent(data: unknown): number | undefined;
1681
+
1682
+ /**
1683
+ * Паузы перед попытками переподключения, мс.
1684
+ *
1685
+ * Значения совпадают с теми, что использует сайт итд.com, — поведение библиотеки
1686
+ * не отличается от привычного пользователю.
1687
+ */
1688
+ declare const RECONNECT_BACKOFF: readonly number[];
1689
+ /** Доля случайного разброса паузы. */
1690
+ declare const RECONNECT_JITTER = 0.3;
1691
+ /**
1692
+ * Сколько раз пытаться переподключиться подряд.
1693
+ *
1694
+ * После исчерпания поток сообщает `giveup` и ждёт ручного `connect()`.
1695
+ */
1696
+ declare const MAX_RECONNECT_ATTEMPTS = 15;
1697
+ /** Настройки переподключения. */
1698
+ interface ReconnectOptions {
1699
+ /** Таблица пауз. Последнее значение действует для всех дальнейших попыток. */
1700
+ backoff?: readonly number[];
1701
+ /** Доля разброса, 0…1. */
1702
+ jitter?: number;
1703
+ /** Предел числа попыток. */
1704
+ maxAttempts?: number;
1705
+ }
1706
+
1707
+ /** Событие, пришедшее по каналу реального времени. */
1708
+ interface TransportEvent {
1709
+ /** Имя события: `notification`, `unread_count` и другие. */
1710
+ name: string;
1711
+ /** Полезная нагрузка, уже разобранная из JSON. */
1712
+ data: unknown;
1713
+ }
1714
+ /** Что транспорт получает от клиента при подключении. */
1715
+ interface TransportContext {
1716
+ /** Базовый URL API. */
1717
+ baseUrl: string;
1718
+ /** Реализация `fetch`. */
1719
+ fetch: typeof fetch;
1720
+ /** Текущий токен доступа. */
1721
+ getToken: () => Promise<string | null>;
1722
+ /** Отмена подключения. */
1723
+ signal: AbortSignal;
1724
+ /** Сообщает о полученном событии. */
1725
+ onEvent: (event: TransportEvent) => void;
1726
+ /** Сообщает о разобранном, но некорректном сообщении. Соединение при этом живёт. */
1727
+ onParseError: (error: unknown, raw: string) => void;
1728
+ /** Вызывается, когда соединение установлено. */
1729
+ onOpen: () => void;
1730
+ }
1731
+ /**
1732
+ * Канал получения событий в реальном времени.
1733
+ *
1734
+ * Сейчас у платформы один такой канал — поток `text/event-stream`. Абстракция нужна
1735
+ * на будущее: политика безопасности сайта уже разрешает `wss://*.xn--d1ah4a.com`,
1736
+ * и когда появится WebSocket, достаточно будет добавить ещё одну реализацию этого
1737
+ * интерфейса. Переподключение, обновление токена и разбор уведомлений от транспорта
1738
+ * не зависят.
1739
+ */
1740
+ interface RealtimeTransport {
1741
+ /** Понятное имя для логов и диагностики. */
1742
+ readonly name: string;
1743
+ /**
1744
+ * Держит соединение, пока оно живо.
1745
+ *
1746
+ * Должен завершиться, когда поток закрылся, и бросить исключение при ошибке.
1747
+ * Отмена через `context.signal` должна приводить к `AbortError`.
1748
+ */
1749
+ connect(context: TransportContext): Promise<void>;
1750
+ }
1751
+
1752
+ /** События потока уведомлений. */
1753
+ interface RealtimeEvents {
1754
+ /** Пришло новое уведомление. */
1755
+ notification: NotificationEvent;
1756
+ /**
1757
+ * Сервер подтвердил подключение и назвал получателя событий.
1758
+ *
1759
+ * Приходит первым кадром сразу после установки соединения.
1760
+ */
1761
+ ready: {
1762
+ userId: string | undefined;
1763
+ };
1764
+ /**
1765
+ * Сервер сообщил актуальное число непрочитанных.
1766
+ *
1767
+ * На практике сервер этого не делает: за всё наблюдение он не прислал ни одного
1768
+ * такого кадра, а в уведомлениях нет поля со счётчиком. Держите счётчик сами
1769
+ * либо запрашивайте `itd.notifications.count()`.
1770
+ */
1771
+ unreadCount: number;
1772
+ /** Изменилось состояние соединения. */
1773
+ status: RealtimeStatus;
1774
+ /** Соединение оборвалось; будет предпринята попытка переподключения. */
1775
+ error: {
1776
+ error: unknown;
1777
+ willReconnect: boolean;
1778
+ };
1779
+ /** Сообщение не удалось разобрать. Соединение при этом продолжает работать. */
1780
+ parseError: {
1781
+ error: unknown;
1782
+ raw: string;
1783
+ };
1784
+ /** Запланировано переподключение. */
1785
+ reconnect: {
1786
+ attempt: number;
1787
+ delay: number;
1788
+ };
1789
+ /** Попытки исчерпаны — соединение восстановится только ручным `connect()`. */
1790
+ giveup: undefined;
1791
+ /** Любое событие потока в необработанном виде, включая неизвестные библиотеке. */
1792
+ message: {
1793
+ name: string;
1794
+ data: unknown;
1795
+ };
1796
+ }
1797
+ /** Способ получения событий. */
1798
+ type RealtimeTransportKind = 'auto' | 'sse' | 'poll';
1799
+ /** Настройки потока уведомлений. */
1800
+ interface RealtimeOptions extends ReconnectOptions {
1801
+ /**
1802
+ * Транспорт. По умолчанию `auto`: поток событий, если среда умеет читать тело ответа
1803
+ * по частям, иначе опрос.
1804
+ *
1805
+ * Можно передать и свою реализацию {@link RealtimeTransport} — это пригодится, если
1806
+ * у платформы появится WebSocket либо нужен нестандартный способ доставки.
1807
+ */
1808
+ transport?: RealtimeTransportKind | RealtimeTransport;
1809
+ /**
1810
+ * Молчание сервера, после которого соединение считается мёртвым, мс. По умолчанию 90 000.
1811
+ *
1812
+ * Сервер не присылает keep-alive, поэтому без этой проверки оборванное соединение
1813
+ * может незаметно «зависнуть».
1814
+ */
1815
+ idleTimeout?: number;
1816
+ /** Как часто опрашивать сервер, если используется запасной транспорт. */
1817
+ pollInterval?: number;
1818
+ /**
1819
+ * Запрашивать число непрочитанных при подключении. По умолчанию `true`.
1820
+ *
1821
+ * Так поступает сайт итд.com: поток присылает только новые события, а начальное
1822
+ * значение счётчика нужно получить отдельно.
1823
+ */
1824
+ syncCount?: boolean;
1825
+ /**
1826
+ * Переподключаться, когда вкладка снова становится видимой. По умолчанию `true`.
1827
+ *
1828
+ * Только в браузере. У сайта итд.com такой обработки нет, из-за чего вкладка,
1829
+ * пролежавшая в фоне, может остаться без соединения.
1830
+ */
1831
+ reconnectOnVisible?: boolean;
1832
+ /** Переподключаться при восстановлении сети. По умолчанию `true`. Только в браузере. */
1833
+ reconnectOnOnline?: boolean;
1834
+ }
1835
+ /** Что поток получает от клиента. */
1836
+ interface RealtimeDeps {
1837
+ baseUrl: string;
1838
+ fetch: typeof fetch;
1839
+ getToken: () => Promise<string | null>;
1840
+ /** Обновляет токен после отказа авторизации. Возвращает `true`, если удалось. */
1841
+ refresh: () => Promise<boolean>;
1842
+ /** Загружает начальное число непрочитанных. */
1843
+ fetchUnreadCount: () => Promise<number>;
1844
+ logger?: Logger | undefined;
1845
+ }
1846
+ /**
1847
+ * Поток уведомлений в реальном времени.
1848
+ *
1849
+ * Получается вызовом `itd.realtime()`. Соединение поднимается методом {@link connect}
1850
+ * и держится само: обрывы, обновление токена и повторные попытки библиотека берёт на себя.
1851
+ *
1852
+ * @example
1853
+ * ```ts
1854
+ * const stream = itd.realtime();
1855
+ *
1856
+ * stream.on('notification', ({ notification, unreadCount }) => {
1857
+ * console.log(formatNotificationText(notification), unreadCount);
1858
+ * });
1859
+ * stream.on('status', (status) => console.log('соединение:', status));
1860
+ *
1861
+ * await stream.connect();
1862
+ * // …позже
1863
+ * stream.disconnect();
1864
+ * ```
1865
+ */
1866
+ declare class ItdRealtime {
1867
+ #private;
1868
+ constructor(deps: RealtimeDeps, options?: RealtimeOptions);
1869
+ /** Текущее состояние соединения. */
1870
+ get status(): RealtimeStatus;
1871
+ /** Какой транспорт используется: `sse` или `poll`. */
1872
+ get transport(): string;
1873
+ /** Подписывается на событие потока. @returns функция отписки */
1874
+ on<K extends keyof RealtimeEvents>(event: K, listener: Listener<RealtimeEvents[K]>): Unsubscribe;
1875
+ /** Подписывается на одно срабатывание. */
1876
+ once<K extends keyof RealtimeEvents>(event: K, listener: Listener<RealtimeEvents[K]>): Unsubscribe;
1877
+ /**
1878
+ * Поднимает соединение.
1879
+ *
1880
+ * Повторный вызов при уже живом соединении ничего не делает — это защита от двойного
1881
+ * подключения при перерисовке интерфейса.
1882
+ *
1883
+ * Возвращает управление сразу после запуска: соединение живёт в фоне.
1884
+ */
1885
+ connect(): Promise<void>;
1886
+ /** Закрывает соединение и отменяет запланированные попытки. */
1887
+ disconnect(): void;
1888
+ /** Снимает все подписки. Соединение при этом не закрывается. */
1889
+ removeAllListeners(): void;
1890
+ }
1891
+
1892
+ /**
1893
+ * Страница списка — единая форма для всех трёх схем пагинации API.
1894
+ *
1895
+ * Какие необязательные поля заполнены, зависит от эндпоинта: у ленты это `nextCursor`,
1896
+ * у подписчиков — `page` и `total`, у уведомлений — `nextOffset`. Обычно они не нужны:
1897
+ * перебор берёт на себя {@link Paginator}.
1898
+ */
1899
+ interface Page<T> {
1900
+ /** Элементы страницы. */
1901
+ items: T[];
1902
+ /** Есть ли следующая страница. */
1903
+ hasMore: boolean;
1904
+ /**
1905
+ * Курсор следующей страницы.
1906
+ *
1907
+ * Непрозрачен: у вкладки `popular` это номер страницы, у `following` — отметка времени.
1908
+ * Передавайте его обратно как есть и не пытайтесь разобрать.
1909
+ */
1910
+ nextCursor?: string | null | undefined;
1911
+ /** Номер текущей страницы при постраничной схеме. */
1912
+ page?: number | undefined;
1913
+ /** Запрошенный размер страницы. */
1914
+ limit?: number | undefined;
1915
+ /** Общее число элементов, если сервер его сообщил. */
1916
+ total?: number | undefined;
1917
+ /** Смещение для следующего запроса при схеме со смещением. */
1918
+ nextOffset?: number | undefined;
1919
+ /** Исходный ответ — на случай, если документация разошлась с реальностью. */
1920
+ raw: unknown;
1921
+ }
1922
+ /** Схема пагинации эндпоинта. */
1923
+ type PaginationMode = 'cursor' | 'page' | 'offset';
1924
+ /** Позиция, с которой запрашивается очередная страница. */
1925
+ interface PageState {
1926
+ cursor?: string | undefined;
1927
+ page?: number | undefined;
1928
+ offset?: number | undefined;
1929
+ }
1930
+ /** Настройки перебора страниц. */
1931
+ interface PaginatorOptions<T> {
1932
+ mode: PaginationMode;
1933
+ /** Загружает одну страницу для указанной позиции. */
1934
+ load: (state: PageState) => Promise<Page<T>>;
1935
+ /**
1936
+ * Предохранитель от бесконечного перебора. По умолчанию 1000.
1937
+ *
1938
+ * Сработает, только если сервер бесконечно сообщает `hasMore` — при нормальной работе
1939
+ * перебор останавливается сам.
1940
+ */
1941
+ maxPages?: number | undefined;
1942
+ /** Отмена перебора. */
1943
+ signal?: AbortSignal | undefined;
1944
+ }
1945
+ /**
1946
+ * Перебор страниц списка.
1947
+ *
1948
+ * Скрывает различия трёх схем пагинации: перебор элементов, страниц и сбор в массив
1949
+ * выглядят одинаково независимо от эндпоинта.
1950
+ *
1951
+ * @example Перебор элементов
1952
+ * ```ts
1953
+ * for await (const post of itd.posts.iterate({ tab: 'following' })) {
1954
+ * console.log(post.content);
1955
+ * }
1956
+ * ```
1957
+ *
1958
+ * @example Первые сто элементов
1959
+ * ```ts
1960
+ * const posts = await itd.posts.iterate({ tab: 'popular' }).collect(100);
1961
+ * ```
1962
+ *
1963
+ * @example Постранично
1964
+ * ```ts
1965
+ * for await (const page of itd.users.followers('durov').pages()) {
1966
+ * console.log(page.items.length, 'из', page.total);
1967
+ * }
1968
+ * ```
1969
+ */
1970
+ declare class Paginator<T> implements AsyncIterable<T> {
1971
+ #private;
1972
+ constructor(options: PaginatorOptions<T>);
1973
+ /**
1974
+ * Загружает следующую страницу.
1975
+ *
1976
+ * @returns страница либо `null`, если перебор закончен
1977
+ */
1978
+ next(): Promise<Page<T> | null>;
1979
+ /**
1980
+ * Перебирает страницы целиком.
1981
+ *
1982
+ * Полезно, когда нужны сведения о самой странице — например `total`.
1983
+ */
1984
+ pages(): AsyncGenerator<Page<T>, void, undefined>;
1985
+ /** Перебирает элементы всех страниц подряд. */
1986
+ [Symbol.asyncIterator](): AsyncGenerator<T, void, undefined>;
1987
+ /**
1988
+ * Собирает элементы в массив.
1989
+ *
1990
+ * @param max сколько элементов достаточно; без него перебираются все страницы
1991
+ */
1992
+ collect(max?: number): Promise<T[]>;
1993
+ }
1994
+
1995
+ /** Общая основа всех групп методов клиента. */
1996
+ declare class BaseResource {
1997
+ /** @internal */
1998
+ protected readonly http: HttpClient;
1999
+ constructor(http: HttpClient);
2000
+ /** Переносит общие поля опций запроса в параметры транспорта. */
2001
+ protected requestOptions(options: RequestOptions | undefined): Partial<RequestOptions>;
2002
+ /**
2003
+ * Собирает перебор страниц.
2004
+ *
2005
+ * @param mode схема пагинации эндпоинта
2006
+ * @param load загружает одну страницу для указанной позиции
2007
+ */
2008
+ protected paginate<T>(mode: PaginationMode, load: (state: PageState) => Promise<Page<T>>, options?: RequestOptions & {
2009
+ maxPages?: number;
2010
+ }): Paginator<T>;
2011
+ }
2012
+
2013
+ /** Учётные данные для входа. */
2014
+ interface Credentials {
2015
+ email: string;
2016
+ password: string;
2017
+ }
2018
+ /**
2019
+ * Результат входа.
2020
+ *
2021
+ * Сервер может как сразу выдать токен, так и потребовать код подтверждения — размеченное
2022
+ * объединение делает оба случая явными.
2023
+ */
2024
+ type SignInResult = {
2025
+ status: 'authenticated';
2026
+ accessToken: string;
2027
+ } | {
2028
+ status: 'otp_required';
2029
+ flowToken: string | undefined;
2030
+ };
2031
+ /** Провайдер внешнего входа. */
2032
+ type OAuthProvider = 'yandex' | 'google';
2033
+ /**
2034
+ * Авторизация, сессии и пароли.
2035
+ *
2036
+ * Доступна как `itd.auth`.
2037
+ */
2038
+ declare class AuthResource extends BaseResource {
2039
+ #private;
2040
+ constructor(http: HttpClient, deps: {
2041
+ auth: AuthManager;
2042
+ });
2043
+ /**
2044
+ * Регистрирует аккаунт и запускает подтверждение по коду.
2045
+ *
2046
+ * @returns `flowToken`, который нужно передать в {@link verifyOtp}
2047
+ */
2048
+ signUp(credentials: Credentials, options?: RequestOptions): Promise<string>;
2049
+ /**
2050
+ * Выполняет вход.
2051
+ *
2052
+ * Если сервер потребовал код подтверждения, вернётся `status: 'otp_required'` —
2053
+ * тогда продолжайте через {@link verifyOtp} либо воспользуйтесь {@link signInWithOtp}.
2054
+ *
2055
+ * При успешном входе токен сохраняется в клиенте автоматически.
2056
+ */
2057
+ signIn(credentials: Credentials, options?: RequestOptions): Promise<SignInResult>;
2058
+ /**
2059
+ * Подтверждает вход кодом из письма.
2060
+ *
2061
+ * Полученный токен сохраняется в клиенте автоматически.
2062
+ */
2063
+ verifyOtp(input: Credentials & {
2064
+ otp: string;
2065
+ flowToken: string;
2066
+ }, options?: RequestOptions): Promise<string>;
2067
+ /** Отправляет код подтверждения повторно. */
2068
+ resendOtp(input: {
2069
+ email: string;
2070
+ flowToken: string;
2071
+ }, options?: RequestOptions): Promise<void>;
2072
+ /**
2073
+ * Полный вход с подтверждением по коду.
2074
+ *
2075
+ * Удобно для скриптов и ботов: код запрашивается функцией `getOtp`, а всё остальное
2076
+ * библиотека делает сама.
2077
+ *
2078
+ * @example
2079
+ * ```ts
2080
+ * import { createInterface } from 'node:readline/promises';
2081
+ *
2082
+ * const rl = createInterface({ input: process.stdin, output: process.stdout });
2083
+ *
2084
+ * const token = await itd.auth.signInWithOtp({
2085
+ * email, password,
2086
+ * getOtp: () => rl.question('Код из письма: '),
2087
+ * });
2088
+ * ```
2089
+ */
2090
+ signInWithOtp(input: Credentials & {
2091
+ getOtp: () => string | Promise<string>;
2092
+ }, options?: RequestOptions): Promise<string>;
2093
+ /**
2094
+ * Обновляет токен доступа.
2095
+ *
2096
+ * Параллельные вызовы объединяются в один сетевой запрос. При включённом `autoRefresh`
2097
+ * вызывать вручную обычно не нужно.
2098
+ */
2099
+ refresh(): Promise<string>;
2100
+ /**
2101
+ * Есть ли признак живой сессии обновления.
2102
+ *
2103
+ * Проверяет cookie `is_auth`, которую сервер ставит рядом с refresh-токеном. Позволяет
2104
+ * не дёргать API у неавторизованного пользователя. В браузере всегда `true`:
2105
+ * cookie ведёт сама среда, и прочитать её из JS нельзя.
2106
+ */
2107
+ hasRefreshSession(): boolean;
2108
+ /** Завершает текущую сессию на сервере и очищает локальную. */
2109
+ logout(options?: RequestOptions): Promise<void>;
2110
+ /** Завершает все сессии пользователя и очищает локальную. */
2111
+ logoutAll(options?: RequestOptions): Promise<void>;
2112
+ /** Забывает сессию локально, не обращаясь к серверу. */
2113
+ signOut(): Promise<void>;
2114
+ /** Запрашивает письмо для сброса пароля. */
2115
+ forgotPassword(email: string, options?: RequestOptions): Promise<void>;
2116
+ /** Устанавливает новый пароль по токену из письма. */
2117
+ resetPassword(input: {
2118
+ token: string;
2119
+ newPassword: string;
2120
+ }, options?: RequestOptions): Promise<void>;
2121
+ /** Меняет пароль. Требует действующей сессии обновления. */
2122
+ changePassword(input: {
2123
+ oldPassword: string;
2124
+ newPassword: string;
2125
+ }, options?: RequestOptions): Promise<void>;
2126
+ /**
2127
+ * Возвращает адрес для входа через внешнего провайдера.
2128
+ *
2129
+ * Сам переход выполняет приложение: в браузере — редиректом, в приложении — открытием
2130
+ * системного браузера.
2131
+ *
2132
+ * @example
2133
+ * ```ts
2134
+ * window.location.href = itd.auth.oauthUrl('yandex');
2135
+ * ```
2136
+ */
2137
+ oauthUrl(provider: OAuthProvider): string;
2138
+ /** Загружает список активных сессий. У текущей поле `isCurrent` равно `true`. */
2139
+ sessions(options?: RequestOptions): Promise<Session[]>;
2140
+ /** Завершает указанную сессию. */
2141
+ revokeSession(sessionId: string, options?: RequestOptions): Promise<void>;
2142
+ /** Завершает все сессии, кроме текущей. */
2143
+ revokeOtherSessions(options?: RequestOptions): Promise<void>;
2144
+ }
2145
+
2146
+ /** Параметры запроса ответов на комментарий. */
2147
+ interface RepliesParams extends RequestOptions {
2148
+ limit?: number;
2149
+ page?: number;
2150
+ maxPages?: number;
2151
+ }
2152
+ /**
2153
+ * Комментарии и ответы на них.
2154
+ *
2155
+ * Доступна как `itd.comments`. Комментарии **к посту** живут в `itd.posts`:
2156
+ * `itd.posts.comments()` и `itd.posts.comment()`.
2157
+ */
2158
+ declare class CommentsResource extends BaseResource {
2159
+ #private;
2160
+ constructor(http: HttpClient, deps: {
2161
+ uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]>;
2162
+ });
2163
+ /**
2164
+ * Загружает страницу ответов на комментарий.
2165
+ *
2166
+ * Здесь пагинация **постраничная**, в отличие от комментариев к посту, где курсорная.
2167
+ */
2168
+ replies(commentId: string, params?: RepliesParams): Promise<Page<Comment>>;
2169
+ /** Перебирает ответы на комментарий. */
2170
+ iterateReplies(commentId: string, params?: RepliesParams): Paginator<Comment>;
2171
+ /**
2172
+ * Отвечает на комментарий.
2173
+ *
2174
+ * @example
2175
+ * ```ts
2176
+ * await itd.comments.reply(commentId, 'согласен');
2177
+ * await itd.comments.reply(commentId, (c) => c.content('и вот почему').replyTo(userId));
2178
+ * ```
2179
+ */
2180
+ reply(commentId: string, input: CommentInput | string, options?: RequestOptions): Promise<Comment>;
2181
+ /** Редактирует текст комментария. */
2182
+ update(commentId: string, content: string, options?: RequestOptions): Promise<Comment>;
2183
+ /** Удаляет комментарий. Восстановить его можно через {@link restore}. */
2184
+ remove(commentId: string, options?: RequestOptions): Promise<void>;
2185
+ /** Восстанавливает удалённый комментарий. */
2186
+ restore(commentId: string, options?: RequestOptions): Promise<Comment>;
2187
+ /** Ставит реакцию на комментарий. */
2188
+ like(commentId: string, options?: RequestOptions): Promise<LikeResult>;
2189
+ /** Убирает реакцию с комментария. */
2190
+ unlike(commentId: string, options?: RequestOptions): Promise<LikeResult>;
2191
+ }
2192
+
2193
+ /** Ответ загрузки файла. */
2194
+ interface UploadedFile {
2195
+ /** Идентификатор вложения — его передают в `attachmentIds`. */
2196
+ id: string;
2197
+ /** Адрес файла на CDN. */
2198
+ url: string;
2199
+ }
2200
+ /** Настройки загрузки. */
2201
+ interface UploadOptions extends RequestOptions {
2202
+ /** Имя файла. По нему определяется тип, если он не задан явно. */
2203
+ filename?: string;
2204
+ /** MIME-тип. Если не указан, определяется по имени файла или по самому `Blob`. */
2205
+ contentType?: string;
2206
+ /**
2207
+ * Проверять тип файла до отправки. По умолчанию `true`.
2208
+ *
2209
+ * Отключайте, если API начал принимать формат, которого ещё нет в списке библиотеки.
2210
+ */
2211
+ validateMime?: boolean;
2212
+ }
2213
+ /** Чтение файла по пути — подставляется точкой входа `itd-api/node`. */
2214
+ type FileReader = (path: string) => Promise<{
2215
+ data: Uint8Array;
2216
+ filename: string;
2217
+ }>;
2218
+ /**
2219
+ * Файлы и медиа.
2220
+ *
2221
+ * Доступна как `itd.files`. Обычно вызывать её напрямую не нужно: `itd.posts.create()`
2222
+ * и `itd.posts.comment()` загружают файлы сами.
2223
+ */
2224
+ declare class FilesResource extends BaseResource {
2225
+ #private;
2226
+ constructor(http: HttpClient, deps?: {
2227
+ readFile?: FileReader;
2228
+ });
2229
+ /**
2230
+ * Подключает чтение файлов с диска.
2231
+ *
2232
+ * Вызывается точкой входа `itd-api/node`; в основном бандле работы с файловой
2233
+ * системой нет, чтобы браузерные сборщики не пытались разрешить `node:fs`.
2234
+ */
2235
+ setFileReader(readFile: FileReader): void;
2236
+ /**
2237
+ * Загружает файл и возвращает его идентификатор.
2238
+ *
2239
+ * @remarks
2240
+ * Кроме типа сервер проверяет и само изображение: слишком маленькие картинки
2241
+ * он отклоняет сообщением «Не удалось проверить изображение». Точный порог
2242
+ * неизвестен, но 64×64 проходит.
2243
+ *
2244
+ * @example
2245
+ * ```ts
2246
+ * const file = await itd.files.upload(blob, { filename: 'photo.jpg' });
2247
+ * await itd.posts.create({ content: 'смотрите', attachmentIds: [file.id] });
2248
+ * ```
2249
+ */
2250
+ upload(input: FileInput, options?: UploadOptions): Promise<UploadedFile>;
2251
+ /**
2252
+ * Загружает несколько файлов, сохраняя порядок.
2253
+ *
2254
+ * Файлы отправляются последовательно: параллельная загрузка нескольких видео легко
2255
+ * упирается в ограничение частоты, а порядок вложений в посте важен.
2256
+ *
2257
+ * @returns идентификаторы вложений в том же порядке, что и входные файлы
2258
+ */
2259
+ uploadMany(files: FileInput[], options?: UploadOptions): Promise<string[]>;
2260
+ /**
2261
+ * Загружает сведения о файле.
2262
+ *
2263
+ * @remarks
2264
+ * Сервер отвечает `404` даже на только что загруженный файл, который ещё никуда
2265
+ * не прикреплён, — проверено на боевом API. Практической пользы у метода пока нет,
2266
+ * он оставлен для полноты.
2267
+ */
2268
+ get(fileId: string, options?: RequestOptions): Promise<unknown>;
2269
+ /** Удаляет загруженный файл. */
2270
+ remove(fileId: string, options?: RequestOptions): Promise<void>;
2271
+ /** Приводит любой поддерживаемый вход к `Blob` с именем и проверенным типом. */
2272
+ private prepare;
2273
+ }
2274
+
2275
+ /** Параметры запроса постов по хэштегу. */
2276
+ interface HashtagPostsParams extends RequestOptions {
2277
+ limit?: number;
2278
+ cursor?: string;
2279
+ maxPages?: number;
2280
+ }
2281
+ /** Результат глобального поиска. */
2282
+ interface SearchResult {
2283
+ users: UserSummary[];
2284
+ hashtags: Hashtag[];
2285
+ }
2286
+ /**
2287
+ * Хэштеги.
2288
+ *
2289
+ * Доступна как `itd.hashtags`.
2290
+ */
2291
+ declare class HashtagsResource extends BaseResource {
2292
+ /**
2293
+ * Ищет хэштеги.
2294
+ *
2295
+ * Без строки запроса возвращает общий список.
2296
+ */
2297
+ search(query?: string, params?: {
2298
+ limit?: number;
2299
+ } & RequestOptions): Promise<Hashtag[]>;
2300
+ /** Загружает трендовые хэштеги. */
2301
+ trending(params?: {
2302
+ limit?: number;
2303
+ } & RequestOptions): Promise<Hashtag[]>;
2304
+ /**
2305
+ * Загружает страницу постов по хэштегу.
2306
+ *
2307
+ * @param tag название без решётки; кодируется автоматически, поэтому кириллица
2308
+ * и пробелы допустимы
2309
+ */
2310
+ posts(tag: string, params?: HashtagPostsParams): Promise<Page<Post>>;
2311
+ /** Перебирает посты по хэштегу. */
2312
+ iteratePosts(tag: string, params?: HashtagPostsParams): Paginator<Post>;
2313
+ }
2314
+ /**
2315
+ * Глобальный поиск.
2316
+ *
2317
+ * Доступна как `itd.search`.
2318
+ */
2319
+ declare class SearchResource extends BaseResource {
2320
+ /**
2321
+ * Ищет пользователей и хэштеги одним запросом.
2322
+ *
2323
+ * @example
2324
+ * ```ts
2325
+ * const { users, hashtags } = await itd.search.all('арт');
2326
+ * ```
2327
+ */
2328
+ all(query: string, options?: RequestOptions): Promise<SearchResult>;
2329
+ }
2330
+ /**
2331
+ * Жалобы на контент и пользователей.
2332
+ *
2333
+ * Доступна как `itd.reports`.
2334
+ */
2335
+ declare class ReportsResource extends BaseResource {
2336
+ /**
2337
+ * Отправляет жалобу.
2338
+ *
2339
+ * Повторная жалоба на тот же объект отклоняется сервером с сообщением
2340
+ * «Вы уже отправляли жалобу на этот контент».
2341
+ *
2342
+ * @example
2343
+ * ```ts
2344
+ * await itd.reports.create(report.post(postId).reason('spam'));
2345
+ * await itd.reports.create({ targetType: 'user', targetId, reason: 'fraud' });
2346
+ * ```
2347
+ */
2348
+ create(input: ReportInput, options?: RequestOptions): Promise<Report>;
2349
+ }
2350
+ /**
2351
+ * Верификация профиля.
2352
+ *
2353
+ * Доступна как `itd.verification`.
2354
+ */
2355
+ declare class VerificationResource extends BaseResource {
2356
+ /** Загружает статус заявки. Значение `none` означает, что заявка не подавалась. */
2357
+ status(options?: RequestOptions): Promise<VerificationStatus>;
2358
+ /** Подаёт заявку на верификацию с видео. */
2359
+ submit(videoUrl: string, options?: RequestOptions): Promise<unknown>;
2360
+ }
2361
+ /**
2362
+ * Подписка и способы оплаты.
2363
+ *
2364
+ * Доступна как `itd.subscription`.
2365
+ */
2366
+ declare class SubscriptionResource extends BaseResource {
2367
+ /** Загружает состояние подписки и её цену. */
2368
+ status(options?: RequestOptions): Promise<Subscription>;
2369
+ /**
2370
+ * Запускает оплату подписки.
2371
+ *
2372
+ * Форма ответа в документации API не описана, поэтому тип результата не уточняется.
2373
+ */
2374
+ pay(options?: RequestOptions): Promise<unknown>;
2375
+ /** Включает или отключает автопродление. */
2376
+ setAutoRenewal(enabled: boolean, options?: RequestOptions): Promise<unknown>;
2377
+ /** Запускает привязку карты. */
2378
+ bindCard(options?: RequestOptions): Promise<unknown>;
2379
+ /** Загружает список способов оплаты. Пустой массив, если карт нет. */
2380
+ methods(options?: RequestOptions): Promise<PaymentMethod[]>;
2381
+ /** Делает способ оплаты основным. */
2382
+ setDefaultMethod(methodId: string, options?: RequestOptions): Promise<unknown>;
2383
+ /** Удаляет способ оплаты. */
2384
+ removeMethod(methodId: string, options?: RequestOptions): Promise<void>;
2385
+ }
2386
+ /**
2387
+ * Сведения о платформе: изменения, анонсы, баннер события.
2388
+ *
2389
+ * Доступна как `itd.platform`.
2390
+ */
2391
+ declare class PlatformResource extends BaseResource {
2392
+ /** Загружает журнал изменений. */
2393
+ changelog(options?: RequestOptions): Promise<ChangelogEntry[]>;
2394
+ /** Загружает анонсы платформы. */
2395
+ announcements(options?: RequestOptions): Promise<Announcement[]>;
2396
+ /** Загружает баннер текущего события — виджет «портал». */
2397
+ portal(options?: RequestOptions): Promise<Portal>;
2398
+ }
2399
+ /** Запись о времени просмотра поста. */
2400
+ interface DwellEntry {
2401
+ /** Идентификатор поста. */
2402
+ postId: string;
2403
+ /** Сколько миллисекунд пост был виден. */
2404
+ duration: number;
2405
+ /** Служебная метка показа из поля `vs` объекта поста. */
2406
+ vs?: string;
2407
+ }
2408
+ /** Запись о взаимодействии с контентом. */
2409
+ interface InteractionEntry {
2410
+ /** Тип взаимодействия: `photo_open`, `video_progress` и подобные. */
2411
+ type: string;
2412
+ /** Значение, смысл которого зависит от типа: доля просмотра, номер кадра. */
2413
+ value?: number;
2414
+ /** Идентификатор поста. */
2415
+ postId?: string;
2416
+ /** Идентификатор вложения. */
2417
+ attachmentId?: string;
2418
+ /** Служебная метка показа. */
2419
+ vs?: string;
2420
+ }
2421
+ /**
2422
+ * Телеметрия просмотров.
2423
+ *
2424
+ * @experimental Эндпоинты `/api/v1/i` и `/api/v1/x` нигде не описаны, а схема их полей
2425
+ * не проверена на реальных запросах и может измениться без предупреждения.
2426
+ *
2427
+ * **Библиотека никогда не отправляет телеметрию сама.** Эти методы нужны только тем,
2428
+ * кто пишет собственный клиент платформы; всем остальным их вызывать не требуется.
2429
+ *
2430
+ * Доступна как `itd.telemetry`.
2431
+ */
2432
+ declare class TelemetryResource extends BaseResource {
2433
+ /**
2434
+ * Отправляет время просмотра постов.
2435
+ *
2436
+ * @experimental Имена полей на проводе сжаты (`ai`, `v`, `s`), и их соответствие
2437
+ * смыслу **не проверено** на реальных запросах. Может измениться без предупреждения.
2438
+ */
2439
+ dwell(entries: DwellEntry[], options?: RequestOptions): Promise<unknown>;
2440
+ /**
2441
+ * Отправляет события взаимодействия с контентом.
2442
+ *
2443
+ * @experimental См. предупреждение у {@link TelemetryResource}.
2444
+ */
2445
+ interaction(entries: InteractionEntry[], options?: RequestOptions): Promise<unknown>;
2446
+ }
2447
+
2448
+ /** Параметры запроса списка уведомлений. */
2449
+ interface NotificationListParams extends RequestOptions {
2450
+ limit?: number;
2451
+ /** Смещение от начала списка. */
2452
+ offset?: number;
2453
+ maxPages?: number;
2454
+ }
2455
+ /** Изменяемые настройки уведомлений. */
2456
+ type UpdateNotificationSettingsInput = Partial<NotificationSettings>;
2457
+ /**
2458
+ * Уведомления: список, счётчик, отметки о прочтении, настройки.
2459
+ *
2460
+ * Доступна как `itd.notifications`. Все уведомления приведены к единой форме, поэтому
2461
+ * объекты отсюда и из потока событий можно складывать в один список.
2462
+ */
2463
+ declare class NotificationsResource extends BaseResource {
2464
+ /**
2465
+ * Загружает страницу уведомлений.
2466
+ *
2467
+ * Пагинация здесь основана на смещении. Сайт итд.com оборачивает смещение в строку
2468
+ * и притворяется, что это курсор; библиотека отдаёт честное число.
2469
+ *
2470
+ * @example
2471
+ * ```ts
2472
+ * const page = await itd.notifications.list({ limit: 20 });
2473
+ * const next = await itd.notifications.list({ limit: 20, offset: page.nextOffset });
2474
+ * ```
2475
+ */
2476
+ list(params?: NotificationListParams): Promise<Page<Notification>>;
2477
+ /**
2478
+ * Перебирает уведомления.
2479
+ *
2480
+ * @example
2481
+ * ```ts
2482
+ * for await (const notification of itd.notifications.iterate()) {
2483
+ * console.log(formatNotificationText(notification));
2484
+ * }
2485
+ * ```
2486
+ */
2487
+ iterate(params?: NotificationListParams): Paginator<Notification>;
2488
+ /** Загружает число непрочитанных уведомлений. */
2489
+ count(options?: RequestOptions): Promise<number>;
2490
+ /**
2491
+ * Отмечает уведомление прочитанным.
2492
+ *
2493
+ * @returns сколько записей отметил сервер
2494
+ */
2495
+ markRead(notificationId: string, options?: RequestOptions): Promise<number>;
2496
+ /**
2497
+ * Отмечает прочитанными сразу несколько уведомлений.
2498
+ *
2499
+ * Список автоматически режется на части по 20 идентификаторов — столько же отправляет
2500
+ * сайт итд.com, поэтому на сервере вероятен предел. Части уходят последовательно,
2501
+ * результат суммируется.
2502
+ *
2503
+ * @returns сколько записей отметил сервер суммарно
2504
+ */
2505
+ markReadBatch(ids: string[], options?: RequestOptions): Promise<number>;
2506
+ /** Отмечает прочитанными все уведомления. */
2507
+ markAllRead(options?: RequestOptions): Promise<number>;
2508
+ /** Загружает настройки уведомлений. */
2509
+ getSettings(options?: RequestOptions): Promise<NotificationSettings>;
2510
+ /**
2511
+ * Обновляет настройки уведомлений.
2512
+ *
2513
+ * Отправляются только изменяемые поля, в том же виде, в каком сервер их возвращает.
2514
+ */
2515
+ updateSettings(input: UpdateNotificationSettingsInput, options?: RequestOptions): Promise<NotificationSettings>;
2516
+ }
2517
+
2518
+ /** Параметры запроса ленты. */
2519
+ interface FeedParams extends RequestOptions {
2520
+ /** Вкладка ленты. По умолчанию сервер отдаёт популярное. */
2521
+ tab?: FeedTab;
2522
+ /** Сколько постов на страницу. */
2523
+ limit?: number;
2524
+ /**
2525
+ * Курсор следующей страницы из предыдущего ответа.
2526
+ *
2527
+ * Передавайте значение как есть: его формат зависит от вкладки и может измениться.
2528
+ */
2529
+ cursor?: string;
2530
+ /** Ограничение числа страниц при переборе. */
2531
+ maxPages?: number;
2532
+ }
2533
+ /** Параметры запроса постов пользователя. */
2534
+ interface UserPostsParams extends RequestOptions {
2535
+ limit?: number;
2536
+ cursor?: string;
2537
+ /** Порядок сортировки. */
2538
+ sort?: string;
2539
+ /** Закреплённый пост, чтобы сервер поднял его наверх. */
2540
+ pinnedPostId?: string;
2541
+ maxPages?: number;
2542
+ }
2543
+ /** Параметры запроса комментариев к посту. */
2544
+ interface CommentsParams extends RequestOptions {
2545
+ limit?: number;
2546
+ /**
2547
+ * Курсор следующей страницы: идентификатор последнего полученного комментария.
2548
+ *
2549
+ * Передавайте значение из `nextCursor` предыдущего ответа как есть.
2550
+ */
2551
+ cursor?: string;
2552
+ sort?: CommentSort;
2553
+ maxPages?: number;
2554
+ }
2555
+ /**
2556
+ * Посты: лента, публикация, реакции, репосты, комментарии.
2557
+ *
2558
+ * Доступна как `itd.posts`.
2559
+ */
2560
+ declare class PostsResource extends BaseResource {
2561
+ #private;
2562
+ constructor(http: HttpClient, deps: {
2563
+ uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]>;
2564
+ });
2565
+ /**
2566
+ * Загружает страницу ленты.
2567
+ *
2568
+ * @example
2569
+ * ```ts
2570
+ * const page = await itd.posts.list({ tab: FeedTab.Following, limit: 20 });
2571
+ * const next = await itd.posts.list({ tab: FeedTab.Following, cursor: page.nextCursor ?? undefined });
2572
+ * ```
2573
+ */
2574
+ list(params?: FeedParams): Promise<Page<Post>>;
2575
+ /**
2576
+ * Перебирает ленту, сама подставляя курсоры.
2577
+ *
2578
+ * @example
2579
+ * ```ts
2580
+ * for await (const post of itd.posts.iterate({ tab: 'following' })) {
2581
+ * console.log(post.author.username, post.content);
2582
+ * }
2583
+ * ```
2584
+ */
2585
+ iterate(params?: FeedParams): Paginator<Post>;
2586
+ /**
2587
+ * Публикует пост.
2588
+ *
2589
+ * Принимает обычный объект, {@link PostBuilder} или функцию-настройщик. Файлы из поля
2590
+ * `files` загружаются автоматически, порядок вложений сохраняется.
2591
+ *
2592
+ * @example
2593
+ * ```ts
2594
+ * await itd.posts.create({ content: 'привет' });
2595
+ * await itd.posts.create((p) => p.content('привет').attach('./photo.jpg'));
2596
+ * ```
2597
+ */
2598
+ create(input: PostInput, options?: RequestOptions): Promise<Post>;
2599
+ /**
2600
+ * Загружает один пост вместе с топовыми комментариями.
2601
+ *
2602
+ * В отличие от списков, здесь у поста заполнено поле `comments`.
2603
+ */
2604
+ get(postId: string, options?: RequestOptions): Promise<Post>;
2605
+ /** Редактирует текст поста. */
2606
+ update(postId: string, input: Pick<CreatePostInput, 'content' | 'spans'>, options?: RequestOptions): Promise<Post>;
2607
+ /** Удаляет пост. Восстановить его можно через {@link restore}. */
2608
+ remove(postId: string, options?: RequestOptions): Promise<void>;
2609
+ /** Восстанавливает удалённый пост. */
2610
+ restore(postId: string, options?: RequestOptions): Promise<Post>;
2611
+ /** Ставит реакцию на пост. */
2612
+ like(postId: string, options?: RequestOptions): Promise<LikeResult>;
2613
+ /** Убирает реакцию с поста. */
2614
+ unlike(postId: string, options?: RequestOptions): Promise<LikeResult>;
2615
+ /**
2616
+ * Делает репост с необязательным комментарием.
2617
+ *
2618
+ * Вложения к репосту не поддерживаются: сервер их игнорирует, поэтому параметров
2619
+ * для файлов здесь нет.
2620
+ */
2621
+ repost(postId: string, content?: string, options?: RequestOptions): Promise<Post>;
2622
+ /** Отменяет репост. */
2623
+ unrepost(postId: string, options?: RequestOptions): Promise<void>;
2624
+ /** Закрепляет пост в профиле. */
2625
+ pin(postId: string, options?: RequestOptions): Promise<PinPostResult>;
2626
+ /** Открепляет пост. */
2627
+ unpin(postId: string, options?: RequestOptions): Promise<PinPostResult>;
2628
+ /**
2629
+ * Голосует в опросе.
2630
+ *
2631
+ * @param optionIds выбранные варианты; несколько допустимы только при `multipleChoice`
2632
+ */
2633
+ vote(postId: string, optionIds: string[], options?: RequestOptions): Promise<Poll>;
2634
+ /** Запрашивает счётчики сразу для нескольких постов. */
2635
+ stats(ids: string[], options?: RequestOptions): Promise<PostStats[]>;
2636
+ /** Загружает страницу постов пользователя (его стену). */
2637
+ byUser(user: UserRef, params?: UserPostsParams): Promise<Page<Post>>;
2638
+ /** Перебирает посты пользователя. */
2639
+ iterateByUser(user: UserRef, params?: UserPostsParams): Paginator<Post>;
2640
+ /** Загружает страницу постов, которые пользователь отметил реакцией. */
2641
+ likedByUser(user: UserRef, params?: UserPostsParams): Promise<Page<Post>>;
2642
+ /** Перебирает посты, которые пользователь отметил реакцией. */
2643
+ iterateLikedByUser(user: UserRef, params?: UserPostsParams): Paginator<Post>;
2644
+ /**
2645
+ * Загружает страницу комментариев к посту.
2646
+ *
2647
+ * У этого эндпоинта курсор и признак продолжения лежат рядом со списком, а не внутри
2648
+ * объекта `pagination`, как у остальных, — разница скрыта внутри.
2649
+ */
2650
+ comments(postId: string, params?: CommentsParams): Promise<Page<Comment>>;
2651
+ /** Перебирает комментарии к посту. */
2652
+ iterateComments(postId: string, params?: CommentsParams): Paginator<Comment>;
2653
+ /**
2654
+ * Комментирует пост.
2655
+ *
2656
+ * @example
2657
+ * ```ts
2658
+ * await itd.posts.comment(postId, 'согласен');
2659
+ * await itd.posts.comment(postId, (c) => c.content('смотри').attach('./meme.png'));
2660
+ * ```
2661
+ */
2662
+ comment(postId: string, input: CommentInput | string, options?: RequestOptions): Promise<Comment>;
2663
+ /**
2664
+ * Отправляет голосовой комментарий.
2665
+ *
2666
+ * Текста у такого комментария нет: сервер ждёт пустой `content` и одно аудиовложение
2667
+ * в формате `audio/ogg`.
2668
+ *
2669
+ * @example
2670
+ * ```ts
2671
+ * await itd.posts.voiceComment(postId, './answer.ogg');
2672
+ * ```
2673
+ */
2674
+ voiceComment(postId: string, audio: FileInput, options?: RequestOptions): Promise<Comment>;
2675
+ }
2676
+
2677
+ /** Постраничные параметры списков пользователей. */
2678
+ interface UserListParams extends RequestOptions {
2679
+ limit?: number;
2680
+ page?: number;
2681
+ maxPages?: number;
2682
+ }
2683
+ /** Изменяемые поля своего профиля. */
2684
+ interface UpdateProfileInput {
2685
+ displayName?: string;
2686
+ username?: string;
2687
+ /** Эмодзи-аватар: символ клана, а не адрес картинки. */
2688
+ avatar?: string;
2689
+ bio?: string;
2690
+ /** Адрес изображения-шапки. */
2691
+ banner?: string;
2692
+ }
2693
+ /** Изменяемые настройки приватности. */
2694
+ type UpdatePrivacyInput = Partial<PrivacySettings>;
2695
+ /**
2696
+ * Пользователи: профили, подписки, блокировки, приватность.
2697
+ *
2698
+ * Доступна как `itd.users`.
2699
+ */
2700
+ declare class UsersResource extends BaseResource {
2701
+ #private;
2702
+ /** Загружает свой профиль — с подпиской и признаком подтверждённого телефона. */
2703
+ me(options?: RequestOptions): Promise<MyProfile>;
2704
+ /** Обновляет свой профиль. Передавайте только изменяемые поля. */
2705
+ updateMe(input: UpdateProfileInput, options?: RequestOptions): Promise<MyProfile>;
2706
+ /** Деактивирует аккаунт. Вернуть его можно через {@link restore}. */
2707
+ deactivate(options?: RequestOptions): Promise<void>;
2708
+ /** Восстанавливает деактивированный аккаунт. */
2709
+ restore(options?: RequestOptions): Promise<void>;
2710
+ /** Создаёт профиль после регистрации. */
2711
+ createProfile(input: {
2712
+ username: string;
2713
+ displayName: string;
2714
+ avatar?: string;
2715
+ }, options?: RequestOptions): Promise<MyProfile>;
2716
+ /**
2717
+ * Загружает профиль пользователя.
2718
+ *
2719
+ * @param user UUID **или** имя пользователя — подходит и то, и другое
2720
+ *
2721
+ * @example
2722
+ * ```ts
2723
+ * const profile = await itd.users.get('durov');
2724
+ * await itd.posts.create({ content: 'привет', wallRecipientId: profile.id });
2725
+ * ```
2726
+ */
2727
+ get(user: UserRef, options?: RequestOptions): Promise<PublicProfile>;
2728
+ /** Проверяет, свободно ли имя пользователя. */
2729
+ checkUsername(username: string, options?: RequestOptions): Promise<boolean>;
2730
+ /** Ищет пользователей по строке запроса. */
2731
+ search(query: string, params?: {
2732
+ limit?: number;
2733
+ } & RequestOptions): Promise<UserSummary[]>;
2734
+ /** Загружает рекомендации, на кого подписаться. */
2735
+ whoToFollow(options?: RequestOptions): Promise<UserSummary[]>;
2736
+ /** Загружает рейтинг кланов. */
2737
+ topClans(options?: RequestOptions): Promise<{
2738
+ avatar: string;
2739
+ memberCount: number;
2740
+ }[]>;
2741
+ /**
2742
+ * Подписывается на пользователя.
2743
+ *
2744
+ * У закрытого профиля вместо подписки отправляется заявка — это видно по полю `status`.
2745
+ */
2746
+ follow(user: UserRef, options?: RequestOptions): Promise<FollowResult>;
2747
+ /** Отписывается от пользователя. */
2748
+ unfollow(user: UserRef, options?: RequestOptions): Promise<void>;
2749
+ /** Загружает страницу подписчиков. */
2750
+ followers(user: UserRef, params?: UserListParams): Promise<Page<UserSummary>>;
2751
+ /** Перебирает подписчиков. */
2752
+ iterateFollowers(user: UserRef, params?: UserListParams): Paginator<UserSummary>;
2753
+ /** Загружает страницу подписок. */
2754
+ following(user: UserRef, params?: UserListParams): Promise<Page<UserSummary>>;
2755
+ /** Перебирает подписки. */
2756
+ iterateFollowing(user: UserRef, params?: UserListParams): Paginator<UserSummary>;
2757
+ /**
2758
+ * Проверяет, подписаны ли вы, сразу для нескольких пользователей.
2759
+ *
2760
+ * @returns объект «идентификатор пользователя → подписаны ли вы»
2761
+ *
2762
+ * @example
2763
+ * ```ts
2764
+ * const statuses = await itd.users.followStatus([userA, userB]);
2765
+ * // { 'b89dee4f-…': true, '35ea3059-…': false }
2766
+ * ```
2767
+ */
2768
+ followStatus(userIds: UserId[], options?: RequestOptions): Promise<Record<string, boolean>>;
2769
+ /** Блокирует пользователя. */
2770
+ block(user: UserRef, options?: RequestOptions): Promise<void>;
2771
+ /** Снимает блокировку. */
2772
+ unblock(user: UserRef, options?: RequestOptions): Promise<void>;
2773
+ /** Загружает страницу заблокированных пользователей. */
2774
+ blocked(params?: UserListParams): Promise<Page<UserSummary>>;
2775
+ /** Перебирает заблокированных пользователей. */
2776
+ iterateBlocked(params?: UserListParams): Paginator<UserSummary>;
2777
+ /** Загружает настройки приватности. */
2778
+ getPrivacy(options?: RequestOptions): Promise<PrivacySettings>;
2779
+ /** Обновляет настройки приватности. Передавайте только изменяемые поля. */
2780
+ updatePrivacy(input: UpdatePrivacyInput, options?: RequestOptions): Promise<PrivacySettings>;
2781
+ /**
2782
+ * Загружает значки профиля и выбранный из них.
2783
+ *
2784
+ * `activePin` — строка-идентификатор, а не объект.
2785
+ */
2786
+ pins(options?: RequestOptions): Promise<PinsResult>;
2787
+ /** Выбирает активный значок профиля. */
2788
+ setPin(slug: string, options?: RequestOptions): Promise<void>;
2789
+ /** Снимает активный значок. */
2790
+ removePin(options?: RequestOptions): Promise<void>;
2791
+ }
2792
+
2793
+ /**
2794
+ * Клиент API итд.com.
2795
+ *
2796
+ * Методы сгруппированы по разделам: `itd.posts`, `itd.users`, `itd.comments`, `itd.auth`,
2797
+ * `itd.files`. Авторизация, обновление токена, повторы и очередь запросов работают сами.
2798
+ *
2799
+ * @example Готовый токен — для разового вызова
2800
+ * ```ts
2801
+ * const itd = new ItdClient({ auth: '<accessToken>' });
2802
+ * const me = await itd.users.me();
2803
+ * ```
2804
+ *
2805
+ * @example Полноценная сессия для бота
2806
+ * ```ts
2807
+ * import { ItdClient } from 'itd-api';
2808
+ * import { FileTokenStorage } from 'itd-api/node';
2809
+ *
2810
+ * const itd = new ItdClient({
2811
+ * auth: { email, password },
2812
+ * storage: new FileTokenStorage('./.itd-session.json'),
2813
+ * rateLimit: { concurrency: 4, rps: 8 },
2814
+ * });
2815
+ *
2816
+ * for await (const post of itd.posts.iterate({ tab: 'following' })) {
2817
+ * if (!post.isLiked) await itd.posts.like(post.id);
2818
+ * }
2819
+ * ```
2820
+ */
2821
+ declare class ItdClient {
2822
+ #private;
2823
+ /** Авторизация, сессии и пароли. */
2824
+ readonly auth: AuthResource;
2825
+ /** Профили, подписки, блокировки, приватность. */
2826
+ readonly users: UsersResource;
2827
+ /** Лента, публикация, реакции, репосты, комментарии к постам. */
2828
+ readonly posts: PostsResource;
2829
+ /** Ответы на комментарии и действия над ними. */
2830
+ readonly comments: CommentsResource;
2831
+ /** Загрузка файлов и медиа. */
2832
+ readonly files: FilesResource;
2833
+ /** Уведомления: список, счётчик, отметки о прочтении, настройки. */
2834
+ readonly notifications: NotificationsResource;
2835
+ /** Хэштеги и посты по ним. */
2836
+ readonly hashtags: HashtagsResource;
2837
+ /** Глобальный поиск по пользователям и хэштегам. */
2838
+ readonly search: SearchResource;
2839
+ /** Жалобы на контент и пользователей. */
2840
+ readonly reports: ReportsResource;
2841
+ /** Верификация профиля. */
2842
+ readonly verification: VerificationResource;
2843
+ /** Подписка и способы оплаты. */
2844
+ readonly subscription: SubscriptionResource;
2845
+ /** Сведения о платформе: изменения, анонсы, баннер события. */
2846
+ readonly platform: PlatformResource;
2847
+ /**
2848
+ * Телеметрия просмотров.
2849
+ *
2850
+ * @experimental Недокументированные эндпоинты. Библиотека никогда не отправляет их сама.
2851
+ */
2852
+ readonly telemetry: TelemetryResource;
2853
+ constructor(options?: ItdClientOptions);
2854
+ /** Базовый URL, к которому обращается клиент. */
2855
+ get baseUrl(): string;
2856
+ /**
2857
+ * Выполняет произвольный запрос к API.
2858
+ *
2859
+ * Запасной путь для случаев, когда нужного метода ещё нет или ответ сервера разошёлся
2860
+ * с документацией. Проходит через ту же авторизацию, очередь и обработку ошибок.
2861
+ *
2862
+ * @example
2863
+ * ```ts
2864
+ * const raw = await itd.request({ method: 'GET', path: '/api/posts', raw: true });
2865
+ * ```
2866
+ */
2867
+ request<T = unknown>(options: RawRequestOptions): Promise<T>;
2868
+ /**
2869
+ * Подписывается на события авторизации.
2870
+ *
2871
+ * Полезно, чтобы сохранять сессию во внешнее хранилище или узнавать, что вход
2872
+ * окончательно потерян.
2873
+ *
2874
+ * @returns функция отписки
2875
+ *
2876
+ * @example
2877
+ * ```ts
2878
+ * itd.on('tokens', ({ accessToken }) => cache.set('itd', accessToken));
2879
+ * itd.on('authError', () => notifyUser('Сессия истекла, войдите заново'));
2880
+ * ```
2881
+ */
2882
+ on<K extends keyof AuthEvents>(event: K, listener: Listener<AuthEvents[K]>): Unsubscribe;
2883
+ /**
2884
+ * Создаёт поток уведомлений в реальном времени.
2885
+ *
2886
+ * Каждый вызов даёт новый независимый поток; обычно он нужен один на приложение.
2887
+ * Соединение поднимается методом `connect()` и держится само.
2888
+ *
2889
+ * @example
2890
+ * ```ts
2891
+ * const stream = itd.realtime();
2892
+ *
2893
+ * stream.on('notification', ({ notification }) => {
2894
+ * console.log(formatNotificationText(notification));
2895
+ * });
2896
+ * stream.on('unreadCount', (count) => setBadge(count));
2897
+ *
2898
+ * await stream.connect();
2899
+ * ```
2900
+ */
2901
+ realtime(options?: RealtimeOptions): ItdRealtime;
2902
+ /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */
2903
+ getSession(): Promise<ItdSession | null>;
2904
+ /** Восстанавливает сохранённую сессию, включая cookie. */
2905
+ setSession(session: ItdSession): Promise<void>;
2906
+ /**
2907
+ * Подключает чтение файлов с диска.
2908
+ *
2909
+ * Вызывается из `itd-api/node`; напрямую обычно не нужно.
2910
+ *
2911
+ * @internal
2912
+ */
2913
+ setFileReader(readFile: FileReader): void;
2914
+ }
2915
+ /**
2916
+ * Создаёт клиент API итд.com.
2917
+ *
2918
+ * То же, что `new ItdClient(options)`, — для тех, кому привычнее фабрика.
2919
+ *
2920
+ * @example
2921
+ * ```ts
2922
+ * const itd = createClient({ auth: token });
2923
+ * ```
2924
+ */
2925
+ declare function createClient(options?: ItdClientOptions): ItdClient;
2926
+
2927
+ /** Бренд, по которому ошибки библиотеки распознаются надёжнее, чем через `instanceof`. */
2928
+ declare const ITD_ERROR: unique symbol;
2929
+ /** Категория ошибки. Определяет, какие поля у неё есть. */
2930
+ type ItdErrorKind = 'api' | 'network' | 'timeout' | 'abort' | 'config';
2931
+ /** Ошибки по полям формы: `{ email: ['уже занят'] }`. */
2932
+ type ItdFieldErrors = Record<string, string[]>;
2933
+ /**
2934
+ * Базовый класс всех ошибок библиотеки.
2935
+ *
2936
+ * Ловить его имеет смысл, чтобы отделить проблемы обращения к итд.com от прочих исключений.
2937
+ * Для разбора конкретной причины используйте {@link isItdApiError} и поле {@link ItdApiError.code}.
2938
+ *
2939
+ * @example
2940
+ * ```ts
2941
+ * try {
2942
+ * await itd.posts.like(id);
2943
+ * } catch (e) {
2944
+ * if (isItdError(e)) console.error('итд.com:', e.message);
2945
+ * else throw e;
2946
+ * }
2947
+ * ```
2948
+ */
2949
+ declare class ItdError extends Error {
2950
+ /** @internal */
2951
+ readonly [ITD_ERROR]: true;
2952
+ /** Категория ошибки. */
2953
+ readonly kind: ItdErrorKind;
2954
+ constructor(kind: ItdErrorKind, message: string, options?: {
2955
+ cause?: unknown;
2956
+ });
2957
+ }
2958
+ /** Параметры конструктора {@link ItdApiError}. */
2959
+ interface ItdApiErrorInit {
2960
+ /** HTTP-статус ответа. */
2961
+ status: number;
2962
+ /** Строковый код ошибки из тела ответа. */
2963
+ code: ItdErrorCode;
2964
+ /** Человекочитаемое сообщение. */
2965
+ message: string;
2966
+ /** Расширенное описание, если сервер его прислал. */
2967
+ detail?: string | undefined;
2968
+ /** Заголовок ошибки, если сервер его прислал. */
2969
+ title?: string | undefined;
2970
+ /** Ошибки по конкретным полям (сведены из `errors` и `violations`). */
2971
+ fieldErrors?: ItdFieldErrors | undefined;
2972
+ /** Идентификатор запроса из заголовков ответа, если есть. */
2973
+ requestId?: string | undefined;
2974
+ /** HTTP-метод запроса. */
2975
+ method: string;
2976
+ /** Путь запроса без базового URL. */
2977
+ path: string;
2978
+ /** Тело ответа как оно пришло — на случай, если документация разошлась с реальностью. */
2979
+ raw: unknown;
2980
+ /** Сам объект ответа. Тело уже прочитано. */
2981
+ response?: Response | undefined;
2982
+ /** Значение `Retry-After` в миллисекундах, если заголовок был. */
2983
+ retryAfter?: number | undefined;
2984
+ /** Сколько запросов разрешено в окне (`x-ratelimit-limit`). */
2985
+ rateLimit?: number | undefined;
2986
+ /** Сколько запросов осталось в окне (`x-ratelimit-remaining`). */
2987
+ rateLimitRemaining?: number | undefined;
2988
+ }
2989
+ /**
2990
+ * Ошибка, возвращённая сервером итд.com (HTTP-статус ≥ 400).
2991
+ *
2992
+ * API отдаёт ошибки в двух разных формах — `{ error: { … } }` и `{ code, message, violations }`.
2993
+ * Библиотека сводит обе к этому классу, поэтому разбирать форму ответа вручную не нужно.
2994
+ *
2995
+ * @example
2996
+ * ```ts
2997
+ * try {
2998
+ * await itd.users.updateMe({ username: 'занятое_имя' });
2999
+ * } catch (e) {
3000
+ * if (e instanceof ItdValidationError) {
3001
+ * console.log(e.fieldErrors.username); // ['Имя уже занято']
3002
+ * }
3003
+ * }
3004
+ * ```
3005
+ */
3006
+ declare class ItdApiError extends ItdError {
3007
+ /** HTTP-статус ответа. */
3008
+ readonly status: number;
3009
+ /** Строковый код ошибки, например `VALIDATION_ERROR`. */
3010
+ readonly code: ItdErrorCode;
3011
+ /** Расширенное описание, если сервер его прислал. */
3012
+ readonly detail: string | undefined;
3013
+ /** Заголовок ошибки, если сервер его прислал. */
3014
+ readonly title: string | undefined;
3015
+ /** Ошибки по полям. Пустой объект, если сервер их не прислал. */
3016
+ readonly fieldErrors: ItdFieldErrors;
3017
+ /** Идентификатор запроса из заголовков ответа. */
3018
+ readonly requestId: string | undefined;
3019
+ /** HTTP-метод запроса. */
3020
+ readonly method: string;
3021
+ /** Путь запроса без базового URL. */
3022
+ readonly path: string;
3023
+ /** Тело ответа как оно пришло. */
3024
+ readonly raw: unknown;
3025
+ /** Объект ответа. Тело уже прочитано и повторно прочитано быть не может. */
3026
+ readonly response: Response | undefined;
3027
+ /** Пауза из заголовка `Retry-After` в миллисекундах. Сервер итд.com его не присылает. */
3028
+ readonly retryAfter: number | undefined;
3029
+ /**
3030
+ * Сколько запросов разрешено в окне — заголовок `x-ratelimit-limit`.
3031
+ *
3032
+ * Времени сброса окна сервер не сообщает, поэтому точный момент повтора неизвестен.
3033
+ */
3034
+ readonly rateLimit: number | undefined;
3035
+ /** Сколько запросов осталось в окне — заголовок `x-ratelimit-remaining`. */
3036
+ readonly rateLimitRemaining: number | undefined;
3037
+ constructor(init: ItdApiErrorInit);
3038
+ /**
3039
+ * Проверяет код ошибки. Удобнее, чем сравнивать строки вручную.
3040
+ *
3041
+ * @example
3042
+ * ```ts
3043
+ * if (err.hasCode('OTP_INVALID', 'MISSING_FLOW_TOKEN')) await restartOtpFlow();
3044
+ * ```
3045
+ */
3046
+ hasCode(...codes: ItdErrorCode[]): boolean;
3047
+ /** Имеет ли смысл повторить запрос: `429` и серверные ошибки `5xx`. */
3048
+ get isRetryable(): boolean;
3049
+ }
3050
+ /** `400` / `422` — данные не прошли валидацию. Подробности в {@link ItdApiError.fieldErrors}. */
3051
+ declare class ItdValidationError extends ItdApiError {
3052
+ constructor(init: ItdApiErrorInit);
3053
+ }
3054
+ /** `401` — токен отсутствует, истёк или отозван. */
3055
+ declare class ItdAuthError extends ItdApiError {
3056
+ constructor(init: ItdApiErrorInit);
3057
+ }
3058
+ /** `403` — доступ запрещён либо действие ограничено настройками приватности. */
3059
+ declare class ItdForbiddenError extends ItdApiError {
3060
+ constructor(init: ItdApiErrorInit);
3061
+ }
3062
+ /** `404` — сущность не найдена. */
3063
+ declare class ItdNotFoundError extends ItdApiError {
3064
+ constructor(init: ItdApiErrorInit);
3065
+ }
3066
+ /** `409` — сущность уже существует. */
3067
+ declare class ItdConflictError extends ItdApiError {
3068
+ constructor(init: ItdApiErrorInit);
3069
+ }
3070
+ /**
3071
+ * `429` — превышен лимит запросов.
3072
+ *
3073
+ * Если сервер прислал `Retry-After`, пауза доступна в {@link ItdApiError.retryAfter}
3074
+ * (в миллисекундах). При включённых ретраях библиотека выдерживает её автоматически.
3075
+ */
3076
+ declare class ItdRateLimitError extends ItdApiError {
3077
+ constructor(init: ItdApiErrorInit);
3078
+ }
3079
+ /**
3080
+ * Действие требует подтверждённого телефона (`PHONE_VERIFICATION_REQUIRED`).
3081
+ *
3082
+ * Подтверждение проходит через Telegram-бота: ссылка лежит в {@link verificationUrl}.
3083
+ */
3084
+ declare class ItdPhoneVerificationError extends ItdApiError {
3085
+ /** Ссылка на бота подтверждения, если удалось определить идентификатор пользователя. */
3086
+ readonly verificationUrl: string | undefined;
3087
+ constructor(init: ItdApiErrorInit & {
3088
+ userId?: string | undefined;
3089
+ });
3090
+ }
3091
+ /** `5xx` — ошибка на стороне сервера. */
3092
+ declare class ItdServerError extends ItdApiError {
3093
+ constructor(init: ItdApiErrorInit);
3094
+ }
3095
+ /** Запрос не дошёл до сервера: DNS, обрыв соединения, отсутствие сети. */
3096
+ declare class ItdNetworkError extends ItdError {
3097
+ /** HTTP-метод запроса. */
3098
+ readonly method: string;
3099
+ /** Путь запроса без базового URL. */
3100
+ readonly path: string;
3101
+ constructor(message: string, init: {
3102
+ method: string;
3103
+ path: string;
3104
+ cause?: unknown;
3105
+ });
3106
+ }
3107
+ /** Истёк таймаут запроса, заданный опцией `timeout`. */
3108
+ declare class ItdTimeoutError extends ItdError {
3109
+ /** Значение таймаута в миллисекундах. */
3110
+ readonly timeout: number;
3111
+ /** HTTP-метод запроса. */
3112
+ readonly method: string;
3113
+ /** Путь запроса без базового URL. */
3114
+ readonly path: string;
3115
+ constructor(init: {
3116
+ timeout: number;
3117
+ method: string;
3118
+ path: string;
3119
+ });
3120
+ }
3121
+ /** Запрос отменён через переданный `AbortSignal`. */
3122
+ declare class ItdAbortError extends ItdError {
3123
+ constructor(message?: string);
3124
+ }
3125
+ /**
3126
+ * Некорректная конфигурация или аргументы — обнаружено до обращения к сети.
3127
+ *
3128
+ * Этим же классом сообщают о нарушенных инвариантах билдеры: например, опрос
3129
+ * с одним вариантом ответа.
3130
+ */
3131
+ declare class ItdConfigError extends ItdError {
3132
+ constructor(message: string);
3133
+ }
3134
+ /** Любая ошибка, порождённая этой библиотекой. */
3135
+ declare function isItdError(value: unknown): value is ItdError;
3136
+ /** Ошибка, пришедшая от сервера итд.com (статус ≥ 400). */
3137
+ declare function isItdApiError(value: unknown): value is ItdApiError;
3138
+ /** Ошибка валидации: `VALIDATION_ERROR` либо статус `400`/`422`. */
3139
+ declare function isItdValidationError(value: unknown): value is ItdValidationError;
3140
+ /** Ошибка авторизации: истёкший или отозванный токен. */
3141
+ declare function isItdAuthError(value: unknown): value is ItdAuthError;
3142
+ /** Превышен лимит запросов. */
3143
+ declare function isItdRateLimitError(value: unknown): value is ItdRateLimitError;
3144
+
3145
+ /** Изображения, которые принимает `POST /api/files/upload`. */
3146
+ declare const IMAGE_MIME_TYPES: readonly string[];
3147
+ /** Видео, которые принимает `POST /api/files/upload`. */
3148
+ declare const VIDEO_MIME_TYPES: readonly string[];
3149
+ /** Аудио для голосовых комментариев. */
3150
+ declare const AUDIO_MIME_TYPES: readonly string[];
3151
+ /** Все типы, которые принимает загрузка. */
3152
+ declare const ALLOWED_MIME_TYPES: readonly string[];
3153
+
3154
+ /**
3155
+ * Собирает текст уведомления на русском.
3156
+ *
3157
+ * Повторяет формулировки сайта итд.com. Для неизвестного типа возвращает
3158
+ * «Новое уведомление» — библиотека не выдумывает текст, которого нет.
3159
+ *
3160
+ * @example
3161
+ * ```ts
3162
+ * formatNotificationText(notification);
3163
+ * // 'Аня и ещё 2 оценили ваш пост'
3164
+ * ```
3165
+ */
3166
+ declare function formatNotificationText(notification: Notification): string;
3167
+
3168
+ /**
3169
+ * Соответствие коротких имён типов уведомлений развёрнутым.
3170
+ *
3171
+ * Сервер — и в списке, и в потоке событий — присылает короткие имена: `like`, `comment`,
3172
+ * `reply`, `repost`, `comment_like`. Развёрнутые (`post_reaction`, `post_comment`)
3173
+ * встречаются в оформлении интерфейса, поэтому библиотека приводит типы к ним:
3174
+ * они однозначно называют и объект, и действие.
3175
+ *
3176
+ * Пришедшее значение всегда остаётся в поле `rawType`.
3177
+ */
3178
+ declare const NOTIFICATION_TYPE_ALIASES: Readonly<Record<string, NotificationType>>;
3179
+ /**
3180
+ * Приводит имя типа к каноническому.
3181
+ *
3182
+ * Неизвестное значение возвращается без изменений. Официальный клиент в этом случае
3183
+ * подставляет `follow`, из-за чего новое уведомление выглядит как подписка, — здесь
3184
+ * такого не происходит.
3185
+ *
3186
+ * @example
3187
+ * ```ts
3188
+ * canonicalNotificationType('like'); // 'post_reaction'
3189
+ * canonicalNotificationType('post_reaction'); // 'post_reaction'
3190
+ * canonicalNotificationType('новое_событие'); // 'новое_событие'
3191
+ * ```
3192
+ */
3193
+ declare function canonicalNotificationType(rawType: string): NotificationType;
3194
+ /**
3195
+ * Известен ли библиотеке этот тип уведомления.
3196
+ *
3197
+ * Полезно, чтобы решить, показывать ли уведомление, для которого нет своего оформления.
3198
+ */
3199
+ declare function isKnownNotificationType(type: string): boolean;
3200
+
3201
+ /**
3202
+ * Вычисляет адрес, на который ведёт уведомление.
3203
+ *
3204
+ * Возвращает путь внутри сайта — без домена, чтобы его можно было передать роутеру
3205
+ * приложения. Поле `clickUrl` от сервера используется только как запасной вариант:
3206
+ * вычисленный путь точнее, поскольку учитывает родительский пост у комментариев.
3207
+ *
3208
+ * @example
3209
+ * ```ts
3210
+ * const url = resolveNotificationUrl(notification);
3211
+ * // '/@durov/post/9f1c…?comment=2b7e…'
3212
+ * ```
3213
+ */
3214
+ declare function resolveNotificationUrl(notification: Notification): string;
3215
+
3216
+ /** Настройки опроса. */
3217
+ interface PollTransportOptions {
3218
+ /** Как часто опрашивать сервер, мс. По умолчанию 15 000. */
3219
+ interval?: number;
3220
+ /** Сколько уведомлений запрашивать за раз. По умолчанию 20. */
3221
+ limit?: number;
3222
+ }
3223
+
3224
+ /** Путь потока уведомлений. */
3225
+ declare const STREAM_PATH = "/api/notifications/stream";
3226
+ /** Настройки SSE-транспорта. */
3227
+ interface SseTransportOptions {
3228
+ /**
3229
+ * Сколько миллисекунд ждать данных, прежде чем считать соединение мёртвым.
3230
+ *
3231
+ * Сервер не присылает keep-alive, а оборванное TCP-соединение может не закрыться
3232
+ * само — без этой проверки поток «тихо умирает» и новых уведомлений не приходит.
3233
+ * По умолчанию 90 000. `0` отключает проверку.
3234
+ */
3235
+ idleTimeout?: number;
3236
+ }
3237
+
3238
+ export { ItdError as $, ALLOWED_MIME_TYPES as A, type BuilderInput as B, type ChangelogEntry as C, DEFAULT_BASE_URL as D, type DwellEntry as E, type FileReader as F, type ErrorContextHook as G, type FeedParams as H, type ItdSession as I, FeedTab as J, type FileInput as K, FilesResource as L, type FollowResult as M, type Hashtag as N, type HashtagPostsParams as O, HashtagsResource as P, IMAGE_MIME_TYPES as Q, type InteractionEntry as R, type IsoDate as S, type TokenStorage as T, ItdAbortError as U, ItdApiError as V, type ItdApiErrorInit as W, ItdAuthError as X, type ItdBuilder as Y, ItdConfigError as Z, ItdConflictError as _, ItdClient as a, type Report as a$, ItdErrorCode as a0, type ItdErrorKind as a1, type ItdFieldErrors as a2, ItdForbiddenError as a3, ItdNetworkError as a4, ItdNotFoundError as a5, ItdPhoneVerificationError as a6, ItdRateLimitError as a7, ItdRealtime as a8, ItdServerError as a9, type PinsResult as aA, PlatformResource as aB, type Poll as aC, PollBuilder as aD, type PollInput as aE, type PollOption as aF, type PollTransportOptions as aG, type Portal as aH, type Post as aI, PostBuilder as aJ, type PostInput as aK, type PostStats as aL, PostsResource as aM, type PrivacySettings as aN, type Profile as aO, type PublicProfile as aP, RECONNECT_BACKOFF as aQ, RECONNECT_JITTER as aR, type RateLimitOptions as aS, type RawRequestOptions as aT, type RealtimeEvents as aU, type RealtimeOptions as aV, RealtimeStatus as aW, type RealtimeTransport as aX, type RealtimeTransportKind as aY, type ReconnectOptions as aZ, type RepliesParams as a_, ItdTimeoutError as aa, ItdValidationError as ab, type LikeResult as ac, LikesVisibility as ad, type Listener as ae, LocalStorageTokenStorage as af, type Logger as ag, type Loose as ah, MAX_RECONNECT_ATTEMPTS as ai, MemoryTokenStorage as aj, type MyProfile as ak, NOTIFICATION_TYPE_ALIASES as al, type Notification as am, type NotificationEvent as an, type NotificationListParams as ao, type NotificationSettings as ap, NotificationType as aq, NotificationsResource as ar, type OAuthProvider as as, type Page as at, type PageState as au, type PaginationMode as av, Paginator as aw, type PaymentMethod as ax, type Pin as ay, type PinPostResult as az, type ItdClientOptions as b, ReportBuilder as b0, type ReportInput as b1, ReportReason as b2, ReportTargetType as b3, ReportsResource as b4, type RequestContext as b5, type RequestOptions as b6, type ResponseContext as b7, type RetryContext as b8, type RetryOptions as b9, VIDEO_MIME_TYPES as bA, VerificationResource as bB, type VerificationStatus as bC, WallAccess as bD, canonicalNotificationType as bE, comment as bF, createTokenStorage as bG, formatNotificationText as bH, isBuilder as bI, isItdApiError as bJ, isItdAuthError as bK, isItdError as bL, isItdRateLimitError as bM, isItdValidationError as bN, isKnownNotificationType as bO, isMyProfile as bP, normalizeNotification as bQ, poll as bR, post as bS, readNotificationEvent as bT, readUnreadCountEvent as bU, report as bV, resolveNotificationUrl as bW, toDate as bX, createClient as bY, type RuntimeMode as ba, STREAM_PATH as bb, SearchResource as bc, type SearchResult as bd, type Session as be, type SignInResult as bf, type Span as bg, type SseTransportOptions as bh, type Subscription as bi, SubscriptionResource as bj, type SubscriptionState as bk, TelemetryResource as bl, type TransportContext as bm, type TransportEvent as bn, type Unsubscribe as bo, type UpdateNotificationSettingsInput as bp, type UpdatePrivacyInput as bq, type UpdateProfileInput as br, type UploadOptions as bs, type UploadedFile as bt, type UserId as bu, type UserListParams as bv, type UserPostsParams as bw, type UserRef as bx, type UserSummary as by, UsersResource as bz, AUDIO_MIME_TYPES as c, type Actor as d, type Announcement as e, type AnnouncementButton as f, type Attachment as g, AttachmentType as h, type AuthInput as i, AuthResource as j, type Author as k, type Clan as l, type ClientHooks as m, type Comment as n, CommentBuilder as o, type CommentInput as p, type CommentReplyTo as q, CommentSort as r, type CommentsParams as s, CommentsResource as t, type CreateCommentInput as u, type CreatePollInput as v, type CreatePostInput as w, type CreateReportInput as x, type Credentials as y, DEFAULT_TIMEOUT as z };