termicord 1.0.1 → 1.0.2
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/backend.ts +41 -7
- package/index.ts +24 -3
- package/middleware.ts +2 -0
- package/package.json +55 -55
package/backend.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface DownloadConfig {
|
|
|
10
10
|
outputDir: string;
|
|
11
11
|
skipExtensions: string[]; // e.g. [".jpg", ".png"]
|
|
12
12
|
foldersPerMessage: boolean;
|
|
13
|
+
saveTxt: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface DownloadProgress {
|
|
@@ -245,8 +246,14 @@ export async function runDownload(
|
|
|
245
246
|
onProgress: ProgressCallback,
|
|
246
247
|
signal?: AbortSignal,
|
|
247
248
|
): Promise<void> {
|
|
248
|
-
const {
|
|
249
|
-
|
|
249
|
+
const {
|
|
250
|
+
token,
|
|
251
|
+
channelId,
|
|
252
|
+
outputDir,
|
|
253
|
+
skipExtensions,
|
|
254
|
+
foldersPerMessage,
|
|
255
|
+
saveTxt,
|
|
256
|
+
} = config;
|
|
250
257
|
|
|
251
258
|
// Validate
|
|
252
259
|
if (!token.trim()) {
|
|
@@ -355,6 +362,30 @@ export async function runDownload(
|
|
|
355
362
|
continue;
|
|
356
363
|
}
|
|
357
364
|
|
|
365
|
+
if (saveTxt) {
|
|
366
|
+
const txtFileName = `${ts}_${author}_${snippet}.txt`;
|
|
367
|
+
const txtPath = path.join(folderPath, txtFileName);
|
|
368
|
+
try {
|
|
369
|
+
const txtContent = [
|
|
370
|
+
`Message ID : ${msg.id}`,
|
|
371
|
+
`Timestamp : ${msg.timestamp}`,
|
|
372
|
+
`Author : ${msg.author.username}`,
|
|
373
|
+
``,
|
|
374
|
+
msg.content || "(no text content)",
|
|
375
|
+
].join("\n");
|
|
376
|
+
fs.writeFileSync(txtPath, txtContent, "utf8");
|
|
377
|
+
onProgress({
|
|
378
|
+
type: "log",
|
|
379
|
+
message: ` 📄 Saved message text: ${txtFileName}`,
|
|
380
|
+
});
|
|
381
|
+
} catch (err) {
|
|
382
|
+
onProgress({
|
|
383
|
+
type: "error",
|
|
384
|
+
message: ` ✗ Failed to save .txt: ${(err as Error).message}`,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
358
389
|
for (const att of msg.attachments) {
|
|
359
390
|
if (signal?.aborted) break;
|
|
360
391
|
|
|
@@ -370,12 +401,15 @@ export async function runDownload(
|
|
|
370
401
|
continue;
|
|
371
402
|
}
|
|
372
403
|
|
|
373
|
-
const
|
|
404
|
+
const safeFilename = foldersPerMessage
|
|
405
|
+
? att.filename
|
|
406
|
+
: `${msg.id}_${att.filename}`;
|
|
407
|
+
const destPath = path.join(folderPath, safeFilename);
|
|
374
408
|
|
|
375
409
|
if (fs.existsSync(destPath)) {
|
|
376
410
|
onProgress({
|
|
377
411
|
type: "file_skip",
|
|
378
|
-
message: ` ↩ Already exists: ${
|
|
412
|
+
message: ` ↩ Already exists: ${safeFilename}`,
|
|
379
413
|
currentFile: att.filename,
|
|
380
414
|
});
|
|
381
415
|
continue;
|
|
@@ -384,7 +418,7 @@ export async function runDownload(
|
|
|
384
418
|
const sizeKb = Math.round(att.size / 1024);
|
|
385
419
|
onProgress({
|
|
386
420
|
type: "file_start",
|
|
387
|
-
message: ` ↓ ${
|
|
421
|
+
message: ` ↓ ${safeFilename} (${sizeKb} KB)`,
|
|
388
422
|
currentFile: att.filename,
|
|
389
423
|
});
|
|
390
424
|
|
|
@@ -393,8 +427,8 @@ export async function runDownload(
|
|
|
393
427
|
downloaded++;
|
|
394
428
|
onProgress({
|
|
395
429
|
type: "file_done",
|
|
396
|
-
message: ` ✓ Saved: ${
|
|
397
|
-
currentFile:
|
|
430
|
+
message: ` ✓ Saved: ${safeFilename}`,
|
|
431
|
+
currentFile: safeFilename,
|
|
398
432
|
filesDownloaded: downloaded,
|
|
399
433
|
filesTotal,
|
|
400
434
|
progress: Math.round((downloaded / filesTotal) * 100),
|
package/index.ts
CHANGED
|
@@ -201,6 +201,7 @@ downloadLocationPanel.add(downloadLocationInput);
|
|
|
201
201
|
skipFilesInputPanel.add(skipFilesInput);
|
|
202
202
|
|
|
203
203
|
let checked = false;
|
|
204
|
+
let saveTxt = false;
|
|
204
205
|
|
|
205
206
|
const checkbox = new TextRenderable(renderer, {
|
|
206
207
|
id: "checkbox",
|
|
@@ -208,6 +209,12 @@ const checkbox = new TextRenderable(renderer, {
|
|
|
208
209
|
fg: c.dim,
|
|
209
210
|
});
|
|
210
211
|
|
|
212
|
+
const saveTxtCheckbox = new TextRenderable(renderer, {
|
|
213
|
+
id: "save-txt-checkbox",
|
|
214
|
+
content: " [ ] Save message content as .txt file",
|
|
215
|
+
fg: c.dim,
|
|
216
|
+
});
|
|
217
|
+
|
|
211
218
|
const downloadButton = new BoxRenderable(renderer, {
|
|
212
219
|
id: "download-button",
|
|
213
220
|
position: "absolute",
|
|
@@ -289,6 +296,7 @@ const configChildren = [
|
|
|
289
296
|
downloadLocationPanel,
|
|
290
297
|
skipFilesInputPanel,
|
|
291
298
|
checkbox,
|
|
299
|
+
saveTxtCheckbox,
|
|
292
300
|
];
|
|
293
301
|
|
|
294
302
|
function showTab(tab: Tab) {
|
|
@@ -317,8 +325,8 @@ function showTab(tab: Tab) {
|
|
|
317
325
|
}
|
|
318
326
|
}
|
|
319
327
|
|
|
320
|
-
// 0-3 = inputs, 4 = checkbox
|
|
321
|
-
const TOTAL_FIELDS =
|
|
328
|
+
// 0-3 = inputs, 4 = checkbox, 5 = saveTxtCheckbox
|
|
329
|
+
const TOTAL_FIELDS = 6;
|
|
322
330
|
const inputPanels = [
|
|
323
331
|
tokenPanel,
|
|
324
332
|
channelIDPanel,
|
|
@@ -340,6 +348,7 @@ function updateFocusStyles() {
|
|
|
340
348
|
panel.borderColor = focusedIndex === i ? c.focus : c.violet;
|
|
341
349
|
});
|
|
342
350
|
checkbox.fg = focusedIndex === 4 ? c.focus : c.dim;
|
|
351
|
+
saveTxtCheckbox.fg = focusedIndex === 5 ? c.focus : c.dim;
|
|
343
352
|
}
|
|
344
353
|
|
|
345
354
|
function focusAt(index: number) {
|
|
@@ -405,6 +414,7 @@ function startDownload() {
|
|
|
405
414
|
addLog(` Location : ${location}`);
|
|
406
415
|
addLog(` Skip ext : ${skip || "(none)"}`);
|
|
407
416
|
addLog(` Folders : ${checked ? "yes (one per message)" : "no"}`);
|
|
417
|
+
addLog(` Save .txt : ${saveTxt ? "yes" : "no"}`);
|
|
408
418
|
addLog(`──────────────────────────────────────────────`);
|
|
409
419
|
|
|
410
420
|
showTab("logs");
|
|
@@ -417,6 +427,7 @@ function startDownload() {
|
|
|
417
427
|
outputDir: location,
|
|
418
428
|
skipExtensions: skip,
|
|
419
429
|
foldersPerMessage: checked,
|
|
430
|
+
saveTxt,
|
|
420
431
|
},
|
|
421
432
|
(line) => addLog(line),
|
|
422
433
|
);
|
|
@@ -488,7 +499,11 @@ renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
|
488
499
|
checked = !checked;
|
|
489
500
|
checkbox.content = ` [${checked ? "♡" : " "}] Create a new folder for every message`;
|
|
490
501
|
}
|
|
491
|
-
if (key.name === "
|
|
502
|
+
if (key.name === "space" && focusedIndex === 5) {
|
|
503
|
+
saveTxt = !saveTxt;
|
|
504
|
+
saveTxtCheckbox.content = ` [${saveTxt ? "♡" : " "}] Save message content as .txt file`;
|
|
505
|
+
}
|
|
506
|
+
if (key.name === "return" && (focusedIndex === 4 || focusedIndex === 5)) {
|
|
492
507
|
startDownload();
|
|
493
508
|
}
|
|
494
509
|
}
|
|
@@ -581,6 +596,12 @@ animateBanner(() => {
|
|
|
581
596
|
setTimeout(() => {
|
|
582
597
|
checkbox.visible = true;
|
|
583
598
|
}, pd * 6);
|
|
599
|
+
setTimeout(
|
|
600
|
+
() => {
|
|
601
|
+
saveTxtCheckbox.visible = true;
|
|
602
|
+
},
|
|
603
|
+
pd * 6 + 60,
|
|
604
|
+
);
|
|
584
605
|
setTimeout(
|
|
585
606
|
() => {
|
|
586
607
|
downloadButton.visible = true;
|
package/middleware.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface DownloadConfigRaw {
|
|
|
11
11
|
outputDir: string;
|
|
12
12
|
skipExtensions: string;
|
|
13
13
|
foldersPerMessage: boolean;
|
|
14
|
+
saveTxt: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface DownloadHandle {
|
|
@@ -31,6 +32,7 @@ export function startDownloadTask(
|
|
|
31
32
|
outputDir: config.outputDir || "./downloads",
|
|
32
33
|
skipExtensions: parseSkipExtensions(config.skipExtensions),
|
|
33
34
|
foldersPerMessage: config.foldersPerMessage,
|
|
35
|
+
saveTxt: config.saveTxt,
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const progressCb: ProgressCallback = (evt) => {
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "termicord",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "A beautiful, terminal-native Discord attachment downloader. Built with TypeScript, powered by Bun, UI rendered by OpenTUI.",
|
|
5
|
-
"module": "index.ts",
|
|
6
|
-
"main": "cli.mjs",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"bin": {
|
|
9
|
-
"termicord": "./cli.mjs"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"cli.mjs",
|
|
13
|
-
"index.ts",
|
|
14
|
-
"middleware.ts",
|
|
15
|
-
"backend.ts",
|
|
16
|
-
"public/termicord.png",
|
|
17
|
-
"README.md",
|
|
18
|
-
"LICENSE"
|
|
19
|
-
],
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=18.0.0",
|
|
22
|
-
"bun": ">=1.3.10"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"discord",
|
|
26
|
-
"downloader",
|
|
27
|
-
"tui",
|
|
28
|
-
"terminal",
|
|
29
|
-
"bun",
|
|
30
|
-
"opentui",
|
|
31
|
-
"cli",
|
|
32
|
-
"attachment",
|
|
33
|
-
"scraper"
|
|
34
|
-
],
|
|
35
|
-
"author": "dilukshann7",
|
|
36
|
-
"license": "MIT",
|
|
37
|
-
"homepage": "https://github.com/dilukshann7/termicord#readme",
|
|
38
|
-
"repository": {
|
|
39
|
-
"type": "git",
|
|
40
|
-
"url": "https://github.com/dilukshann7/termicord.git"
|
|
41
|
-
},
|
|
42
|
-
"bugs": {
|
|
43
|
-
"url": "https://github.com/dilukshann7/termicord/issues"
|
|
44
|
-
},
|
|
45
|
-
"private": false,
|
|
46
|
-
"devDependencies": {
|
|
47
|
-
"@types/bun": "latest"
|
|
48
|
-
},
|
|
49
|
-
"peerDependencies": {
|
|
50
|
-
"typescript": "^5"
|
|
51
|
-
},
|
|
52
|
-
"dependencies": {
|
|
53
|
-
"@opentui/core": "^0.1.87"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "termicord",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "A beautiful, terminal-native Discord attachment downloader. Built with TypeScript, powered by Bun, UI rendered by OpenTUI.",
|
|
5
|
+
"module": "index.ts",
|
|
6
|
+
"main": "cli.mjs",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"termicord": "./cli.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"cli.mjs",
|
|
13
|
+
"index.ts",
|
|
14
|
+
"middleware.ts",
|
|
15
|
+
"backend.ts",
|
|
16
|
+
"public/termicord.png",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0",
|
|
22
|
+
"bun": ">=1.3.10"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"discord",
|
|
26
|
+
"downloader",
|
|
27
|
+
"tui",
|
|
28
|
+
"terminal",
|
|
29
|
+
"bun",
|
|
30
|
+
"opentui",
|
|
31
|
+
"cli",
|
|
32
|
+
"attachment",
|
|
33
|
+
"scraper"
|
|
34
|
+
],
|
|
35
|
+
"author": "dilukshann7",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"homepage": "https://github.com/dilukshann7/termicord#readme",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/dilukshann7/termicord.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/dilukshann7/termicord/issues"
|
|
44
|
+
},
|
|
45
|
+
"private": false,
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/bun": "latest"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"typescript": "^5"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@opentui/core": "^0.1.87"
|
|
54
|
+
}
|
|
55
|
+
}
|