shan-server 1.0.5 → 1.0.7
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/index.js +1507 -307
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -3,24 +3,128 @@
|
|
|
3
3
|
const axios = require('axios');
|
|
4
4
|
const readline = require('readline');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const crypto = require('crypto');
|
|
6
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
|
+
}
|
|
107
|
+
|
|
108
|
+
// --------------------------------------------------------------
|
|
109
|
+
// AUTO-AUTHOR DETECTION (now actually returns the detected user)
|
|
110
|
+
// --------------------------------------------------------------
|
|
7
111
|
function getDefaultAuthor() {
|
|
8
112
|
try {
|
|
9
113
|
const username = os.userInfo().username;
|
|
10
114
|
if (username && username !== 'root' && username !== 'admin') {
|
|
11
|
-
return
|
|
115
|
+
return '♡︎ 𝗦𝗵𝗔𝗻 ♡︎';
|
|
12
116
|
}
|
|
13
|
-
} catch (e) {
|
|
14
|
-
}
|
|
117
|
+
} catch (e) {}
|
|
15
118
|
|
|
16
119
|
const envUser = process.env.USER || process.env.USERNAME || process.env.LOGNAME;
|
|
17
120
|
if (envUser && envUser !== 'root' && envUser !== 'admin') {
|
|
18
|
-
return
|
|
121
|
+
return '♡︎ 𝗦𝗵𝗔𝗻 ♡︎';
|
|
19
122
|
}
|
|
20
123
|
|
|
21
|
-
return '
|
|
124
|
+
return 'Guest';
|
|
22
125
|
}
|
|
23
126
|
|
|
127
|
+
// Detect platform
|
|
24
128
|
function getPlatform() {
|
|
25
129
|
const platform = os.platform();
|
|
26
130
|
const platformMap = {
|
|
@@ -64,12 +168,634 @@ function getShell() {
|
|
|
64
168
|
return 'Unknown Shell';
|
|
65
169
|
}
|
|
66
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
|
+
// --------------------------------------------------------------
|
|
67
791
|
function showLogo() {
|
|
68
792
|
const platform = getPlatform();
|
|
69
793
|
const terminal = getTerminalType();
|
|
70
794
|
const shell = getShell();
|
|
71
795
|
const author = getDefaultAuthor();
|
|
72
|
-
|
|
796
|
+
const stats = downloadManager.getDownloadStats();
|
|
797
|
+
const historyStats = downloadManager.getHistoryStats();
|
|
798
|
+
|
|
73
799
|
console.clear();
|
|
74
800
|
console.log(`
|
|
75
801
|
\x1b[36m
|
|
@@ -79,8 +805,8 @@ function showLogo() {
|
|
|
79
805
|
╚════██║██╔══██║██╔══██║██║╚██╗██║
|
|
80
806
|
███████║██║ ██║██║ ██║██║ ╚████║
|
|
81
807
|
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝
|
|
82
|
-
|
|
83
|
-
███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
|
|
808
|
+
|
|
809
|
+
███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
|
|
84
810
|
██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗
|
|
85
811
|
███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝
|
|
86
812
|
╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
|
@@ -88,16 +814,21 @@ function showLogo() {
|
|
|
88
814
|
╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
|
89
815
|
\x1b[0m
|
|
90
816
|
\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
91
|
-
\x1b[36m SHAN SERVER -
|
|
817
|
+
\x1b[36m SHAN SERVER - v4.0.0 (Ultimate)\x1b[0m
|
|
92
818
|
\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
93
819
|
\x1b[90m Platform: ${platform.padEnd(20)} Terminal: ${terminal}\x1b[0m
|
|
94
|
-
\x1b[90m Shell: ${shell.padEnd(20)} API:
|
|
820
|
+
\x1b[90m Shell: ${shell.padEnd(20)} API: ${CONFIG.apiBase.replace(/^https?:\/\//, '')}\x1b[0m
|
|
95
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
|
|
96
824
|
\x1b[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
97
825
|
`);
|
|
98
826
|
}
|
|
99
827
|
|
|
100
|
-
|
|
828
|
+
// --------------------------------------------------------------
|
|
829
|
+
// API Configuration
|
|
830
|
+
// --------------------------------------------------------------
|
|
831
|
+
const Sh4n = CONFIG.apiBase;
|
|
101
832
|
|
|
102
833
|
const api = {
|
|
103
834
|
ShAnAlldl: (url, author) => axios.get(`${Sh4n}ShAn-alldl?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
@@ -115,6 +846,7 @@ const api = {
|
|
|
115
846
|
ShAnCapcutdl: (url, author) => axios.get(`${Sh4n}ShAn-capcutDL?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
116
847
|
ShAnLikeedl: (url, author) => axios.get(`${Sh4n}ShAn-likeeDL?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
117
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),
|
|
118
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),
|
|
119
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),
|
|
120
852
|
ShAnBrans: (author) => axios.get(`${Sh4n}ShAn-brans?author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
@@ -122,12 +854,12 @@ const api = {
|
|
|
122
854
|
ShAnBlist: (font, author) => axios.get(`${Sh4n}ShAn-blist?font=${font}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
123
855
|
ShAnBedit: (ask, newAsk, uid, font, author, index) => {
|
|
124
856
|
let url = `${Sh4n}ShAn-bedit?ask=${encodeURIComponent(ask)}&newAsk=${encodeURIComponent(newAsk)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`;
|
|
125
|
-
if (index) url += `&index=${index}`;
|
|
857
|
+
if (index) url += `&index=${encodeURIComponent(index)}`;
|
|
126
858
|
return axios.get(url).then(res => res.data);
|
|
127
859
|
},
|
|
128
860
|
ShAnBdelete: (text, uid, font, author, index) => {
|
|
129
861
|
let url = `${Sh4n}ShAn-bdelete?text=${encodeURIComponent(text)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`;
|
|
130
|
-
if (index) url += `&index=${index}`;
|
|
862
|
+
if (index) url += `&index=${encodeURIComponent(index)}`;
|
|
131
863
|
return axios.delete(url).then(res => res.data);
|
|
132
864
|
},
|
|
133
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),
|
|
@@ -136,12 +868,12 @@ const api = {
|
|
|
136
868
|
ShAnHlist: (font, author) => axios.get(`${Sh4n}ShAn-hlist?font=${font}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
137
869
|
ShAnHedit: (ask, newAsk, uid, font, author, index) => {
|
|
138
870
|
let url = `${Sh4n}ShAn-hedit?ask=${encodeURIComponent(ask)}&newAsk=${encodeURIComponent(newAsk)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`;
|
|
139
|
-
if (index) url += `&index=${index}`;
|
|
871
|
+
if (index) url += `&index=${encodeURIComponent(index)}`;
|
|
140
872
|
return axios.get(url).then(res => res.data);
|
|
141
873
|
},
|
|
142
874
|
ShAnHdelete: (text, uid, font, author, index) => {
|
|
143
875
|
let url = `${Sh4n}ShAn-hdelete?text=${encodeURIComponent(text)}&uid=${uid}&font=${font}&author=${encodeURIComponent(author)}`;
|
|
144
|
-
if (index) url += `&index=${index}`;
|
|
876
|
+
if (index) url += `&index=${encodeURIComponent(index)}`;
|
|
145
877
|
return axios.delete(url).then(res => res.data);
|
|
146
878
|
},
|
|
147
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),
|
|
@@ -149,7 +881,7 @@ const api = {
|
|
|
149
881
|
ShAnalbumDelete: (url, author, key) => axios.delete(`${Sh4n}ShAn-album-delete?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}&key=${encodeURIComponent(key)}`).then(res => res.data),
|
|
150
882
|
ShAnalbumList: (author) => axios.get(`${Sh4n}ShAn-album-list?author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
151
883
|
ShAnImgur: (videoUrl, author) => axios.post(`${Sh4n}ShAn-imgur?url=${encodeURIComponent(videoUrl)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
152
|
-
|
|
884
|
+
ShAnImgbb: (url, author) => axios.get(`${Sh4n}ShAn-imgbb?url=${encodeURIComponent(url)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
153
885
|
ShAnFont: (text, font, author) => axios.get(`${Sh4n}ShAn-font?text=${encodeURIComponent(text)}&font=${encodeURIComponent(font)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
154
886
|
ShAnfontList: (author) => axios.get(`${Sh4n}ShAn-fontList?author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
155
887
|
ShAnWish: (name, font, author) => axios.get(`${Sh4n}ShAn-wish?name=${encodeURIComponent(name)}&font=${encodeURIComponent(font)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
@@ -157,10 +889,19 @@ const api = {
|
|
|
157
889
|
ShAncaptionList: (language, author) => axios.get(`${Sh4n}ShAn-caption-list?language=${encodeURIComponent(language)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
158
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),
|
|
159
891
|
ShAnmemeAdd: (memeUrl, senderID, author) => axios.post(`${Sh4n}ShAn-meme-add?memeUrl=${encodeURIComponent(memeUrl)}&senderID=${encodeURIComponent(senderID)}&author=${encodeURIComponent(author)}`).then(res => res.data),
|
|
160
|
-
ShAnMeme: (author) => axios.get(`${Sh4n}ShAn-meme?author=${encodeURIComponent(author)}`).then(res => res.data)
|
|
161
|
-
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)
|
|
162
893
|
};
|
|
163
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
|
+
// --------------------------------------------------------------
|
|
164
905
|
function createInterface() {
|
|
165
906
|
return readline.createInterface({
|
|
166
907
|
input: process.stdin,
|
|
@@ -185,7 +926,7 @@ async function selectOption(question, options) {
|
|
|
185
926
|
options.forEach((opt, idx) => {
|
|
186
927
|
console.log(` ${idx + 1}. ${opt}`);
|
|
187
928
|
});
|
|
188
|
-
|
|
929
|
+
|
|
189
930
|
return new Promise((resolve) => {
|
|
190
931
|
rl.question('\x1b[33mEnter number (or name): \x1b[0m', (answer) => {
|
|
191
932
|
rl.close();
|
|
@@ -202,460 +943,899 @@ async function selectOption(question, options) {
|
|
|
202
943
|
});
|
|
203
944
|
}
|
|
204
945
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
\x1b[36m┌─────────────────────────────────────────────────────────┐\x1b[0m
|
|
210
|
-
\x1b[36m│ 📋 MAIN MENU │\x1b[0m
|
|
211
|
-
\x1b[36m├─────────────────────────────────────────────────────────┤\x1b[0m
|
|
212
|
-
\x1b[32m│ 1. 📥 Download Videos │\x1b[0m
|
|
213
|
-
\x1b[32m│ 2. 🔍 Search Content │\x1b[0m
|
|
214
|
-
\x1b[32m│ 3. 🤖 AI Chatbots (Baby/Honey) │\x1b[0m
|
|
215
|
-
\x1b[32m│ 4. 💾 Album Management │\x1b[0m
|
|
216
|
-
\x1b[32m│ 5. 🎨 Font & Text Utilities │\x1b[0m
|
|
217
|
-
\x1b[32m│ 6. 📝 Caption Manager │\x1b[0m
|
|
218
|
-
\x1b[32m│ 7. 🖼️ Meme Generator │\x1b[0m
|
|
219
|
-
\x1b[32m│ 8. ☁️ Cloud Upload (Imgur/ImgBB) │\x1b[0m
|
|
220
|
-
\x1b[31m│ 0. ❌ Exit │\x1b[0m
|
|
221
|
-
\x1b[36m└─────────────────────────────────────────────────────────┘\x1b[0m
|
|
222
|
-
`);
|
|
223
|
-
|
|
224
|
-
const choice = await askQuestion('\x1b[36mSelect an option\x1b[0m', '0');
|
|
225
|
-
return choice;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async function downloadMenu() {
|
|
229
|
-
console.clear();
|
|
230
|
-
showLogo();
|
|
231
|
-
|
|
232
|
-
const platform = await selectOption('📥 Select Platform:', [
|
|
233
|
-
'YouTube', 'TikTok', 'Instagram', 'Facebook', 'Twitter/X',
|
|
234
|
-
'Threads', 'Pinterest', 'CapCut', 'Likee', 'All-in-One'
|
|
235
|
-
]);
|
|
236
|
-
|
|
237
|
-
let command = '';
|
|
238
|
-
switch(platform) {
|
|
239
|
-
case 'YouTube': command = 'ytdl'; break;
|
|
240
|
-
case 'TikTok': command = 'tikdl'; break;
|
|
241
|
-
case 'Instagram': command = 'instadl'; break;
|
|
242
|
-
case 'Facebook': command = 'fbdl'; break;
|
|
243
|
-
case 'Twitter/X': command = 'twitdl'; break;
|
|
244
|
-
case 'Threads': command = 'threadl'; break;
|
|
245
|
-
case 'Pinterest': command = 'pindl'; break;
|
|
246
|
-
case 'CapCut': command = 'capcutdl'; break;
|
|
247
|
-
case 'Likee': command = 'likeedl'; break;
|
|
248
|
-
case 'All-in-One': command = 'alldl'; break;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const url = await askQuestion('\x1b[36mEnter video URL\x1b[0m');
|
|
252
|
-
if (!url) {
|
|
253
|
-
console.log('\x1b[31m❌ URL is required!\x1b[0m');
|
|
254
|
-
await askQuestion('\nPress Enter to continue...');
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const defaultAuthor = getDefaultAuthor();
|
|
259
|
-
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
260
|
-
|
|
261
|
-
console.log('\n\x1b[33m⏳ Processing your request...\x1b[0m\n');
|
|
262
|
-
|
|
946
|
+
// --------------------------------------------------------------
|
|
947
|
+
// Generic error-safe API call wrapper used by the simpler menus
|
|
948
|
+
// --------------------------------------------------------------
|
|
949
|
+
async function runSafely(actionFn) {
|
|
263
950
|
try {
|
|
264
|
-
|
|
265
|
-
switch(command) {
|
|
266
|
-
case 'ytdl': result = await api.ShAnYtdl(url, author); break;
|
|
267
|
-
case 'tikdl': result = await api.ShAnTikdl(url, author); break;
|
|
268
|
-
case 'instadl': result = await api.ShAnInstadl(url, author); break;
|
|
269
|
-
case 'fbdl': result = await api.ShAnFbdl(url, author); break;
|
|
270
|
-
case 'twitdl': result = await api.ShAnTwitdl(url, author); break;
|
|
271
|
-
case 'threadl': result = await api.ShAnThreadl(url, author); break;
|
|
272
|
-
case 'pindl': result = await api.ShAnPindl(url, author); break;
|
|
273
|
-
case 'capcutdl': result = await api.ShAnCapcutdl(url, author); break;
|
|
274
|
-
case 'likeedl': result = await api.ShAnLikeedl(url, author); break;
|
|
275
|
-
default: result = await api.ShAnAlldl(url, author);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
console.log('\n\x1b[32m✅ SUCCESS!\x1b[0m\n');
|
|
279
|
-
console.log(JSON.stringify(result, null, 2));
|
|
951
|
+
await actionFn();
|
|
280
952
|
} catch (err) {
|
|
281
953
|
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
282
954
|
}
|
|
283
|
-
|
|
284
|
-
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
285
955
|
}
|
|
286
956
|
|
|
287
|
-
|
|
957
|
+
// --------------------------------------------------------------
|
|
958
|
+
// RANDOM TEACH MODE - ShAnBrans returns question, user provides answer
|
|
959
|
+
// --------------------------------------------------------------
|
|
960
|
+
async function randomTeachMode(bot, author, uid, font) {
|
|
288
961
|
console.clear();
|
|
289
962
|
showLogo();
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
console.log(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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`);
|
|
311
1039
|
}
|
|
312
|
-
|
|
313
|
-
console.log('\x1b[32m✅ Search Results:\x1b[0m\n');
|
|
314
|
-
console.log(JSON.stringify(result, null, 2));
|
|
315
|
-
} catch (err) {
|
|
316
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
317
1040
|
}
|
|
318
|
-
|
|
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`);
|
|
319
1059
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
320
1060
|
}
|
|
321
1061
|
|
|
1062
|
+
// --------------------------------------------------------------
|
|
1063
|
+
// AI CHATBOT MENU
|
|
1064
|
+
// --------------------------------------------------------------
|
|
322
1065
|
async function aiChatbotMenu() {
|
|
323
1066
|
console.clear();
|
|
324
1067
|
showLogo();
|
|
325
|
-
|
|
1068
|
+
|
|
326
1069
|
const bot = await selectOption('🤖 Select AI Chatbot:', ['Baby', 'Honey']);
|
|
327
|
-
const action = await selectOption('Select Action:', [
|
|
328
|
-
|
|
329
|
-
|
|
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();
|
|
330
1081
|
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
331
|
-
const uid = await askQuestion('\x1b[36mEnter User ID (your unique identifier)\x1b[0m', 'user123');
|
|
332
|
-
|
|
333
|
-
|
|
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
|
+
|
|
334
1116
|
try {
|
|
335
1117
|
let result;
|
|
336
|
-
|
|
1118
|
+
let formattedResponse;
|
|
1119
|
+
|
|
337
1120
|
if (action === 'Chat') {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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`);
|
|
352
1150
|
}
|
|
1151
|
+
|
|
1152
|
+
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
1153
|
+
return;
|
|
353
1154
|
}
|
|
1155
|
+
|
|
354
1156
|
else if (action === 'Random Response') {
|
|
355
1157
|
if (bot === 'Baby') {
|
|
356
1158
|
result = await api.ShAnBrans(author);
|
|
1159
|
+
formattedResponse = formatResponse(result, 'baby');
|
|
1160
|
+
console.log(`\n\x1b[36m🎲 Random Baby Response:\x1b[0m ${formattedResponse}`);
|
|
357
1161
|
} else {
|
|
358
1162
|
console.log('\x1b[33m⚠️ Random response only available for Baby bot\x1b[0m');
|
|
359
|
-
await askQuestion('\nPress Enter to continue...');
|
|
360
|
-
return;
|
|
361
1163
|
}
|
|
362
1164
|
}
|
|
1165
|
+
|
|
363
1166
|
else if (action === 'List Data') {
|
|
364
1167
|
if (bot === 'Baby') {
|
|
365
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}`);
|
|
366
1171
|
} else {
|
|
367
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}`);
|
|
368
1175
|
}
|
|
369
1176
|
}
|
|
1177
|
+
|
|
370
1178
|
else if (action === 'Edit') {
|
|
371
1179
|
const ask = await askQuestion('\x1b[36mEnter the question to edit\x1b[0m');
|
|
372
1180
|
const newAsk = await askQuestion('\x1b[36mEnter the new question\x1b[0m');
|
|
373
1181
|
const index = await askQuestion('\x1b[36mEnter index (optional)\x1b[0m');
|
|
1182
|
+
|
|
374
1183
|
if (bot === 'Baby') {
|
|
375
1184
|
result = await api.ShAnBedit(ask, newAsk, uid, font, author, index);
|
|
376
1185
|
} else {
|
|
377
1186
|
result = await api.ShAnHedit(ask, newAsk, uid, font, author, index);
|
|
378
1187
|
}
|
|
1188
|
+
const formatted = formatResponse(result, 'teach');
|
|
1189
|
+
console.log(`\n\x1b[32m✅ ${formatted || 'Successfully edited!'}\x1b[0m`);
|
|
379
1190
|
}
|
|
1191
|
+
|
|
380
1192
|
else if (action === 'Delete') {
|
|
381
1193
|
const text = await askQuestion('\x1b[36mEnter text to delete\x1b[0m');
|
|
382
1194
|
const index = await askQuestion('\x1b[36mEnter index (optional)\x1b[0m');
|
|
1195
|
+
|
|
383
1196
|
if (bot === 'Baby') {
|
|
384
1197
|
result = await api.ShAnBdelete(text, uid, font, author, index);
|
|
385
1198
|
} else {
|
|
386
1199
|
result = await api.ShAnHdelete(text, uid, font, author, index);
|
|
387
1200
|
}
|
|
1201
|
+
const formatted = formatResponse(result, 'teach');
|
|
1202
|
+
console.log(`\n\x1b[32m✅ ${formatted || 'Successfully deleted!'}\x1b[0m`);
|
|
388
1203
|
}
|
|
389
|
-
|
|
390
|
-
console.log('\x1b[32m✅ Success!\x1b[0m\n');
|
|
391
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1204
|
+
|
|
392
1205
|
} catch (err) {
|
|
393
1206
|
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
394
1207
|
}
|
|
395
|
-
|
|
1208
|
+
|
|
396
1209
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
397
1210
|
}
|
|
398
1211
|
|
|
399
|
-
|
|
1212
|
+
// --------------------------------------------------------------
|
|
1213
|
+
// BATCH DOWNLOAD
|
|
1214
|
+
// --------------------------------------------------------------
|
|
1215
|
+
async function batchDownloadMenu() {
|
|
400
1216
|
console.clear();
|
|
401
1217
|
showLogo();
|
|
402
|
-
|
|
403
|
-
|
|
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
|
+
|
|
404
1222
|
const defaultAuthor = getDefaultAuthor();
|
|
405
1223
|
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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;
|
|
410
1523
|
if (action === 'List Albums') {
|
|
411
1524
|
result = await api.ShAnalbumList(author);
|
|
412
|
-
|
|
413
|
-
|
|
1525
|
+
formatted = formatResponse(result, 'album');
|
|
1526
|
+
console.log(`\n\x1b[32m✅ Albums:\x1b[0m\n${formatted}`);
|
|
1527
|
+
} else if (action === 'Add Video') {
|
|
414
1528
|
const category = await askQuestion('\x1b[36mEnter category name\x1b[0m');
|
|
415
1529
|
const videoUrl = await askQuestion('\x1b[36mEnter video URL\x1b[0m');
|
|
416
1530
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
417
1531
|
result = await api.ShAnalbumAdd(category, videoUrl, senderID, author);
|
|
418
|
-
|
|
419
|
-
|
|
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') {
|
|
420
1535
|
const category = await askQuestion('\x1b[36mEnter category name\x1b[0m');
|
|
421
1536
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
422
1537
|
const key = await askQuestion('\x1b[36mEnter access key (optional)\x1b[0m');
|
|
423
1538
|
result = await api.ShAnalbumVideos(category, senderID, author, key);
|
|
424
|
-
|
|
425
|
-
|
|
1539
|
+
formatted = formatResponse(result, 'album');
|
|
1540
|
+
console.log(`\n\x1b[32m✅ Videos in "${category}":\x1b[0m\n${formatted}`);
|
|
1541
|
+
} else if (action === 'Delete Video') {
|
|
426
1542
|
const url = await askQuestion('\x1b[36mEnter video URL to delete\x1b[0m');
|
|
427
1543
|
const key = await askQuestion('\x1b[36mEnter access key\x1b[0m');
|
|
428
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`);
|
|
429
1547
|
}
|
|
430
|
-
|
|
431
|
-
console.log('\x1b[32m✅ Success!\x1b[0m\n');
|
|
432
|
-
console.log(JSON.stringify(result, null, 2));
|
|
433
|
-
} catch (err) {
|
|
434
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
435
|
-
}
|
|
436
|
-
|
|
1548
|
+
});
|
|
437
1549
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
438
1550
|
}
|
|
439
1551
|
|
|
1552
|
+
// --------------------------------------------------------------
|
|
1553
|
+
// FONT MENU
|
|
1554
|
+
// --------------------------------------------------------------
|
|
440
1555
|
async function fontMenu() {
|
|
441
1556
|
console.clear();
|
|
442
1557
|
showLogo();
|
|
443
|
-
|
|
444
1558
|
const action = await selectOption('🎨 Font Utilities:', ['Apply Font to Text', 'List Available Fonts', 'Generate Wish Card']);
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
try {
|
|
1559
|
+
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', getDefaultAuthor());
|
|
1560
|
+
await runSafely(async () => {
|
|
449
1561
|
if (action === 'List Available Fonts') {
|
|
450
1562
|
const result = await api.ShAnfontList(author);
|
|
451
|
-
|
|
452
|
-
console.log(
|
|
453
|
-
}
|
|
454
|
-
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') {
|
|
455
1566
|
const text = await askQuestion('\x1b[36mEnter your text\x1b[0m');
|
|
456
1567
|
const font = await askQuestion('\x1b[36mEnter font name\x1b[0m', 'Arial');
|
|
457
1568
|
const result = await api.ShAnFont(text, font, author);
|
|
458
|
-
|
|
459
|
-
console.log(
|
|
460
|
-
}
|
|
461
|
-
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') {
|
|
462
1572
|
const name = await askQuestion('\x1b[36mEnter name for wish card\x1b[0m');
|
|
463
1573
|
const font = await askQuestion('\x1b[36mEnter font name\x1b[0m', 'Arial');
|
|
464
1574
|
const result = await api.ShAnWish(name, font, author);
|
|
465
|
-
|
|
466
|
-
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));
|
|
467
1578
|
}
|
|
468
|
-
}
|
|
469
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
470
|
-
}
|
|
471
|
-
|
|
1579
|
+
});
|
|
472
1580
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
473
1581
|
}
|
|
474
1582
|
|
|
1583
|
+
// --------------------------------------------------------------
|
|
1584
|
+
// CAPTION MENU
|
|
1585
|
+
// --------------------------------------------------------------
|
|
475
1586
|
async function captionMenu() {
|
|
476
1587
|
console.clear();
|
|
477
1588
|
showLogo();
|
|
478
|
-
|
|
479
1589
|
const action = await selectOption('📝 Caption Manager:', ['Add Caption', 'Get Caption', 'List Captions']);
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
try {
|
|
1590
|
+
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', getDefaultAuthor());
|
|
1591
|
+
await runSafely(async () => {
|
|
484
1592
|
if (action === 'Add Caption') {
|
|
485
1593
|
const category = await askQuestion('\x1b[36mEnter category\x1b[0m');
|
|
486
1594
|
const language = await askQuestion('\x1b[36mEnter language (e.g., en, es, hi)\x1b[0m');
|
|
487
1595
|
const caption = await askQuestion('\x1b[36mEnter caption text\x1b[0m');
|
|
488
1596
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
489
1597
|
const result = await api.ShAncaptionAdd(category, language, caption, senderID, author);
|
|
490
|
-
|
|
491
|
-
console.log(
|
|
492
|
-
}
|
|
493
|
-
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') {
|
|
494
1601
|
const language = await askQuestion('\x1b[36mEnter language\x1b[0m', 'en');
|
|
495
1602
|
const result = await api.ShAncaptionList(language, author);
|
|
496
|
-
|
|
497
|
-
console.log(
|
|
498
|
-
}
|
|
499
|
-
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') {
|
|
500
1606
|
const category = await askQuestion('\x1b[36mEnter category\x1b[0m');
|
|
501
1607
|
const language = await askQuestion('\x1b[36mEnter language\x1b[0m');
|
|
502
1608
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
503
1609
|
const key = await askQuestion('\x1b[36mEnter access key (optional)\x1b[0m');
|
|
504
1610
|
const result = await api.ShAnCaption(category, language, senderID, author, key);
|
|
505
|
-
|
|
506
|
-
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));
|
|
507
1614
|
}
|
|
508
|
-
}
|
|
509
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
510
|
-
}
|
|
511
|
-
|
|
1615
|
+
});
|
|
512
1616
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
513
1617
|
}
|
|
514
1618
|
|
|
1619
|
+
// --------------------------------------------------------------
|
|
1620
|
+
// MEME MENU
|
|
1621
|
+
// --------------------------------------------------------------
|
|
515
1622
|
async function memeMenu() {
|
|
516
1623
|
console.clear();
|
|
517
1624
|
showLogo();
|
|
518
|
-
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
const author = await askQuestion('\x1b[36mEnter your name/author ID\x1b[0m', defaultAuthor);
|
|
522
|
-
|
|
523
|
-
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 () => {
|
|
524
1628
|
if (action === 'Get Random Meme') {
|
|
525
1629
|
const result = await api.ShAnMeme(author);
|
|
526
|
-
|
|
527
|
-
console.log(
|
|
528
|
-
|
|
529
|
-
|
|
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') {
|
|
530
1641
|
const memeUrl = await askQuestion('\x1b[36mEnter meme image/video URL\x1b[0m');
|
|
531
1642
|
const senderID = await askQuestion('\x1b[36mEnter sender ID\x1b[0m');
|
|
532
1643
|
const result = await api.ShAnmemeAdd(memeUrl, senderID, author);
|
|
533
|
-
|
|
534
|
-
console.log(
|
|
1644
|
+
const formatted = formatResponse(result, 'teach');
|
|
1645
|
+
console.log(`\n\x1b[32m✅ ${formatted || 'Meme added successfully!'}\x1b[0m`);
|
|
535
1646
|
}
|
|
536
|
-
}
|
|
537
|
-
console.error('\x1b[31m❌ Error:\x1b[0m', err.response?.data || err.message);
|
|
538
|
-
}
|
|
539
|
-
|
|
1647
|
+
});
|
|
540
1648
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
541
1649
|
}
|
|
542
1650
|
|
|
1651
|
+
// --------------------------------------------------------------
|
|
1652
|
+
// CLOUD MENU
|
|
1653
|
+
// --------------------------------------------------------------
|
|
543
1654
|
async function cloudMenu() {
|
|
544
1655
|
console.clear();
|
|
545
1656
|
showLogo();
|
|
546
|
-
|
|
547
|
-
const platform = await selectOption('☁️ Upload To:', ['Imgur', 'ImgBB']);
|
|
1657
|
+
const platform = await selectOption('☁️ Upload To:', ['Imgur', 'ImgBB']);
|
|
548
1658
|
const url = await askQuestion('\x1b[36mEnter media URL to upload\x1b[0m');
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (platform === 'Imgur') {
|
|
563
|
-
result = await api.ShAnImgur(url, author);
|
|
564
|
-
} else {
|
|
565
|
-
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));
|
|
566
1672
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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...');
|
|
572
1716
|
}
|
|
573
|
-
|
|
574
1717
|
await askQuestion('\n\x1b[36mPress Enter to continue...\x1b[0m');
|
|
575
1718
|
}
|
|
576
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
|
+
// --------------------------------------------------------------
|
|
577
1752
|
async function main() {
|
|
578
1753
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
579
1754
|
showLogo();
|
|
580
1755
|
console.log(`
|
|
581
|
-
\x1b[36mSHAN SERVER -
|
|
1756
|
+
\x1b[36mSHAN SERVER - Ultimate Media Downloader & Utilities v4.0\x1b[0m
|
|
582
1757
|
|
|
583
1758
|
\x1b[33mUSAGE:\x1b[0m
|
|
584
1759
|
shan Start interactive menu
|
|
585
1760
|
shan --help Show this help
|
|
586
1761
|
shan --version Show version
|
|
587
1762
|
|
|
588
|
-
\x1b[
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
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)
|
|
596
1770
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
Instagram, Facebook, Twitter, and more. Also includes AI chatbots,
|
|
600
|
-
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.
|
|
601
1773
|
|
|
602
|
-
\x1b[
|
|
603
|
-
✓
|
|
604
|
-
✓
|
|
605
|
-
✓
|
|
606
|
-
✓
|
|
607
|
-
✓
|
|
608
|
-
✓
|
|
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
|
|
609
1796
|
|
|
610
|
-
\x1b[
|
|
611
|
-
|
|
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
|
|
612
1803
|
|
|
613
1804
|
\x1b[33mEXAMPLES:\x1b[0m
|
|
614
1805
|
$ shan # Start interactive menu
|
|
615
1806
|
$ npx shan-server # Run without installing
|
|
616
|
-
$ node -e "require('shan-server').ShAnYtdl('url', '♡︎ 𝗦𝗵𝗔𝗻 ♡︎')"
|
|
617
1807
|
|
|
618
1808
|
\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
619
1809
|
`);
|
|
620
1810
|
process.exit(0);
|
|
621
1811
|
}
|
|
622
|
-
|
|
1812
|
+
|
|
623
1813
|
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
624
|
-
console.log('shan-server
|
|
625
|
-
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}`);
|
|
626
1819
|
process.exit(0);
|
|
627
1820
|
}
|
|
628
|
-
|
|
1821
|
+
|
|
629
1822
|
while (true) {
|
|
630
1823
|
const choice = await showMainMenu();
|
|
631
|
-
|
|
632
1824
|
switch(choice) {
|
|
633
|
-
case '1':
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
case '
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
case '
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
case '
|
|
643
|
-
|
|
644
|
-
break;
|
|
645
|
-
case '5':
|
|
646
|
-
await fontMenu();
|
|
647
|
-
break;
|
|
648
|
-
case '6':
|
|
649
|
-
await captionMenu();
|
|
650
|
-
break;
|
|
651
|
-
case '7':
|
|
652
|
-
await memeMenu();
|
|
653
|
-
break;
|
|
654
|
-
case '8':
|
|
655
|
-
await cloudMenu();
|
|
656
|
-
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;
|
|
657
1836
|
case '0':
|
|
658
1837
|
console.log('\n\x1b[36m👋 Thank you for using SHAN SERVER!\x1b[0m\n');
|
|
1838
|
+
console.log(`📁 Downloads saved in: ${downloadManager.downloadDir}`);
|
|
659
1839
|
process.exit(0);
|
|
660
1840
|
default:
|
|
661
1841
|
console.log('\x1b[31m❌ Invalid option! Please try again.\x1b[0m');
|
|
@@ -665,14 +1845,24 @@ async function main() {
|
|
|
665
1845
|
}
|
|
666
1846
|
|
|
667
1847
|
process.on('SIGINT', () => {
|
|
668
|
-
|
|
669
|
-
|
|
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);
|
|
670
1857
|
});
|
|
671
1858
|
|
|
672
1859
|
if (require.main === module) {
|
|
673
1860
|
main().catch(console.error);
|
|
674
1861
|
}
|
|
675
1862
|
|
|
1863
|
+
// --------------------------------------------------------------
|
|
1864
|
+
// EXPORT
|
|
1865
|
+
// --------------------------------------------------------------
|
|
676
1866
|
module.exports = {
|
|
677
1867
|
ShAnAlldl: api.ShAnAlldl,
|
|
678
1868
|
ShAnAlldl2: api.ShAnAlldl2,
|
|
@@ -689,6 +1879,7 @@ module.exports = {
|
|
|
689
1879
|
ShAnCapcutdl: api.ShAnCapcutdl,
|
|
690
1880
|
ShAnLikeedl: api.ShAnLikeedl,
|
|
691
1881
|
ShAnytSearch: api.ShAnytSearch,
|
|
1882
|
+
ShAntikSearch: api.ShAntikSearch,
|
|
692
1883
|
ShAnBaby: api.ShAnBaby,
|
|
693
1884
|
ShAnBteach: api.ShAnBteach,
|
|
694
1885
|
ShAnBrans: api.ShAnBrans,
|
|
@@ -707,7 +1898,7 @@ module.exports = {
|
|
|
707
1898
|
ShAnalbumDelete: api.ShAnalbumDelete,
|
|
708
1899
|
ShAnalbumList: api.ShAnalbumList,
|
|
709
1900
|
ShAnImgur: api.ShAnImgur,
|
|
710
|
-
|
|
1901
|
+
ShAnImgbb: api.ShAnImgbb,
|
|
711
1902
|
ShAnFont: api.ShAnFont,
|
|
712
1903
|
ShAnfontList: api.ShAnfontList,
|
|
713
1904
|
ShAnWish: api.ShAnWish,
|
|
@@ -716,6 +1907,15 @@ module.exports = {
|
|
|
716
1907
|
ShAnCaption: api.ShAnCaption,
|
|
717
1908
|
ShAnmemeAdd: api.ShAnmemeAdd,
|
|
718
1909
|
ShAnMeme: api.ShAnMeme,
|
|
719
|
-
|
|
720
|
-
|
|
1910
|
+
downloadManager,
|
|
1911
|
+
AITeachingSession,
|
|
1912
|
+
getDefaultAuthor,
|
|
1913
|
+
detectPlatform,
|
|
1914
|
+
isValidHttpUrl,
|
|
1915
|
+
withRetry,
|
|
1916
|
+
asyncPool,
|
|
1917
|
+
logEvent,
|
|
1918
|
+
loadPersistedConfig,
|
|
1919
|
+
savePersistedConfig,
|
|
1920
|
+
CONFIG
|
|
721
1921
|
};
|