itd-api 0.0.11 → 0.2.0
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 +114 -485
- package/dist/index.cjs +8781 -415
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4990 -1
- package/dist/index.d.ts +4990 -1
- package/dist/index.js +8688 -2
- package/dist/index.js.map +1 -1
- package/dist/multi-storage-BhcA2Izn.d.ts +198 -0
- package/dist/multi-storage-CyMe404l.js +805 -0
- package/dist/multi-storage-CyMe404l.js.map +1 -0
- package/dist/multi-storage-D1keK2Op.cjs +930 -0
- package/dist/multi-storage-D1keK2Op.cjs.map +1 -0
- package/dist/multi-storage-NDqzRQcD.d.cts +198 -0
- package/dist/node.cjs +225 -548
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +46 -59
- package/dist/node.d.ts +46 -59
- package/dist/node.js +223 -126
- package/dist/node.js.map +1 -1
- package/dist/runtime-CFEsf-jD.cjs +185 -0
- package/dist/runtime-CFEsf-jD.cjs.map +1 -0
- package/dist/runtime-DHxDn8gf.js +126 -0
- package/dist/runtime-DHxDn8gf.js.map +1 -0
- package/dist/storage-BjNRlkbE.d.cts +82 -0
- package/dist/storage-BjNRlkbE.d.ts +82 -0
- package/dist/storage-D9tfHx7Z.js +424 -0
- package/dist/storage-D9tfHx7Z.js.map +1 -0
- package/dist/storage-ycBqLBRB.cjs +615 -0
- package/dist/storage-ycBqLBRB.cjs.map +1 -0
- package/dist/web.cjs +87 -0
- package/dist/web.cjs.map +1 -0
- package/dist/web.d.cts +27 -0
- package/dist/web.d.ts +27 -0
- package/dist/web.js +86 -0
- package/dist/web.js.map +1 -0
- package/package.json +34 -14
- package/dist/chunk-QD4UHJFF.cjs +0 -7387
- package/dist/chunk-QD4UHJFF.cjs.map +0 -1
- package/dist/chunk-TB7HW3VX.js +0 -7277
- package/dist/chunk-TB7HW3VX.js.map +0 -1
- package/dist/index-CrlTO7sR.d.cts +0 -4858
- package/dist/index-CrlTO7sR.d.ts +0 -4858
- package/guides/README.md +0 -22
- package/guides/authentication/README.md +0 -176
- package/guides/authentication/examples/bot-with-session.mjs +0 -98
- package/guides/authentication/examples/turnstile-login.mjs +0 -56
- package/guides/integrations/README.md +0 -62
- package/guides/integrations/examples/proxy.mjs +0 -26
- package/guides/multi-accounts/README.md +0 -142
- package/guides/multi-accounts/examples/multi-accounts.mjs +0 -71
- package/guides/plugins/README.md +0 -162
- package/guides/plugins/examples/cache.mjs +0 -33
- package/guides/plugins/examples/crypto.mjs +0 -54
- package/guides/quickstart/README.md +0 -124
- package/guides/quickstart/examples/quick-start.mjs +0 -44
- package/guides/quickstart/examples/typescript.ts +0 -90
- package/guides/realtime/README.md +0 -109
- package/guides/realtime/examples/notifications.mjs +0 -62
- package/guides/text-markup/README.md +0 -214
- package/guides/text-markup/examples/create-post.mjs +0 -64
package/guides/plugins/README.md
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# Плагины
|
|
2
|
-
|
|
3
|
-
Плагин расширяет клиент обёртками вокруг запросов и ответов. Одна установка действует на
|
|
4
|
-
все ресурсы:
|
|
5
|
-
|
|
6
|
-
```ts
|
|
7
|
-
import { ItdClient } from 'itd-api';
|
|
8
|
-
import { crypt } from '@itd-api/crypto';
|
|
9
|
-
|
|
10
|
-
const itd = new ItdClient({ auth: token });
|
|
11
|
-
itd.use(crypt());
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Cache
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm install @itd-api/cache
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
import { cache } from '@itd-api/cache';
|
|
22
|
-
|
|
23
|
-
const cached = cache({
|
|
24
|
-
ttl: 60_000,
|
|
25
|
-
routes: ['users.get', 'posts.get', 'posts.list'],
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
itd.use(cached);
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Плагин хранит успешные ответы в LRU-кэше и объединяет одновременные одинаковые запросы.
|
|
32
|
-
Маршруты выбираются явно; после мутаций связанные данные инвалидируются автоматически.
|
|
33
|
-
У каждого клиента и аккаунта свой раздел кэша, а изменения общих сущностей сбрасывают
|
|
34
|
-
соответствующие маршруты во всех разделах.
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
await itd.posts.get(postId, { cache: 'reload' });
|
|
38
|
-
cached.invalidate('posts.get');
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Полный каталог маршрутов, подключение к нескольким клиентам и привязка к realtime описаны в
|
|
42
|
-
[README пакета](../../cache/README.md).
|
|
43
|
-
|
|
44
|
-
Запускаемый пример:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
ITD_TOKEN=<accessToken> ITD_POST_ID=<postId> \
|
|
48
|
-
node guides/plugins/examples/cache.mjs
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Crypto
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
npm install @itd-api/crypto
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
import { crypt } from '@itd-api/crypto';
|
|
59
|
-
|
|
60
|
-
itd.use(crypt());
|
|
61
|
-
|
|
62
|
-
const created = await itd.posts.create(
|
|
63
|
-
{ content: 'секретный текст' },
|
|
64
|
-
{ encrypt: { cipher: 'invisible', cover: 'обычный пост' } },
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const post = await itd.posts.get(created.id);
|
|
68
|
-
console.log(post.content); // обложка
|
|
69
|
-
console.log(post.secret?.text); // секретный текст
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Плагин обрабатывает посты, комментарии, ответы и текстовые поля профиля. Доступны
|
|
73
|
-
`invisible` и `beecrypt`; подробные ограничения описаны в README пакета.
|
|
74
|
-
|
|
75
|
-
Запускаемый пример:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
ITD_TOKEN=<accessToken> node guides/plugins/examples/crypto.mjs
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Собственный плагин
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
import type { ItdPlugin } from 'itd-api';
|
|
85
|
-
|
|
86
|
-
const timing: ItdPlugin = {
|
|
87
|
-
name: 'timing',
|
|
88
|
-
install({ use, logger }) {
|
|
89
|
-
use(async (request, next) => {
|
|
90
|
-
const started = Date.now();
|
|
91
|
-
try {
|
|
92
|
-
return await next(request);
|
|
93
|
-
} finally {
|
|
94
|
-
logger?.info(`${request.method} ${request.path}: ${Date.now() - started} мс`);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
itd.use(timing);
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Обёртка может:
|
|
104
|
-
|
|
105
|
-
- передать в `next()` изменённую копию запроса;
|
|
106
|
-
- изменить полученный ответ;
|
|
107
|
-
- вернуть результат без обращения к сети;
|
|
108
|
-
- выбросить собственную ошибку.
|
|
109
|
-
|
|
110
|
-
Подключённая раньше обёртка оказывается снаружи. Она выполняется один раз на логический
|
|
111
|
-
запрос, независимо от внутренних повторов транспорта.
|
|
112
|
-
|
|
113
|
-
## Собственные опции метода
|
|
114
|
-
|
|
115
|
-
Плагин объявляет разрешённые ключи:
|
|
116
|
-
|
|
117
|
-
```ts
|
|
118
|
-
const plugin: ItdPlugin = {
|
|
119
|
-
name: 'мой',
|
|
120
|
-
optionKeys: ['мояОпция'],
|
|
121
|
-
install({ use }) {
|
|
122
|
-
use(async (request, next) => {
|
|
123
|
-
console.log(request.мояОпция);
|
|
124
|
-
return next(request);
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
declare module 'itd-api' {
|
|
130
|
-
interface RequestOptions {
|
|
131
|
-
мояОпция?: string | undefined;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Ключи самого запроса — `path`, `body`, `headers`, `signal` и другие системные поля —
|
|
137
|
-
зарезервированы. Плагин с конфликтующим `optionKeys` отклоняется при подключении.
|
|
138
|
-
|
|
139
|
-
## Несколько аккаунтов
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
accounts.use(plugin);
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Плагин установится всем существующим аккаунтам и будет автоматически применяться к новым.
|
|
146
|
-
|
|
147
|
-
## Структура нового плагина
|
|
148
|
-
|
|
149
|
-
Структура пакета плагина:
|
|
150
|
-
|
|
151
|
-
```text
|
|
152
|
-
my-plugin/
|
|
153
|
-
├── package.json
|
|
154
|
-
├── README.md
|
|
155
|
-
├── src/
|
|
156
|
-
│ └── index.ts
|
|
157
|
-
└── test/
|
|
158
|
-
└── plugin.test.ts
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Пакет должен иметь собственные тесты, сборку и экспортировать фабрику либо объект,
|
|
162
|
-
совместимый с `ItdPlugin`.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Запуск:
|
|
3
|
-
* ITD_TOKEN=<accessToken> ITD_POST_ID=<postId> \
|
|
4
|
-
* node guides/plugins/examples/cache.mjs
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { cache } from '@itd-api/cache';
|
|
8
|
-
import { ItdClient } from 'itd-api';
|
|
9
|
-
|
|
10
|
-
const token = process.env.ITD_TOKEN;
|
|
11
|
-
const postId = process.env.ITD_POST_ID;
|
|
12
|
-
|
|
13
|
-
if (!token || !postId) {
|
|
14
|
-
throw new Error('Передайте ITD_TOKEN и ITD_POST_ID');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const itd = new ItdClient({ auth: token });
|
|
18
|
-
const cached = cache({
|
|
19
|
-
ttl: 60_000,
|
|
20
|
-
routes: ['posts.get', 'users.get'],
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
itd.use(cached);
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const first = await itd.posts.get(postId);
|
|
27
|
-
const second = await itd.posts.get(postId);
|
|
28
|
-
|
|
29
|
-
console.log(first.content);
|
|
30
|
-
console.log(`Повторный ответ получен из кэша: ${first.id === second.id}`);
|
|
31
|
-
} finally {
|
|
32
|
-
await itd.close();
|
|
33
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Скрытое сообщение в обычном посте.
|
|
3
|
-
*
|
|
4
|
-
* Запуск:
|
|
5
|
-
* ITD_TOKEN=<accessToken> node guides/plugins/examples/crypto.mjs
|
|
6
|
-
*
|
|
7
|
-
* Плагин лежит в отдельном пакете — основному он не нужен:
|
|
8
|
-
*
|
|
9
|
-
* npm i @itd-api/crypto
|
|
10
|
-
*
|
|
11
|
-
* Пример публикует пост, читает его обратно **с сервера** и сравнивает результат.
|
|
12
|
-
* Смысл именно в обратном чтении: сервер итд.com нормализует текст поста при сохранении,
|
|
13
|
-
* и проверить, что нагрузка это пережила, можно только на живом API.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { ItdClient, isItdApiError } from 'itd-api';
|
|
17
|
-
import { crypt, stripInvisible } from '@itd-api/crypto';
|
|
18
|
-
|
|
19
|
-
const COVER = 'обычный пост, ничего необычного';
|
|
20
|
-
const SECRET = 'секретный текст: 🦎 привет из @itd-api/crypto';
|
|
21
|
-
|
|
22
|
-
const itd = new ItdClient({ auth: process.env.ITD_TOKEN });
|
|
23
|
-
|
|
24
|
-
// Одна строка — и шифрование доступно во всех методах, принимающих текст.
|
|
25
|
-
itd.use(crypt());
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const created = await itd.posts.create(
|
|
29
|
-
{ content: SECRET },
|
|
30
|
-
{ encrypt: { cipher: 'invisible', cover: COVER } },
|
|
31
|
-
);
|
|
32
|
-
console.log(`Опубликован пост ${created.id}`);
|
|
33
|
-
|
|
34
|
-
// Читаем с сервера, а не берём ответ на публикацию: интересно именно то,
|
|
35
|
-
// что сохранилось после нормализации.
|
|
36
|
-
const post = await itd.posts.get(created.id);
|
|
37
|
-
|
|
38
|
-
console.log(`Видят все: ${stripInvisible(post.content)}`);
|
|
39
|
-
console.log(`Спрятано: ${post.secret?.text ?? '— ничего не нашлось —'}`);
|
|
40
|
-
console.log(`Длина: ${post.content.length} символов вместо ${COVER.length}`);
|
|
41
|
-
|
|
42
|
-
console.log(
|
|
43
|
-
post.secret?.text === SECRET
|
|
44
|
-
? '✔ сообщение пережило сохранение на сервере'
|
|
45
|
-
: '✘ сообщение потерялось — формат разошёлся с тем, что делает сервер',
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
// Прибираем за собой: пост нужен был только для проверки.
|
|
49
|
-
await itd.posts.remove(created.id);
|
|
50
|
-
console.log('Пост удалён');
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.error(isItdApiError(error) ? `${error.code}: ${error.message}` : error);
|
|
53
|
-
process.exitCode = 1;
|
|
54
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# Быстрый старт
|
|
2
|
-
|
|
3
|
-
## Установка
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npm install itd-api
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
Пакет поддерживает ESM и CommonJS, Node 18+, браузер, Bun, Deno и React Native.
|
|
10
|
-
|
|
11
|
-
## Создание клиента
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import { FeedTab, ItdClient } from 'itd-api';
|
|
15
|
-
|
|
16
|
-
const itd = new ItdClient({
|
|
17
|
-
auth: process.env.ITD_TOKEN,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const me = await itd.users.me();
|
|
21
|
-
console.log(`@${me.username}, подписчиков: ${me.followersCount}`);
|
|
22
|
-
|
|
23
|
-
for await (const post of itd.posts.iterate({ tab: FeedTab.Following })) {
|
|
24
|
-
console.log(post.author.username, post.content);
|
|
25
|
-
if (!post.isLiked) await itd.posts.like(post.id);
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Для загрузки файлов по пути и файлового хранилища сессии используйте Node-вход:
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
import { FileTokenStorage, ItdClient } from 'itd-api/node';
|
|
33
|
-
|
|
34
|
-
const itd = new ItdClient({
|
|
35
|
-
storage: new FileTokenStorage('./.itd-session.json'),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
await itd.posts.create((p) =>
|
|
39
|
-
p.content('привет').attach('./photo.jpg'),
|
|
40
|
-
);
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Публикация
|
|
44
|
-
|
|
45
|
-
Методы принимают обычный объект, готовый билдер или функцию-настройщик:
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
import { post } from 'itd-api';
|
|
49
|
-
|
|
50
|
-
await itd.posts.create({ content: 'привет' });
|
|
51
|
-
|
|
52
|
-
await itd.posts.create((p) =>
|
|
53
|
-
p
|
|
54
|
-
.content('смотрите')
|
|
55
|
-
.attach('./photo.jpg')
|
|
56
|
-
.poll((q) => q.question('нравится?').options('да', 'нет')),
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const draft = post().onWall(userId);
|
|
60
|
-
await itd.posts.create(draft.content('первый'));
|
|
61
|
-
await itd.posts.create(draft.content('второй'));
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Билдеры неизменяемые, а `build()` проверяет данные до обращения к сети.
|
|
65
|
-
|
|
66
|
-
## Пагинация
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
for await (const post of itd.posts.iterate({ tab: 'popular' })) {
|
|
70
|
-
console.log(post.id);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const page = await itd.posts.list({ tab: 'popular', limit: 20 });
|
|
74
|
-
const next = await itd.posts.list({
|
|
75
|
-
tab: 'popular',
|
|
76
|
-
cursor: page.nextCursor ?? undefined,
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Курсор непрозрачен — передавайте его обратно без разбора. Итератор одноразовый; для второго
|
|
81
|
-
прохода создайте новый.
|
|
82
|
-
|
|
83
|
-
## Ошибки
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
import {
|
|
87
|
-
ItdRateLimitError,
|
|
88
|
-
ItdValidationError,
|
|
89
|
-
isItdApiError,
|
|
90
|
-
} from 'itd-api';
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
await itd.users.updateMe({ username: 'занятое_имя' });
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (error instanceof ItdValidationError) {
|
|
96
|
-
console.error(error.fieldErrors);
|
|
97
|
-
} else if (error instanceof ItdRateLimitError) {
|
|
98
|
-
console.error(error.retryAfter);
|
|
99
|
-
} else if (isItdApiError(error)) {
|
|
100
|
-
console.error(error.status, error.code, error.message);
|
|
101
|
-
} else {
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Куда дальше
|
|
108
|
-
|
|
109
|
-
- [Авторизация и сессии](../authentication/README.md)
|
|
110
|
-
- [Разметка текста](../text-markup/README.md)
|
|
111
|
-
- [Realtime](../realtime/README.md)
|
|
112
|
-
- [Несколько аккаунтов](../multi-accounts/README.md)
|
|
113
|
-
- [Интеграции](../integrations/README.md)
|
|
114
|
-
- [Плагины](../plugins/README.md)
|
|
115
|
-
|
|
116
|
-
## Примеры
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
ITD_TOKEN=<accessToken> node guides/quickstart/examples/quick-start.mjs
|
|
120
|
-
npx tsx guides/quickstart/examples/typescript.ts
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
- [`examples/quick-start.mjs`](./examples/quick-start.mjs) — профиль и чтение ленты.
|
|
124
|
-
- [`examples/typescript.ts`](./examples/typescript.ts) — типы, билдеры, пагинация и ошибки.
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Быстрый старт: чтение ленты и реакция на пост.
|
|
3
|
-
*
|
|
4
|
-
* Запуск:
|
|
5
|
-
* ITD_TOKEN=<ваш accessToken> node guides/quickstart/examples/quick-start.mjs
|
|
6
|
-
*
|
|
7
|
-
* Где взять токен: откройте итд.com, войдите, затем в консоли браузера выполните
|
|
8
|
-
* запрос к /api/v1/auth/refresh — либо воспользуйтесь руководством по авторизации.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { FeedTab, ItdClient, isItdApiError } from 'itd-api';
|
|
12
|
-
|
|
13
|
-
const itd = new ItdClient({
|
|
14
|
-
// Опции допускают undefined, поэтому переменную окружения можно передавать напрямую.
|
|
15
|
-
auth: process.env.ITD_TOKEN,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const me = await itd.users.me();
|
|
20
|
-
console.log(`Вы вошли как ${me.displayName} (@${me.username})`);
|
|
21
|
-
console.log(`Подписчиков: ${me.followersCount}, записей: ${me.postsCount}\n`);
|
|
22
|
-
|
|
23
|
-
// Одна страница ленты.
|
|
24
|
-
const page = await itd.posts.list({ tab: FeedTab.Popular, limit: 5 });
|
|
25
|
-
|
|
26
|
-
for (const post of page.items) {
|
|
27
|
-
const text = post.content.slice(0, 60).replace(/\n/g, ' ');
|
|
28
|
-
console.log(`${post.author.avatar} @${post.author.username}: ${text}`);
|
|
29
|
-
console.log(` ❤ ${post.likesCount} 💬 ${post.commentsCount} 🔁 ${post.repostsCount}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Перебор нескольких страниц: курсоры подставляются сами.
|
|
33
|
-
console.log('\nПервые 12 записей из подписок:');
|
|
34
|
-
|
|
35
|
-
const posts = await itd.posts.iterate({ tab: FeedTab.Following }).collect(12);
|
|
36
|
-
console.log(`получено ${posts.length}`);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (isItdApiError(error)) {
|
|
39
|
-
console.error(`Ошибка API [${error.code}] ${error.status}: ${error.message}`);
|
|
40
|
-
if (Object.keys(error.fieldErrors).length > 0) console.error(error.fieldErrors);
|
|
41
|
-
} else {
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript: типы, билдеры и разбор ошибок.
|
|
3
|
-
*
|
|
4
|
-
* Запуск:
|
|
5
|
-
* npx tsx guides/quickstart/examples/typescript.ts
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
FeedTab,
|
|
10
|
-
ItdClient,
|
|
11
|
-
ItdValidationError,
|
|
12
|
-
type Notification,
|
|
13
|
-
type Post,
|
|
14
|
-
ReportReason,
|
|
15
|
-
isItdApiError,
|
|
16
|
-
ItdErrorCode,
|
|
17
|
-
ItdRateLimitError,
|
|
18
|
-
poll,
|
|
19
|
-
post,
|
|
20
|
-
report,
|
|
21
|
-
} from 'itd-api';
|
|
22
|
-
|
|
23
|
-
const itd = new ItdClient({ auth: process.env.ITD_TOKEN });
|
|
24
|
-
|
|
25
|
-
// ── Перечисления вместо магических строк ────────────────────────────────────────
|
|
26
|
-
// Работают обе формы: константа и обычная строка.
|
|
27
|
-
await itd.posts.list({ tab: FeedTab.Popular });
|
|
28
|
-
await itd.posts.list({ tab: 'following' });
|
|
29
|
-
|
|
30
|
-
// ── Заготовки билдеров переиспользуются ─────────────────────────────────────────
|
|
31
|
-
// Билдер неизменяемый, поэтому заготовку не испортить.
|
|
32
|
-
const draft = post().content('черновик');
|
|
33
|
-
|
|
34
|
-
const first: Post = await itd.posts.create(draft.append('первая версия'));
|
|
35
|
-
const second: Post = await itd.posts.create(draft.append('вторая версия'));
|
|
36
|
-
console.log(first.id, second.id);
|
|
37
|
-
|
|
38
|
-
// Опрос можно собрать заранее и передать в несколько записей.
|
|
39
|
-
const survey = poll('Какой язык удобнее?').options('TypeScript', 'JavaScript').multipleChoice();
|
|
40
|
-
await itd.posts.create({ content: 'голосуем', poll: survey });
|
|
41
|
-
|
|
42
|
-
// ── Пагинация: три способа ──────────────────────────────────────────────────────
|
|
43
|
-
// По элементам.
|
|
44
|
-
for await (const item of itd.posts.iterate({ tab: FeedTab.Popular })) {
|
|
45
|
-
console.log(item.content);
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// По страницам — когда нужны сведения о самой странице.
|
|
50
|
-
for await (const page of itd.users.iterateFollowers('durov').pages()) {
|
|
51
|
-
console.log(`${page.items.length} из ${page.total ?? '?'}`);
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Собрать нужное количество и остановиться.
|
|
56
|
-
const top: Post[] = await itd.posts.iterate({ tab: FeedTab.Popular }).collect(50);
|
|
57
|
-
console.log(`собрано ${top.length}`);
|
|
58
|
-
|
|
59
|
-
// ── Разбор ошибок ───────────────────────────────────────────────────────────────
|
|
60
|
-
try {
|
|
61
|
-
await itd.users.updateMe({ username: 'занятое_имя' });
|
|
62
|
-
} catch (error) {
|
|
63
|
-
if (error instanceof ItdValidationError) {
|
|
64
|
-
// Обе формы ошибок API сведены к одной структуре.
|
|
65
|
-
for (const [field, messages] of Object.entries(error.fieldErrors)) {
|
|
66
|
-
console.error(`${field}: ${messages.join(', ')}`);
|
|
67
|
-
}
|
|
68
|
-
} else if (error instanceof ItdRateLimitError) {
|
|
69
|
-
console.error(`Лимит запросов, повтор через ${error.retryAfter ?? '?'} мс`);
|
|
70
|
-
} else if (isItdApiError(error) && error.hasCode(ItdErrorCode.PROFILE_USERNAME_TAKEN)) {
|
|
71
|
-
console.error('Имя уже занято');
|
|
72
|
-
} else {
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ── Стена другого пользователя требует UUID ─────────────────────────────────────
|
|
78
|
-
// Проверка сработает до обращения к сети и подскажет, где взять идентификатор.
|
|
79
|
-
const target = await itd.users.get('durov');
|
|
80
|
-
await itd.posts.create((p) => p.content('привет!').onWall(target.id));
|
|
81
|
-
|
|
82
|
-
// ── Жалоба: тип объекта и его идентификатор нельзя рассогласовать ───────────────
|
|
83
|
-
await itd.reports.create(report.post(first.id).reason(ReportReason.Spam));
|
|
84
|
-
|
|
85
|
-
// ── Уведомления из REST и из потока имеют одну форму ────────────────────────────
|
|
86
|
-
const feed: Notification[] = (await itd.notifications.list({ limit: 10 })).items;
|
|
87
|
-
|
|
88
|
-
const stream = itd.realtime();
|
|
89
|
-
stream.on('notification', ({ notification }) => feed.unshift(notification));
|
|
90
|
-
await stream.connect();
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# Уведомления и realtime
|
|
2
|
-
|
|
3
|
-
`itd.realtime()` открывает поток новых уведомлений:
|
|
4
|
-
|
|
5
|
-
```ts
|
|
6
|
-
import { formatNotificationText, resolveNotificationUrl } from 'itd-api';
|
|
7
|
-
|
|
8
|
-
const stream = itd.realtime();
|
|
9
|
-
|
|
10
|
-
stream.on('notification', ({ notification, sound }) => {
|
|
11
|
-
console.log(sound ? '🔔' : '🔕');
|
|
12
|
-
console.log(formatNotificationText(notification));
|
|
13
|
-
console.log(resolveNotificationUrl(notification));
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
await stream.connect();
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## REST и поток
|
|
20
|
-
|
|
21
|
-
Уведомления из `itd.notifications.list()` и realtime приведены к общей форме, поэтому их
|
|
22
|
-
можно хранить в одном массиве:
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
const history = await itd.notifications.list({ limit: 20 });
|
|
26
|
-
|
|
27
|
-
stream.on('notification', ({ notification }) => {
|
|
28
|
-
history.items.unshift(notification);
|
|
29
|
-
});
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Сервер использует короткие типы вроде `like`, `comment` и `repost`. Библиотека приводит
|
|
33
|
-
их к однозначным `post_reaction`, `post_comment`, `post_repost`, сохраняя исходное значение
|
|
34
|
-
в `rawType`, а исходный объект — в `raw`.
|
|
35
|
-
|
|
36
|
-
`resolveNotificationUrl()` учитывает смысл идентификаторов конкретного типа и строит ссылку
|
|
37
|
-
на профиль, пост или комментарий.
|
|
38
|
-
|
|
39
|
-
## Переподключение
|
|
40
|
-
|
|
41
|
-
Поток самостоятельно обрабатывает:
|
|
42
|
-
|
|
43
|
-
- обрыв соединения;
|
|
44
|
-
- обновление access token;
|
|
45
|
-
- восстановление сети;
|
|
46
|
-
- возвращение браузерной вкладки из фона;
|
|
47
|
-
- отсутствие keep-alive дольше `idleTimeout`.
|
|
48
|
-
|
|
49
|
-
По умолчанию используются задержки `[1, 2, 4, 8, 16, 30]` секунд с джиттером ±30% и не
|
|
50
|
-
более 15 последовательных попыток. Сервер обычно отправляет `: ping` каждые 15 секунд,
|
|
51
|
-
а стандартный `idleTimeout` равен 90 секундам.
|
|
52
|
-
|
|
53
|
-
Состояние можно отслеживать:
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
stream.on('status', (status) => {
|
|
57
|
-
console.log(status); // connecting, connected, reconnecting, disconnected, error
|
|
58
|
-
});
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Завершение:
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
stream.disconnect();
|
|
65
|
-
await itd.close();
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Счётчик непрочитанных
|
|
69
|
-
|
|
70
|
-
Сервер практически не присылает отдельное событие изменения счётчика. Получите начальное
|
|
71
|
-
значение через REST и обновляйте локально:
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
let unread = await itd.notifications.count();
|
|
75
|
-
|
|
76
|
-
stream.on('notification', () => {
|
|
77
|
-
unread += 1;
|
|
78
|
-
});
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
После массовой отметки о прочтении лучше снова запросить актуальное значение.
|
|
82
|
-
|
|
83
|
-
## Polling fallback
|
|
84
|
-
|
|
85
|
-
В средах без потокового чтения ответа, например в некоторых версиях React Native, realtime
|
|
86
|
-
автоматически переключается на периодический опрос. Интервал настраивается через
|
|
87
|
-
`pollInterval`.
|
|
88
|
-
|
|
89
|
-
Можно выбрать транспорт явно:
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
const stream = itd.realtime({
|
|
93
|
-
transport: 'poll',
|
|
94
|
-
pollInterval: 5_000,
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Несколько аккаунтов
|
|
99
|
-
|
|
100
|
-
Каждый вызов `itd.realtime()` держит собственное соединение. Для десяти аккаунтов это десять
|
|
101
|
-
SSE-соединений, поэтому открывайте поток только там, где он действительно нужен.
|
|
102
|
-
|
|
103
|
-
## Запускаемый пример
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
ITD_TOKEN=<accessToken> node guides/realtime/examples/notifications.mjs
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Исходник: [`examples/notifications.mjs`](./examples/notifications.mjs).
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Уведомления в реальном времени.
|
|
3
|
-
*
|
|
4
|
-
* Запуск:
|
|
5
|
-
* ITD_TOKEN=<ваш accessToken> node guides/realtime/examples/notifications.mjs
|
|
6
|
-
*
|
|
7
|
-
* Соединение держится само: обрывы, обновление токена и повторные попытки библиотека
|
|
8
|
-
* берёт на себя. Завершение — Ctrl+C.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { ItdClient, formatNotificationText, resolveNotificationUrl } from 'itd-api';
|
|
12
|
-
|
|
13
|
-
const itd = new ItdClient({ auth: process.env.ITD_TOKEN });
|
|
14
|
-
|
|
15
|
-
// Сначала — то, что уже накопилось.
|
|
16
|
-
const history = await itd.notifications.list({ limit: 5 });
|
|
17
|
-
|
|
18
|
-
console.log(`Непрочитанных: ${await itd.notifications.count()}`);
|
|
19
|
-
console.log('\nПоследние уведомления:');
|
|
20
|
-
|
|
21
|
-
for (const notification of history.items) {
|
|
22
|
-
const mark = notification.isRead ? ' ' : '•';
|
|
23
|
-
console.log(`${mark} ${formatNotificationText(notification)}`);
|
|
24
|
-
console.log(` → ${resolveNotificationUrl(notification)}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Теперь поток новых.
|
|
28
|
-
const stream = itd.realtime();
|
|
29
|
-
|
|
30
|
-
stream.on('notification', ({ notification, sound }) => {
|
|
31
|
-
console.log(`\n${sound ? '🔔' : '🔕'} ${formatNotificationText(notification)}`);
|
|
32
|
-
console.log(` → ${resolveNotificationUrl(notification)}`);
|
|
33
|
-
|
|
34
|
-
// Объекты из списка и из потока имеют одинаковую форму — их можно складывать вместе.
|
|
35
|
-
history.items.unshift(notification);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Счётчик непрочитанных сервер по потоку не присылает — ведём его сами.
|
|
39
|
-
let unread = await itd.notifications.count();
|
|
40
|
-
|
|
41
|
-
stream.on('notification', () => {
|
|
42
|
-
unread += 1;
|
|
43
|
-
console.log(` непрочитанных: ${unread}`);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
stream.on('ready', ({ userId }) => console.log(`[поток подтвердил получателя ${userId}]`));
|
|
47
|
-
stream.on('status', (status) => console.log(`[соединение: ${status}]`));
|
|
48
|
-
stream.on('reconnect', ({ attempt, delay }) => {
|
|
49
|
-
console.log(`[переподключение №${attempt} через ${delay} мс]`);
|
|
50
|
-
});
|
|
51
|
-
stream.on('giveup', () => {
|
|
52
|
-
console.error('[попытки исчерпаны, соединение восстановится только вручную]');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
await stream.connect();
|
|
56
|
-
console.log(`\nЖдём события (транспорт: ${stream.transport}). Ctrl+C для выхода.`);
|
|
57
|
-
|
|
58
|
-
process.on('SIGINT', () => {
|
|
59
|
-
stream.disconnect();
|
|
60
|
-
console.log('\nОтключено');
|
|
61
|
-
process.exit(0);
|
|
62
|
-
});
|