andrade-soulseek-downloader
Version:
Simple, safe Soulseek download library with built-in rate limiting to prevent bans
395 lines • 15.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logger = void 0;
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const cliProgress = __importStar(require("cli-progress"));
const boxen_1 = __importDefault(require("boxen"));
class Logger {
spinner = null;
progressBar = null;
multiBar = null;
racingBars = new Map();
formatSize(bytes) {
const sizes = ['B', 'KB', 'MB', 'GB'];
if (bytes === 0)
return '0 B';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
}
formatSpeed(speed) {
if (speed < 1000)
return `${speed} B/s`;
if (speed < 1000000)
return `${(speed / 1000).toFixed(1)} KB/s`;
return `${(speed / 1000000).toFixed(1)} MB/s`;
}
formatTime(seconds) {
if (seconds < 60)
return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${minutes}m ${secs}s`;
}
title(message) {
console.log('\n' + (0, boxen_1.default)(chalk_1.default.cyan.bold(message), {
padding: 1,
margin: 0,
borderStyle: 'round',
borderColor: 'cyan'
}));
}
info(message) {
console.log(chalk_1.default.blue('ℹ ') + message);
}
success(message) {
console.log(chalk_1.default.green('✔ ') + chalk_1.default.green(message));
}
error(message) {
console.log(chalk_1.default.red('✖ ') + chalk_1.default.red(message));
}
warning(message) {
console.log(chalk_1.default.yellow('⚠ ') + chalk_1.default.yellow(message));
}
debug(message) {
console.log(chalk_1.default.gray('• ') + chalk_1.default.gray(message));
}
section(title) {
console.log('\n' + chalk_1.default.bold.underline(title));
}
searchResult(result, index, total) {
const slotIcon = result.slots ? chalk_1.default.green('✓') : chalk_1.default.red('✗');
const filename = result.file.split(/[\\\/]/).pop() || result.file;
// Quality indicator based on bitrate
let qualityBadge = '';
if (result.bitrate >= 320)
qualityBadge = chalk_1.default.green(' ⭐ HQ');
else if (result.bitrate >= 256)
qualityBadge = chalk_1.default.yellow(' ◆ MQ');
else if (result.bitrate >= 192)
qualityBadge = chalk_1.default.gray(' ● LQ');
console.log(chalk_1.default.gray(`[${index}/${total}]`) + ' ' +
chalk_1.default.cyan(filename.substring(0, 50)) +
(filename.length > 50 ? '...' : '') +
qualityBadge);
console.log(' ' + chalk_1.default.gray('User:') + ' ' + chalk_1.default.white(result.user) +
' ' + chalk_1.default.gray('Size:') + ' ' + chalk_1.default.yellow(this.formatSize(result.size)) +
' ' + chalk_1.default.gray('Bitrate:') + ' ' + chalk_1.default.magenta(`${result.bitrate}kbps`) +
' ' + chalk_1.default.gray('Slots:') + ' ' + slotIcon +
' ' + chalk_1.default.gray('Speed:') + ' ' + chalk_1.default.blue(this.formatSpeed(result.speed)));
if (result.score !== undefined) {
const scoreColor = result.score > 0.7 ? chalk_1.default.green : result.score > 0.4 ? chalk_1.default.yellow : chalk_1.default.red;
console.log(' ' + chalk_1.default.gray('Match:') + ' ' + scoreColor(`${(result.score * 100).toFixed(0)}%`));
}
if (result.qualityScore !== undefined) {
const qColor = result.qualityScore > 80 ? chalk_1.default.green : result.qualityScore > 60 ? chalk_1.default.yellow : chalk_1.default.red;
console.log(' ' + chalk_1.default.gray('Quality Score:') + ' ' + qColor(`${result.qualityScore.toFixed(0)}/100`));
}
}
displaySearchResults(results, maxDisplay = 20) {
if (results.length === 0) {
this.warning('No results to display');
return;
}
this.section(`📊 Search Results (showing top ${Math.min(results.length, maxDisplay)} of ${results.length})`);
this.divider();
// Display top results
const displayCount = Math.min(results.length, maxDisplay);
for (let i = 0; i < displayCount; i++) {
const result = results[i];
const filename = result.file.split(/[\\\/]/).pop() || result.file;
// Rank indicator
let rankIcon = '';
if (i === 0)
rankIcon = '🥇';
else if (i === 1)
rankIcon = '🥈';
else if (i === 2)
rankIcon = '🥉';
else
rankIcon = chalk_1.default.gray(`#${i + 1}`);
// Quality badge
let qualityBadge = '';
if (result.bitrate >= 320)
qualityBadge = chalk_1.default.green('⭐');
else if (result.bitrate >= 256)
qualityBadge = chalk_1.default.yellow('◆');
else
qualityBadge = chalk_1.default.gray('●');
// Slots indicator
// Show number of slots if available
let slotIcon;
if (!result.slots) {
slotIcon = chalk_1.default.red('✗');
}
else if (typeof result.slots === 'number') {
slotIcon = result.slots >= 5 ? chalk_1.default.green(`✓(${result.slots})`) :
result.slots >= 2 ? chalk_1.default.yellow(`✓(${result.slots})`) :
chalk_1.default.cyan(`✓(${result.slots})`);
}
else {
slotIcon = chalk_1.default.green('✓');
}
// Speed indicator color
const speedColor = result.speed > 1000000 ? chalk_1.default.green : result.speed > 500000 ? chalk_1.default.yellow : chalk_1.default.red;
// Main line
console.log(`${rankIcon} ${qualityBadge} ` +
chalk_1.default.cyan(filename.substring(0, 45)) +
(filename.length > 45 ? '...' : ''));
// Details line
console.log(' ' +
chalk_1.default.gray('User:') + ' ' + chalk_1.default.white(result.user.substring(0, 15)) +
(result.user.length > 15 ? '..' : '') + ' ' +
chalk_1.default.gray('│') + ' ' +
chalk_1.default.magenta(`${result.bitrate}kbps`) + ' ' +
chalk_1.default.gray('│') + ' ' +
chalk_1.default.yellow(this.formatSize(result.size)) + ' ' +
chalk_1.default.gray('│') + ' ' +
speedColor(this.formatSpeed(result.speed)) + ' ' +
chalk_1.default.gray('│') + ' ' +
'Slot:' + slotIcon);
// Match score if available
if (result.discoseekMatchingScore !== undefined) {
const score = result.discoseekMatchingScore * 100;
const scoreColor = score > 70 ? chalk_1.default.green : score > 40 ? chalk_1.default.yellow : chalk_1.default.red;
console.log(' ' + chalk_1.default.gray('Match:') + ' ' + scoreColor(`${score.toFixed(0)}%`));
}
if (i < displayCount - 1) {
console.log(chalk_1.default.gray(' ─────'));
}
}
if (results.length > maxDisplay) {
this.divider();
this.info(`... and ${results.length - maxDisplay} more results`);
}
this.divider();
}
startSpinner(message) {
this.spinner = (0, ora_1.default)({
text: message,
spinner: 'dots',
color: 'cyan'
}).start();
}
updateSpinner(message) {
if (this.spinner) {
this.spinner.text = message;
}
}
succeedSpinner(message) {
if (this.spinner) {
this.spinner.succeed(message);
this.spinner = null;
}
}
failSpinner(message) {
if (this.spinner) {
this.spinner.fail(message);
this.spinner = null;
}
}
stopSpinner() {
if (this.spinner) {
this.spinner.stop();
this.spinner = null;
}
}
startCountdownSpinner(message, totalMs) {
const startTime = Date.now();
const totalSeconds = Math.ceil(totalMs / 1000);
this.spinner = (0, ora_1.default)({
text: `${message} (${totalSeconds}s remaining)`,
spinner: 'dots',
color: 'cyan'
}).start();
const updateInterval = setInterval(() => {
if (!this.spinner) {
clearInterval(updateInterval);
return;
}
const elapsed = Date.now() - startTime;
const remaining = Math.max(0, totalMs - elapsed);
const remainingSeconds = Math.ceil(remaining / 1000);
if (remaining <= 0) {
clearInterval(updateInterval);
this.spinner.text = `${message} (timeout reached)`;
}
else {
this.spinner.text = `${message} (${remainingSeconds}s remaining)`;
}
}, 100);
// Store interval ID for cleanup
this.spinner._countdownInterval = updateInterval;
}
stopCountdownSpinner() {
if (this.spinner) {
// Clear the countdown interval if it exists
const interval = this.spinner._countdownInterval;
if (interval) {
clearInterval(interval);
}
this.spinner.stop();
this.spinner = null;
}
}
succeedCountdownSpinner(message) {
if (this.spinner) {
// Clear the countdown interval if it exists
const interval = this.spinner._countdownInterval;
if (interval) {
clearInterval(interval);
}
this.spinner.succeed(message);
this.spinner = null;
}
}
failCountdownSpinner(message) {
if (this.spinner) {
// Clear the countdown interval if it exists
const interval = this.spinner._countdownInterval;
if (interval) {
clearInterval(interval);
}
this.spinner.fail(message);
this.spinner = null;
}
}
startProgressBar(total, startValue = 0) {
this.progressBar = new cliProgress.SingleBar({
format: chalk_1.default.cyan('{bar}') + ' | {percentage}% | {value}/{total} | {duration_formatted} | {speed}',
barCompleteChar: '█',
barIncompleteChar: '░',
hideCursor: true,
formatBar: (progress, options) => {
const completeSize = Math.round(progress * (options.barsize || 40));
const incompleteSize = (options.barsize || 40) - completeSize;
const complete = chalk_1.default.green(options.barCompleteString?.substring(0, completeSize));
const incomplete = chalk_1.default.gray(options.barIncompleteString?.substring(0, incompleteSize));
return complete + incomplete;
}
}, cliProgress.Presets.shades_classic);
this.progressBar.start(total, startValue, {
speed: 'N/A'
});
}
updateProgressBar(value, payload) {
if (this.progressBar) {
this.progressBar.update(value, payload);
}
}
stopProgressBar() {
if (this.progressBar) {
this.progressBar.stop();
this.progressBar = null;
}
}
// Racing progress bar methods
startRacingBars(racers) {
// Stop any existing progress bars
this.stopProgressBar();
this.stopRacingBars();
// Create multi-bar container
this.multiBar = new cliProgress.MultiBar({
clearOnComplete: false,
hideCursor: true,
format: '{user} [{bar}] {percentage}% | {speed} | {status}',
barCompleteChar: '█',
barIncompleteChar: '░',
}, cliProgress.Presets.shades_classic);
// Create a progress bar for each racer
racers.forEach(racer => {
const bar = this.multiBar.create(racer.size, 0, {
user: chalk_1.default.cyan(racer.user.padEnd(15)),
speed: 'Starting...',
status: ''
});
this.racingBars.set(racer.user, bar);
});
}
updateRacingBar(user, value, speed) {
const bar = this.racingBars.get(user);
if (bar && this.multiBar) {
bar.update(value, {
user: chalk_1.default.cyan(user.padEnd(15)),
speed: chalk_1.default.yellow(speed),
status: ''
});
}
}
markRacingWinner(user) {
const bar = this.racingBars.get(user);
if (bar) {
bar.update(bar.getTotal(), {
user: chalk_1.default.green(user.padEnd(15)),
speed: chalk_1.default.green('Complete'),
status: chalk_1.default.green('🏆 WINNER!')
});
}
}
markRacingLoser(user, reason = 'Cancelled') {
const bar = this.racingBars.get(user);
if (bar) {
// Keep current progress but gray out the display
bar.update(bar.getProgress() * bar.getTotal(), {
user: chalk_1.default.gray(user.padEnd(15)),
speed: chalk_1.default.gray(reason),
status: chalk_1.default.gray('✗')
});
}
}
stopRacingBars() {
if (this.multiBar) {
this.multiBar.stop();
this.multiBar = null;
this.racingBars.clear();
}
}
table(data) {
const maxKeyLength = Math.max(...Object.keys(data).map(k => k.length));
Object.entries(data).forEach(([key, value]) => {
const paddedKey = key.padEnd(maxKeyLength);
console.log(` ${chalk_1.default.gray(paddedKey)} : ${chalk_1.default.white(value)}`);
});
}
divider() {
console.log(chalk_1.default.gray('─'.repeat(60)));
}
}
exports.Logger = Logger;
//# sourceMappingURL=logger.js.map