UNPKG

andrade-soulseek-downloader

Version:

Simple, safe Soulseek download library with built-in rate limiting to prevent bans

163 lines 6.58 kB
"use strict"; 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 __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; 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 __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SoulseekTrackRepository = void 0; const inversify_1 = require("inversify"); const domain_1 = require("../../domain"); const string_similarity_js_1 = require("string-similarity-js"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Infrastructure adapter for Soulseek network operations. * Implements ITrackRepository port for hexagonal architecture. */ let SoulseekTrackRepository = class SoulseekTrackRepository { client; /** * @param client - Soulseek client instance */ constructor(client) { this.client = client; } /** * Searches Soulseek network for matching tracks. * @inheritdoc */ async search(artist, title, minBitrate, maxBitrate, timeout) { const query = `${artist} ${title}`.toLowerCase(); const searchTimeout = timeout || parseInt(process.env.SOULSEEK_SEARCH_TIMEOUT || '30000'); return new Promise((resolve, reject) => { this.client.search({ req: query, timeout: searchTimeout }, (err, results) => { if (err) { reject(err); return; } const tracks = results .filter(r => { // Apply bitrate filtering const meetsMinimum = r.bitrate >= minBitrate; const meetsMaximum = !maxBitrate || r.bitrate <= maxBitrate; return meetsMinimum && meetsMaximum; }) .map(r => this.mapToTrack(r, artist, title)) .filter(t => t !== null); resolve(tracks); }); }); } /** * Downloads track from Soulseek peer. * @inheritdoc */ async download(track, destinationPath) { return new Promise((resolve) => { const timeout = setTimeout(() => { resolve(null); }, 120000); this.client.download({ file: { user: track.getUser(), file: track.getFilePath(), size: track.getFileSize(), bitrate: track.getBitrate().getValue(), slots: track.hasAvailableSlots(), speed: track.getSpeed() }, path: destinationPath }, (err) => { clearTimeout(timeout); if (err) { resolve(null); } else { resolve(destinationPath); } }); }); } /** * Checks local filesystem for existing file. * @inheritdoc */ async exists(destinationPath) { return fs.existsSync(destinationPath); } /** * Maps Soulseek result to domain Track entity. * @returns Track or null if invalid data */ mapToTrack(result, artist, title) { try { const filename = path.basename(result.file); const matchScore = this.calculateMatchScore(filename, artist, title); return new domain_1.Track(domain_1.TrackId.generate(result.user, result.file), artist, title, new domain_1.Bitrate(result.bitrate), result.size, result.file, result.user, result.slots, result.speed, matchScore); } catch (error) { // Invalid track data, skip return null; } } /** * Calculates similarity score between query and filename. * @returns Score from 0-1 */ calculateMatchScore(filename, artist, title) { const query = `${artist} ${title}`.toLowerCase(); const cleanFilename = filename.toLowerCase().replace(/\.[^/.]+$/, ''); return (0, string_similarity_js_1.stringSimilarity)(query, cleanFilename); } }; exports.SoulseekTrackRepository = SoulseekTrackRepository; exports.SoulseekTrackRepository = SoulseekTrackRepository = __decorate([ (0, inversify_1.injectable)(), __param(0, (0, inversify_1.inject)('SoulseekClient')), __metadata("design:paramtypes", [Object]) ], SoulseekTrackRepository); //# sourceMappingURL=soulseek-track-repository.js.map