Skip to content

Commit 25883c1

Browse files
committed
Update version, add tb-clouddl command, and cli-progress dependency
1 parent 6db3a98 commit 25883c1

3 files changed

Lines changed: 293 additions & 7 deletions

File tree

app/app-clouddl.js

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// build-in
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
5+
// req modules
6+
import { filesize } from 'filesize'
7+
import { checkbox, select } from '@inquirer/prompts';
8+
9+
// main api/app
10+
import { selectAccount, loadYaml, delay } from './module-helper.js';
11+
import { formatEta } from 'terabox-api/helper.js';
12+
13+
// TB App
14+
import cliProgress from 'cli-progress';
15+
import TeraBoxApp from 'terabox-api';
16+
17+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
18+
const config = loadYaml(path.resolve(__dirname, './.config.yaml'));
19+
const REFRESH_INTERVAL = 5000;
20+
let app = {};
21+
22+
(async () => {
23+
if(!config.accounts){
24+
console.error('[ERROR] Accounts not set!');
25+
return;
26+
}
27+
28+
let cur_acc = await selectAccount(config);
29+
app = new TeraBoxApp(cur_acc);
30+
31+
try{
32+
await startApiSequence();
33+
}
34+
catch(error){
35+
console.error(error);
36+
}
37+
})();
38+
39+
async function startApiSequence(){
40+
await app.updateAppData();
41+
42+
const modeList = [
43+
{ name: 'monitor tasks', value: 'monitor' },
44+
{ name: 'add task', value: 'add' },
45+
{ name: 'remove tasks', value: 'remove' },
46+
];
47+
48+
const mode = await select({
49+
message: 'Select Mode:',
50+
choices: modeList,
51+
});
52+
53+
if(mode == 'monitor'){
54+
const taskIds = await collectTasks();
55+
console.log();
56+
if(taskIds.length > 0){
57+
await monitor(taskIds.join(','));
58+
}
59+
}
60+
61+
if(mode == 'add'){
62+
const remFiles = await app.search('.torrent');
63+
const torrents = remFiles.list.filter(f => f.isdir === 0 && f.category === 6 && f.path?.toLowerCase().endsWith('.torrent'));
64+
65+
const selTorrent = [{ value: 'exit' }];
66+
for(const f of torrents){
67+
selTorrent.push({ name: f.path, value: { p: f.path.split('/').slice(0, -1).join('/'), t: f.server_filename }});
68+
}
69+
70+
if(selTorrent.length < 2){
71+
return;
72+
}
73+
74+
const tfile = await select({
75+
message: 'Select Torrent File:',
76+
choices: selTorrent,
77+
});
78+
79+
if(tfile === 'exit'){
80+
return;
81+
}
82+
83+
await createTask(tfile.p + '/', tfile.t);
84+
}
85+
86+
if(mode == 'remove'){
87+
const tsklst = await collectTasks(true);
88+
console.log();
89+
90+
const tselect = await checkbox({
91+
message: 'Select Tasks to remove:',
92+
choices: tsklst,
93+
});
94+
if(tselect.length > 0){
95+
await deleteTasks(tselect.join(','));
96+
}
97+
}
98+
}
99+
100+
async function collectTasks(is_all = false){
101+
const tsklst = await app.clouddl_tasklist();
102+
const taskIds = [];
103+
104+
for (const t of tsklst.task_info){
105+
const itask_nm = `${t.task_id} : [${statuses[t.status]}] ${t.source_url}`
106+
const itask_id = !is_all ? t.task_id : { name: itask_nm, value: t.task_id }
107+
108+
if(t.status == '1' || is_all){
109+
taskIds.push(itask_id);
110+
}
111+
112+
console.log(itask_nm);
113+
}
114+
115+
return taskIds;
116+
}
117+
118+
async function createTask(upfld, src){
119+
const rUpload1 = await app.clouddl_query_sinfo(upfld + src);
120+
const idxList = [
121+
{ name: 'skip files', value: '-1' },
122+
{ name: 'all files', value: '0' },
123+
];
124+
125+
if(rUpload1.torrent_info){
126+
for (const [index, value] of rUpload1.torrent_info.file_info.entries()) {
127+
idxList.push({ name: String(index+1).padStart(3) + ': ' + value.file_name + ` (${fb2str(Number(value.size))})`, value: String(index+1) });
128+
}
129+
130+
const selectedIndex = await checkbox({
131+
message: 'Select Files',
132+
choices: idxList,
133+
});
134+
135+
if(selectedIndex.length > 0 && !selectedIndex.includes('-1')){
136+
const selFiles = selectedIndex.includes('0') ? Array.from({ length: rUpload1.torrent_info.file_count }, (_, i) => 1 + i).join(',') : selectedIndex.join(',');
137+
console.log('Selected Index:', selFiles);
138+
const rUpload2 = await app.clouddl_add_task(upfld + src, rUpload1.torrent_info.sha1, selFiles, upfld);
139+
console.log(rUpload2);
140+
}
141+
}
142+
}
143+
144+
async function deleteTasks(tIds){
145+
const tskIds = tIds.split(',');
146+
for (const t of tskIds){
147+
const c = await app.clouddl_cancel_task(t);
148+
console.log('cancel task:', t, c);
149+
const d = await app.clouddl_delete_task(t);
150+
console.log('delete task:', t, d);
151+
}
152+
}
153+
154+
const statuses = {
155+
'0': 'DONE! ',
156+
'1': 'DOWNLOAD',
157+
'2': 'SYS ERR ',
158+
'3': 'NO RES ',
159+
'4': 'TIMEOUT ',
160+
'5': 'FAILED ',
161+
'6': 'NO SPACE',
162+
'7': 'EXISTS ',
163+
'8': 'CANCELED',
164+
}
165+
166+
const previousStats = new Map();
167+
function calcSpeed(taskId, finished) {
168+
const now = Date.now();
169+
const prev = previousStats.get(taskId);
170+
if (!prev) {
171+
previousStats.set(taskId, { finished, ts: now, speed: 0 });
172+
return 0;
173+
}
174+
const dt = (now - prev.ts) / 1000;
175+
const df = finished - prev.finished;
176+
let instant = 0;
177+
if (dt > 0 && df >= 0) instant = df / dt;
178+
const smooth = prev.speed * 0.7 + instant * 0.3;
179+
previousStats.set(taskId, { finished, ts: now, speed: smooth });
180+
return smooth;
181+
}
182+
183+
const multibar = new cliProgress.MultiBar(
184+
{
185+
clearOnComplete: false,
186+
hideCursor: true,
187+
format: '{name}: {status} |{bar}| {percentHR}% | {valueHR}/{remainingHR}/{totalHR} | {speedHR}/s | ETA: {etaHR}',
188+
},
189+
cliProgress.Presets.shades_classic
190+
);
191+
192+
const bars = new Map();
193+
function updateBars(data) {
194+
for (const [taskId, t] of Object.entries(data.task_info)) {
195+
const total = Number(t.file_size || 0);
196+
197+
if (!bars.has(taskId)) {
198+
bars.set(taskId, multibar.create(total, 0, {name: taskId}));
199+
}
200+
201+
const finished = Number(t.finished_size || 0);
202+
const speedBps = calcSpeed(taskId, finished);
203+
const remaining = Math.max(0, total - finished);
204+
const etaSec = speedBps > 0 ? remaining / speedBps : NaN;
205+
206+
const percent = total > 0 ? (finished / total) * 100 : 0;
207+
const bar = bars.get(taskId);
208+
209+
if (bar.getTotal() !== total) {
210+
bar.setTotal(total);
211+
}
212+
213+
bar.update(finished, {
214+
status: statuses[t.status],
215+
percentHR: percent.toFixed(2).padStart(6),
216+
valueHR: fb2str(finished).padStart(11),
217+
remainingHR: fb2str(remaining).padStart(11),
218+
totalHR: fb2str(total).padStart(11),
219+
speedHR: fb2str(speedBps).padStart(11),
220+
etaHR: formatEta(etaSec).padStart(9),
221+
});
222+
}
223+
}
224+
225+
function fb2str(bytes) {
226+
return filesize(bytes, {standard: 'iec', round: 2, pad: true, separator: '.'})
227+
}
228+
229+
async function monitor(taskIds) {
230+
while (true) {
231+
try{
232+
const data = await app.clouddl_query_task(1, taskIds);
233+
updateBars(data);
234+
}
235+
catch(e){}
236+
await delay(REFRESH_INTERVAL);
237+
}
238+
}

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "terabox-node",
33
"name_ext": "TeraBox node",
4-
"version": "3.2.2",
4+
"version": "3.3.0",
55
"type": "module",
66
"bin": {
77
"tb-check": "app/app-check.js",
@@ -13,6 +13,7 @@
1313
"tb-getdl-share": "app/app-getdl-share.js",
1414
"tb-mkhash": "app/app-mktbhash.js",
1515
"tb-upload": "app/app-uploader.js",
16+
"tb-clouddl": "app/app-clouddl.js",
1617
"tb-share": "app/app-share.js"
1718
},
1819
"keywords": [
@@ -23,10 +24,11 @@
2324
"description": "NodeJS tool for interacting with the TeraBox cloud service without the need to use the website or app ☁️",
2425
"dependencies": {
2526
"@inquirer/prompts": "^8.3.0",
27+
"cli-progress": "^3.12.0",
2628
"dateformat": "^5.0.3",
2729
"filesize": "^11.0.13",
2830
"qrcode-terminal": "^0.12.0",
29-
"terabox-api": "^2.7.1",
31+
"terabox-api": "^2.8.0",
3032
"terabox-node": "link:",
3133
"tough-cookie": "^6.0.0",
3234
"undici": "^7.22.0",

0 commit comments

Comments
 (0)