itd-api 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/events/index.cjs +2 -2
  2. package/dist/events/index.d.cts +2 -2
  3. package/dist/events/index.d.ts +2 -2
  4. package/dist/events/index.js +2 -2
  5. package/dist/index.cjs +105 -58
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +160 -132
  8. package/dist/index.d.ts +160 -132
  9. package/dist/index.js +105 -58
  10. package/dist/index.js.map +1 -1
  11. package/dist/rest/index.cjs +2 -2
  12. package/dist/rest/index.d.cts +2 -2
  13. package/dist/rest/index.d.ts +2 -2
  14. package/dist/rest/index.js +2 -2
  15. package/dist/shared/{events-BNGjJVI3.d.cts → events-CZXU16qU.d.cts} +2 -2
  16. package/dist/shared/{events-B0Wtn4cF.js → events-Cipe-Fm9.js} +2 -2
  17. package/dist/shared/{events-B0Wtn4cF.js.map → events-Cipe-Fm9.js.map} +1 -1
  18. package/dist/shared/{events-CJO6q93Z.d.ts → events-D3CSlpXR.d.ts} +2 -2
  19. package/dist/shared/{events-BB791aPk.cjs → events-MtRNe_TF.cjs} +2 -2
  20. package/dist/shared/{events-BB791aPk.cjs.map → events-MtRNe_TF.cjs.map} +1 -1
  21. package/dist/shared/{render-DC5nvqcP.d.ts → render-Cs6AOXbD.d.ts} +2 -2
  22. package/dist/shared/{render-CkhvH_yh.js → render-CubTTof_.js} +2 -2
  23. package/dist/shared/{render-CkhvH_yh.js.map → render-CubTTof_.js.map} +1 -1
  24. package/dist/shared/{render-C1A57W-5.d.cts → render-bZwOk3CC.d.cts} +2 -2
  25. package/dist/shared/{render-BimHjOhw.cjs → render-dscKp9CC.cjs} +2 -2
  26. package/dist/shared/{render-BimHjOhw.cjs.map → render-dscKp9CC.cjs.map} +1 -1
  27. package/dist/shared/{url-BA5hEwj2.js → url-CntTEAxu.js} +4 -4
  28. package/dist/shared/{url-BA5hEwj2.js.map → url-CntTEAxu.js.map} +1 -1
  29. package/dist/shared/{url-CDVgMIq5.cjs → url-DgTYZN_v.cjs} +4 -4
  30. package/dist/shared/{url-CDVgMIq5.cjs.map → url-DgTYZN_v.cjs.map} +1 -1
  31. package/dist/shared/{url-DkMueFAT.d.cts → url-Dum7r_FT.d.cts} +4 -4
  32. package/dist/shared/{url-DkMueFAT.d.ts → url-Dum7r_FT.d.ts} +4 -4
  33. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { A as ViewReason, B as BUCKET_LIMITS, C as ItdErrorCode, D as ReportTargetType, E as ReportReason, F as defineBuiltInOperation, G as pickArray, H as createClientRuntime, I as isBuiltInOperationId, L as operationBucket, M as WallAccess, N as ITD_CATALOG, O as ServiceState, P as OPERATIONS, R as operationMethod, S as InteractionType, T as NotificationType, U as createApiError, V as DEFAULT_RATE_LIMIT_BUCKET, X as RetrySafety, Y as pickString, _ as CaptchaType, at as orderPluginDefinitions, b as FeedTab, c as normalizeNotification, ct as STATUS_SERVICE, d as canonicalNotificationType, dt as isRecord, et as createRequestAbortScope, f as isKnownNotificationType, ft as requireOptionalBoolean, g as CaptchaField, gt as installAsyncDisposeFallback, h as CaptchaChoice, ht as systemClock, it as assertPluginRemovable, j as ViewSource, k as SpanType, lt as resolveRateLimit, m as AttachmentType, n as formatNotificationText, o as passthroughOperation, ot as DEFAULT_BASE_URL, p as AccessType, pt as RateLimitPacing, rt as RequestQueuePool, s as voidOperation, t as resolveNotificationUrl, tt as requestAbortError, ut as LIBRARY_VERSION, v as CommentSort, w as LikesVisibility, x as IncidentKind, y as EventChannelStatus, z as operationRetrySafety } from "./shared/url-BA5hEwj2.js";
1
+ import { A as ViewReason, B as BUCKET_LIMITS, C as ItdErrorCode, D as ReportTargetType, E as ReportReason, F as defineBuiltInOperation, G as pickArray, H as createClientRuntime, I as isBuiltInOperationId, L as operationBucket, M as WallAccess, N as ITD_CATALOG, O as ServiceState, P as OPERATIONS, R as operationMethod, S as InteractionType, T as NotificationType, U as createApiError, V as DEFAULT_RATE_LIMIT_BUCKET, X as RetrySafety, Y as pickString, _ as CaptchaType, at as orderPluginDefinitions, b as FeedTab, c as normalizeNotification, ct as STATUS_SERVICE, d as canonicalNotificationType, dt as isRecord, et as createRequestAbortScope, f as isKnownNotificationType, ft as requireOptionalBoolean, g as CaptchaField, gt as installAsyncDisposeFallback, h as CaptchaChoice, ht as systemClock, it as assertPluginRemovable, j as ViewSource, k as SpanType, lt as resolveRateLimit, m as AttachmentType, n as formatNotificationText, o as passthroughOperation, ot as DEFAULT_BASE_URL, p as AccessType, pt as RateLimitPacing, rt as RequestQueuePool, s as voidOperation, t as resolveNotificationUrl, tt as requestAbortError, ut as LIBRARY_VERSION, v as CommentSort, w as LikesVisibility, x as IncidentKind, y as EventChannelStatus, z as operationRetrySafety } from "./shared/url-CntTEAxu.js";
2
2
  import { A as isItdStateError, C as isItdError, D as isItdPhoneVerificationError, E as isItdNotFoundError, O as isItdRateLimitError, S as isItdConflictError, T as isItdForbiddenError, _ as ItdStateError, a as ItdConfigError, b as isItdApiError, c as ItdErrorKind, d as ItdForbiddenError, f as ItdNetworkError, g as ItdServerError, h as ItdRateLimitError, i as ItdAuthError, j as isItdValidationError, k as isItdServerError, l as ItdFileError, m as ItdPhoneVerificationError, n as ItdApiError, o as ItdConflictError, p as ItdNotFoundError, r as ItdApiErrorKind, s as ItdError, t as ItdAbortError, u as ItdFileErrorReason, v as ItdTimeoutError, w as isItdFileError, x as isItdAuthError, y as ItdValidationError } from "./shared/errors-GI10kZxk.js";
3
3
  import { c as REFRESH_COOKIE, h as joinUrl, l as REFRESH_COOKIE_PATH, s as CookieJar, u as isSerializedCookieEntry, v as RuntimeMode, y as createDeviceId } from "./shared/redact-DJV0XM3n.js";
4
- import { A as utcStampToIso, C as AUDIO_MIME_TYPES, D as isBuilder, E as comment, F as mapPage, I as createFeatureHost, L as ExtensibleOperationCatalog, M as BaseResource, N as PaginationMode, O as createStatusFeature, P as Paginator, S as ALLOWED_MIME_TYPES, T as VIDEO_MIME_TYPES, _ as poll, a as createShopOrderAccessStorage, b as parseHtml, c as ShopOrderStatus, d as isMyProfile, f as fromStream, g as post, h as report, i as MemoryShopOrderAccessStorage, j as NotificationsResource, k as toDate, l as ShopProductCategory, m as createResources, n as renderSpans, o as statusDays, p as fromUrl, r as createShopFeature, s as ShopConsentKind, t as SpanRenderFormat, u as ShopProductStatus, v as autoSpans, w as IMAGE_MIME_TYPES, x as parseMarkdown, y as markup } from "./shared/render-CkhvH_yh.js";
4
+ import { A as utcStampToIso, C as AUDIO_MIME_TYPES, D as isBuilder, E as comment, F as mapPage, I as createFeatureHost, L as ExtensibleOperationCatalog, M as BaseResource, N as PaginationMode, O as createStatusFeature, P as Paginator, S as ALLOWED_MIME_TYPES, T as VIDEO_MIME_TYPES, _ as poll, a as createShopOrderAccessStorage, b as parseHtml, c as ShopOrderStatus, d as isMyProfile, f as fromStream, g as post, h as report, i as MemoryShopOrderAccessStorage, j as NotificationsResource, k as toDate, l as ShopProductCategory, m as createResources, n as renderSpans, o as statusDays, p as fromUrl, r as createShopFeature, s as ShopConsentKind, t as SpanRenderFormat, u as ShopProductStatus, v as autoSpans, w as IMAGE_MIME_TYPES, x as parseMarkdown, y as markup } from "./shared/render-CubTTof_.js";
5
5
  import { c as FileTransferMode } from "./shared/options-i4LrKfqx.js";
6
- import { _ as Emitter, a as WebSocketTransport, c as NotificationEventsTransport, d as NotificationUpdateOrigin, f as NotificationUpdateType, g as runEventMiddleware, h as UnauthorizedStreamError, i as createNotificationEventsClient, l as resolveNotificationEventsOptions, m as EventChannel, n as EventRouter, o as EVENT_OPERATIONS, p as createParser, r as NotificationEventsClient, s as NotificationEvents, t as EventComposer, u as setNotificationEventsConnectGuard, v as reportListenerError } from "./shared/events-B0Wtn4cF.js";
6
+ import { _ as Emitter, a as WebSocketTransport, c as NotificationEventsTransport, d as NotificationUpdateOrigin, f as NotificationUpdateType, g as runEventMiddleware, h as UnauthorizedStreamError, i as createNotificationEventsClient, l as resolveNotificationEventsOptions, m as EventChannel, n as EventRouter, o as EVENT_OPERATIONS, p as createParser, r as NotificationEventsClient, s as NotificationEvents, t as EventComposer, u as setNotificationEventsConnectGuard, v as reportListenerError } from "./shared/events-Cipe-Fm9.js";
7
7
  import { a as isEnumerableKeyValueStore, i as createRecordKeyValueStore, o as withCodec, r as createKeyValueStore, s as withNamespace, t as MemoryKeyValueStore } from "./shared/key-value-store-Bt2PSMxY.js";
8
8
  import { n as copySession, r as createTokenStorage, t as MemoryTokenStorage } from "./shared/storage-DiDe0PZM.js";
9
9
  import { a as scopedTokenStorage, i as isRestorableSession, n as controlledTokenStorage, r as createMultiTokenStorage, t as MemoryMultiTokenStorage } from "./shared/multi-storage-C3Ngtop1.js";
@@ -91,21 +91,36 @@ function requireCaptchaText(value, field) {
91
91
  if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) throw new ItdConfigError(`${field} должен быть непустой строкой`);
92
92
  }
93
93
  /**
94
- * Проверяет форму блока `auth.captcha`.
94
+ * Проверяет форму источника токена и приводит его к объекту.
95
95
  *
96
- * Отсутствие источника токена ошибкой не считается: до входа по паролю дело может и не
97
- * дойти. О нехватке сообщит {@link resolveCaptchaBody} в момент входа.
96
+ * Сам объект сохраняется как есть: `getToken` вызывается на нём, чтобы источнику остался
97
+ * доступен собственный `this`.
98
98
  *
99
99
  * @throws {ItdConfigError} при некорректных значениях
100
100
  */
101
- function validateCaptchaOptions(captcha) {
101
+ function resolveCaptchaSolver(captcha) {
102
+ if (captcha === void 0) return void 0;
103
+ if (typeof captcha === "function") return { getToken: captcha };
104
+ if (!isRecord(captcha)) throw new ItdConfigError("captcha должен быть функцией либо объектом с getToken");
105
+ const { getToken, type, field } = captcha;
106
+ if (typeof getToken !== "function") throw new ItdConfigError("captcha.getToken должен быть функцией");
107
+ requireCaptchaText(type, "captcha.type");
108
+ requireCaptchaText(field, "captcha.field");
109
+ return captcha;
110
+ }
111
+ /**
112
+ * Проверяет форму готового токена капчи.
113
+ *
114
+ * @param path имя опции или аргумента для текста ошибки
115
+ * @throws {ItdConfigError} если токен или его тип не указаны
116
+ */
117
+ function validateCaptchaToken(captcha, path) {
102
118
  if (captcha === void 0) return;
103
- if (!isRecord(captcha)) throw new ItdConfigError("auth.captcha должен быть объектом");
104
- const { type, token, getToken, field } = captcha;
105
- requireCaptchaText(type, "auth.captcha.type");
106
- requireCaptchaText(token, "auth.captcha.token");
107
- requireCaptchaText(field, "auth.captcha.field");
108
- if (getToken !== void 0 && typeof getToken !== "function") throw new ItdConfigError("auth.captcha.getToken должен быть функцией");
119
+ if (!isRecord(captcha)) throw new ItdConfigError(`${path} должен быть объектом { type, token }`);
120
+ const { type, token, field } = captcha;
121
+ if (typeof token !== "string" || token.trim() === "") throw new ItdConfigError(`${path}.token должен быть непустой строкой`);
122
+ if (typeof type !== "string" || type.trim() === "") throw new ItdConfigError(`${path}.type должен быть непустой строкой`);
123
+ requireCaptchaText(field, `${path}.field`);
109
124
  }
110
125
  /** Разбирает ответ `GET /api/v1/auth/captcha/provider` для автоматического входа. */
111
126
  function readCaptchaProvider(body) {
@@ -122,43 +137,40 @@ function readCaptchaProvider(body) {
122
137
  function resolveField(type, configured) {
123
138
  const field = configured ?? CAPTCHA_FIELDS[type];
124
139
  if (field) return field;
125
- throw new ItdConfigError(`Неизвестно, в каком поле сервер ждёт токен капчи «${type}». Передайте auth.captcha.field либо оставьте auth.captcha.type = ${CaptchaChoice.Auto}, чтобы поле назвал сервер.`);
140
+ throw new ItdConfigError(`Неизвестно, в каком поле сервер ждёт токен капчи «${type}». Передайте captcha.field либо оставьте captcha.type = ${CaptchaChoice.Auto}, чтобы поле назвал сервер.`);
126
141
  }
127
- /** Спрашивает свежий токен, затем берёт разовый. */
128
- async function resolveToken(captcha, type) {
129
- const token = await captcha.getToken?.(type) || captcha.token;
130
- if (token) return token;
131
- throw new ItdConfigError(`Источник капчи не вернул токен для провайдера «${type}». Проверьте auth.captcha.getToken.`);
142
+ /** Спрашивает токен у источника и убеждается, что он непустой. */
143
+ async function requireSolvedToken(solver, type) {
144
+ const token = await solver.getToken(type);
145
+ if (typeof token === "string" && token.trim() !== "") return token;
146
+ throw new ItdConfigError(`Источник капчи не вернул токен для провайдера «${type}». Проверьте captcha.getToken.`);
132
147
  }
133
148
  /**
134
- * Готовит фрагмент тела запроса с токеном капчи.
149
+ * Готовит фрагмент тела запроса, взяв токен у источника.
135
150
  *
136
151
  * При `CaptchaChoice.Auto` провайдер спрашивается у сервера — тогда и тип виджета,
137
- * и имя поля приходят оттуда. Явно названный тип решается без этого запроса.
152
+ * и имя поля приходят оттуда. Явно названный тип обходится без этого запроса.
138
153
  *
139
154
  * @param askProvider как спросить активного провайдера; вызывается, только если это нужно
140
- * @throws {ItdConfigError} если капча не настроена или токен не получен
155
+ * @throws {ItdConfigError} если токен получить не удалось
141
156
  */
142
- async function resolveCaptchaBody(captcha, askProvider) {
143
- if (!captcha?.token && !captcha?.getToken) throw new ItdConfigError("Вход по email и паролю требует токен капчи. Передайте auth.captcha.getToken источник свежего токена — либо разовый auth.captcha.token. В Node токен получает пакет @itd-api/captcha.");
144
- const choice = captcha.type ?? CaptchaChoice.Auto;
157
+ async function solveCaptchaBody(solver, askProvider) {
158
+ const choice = solver.type ?? CaptchaChoice.Auto;
145
159
  if (choice === CaptchaChoice.Auto) {
146
160
  const { provider, field } = await askProvider();
147
- return { [field]: await resolveToken(captcha, provider) };
161
+ return { [field]: await requireSolvedToken(solver, provider) };
148
162
  }
149
- return { [resolveField(choice, captcha.field)]: await resolveToken(captcha, choice) };
163
+ return { [resolveField(choice, solver.field)]: await requireSolvedToken(solver, choice) };
150
164
  }
151
165
  /**
152
- * Готовит фрагмент тела для разового вызова с готовым доказательством.
166
+ * Готовит фрагмент тела из готового токена.
153
167
  *
154
- * Поле берётся из самого доказательства либо из умолчания провайдера; запроса к серверу нет.
168
+ * Поле берётся из самого токена либо из умолчания провайдера; запроса к серверу нет.
155
169
  *
156
- * @throws {ItdConfigError} если доказательство неполное
170
+ * @throws {ItdConfigError} если токен или его тип не указаны
157
171
  */
158
172
  function captchaBody(captcha) {
159
- if (!isRecord(captcha)) throw new ItdConfigError("captcha должен быть объектом { type, token }");
160
- if (typeof captcha.token !== "string" || captcha.token.trim() === "") throw new ItdConfigError("captcha.token должен быть непустой строкой");
161
- if (typeof captcha.type !== "string" || captcha.type.trim() === "") throw new ItdConfigError("captcha.type должен быть непустой строкой");
173
+ validateCaptchaToken(captcha, "captcha");
162
174
  return { [resolveField(captcha.type, captcha.field)]: captcha.token };
163
175
  }
164
176
  //#endregion
@@ -236,7 +248,7 @@ function validateAuth(auth) {
236
248
  const { email, password, captcha } = auth;
237
249
  if (typeof email !== "string" || email.trim() === "") throw new ItdConfigError("auth.email должен быть непустой строкой");
238
250
  if (typeof password !== "string" || password === "") throw new ItdConfigError("auth.password должен быть непустой строкой");
239
- validateCaptchaOptions(captcha);
251
+ validateCaptchaToken(captcha, "auth.captcha");
240
252
  return { ...auth };
241
253
  }
242
254
  throw new ItdConfigError("auth не распознан. Ожидается строка с accessToken либо объект { accessToken }, { email, password } или { getToken }");
@@ -265,6 +277,7 @@ function resolveSessionConfig(options, runtime) {
265
277
  return {
266
278
  ...runtime,
267
279
  auth: validateAuth(options.auth),
280
+ captcha: resolveCaptchaSolver(options.captcha),
268
281
  storage: resolveStorage(options.storage),
269
282
  deviceId: options.deviceId,
270
283
  autoRefresh: options.autoRefresh ?? true,
@@ -436,6 +449,8 @@ var AuthManager = class {
436
449
  */
437
450
  #authEpoch = 0;
438
451
  #externalReadGeneration = 0;
452
+ /** Разовый токен из `auth.captcha` тратится ровно на один запрос. */
453
+ #configCaptchaSpent = false;
439
454
  #disposed = false;
440
455
  #persistence = Promise.resolve();
441
456
  constructor(config, http, jar, hooks = {}) {
@@ -963,6 +978,34 @@ var AuthManager = class {
963
978
  this.#signingIn = promise;
964
979
  return promise;
965
980
  }
981
+ /**
982
+ * Готовит фрагмент тела с токеном капчи для запроса, который её требует.
983
+ *
984
+ * Источники в порядке убывания приоритета: токен, переданный в сам вызов; разовый токен
985
+ * из `auth.captcha`; опция `captcha`. Когда ни одного нет, фрагмент пустой: нужна ли
986
+ * капча этому запросу, решает сервер.
987
+ *
988
+ * @param explicit готовый токен, переданный в вызов
989
+ * @throws {ItdConfigError} если токен неполный или источник его не вернул
990
+ */
991
+ async captchaBody(explicit) {
992
+ if (explicit) return captchaBody(explicit);
993
+ const configured = this.#takeConfiguredCaptcha();
994
+ if (configured) return captchaBody(configured);
995
+ const solver = this.#config.captcha;
996
+ if (solver) return solveCaptchaBody(solver, () => this.#resolveCaptchaProvider());
997
+ this.#config.logger?.debug("Запрос уходит без токена капчи: не передан аргумент captcha и не задана опция captcha");
998
+ return {};
999
+ }
1000
+ /** Отдаёт разовый токен из `auth.captcha`, помечая его потраченным. */
1001
+ #takeConfiguredCaptcha() {
1002
+ if (this.#configCaptchaSpent) return void 0;
1003
+ const auth = this.#config.auth;
1004
+ const captcha = isRecord(auth) ? auth.captcha : void 0;
1005
+ if (!captcha) return void 0;
1006
+ this.#configCaptchaSpent = true;
1007
+ return captcha;
1008
+ }
966
1009
  /** Спрашивает у сервера активного провайдера и поле, в котором он ждёт токен. */
967
1010
  async #resolveCaptchaProvider() {
968
1011
  return readCaptchaProvider(await this.#http.execute(AUTH_CAPTCHA_PROVIDER, {
@@ -972,7 +1015,7 @@ var AuthManager = class {
972
1015
  }));
973
1016
  }
974
1017
  async #performSignIn(credentials, revision) {
975
- const captcha = await resolveCaptchaBody(credentials.captcha, () => this.#resolveCaptchaProvider());
1018
+ const captcha = await this.captchaBody();
976
1019
  const flowCookies = new CookieJar();
977
1020
  const result = await this.#http.execute(AUTH_SIGN_IN, {
978
1021
  path: AUTH_PATHS.signIn,
@@ -1153,7 +1196,8 @@ var AuthResource = class extends BaseResource {
1153
1196
  if (existing) return existing;
1154
1197
  const flow = {
1155
1198
  cookies: this.#auth.createCookieFlow(false),
1156
- expiresAt: void 0
1199
+ expiresAt: void 0,
1200
+ captchaRequired: false
1157
1201
  };
1158
1202
  this.#qrFlows.set(qrId, flow);
1159
1203
  return flow;
@@ -1204,7 +1248,8 @@ var AuthResource = class extends BaseResource {
1204
1248
  if (typeof result.qrId === "string" && result.qrId !== "") {
1205
1249
  const flow = {
1206
1250
  cookies,
1207
- expiresAt: void 0
1251
+ expiresAt: void 0,
1252
+ captchaRequired: false
1208
1253
  };
1209
1254
  this.#touchQrFlow(flow, result.expiresIn);
1210
1255
  this.#qrFlows.set(result.qrId, flow);
@@ -1214,20 +1259,21 @@ var AuthResource = class extends BaseResource {
1214
1259
  /**
1215
1260
  * Проверяет QR-сессию и завершает вход после подтверждения на другом устройстве.
1216
1261
  *
1217
- * До подтверждения возвращает промежуточный статус. Если сервер потребовал капчу,
1218
- * повторите вызов с `captcha` тип берётся из {@link captchaProvider}.
1219
- * Полученный `accessToken` автоматически сохраняется в клиенте.
1262
+ * До подтверждения возвращает промежуточный статус. Токен капчи добывается, только когда
1263
+ * сервер о ней попросил — статусом `captcha_required` на прошлой проверке, либо когда
1264
+ * передан в сам вызов. Полученный `accessToken` автоматически сохраняется в клиенте.
1220
1265
  */
1221
1266
  async claimQrLogin(input, options = {}) {
1222
1267
  const revision = this.#auth.revision();
1223
1268
  const flow = this.#qrFlow(input.qrId);
1224
1269
  try {
1225
1270
  const { captcha, ...rest } = input;
1271
+ const needsCaptcha = captcha !== void 0 || flow.captchaRequired;
1226
1272
  const result = await this.http.execute(AUTH_QR_CLAIM, {
1227
1273
  path: AUTH_PATHS.qrClaim,
1228
1274
  body: {
1229
1275
  ...rest,
1230
- ...captcha ? captchaBody(captcha) : {}
1276
+ ...needsCaptcha ? await this.#auth.captchaBody(captcha) : {}
1231
1277
  },
1232
1278
  skipAuth: true,
1233
1279
  skipAuthRefresh: true,
@@ -1235,6 +1281,7 @@ var AuthResource = class extends BaseResource {
1235
1281
  ...options
1236
1282
  });
1237
1283
  this.#touchQrFlow(flow, result.expiresIn);
1284
+ if (result.status === QrLoginStatus.CaptchaRequired) flow.captchaRequired = true;
1238
1285
  if (result.status === QrLoginStatus.Authorized) {
1239
1286
  await this.#auth.commitAuthFlow(result.accessToken, flow.cookies, revision);
1240
1287
  this.#qrFlows.delete(input.qrId);
@@ -1270,13 +1317,13 @@ var AuthResource = class extends BaseResource {
1270
1317
  *
1271
1318
  * @returns `flowToken`, который нужно передать в {@link verifyOtp}
1272
1319
  */
1273
- signUp(credentials, options = {}) {
1320
+ async signUp(credentials, options = {}) {
1274
1321
  const { captcha, ...rest } = credentials;
1275
1322
  return this.http.execute(AUTH_SIGN_UP, {
1276
1323
  path: AUTH_PATHS.signUp,
1277
1324
  body: {
1278
1325
  ...rest,
1279
- ...captchaBody(captcha)
1326
+ ...await this.#auth.captchaBody(captcha)
1280
1327
  },
1281
1328
  skipAuth: true,
1282
1329
  skipAuthRefresh: true,
@@ -1291,18 +1338,19 @@ var AuthResource = class extends BaseResource {
1291
1338
  *
1292
1339
  * При успешном входе токен сохраняется в клиенте автоматически.
1293
1340
  *
1294
- * @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
1341
+ * @param credentials email, пароль и токен капчи — см. {@link CredentialsWithCaptcha}
1295
1342
  */
1296
1343
  async signIn(credentials, options = {}) {
1297
1344
  const { captcha, ...rest } = credentials;
1298
1345
  const revision = this.#auth.revision();
1299
1346
  const cookies = this.#auth.createCookieFlow(false);
1347
+ const body = {
1348
+ ...rest,
1349
+ ...await this.#auth.captchaBody(captcha)
1350
+ };
1300
1351
  const result = await this.http.execute(AUTH_SIGN_IN, {
1301
1352
  path: AUTH_PATHS.signIn,
1302
- body: {
1303
- ...rest,
1304
- ...captchaBody(captcha)
1305
- },
1353
+ body,
1306
1354
  skipAuth: true,
1307
1355
  skipAuthRefresh: true,
1308
1356
  cookieJar: cookies,
@@ -1430,12 +1478,12 @@ var AuthResource = class extends BaseResource {
1430
1478
  *
1431
1479
  * @returns `flowToken`, который нужно передать в {@link resetPassword}
1432
1480
  */
1433
- forgotPassword(input, options = {}) {
1481
+ async forgotPassword(input, options = {}) {
1434
1482
  return this.http.execute(AUTH_FORGOT_PASSWORD, {
1435
1483
  path: AUTH_PATHS.forgotPassword,
1436
1484
  body: {
1437
1485
  email: input.email,
1438
- ...captchaBody(input.captcha)
1486
+ ...await this.#auth.captchaBody(input.captcha)
1439
1487
  },
1440
1488
  skipAuth: true,
1441
1489
  skipAuthRefresh: true,
@@ -1570,8 +1618,9 @@ function createManagedClient(options, internals) {
1570
1618
  * const itd = new ItdClient({
1571
1619
  * // `auth` не обязателен: когда хранилище уже содержит сессию, токен берётся оттуда,
1572
1620
  * // а истёкший продлевается сам. Здесь он нужен на первый запуск.
1573
- * // Вход по паролю требует токена капчи — см. CaptchaOptions.
1574
- * auth: { email, password, captcha: createCaptchaSolver() },
1621
+ * auth: { email, password },
1622
+ * // Капчу входа решает пакет @itd-api/captcha см. CaptchaSolver.
1623
+ * captcha: createCaptchaSolver(),
1575
1624
  * storage: new FileTokenStorage('./.itd-session.json'),
1576
1625
  * rateLimit: { concurrency: 4, rps: 8 },
1577
1626
  * });
@@ -2085,6 +2134,8 @@ function assertAccountsActive(accounts, action) {
2085
2134
  *
2086
2135
  * await using accounts = new ItdAccounts({
2087
2136
  * storage: new FileMultiTokenStorage('./.itd-sessions.json'),
2137
+ * // Общие опции достаются каждому аккаунту: один источник токена капчи на всех.
2138
+ * captcha: createCaptchaSolver(),
2088
2139
  * rateLimit: { concurrency: 4 },
2089
2140
  * });
2090
2141
  *
@@ -2092,9 +2143,7 @@ function assertAccountsActive(accounts, action) {
2092
2143
  * await accounts.restore();
2093
2144
  *
2094
2145
  * if (!accounts.has('kiow')) {
2095
- * accounts.addAccount('kiow', {
2096
- * auth: { email, password, captcha: createCaptchaSolver() },
2097
- * });
2146
+ * accounts.addAccount('kiow', { auth: { email, password } });
2098
2147
  * }
2099
2148
  *
2100
2149
  * await accounts.account('kiow').posts.create({ content: 'привет' });
@@ -2173,9 +2222,7 @@ var ItdAccounts = class ItdAccounts {
2173
2222
  *
2174
2223
  * @example
2175
2224
  * ```ts
2176
- * accounts.addAccount('bot', {
2177
- * auth: { email, password, captcha: createCaptchaSolver() },
2178
- * });
2225
+ * accounts.addAccount('bot', { auth: { email, password } });
2179
2226
  * accounts.addAccount('reader', { auth: '<accessToken>' });
2180
2227
  * accounts.addAccount('через-прокси', { fetch: proxyFetch('socks5://…') });
2181
2228
  * ```