itd-api 0.0.3 → 0.0.5
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 -25
- package/dist/{chunk-JIT55WDH.js → chunk-RUPF4X5L.js} +351 -184
- package/dist/chunk-RUPF4X5L.js.map +1 -0
- package/dist/{chunk-3ODOEKQO.cjs → chunk-YM2YUO4D.cjs} +364 -183
- package/dist/chunk-YM2YUO4D.cjs.map +1 -0
- package/dist/{index-BCuk8jNA.d.cts → index-Dv0LXpMf.d.cts} +216 -42
- package/dist/{index-BCuk8jNA.d.ts → index-Dv0LXpMf.d.ts} +216 -42
- package/dist/index.cjs +126 -70
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node.cjs +125 -69
- 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 +7 -3
- package/dist/chunk-3ODOEKQO.cjs.map +0 -1
- package/dist/chunk-JIT55WDH.js.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,26 +49,70 @@ 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 } }); // восстановить сессию
|
|
57
|
+
new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию строками
|
|
58
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
|
|
63
|
+
```
|
|
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(); // токен подставится сам
|
|
60
79
|
```
|
|
61
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
|
+
|
|
68
112
|
### Капча обязательна при входе
|
|
69
113
|
|
|
70
|
-
`signIn`, `signUp` и `forgotPassword` требуют токен Cloudflare Turnstile.
|
|
71
|
-
|
|
72
|
-
снаружи, а библиотека принимает готовый токен.
|
|
114
|
+
`signIn`, `signUp` и `forgotPassword` требуют токен Cloudflare Turnstile. Сам клиент капчу
|
|
115
|
+
не решает — он принимает готовый токен, а решает его кто-то снаружи.
|
|
73
116
|
|
|
74
117
|
```ts
|
|
75
118
|
import { TURNSTILE_SITE_KEY } from 'itd-api';
|
|
@@ -90,6 +133,24 @@ new ItdClient({
|
|
|
90
133
|
});
|
|
91
134
|
```
|
|
92
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';
|
|
147
|
+
|
|
148
|
+
new ItdClient({
|
|
149
|
+
storage: new FileTokenStorage('./.itd-session.json'),
|
|
150
|
+
auth: { email, password, getTurnstileToken: createTurnstileSolver() },
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
93
154
|
### Вход с кодом из письма
|
|
94
155
|
|
|
95
156
|
```ts
|
|
@@ -123,24 +184,33 @@ await itd.auth.resetPasswordWithOtp({
|
|
|
123
184
|
| `FileTokenStorage` | `itd-api/node` | Node, Bun, Deno |
|
|
124
185
|
| `createTokenStorage({ get, set, clear })` | `itd-api` | своё: Redis, БД, AsyncStorage |
|
|
125
186
|
|
|
126
|
-
|
|
127
|
-
библиотека
|
|
128
|
-
|
|
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 ведёт нативный слой.
|
|
129
195
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
196
|
+
`deviceId` — идентификатор устройства из заголовка `X-Device-Id`. Сервер различает по нему
|
|
197
|
+
записи в списке сессий (`itd.auth.sessions()`), поэтому при постоянном хранилище он переживает
|
|
198
|
+
перезапуск и бот не плодит по новой сессии на каждый старт. Своё значение — опцией `deviceId`.
|
|
133
199
|
|
|
134
|
-
|
|
135
|
-
|
|
200
|
+
Refresh-токен можно передать и строкой (`auth: { accessToken, refreshToken }`) — вне браузера
|
|
201
|
+
библиотека сама подставит его нужной cookie. В браузере так не выйдет: cookie помечена
|
|
202
|
+
`HttpOnly`, и из JS её не выставить.
|
|
203
|
+
|
|
204
|
+
**Сервер выдаёт при каждом продлении новый refresh-токен взамен прежнего.** Со штатным
|
|
205
|
+
`storage` это происходит само. Если же вы храните сессию сами, снимайте её после каждого
|
|
206
|
+
обновления, а не один раз при входе, — иначе сохранённое значение протухнет:
|
|
136
207
|
|
|
137
208
|
```ts
|
|
138
209
|
itd.on('tokens', async () => saveSomewhere(await itd.getSession()));
|
|
139
|
-
```
|
|
140
210
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
211
|
+
// при следующем запуске
|
|
212
|
+
await itd.setSession(await loadFromSomewhere());
|
|
213
|
+
```
|
|
144
214
|
|
|
145
215
|
---
|
|
146
216
|
|
|
@@ -152,8 +222,8 @@ itd.on('tokens', async () => saveSomewhere(await itd.getSession()));
|
|
|
152
222
|
// по элементам
|
|
153
223
|
for await (const post of itd.posts.iterate({ tab: 'popular' })) { … }
|
|
154
224
|
|
|
155
|
-
// по страницам — когда
|
|
156
|
-
for await (const page of itd.
|
|
225
|
+
// по страницам — когда нужны сведения о самой странице
|
|
226
|
+
for await (const page of itd.posts.iterateComments(postId).pages()) {
|
|
157
227
|
console.log(page.items.length, 'из', page.total);
|
|
158
228
|
}
|
|
159
229
|
|
|
@@ -171,6 +241,24 @@ const next = await itd.posts.list({ tab: 'popular', cursor: page.nextCursor ?? u
|
|
|
171
241
|
Курсор непрозрачен: у вкладки `popular` это номер страницы, у `following` — отметка времени.
|
|
172
242
|
Передавайте его обратно как есть.
|
|
173
243
|
|
|
244
|
+
Перебор одноразовый: позиция хранится внутри, поэтому второй `for await` по тому же объекту
|
|
245
|
+
ничего не выдаст. Нужен ещё проход — возьмите новый перебор у того же метода.
|
|
246
|
+
|
|
247
|
+
### Чего API не умеет
|
|
248
|
+
|
|
249
|
+
**Подписчики, подписки и заблокированные не листаются.** Сервер отдаёт первые 20 записей
|
|
250
|
+
и на этом всё: `page` он игнорирует, `limit` больше 20 молча уменьшает, а `hasMore` всегда
|
|
251
|
+
`false`. Числу `total` там тоже верить нельзя: оно расходится с `followersCount` из профиля.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
// вернёт 20 записей и остановится — это предел API, а не библиотеки
|
|
255
|
+
const all = await itd.users.iterateFollowers('durov').collect();
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**`posts.byUser()` — это стена, а не авторские посты.** В неё входят и записи, которые
|
|
259
|
+
другие оставили на странице пользователя, поэтому записей обычно больше, чем `postsCount`
|
|
260
|
+
в профиле. Нужны только свои — отфильтруйте по `post.author.id`.
|
|
261
|
+
|
|
174
262
|
---
|
|
175
263
|
|
|
176
264
|
## Публикация
|
|
@@ -283,11 +371,14 @@ try {
|
|
|
283
371
|
```ts
|
|
284
372
|
const itd = new ItdClient({
|
|
285
373
|
baseUrl: 'https://xn--d1ah4a.com', // свой прокси, если работаете из браузера
|
|
286
|
-
auth: { email, password },
|
|
374
|
+
auth: { email, password, getTurnstileToken },
|
|
287
375
|
storage: new FileTokenStorage('./.itd-session.json'),
|
|
288
376
|
timeout: 30_000,
|
|
289
377
|
retry: { attempts: 3, retryWrites: false },
|
|
290
378
|
rateLimit: { concurrency: 4, rps: 8 },
|
|
379
|
+
// Заголовки латиницей: кириллица в них запрещена самим HTTP.
|
|
380
|
+
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 — не слать
|
|
381
|
+
deviceId: '3f2a…-uuid', // по умолчанию заводится сам и живёт в сессии
|
|
291
382
|
logger: true, // токены и пароли в логах маскируются
|
|
292
383
|
hooks: {
|
|
293
384
|
onRequest: (ctx) => console.log(ctx.method, ctx.path),
|
|
@@ -300,7 +391,24 @@ const itd = new ItdClient({
|
|
|
300
391
|
по умолчанию не повторяется (`retryWrites: false`): повтор мог бы создать дубль поста.
|
|
301
392
|
Чтения повторяются с экспоненциальным откатом.
|
|
302
393
|
|
|
303
|
-
|
|
394
|
+
**Очередь: `concurrency` и `rps` решают разные задачи.** Все запросы идут через одну очередь
|
|
395
|
+
клиента, поэтому достаточно **одного экземпляра `ItdClient` на приложение** — разложите его
|
|
396
|
+
по модулям, и темп будет общим.
|
|
397
|
+
|
|
398
|
+
`concurrency` (по умолчанию 6) ограничивает только одновременность. От ограничения частоты
|
|
399
|
+
он почти не спасает: десять запросов подряд при `concurrency: 1` уходят за ~150 мс,
|
|
400
|
+
а окно сервера измеряется десятками секунд. Темп задаёт `rps`:
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
rateLimit: { concurrency: 2, rps: 0.5 } // не чаще одного запроса в 2 секунды
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Ставить `concurrency: 1` без нужды не стоит: загрузка видео с таймаутом в 300 секунд
|
|
407
|
+
заблокирует на это время вообще всё остальное.
|
|
408
|
+
|
|
409
|
+
**Ограничение частоты — отдельный механизм.** Лимит у каждого эндпоинта свой: замеры по
|
|
410
|
+
`x-ratelimit-limit` дали 90 у `/api/posts`, 40 у `/api/users/me` и `/api/notifications/`,
|
|
411
|
+
25 у `/api/v1/auth/refresh` и всего 15 у `/api/files/upload`. Сервер
|
|
304
412
|
не присылает `Retry-After` и не сообщает, когда окно сбросится: есть только заголовки
|
|
305
413
|
`x-ratelimit-limit` и `x-ratelimit-remaining` (доступны на `ItdRateLimitError` как
|
|
306
414
|
`rateLimit` и `rateLimitRemaining`).
|