itd-api 0.0.8 → 0.0.10

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.
Files changed (38) hide show
  1. package/README.md +155 -253
  2. package/dist/{chunk-ATCZ4T2K.cjs → chunk-BN4AC3DP.cjs} +3303 -1628
  3. package/dist/chunk-BN4AC3DP.cjs.map +1 -0
  4. package/dist/{chunk-JIYN33FG.js → chunk-SMV7TF5P.js} +3283 -1629
  5. package/dist/chunk-SMV7TF5P.js.map +1 -0
  6. package/dist/{index-Duh31Wnx.d.cts → index-CSjDNGCE.d.cts} +1298 -410
  7. package/dist/{index-Duh31Wnx.d.ts → index-CSjDNGCE.d.ts} +1298 -410
  8. package/dist/index.cjs +169 -89
  9. package/dist/index.d.cts +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/node.cjs +266 -124
  13. package/dist/node.cjs.map +1 -1
  14. package/dist/node.d.cts +42 -4
  15. package/dist/node.d.ts +42 -4
  16. package/dist/node.js +106 -39
  17. package/dist/node.js.map +1 -1
  18. package/guides/README.md +22 -0
  19. package/guides/authentication/README.md +176 -0
  20. package/guides/authentication/examples/bot-with-session.mjs +98 -0
  21. package/guides/authentication/examples/turnstile-login.mjs +56 -0
  22. package/guides/integrations/README.md +62 -0
  23. package/guides/integrations/examples/proxy.mjs +26 -0
  24. package/guides/multi-accounts/README.md +142 -0
  25. package/guides/multi-accounts/examples/multi-accounts.mjs +71 -0
  26. package/guides/plugins/README.md +162 -0
  27. package/guides/plugins/examples/cache.mjs +33 -0
  28. package/guides/plugins/examples/crypto.mjs +54 -0
  29. package/guides/quickstart/README.md +124 -0
  30. package/guides/quickstart/examples/quick-start.mjs +44 -0
  31. package/guides/quickstart/examples/typescript.ts +90 -0
  32. package/guides/realtime/README.md +109 -0
  33. package/guides/realtime/examples/notifications.mjs +62 -0
  34. package/guides/text-markup/README.md +214 -0
  35. package/guides/text-markup/examples/create-post.mjs +64 -0
  36. package/package.json +6 -4
  37. package/dist/chunk-ATCZ4T2K.cjs.map +0 -1
  38. package/dist/chunk-JIYN33FG.js.map +0 -1
@@ -1,37 +1,3 @@
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
1
  /**
36
2
  * Перечисления API итд.com.
37
3
  *
@@ -155,6 +121,24 @@ declare const RealtimeStatus: Readonly<{
155
121
  readonly Disconnected: "disconnected";
156
122
  }>;
157
123
  type RealtimeStatus = (typeof RealtimeStatus)[keyof typeof RealtimeStatus];
124
+ /** Состояние сервиса платформы. Тип открытый. */
125
+ declare const ServiceState: Readonly<{
126
+ /** Работает штатно. */
127
+ readonly Operational: "operational";
128
+ /** Работает с деградацией. */
129
+ readonly Degraded: "degraded";
130
+ /** Недоступен. */
131
+ readonly Downtime: "downtime";
132
+ }>;
133
+ type ServiceState = Loose<(typeof ServiceState)[keyof typeof ServiceState]>;
134
+ /** Вид происшествия в истории сервиса. Тип открытый. */
135
+ declare const IncidentKind: Readonly<{
136
+ /** Недоступен. */
137
+ readonly Down: "down";
138
+ /** Деградация. */
139
+ readonly Degraded: "deg";
140
+ }>;
141
+ type IncidentKind = Loose<(typeof IncidentKind)[keyof typeof IncidentKind]>;
158
142
  /**
159
143
  * Уровень доступа к разделу профиля.
160
144
  *
@@ -361,13 +345,11 @@ type UserId = string;
361
345
  */
362
346
  type UserRef = string;
363
347
  /**
364
- * Разметка в тексте поста.
348
+ * Разметка в тексте поста или комментария.
365
349
  *
366
- * Приходит от сервера и отправляется обратно как есть библиотека разметку не генерирует
367
- * и не пересчитывает.
368
- *
369
- * Единицы `offset` и `length` в документации API не уточнены (UTF-16 или кодовые точки),
370
- * поэтому при работе с эмодзи проверяйте результат.
350
+ * `offset` и `length` измеряются в UTF-16 code units: это те же индексы, которые используют
351
+ * `String#slice`, `substring` и DOM Selection в JavaScript. Эмодзи вне BMP обычно занимают
352
+ * две единицы.
371
353
  */
372
354
  interface Span {
373
355
  /** Тип фрагмента — см. {@link SpanType}. */
@@ -376,10 +358,14 @@ interface Span {
376
358
  offset: number;
377
359
  /** Длина фрагмента. */
378
360
  length: number;
379
- /** Имя хэштега без решётки либо имя пользователя. */
361
+ /** Имя хэштега без решётки. У старых mention-объектов может содержать username. */
380
362
  tag?: string;
381
363
  /** Адрес ссылки. Только у `link`: у него вместо `tag` отдельное поле. */
382
364
  url?: string;
365
+ /** Имя пользователя у `mention`. */
366
+ username?: string;
367
+ /** Идентификатор пользователя у некоторых ответов API с `mention`. */
368
+ id?: string;
383
369
  }
384
370
  /**
385
371
  * Значок-«пин» в профиле — награда или отметка платформы.
@@ -628,6 +614,14 @@ interface Comment {
628
614
  id: string;
629
615
  /** Текст. У голосового комментария пустой. */
630
616
  content: string;
617
+ /**
618
+ * Разметка текста, включая автоматически найденные сервером хэштеги и упоминания.
619
+ *
620
+ * Скачанный клиент читает это поле, но comment endpoints принимают только `content`,
621
+ * поэтому библиотека не отправляет ручные spans при создании и редактировании комментария.
622
+ * Поле необязательно: отдельные ответы сервера могут его не содержать.
623
+ */
624
+ spans?: Span[];
631
625
  author: Author;
632
626
  likesCount: number;
633
627
  repliesCount: number;
@@ -815,6 +809,61 @@ interface Portal {
815
809
  title: string;
816
810
  url: string;
817
811
  }
812
+ /** Происшествие в истории сервиса. */
813
+ interface StatusIncidentLine {
814
+ /** Вид происшествия. */
815
+ t: IncidentKind;
816
+ /**
817
+ * Готовая строка для показа: `недоступен 6 мин (12:00–12:06)`. Время московское.
818
+ * Длительность и границы интервала отдельными полями не приходят.
819
+ */
820
+ text: string;
821
+ }
822
+ /** Одни сутки в истории сервиса. */
823
+ interface StatusDay {
824
+ /** Худшее состояние за сутки. */
825
+ type: ServiceState;
826
+ /** Дата суток, `YYYY-MM-DD`. Сутки нарезаны по UTC. */
827
+ date_key: string;
828
+ /** Доступность за сутки в процентах. */
829
+ uptime: number;
830
+ /** Происшествия за сутки. */
831
+ lines: StatusIncidentLine[];
832
+ }
833
+ /** Сервис платформы и его история доступности. */
834
+ interface ServiceStatus {
835
+ /** Идентификатор: `auth`, `main`, `media` и прочие. */
836
+ id: string;
837
+ /** Отображаемое название. */
838
+ name: string;
839
+ current_status: ServiceState;
840
+ /** Пояснение к текущему состоянию, например `No downtime`. */
841
+ current_message: string;
842
+ /** Задержка последней проверки в миллисекундах. */
843
+ latency_ms: number;
844
+ /**
845
+ * Момент последней проверки. Сервер отдаёт `YYYY-MM-DD HH:mm:ss` в UTC, библиотека
846
+ * приводит значение к ISO.
847
+ */
848
+ last_checked: IsoDate;
849
+ /** Доступность за 90 суток в процентах. */
850
+ uptime_90d: number;
851
+ /**
852
+ * История по суткам. Ключ — сколько суток назад, `'0'` — сегодня.
853
+ *
854
+ * Объект разреженный: сутки без данных сервер пропускает. Ровный массив даёт
855
+ * {@link statusDays}.
856
+ */
857
+ days: Record<string, StatusDay | undefined>;
858
+ }
859
+ /** Состояние платформы — ответ `itd.platform.status()`. */
860
+ interface PlatformStatus {
861
+ /** Худшее состояние среди сервисов. */
862
+ overall_status: ServiceState;
863
+ /** Когда данные последний раз пересчитаны. */
864
+ updated_at: IsoDate;
865
+ services: ServiceStatus[];
866
+ }
818
867
  /** Статус заявки на верификацию. `none` означает, что заявка не подавалась. */
819
868
  interface VerificationStatus {
820
869
  status: Loose<'none' | 'pending' | 'approved' | 'rejected'>;
@@ -861,380 +910,137 @@ interface PinsResult {
861
910
  * ```
862
911
  */
863
912
  declare function toDate(value: IsoDate | null | undefined): Date | null;
864
-
865
- /**
866
- * Билдер опроса.
867
- *
868
- * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно
869
- * переиспользовать, не боясь её испортить. Создаётся функцией {@link poll}.
870
- */
871
- declare class PollBuilder implements ItdBuilder<CreatePollInput> {
872
- #private;
873
- /** @internal */
874
- readonly [BUILDER]: true;
875
- /** @internal Создавайте билдер функцией {@link poll}. */
876
- constructor(state: CreatePollInput);
877
- /** Задаёт вопрос. */
878
- question(text: string): PollBuilder;
879
- /** Добавляет один вариант ответа. */
880
- option(text: string): PollBuilder;
881
- /**
882
- * Добавляет несколько вариантов сразу.
883
- *
884
- * @example
885
- * ```ts
886
- * poll('ну как?').options('да', 'нет', 'не знаю');
887
- * ```
888
- */
889
- options(...texts: string[]): PollBuilder;
890
- /** Разрешает выбор нескольких вариантов. */
891
- multipleChoice(enabled?: boolean): PollBuilder;
892
- build(): CreatePollInput;
893
- toJSON(): CreatePollInput;
894
- }
895
913
  /**
896
- * Начинает сборку опроса.
914
+ * Разворачивает историю сервиса в массив на 90 суток.
915
+ * Сутки без данных становятся `null`.
897
916
  *
898
- * @param question вопрос; можно задать позже методом {@link PollBuilder.question}
917
+ * @returns массив, где индекс сколько суток назад: `[0]` — сегодня
899
918
  *
900
919
  * @example
901
920
  * ```ts
902
- * import { poll } from 'itd-api';
903
- *
904
- * const q = poll('Какой язык лучше?')
905
- * .options('TypeScript', 'JavaScript')
906
- * .multipleChoice();
921
+ * const status = await itd.platform.status();
922
+ * const days = statusDays(status.services[0]);
907
923
  *
908
- * await itd.posts.create({ content: 'голосуем', poll: q });
924
+ * days[0]?.uptime; // доступность за сегодня
925
+ * days.filter((day) => day === null).length; // за сколько суток данных нет
909
926
  * ```
910
927
  */
911
- declare function poll(question?: string): PollBuilder;
912
- /** Что принимает параметр опроса: объект, билдер или функция-настройщик. */
913
- type PollInput = BuilderInput<CreatePollInput, PollBuilder>;
928
+ declare function statusDays(service: ServiceStatus): (StatusDay | null)[];
914
929
 
915
930
  /**
916
- * Файл для загрузки.
931
+ * Как библиотека обращается с cookie.
917
932
  *
918
- * Строка означает **путь на диске** и работает только в Node, Bun и Deno — для этого
919
- * подключите `itd-api/node`. В браузере и React Native передавайте `File` или `Blob`.
933
+ * - `browser` cookie ведёт браузер, запросы уходят с `credentials: 'include'`;
934
+ * - `server` cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную;
935
+ * - `auto` — определяется по среде исполнения (значение по умолчанию).
920
936
  */
921
- type FileInput = Blob | ArrayBuffer | Uint8Array | string | {
922
- /** Содержимое файла. */
923
- data: Blob | ArrayBuffer | Uint8Array;
924
- /** Имя файла. Влияет на определение типа, если `contentType` не задан. */
925
- filename?: string;
926
- /** MIME-тип. Если не указан, определяется по расширению или по самому `Blob`. */
927
- contentType?: string;
928
- };
929
- /** Данные для создания опроса. */
930
- interface CreatePollInput {
931
- /** Вопрос. Не может быть пустым. */
932
- question: string;
933
- /** Варианты ответа. Требуется минимум два. */
934
- options: {
935
- text: string;
936
- }[];
937
- /** Разрешить выбор нескольких вариантов. По умолчанию `false`. */
938
- multipleChoice?: boolean;
939
- }
940
- /** Данные для создания поста. */
941
- interface CreatePostInput {
942
- /** Текст поста. */
943
- content?: string;
944
- /** Разметка текста. Передаётся серверу без изменений — библиотека её не генерирует. */
945
- spans?: Span[];
937
+ declare const RuntimeMode: Readonly<{
938
+ /** Определяется по среде исполнения. Значение по умолчанию. */
939
+ readonly Auto: "auto";
940
+ /** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */
941
+ readonly Browser: "browser";
942
+ /** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */
943
+ readonly Server: "server";
944
+ }>;
945
+ type RuntimeMode = (typeof RuntimeMode)[keyof typeof RuntimeMode];
946
+ /** Распознанная среда исполнения. */
947
+ declare const DetectedRuntime: Readonly<{
948
+ readonly Browser: "browser";
949
+ /** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */
950
+ readonly ReactNative: "react-native";
951
+ readonly Server: "server";
952
+ }>;
953
+ type DetectedRuntime = (typeof DetectedRuntime)[keyof typeof DetectedRuntime];
954
+
955
+ /** Сервис платформы на отдельном домене. */
956
+ interface ServiceDefinition {
957
+ /** Имя, по которому запрос выбирает сервис: `{ service: 'status' }`. */
958
+ name: string;
959
+ /** Базовый URL сервиса. */
960
+ baseUrl: string;
961
+ /** Заголовки, добавляемые к каждому запросу сервиса. Заголовки вызова важнее. */
962
+ headers?: Record<string, string> | undefined;
946
963
  /**
947
- * Чья стена, если пост публикуется не у себя.
964
+ * Слать ли заголовок авторизации.
948
965
  *
949
- * Требуется **UUID**: имя пользователя здесь не работает, его можно получить
950
- * из профиля через `itd.users.get(username)`.
966
+ * По умолчанию включено для основного хоста и его поддоменов. Для остальных хостов
967
+ * авторизацию нужно разрешить явно: `auth: true`.
951
968
  */
952
- wallRecipientId?: UserId | null;
953
- /** Идентификаторы заранее загруженных вложений. */
954
- attachmentIds?: string[];
955
- /** Файлы, которые нужно загрузить перед публикацией. Порядок сохраняется. */
956
- files?: FileInput[];
957
- /** Опрос: обычный объект, {@link PollBuilder} или функция-настройщик. */
958
- poll?: PollInput;
969
+ auth?: boolean | undefined;
959
970
  }
960
- /** Данные для создания комментария или ответа. */
961
- interface CreateCommentInput {
962
- /** Текст. У голосового комментария должен быть пустым. */
963
- content?: string;
964
- /** Идентификаторы заранее загруженных вложений. */
965
- attachmentIds?: string[];
966
- /** Файлы, которые нужно загрузить перед отправкой. */
967
- files?: FileInput[];
971
+ /** Именованные сервисы клиента. */
972
+ declare class ServiceRegistry {
973
+ #private;
974
+ /** @param primaryBaseUrl базовый URL клиента */
975
+ constructor(primaryBaseUrl?: string);
968
976
  /**
969
- * Кому адресован ответ.
977
+ * Регистрирует сервис. Имя очищается от краевых пробелов, базовый URL приводится
978
+ * к каноничному виду, а незаданный `auth` выводится из хоста.
970
979
  *
971
- * Применимо только в `itd.comments.reply()`; в комментарии к посту поле не имеет смысла.
980
+ * @throws {ItdConfigError} если имя пустое, имя занято или `baseUrl` не абсолютный URL
972
981
  */
973
- replyToUserId?: UserId;
974
- }
975
- /** Данные для создания жалобы. */
976
- interface CreateReportInput {
977
- /** На что жалоба. */
978
- targetType: ReportTargetType;
979
- /** Идентификатор объекта жалобы. */
980
- targetId: string;
981
- /** Причина. */
982
- reason: ReportReason;
983
- /** Пояснение в свободной форме. */
984
- description?: string;
982
+ define(definition: ServiceDefinition): void;
983
+ /** Определение сервиса либо `undefined`, если такого нет. */
984
+ get(name: string): ServiceDefinition | undefined;
985
+ /** Зарегистрирован ли сервис с таким именем. */
986
+ has(name: string): boolean;
987
+ /**
988
+ * Определение сервиса.
989
+ *
990
+ * @throws {ItdConfigError} если сервис не зарегистрирован
991
+ */
992
+ require(name: string): ServiceDefinition;
993
+ /**
994
+ * Базовый URL сервиса.
995
+ *
996
+ * @throws {ItdConfigError} если сервис не зарегистрирован
997
+ */
998
+ resolveBaseUrl(name: string): string;
999
+ /**
1000
+ * Принадлежит ли URL основному хосту клиента или его поддомену.
1001
+ *
1002
+ * Используется для безопасного значения по умолчанию у разового `baseUrl`: Bearer-токен
1003
+ * не должен уходить на посторонний хост без явного `skipAuth: false`.
1004
+ *
1005
+ * @internal
1006
+ */
1007
+ isPrimarySite(baseUrl: string): boolean;
985
1008
  }
986
1009
 
987
- /** Внутреннее состояние {@link CommentBuilder}. */
988
- interface CommentState extends CreateCommentInput {
989
- content: string;
990
- attachmentIds: string[];
991
- files: FileInput[];
992
- /** Голосовой комментарий: текста быть не должно, вложение ровно одно. */
993
- voice: boolean;
994
- }
995
1010
  /**
996
- * Билдер комментария и ответа на комментарий.
1011
+ * Сохранённая сессия.
997
1012
  *
998
- * Неизменяемый: каждый вызов возвращает новый экземпляр. Создаётся функцией {@link comment}.
1013
+ * Кроме токенов сюда попадают cookie: refresh-токен итд.com живёт именно в cookie, и без них
1014
+ * восстановить сессию после перезапуска процесса невозможно.
999
1015
  */
1000
- declare class CommentBuilder implements ItdBuilder<CreateCommentInput> {
1001
- #private;
1002
- /** @internal */
1003
- readonly [BUILDER]: true;
1004
- /** @internal Создавайте билдер функцией {@link comment}. */
1005
- constructor(state: CommentState);
1006
- /** Задаёт текст комментария. */
1007
- content(text: string): CommentBuilder;
1008
- /** Прикладывает файл — он будет загружен перед отправкой. */
1009
- attach(file: FileInput): CommentBuilder;
1010
- /** Прикладывает уже загруженное вложение. */
1011
- attachId(attachmentId: string): CommentBuilder;
1016
+ interface ItdSession {
1017
+ /** Токен доступа для заголовка `Authorization: Bearer`. */
1018
+ accessToken?: string | undefined;
1012
1019
  /**
1013
- * Делает комментарий голосовым.
1014
- *
1015
- * Текста у такого комментария быть не должно, а вложение ровно одно — аудио в формате
1016
- * `audio/ogg`. Так его принимает API.
1020
+ * Refresh-токен, если удалось получить его явным значением.
1017
1021
  *
1018
- * @example
1019
- * ```ts
1020
- * await itd.posts.comment(postId, (c) => c.voice('./answer.ogg'));
1021
- * ```
1022
+ * Обычно сервер держит его в httpOnly-cookie и наружу не отдаёт — тогда поле останется
1023
+ * пустым, а обновление пойдёт через {@link ItdSession.cookies}.
1022
1024
  */
1023
- voice(audio: FileInput): CommentBuilder;
1025
+ refreshToken?: string | undefined;
1026
+ /** Сырые cookie в форме `имя=значение`, привязанные к origin API. */
1027
+ cookies?: string[] | undefined;
1024
1028
  /**
1025
- * Кому адресован ответ.
1029
+ * Идентификатор устройства для заголовка `X-Device-Id`.
1026
1030
  *
1027
- * Имеет смысл только в `itd.comments.reply()`; при отправке комментария к посту
1028
- * это поле вызовет ошибку.
1031
+ * Сервер различает по нему записи в списке сессий, поэтому значение должно пережить
1032
+ * перезапуск процесса иначе каждый старт бота порождает новую сессию. Библиотека
1033
+ * заводит его сама при первом запросе и хранит здесь.
1029
1034
  */
1030
- replyTo(userId: UserId): CommentBuilder;
1031
- build(): CreateCommentInput;
1032
- toJSON(): CreateCommentInput;
1035
+ deviceId?: string | undefined;
1036
+ /** Когда сессия получена, мс с начала эпохи. Нужно для диагностики. */
1037
+ obtainedAt?: number | undefined;
1033
1038
  }
1034
1039
  /**
1035
- * Начинает сборку комментария.
1040
+ * Хранилище сессии.
1036
1041
  *
1037
- * @param content текст; можно задать позже методом {@link CommentBuilder.content}
1038
- *
1039
- * @example
1040
- * ```ts
1041
- * import { comment } from 'itd-api';
1042
- *
1043
- * await itd.posts.comment(postId, comment('согласен').attach('./meme.png'));
1044
- * ```
1045
- */
1046
- declare function comment(content?: string): CommentBuilder;
1047
- /** Что принимает параметр комментария: объект, билдер или функция-настройщик. */
1048
- type CommentInput = BuilderInput<CreateCommentInput, CommentBuilder>;
1049
-
1050
- /** Внутреннее состояние {@link PostBuilder}. */
1051
- interface PostState extends CreatePostInput {
1052
- content: string;
1053
- attachmentIds: string[];
1054
- files: FileInput[];
1055
- }
1056
- /**
1057
- * Билдер поста.
1058
- *
1059
- * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно
1060
- * переиспользовать. Создаётся функцией {@link post}.
1061
- *
1062
- * @example Заготовка для нескольких постов
1063
- * ```ts
1064
- * const onWall = post().onWall(userId);
1065
- *
1066
- * await itd.posts.create(onWall.content('первый'));
1067
- * await itd.posts.create(onWall.content('второй')); // заготовка не испорчена
1068
- * ```
1069
- */
1070
- declare class PostBuilder implements ItdBuilder<CreatePostInput> {
1071
- #private;
1072
- /** @internal */
1073
- readonly [BUILDER]: true;
1074
- /** @internal Создавайте билдер функцией {@link post}. */
1075
- constructor(state: PostState);
1076
- /** Задаёт текст поста, заменяя прежний. */
1077
- content(text: string): PostBuilder;
1078
- /** Дописывает текст к уже заданному. */
1079
- append(text: string): PostBuilder;
1080
- /**
1081
- * Задаёт разметку текста.
1082
- *
1083
- * Библиотека разметку не генерирует: хэштеги и упоминания нужно размечать самостоятельно
1084
- * либо не размечать вовсе.
1085
- */
1086
- spans(spans: Span[]): PostBuilder;
1087
- /**
1088
- * Публикует пост на стене другого пользователя.
1089
- *
1090
- * @param userId **UUID** пользователя; имя пользователя не подойдёт
1091
- */
1092
- onWall(userId: UserId): PostBuilder;
1093
- /**
1094
- * Прикладывает файл — он будет загружен перед публикацией.
1095
- *
1096
- * Порядок вызовов сохраняется в порядке вложений.
1097
- */
1098
- attach(file: FileInput): PostBuilder;
1099
- /** Прикладывает уже загруженное вложение по его идентификатору. */
1100
- attachId(attachmentId: string): PostBuilder;
1101
- /**
1102
- * Добавляет опрос.
1103
- *
1104
- * Принимает объект, {@link PollBuilder} или функцию-настройщик.
1105
- *
1106
- * @example
1107
- * ```ts
1108
- * post('голосуем').poll((q) => q.question('ну как?').options('да', 'нет'));
1109
- * ```
1110
- */
1111
- poll(input: PollInput): PostBuilder;
1112
- build(): CreatePostInput;
1113
- toJSON(): CreatePostInput;
1114
- }
1115
- /**
1116
- * Начинает сборку поста.
1117
- *
1118
- * @param content текст; можно задать позже методом {@link PostBuilder.content}
1119
- *
1120
- * @example
1121
- * ```ts
1122
- * import { post } from 'itd-api';
1123
- *
1124
- * await itd.posts.create(
1125
- * post('смотрите что нашёл')
1126
- * .attach('./photo.jpg')
1127
- * .poll((q) => q.question('нравится?').options('да', 'нет')),
1128
- * );
1129
- * ```
1130
- */
1131
- declare function post(content?: string): PostBuilder;
1132
- /** Что принимает параметр поста: объект, билдер или функция-настройщик. */
1133
- type PostInput = BuilderInput<CreatePostInput, PostBuilder>;
1134
-
1135
- /**
1136
- * Билдер жалобы.
1137
- *
1138
- * Точка входа задаёт объект жалобы и его тип одновременно, поэтому рассогласовать
1139
- * `targetType` и `targetId` невозможно. Создаётся объектом {@link report}.
1140
- */
1141
- declare class ReportBuilder implements ItdBuilder<CreateReportInput> {
1142
- #private;
1143
- /** @internal */
1144
- readonly [BUILDER]: true;
1145
- /** @internal Создавайте билдер через {@link report}. */
1146
- constructor(state: Partial<CreateReportInput>);
1147
- /** Указывает причину жалобы. */
1148
- reason(reason: ReportReason): ReportBuilder;
1149
- /** Добавляет пояснение в свободной форме. */
1150
- description(text: string): ReportBuilder;
1151
- build(): CreateReportInput;
1152
- toJSON(): CreateReportInput;
1153
- }
1154
- /**
1155
- * Начинает сборку жалобы.
1156
- *
1157
- * Тип объекта выбирается точкой входа, так что указать идентификатор комментария
1158
- * с типом «пост» нельзя в принципе.
1159
- *
1160
- * @example
1161
- * ```ts
1162
- * import { report, ReportReason } from 'itd-api';
1163
- *
1164
- * await itd.reports.create(report.post(postId).reason(ReportReason.Spam));
1165
- * await itd.reports.create(report.user(userId).reason('fraud').description('пишет в личку'));
1166
- * ```
1167
- */
1168
- declare const report: Readonly<{
1169
- /** Жалоба на пост. */
1170
- post: (postId: string) => ReportBuilder;
1171
- /** Жалоба на комментарий. */
1172
- comment: (commentId: string) => ReportBuilder;
1173
- /** Жалоба на пользователя. */
1174
- user: (userId: string) => ReportBuilder;
1175
- }>;
1176
- /** Что принимает параметр жалобы: объект, билдер или функция-настройщик. */
1177
- type ReportInput = BuilderInput<CreateReportInput, ReportBuilder>;
1178
-
1179
- /**
1180
- * Как библиотека обращается с cookie.
1181
- *
1182
- * - `browser` — cookie ведёт браузер, запросы уходят с `credentials: 'include'`;
1183
- * - `server` — cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную;
1184
- * - `auto` — определяется по среде исполнения (значение по умолчанию).
1185
- */
1186
- declare const RuntimeMode: Readonly<{
1187
- /** Определяется по среде исполнения. Значение по умолчанию. */
1188
- readonly Auto: "auto";
1189
- /** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */
1190
- readonly Browser: "browser";
1191
- /** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */
1192
- readonly Server: "server";
1193
- }>;
1194
- type RuntimeMode = (typeof RuntimeMode)[keyof typeof RuntimeMode];
1195
- /** Распознанная среда исполнения. */
1196
- declare const DetectedRuntime: Readonly<{
1197
- readonly Browser: "browser";
1198
- /** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */
1199
- readonly ReactNative: "react-native";
1200
- readonly Server: "server";
1201
- }>;
1202
- type DetectedRuntime = (typeof DetectedRuntime)[keyof typeof DetectedRuntime];
1203
-
1204
- /**
1205
- * Сохранённая сессия.
1206
- *
1207
- * Кроме токенов сюда попадают cookie: refresh-токен итд.com живёт именно в cookie, и без них
1208
- * восстановить сессию после перезапуска процесса невозможно.
1209
- */
1210
- interface ItdSession {
1211
- /** Токен доступа для заголовка `Authorization: Bearer`. */
1212
- accessToken?: string | undefined;
1213
- /**
1214
- * Refresh-токен, если удалось получить его явным значением.
1215
- *
1216
- * Обычно сервер держит его в httpOnly-cookie и наружу не отдаёт — тогда поле останется
1217
- * пустым, а обновление пойдёт через {@link ItdSession.cookies}.
1218
- */
1219
- refreshToken?: string | undefined;
1220
- /** Сырые cookie в форме `имя=значение`, привязанные к origin API. */
1221
- cookies?: string[] | undefined;
1222
- /**
1223
- * Идентификатор устройства для заголовка `X-Device-Id`.
1224
- *
1225
- * Сервер различает по нему записи в списке сессий, поэтому значение должно пережить
1226
- * перезапуск процесса — иначе каждый старт бота порождает новую сессию. Библиотека
1227
- * заводит его сама при первом запросе и хранит здесь.
1228
- */
1229
- deviceId?: string | undefined;
1230
- /** Когда сессия получена, мс с начала эпохи. Нужно для диагностики. */
1231
- obtainedAt?: number | undefined;
1232
- }
1233
- /**
1234
- * Хранилище сессии.
1235
- *
1236
- * Подключаемый компонент: библиотека не знает, где вы держите токены, и обращается к ним
1237
- * только через этот интерфейс. Все методы могут быть как синхронными, так и асинхронными.
1042
+ * Подключаемый компонент: библиотека не знает, где вы держите токены, и обращается к ним
1043
+ * только через этот интерфейс. Все методы могут быть как синхронными, так и асинхронными.
1238
1044
  *
1239
1045
  * @example Своё хранилище поверх AsyncStorage в React Native
1240
1046
  * ```ts
@@ -1274,6 +1080,9 @@ declare class MemoryTokenStorage implements TokenStorage {
1274
1080
  *
1275
1081
  * Помните, что `localStorage` доступен любому скрипту на странице: не используйте его,
1276
1082
  * если для вашего приложения это неприемлемый риск.
1083
+ *
1084
+ * После ошибки записи или удаления хранилище переключается на память до конца
1085
+ * своего жизненного цикла.
1277
1086
  */
1278
1087
  declare class LocalStorageTokenStorage implements TokenStorage {
1279
1088
  #private;
@@ -1455,6 +1264,23 @@ interface ItdClientOptions {
1455
1264
  * источников на итд.com, скорее всего, не настроен.
1456
1265
  */
1457
1266
  baseUrl?: string | undefined;
1267
+ /**
1268
+ * Сервисы платформы на отдельных доменах.
1269
+ *
1270
+ * Ключ — имя сервиса, значение — базовый URL или определение целиком. Имя встроенного
1271
+ * сервиса задаёт его хост. Встроен один: `status` — хост `itd.platform.status()`.
1272
+ *
1273
+ * @example
1274
+ * ```ts
1275
+ * const itd = new ItdClient({
1276
+ * services: {
1277
+ * status: 'https://my-proxy.example/status',
1278
+ * pb: { baseUrl: 'https://pbapi.xn--d1ah4a.com', headers: { Referer: 'https://pixel.xn--d1ah4a.com/' } },
1279
+ * },
1280
+ * });
1281
+ * ```
1282
+ */
1283
+ services?: Record<string, string | Omit<ServiceDefinition, 'name'>> | undefined;
1458
1284
  /** Авторизация. Без неё доступны только публичные эндпоинты. */
1459
1285
  auth?: AuthInput | undefined;
1460
1286
  /** Где хранить сессию. По умолчанию {@link MemoryTokenStorage}. */
@@ -1516,15 +1342,46 @@ interface RequestOptions {
1516
1342
  /** Повторы только для этого запроса. Переопределяют глобальную настройку `retry`. */
1517
1343
  retry?: RetryOptions | false | undefined;
1518
1344
  }
1345
+ /**
1346
+ * Имена полей {@link RequestOptions} — единственный источник истины.
1347
+ *
1348
+ * Ресурсы переносят в описание запроса только эти поля (плюс заявленные плагинами),
1349
+ * потому что параметры методов подмешивают к ним `limit`, `cursor` и прочее, чему
1350
+ * в транспорте делать нечего. Список стоит рядом с интерфейсом, чтобы новое поле нельзя
1351
+ * было забыть.
1352
+ *
1353
+ * `satisfies` гарантирует, что каждое имя в списке — действительно поле `RequestOptions`.
1354
+ * Обратную полноту (не забыто ли новое поле) проверяет тип {@link RequestOptionKeysComplete}
1355
+ * в тесте: здесь её проверять нельзя — плагины расширяют `RequestOptions` своими опциями
1356
+ * (`encrypt`, `decrypt` и подобными), которых в этом списке быть и не должно.
1357
+ */
1358
+ declare const REQUEST_OPTION_KEYS: readonly ["signal", "timeout", "headers", "retry"];
1519
1359
  /** Полное описание запроса для низкоуровневого `itd.request()`. */
1520
1360
  interface RawRequestOptions extends RequestOptions {
1521
1361
  method: string;
1522
1362
  /** Путь с ведущим слэшем, например `/api/posts`. Завершающий слэш значим. */
1523
1363
  path: string;
1364
+ /**
1365
+ * Имя сервиса, на хост которого уйдёт запрос. Без него запрос идёт на основной `baseUrl`
1366
+ * клиента. Сервисы задаются опцией {@link ItdClientOptions.services}.
1367
+ */
1368
+ service?: string | undefined;
1369
+ /**
1370
+ * Хост этого запроса. Важнее, чем {@link RawRequestOptions.service}.
1371
+ *
1372
+ * На посторонний основному API хост Bearer-токен по умолчанию не отправляется.
1373
+ * Для осознанного разрешения укажите `skipAuth: false`.
1374
+ */
1375
+ baseUrl?: string | undefined;
1524
1376
  query?: QueryParams | undefined;
1525
1377
  /** Тело: будет отправлено как JSON. Для загрузки файлов передайте `FormData`. */
1526
1378
  body?: unknown;
1527
- /** Не подставлять заголовок авторизации. */
1379
+ /**
1380
+ * Не подставлять заголовок авторизации.
1381
+ *
1382
+ * Явное `false` разрешает авторизацию и для разового внешнего `baseUrl`; без него
1383
+ * токен автоматически отправляется только основному хосту и его поддоменам.
1384
+ */
1528
1385
  skipAuth?: boolean | undefined;
1529
1386
  /** Не пытаться обновить токен при `401` — используется самими эндпоинтами авторизации. */
1530
1387
  skipAuthRefresh?: boolean | undefined;
@@ -1541,10 +1398,16 @@ interface RawRequestOptions extends RequestOptions {
1541
1398
  }
1542
1399
 
1543
1400
  /** Версия библиотеки. Попадает в `User-Agent`. */
1544
- declare const LIBRARY_VERSION = "0.0.8";
1401
+ declare const LIBRARY_VERSION = "0.0.10";
1545
1402
 
1546
1403
  /** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
1547
1404
  declare const DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
1405
+ /** Базовый URL страницы статуса. Домен записан в punycode: `статус.итд.com`. */
1406
+ declare const DEFAULT_STATUS_BASE_URL = "https://xn--80a7abcbg.xn--d1ah4a.com";
1407
+ /** Имя встроенного сервиса статуса. */
1408
+ declare const STATUS_SERVICE = "status";
1409
+ /** Сервисы, зарегистрированные у любого клиента. */
1410
+ declare const BUILT_IN_SERVICES: readonly ServiceDefinition[];
1548
1411
  /** Таймаут запроса по умолчанию. Столько же использует официальный клиент итд.com. */
1549
1412
  declare const DEFAULT_TIMEOUT = 30000;
1550
1413
 
@@ -1558,7 +1421,14 @@ declare const DEFAULT_TIMEOUT = 30000;
1558
1421
  * В браузере заголовок не выставляется — `User-Agent` там запрещён к изменению, и среда
1559
1422
  * молча его игнорирует.
1560
1423
  */
1561
- declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; itd-api/0.0.8; +https://github.com/KiowDev/itd-api)";
1424
+ declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; itd-api/0.0.10; +https://github.com/KiowDev/itd-api)";
1425
+ /** Настройки очереди со всеми значениями по умолчанию. */
1426
+ interface ResolvedRateLimitOptions {
1427
+ concurrency: number;
1428
+ rps: number | undefined;
1429
+ retryDelays: readonly number[];
1430
+ respectHeaders: boolean;
1431
+ }
1562
1432
  /**
1563
1433
  * Срез конфигурации, нужный слою авторизации.
1564
1434
  *
@@ -1649,7 +1519,7 @@ declare class CookieJar {
1649
1519
 
1650
1520
  /** Обработчик события. */
1651
1521
  type Listener<T> = (payload: T) => void;
1652
- /** Функция отписки, которую возвращает {@link Emitter.on}. */
1522
+ /** Функция отписки, которую возвращает подписка на событие. */
1653
1523
  type Unsubscribe = () => void;
1654
1524
  /**
1655
1525
  * Минимальный типизированный источник событий.
@@ -1716,10 +1586,20 @@ interface PipelineRequest extends RawRequestOptions {
1716
1586
  /** Обработчик запроса. Самый внутренний в цепочке — транспорт. */
1717
1587
  type RequestHandler = (request: PipelineRequest) => Promise<unknown>;
1718
1588
 
1719
- /** Пути авторизации. Вынесены, чтобы не расходиться между модулями. */
1589
+ /** Пути эндпоинтов авторизации. */
1720
1590
  declare const AUTH_PATHS: {
1591
+ readonly signUp: "/api/v1/auth/sign-up";
1721
1592
  readonly signIn: "/api/v1/auth/sign-in";
1593
+ readonly verifyOtp: "/api/v1/auth/verify-otp";
1594
+ readonly resendOtp: "/api/v1/auth/resend-otp";
1722
1595
  readonly refresh: "/api/v1/auth/refresh";
1596
+ readonly logout: "/api/v1/auth/logout";
1597
+ readonly forgotPassword: "/api/v1/auth/forgot-password";
1598
+ readonly resetPassword: "/api/v1/auth/reset-password";
1599
+ readonly changePassword: "/api/v1/auth/change-password";
1600
+ readonly sessions: "/api/v1/auth/sessions";
1601
+ /** Префикс внешнего входа: к нему дописывается имя провайдера. */
1602
+ readonly oauthLogin: "/api/v1/auth/login";
1723
1603
  };
1724
1604
  /**
1725
1605
  * Публичный ключ Cloudflare Turnstile платформы итд.com.
@@ -1770,6 +1650,8 @@ declare class AuthManager {
1770
1650
  get on(): Emitter<AuthEvents>['on'];
1771
1651
  /** Подписка на одно срабатывание. */
1772
1652
  get once(): Emitter<AuthEvents>['once'];
1653
+ /** Непрозрачная область авторизации; токен и идентификатор пользователя не раскрываются. */
1654
+ getAuthScope(): string;
1773
1655
  /**
1774
1656
  * Есть ли признак живой refresh-сессии.
1775
1657
  *
@@ -1816,7 +1698,14 @@ declare class AuthManager {
1816
1698
  setAccessToken(accessToken: string): Promise<void>;
1817
1699
  /** Текущая сессия целиком. Полезно, чтобы сохранить её самому. */
1818
1700
  getSession(): Promise<ItdSession | null>;
1819
- /** Заменяет сессию целиком. */
1701
+ /**
1702
+ * Идентификатор владельца сессии.
1703
+ *
1704
+ * Считается непосредственно из текущего токена и отдельно не сохраняется: после замены
1705
+ * токена идентификатор прежнего владельца остаться не может.
1706
+ */
1707
+ getUserId(): Promise<UserId | undefined>;
1708
+ /** Заменяет сессию и связанные с ней cookie целиком. */
1820
1709
  setSession(session: ItdSession): Promise<void>;
1821
1710
  /**
1822
1711
  * Забывает сессию и cookie. Сетевой запрос не выполняется.
@@ -1850,6 +1739,14 @@ interface PluginContext {
1850
1739
  baseUrl: string;
1851
1740
  /** Отладочный вывод клиента, если он включён. */
1852
1741
  logger: Logger | undefined;
1742
+ /**
1743
+ * Непрозрачная область текущей авторизации.
1744
+ *
1745
+ * Значение уникально для клиента и меняется при замене либо очистке его сессии. Плагин может
1746
+ * использовать его только для разделения состояния; идентификатор пользователя и токен в нём
1747
+ * не раскрываются.
1748
+ */
1749
+ getAuthScope?: (() => string) | undefined;
1853
1750
  /** Добавляет обёртку запроса. Подключённые раньше оказываются снаружи. */
1854
1751
  use(transformer: Transformer): void;
1855
1752
  }
@@ -1929,6 +1826,57 @@ declare class PluginRegistry {
1929
1826
  run(request: RawRequestOptions, execute: (request: RawRequestOptions) => Promise<unknown>): Promise<unknown>;
1930
1827
  }
1931
1828
 
1829
+ /**
1830
+ * Очередь запросов: ограничивает одновременность и частоту.
1831
+ *
1832
+ * Нужна прежде всего ботам: без неё цикл по сотне постов уходит в API одним залпом
1833
+ * и упирается в `RATE_LIMIT_EXCEEDED`.
1834
+ *
1835
+ * Частота выдерживается равномерным разносом стартов (`1000 / rps` между запросами),
1836
+ * а не окном со счётчиком: так нагрузка ровная, без всплеска в начале каждой секунды.
1837
+ *
1838
+ * @internal
1839
+ */
1840
+ declare class RequestQueue {
1841
+ #private;
1842
+ constructor(options: ResolvedRateLimitOptions);
1843
+ /** Сколько задач выполняется прямо сейчас. */
1844
+ get active(): number;
1845
+ /** Сколько задач ждёт очереди. */
1846
+ get pending(): number;
1847
+ /**
1848
+ * Ставит задачу в очередь.
1849
+ *
1850
+ * @returns результат задачи; ошибка задачи пробрасывается без изменений
1851
+ */
1852
+ schedule<T>(task: () => Promise<T>, signal?: AbortSignal): Promise<T>;
1853
+ /**
1854
+ * Останавливает очередь: снимает отложенную паузу и отклоняет ещё не начатые задачи
1855
+ * ошибкой `ItdAbortError`. Уже выполняющиеся задачи доводятся до конца.
1856
+ */
1857
+ stop(): void;
1858
+ /**
1859
+ * Придерживает всю очередь на заданное время.
1860
+ *
1861
+ * Вызывается при получении `429` с заголовком `Retry-After`: тормозить нужно все запросы,
1862
+ * а не только тот, который наткнулся на лимит, — иначе остальные продолжат добивать API.
1863
+ */
1864
+ pause(ms: number): void;
1865
+ }
1866
+ /**
1867
+ * Очереди по хостам: основная и по одной на каждый сервис платформы.
1868
+ *
1869
+ * @internal
1870
+ */
1871
+ declare class RequestQueuePool {
1872
+ #private;
1873
+ constructor(options: ResolvedRateLimitOptions);
1874
+ /** Очередь хоста. */
1875
+ for(service: string | undefined): RequestQueue;
1876
+ /** Останавливает все очереди. */
1877
+ stop(): void;
1878
+ }
1879
+
1932
1880
  /** Событие потока уведомлений после разбора. */
1933
1881
  interface NotificationEvent {
1934
1882
  /** Само уведомление в единой форме. */
@@ -2018,6 +1966,11 @@ interface TransportContext {
2018
1966
  baseUrl: string;
2019
1967
  /** Реализация `fetch`. */
2020
1968
  fetch: typeof fetch;
1969
+ /**
1970
+ * Общие заголовки клиента: `User-Agent`, `X-Device-Id`, заголовки конфигурации
1971
+ * и cookie для указанного адреса.
1972
+ */
1973
+ baseHeaders: (url: string) => Promise<Headers>;
2021
1974
  /** Текущий токен доступа. */
2022
1975
  getToken: () => Promise<string | null>;
2023
1976
  /** Отмена подключения. */
@@ -2149,6 +2102,8 @@ interface RealtimeOptions extends ReconnectOptions {
2149
2102
  interface RealtimeDeps {
2150
2103
  baseUrl: string;
2151
2104
  fetch: typeof fetch;
2105
+ /** Общие заголовки клиента для адреса — см. {@link TransportContext.baseHeaders}. */
2106
+ baseHeaders: (url: string) => Promise<Headers>;
2152
2107
  getToken: () => Promise<string | null>;
2153
2108
  /** Обновляет токен после отказа авторизации. Возвращает `true`, если удалось. */
2154
2109
  refresh: () => Promise<boolean>;
@@ -2283,6 +2238,8 @@ declare const PaginationMode: Readonly<{
2283
2238
  readonly Offset: "offset";
2284
2239
  }>;
2285
2240
  type PaginationMode = (typeof PaginationMode)[keyof typeof PaginationMode];
2241
+ /** Применяет преобразование к элементам страницы, сохраняя сведения о пагинации. */
2242
+ declare function mapPage<T, R>(page: Page<T>, map: (item: T) => R): Page<R>;
2286
2243
  /** Позиция, с которой запрашивается очередная страница. */
2287
2244
  interface PageState {
2288
2245
  cursor?: string | undefined;
@@ -2494,10 +2451,10 @@ type SignInStatus = (typeof SignInStatus)[keyof typeof SignInStatus];
2494
2451
  * объединение делает оба случая явными.
2495
2452
  */
2496
2453
  type SignInResult = {
2497
- status: typeof SignInStatus.Authenticated;
2454
+ status: 'authenticated';
2498
2455
  accessToken: string;
2499
2456
  } | {
2500
- status: typeof SignInStatus.OtpRequired;
2457
+ status: 'otp_required';
2501
2458
  flowToken: string | undefined;
2502
2459
  };
2503
2460
  /**
@@ -2636,9 +2593,14 @@ declare class AuthResource extends BaseResource {
2636
2593
  * Меняет пароль. Требует действующей сессии.
2637
2594
  *
2638
2595
  * При неверном текущем пароле сервер отвечает `ACCOUNT_CURRENT_PASSWORD_INCORRECT`.
2596
+ *
2597
+ * @example
2598
+ * ```ts
2599
+ * await itd.auth.changePassword({ currentPassword, newPassword });
2600
+ * ```
2639
2601
  */
2640
2602
  changePassword(input: {
2641
- oldPassword: string;
2603
+ currentPassword: string;
2642
2604
  newPassword: string;
2643
2605
  }, options?: RequestOptions): Promise<void>;
2644
2606
  /**
@@ -2661,9 +2623,238 @@ declare class AuthResource extends BaseResource {
2661
2623
  revokeOtherSessions(options?: RequestOptions): Promise<void>;
2662
2624
  }
2663
2625
 
2664
- /** Параметры запроса ответов на комментарий. */
2665
- interface RepliesParams extends RequestOptions {
2666
- limit?: number;
2626
+ /** Метка билдера. Через `Symbol.for` чтобы распознавание переживало смешивание ESM и CJS. */
2627
+ declare const BUILDER: unique symbol;
2628
+ /**
2629
+ * Билдер входных данных.
2630
+ *
2631
+ * Билдеры необязательны: любой метод, принимающий билдер, принимает и обычный объект.
2632
+ * Проверки одинаковы в обоих случаях.
2633
+ */
2634
+ interface ItdBuilder<T> {
2635
+ /** @internal */
2636
+ readonly [BUILDER]: true;
2637
+ /**
2638
+ * Собирает и проверяет результат.
2639
+ *
2640
+ * @throws {ItdConfigError} если нарушены требования к данным
2641
+ */
2642
+ build(): T;
2643
+ /** Чтобы билдер корректно вёл себя внутри `JSON.stringify`. */
2644
+ toJSON(): T;
2645
+ }
2646
+ /**
2647
+ * Три равноправные формы входа: обычный объект, готовый билдер или функция-настройщик.
2648
+ *
2649
+ * @example
2650
+ * ```ts
2651
+ * itd.posts.create({ content: 'привет' }); // объект
2652
+ * itd.posts.create(post().content('привет')); // билдер
2653
+ * itd.posts.create((p) => p.content('привет')); // функция
2654
+ * ```
2655
+ */
2656
+ type BuilderInput<T, B extends ItdBuilder<T>> = T | B | ((builder: B) => B | T);
2657
+ /** Является ли значение билдером. */
2658
+ declare function isBuilder<T>(value: unknown): value is ItdBuilder<T>;
2659
+
2660
+ /**
2661
+ * Билдер опроса.
2662
+ *
2663
+ * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно
2664
+ * переиспользовать, не боясь её испортить. Создаётся функцией {@link poll}.
2665
+ */
2666
+ declare class PollBuilder implements ItdBuilder<CreatePollInput> {
2667
+ #private;
2668
+ /** @internal */
2669
+ readonly [BUILDER]: true;
2670
+ /** @internal Создавайте билдер функцией {@link poll}. */
2671
+ constructor(state: CreatePollInput);
2672
+ /** Задаёт вопрос. */
2673
+ question(text: string): PollBuilder;
2674
+ /** Добавляет один вариант ответа. */
2675
+ option(text: string): PollBuilder;
2676
+ /**
2677
+ * Добавляет несколько вариантов сразу.
2678
+ *
2679
+ * @example
2680
+ * ```ts
2681
+ * poll('ну как?').options('да', 'нет', 'не знаю');
2682
+ * ```
2683
+ */
2684
+ options(...texts: string[]): PollBuilder;
2685
+ /** Разрешает выбор нескольких вариантов. */
2686
+ multipleChoice(enabled?: boolean): PollBuilder;
2687
+ build(): CreatePollInput;
2688
+ toJSON(): CreatePollInput;
2689
+ }
2690
+ /**
2691
+ * Начинает сборку опроса.
2692
+ *
2693
+ * @param question вопрос; можно задать позже методом {@link PollBuilder.question}
2694
+ *
2695
+ * @example
2696
+ * ```ts
2697
+ * import { poll } from 'itd-api';
2698
+ *
2699
+ * const q = poll('Какой язык лучше?')
2700
+ * .options('TypeScript', 'JavaScript')
2701
+ * .multipleChoice();
2702
+ *
2703
+ * await itd.posts.create({ content: 'голосуем', poll: q });
2704
+ * ```
2705
+ */
2706
+ declare function poll(question?: string): PollBuilder;
2707
+ /** Что принимает параметр опроса: объект, билдер или функция-настройщик. */
2708
+ type PollInput = BuilderInput<CreatePollInput, PollBuilder>;
2709
+
2710
+ /**
2711
+ * Файл для загрузки.
2712
+ *
2713
+ * Строка означает **путь на диске** и работает только в Node, Bun и Deno — для этого
2714
+ * подключите `itd-api/node`. В браузере и React Native передавайте `File` или `Blob`.
2715
+ */
2716
+ type FileInput = Blob | ArrayBuffer | Uint8Array | string | {
2717
+ /** Содержимое файла. */
2718
+ data: Blob | ArrayBuffer | Uint8Array;
2719
+ /** Имя файла. Влияет на определение типа, если `contentType` не задан. */
2720
+ filename?: string;
2721
+ /** MIME-тип. Если не указан, определяется по расширению или по самому `Blob`. */
2722
+ contentType?: string;
2723
+ };
2724
+ /** Данные для создания опроса. */
2725
+ interface CreatePollInput {
2726
+ /** Вопрос. Не может быть пустым. */
2727
+ question: string;
2728
+ /** Варианты ответа. Требуется минимум два. */
2729
+ options: {
2730
+ text: string;
2731
+ }[];
2732
+ /** Разрешить выбор нескольких вариантов. По умолчанию `false`. */
2733
+ multipleChoice?: boolean;
2734
+ }
2735
+ /** Данные для создания поста. */
2736
+ interface CreatePostInput {
2737
+ /** Текст поста. */
2738
+ content?: string;
2739
+ /**
2740
+ * Разметка текста. Сырые spans проверяются относительно `content`;
2741
+ * для автоматического построения доступны `post().markup()` и `post().autoSpans()`.
2742
+ */
2743
+ spans?: Span[];
2744
+ /**
2745
+ * Чья стена, если пост публикуется не у себя.
2746
+ *
2747
+ * Требуется **UUID**: имя пользователя здесь не работает, его можно получить
2748
+ * из профиля через `itd.users.get(username)`.
2749
+ */
2750
+ wallRecipientId?: UserId | null;
2751
+ /** Идентификаторы заранее загруженных вложений. */
2752
+ attachmentIds?: string[];
2753
+ /** Файлы, которые нужно загрузить перед публикацией. Порядок сохраняется. */
2754
+ files?: FileInput[];
2755
+ /** Опрос: обычный объект, {@link PollBuilder} или функция-настройщик. */
2756
+ poll?: PollInput;
2757
+ }
2758
+ /** Поля поста, которые принимает `itd.posts.update()`. */
2759
+ interface UpdatePostInput {
2760
+ /** Новый текст поста. Обязателен, чтобы обновление одних spans не стёрло текущий текст. */
2761
+ content: string;
2762
+ /** Разметка нового текста. */
2763
+ spans?: Span[];
2764
+ }
2765
+ /** Данные для создания комментария или ответа. */
2766
+ interface CreateCommentInput {
2767
+ /** Текст. У голосового комментария должен быть пустым. */
2768
+ content?: string;
2769
+ /** Идентификаторы заранее загруженных вложений. */
2770
+ attachmentIds?: string[];
2771
+ /** Файлы, которые нужно загрузить перед отправкой. */
2772
+ files?: FileInput[];
2773
+ /**
2774
+ * Кому адресован ответ.
2775
+ *
2776
+ * Применимо только в `itd.comments.reply()`; в комментарии к посту поле не имеет смысла.
2777
+ */
2778
+ replyToUserId?: UserId;
2779
+ }
2780
+ /** Данные для создания жалобы. */
2781
+ interface CreateReportInput {
2782
+ /** На что жалоба. */
2783
+ targetType: ReportTargetType;
2784
+ /** Идентификатор объекта жалобы. */
2785
+ targetId: string;
2786
+ /** Причина. */
2787
+ reason: ReportReason;
2788
+ /** Пояснение в свободной форме. */
2789
+ description?: string;
2790
+ }
2791
+
2792
+ /** Внутреннее состояние {@link CommentBuilder}. */
2793
+ interface CommentState extends CreateCommentInput {
2794
+ content: string;
2795
+ attachmentIds: string[];
2796
+ files: FileInput[];
2797
+ /** Голосовой комментарий: текста быть не должно, вложение ровно одно. */
2798
+ voice: boolean;
2799
+ }
2800
+ /**
2801
+ * Билдер комментария и ответа на комментарий.
2802
+ *
2803
+ * Неизменяемый: каждый вызов возвращает новый экземпляр. Создаётся функцией {@link comment}.
2804
+ */
2805
+ declare class CommentBuilder implements ItdBuilder<CreateCommentInput> {
2806
+ #private;
2807
+ /** @internal */
2808
+ readonly [BUILDER]: true;
2809
+ /** @internal Создавайте билдер функцией {@link comment}. */
2810
+ constructor(state: CommentState);
2811
+ /** Задаёт текст комментария. */
2812
+ content(text: string): CommentBuilder;
2813
+ /** Прикладывает файл — он будет загружен перед отправкой. */
2814
+ attach(file: FileInput): CommentBuilder;
2815
+ /** Прикладывает уже загруженное вложение. */
2816
+ attachId(attachmentId: string): CommentBuilder;
2817
+ /**
2818
+ * Делает комментарий голосовым.
2819
+ *
2820
+ * Текста у такого комментария быть не должно, а вложение ровно одно — аудио в формате
2821
+ * `audio/ogg`. Так его принимает API.
2822
+ *
2823
+ * @example
2824
+ * ```ts
2825
+ * await itd.posts.comment(postId, (c) => c.voice('./answer.ogg'));
2826
+ * ```
2827
+ */
2828
+ voice(audio: FileInput): CommentBuilder;
2829
+ /**
2830
+ * Кому адресован ответ.
2831
+ *
2832
+ * Имеет смысл только в `itd.comments.reply()`; при отправке комментария к посту
2833
+ * это поле вызовет ошибку.
2834
+ */
2835
+ replyTo(userId: UserId): CommentBuilder;
2836
+ build(): CreateCommentInput;
2837
+ toJSON(): CreateCommentInput;
2838
+ }
2839
+ /**
2840
+ * Начинает сборку комментария.
2841
+ *
2842
+ * @param content текст; можно задать позже методом {@link CommentBuilder.content}
2843
+ *
2844
+ * @example
2845
+ * ```ts
2846
+ * import { comment } from 'itd-api';
2847
+ *
2848
+ * await itd.posts.comment(postId, comment('согласен').attach('./meme.png'));
2849
+ * ```
2850
+ */
2851
+ declare function comment(content?: string): CommentBuilder;
2852
+ /** Что принимает параметр комментария: объект, билдер или функция-настройщик. */
2853
+ type CommentInput = BuilderInput<CreateCommentInput, CommentBuilder>;
2854
+
2855
+ /** Параметры запроса ответов на комментарий. */
2856
+ interface RepliesParams extends RequestOptions {
2857
+ limit?: number;
2667
2858
  page?: number;
2668
2859
  maxPages?: number;
2669
2860
  }
@@ -2728,6 +2919,13 @@ interface UploadOptions extends RequestOptions {
2728
2919
  */
2729
2920
  validateMime?: boolean;
2730
2921
  }
2922
+ /**
2923
+ * Таймаут загрузки файла по умолчанию.
2924
+ *
2925
+ * Заметно больше обычного: видео на несколько десятков мегабайт не укладывается
2926
+ * в стандартные 30 секунд, и запрос обрывался бы на середине.
2927
+ */
2928
+ declare const DEFAULT_UPLOAD_TIMEOUT = 300000;
2731
2929
  /** Чтение файла по пути — подставляется точкой входа `itd-api/node`. */
2732
2930
  type FileReader = (path: string) => Promise<{
2733
2931
  data: Uint8Array;
@@ -2894,16 +3092,231 @@ declare class NotificationsResource extends BaseResource {
2894
3092
  /**
2895
3093
  * Сведения о платформе: изменения, анонсы, баннер события.
2896
3094
  *
2897
- * Доступна как `itd.platform`.
3095
+ * Доступна как `itd.platform`.
3096
+ */
3097
+ declare class PlatformResource extends BaseResource {
3098
+ /** Загружает журнал изменений. */
3099
+ changelog(options?: RequestOptions): Promise<ChangelogEntry[]>;
3100
+ /** Загружает анонсы платформы. */
3101
+ announcements(options?: RequestOptions): Promise<Announcement[]>;
3102
+ /** Загружает баннер текущего события — виджет «портал». */
3103
+ portal(options?: RequestOptions): Promise<Portal>;
3104
+ /**
3105
+ * Загружает состояние сервисов платформы за последние 90 суток.
3106
+ *
3107
+ * Идёт на хост `статус.итд.com` без авторизации. Ответ кэшируется сервером на минуту.
3108
+ * История по суткам приходит разреженной, ровный массив даёт `statusDays`.
3109
+ *
3110
+ * @example
3111
+ * ```ts
3112
+ * const status = await itd.platform.status();
3113
+ *
3114
+ * if (status.overall_status !== 'operational') {
3115
+ * const broken = status.services.filter((s) => s.current_status !== 'operational');
3116
+ * console.log('лежит:', broken.map((s) => s.name).join(', '));
3117
+ * }
3118
+ * ```
3119
+ */
3120
+ status(options?: RequestOptions): Promise<PlatformStatus>;
3121
+ }
3122
+
3123
+ /** Текст вместе с рассчитанной разметкой. */
3124
+ interface TextMarkup {
3125
+ content: string;
3126
+ spans: Span[];
3127
+ }
3128
+ /** Описание фрагмента без смещения: его вычисляет {@link MarkupBuilder}. */
3129
+ type MarkupSpan = Omit<Span, 'offset' | 'length'>;
3130
+ /** Что принимает метод разметки: результат, билдер или функция-настройщик. */
3131
+ type MarkupInput = BuilderInput<TextMarkup, MarkupBuilder>;
3132
+ /**
3133
+ * Содержимое форматированного фрагмента.
3134
+ *
3135
+ * Строка создаёт простой фрагмент. Билдер, готовая разметка или функция позволяют вложить
3136
+ * одни spans в другие, как при последовательном форматировании выделения в редакторе сайта.
3137
+ */
3138
+ type MarkupContent = string | MarkupInput;
3139
+ /** Какие сущности искать в {@link autoSpans}. */
3140
+ interface AutoSpansOptions {
3141
+ /** Находить `#хэштеги`. По умолчанию `true`. */
3142
+ hashtags?: boolean;
3143
+ /** Находить `@упоминания`. По умолчанию `true`. */
3144
+ mentions?: boolean;
3145
+ /** Находить абсолютные HTTP(S)-ссылки. По умолчанию `true`. */
3146
+ links?: boolean;
3147
+ }
3148
+ /**
3149
+ * Неизменяемый билдер текста с разметкой.
3150
+ *
3151
+ * Каждый метод дописывает фрагмент и сам считает `offset` и `length` в единицах UTF-16 —
3152
+ * именно такие индексы использует JavaScript-редактор сайта.
3153
+ */
3154
+ declare class MarkupBuilder implements ItdBuilder<TextMarkup> {
3155
+ #private;
3156
+ /** @internal */
3157
+ readonly [BUILDER]: true;
3158
+ /** @internal Создавайте билдер функцией {@link markup}. */
3159
+ constructor(content: string, spans: Span[]);
3160
+ /** Дописывает обычный текст без разметки. */
3161
+ text(value: string): MarkupBuilder;
3162
+ /** Дописывает переводы строк. */
3163
+ newline(count?: number): MarkupBuilder;
3164
+ /**
3165
+ * Дописывает фрагмент с произвольным типом разметки.
3166
+ *
3167
+ * Вложенный билдер позволяет форматировать часть фрагмента дополнительным стилем.
3168
+ * Для нескольких стилей на всём фрагменте используйте {@link styled}.
3169
+ */
3170
+ span(value: MarkupContent, span: MarkupSpan): MarkupBuilder;
3171
+ /**
3172
+ * Дописывает фрагмент с несколькими стилями на одном диапазоне.
3173
+ *
3174
+ * Для `link`, которому нужен `url`, используйте {@link link}; произвольные spans с
3175
+ * метаданными можно объединять через вложенные вызовы {@link span}.
3176
+ */
3177
+ styled(value: MarkupContent, ...types: Span['type'][]): MarkupBuilder;
3178
+ /** Дописывает `#хэштег` и сохраняет имя без решётки в `tag`. */
3179
+ hashtag(tag: string): MarkupBuilder;
3180
+ /** Дописывает `@username` и сохраняет имя пользователя в `username`. */
3181
+ mention(username: string): MarkupBuilder;
3182
+ /**
3183
+ * Дописывает ссылку.
3184
+ *
3185
+ * Для строки адрес по умолчанию становится и текстом ссылки. Вложенному форматированному
3186
+ * фрагменту URL нужно передать явно.
3187
+ */
3188
+ link(content: MarkupContent, url?: string): MarkupBuilder;
3189
+ bold(content: MarkupContent): MarkupBuilder;
3190
+ italic(content: MarkupContent): MarkupBuilder;
3191
+ underline(content: MarkupContent): MarkupBuilder;
3192
+ strike(content: MarkupContent): MarkupBuilder;
3193
+ spoiler(content: MarkupContent): MarkupBuilder;
3194
+ monospace(content: MarkupContent): MarkupBuilder;
3195
+ quote(content: MarkupContent): MarkupBuilder;
3196
+ build(): TextMarkup;
3197
+ toJSON(): TextMarkup;
3198
+ }
3199
+ /** Начинает сборку текста с автоматически вычисляемыми смещениями. */
3200
+ declare function markup(content?: string): MarkupBuilder;
3201
+ /**
3202
+ * Находит в тексте те сущности, которые сайт получает после серверного разбора:
3203
+ * HTTP(S)-ссылки, `#хэштеги` и `@упоминания`.
3204
+ *
3205
+ * Смещения выражены в UTF-16 code units, поэтому совпадают с `String#slice`,
3206
+ * `substring`, DOM Selection и wire-форматом сайта даже при наличии эмодзи.
3207
+ */
3208
+ declare function autoSpans(text: string, options?: AutoSpansOptions): Span[];
3209
+
3210
+ declare const BUILD_UPDATE: unique symbol;
3211
+ /** Внутреннее состояние {@link PostBuilder}. */
3212
+ interface PostState extends CreatePostInput {
3213
+ content: string;
3214
+ contentSet: boolean;
3215
+ attachmentIds: string[];
3216
+ files: FileInput[];
3217
+ }
3218
+ /**
3219
+ * Билдер поста.
3220
+ *
3221
+ * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно
3222
+ * переиспользовать. Создаётся функцией {@link post}.
3223
+ *
3224
+ * @example Заготовка для нескольких постов
3225
+ * ```ts
3226
+ * const onWall = post().onWall(userId);
3227
+ *
3228
+ * await itd.posts.create(onWall.content('первый'));
3229
+ * await itd.posts.create(onWall.content('второй')); // заготовка не испорчена
3230
+ * ```
3231
+ */
3232
+ declare class PostBuilder implements ItdBuilder<CreatePostInput> {
3233
+ #private;
3234
+ /** @internal */
3235
+ readonly [BUILDER]: true;
3236
+ /** @internal Создавайте билдер функцией {@link post}. */
3237
+ constructor(state: PostState);
3238
+ /**
3239
+ * Задаёт текст поста, заменяя прежний вместе с его разметкой.
3240
+ *
3241
+ * Spans привязаны к конкретному тексту, поэтому после замены их нужно задать заново
3242
+ * через {@link spans}, {@link markup} или {@link autoSpans}.
3243
+ */
3244
+ content(text: string): PostBuilder;
3245
+ /** Дописывает текст к уже заданному. */
3246
+ append(text: string): PostBuilder;
3247
+ /**
3248
+ * Задаёт готовую разметку текста. Смещения проверяются при {@link build}.
3249
+ *
3250
+ * Для автоматического поиска сущностей есть {@link autoSpans}, а для вычисления смещений
3251
+ * при сборке текста — {@link markup}.
3252
+ */
3253
+ spans(spans: Span[]): PostBuilder;
3254
+ /**
3255
+ * Заменяет текст и разметку результатом {@link MarkupBuilder}.
3256
+ *
3257
+ * @example
3258
+ * ```ts
3259
+ * post().markup((m) => m.text('смотрите ').hashtag('котики').text(' от ').mention('durov'));
3260
+ * ```
3261
+ */
3262
+ markup(input: MarkupInput): PostBuilder;
3263
+ /**
3264
+ * Находит HTTP(S)-ссылки, хэштеги и упоминания в уже заданном тексте.
3265
+ *
3266
+ * Ручные стили сохраняются. Повторный вызов не дублирует уже найденные сущности.
3267
+ */
3268
+ autoSpans(options?: AutoSpansOptions): PostBuilder;
3269
+ /**
3270
+ * Публикует пост на стене другого пользователя.
3271
+ *
3272
+ * @param userId **UUID** пользователя; имя пользователя не подойдёт
3273
+ */
3274
+ onWall(userId: UserId): PostBuilder;
3275
+ /**
3276
+ * Прикладывает файл — он будет загружен перед публикацией.
3277
+ *
3278
+ * Порядок вызовов сохраняется в порядке вложений.
3279
+ */
3280
+ attach(file: FileInput): PostBuilder;
3281
+ /** Прикладывает уже загруженное вложение по его идентификатору. */
3282
+ attachId(attachmentId: string): PostBuilder;
3283
+ /**
3284
+ * Добавляет опрос.
3285
+ *
3286
+ * Принимает объект, {@link PollBuilder} или функцию-настройщик.
3287
+ *
3288
+ * @example
3289
+ * ```ts
3290
+ * post('голосуем').poll((q) => q.question('ну как?').options('да', 'нет'));
3291
+ * ```
3292
+ */
3293
+ poll(input: PollInput): PostBuilder;
3294
+ build(): CreatePostInput;
3295
+ /** @internal Собирает данные по правилам `posts.update`, не применяя правила создания. */
3296
+ [BUILD_UPDATE](): UpdatePostInput;
3297
+ toJSON(): CreatePostInput;
3298
+ }
3299
+ /**
3300
+ * Начинает сборку поста.
3301
+ *
3302
+ * @param content текст; можно задать позже методом {@link PostBuilder.content}
3303
+ *
3304
+ * @example
3305
+ * ```ts
3306
+ * import { post } from 'itd-api';
3307
+ *
3308
+ * await itd.posts.create(
3309
+ * post('смотрите что нашёл')
3310
+ * .attach('./photo.jpg')
3311
+ * .poll((q) => q.question('нравится?').options('да', 'нет')),
3312
+ * );
3313
+ * ```
2898
3314
  */
2899
- declare class PlatformResource extends BaseResource {
2900
- /** Загружает журнал изменений. */
2901
- changelog(options?: RequestOptions): Promise<ChangelogEntry[]>;
2902
- /** Загружает анонсы платформы. */
2903
- announcements(options?: RequestOptions): Promise<Announcement[]>;
2904
- /** Загружает баннер текущего события — виджет «портал». */
2905
- portal(options?: RequestOptions): Promise<Portal>;
2906
- }
3315
+ declare function post(content?: string): PostBuilder;
3316
+ /** Что принимает параметр поста: объект, билдер или функция-настройщик. */
3317
+ type PostInput = BuilderInput<CreatePostInput, PostBuilder>;
3318
+ /** Что принимает `posts.update`: объект, билдер поста или функция-настройщик. */
3319
+ type PostUpdateInput = UpdatePostInput | PostBuilder | ((builder: PostBuilder) => PostBuilder | UpdatePostInput);
2907
3320
 
2908
3321
  /** Параметры запроса ленты. */
2909
3322
  interface FeedParams extends RequestOptions {
@@ -2992,8 +3405,14 @@ declare class PostsResource extends BaseResource {
2992
3405
  * В отличие от списков, здесь у поста заполнено поле `comments`.
2993
3406
  */
2994
3407
  get(postId: string, options?: RequestOptions): Promise<Post>;
2995
- /** Редактирует текст поста. */
2996
- update(postId: string, input: Pick<CreatePostInput, 'content' | 'spans'>, options?: RequestOptions): Promise<Post>;
3408
+ /**
3409
+ * Редактирует текст и разметку поста.
3410
+ *
3411
+ * Как и {@link create}, принимает объект, готовый {@link PostBuilder} или
3412
+ * функцию-настройщик. Поля создания поста, которые update endpoint не поддерживает
3413
+ * (вложения, опрос и стена), отвергаются до запроса.
3414
+ */
3415
+ update(postId: string, input: PostUpdateInput, options?: RequestOptions): Promise<Post>;
2997
3416
  /** Удаляет пост. Восстановить его можно через {@link restore}. */
2998
3417
  remove(postId: string, options?: RequestOptions): Promise<void>;
2999
3418
  /** Восстанавливает удалённый пост. */
@@ -3073,6 +3492,50 @@ declare class PostsResource extends BaseResource {
3073
3492
  voiceComment(postId: string, audio: FileInput, options?: RequestOptions): Promise<Comment>;
3074
3493
  }
3075
3494
 
3495
+ /**
3496
+ * Билдер жалобы.
3497
+ *
3498
+ * Точка входа задаёт объект жалобы и его тип одновременно, поэтому рассогласовать
3499
+ * `targetType` и `targetId` невозможно. Создаётся объектом {@link report}.
3500
+ */
3501
+ declare class ReportBuilder implements ItdBuilder<CreateReportInput> {
3502
+ #private;
3503
+ /** @internal */
3504
+ readonly [BUILDER]: true;
3505
+ /** @internal Создавайте билдер через {@link report}. */
3506
+ constructor(state: Partial<CreateReportInput>);
3507
+ /** Указывает причину жалобы. */
3508
+ reason(reason: ReportReason): ReportBuilder;
3509
+ /** Добавляет пояснение в свободной форме. */
3510
+ description(text: string): ReportBuilder;
3511
+ build(): CreateReportInput;
3512
+ toJSON(): CreateReportInput;
3513
+ }
3514
+ /**
3515
+ * Начинает сборку жалобы.
3516
+ *
3517
+ * Тип объекта выбирается точкой входа, так что указать идентификатор комментария
3518
+ * с типом «пост» нельзя в принципе.
3519
+ *
3520
+ * @example
3521
+ * ```ts
3522
+ * import { report, ReportReason } from 'itd-api';
3523
+ *
3524
+ * await itd.reports.create(report.post(postId).reason(ReportReason.Spam));
3525
+ * await itd.reports.create(report.user(userId).reason('fraud').description('пишет в личку'));
3526
+ * ```
3527
+ */
3528
+ declare const report: Readonly<{
3529
+ /** Жалоба на пост. */
3530
+ post: (postId: string) => ReportBuilder;
3531
+ /** Жалоба на комментарий. */
3532
+ comment: (commentId: string) => ReportBuilder;
3533
+ /** Жалоба на пользователя. */
3534
+ user: (userId: string) => ReportBuilder;
3535
+ }>;
3536
+ /** Что принимает параметр жалобы: объект, билдер или функция-настройщик. */
3537
+ type ReportInput = BuilderInput<CreateReportInput, ReportBuilder>;
3538
+
3076
3539
  /**
3077
3540
  * Жалобы на контент и пользователей.
3078
3541
  *
@@ -3188,13 +3651,13 @@ interface InteractionEntry {
3188
3651
  vs: string;
3189
3652
  /** Идентификатор поста. Поле `ai`. */
3190
3653
  postId: string;
3191
- /** Индекс вложения (с нуля) — для {@link InteractionType.PhotoOpen}. Поле `mi`. */
3654
+ /** Индекс вложения (с нуля) — для `InteractionType.PhotoOpen` ({@link InteractionType}). Поле `mi`. */
3192
3655
  mediaIndex?: number;
3193
3656
  /** Источник показа. Поле `s`. */
3194
3657
  source?: ViewSource;
3195
- /** Просмотрено мс — для {@link InteractionType.VideoProgress}. Поле `pm`. */
3658
+ /** Просмотрено мс — для `InteractionType.VideoProgress` ({@link InteractionType}). Поле `pm`. */
3196
3659
  positionMs?: number;
3197
- /** Длительность видео в мс — для {@link InteractionType.VideoProgress}. Поле `dm`. */
3660
+ /** Длительность видео в мс — для `InteractionType.VideoProgress` ({@link InteractionType}). Поле `dm`. */
3198
3661
  durationMs?: number;
3199
3662
  }
3200
3663
  /**
@@ -3394,6 +3857,12 @@ declare global {
3394
3857
  */
3395
3858
  interface ItdClientInternals {
3396
3859
  fileReader?: FileReader | undefined;
3860
+ /**
3861
+ * Готовая очередь запросов — так {@link ItdAccounts} с `rateLimitScope: 'shared'` даёт
3862
+ * нескольким клиентам одну на всех. Свою клиент в этом случае не заводит и, что важнее,
3863
+ * не гасит при `close()`: чужие ожидающие запросы это отменило бы.
3864
+ */
3865
+ queues?: RequestQueuePool | undefined;
3397
3866
  }
3398
3867
  /**
3399
3868
  * Клиент API итд.com.
@@ -3484,13 +3953,43 @@ declare class ItdClient {
3484
3953
  *
3485
3954
  * @example
3486
3955
  * ```ts
3487
- * import { crypt } from 'itd-api-crypto';
3956
+ * import { crypt } from '@itd-api/crypto';
3488
3957
  *
3489
3958
  * itd.use(crypt());
3490
3959
  * await itd.posts.create({ content: 'секрет' }, { encrypt: 'invis' });
3491
3960
  * ```
3492
3961
  */
3493
3962
  use(plugin: ItdPlugin): this;
3963
+ /**
3964
+ * Регистрирует сервис платформы — домен, отличный от основного.
3965
+ *
3966
+ * Запросы с `{ service: 'имя' }` уходят на его хост с его заголовками. То же самое умеет
3967
+ * опция `services` конструктора. Занятое имя не переопределяется — ни своё, ни встроенное:
3968
+ * разовому запросу хост задаётся полем `baseUrl`.
3969
+ *
3970
+ * Заголовок авторизации по умолчанию уходит только своим — домену клиента и его
3971
+ * поддоменам. Стороннему хосту токен нужно разрешить явно: `auth: true`.
3972
+ *
3973
+ * @throws {ItdConfigError} если определение неверно или имя уже занято
3974
+ *
3975
+ * @example Сервис платформы на поддомене — токен уходит сам
3976
+ * ```ts
3977
+ * itd.defineService({
3978
+ * name: 'pb',
3979
+ * baseUrl: 'https://pbapi.xn--d1ah4a.com',
3980
+ * headers: { Referer: 'https://pixel.xn--d1ah4a.com/' },
3981
+ * });
3982
+ *
3983
+ * await itd.request({ method: 'GET', service: 'pb', path: '/api/pixel-info' });
3984
+ * ```
3985
+ */
3986
+ defineService(definition: ServiceDefinition): this;
3987
+ /**
3988
+ * Базовый URL зарегистрированного сервиса.
3989
+ *
3990
+ * @throws {ItdConfigError} если сервис не зарегистрирован
3991
+ */
3992
+ serviceBaseUrl(name: string): string;
3494
3993
  /**
3495
3994
  * Подписывается на события авторизации.
3496
3995
  *
@@ -3532,6 +4031,9 @@ declare class ItdClient {
3532
4031
  * После вызова клиентом можно пользоваться снова — новые запросы поднимут всё заново,
3533
4032
  * но уже созданные потоки останутся закрытыми.
3534
4033
  *
4034
+ * Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам
4035
+ * контейнер, когда закрывает все аккаунты разом.
4036
+ *
3535
4037
  * @example
3536
4038
  * ```ts
3537
4039
  * await using itd = new ItdClient({ auth: token });
@@ -3544,6 +4046,20 @@ declare class ItdClient {
3544
4046
  [Symbol.asyncDispose](): Promise<void>;
3545
4047
  /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */
3546
4048
  getSession(): Promise<ItdSession | null>;
4049
+ /**
4050
+ * Идентификатор аккаунта, под которым работает клиент.
4051
+ *
4052
+ * Читается из токена доступа и не стоит ни одного запроса. `undefined`, когда сессии
4053
+ * ещё нет или токен выдан не в формате JWT. Полезен прежде всего с {@link ItdAccounts}:
4054
+ * показывает, какому профилю соответствует восстановленная из хранилища запись.
4055
+ * Свежий профиль целиком отдаёт `itd.users.me()`.
4056
+ *
4057
+ * @example
4058
+ * ```ts
4059
+ * for (const [name, itd] of accounts) console.log(name, await itd.getUserId());
4060
+ * ```
4061
+ */
4062
+ getUserId(): Promise<UserId | undefined>;
3547
4063
  /** Восстанавливает сохранённую сессию, включая cookie. */
3548
4064
  setSession(session: ItdSession): Promise<void>;
3549
4065
  }
@@ -3559,6 +4075,340 @@ declare class ItdClient {
3559
4075
  */
3560
4076
  declare function createClient(options?: ItdClientOptions): ItdClient;
3561
4077
 
4078
+ /**
4079
+ * Хранилище сессий нескольких аккаунтов.
4080
+ *
4081
+ * Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:
4082
+ * так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,
4083
+ * что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:
4084
+ * библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения
4085
+ * на длину ключа остаются за адаптером.
4086
+ *
4087
+ * Все методы могут быть как синхронными, так и асинхронными.
4088
+ *
4089
+ * @example Своё хранилище поверх Redis
4090
+ * ```ts
4091
+ * const storage = createMultiTokenStorage({
4092
+ * get: async (account) => JSON.parse((await redis.get(`itd:session:${account}`)) ?? 'null'),
4093
+ * set: async (account, session) => {
4094
+ * await redis.set(`itd:session:${account}`, JSON.stringify(session));
4095
+ * await redis.sadd('itd:accounts', account);
4096
+ * },
4097
+ * clear: async (account) => {
4098
+ * await redis.del(`itd:session:${account}`);
4099
+ * await redis.srem('itd:accounts', account);
4100
+ * },
4101
+ * accounts: () => redis.smembers('itd:accounts'),
4102
+ * });
4103
+ * ```
4104
+ */
4105
+ interface MultiTokenStorage {
4106
+ /** Прочитать сессию аккаунта. `null`, если её нет. */
4107
+ get(account: string): ItdSession | null | Promise<ItdSession | null>;
4108
+ /** Сохранить сессию аккаунта целиком. */
4109
+ set(account: string, session: ItdSession): void | Promise<void>;
4110
+ /** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */
4111
+ clear(account: string): void | Promise<void>;
4112
+ /**
4113
+ * Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов
4114
+ * после перезапуска процесса.
4115
+ *
4116
+ * Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,
4117
+ * а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.
4118
+ * Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после
4119
+ * выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.
4120
+ * Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть
4121
+ * доступны по имени.
4122
+ */
4123
+ accounts(): readonly string[] | Promise<readonly string[]>;
4124
+ }
4125
+ /**
4126
+ * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
4127
+ * отдельный `ItdClient`, который про соседние аккаунты ничего не знает.
4128
+ */
4129
+ declare function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage;
4130
+ /**
4131
+ * Мультихранилище в памяти процесса — вариант по умолчанию.
4132
+ *
4133
+ * Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`
4134
+ * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.
4135
+ */
4136
+ declare class MemoryMultiTokenStorage implements MultiTokenStorage {
4137
+ #private;
4138
+ constructor(initial?: Readonly<Record<string, ItdSession>> | null);
4139
+ get(account: string): ItdSession | null;
4140
+ set(account: string, session: ItdSession): void;
4141
+ clear(account: string): void;
4142
+ accounts(): string[];
4143
+ }
4144
+ /**
4145
+ * Собирает {@link MultiTokenStorage} из четырёх функций — когда заводить класс избыточно.
4146
+ * Аналог `createTokenStorage` для нескольких аккаунтов.
4147
+ */
4148
+ declare function createMultiTokenStorage(handlers: MultiTokenStorage): MultiTokenStorage;
4149
+ /** Источник, который читается и пишется целиком: файл, ключ в `localStorage`, строка в БД. */
4150
+ interface RecordStorageSource {
4151
+ /** Прочитать все сессии разом. `null` — записи ещё нет. */
4152
+ read(): Promise<Record<string, ItdSession> | null>;
4153
+ /** Записать все сессии разом. */
4154
+ write(record: Record<string, ItdSession>): Promise<void>;
4155
+ /** Вызывается вместо {@link RecordStorageSource.write}, когда не осталось ни одной сессии. */
4156
+ remove?(): Promise<void>;
4157
+ }
4158
+ /**
4159
+ * Мультихранилище поверх источника, который читается и пишется целиком.
4160
+ *
4161
+ * Решает главную проблему такого способа хранения — **гонку «прочитать, изменить,
4162
+ * записать»**: десять аккаунтов пишут в одну запись, и наивная реализация теряла бы
4163
+ * чужие сессии. Источник читается один раз, дальше слепок живёт в памяти, а записи
4164
+ * выстраиваются в цепочку и идут по очереди.
4165
+ *
4166
+ * Внутри процесса этого достаточно. Несколько процессов, пишущих в одну запись,
4167
+ * по-прежнему затирают друг друга — как и несколько экземпляров этого адаптера,
4168
+ * направленных на один источник в одном процессе.
4169
+ */
4170
+ declare function createRecordMultiStorage(source: RecordStorageSource): MultiTokenStorage;
4171
+
4172
+ /** Как аккаунты делят между собой очередь запросов. */
4173
+ type RateLimitScope = 'account' | 'shared';
4174
+ /**
4175
+ * Опции конструктора {@link ItdAccounts}.
4176
+ *
4177
+ * Всё, что понимает `ItdClient`, кроме `auth` и `deviceId`: они у каждого аккаунта свои
4178
+ * и задаются в {@link ItdAccounts.addAccount}. Обычный `TokenStorage` клиента здесь заменён
4179
+ * общей опцией {@link ItdAccountsOptions.storage} типа {@link MultiTokenStorage}; контейнер
4180
+ * сам выдаёт каждому клиенту изолированный срез по имени. Общий `deviceId` особенно вреден —
4181
+ * сервер различает по нему записи в списке сессий, и один на всех сложил бы все аккаунты
4182
+ * в одну.
4183
+ */
4184
+ interface ItdAccountsOptions extends Omit<ItdClientOptions, 'auth' | 'storage' | 'deviceId'> {
4185
+ /** Общее хранилище сессий всех аккаунтов. По умолчанию {@link MemoryMultiTokenStorage}. */
4186
+ storage?: MultiTokenStorage | undefined;
4187
+ /** Плагины, подключаемые каждому аккаунту, в том числе добавленному позже. */
4188
+ plugins?: readonly ItdPlugin[] | undefined;
4189
+ /**
4190
+ * Как делить очередь запросов. По умолчанию `'account'` — своя у каждого.
4191
+ *
4192
+ * Лимиты итд.com считаются по аккаунту, а при работе через разные прокси общая очередь
4193
+ * только мешает. Она нужна в другом случае: когда все аккаунты сидят на одном IP
4194
+ * и упираются в ограничение по адресу, — тогда `'shared'` разводит их запросы во времени
4195
+ * все разом, а не поаккаунтно.
4196
+ *
4197
+ * Настройки самой очереди берутся из общей опции `rateLimit`. Личный объект `rateLimit`
4198
+ * в этом режиме запрещён, потому что не может изменить уже созданную очередь;
4199
+ * `rateLimit: false` у отдельного аккаунта выводит его из неё.
4200
+ */
4201
+ rateLimitScope?: RateLimitScope | undefined;
4202
+ }
4203
+ /**
4204
+ * Настройки одного аккаунта. Общее мультихранилище задаёт контейнер, а аккаунт получает
4205
+ * свой срез автоматически; остальное — как у `ItdClient`.
4206
+ *
4207
+ * При `rateLimitScope: 'shared'` объект `rateLimit` задаётся только контейнеру; аккаунту
4208
+ * разрешено передать `false`, чтобы не ставить его запросы в общую очередь.
4209
+ */
4210
+ type AddAccountOptions = Omit<ItdClientOptions, 'storage'>;
4211
+ /** Что можно уточнить при удалении аккаунта. */
4212
+ interface RemoveAccountOptions {
4213
+ /**
4214
+ * Удалить и сохранённую сессию. По умолчанию `false` — аккаунт убирается только
4215
+ * из памяти, а его токены остаются в хранилище и переживут перезапуск.
4216
+ */
4217
+ forget?: boolean | undefined;
4218
+ }
4219
+ /**
4220
+ * События авторизации всех аккаунтов сразу.
4221
+ *
4222
+ * Те же, что у одиночного клиента, плюс имя аккаунта: подписка на контейнер избавляет
4223
+ * от нужды вешать обработчик на каждого.
4224
+ */
4225
+ interface AccountEvents {
4226
+ /** Токен получен или обновлён. */
4227
+ tokens: {
4228
+ account: string;
4229
+ accessToken: string;
4230
+ };
4231
+ /** Выполнен вход. */
4232
+ signIn: {
4233
+ account: string;
4234
+ accessToken: string;
4235
+ };
4236
+ /** Сессия очищена — вручную или из-за неудачного обновления. */
4237
+ signOut: {
4238
+ account: string;
4239
+ };
4240
+ /** Обновить сессию не удалось; дальнейшие запросы этого аккаунта будут падать с 401. */
4241
+ authError: {
4242
+ account: string;
4243
+ error: unknown;
4244
+ };
4245
+ }
4246
+ /**
4247
+ * Скрытые параметры конструктора — не часть публичного API.
4248
+ *
4249
+ * Через них точка входа `itd-api/node` подставляет свою фабрику клиентов, чтобы аккаунты
4250
+ * умели читать файлы с диска.
4251
+ *
4252
+ * @internal
4253
+ */
4254
+ interface ItdAccountsInternals {
4255
+ createClient?: ((options: ItdClientOptions, internals: ItdClientInternals) => ItdClient) | undefined;
4256
+ }
4257
+ /**
4258
+ * Несколько аккаунтов итд.com в одном месте.
4259
+ *
4260
+ * Контейнер именованных `ItdClient`: каждый аккаунт получает собственный токен, cookie
4261
+ * и `deviceId`, а сессии всех складываются в одно хранилище — обычно в один файл, а не
4262
+ * в десяток. Имя аккаунта выбираете вы; сервер о нём ничего не знает.
4263
+ *
4264
+ * @example Бот на нескольких аккаунтах
4265
+ * ```ts
4266
+ * import { ItdAccounts, FileMultiTokenStorage } from 'itd-api/node';
4267
+ *
4268
+ * await using accounts = new ItdAccounts({
4269
+ * storage: new FileMultiTokenStorage('./.itd-sessions.json'),
4270
+ * rateLimit: { concurrency: 4 },
4271
+ * });
4272
+ *
4273
+ * // Восстанавливаем тех, кто уже входил раньше: токен возьмётся из хранилища.
4274
+ * await accounts.restore();
4275
+ *
4276
+ * if (!accounts.has('kiow')) {
4277
+ * accounts.addAccount('kiow', { auth: { email, password, getTurnstileToken } });
4278
+ * }
4279
+ *
4280
+ * await accounts.account('kiow').posts.create({ content: 'привет' });
4281
+ *
4282
+ * for (const [name, itd] of accounts) {
4283
+ * console.log(name, await itd.getUserId());
4284
+ * }
4285
+ * ```
4286
+ */
4287
+ declare class ItdAccounts {
4288
+ #private;
4289
+ constructor(options?: ItdAccountsOptions, internals?: ItdAccountsInternals);
4290
+ /** Общее хранилище сессий — то же, что передано опцией `storage`. */
4291
+ get storage(): MultiTokenStorage;
4292
+ /** Сколько аккаунтов заведено. */
4293
+ get size(): number;
4294
+ /** Имена заведённых аккаунтов в порядке добавления. */
4295
+ names(): string[];
4296
+ /** Заведён ли аккаунт с таким именем. */
4297
+ has(name: string): boolean;
4298
+ /**
4299
+ * Заводит аккаунт.
4300
+ *
4301
+ * Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и `realtime()`.
4302
+ * Хранилище ему подставляется само: срез общего по имени аккаунта.
4303
+ *
4304
+ * Опция `auth` не обязательна: когда сессия этого аккаунта уже лежит в хранилище,
4305
+ * токен возьмётся оттуда, а истёкший продлится сам.
4306
+ *
4307
+ * @throws {ItdConfigError} если имя пустое или уже занято
4308
+ *
4309
+ * @example
4310
+ * ```ts
4311
+ * accounts.addAccount('bot', { auth: { email, password, getTurnstileToken } });
4312
+ * accounts.addAccount('reader', { auth: '<accessToken>' });
4313
+ * accounts.addAccount('через-прокси', { fetch: proxyFetch('socks5://…') });
4314
+ * ```
4315
+ */
4316
+ addAccount(name: string, options?: AddAccountOptions): ItdClient;
4317
+ /**
4318
+ * Клиент аккаунта.
4319
+ *
4320
+ * @throws {ItdConfigError} если такого аккаунта нет
4321
+ *
4322
+ * @example
4323
+ * ```ts
4324
+ * await accounts.account('kiow').posts.like(postId);
4325
+ * ```
4326
+ */
4327
+ account(name: string): ItdClient;
4328
+ /**
4329
+ * Поднимает аккаунты, сессии которых уже лежат в хранилище.
4330
+ *
4331
+ * То, ради чего мультихранилище знает свой состав: после перезапуска процесса
4332
+ * ни `auth`, ни капча не нужны — токен, `deviceId` и cookie берутся из сохранённого.
4333
+ * Уже заведённые аккаунты не трогаются. Записи, в которых после выхода остался только
4334
+ * `deviceId`, пропускаются: авторизованной сессии в них уже нет.
4335
+ *
4336
+ * @returns имена добавленных аккаунтов
4337
+ *
4338
+ * @example
4339
+ * ```ts
4340
+ * const restored = await accounts.restore();
4341
+ * console.log(`подняли ${restored.length} аккаунтов без единого входа`);
4342
+ * ```
4343
+ */
4344
+ restore(): Promise<string[]>;
4345
+ /**
4346
+ * Убирает аккаунт: закрывает его клиента и, если попросить, забывает сессию.
4347
+ *
4348
+ * Сетевого запроса не выполняет. Чтобы завершить сессию на сервере, вызовите
4349
+ * `itd.auth.logout()` до удаления.
4350
+ *
4351
+ * @returns `false`, если такого аккаунта и не было
4352
+ */
4353
+ removeAccount(name: string, options?: RemoveAccountOptions): Promise<boolean>;
4354
+ /**
4355
+ * Подключает плагин всем аккаунтам — и заведённым, и будущим.
4356
+ *
4357
+ * @throws {ItdConfigError} если плагин задан неверно или уже подключён
4358
+ *
4359
+ * @example
4360
+ * ```ts
4361
+ * accounts.use(crypt());
4362
+ * ```
4363
+ */
4364
+ use(plugin: ItdPlugin): this;
4365
+ /**
4366
+ * Подписывается на события авторизации всех аккаунтов сразу.
4367
+ *
4368
+ * @returns функция отписки
4369
+ *
4370
+ * @example
4371
+ * ```ts
4372
+ * accounts.on('authError', ({ account }) => console.warn(`${account}: сессия потеряна`));
4373
+ * ```
4374
+ */
4375
+ on<K extends keyof AccountEvents>(event: K, listener: Listener<AccountEvents[K]>): Unsubscribe;
4376
+ /**
4377
+ * Перебор аккаунтов парами «имя — клиент».
4378
+ *
4379
+ * @example
4380
+ * ```ts
4381
+ * for (const [name, itd] of accounts) {
4382
+ * const me = await itd.users.me();
4383
+ * console.log(name, me.nickname);
4384
+ * }
4385
+ * ```
4386
+ */
4387
+ [Symbol.iterator](): IterableIterator<[string, ItdClient]>;
4388
+ /**
4389
+ * Закрывает все аккаунты и останавливает общую очередь.
4390
+ *
4391
+ * Аккаунты остаются в контейнере и работоспособны: новые запросы поднимут всё заново,
4392
+ * но уже созданные потоки уведомлений останутся закрытыми.
4393
+ *
4394
+ * @example
4395
+ * ```ts
4396
+ * await using accounts = new ItdAccounts({ storage });
4397
+ * // …работа…
4398
+ * // close() вызовется сам на выходе из блока
4399
+ * ```
4400
+ */
4401
+ close(): Promise<void>;
4402
+ /** Позволяет использовать контейнер с `await using`. */
4403
+ [Symbol.asyncDispose](): Promise<void>;
4404
+ }
4405
+ /**
4406
+ * Создаёт контейнер аккаунтов.
4407
+ *
4408
+ * То же, что `new ItdAccounts(options)`, — для тех, кому привычнее фабрика.
4409
+ */
4410
+ declare function createAccounts(options?: ItdAccountsOptions): ItdAccounts;
4411
+
3562
4412
  /** Бренд, по которому ошибки библиотеки распознаются надёжнее, чем через `instanceof`. */
3563
4413
  declare const ITD_ERROR: unique symbol;
3564
4414
  /**
@@ -3846,6 +4696,19 @@ type AudioMimeType = (typeof AUDIO_MIME_TYPES)[number];
3846
4696
  declare const ALLOWED_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/gif", "image/webp", "image/avif", "image/heic", "image/heif", "video/mp4", "video/webm", "video/quicktime", "audio/ogg"];
3847
4697
  type AllowedMimeType = (typeof ALLOWED_MIME_TYPES)[number];
3848
4698
 
4699
+ /**
4700
+ * Приводит отметку времени без часового пояса к ISO-8601, считая её временем UTC.
4701
+ *
4702
+ * Строку другого вида возвращает нетронутой.
4703
+ *
4704
+ * @example
4705
+ * ```ts
4706
+ * utcStampToIso('2026-07-23 23:14:25'); // '2026-07-23T23:14:25Z'
4707
+ * utcStampToIso('2026-07-23T23:14:25Z'); // без изменений
4708
+ * ```
4709
+ */
4710
+ declare function utcStampToIso(value: string): string;
4711
+
3849
4712
  /**
3850
4713
  * Собирает текст уведомления на русском.
3851
4714
  *
@@ -3930,4 +4793,29 @@ interface SseTransportOptions {
3930
4793
  idleTimeout?: number;
3931
4794
  }
3932
4795
 
3933
- export { HashtagsResource as $, ALLOWED_MIME_TYPES as A, type BuilderInput as B, type CaptchaCredentials as C, type CreateCommentInput as D, type CreatePollInput as E, type FileReader as F, type CreatePostInput as G, type CreateReportInput as H, type ItdSession as I, type Credentials as J, type CredentialsAuth as K, DEFAULT_BASE_URL as L, DEFAULT_TIMEOUT as M, DEFAULT_USER_AGENT as N, DEVICE_ID_HEADER as O, DetectedRuntime as P, type DwellEntry as Q, type ErrorContextHook as R, type FeedParams as S, type TokenStorage as T, FeedTab as U, type FileInput as V, FilesResource as W, type FollowResult as X, type ForgotPasswordInput as Y, type Hashtag as Z, type HashtagPostsParams as _, ItdClient as a, type PostInput as a$, IMAGE_MIME_TYPES as a0, type ImageMimeType as a1, type InteractionEntry as a2, InteractionType as a3, type IsoDate as a4, ItdAbortError as a5, ItdApiError as a6, type ItdApiErrorInit as a7, ItdApiErrorKind as a8, ItdAuthError as a9, type MyProfile as aA, NOTIFICATION_TYPE_ALIASES as aB, type Notification as aC, type NotificationEvent as aD, type NotificationListParams as aE, type NotificationSettings as aF, NotificationType as aG, NotificationsResource as aH, OAuthProvider as aI, type Page as aJ, type PageState as aK, PaginationMode as aL, Paginator as aM, type PaymentMethod as aN, type Pin as aO, type PinPostResult as aP, type PinsResult as aQ, PlatformResource as aR, type PluginContext as aS, type Poll as aT, PollBuilder as aU, type PollInput as aV, type PollOption as aW, type PollTransportOptions as aX, type Portal as aY, type Post as aZ, PostBuilder as a_, type ItdBuilder as aa, ItdConfigError as ab, ItdConflictError as ac, ItdError as ad, ItdErrorCode as ae, ItdErrorKind as af, type ItdFieldErrors as ag, ItdForbiddenError as ah, ItdNetworkError as ai, ItdNotFoundError as aj, ItdPhoneVerificationError as ak, type ItdPlugin as al, ItdRateLimitError as am, ItdRealtime as an, ItdServerError as ao, ItdTimeoutError as ap, ItdValidationError as aq, LIBRARY_VERSION as ar, type LikeResult as as, LikesVisibility as at, type Listener as au, LocalStorageTokenStorage as av, type Logger as aw, type Loose as ax, MAX_RECONNECT_ATTEMPTS as ay, MemoryTokenStorage as az, type ItdClientOptions as b, VerificationResource as b$, type PostStats as b0, PostsResource as b1, type PrivacySettings as b2, type Profile as b3, type PublicProfile as b4, RECONNECT_BACKOFF as b5, RECONNECT_JITTER as b6, REFRESH_COOKIE as b7, REFRESH_COOKIE_PATH as b8, type RateLimitOptions as b9, SignInStatus as bA, type Span as bB, SpanType as bC, type SseTransportOptions as bD, type Subscription as bE, SubscriptionResource as bF, type SubscriptionState as bG, TURNSTILE_SITE_KEY as bH, type TelemetryOptions as bI, TelemetryResource as bJ, type Transformer as bK, type TransportContext as bL, type TransportEvent as bM, UnauthorizedStreamError 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, VIDEO_MIME_TYPES as b_, type RawRequestOptions as ba, type RealtimeEvents as bb, type RealtimeOptions as bc, RealtimeStatus as bd, type RealtimeTransport as be, RealtimeTransportKind as bf, type ReconnectOptions as bg, type RepliesParams as bh, type Report as bi, ReportBuilder as bj, type ReportInput as bk, ReportReason as bl, ReportTargetType as bm, ReportsResource as bn, type RequestContext as bo, type RequestOptions as bp, type ResetPasswordInput as bq, type ResponseContext as br, type RetryContext as bs, type RetryOptions as bt, RuntimeMode as bu, STREAM_PATH as bv, SearchResource as bw, type SearchResult as bx, type Session as by, type SignInResult as bz, AUDIO_MIME_TYPES as c, type VerificationStatus as c0, type VideoMimeType as c1, ViewReason as c2, ViewSource as c3, WallAccess as c4, canonicalNotificationType as c5, comment as c6, createTokenStorage as c7, formatNotificationText as c8, isBuilder as c9, isItdApiError as ca, isItdAuthError as cb, isItdConflictError as cc, isItdError as cd, isItdForbiddenError as ce, isItdNotFoundError as cf, isItdPhoneVerificationError as cg, isItdRateLimitError as ch, isItdServerError as ci, isItdValidationError as cj, isKnownNotificationType as ck, isMyProfile as cl, normalizeNotification as cm, poll as cn, post as co, readNotificationEvent as cp, readUnreadCountEvent as cq, report as cr, resolveNotificationUrl as cs, toDate as ct, createClient as cu, AUTH_FLAG_COOKIE as d, AUTH_PATHS as e, AccessType as f, type Actor as g, type AllowedMimeType as h, type Announcement as i, type AnnouncementButton as j, type Attachment as k, AttachmentType as l, type AudioMimeType as m, type AuthInput as n, AuthResource as o, type Author as p, type ChangelogEntry as q, type Clan as r, type ClientHooks as s, type Comment as t, CommentBuilder as u, type CommentInput as v, type CommentReplyTo as w, CommentSort as x, type CommentsParams as y, CommentsResource as z };
4796
+ /** Формат результата {@link renderSpans}. */
4797
+ type SpanRenderFormat = 'html' | 'markdown' | 'ansi';
4798
+ interface RenderSpansOptions {
4799
+ /** Формат результата. По умолчанию `html`. */
4800
+ format?: SpanRenderFormat;
4801
+ /** Строит адрес упоминания. `null` или `undefined` отключает ссылку. */
4802
+ mentionUrl?: (username: string) => string | null | undefined;
4803
+ /** Строит адрес хэштега. `null` или `undefined` отключает ссылку. */
4804
+ hashtagUrl?: (tag: string) => string | null | undefined;
4805
+ /**
4806
+ * Префикс HTML-классов. По умолчанию `itd`; пустая строка или `null` отключает классы.
4807
+ *
4808
+ * Например, `app` создаёт `app-mention`, `app-hashtag`, `app-quote` и `app-spoiler`.
4809
+ */
4810
+ classPrefix?: string | null;
4811
+ }
4812
+ /**
4813
+ * Преобразует текст и wire-разметку API в безопасный HTML, Markdown или ANSI.
4814
+ *
4815
+ * Некорректные серверные spans игнорируются либо обрезаются по границам строки. Пересекающиеся
4816
+ * spans разбиваются на независимые сегменты, поэтому HTML остаётся корректно вложенным.
4817
+ * Отсутствующий массив считается пустым; формат по умолчанию — HTML.
4818
+ */
4819
+ declare function renderSpans(content: string, spans?: readonly Span[] | null | undefined, options?: RenderSpansOptions): string;
4820
+
4821
+ export { DetectedRuntime as $, ALLOWED_MIME_TYPES as A, BUILT_IN_SERVICES as B, type CaptchaCredentials as C, type ClientHooks as D, type Comment as E, type FileReader as F, CommentBuilder as G, type CommentInput as H, type ItdSession as I, type CommentReplyTo as J, CommentSort as K, type CommentsParams as L, type MultiTokenStorage as M, CommentsResource as N, type CreateCommentInput as O, type CreatePollInput as P, type CreatePostInput as Q, type CreateReportInput as R, type Credentials as S, type TokenStorage as T, type CredentialsAuth as U, DEFAULT_BASE_URL as V, DEFAULT_STATUS_BASE_URL as W, DEFAULT_TIMEOUT as X, DEFAULT_UPLOAD_TIMEOUT as Y, DEFAULT_USER_AGENT as Z, DEVICE_ID_HEADER as _, ItdAccounts as a, type PageState as a$, type DwellEntry as a0, type ErrorContextHook as a1, type FeedParams as a2, FeedTab as a3, type FileInput as a4, FilesResource as a5, type FollowResult as a6, type ForgotPasswordInput as a7, type Hashtag as a8, type HashtagPostsParams as a9, ItdServerError as aA, ItdTimeoutError as aB, ItdValidationError as aC, LIBRARY_VERSION as aD, type LikeResult as aE, LikesVisibility as aF, type Listener as aG, LocalStorageTokenStorage as aH, type Logger as aI, type Loose as aJ, MAX_RECONNECT_ATTEMPTS as aK, MarkupBuilder as aL, type MarkupContent as aM, type MarkupInput as aN, type MarkupSpan as aO, MemoryMultiTokenStorage as aP, MemoryTokenStorage as aQ, type MyProfile as aR, NOTIFICATION_TYPE_ALIASES as aS, type Notification as aT, type NotificationEvent as aU, type NotificationListParams as aV, type NotificationSettings as aW, NotificationType as aX, NotificationsResource as aY, OAuthProvider as aZ, type Page as a_, HashtagsResource as aa, IMAGE_MIME_TYPES as ab, type ImageMimeType as ac, IncidentKind as ad, type InteractionEntry as ae, InteractionType as af, type IsoDate as ag, ItdAbortError as ah, ItdApiError as ai, type ItdApiErrorInit as aj, ItdApiErrorKind as ak, ItdAuthError as al, type ItdBuilder as am, ItdConfigError as an, ItdConflictError as ao, ItdError as ap, ItdErrorCode as aq, ItdErrorKind as ar, type ItdFieldErrors as as, ItdForbiddenError as at, ItdNetworkError as au, ItdNotFoundError as av, ItdPhoneVerificationError as aw, type ItdPlugin as ax, ItdRateLimitError as ay, ItdRealtime as az, type ItdAccountsOptions as b, type ServiceDefinition as b$, PaginationMode as b0, Paginator as b1, type PaginatorOptions as b2, type PaymentMethod as b3, type Pin as b4, type PinPostResult as b5, type PinsResult as b6, PlatformResource as b7, type PlatformStatus as b8, type PluginContext as b9, type RealtimeEvents as bA, type RealtimeOptions as bB, RealtimeStatus as bC, type RealtimeTransport as bD, RealtimeTransportKind as bE, type ReconnectOptions as bF, type RecordStorageSource as bG, type RemoveAccountOptions as bH, type RenderSpansOptions as bI, type RepliesParams as bJ, type Report as bK, ReportBuilder as bL, type ReportInput as bM, ReportReason as bN, ReportTargetType as bO, ReportsResource as bP, type RequestContext as bQ, type RequestOptions as bR, type ResetPasswordInput as bS, type ResponseContext as bT, type RetryContext as bU, type RetryOptions as bV, RuntimeMode as bW, STATUS_SERVICE as bX, STREAM_PATH as bY, SearchResource as bZ, type SearchResult as b_, type Poll as ba, PollBuilder as bb, type PollInput as bc, type PollOption as bd, type PollTransportOptions as be, type Portal as bf, type Post as bg, PostBuilder as bh, type PostInput as bi, type PostStats as bj, type PostUpdateInput as bk, PostsResource as bl, type PrivacySettings as bm, type Profile as bn, type PublicProfile as bo, type QueryParams as bp, type QueryValue as bq, RECONNECT_BACKOFF as br, RECONNECT_JITTER as bs, REFRESH_COOKIE as bt, REFRESH_COOKIE_PATH as bu, REQUEST_OPTION_KEYS as bv, type RateLimitOptions as bw, type RateLimitScope as bx, type RawRequestOptions as by, type RealtimeDeps as bz, ItdClient as c, mapPage as c$, ServiceRegistry as c0, ServiceState as c1, type ServiceStatus as c2, type Session as c3, type SignInResult as c4, SignInStatus as c5, type Span as c6, type SpanRenderFormat as c7, SpanType as c8, type SseTransportOptions as c9, VIDEO_MIME_TYPES as cA, VerificationResource as cB, type VerificationStatus as cC, type VideoMimeType as cD, ViewReason as cE, ViewSource as cF, WallAccess as cG, autoSpans as cH, canonicalNotificationType as cI, comment as cJ, createMultiTokenStorage as cK, createRecordMultiStorage as cL, createTokenStorage as cM, formatNotificationText as cN, isBuilder as cO, isItdApiError as cP, isItdAuthError as cQ, isItdConflictError as cR, isItdError as cS, isItdForbiddenError as cT, isItdNotFoundError as cU, isItdPhoneVerificationError as cV, isItdRateLimitError as cW, isItdServerError as cX, isItdValidationError as cY, isKnownNotificationType as cZ, isMyProfile as c_, type StatusDay as ca, type StatusIncidentLine as cb, type Subscription as cc, SubscriptionResource as cd, type SubscriptionState as ce, TURNSTILE_SITE_KEY as cf, type TelemetryOptions as cg, TelemetryResource as ch, type TextMarkup as ci, type Transformer as cj, type TransportContext as ck, type TransportEvent as cl, UnauthorizedStreamError as cm, type Unsubscribe as cn, type UpdateNotificationSettingsInput as co, type UpdatePostInput as cp, type UpdatePrivacyInput as cq, type UpdateProfileInput as cr, type UploadOptions as cs, type UploadedFile as ct, type UserId as cu, type UserListParams as cv, type UserPostsParams as cw, type UserRef as cx, type UserSummary as cy, UsersResource as cz, type ItdClientOptions as d, markup as d0, normalizeNotification as d1, poll as d2, post as d3, readNotificationEvent as d4, readUnreadCountEvent as d5, renderSpans as d6, report as d7, resolveNotificationUrl as d8, scopedTokenStorage as d9, statusDays as da, toDate as db, utcStampToIso as dc, createAccounts as dd, createClient as de, type ItdClientInternals as e, AUDIO_MIME_TYPES as f, AUTH_FLAG_COOKIE as g, AUTH_PATHS as h, AccessType as i, type AccountEvents as j, type Actor as k, type AddAccountOptions as l, type AllowedMimeType as m, type Announcement as n, type AnnouncementButton as o, type Attachment as p, AttachmentType as q, type AudioMimeType as r, type AuthEvents as s, type AuthInput as t, AuthResource as u, type Author as v, type AutoSpansOptions as w, type BuilderInput as x, type ChangelogEntry as y, type Clan as z };