itd-api 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 itd-api contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,56 @@
1
+ itd-api включает в свою сборку код следующих библиотек.
2
+ Их лицензии приведены полностью, как того требует MIT.
3
+
4
+ ================================================================================
5
+ eventsource-parser — https://github.com/rexxars/eventsource-parser
6
+ Используется для разбора кадров text/event-stream в потоке уведомлений.
7
+ ================================================================================
8
+
9
+ MIT License
10
+
11
+ Copyright (c) 2026 Espen Hovlandsdal <espen@hovlandsdal.com>
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+
31
+ ================================================================================
32
+ set-cookie-parser — https://github.com/nfriedly/set-cookie-parser
33
+ Используется для разбора заголовков Set-Cookie вне браузера.
34
+ ================================================================================
35
+
36
+ The MIT License (MIT)
37
+
38
+ Copyright (c) 2015 Nathan Friedly <nathan@nfriedly.com> (http://nfriedly.com/)
39
+
40
+ Permission is hereby granted, free of charge, to any person obtaining a copy
41
+ of this software and associated documentation files (the "Software"), to deal
42
+ in the Software without restriction, including without limitation the rights
43
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
44
+ copies of the Software, and to permit persons to whom the Software is
45
+ furnished to do so, subject to the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be included in
48
+ all copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
52
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
53
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
54
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
55
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
56
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,342 @@
1
+ # itd-api
2
+
3
+ Клиент REST и realtime API социальной сети **итд.com** для JavaScript и TypeScript.
4
+
5
+ - **Ноль зависимостей** у установленного пакета
6
+ - **Работает везде**: Node 18+, браузер, Bun, Deno, React Native — только web-стандарты
7
+ - **ESM и CommonJS**, полные `.d.ts` с описаниями на русском
8
+ - Авторизация, продление токена, повторы и очередь запросов — **сами**
9
+ - Три схемы пагинации спрятаны за одним `for await`
10
+ - Уведомления из REST и из потока приведены к **одной форме**
11
+
12
+ ```bash
13
+ npm install itd-api
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Быстрый старт
19
+
20
+ ```ts
21
+ import { ItdClient, FeedTab } from 'itd-api';
22
+
23
+ const itd = new ItdClient({ auth: process.env.ITD_TOKEN });
24
+
25
+ const me = await itd.users.me();
26
+ console.log(`@${me.username}, подписчиков: ${me.followersCount}`);
27
+
28
+ for await (const post of itd.posts.iterate({ tab: FeedTab.Following })) {
29
+ if (!post.isLiked) await itd.posts.like(post.id);
30
+ }
31
+ ```
32
+
33
+ В Node, Bun и Deno импортируйте `itd-api/node` — оттуда доступны загрузка файлов по пути
34
+ и хранение сессии в файле:
35
+
36
+ ```ts
37
+ import { ItdClient, FileTokenStorage } from 'itd-api/node';
38
+
39
+ const itd = new ItdClient({
40
+ auth: { email, password },
41
+ storage: new FileTokenStorage('./.itd-session.json'),
42
+ });
43
+
44
+ await itd.posts.create((p) => p.content('привет').attach('./photo.jpg'));
45
+ ```
46
+
47
+ Готовые примеры — в папке [`examples/`](./examples).
48
+
49
+ ---
50
+
51
+ ## Авторизация
52
+
53
+ Четыре формы на выбор:
54
+
55
+ ```ts
56
+ new ItdClient({ auth: '<accessToken>' }); // разовый вызов
57
+ new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию
58
+ new ItdClient({ auth: { email, password } }); // войти самому
59
+ new ItdClient({ auth: { getToken: () => vault.read() } }); // токен извне
60
+ ```
61
+
62
+ При ответе `401` библиотека продлевает сессию и повторяет запрос. Параллельные запросы,
63
+ одновременно получившие `401`, ждут **одного** обновления, а не запускают своё — иначе сервер
64
+ увидел бы десяток одновременных `refresh` и отверг бы все, кроме первого.
65
+
66
+ Отключить автоматику: `autoRefresh: false`, дальше `await itd.auth.refresh()` вручную.
67
+
68
+ ### Вход с кодом из письма
69
+
70
+ ```ts
71
+ import { createInterface } from 'node:readline/promises';
72
+
73
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
74
+
75
+ await itd.auth.signInWithOtp({
76
+ email, password,
77
+ getOtp: () => rl.question('Код из письма: '),
78
+ });
79
+ ```
80
+
81
+ ### Хранение сессии
82
+
83
+ | Хранилище | Откуда | Среда |
84
+ |---|---|---|
85
+ | `MemoryTokenStorage` (по умолчанию) | `itd-api` | везде |
86
+ | `LocalStorageTokenStorage` | `itd-api` | браузер |
87
+ | `FileTokenStorage` | `itd-api/node` | Node, Bun, Deno |
88
+ | `createTokenStorage({ get, set, clear })` | `itd-api` | своё: Redis, БД, AsyncStorage |
89
+
90
+ Refresh-токен приходит в cookie, а `fetch` вне браузера их не хранит — библиотека ведёт
91
+ собственный cookie-jar и сохраняет его вместе с сессией. В браузере используется
92
+ `credentials: 'include'`, в React Native cookie ведёт нативный слой.
93
+
94
+ ---
95
+
96
+ ## Пагинация
97
+
98
+ Три разные схемы API (курсор, страницы, смещение) выглядят одинаково:
99
+
100
+ ```ts
101
+ // по элементам
102
+ for await (const post of itd.posts.iterate({ tab: 'popular' })) { … }
103
+
104
+ // по страницам — когда нужен, например, total
105
+ for await (const page of itd.users.iterateFollowers('durov').pages()) {
106
+ console.log(page.items.length, 'из', page.total);
107
+ }
108
+
109
+ // набрать нужное количество и остановиться
110
+ const posts = await itd.posts.iterate({ tab: 'popular' }).collect(100);
111
+ ```
112
+
113
+ Отдельные страницы тоже доступны:
114
+
115
+ ```ts
116
+ const page = await itd.posts.list({ tab: 'popular', limit: 20 });
117
+ const next = await itd.posts.list({ tab: 'popular', cursor: page.nextCursor ?? undefined });
118
+ ```
119
+
120
+ Курсор непрозрачен: у вкладки `popular` это номер страницы, у `following` — отметка времени.
121
+ Передавайте его обратно как есть.
122
+
123
+ ---
124
+
125
+ ## Публикация
126
+
127
+ Три равноправные формы, проверки одинаковы для каждой:
128
+
129
+ ```ts
130
+ // обычный объект
131
+ await itd.posts.create({ content: 'привет' });
132
+
133
+ // функция-настройщик — импорты не нужны
134
+ await itd.posts.create((p) =>
135
+ p.content('привет')
136
+ .attach('./photo.jpg')
137
+ .poll((q) => q.question('нравится?').options('да', 'нет')),
138
+ );
139
+
140
+ // билдер — когда объект готовится заранее
141
+ import { post, poll } from 'itd-api';
142
+
143
+ const draft = post().onWall(userId);
144
+ await itd.posts.create(draft.content('первый'));
145
+ await itd.posts.create(draft.content('второй')); // заготовка не испорчена
146
+ ```
147
+
148
+ Файлы из `attach()` загружаются автоматически, порядок вложений сохраняется, MIME-тип
149
+ проверяется до отправки.
150
+
151
+ Билдеры есть у поста, комментария, опроса и жалобы. Все они неизменяемые, а `build()`
152
+ проверяет данные и бросает `ItdConfigError` **до** обращения к сети:
153
+
154
+ ```ts
155
+ post('привет').onWall('durov');
156
+ // ItdConfigError: wallRecipientId должен быть UUID, а не именем пользователя
157
+ // (получено: «durov»). Идентификатор можно взять из профиля:
158
+ // (await itd.users.get(username)).id
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Уведомления и realtime
164
+
165
+ ```ts
166
+ import { formatNotificationText, resolveNotificationUrl } from 'itd-api';
167
+
168
+ const stream = itd.realtime();
169
+
170
+ stream.on('notification', ({ notification }) => {
171
+ console.log(formatNotificationText(notification)); // «Аня и ещё 2 оценили ваш пост»
172
+ console.log(resolveNotificationUrl(notification)); // '/@anya/post/9f1c…'
173
+ });
174
+ stream.on('unreadCount', (count) => setBadge(count));
175
+
176
+ await stream.connect();
177
+ ```
178
+
179
+ События приходят почти мгновенно — реакция, комментарий, подписка и репост долетают
180
+ за доли секунды после действия.
181
+
182
+ Уведомления из потока и из `itd.notifications.list()` приведены к общей форме, поэтому
183
+ складываются в один список. Сервер называет типы коротко (`like`, `comment`, `repost`),
184
+ библиотека приводит их к однозначным (`post_reaction`, `post_comment`, `post_repost`),
185
+ а пришедшее значение оставляет в `rawType`; весь исходный объект — в `raw`.
186
+
187
+ `resolveNotificationUrl()` учитывает, что смысл полей зависит от типа: у комментария
188
+ цель — пост, а предмет — сам комментарий; у репоста наоборот, цель — репост, а предмет —
189
+ исходная запись. Поэтому ссылка на комментарий ведёт на пост с якорем, а на репост —
190
+ на сам репост.
191
+
192
+ Соединение держится само: обрывы, продление токена и повторные попытки
193
+ (`[1, 2, 4, 8, 16, 30] с`, джиттер ±30%, 15 попыток подряд) обрабатываются внутри.
194
+ Сервер шлёт keep-alive `: ping` каждые 15 секунд; если тишина длится дольше `idleTimeout`
195
+ (90 секунд по умолчанию), соединение считается мёртвым и поднимается заново.
196
+ В браузере поток дополнительно переподключается при возврате вкладки из фона
197
+ и восстановлении сети.
198
+
199
+ > Счётчик непрочитанных сервер по потоку **не присылает** — событие `unreadCount`
200
+ > на практике не срабатывает. Считайте сами либо запрашивайте `itd.notifications.count()`.
201
+
202
+ ---
203
+
204
+ ## Ошибки
205
+
206
+ Обе формы ошибок API сведены к одному классу:
207
+
208
+ ```ts
209
+ import { ItdValidationError, ItdRateLimitError, isItdApiError } from 'itd-api';
210
+
211
+ try {
212
+ await itd.users.updateMe({ username: 'занятое_имя' });
213
+ } catch (error) {
214
+ if (error instanceof ItdValidationError) {
215
+ console.log(error.fieldErrors.username); // ['Имя уже занято']
216
+ } else if (error instanceof ItdRateLimitError) {
217
+ console.log(error.retryAfter); // мс
218
+ } else if (isItdApiError(error)) {
219
+ console.log(error.status, error.code, error.message);
220
+ }
221
+ }
222
+ ```
223
+
224
+ `ItdApiError` → `ItdValidationError`, `ItdAuthError`, `ItdForbiddenError`, `ItdNotFoundError`,
225
+ `ItdConflictError`, `ItdRateLimitError`, `ItdPhoneVerificationError`, `ItdServerError`.
226
+ Отдельно: `ItdNetworkError`, `ItdTimeoutError`, `ItdAbortError`, `ItdConfigError`.
227
+
228
+ ---
229
+
230
+ ## Настройка
231
+
232
+ ```ts
233
+ const itd = new ItdClient({
234
+ baseUrl: 'https://xn--d1ah4a.com', // свой прокси, если работаете из браузера
235
+ auth: { email, password },
236
+ storage: new FileTokenStorage('./.itd-session.json'),
237
+ timeout: 30_000,
238
+ retry: { attempts: 3, retryWrites: false },
239
+ rateLimit: { concurrency: 4, rps: 8 },
240
+ logger: true, // токены и пароли в логах маскируются
241
+ hooks: {
242
+ onRequest: (ctx) => console.log(ctx.method, ctx.path),
243
+ onRetry: (ctx) => console.log('повтор через', ctx.delay),
244
+ },
245
+ });
246
+ ```
247
+
248
+ **Повторы.** Обрыв сети и `5xx` не гарантируют, что запрос не был обработан, поэтому запись
249
+ по умолчанию не повторяется (`retryWrites: false`): повтор мог бы создать дубль поста.
250
+ Чтения повторяются с экспоненциальным откатом.
251
+
252
+ **Ограничение частоты — отдельный механизм.** Сервер разрешает около 5 запросов в окно,
253
+ не присылает `Retry-After` и не сообщает, когда окно сбросится: есть только заголовки
254
+ `x-ratelimit-limit` и `x-ratelimit-remaining` (доступны на `ItdRateLimitError` как
255
+ `rateLimit` и `rateLimitRemaining`).
256
+
257
+ Экспоненциальный откат в сотни миллисекунд при окне около минуты бесполезен, поэтому
258
+ для `429` используется лестница пауз:
259
+
260
+ ```ts
261
+ rateLimit: { retryDelays: [1000, 5000, 30_000, 60_000, 90_000] } // по умолчанию
262
+ ```
263
+
264
+ Первый шаг короткий — вдруг окно уже истекло, тогда работа продолжится почти сразу.
265
+ Дальше паузы выходят на масштаб окна. Когда лестница кончилась, `ItdRateLimitError`
266
+ пробрасывается вам. Список не зависит от `retry.attempts` и переопределяется одной строкой.
267
+
268
+ Дополнительно очередь **тормозит заранее**: как только `x-ratelimit-remaining` доходит
269
+ до нуля, запросы придерживаются, не дожидаясь отказа. Отключается через
270
+ `rateLimit: { respectHeaders: false }`.
271
+
272
+ ### Про CORS
273
+
274
+ **Напрямую из браузера запросы работать не будут.** Проверено запросами к боевому API:
275
+ на preflight сервер отвечает `204` с `Access-Control-Allow-Methods` и
276
+ `Access-Control-Allow-Credentials`, но **без `Access-Control-Allow-Origin`** — браузер
277
+ такой ответ отвергает.
278
+
279
+ Поэтому в браузерном приложении укажите в `baseUrl` адрес своего прокси. В Node, Bun,
280
+ Deno и React Native ограничение не действует.
281
+
282
+ ---
283
+
284
+ ## Что доступно
285
+
286
+ | Раздел | Методы |
287
+ |---|---|
288
+ | `itd.auth` | вход, регистрация, OTP, пароли, сессии, OAuth-ссылки |
289
+ | `itd.users` | профили, подписки, блокировки, приватность, значки |
290
+ | `itd.posts` | лента, публикация, реакции, репосты, опросы, комментарии к постам |
291
+ | `itd.comments` | ответы, редактирование, реакции |
292
+ | `itd.notifications` | список, счётчик, отметки о прочтении, настройки |
293
+ | `itd.files` | загрузка медиа |
294
+ | `itd.hashtags` · `itd.search` | хэштеги, трендовые, глобальный поиск |
295
+ | `itd.reports` · `itd.verification` | жалобы, заявка на верификацию |
296
+ | `itd.subscription` · `itd.platform` | подписка, способы оплаты, анонсы |
297
+ | `itd.realtime()` | поток уведомлений |
298
+ | `itd.request()` | произвольный запрос, если метода ещё нет |
299
+
300
+ Метода не хватает или ответ разошёлся с документацией — есть запасной путь:
301
+
302
+ ```ts
303
+ const raw = await itd.request({ method: 'GET', path: '/api/что-то', raw: true });
304
+ ```
305
+
306
+ ---
307
+
308
+ ## Совместимость
309
+
310
+ | Среда | Поддержка |
311
+ |---|---|
312
+ | Node.js 18+ | полная, включая `itd-api/node` |
313
+ | Bun, Deno | полная |
314
+ | Браузер | всё, кроме файловой системы; нужен прокси из-за CORS |
315
+ | React Native | полная; realtime автоматически переключается на опрос, если нет потокового чтения |
316
+
317
+ TypeScript 5.0+. Пакет собран в ESM и CommonJS, типы корректны во всех режимах
318
+ резолвинга (проверено `publint` и `@arethetypeswrong/cli`).
319
+
320
+ ---
321
+
322
+ ## Разработка
323
+
324
+ ```bash
325
+ npm install
326
+ npm test # 342 теста
327
+ npm run typecheck
328
+ npm run lint
329
+ npm run build
330
+ npm run check:pack # publint + attw
331
+ npm run docs # сайт документации из TSDoc
332
+ ```
333
+
334
+ Тесты не обращаются к сети: `fetch` подменяется через опцию конфигурации.
335
+
336
+ ---
337
+
338
+ ## Лицензия
339
+
340
+ MIT. Библиотека не связана с итд.com и разработана независимо.
341
+
342
+ Сторонний код, включённый в сборку, перечислен в [NOTICE](./NOTICE).