itd-sdk-js 1.0.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/.cookies.example +1 -0
- package/.env.example +45 -0
- package/API_REFERENCE.md +237 -0
- package/README.md +85 -0
- package/examples/README.md +65 -0
- package/examples/auto-refresh.js +63 -0
- package/examples/basic-usage.js +54 -0
- package/examples/quick-start.js +326 -0
- package/examples/user-friendly.js +79 -0
- package/package.json +57 -0
- package/src/auth.js +126 -0
- package/src/client.js +920 -0
- package/src/comments.js +256 -0
- package/src/files.js +59 -0
- package/src/hashtags.js +101 -0
- package/src/notifications.js +215 -0
- package/src/posts.js +480 -0
- package/src/reports.js +97 -0
- package/src/search.js +86 -0
- package/src/token-storage.js +66 -0
- package/src/users.js +416 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Утилита для сохранения токена в .env файл
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Обновляет ITD_ACCESS_TOKEN в .env файле
|
|
13
|
+
*
|
|
14
|
+
* @param {string} newToken - Новый access token
|
|
15
|
+
* @returns {Promise<boolean>} True если успешно
|
|
16
|
+
*/
|
|
17
|
+
export async function saveAccessToken(newToken) {
|
|
18
|
+
try {
|
|
19
|
+
const envPath = path.join(__dirname, '..', '.env');
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(envPath)) {
|
|
22
|
+
console.warn('⚠️ Файл .env не найден, токен не сохранен');
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let content = fs.readFileSync(envPath, 'utf8');
|
|
27
|
+
|
|
28
|
+
// Ищем строку с ITD_ACCESS_TOKEN
|
|
29
|
+
const tokenRegex = /^ITD_ACCESS_TOKEN=.*$/m;
|
|
30
|
+
|
|
31
|
+
if (tokenRegex.test(content)) {
|
|
32
|
+
// Заменяем существующий токен
|
|
33
|
+
content = content.replace(tokenRegex, `ITD_ACCESS_TOKEN=${newToken}`);
|
|
34
|
+
} else {
|
|
35
|
+
// Добавляем новую строку, если токена нет
|
|
36
|
+
content += `\nITD_ACCESS_TOKEN=${newToken}\n`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fs.writeFileSync(envPath, content, 'utf8');
|
|
40
|
+
console.log('✅ Токен сохранен в .env');
|
|
41
|
+
return true;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('❌ Ошибка сохранения токена в .env:', error.message);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Сохраняет cookies в файл .cookies
|
|
50
|
+
*
|
|
51
|
+
* @param {string} newCookieHeader - Новый cookie header
|
|
52
|
+
* @returns {Promise<boolean>} True если успешно
|
|
53
|
+
*/
|
|
54
|
+
export async function saveCookieHeader(newCookieHeader) {
|
|
55
|
+
try {
|
|
56
|
+
const cookiesPath = path.join(__dirname, '..', '.cookies');
|
|
57
|
+
|
|
58
|
+
// Просто записываем cookies в файл (одна строка)
|
|
59
|
+
fs.writeFileSync(cookiesPath, newCookieHeader, 'utf8');
|
|
60
|
+
console.log('✅ Cookies сохранены в .cookies');
|
|
61
|
+
return true;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('❌ Ошибка сохранения cookies в .cookies:', error.message);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/users.js
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Модуль для работы с пользователями
|
|
3
|
+
*/
|
|
4
|
+
export class UsersManager {
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
this.axios = client.axios;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Обновляет описание профиля текущего пользователя
|
|
12
|
+
*
|
|
13
|
+
* @param {string} bio - Новое описание профиля
|
|
14
|
+
* @param {string|null} displayName - Новое отображаемое имя (опционально)
|
|
15
|
+
* @returns {Promise<Object|null>} Обновленные данные профиля или null при ошибке
|
|
16
|
+
*/
|
|
17
|
+
async updateProfile(bio, displayName = null) {
|
|
18
|
+
if (!await this.client.auth.checkAuth()) {
|
|
19
|
+
console.error('Ошибка: необходимо войти в аккаунт');
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const updateUrl = `${this.client.baseUrl}/api/users/me`;
|
|
25
|
+
|
|
26
|
+
const updateData = {};
|
|
27
|
+
if (bio !== null && bio !== undefined) {
|
|
28
|
+
updateData.bio = bio;
|
|
29
|
+
}
|
|
30
|
+
if (displayName !== null && displayName !== undefined) {
|
|
31
|
+
updateData.displayName = displayName;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const response = await this.axios.put(updateUrl, updateData);
|
|
35
|
+
|
|
36
|
+
if (response.status === 200) {
|
|
37
|
+
return response.data;
|
|
38
|
+
} else {
|
|
39
|
+
console.error(`Ошибка обновления профиля: ${response.status}`);
|
|
40
|
+
if (response.data) {
|
|
41
|
+
console.error('Response data:', response.data);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Исключение при обновлении профиля:', error.message);
|
|
47
|
+
if (error.response) {
|
|
48
|
+
console.error('Response status:', error.response.status);
|
|
49
|
+
console.error('Response data:', error.response.data);
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Получает данные текущего пользователя
|
|
57
|
+
*
|
|
58
|
+
* @returns {Promise<Object|null>} Данные профиля или null при ошибке
|
|
59
|
+
*/
|
|
60
|
+
async getMyProfile() {
|
|
61
|
+
if (!await this.client.auth.checkAuth()) {
|
|
62
|
+
console.error('Ошибка: необходимо войти в аккаунт');
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const profileUrl = `${this.client.baseUrl}/api/users/me`;
|
|
68
|
+
const response = await this.axios.get(profileUrl);
|
|
69
|
+
|
|
70
|
+
if (response.status === 200) {
|
|
71
|
+
return response.data;
|
|
72
|
+
} else {
|
|
73
|
+
console.error(`Ошибка получения профиля: ${response.status}`);
|
|
74
|
+
if (response.data) {
|
|
75
|
+
console.error('Response data:', response.data);
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Исключение при получении профиля:', error.message);
|
|
81
|
+
if (error.response) {
|
|
82
|
+
console.error('Response status:', error.response.status);
|
|
83
|
+
console.error('Response data:', error.response.data);
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Получает профиль пользователя по username
|
|
91
|
+
*
|
|
92
|
+
* @param {string} username - Имя пользователя
|
|
93
|
+
* @returns {Promise<Object|null>} Данные профиля или null при ошибке
|
|
94
|
+
*
|
|
95
|
+
* Примечание: Авторизация не требуется для просмотра публичных профилей
|
|
96
|
+
*/
|
|
97
|
+
async getUserProfile(username) {
|
|
98
|
+
try {
|
|
99
|
+
const profileUrl = `${this.client.baseUrl}/api/users/${username}`;
|
|
100
|
+
const response = await this.axios.get(profileUrl);
|
|
101
|
+
|
|
102
|
+
if (response.status === 200) {
|
|
103
|
+
// Структура может быть { data: {...} } или просто {...}
|
|
104
|
+
return response.data.data || response.data;
|
|
105
|
+
} else {
|
|
106
|
+
console.error(`Ошибка получения профиля пользователя: ${response.status}`);
|
|
107
|
+
if (response.data) {
|
|
108
|
+
console.error('Response data:', response.data);
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Исключение при получении профиля пользователя:', error.message);
|
|
114
|
+
if (error.response) {
|
|
115
|
+
console.error('Response status:', error.response.status);
|
|
116
|
+
console.error('Response data:', error.response.data);
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Подписывается на пользователя
|
|
124
|
+
*
|
|
125
|
+
* @param {string} username - Имя пользователя
|
|
126
|
+
* @returns {Promise<Object|null>} { following: true, followersCount: number } или null при ошибке
|
|
127
|
+
*/
|
|
128
|
+
async followUser(username) {
|
|
129
|
+
if (!await this.client.auth.checkAuth()) {
|
|
130
|
+
console.error('Ошибка: необходимо войти в аккаунт');
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const followUrl = `${this.client.baseUrl}/api/users/${username}/follow`;
|
|
136
|
+
const response = await this.axios.post(followUrl);
|
|
137
|
+
|
|
138
|
+
if (response.status === 200 || response.status === 201) {
|
|
139
|
+
return response.data; // { following: true, followersCount: number }
|
|
140
|
+
} else {
|
|
141
|
+
console.error(`Ошибка подписки: ${response.status}`);
|
|
142
|
+
if (response.data) {
|
|
143
|
+
console.error('Response data:', response.data);
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('Исключение при подписке:', error.message);
|
|
149
|
+
if (error.response) {
|
|
150
|
+
console.error('Response status:', error.response.status);
|
|
151
|
+
console.error('Response data:', error.response.data);
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Отписывается от пользователя
|
|
159
|
+
*
|
|
160
|
+
* @param {string} username - Имя пользователя
|
|
161
|
+
* @returns {Promise<Object|null>} { following: false, followersCount: number } или null при ошибке
|
|
162
|
+
*/
|
|
163
|
+
async unfollowUser(username) {
|
|
164
|
+
if (!await this.client.auth.checkAuth()) {
|
|
165
|
+
console.error('Ошибка: необходимо войти в аккаунт');
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const unfollowUrl = `${this.client.baseUrl}/api/users/${username}/follow`;
|
|
171
|
+
const response = await this.axios.delete(unfollowUrl);
|
|
172
|
+
|
|
173
|
+
if (response.status === 200 || response.status === 204) {
|
|
174
|
+
return response.data || { following: false, followersCount: 0 };
|
|
175
|
+
} else {
|
|
176
|
+
console.error(`Ошибка отписки: ${response.status}`);
|
|
177
|
+
if (response.data) {
|
|
178
|
+
console.error('Response data:', response.data);
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('Исключение при отписке:', error.message);
|
|
184
|
+
if (error.response) {
|
|
185
|
+
console.error('Response status:', error.response.status);
|
|
186
|
+
console.error('Response data:', error.response.data);
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Получает список подписчиков пользователя
|
|
194
|
+
*
|
|
195
|
+
* @param {string} username - Имя пользователя
|
|
196
|
+
* @param {number} page - Номер страницы (начиная с 1)
|
|
197
|
+
* @param {number} limit - Количество на странице
|
|
198
|
+
* @returns {Promise<Object|null>} { users: [], pagination: {} } или null при ошибке
|
|
199
|
+
*
|
|
200
|
+
* Примечание: Авторизация не требуется для просмотра подписчиков
|
|
201
|
+
*/
|
|
202
|
+
async getFollowers(username, page = 1, limit = 30) {
|
|
203
|
+
try {
|
|
204
|
+
const followersUrl = `${this.client.baseUrl}/api/users/${username}/followers`;
|
|
205
|
+
const params = { page, limit };
|
|
206
|
+
const response = await this.axios.get(followersUrl, { params });
|
|
207
|
+
|
|
208
|
+
if (response.status === 200) {
|
|
209
|
+
const data = response.data;
|
|
210
|
+
// Структура: { data: { users: [...], pagination: {...} } }
|
|
211
|
+
if (data.data && data.data.users) {
|
|
212
|
+
return {
|
|
213
|
+
users: data.data.users,
|
|
214
|
+
pagination: data.data.pagination || {}
|
|
215
|
+
};
|
|
216
|
+
} else if (data.users) {
|
|
217
|
+
return {
|
|
218
|
+
users: data.users,
|
|
219
|
+
pagination: data.pagination || {}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return { users: [], pagination: {} };
|
|
223
|
+
} else {
|
|
224
|
+
console.error(`Ошибка получения подписчиков: ${response.status}`);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error('Исключение при получении подписчиков:', error.message);
|
|
229
|
+
if (error.response) {
|
|
230
|
+
console.error('Response status:', error.response.status);
|
|
231
|
+
console.error('Response data:', error.response.data);
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Получает список подписок пользователя
|
|
239
|
+
*
|
|
240
|
+
* @param {string} username - Имя пользователя
|
|
241
|
+
* @param {number} page - Номер страницы (начиная с 1)
|
|
242
|
+
* @param {number} limit - Количество на странице
|
|
243
|
+
* @returns {Promise<Object|null>} { users: [], pagination: {} } или null при ошибке
|
|
244
|
+
*
|
|
245
|
+
* Примечание: Авторизация не требуется для просмотра подписок
|
|
246
|
+
*/
|
|
247
|
+
async getFollowing(username, page = 1, limit = 30) {
|
|
248
|
+
try {
|
|
249
|
+
const followingUrl = `${this.client.baseUrl}/api/users/${username}/following`;
|
|
250
|
+
const params = { page, limit };
|
|
251
|
+
const response = await this.axios.get(followingUrl, { params });
|
|
252
|
+
|
|
253
|
+
if (response.status === 200) {
|
|
254
|
+
const data = response.data;
|
|
255
|
+
// Структура: { data: { users: [...], pagination: {...} } }
|
|
256
|
+
if (data.data && data.data.users) {
|
|
257
|
+
return {
|
|
258
|
+
users: data.data.users,
|
|
259
|
+
pagination: data.data.pagination || {}
|
|
260
|
+
};
|
|
261
|
+
} else if (data.users) {
|
|
262
|
+
return {
|
|
263
|
+
users: data.users,
|
|
264
|
+
pagination: data.pagination || {}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return { users: [], pagination: {} };
|
|
268
|
+
} else {
|
|
269
|
+
console.error(`Ошибка получения подписок: ${response.status}`);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('Исключение при получении подписок:', error.message);
|
|
274
|
+
if (error.response) {
|
|
275
|
+
console.error('Response status:', error.response.status);
|
|
276
|
+
console.error('Response data:', error.response.data);
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Получает клан пользователя (эмодзи из avatar)
|
|
284
|
+
*
|
|
285
|
+
* @param {string} username - Имя пользователя
|
|
286
|
+
* @returns {Promise<string|null>} Эмодзи клана или null при ошибке
|
|
287
|
+
*/
|
|
288
|
+
async getUserClan(username) {
|
|
289
|
+
const profile = await this.getUserProfile(username);
|
|
290
|
+
if (!profile) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
// Клан - это эмодзи в поле avatar
|
|
294
|
+
return profile.avatar || null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Получает топ кланов по количеству участников
|
|
299
|
+
*
|
|
300
|
+
* @returns {Promise<Array|null>} Массив кланов [{ avatar: "🦎", memberCount: 3794 }, ...] или null при ошибке
|
|
301
|
+
*/
|
|
302
|
+
async getTopClans() {
|
|
303
|
+
try {
|
|
304
|
+
const topClansUrl = `${this.client.baseUrl}/api/users/stats/top-clans`;
|
|
305
|
+
const response = await this.axios.get(topClansUrl);
|
|
306
|
+
|
|
307
|
+
if (response.status === 200) {
|
|
308
|
+
const data = response.data;
|
|
309
|
+
// Структура: { clans: [...] }
|
|
310
|
+
return data.clans || [];
|
|
311
|
+
} else {
|
|
312
|
+
console.error(`Ошибка получения топ кланов: ${response.status}`);
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.error('Исключение при получении топ кланов:', error.message);
|
|
317
|
+
if (error.response) {
|
|
318
|
+
console.error('Response status:', error.response.status);
|
|
319
|
+
console.error('Response data:', error.response.data);
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Получает рекомендации кого подписаться
|
|
327
|
+
*
|
|
328
|
+
* @returns {Promise<Array|null>} Массив пользователей или null при ошибке
|
|
329
|
+
*/
|
|
330
|
+
async getWhoToFollow() {
|
|
331
|
+
if (!await this.client.auth.checkAuth()) {
|
|
332
|
+
console.error('Ошибка: необходимо войти в аккаунт');
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const suggestionsUrl = `${this.client.baseUrl}/api/users/suggestions/who-to-follow`;
|
|
338
|
+
const response = await this.axios.get(suggestionsUrl);
|
|
339
|
+
|
|
340
|
+
if (response.status === 200) {
|
|
341
|
+
const data = response.data;
|
|
342
|
+
// Структура: { users: [...] }
|
|
343
|
+
return data.users || [];
|
|
344
|
+
} else {
|
|
345
|
+
console.error(`Ошибка получения рекомендаций: ${response.status}`);
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error('Исключение при получении рекомендаций:', error.message);
|
|
350
|
+
if (error.response) {
|
|
351
|
+
console.error('Response status:', error.response.status);
|
|
352
|
+
console.error('Response data:', error.response.data);
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ========== USER-FRIENDLY МЕТОДЫ ==========
|
|
359
|
+
|
|
360
|
+
// getMyProfile() уже реализован выше (строка 60) - это удобный метод по умолчанию
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Проверяет, подписан ли текущий пользователь на указанного (удобный метод)
|
|
364
|
+
*
|
|
365
|
+
* @param {string} username - Имя пользователя для проверки
|
|
366
|
+
* @returns {Promise<boolean>} True если подписан, false если нет или ошибка
|
|
367
|
+
*/
|
|
368
|
+
async isFollowing(username) {
|
|
369
|
+
if (!await this.client.auth.checkAuth()) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
const profile = await this.getUserProfile(username);
|
|
373
|
+
return profile ? (profile.isFollowing === true) : false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Получает количество своих подписчиков (удобный метод)
|
|
378
|
+
*
|
|
379
|
+
* @returns {Promise<number>} Количество подписчиков
|
|
380
|
+
*/
|
|
381
|
+
async getMyFollowersCount() {
|
|
382
|
+
const profile = await this.getMyProfile();
|
|
383
|
+
return profile ? (profile.followersCount || 0) : 0;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Получает количество своих подписок (удобный метод)
|
|
388
|
+
*
|
|
389
|
+
* @returns {Promise<number>} Количество подписок
|
|
390
|
+
*/
|
|
391
|
+
async getMyFollowingCount() {
|
|
392
|
+
const profile = await this.getMyProfile();
|
|
393
|
+
return profile ? (profile.followingCount || 0) : 0;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Получает свой клан (эмодзи аватара) (удобный метод)
|
|
398
|
+
*
|
|
399
|
+
* @returns {Promise<string|null>} Эмодзи клана или null
|
|
400
|
+
*/
|
|
401
|
+
async getMyClan() {
|
|
402
|
+
const profile = await this.getMyProfile();
|
|
403
|
+
return profile ? (profile.avatar || null) : null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Получает клан пользователя (эмодзи аватара) (удобный метод)
|
|
408
|
+
*
|
|
409
|
+
* @param {string} username - Имя пользователя
|
|
410
|
+
* @returns {Promise<string|null>} Эмодзи клана или null
|
|
411
|
+
*/
|
|
412
|
+
async getUserClan(username) {
|
|
413
|
+
const profile = await this.getUserProfile(username);
|
|
414
|
+
return profile ? (profile.avatar || null) : null;
|
|
415
|
+
}
|
|
416
|
+
}
|