itd-api 0.0.5 → 0.0.6

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.
@@ -579,6 +579,24 @@ var AttachmentType = Object.freeze({
579
579
  /** Голосовые комментарии: `audio/ogg`, с полем `duration`. */
580
580
  Audio: "audio"
581
581
  });
582
+ var SpanType = Object.freeze({
583
+ /** Хэштег. Название без решётки лежит в `tag`. */
584
+ Hashtag: "hashtag",
585
+ /** Упоминание. Имя пользователя лежит в `tag`. */
586
+ Mention: "mention",
587
+ /** Ссылка. Адрес лежит в `url`, а не в `tag`. */
588
+ Link: "link",
589
+ Bold: "bold",
590
+ Italic: "italic",
591
+ Underline: "underline",
592
+ /** Зачёркнутый. */
593
+ Strike: "strike",
594
+ /** Спойлер: текст скрыт до нажатия. */
595
+ Spoiler: "spoiler",
596
+ /** Моноширинный. */
597
+ Monospace: "monospace",
598
+ Quote: "quote"
599
+ });
582
600
  var ReportTargetType = Object.freeze({
583
601
  Post: "post",
584
602
  Comment: "comment",
@@ -1698,7 +1716,7 @@ function normalizeBaseUrl(baseUrl) {
1698
1716
  // src/core/config.ts
1699
1717
  var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
1700
1718
  var DEFAULT_TIMEOUT = 3e4;
1701
- var LIBRARY_VERSION = "0.0.5";
1719
+ var LIBRARY_VERSION = "0.0.6";
1702
1720
  var DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; itd-api/${LIBRARY_VERSION}; +https://github.com/KiowDev/itd-api)`;
1703
1721
  var DEFAULT_RATE_LIMIT_DELAYS = Object.freeze([1e3, 5e3, 3e4, 6e4, 9e4]);
1704
1722
  function requirePositive(value, name) {
@@ -2093,6 +2111,7 @@ function createApiError(context) {
2093
2111
  function sleep(ms) {
2094
2112
  return new Promise((resolve) => setTimeout(resolve, ms));
2095
2113
  }
2114
+ var EMPTY_KEYS = /* @__PURE__ */ new Set();
2096
2115
  function setHeader(headers, name, value) {
2097
2116
  try {
2098
2117
  headers.set(name, value);
@@ -2146,6 +2165,7 @@ function createAbortBundle(userSignal, timeout) {
2146
2165
  var HttpClient = class {
2147
2166
  #config;
2148
2167
  #collaborators;
2168
+ #plugins;
2149
2169
  constructor(config, collaborators = {}) {
2150
2170
  this.#config = config;
2151
2171
  this.#collaborators = collaborators;
@@ -2154,6 +2174,19 @@ var HttpClient = class {
2154
2174
  get baseUrl() {
2155
2175
  return this.#config.baseUrl;
2156
2176
  }
2177
+ /**
2178
+ * Имена опций запроса, заявленные плагинами.
2179
+ *
2180
+ * Читается ресурсами: они переносят в транспорт только известные поля, а чужие,
2181
+ * если их никто не заявил, отсеивают.
2182
+ */
2183
+ get pluginOptionKeys() {
2184
+ return this.#plugins?.optionKeys ?? EMPTY_KEYS;
2185
+ }
2186
+ /** Подключает список плагинов. Реестр общий с клиентом и пополняется через `itd.use()`. */
2187
+ usePlugins(plugins) {
2188
+ this.#plugins = plugins;
2189
+ }
2157
2190
  /**
2158
2191
  * Подключает недостающие части конвейера.
2159
2192
  *
@@ -2173,10 +2206,22 @@ var HttpClient = class {
2173
2206
  * @throws {ItdNetworkError} если запрос не дошёл до сервера
2174
2207
  */
2175
2208
  async request(options) {
2176
- const task = () => this.#withRetries(options);
2209
+ const task = () => this.#withPlugins(options);
2177
2210
  if (!this.#collaborators.schedule || options.skipQueue) return task();
2178
2211
  return this.#collaborators.schedule(task);
2179
2212
  }
2213
+ /**
2214
+ * Прогоняет запрос через обёртки плагинов.
2215
+ *
2216
+ * Цепочка стоит **снаружи повторов и внутри очереди**: плагин должен увидеть запрос
2217
+ * и ответ по одному разу, независимо от того, сколько попыток понадобилось, — иначе,
2218
+ * например, текст поста зашифруется повторно на второй попытке.
2219
+ */
2220
+ #withPlugins(options) {
2221
+ const plugins = this.#plugins;
2222
+ if (!plugins || plugins.size === 0) return this.#withRetries(options);
2223
+ return plugins.run(options, (request) => this.#withRetries(request));
2224
+ }
2180
2225
  async #withRetries(options) {
2181
2226
  const method = options.method.toUpperCase();
2182
2227
  for (let attempt = 1; ; attempt++) {
@@ -2322,6 +2367,97 @@ var HttpClient = class {
2322
2367
  }
2323
2368
  };
2324
2369
 
2370
+ // src/core/plugins.ts
2371
+ var NO_KEYS = /* @__PURE__ */ new Set();
2372
+ var RESERVED_OPTION_KEYS = /* @__PURE__ */ new Set([
2373
+ "signal",
2374
+ "timeout",
2375
+ "headers",
2376
+ "retry",
2377
+ "method",
2378
+ "path",
2379
+ "query",
2380
+ "body",
2381
+ "skipAuth",
2382
+ "skipAuthRefresh",
2383
+ "skipQueue",
2384
+ "raw"
2385
+ ]);
2386
+ var PluginRegistry = class {
2387
+ #transformers = [];
2388
+ #optionKeys = /* @__PURE__ */ new Set();
2389
+ #names = /* @__PURE__ */ new Set();
2390
+ /** Сколько обёрток подключено. Ноль означает, что запрос идёт прежним путём. */
2391
+ get size() {
2392
+ return this.#transformers.length;
2393
+ }
2394
+ /** Имена опций запроса, заявленные плагинами. */
2395
+ get optionKeys() {
2396
+ return this.#optionKeys.size === 0 ? NO_KEYS : this.#optionKeys;
2397
+ }
2398
+ /**
2399
+ * Подключает плагин.
2400
+ *
2401
+ * @throws {ItdConfigError} если плагин задан неверно, уже подключён или заявил занятое
2402
+ * имя опции
2403
+ */
2404
+ add(plugin, context) {
2405
+ if (typeof plugin?.install !== "function") {
2406
+ throw new ItdConfigError("\u041F\u043B\u0430\u0433\u0438\u043D \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u0441 \u043C\u0435\u0442\u043E\u0434\u043E\u043C install()");
2407
+ }
2408
+ const name = plugin.name;
2409
+ if (typeof name !== "string" || name.trim() === "") {
2410
+ throw new ItdConfigError("\u0423 \u043F\u043B\u0430\u0433\u0438\u043D\u0430 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0435 \u0438\u043C\u044F");
2411
+ }
2412
+ if (this.#names.has(name)) {
2413
+ throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0443\u0436\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D`);
2414
+ }
2415
+ const keys = plugin.optionKeys ?? [];
2416
+ for (const key of keys) {
2417
+ if (typeof key !== "string" || key.trim() === "") {
2418
+ throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0437\u0430\u044F\u0432\u0438\u043B \u043F\u0443\u0441\u0442\u043E\u0435 \u0438\u043C\u044F \u043E\u043F\u0446\u0438\u0438`);
2419
+ }
2420
+ if (RESERVED_OPTION_KEYS.has(key)) {
2421
+ throw new ItdConfigError(
2422
+ `\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0437\u0430\u044F\u0432\u0438\u043B \u043E\u043F\u0446\u0438\u044E \xAB${key}\xBB: \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u0430, \u0438\u043C\u044F \u0437\u0430\u043D\u044F\u0442\u043E. \u0417\u0430\u043D\u044F\u0442\u044B\u0435 \u0438\u043C\u0435\u043D\u0430: ${[...RESERVED_OPTION_KEYS].join(", ")}`
2423
+ );
2424
+ }
2425
+ }
2426
+ const before = this.#transformers.length;
2427
+ try {
2428
+ plugin.install({
2429
+ ...context,
2430
+ use: (transformer) => {
2431
+ if (typeof transformer !== "function") {
2432
+ throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u043F\u0435\u0440\u0435\u0434\u0430\u043B \u0432 use() \u043D\u0435 \u0444\u0443\u043D\u043A\u0446\u0438\u044E`);
2433
+ }
2434
+ this.#transformers.push(transformer);
2435
+ }
2436
+ });
2437
+ } catch (error) {
2438
+ this.#transformers.length = before;
2439
+ throw error;
2440
+ }
2441
+ this.#names.add(name);
2442
+ for (const key of keys) this.#optionKeys.add(key);
2443
+ }
2444
+ /**
2445
+ * Прогоняет запрос через цепочку обёрток.
2446
+ *
2447
+ * Цепочка собирается на каждый запрос заново: плагин можно подключить в любой момент,
2448
+ * а обёрток единицы — экономить тут не на чем.
2449
+ *
2450
+ * @param execute настоящий запрос, вызывается самой внутренней обёрткой
2451
+ */
2452
+ run(request, execute) {
2453
+ const chain = this.#transformers.reduceRight(
2454
+ (next, transformer) => (current) => transformer(current, next),
2455
+ execute
2456
+ );
2457
+ return chain(request);
2458
+ }
2459
+ };
2460
+
2325
2461
  // src/core/rate-limit.ts
2326
2462
  var RequestQueue = class {
2327
2463
  #concurrency;
@@ -3242,15 +3378,31 @@ var BaseResource = class {
3242
3378
  constructor(http) {
3243
3379
  this.http = http;
3244
3380
  }
3245
- /** Переносит общие поля опций запроса в параметры транспорта. */
3381
+ /**
3382
+ * Переносит общие поля опций запроса в параметры транспорта.
3383
+ *
3384
+ * Поля перечислены поимённо, а не скопированы целиком: параметры методов наследуют
3385
+ * {@link RequestOptions} и приносят с собой `limit`, `cursor` и прочее, чему в описании
3386
+ * запроса делать нечего. Исключение — опции, заявленные плагинами: их библиотека
3387
+ * не понимает, но обязана донести до обёрток нетронутыми.
3388
+ */
3246
3389
  requestOptions(options) {
3247
3390
  if (!options) return {};
3248
- return {
3391
+ const result = {
3249
3392
  ...options.signal !== void 0 ? { signal: options.signal } : {},
3250
3393
  ...options.timeout !== void 0 ? { timeout: options.timeout } : {},
3251
3394
  ...options.headers !== void 0 ? { headers: options.headers } : {},
3252
3395
  ...options.retry !== void 0 ? { retry: options.retry } : {}
3253
3396
  };
3397
+ const pluginKeys = this.http.pluginOptionKeys;
3398
+ if (pluginKeys.size === 0) return result;
3399
+ const source = options;
3400
+ const target = result;
3401
+ for (const key of pluginKeys) {
3402
+ const value = source[key];
3403
+ if (value !== void 0) target[key] = value;
3404
+ }
3405
+ return result;
3254
3406
  }
3255
3407
  /**
3256
3408
  * Собирает перебор страниц.
@@ -4911,6 +5063,7 @@ var ItdClient = class {
4911
5063
  #authManager;
4912
5064
  #jar;
4913
5065
  #queue;
5066
+ #plugins = new PluginRegistry();
4914
5067
  /** Авторизация, сессии и пароли. */
4915
5068
  auth;
4916
5069
  /** Профили, подписки, блокировки, приватность. */
@@ -4947,6 +5100,7 @@ var ItdClient = class {
4947
5100
  this.#http = new HttpClient(this.#config);
4948
5101
  this.#authManager = new AuthManager(this.#config, this.#http, this.#jar);
4949
5102
  this.#queue = this.#config.rateLimit ? new RequestQueue(this.#config.rateLimit) : void 0;
5103
+ this.#http.usePlugins(this.#plugins);
4950
5104
  this.#http.setCollaborators({
4951
5105
  getAuthHeaders: () => this.#authManager.getAuthHeaders(),
4952
5106
  getDeviceId: () => this.#authManager.getDeviceId(),
@@ -4992,6 +5146,27 @@ var ItdClient = class {
4992
5146
  request(options) {
4993
5147
  return this.#http.request(options);
4994
5148
  }
5149
+ /**
5150
+ * Подключает плагин.
5151
+ *
5152
+ * Плагин работает на уровне транспорта: видит запрос до отправки и разобранный ответ,
5153
+ * поэтому одна обёртка охватывает сразу все методы клиента. Подключать можно в любой
5154
+ * момент, но обычно это делают сразу после создания клиента.
5155
+ *
5156
+ * @throws {ItdConfigError} если плагин задан неверно или уже подключён
5157
+ *
5158
+ * @example
5159
+ * ```ts
5160
+ * import { crypt } from 'itd-api-crypto';
5161
+ *
5162
+ * itd.use(crypt());
5163
+ * await itd.posts.create({ content: 'секрет' }, { encrypt: 'invis' });
5164
+ * ```
5165
+ */
5166
+ use(plugin) {
5167
+ this.#plugins.add(plugin, { baseUrl: this.#config.baseUrl, logger: this.#config.logger });
5168
+ return this;
5169
+ }
4995
5170
  /**
4996
5171
  * Подписывается на события авторизации.
4997
5172
  *
@@ -5271,6 +5446,7 @@ exports.ReportTargetType = ReportTargetType;
5271
5446
  exports.RuntimeMode = RuntimeMode;
5272
5447
  exports.STREAM_PATH = STREAM_PATH;
5273
5448
  exports.SignInStatus = SignInStatus;
5449
+ exports.SpanType = SpanType;
5274
5450
  exports.TURNSTILE_SITE_KEY = TURNSTILE_SITE_KEY;
5275
5451
  exports.UnauthorizedStreamError = UnauthorizedStreamError;
5276
5452
  exports.VIDEO_MIME_TYPES = VIDEO_MIME_TYPES;
@@ -5301,5 +5477,5 @@ exports.readUnreadCountEvent = readUnreadCountEvent;
5301
5477
  exports.report = report;
5302
5478
  exports.resolveNotificationUrl = resolveNotificationUrl;
5303
5479
  exports.toDate = toDate;
5304
- //# sourceMappingURL=chunk-YM2YUO4D.cjs.map
5305
- //# sourceMappingURL=chunk-YM2YUO4D.cjs.map
5480
+ //# sourceMappingURL=chunk-K7NBFQEE.cjs.map
5481
+ //# sourceMappingURL=chunk-K7NBFQEE.cjs.map