UNPKG

utorrent-block-xunlei

Version:
553 lines 20.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UTorrent = exports.Torrent = exports.Status = exports.Peer = void 0; const tslib_1 = require("tslib"); const net_1 = require("net"); const xshell_1 = require("xshell"); const lib_qqwry_1 = tslib_1.__importDefault(require("lib-qqwry")); tslib_1.__exportStar(require("./resume-data"), exports); class Peer { country; ip; reverse_dns; utp; port; client; flags; /** 0 - 1000 */ progress; download_speed; upload_speed; requests_out; requests_in; waited; uploaded; downloaded; hash_error; /** 用户 */ peer_download_speed; max_upload_speed; max_download_speed; queued; inactive; relevance; torrent; should_block; constructor(raw_data, torrent) { this.torrent = torrent; let [country, ip, reverse_dns, utp, port, client, flags, progress, download_speed, upload_speed, requests_out, requests_in, waited, uploaded, downloaded, hash_error, peer_download_speed, max_upload_speed, max_download_speed, queued, inactive, relevance] = raw_data; waited = new Date(waited * 1000); Object.assign(this, { country, ip, reverse_dns, utp, port, client, flags, progress, download_speed, upload_speed, requests_out, requests_in, waited, uploaded, downloaded, hash_error, peer_download_speed, max_upload_speed, max_download_speed, queued, inactive, relevance }); this.should_block = this.torrent.state[0] === '做种' ? /-XL0012-|Xunlei|^7\.|aria2|Xfplay|dandanplay|FDM|go\.torrent|Mozilla/i.test(this.client) && this.upload_speed > 10 * 2 ** 10 : this.torrent.state[0] === '下载' ? /-XL0012-|Xunlei|^7\.|aria2|Xfplay|dandanplay|FDM|go\.torrent|Mozilla/i.test(this.client) && this.uploaded > this.downloaded * 10 + 5 * 2 ** 20 : false; } to_str() { return '' + /* 名称 */ this.flags.pad(8) + ' ' + /* Client UA */ this.format_client() + ' ' + /* IP:PORT IP 信息 */ this.format_ipinfo() + /* 下载速度 */ this.format_download_speed() + ' / ' + /* 上传速度 */ this.format_upload_speed() + ' | ' + /* 累计下载 */ this.format_downloaded() + ' / ' + /* 累计上传 */ this.uploaded.to_fsize_str().pad(12) + ' ' + /* 种子名称 */ this.torrent.name.limit(80); } format_client() { if (this.client.includes('-XL0012-')) return (this.client.slice(0, '-XL0012-'.length) + ' ***').limit(20); return this.client.limit(20); } format_download_speed() { const s = (this.torrent.state[0] === '做种' ? '' : this.download_speed.to_fsize_str() + '/s').pad(12); return (this.download_speed > 50 * 2 ** 10 ? s.magenta : s); } format_upload_speed() { const s = (this.upload_speed.to_fsize_str() + '/s').pad(12); return this.upload_speed > 200 * 2 ** 10 ? s.green : s; } format_downloaded() { return (this.torrent.state[0] === '做种' ? '' : this.downloaded.to_fsize_str()).pad(10); } format_ipinfo() { const ip_width = 24; const info_width = 50; return net_1.isIPv6(this.ip) ? (this.ip.bracket('SQUARE') + ':' + this.port).pad(ip_width + info_width) : (this.ip + ':' + this.port).pad(ip_width) + get_ip_info(this.ip).limit(info_width); } [xshell_1.inspect.custom]() { return xshell_1.inspect({ ...this, should_block: this.should_block, progress: `${this.progress / 10}%`, uploaded: this.uploaded.to_fsize_str(), downloaded: this.downloaded.to_fsize_str(), upload_speed: `${this.upload_speed.to_fsize_str()}/s`, download_speed: `${this.download_speed.to_fsize_str()}/s`, peer_download_speed: `${this.peer_download_speed.to_fsize_str()}/s`, max_upload_speed: `${this.max_upload_speed.to_fsize_str()}/s`, max_download_speed: `${this.max_download_speed.to_fsize_str()}/s`, waited: this.waited.to_str(), }, { omit: [ 'torrent', ...this.utp === 0 ? ['utp'] : [], 'relevance', ...this.torrent.state[0] === '做种' ? ['download_speed'] : [], ] }); } } exports.Peer = Peer; var Status; (function (Status) { Status[Status["STARTED"] = 1] = "STARTED"; Status[Status["CHECKING"] = 2] = "CHECKING"; Status[Status["ERROR"] = 16] = "ERROR"; Status[Status["PAUSED"] = 32] = "PAUSED"; Status[Status["QUEUED"] = 64] = "QUEUED"; })(Status = exports.Status || (exports.Status = {})); class Torrent { hash; status; name; /** bytes */ size; /** 0 - 1000 */ progress; /** uTorrent 累计下载量 bytes */ downloaded; /** uTorrent 累积上传量 bytes */ uploaded; /** 需除以 1000 ? */ ratio; /** bytes/second */ upload_speed; /** bytes/second */ download_speed; /** estimated time of arrival (seconds) */ time_remaining; label; peers_connected; peers_in_swarm; seeds_connected; seeds_in_swarm; availability; /** -1 表示未在队列中 */ queue_position; /** 剩余大小 */ remaining; download_url; rss_feed_url; status_message; stream_id; date_added; date_completed; app_update_url; save_path; /** status 按位与 Status 相与并结合 progress 信息得到 */ state; utorrent; peers; constructor(raw_data, utorrent) { this.utorrent = utorrent; let [hash, status, name, size, progress, downloaded, uploaded, ratio, upload_speed, download_speed, time_remaining, label, peers_connected, peers_in_swarm, seeds_connected, seeds_in_swarm, availability, queue_position, remaining, download_url, rss_feed_url, status_message, stream_id, date_added, date_completed, app_update_url, save_path] = raw_data; this.state = status & Status.PAUSED ? ['暂停', status & Status.CHECKING ? `已检查 ${progress / 10}` : '下载'] : status & Status.STARTED ? [progress === 1000 ? '做种' : '下载', status & Status.QUEUED ? '' : '强制'] : status & Status.CHECKING ? ['检查', `已检查 ${progress / 10}`] : status & Status.ERROR ? ['错误', status_message] : status & Status.QUEUED ? ['排队', progress === 1000 ? '做种' : '下载'] : progress === 1000 ? ['完成', ''] : ['停止', '']; date_added = new Date(date_added * 1000); date_completed = new Date(date_completed * 1000); save_path = save_path.to_slash(); Object.assign(this, { hash, status, name, size, progress, downloaded, uploaded, ratio, upload_speed, download_speed, time_remaining, label, peers_connected, peers_in_swarm, seeds_connected, seeds_in_swarm, availability, queue_position, remaining, download_url, rss_feed_url, status_message, stream_id, date_added, date_completed, app_update_url, save_path }); } async get_peers() { const result = await this.utorrent.rpc({ params: { action: 'getpeers', hash: this.hash } }); return this.peers = result.peers[1].map(peer => new Peer(peer, this)); } [xshell_1.inspect.custom]() { return xshell_1.inspect({ ...this, ...this.peers ? { peers: this.peers.length } : {}, size: this.size.to_fsize_str(), progress: this.progress / 10 + '%', remaining: this.remaining.to_fsize_str(), downloaded: this.downloaded.to_fsize_str(), uploaded: this.uploaded.to_fsize_str(), upload_speed: this.upload_speed.to_fsize_str() + '/s', download_speed: this.download_speed.to_fsize_str() + '/s', time_remaining: Math.ceil(this.time_remaining / 60) + ' min', date_added: this.date_added.to_str(), date_completed: this.date_completed.to_str(), save_path: this.save_path, }, { omit: [ 'utorrent', 'status', 'ratio', 'label', 'availability', 'queue_position', 'download_url', 'rss_feed_url', 'stream_id', 'app_update_url', ...(() => { if (this.state[0] === '做种') return ['progress', 'downloaded', 'download_speed', 'time_remaining', 'seeds_connected', 'remaining', 'status_message']; if (this.state[0] === '下载') return []; return ['peers', 'upload_speed', 'download_speed', 'time_remaining', 'peers_connected', 'peers_in_swarm', 'seeds_connected', 'seeds_in_swarm']; })() ] }); } to_str() { return '' + /* 种子名称 */ this.name.limit(100) + ' ' + /* 状态 */ this.format_state() + ' ' + /* 进度 */ (this.state[0] !== '做种' ? (this.progress / 10 + '%') : '').pad(6) + ' ' + /* 下载速度 */ this.format_download_speed() + ' / ' + /* 上传速度 */ this.format_upload_speed() + ' | ' + /* 累计上传大小 */ this.uploaded.to_fsize_str().pad(8) + ' ' + /* 已连接种子数 (总种子数) */ (this.seeds_connected.toString().pad(3) + '(' + this.seeds_in_swarm + ')').pad(7) + '/ ' + /* 已连接用户数 (总用户数) */ (this.peers_connected.toString().pad(3) + '(' + this.peers_in_swarm + ')').pad(8) + ' ' + /* 剩余时间 */ this.format_time_remaining(); } format_upload_speed() { const s = `${this.upload_speed.to_fsize_str()}/s`.pad(12); return this.upload_speed > 500 * 2 ** 10 ? s.green : s; } format_download_speed() { const width = 12; if (this.state[0] !== '下载') return ''.pad(width); const s = `${this.download_speed.to_fsize_str()}/s`.pad(width); return (this.download_speed > 100 * 2 ** 10 ? s.magenta : s.yellow); } format_state() { const s = (this.state[0] + (this.state[1] ? ` (${this.state[1]})` : '')).pad(10); return this.state[0] === '下载' ? s.yellow : s; } format_time_remaining() { if (this.state[0] !== '下载') return ''; const hours = Math.floor(this.time_remaining / 3600); const minutes = Math.ceil(this.time_remaining / 60); if (hours === -1) return '-'; if (hours) return (hours + ' 小时').yellow; if (minutes) return (minutes + ' 分钟').yellow; return ''; } } exports.Torrent = Torrent; class UTorrent { root_url; username; password; fp_ipfilter; /** 启动时 ipfilter 中已有的数据 */ static_ipfilter; /** 检测 peers 并屏蔽的时间间隔 */ interval; /** 间隔 interval 秒自动重置当前时间间隔内被动态屏蔽的 IP */ interval_reset; state = 'INIT'; state_resetting = 'INIT'; print = { torrents: '所有', peers: true }; token; blocked_ips = new Set(); torrents; static async launch(exe) { xshell_1.start(exe, [], { detached: true }); await xshell_1.delay(1000 * 20); } constructor(data) { Object.assign(this, data); } static async connect(options) { let utorrent = new UTorrent({ ...options, static_ipfilter: await xshell_1.fread(options.fp_ipfilter), blocked_ips: new Set(), print: (() => { if (!('print' in options)) return { torrents: true, peers: true }; if (typeof options.print === 'boolean') return { torrents: options.print, peers: options.print }; return options.print; })() }); await utorrent.get_token(); return utorrent; } /** get token */ async get_token() { const $ = await xshell_1.request_page(`${this.root_url}token.html`, { auth: { username: this.username, password: this.password } }); this.token = $('#token').text(); } async rpc({ api = '', params, method = 'GET' } = {}) { return await xshell_1.request_json(this.root_url + api, { method, queries: { token: this.token, ...params }, auth: { username: this.username, password: this.password } }); } async get_torrents() { return this.torrents = (await this.rpc({ params: { list: 1 } })).torrents.map((t) => new Torrent(t, this)); } async get_peers(torrents) { if (!torrents) torrents = await this.get_torrents(); return (await Promise.all(torrents .filter(torrent => torrent.state[0] === '下载' || torrent.state[0] === '做种') .map(async (torrent) => await torrent.get_peers()))).flat(); } async print_peers(peers, { limit } = {}) { if (!peers) peers = await this.get_peers(); peers .sort((a, b) => -(((a.should_block ? 1 : 0) * 2 ** 30 + a.download_speed * 10 + a.upload_speed) - ((b.should_block ? 1 : 0) * 2 ** 30 + b.download_speed * 10 + b.upload_speed))) .filter((peer, i) => peer.should_block || (peer.upload_speed + peer.download_speed) > 5 * 2 ** 10 && (limit ? i < limit : true)) .forEach(peer => { const s = peer.to_str(); console.log(peer.should_block ? s.red : s); }); } async print_torrents({ torrents, filter = torrent => torrent.state[0] === '下载' || (torrent.state[0] === '做种' ? torrent.upload_speed > 10 * 2 ** 10 : false) } = {}) { if (!torrents) torrents = await this.get_torrents(); torrents .filter(filter) .filter(torrent => this.print.torrents && (this.print.torrents === '所有' || this.print.torrents === torrent.state[0])) .sort((a, b) => -(((a.state[0] === '下载' ? 1 : 0) * 2 ** 30 + a.download_speed * 10 + a.upload_speed) - ((b.state[0] === '下载' ? 1 : 0) * 2 ** 30 + b.download_speed * 10 + b.upload_speed))) .forEach(torrent => { console.log(torrent.to_str()); }); } async print_blockeds() { this.blocked_ips.forEach(ip => { console.log(ip.pad(20) + get_ip_info(ip)); }); } async block_peers(torrents) { if (!torrents) torrents = await this.get_torrents(); const peers = await this.get_peers(torrents.filter(torrent => torrent.download_speed + torrent.upload_speed > 20 * 2 ** 10)); const peers2block = peers.filter(peer => peer.should_block); if (this.print.peers) await this.print_peers(peers, { limit: 50 }); if (!peers2block.length) return; peers2block.forEach(peer => { this.blocked_ips.add(peer.ip); }); await xshell_1.fwrite(this.fp_ipfilter, (this.static_ipfilter + '\n' + [...this.blocked_ips].join_lines() + '\n'), { print: Boolean(this.print.peers || this.print.torrents) }); await this.reload_ipfilter(); return peers2block; } async reload_ipfilter() { return await this.rpc({ params: { action: 'setsetting', s: 'ipfilter.enable', v: '1' } }); } async reset_ipfilter() { this.blocked_ips = new Set(); await xshell_1.fwrite(this.fp_ipfilter, ''); await this.reload_ipfilter(); } async reset_blocked_ips() { await xshell_1.fwrite(this.fp_ipfilter, this.static_ipfilter); xshell_1.log_section('reset dynamic blocked ips', { time: true }); this.blocked_ips = new Set(); await utorrent.reload_ipfilter(); } find_torrent(name_pattern) { if (typeof name_pattern === 'string') name_pattern = new RegExp(name_pattern, 'i'); return this.torrents.find(torrent => name_pattern.test(torrent.name)); } async start_blocking() { await this.get_token(); if (this.state === 'RUNNING') return; if (this.state === 'IDLE') { this.state = 'RUNNING'; return; } // this.state === 'INIT' this.state = 'RUNNING'; (async () => { while (this.state === 'RUNNING') { if (this.print.torrents || this.print.peers) console.log('\n\n\n' + new Date().to_str()); try { if (this.print.torrents) { await this.print_torrents(); xshell_1.log_line(200); await this.block_peers(this.torrents); } else await this.block_peers(); } catch (error) { this.state = 'INIT'; throw error; } await xshell_1.delay(this.interval); } if (this.state === 'IDLE') this.state = 'INIT'; })(); await this.start_resetting_blocked_ips(); } async start_resetting_blocked_ips() { if (this.state_resetting === 'RUNNING') return; if (this.state_resetting === 'IDLE') { this.state_resetting = 'RUNNING'; return; } // this.state_resetting === 'INIT' this.state_resetting = 'RUNNING'; (async () => { while (true) { await xshell_1.delay(this.interval_reset); if (this.state_resetting !== 'RUNNING') break; await this.reset_blocked_ips(); } if (this.state_resetting === 'IDLE') this.state_resetting = 'INIT'; })(); } async stop_blocking() { if (this.state === 'INIT') { console.log('blocking hasn\'t started'); return; } if (this.state === 'IDLE') { console.log('blocking already stopped'); return; } // this.state === 'RUNNING' this.state = 'IDLE'; await this.reset_blocked_ips(); console.log('uTorrent stopped blocking'); } hide_display() { this.print = { torrents: false, peers: false }; } show_display() { this.print = { torrents: '所有', peers: true }; } } exports.UTorrent = UTorrent; const qqwry = new lib_qqwry_1.default(true); function get_ip_info(ip) { /* const data = await request_market_api({ url: 'https://service-jr977f7k-1301115409.gz.apigw.tencentcs.com/release/', secret_id: 'AKIDBggAWhLvbewlabivKmwTKs5ZGzm37tbZ642', secret_key: 'aL3TRtUyWtDHcUlA8US467CMT4hn87JE64XX7E0U', queries: { ip: '' } }) */ if (!net_1.isIPv4(ip)) return ''; const { Country, Area } = qqwry.searchIP(ip); const country = Country.trim().replace('浙金省', '浙江省').space(); const area = Area.trim().rm('CZ88.NET').space(); return country + (area ? '/' + area : ''); } exports.default = UTorrent; //# sourceMappingURL=index.js.map