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
package/README.md CHANGED
@@ -43,65 +43,24 @@ const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json')
43
43
  await itd.posts.create((p) => p.content('привет').attach('./photo.jpg'));
44
44
  ```
45
45
 
46
- Готовые примеры — в папке [`examples/`](./examples).
46
+ Продолжение с запускаемыми примерами — в [руководстве по быстрому старту](./guides/quickstart/README.md).
47
+ Все тематические материалы собраны в [`guides/`](./guides/README.md).
47
48
 
48
49
  ---
49
50
 
50
51
  ## Авторизация
51
52
 
52
- Откуда клиент берёт доступ к API — либо из опции `auth`, либо из `storage`, либо
53
- из явного вызова входа. Всё это взаимозаменяемо, и **обязательного варианта нет**.
53
+ Клиент может взять доступ из `auth`, сохранённой сессии или явного вызова `itd.auth`:
54
54
 
55
55
  ```ts
56
- new ItdClient({ auth: '<accessToken>' }); // разовый вызов
57
- new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию строками
58
- new ItdClient({ auth: { email, password, getTurnstileToken } }); // войти самому
59
- new ItdClient({ auth: { getToken: () => vault.read() } }); // токен извне
60
-
61
- new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') }); // сессия с прошлого раза
62
- new ItdClient(); // войти позже, через itd.auth
56
+ new ItdClient({ auth: '<accessToken>' });
57
+ new ItdClient({ auth: { accessToken, refreshToken } });
58
+ new ItdClient({ auth: { email, password, getTurnstileToken } });
59
+ new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });
63
60
  ```
64
61
 
65
- `auth` и `storage` не конкурируют, а дополняют друг друга: **хранилище главнее** — оно
66
- отражает текущее состояние сессии, а недостающие поля берутся из `auth`. Типичный случай:
67
- в хранилище лежит только `accessToken`, а `refreshToken` приходит из настроек приложения.
68
-
69
- ### Сессия из хранилища — `auth` не нужен
70
-
71
- Если предыдущий запуск сохранил сессию, для работы достаточно одного `storage`:
72
-
73
- ```ts
74
- import { ItdClient, FileTokenStorage } from 'itd-api/node';
75
-
76
- const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });
77
-
78
- const me = await itd.users.me(); // токен подставится сам
79
- ```
80
-
81
- Более того, сохранённого `accessToken` не требуется вовсе. Хватает cookie: первый запрос
82
- получит `401`, библиотека продлит сессию и повторит его — вызывающий код ничего не заметит.
83
-
84
- Refresh-токен живёт 30 суток и обновляется при каждом продлении, поэтому регулярно
85
- работающему боту капчу достаточно решить один раз, при первом запуске.
86
-
87
- Проверить, есть ли что продлевать, можно заранее:
88
-
89
- ```ts
90
- if (await itd.auth.hasRefreshSession()) await itd.auth.refresh();
91
- else redirectToLogin();
92
- ```
93
-
94
- ### Продление токена
95
-
96
- При ответе `401` библиотека продлевает сессию и повторяет запрос. Параллельные запросы,
97
- одновременно получившие `401`, ждут **одного** обновления, а не запускают своё — иначе сервер
98
- увидел бы десяток одновременных `refresh` и отверг бы все, кроме первого.
99
-
100
- Отключить автоматику: `autoRefresh: false`, дальше `await itd.auth.refresh()` вручную.
101
-
102
- Когда продлить не удалось, `refresh()` бросает ошибку **сервера** — по её коду видно, что
103
- именно случилось: `SESSION_NOT_FOUND` (сессия отозвана или истекла), `SESSION_REVOKED`,
104
- `REFRESH_TOKEN_MISSING` (продлевать нечем). Та же ошибка приходит в событии `authError`:
62
+ При `401` сессия продлевается, исходный запрос повторяется, а новое состояние сохраняется.
63
+ Параллельные запросы ждут одного refresh. Потерю сессии можно отследить:
105
64
 
106
65
  ```ts
107
66
  itd.on('authError', ({ error }) => {
@@ -109,108 +68,39 @@ itd.on('authError', ({ error }) => {
109
68
  });
110
69
  ```
111
70
 
112
- ### Капча обязательна при входе
113
-
114
- `signIn`, `signUp` и `forgotPassword` требуют токен Cloudflare Turnstile. Сам клиент капчу
115
- не решает — он принимает готовый токен, а решает его кто-то снаружи.
116
-
117
- ```ts
118
- import { TURNSTILE_SITE_KEY } from 'itd-api';
119
-
120
- // в браузере — виджет Turnstile с этим ключом
121
- turnstile.render('#captcha', {
122
- sitekey: TURNSTILE_SITE_KEY,
123
- callback: (turnstileToken) => itd.auth.signIn({ email, password, turnstileToken }),
124
- });
125
- ```
126
-
127
- Токен одноразовый и живёт несколько минут. Долгоживущему боту передавайте не строку,
128
- а источник — он спрашивается заново перед каждой попыткой входа:
71
+ Вход, регистрация и восстановление пароля требуют одноразовый Turnstile token. Подробности
72
+ о cookie, `deviceId`, OTP, собственном storage и автоматическом решателе:
73
+ [руководство по авторизации](./guides/authentication/README.md).
129
74
 
130
- ```ts
131
- new ItdClient({
132
- auth: { email, password, getTurnstileToken: () => captchaSolver.solve() },
133
- });
134
- ```
135
-
136
- В Node таким источником может быть [`itd-api-turnstile`](./turnstile) — отдельный пакет,
137
- который поднимает браузер и приносит токен. Отдельный он намеренно: тянет за собой Playwright
138
- и требует графической оболочки, а нужен далеко не всем — с сохранённой сессией до входа
139
- по паролю дело обычно вообще не доходит.
140
-
141
- ```sh
142
- npm i itd-api-turnstile playwright
143
- ```
144
-
145
- ```ts
146
- import { createTurnstileSolver } from 'itd-api-turnstile';
75
+ ---
147
76
 
148
- new ItdClient({
149
- storage: new FileTokenStorage('./.itd-session.json'),
150
- auth: { email, password, getTurnstileToken: createTurnstileSolver() },
151
- });
152
- ```
77
+ ## Несколько аккаунтов
153
78
 
154
- ### Вход с кодом из письма
79
+ `ItdAccounts` хранит именованные клиенты с отдельными tokens, cookie и `deviceId`, но одним
80
+ `MultiTokenStorage`:
155
81
 
156
82
  ```ts
157
- import { createInterface } from 'node:readline/promises';
83
+ import { ItdAccounts, FileMultiTokenStorage } from 'itd-api/node';
158
84
 
159
- const rl = createInterface({ input: process.stdin, output: process.stdout });
160
-
161
- await itd.auth.signInWithOtp({
162
- email, password, turnstileToken,
163
- getOtp: () => rl.question('Код из письма: '),
85
+ const accounts = new ItdAccounts({
86
+ storage: new FileMultiTokenStorage('./.itd-sessions.json'),
87
+ rateLimit: { concurrency: 4 },
164
88
  });
165
- ```
166
89
 
167
- ### Сброс пароля
90
+ // Поднимаем тех, кто уже входил раньше: ни auth, ни капча не нужны.
91
+ await accounts.restore();
168
92
 
169
- Идёт тем же потоком с кодом: `forgotPassword` возвращает `flowToken`, письмо приносит код.
93
+ if (!accounts.has('kiow')) {
94
+ accounts.addAccount('kiow', { auth: { email, password, getTurnstileToken } });
95
+ }
170
96
 
171
- ```ts
172
- await itd.auth.resetPasswordWithOtp({
173
- email, turnstileToken, newPassword,
174
- getOtp: () => rl.question('Код из письма: '),
175
- });
97
+ const itd = accounts.account('kiow'); // обычный ItdClient со всеми разделами
98
+ await itd.posts.create({ content: 'привет' });
99
+ await accounts.close();
176
100
  ```
177
101
 
178
- ### Хранение сессии
179
-
180
- | Хранилище | Откуда | Среда |
181
- |---|---|---|
182
- | `MemoryTokenStorage` (по умолчанию) | `itd-api` | везде |
183
- | `LocalStorageTokenStorage` | `itd-api` | браузер |
184
- | `FileTokenStorage` | `itd-api/node` | Node, Bun, Deno |
185
- | `createTokenStorage({ get, set, clear })` | `itd-api` | своё: Redis, БД, AsyncStorage |
186
-
187
- По умолчанию сессия живёт в памяти процесса и теряется при перезапуске. Укажите `storage` —
188
- и библиотека сама запишет туда всё нужное после входа и после каждого продления; отдельно
189
- сохранять ничего не надо.
190
-
191
- В сессию попадают `accessToken`, `refreshToken`, cookie и `deviceId`. Сохранять её целиком
192
- важно: refresh-токен приходит в cookie `refresh_token`, а `fetch` вне браузера их не хранит,
193
- поэтому библиотека ведёт собственный cookie-jar. В браузере используется
194
- `credentials: 'include'`, в React Native cookie ведёт нативный слой.
195
-
196
- `deviceId` — идентификатор устройства из заголовка `X-Device-Id`. Сервер различает по нему
197
- записи в списке сессий (`itd.auth.sessions()`), поэтому при постоянном хранилище он переживает
198
- перезапуск и бот не плодит по новой сессии на каждый старт. Своё значение — опцией `deviceId`.
199
-
200
- Refresh-токен можно передать и строкой (`auth: { accessToken, refreshToken }`) — вне браузера
201
- библиотека сама подставит его нужной cookie. В браузере так не выйдет: cookie помечена
202
- `HttpOnly`, и из JS её не выставить.
203
-
204
- **Сервер выдаёт при каждом продлении новый refresh-токен взамен прежнего.** Со штатным
205
- `storage` это происходит само. Если же вы храните сессию сами, снимайте её после каждого
206
- обновления, а не один раз при входе, — иначе сохранённое значение протухнет:
207
-
208
- ```ts
209
- itd.on('tokens', async () => saveSomewhere(await itd.getSession()));
210
-
211
- // при следующем запуске
212
- await itd.setSession(await loadFromSomewhere());
213
- ```
102
+ Личные прокси, общая или раздельные очереди, собственное хранилище и события контейнера
103
+ описаны в [руководстве по нескольким аккаунтам](./guides/multi-accounts/README.md).
214
104
 
215
105
  ---
216
106
 
@@ -287,25 +177,48 @@ await itd.posts.create(draft.content('второй')); // заготовка
287
177
  Файлы из `attach()` загружаются автоматически, порядок вложений сохраняется, MIME-тип
288
178
  проверяется до отправки.
289
179
 
290
- Разметка текста передаётся полем `spans` — библиотека её не генерирует и не пересчитывает.
291
- Известные типы собраны в `SpanType`: `hashtag`, `mention`, `link`, `bold`, `italic`,
292
- `underline`, `strike`, `spoiler`, `monospace`, `quote`.
180
+ ### Разметка текста
181
+
182
+ Билдер собирает текст и сам считает смещения:
293
183
 
294
184
  ```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
- });
185
+ import { post, renderSpans } from 'itd-api';
186
+
187
+ const created = await itd.posts.create(
188
+ post().markup((m) =>
189
+ m
190
+ .text('смотрите ')
191
+ .hashtag('котики')
192
+ .text(' от ')
193
+ .mention('durov')
194
+ .text(': ')
195
+ .bold('важно'),
196
+ ),
197
+ );
198
+
199
+ renderSpans(created.content, created.spans); // безопасный HTML по умолчанию
304
200
  ```
305
201
 
306
- У `link` адрес лежит в `url`, у `hashtag` и `mention` — имя в `tag`.
202
+ Доступны `bold`, `italic`, `underline`, `strike`, `spoiler`, `monospace`, `quote`, `link`,
203
+ `hashtag`, `mention`, `span()` и несколько стилей сразу через `styled()`. Вложенные и
204
+ пересекающиеся spans поддерживаются. Смещения измеряются в единицах UTF-16, как индексы
205
+ строк и DOM Selection в JavaScript.
206
+
207
+ Для обычного текста есть автоматическое обнаружение ссылок, хэштегов и упоминаний:
307
208
 
308
- Билдеры есть у поста, комментария, опроса и жалобы. Все они неизменяемые, а `build()`
209
+ ```ts
210
+ await itd.posts.create(post('#котики от @durov: https://example.com').autoSpans());
211
+ ```
212
+
213
+ `renderSpans()` также выводит Markdown и ANSI и позволяет настроить маршруты упоминаний,
214
+ хэштегов и префикс CSS-классов. Вызов `postBuilder.content(newText)` сбрасывает прежние
215
+ spans, поскольку они рассчитаны для другого текста. `posts.update()` принимает тот же билдер,
216
+ но требует явно заданный `content`.
217
+
218
+ Авторазметка, пересекающиеся стили, обновление и настройка рендера подробно разобраны
219
+ в [руководстве по разметке](./guides/text-markup/README.md).
220
+
221
+ Билдеры есть у разметки, поста, комментария, опроса и жалобы. Все они неизменяемые, а `build()`
309
222
  проверяет данные и бросает `ItdConfigError` **до** обращения к сети:
310
223
 
311
224
  ```ts
@@ -328,33 +241,72 @@ stream.on('notification', ({ notification }) => {
328
241
  console.log(formatNotificationText(notification)); // «Аня и ещё 2 оценили ваш пост»
329
242
  console.log(resolveNotificationUrl(notification)); // '/@anya/post/9f1c…'
330
243
  });
331
- stream.on('unreadCount', (count) => setBadge(count));
332
244
 
333
245
  await stream.connect();
334
246
  ```
335
247
 
336
- События приходят почти мгновенно реакция, комментарий, подписка и репост долетают
337
- за доли секунды после действия.
248
+ REST и поток используют одну форму уведомления. Переподключение, refresh token, keep-alive
249
+ и fallback на polling обрабатываются внутри. Эксплуатационные настройки и счётчик
250
+ непрочитанных разобраны в [руководстве по realtime](./guides/realtime/README.md).
338
251
 
339
- Уведомления из потока и из `itd.notifications.list()` приведены к общей форме, поэтому
340
- складываются в один список. Сервер называет типы коротко (`like`, `comment`, `repost`),
341
- библиотека приводит их к однозначным (`post_reaction`, `post_comment`, `post_repost`),
342
- а пришедшее значение оставляет в `rawType`; весь исходный объект — в `raw`.
252
+ ---
343
253
 
344
- `resolveNotificationUrl()` учитывает, что смысл полей зависит от типа: у комментария
345
- цель — пост, а предмет — сам комментарий; у репоста наоборот, цель — репост, а предмет —
346
- исходная запись. Поэтому ссылка на комментарий ведёт на пост с якорем, а на репост —
347
- на сам репост.
254
+ ## Статус сервисов
255
+
256
+ `itd.platform.status()` отдаёт состояние платформы и историю доступности за 90 суток.
257
+ Авторизация не нужна, ответ кэшируется сервером на минуту.
258
+
259
+ ```ts
260
+ import { statusDays } from 'itd-api';
348
261
 
349
- Соединение держится само: обрывы, продление токена и повторные попытки
350
- (`[1, 2, 4, 8, 16, 30] с`, джиттер ±30%, 15 попыток подряд) обрабатываются внутри.
351
- Сервер шлёт keep-alive `: ping` каждые 15 секунд; если тишина длится дольше `idleTimeout`
352
- (90 секунд по умолчанию), соединение считается мёртвым и поднимается заново.
353
- В браузере поток дополнительно переподключается при возврате вкладки из фона
354
- и восстановлении сети.
262
+ const status = await itd.platform.status();
263
+
264
+ status.overall_status; // 'operational' | 'degraded' | 'downtime'
265
+ status.services.map((s) => s.current_status);
266
+
267
+ const auth = status.services.find((s) => s.id === 'auth');
268
+ auth?.uptime_90d; // 97.92
269
+ auth?.last_checked; // '2026-07-23T23:14:25Z'
270
+
271
+ const days = auth ? statusDays(auth) : []; // 90 элементов, [0] — сегодня
272
+ days[0]?.uptime; // 100
273
+ days[0]?.lines; // [{ t: 'down', text: 'недоступен 6 мин (12:00–12:06)' }]
274
+ ```
275
+
276
+ Поле `days` приходит объектом с числовыми ключами, и сутки без данных сервер пропускает —
277
+ `statusDays()` разворачивает его в массив, где пропуски равны `null`. Строки в `lines`
278
+ готовы к показу как есть: длительность и границы интервала отдельными полями не приходят,
279
+ время в них московское, тогда как `date_key` суток нарезан по UTC.
280
+
281
+ ### Сервисы платформы
282
+
283
+ Статус живёт на отдельном домене — `статус.итд.com`. Такие домены описываются как сервисы:
284
+ у каждого своё имя, хост, заголовки и признак публичности. Запрос выбирает сервис
285
+ полем `service`.
286
+
287
+ ```ts
288
+ const itd = new ItdClient({
289
+ services: {
290
+ pb: {
291
+ baseUrl: 'https://pbapi.xn--d1ah4a.com',
292
+ headers: { Referer: 'https://pixel.xn--d1ah4a.com/' },
293
+ },
294
+ },
295
+ });
296
+
297
+ await itd.request({ method: 'GET', service: 'pb', path: '/api/pixel-info', query: { x: 1, y: 2 } });
298
+ ```
355
299
 
356
- > Счётчик непрочитанных сервер по потоку **не присылает** событие `unreadCount`
357
- > на практике не срабатывает. Считайте сами либо запрашивайте `itd.notifications.count()`.
300
+ То же самое после создания клиента `itd.defineService({ name, baseUrl, headers, auth })`;
301
+ базовый URL сервиса отдаёт `itd.serviceBaseUrl(name)`.
302
+
303
+ Bearer-токен по умолчанию отправляется только основному хосту и его поддоменам.
304
+ Публичный или сторонний сервис его не получает. То же правило действует для разового
305
+ `itd.request({ baseUrl })`; если внешнему хосту действительно нужна авторизация,
306
+ разрешите её явно через `skipAuth: false`.
307
+
308
+ У каждого сервиса своя очередь `rateLimit`: лимит частоты сервер считает по хосту, поэтому
309
+ `429` от статуса не тормозит основной API и наоборот.
358
310
 
359
311
  ---
360
312
 
@@ -397,6 +349,7 @@ const itd = new ItdClient({
397
349
  // Заголовки латиницей: кириллица в них запрещена самим HTTP.
398
350
  userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36', // по умолчанию itd-api/<версия>; false — не слать
399
351
  deviceId: '3f2a…-uuid', // по умолчанию заводится сам и живёт в сессии
352
+ services: { pb: 'https://pbapi.xn--d1ah4a.com' }, // домены сервисов платформы, см. ниже
400
353
  logger: true, // токены и пароли в логах маскируются
401
354
  hooks: {
402
355
  onRequest: (ctx) => console.log(ctx.method, ctx.path),
@@ -456,111 +409,59 @@ rateLimit: { retryDelays: [1000, 5000, 30_000, 60_000, 90_000] } // по умо
456
409
  Поэтому в браузерном приложении укажите в `baseUrl` адрес своего прокси. В Node, Bun,
457
410
  Deno и React Native ограничение не действует.
458
411
 
412
+ Исключение — `itd.platform.status()`: страница статуса отдаёт
413
+ `Access-Control-Allow-Origin: *`, и этот метод работает из браузера напрямую.
414
+
459
415
  ### Прокси (HTTP/SOCKS5)
460
416
 
461
417
  Чтобы направить запросы клиента через прокси, возьмите `fetch` из пакета
462
- [`itd-api-proxy`](./proxy):
418
+ [`@itd-api/proxy`](./proxy/README.md):
463
419
 
464
420
  ```sh
465
- npm i itd-api-proxy
421
+ npm i @itd-api/proxy
466
422
  ```
467
423
 
468
424
  ```ts
469
425
  import { ItdClient } from 'itd-api';
470
- import { proxyFetch } from 'itd-api-proxy';
426
+ import { proxyFetch } from '@itd-api/proxy';
471
427
 
472
- const itd = new ItdClient({ fetch: proxyFetch('socks5://127.0.0.1:1080') });
428
+ const fetch = proxyFetch('socks5://127.0.0.1:1080');
473
429
  // http://…, https://…, socks5://… — можно с user:pass@
430
+ const itd = new ItdClient({ fetch });
431
+
432
+ // …работа…
433
+
434
+ await itd.close();
435
+ await fetch.close(); // закрывает пул соединений
474
436
  ```
475
437
 
476
438
  Через тот же `fetch` пойдут авторизация, cookie, очередь, повторы и поток уведомлений.
477
- Только для Node/Bun/Deno. Подробности в [README пакета](./proxy).
439
+ Только для Node/Bun/Deno. Подключение proxy и Turnstile разобрано в
440
+ [руководстве по интеграциям](./guides/integrations/README.md), параметры транспорта — в
441
+ [README пакета](./proxy/README.md).
478
442
 
479
443
  ---
480
444
 
481
445
  ## Плагины
482
446
 
483
- Плагин обёртка вокруг запроса: она видит тело до отправки и разобранный ответ, поэтому
484
- одна обёртка охватывает сразу все методы клиента. Подключается через `itd.use()`:
447
+ Плагин оборачивает запросы и ответы сразу всех ресурсов:
485
448
 
486
449
  ```ts
487
450
  import { ItdClient } from 'itd-api';
488
- import { crypt } from 'itd-api-crypto';
451
+ import { cache } from '@itd-api/cache';
489
452
 
490
453
  const itd = new ItdClient({ auth: token });
491
- itd.use(crypt());
492
- ```
493
-
494
- ### `itd-api-crypto` скрытые сообщения
495
-
496
- [Отдельный пакет](./crypto): прячет текст в невидимых символах внутри обычного поста.
497
- Читатель видит обложку, а тот, у кого подключён плагин, получает спрятанное отдельным полем.
498
-
499
- ```sh
500
- npm i itd-api-crypto
501
- ```
502
-
503
- ```ts
504
- // отправка: текст прогоняется через шифр, обложка остаётся видимой
505
- const created = await itd.posts.create(
506
- { content: 'секретный текст' },
507
- { encrypt: { cipher: 'invisible', cover: 'обычный пост' } },
454
+ itd.use(
455
+ cache({
456
+ ttl: 60_000,
457
+ routes: ['users.get', 'posts.get', 'posts.list'],
458
+ }),
508
459
  );
509
-
510
- // чтение: content не меняется, расшифровка приезжает рядом
511
- const post = await itd.posts.get(created.id);
512
- post.secret?.text; // 'секретный текст'
513
- ```
514
-
515
- Работает для постов, комментариев, ответов, имени и подписи профиля. Расшифровка идёт сама
516
- и вглубь: находки появляются и у постов ленты, и у исходного поста репоста, и у авторов.
517
-
518
- Шифра два: `invisible` — невидимые символы с обложкой, `beecrypt` — видимый текст из букв
519
- `жъЖЪ`. Подробности, ограничения и то, как подключить свой шифр, — в
520
- [README пакета](./crypto).
521
-
522
- ### Свой плагин
523
-
524
- ```ts
525
- import type { ItdPlugin } from 'itd-api';
526
-
527
- const timing: ItdPlugin = {
528
- name: 'timing',
529
- install({ use, logger }) {
530
- use(async (request, next) => {
531
- const started = Date.now();
532
- try {
533
- return await next(request);
534
- } finally {
535
- logger?.info(`${request.method} ${request.path}: ${Date.now() - started} мс`);
536
- }
537
- });
538
- },
539
- };
540
- ```
541
-
542
- Обёртка может изменить запрос (передайте в `next` копию), подменить ответ или вернуть своё,
543
- не обращаясь к сети. Подключённая раньше оказывается снаружи. Выполняется она один раз
544
- на запрос, независимо от числа повторов.
545
-
546
- Свои опции запроса плагин объявляет сам — библиотека их не понимает, но доносит до обёртки
547
- нетронутыми:
548
-
549
- ```ts
550
- const plugin: ItdPlugin = {
551
- name: 'мой',
552
- optionKeys: ['мояОпция'],
553
- install({ use }) { /* … */ },
554
- };
555
-
556
- declare module 'itd-api' {
557
- interface RequestOptions { мояОпция?: string | undefined }
558
- }
559
460
  ```
560
461
 
561
- Имена полей самого запроса (`path`, `body`, `headers`, `signal` и прочие) заявить нельзя:
562
- подключение такого плагина завершится `ItdConfigError`. Иначе опечатка в `optionKeys`
563
- молча подменяла бы путь или тело любого вызова.
462
+ Официальные плагины Cache и Crypto, полный контракт `ItdPlugin`, собственные опции и
463
+ структура пакета описаны в
464
+ [руководстве по плагинам](./guides/plugins/README.md).
564
465
 
565
466
  ---
566
467
 
@@ -576,10 +477,11 @@ declare module 'itd-api' {
576
477
  | `itd.files` | загрузка медиа |
577
478
  | `itd.hashtags` · `itd.search` | хэштеги, трендовые, глобальный поиск |
578
479
  | `itd.reports` · `itd.verification` | жалобы, заявка на верификацию |
579
- | `itd.subscription` · `itd.platform` | подписка, способы оплаты, анонсы |
480
+ | `itd.subscription` · `itd.platform` | подписка, способы оплаты, анонсы, статус сервисов |
580
481
  | `itd.realtime()` | поток уведомлений |
581
482
  | `itd.use()` | плагины: обёртки вокруг запроса и ответа |
582
483
  | `itd.request()` | произвольный запрос, если метода ещё нет |
484
+ | `ItdAccounts` | несколько аккаунтов с общим хранилищем сессий |
583
485
 
584
486
  Метода не хватает или ответ разошёлся с документацией — есть запасной путь:
585
487
 
@@ -607,7 +509,7 @@ TypeScript 5.0+. Пакет собран в ESM и CommonJS, типы корре
607
509
 
608
510
  ```bash
609
511
  npm install
610
- npm test # 417 тестов
512
+ npm test # 611 тестов
611
513
  npm run test:all # вместе с пакетами workspace
612
514
  npm run typecheck
613
515
  npm run lint