itd-api 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -1
- package/dist/{chunk-YM2YUO4D.cjs → chunk-3RNNZJZ4.cjs} +290 -55
- package/dist/chunk-3RNNZJZ4.cjs.map +1 -0
- package/dist/{chunk-RUPF4X5L.js → chunk-CG4SERVM.js} +286 -56
- package/dist/chunk-CG4SERVM.js.map +1 -0
- package/dist/{index-Dv0LXpMf.d.cts → index-DNFPX_Z1.d.cts} +348 -62
- package/dist/{index-Dv0LXpMf.d.ts → index-DNFPX_Z1.d.ts} +348 -62
- package/dist/index.cjs +104 -84
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node.cjs +103 -83
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/package.json +5 -4
- package/dist/chunk-RUPF4X5L.js.map +0 -1
- package/dist/chunk-YM2YUO4D.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -287,6 +287,24 @@ await itd.posts.create(draft.content('второй')); // заготовка
|
|
|
287
287
|
Файлы из `attach()` загружаются автоматически, порядок вложений сохраняется, MIME-тип
|
|
288
288
|
проверяется до отправки.
|
|
289
289
|
|
|
290
|
+
Разметка текста передаётся полем `spans` — библиотека её не генерирует и не пересчитывает.
|
|
291
|
+
Известные типы собраны в `SpanType`: `hashtag`, `mention`, `link`, `bold`, `italic`,
|
|
292
|
+
`underline`, `strike`, `spoiler`, `monospace`, `quote`.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { SpanType } from 'itd-api';
|
|
296
|
+
|
|
297
|
+
await itd.posts.create({
|
|
298
|
+
content: 'жирное слово и ссылка',
|
|
299
|
+
spans: [
|
|
300
|
+
{ type: SpanType.Bold, offset: 0, length: 6 },
|
|
301
|
+
{ type: SpanType.Link, offset: 15, length: 6, url: 'https://example.com' },
|
|
302
|
+
],
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
У `link` адрес лежит в `url`, у `hashtag` и `mention` — имя в `tag`.
|
|
307
|
+
|
|
290
308
|
Билдеры есть у поста, комментария, опроса и жалобы. Все они неизменяемые, а `build()`
|
|
291
309
|
проверяет данные и бросает `ItdConfigError` **до** обращения к сети:
|
|
292
310
|
|
|
@@ -440,6 +458,92 @@ Deno и React Native ограничение не действует.
|
|
|
440
458
|
|
|
441
459
|
---
|
|
442
460
|
|
|
461
|
+
## Плагины
|
|
462
|
+
|
|
463
|
+
Плагин — обёртка вокруг запроса: она видит тело до отправки и разобранный ответ, поэтому
|
|
464
|
+
одна обёртка охватывает сразу все методы клиента. Подключается через `itd.use()`:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
import { ItdClient } from 'itd-api';
|
|
468
|
+
import { crypt } from 'itd-api-crypto';
|
|
469
|
+
|
|
470
|
+
const itd = new ItdClient({ auth: token });
|
|
471
|
+
itd.use(crypt());
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### `itd-api-crypto` — скрытые сообщения
|
|
475
|
+
|
|
476
|
+
[Отдельный пакет](./crypto): прячет текст в невидимых символах внутри обычного поста.
|
|
477
|
+
Читатель видит обложку, а тот, у кого подключён плагин, получает спрятанное отдельным полем.
|
|
478
|
+
|
|
479
|
+
```sh
|
|
480
|
+
npm i itd-api-crypto
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
// отправка: текст прогоняется через шифр, обложка остаётся видимой
|
|
485
|
+
const created = await itd.posts.create(
|
|
486
|
+
{ content: 'секретный текст' },
|
|
487
|
+
{ encrypt: { cipher: 'invisible', cover: 'обычный пост' } },
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
// чтение: content не меняется, расшифровка приезжает рядом
|
|
491
|
+
const post = await itd.posts.get(created.id);
|
|
492
|
+
post.secret?.text; // 'секретный текст'
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Работает для постов, комментариев, ответов, имени и подписи профиля. Расшифровка идёт сама
|
|
496
|
+
и вглубь: находки появляются и у постов ленты, и у исходного поста репоста, и у авторов.
|
|
497
|
+
|
|
498
|
+
Шифра два: `invisible` — невидимые символы с обложкой, `beecrypt` — видимый текст из букв
|
|
499
|
+
`жъЖЪ`. Подробности, ограничения и то, как подключить свой шифр, — в
|
|
500
|
+
[README пакета](./crypto).
|
|
501
|
+
|
|
502
|
+
### Свой плагин
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
import type { ItdPlugin } from 'itd-api';
|
|
506
|
+
|
|
507
|
+
const timing: ItdPlugin = {
|
|
508
|
+
name: 'timing',
|
|
509
|
+
install({ use, logger }) {
|
|
510
|
+
use(async (request, next) => {
|
|
511
|
+
const started = Date.now();
|
|
512
|
+
try {
|
|
513
|
+
return await next(request);
|
|
514
|
+
} finally {
|
|
515
|
+
logger?.info(`${request.method} ${request.path}: ${Date.now() - started} мс`);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Обёртка может изменить запрос (передайте в `next` копию), подменить ответ или вернуть своё,
|
|
523
|
+
не обращаясь к сети. Подключённая раньше оказывается снаружи. Выполняется она один раз
|
|
524
|
+
на запрос, независимо от числа повторов.
|
|
525
|
+
|
|
526
|
+
Свои опции запроса плагин объявляет сам — библиотека их не понимает, но доносит до обёртки
|
|
527
|
+
нетронутыми:
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
const plugin: ItdPlugin = {
|
|
531
|
+
name: 'мой',
|
|
532
|
+
optionKeys: ['мояОпция'],
|
|
533
|
+
install({ use }) { /* … */ },
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
declare module 'itd-api' {
|
|
537
|
+
interface RequestOptions { мояОпция?: string | undefined }
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Имена полей самого запроса (`path`, `body`, `headers`, `signal` и прочие) заявить нельзя:
|
|
542
|
+
подключение такого плагина завершится `ItdConfigError`. Иначе опечатка в `optionKeys`
|
|
543
|
+
молча подменяла бы путь или тело любого вызова.
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
443
547
|
## Что доступно
|
|
444
548
|
|
|
445
549
|
| Раздел | Методы |
|
|
@@ -454,6 +558,7 @@ Deno и React Native ограничение не действует.
|
|
|
454
558
|
| `itd.reports` · `itd.verification` | жалобы, заявка на верификацию |
|
|
455
559
|
| `itd.subscription` · `itd.platform` | подписка, способы оплаты, анонсы |
|
|
456
560
|
| `itd.realtime()` | поток уведомлений |
|
|
561
|
+
| `itd.use()` | плагины: обёртки вокруг запроса и ответа |
|
|
457
562
|
| `itd.request()` | произвольный запрос, если метода ещё нет |
|
|
458
563
|
|
|
459
564
|
Метода не хватает или ответ разошёлся с документацией — есть запасной путь:
|
|
@@ -482,7 +587,8 @@ TypeScript 5.0+. Пакет собран в ESM и CommonJS, типы корре
|
|
|
482
587
|
|
|
483
588
|
```bash
|
|
484
589
|
npm install
|
|
485
|
-
npm test #
|
|
590
|
+
npm test # 417 тестов
|
|
591
|
+
npm run test:all # вместе с пакетами workspace
|
|
486
592
|
npm run typecheck
|
|
487
593
|
npm run lint
|
|
488
594
|
npm run build
|
|
@@ -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",
|
|
@@ -598,14 +616,18 @@ var RealtimeStatus = Object.freeze({
|
|
|
598
616
|
Error: "error",
|
|
599
617
|
Disconnected: "disconnected"
|
|
600
618
|
});
|
|
601
|
-
var
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
var LikesVisibility = Object.freeze({
|
|
605
|
-
Everyone: "everyone",
|
|
619
|
+
var AccessType = Object.freeze({
|
|
620
|
+
/** Никто. */
|
|
621
|
+
Nobody: "nobody",
|
|
606
622
|
/** Только взаимные подписки. */
|
|
607
|
-
Mutual: "mutual"
|
|
623
|
+
Mutual: "mutual",
|
|
624
|
+
/** Подписчики. */
|
|
625
|
+
Followers: "followers",
|
|
626
|
+
/** Все. */
|
|
627
|
+
Everyone: "everyone"
|
|
608
628
|
});
|
|
629
|
+
var WallAccess = AccessType;
|
|
630
|
+
var LikesVisibility = AccessType;
|
|
609
631
|
var NotificationType = Object.freeze({
|
|
610
632
|
/** Реакция на пост. Старое имя — `like`. */
|
|
611
633
|
PostReaction: "post_reaction",
|
|
@@ -634,6 +656,36 @@ var NotificationType = Object.freeze({
|
|
|
634
656
|
/** Верификация отклонена. Приходит только по REST. */
|
|
635
657
|
VerificationRejected: "verification_rejected"
|
|
636
658
|
});
|
|
659
|
+
var InteractionType = Object.freeze({
|
|
660
|
+
/** Открытие фотографии. */
|
|
661
|
+
PhotoOpen: 1,
|
|
662
|
+
/** Прогресс просмотра видео. Несёт поля `pm`/`dm`. */
|
|
663
|
+
VideoProgress: 2
|
|
664
|
+
});
|
|
665
|
+
var ViewSource = Object.freeze({
|
|
666
|
+
FeedGlobal: 1,
|
|
667
|
+
FeedFollowing: 2,
|
|
668
|
+
FeedClan: 3,
|
|
669
|
+
Profile: 4,
|
|
670
|
+
Hashtag: 5,
|
|
671
|
+
PostPage: 6,
|
|
672
|
+
Link: 7,
|
|
673
|
+
Search: 8
|
|
674
|
+
});
|
|
675
|
+
var ViewReason = Object.freeze({
|
|
676
|
+
/** Пост ушёл из зоны видимости при обычной прокрутке. */
|
|
677
|
+
Normal: 0,
|
|
678
|
+
/** Потеря фокуса окна. */
|
|
679
|
+
Blur: 1,
|
|
680
|
+
/** Вкладка скрыта. */
|
|
681
|
+
Hidden: 2,
|
|
682
|
+
/** Уход со страницы (`pagehide`). */
|
|
683
|
+
PageHide: 3,
|
|
684
|
+
/** Элемент перестал наблюдаться. */
|
|
685
|
+
Unobserve: 4,
|
|
686
|
+
/** Достигнут порог времени просмотра. */
|
|
687
|
+
ThresholdMet: 5
|
|
688
|
+
});
|
|
637
689
|
var ItdErrorCode = Object.freeze({
|
|
638
690
|
BAD_REQUEST: "BAD_REQUEST",
|
|
639
691
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
@@ -1698,7 +1750,7 @@ function normalizeBaseUrl(baseUrl) {
|
|
|
1698
1750
|
// src/core/config.ts
|
|
1699
1751
|
var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
|
|
1700
1752
|
var DEFAULT_TIMEOUT = 3e4;
|
|
1701
|
-
var LIBRARY_VERSION = "0.0.
|
|
1753
|
+
var LIBRARY_VERSION = "0.0.7";
|
|
1702
1754
|
var DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; itd-api/${LIBRARY_VERSION}; +https://github.com/KiowDev/itd-api)`;
|
|
1703
1755
|
var DEFAULT_RATE_LIMIT_DELAYS = Object.freeze([1e3, 5e3, 3e4, 6e4, 9e4]);
|
|
1704
1756
|
function requirePositive(value, name) {
|
|
@@ -2093,6 +2145,7 @@ function createApiError(context) {
|
|
|
2093
2145
|
function sleep(ms) {
|
|
2094
2146
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2095
2147
|
}
|
|
2148
|
+
var EMPTY_KEYS = /* @__PURE__ */ new Set();
|
|
2096
2149
|
function setHeader(headers, name, value) {
|
|
2097
2150
|
try {
|
|
2098
2151
|
headers.set(name, value);
|
|
@@ -2146,6 +2199,7 @@ function createAbortBundle(userSignal, timeout) {
|
|
|
2146
2199
|
var HttpClient = class {
|
|
2147
2200
|
#config;
|
|
2148
2201
|
#collaborators;
|
|
2202
|
+
#plugins;
|
|
2149
2203
|
constructor(config, collaborators = {}) {
|
|
2150
2204
|
this.#config = config;
|
|
2151
2205
|
this.#collaborators = collaborators;
|
|
@@ -2154,6 +2208,19 @@ var HttpClient = class {
|
|
|
2154
2208
|
get baseUrl() {
|
|
2155
2209
|
return this.#config.baseUrl;
|
|
2156
2210
|
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Имена опций запроса, заявленные плагинами.
|
|
2213
|
+
*
|
|
2214
|
+
* Читается ресурсами: они переносят в транспорт только известные поля, а чужие,
|
|
2215
|
+
* если их никто не заявил, отсеивают.
|
|
2216
|
+
*/
|
|
2217
|
+
get pluginOptionKeys() {
|
|
2218
|
+
return this.#plugins?.optionKeys ?? EMPTY_KEYS;
|
|
2219
|
+
}
|
|
2220
|
+
/** Подключает список плагинов. Реестр общий с клиентом и пополняется через `itd.use()`. */
|
|
2221
|
+
usePlugins(plugins) {
|
|
2222
|
+
this.#plugins = plugins;
|
|
2223
|
+
}
|
|
2157
2224
|
/**
|
|
2158
2225
|
* Подключает недостающие части конвейера.
|
|
2159
2226
|
*
|
|
@@ -2173,10 +2240,22 @@ var HttpClient = class {
|
|
|
2173
2240
|
* @throws {ItdNetworkError} если запрос не дошёл до сервера
|
|
2174
2241
|
*/
|
|
2175
2242
|
async request(options) {
|
|
2176
|
-
const task = () => this.#
|
|
2243
|
+
const task = () => this.#withPlugins(options);
|
|
2177
2244
|
if (!this.#collaborators.schedule || options.skipQueue) return task();
|
|
2178
2245
|
return this.#collaborators.schedule(task);
|
|
2179
2246
|
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Прогоняет запрос через обёртки плагинов.
|
|
2249
|
+
*
|
|
2250
|
+
* Цепочка стоит **снаружи повторов и внутри очереди**: плагин должен увидеть запрос
|
|
2251
|
+
* и ответ по одному разу, независимо от того, сколько попыток понадобилось, — иначе,
|
|
2252
|
+
* например, текст поста зашифруется повторно на второй попытке.
|
|
2253
|
+
*/
|
|
2254
|
+
#withPlugins(options) {
|
|
2255
|
+
const plugins = this.#plugins;
|
|
2256
|
+
if (!plugins || plugins.size === 0) return this.#withRetries(options);
|
|
2257
|
+
return plugins.run(options, (request) => this.#withRetries(request));
|
|
2258
|
+
}
|
|
2180
2259
|
async #withRetries(options) {
|
|
2181
2260
|
const method = options.method.toUpperCase();
|
|
2182
2261
|
for (let attempt = 1; ; attempt++) {
|
|
@@ -2322,6 +2401,97 @@ var HttpClient = class {
|
|
|
2322
2401
|
}
|
|
2323
2402
|
};
|
|
2324
2403
|
|
|
2404
|
+
// src/core/plugins.ts
|
|
2405
|
+
var NO_KEYS = /* @__PURE__ */ new Set();
|
|
2406
|
+
var RESERVED_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
2407
|
+
"signal",
|
|
2408
|
+
"timeout",
|
|
2409
|
+
"headers",
|
|
2410
|
+
"retry",
|
|
2411
|
+
"method",
|
|
2412
|
+
"path",
|
|
2413
|
+
"query",
|
|
2414
|
+
"body",
|
|
2415
|
+
"skipAuth",
|
|
2416
|
+
"skipAuthRefresh",
|
|
2417
|
+
"skipQueue",
|
|
2418
|
+
"raw"
|
|
2419
|
+
]);
|
|
2420
|
+
var PluginRegistry = class {
|
|
2421
|
+
#transformers = [];
|
|
2422
|
+
#optionKeys = /* @__PURE__ */ new Set();
|
|
2423
|
+
#names = /* @__PURE__ */ new Set();
|
|
2424
|
+
/** Сколько обёрток подключено. Ноль означает, что запрос идёт прежним путём. */
|
|
2425
|
+
get size() {
|
|
2426
|
+
return this.#transformers.length;
|
|
2427
|
+
}
|
|
2428
|
+
/** Имена опций запроса, заявленные плагинами. */
|
|
2429
|
+
get optionKeys() {
|
|
2430
|
+
return this.#optionKeys.size === 0 ? NO_KEYS : this.#optionKeys;
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* Подключает плагин.
|
|
2434
|
+
*
|
|
2435
|
+
* @throws {ItdConfigError} если плагин задан неверно, уже подключён или заявил занятое
|
|
2436
|
+
* имя опции
|
|
2437
|
+
*/
|
|
2438
|
+
add(plugin, context) {
|
|
2439
|
+
if (typeof plugin?.install !== "function") {
|
|
2440
|
+
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()");
|
|
2441
|
+
}
|
|
2442
|
+
const name = plugin.name;
|
|
2443
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
2444
|
+
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");
|
|
2445
|
+
}
|
|
2446
|
+
if (this.#names.has(name)) {
|
|
2447
|
+
throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0443\u0436\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D`);
|
|
2448
|
+
}
|
|
2449
|
+
const keys = plugin.optionKeys ?? [];
|
|
2450
|
+
for (const key of keys) {
|
|
2451
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
2452
|
+
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`);
|
|
2453
|
+
}
|
|
2454
|
+
if (RESERVED_OPTION_KEYS.has(key)) {
|
|
2455
|
+
throw new ItdConfigError(
|
|
2456
|
+
`\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(", ")}`
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
const before = this.#transformers.length;
|
|
2461
|
+
try {
|
|
2462
|
+
plugin.install({
|
|
2463
|
+
...context,
|
|
2464
|
+
use: (transformer) => {
|
|
2465
|
+
if (typeof transformer !== "function") {
|
|
2466
|
+
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`);
|
|
2467
|
+
}
|
|
2468
|
+
this.#transformers.push(transformer);
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
this.#transformers.length = before;
|
|
2473
|
+
throw error;
|
|
2474
|
+
}
|
|
2475
|
+
this.#names.add(name);
|
|
2476
|
+
for (const key of keys) this.#optionKeys.add(key);
|
|
2477
|
+
}
|
|
2478
|
+
/**
|
|
2479
|
+
* Прогоняет запрос через цепочку обёрток.
|
|
2480
|
+
*
|
|
2481
|
+
* Цепочка собирается на каждый запрос заново: плагин можно подключить в любой момент,
|
|
2482
|
+
* а обёрток единицы — экономить тут не на чем.
|
|
2483
|
+
*
|
|
2484
|
+
* @param execute настоящий запрос, вызывается самой внутренней обёрткой
|
|
2485
|
+
*/
|
|
2486
|
+
run(request, execute) {
|
|
2487
|
+
const chain = this.#transformers.reduceRight(
|
|
2488
|
+
(next, transformer) => (current) => transformer(current, next),
|
|
2489
|
+
execute
|
|
2490
|
+
);
|
|
2491
|
+
return chain(request);
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
|
|
2325
2495
|
// src/core/rate-limit.ts
|
|
2326
2496
|
var RequestQueue = class {
|
|
2327
2497
|
#concurrency;
|
|
@@ -3242,15 +3412,31 @@ var BaseResource = class {
|
|
|
3242
3412
|
constructor(http) {
|
|
3243
3413
|
this.http = http;
|
|
3244
3414
|
}
|
|
3245
|
-
/**
|
|
3415
|
+
/**
|
|
3416
|
+
* Переносит общие поля опций запроса в параметры транспорта.
|
|
3417
|
+
*
|
|
3418
|
+
* Поля перечислены поимённо, а не скопированы целиком: параметры методов наследуют
|
|
3419
|
+
* {@link RequestOptions} и приносят с собой `limit`, `cursor` и прочее, чему в описании
|
|
3420
|
+
* запроса делать нечего. Исключение — опции, заявленные плагинами: их библиотека
|
|
3421
|
+
* не понимает, но обязана донести до обёрток нетронутыми.
|
|
3422
|
+
*/
|
|
3246
3423
|
requestOptions(options) {
|
|
3247
3424
|
if (!options) return {};
|
|
3248
|
-
|
|
3425
|
+
const result = {
|
|
3249
3426
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
3250
3427
|
...options.timeout !== void 0 ? { timeout: options.timeout } : {},
|
|
3251
3428
|
...options.headers !== void 0 ? { headers: options.headers } : {},
|
|
3252
3429
|
...options.retry !== void 0 ? { retry: options.retry } : {}
|
|
3253
3430
|
};
|
|
3431
|
+
const pluginKeys = this.http.pluginOptionKeys;
|
|
3432
|
+
if (pluginKeys.size === 0) return result;
|
|
3433
|
+
const source = options;
|
|
3434
|
+
const target = result;
|
|
3435
|
+
for (const key of pluginKeys) {
|
|
3436
|
+
const value = source[key];
|
|
3437
|
+
if (value !== void 0) target[key] = value;
|
|
3438
|
+
}
|
|
3439
|
+
return result;
|
|
3254
3440
|
}
|
|
3255
3441
|
/**
|
|
3256
3442
|
* Собирает перебор страниц.
|
|
@@ -4091,49 +4277,6 @@ var PlatformResource = class extends BaseResource {
|
|
|
4091
4277
|
});
|
|
4092
4278
|
}
|
|
4093
4279
|
};
|
|
4094
|
-
var TelemetryResource = class extends BaseResource {
|
|
4095
|
-
/**
|
|
4096
|
-
* Отправляет время просмотра постов.
|
|
4097
|
-
*
|
|
4098
|
-
* @experimental Имена полей на проводе сжаты (`ai`, `v`, `s`), и их соответствие
|
|
4099
|
-
* смыслу **не проверено** на реальных запросах. Может измениться без предупреждения.
|
|
4100
|
-
*/
|
|
4101
|
-
dwell(entries, options = {}) {
|
|
4102
|
-
return this.http.request({
|
|
4103
|
-
method: "POST",
|
|
4104
|
-
path: "/api/v1/i",
|
|
4105
|
-
body: {
|
|
4106
|
-
items: entries.map((entry) => ({
|
|
4107
|
-
ai: entry.postId,
|
|
4108
|
-
v: entry.duration,
|
|
4109
|
-
...entry.vs ? { s: entry.vs } : {}
|
|
4110
|
-
}))
|
|
4111
|
-
},
|
|
4112
|
-
...this.requestOptions(options)
|
|
4113
|
-
});
|
|
4114
|
-
}
|
|
4115
|
-
/**
|
|
4116
|
-
* Отправляет события взаимодействия с контентом.
|
|
4117
|
-
*
|
|
4118
|
-
* @experimental См. предупреждение у {@link TelemetryResource}.
|
|
4119
|
-
*/
|
|
4120
|
-
interaction(entries, options = {}) {
|
|
4121
|
-
return this.http.request({
|
|
4122
|
-
method: "POST",
|
|
4123
|
-
path: "/api/v1/x",
|
|
4124
|
-
body: {
|
|
4125
|
-
items: entries.map((entry) => ({
|
|
4126
|
-
t: entry.type,
|
|
4127
|
-
...entry.value !== void 0 ? { v: entry.value } : {},
|
|
4128
|
-
...entry.postId ? { ai: entry.postId } : {},
|
|
4129
|
-
...entry.attachmentId ? { mi: entry.attachmentId } : {},
|
|
4130
|
-
...entry.vs ? { s: entry.vs } : {}
|
|
4131
|
-
}))
|
|
4132
|
-
},
|
|
4133
|
-
...this.requestOptions(options)
|
|
4134
|
-
});
|
|
4135
|
-
}
|
|
4136
|
-
};
|
|
4137
4280
|
|
|
4138
4281
|
// src/resources/notifications.ts
|
|
4139
4282
|
var READ_BATCH_SIZE = 20;
|
|
@@ -4623,6 +4766,70 @@ var PostsResource = class extends BaseResource {
|
|
|
4623
4766
|
}
|
|
4624
4767
|
};
|
|
4625
4768
|
|
|
4769
|
+
// src/resources/telemetry.ts
|
|
4770
|
+
var TelemetryResource = class extends BaseResource {
|
|
4771
|
+
/** Идентификатор сессии телеметрии, общий для всех событий этого объекта. */
|
|
4772
|
+
#sessionId;
|
|
4773
|
+
/**
|
|
4774
|
+
* Идентификатор сессии телеметрии (`sid`).
|
|
4775
|
+
*
|
|
4776
|
+
* Создаётся лениво при первом обращении и далее неизменен.
|
|
4777
|
+
*/
|
|
4778
|
+
get sessionId() {
|
|
4779
|
+
this.#sessionId ??= createDeviceId();
|
|
4780
|
+
return this.#sessionId;
|
|
4781
|
+
}
|
|
4782
|
+
/**
|
|
4783
|
+
* Отправляет события просмотра постов (`POST /api/v1/i`).
|
|
4784
|
+
*
|
|
4785
|
+
* @experimental См. предупреждение у {@link TelemetryResource}.
|
|
4786
|
+
*/
|
|
4787
|
+
dwell(entries, options = {}) {
|
|
4788
|
+
return this.http.request({
|
|
4789
|
+
method: "POST",
|
|
4790
|
+
path: "/api/v1/i",
|
|
4791
|
+
body: {
|
|
4792
|
+
sid: options.sid ?? this.sessionId,
|
|
4793
|
+
e: entries.map((entry) => ({
|
|
4794
|
+
md: entry.durationMs ?? entry.exitAt - entry.enterAt,
|
|
4795
|
+
et: entry.enterAt,
|
|
4796
|
+
xt: entry.exitAt,
|
|
4797
|
+
r: entry.reason,
|
|
4798
|
+
v: entry.vs,
|
|
4799
|
+
...entry.sourceContext !== void 0 ? { sc: entry.sourceContext } : {},
|
|
4800
|
+
...entry.source !== void 0 ? { s: entry.source } : {},
|
|
4801
|
+
...entry.repeat ? { b: 1 } : {}
|
|
4802
|
+
}))
|
|
4803
|
+
},
|
|
4804
|
+
...this.requestOptions(options)
|
|
4805
|
+
});
|
|
4806
|
+
}
|
|
4807
|
+
/**
|
|
4808
|
+
* Отправляет события взаимодействия с контентом (`POST /api/v1/x`).
|
|
4809
|
+
*
|
|
4810
|
+
* @experimental См. предупреждение у {@link TelemetryResource}.
|
|
4811
|
+
*/
|
|
4812
|
+
interaction(entries, options = {}) {
|
|
4813
|
+
return this.http.request({
|
|
4814
|
+
method: "POST",
|
|
4815
|
+
path: "/api/v1/x",
|
|
4816
|
+
body: {
|
|
4817
|
+
sid: options.sid ?? this.sessionId,
|
|
4818
|
+
e: entries.map((entry) => ({
|
|
4819
|
+
t: entry.type,
|
|
4820
|
+
v: entry.vs,
|
|
4821
|
+
ai: entry.postId,
|
|
4822
|
+
...entry.mediaIndex !== void 0 ? { mi: entry.mediaIndex } : {},
|
|
4823
|
+
...entry.source !== void 0 ? { s: entry.source } : {},
|
|
4824
|
+
...entry.positionMs !== void 0 ? { pm: Math.round(entry.positionMs) } : {},
|
|
4825
|
+
...entry.durationMs !== void 0 ? { dm: Math.round(entry.durationMs) } : {}
|
|
4826
|
+
}))
|
|
4827
|
+
},
|
|
4828
|
+
...this.requestOptions(options)
|
|
4829
|
+
});
|
|
4830
|
+
}
|
|
4831
|
+
};
|
|
4832
|
+
|
|
4626
4833
|
// src/resources/users.ts
|
|
4627
4834
|
var UsersResource = class extends BaseResource {
|
|
4628
4835
|
/** Загружает свой профиль — с подпиской и признаком подтверждённого телефона. */
|
|
@@ -4911,6 +5118,7 @@ var ItdClient = class {
|
|
|
4911
5118
|
#authManager;
|
|
4912
5119
|
#jar;
|
|
4913
5120
|
#queue;
|
|
5121
|
+
#plugins = new PluginRegistry();
|
|
4914
5122
|
/** Авторизация, сессии и пароли. */
|
|
4915
5123
|
auth;
|
|
4916
5124
|
/** Профили, подписки, блокировки, приватность. */
|
|
@@ -4947,6 +5155,7 @@ var ItdClient = class {
|
|
|
4947
5155
|
this.#http = new HttpClient(this.#config);
|
|
4948
5156
|
this.#authManager = new AuthManager(this.#config, this.#http, this.#jar);
|
|
4949
5157
|
this.#queue = this.#config.rateLimit ? new RequestQueue(this.#config.rateLimit) : void 0;
|
|
5158
|
+
this.#http.usePlugins(this.#plugins);
|
|
4950
5159
|
this.#http.setCollaborators({
|
|
4951
5160
|
getAuthHeaders: () => this.#authManager.getAuthHeaders(),
|
|
4952
5161
|
getDeviceId: () => this.#authManager.getDeviceId(),
|
|
@@ -4992,6 +5201,27 @@ var ItdClient = class {
|
|
|
4992
5201
|
request(options) {
|
|
4993
5202
|
return this.#http.request(options);
|
|
4994
5203
|
}
|
|
5204
|
+
/**
|
|
5205
|
+
* Подключает плагин.
|
|
5206
|
+
*
|
|
5207
|
+
* Плагин работает на уровне транспорта: видит запрос до отправки и разобранный ответ,
|
|
5208
|
+
* поэтому одна обёртка охватывает сразу все методы клиента. Подключать можно в любой
|
|
5209
|
+
* момент, но обычно это делают сразу после создания клиента.
|
|
5210
|
+
*
|
|
5211
|
+
* @throws {ItdConfigError} если плагин задан неверно или уже подключён
|
|
5212
|
+
*
|
|
5213
|
+
* @example
|
|
5214
|
+
* ```ts
|
|
5215
|
+
* import { crypt } from 'itd-api-crypto';
|
|
5216
|
+
*
|
|
5217
|
+
* itd.use(crypt());
|
|
5218
|
+
* await itd.posts.create({ content: 'секрет' }, { encrypt: 'invis' });
|
|
5219
|
+
* ```
|
|
5220
|
+
*/
|
|
5221
|
+
use(plugin) {
|
|
5222
|
+
this.#plugins.add(plugin, { baseUrl: this.#config.baseUrl, logger: this.#config.logger });
|
|
5223
|
+
return this;
|
|
5224
|
+
}
|
|
4995
5225
|
/**
|
|
4996
5226
|
* Подписывается на события авторизации.
|
|
4997
5227
|
*
|
|
@@ -5222,6 +5452,7 @@ exports.ALLOWED_MIME_TYPES = ALLOWED_MIME_TYPES;
|
|
|
5222
5452
|
exports.AUDIO_MIME_TYPES = AUDIO_MIME_TYPES;
|
|
5223
5453
|
exports.AUTH_FLAG_COOKIE = AUTH_FLAG_COOKIE;
|
|
5224
5454
|
exports.AUTH_PATHS = AUTH_PATHS;
|
|
5455
|
+
exports.AccessType = AccessType;
|
|
5225
5456
|
exports.AttachmentType = AttachmentType;
|
|
5226
5457
|
exports.CommentSort = CommentSort;
|
|
5227
5458
|
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
@@ -5231,6 +5462,7 @@ exports.DEVICE_ID_HEADER = DEVICE_ID_HEADER;
|
|
|
5231
5462
|
exports.DetectedRuntime = DetectedRuntime;
|
|
5232
5463
|
exports.FeedTab = FeedTab;
|
|
5233
5464
|
exports.IMAGE_MIME_TYPES = IMAGE_MIME_TYPES;
|
|
5465
|
+
exports.InteractionType = InteractionType;
|
|
5234
5466
|
exports.ItdAbortError = ItdAbortError;
|
|
5235
5467
|
exports.ItdApiError = ItdApiError;
|
|
5236
5468
|
exports.ItdApiErrorKind = ItdApiErrorKind;
|
|
@@ -5271,9 +5503,12 @@ exports.ReportTargetType = ReportTargetType;
|
|
|
5271
5503
|
exports.RuntimeMode = RuntimeMode;
|
|
5272
5504
|
exports.STREAM_PATH = STREAM_PATH;
|
|
5273
5505
|
exports.SignInStatus = SignInStatus;
|
|
5506
|
+
exports.SpanType = SpanType;
|
|
5274
5507
|
exports.TURNSTILE_SITE_KEY = TURNSTILE_SITE_KEY;
|
|
5275
5508
|
exports.UnauthorizedStreamError = UnauthorizedStreamError;
|
|
5276
5509
|
exports.VIDEO_MIME_TYPES = VIDEO_MIME_TYPES;
|
|
5510
|
+
exports.ViewReason = ViewReason;
|
|
5511
|
+
exports.ViewSource = ViewSource;
|
|
5277
5512
|
exports.WallAccess = WallAccess;
|
|
5278
5513
|
exports.canonicalNotificationType = canonicalNotificationType;
|
|
5279
5514
|
exports.comment = comment;
|
|
@@ -5301,5 +5536,5 @@ exports.readUnreadCountEvent = readUnreadCountEvent;
|
|
|
5301
5536
|
exports.report = report;
|
|
5302
5537
|
exports.resolveNotificationUrl = resolveNotificationUrl;
|
|
5303
5538
|
exports.toDate = toDate;
|
|
5304
|
-
//# sourceMappingURL=chunk-
|
|
5305
|
-
//# sourceMappingURL=chunk-
|
|
5539
|
+
//# sourceMappingURL=chunk-3RNNZJZ4.cjs.map
|
|
5540
|
+
//# sourceMappingURL=chunk-3RNNZJZ4.cjs.map
|