itd-sdk-js 1.0.2 → 1.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/API_REFERENCE.md +59 -2
- package/examples/token-validation.js +88 -0
- package/package.json +3 -2
- package/src/auth.js +66 -1
- package/src/client.js +19 -0
- package/src/posts.js +4 -2
package/API_REFERENCE.md
CHANGED
|
@@ -53,13 +53,45 @@ client.auth.isAuthenticated = true;
|
|
|
53
53
|
|
|
54
54
|
При получении ошибки `401 Unauthorized` клиент автоматически обращается к эндпоинту `/api/v1/auth/refresh`, используя данные из `.cookies`. В случае успеха новый токен сохраняется в `.env`, обновляются куки, и исходный запрос повторяется.
|
|
55
55
|
|
|
56
|
+
**Важно:** Для автоматического обновления токена необходим `refresh_token` в файле `.cookies`. Если его нет, SDK не сможет обновить токен автоматически.
|
|
57
|
+
|
|
58
|
+
**Проверка наличия refresh_token:**
|
|
59
|
+
```javascript
|
|
60
|
+
if (client.hasRefreshToken()) {
|
|
61
|
+
console.log('✅ Refresh token доступен, токен будет обновляться автоматически');
|
|
62
|
+
} else {
|
|
63
|
+
console.log('⚠️ Refresh token не найден - обновите файл .cookies');
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Ручная проверка и обновление токена:**
|
|
68
|
+
```javascript
|
|
69
|
+
// Проверяет валидность токена и обновляет его при необходимости
|
|
70
|
+
const isValid = await client.validateAndRefreshToken();
|
|
71
|
+
if (isValid) {
|
|
72
|
+
console.log('✅ Токен валиден или успешно обновлен');
|
|
73
|
+
} else {
|
|
74
|
+
console.log('❌ Токен невалиден и не удалось обновить');
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Рекомендация для множественных запросов с интервалами:**
|
|
79
|
+
Если вы делаете запросы с большими интервалами (более 10-15 минут), рекомендуется проверять токен перед каждым запросом:
|
|
80
|
+
```javascript
|
|
81
|
+
// Перед публикацией поста
|
|
82
|
+
await client.validateAndRefreshToken();
|
|
83
|
+
const post = await client.createPost('Текст поста', 'image.jpg');
|
|
84
|
+
```
|
|
85
|
+
|
|
56
86
|
---
|
|
57
87
|
|
|
58
88
|
## Методы API: Посты
|
|
59
89
|
|
|
60
90
|
### createPost(text, imagePath?)
|
|
61
91
|
|
|
62
|
-
Создает новый пост. При указании `imagePath` файл предварительно загружается через `/api/files/upload`, после чего ID файла прикрепляется к
|
|
92
|
+
Создает новый пост. При указании `imagePath` файл предварительно загружается через `/api/files/upload`, после чего ID файла прикрепляется к посту через поле `attachmentIds`.
|
|
93
|
+
|
|
94
|
+
**Важно:** API использует поле `attachmentIds` (массив ID файлов), а не `attachments`. SDK автоматически использует правильное поле.
|
|
63
95
|
|
|
64
96
|
- **Параметры**: `text` (string), `imagePath` (string, опционально).
|
|
65
97
|
|
|
@@ -67,8 +99,9 @@ client.auth.isAuthenticated = true;
|
|
|
67
99
|
|
|
68
100
|
Создает пост **на стене другого пользователя** (wall post).
|
|
69
101
|
|
|
70
|
-
- Делается через `POST /api/posts` с телом `{ content, wallRecipientId }`.
|
|
102
|
+
- Делается через `POST /api/posts` с телом `{ content, wallRecipientId, attachmentIds? }`.
|
|
71
103
|
- `wallRecipientId` — это **ID пользователя-получателя**, поэтому метод сначала запрашивает профиль через `getUserProfile(username)` и берет `profile.id`.
|
|
104
|
+
- При указании `imagePath` файл загружается и прикрепляется через `attachmentIds`.
|
|
72
105
|
|
|
73
106
|
- **Параметры**:
|
|
74
107
|
- `username` — username получателя (string)
|
|
@@ -134,6 +167,14 @@ client.auth.isAuthenticated = true;
|
|
|
134
167
|
- `followUser(username)` **/** `unfollowUser(username)` — подписка/отписка.
|
|
135
168
|
- `getUserClan(username)` — получение эмодзи-аватара пользователя.
|
|
136
169
|
|
|
170
|
+
## Методы API: Управление токенами
|
|
171
|
+
|
|
172
|
+
- `hasRefreshToken()` — проверяет наличие refresh_token в cookies. Возвращает `boolean`.
|
|
173
|
+
- `validateAndRefreshToken()` — проверяет валидность токена и обновляет его при необходимости. Возвращает `Promise<boolean>`.
|
|
174
|
+
- `refreshAccessToken()` — принудительно обновляет токен через refresh endpoint. Возвращает `Promise<string|null>`.
|
|
175
|
+
|
|
176
|
+
**Рекомендация:** При множественных запросах с интервалами (более 10-15 минут) вызывайте `validateAndRefreshToken()` перед каждым запросом.
|
|
177
|
+
|
|
137
178
|
---
|
|
138
179
|
|
|
139
180
|
## Методы API: Уведомления и поиск
|
|
@@ -261,3 +302,19 @@ if (result.pagination.hasMore) {
|
|
|
261
302
|
---
|
|
262
303
|
|
|
263
304
|
**Последнее обновление документации**: 2026-01-28.
|
|
305
|
+
|
|
306
|
+
## Загрузка изображений
|
|
307
|
+
|
|
308
|
+
При создании поста с изображением SDK использует поле `attachmentIds` (массив ID файлов), а не `attachments`. Это правильное поле, которое требует API итд.com.
|
|
309
|
+
|
|
310
|
+
**Пример:**
|
|
311
|
+
```javascript
|
|
312
|
+
// Создание поста с изображением
|
|
313
|
+
await client.createPost('Текст поста', './image.jpg');
|
|
314
|
+
// SDK автоматически:
|
|
315
|
+
// 1. Загружает файл через /api/files/upload
|
|
316
|
+
// 2. Получает ID файла
|
|
317
|
+
// 3. Создает пост с attachmentIds: [fileId]
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Важно:** API возвращает attachments в ответе GET запроса, но для создания поста требуется использовать `attachmentIds` в payload.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Пример работы с токенами при множественных запросах
|
|
3
|
+
*
|
|
4
|
+
* Демонстрирует правильную обработку истечения токена
|
|
5
|
+
* при публикации нескольких постов с интервалами
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ITDClient } from '../src/client.js';
|
|
9
|
+
import dotenv from 'dotenv';
|
|
10
|
+
|
|
11
|
+
dotenv.config();
|
|
12
|
+
|
|
13
|
+
async function publishMultiplePosts() {
|
|
14
|
+
const client = new ITDClient();
|
|
15
|
+
client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
|
|
16
|
+
client.auth.isAuthenticated = true;
|
|
17
|
+
|
|
18
|
+
console.log('📝 Публикация нескольких постов с проверкой токена\n');
|
|
19
|
+
|
|
20
|
+
// Проверяем наличие refresh_token
|
|
21
|
+
if (!client.hasRefreshToken()) {
|
|
22
|
+
console.error('❌ ВНИМАНИЕ: refresh_token не найден в cookies!');
|
|
23
|
+
console.error('💡 Решение:');
|
|
24
|
+
console.error(' 1. Откройте итд.com в браузере и войдите');
|
|
25
|
+
console.error(' 2. Откройте DevTools (F12) → Network');
|
|
26
|
+
console.error(' 3. Найдите любой запрос к итд.com');
|
|
27
|
+
console.error(' 4. Скопируйте значение заголовка Cookie');
|
|
28
|
+
console.error(' 5. Вставьте в файл .cookies в корне проекта');
|
|
29
|
+
console.error(' 6. Убедитесь, что в Cookie есть refresh_token\n');
|
|
30
|
+
console.error('⚠️ Без refresh_token токен не будет обновляться автоматически!\n');
|
|
31
|
+
} else {
|
|
32
|
+
console.log('✅ Refresh token найден - токен будет обновляться автоматически\n');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const posts = [
|
|
36
|
+
{ text: 'Первый пост', image: 'image1.jpg' },
|
|
37
|
+
{ text: 'Второй пост', image: 'image2.jpg' },
|
|
38
|
+
{ text: 'Третий пост', image: 'image3.jpg' }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < posts.length; i++) {
|
|
42
|
+
const post = posts[i];
|
|
43
|
+
console.log(`📝 Публикация поста ${i + 1}/${posts.length}...`);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// ВАЖНО: Проверяем и обновляем токен перед каждым запросом
|
|
47
|
+
// Это особенно важно при больших интервалах между запросами
|
|
48
|
+
const tokenValid = await client.validateAndRefreshToken();
|
|
49
|
+
|
|
50
|
+
if (!tokenValid) {
|
|
51
|
+
console.error(`❌ Токен невалиден и не удалось обновить`);
|
|
52
|
+
console.error(` Пропускаю пост: ${post.text}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Публикуем пост
|
|
57
|
+
const result = await client.createPost(post.text, post.image);
|
|
58
|
+
|
|
59
|
+
if (result) {
|
|
60
|
+
console.log(`✅ Пост ${i + 1} опубликован: ${result.id}\n`);
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`❌ Не удалось опубликовать пост ${i + 1}\n`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`❌ Ошибка при публикации поста ${i + 1}: ${error.message}\n`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Имитация интервала между постами
|
|
70
|
+
if (i < posts.length - 1) {
|
|
71
|
+
console.log('⏳ Ожидание перед следующим постом...\n');
|
|
72
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('✅ Все посты обработаны');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
try {
|
|
81
|
+
await publishMultiplePosts();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('❌ Критическая ошибка:', error.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "itd-sdk-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Unofficial SDK for итд.com - Node.js library for working with API. Automatic token refresh, session management, and convenient methods for posts, comments, users, and notifications.",
|
|
5
5
|
"main": "src/client.js",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"examples/basic-usage.js",
|
|
16
16
|
"examples/quick-start.js",
|
|
17
17
|
"examples/user-friendly.js",
|
|
18
|
+
"examples/token-validation.js",
|
|
18
19
|
"examples/README.md",
|
|
19
20
|
".env.example",
|
|
20
21
|
".cookies.example"
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"license": "MIT",
|
|
39
40
|
"repository": {
|
|
40
41
|
"type": "git",
|
|
41
|
-
"url": "
|
|
42
|
+
"url": "https://github.com/FriceKa/ITD-SDK-js.git"
|
|
42
43
|
},
|
|
43
44
|
"bugs": {
|
|
44
45
|
"url": "https://github.com/FriceKa/ITD-SDK-js/issues"
|
package/src/auth.js
CHANGED
|
@@ -16,6 +16,20 @@ export class AuthManager {
|
|
|
16
16
|
this.userData = null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Проверяет наличие refresh_token в cookies
|
|
21
|
+
*
|
|
22
|
+
* @returns {boolean} True если refresh_token доступен
|
|
23
|
+
*/
|
|
24
|
+
hasRefreshToken() {
|
|
25
|
+
try {
|
|
26
|
+
const cookies = this.client.cookieJar.getCookiesSync(this.client.baseUrl);
|
|
27
|
+
return cookies.some(c => c.key === 'refresh_token');
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
/**
|
|
20
34
|
* Обновляет accessToken через /api/v1/auth/refresh
|
|
21
35
|
* ВАЖНО: обычно этот endpoint работает только при наличии refresh-cookie,
|
|
@@ -24,6 +38,19 @@ export class AuthManager {
|
|
|
24
38
|
* @returns {Promise<string|null>} accessToken или null
|
|
25
39
|
*/
|
|
26
40
|
async refreshAccessToken() {
|
|
41
|
+
// Проверяем наличие refresh_token перед попыткой обновления
|
|
42
|
+
if (!this.hasRefreshToken()) {
|
|
43
|
+
console.error('❌ Не удалось обновить токен: refresh_token не найден в cookies');
|
|
44
|
+
console.error('💡 Решение:');
|
|
45
|
+
console.error(' 1. Откройте итд.com в браузере и войдите в аккаунт');
|
|
46
|
+
console.error(' 2. Откройте DevTools (F12) → Network');
|
|
47
|
+
console.error(' 3. Найдите любой запрос к итд.com');
|
|
48
|
+
console.error(' 4. Скопируйте значение заголовка Cookie');
|
|
49
|
+
console.error(' 5. Вставьте в файл .cookies в корне проекта');
|
|
50
|
+
console.error(' 6. Убедитесь, что в Cookie есть refresh_token');
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
27
54
|
try {
|
|
28
55
|
const refreshUrl = `${this.client.baseUrl}/api/v1/auth/refresh`;
|
|
29
56
|
|
|
@@ -80,7 +107,13 @@ export class AuthManager {
|
|
|
80
107
|
return null;
|
|
81
108
|
} catch (error) {
|
|
82
109
|
if (error.response) {
|
|
83
|
-
|
|
110
|
+
const errorData = error.response.data;
|
|
111
|
+
if (errorData?.error?.code === 'REFRESH_TOKEN_MISSING') {
|
|
112
|
+
console.error('❌ Не удалось обновить токен: refresh_token не найден');
|
|
113
|
+
console.error('💡 Решение: обновите файл .cookies из браузера (см. инструкцию выше)');
|
|
114
|
+
} else {
|
|
115
|
+
console.error('refreshAccessToken failed:', error.response.status, error.response.data);
|
|
116
|
+
}
|
|
84
117
|
} else {
|
|
85
118
|
console.error('refreshAccessToken failed:', error.message);
|
|
86
119
|
}
|
|
@@ -123,4 +156,36 @@ export class AuthManager {
|
|
|
123
156
|
// Реальная проверка происходит на уровне API (401 ошибка)
|
|
124
157
|
return !!(this.client.accessToken || this.isAuthenticated);
|
|
125
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Проверяет валидность токена, делая тестовый запрос
|
|
162
|
+
* Если токен истек и есть refresh_token, автоматически обновляет его
|
|
163
|
+
*
|
|
164
|
+
* @returns {Promise<boolean>} True если токен валиден или успешно обновлен
|
|
165
|
+
*/
|
|
166
|
+
async validateAndRefreshToken() {
|
|
167
|
+
if (!this.client.accessToken) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// Делаем легкий запрос для проверки токена
|
|
173
|
+
const profileUrl = `${this.client.baseUrl}/api/users/me`;
|
|
174
|
+
const response = await this.client.axios.get(profileUrl);
|
|
175
|
+
|
|
176
|
+
if (response.status === 200) {
|
|
177
|
+
return true; // Токен валиден
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return false;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
// Если получили 401, пытаемся обновить токен
|
|
183
|
+
if (error.response?.status === 401) {
|
|
184
|
+
const newToken = await this.refreshAccessToken();
|
|
185
|
+
return !!newToken;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
126
191
|
}
|
package/src/client.js
CHANGED
|
@@ -219,6 +219,25 @@ export class ITDClient {
|
|
|
219
219
|
return await this.auth.refreshAccessToken();
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Проверяет наличие refresh_token в cookies
|
|
224
|
+
*
|
|
225
|
+
* @returns {boolean} True если refresh_token доступен для обновления токена
|
|
226
|
+
*/
|
|
227
|
+
hasRefreshToken() {
|
|
228
|
+
return this.auth.hasRefreshToken();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Проверяет валидность токена и обновляет его при необходимости
|
|
233
|
+
* Полезно вызывать перед множественными запросами с большими интервалами
|
|
234
|
+
*
|
|
235
|
+
* @returns {Promise<boolean>} True если токен валиден или успешно обновлен
|
|
236
|
+
*/
|
|
237
|
+
async validateAndRefreshToken() {
|
|
238
|
+
return await this.auth.validateAndRefreshToken();
|
|
239
|
+
}
|
|
240
|
+
|
|
222
241
|
/**
|
|
223
242
|
* Выход из аккаунта
|
|
224
243
|
*
|
package/src/posts.js
CHANGED
|
@@ -47,7 +47,8 @@ export class PostsManager {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Затем создаем пост с ID загруженного файла
|
|
50
|
-
|
|
50
|
+
// Используем attachmentIds (не attachments!) - это правильное поле из API
|
|
51
|
+
postData.attachmentIds = [uploadedFile.id];
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// Создаем пост (с изображением или без)
|
|
@@ -112,7 +113,8 @@ export class PostsManager {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
// Затем создаем пост с ID загруженного файла
|
|
115
|
-
|
|
116
|
+
// Используем attachmentIds (не attachments!) - это правильное поле из API
|
|
117
|
+
postData.attachmentIds = [uploadedFile.id];
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
// Создаем пост на стене
|