pal-explorer-cli 0.4.12 → 0.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -149
- package/bin/pal.js +63 -2
- package/extensions/@palexplorer/analytics/extension.json +20 -1
- package/extensions/@palexplorer/analytics/index.js +19 -9
- package/extensions/@palexplorer/audit/extension.json +14 -0
- package/extensions/@palexplorer/auth-email/extension.json +15 -0
- package/extensions/@palexplorer/auth-oauth/extension.json +15 -0
- package/extensions/@palexplorer/chat/extension.json +14 -0
- package/extensions/@palexplorer/discovery/extension.json +17 -0
- package/extensions/@palexplorer/discovery/index.js +1 -1
- package/extensions/@palexplorer/email-notifications/extension.json +23 -0
- package/extensions/@palexplorer/groups/extension.json +15 -0
- package/extensions/@palexplorer/share-links/extension.json +15 -0
- package/extensions/@palexplorer/sync/extension.json +16 -0
- package/extensions/@palexplorer/user-mgmt/extension.json +15 -0
- package/lib/capabilities.js +24 -24
- package/lib/commands/analytics.js +175 -175
- package/lib/commands/api-keys.js +131 -131
- package/lib/commands/audit.js +235 -235
- package/lib/commands/auth.js +137 -137
- package/lib/commands/backup.js +76 -76
- package/lib/commands/billing.js +148 -148
- package/lib/commands/chat.js +217 -217
- package/lib/commands/cloud-backup.js +231 -231
- package/lib/commands/comment.js +99 -99
- package/lib/commands/completion.js +203 -203
- package/lib/commands/compliance.js +218 -218
- package/lib/commands/config.js +136 -136
- package/lib/commands/connect.js +44 -44
- package/lib/commands/dept.js +294 -294
- package/lib/commands/device.js +146 -146
- package/lib/commands/download.js +240 -226
- package/lib/commands/explorer.js +178 -178
- package/lib/commands/extension.js +1060 -970
- package/lib/commands/favorite.js +90 -90
- package/lib/commands/federation.js +270 -270
- package/lib/commands/file.js +533 -533
- package/lib/commands/group.js +271 -271
- package/lib/commands/gui-share.js +29 -29
- package/lib/commands/init.js +61 -61
- package/lib/commands/invite.js +59 -59
- package/lib/commands/list.js +58 -58
- package/lib/commands/log.js +116 -116
- package/lib/commands/nearby.js +108 -108
- package/lib/commands/network.js +251 -251
- package/lib/commands/notify.js +198 -198
- package/lib/commands/org.js +273 -273
- package/lib/commands/pal.js +403 -180
- package/lib/commands/permissions.js +216 -216
- package/lib/commands/pin.js +97 -97
- package/lib/commands/protocol.js +357 -357
- package/lib/commands/rbac.js +147 -147
- package/lib/commands/recover.js +36 -36
- package/lib/commands/register.js +171 -171
- package/lib/commands/relay.js +131 -131
- package/lib/commands/remote.js +368 -368
- package/lib/commands/revoke.js +50 -50
- package/lib/commands/scanner.js +280 -280
- package/lib/commands/schedule.js +344 -344
- package/lib/commands/scim.js +203 -203
- package/lib/commands/search.js +181 -181
- package/lib/commands/serve.js +438 -438
- package/lib/commands/server.js +350 -350
- package/lib/commands/share-link.js +199 -199
- package/lib/commands/share.js +336 -323
- package/lib/commands/sso.js +200 -200
- package/lib/commands/status.js +145 -145
- package/lib/commands/stream.js +562 -562
- package/lib/commands/su.js +187 -187
- package/lib/commands/sync.js +979 -979
- package/lib/commands/transfers.js +152 -152
- package/lib/commands/uninstall.js +188 -188
- package/lib/commands/update.js +204 -204
- package/lib/commands/user.js +276 -276
- package/lib/commands/vfs.js +84 -84
- package/lib/commands/web-login.js +79 -79
- package/lib/commands/web.js +52 -52
- package/lib/commands/webhook.js +180 -180
- package/lib/commands/whoami.js +59 -59
- package/lib/commands/workspace.js +121 -121
- package/lib/core/billing.js +16 -5
- package/lib/core/dhtDiscovery.js +9 -2
- package/lib/core/discoveryClient.js +13 -7
- package/lib/core/extensions.js +142 -1
- package/lib/core/identity.js +33 -2
- package/lib/core/imageProcessor.js +109 -0
- package/lib/core/imageTorrent.js +167 -0
- package/lib/core/permissions.js +1 -1
- package/lib/core/pro.js +11 -4
- package/lib/core/serverList.js +4 -1
- package/lib/core/shares.js +12 -1
- package/lib/core/signalingServer.js +14 -2
- package/lib/core/su.js +1 -1
- package/lib/core/users.js +1 -1
- package/lib/protocol/messages.js +12 -3
- package/lib/utils/explorer.js +1 -1
- package/lib/utils/help.js +357 -357
- package/lib/utils/torrent.js +1 -0
- package/package.json +4 -3
package/lib/commands/download.js
CHANGED
|
@@ -1,226 +1,240 @@
|
|
|
1
|
-
import WebTorrent from 'webtorrent';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { trackTransfer, getTransfers, completeTransfer } from '../core/transfers.js';
|
|
6
|
-
import { getIdentity } from '../core/identity.js';
|
|
7
|
-
import { decryptFromDownload, getDecryptedDownloadDir } from '../crypto/streamEncryption.js';
|
|
8
|
-
import logger from '../utils/logger.js';
|
|
9
|
-
import { getNearbyPeers, startMdns, isRunning as isMdnsRunning } from '../core/mdnsService.js';
|
|
10
|
-
import { injectLanPeers, tryLanHttpDownload } from '../utils/torrent.js';
|
|
11
|
-
import config from '../utils/config.js';
|
|
12
|
-
import { getDownloadDir } from '../utils/downloadDir.js';
|
|
13
|
-
import fs from 'fs';
|
|
14
|
-
|
|
15
|
-
function startMdnsIfNeeded() {
|
|
16
|
-
if (isMdnsRunning()) return;
|
|
17
|
-
const identity = config.get('identity');
|
|
18
|
-
if (identity?.publicKey) {
|
|
19
|
-
const pidPath = path.join(path.dirname(config.path), 'serve.pid');
|
|
20
|
-
const daemonRunning = fs.existsSync(pidPath);
|
|
21
|
-
startMdns(identity, { advertise: !daemonRunning });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default function downloadCommand(program) {
|
|
26
|
-
program
|
|
27
|
-
.command('download <magnet> [moreMagnets...]')
|
|
28
|
-
.description('download file(s) from Magnet Link(s)')
|
|
29
|
-
.option('--key <encryptedShareKey>', 'Encrypted share key for decryption (for private shares)')
|
|
30
|
-
.option('--lan-only', 'Only download from LAN peers (skip public trackers)')
|
|
31
|
-
.option('--share-name <name>', 'Share name for LAN HTTP download')
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
$
|
|
37
|
-
$
|
|
38
|
-
$
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
1
|
+
import WebTorrent from 'webtorrent';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { trackTransfer, getTransfers, completeTransfer } from '../core/transfers.js';
|
|
6
|
+
import { getIdentity } from '../core/identity.js';
|
|
7
|
+
import { decryptFromDownload, getDecryptedDownloadDir } from '../crypto/streamEncryption.js';
|
|
8
|
+
import logger from '../utils/logger.js';
|
|
9
|
+
import { getNearbyPeers, startMdns, isRunning as isMdnsRunning } from '../core/mdnsService.js';
|
|
10
|
+
import { injectLanPeers, tryLanHttpDownload } from '../utils/torrent.js';
|
|
11
|
+
import config from '../utils/config.js';
|
|
12
|
+
import { getDownloadDir } from '../utils/downloadDir.js';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
|
|
15
|
+
function startMdnsIfNeeded() {
|
|
16
|
+
if (isMdnsRunning()) return;
|
|
17
|
+
const identity = config.get('identity');
|
|
18
|
+
if (identity?.publicKey) {
|
|
19
|
+
const pidPath = path.join(path.dirname(config.path), 'serve.pid');
|
|
20
|
+
const daemonRunning = fs.existsSync(pidPath);
|
|
21
|
+
startMdns(identity, { advertise: !daemonRunning });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function downloadCommand(program) {
|
|
26
|
+
program
|
|
27
|
+
.command('download <magnet> [moreMagnets...]')
|
|
28
|
+
.description('download file(s) from Magnet Link(s)')
|
|
29
|
+
.option('--key <encryptedShareKey>', 'Encrypted share key for decryption (for private shares)')
|
|
30
|
+
.option('--lan-only', 'Only download from LAN peers (skip public trackers)')
|
|
31
|
+
.option('--share-name <name>', 'Share name for LAN HTTP download')
|
|
32
|
+
.option('-o, --output <dir>', 'Output directory (overrides default)')
|
|
33
|
+
.option('-p, --peer <addr>', 'Direct peer address (ip:port) to add')
|
|
34
|
+
.addHelpText('after', `
|
|
35
|
+
Examples:
|
|
36
|
+
$ pal download "magnet:?xt=urn:btih:..." Download a public share
|
|
37
|
+
$ pal download "magnet:?xt=..." --key abc123 Download encrypted share
|
|
38
|
+
$ pal download "magnet:?xt=..." -p 1.2.3.4:6881 Direct connection to peer
|
|
39
|
+
$ pal download "magnet:?xt=..." --lan-only LAN peers only
|
|
40
|
+
$ pal download "magnet:?xt=..." --share-name Photos Try direct HTTP from LAN first
|
|
41
|
+
`)
|
|
42
|
+
.action(async (magnet, moreMagnets, opts) => {
|
|
43
|
+
// Start mDNS to discover LAN peers
|
|
44
|
+
startMdnsIfNeeded();
|
|
45
|
+
const lanPeers = getNearbyPeers();
|
|
46
|
+
if (lanPeers.length > 0) {
|
|
47
|
+
logger.info(`Found ${lanPeers.length} LAN peer(s) via mDNS`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const allMagnets = [magnet, ...(moreMagnets || [])];
|
|
51
|
+
|
|
52
|
+
if (allMagnets.length > 1) {
|
|
53
|
+
console.log(chalk.blue(`Batch downloading ${allMagnets.length} items...`));
|
|
54
|
+
const client = new WebTorrent();
|
|
55
|
+
let completed = 0;
|
|
56
|
+
let failed = 0;
|
|
57
|
+
|
|
58
|
+
for (const m of allMagnets) {
|
|
59
|
+
if (!m.startsWith('magnet:?') && !/^[0-9a-fA-F]{40}$/.test(m)) {
|
|
60
|
+
console.error(chalk.red(`Skipping invalid magnet: ${m.slice(0, 50)}...`));
|
|
61
|
+
failed++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const dlDir = opts.output || getDownloadDir();
|
|
67
|
+
if (!fs.existsSync(dlDir)) fs.mkdirSync(dlDir, { recursive: true });
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
const timeout = setTimeout(() => reject(new Error('Peer timeout (60s)')), 60000);
|
|
70
|
+
const addOpts = { path: dlDir };
|
|
71
|
+
const t = client.add(opts.lanOnly ? m : m, addOpts, (torrent) => {
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
console.log(chalk.cyan(` Downloading: ${torrent.name}`));
|
|
74
|
+
trackTransfer(m, torrent.name, dlDir, null);
|
|
75
|
+
torrent.on('done', () => {
|
|
76
|
+
console.log(chalk.green(` ✔ ${torrent.name}`));
|
|
77
|
+
completeTransfer(m);
|
|
78
|
+
completed++;
|
|
79
|
+
resolve();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Inject LAN peers and manual peer
|
|
84
|
+
if (lanPeers.length > 0) injectLanPeers(t, lanPeers);
|
|
85
|
+
if (opts.peer) {
|
|
86
|
+
try { t.addPeer(opts.peer); } catch (e) { console.error(chalk.yellow(` Warning: Could not add peer ${opts.peer}: ${e.message}`)); }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
client.on('error', (err) => { clearTimeout(timeout); reject(err); });
|
|
90
|
+
});
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error(chalk.red(` ✖ Failed: ${err.message}`));
|
|
93
|
+
failed++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('');
|
|
98
|
+
console.log(chalk.green(`Completed: ${completed}`) + (failed > 0 ? chalk.red(` Failed: ${failed}`) : ''));
|
|
99
|
+
client.destroy();
|
|
100
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Single download
|
|
105
|
+
if (!magnet.startsWith('magnet:?') && !/^[0-9a-fA-F]{40}$/.test(magnet)) {
|
|
106
|
+
console.error(chalk.red('Invalid magnet URI. Must start with "magnet:?" or be a 40-character hex infohash.'));
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const dlDir = opts.output || getDownloadDir();
|
|
112
|
+
if (!fs.existsSync(dlDir)) fs.mkdirSync(dlDir, { recursive: true });
|
|
113
|
+
|
|
114
|
+
// Strategy 1: Try direct HTTP download from LAN peer (fastest)
|
|
115
|
+
if (opts.shareName && lanPeers.length > 0) {
|
|
116
|
+
console.log(chalk.blue(`Trying direct LAN download from ${lanPeers.length} nearby peer(s)...`));
|
|
117
|
+
const spinner = ora('Downloading via LAN HTTP...').start();
|
|
118
|
+
try {
|
|
119
|
+
const result = await tryLanHttpDownload(lanPeers, opts.shareName, dlDir, {
|
|
120
|
+
onProgress: (pct, fileName) => {
|
|
121
|
+
spinner.text = `LAN download... ${(pct * 100).toFixed(1)}% (${fileName})`;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
if (result) {
|
|
125
|
+
spinner.succeed(chalk.green(`LAN download complete from ${result.peerIp} (${result.files.length} files, ${(result.totalBytes / 1024 / 1024).toFixed(2)} MB)`));
|
|
126
|
+
trackTransfer(magnet, opts.shareName, dlDir, opts.key || null);
|
|
127
|
+
completeTransfer(magnet);
|
|
128
|
+
process.exit(0);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
spinner.info(chalk.dim('No LAN peer serving this share. Falling back to BitTorrent...'));
|
|
132
|
+
} catch (err) {
|
|
133
|
+
spinner.info(chalk.dim(`LAN download failed: ${err.message}. Falling back to BitTorrent...`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (opts.lanOnly && lanPeers.length === 0) {
|
|
138
|
+
console.error(chalk.red('No LAN peers found. Remove --lan-only to use public trackers.'));
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Strategy 2: WebTorrent with LAN peer injection
|
|
144
|
+
logger.info('Starting download', { magnet: magnet.slice(0, 40) });
|
|
145
|
+
console.log(chalk.blue('Connecting to peers...'));
|
|
146
|
+
if (lanPeers.length > 0) {
|
|
147
|
+
console.log(chalk.cyan(` ${lanPeers.length} LAN peer(s) will be injected for faster discovery`));
|
|
148
|
+
}
|
|
149
|
+
if (opts.peer) {
|
|
150
|
+
console.log(chalk.cyan(` Manual peer ${opts.peer} will be injected`));
|
|
151
|
+
}
|
|
152
|
+
const spinner = ora('Finding peers...').start();
|
|
153
|
+
|
|
154
|
+
const client = new WebTorrent();
|
|
155
|
+
|
|
156
|
+
const peerTimeout = setTimeout(() => {
|
|
157
|
+
const torrents = client.torrents;
|
|
158
|
+
if (torrents.length > 0 && torrents[0].numPeers === 0) {
|
|
159
|
+
spinner.warn(chalk.yellow('⚠ No peers found after 120 seconds. Check that the share is being seeded.'));
|
|
160
|
+
client.destroy();
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
}
|
|
163
|
+
}, 120000);
|
|
164
|
+
|
|
165
|
+
const addOpts = { path: dlDir };
|
|
166
|
+
const torrentSrc = opts.lanOnly ? magnet : magnet;
|
|
167
|
+
|
|
168
|
+
client.add(torrentSrc, addOpts, (torrent) => {
|
|
169
|
+
clearTimeout(peerTimeout);
|
|
170
|
+
spinner.succeed(chalk.green(`Connected! Downloading: ${torrent.name}`));
|
|
171
|
+
|
|
172
|
+
// Track transfer, storing the encrypted key if provided
|
|
173
|
+
trackTransfer(magnet, torrent.name, dlDir, opts.key || null);
|
|
174
|
+
|
|
175
|
+
const progressSpinner = ora('Downloading... 0%').start();
|
|
176
|
+
|
|
177
|
+
torrent.on('download', (bytes) => {
|
|
178
|
+
const progress = (torrent.progress * 100).toFixed(1);
|
|
179
|
+
progressSpinner.text = `Downloading... ${progress}% (${(torrent.downloaded / 1024 / 1024).toFixed(2)} MB / ${(torrent.length / 1024 / 1024).toFixed(2)} MB)`;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
torrent.on('done', async () => {
|
|
183
|
+
logger.info(`Download complete: ${torrent.name}`, { size: torrent.length });
|
|
184
|
+
progressSpinner.succeed(chalk.green('Download Complete!'));
|
|
185
|
+
const downloadedDir = path.join(dlDir, torrent.name);
|
|
186
|
+
console.log(chalk.cyan(`File saved to: ${downloadedDir}`));
|
|
187
|
+
|
|
188
|
+
// Attempt decryption if an encrypted share key was provided
|
|
189
|
+
const transfers = getTransfers();
|
|
190
|
+
const thisTransfer = transfers.find(t => t.magnet === magnet);
|
|
191
|
+
if (thisTransfer?.encryptedShareKey) {
|
|
192
|
+
const identity = await getIdentity();
|
|
193
|
+
if (identity?.publicKey && identity?.privateKey) {
|
|
194
|
+
try {
|
|
195
|
+
const { decryptShareKey } = await import('../crypto/shareEncryption.js');
|
|
196
|
+
const shareKey = decryptShareKey(thisTransfer.encryptedShareKey, identity.publicKey, identity.privateKey);
|
|
197
|
+
const shareName = thisTransfer.name || 'download';
|
|
198
|
+
const outDir = getDecryptedDownloadDir(shareName);
|
|
199
|
+
console.log(chalk.blue('Decrypting E2EE files...'));
|
|
200
|
+
decryptFromDownload(downloadedDir, outDir, shareKey);
|
|
201
|
+
console.log(chalk.green(`Decrypted files saved to: ${outDir}`));
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.log(chalk.yellow(`Warning: Could not decrypt files — ${e.message}`));
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
console.log(chalk.yellow('Warning: No identity found — skipping decryption. Run `pal identity` to set up.'));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
completeTransfer(magnet);
|
|
211
|
+
client.destroy();
|
|
212
|
+
process.exit(0);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Inject LAN peers into the torrent right after adding
|
|
217
|
+
const currentTorrent = client.torrents[client.torrents.length - 1];
|
|
218
|
+
if (currentTorrent) {
|
|
219
|
+
if (lanPeers.length > 0) {
|
|
220
|
+
const injected = injectLanPeers(currentTorrent, lanPeers);
|
|
221
|
+
if (injected > 0) logger.info(`Injected ${injected} LAN peer(s) into torrent`);
|
|
222
|
+
}
|
|
223
|
+
if (opts.peer) {
|
|
224
|
+
try {
|
|
225
|
+
currentTorrent.addPeer(opts.peer);
|
|
226
|
+
logger.info(`Injected manual peer ${opts.peer} into torrent`);
|
|
227
|
+
} catch (e) {
|
|
228
|
+
console.error(chalk.yellow(`Warning: Could not add peer ${opts.peer}: ${e.message}`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
client.on('error', (err) => {
|
|
234
|
+
logger.error(`Download failed: ${err.message}`, { magnet: magnet.slice(0, 40) });
|
|
235
|
+
spinner.fail(chalk.red('Download Failed'));
|
|
236
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
237
|
+
process.exit(1);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|