itd-sdk-js 1.0.6 → 1.0.8
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 +47 -11
- package/README.md +3 -4
- package/examples/auto-refresh.js +1 -4
- package/examples/basic-usage.js +1 -3
- package/examples/quick-start.js +15 -20
- package/examples/token-validation.js +0 -2
- package/examples/user-friendly.js +0 -2
- package/package.json +1 -1
- package/src/auth.js +2 -8
- package/src/client.js +77 -11
- package/src/comments.js +90 -41
package/API_REFERENCE.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Техническое руководство по методам и настройке библиотеки для работы с API сайта `итд.com`.
|
|
4
4
|
|
|
5
|
+
## Структура SDK
|
|
6
|
+
|
|
7
|
+
| Файл | Назначение |
|
|
8
|
+
|------|------------|
|
|
9
|
+
| `client.js` | Главный клиент: создание axios, загрузка cookies, хранение токена, менеджеры, хелперы `get/post/put/patch/delete` |
|
|
10
|
+
| `auth.js` | Авторизация: refresh, logout, checkAuth, ensureAuthenticated, validateAndRefreshToken |
|
|
11
|
+
| `token-storage.js` | Сохранение токена в .env и cookies в .cookies (используется auth при refresh) |
|
|
12
|
+
| `posts.js` | Посты: createPost, getPosts, editPost, deletePost и др. |
|
|
13
|
+
| `comments.js` | Комментарии: addComment, replyToComment, getComments, likeComment и др. |
|
|
14
|
+
| `users.js` | Пользователи: getMyProfile, getUserProfile, followUser, getTopClans и др. |
|
|
15
|
+
| `notifications.js` | Уведомления |
|
|
16
|
+
| `hashtags.js` | Хэштеги |
|
|
17
|
+
| `files.js` | Загрузка файлов |
|
|
18
|
+
| `search.js` | Поиск |
|
|
19
|
+
| `reports.js` | Жалобы |
|
|
20
|
+
|
|
5
21
|
## Установка
|
|
6
22
|
|
|
7
23
|
### Через npm
|
|
@@ -38,7 +54,7 @@ import { ITDClient } from 'itd-sdk-js';
|
|
|
38
54
|
|
|
39
55
|
### Инициализация клиента
|
|
40
56
|
|
|
41
|
-
**Простой
|
|
57
|
+
**Простой вариант:**
|
|
42
58
|
|
|
43
59
|
```javascript
|
|
44
60
|
import { ITDClient } from 'itd-sdk-js';
|
|
@@ -47,8 +63,14 @@ import dotenv from 'dotenv';
|
|
|
47
63
|
dotenv.config();
|
|
48
64
|
|
|
49
65
|
const client = new ITDClient();
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
// Токен подхватывается из .env автоматически
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Только .cookies (без ITD_ACCESS_TOKEN в .env):**
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const client = new ITDClient();
|
|
73
|
+
await client.ensureAuthenticated(); // получит токен через refresh из .cookies
|
|
52
74
|
```
|
|
53
75
|
|
|
54
76
|
**С опциями (projectRoot / envPath / cookiesPath):**
|
|
@@ -59,13 +81,11 @@ client.auth.isAuthenticated = true;
|
|
|
59
81
|
const client = new ITDClient({
|
|
60
82
|
baseUrl: 'https://xn--d1ah4a.com',
|
|
61
83
|
userAgent: '...',
|
|
62
|
-
projectRoot: process.cwd(),
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
uploadTimeout: 120000, // таймаут загрузки файлов и создания поста, мс (по умолчанию 120 с)
|
|
84
|
+
projectRoot: process.cwd(),
|
|
85
|
+
requestTimeout: 60000,
|
|
86
|
+
uploadTimeout: 120000,
|
|
87
|
+
accessToken: '...', // опционально; по умолчанию из .env ITD_ACCESS_TOKEN
|
|
67
88
|
});
|
|
68
|
-
client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
|
|
69
89
|
```
|
|
70
90
|
|
|
71
91
|
- `projectRoot` — директория, в которой ищутся `.env` и `.cookies` (по умолчанию `process.cwd()`).
|
|
@@ -174,11 +194,24 @@ const post = await client.createPost('Текст поста', 'image.jpg');
|
|
|
174
194
|
|
|
175
195
|
Получает дерево комментариев к посту.
|
|
176
196
|
|
|
177
|
-
- **Параметры**: `postId`, `limit
|
|
197
|
+
- **Параметры**: `postId`, `limit` (по умолчанию 20, в запросе ограничивается 1–100), `sort` — в SDK можно передавать `"popular"`, `"new"`, `"old"`; в API уходит `popular`, `newest`, `oldest` соответственно.
|
|
198
|
+
- **Ответ API:** `{ data: { comments: [], total, hasMore, nextCursor } }`. Комментарии могут содержать вложенные `replies`, у ответов — поле `replyTo`.
|
|
178
199
|
|
|
179
200
|
### addComment(postId, text, replyToCommentId?)
|
|
180
201
|
|
|
181
|
-
Добавляет новый комментарий или ответ на
|
|
202
|
+
Добавляет новый комментарий или ответ на существующий (POST к посту: `/api/posts/:postId/comments`).
|
|
203
|
+
|
|
204
|
+
### replyToComment(commentId, content, replyToUserId)
|
|
205
|
+
|
|
206
|
+
Ответ на комментарий через отдельный эндпоинт **POST** `/api/comments/:commentId/replies`.
|
|
207
|
+
|
|
208
|
+
- **Параметры**:
|
|
209
|
+
- `commentId` — ID комментария, на который отвечаем.
|
|
210
|
+
- `content` — текст ответа.
|
|
211
|
+
- `replyToUserId` — ID пользователя-автора комментария (обязательно для API).
|
|
212
|
+
- **Возвращает:** объект созданного комментария-ответа или `null` при ошибке.
|
|
213
|
+
|
|
214
|
+
Пример: `client.replyToComment('80a775df-811a-4b60-b2fd-24651c1e546e', 'кака', '220e565c-45b9-4634-bdba-a6ebe6e8c5d1')`.
|
|
182
215
|
|
|
183
216
|
### Управление комментариями
|
|
184
217
|
|
|
@@ -200,9 +233,12 @@ const post = await client.createPost('Текст поста', 'image.jpg');
|
|
|
200
233
|
## Методы API: Управление токенами
|
|
201
234
|
|
|
202
235
|
- `hasRefreshToken()` — проверяет наличие refresh_token в cookies. Возвращает `boolean`.
|
|
236
|
+
- `ensureAuthenticated()` — если нет accessToken, но есть refresh_token — вызывает refresh и получает токен. Возвращает `Promise<boolean>`. Для сценария «только .cookies»: `await client.ensureAuthenticated()` перед первым запросом.
|
|
203
237
|
- `validateAndRefreshToken()` — проверяет валидность токена и обновляет его при необходимости. Возвращает `Promise<boolean>`.
|
|
204
238
|
- `refreshAccessToken()` — принудительно обновляет токен через refresh endpoint. Возвращает `Promise<string|null>`.
|
|
205
239
|
|
|
240
|
+
**Кастомные запросы:** `client.get(path)`, `client.post(path, data)`, `client.put(path, data)`, `client.patch(path, data)`, `client.delete(path)` — для произвольных эндпоинтов (baseURL уже подставлен).
|
|
241
|
+
|
|
206
242
|
**Рекомендация:** При множественных запросах с интервалами (более 10-15 минут) вызывайте `validateAndRefreshToken()` перед каждым запросом.
|
|
207
243
|
|
|
208
244
|
---
|
package/README.md
CHANGED
|
@@ -27,8 +27,8 @@ npm install
|
|
|
27
27
|
## Настройка
|
|
28
28
|
|
|
29
29
|
1. Создайте `.env` в корне проекта на основе `.env.example` (или используйте переменные окружения).
|
|
30
|
-
2.
|
|
31
|
-
3. Для
|
|
30
|
+
2. Токен: добавьте `ITD_ACCESS_TOKEN` в .env или положите `.cookies` с `refresh_token` — клиент сам подхватит токен из .env или получит через refresh.
|
|
31
|
+
3. Для авто-обновления токена создайте файл `.cookies` с Cookie из браузера (обязательно должен быть `refresh_token`).
|
|
32
32
|
|
|
33
33
|
SDK по умолчанию читает и пишет `.env` и `.cookies` в корне проекта (`process.cwd()`). При обновлении токена изменения сохраняются в ваш проект. При необходимости можно задать `projectRoot` или явные пути в конструкторе — см. [API_REFERENCE.md](API_REFERENCE.md).
|
|
34
34
|
|
|
@@ -45,8 +45,7 @@ import dotenv from 'dotenv';
|
|
|
45
45
|
dotenv.config();
|
|
46
46
|
|
|
47
47
|
const client = new ITDClient();
|
|
48
|
-
client.
|
|
49
|
-
client.auth.isAuthenticated = true;
|
|
48
|
+
// Токен подхватывается из .env. Если только .cookies — await client.ensureAuthenticated();
|
|
50
49
|
|
|
51
50
|
// Получаем профиль и тренды
|
|
52
51
|
const myProfile = await client.getMyProfile();
|
package/examples/auto-refresh.js
CHANGED
|
@@ -14,10 +14,7 @@ async function main() {
|
|
|
14
14
|
console.log('🔄 === Автоматическое обновление токена ===\n');
|
|
15
15
|
|
|
16
16
|
const client = new ITDClient();
|
|
17
|
-
|
|
18
|
-
// Устанавливаем токен (может быть уже истёкшим)
|
|
19
|
-
client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
|
|
20
|
-
client.auth.isAuthenticated = true;
|
|
17
|
+
// Токен подхватывается из .env автоматически
|
|
21
18
|
|
|
22
19
|
console.log('💡 SDK автоматически обновит токен при истечении!');
|
|
23
20
|
console.log(' Вы просто используете API - всё работает "из коробки"\n');
|
package/examples/basic-usage.js
CHANGED
|
@@ -12,10 +12,8 @@ dotenv.config();
|
|
|
12
12
|
async function main() {
|
|
13
13
|
console.log('📝 === Базовое использование SDK ===\n');
|
|
14
14
|
|
|
15
|
-
// Создаём клиент
|
|
15
|
+
// Создаём клиент (токен подхватывается из .env автоматически)
|
|
16
16
|
const client = new ITDClient();
|
|
17
|
-
client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
|
|
18
|
-
client.auth.isAuthenticated = true;
|
|
19
17
|
|
|
20
18
|
try {
|
|
21
19
|
// Получаем свой профиль
|
package/examples/quick-start.js
CHANGED
|
@@ -37,31 +37,26 @@ async function quickStart() {
|
|
|
37
37
|
// ============================================
|
|
38
38
|
// ШАГ 2: Настройка авторизации
|
|
39
39
|
// ============================================
|
|
40
|
-
console.log('🔐 Шаг 2:
|
|
40
|
+
console.log('🔐 Шаг 2: Проверяю авторизацию...\n');
|
|
41
41
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
// Токен подхватывается из .env автоматически. Если нет — пробуем refresh из .cookies
|
|
43
|
+
if (!client.accessToken && client.hasRefreshToken()) {
|
|
44
|
+
console.log(' Токена в .env нет, получаю через refresh из .cookies...\n');
|
|
45
|
+
const ok = await client.ensureAuthenticated();
|
|
46
|
+
if (!ok) {
|
|
47
|
+
console.log('❌ Не удалось получить токен из refresh_token\n');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!client.accessToken) {
|
|
52
|
+
console.log('❌ Нет токена. Добавьте ITD_ACCESS_TOKEN в .env или refresh_token в .cookies\n');
|
|
47
53
|
console.log('📋 Как получить токен:');
|
|
48
54
|
console.log('1. Откройте итд.com в браузере и войдите');
|
|
49
|
-
console.log('2.
|
|
50
|
-
console.log('3.
|
|
51
|
-
console.log('4. Откройте Response → скопируйте accessToken');
|
|
52
|
-
console.log('5. Вставьте в .env: ITD_ACCESS_TOKEN=ваш_токен\n');
|
|
55
|
+
console.log('2. DevTools (F12) → Network → скопируйте Cookie в .cookies');
|
|
56
|
+
console.log('3. Или скопируйте accessToken из ответа /api/v1/auth/refresh в .env\n');
|
|
53
57
|
return;
|
|
54
58
|
}
|
|
55
|
-
|
|
56
|
-
// Устанавливаем токен в клиент
|
|
57
|
-
// Это нужно для авторизации всех запросов
|
|
58
|
-
client.setAccessToken(accessToken);
|
|
59
|
-
|
|
60
|
-
// Помечаем клиент как авторизованный
|
|
61
|
-
// Это позволяет использовать методы, требующие авторизации
|
|
62
|
-
client.auth.isAuthenticated = true;
|
|
63
|
-
|
|
64
|
-
console.log('✅ Авторизация настроена (токен загружен из .env)\n');
|
|
59
|
+
console.log('✅ Авторизация настроена\n');
|
|
65
60
|
|
|
66
61
|
// ============================================
|
|
67
62
|
// ШАГ 3: Получение информации о себе
|
|
@@ -12,8 +12,6 @@ dotenv.config();
|
|
|
12
12
|
|
|
13
13
|
async function publishMultiplePosts() {
|
|
14
14
|
const client = new ITDClient();
|
|
15
|
-
client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
|
|
16
|
-
client.auth.isAuthenticated = true;
|
|
17
15
|
|
|
18
16
|
console.log('📝 Публикация нескольких постов с проверкой токена\n');
|
|
19
17
|
|
|
@@ -14,8 +14,6 @@ async function main() {
|
|
|
14
14
|
console.log('✨ === Удобные методы SDK ===\n');
|
|
15
15
|
|
|
16
16
|
const client = new ITDClient();
|
|
17
|
-
client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
|
|
18
|
-
client.auth.isAuthenticated = true;
|
|
19
17
|
|
|
20
18
|
try {
|
|
21
19
|
// Пример 1: Проверка подписки - одна строка вместо сложного запроса
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "itd-sdk-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
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",
|
package/src/auth.js
CHANGED
|
@@ -12,8 +12,6 @@ export class AuthManager {
|
|
|
12
12
|
constructor(client) {
|
|
13
13
|
this.client = client;
|
|
14
14
|
this.axios = client.axios;
|
|
15
|
-
this.isAuthenticated = false;
|
|
16
|
-
this.userData = null;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
/**
|
|
@@ -66,7 +64,6 @@ export class AuthManager {
|
|
|
66
64
|
|
|
67
65
|
// Обновляем токен в клиенте
|
|
68
66
|
this.client.setAccessToken(newToken);
|
|
69
|
-
this.isAuthenticated = true;
|
|
70
67
|
|
|
71
68
|
// Сохраняем токен в .env файл (в корне проекта)
|
|
72
69
|
await saveAccessToken(newToken, this.client.envPath);
|
|
@@ -132,8 +129,6 @@ export class AuthManager {
|
|
|
132
129
|
const response = await this.axios.post(logoutUrl);
|
|
133
130
|
|
|
134
131
|
if (response.status === 200) {
|
|
135
|
-
this.isAuthenticated = false;
|
|
136
|
-
this.userData = null;
|
|
137
132
|
this.client.setAccessToken(null);
|
|
138
133
|
// Очистка cookies
|
|
139
134
|
this.axios.defaults.headers.common['Cookie'] = '';
|
|
@@ -152,9 +147,8 @@ export class AuthManager {
|
|
|
152
147
|
* @returns {Promise<boolean>} True если авторизован
|
|
153
148
|
*/
|
|
154
149
|
async checkAuth() {
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
return !!(this.client.accessToken || this.isAuthenticated);
|
|
150
|
+
// Авторизован, если есть accessToken (реальная проверка — 401 при истечении)
|
|
151
|
+
return !!this.client.accessToken;
|
|
158
152
|
}
|
|
159
153
|
|
|
160
154
|
/**
|
package/src/client.js
CHANGED
|
@@ -34,9 +34,10 @@ export class ITDClient {
|
|
|
34
34
|
* @param {string} [options.cookiesPath] - Полный путь к .cookies (переопределяет projectRoot для .cookies)
|
|
35
35
|
* @param {number} [options.requestTimeout] - Таймаут обычных запросов в мс (по умолчанию 60000)
|
|
36
36
|
* @param {number} [options.uploadTimeout] - Таймаут загрузки файлов и создания поста в мс (по умолчанию 120000)
|
|
37
|
+
* @param {string} [options.accessToken] - JWT токен (если не указан — берётся из .env ITD_ACCESS_TOKEN)
|
|
37
38
|
*/
|
|
38
39
|
constructor(baseUrlOrOptions = null, userAgent = null) {
|
|
39
|
-
let baseUrl, projectRoot, envPath, cookiesPath, requestTimeout, uploadTimeout;
|
|
40
|
+
let baseUrl, projectRoot, envPath, cookiesPath, requestTimeout, uploadTimeout, accessToken;
|
|
40
41
|
|
|
41
42
|
if (baseUrlOrOptions && typeof baseUrlOrOptions === 'object' && !(baseUrlOrOptions instanceof URL)) {
|
|
42
43
|
const opts = baseUrlOrOptions;
|
|
@@ -47,6 +48,7 @@ export class ITDClient {
|
|
|
47
48
|
cookiesPath = opts.cookiesPath ?? path.join(projectRoot, '.cookies');
|
|
48
49
|
requestTimeout = opts.requestTimeout ?? 60000;
|
|
49
50
|
uploadTimeout = opts.uploadTimeout ?? 120000;
|
|
51
|
+
accessToken = opts.accessToken ?? process.env.ITD_ACCESS_TOKEN ?? null;
|
|
50
52
|
} else {
|
|
51
53
|
projectRoot = process.cwd();
|
|
52
54
|
baseUrl = baseUrlOrOptions || process.env.ITD_BASE_URL || 'https://xn--d1ah4a.com';
|
|
@@ -54,6 +56,7 @@ export class ITDClient {
|
|
|
54
56
|
cookiesPath = path.join(projectRoot, '.cookies');
|
|
55
57
|
requestTimeout = 60000;
|
|
56
58
|
uploadTimeout = 120000;
|
|
59
|
+
accessToken = process.env.ITD_ACCESS_TOKEN ?? null;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
// Используем реальный домен (IDN: итд.com = xn--d1ah4a.com)
|
|
@@ -71,7 +74,7 @@ export class ITDClient {
|
|
|
71
74
|
this.uploadTimeout = uploadTimeout;
|
|
72
75
|
|
|
73
76
|
/** @type {string|null} */
|
|
74
|
-
this.accessToken = null;
|
|
77
|
+
this.accessToken = accessToken || null;
|
|
75
78
|
|
|
76
79
|
// Прокси (важно, если браузер ходит через 127.0.0.1:10808)
|
|
77
80
|
// Можно задать: ITD_PROXY=http://127.0.0.1:10808
|
|
@@ -206,6 +209,66 @@ export class ITDClient {
|
|
|
206
209
|
setAccessToken(token) {
|
|
207
210
|
this.accessToken = token || null;
|
|
208
211
|
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Убедиться, что есть accessToken. Если нет, но есть refresh_token в cookies — вызывает refresh.
|
|
215
|
+
* Для сценария «только .cookies»: await client.ensureAuthenticated() перед первым запросом.
|
|
216
|
+
* @returns {Promise<boolean>} true если токен есть или получен, false если нет
|
|
217
|
+
*/
|
|
218
|
+
async ensureAuthenticated() {
|
|
219
|
+
if (this.accessToken) return true;
|
|
220
|
+
if (!this.hasRefreshToken()) return false;
|
|
221
|
+
const token = await this.refreshAccessToken();
|
|
222
|
+
return !!token;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Кастомный GET запрос (baseURL уже подставлен)
|
|
227
|
+
* @param {string} path - Путь, например /api/users/me
|
|
228
|
+
* @param {Object} [config] - Доп. опции axios
|
|
229
|
+
*/
|
|
230
|
+
get(path, config = {}) {
|
|
231
|
+
return this.axios.get(path, config);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Кастомный POST запрос
|
|
236
|
+
* @param {string} path - Путь, например /api/posts
|
|
237
|
+
* @param {Object} [data] - Тело запроса (JSON)
|
|
238
|
+
* @param {Object} [config] - Доп. опции axios
|
|
239
|
+
*/
|
|
240
|
+
post(path, data = {}, config = {}) {
|
|
241
|
+
return this.axios.post(path, data, config);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Кастомный PUT запрос
|
|
246
|
+
* @param {string} path - Путь
|
|
247
|
+
* @param {Object} [data] - Тело запроса
|
|
248
|
+
* @param {Object} [config] - Доп. опции axios
|
|
249
|
+
*/
|
|
250
|
+
put(path, data = {}, config = {}) {
|
|
251
|
+
return this.axios.put(path, data, config);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Кастомный PATCH запрос
|
|
256
|
+
* @param {string} path - Путь
|
|
257
|
+
* @param {Object} [data] - Тело запроса
|
|
258
|
+
* @param {Object} [config] - Доп. опции axios
|
|
259
|
+
*/
|
|
260
|
+
patch(path, data = {}, config = {}) {
|
|
261
|
+
return this.axios.patch(path, data, config);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Кастомный DELETE запрос
|
|
266
|
+
* @param {string} path - Путь
|
|
267
|
+
* @param {Object} [config] - Доп. опции axios
|
|
268
|
+
*/
|
|
269
|
+
delete(path, config = {}) {
|
|
270
|
+
return this.axios.delete(path, config);
|
|
271
|
+
}
|
|
209
272
|
|
|
210
273
|
/**
|
|
211
274
|
* Загружает cookies из файла .cookies
|
|
@@ -481,6 +544,18 @@ export class ITDClient {
|
|
|
481
544
|
async addComment(postId, text, replyToCommentId = null) {
|
|
482
545
|
return await this.comments.addComment(postId, text, replyToCommentId);
|
|
483
546
|
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Ответ на комментарий (POST /api/comments/:id/replies).
|
|
550
|
+
*
|
|
551
|
+
* @param {string} commentId - ID комментария, на который отвечаем
|
|
552
|
+
* @param {string} content - Текст ответа
|
|
553
|
+
* @param {string} replyToUserId - ID пользователя-автора комментария (обязательно для API)
|
|
554
|
+
* @returns {Promise<Object|null>} Данные созданного комментария-ответа или null при ошибке
|
|
555
|
+
*/
|
|
556
|
+
async replyToComment(commentId, content, replyToUserId) {
|
|
557
|
+
return await this.comments.replyToComment(commentId, content, replyToUserId);
|
|
558
|
+
}
|
|
484
559
|
|
|
485
560
|
/**
|
|
486
561
|
* Ставит лайк на комментарий
|
|
@@ -883,15 +958,6 @@ export class ITDClient {
|
|
|
883
958
|
|
|
884
959
|
// === Пользователи ===
|
|
885
960
|
|
|
886
|
-
/**
|
|
887
|
-
* Получает свой профиль (удобный метод)
|
|
888
|
-
*
|
|
889
|
-
* @returns {Promise<Object|null>} Данные профиля или null
|
|
890
|
-
*/
|
|
891
|
-
async getMyProfile() {
|
|
892
|
-
return await this.users.getMyProfile();
|
|
893
|
-
}
|
|
894
|
-
|
|
895
961
|
/**
|
|
896
962
|
* Проверяет, подписан ли текущий пользователь на указанного (удобный метод)
|
|
897
963
|
*
|
package/src/comments.js
CHANGED
|
@@ -54,58 +54,107 @@ export class CommentsManager {
|
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Ответ на комментарий (отдельный эндпоинт /api/comments/:id/replies).
|
|
60
|
+
*
|
|
61
|
+
* @param {string} commentId - ID комментария, на который отвечаем
|
|
62
|
+
* @param {string} content - Текст ответа
|
|
63
|
+
* @param {string} replyToUserId - ID пользователя-автора комментария (обязательно для API)
|
|
64
|
+
* @returns {Promise<Object|null>} Данные созданного комментария-ответа или null при ошибке
|
|
65
|
+
*/
|
|
66
|
+
async replyToComment(commentId, content, replyToUserId) {
|
|
67
|
+
if (!await this.client.auth.checkAuth()) {
|
|
68
|
+
console.error('Ошибка: необходимо войти в аккаунт');
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (!replyToUserId) {
|
|
72
|
+
console.error('Ошибка: replyToUserId обязателен для ответа на комментарий');
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const url = `${this.client.baseUrl}/api/comments/${commentId}/replies`;
|
|
77
|
+
const response = await this.axios.post(url, {
|
|
78
|
+
content,
|
|
79
|
+
replyToUserId,
|
|
80
|
+
});
|
|
81
|
+
if (response.status === 200 || response.status === 201) {
|
|
82
|
+
return response.data;
|
|
83
|
+
}
|
|
84
|
+
console.error(`Ошибка ответа на комментарий: ${response.status} - ${JSON.stringify(response.data)}`);
|
|
85
|
+
return null;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Исключение при ответе на комментарий:', error.message);
|
|
88
|
+
if (error.response) {
|
|
89
|
+
console.error('Response status:', error.response.status);
|
|
90
|
+
console.error('Response data:', error.response.data);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
57
95
|
|
|
58
96
|
/**
|
|
59
|
-
* Получает комментарии к
|
|
60
|
-
*
|
|
97
|
+
* Получает комментарии к посту.
|
|
98
|
+
* API ожидает sort: "newest" | "oldest" | "popular". SDK принимает "new"/"old"/"popular" и маппит в newest/oldest/popular.
|
|
99
|
+
*
|
|
61
100
|
* @param {string} postId - ID поста
|
|
62
|
-
* @param {number} limit - Количество комментариев
|
|
63
|
-
* @param {string} sort - Сортировка: "popular", "new", "old" (
|
|
101
|
+
* @param {number} limit - Количество комментариев (по умолчанию 20)
|
|
102
|
+
* @param {string} sort - Сортировка: "popular", "new", "old" (в API уходит как popular, newest, oldest)
|
|
64
103
|
* @returns {Promise<Object>} { comments: [], total, hasMore, nextCursor } или { comments: [] } при ошибке
|
|
65
|
-
*
|
|
66
|
-
* Примечание: Авторизация не требуется для просмотра комментариев
|
|
67
104
|
*/
|
|
68
105
|
async getComments(postId, limit = 20, sort = 'popular') {
|
|
106
|
+
const commentsUrl = `${this.client.baseUrl}/api/posts/${postId}/comments`;
|
|
107
|
+
const reqLimit = Math.min(Math.max(1, Number(limit) || 20), 100);
|
|
108
|
+
const sortMap = { new: 'newest', old: 'oldest', popular: 'popular', newest: 'newest', oldest: 'oldest' };
|
|
109
|
+
const reqSort = sortMap[sort] || 'popular';
|
|
110
|
+
|
|
111
|
+
const parseResponse = (response) => {
|
|
112
|
+
const data = response.data?.data ?? response.data;
|
|
113
|
+
if (data?.comments) {
|
|
114
|
+
return {
|
|
115
|
+
comments: data.comments,
|
|
116
|
+
total: data.total ?? data.comments.length,
|
|
117
|
+
hasMore: data.hasMore ?? false,
|
|
118
|
+
nextCursor: data.nextCursor ?? null
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(data)) {
|
|
122
|
+
return {
|
|
123
|
+
comments: data,
|
|
124
|
+
total: data.length,
|
|
125
|
+
hasMore: false,
|
|
126
|
+
nextCursor: null
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { comments: [], total: 0, hasMore: false, nextCursor: null };
|
|
130
|
+
};
|
|
131
|
+
|
|
69
132
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const response = await this.axios.get(commentsUrl, { params });
|
|
77
|
-
|
|
133
|
+
const response = await this.axios.get(commentsUrl, {
|
|
134
|
+
params: { limit: reqLimit, sort: reqSort },
|
|
135
|
+
});
|
|
136
|
+
|
|
78
137
|
if (response.status === 200) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
hasMore: data.data.hasMore || false,
|
|
86
|
-
nextCursor: data.data.nextCursor || null
|
|
87
|
-
};
|
|
88
|
-
} else if (data.comments) {
|
|
89
|
-
return {
|
|
90
|
-
comments: data.comments,
|
|
91
|
-
total: data.total || 0,
|
|
92
|
-
hasMore: data.hasMore || false,
|
|
93
|
-
nextCursor: data.nextCursor || null
|
|
94
|
-
};
|
|
95
|
-
} else if (Array.isArray(data)) {
|
|
96
|
-
return {
|
|
97
|
-
comments: data,
|
|
98
|
-
total: data.length,
|
|
99
|
-
hasMore: false,
|
|
100
|
-
nextCursor: null
|
|
101
|
-
};
|
|
138
|
+
return parseResponse(response);
|
|
139
|
+
}
|
|
140
|
+
if (response.status === 422) {
|
|
141
|
+
const fallback = await this.axios.get(commentsUrl, { params: { limit: reqLimit, sort: 'popular' } });
|
|
142
|
+
if (fallback.status === 200) {
|
|
143
|
+
return parseResponse(fallback);
|
|
102
144
|
}
|
|
103
|
-
|
|
104
|
-
} else {
|
|
105
|
-
console.error(`Ошибка получения комментариев: ${response.status}`);
|
|
106
|
-
return { comments: [], total: 0, hasMore: false, nextCursor: null };
|
|
145
|
+
console.warn('⚠️ GET /api/posts/:postId/comments: 422. API ожидает sort: newest | oldest | popular.');
|
|
107
146
|
}
|
|
147
|
+
console.error(`Ошибка получения комментариев: ${response.status}`);
|
|
148
|
+
return { comments: [], total: 0, hasMore: false, nextCursor: null };
|
|
108
149
|
} catch (error) {
|
|
150
|
+
if (error.response?.status === 422) {
|
|
151
|
+
try {
|
|
152
|
+
const retry = await this.axios.get(commentsUrl, { params: { limit: 20, sort: 'popular' } });
|
|
153
|
+
if (retry.status === 200) {
|
|
154
|
+
return parseResponse(retry);
|
|
155
|
+
}
|
|
156
|
+
} catch (_) {}
|
|
157
|
+
}
|
|
109
158
|
console.error('Исключение при получении комментариев:', error.message);
|
|
110
159
|
if (error.response) {
|
|
111
160
|
console.error('Response status:', error.response.status);
|