shan-server 1.0.4 → 1.0.6
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 +1 -1
- package/index.js +1509 -307
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,24 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
1
3
|
const axios = require('axios');
|
|
2
4
|
const readline = require('readline');
|
|
3
5
|
const os = require('os');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
|
|
10
|
+
const USER_CONFIG_PATH = path.join(os.homedir(), '.shan-server', 'config.json');
|
|
11
|
+
|
|
12
|
+
function loadPersistedConfig() {
|
|
13
|
+
try {
|
|
14
|
+
if (fs.existsSync(USER_CONFIG_PATH)) {
|
|
15
|
+
return JSON.parse(fs.readFileSync(USER_CONFIG_PATH, 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
} catch (e) {}
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function savePersistedConfig(partial) {
|
|
22
|
+
try {
|
|
23
|
+
const dir = path.dirname(USER_CONFIG_PATH);
|
|
24
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
const current = loadPersistedConfig();
|
|
26
|
+
const merged = { ...current, ...partial };
|
|
27
|
+
fs.writeFileSync(USER_CONFIG_PATH, JSON.stringify(merged, null, 2));
|
|
28
|
+
return merged;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const persisted = loadPersistedConfig();
|
|
35
|
+
|
|
36
|
+
const CONFIG = {
|
|
37
|
+
apiBase: process.env.SHAN_API_BASE || persisted.apiBase || 'https://shans-api-07-p00o.onrender.com' || 'https://sh-ans-api-07.vercel.app/',
|
|
38
|
+
downloadDir: process.env.SHAN_DOWNLOAD_DIR || persisted.downloadDir || path.join(process.cwd(), 'downloads'),
|
|
39
|
+
requestTimeoutMs: parseInt(process.env.SHAN_TIMEOUT_MS || persisted.requestTimeoutMs || '60000', 10),
|
|
40
|
+
batchConcurrency: parseInt(process.env.SHAN_CONCURRENCY || persisted.batchConcurrency || '3', 10),
|
|
41
|
+
maxRetries: parseInt(process.env.SHAN_RETRIES || persisted.maxRetries || '2', 10),
|
|
42
|
+
retryBaseDelayMs: parseInt(process.env.SHAN_RETRY_DELAY_MS || persisted.retryBaseDelayMs || '400', 10),
|
|
43
|
+
defaultAuthor: persisted.defaultAuthor || null,
|
|
44
|
+
defaultUid: persisted.defaultUid || null,
|
|
45
|
+
defaultFont: persisted.defaultFont || null
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// --------------------------------------------------------------
|
|
49
|
+
// LIGHTWEIGHT FILE LOGGER
|
|
50
|
+
// --------------------------------------------------------------
|
|
51
|
+
function logEvent(level, message) {
|
|
52
|
+
try {
|
|
53
|
+
const dir = downloadManagerLogDir();
|
|
54
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
55
|
+
const line = `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}\n`;
|
|
56
|
+
fs.appendFileSync(path.join(dir, 'activity.log'), line);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
// Logging must never crash the app
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function downloadManagerLogDir() {
|
|
62
|
+
return path.join(CONFIG.downloadDir, 'logs');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --------------------------------------------------------------
|
|
66
|
+
// RETRY HELPER - exponential backoff for flaky network calls
|
|
67
|
+
// --------------------------------------------------------------
|
|
68
|
+
async function withRetry(fn, { retries = CONFIG.maxRetries, baseDelayMs = CONFIG.retryBaseDelayMs, label = 'request' } = {}) {
|
|
69
|
+
let attempt = 0;
|
|
70
|
+
let lastError;
|
|
71
|
+
while (attempt <= retries) {
|
|
72
|
+
try {
|
|
73
|
+
return await fn();
|
|
74
|
+
} catch (err) {
|
|
75
|
+
lastError = err;
|
|
76
|
+
const status = err?.response?.status;
|
|
77
|
+
// Don't retry on client errors (4xx) - those won't fix themselves
|
|
78
|
+
if (status && status >= 400 && status < 500) throw err;
|
|
79
|
+
attempt++;
|
|
80
|
+
if (attempt > retries) break;
|
|
81
|
+
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
82
|
+
logEvent('warn', `${label} failed (attempt ${attempt}/${retries}): ${err.message}. Retrying in ${delay}ms`);
|
|
83
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw lastError;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --------------------------------------------------------------
|
|
90
|
+
// CONCURRENCY-LIMITED ASYNC POOL - used for parallel batch downloads
|
|
91
|
+
// --------------------------------------------------------------
|
|
92
|
+
async function asyncPool(limit, items, iteratorFn) {
|
|
93
|
+
const results = new Array(items.length);
|
|
94
|
+
let cursor = 0;
|
|
95
|
+
|
|
96
|
+
async function worker() {
|
|
97
|
+
while (cursor < items.length) {
|
|
98
|
+
const current = cursor++;
|
|
99
|
+
results[current] = await iteratorFn(items[current], current);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
|
|
104
|
+
await Promise.all(workers);
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
4
107
|
|
|
108
|
+
// --------------------------------------------------------------
|
|
109
|
+
// AUTO-AUTHOR DETECTION (now actually returns the detected user)
|
|
110
|
+
// --------------------------------------------------------------
|
|
5
111
|
function getDefaultAuthor() {
|
|
6
112
|
try {
|
|
7
113
|
const username = os.userInfo().username;
|
|
8
114
|
if (username && username !== 'root' && username !== 'admin') {
|
|
9
|
-
return
|
|
115
|
+
return '♡︎ 𝗦𝗵𝗔𝗻 ♡︎';
|
|
10
116
|
}
|
|
11
|
-
} catch (e) {
|
|
12
|
-
}
|
|
117
|
+
} catch (e) {}
|
|
13
118
|
|
|
14
119
|
const envUser = process.env.USER || process.env.USERNAME || process.env.LOGNAME;
|
|
15
120
|
if (envUser && envUser !== 'root' && envUser !== 'admin') {
|
|
16
|
-
return
|
|
121
|
+
return '♡︎ 𝗦𝗵𝗔𝗻 ♡︎';
|
|
17
122
|
}
|
|
18
123
|
|
|
19
|
-
return '
|
|
124
|
+
return 'Guest';
|
|
20
125
|
}
|
|
21
126
|
|
|
127
|
+
// Detect platform
|
|
22
128
|
function getPlatform() {
|
|
23
129
|
const platform = os.platform();
|
|
24
130
|
const platformMap = {
|
|
@@ -62,12 +168,634 @@ function getShell() {
|
|
|
62
168
|
return 'Unknown Shell';
|
|
63
169
|
}
|
|
64
170
|
|
|
171
|
+
// --------------------------------------------------------------
|
|
172
|
+
// URL VALIDATION
|
|
173
|
+
// --------------------------------------------------------------
|
|
174
|
+
function isValidHttpUrl(value) {
|
|
175
|
+
if (!value || typeof value !== 'string') return false;
|
|
176
|
+
try {
|
|
177
|
+
const u = new URL(value.trim());
|
|
178
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
179
|
+
} catch (e) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --------------------------------------------------------------
|
|
185
|
+
// RESPONSE FORMATTER - Extract data from API response
|
|
186
|
+
// --------------------------------------------------------------
|
|
187
|
+
function extractResponseData(data) {
|
|
188
|
+
if (!data) return null;
|
|
189
|
+
if (typeof data === 'string') return data;
|
|
190
|
+
if (data.ShAn) return data.ShAn;
|
|
191
|
+
if (data.data) return data.data;
|
|
192
|
+
if (data.response) return data.response;
|
|
193
|
+
if (data.result) return data.result;
|
|
194
|
+
if (data.message) return data.message;
|
|
195
|
+
return data;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function formatResponse(data, type) {
|
|
199
|
+
const responseData = extractResponseData(data);
|
|
200
|
+
if (!responseData) return 'No response received';
|
|
201
|
+
if (typeof responseData === 'string') return responseData;
|
|
202
|
+
|
|
203
|
+
switch(type) {
|
|
204
|
+
case 'baby':
|
|
205
|
+
case 'honey':
|
|
206
|
+
case 'chat':
|
|
207
|
+
if (responseData.response) return responseData.response;
|
|
208
|
+
if (responseData.message) return responseData.message;
|
|
209
|
+
if (responseData.reply) return responseData.reply;
|
|
210
|
+
if (responseData.text) return responseData.text;
|
|
211
|
+
if (responseData.ans) return responseData.ans;
|
|
212
|
+
if (responseData.msg) return responseData.msg;
|
|
213
|
+
if (responseData.content) return responseData.content;
|
|
214
|
+
if (Array.isArray(responseData) && responseData.length > 0) {
|
|
215
|
+
return responseData.map(item => {
|
|
216
|
+
if (typeof item === 'string') return item;
|
|
217
|
+
return item.response || item.message || item.text || JSON.stringify(item);
|
|
218
|
+
}).join('\n');
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case 'teach':
|
|
223
|
+
if (responseData.message) return `✅ ${responseData.message}`;
|
|
224
|
+
if (responseData.status) return `✅ ${responseData.status}`;
|
|
225
|
+
if (responseData.success) return `✅ Successfully taught!`;
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
case 'search':
|
|
229
|
+
if (Array.isArray(responseData)) {
|
|
230
|
+
let result = `\x1b[36m📋 Found ${responseData.length} results:\x1b[0m\n`;
|
|
231
|
+
responseData.slice(0, 5).forEach((item, i) => {
|
|
232
|
+
const title = item.title || item.name || item.videoTitle || 'Untitled';
|
|
233
|
+
result += `\n ${i+1}. \x1b[32m${title}\x1b[0m`;
|
|
234
|
+
if (item.url) result += `\n 🔗 ${item.url}`;
|
|
235
|
+
if (item.channel || item.author) result += `\n 📺 ${item.channel || item.author}`;
|
|
236
|
+
if (item.duration) result += `\n ⏱️ ${item.duration}`;
|
|
237
|
+
});
|
|
238
|
+
if (responseData.length > 5) {
|
|
239
|
+
result += `\n\n \x1b[33m... and ${responseData.length - 5} more results\x1b[0m`;
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
|
|
245
|
+
case 'download':
|
|
246
|
+
const url = responseData.url || responseData.downloadUrl || responseData.link || responseData.videoUrl || responseData.ShAn;
|
|
247
|
+
if (url) {
|
|
248
|
+
let result = `\x1b[32m✅ Download URL:\x1b[0m\n🔗 ${url}`;
|
|
249
|
+
if (responseData.title) result += `\n\n📝 Title: ${responseData.title}`;
|
|
250
|
+
if (responseData.duration) result += `\n⏱️ Duration: ${responseData.duration}`;
|
|
251
|
+
if (responseData.thumbnail) result += `\n🖼️ Thumbnail: ${responseData.thumbnail}`;
|
|
252
|
+
if (responseData.quality) result += `\n📊 Quality: ${responseData.quality}`;
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case 'album':
|
|
258
|
+
if (Array.isArray(responseData)) {
|
|
259
|
+
let result = `\x1b[36m📂 Album contains ${responseData.length} items:\x1b[0m\n`;
|
|
260
|
+
responseData.slice(0, 10).forEach((item, i) => {
|
|
261
|
+
const title = item.title || item.name || item.videoTitle || 'Untitled';
|
|
262
|
+
result += `\n ${i+1}. \x1b[32m${title}\x1b[0m`;
|
|
263
|
+
if (item.url) result += `\n 🔗 ${item.url}`;
|
|
264
|
+
if (item.category) result += `\n 📁 ${item.category}`;
|
|
265
|
+
});
|
|
266
|
+
if (responseData.length > 10) {
|
|
267
|
+
result += `\n\n \x1b[33m... and ${responseData.length - 10} more items\x1b[0m`;
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case 'list':
|
|
274
|
+
if (Array.isArray(responseData)) {
|
|
275
|
+
let result = `\x1b[36m📋 Total: ${responseData.length} items\x1b[0m\n`;
|
|
276
|
+
responseData.slice(0, 10).forEach((item, i) => {
|
|
277
|
+
if (typeof item === 'string') {
|
|
278
|
+
result += `\n ${i+1}. ${item}`;
|
|
279
|
+
} else {
|
|
280
|
+
const name = item.name || item.title || item.id || 'Item';
|
|
281
|
+
result += `\n ${i+1}. ${name}`;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
if (responseData.length > 10) {
|
|
285
|
+
result += `\n\n \x1b[33m... and ${responseData.length - 10} more\x1b[0m`;
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (responseData.message) return responseData.message;
|
|
293
|
+
if (responseData.status && responseData.message) {
|
|
294
|
+
return `[${responseData.status}] ${responseData.message}`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const keys = Object.keys(responseData);
|
|
298
|
+
if (keys.length === 1 && typeof responseData[keys[0]] === 'string') {
|
|
299
|
+
return responseData[keys[0]];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return JSON.stringify(responseData, null, 2);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// --------------------------------------------------------------
|
|
306
|
+
// URL PLATFORM DETECTOR
|
|
307
|
+
// --------------------------------------------------------------
|
|
308
|
+
function detectPlatform(url) {
|
|
309
|
+
if (!url || typeof url !== 'string') return null;
|
|
310
|
+
|
|
311
|
+
const lowerUrl = url.toLowerCase();
|
|
312
|
+
|
|
313
|
+
if (lowerUrl.includes('youtube.com') || lowerUrl.includes('youtu.be')) {
|
|
314
|
+
return { platform: 'YouTube', api: 'ShAnYtdl' };
|
|
315
|
+
} else if (lowerUrl.includes('tiktok.com')) {
|
|
316
|
+
return { platform: 'TikTok', api: 'ShAnTikdl' };
|
|
317
|
+
} else if (lowerUrl.includes('instagram.com')) {
|
|
318
|
+
return { platform: 'Instagram', api: 'ShAnInstadl' };
|
|
319
|
+
} else if (lowerUrl.includes('facebook.com') || lowerUrl.includes('fb.com') || lowerUrl.includes('fb.watch')) {
|
|
320
|
+
return { platform: 'Facebook', api: 'ShAnFbdl' };
|
|
321
|
+
} else if (lowerUrl.includes('twitter.com') || lowerUrl.includes('x.com')) {
|
|
322
|
+
return { platform: 'Twitter/X', api: 'ShAnTwitdl' };
|
|
323
|
+
} else if (lowerUrl.includes('threads.com') || lowerUrl.includes('threads.net')) {
|
|
324
|
+
return { platform: 'Threads', api: 'ShAnThreadl' };
|
|
325
|
+
} else if (lowerUrl.includes('pin.it') || lowerUrl.includes('pinterest.com')) {
|
|
326
|
+
return { platform: 'Pinterest', api: 'ShAnPindl' };
|
|
327
|
+
} else if (lowerUrl.includes('capcut.com')) {
|
|
328
|
+
return { platform: 'CapCut', api: 'ShAnCapcutdl' };
|
|
329
|
+
} else if (lowerUrl.includes('likee.com') || lowerUrl.includes('likee.video') || lowerUrl.includes('l.likee.video')) {
|
|
330
|
+
return { platform: 'Likee', api: 'ShAnLikeedl' };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// --------------------------------------------------------------
|
|
337
|
+
// DOWNLOAD MANAGER
|
|
338
|
+
// --------------------------------------------------------------
|
|
339
|
+
class DownloadManager {
|
|
340
|
+
constructor() {
|
|
341
|
+
this.downloadDir = CONFIG.downloadDir;
|
|
342
|
+
this.history = [];
|
|
343
|
+
this.activeControllers = new Set(); // AbortControllers for in-flight downloads
|
|
344
|
+
this.createDownloadDir();
|
|
345
|
+
this.loadHistory();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Used by the SIGINT handler to cancel in-flight downloads cleanly
|
|
349
|
+
abortAll() {
|
|
350
|
+
for (const controller of this.activeControllers) {
|
|
351
|
+
try { controller.abort(); } catch (e) {}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async computeChecksum(filePath) {
|
|
356
|
+
return new Promise((resolve, reject) => {
|
|
357
|
+
const hash = crypto.createHash('sha256');
|
|
358
|
+
const stream = fs.createReadStream(filePath);
|
|
359
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
360
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
361
|
+
stream.on('error', reject);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
createDownloadDir() {
|
|
366
|
+
if (!fs.existsSync(this.downloadDir)) {
|
|
367
|
+
fs.mkdirSync(this.downloadDir, { recursive: true });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
loadHistory() {
|
|
372
|
+
const historyFile = path.join(this.downloadDir, 'history.json');
|
|
373
|
+
if (fs.existsSync(historyFile)) {
|
|
374
|
+
try {
|
|
375
|
+
this.history = JSON.parse(fs.readFileSync(historyFile, 'utf8'));
|
|
376
|
+
} catch (e) {
|
|
377
|
+
this.history = [];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
saveHistory() {
|
|
383
|
+
const historyFile = path.join(this.downloadDir, 'history.json');
|
|
384
|
+
try {
|
|
385
|
+
fs.writeFileSync(historyFile, JSON.stringify(this.history, null, 2));
|
|
386
|
+
} catch (e) {}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Strips query string / fragment before inspecting the extension,
|
|
390
|
+
// and falls back sensibly when nothing usable is found.
|
|
391
|
+
getFileExtension(url, fallback = '.mp4') {
|
|
392
|
+
if (!url || typeof url !== 'string') return fallback;
|
|
393
|
+
try {
|
|
394
|
+
const parsedUrl = new URL(url);
|
|
395
|
+
const ext = path.extname(parsedUrl.pathname);
|
|
396
|
+
if (ext && ext.length <= 5) return ext;
|
|
397
|
+
return fallback;
|
|
398
|
+
} catch (e) {
|
|
399
|
+
return fallback;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getFileName(url, title, fallbackExt = '.mp4') {
|
|
404
|
+
const cleanTitle = title
|
|
405
|
+
.replace(/[^a-zA-Z0-9 ]/g, '')
|
|
406
|
+
.replace(/\s+/g, '_')
|
|
407
|
+
.substring(0, 50);
|
|
408
|
+
|
|
409
|
+
const ext = this.getFileExtension(url, fallbackExt);
|
|
410
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
411
|
+
return `${cleanTitle || 'video'}_${timestamp}${ext}`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async downloadFile(url, filePath, progressCallback) {
|
|
415
|
+
const controller = new AbortController();
|
|
416
|
+
this.activeControllers.add(controller);
|
|
417
|
+
try {
|
|
418
|
+
const response = await axios({
|
|
419
|
+
url: url,
|
|
420
|
+
method: 'GET',
|
|
421
|
+
responseType: 'stream',
|
|
422
|
+
headers: {
|
|
423
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
424
|
+
'Accept': '*/*',
|
|
425
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
426
|
+
'Connection': 'keep-alive'
|
|
427
|
+
},
|
|
428
|
+
timeout: CONFIG.requestTimeoutMs,
|
|
429
|
+
maxRedirects: 10,
|
|
430
|
+
signal: controller.signal
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const totalSize = parseInt(response.headers['content-length'], 10);
|
|
434
|
+
let downloadedSize = 0;
|
|
435
|
+
let startTime = Date.now();
|
|
436
|
+
|
|
437
|
+
return new Promise((resolve, reject) => {
|
|
438
|
+
const writer = fs.createWriteStream(filePath);
|
|
439
|
+
|
|
440
|
+
response.data.on('data', (chunk) => {
|
|
441
|
+
downloadedSize += chunk.length;
|
|
442
|
+
if (progressCallback && totalSize) {
|
|
443
|
+
const progress = (downloadedSize / totalSize) * 100;
|
|
444
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
445
|
+
const speed = elapsed > 0 ? downloadedSize / elapsed : 0;
|
|
446
|
+
progressCallback(progress, totalSize, downloadedSize, speed);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
response.data.pipe(writer);
|
|
451
|
+
|
|
452
|
+
writer.on('finish', () => {
|
|
453
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
454
|
+
this.activeControllers.delete(controller);
|
|
455
|
+
resolve({
|
|
456
|
+
path: filePath,
|
|
457
|
+
size: downloadedSize,
|
|
458
|
+
totalSize: totalSize,
|
|
459
|
+
time: elapsed,
|
|
460
|
+
speed: elapsed > 0 ? downloadedSize / elapsed : 0
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
writer.on('error', (err) => {
|
|
465
|
+
try { fs.unlinkSync(filePath); } catch (e) {}
|
|
466
|
+
this.activeControllers.delete(controller);
|
|
467
|
+
reject(err);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
response.data.on('error', (err) => {
|
|
471
|
+
try { fs.unlinkSync(filePath); } catch (e) {}
|
|
472
|
+
this.activeControllers.delete(controller);
|
|
473
|
+
reject(err);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
} catch (error) {
|
|
477
|
+
this.activeControllers.delete(controller);
|
|
478
|
+
if (axios.isCancel?.(error) || error.name === 'CanceledError' || error.message === 'canceled') {
|
|
479
|
+
throw new Error('Download canceled');
|
|
480
|
+
}
|
|
481
|
+
throw new Error(`Download failed: ${error.message}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async downloadFromApi(apiFunction, url, author, title, platform, { showProgress = true } = {}) {
|
|
486
|
+
try {
|
|
487
|
+
if (!isValidHttpUrl(url)) {
|
|
488
|
+
console.log('\x1b[31m❌ Invalid URL supplied.\x1b[0m');
|
|
489
|
+
return { success: false, error: 'Invalid URL' };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
console.log('\x1b[33m⏳ Fetching video info...\x1b[0m');
|
|
493
|
+
const result = await withRetry(() => apiFunction(url, author), { label: `fetch info (${platform || 'unknown'})` });
|
|
494
|
+
|
|
495
|
+
let downloadUrl = null;
|
|
496
|
+
|
|
497
|
+
if (result && result.ShAn) {
|
|
498
|
+
downloadUrl = result.ShAn;
|
|
499
|
+
} else if (result && result.data && result.data.ShAn) {
|
|
500
|
+
downloadUrl = result.data.ShAn;
|
|
501
|
+
} else if (typeof result === 'string') {
|
|
502
|
+
downloadUrl = result;
|
|
503
|
+
} else if (result) {
|
|
504
|
+
downloadUrl = result.url || result.downloadUrl || result.link || result.videoUrl;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!downloadUrl) {
|
|
508
|
+
const data = extractResponseData(result);
|
|
509
|
+
if (data) {
|
|
510
|
+
downloadUrl = data.url || data.downloadUrl || data.link || data.videoUrl || data.ShAn;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (!downloadUrl) {
|
|
515
|
+
console.log('\x1b[33m⚠️ No download URL found. Response:\x1b[0m');
|
|
516
|
+
console.log(JSON.stringify(result, null, 2));
|
|
517
|
+
return { success: false, error: 'No download URL found in API response' };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
downloadUrl = String(downloadUrl).trim();
|
|
521
|
+
|
|
522
|
+
if (!downloadUrl || downloadUrl === 'undefined' || downloadUrl === 'null' || !isValidHttpUrl(downloadUrl)) {
|
|
523
|
+
console.log('\x1b[33m⚠️ Invalid download URL.\x1b[0m');
|
|
524
|
+
return { success: false, error: 'Invalid download URL' };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const videoTitle = title || result?.title || 'video';
|
|
528
|
+
const filename = this.getFileName(downloadUrl, videoTitle);
|
|
529
|
+
const filePath = path.join(this.downloadDir, filename);
|
|
530
|
+
|
|
531
|
+
console.log('\x1b[33m⏳ Downloading video...\x1b[0m');
|
|
532
|
+
console.log(`📁 Saving to: ${filePath}`);
|
|
533
|
+
|
|
534
|
+
const downloadResult = await this.downloadFile(downloadUrl, filePath, !showProgress ? null : (progress, total, downloaded, speed) => {
|
|
535
|
+
const safeProgress = Number.isFinite(progress) ? progress : 0;
|
|
536
|
+
const progressBar = this.createProgressBar(safeProgress);
|
|
537
|
+
const sizeStr = total ? this.formatBytes(downloaded) : '?';
|
|
538
|
+
const totalStr = total ? this.formatBytes(total) : '?';
|
|
539
|
+
const speedStr = speed ? this.formatBytes(speed) + '/s' : '?';
|
|
540
|
+
process.stdout.write(`\r${progressBar} ${safeProgress.toFixed(1)}% (${sizeStr}/${totalStr}) @ ${speedStr}`);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
if (showProgress) console.log('\n');
|
|
544
|
+
console.log('\x1b[32m✅ Download Complete!\x1b[0m');
|
|
545
|
+
console.log(`📁 File saved: ${downloadResult.path}`);
|
|
546
|
+
console.log(`📊 Size: ${this.formatBytes(downloadResult.size)}`);
|
|
547
|
+
console.log(`⏱️ Time: ${downloadResult.time.toFixed(1)}s`);
|
|
548
|
+
console.log(`⚡ Speed: ${this.formatBytes(downloadResult.speed)}/s`);
|
|
549
|
+
|
|
550
|
+
let checksum = null;
|
|
551
|
+
try {
|
|
552
|
+
checksum = await this.computeChecksum(downloadResult.path);
|
|
553
|
+
console.log(`🔒 SHA-256: ${checksum}`);
|
|
554
|
+
} catch (e) {
|
|
555
|
+
// Checksum is a nice-to-have, never fail the download over it
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
this.history.push({
|
|
559
|
+
url: url,
|
|
560
|
+
platform: platform || 'Unknown',
|
|
561
|
+
title: videoTitle,
|
|
562
|
+
filename: filename,
|
|
563
|
+
size: downloadResult.size,
|
|
564
|
+
time: downloadResult.time,
|
|
565
|
+
checksum: checksum,
|
|
566
|
+
downloadedAt: new Date().toISOString()
|
|
567
|
+
});
|
|
568
|
+
this.saveHistory();
|
|
569
|
+
logEvent('info', `Downloaded ${filename} (${platform || 'Unknown'}, ${this.formatBytes(downloadResult.size)})`);
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
success: true,
|
|
573
|
+
filePath: downloadResult.path,
|
|
574
|
+
filename: filename,
|
|
575
|
+
size: downloadResult.size,
|
|
576
|
+
checksum: checksum,
|
|
577
|
+
metadata: result
|
|
578
|
+
};
|
|
579
|
+
} catch (error) {
|
|
580
|
+
console.error('\x1b[31m❌ Download failed:\x1b[0m', error.message);
|
|
581
|
+
logEvent('error', `Download failed for ${url}: ${error.message}`);
|
|
582
|
+
return { success: false, error: error.message };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
createProgressBar(percentage) {
|
|
587
|
+
const clamped = Math.max(0, Math.min(100, Number.isFinite(percentage) ? percentage : 0));
|
|
588
|
+
const barLength = 30;
|
|
589
|
+
const filled = Math.floor((clamped / 100) * barLength);
|
|
590
|
+
const empty = barLength - filled;
|
|
591
|
+
return `\x1b[36m[${'█'.repeat(filled)}${'░'.repeat(empty)}]\x1b[0m`;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
formatBytes(bytes) {
|
|
595
|
+
if (!bytes || bytes <= 0) return '0 Bytes';
|
|
596
|
+
const k = 1024;
|
|
597
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
598
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
599
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
listDownloads() {
|
|
603
|
+
if (!fs.existsSync(this.downloadDir)) {
|
|
604
|
+
return [];
|
|
605
|
+
}
|
|
606
|
+
return fs.readdirSync(this.downloadDir)
|
|
607
|
+
.filter(file => file !== 'history.json')
|
|
608
|
+
.map(file => ({
|
|
609
|
+
name: file,
|
|
610
|
+
path: path.join(this.downloadDir, file),
|
|
611
|
+
size: fs.statSync(path.join(this.downloadDir, file)).size,
|
|
612
|
+
created: fs.statSync(path.join(this.downloadDir, file)).birthtime
|
|
613
|
+
}));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
getDownloadStats() {
|
|
617
|
+
const files = this.listDownloads();
|
|
618
|
+
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
619
|
+
return {
|
|
620
|
+
count: files.length,
|
|
621
|
+
totalSize: this.formatBytes(totalSize),
|
|
622
|
+
history: this.history.slice(-10),
|
|
623
|
+
files: files
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
getHistoryStats() {
|
|
628
|
+
const total = this.history.length;
|
|
629
|
+
const totalSize = this.history.reduce((sum, h) => sum + (h.size || 0), 0);
|
|
630
|
+
const platforms = {};
|
|
631
|
+
this.history.forEach(h => {
|
|
632
|
+
platforms[h.platform] = (platforms[h.platform] || 0) + 1;
|
|
633
|
+
});
|
|
634
|
+
return { total, totalSize: this.formatBytes(totalSize), platforms };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
clearDownloads() {
|
|
638
|
+
const files = this.listDownloads();
|
|
639
|
+
for (const file of files) {
|
|
640
|
+
try { fs.unlinkSync(file.path); } catch (e) {}
|
|
641
|
+
}
|
|
642
|
+
this.history = [];
|
|
643
|
+
this.saveHistory();
|
|
644
|
+
return files.length;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const downloadManager = new DownloadManager();
|
|
649
|
+
|
|
650
|
+
// --------------------------------------------------------------
|
|
651
|
+
// AI TEACHING SESSION - Continuous Learning Mode
|
|
652
|
+
// --------------------------------------------------------------
|
|
653
|
+
class AITeachingSession {
|
|
654
|
+
constructor(bot, api, author, uid, font) {
|
|
655
|
+
this.bot = bot;
|
|
656
|
+
this.api = api;
|
|
657
|
+
this.author = author;
|
|
658
|
+
this.uid = uid;
|
|
659
|
+
this.font = font;
|
|
660
|
+
this.questions = [];
|
|
661
|
+
this.answers = [];
|
|
662
|
+
this.sessionCount = 0;
|
|
663
|
+
this.isActive = false;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async start() {
|
|
667
|
+
this.isActive = true;
|
|
668
|
+
this.sessionCount = 0;
|
|
669
|
+
|
|
670
|
+
console.clear();
|
|
671
|
+
console.log(`
|
|
672
|
+
\x1b[36m╔════════════════════════════════════════════════════════════╗\x1b[0m
|
|
673
|
+
\x1b[36m║ 🤖 AI TEACHING SESSION - ${this.bot.toUpperCase()} BOT ║\x1b[0m
|
|
674
|
+
\x1b[36m╚════════════════════════════════════════════════════════════╝\x1b[0m
|
|
675
|
+
\x1b[33m 🎯 Teach the AI by asking questions and providing answers!\x1b[0m
|
|
676
|
+
\x1b[33m 📝 Type your question, then provide the answer.\x1b[0m
|
|
677
|
+
\x1b[33m 🔄 Continue until you press \x1b[31mCtrl+C\x1b[0m\x1b[33m to exit.\x1b[0m
|
|
678
|
+
\x1b[33m 📊 Each session saves a pair (Question → Answer).\x1b[0m
|
|
679
|
+
\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
680
|
+
`);
|
|
681
|
+
|
|
682
|
+
console.log(`\x1b[90m Author: ${this.author} | UID: ${this.uid} | Font: ${this.font}\x1b[0m\n`);
|
|
683
|
+
|
|
684
|
+
await this.startTeachingCycle();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async startTeachingCycle() {
|
|
688
|
+
while (this.isActive) {
|
|
689
|
+
try {
|
|
690
|
+
this.sessionCount++;
|
|
691
|
+
console.log(`\x1b[36m┌─[ Session #${this.sessionCount} ]──────────────────────────────┐\x1b[0m`);
|
|
692
|
+
|
|
693
|
+
const ask = await this.promptQuestion(`\x1b[32m❓ Enter your question:\x1b[0m`);
|
|
694
|
+
if (!ask || ask.toLowerCase() === 'exit') {
|
|
695
|
+
await this.endSession();
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const ans = await this.promptQuestion(`\x1b[33m💡 Enter the answer:\x1b[0m`);
|
|
700
|
+
if (!ans || ans.toLowerCase() === 'exit') {
|
|
701
|
+
await this.endSession();
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
this.questions.push(ask);
|
|
706
|
+
this.answers.push(ans);
|
|
707
|
+
|
|
708
|
+
console.log(`\x1b[90m⏳ Teaching ${this.bot} bot...\x1b[0m`);
|
|
709
|
+
|
|
710
|
+
let result;
|
|
711
|
+
if (this.bot === 'baby') {
|
|
712
|
+
result = await this.api.ShAnBteach(ask, ans, this.uid, this.font, this.author);
|
|
713
|
+
} else {
|
|
714
|
+
result = await this.api.ShAnHteach(ask, ans, this.uid, this.font, this.author);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const formatted = formatResponse(result, 'teach');
|
|
718
|
+
console.log(`\x1b[32m✅ ${formatted}\x1b[0m`);
|
|
719
|
+
console.log(`\x1b[90m Q: ${ask.substring(0, 50)}${ask.length > 50 ? '...' : ''}\x1b[0m`);
|
|
720
|
+
console.log(`\x1b[90m A: ${ans.substring(0, 50)}${ans.length > 50 ? '...' : ''}\x1b[0m`);
|
|
721
|
+
console.log(`\x1b[36m└────────────────────────────────────────────────────────────┘\x1b[0m\n`);
|
|
722
|
+
|
|
723
|
+
const shouldContinue = await this.promptQuestion(`\x1b[36mContinue teaching? (y/n, or press Ctrl+C to exit)\x1b[0m`, 'y');
|
|
724
|
+
if (shouldContinue.toLowerCase() !== 'y' && shouldContinue.toLowerCase() !== 'yes') {
|
|
725
|
+
await this.endSession();
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
} catch (error) {
|
|
730
|
+
if (error.message && (error.message.includes('canceled') || error.message.includes('exit'))) {
|
|
731
|
+
await this.endSession();
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
console.error(`\x1b[31m❌ Error during teaching: ${error.message}\x1b[0m`);
|
|
735
|
+
const retry = await this.promptQuestion(`\x1b[33mContinue or exit? (c/e)\x1b[0m`, 'c');
|
|
736
|
+
if (retry.toLowerCase() === 'e') {
|
|
737
|
+
await this.endSession();
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
promptQuestion(question, defaultValue = '') {
|
|
745
|
+
return new Promise((resolve) => {
|
|
746
|
+
const rl = readline.createInterface({
|
|
747
|
+
input: process.stdin,
|
|
748
|
+
output: process.stdout
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
const prompt = defaultValue ? `${question} \x1b[90m[${defaultValue}]\x1b[0m: ` : `${question}: `;
|
|
752
|
+
rl.question(prompt, (answer) => {
|
|
753
|
+
rl.close();
|
|
754
|
+
resolve(answer.trim() || defaultValue);
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
async endSession() {
|
|
760
|
+
this.isActive = false;
|
|
761
|
+
console.log(`
|
|
762
|
+
\x1b[36m╔════════════════════════════════════════════════════════════╗\x1b[0m
|
|
763
|
+
\x1b[36m║ 📊 TEACHING SESSION SUMMARY ║\x1b[0m
|
|
764
|
+
\x1b[36m╚════════════════════════════════════════════════════════════╝\x1b[0m
|
|
765
|
+
\x1b[32m ✅ Total Lessons: ${this.sessionCount}\x1b[0m
|
|
766
|
+
\x1b[32m 📝 Questions: ${this.questions.length}\x1b[0m
|
|
767
|
+
\x1b[32m 💡 Answers: ${this.answers.length}\x1b[0m
|
|
768
|
+
\x1b[32m 🤖 Bot: ${this.bot.toUpperCase()}\x1b[0m
|
|
769
|
+
`);
|
|
770
|
+
|
|
771
|
+
if (this.questions.length > 0) {
|
|
772
|
+
console.log(`\x1b[36m┌─ Last 3 Lessons ───────────────────────────────┐\x1b[0m`);
|
|
773
|
+
const lastThree = this.questions.slice(-3);
|
|
774
|
+
const lastThreeAns = this.answers.slice(-3);
|
|
775
|
+
lastThree.forEach((q, i) => {
|
|
776
|
+
console.log(`\x1b[90m ${i + 1}. Q: ${q.substring(0, 40)}${q.length > 40 ? '...' : ''}\x1b[0m`);
|
|
777
|
+
console.log(`\x1b[90m A: ${lastThreeAns[i].substring(0, 40)}${lastThreeAns[i].length > 40 ? '...' : ''}\x1b[0m`);
|
|
778
|
+
});
|
|
779
|
+
console.log(`\x1b[36m└──────────────────────────────────────────────────┘\x1b[0m`);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
console.log(`\n\x1b[33m🎯 AI has been taught successfully! You can now chat with it.\x1b[0m`);
|
|
783
|
+
console.log(`\x1b[36m👋 Press Enter to return to main menu...\x1b[0m`);
|
|
784
|
+
await this.promptQuestion('');
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// --------------------------------------------------------------
|
|
789
|
+
// BIG SHAN SERVER LOGO
|
|
790
|
+
// --------------------------------------------------------------
|
|
65
791
|
function showLogo() {
|
|
66
792
|
const platform = getPlatform();
|
|
67
793
|
const terminal = getTerminalType();
|
|
68
794
|
const shell = getShell();
|
|
69
795
|
const author = getDefaultAuthor();
|
|
70
|
-
|
|
796
|
+
const stats = downloadManager.getDownloadStats();
|
|
797
|
+
const historyStats = downloadManager.getHistoryStats();
|
|
798
|
+
|
|
71
799
|
console.clear();
|
|
72
800
|
console.log(`
|
|
73
801
|
\x1b[36m
|
|
@@ -77,8 +805,8 @@ function showLogo() {
|
|
|
77
805
|
╚════██║██╔══██║██╔══██║██║╚██╗██║
|
|
78
806
|
███████║██║ ██║██║ ██║██║ ╚████║
|
|
79
807
|
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝
|
|
80
|
-
|
|
81
|
-
███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
|
|
808
|
+
|
|
809
|
+
███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
|
|
82
810
|
██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗
|
|
83
811
|
███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝
|
|
84
812
|
╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
|
@@ -86,16 +814,21 @@ function showLogo() {
|
|
|
86
814
|
╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
|
87
815
|
\x1b[0m
|
|
88
816
|
\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
89
|
-
\x1b[36m SHAN SERVER -
|
|
817
|
+
\x1b[36m SHAN SERVER - v4.0.0 (Ultimate)\x1b[0m
|
|
90
818
|
\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
91
819
|
\x1b[90m Platform: ${platform.padEnd(20)} Terminal: ${terminal}\x1b[0m
|
|
92
|
-
\x1b[90m Shell: ${shell.padEnd(20)} API:
|
|
820
|
+
\x1b[90m Shell: ${shell.padEnd(20)} API: ${CONFIG.apiBase.replace(/^https?:\/\//, '')}\x1b[0m
|
|
93
821
|
\x1b[90m Default Author: ${author}\x1b[0m
|
|
822
|
+
\x1b[90m 📁 Downloads: ${stats.count} files | ${stats.totalSize}\x1b[0m
|
|
823
|
+
\x1b[90m 📊 History: ${historyStats.total} downloads | ${historyStats.totalSize}\x1b[0m
|
|
94
824
|
\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
95
825
|
`);
|
|
96
826
|
}
|
|
97
827
|
|
|
98
|
-
|
|
828
|
+
// --------------------------------------------------------------
|
|
829
|
+
// API Configuration
|
|
830
|
+
// --------------------------------------------------------------
|
|
831
|
+
const Sh4n = CONFIG.apiBase;
|
|
99
832
|
|
|
100
833
|
const api = {
|
|
101
834
|
ShAnAlldl: (url, author) => axios.get(`${Sh4n}ShAn-alldl?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
@@ -113,6 +846,7 @@ const api = {
|
|
|
113
846
|
ShAnCapcutdl: (url, author) => axios.get(`${Sh4n}ShAn-capcutDL?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
114
847
|
ShAnLikeedl: (url, author) => axios.get(`${Sh4n}ShAn-likeeDL?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
115
848
|
ShAnytSearch: (query, author) => axios.get(`${Sh4n}ShAn-ytsearch?query=${encodeURIComponent(query)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
849
|
+
ShAntikSearch: (query, author) => axios.get(`${Sh4n}ShAn-tiksearch?query=${encodeURIComponent(query)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
116
850
|
ShAnBaby: (text, uid, font, author) => axios.get(`${Sh4n}ShAn-bby?text=${encodeURIComponent(text)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
117
851
|
ShAnBteach: (ask, ans, uid, font, author) => axios.get(`${Sh4n}ShAn-bteach?ask=${encodeURIComponent(ask)}&ans=${encodeURIComponent(ans)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
118
852
|
ShAnBrans: (author) => axios.get(`${Sh4n}ShAn-brans?author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
@@ -120,12 +854,12 @@ const api = {
|
|
|
120
854
|
ShAnBlist: (font, author) => axios.get(`${Sh4n}ShAn-blist?font=${font}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
121
855
|
ShAnBedit: (ask, newAsk, uid, font, author, index) => {
|
|
122
856
|
let url = `${Sh4n}ShAn-bedit?ask=${encodeURIComponent(ask)}&newAsk=${encodeURIComponent(newAsk)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`;
|
|
123
|
-
if (index) url += `&index=${index}`;
|
|
857
|
+
if (index) url += `&index=${encodeURIComponent(index)}`;
|
|
124
858
|
return axios.get(url).then(res => res.data);
|
|
125
859
|
},
|
|
126
860
|
ShAnBdelete: (text, uid, font, author, index) => {
|
|
127
861
|
let url = `${Sh4n}ShAn-bdelete?text=${encodeURIComponent(text)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`;
|
|
128
|
-
if (index) url += `&index=${index}`;
|
|
862
|
+
if (index) url += `&index=${encodeURIComponent(index)}`;
|
|
129
863
|
return axios.delete(url).then(res => res.data);
|
|
130
864
|
},
|
|
131
865
|
ShAnHoney: (text, uid, font, author) => axios.get(`${Sh4n}ShAn-honey?text=${encodeURIComponent(text)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
@@ -134,12 +868,12 @@ const api = {
|
|
|
134
868
|
ShAnHlist: (font, author) => axios.get(`${Sh4n}ShAn-hlist?font=${font}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
135
869
|
ShAnHedit: (ask, newAsk, uid, font, author, index) => {
|
|
136
870
|
let url = `${Sh4n}ShAn-hedit?ask=${encodeURIComponent(ask)}&newAsk=${encodeURIComponent(newAsk)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`;
|
|
137
|
-
if (index) url += `&index=${index}`;
|
|
871
|
+
if (index) url += `&index=${encodeURIComponent(index)}`;
|
|
138
872
|
return axios.get(url).then(res => res.data);
|
|
139
873
|
},
|
|
140
874
|
ShAnHdelete: (text, uid, font, author, index) => {
|
|
141
875
|
let url = `${Sh4n}ShAn-hdelete?text=${encodeURIComponent(text)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`;
|
|
142
|
-
if (index) url += `&index=${index}`;
|
|
876
|
+
if (index) url += `&index=${encodeURIComponent(index)}`;
|
|
143
877
|
return axios.delete(url).then(res => res.data);
|
|
144
878
|
},
|
|
145
879
|
ShAnalbumVideos: (category, senderID, author, key) => axios.get(`${Sh4n}ShAn-album-videos?category=${category}&senderID=${senderID}&author=${encodeURIComponent(author)}&key=${encodeURIComponent(key)}`).then(res => res.data),
|
|
@@ -147,7 +881,7 @@ const api = {
|
|
|
147
881
|
ShAnalbumDelete: (url, author, key) => axios.delete(`${Sh4n}ShAn-album-delete?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}&key=${encodeURIComponent(key)}`).then(res => res.data),
|
|
148
882
|
ShAnalbumList: (author) => axios.get(`${Sh4n}ShAn-album-list?author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
149
883
|
ShAnImgur: (videoUrl, author) => axios.post(`${Sh4n}ShAn-imgur?url=${encodeURIComponent(videoUrl)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
150
|
-
|
|
884
|
+
ShAnImgbb: (url, author) => axios.get(`${Sh4n}ShAn-imgbb?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
151
885
|
ShAnFont: (text, font, author) => axios.get(`${Sh4n}ShAn-font?text=${encodeURIComponent(text)}&font=${encodeURIComponent(font)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
152
886
|
ShAnfontList: (author) => axios.get(`${Sh4n}ShAn-fontList?author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
153
887
|
ShAnWish: (name, font, author) => axios.get(`${Sh4n}ShAn-wish?name=${encodeURIComponent(name)}&font=${encodeURIComponent(font)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
@@ -155,10 +889,19 @@ const api = {
|
|
|
155
889
|
ShAncaptionList: (language, author) => axios.get(`${Sh4n}ShAn-caption-list?language=${encodeURIComponent(language)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
156
890
|
ShAnCaption: (category, language, senderID, author, key) => axios.get(`${Sh4n}ShAn-caption?category=${encodeURIComponent(category)}&language=${encodeURIComponent(language)}&senderID=${encodeURIComponent(senderID)}&author=${encodeURIComponent(author)}&key=${encodeURIComponent(key)}`).then(res => res.data),
|
|
157
891
|
ShAnmemeAdd: (memeUrl, senderID, author) => axios.post(`${Sh4n}ShAn-meme-add?memeUrl=${encodeURIComponent(memeUrl)}&senderID=${encodeURIComponent(senderID)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
158
|
-
ShAnMeme: (author) => axios.get(`${Sh4n}ShAn-meme?author=${encodeURIComponent(author)}`).then(res => res.data)
|
|
159
|
-
ShAnImgbb: (url, author) => axios.get(`${Sh4n}ShAn-imgbb?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data)
|
|
892
|
+
ShAnMeme: (author) => axios.get(`${Sh4n}ShAn-meme?author=${encodeURIComponent(author)}`).then(res => res.data)
|
|
160
893
|
};
|
|
161
894
|
|
|
895
|
+
// Wrap every API function with automatic retry/backoff so transient network
|
|
896
|
+
// blips don't force the user to redo an entire menu flow.
|
|
897
|
+
for (const key of Object.keys(api)) {
|
|
898
|
+
const original = api[key];
|
|
899
|
+
api[key] = (...args) => withRetry(() => original(...args), { label: key });
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// --------------------------------------------------------------
|
|
903
|
+
// PROMPT FUNCTIONS
|
|
904
|
+
// --------------------------------------------------------------
|
|
162
905
|
function createInterface() {
|
|
163
906
|
return readline.createInterface({
|
|
164
907
|
input: process.stdin,
|
|
@@ -183,7 +926,7 @@ async function selectOption(question, options) {
|
|
|
183
926
|
options.forEach((opt, idx) => {
|
|
184
927
|
console.log(` ${idx + 1}. ${opt}`);
|
|
185
928
|
});
|
|
186
|
-
|
|
929
|
+
|
|
187
930
|
return new Promise((resolve) => {
|
|
188
931
|
rl.question('\x1b[33mEnter number (or name): \x1b[0m', (answer) => {
|
|
189
932
|
rl.close();
|
|
@@ -200,460 +943,899 @@ async function selectOption(question, options) {
|
|
|
200
943
|
});
|
|
201
944
|
}
|
|
202
945
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
\x1b[36m┌─────────────────────────────────────────────────────────┐\x1b[0m
|
|
208
|
-
\x1b[36m│ 📋 MAIN MENU │\x1b[0m
|
|
209
|
-
\x1b[36m├─────────────────────────────────────────────────────────┤\x1b[0m
|
|
210
|
-
\x1b[32m│ 1. 📥 Download Videos │\x1b[0m
|
|
211
|
-
\x1b[32m│ 2. 🔍 Search Content │\x1b[0m
|
|
212
|
-
\x1b[32m│ 3. 🤖 AI Chatbots (Baby/Honey) │\x1b[0m
|
|
213
|
-
\x1b[32m│ 4. 💾 Album Management │\x1b[0m
|
|
214
|
-
\x1b[32m│ 5. 🎨 Font & Text Utilities │\x1b[0m
|
|
215
|
-
\x1b[32m│ 6. 📝 Caption Manager │\x1b[0m
|
|
216
|
-
\x1b[32m│ 7. 🖼️ Meme Generator │\x1b[0m
|
|
217
|
-
\x1b[32m│ 8. ☁️ Cloud Upload (Imgur/ImgBB) │\x1b[0m
|
|
218
|
-
\x1b[31m│ 0. ❌ Exit │\x1b[0m
|
|
219
|
-
\x1b[36m└─────────────────────────────────────────────────────────┘\x1b[0m
|
|
220
|
-
`);
|
|
221
|
-
|
|
222
|
-
const choice = await askQuestion('\x1b[36mSelect an option\x1b[0m', '0');
|
|
223
|
-
return choice;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async function downloadMenu() {
|
|
227
|
-
console.clear();
|
|
228
|
-
showLogo();
|
|
229
|
-
|
|
230
|
-
const platform = await selectOption('📥 Select Platform:', [
|
|
231
|
-
'YouTube', 'TikTok', 'Instagram', 'Facebook', 'Twitter/X',
|
|
232
|
-
'Threads', 'Pinterest', 'CapCut', 'Likee', 'All-in-One'
|
|
233
|
-
]);
|
|
234
|
-
|
|
235
|
-
let command = '';
|
|
236
|
-
switch(platform) {
|
|
237
|
-
case 'YouTube': command = 'ytdl'; break;
|
|
238
|
-
case 'TikTok': command = 'tikdl'; break;
|
|
239
|
-
case 'Instagram': command = 'instadl'; break;
|
|
240
|
-
case 'Facebook': command = 'fbdl'; break;
|
|
241
|
-
case 'Twitter/X': command = 'twitdl'; break;
|
|
242
|
-
case 'Threads': command = 'threadl'; break;
|
|
243
|
-
case 'Pinterest': command = 'pindl'; break;
|
|
244
|
-
case 'CapCut': command = 'capcutdl'; break;
|
|
245
|
-
case 'Likee': command = 'likeedl'; break;
|
|
246
|
-
case 'All-in-One': command = 'alldl'; break;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const url = await askQuestion('\x1b[36mEnter video URL\x1b[0m');
|
|
250
|
-
if (!url) {
|
|
251
|
-
console.log('\x1b[31m❌ URL is required!\x1b[0m');
|
|
252
|
-
await askQuestion('\nPress Enter to continue...');
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const defaultAuthor = getDefaultAuthor();
|
|
257
|
-
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
258
|
-
|
|
259
|
-
console.log('\n\x1b[33m⏳ Processing your request...\x1b[0m\n');
|
|
260
|
-
|
|
946
|
+
// --------------------------------------------------------------
|
|
947
|
+
// Generic error-safe API call wrapper used by the simpler menus
|
|
948
|
+
// --------------------------------------------------------------
|
|
949
|
+
async function runSafely(actionFn) {
|
|
261
950
|
try {
|
|
262
|
-
|
|
263
|
-
switch(command) {
|
|
264
|
-
case 'ytdl': result = await api.ShAnYtdl(url, author); break;
|
|
265
|
-
case 'tikdl': result = await api.ShAnTikdl(url, author); break;
|
|
266
|
-
case 'instadl': result = await api.ShAnInstadl(url, author); break;
|
|
267
|
-
case 'fbdl': result = await api.ShAnFbdl(url, author); break;
|
|
268
|
-
case 'twitdl': result = await api.ShAnTwitdl(url, author); break;
|
|
269
|
-
case 'threadl': result = await api.ShAnThreadl(url, author); break;
|
|
270
|
-
case 'pindl': result = await api.ShAnPindl(url, author); break;
|
|
271
|
-
case 'capcutdl': result = await api.ShAnCapcutdl(url, author); break;
|
|
272
|
-
case 'likeedl': result = await api.ShAnLikeedl(url, author); break;
|
|
273
|
-
default: result = await api.ShAnAlldl(url, author);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
console.log('\n\x1b[32m✅ SUCCESS!\x1b[0m\n');
|
|
277
|
-
console.log(JSON.stringify(result, null, 2));
|
|
951
|
+
await actionFn();
|
|
278
952
|
} catch (err) {
|
|
279
953
|
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
280
954
|
}
|
|
281
|
-
|
|
282
|
-
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
283
955
|
}
|
|
284
956
|
|
|
285
|
-
|
|
957
|
+
// --------------------------------------------------------------
|
|
958
|
+
// RANDOM TEACH MODE - ShAnBrans returns question, user provides answer
|
|
959
|
+
// --------------------------------------------------------------
|
|
960
|
+
async function randomTeachMode(bot, author, uid, font) {
|
|
286
961
|
console.clear();
|
|
287
962
|
showLogo();
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
console.log(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
963
|
+
|
|
964
|
+
console.log(`
|
|
965
|
+
\x1b[36m╔════════════════════════════════════════════════════════════╗\x1b[0m
|
|
966
|
+
\x1b[36m║ 🎯 RANDOM TEACH MODE - ${bot.toUpperCase()} BOT ║\x1b[0m
|
|
967
|
+
\x1b[36m╚════════════════════════════════════════════════════════════╝\x1b[0m
|
|
968
|
+
\x1b[33m 🤖 AI gives a random question!\x1b[0m
|
|
969
|
+
\x1b[33m 💡 You provide the answer for that question!\x1b[0m
|
|
970
|
+
\x1b[33m 📝 This teaches the AI new responses!\x1b[0m
|
|
971
|
+
\x1b[33m 🔄 Type \x1b[31m"exit"\x1b[33m to stop teaching\x1b[0m
|
|
972
|
+
\x1b[33m 📊 Type \x1b[31m"skip"\x1b[33m to skip current question\x1b[0m
|
|
973
|
+
\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
974
|
+
`);
|
|
975
|
+
|
|
976
|
+
console.log(`\x1b[90m Author: ${author} | UID: ${uid} | Font: ${font}\x1b[0m\n`);
|
|
977
|
+
|
|
978
|
+
let teachingCount = 0;
|
|
979
|
+
let continueTeaching = true;
|
|
980
|
+
|
|
981
|
+
while (continueTeaching) {
|
|
982
|
+
try {
|
|
983
|
+
// Get random question from Baby
|
|
984
|
+
const randomResult = await api.ShAnBrans(author);
|
|
985
|
+
const randomQuestion = extractResponseData(randomResult);
|
|
986
|
+
|
|
987
|
+
// Handle different response formats
|
|
988
|
+
let questionText = '';
|
|
989
|
+
if (typeof randomQuestion === 'string') {
|
|
990
|
+
questionText = randomQuestion;
|
|
991
|
+
} else if (randomQuestion) {
|
|
992
|
+
questionText = randomQuestion.response || randomQuestion.message || randomQuestion.text || randomQuestion.ans || randomQuestion.msg || JSON.stringify(randomQuestion);
|
|
993
|
+
} else {
|
|
994
|
+
questionText = 'No question received';
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
console.log(`\x1b[36m┌─[ Teaching Session #${teachingCount + 1} ]────────────────────────────┐\x1b[0m`);
|
|
998
|
+
console.log(`\x1b[32m🤖 ${bot} asks (random question):\x1b[0m ${questionText}`);
|
|
999
|
+
|
|
1000
|
+
const answer = await askQuestion(`\x1b[33m💡 Enter your answer (or type "exit" to stop):\x1b[0m`);
|
|
1001
|
+
|
|
1002
|
+
// Check for exit
|
|
1003
|
+
if (!answer || answer.toLowerCase() === 'exit') {
|
|
1004
|
+
console.log(`\n\x1b[36m👋 Ending teaching session!\x1b[0m`);
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Check for skip
|
|
1009
|
+
if (answer.toLowerCase() === 'skip') {
|
|
1010
|
+
console.log(`\x1b[33m⏭️ Skipped this question!\x1b[0m`);
|
|
1011
|
+
console.log(`\x1b[36m└────────────────────────────────────────────────────────────────┘\x1b[0m\n`);
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
console.log(`\x1b[90m⏳ Teaching ${bot} bot...\x1b[0m`);
|
|
1016
|
+
|
|
1017
|
+
// Teach the bot with the question and answer
|
|
1018
|
+
let result;
|
|
1019
|
+
if (bot === 'Baby') {
|
|
1020
|
+
result = await api.ShAnBteach(questionText, answer, uid, font, author);
|
|
1021
|
+
} else {
|
|
1022
|
+
result = await api.ShAnHteach(questionText, answer, uid, font, author);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
teachingCount++;
|
|
1026
|
+
const formatted = formatResponse(result, 'teach');
|
|
1027
|
+
|
|
1028
|
+
console.log(`\x1b[32m✅ ${formatted || 'Successfully taught!'}\x1b[0m`);
|
|
1029
|
+
console.log(`\x1b[90m Q: ${questionText.substring(0, 50)}${questionText.length > 50 ? '...' : ''}\x1b[0m`);
|
|
1030
|
+
console.log(`\x1b[90m A: ${answer.substring(0, 50)}${answer.length > 50 ? '...' : ''}\x1b[0m`);
|
|
1031
|
+
console.log(`\x1b[36m└────────────────────────────────────────────────────────────────┘\x1b[0m\n`);
|
|
1032
|
+
|
|
1033
|
+
// Show progress
|
|
1034
|
+
console.log(`\x1b[90m📊 Taught: ${teachingCount} responses\x1b[0m\n`);
|
|
1035
|
+
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
console.error(`\x1b[31m❌ Error: ${error.message}\x1b[0m`);
|
|
1038
|
+
console.log(`\x1b[33m⏳ Continuing to next random question...\x1b[0m\n`);
|
|
309
1039
|
}
|
|
310
|
-
|
|
311
|
-
console.log('\x1b[32m✅ Search Results:\x1b[0m\n');
|
|
312
|
-
console.log(JSON.stringify(result, null, 2));
|
|
313
|
-
} catch (err) {
|
|
314
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
315
1040
|
}
|
|
316
|
-
|
|
1041
|
+
|
|
1042
|
+
// Summary
|
|
1043
|
+
console.log(`
|
|
1044
|
+
\x1b[36m╔════════════════════════════════════════════════════════════╗\x1b[0m
|
|
1045
|
+
\x1b[36m║ 📊 TEACHING SESSION SUMMARY ║\x1b[0m
|
|
1046
|
+
\x1b[36m╚════════════════════════════════════════════════════════════╝\x1b[0m
|
|
1047
|
+
\x1b[32m ✅ Total Lessons: ${teachingCount}\x1b[0m
|
|
1048
|
+
\x1b[32m 🤖 Bot: ${bot.toUpperCase()}\x1b[0m
|
|
1049
|
+
\x1b[32m 👤 Author: ${author}\x1b[0m
|
|
1050
|
+
`);
|
|
1051
|
+
|
|
1052
|
+
if (teachingCount > 0) {
|
|
1053
|
+
console.log(`\x1b[36m🎯 ${bot} has been taught ${teachingCount} new responses!\x1b[0m`);
|
|
1054
|
+
} else {
|
|
1055
|
+
console.log(`\x1b[33m⚠️ No new responses were taught.\x1b[0m`);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
console.log(`\n\x1b[33m🎯 You can now chat with ${bot} and see the new responses!\x1b[0m`);
|
|
317
1059
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
318
1060
|
}
|
|
319
1061
|
|
|
1062
|
+
// --------------------------------------------------------------
|
|
1063
|
+
// AI CHATBOT MENU
|
|
1064
|
+
// --------------------------------------------------------------
|
|
320
1065
|
async function aiChatbotMenu() {
|
|
321
1066
|
console.clear();
|
|
322
1067
|
showLogo();
|
|
323
|
-
|
|
1068
|
+
|
|
324
1069
|
const bot = await selectOption('🤖 Select AI Chatbot:', ['Baby', 'Honey']);
|
|
325
|
-
const action = await selectOption('Select Action:', [
|
|
326
|
-
|
|
327
|
-
|
|
1070
|
+
const action = await selectOption('Select Action:', [
|
|
1071
|
+
'💬 Chat',
|
|
1072
|
+
'📚 Teach (Continuous)',
|
|
1073
|
+
'🎯 Random Teach',
|
|
1074
|
+
'🎲 Random Response',
|
|
1075
|
+
'📋 List Data',
|
|
1076
|
+
'✏️ Edit',
|
|
1077
|
+
'🗑️ Delete'
|
|
1078
|
+
]);
|
|
1079
|
+
|
|
1080
|
+
const defaultAuthor = CONFIG.defaultAuthor || getDefaultAuthor();
|
|
328
1081
|
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
329
|
-
const uid = await askQuestion('\x1b[36mEnter User ID (your unique identifier)\x1b[0m', 'user123');
|
|
330
|
-
|
|
331
|
-
|
|
1082
|
+
const uid = await askQuestion('\x1b[36mEnter User ID (your unique identifier)\x1b[0m', CONFIG.defaultUid || 'user123');
|
|
1083
|
+
|
|
1084
|
+
// Show available fonts
|
|
1085
|
+
console.log(`\n\x1b[36m🎨 Available Font Styles (1-5):\x1b[0m`);
|
|
1086
|
+
console.log(` 1. Bold → 𝐄𝐱𝐚𝐦𝐩𝐥𝐞`);
|
|
1087
|
+
console.log(` 2. Script → 𝓔𝔁𝓪𝓶𝓹𝓵𝓮`);
|
|
1088
|
+
console.log(` 3. Sans Serif → 𝘌𝘹𝘢𝘮𝘱𝘭𝘦`);
|
|
1089
|
+
console.log(` 4. Math Style → 𝔼𝕩𝕒𝕞𝕡𝕝𝕖`);
|
|
1090
|
+
console.log(` 5. Fraktur → 𝔈𝔵𝔞𝔪𝔭𝔩𝔢`);
|
|
1091
|
+
|
|
1092
|
+
const font = await askQuestion('\x1b[36mEnter Font number (1-5, default: 3)\x1b[0m', CONFIG.defaultFont || '3');
|
|
1093
|
+
savePersistedConfig({ defaultAuthor: author, defaultUid: uid, defaultFont: font });
|
|
1094
|
+
|
|
1095
|
+
// Random Teach Mode
|
|
1096
|
+
if (action === 'Random Teach') {
|
|
1097
|
+
await randomTeachMode(bot, author, uid, font);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if (action === 'Teach (Continuous)') {
|
|
1102
|
+
const session = new AITeachingSession(bot.toLowerCase(), api, author, uid, font);
|
|
1103
|
+
const originalSigInt = process.listeners('SIGINT')[0];
|
|
1104
|
+
process.removeAllListeners('SIGINT');
|
|
1105
|
+
process.on('SIGINT', async () => {
|
|
1106
|
+
console.log('\n\x1b[33m\n⚠️ Teaching session interrupted!\x1b[0m');
|
|
1107
|
+
await session.endSession();
|
|
1108
|
+
process.exit(0);
|
|
1109
|
+
});
|
|
1110
|
+
await session.start();
|
|
1111
|
+
process.removeAllListeners('SIGINT');
|
|
1112
|
+
if (originalSigInt) process.on('SIGINT', originalSigInt);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
332
1116
|
try {
|
|
333
1117
|
let result;
|
|
334
|
-
|
|
1118
|
+
let formattedResponse;
|
|
1119
|
+
|
|
335
1120
|
if (action === 'Chat') {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
1121
|
+
let continueChatting = true;
|
|
1122
|
+
let messageCount = 0;
|
|
1123
|
+
|
|
1124
|
+
console.log(`\n\x1b[36m💬 Starting chat with ${bot}...\x1b[0m`);
|
|
1125
|
+
console.log(`\x1b[33mType "exit" to end chat\x1b[0m\n`);
|
|
1126
|
+
|
|
1127
|
+
while (continueChatting) {
|
|
1128
|
+
const text = await askQuestion(`\x1b[32m💬 You (${bot}):\x1b[0m`);
|
|
1129
|
+
|
|
1130
|
+
if (!text) continue;
|
|
1131
|
+
|
|
1132
|
+
if (text.toLowerCase() === 'exit') {
|
|
1133
|
+
console.log(`\n\x1b[36m👋 Ending chat with ${bot}. Goodbye!\x1b[0m`);
|
|
1134
|
+
break;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
messageCount++;
|
|
1138
|
+
console.log(`\x1b[90m⏳ ${bot} is thinking...\x1b[0m`);
|
|
1139
|
+
|
|
1140
|
+
if (bot === 'Baby') {
|
|
1141
|
+
result = await api.ShAnBaby(text, uid, font, author);
|
|
1142
|
+
formattedResponse = formatResponse(result, 'baby');
|
|
1143
|
+
console.log(`\n\x1b[36m👶 Baby:\x1b[0m ${formattedResponse}`);
|
|
1144
|
+
} else {
|
|
1145
|
+
result = await api.ShAnHoney(text, uid, font, author);
|
|
1146
|
+
formattedResponse = formatResponse(result, 'honey');
|
|
1147
|
+
console.log(`\n\x1b[33m🍯 Honey:\x1b[0m ${formattedResponse}`);
|
|
1148
|
+
}
|
|
1149
|
+
console.log(`\x1b[90m📝 Message ${messageCount}\x1b[0m\n`);
|
|
350
1150
|
}
|
|
1151
|
+
|
|
1152
|
+
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
1153
|
+
return;
|
|
351
1154
|
}
|
|
1155
|
+
|
|
352
1156
|
else if (action === 'Random Response') {
|
|
353
1157
|
if (bot === 'Baby') {
|
|
354
1158
|
result = await api.ShAnBrans(author);
|
|
1159
|
+
formattedResponse = formatResponse(result, 'baby');
|
|
1160
|
+
console.log(`\n\x1b[36m🎲 Random Baby Response:\x1b[0m ${formattedResponse}`);
|
|
355
1161
|
} else {
|
|
356
1162
|
console.log('\x1b[33m⚠️ Random response only available for Baby bot\x1b[0m');
|
|
357
|
-
await askQuestion('\nPress Enter to continue...');
|
|
358
|
-
return;
|
|
359
1163
|
}
|
|
360
1164
|
}
|
|
1165
|
+
|
|
361
1166
|
else if (action === 'List Data') {
|
|
362
1167
|
if (bot === 'Baby') {
|
|
363
1168
|
result = await api.ShAnBlist(font, author);
|
|
1169
|
+
formattedResponse = formatResponse(result, 'list');
|
|
1170
|
+
console.log(`\n\x1b[36m📋 Baby Bot Data:\x1b[0m\n${formattedResponse}`);
|
|
364
1171
|
} else {
|
|
365
1172
|
result = await api.ShAnHlist(font, author);
|
|
1173
|
+
formattedResponse = formatResponse(result, 'list');
|
|
1174
|
+
console.log(`\n\x1b[36m📋 Honey Bot Data:\x1b[0m\n${formattedResponse}`);
|
|
366
1175
|
}
|
|
367
1176
|
}
|
|
1177
|
+
|
|
368
1178
|
else if (action === 'Edit') {
|
|
369
1179
|
const ask = await askQuestion('\x1b[36mEnter the question to edit\x1b[0m');
|
|
370
1180
|
const newAsk = await askQuestion('\x1b[36mEnter the new question\x1b[0m');
|
|
371
1181
|
const index = await askQuestion('\x1b[36mEnter index (optional)\x1b[0m');
|
|
1182
|
+
|
|
372
1183
|
if (bot === 'Baby') {
|
|
373
1184
|
result = await api.ShAnBedit(ask, newAsk, uid, font, author, index);
|
|
374
1185
|
} else {
|
|
375
1186
|
result = await api.ShAnHedit(ask, newAsk, uid, font, author, index);
|
|
376
1187
|
}
|
|
1188
|
+
const formatted = formatResponse(result, 'teach');
|
|
1189
|
+
console.log(`\n\x1b[32m✅ ${formatted || 'Successfully edited!'}\x1b[0m`);
|
|
377
1190
|
}
|
|
1191
|
+
|
|
378
1192
|
else if (action === 'Delete') {
|
|
379
1193
|
const text = await askQuestion('\x1b[36mEnter text to delete\x1b[0m');
|
|
380
1194
|
const index = await askQuestion('\x1b[36mEnter index (optional)\x1b[0m');
|
|
1195
|
+
|
|
381
1196
|
if (bot === 'Baby') {
|
|
382
1197
|
result = await api.ShAnBdelete(text, uid, font, author, index);
|
|
383
1198
|
} else {
|
|
384
1199
|
result = await api.ShAnHdelete(text, uid, font, author, index);
|
|
385
1200
|
}
|
|
1201
|
+
const formatted = formatResponse(result, 'teach');
|
|
1202
|
+
console.log(`\n\x1b[32m✅ ${formatted || 'Successfully deleted!'}\x1b[0m`);
|
|
386
1203
|
}
|
|
387
|
-
|
|
388
|
-
console.log('\x1b[32m✅ Success!\x1b[0m\n');
|
|
389
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1204
|
+
|
|
390
1205
|
} catch (err) {
|
|
391
1206
|
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
392
1207
|
}
|
|
393
|
-
|
|
1208
|
+
|
|
394
1209
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
395
1210
|
}
|
|
396
1211
|
|
|
397
|
-
|
|
1212
|
+
// --------------------------------------------------------------
|
|
1213
|
+
// BATCH DOWNLOAD
|
|
1214
|
+
// --------------------------------------------------------------
|
|
1215
|
+
async function batchDownloadMenu() {
|
|
398
1216
|
console.clear();
|
|
399
1217
|
showLogo();
|
|
400
|
-
|
|
401
|
-
|
|
1218
|
+
|
|
1219
|
+
console.log(`\x1b[36m📋 BATCH DOWNLOAD MODE\x1b[0m`);
|
|
1220
|
+
console.log(`\x1b[33mEnter multiple URLs (one per line). Type "done" when finished.\x1b[0m\n`);
|
|
1221
|
+
|
|
402
1222
|
const defaultAuthor = getDefaultAuthor();
|
|
403
1223
|
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
1224
|
+
|
|
1225
|
+
const urls = [];
|
|
1226
|
+
console.log(`\n\x1b[36mEnter URLs (type "done" to finish):\x1b[0m`);
|
|
1227
|
+
|
|
1228
|
+
while (true) {
|
|
1229
|
+
const url = await askQuestion(`\x1b[90mURL ${urls.length + 1}:\x1b[0m`);
|
|
1230
|
+
if (url.toLowerCase() === 'done' || url.toLowerCase() === 'exit') break;
|
|
1231
|
+
if (url.trim()) {
|
|
1232
|
+
if (!isValidHttpUrl(url.trim())) {
|
|
1233
|
+
console.log('\x1b[31m❌ That does not look like a valid URL, skipping.\x1b[0m');
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
urls.push(url.trim());
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
if (urls.length === 0) {
|
|
1241
|
+
console.log('\x1b[33m⚠️ No URLs entered.\x1b[0m');
|
|
1242
|
+
await askQuestion('\nPress Enter to continue...');
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const concurrencyInput = await askQuestion(
|
|
1247
|
+
`\x1b[36mHow many downloads in parallel?\x1b[0m`,
|
|
1248
|
+
String(CONFIG.batchConcurrency)
|
|
1249
|
+
);
|
|
1250
|
+
const concurrency = Math.max(1, Math.min(10, parseInt(concurrencyInput, 10) || CONFIG.batchConcurrency));
|
|
1251
|
+
|
|
1252
|
+
console.log(`\n\x1b[32m📥 Starting batch download of ${urls.length} videos (${concurrency} at a time)...\x1b[0m\n`);
|
|
1253
|
+
|
|
1254
|
+
let successCount = 0;
|
|
1255
|
+
let failCount = 0;
|
|
1256
|
+
let completed = 0;
|
|
1257
|
+
|
|
1258
|
+
await asyncPool(concurrency, urls, async (url, i) => {
|
|
1259
|
+
console.log(`\x1b[36m📥 [${i + 1}/${urls.length}] Processing:\x1b[0m ${url}`);
|
|
1260
|
+
|
|
1261
|
+
const detected = detectPlatform(url);
|
|
1262
|
+
if (!detected) {
|
|
1263
|
+
console.log(`\x1b[31m❌ Unsupported platform for URL: ${url}\x1b[0m`);
|
|
1264
|
+
failCount++;
|
|
1265
|
+
completed++;
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const apiFunction = api[detected.api];
|
|
1270
|
+
if (!apiFunction) {
|
|
1271
|
+
console.log(`\x1b[31m❌ API function not found for: ${detected.platform}\x1b[0m`);
|
|
1272
|
+
failCount++;
|
|
1273
|
+
completed++;
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const result = await downloadManager.downloadFromApi(apiFunction, url, author, '', detected.platform, { showProgress: concurrency === 1 });
|
|
1278
|
+
completed++;
|
|
1279
|
+
if (result.success) {
|
|
1280
|
+
successCount++;
|
|
1281
|
+
console.log(`\x1b[90m📊 Progress: ${completed}/${urls.length} complete\x1b[0m`);
|
|
1282
|
+
} else {
|
|
1283
|
+
failCount++;
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
console.log(`\n\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
1288
|
+
console.log(`\x1b[32m✅ Batch Download Complete!\x1b[0m`);
|
|
1289
|
+
console.log(` ✅ Success: ${successCount}`);
|
|
1290
|
+
console.log(` ❌ Failed: ${failCount}`);
|
|
1291
|
+
console.log(` 📁 Total: ${urls.length}`);
|
|
1292
|
+
|
|
1293
|
+
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// --------------------------------------------------------------
|
|
1297
|
+
// DOWNLOAD MENU (Single)
|
|
1298
|
+
// --------------------------------------------------------------
|
|
1299
|
+
async function downloadMenu() {
|
|
1300
|
+
console.clear();
|
|
1301
|
+
showLogo();
|
|
1302
|
+
|
|
1303
|
+
let continueDownloading = true;
|
|
1304
|
+
let author = null;
|
|
1305
|
+
let currentApiFunction = null;
|
|
1306
|
+
let currentPlatformName = '';
|
|
1307
|
+
let downloadCount = 0;
|
|
1308
|
+
|
|
1309
|
+
while (continueDownloading) {
|
|
1310
|
+
if (!author) {
|
|
1311
|
+
const defaultAuthor = CONFIG.defaultAuthor || getDefaultAuthor();
|
|
1312
|
+
author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
1313
|
+
savePersistedConfig({ defaultAuthor: author });
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
if (!currentApiFunction) {
|
|
1317
|
+
const platform = await selectOption('📥 Select Platform:', [
|
|
1318
|
+
'YouTube', 'TikTok', 'Instagram', 'Facebook', 'Twitter/X',
|
|
1319
|
+
'Threads', 'Pinterest', 'CapCut', 'Likee', 'All-in-One', 'Auto-Detect'
|
|
1320
|
+
]);
|
|
1321
|
+
|
|
1322
|
+
if (platform === 'Auto-Detect') {
|
|
1323
|
+
currentApiFunction = null;
|
|
1324
|
+
currentPlatformName = 'Auto-Detect';
|
|
1325
|
+
} else {
|
|
1326
|
+
switch(platform) {
|
|
1327
|
+
case 'YouTube': currentApiFunction = api.ShAnYtdl; currentPlatformName = 'YouTube'; break;
|
|
1328
|
+
case 'TikTok': currentApiFunction = api.ShAnTikdl; currentPlatformName = 'TikTok'; break;
|
|
1329
|
+
case 'Instagram': currentApiFunction = api.ShAnInstadl; currentPlatformName = 'Instagram'; break;
|
|
1330
|
+
case 'Facebook': currentApiFunction = api.ShAnFbdl; currentPlatformName = 'Facebook'; break;
|
|
1331
|
+
case 'Twitter/X': currentApiFunction = api.ShAnTwitdl; currentPlatformName = 'Twitter/X'; break;
|
|
1332
|
+
case 'Threads': currentApiFunction = api.ShAnThreadl; currentPlatformName = 'Threads'; break;
|
|
1333
|
+
case 'Pinterest': currentApiFunction = api.ShAnPindl; currentPlatformName = 'Pinterest'; break;
|
|
1334
|
+
case 'CapCut': currentApiFunction = api.ShAnCapcutdl; currentPlatformName = 'CapCut'; break;
|
|
1335
|
+
case 'Likee': currentApiFunction = api.ShAnLikeedl; currentPlatformName = 'Likee'; break;
|
|
1336
|
+
case 'All-in-One': currentApiFunction = api.ShAnAlldl; currentPlatformName = 'All-in-One'; break;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const url = await askQuestion('\x1b[36mEnter video URL\x1b[0m');
|
|
1342
|
+
if (!url) {
|
|
1343
|
+
console.log('\x1b[31m❌ URL is required!\x1b[0m');
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (!isValidHttpUrl(url)) {
|
|
1347
|
+
console.log('\x1b[31m❌ That does not look like a valid URL!\x1b[0m');
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
let apiFunction = currentApiFunction;
|
|
1352
|
+
let platformName = currentPlatformName;
|
|
1353
|
+
|
|
1354
|
+
if (currentPlatformName === 'Auto-Detect' || !currentApiFunction) {
|
|
1355
|
+
const detected = detectPlatform(url);
|
|
1356
|
+
if (detected) {
|
|
1357
|
+
apiFunction = api[detected.api];
|
|
1358
|
+
platformName = detected.platform;
|
|
1359
|
+
console.log(`\x1b[90m🔍 Detected: ${platformName}\x1b[0m`);
|
|
1360
|
+
} else {
|
|
1361
|
+
console.log('\x1b[31m❌ Could not detect platform. Please select manually.\x1b[0m');
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
const customTitle = await askQuestion('\x1b[36mEnter custom title (optional)\x1b[0m');
|
|
1367
|
+
const shouldDownload = await askQuestion('\x1b[33mDownload and save to device? (y/n)\x1b[0m', 'y');
|
|
1368
|
+
|
|
1369
|
+
if (shouldDownload.toLowerCase() === 'y' || shouldDownload.toLowerCase() === 'yes') {
|
|
1370
|
+
downloadCount++;
|
|
1371
|
+
const result = await downloadManager.downloadFromApi(apiFunction, url, author, customTitle, platformName);
|
|
1372
|
+
if (result.success) {
|
|
1373
|
+
console.log('\n\x1b[32m✅ Video saved successfully!\x1b[0m');
|
|
1374
|
+
console.log(`📁 Location: ${result.filePath}`);
|
|
1375
|
+
|
|
1376
|
+
const stats = downloadManager.getDownloadStats();
|
|
1377
|
+
console.log(`\n\x1b[36m📊 Download Statistics:\x1b[0m`);
|
|
1378
|
+
console.log(` Downloads in session: ${downloadCount}`);
|
|
1379
|
+
console.log(` Total Files: ${stats.count}`);
|
|
1380
|
+
console.log(` Total Size: ${stats.totalSize}`);
|
|
1381
|
+
}
|
|
1382
|
+
} else {
|
|
1383
|
+
console.log(`\n\x1b[33m⏳ Fetching ${platformName} video info...\x1b[0m`);
|
|
1384
|
+
await runSafely(async () => {
|
|
1385
|
+
const result = await apiFunction(url, author);
|
|
1386
|
+
const formatted = formatResponse(result, 'download');
|
|
1387
|
+
console.log(`\n\x1b[32m✅ ${platformName} Video Info:\x1b[0m\n${formatted}`);
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
console.log(`\n\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
1392
|
+
console.log(`\x1b[33m📌 Options:\x1b[0m`);
|
|
1393
|
+
console.log(` \x1b[32m1.\x1b[0m Press \x1b[36mEnter\x1b[0m to return to Main Menu`);
|
|
1394
|
+
console.log(` \x1b[32m2.\x1b[0m Enter a new \x1b[36mURL\x1b[0m to download more videos`);
|
|
1395
|
+
console.log(` \x1b[32m3.\x1b[0m Type \x1b[31m"exit"\x1b[0m to quit`);
|
|
1396
|
+
console.log(` \x1b[32m4.\x1b[0m Type \x1b[33m"change"\x1b[0m to change platform`);
|
|
1397
|
+
console.log(` \x1b[32m5.\x1b[0m Type \x1b[33m"author"\x1b[0m to change author name`);
|
|
1398
|
+
console.log(` \x1b[32m6.\x1b[0m Type \x1b[33m"stats"\x1b[0m to show download stats`);
|
|
1399
|
+
console.log(`\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
1400
|
+
|
|
1401
|
+
const nextAction = await askQuestion(`\x1b[36mEnter URL, press Enter for Main Menu, or type a command\x1b[0m`);
|
|
1402
|
+
|
|
1403
|
+
if (nextAction.toLowerCase() === 'exit') {
|
|
1404
|
+
console.log('\n\x1b[36m👋 Goodbye!\x1b[0m');
|
|
1405
|
+
process.exit(0);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
if (nextAction.toLowerCase() === 'change') {
|
|
1409
|
+
currentApiFunction = null;
|
|
1410
|
+
currentPlatformName = '';
|
|
1411
|
+
console.log('\n\x1b[33m🔄 Platform changed. Select new platform...\x1b[0m');
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
if (nextAction.toLowerCase() === 'author') {
|
|
1416
|
+
author = null;
|
|
1417
|
+
console.log('\n\x1b[33m👤 Author changed. Enter new author...\x1b[0m');
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (nextAction.toLowerCase() === 'stats') {
|
|
1422
|
+
const stats = downloadManager.getDownloadStats();
|
|
1423
|
+
console.log(`\n\x1b[36m📊 Download Statistics:\x1b[0m`);
|
|
1424
|
+
console.log(` Total Files: ${stats.count}`);
|
|
1425
|
+
console.log(` Total Size: ${stats.totalSize}`);
|
|
1426
|
+
console.log(` Downloads in session: ${downloadCount}`);
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (nextAction && nextAction.trim() !== '') {
|
|
1431
|
+
if (isValidHttpUrl(nextAction.trim())) {
|
|
1432
|
+
const detected = detectPlatform(nextAction);
|
|
1433
|
+
if (detected) {
|
|
1434
|
+
const result = await downloadManager.downloadFromApi(api[detected.api], nextAction.trim(), author, '', detected.platform);
|
|
1435
|
+
if (result.success) {
|
|
1436
|
+
downloadCount++;
|
|
1437
|
+
console.log('\n\x1b[32m✅ Video saved successfully!\x1b[0m');
|
|
1438
|
+
console.log(`📁 Location: ${result.filePath}`);
|
|
1439
|
+
|
|
1440
|
+
const stats = downloadManager.getDownloadStats();
|
|
1441
|
+
console.log(`\n\x1b[36m📊 Download Statistics:\x1b[0m`);
|
|
1442
|
+
console.log(` Downloads in session: ${downloadCount}`);
|
|
1443
|
+
console.log(` Total Files: ${stats.count}`);
|
|
1444
|
+
console.log(` Total Size: ${stats.totalSize}`);
|
|
1445
|
+
}
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
console.log('\x1b[33m⚠️ Invalid input. Returning to Main Menu...\x1b[0m');
|
|
1450
|
+
await askQuestion('\nPress Enter to continue...');
|
|
1451
|
+
return;
|
|
1452
|
+
} else {
|
|
1453
|
+
console.log('\n\x1b[36m↩️ Returning to Main Menu...\x1b[0m');
|
|
1454
|
+
await askQuestion('\nPress Enter to continue...');
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// --------------------------------------------------------------
|
|
1461
|
+
// HISTORY MENU
|
|
1462
|
+
// --------------------------------------------------------------
|
|
1463
|
+
async function historyMenu() {
|
|
1464
|
+
console.clear();
|
|
1465
|
+
showLogo();
|
|
1466
|
+
|
|
1467
|
+
const historyStats = downloadManager.getHistoryStats();
|
|
1468
|
+
console.log(`\n\x1b[36m📊 Download History\x1b[0m`);
|
|
1469
|
+
console.log(`\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m`);
|
|
1470
|
+
console.log(` Total Downloads: ${historyStats.total}`);
|
|
1471
|
+
console.log(` Total Size: ${historyStats.totalSize}`);
|
|
1472
|
+
console.log(`\n \x1b[36mPlatforms:\x1b[0m`);
|
|
1473
|
+
for (const [platform, count] of Object.entries(historyStats.platforms)) {
|
|
1474
|
+
console.log(` • ${platform}: ${count}`);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
const history = downloadManager.history.slice(-10).reverse();
|
|
1478
|
+
if (history.length > 0) {
|
|
1479
|
+
console.log(`\n\x1b[36m📋 Last 10 Downloads:\x1b[0m`);
|
|
1480
|
+
history.forEach((item, i) => {
|
|
1481
|
+
console.log(` ${i + 1}. \x1b[32m${item.title || 'Untitled'}\x1b[0m`);
|
|
1482
|
+
console.log(` 📁 ${item.filename}`);
|
|
1483
|
+
console.log(` 📊 ${downloadManager.formatBytes(item.size || 0)}`);
|
|
1484
|
+
console.log(` ⏱️ ${item.time ? item.time.toFixed(1) + 's' : 'N/A'}`);
|
|
1485
|
+
console.log(` 📅 ${new Date(item.downloadedAt).toLocaleString()}`);
|
|
1486
|
+
});
|
|
1487
|
+
} else {
|
|
1488
|
+
console.log('\n\x1b[33m⚠️ No download history yet.\x1b[0m');
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// --------------------------------------------------------------
|
|
1495
|
+
// SEARCH MENU
|
|
1496
|
+
// --------------------------------------------------------------
|
|
1497
|
+
async function searchMenu() {
|
|
1498
|
+
console.clear();
|
|
1499
|
+
showLogo();
|
|
1500
|
+
const platform = await selectOption('🔍 Search On:', ['YouTube', 'TikTok']);
|
|
1501
|
+
const query = await askQuestion('\x1b[36mEnter search query\x1b[0m');
|
|
1502
|
+
if (!query) { console.log('\x1b[31m❌ Query is required!\x1b[0m'); await askQuestion('\nPress Enter to continue...'); return; }
|
|
1503
|
+
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', getDefaultAuthor());
|
|
1504
|
+
console.log(`\n\x1b[33m⏳ Searching ${platform} for "${query}"...\x1b[0m\n`);
|
|
1505
|
+
await runSafely(async () => {
|
|
1506
|
+
const result = platform === 'YouTube' ? await api.ShAnytSearch(query, author) : await api.ShAntikSearch(query, author);
|
|
1507
|
+
const formatted = formatResponse(result, 'search');
|
|
1508
|
+
console.log(`\x1b[32m✅ Search Results:\x1b[0m\n${formatted}`);
|
|
1509
|
+
});
|
|
1510
|
+
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// --------------------------------------------------------------
|
|
1514
|
+
// ALBUM MENU
|
|
1515
|
+
// --------------------------------------------------------------
|
|
1516
|
+
async function albumMenu() {
|
|
1517
|
+
console.clear();
|
|
1518
|
+
showLogo();
|
|
1519
|
+
const action = await selectOption('💾 Album Actions:', ['List Albums', 'Add Video', 'View Videos', 'Delete Video']);
|
|
1520
|
+
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', getDefaultAuthor());
|
|
1521
|
+
await runSafely(async () => {
|
|
1522
|
+
let result, formatted;
|
|
408
1523
|
if (action === 'List Albums') {
|
|
409
1524
|
result = await api.ShAnalbumList(author);
|
|
410
|
-
|
|
411
|
-
|
|
1525
|
+
formatted = formatResponse(result, 'album');
|
|
1526
|
+
console.log(`\n\x1b[32m✅ Albums:\x1b[0m\n${formatted}`);
|
|
1527
|
+
} else if (action === 'Add Video') {
|
|
412
1528
|
const category = await askQuestion('\x1b[36mEnter category name\x1b[0m');
|
|
413
1529
|
const videoUrl = await askQuestion('\x1b[36mEnter video URL\x1b[0m');
|
|
414
1530
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
415
1531
|
result = await api.ShAnalbumAdd(category, videoUrl, senderID, author);
|
|
416
|
-
|
|
417
|
-
|
|
1532
|
+
const formattedMsg = formatResponse(result, 'teach');
|
|
1533
|
+
console.log(`\n\x1b[32m✅ ${formattedMsg || `Video added to "${category}" album!`}\x1b[0m`);
|
|
1534
|
+
} else if (action === 'View Videos') {
|
|
418
1535
|
const category = await askQuestion('\x1b[36mEnter category name\x1b[0m');
|
|
419
1536
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
420
1537
|
const key = await askQuestion('\x1b[36mEnter access key (optional)\x1b[0m');
|
|
421
1538
|
result = await api.ShAnalbumVideos(category, senderID, author, key);
|
|
422
|
-
|
|
423
|
-
|
|
1539
|
+
formatted = formatResponse(result, 'album');
|
|
1540
|
+
console.log(`\n\x1b[32m✅ Videos in "${category}":\x1b[0m\n${formatted}`);
|
|
1541
|
+
} else if (action === 'Delete Video') {
|
|
424
1542
|
const url = await askQuestion('\x1b[36mEnter video URL to delete\x1b[0m');
|
|
425
1543
|
const key = await askQuestion('\x1b[36mEnter access key\x1b[0m');
|
|
426
1544
|
result = await api.ShAnalbumDelete(url, author, key);
|
|
1545
|
+
const formattedMsg = formatResponse(result, 'teach');
|
|
1546
|
+
console.log(`\n\x1b[32m✅ ${formattedMsg || 'Video deleted successfully!'}\x1b[0m`);
|
|
427
1547
|
}
|
|
428
|
-
|
|
429
|
-
console.log('\x1b[32m✅ Success!\x1b[0m\n');
|
|
430
|
-
console.log(JSON.stringify(result, null, 2));
|
|
431
|
-
} catch (err) {
|
|
432
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
433
|
-
}
|
|
434
|
-
|
|
1548
|
+
});
|
|
435
1549
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
436
1550
|
}
|
|
437
1551
|
|
|
1552
|
+
// --------------------------------------------------------------
|
|
1553
|
+
// FONT MENU
|
|
1554
|
+
// --------------------------------------------------------------
|
|
438
1555
|
async function fontMenu() {
|
|
439
1556
|
console.clear();
|
|
440
1557
|
showLogo();
|
|
441
|
-
|
|
442
1558
|
const action = await selectOption('🎨 Font Utilities:', ['Apply Font to Text', 'List Available Fonts', 'Generate Wish Card']);
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
try {
|
|
1559
|
+
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', getDefaultAuthor());
|
|
1560
|
+
await runSafely(async () => {
|
|
447
1561
|
if (action === 'List Available Fonts') {
|
|
448
1562
|
const result = await api.ShAnfontList(author);
|
|
449
|
-
|
|
450
|
-
console.log(
|
|
451
|
-
}
|
|
452
|
-
else if (action === 'Apply Font to Text') {
|
|
1563
|
+
const formatted = formatResponse(result, 'list');
|
|
1564
|
+
console.log(`\n\x1b[32m✅ Available Fonts:\x1b[0m\n${formatted}`);
|
|
1565
|
+
} else if (action === 'Apply Font to Text') {
|
|
453
1566
|
const text = await askQuestion('\x1b[36mEnter your text\x1b[0m');
|
|
454
1567
|
const font = await askQuestion('\x1b[36mEnter font name\x1b[0m', 'Arial');
|
|
455
1568
|
const result = await api.ShAnFont(text, font, author);
|
|
456
|
-
|
|
457
|
-
console.log(
|
|
458
|
-
}
|
|
459
|
-
else if (action === 'Generate Wish Card') {
|
|
1569
|
+
const formatted = formatResponse(result, 'chat');
|
|
1570
|
+
console.log(`\n\x1b[32m✅ Formatted Text:\x1b[0m\n${formatted}`);
|
|
1571
|
+
} else if (action === 'Generate Wish Card') {
|
|
460
1572
|
const name = await askQuestion('\x1b[36mEnter name for wish card\x1b[0m');
|
|
461
1573
|
const font = await askQuestion('\x1b[36mEnter font name\x1b[0m', 'Arial');
|
|
462
1574
|
const result = await api.ShAnWish(name, font, author);
|
|
463
|
-
|
|
464
|
-
console.log(
|
|
1575
|
+
const data = extractResponseData(result);
|
|
1576
|
+
console.log(`\n\x1b[32m✅ Wish Card Generated:\x1b[0m`);
|
|
1577
|
+
console.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
|
|
465
1578
|
}
|
|
466
|
-
}
|
|
467
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
468
|
-
}
|
|
469
|
-
|
|
1579
|
+
});
|
|
470
1580
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
471
1581
|
}
|
|
472
1582
|
|
|
1583
|
+
// --------------------------------------------------------------
|
|
1584
|
+
// CAPTION MENU
|
|
1585
|
+
// --------------------------------------------------------------
|
|
473
1586
|
async function captionMenu() {
|
|
474
1587
|
console.clear();
|
|
475
1588
|
showLogo();
|
|
476
|
-
|
|
477
1589
|
const action = await selectOption('📝 Caption Manager:', ['Add Caption', 'Get Caption', 'List Captions']);
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
try {
|
|
1590
|
+
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', getDefaultAuthor());
|
|
1591
|
+
await runSafely(async () => {
|
|
482
1592
|
if (action === 'Add Caption') {
|
|
483
1593
|
const category = await askQuestion('\x1b[36mEnter category\x1b[0m');
|
|
484
1594
|
const language = await askQuestion('\x1b[36mEnter language (e.g., en, es, hi)\x1b[0m');
|
|
485
1595
|
const caption = await askQuestion('\x1b[36mEnter caption text\x1b[0m');
|
|
486
1596
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
487
1597
|
const result = await api.ShAncaptionAdd(category, language, caption, senderID, author);
|
|
488
|
-
|
|
489
|
-
console.log(
|
|
490
|
-
}
|
|
491
|
-
else if (action === 'List Captions') {
|
|
1598
|
+
const formatted = formatResponse(result, 'teach');
|
|
1599
|
+
console.log(`\n\x1b[32m✅ ${formatted || 'Caption added successfully!'}\x1b[0m`);
|
|
1600
|
+
} else if (action === 'List Captions') {
|
|
492
1601
|
const language = await askQuestion('\x1b[36mEnter language\x1b[0m', 'en');
|
|
493
1602
|
const result = await api.ShAncaptionList(language, author);
|
|
494
|
-
|
|
495
|
-
console.log(
|
|
496
|
-
}
|
|
497
|
-
else if (action === 'Get Caption') {
|
|
1603
|
+
const formatted = formatResponse(result, 'list');
|
|
1604
|
+
console.log(`\n\x1b[32m✅ Captions List:\x1b[0m\n${formatted}`);
|
|
1605
|
+
} else if (action === 'Get Caption') {
|
|
498
1606
|
const category = await askQuestion('\x1b[36mEnter category\x1b[0m');
|
|
499
1607
|
const language = await askQuestion('\x1b[36mEnter language\x1b[0m');
|
|
500
1608
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
501
1609
|
const key = await askQuestion('\x1b[36mEnter access key (optional)\x1b[0m');
|
|
502
1610
|
const result = await api.ShAnCaption(category, language, senderID, author, key);
|
|
503
|
-
|
|
504
|
-
console.log(
|
|
1611
|
+
const data = extractResponseData(result);
|
|
1612
|
+
console.log(`\n\x1b[32m✅ Caption:\x1b[0m`);
|
|
1613
|
+
console.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
|
|
505
1614
|
}
|
|
506
|
-
}
|
|
507
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
508
|
-
}
|
|
509
|
-
|
|
1615
|
+
});
|
|
510
1616
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
511
1617
|
}
|
|
512
1618
|
|
|
1619
|
+
// --------------------------------------------------------------
|
|
1620
|
+
// MEME MENU
|
|
1621
|
+
// --------------------------------------------------------------
|
|
513
1622
|
async function memeMenu() {
|
|
514
1623
|
console.clear();
|
|
515
1624
|
showLogo();
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
520
|
-
|
|
521
|
-
try {
|
|
1625
|
+
const action = await selectOption('🖼️ Meme Generator:', ['Get Random Meme', 'Add New Meme']);
|
|
1626
|
+
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', getDefaultAuthor());
|
|
1627
|
+
await runSafely(async () => {
|
|
522
1628
|
if (action === 'Get Random Meme') {
|
|
523
1629
|
const result = await api.ShAnMeme(author);
|
|
524
|
-
|
|
525
|
-
console.log(
|
|
526
|
-
|
|
527
|
-
|
|
1630
|
+
const data = extractResponseData(result);
|
|
1631
|
+
console.log(`\n\x1b[32m✅ Random Meme:\x1b[0m`);
|
|
1632
|
+
if (typeof data === 'string') console.log(data);
|
|
1633
|
+
else if (data) {
|
|
1634
|
+
if (data.url) console.log(`🖼️ ${data.url}`);
|
|
1635
|
+
if (data.title) console.log(`📝 ${data.title}`);
|
|
1636
|
+
if (data.text) console.log(`💬 ${data.text}`);
|
|
1637
|
+
if (data.image) console.log(`🖼️ ${data.image}`);
|
|
1638
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1639
|
+
}
|
|
1640
|
+
} else if (action === 'Add New Meme') {
|
|
528
1641
|
const memeUrl = await askQuestion('\x1b[36mEnter meme image/video URL\x1b[0m');
|
|
529
1642
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
530
1643
|
const result = await api.ShAnmemeAdd(memeUrl, senderID, author);
|
|
531
|
-
|
|
532
|
-
console.log(
|
|
1644
|
+
const formatted = formatResponse(result, 'teach');
|
|
1645
|
+
console.log(`\n\x1b[32m✅ ${formatted || 'Meme added successfully!'}\x1b[0m`);
|
|
533
1646
|
}
|
|
534
|
-
}
|
|
535
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
536
|
-
}
|
|
537
|
-
|
|
1647
|
+
});
|
|
538
1648
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
539
1649
|
}
|
|
540
1650
|
|
|
1651
|
+
// --------------------------------------------------------------
|
|
1652
|
+
// CLOUD MENU
|
|
1653
|
+
// --------------------------------------------------------------
|
|
541
1654
|
async function cloudMenu() {
|
|
542
1655
|
console.clear();
|
|
543
1656
|
showLogo();
|
|
544
|
-
|
|
545
|
-
const platform = await selectOption('☁️ Upload To:', ['Imgur', 'ImgBB']);
|
|
1657
|
+
const platform = await selectOption('☁️ Upload To:', ['Imgur', 'ImgBB']);
|
|
546
1658
|
const url = await askQuestion('\x1b[36mEnter media URL to upload\x1b[0m');
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (platform === 'Imgur') {
|
|
561
|
-
result = await api.ShAnImgur(url, author);
|
|
562
|
-
} else {
|
|
563
|
-
result = await api.ShAnImgbb(url, author);
|
|
1659
|
+
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', getDefaultAuthor());
|
|
1660
|
+
if (!url || !isValidHttpUrl(url)) { console.log('\x1b[31m❌ A valid URL is required!\x1b[0m'); await askQuestion('\nPress Enter to continue...'); return; }
|
|
1661
|
+
console.log(`\n\x1b[33m⏳ Uploading to ${platform}...\x1b[0m\n`);
|
|
1662
|
+
await runSafely(async () => {
|
|
1663
|
+
const result = platform === 'Imgur' ? await api.ShAnImgur(url, author) : await api.ShAnImgbb(url, author);
|
|
1664
|
+
const data = extractResponseData(result);
|
|
1665
|
+
console.log(`\x1b[32m✅ Upload Successful!\x1b[0m`);
|
|
1666
|
+
if (typeof data === 'string') console.log(data);
|
|
1667
|
+
else if (data) {
|
|
1668
|
+
if (data.url) console.log(`🔗 URL: ${data.url}`);
|
|
1669
|
+
if (data.deleteHash) console.log(`🗑️ Delete Hash: ${data.deleteHash}`);
|
|
1670
|
+
if (data.link) console.log(`🔗 Link: ${data.link}`);
|
|
1671
|
+
console.log(JSON.stringify(data, null, 2));
|
|
564
1672
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1673
|
+
});
|
|
1674
|
+
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// --------------------------------------------------------------
|
|
1678
|
+
// MANAGE DOWNLOADS MENU
|
|
1679
|
+
// --------------------------------------------------------------
|
|
1680
|
+
async function manageDownloadsMenu() {
|
|
1681
|
+
console.clear();
|
|
1682
|
+
showLogo();
|
|
1683
|
+
const stats = downloadManager.getDownloadStats();
|
|
1684
|
+
console.log(`\n\x1b[36m📁 Download Statistics:\x1b[0m`);
|
|
1685
|
+
console.log(` Total Files: ${stats.count}`);
|
|
1686
|
+
console.log(` Total Size: ${stats.totalSize}`);
|
|
1687
|
+
console.log(` Location: ${downloadManager.downloadDir}`);
|
|
1688
|
+
if (stats.count > 0) {
|
|
1689
|
+
console.log(`\n\x1b[36m📋 Recent Downloads:\x1b[0m`);
|
|
1690
|
+
const files = downloadManager.listDownloads().slice(0, 10);
|
|
1691
|
+
files.forEach((file, index) => {
|
|
1692
|
+
console.log(` ${index + 1}. ${file.name} (${downloadManager.formatBytes(file.size)})`);
|
|
1693
|
+
});
|
|
1694
|
+
const action = await selectOption('\n📂 Actions:', ['Open Downloads Folder', 'Clear All Downloads', 'Back to Main Menu']);
|
|
1695
|
+
if (action === 'Open Downloads Folder') {
|
|
1696
|
+
const open = require('child_process');
|
|
1697
|
+
const platform = os.platform();
|
|
1698
|
+
let command;
|
|
1699
|
+
if (platform === 'win32') command = `start "" "${downloadManager.downloadDir}"`;
|
|
1700
|
+
else if (platform === 'darwin') command = `open "${downloadManager.downloadDir}"`;
|
|
1701
|
+
else command = `xdg-open "${downloadManager.downloadDir}"`;
|
|
1702
|
+
open.exec(command, (err) => {
|
|
1703
|
+
if (err) console.log('\x1b[33m⚠️ Could not open folder automatically\x1b[0m');
|
|
1704
|
+
console.log(`📁 Downloads folder: ${downloadManager.downloadDir}`);
|
|
1705
|
+
});
|
|
1706
|
+
} else if (action === 'Clear All Downloads') {
|
|
1707
|
+
const confirm = await askQuestion('\x1b[31m⚠️ Delete all downloaded files? (y/n)\x1b[0m', 'n');
|
|
1708
|
+
if (confirm.toLowerCase() === 'y') {
|
|
1709
|
+
const count = downloadManager.clearDownloads();
|
|
1710
|
+
console.log(`\x1b[32m✅ Deleted ${count} files\x1b[0m`);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
} else {
|
|
1714
|
+
console.log('\n\x1b[33m⚠️ No downloads found.\x1b[0m');
|
|
1715
|
+
await askQuestion('\nPress Enter to continue...');
|
|
570
1716
|
}
|
|
571
|
-
|
|
572
1717
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
573
1718
|
}
|
|
574
1719
|
|
|
1720
|
+
// --------------------------------------------------------------
|
|
1721
|
+
// MAIN MENU
|
|
1722
|
+
// --------------------------------------------------------------
|
|
1723
|
+
async function showMainMenu() {
|
|
1724
|
+
showLogo();
|
|
1725
|
+
|
|
1726
|
+
console.log(`
|
|
1727
|
+
\x1b[36m┌─────────────────────────────────────────────────────────┐\x1b[0m
|
|
1728
|
+
\x1b[36m│ 📋 MAIN MENU │\x1b[0m
|
|
1729
|
+
\x1b[36m├─────────────────────────────────────────────────────────┤\x1b[0m
|
|
1730
|
+
\x1b[32m│ 1. 📥 Download & Save Videos │\x1b[0m
|
|
1731
|
+
\x1b[32m│ 2. 📦 Batch Download (Multiple URLs) │\x1b[0m
|
|
1732
|
+
\x1b[32m│ 3. 📂 Manage Downloads │\x1b[0m
|
|
1733
|
+
\x1b[32m│ 4. 📊 Download History │\x1b[0m
|
|
1734
|
+
\x1b[32m│ 5. 🔍 Search Content │\x1b[0m
|
|
1735
|
+
\x1b[32m│ 6. 🤖 AI Chatbots (Baby/Honey) │\x1b[0m
|
|
1736
|
+
\x1b[32m│ 7. 💾 Album Management │\x1b[0m
|
|
1737
|
+
\x1b[32m│ 8. 🎨 Font & Text Utilities │\x1b[0m
|
|
1738
|
+
\x1b[32m│ 9. 📝 Caption Manager │\x1b[0m
|
|
1739
|
+
\x1b[32m│ 10. 🖼️ Meme Generator │\x1b[0m
|
|
1740
|
+
\x1b[32m│ 11. ☁️ Cloud Upload (Imgur/ImgBB) │\x1b[0m
|
|
1741
|
+
\x1b[31m│ 0. ❌ Exit │\x1b[0m
|
|
1742
|
+
\x1b[36m└─────────────────────────────────────────────────────────┘\x1b[0m
|
|
1743
|
+
`);
|
|
1744
|
+
|
|
1745
|
+
const choice = await askQuestion('\x1b[36mSelect an option\x1b[0m', '0');
|
|
1746
|
+
return choice;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// --------------------------------------------------------------
|
|
1750
|
+
// MAIN PROGRAM
|
|
1751
|
+
// --------------------------------------------------------------
|
|
575
1752
|
async function main() {
|
|
576
1753
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
577
1754
|
showLogo();
|
|
578
1755
|
console.log(`
|
|
579
|
-
\x1b[36mSHAN SERVER -
|
|
1756
|
+
\x1b[36mSHAN SERVER - Ultimate Media Downloader & Utilities v4.0\x1b[0m
|
|
580
1757
|
|
|
581
1758
|
\x1b[33mUSAGE:\x1b[0m
|
|
582
1759
|
shan Start interactive menu
|
|
583
1760
|
shan --help Show this help
|
|
584
1761
|
shan --version Show version
|
|
585
1762
|
|
|
586
|
-
\x1b[
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const result = await shan.ShAnYtdl(url, '♡︎ 𝗦𝗵𝗔𝗻 ♡︎');
|
|
1763
|
+
\x1b[33mENV VARS:\x1b[0m
|
|
1764
|
+
SHAN_API_BASE Override the backend API base URL
|
|
1765
|
+
SHAN_DOWNLOAD_DIR Override where downloads are saved
|
|
1766
|
+
SHAN_TIMEOUT_MS Override the download request timeout
|
|
1767
|
+
SHAN_CONCURRENCY Default parallel downloads for batch mode (default: 3)
|
|
1768
|
+
SHAN_RETRIES Retry attempts for flaky API/network calls (default: 2)
|
|
1769
|
+
SHAN_RETRY_DELAY_MS Base backoff delay between retries (default: 400)
|
|
594
1770
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
Instagram, Facebook, Twitter, and more. Also includes AI chatbots,
|
|
598
|
-
album storage, captions, fonts, memes, and cloud uploads.
|
|
1771
|
+
Your author/UID/font choices and these settings also persist between runs in
|
|
1772
|
+
~/.shan-server/config.json, so you won't need to re-enter them every time.
|
|
599
1773
|
|
|
600
|
-
\x1b[
|
|
601
|
-
✓
|
|
602
|
-
✓
|
|
603
|
-
✓
|
|
604
|
-
✓
|
|
605
|
-
✓
|
|
606
|
-
✓
|
|
1774
|
+
\x1b[33mFEATURES:\x1b[0m
|
|
1775
|
+
✓ Download videos from 10+ platforms
|
|
1776
|
+
✓ Parallel batch download with configurable concurrency
|
|
1777
|
+
✓ Automatic retry with exponential backoff on network hiccups
|
|
1778
|
+
✓ SHA-256 checksum verification after every download
|
|
1779
|
+
✓ Graceful Ctrl+C: cancels in-flight downloads and cleans up partial files
|
|
1780
|
+
✓ Persisted preferences (author, UID, font, concurrency) across sessions
|
|
1781
|
+
✓ Activity log file for auditing downloads/errors
|
|
1782
|
+
✓ Auto-detect platform from URL
|
|
1783
|
+
✓ Download history with stats
|
|
1784
|
+
✓ Save directly to device with speed indicator
|
|
1785
|
+
✓ Continuous download loop
|
|
1786
|
+
✓ Change platform & author without restarting
|
|
1787
|
+
✓ Random Teach Mode - AI asks questions, you provide answers
|
|
1788
|
+
✓ Continuous AI Teaching Mode (Ctrl+C to exit)
|
|
1789
|
+
✓ Beautiful chat-style responses
|
|
1790
|
+
✓ AI Chatbots (Baby/Honey)
|
|
1791
|
+
✓ Album Management
|
|
1792
|
+
✓ Font Styling
|
|
1793
|
+
✓ Caption Manager
|
|
1794
|
+
✓ Meme Generator
|
|
1795
|
+
✓ Cloud Upload
|
|
607
1796
|
|
|
608
|
-
\x1b[
|
|
609
|
-
|
|
1797
|
+
\x1b[33mRANDOM TEACH MODE:\x1b[0m
|
|
1798
|
+
• AI gives a random question using ShAnBrans
|
|
1799
|
+
• You provide the answer
|
|
1800
|
+
• AI learns automatically
|
|
1801
|
+
• Type "exit" to stop
|
|
1802
|
+
• Type "skip" to skip current question
|
|
610
1803
|
|
|
611
1804
|
\x1b[33mEXAMPLES:\x1b[0m
|
|
612
1805
|
$ shan # Start interactive menu
|
|
613
1806
|
$ npx shan-server # Run without installing
|
|
614
|
-
$ node -e "require('shan-server').ShAnYtdl('url', '♡︎ 𝗦𝗵𝗔𝗻 ♡︎')"
|
|
615
1807
|
|
|
616
1808
|
\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
617
1809
|
`);
|
|
618
1810
|
process.exit(0);
|
|
619
1811
|
}
|
|
620
|
-
|
|
1812
|
+
|
|
621
1813
|
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
622
|
-
console.log('shan-server
|
|
623
|
-
console.log(`Default Author: ${getDefaultAuthor()}`);
|
|
1814
|
+
console.log('shan-server v4.0.0 (Ultimate)');
|
|
1815
|
+
console.log(`Default Author (CLI): ${getDefaultAuthor()}`);
|
|
1816
|
+
console.log(`Downloads Folder: ${downloadManager.downloadDir}`);
|
|
1817
|
+
const stats = downloadManager.getDownloadStats();
|
|
1818
|
+
console.log(`Downloads: ${stats.count} files | ${stats.totalSize}`);
|
|
624
1819
|
process.exit(0);
|
|
625
1820
|
}
|
|
626
|
-
|
|
1821
|
+
|
|
627
1822
|
while (true) {
|
|
628
1823
|
const choice = await showMainMenu();
|
|
629
|
-
|
|
630
1824
|
switch(choice) {
|
|
631
|
-
case '1':
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
case '
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
case '
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
case '
|
|
641
|
-
|
|
642
|
-
break;
|
|
643
|
-
case '5':
|
|
644
|
-
await fontMenu();
|
|
645
|
-
break;
|
|
646
|
-
case '6':
|
|
647
|
-
await captionMenu();
|
|
648
|
-
break;
|
|
649
|
-
case '7':
|
|
650
|
-
await memeMenu();
|
|
651
|
-
break;
|
|
652
|
-
case '8':
|
|
653
|
-
await cloudMenu();
|
|
654
|
-
break;
|
|
1825
|
+
case '1': await downloadMenu(); break;
|
|
1826
|
+
case '2': await batchDownloadMenu(); break;
|
|
1827
|
+
case '3': await manageDownloadsMenu(); break;
|
|
1828
|
+
case '4': await historyMenu(); break;
|
|
1829
|
+
case '5': await searchMenu(); break;
|
|
1830
|
+
case '6': await aiChatbotMenu(); break;
|
|
1831
|
+
case '7': await albumMenu(); break;
|
|
1832
|
+
case '8': await fontMenu(); break;
|
|
1833
|
+
case '9': await captionMenu(); break;
|
|
1834
|
+
case '10': await memeMenu(); break;
|
|
1835
|
+
case '11': await cloudMenu(); break;
|
|
655
1836
|
case '0':
|
|
656
1837
|
console.log('\n\x1b[36m👋 Thank you for using SHAN SERVER!\x1b[0m\n');
|
|
1838
|
+
console.log(`📁 Downloads saved in: ${downloadManager.downloadDir}`);
|
|
657
1839
|
process.exit(0);
|
|
658
1840
|
default:
|
|
659
1841
|
console.log('\x1b[31m❌ Invalid option! Please try again.\x1b[0m');
|
|
@@ -663,14 +1845,24 @@ async function main() {
|
|
|
663
1845
|
}
|
|
664
1846
|
|
|
665
1847
|
process.on('SIGINT', () => {
|
|
666
|
-
|
|
667
|
-
|
|
1848
|
+
if (downloadManager.activeControllers.size > 0) {
|
|
1849
|
+
console.log(`\n\n\x1b[33m⚠️ Canceling ${downloadManager.activeControllers.size} in-progress download(s)...\x1b[0m`);
|
|
1850
|
+
downloadManager.abortAll();
|
|
1851
|
+
}
|
|
1852
|
+
console.log('\n\x1b[36m👋 Goodbye from SHAN SERVER!\x1b[0m\n');
|
|
1853
|
+
console.log(`📁 Downloads saved in: ${downloadManager.downloadDir}`);
|
|
1854
|
+
logEvent('info', 'Session ended via SIGINT');
|
|
1855
|
+
// Give in-flight cleanup handlers (unlink of partial files) a brief moment to run
|
|
1856
|
+
setTimeout(() => process.exit(0), 150);
|
|
668
1857
|
});
|
|
669
1858
|
|
|
670
1859
|
if (require.main === module) {
|
|
671
1860
|
main().catch(console.error);
|
|
672
1861
|
}
|
|
673
1862
|
|
|
1863
|
+
// --------------------------------------------------------------
|
|
1864
|
+
// EXPORT
|
|
1865
|
+
// --------------------------------------------------------------
|
|
674
1866
|
module.exports = {
|
|
675
1867
|
ShAnAlldl: api.ShAnAlldl,
|
|
676
1868
|
ShAnAlldl2: api.ShAnAlldl2,
|
|
@@ -687,6 +1879,7 @@ module.exports = {
|
|
|
687
1879
|
ShAnCapcutdl: api.ShAnCapcutdl,
|
|
688
1880
|
ShAnLikeedl: api.ShAnLikeedl,
|
|
689
1881
|
ShAnytSearch: api.ShAnytSearch,
|
|
1882
|
+
ShAntikSearch: api.ShAntikSearch,
|
|
690
1883
|
ShAnBaby: api.ShAnBaby,
|
|
691
1884
|
ShAnBteach: api.ShAnBteach,
|
|
692
1885
|
ShAnBrans: api.ShAnBrans,
|
|
@@ -705,7 +1898,7 @@ module.exports = {
|
|
|
705
1898
|
ShAnalbumDelete: api.ShAnalbumDelete,
|
|
706
1899
|
ShAnalbumList: api.ShAnalbumList,
|
|
707
1900
|
ShAnImgur: api.ShAnImgur,
|
|
708
|
-
|
|
1901
|
+
ShAnImgbb: api.ShAnImgbb,
|
|
709
1902
|
ShAnFont: api.ShAnFont,
|
|
710
1903
|
ShAnfontList: api.ShAnfontList,
|
|
711
1904
|
ShAnWish: api.ShAnWish,
|
|
@@ -714,6 +1907,15 @@ module.exports = {
|
|
|
714
1907
|
ShAnCaption: api.ShAnCaption,
|
|
715
1908
|
ShAnmemeAdd: api.ShAnmemeAdd,
|
|
716
1909
|
ShAnMeme: api.ShAnMeme,
|
|
717
|
-
|
|
718
|
-
|
|
1910
|
+
downloadManager,
|
|
1911
|
+
AITeachingSession,
|
|
1912
|
+
getDefaultAuthor,
|
|
1913
|
+
detectPlatform,
|
|
1914
|
+
isValidHttpUrl,
|
|
1915
|
+
withRetry,
|
|
1916
|
+
asyncPool,
|
|
1917
|
+
logEvent,
|
|
1918
|
+
loadPersistedConfig,
|
|
1919
|
+
savePersistedConfig,
|
|
1920
|
+
CONFIG
|
|
719
1921
|
};
|