@iksdev/shard-cli 0.1.46 → 0.1.48
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/bin/shard.js +264 -186
- package/package.json +1 -1
package/bin/shard.js
CHANGED
|
@@ -106,7 +106,7 @@ Options avancees:
|
|
|
106
106
|
whoami [--server <url>]
|
|
107
107
|
account [--server <url>]
|
|
108
108
|
sync <dossier> [--server <url>] [--dry-run] [--force] [--once] [--interval-ms <n>]
|
|
109
|
-
share <fichier> [--server <url>] [--limits <n>] [--temps <jours>]
|
|
109
|
+
share <fichier> [--server <url>] [--limits <n>] [--temps <jours>]
|
|
110
110
|
|
|
111
111
|
Exemples:
|
|
112
112
|
shard login
|
|
@@ -114,7 +114,6 @@ Exemples:
|
|
|
114
114
|
shard sync ./MonDossier
|
|
115
115
|
shard sync ./MonDossier --once
|
|
116
116
|
shard share ./MonFichier.mp4
|
|
117
|
-
shard share ./MonFichier.mp4 --upload
|
|
118
117
|
|
|
119
118
|
Serveur par defaut: https://shard-0ow4.onrender.com
|
|
120
119
|
`);
|
|
@@ -473,14 +472,6 @@ async function uploadOneFile(server, token, file) {
|
|
|
473
472
|
});
|
|
474
473
|
}
|
|
475
474
|
|
|
476
|
-
async function findRemoteFileByNameAndSize(server, token, fileName, fileSize) {
|
|
477
|
-
const data = await httpJson(`${server}/api/files?limit=100&offset=0&sort=created_at&order=desc&search=${encodeURIComponent(fileName)}`, {
|
|
478
|
-
method: 'GET',
|
|
479
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
480
|
-
});
|
|
481
|
-
const rows = Array.isArray(data.files) ? data.files : [];
|
|
482
|
-
return rows.find((row) => row.original_name === fileName && Number(row.file_size || 0) === Number(fileSize || 0)) || null;
|
|
483
|
-
}
|
|
484
475
|
|
|
485
476
|
function parseOptionalPositiveInt(raw, flagName) {
|
|
486
477
|
if (raw === undefined || raw === null) return undefined;
|
|
@@ -520,7 +511,7 @@ async function shareFile(positionals, flags) {
|
|
|
520
511
|
target = await askText('Fichier a partager');
|
|
521
512
|
}
|
|
522
513
|
if (!target) {
|
|
523
|
-
throw new Error('Usage: shard share <file> [--server <url>] [--limits <n>] [--temps <jours>]
|
|
514
|
+
throw new Error('Usage: shard share <file> [--server <url>] [--limits <n>] [--temps <jours>]');
|
|
524
515
|
}
|
|
525
516
|
|
|
526
517
|
const absPath = path.resolve(process.cwd(), target);
|
|
@@ -534,7 +525,7 @@ async function shareFile(positionals, flags) {
|
|
|
534
525
|
|
|
535
526
|
const limits = parseOptionalPositiveInt(flags.limits, '--limits');
|
|
536
527
|
const temps = parseOptionalPositiveInt(flags.temps, '--temps');
|
|
537
|
-
const localMode =
|
|
528
|
+
const localMode = true; // transfert direct uniquement — upload serveur désactivé
|
|
538
529
|
|
|
539
530
|
const config = await readConfig();
|
|
540
531
|
const server = getServer(flags, config);
|
|
@@ -710,25 +701,74 @@ async function shareFile(positionals, flags) {
|
|
|
710
701
|
const n = Number(share.id);
|
|
711
702
|
if (Number.isFinite(n) && n > 0) createdShareId = n;
|
|
712
703
|
}
|
|
713
|
-
console.log(`Partage relay cree pour: ${fileName}`);
|
|
714
|
-
if (share.url) console.log(`URL Shard: ${share.url}`);
|
|
715
|
-
if (share.token) console.log(`Token: ${share.token}`);
|
|
716
|
-
console.log(`Relay client id: ${relayClientId}`);
|
|
717
|
-
console.log(`Relay file id: ${relayFileId}`);
|
|
718
|
-
console.log(`Limite downloads: ${limits && limits > 0 ? limits : 'illimitee'}`);
|
|
719
|
-
console.log(`Expiration: ${temps && temps > 0 ? `${temps} jour(s)` : 'aucune'}`);
|
|
720
|
-
console.log('Laisse cette commande ouverte tant que le partage doit fonctionner.');
|
|
721
704
|
|
|
705
|
+
// ── Affichage statut partage actif ─────────────────────────────────────
|
|
706
|
+
const WS = 58;
|
|
707
|
+
const hr = (l, m, r, col) => `${c(col, l + m.repeat(WS) + r)}`;
|
|
708
|
+
const rowS = (content, col) => {
|
|
709
|
+
const vis = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
710
|
+
const pad = WS - vis.length;
|
|
711
|
+
return `${c(col, '│')}${content}${' '.repeat(Math.max(0, pad))}${c(col, '│')}`;
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
process.stdout.write('\x1Bc');
|
|
715
|
+
console.log('');
|
|
716
|
+
console.log(hr('┌', '─', '┐', '\x1b[32m'));
|
|
717
|
+
const titleShare = '\x1b[1m\x1b[32m' + ' PARTAGE ACTIF' + '\x1b[0m';
|
|
718
|
+
console.log(rowS(titleShare + ' '.repeat(WS - 15), '\x1b[32m'));
|
|
719
|
+
console.log(hr('├', '─', '┤', '\x1b[32m'));
|
|
720
|
+
console.log(rowS('', '\x1b[32m'));
|
|
721
|
+
if (share.url) {
|
|
722
|
+
const urlLabel = ' \x1b[90mURL \x1b[0m \x1b[1m\x1b[97m' + share.url + '\x1b[0m';
|
|
723
|
+
console.log(rowS(urlLabel, '\x1b[32m'));
|
|
724
|
+
}
|
|
725
|
+
if (share.token) {
|
|
726
|
+
const tokLabel = ' \x1b[90mToken \x1b[0m \x1b[36m' + share.token + '\x1b[0m';
|
|
727
|
+
console.log(rowS(tokLabel, '\x1b[32m'));
|
|
728
|
+
}
|
|
729
|
+
const fileLabel = ' \x1b[90mFichier \x1b[0m \x1b[97m' + fileName + '\x1b[0m';
|
|
730
|
+
console.log(rowS(fileLabel, '\x1b[32m'));
|
|
731
|
+
const dlLabel = ' \x1b[90mLimite \x1b[0m \x1b[97m' + (limits && limits > 0 ? String(limits) + ' telechargement(s)' : 'illimite') + '\x1b[0m';
|
|
732
|
+
console.log(rowS(dlLabel, '\x1b[32m'));
|
|
733
|
+
const expLabel = ' \x1b[90mExpire \x1b[0m \x1b[97m' + (temps && temps > 0 ? `dans ${temps} jour(s)` : 'jamais') + '\x1b[0m';
|
|
734
|
+
console.log(rowS(expLabel, '\x1b[32m'));
|
|
735
|
+
console.log(rowS('', '\x1b[32m'));
|
|
736
|
+
console.log(hr('├', '─', '┤', '\x1b[90m'));
|
|
737
|
+
const hint = ' \x1b[90mTape \x1b[0m\x1b[1m\x1b[97m q \x1b[0m\x1b[90m + Entree pour cloture le partage et revenir au menu\x1b[0m';
|
|
738
|
+
console.log(rowS(hint, '\x1b[90m'));
|
|
739
|
+
console.log(hr('└', '─', '┘', '\x1b[90m'));
|
|
740
|
+
console.log('');
|
|
741
|
+
|
|
742
|
+
// Attend soit la fermeture du socket, soit que l'utilisateur tape 'q'
|
|
722
743
|
await new Promise((resolve) => {
|
|
723
|
-
|
|
744
|
+
// Fermeture côté serveur
|
|
745
|
+
relaySocket.on('close', () => resolve('closed'));
|
|
746
|
+
|
|
747
|
+
// Écoute clavier : 'q' + Entrée = clôture manuelle
|
|
748
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
749
|
+
const onLine = (line) => {
|
|
750
|
+
if (line.trim().toLowerCase() === 'q') {
|
|
751
|
+
rl.close();
|
|
752
|
+
resolve('quit');
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
rl.on('line', onLine);
|
|
756
|
+
// Nettoyer si le socket se ferme en premier
|
|
757
|
+
relaySocket.once('close', () => {
|
|
758
|
+
rl.removeListener('line', onLine);
|
|
759
|
+
rl.close();
|
|
760
|
+
});
|
|
724
761
|
});
|
|
762
|
+
|
|
725
763
|
if (heartbeat) {
|
|
726
764
|
clearInterval(heartbeat);
|
|
727
765
|
heartbeat = null;
|
|
728
766
|
}
|
|
767
|
+
await revokeCreatedShare();
|
|
729
768
|
for (const sig of stopSignals) {
|
|
730
769
|
process.off(sig, stopRelayShare);
|
|
731
770
|
}
|
|
771
|
+
closeRelay();
|
|
732
772
|
return;
|
|
733
773
|
} catch (error) {
|
|
734
774
|
if (heartbeat) {
|
|
@@ -743,43 +783,6 @@ async function shareFile(positionals, flags) {
|
|
|
743
783
|
throw error;
|
|
744
784
|
}
|
|
745
785
|
}
|
|
746
|
-
|
|
747
|
-
let remote = await findRemoteFileByNameAndSize(server, token, fileName, st.size);
|
|
748
|
-
let fileId = remote?.id || null;
|
|
749
|
-
|
|
750
|
-
if (!fileId) {
|
|
751
|
-
console.log(`Upload necessaire: ${fileName} (${formatBytes(st.size)})`);
|
|
752
|
-
const uploaded = await uploadOneFile(server, token, {
|
|
753
|
-
absPath,
|
|
754
|
-
relPath: fileName,
|
|
755
|
-
size: st.size,
|
|
756
|
-
mtimeMs: Math.round(st.mtimeMs)
|
|
757
|
-
});
|
|
758
|
-
fileId = uploaded?.file?.id;
|
|
759
|
-
if (!fileId) {
|
|
760
|
-
throw new Error('Upload reussi mais ID fichier manquant');
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const payload = { fileId };
|
|
765
|
-
if (limits !== undefined && limits > 0) payload.maxDownloads = limits;
|
|
766
|
-
if (temps !== undefined && temps > 0) payload.expiresInDays = temps;
|
|
767
|
-
|
|
768
|
-
const created = await httpJson(`${server}/api/share/create`, {
|
|
769
|
-
method: 'POST',
|
|
770
|
-
headers: {
|
|
771
|
-
Authorization: `Bearer ${token}`,
|
|
772
|
-
'Content-Type': 'application/json'
|
|
773
|
-
},
|
|
774
|
-
body: JSON.stringify(payload)
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
const share = created?.share || {};
|
|
778
|
-
console.log(`Partage cree pour: ${fileName}`);
|
|
779
|
-
if (share.url) console.log(`URL: ${share.url}`);
|
|
780
|
-
if (share.token) console.log(`Token: ${share.token}`);
|
|
781
|
-
console.log(`Limite downloads: ${limits && limits > 0 ? limits : 'illimitee'}`);
|
|
782
|
-
console.log(`Expiration: ${temps && temps > 0 ? `${temps} jour(s)` : 'aucune'}`);
|
|
783
786
|
}
|
|
784
787
|
|
|
785
788
|
function fileListToMap(files) {
|
|
@@ -1144,114 +1147,164 @@ async function syncFolder(positionals, flags) {
|
|
|
1144
1147
|
}
|
|
1145
1148
|
}
|
|
1146
1149
|
|
|
1150
|
+
function printPlanLimitBox(error) {
|
|
1151
|
+
const data = error?.data || {};
|
|
1152
|
+
const plan = String(data.plan || '').toUpperCase() || 'FREE';
|
|
1153
|
+
const maxFileSize = Number(data.maxFileSize || 0);
|
|
1154
|
+
const fileSize = Number(data.fileSize || 0);
|
|
1155
|
+
const maxLabel = maxFileSize > 0 ? formatBytes(maxFileSize) : '?';
|
|
1156
|
+
const fileLabel = fileSize > 0 ? formatBytes(fileSize) : '?';
|
|
1157
|
+
const lines = [
|
|
1158
|
+
'',
|
|
1159
|
+
`${c(C.bYellow, '╔════════════════════════════════════════════════════╗')}`,
|
|
1160
|
+
`${c(C.bYellow, '║')} ${c(C.bRed, '⚠')} ${c(C.bWhite, `Limite du plan ${plan}`)}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
|
|
1161
|
+
`${c(C.bYellow, '╠════════════════════════════════════════════════════╣')}`,
|
|
1162
|
+
`${c(C.bYellow, '║')} ${c(C.gray, 'Taille fichier')} : ${c(C.white, fileLabel)}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
|
|
1163
|
+
`${c(C.bYellow, '║')} ${c(C.gray, 'Limite max')} : ${c(C.white, maxLabel + ' / fichier')}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
|
|
1164
|
+
`${c(C.bYellow, '║')} ${c(C.bYellow, '║')}`,
|
|
1165
|
+
`${c(C.bYellow, '║')} ${c(C.gray, 'Passe à une offre supérieure pour continuer.')} ${c(C.bYellow, '║')}`,
|
|
1166
|
+
`${c(C.bYellow, '╚════════════════════════════════════════════════════╝')}`,
|
|
1167
|
+
''
|
|
1168
|
+
];
|
|
1169
|
+
console.error(lines.join('\n'));
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1147
1172
|
// ─── Couleurs ANSI ───────────────────────────────────────────────────────────
|
|
1148
1173
|
|
|
1149
1174
|
const C = {
|
|
1150
|
-
reset:
|
|
1151
|
-
bold:
|
|
1152
|
-
dim:
|
|
1153
|
-
cyan:
|
|
1154
|
-
blue:
|
|
1155
|
-
green:
|
|
1156
|
-
yellow:
|
|
1157
|
-
red:
|
|
1158
|
-
white:
|
|
1159
|
-
gray:
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1175
|
+
reset: '\x1b[0m',
|
|
1176
|
+
bold: '\x1b[1m',
|
|
1177
|
+
dim: '\x1b[2m',
|
|
1178
|
+
cyan: '\x1b[36m',
|
|
1179
|
+
blue: '\x1b[34m',
|
|
1180
|
+
green: '\x1b[32m',
|
|
1181
|
+
yellow: '\x1b[33m',
|
|
1182
|
+
red: '\x1b[31m',
|
|
1183
|
+
white: '\x1b[97m',
|
|
1184
|
+
gray: '\x1b[90m',
|
|
1185
|
+
bgCyan: '\x1b[46m',
|
|
1186
|
+
bgGray: '\x1b[100m',
|
|
1187
|
+
black: '\x1b[30m',
|
|
1188
|
+
bCyan: '\x1b[1m\x1b[36m',
|
|
1189
|
+
bWhite: '\x1b[1m\x1b[97m',
|
|
1190
|
+
bGreen: '\x1b[1m\x1b[32m',
|
|
1191
|
+
bRed: '\x1b[1m\x1b[31m',
|
|
1192
|
+
bYellow: '\x1b[1m\x1b[33m',
|
|
1193
|
+
bBlue: '\x1b[1m\x1b[34m',
|
|
1166
1194
|
};
|
|
1167
1195
|
|
|
1168
1196
|
function c(color, text) { return `${color}${text}${C.reset}`; }
|
|
1197
|
+
function strip(s) { return s.replace(/\x1b\[[0-9;]*m/g, ''); }
|
|
1169
1198
|
|
|
1170
1199
|
// ─── Panel interactif ────────────────────────────────────────────────────────
|
|
1171
1200
|
|
|
1172
|
-
const W =
|
|
1201
|
+
const W = 58; // largeur intérieure (sans les bordures)
|
|
1173
1202
|
|
|
1174
1203
|
function clearScreen() {
|
|
1175
1204
|
process.stdout.write('\x1Bc');
|
|
1176
1205
|
}
|
|
1177
1206
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1207
|
+
// Ligne de bordure
|
|
1208
|
+
function hr(left = '├', mid = '─', right = '┤', color = C.gray) {
|
|
1209
|
+
return c(color, `${left}${mid.repeat(W)}${right}`);
|
|
1180
1210
|
}
|
|
1181
1211
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1212
|
+
// Ligne avec bordures latérales, contenu centré ou libre
|
|
1213
|
+
function row(content = '', color = C.gray) {
|
|
1214
|
+
const vis = strip(content);
|
|
1215
|
+
const pad = W - vis.length;
|
|
1216
|
+
return `${c(color, '│')}${content}${' '.repeat(Math.max(0, pad))}${c(color, '│')}`;
|
|
1186
1217
|
}
|
|
1187
1218
|
|
|
1188
|
-
function
|
|
1189
|
-
const
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1192
|
-
return ' '.repeat(
|
|
1219
|
+
function rowCentered(content, color = C.gray) {
|
|
1220
|
+
const vis = strip(content);
|
|
1221
|
+
const l = Math.floor((W - vis.length) / 2);
|
|
1222
|
+
const r = W - vis.length - l;
|
|
1223
|
+
return row(' '.repeat(l) + content + ' '.repeat(r), color);
|
|
1193
1224
|
}
|
|
1194
1225
|
|
|
1195
1226
|
function printPanel(username, plan, statusMsg) {
|
|
1196
1227
|
const now = new Date();
|
|
1197
1228
|
const timeStr = now.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
1229
|
+
const planLabel = plan ? plan.toUpperCase() : 'FREE';
|
|
1230
|
+
const planColor = planLabel === 'FREE' ? C.gray : C.bYellow;
|
|
1198
1231
|
|
|
1232
|
+
// ── Titre ──────────────────────────────────────────────────────────────────
|
|
1199
1233
|
console.log('');
|
|
1200
|
-
console.log(
|
|
1201
|
-
|
|
1202
|
-
|
|
1234
|
+
console.log(hr('┌', '─', '┐', C.cyan));
|
|
1235
|
+
|
|
1236
|
+
const title = c(C.bCyan, 'S H A R D');
|
|
1237
|
+
console.log(rowCentered(title, C.cyan));
|
|
1238
|
+
|
|
1239
|
+
console.log(hr('├', '─', '┤', C.cyan));
|
|
1203
1240
|
|
|
1204
|
-
// Infos
|
|
1241
|
+
// ── Infos session ──────────────────────────────────────────────────────────
|
|
1205
1242
|
if (username) {
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1243
|
+
const userPart = ` ${c(C.gray, 'Utilisateur')} ${c(C.bWhite, username)}`;
|
|
1244
|
+
const planPart = ` ${c(C.gray, 'Plan')} ${c(planColor, planLabel)} `;
|
|
1245
|
+
const userVis = strip(userPart);
|
|
1246
|
+
const planVis = strip(planPart);
|
|
1247
|
+
const spacer = W - userVis.length - planVis.length;
|
|
1248
|
+
const infoLine = userPart + ' '.repeat(Math.max(1, spacer)) + planPart;
|
|
1249
|
+
console.log(row(infoLine, C.cyan));
|
|
1250
|
+
console.log(hr('├', '─', '┤', C.cyan));
|
|
1208
1251
|
}
|
|
1209
|
-
console.log(`${c(C.cyan, '╠' + '═'.repeat(W - 2) + '╣')}`);
|
|
1210
1252
|
|
|
1211
|
-
// Menu
|
|
1253
|
+
// ── Menu ───────────────────────────────────────────────────────────────────
|
|
1212
1254
|
const entries = [
|
|
1213
|
-
{ key: '1',
|
|
1214
|
-
{ key: '2',
|
|
1215
|
-
{ key: '3',
|
|
1216
|
-
{ key: '4',
|
|
1217
|
-
{ key: '5',
|
|
1218
|
-
{ key: '6',
|
|
1255
|
+
{ key: '1', tag: 'SHARE', label: 'share', desc: 'Partager un fichier via relay' },
|
|
1256
|
+
{ key: '2', tag: 'SYNC', label: 'sync', desc: 'Synchroniser un dossier local' },
|
|
1257
|
+
{ key: '3', tag: 'ACCOUNT', label: 'account', desc: 'Informations du compte' },
|
|
1258
|
+
{ key: '4', tag: 'WHOAMI', label: 'whoami', desc: 'Utilisateur connecté' },
|
|
1259
|
+
{ key: '5', tag: 'CONFIG', label: 'config', desc: 'Configuration serveur' },
|
|
1260
|
+
{ key: '6', tag: 'LOGOUT', label: 'logout', desc: 'Se déconnecter et quitter' },
|
|
1219
1261
|
];
|
|
1220
1262
|
|
|
1221
|
-
console.log(
|
|
1263
|
+
console.log(row('', C.cyan));
|
|
1222
1264
|
for (const e of entries) {
|
|
1223
|
-
const
|
|
1224
|
-
const
|
|
1225
|
-
const
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1265
|
+
const num = ` ${c(C.bgGray + C.bWhite, ` ${e.key} `)}`;
|
|
1266
|
+
const lbl = ` ${c(C.bWhite, e.label.padEnd(9))}`;
|
|
1267
|
+
const sep = `${c(C.gray, '│')}`;
|
|
1268
|
+
const desc = ` ${c(C.gray, e.desc)}`;
|
|
1269
|
+
const numVis = strip(num);
|
|
1270
|
+
const lblVis = strip(lbl);
|
|
1271
|
+
const descVis = strip(desc);
|
|
1272
|
+
const padRight = W - numVis.length - lblVis.length - 1 - descVis.length;
|
|
1273
|
+
const line = `${num}${lbl}${sep}${desc}${' '.repeat(Math.max(0, padRight - 2))}`;
|
|
1274
|
+
console.log(row(line, C.cyan));
|
|
1228
1275
|
}
|
|
1229
|
-
console.log(
|
|
1276
|
+
console.log(row('', C.cyan));
|
|
1230
1277
|
|
|
1231
|
-
|
|
1278
|
+
// ── Footer ─────────────────────────────────────────────────────────────────
|
|
1279
|
+
console.log(hr('├', '─', '┤', C.gray));
|
|
1232
1280
|
|
|
1233
|
-
// Status / message
|
|
1234
1281
|
if (statusMsg) {
|
|
1235
|
-
console.log(
|
|
1282
|
+
console.log(rowCentered(statusMsg, C.gray));
|
|
1236
1283
|
} else {
|
|
1237
|
-
|
|
1284
|
+
const hint = `${c(C.gray, timeStr + ' ── Entre un numéro ou un nom de commande')}`;
|
|
1285
|
+
console.log(rowCentered(hint, C.gray));
|
|
1238
1286
|
}
|
|
1239
1287
|
|
|
1240
|
-
console.log(
|
|
1288
|
+
console.log(hr('└', '─', '┘', C.gray));
|
|
1241
1289
|
console.log('');
|
|
1242
1290
|
}
|
|
1243
1291
|
|
|
1244
|
-
|
|
1292
|
+
// ── Sous-écrans ───────────────────────────────────────────────────────────────
|
|
1293
|
+
|
|
1294
|
+
function printSubHeader(title) {
|
|
1245
1295
|
console.log('');
|
|
1246
|
-
console.log(
|
|
1247
|
-
|
|
1248
|
-
|
|
1296
|
+
console.log(c(C.cyan, `┌${'─'.repeat(W)}┐`));
|
|
1297
|
+
const vis = strip(title);
|
|
1298
|
+
const l = Math.floor((W - vis.length) / 2);
|
|
1299
|
+
const r = W - vis.length - l;
|
|
1300
|
+
console.log(`${c(C.cyan, '│')}${' '.repeat(l)}${c(C.bWhite, title)}${' '.repeat(r)}${c(C.cyan, '│')}`);
|
|
1301
|
+
console.log(c(C.gray, `└${'─'.repeat(W)}┘`));
|
|
1249
1302
|
console.log('');
|
|
1250
1303
|
}
|
|
1251
1304
|
|
|
1252
|
-
function printSuccess(msg) { console.log(`\n ${c(C.bGreen, '
|
|
1253
|
-
function printError(msg) { console.log(`\n ${c(C.bRed,
|
|
1254
|
-
function printInfo(msg) { console.log(` ${c(C.
|
|
1305
|
+
function printSuccess(msg) { console.log(`\n ${c(C.bGreen, '+ ')} ${c(C.green, msg)}`); }
|
|
1306
|
+
function printError(msg) { console.log(`\n ${c(C.bRed, 'x ')} ${c(C.red, msg)}`); }
|
|
1307
|
+
function printInfo(msg) { console.log(` ${c(C.bCyan, '> ')} ${c(C.white, msg)}`); }
|
|
1255
1308
|
|
|
1256
1309
|
async function askPanelInput(prompt) {
|
|
1257
1310
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -1261,50 +1314,54 @@ async function askPanelInput(prompt) {
|
|
|
1261
1314
|
}
|
|
1262
1315
|
|
|
1263
1316
|
async function pressEnter() {
|
|
1264
|
-
await askPanelInput(`\n ${c(C.gray, 'Appuie sur
|
|
1317
|
+
await askPanelInput(`\n ${c(C.gray, 'Appuie sur Entree pour revenir au menu…')}`);
|
|
1265
1318
|
}
|
|
1266
1319
|
|
|
1320
|
+
// ── Actions ───────────────────────────────────────────────────────────────────
|
|
1321
|
+
|
|
1267
1322
|
async function panelShare() {
|
|
1268
1323
|
clearScreen();
|
|
1269
|
-
printSubHeader('Partager un fichier'
|
|
1324
|
+
printSubHeader('Partager un fichier');
|
|
1270
1325
|
|
|
1271
|
-
const target = await askText(` ${c(C.cyan, '
|
|
1326
|
+
const target = await askText(` ${c(C.cyan, '>')} Chemin du fichier`);
|
|
1272
1327
|
if (!target) return;
|
|
1273
|
-
|
|
1274
|
-
const
|
|
1275
|
-
const temps = await askText(` ${c(C.cyan, '›')} Expiration en jours ${c(C.gray, '(0 = aucune)')}`, '0');
|
|
1276
|
-
const modeRaw = await askText(` ${c(C.cyan, '›')} Mode upload serveur ? ${c(C.gray, '[o/n]')}`, 'n');
|
|
1328
|
+
const limits = await askText(` ${c(C.cyan, '>')} Limite de telechargements ${c(C.gray, '(0 = illimite)')}`, '0');
|
|
1329
|
+
const temps = await askText(` ${c(C.cyan, '>')} Expiration en jours ${c(C.gray, '(0 = aucune)')}`, '0');
|
|
1277
1330
|
|
|
1278
1331
|
const flags = {};
|
|
1279
1332
|
if (parseInt(limits, 10) > 0) flags.limits = limits;
|
|
1280
1333
|
if (parseInt(temps, 10) > 0) flags.temps = temps;
|
|
1281
|
-
if (modeRaw.toLowerCase() === 'o') flags.upload = true;
|
|
1282
1334
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1335
|
+
clearScreen();
|
|
1336
|
+
printSubHeader('Partage en cours...');
|
|
1337
|
+
console.log(` ${c(C.gray, 'Connexion au relay...')}\n`);
|
|
1338
|
+
|
|
1285
1339
|
try {
|
|
1286
1340
|
await shareFile([target], flags);
|
|
1287
|
-
|
|
1341
|
+
// shareFile retourne quand l'utilisateur tape 'q' ou que le socket se ferme
|
|
1342
|
+
printSuccess('Partage cloture.');
|
|
1288
1343
|
} catch (err) {
|
|
1289
1344
|
if (err?.data?.code === 'PLAN_FILE_LIMIT_EXCEEDED') {
|
|
1290
1345
|
printPlanLimitBox(err);
|
|
1291
1346
|
} else {
|
|
1292
1347
|
printError(err.message);
|
|
1293
1348
|
}
|
|
1349
|
+
await pressEnter();
|
|
1350
|
+
return;
|
|
1294
1351
|
}
|
|
1352
|
+
|
|
1295
1353
|
await pressEnter();
|
|
1296
1354
|
}
|
|
1297
1355
|
|
|
1298
1356
|
async function panelSync() {
|
|
1299
1357
|
clearScreen();
|
|
1300
|
-
printSubHeader('Synchroniser un dossier'
|
|
1358
|
+
printSubHeader('Synchroniser un dossier');
|
|
1301
1359
|
|
|
1302
|
-
const target
|
|
1360
|
+
const target = await askText(` ${c(C.cyan, '>')} Chemin du dossier`);
|
|
1303
1361
|
if (!target) return;
|
|
1304
|
-
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1307
|
-
const forceRaw = await askText(` ${c(C.cyan, '›')} Forcer le re-upload de tout ? ${c(C.gray, '[o/n]')}`, 'n');
|
|
1362
|
+
const onceRaw = await askText(` ${c(C.cyan, '>')} Sync unique (sans surveillance) ? ${c(C.gray, '[o/n]')}`, 'n');
|
|
1363
|
+
const dryRaw = await askText(` ${c(C.cyan, '>')} Simulation dry-run ? ${c(C.gray, '[o/n]')}`, 'n');
|
|
1364
|
+
const forceRaw = await askText(` ${c(C.cyan, '>')} Forcer le re-upload ? ${c(C.gray, '[o/n]')}`, 'n');
|
|
1308
1365
|
|
|
1309
1366
|
const flags = {};
|
|
1310
1367
|
if (onceRaw.toLowerCase() === 'o') flags.once = true;
|
|
@@ -1312,14 +1369,13 @@ async function panelSync() {
|
|
|
1312
1369
|
if (forceRaw.toLowerCase() === 'o') flags.force = true;
|
|
1313
1370
|
|
|
1314
1371
|
console.log('');
|
|
1315
|
-
console.log(
|
|
1372
|
+
console.log(c(C.gray, '─'.repeat(W + 2)));
|
|
1316
1373
|
try {
|
|
1317
1374
|
await syncFolder([target], flags);
|
|
1318
1375
|
if (flags.once || flags['dry-run']) {
|
|
1319
|
-
printSuccess('Synchronisation
|
|
1376
|
+
printSuccess('Synchronisation terminee.');
|
|
1320
1377
|
await pressEnter();
|
|
1321
1378
|
}
|
|
1322
|
-
// mode watch : syncFolder bloque, on revient au menu quand il se termine
|
|
1323
1379
|
} catch (err) {
|
|
1324
1380
|
printError(err.message);
|
|
1325
1381
|
await pressEnter();
|
|
@@ -1328,9 +1384,24 @@ async function panelSync() {
|
|
|
1328
1384
|
|
|
1329
1385
|
async function panelAccount() {
|
|
1330
1386
|
clearScreen();
|
|
1331
|
-
printSubHeader('Informations du compte'
|
|
1387
|
+
printSubHeader('Informations du compte');
|
|
1332
1388
|
try {
|
|
1333
|
-
await
|
|
1389
|
+
const config = await readConfig();
|
|
1390
|
+
const server = getServer({}, config);
|
|
1391
|
+
const token = getToken(config);
|
|
1392
|
+
if (!token) throw new Error('Non connecte.');
|
|
1393
|
+
const data = await httpJson(`${server}/api/auth/profile`, {
|
|
1394
|
+
method: 'GET',
|
|
1395
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1396
|
+
});
|
|
1397
|
+
const u = data.user || {};
|
|
1398
|
+
const planRaw = String(u.billing_plan || 'free').toUpperCase();
|
|
1399
|
+
const planCol = planRaw === 'FREE' ? C.gray : C.bYellow;
|
|
1400
|
+
console.log('');
|
|
1401
|
+
printInfo(`Utilisateur ${c(C.bWhite, u.username || '—')}`);
|
|
1402
|
+
printInfo(`Email ${c(C.white, u.email || '—')}`);
|
|
1403
|
+
printInfo(`Plan ${c(planCol, planRaw)}`);
|
|
1404
|
+
printInfo(`Serveur ${c(C.gray, server)}`);
|
|
1334
1405
|
} catch (err) {
|
|
1335
1406
|
printError(err.message);
|
|
1336
1407
|
}
|
|
@@ -1339,9 +1410,21 @@ async function panelAccount() {
|
|
|
1339
1410
|
|
|
1340
1411
|
async function panelWhoami() {
|
|
1341
1412
|
clearScreen();
|
|
1342
|
-
printSubHeader('Utilisateur
|
|
1413
|
+
printSubHeader('Utilisateur connecte');
|
|
1343
1414
|
try {
|
|
1344
|
-
await
|
|
1415
|
+
const config = await readConfig();
|
|
1416
|
+
const server = getServer({}, config);
|
|
1417
|
+
const token = getToken(config);
|
|
1418
|
+
if (!token) throw new Error('Non connecte.');
|
|
1419
|
+
const data = await httpJson(`${server}/api/auth/verify`, {
|
|
1420
|
+
method: 'POST',
|
|
1421
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1422
|
+
});
|
|
1423
|
+
const u = data.user || {};
|
|
1424
|
+
console.log('');
|
|
1425
|
+
printInfo(`Utilisateur ${c(C.bWhite, u.username || u.userId || '—')}`);
|
|
1426
|
+
if (u.email) printInfo(`Email ${c(C.white, u.email)}`);
|
|
1427
|
+
printInfo(`Serveur ${c(C.gray, server)}`);
|
|
1345
1428
|
} catch (err) {
|
|
1346
1429
|
printError(err.message);
|
|
1347
1430
|
}
|
|
@@ -1350,38 +1433,42 @@ async function panelWhoami() {
|
|
|
1350
1433
|
|
|
1351
1434
|
async function panelConfig() {
|
|
1352
1435
|
clearScreen();
|
|
1353
|
-
printSubHeader('Configuration'
|
|
1436
|
+
printSubHeader('Configuration');
|
|
1354
1437
|
|
|
1355
|
-
printInfo(`${c(C.
|
|
1356
|
-
printInfo(`${c(C.
|
|
1438
|
+
printInfo(`${c(C.bgGray + C.bWhite, ' a ')} Afficher la configuration actuelle`);
|
|
1439
|
+
printInfo(`${c(C.bgGray + C.bWhite, ' b ')} Changer de serveur`);
|
|
1357
1440
|
console.log('');
|
|
1358
1441
|
|
|
1359
|
-
const choice = await askPanelInput(` ${c(C.cyan, '
|
|
1442
|
+
const choice = await askPanelInput(` ${c(C.cyan, '>')} Choix ${c(C.gray, '[a / b]')} : `);
|
|
1360
1443
|
|
|
1361
1444
|
if (choice.toLowerCase() === 'a') {
|
|
1362
1445
|
console.log('');
|
|
1363
|
-
console.log(
|
|
1446
|
+
console.log(c(C.gray, '─'.repeat(W + 2)));
|
|
1364
1447
|
try {
|
|
1365
|
-
await
|
|
1448
|
+
const config = await readConfig();
|
|
1449
|
+
printInfo(`Serveur ${c(C.white, config.server)}`);
|
|
1450
|
+
printInfo(`Token ${config.token ? c(C.green, 'present') : c(C.red, 'absent')}`);
|
|
1366
1451
|
} catch (err) {
|
|
1367
1452
|
printError(err.message);
|
|
1368
1453
|
}
|
|
1369
1454
|
} else if (choice.toLowerCase() === 'b') {
|
|
1370
|
-
const url = await askText(` ${c(C.cyan, '
|
|
1455
|
+
const url = await askText(` ${c(C.cyan, '>')} Nouvelle URL du serveur`);
|
|
1371
1456
|
if (url) {
|
|
1372
1457
|
try {
|
|
1373
1458
|
await setServer([url]);
|
|
1374
|
-
printSuccess(`Serveur mis
|
|
1459
|
+
printSuccess(`Serveur mis a jour : ${url}`);
|
|
1375
1460
|
} catch (err) {
|
|
1376
1461
|
printError(err.message);
|
|
1377
1462
|
}
|
|
1378
1463
|
}
|
|
1379
1464
|
} else {
|
|
1380
|
-
printError('Choix invalide.');
|
|
1465
|
+
printError('Choix invalide. Entre a ou b.');
|
|
1381
1466
|
}
|
|
1382
1467
|
await pressEnter();
|
|
1383
1468
|
}
|
|
1384
1469
|
|
|
1470
|
+
// ── Boucle principale ─────────────────────────────────────────────────────────
|
|
1471
|
+
|
|
1385
1472
|
async function runPanel(username, plan) {
|
|
1386
1473
|
// Logout automatique à la fermeture du process
|
|
1387
1474
|
process.on('exit', () => {
|
|
@@ -1394,11 +1481,11 @@ async function runPanel(username, plan) {
|
|
|
1394
1481
|
});
|
|
1395
1482
|
|
|
1396
1483
|
const handleStop = async (sig) => {
|
|
1397
|
-
process.stdout.write(`\n\n ${c(C.gray, `Fermeture (${sig})
|
|
1484
|
+
process.stdout.write(`\n\n ${c(C.gray, `Fermeture (${sig})...`)}\n`);
|
|
1398
1485
|
try {
|
|
1399
1486
|
const cfg = await readConfig();
|
|
1400
1487
|
await writeConfig({ ...cfg, token: '' });
|
|
1401
|
-
process.stdout.write(` ${c(C.green, '
|
|
1488
|
+
process.stdout.write(` ${c(C.green, '+')} Session fermee. A bientot !\n\n`);
|
|
1402
1489
|
} catch (_) {}
|
|
1403
1490
|
process.exit(0);
|
|
1404
1491
|
};
|
|
@@ -1412,7 +1499,7 @@ async function runPanel(username, plan) {
|
|
|
1412
1499
|
printPanel(username, plan, statusMsg);
|
|
1413
1500
|
statusMsg = null;
|
|
1414
1501
|
|
|
1415
|
-
const answer = (await askPanelInput(` ${c(C.bCyan, '
|
|
1502
|
+
const answer = (await askPanelInput(` ${c(C.bCyan, '>')} `)).toLowerCase();
|
|
1416
1503
|
|
|
1417
1504
|
if (answer === '1' || answer === 'share') { await panelShare(); }
|
|
1418
1505
|
else if (answer === '2' || answer === 'sync') { await panelSync(); }
|
|
@@ -1424,12 +1511,12 @@ async function runPanel(username, plan) {
|
|
|
1424
1511
|
process.removeListener('SIGINT', handleStop);
|
|
1425
1512
|
process.removeListener('SIGTERM', handleStop);
|
|
1426
1513
|
await logout();
|
|
1427
|
-
console.log(`\n ${c(C.bGreen, '
|
|
1514
|
+
console.log(`\n ${c(C.bGreen, '+')} Deconnecte avec succes. A bientot !\n`);
|
|
1428
1515
|
process.exit(0);
|
|
1429
1516
|
} else if (answer === '') {
|
|
1430
|
-
// reboucle
|
|
1517
|
+
// reboucle
|
|
1431
1518
|
} else {
|
|
1432
|
-
statusMsg = c(C.bRed,
|
|
1519
|
+
statusMsg = c(C.bRed, `x Inconnu : "${answer}" ── choisis 1 a 6`);
|
|
1433
1520
|
}
|
|
1434
1521
|
}
|
|
1435
1522
|
}
|
|
@@ -1469,17 +1556,28 @@ async function main() {
|
|
|
1469
1556
|
const server = getServer(flags, config);
|
|
1470
1557
|
const token = getToken(config);
|
|
1471
1558
|
let username = String(flags.username || '').trim();
|
|
1472
|
-
|
|
1473
|
-
|
|
1559
|
+
let plan = '';
|
|
1560
|
+
// Récupère le username et le plan depuis le serveur si possible
|
|
1561
|
+
if (token) {
|
|
1474
1562
|
try {
|
|
1475
|
-
const data = await httpJson(`${server}/api/auth/
|
|
1476
|
-
method: '
|
|
1563
|
+
const data = await httpJson(`${server}/api/auth/profile`, {
|
|
1564
|
+
method: 'GET',
|
|
1477
1565
|
headers: { Authorization: `Bearer ${token}` }
|
|
1478
1566
|
});
|
|
1479
|
-
|
|
1480
|
-
|
|
1567
|
+
const user = data.user || {};
|
|
1568
|
+
if (!username) username = user.username || user.userId || '';
|
|
1569
|
+
plan = String(user.billing_plan || 'free').toLowerCase();
|
|
1570
|
+
} catch (_) {
|
|
1571
|
+
try {
|
|
1572
|
+
const data = await httpJson(`${server}/api/auth/verify`, {
|
|
1573
|
+
method: 'POST',
|
|
1574
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1575
|
+
});
|
|
1576
|
+
if (!username) username = data.user?.username || data.user?.userId || '';
|
|
1577
|
+
} catch (_2) {}
|
|
1578
|
+
}
|
|
1481
1579
|
}
|
|
1482
|
-
await runPanel(username);
|
|
1580
|
+
await runPanel(username, plan);
|
|
1483
1581
|
}
|
|
1484
1582
|
return;
|
|
1485
1583
|
}
|
|
@@ -1525,27 +1623,7 @@ async function main() {
|
|
|
1525
1623
|
throw new Error(`Commande inconnue: ${command}`);
|
|
1526
1624
|
}
|
|
1527
1625
|
|
|
1528
|
-
|
|
1529
|
-
const data = error?.data || {};
|
|
1530
|
-
const plan = String(data.plan || '').toUpperCase() || 'FREE';
|
|
1531
|
-
const maxFileSize = Number(data.maxFileSize || 0);
|
|
1532
|
-
const fileSize = Number(data.fileSize || 0);
|
|
1533
|
-
const maxLabel = maxFileSize > 0 ? formatBytes(maxFileSize) : '?';
|
|
1534
|
-
const fileLabel = fileSize > 0 ? formatBytes(fileSize) : '?';
|
|
1535
|
-
const lines = [
|
|
1536
|
-
'',
|
|
1537
|
-
'╔════════════════════════════════════════════════════╗',
|
|
1538
|
-
`║ ⚠ Limite du plan ${plan}`.padEnd(52) + ' ║',
|
|
1539
|
-
'╠════════════════════════════════════════════════════╣',
|
|
1540
|
-
`║ Taille fichier : ${fileLabel}`.padEnd(52) + ' ║',
|
|
1541
|
-
`║ Limite max : ${maxLabel} / fichier`.padEnd(52) + ' ║',
|
|
1542
|
-
'║ ║',
|
|
1543
|
-
'║ Passe a une offre superieure pour continuer. ║',
|
|
1544
|
-
'╚════════════════════════════════════════════════════╝',
|
|
1545
|
-
''
|
|
1546
|
-
];
|
|
1547
|
-
console.error(lines.join('\n'));
|
|
1548
|
-
}
|
|
1626
|
+
|
|
1549
1627
|
|
|
1550
1628
|
main().catch((error) => {
|
|
1551
1629
|
if (error?.data?.code === 'PLAN_FILE_LIMIT_EXCEEDED') {
|