UNPKG

andrade-soulseek-downloader

Version:

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

287 lines 12.4 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 __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.SlskClient = void 0; const events_1 = require("events"); const crypto = __importStar(require("crypto")); const stream = __importStar(require("stream")); const net = __importStar(require("net")); const server_1 = require("./server"); const default_peer_1 = require("./peer/default-peer"); const distributed_peer_1 = require("./peer/distributed-peer"); const download_peer_file_1 = require("./peer/download-peer-file"); const listen_1 = require("./listen"); const shared_1 = require("./share/shared"); const stack_1 = __importDefault(require("./stack")); const debug = (msg) => { if (process.env.DEBUG?.includes('slsk')) { console.log(`[slsk] ${msg}`); } }; let server; let listen; let peers = {}; let shared; class SlskClient extends events_1.EventEmitter { serverAddress; sharedFolders; constructor(serverAddress, sharedFolders) { super(); this.serverAddress = serverAddress; this.sharedFolders = sharedFolders; } init(cb) { debug('Init client'); server = new server_1.Server(this.serverAddress, cb); server.on('connect-to-peer', (peer) => { this.connectToPeer(peer); }); server.on('get-peer-address', (peer) => { if (peers[peer.user]) { peers[peer.user].setAddress(peer.host, peer.port); } else { peers[peer.user] = new default_peer_1.DefaultPeer(net.createConnection({ host: peer.host, port: peer.port }), peer); if (stack_1.default.peerSearchMatches[peer.user]) { Object.keys(stack_1.default.peerSearchMatches[peer.user]).forEach(ticket => { peers[peer.user].fileSearchResult(stack_1.default.peerSearchMatches[peer.user][ticket], ticket, stack_1.default.currentLogin); }); stack_1.default.peerSearchMatches[peer.user] = {}; } } }); shared = new shared_1.Shared(); this.sharedFolders.forEach(folder => shared.scanFolder(folder)); } connectToPeer(peer) { debug(`connectToPeer ${peer.user} ${peer.host} ${peer.port} ${peer.token} ${peer.type}`); switch (peer.type) { case 'F': { debug(`Handling file transfer for token ${peer.token} from user ${peer.user}`); const downloader = (0, download_peer_file_1.downloadPeerFile)(peer.host, peer.port, peer.token, peer.user, false); // Try to find progress emitter by token first, then by user+file let mainEmitter = null; if (stack_1.default.downloadTokens[peer.token] && stack_1.default.downloadTokens[peer.token].progressEmitter) { debug(`Found progressEmitter by token ${peer.token}`); mainEmitter = stack_1.default.downloadTokens[peer.token].progressEmitter; } else { // Try to find by looking at active downloads for this user const downloadKeys = Object.keys(stack_1.default.download).filter(key => key.startsWith(peer.user + '_')); if (downloadKeys.length > 0) { const download = stack_1.default.download[downloadKeys[0]]; if (download.progressEmitter) { debug(`Found progressEmitter by user ${peer.user}`); mainEmitter = download.progressEmitter; // Also store it in the token for future use if (!stack_1.default.downloadTokens[peer.token]) { stack_1.default.downloadTokens[peer.token] = {}; } stack_1.default.downloadTokens[peer.token].progressEmitter = mainEmitter; } } } if (mainEmitter) { debug(`Linking progress emitters`); downloader.on('start', (data) => { debug(`Download started event received, forwarding`); mainEmitter.emit('start', data); }); downloader.on('progress', (data) => { debug(`Progress event received: ${data.percentage}%`); mainEmitter.emit('progress', data); }); downloader.on('complete', (data) => { debug(`Complete event received`); mainEmitter.emit('complete', data); }); downloader.on('error', (err) => { debug(`Error event received`); mainEmitter.emit('error', err); }); } else { debug(`No progress emitter found for user ${peer.user}`); } break; } case 'D': { peers[peer.user] = new distributed_peer_1.DistributedPeer(net.createConnection({ host: peer.host, port: peer.port }), peer); peers[peer.user].on('search', (search) => { const matched = shared.search(search.query); if (matched.length > 0) { if (!peers[search.user]) { server.getPeerAddress(search.user); if (!stack_1.default.peerSearchMatches[search.user]) { stack_1.default.peerSearchMatches[search.user] = { [search.ticket]: matched }; } else { stack_1.default.peerSearchMatches[search.user][search.ticket] = matched; } } else { peers[search.user].fileSearchResult(matched, search.ticket, stack_1.default.currentLogin); } debug(`Search from peer ${search.user}, query: ${search.query}. Matched: ${matched.length} files`); } }); peers[peer.user].on('disconnect', () => { delete peers[peer.user]; }); break; } default: { peers[peer.user] = new default_peer_1.DefaultPeer(net.createConnection({ host: peer.host, port: peer.port }), peer); peers[peer.user].on('disconnect', () => { delete peers[peer.user]; }); } } } login(credentials, cb) { stack_1.default.currentLogin = credentials.user; server.login(credentials); const incomingPort = credentials.incomingPort || 2234; listen = new listen_1.Listen(incomingPort); listen.on('new-peer', (evt) => { const peer = evt.peer; if (peers[peer.user]) { debug(`Already connected to ${peer.user}`); } else { server.getPeerAddress(peer.user); debug(`new Peer connected ${peer.user} token ${peer.token}`); peers[peer.user] = new default_peer_1.DefaultPeer(evt.socket, peer); peers[peer.user].on('disconnect', () => { delete peers[peer.user]; }); } }); server.setWaitPort(incomingPort); stack_1.default.login = (err) => cb(err || undefined); } search(obj, cb) { if (typeof cb !== 'function') { throw new Error('2nd argument must be callback function'); } const token = crypto.randomBytes(4).toString('hex'); const timeout = obj.timeout || 4000; const results = []; setTimeout(() => { delete stack_1.default.search[token]; cb(null, results); }, timeout); stack_1.default.search[token] = { cb: (res) => { this.emit('found', res); this.emit(`found:${obj.req}`, res); results.push(res); }, query: obj.req }; server.fileSearch(obj.req, token); } download(obj, cb, streamParam) { debug(`launch download ${obj.file.user} ${obj.file.file}`); const progressEmitter = new events_1.EventEmitter(); if (typeof cb !== 'function' && !streamParam) { throw new Error('2nd argument must be callback function'); } if (typeof obj.file === 'undefined') { throw new Error('You must specify file'); } if (!peers[obj.file.user]) { cb(new Error('User not exist')); return progressEmitter; } const token = crypto.randomBytes(4).toString('hex'); stack_1.default.downloadTokens[token] = { user: obj.file.user, file: obj.file.file, size: obj.file.size, progressEmitter }; stack_1.default.download[obj.file.user + '_' + obj.file.file] = { cb, path: obj.path, stream: streamParam, progressEmitter // Store emitter here too for access by user+file }; // Store token reference for later progress tracking progressEmitter.on('newEmitter', (newEmitter) => { // Forward all events from the actual download emitter newEmitter.on('start', (data) => progressEmitter.emit('start', data)); newEmitter.on('progress', (data) => progressEmitter.emit('progress', data)); newEmitter.on('complete', (data) => progressEmitter.emit('complete', data)); newEmitter.on('error', (err) => progressEmitter.emit('error', err)); }); // Store the token so we can link it when download starts progressEmitter.token = token; peers[obj.file.user].transferRequest(obj.file.file, token); return progressEmitter; } downloadStream(obj, cb) { const s = new stream.Readable(); s._read = () => { }; cb(null, s); this.download(obj, null, s); } destroy() { if (server) server.destroy(); if (listen) listen.destroy(); Object.keys(peers).forEach(peer => { peers[peer].destroy(); }); delete stack_1.default.login; } } exports.SlskClient = SlskClient; //# sourceMappingURL=slsk-client.js.map