itd-api 0.0.2 → 0.0.4
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 +133 -11
- package/dist/{chunk-76C65H5J.js → chunk-JV75JWOX.js} +646 -275
- package/dist/chunk-JV75JWOX.js.map +1 -0
- package/dist/{chunk-QILCVTJI.cjs → chunk-XG43KEYF.cjs} +667 -274
- package/dist/chunk-XG43KEYF.cjs.map +1 -0
- package/dist/{index-RzyK1gKg.d.cts → index-olL_q_yu.d.cts} +392 -47
- package/dist/{index-RzyK1gKg.d.ts → index-olL_q_yu.d.ts} +392 -47
- package/dist/index.cjs +150 -62
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node.cjs +149 -61
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +8 -12
- package/dist/node.d.ts +8 -12
- package/dist/node.js +2 -2
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-76C65H5J.js.map +0 -1
- package/dist/chunk-QILCVTJI.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -36,10 +36,9 @@ for await (const post of itd.posts.iterate({ tab: FeedTab.Following })) {
|
|
|
36
36
|
```ts
|
|
37
37
|
import { ItdClient, FileTokenStorage } from 'itd-api/node';
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
39
|
+
// Сессия из файла: продлевается сама, `auth` не нужен. Как её туда положить —
|
|
40
|
+
// в разделе «Авторизация»: вход требует капчи, поэтому делается один раз.
|
|
41
|
+
const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });
|
|
43
42
|
|
|
44
43
|
await itd.posts.create((p) => p.content('привет').attach('./photo.jpg'));
|
|
45
44
|
```
|
|
@@ -50,21 +49,91 @@ await itd.posts.create((p) => p.content('привет').attach('./photo.jpg'));
|
|
|
50
49
|
|
|
51
50
|
## Авторизация
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
Откуда клиент берёт доступ к API — либо из опции `auth`, либо из `storage`, либо
|
|
53
|
+
из явного вызова входа. Всё это взаимозаменяемо, и **обязательного варианта нет**.
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
56
|
new ItdClient({ auth: '<accessToken>' }); // разовый вызов
|
|
57
|
-
new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию
|
|
58
|
-
new ItdClient({ auth: { email, password } });
|
|
57
|
+
new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию строками
|
|
58
|
+
new ItdClient({ auth: { email, password, getTurnstileToken } }); // войти самому
|
|
59
59
|
new ItdClient({ auth: { getToken: () => vault.read() } }); // токен извне
|
|
60
|
+
|
|
61
|
+
new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') }); // сессия с прошлого раза
|
|
62
|
+
new ItdClient(); // войти позже, через itd.auth
|
|
60
63
|
```
|
|
61
64
|
|
|
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
|
+
|
|
62
96
|
При ответе `401` библиотека продлевает сессию и повторяет запрос. Параллельные запросы,
|
|
63
97
|
одновременно получившие `401`, ждут **одного** обновления, а не запускают своё — иначе сервер
|
|
64
98
|
увидел бы десяток одновременных `refresh` и отверг бы все, кроме первого.
|
|
65
99
|
|
|
66
100
|
Отключить автоматику: `autoRefresh: false`, дальше `await itd.auth.refresh()` вручную.
|
|
67
101
|
|
|
102
|
+
Когда продлить не удалось, `refresh()` бросает ошибку **сервера** — по её коду видно, что
|
|
103
|
+
именно случилось: `SESSION_NOT_FOUND` (сессия отозвана или истекла), `SESSION_REVOKED`,
|
|
104
|
+
`REFRESH_TOKEN_MISSING` (продлевать нечем). Та же ошибка приходит в событии `authError`:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
itd.on('authError', ({ error }) => {
|
|
108
|
+
if (isItdApiError(error)) console.error('Сессия потеряна:', error.code);
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Капча обязательна при входе
|
|
113
|
+
|
|
114
|
+
`signIn`, `signUp` и `forgotPassword` требуют токен Cloudflare Turnstile. Полностью
|
|
115
|
+
автоматического входа по логину и паролю поэтому не бывает: капчу должен решить кто-то
|
|
116
|
+
снаружи, а библиотека принимает готовый токен.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { TURNSTILE_SITE_KEY } from 'itd-api';
|
|
120
|
+
|
|
121
|
+
// в браузере — виджет Turnstile с этим ключом
|
|
122
|
+
turnstile.render('#captcha', {
|
|
123
|
+
sitekey: TURNSTILE_SITE_KEY,
|
|
124
|
+
callback: (turnstileToken) => itd.auth.signIn({ email, password, turnstileToken }),
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Токен одноразовый и живёт несколько минут. Долгоживущему боту передавайте не строку,
|
|
129
|
+
а источник — он спрашивается заново перед каждой попыткой входа:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
new ItdClient({
|
|
133
|
+
auth: { email, password, getTurnstileToken: () => captchaSolver.solve() },
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
68
137
|
### Вход с кодом из письма
|
|
69
138
|
|
|
70
139
|
```ts
|
|
@@ -73,7 +142,18 @@ import { createInterface } from 'node:readline/promises';
|
|
|
73
142
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
74
143
|
|
|
75
144
|
await itd.auth.signInWithOtp({
|
|
76
|
-
email, password,
|
|
145
|
+
email, password, turnstileToken,
|
|
146
|
+
getOtp: () => rl.question('Код из письма: '),
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Сброс пароля
|
|
151
|
+
|
|
152
|
+
Идёт тем же потоком с кодом: `forgotPassword` возвращает `flowToken`, письмо приносит код.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
await itd.auth.resetPasswordWithOtp({
|
|
156
|
+
email, turnstileToken, newPassword,
|
|
77
157
|
getOtp: () => rl.question('Код из письма: '),
|
|
78
158
|
});
|
|
79
159
|
```
|
|
@@ -87,10 +167,34 @@ await itd.auth.signInWithOtp({
|
|
|
87
167
|
| `FileTokenStorage` | `itd-api/node` | Node, Bun, Deno |
|
|
88
168
|
| `createTokenStorage({ get, set, clear })` | `itd-api` | своё: Redis, БД, AsyncStorage |
|
|
89
169
|
|
|
90
|
-
|
|
91
|
-
|
|
170
|
+
По умолчанию сессия живёт в памяти процесса и теряется при перезапуске. Укажите `storage` —
|
|
171
|
+
и библиотека сама запишет туда всё нужное после входа и после каждого продления; отдельно
|
|
172
|
+
сохранять ничего не надо.
|
|
173
|
+
|
|
174
|
+
В сессию попадают `accessToken`, `refreshToken`, cookie и `deviceId`. Сохранять её целиком
|
|
175
|
+
важно: refresh-токен приходит в cookie `refresh_token`, а `fetch` вне браузера их не хранит,
|
|
176
|
+
поэтому библиотека ведёт собственный cookie-jar. В браузере используется
|
|
92
177
|
`credentials: 'include'`, в React Native cookie ведёт нативный слой.
|
|
93
178
|
|
|
179
|
+
`deviceId` — идентификатор устройства из заголовка `X-Device-Id`. Сервер различает по нему
|
|
180
|
+
записи в списке сессий (`itd.auth.sessions()`), поэтому при постоянном хранилище он переживает
|
|
181
|
+
перезапуск и бот не плодит по новой сессии на каждый старт. Своё значение — опцией `deviceId`.
|
|
182
|
+
|
|
183
|
+
Refresh-токен можно передать и строкой (`auth: { accessToken, refreshToken }`) — вне браузера
|
|
184
|
+
библиотека сама подставит его нужной cookie. В браузере так не выйдет: cookie помечена
|
|
185
|
+
`HttpOnly`, и из JS её не выставить.
|
|
186
|
+
|
|
187
|
+
**Сервер выдаёт при каждом продлении новый refresh-токен взамен прежнего.** Со штатным
|
|
188
|
+
`storage` это происходит само. Если же вы храните сессию сами, снимайте её после каждого
|
|
189
|
+
обновления, а не один раз при входе, — иначе сохранённое значение протухнет:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
itd.on('tokens', async () => saveSomewhere(await itd.getSession()));
|
|
193
|
+
|
|
194
|
+
// при следующем запуске
|
|
195
|
+
await itd.setSession(await loadFromSomewhere());
|
|
196
|
+
```
|
|
197
|
+
|
|
94
198
|
---
|
|
95
199
|
|
|
96
200
|
## Пагинация
|
|
@@ -232,11 +336,14 @@ try {
|
|
|
232
336
|
```ts
|
|
233
337
|
const itd = new ItdClient({
|
|
234
338
|
baseUrl: 'https://xn--d1ah4a.com', // свой прокси, если работаете из браузера
|
|
235
|
-
auth: { email, password },
|
|
339
|
+
auth: { email, password, getTurnstileToken },
|
|
236
340
|
storage: new FileTokenStorage('./.itd-session.json'),
|
|
237
341
|
timeout: 30_000,
|
|
238
342
|
retry: { attempts: 3, retryWrites: false },
|
|
239
343
|
rateLimit: { concurrency: 4, rps: 8 },
|
|
344
|
+
// Заголовки латиницей: кириллица в них запрещена самим HTTP.
|
|
345
|
+
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 — не слать
|
|
346
|
+
deviceId: '3f2a…-uuid', // по умолчанию заводится сам и живёт в сессии
|
|
240
347
|
logger: true, // токены и пароли в логах маскируются
|
|
241
348
|
hooks: {
|
|
242
349
|
onRequest: (ctx) => console.log(ctx.method, ctx.path),
|
|
@@ -249,6 +356,21 @@ const itd = new ItdClient({
|
|
|
249
356
|
по умолчанию не повторяется (`retryWrites: false`): повтор мог бы создать дубль поста.
|
|
250
357
|
Чтения повторяются с экспоненциальным откатом.
|
|
251
358
|
|
|
359
|
+
**Очередь: `concurrency` и `rps` решают разные задачи.** Все запросы идут через одну очередь
|
|
360
|
+
клиента, поэтому достаточно **одного экземпляра `ItdClient` на приложение** — разложите его
|
|
361
|
+
по модулям, и темп будет общим.
|
|
362
|
+
|
|
363
|
+
`concurrency` (по умолчанию 6) ограничивает только одновременность. От ограничения частоты
|
|
364
|
+
он почти не спасает: десять запросов подряд при `concurrency: 1` уходят за ~150 мс, а окно
|
|
365
|
+
сервера — около 5 запросов на минуту с лишним. Темп задаёт `rps`:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
rateLimit: { concurrency: 2, rps: 0.5 } // не чаще одного запроса в 2 секунды
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Ставить `concurrency: 1` без нужды не стоит: загрузка видео с таймаутом в 300 секунд
|
|
372
|
+
заблокирует на это время вообще всё остальное.
|
|
373
|
+
|
|
252
374
|
**Ограничение частоты — отдельный механизм.** Сервер разрешает около 5 запросов в окно,
|
|
253
375
|
не присылает `Retry-After` и не сообщает, когда окно сбросится: есть только заголовки
|
|
254
376
|
`x-ratelimit-limit` и `x-ratelimit-remaining` (доступны на `ItdRateLimitError` как
|