UNPKG

utorrent-block-xunlei

Version:
334 lines (333 loc) 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResumeData = exports.TorrentFile = exports.TorrentData = void 0; const tslib_1 = require("tslib"); const bencode_1 = tslib_1.__importDefault(require("bencode")); const omit_js_1 = tslib_1.__importDefault(require("lodash/omit.js")); const sortBy_js_1 = tslib_1.__importDefault(require("lodash/sortBy.js")); const xshell_1 = require("xshell"); class TorrentData { fp_torrent; /** 种子任务名称 */ caption; /** 错误说明,如:任务文件丢失。请重新检查。 */ dl_error; /** 若种子文件只包含一个文件,则 path 为该文件的完整存储路径, 否则 path 为文件夹的位置 */ path; /** 高级 > 设置目标名称 文件位置的重定位表,格式为 [index, fp][],未重定位则无此字段 index: 种子文件的文件列表所对应的文件序号 (从 0 开始编号) fp: 文件重定位后的相对 path 的路径 比如: 0 : 1.mkv 2 : 3.mkv 351: SPs/24-blabla.mkv */ targets; downloaded; uploaded; blocksize; trackers; labels; started; valid; have; hashed; known; constructor(fp_torrent, data) { this.fp_torrent = fp_torrent.to_slash(); Object.entries(data).forEach(([key, value]) => { if (key === 'caption' || key === 'dl_error') { this[key] = value.toString(); return; } if (key === 'trackers' || key === 'labels') { this[key] = value.map(buf => buf.toString()); return; } if (key === 'targets') { this[key] = value.map(([index, fp]) => ([index, fp.toString().to_slash()])); return; } if (key === 'path') { this[key] = value.toString().to_slash(); return; } this[key] = value; }); } to_str() { const title_width = 14; return `${this.caption}\n` + [ `${'种子:'.pad(title_width)}${this.fp_torrent}`, `${'文件夹:'.pad(title_width)}${this.path}`, `${'标签:'.pad(title_width)}${this.labels.join(', ')}`, `${'已下载:'.pad(title_width)}${this.downloaded.to_fsize_str()} / 已上传: ${this.uploaded.to_fsize_str()} 分块大小: ${this.blocksize.to_fsize_str()}`, ...!this.valid ? [ `${'错误:'.pad(title_width)}${this.dl_error || ''}` ] : [], `${'trackers:'.pad(title_width)}Array<${this.trackers.length}> [${this.trackers.slice(0, 2).join(', ')}]`, ...this.targets ? [ '文件重定位:', ...this.targets.map(([index, fp]) => `${index}: ${fp}`).indent(2), ] : [], ].indent(2).join_lines(); } [xshell_1.inspect.custom]() { return this.to_str(); } to_data() { const data = omit_js_1.default(this, ['fp_torrent']); return { ...data, ...this.targets ? { targets: this.targets.map(([index, fp]) => ([index, fp.to_backslash()])) } : {}, path: this.path.to_backslash(), }; } relocate_torrent(fp_torrent) { if (!fp_torrent.fexists) throw new Error(`fp_torrent 文件不存在: ${fp_torrent}`); this.fp_torrent = fp_torrent.to_backslash(); } fix() { this.started = 0; this.valid = 1; this.have.fill(0xff); this.hashed.fill(0xff); this.known.fill(0xff); } } exports.TorrentData = TorrentData; class TorrentFile { info; constructor(data) { Object.assign(this, data); } static async parse(fp) { const data = bencode_1.default.decode(await xshell_1.fread(fp, { encoding: 'BINARY' })); const files = data.info.files; return new TorrentFile({ ...data, info: { ...data.info, ...files ? { files: files.map(file => ({ length: file.length, path: file.path.map((fp) => fp.toString()).join('/') })) } : {} } }); } get_fps() { return this.info.files.map(file => file.path); } [xshell_1.inspect.custom]() { return [ 'TorrentFile {', ...[ 'files:', ...this.get_fps().map((fp, i) => i.toString().pad(4) + ': ' + fp).indent(2), '' ].indent(2), '}', ].join_lines(); } } exports.TorrentFile = TorrentFile; class ResumeData { fp_resume_dat; rec; torrents; constructor(data) { Object.assign(this, data); } static async parse(fp_resume_dat) { const data = bencode_1.default.decode(await xshell_1.fread(fp_resume_dat, { encoding: 'BINARY' })); return new ResumeData({ fp_resume_dat, rec: data.rec, torrents: sortBy_js_1.default(Object.entries(data).filter(([key]) => key !== 'rec' && key !== '.fileguard').map(([fp_torrent, data]) => new TorrentData(fp_torrent, data)), 'caption') }); } async save(fp = this.fp_resume_dat) { await xshell_1.fwrite(fp, bencode_1.default.encode({ rec: this.rec, ...Object.fromEntries(this.torrents.map(torrent => ([torrent.fp_torrent.to_backslash(), torrent.to_data()]))) })); } relocate_paths(src, dest, filter) { // const td: TorrentData = this[key] // if (filter && !filter(td)) continue // const path_ = td.path.replace(src, dest) // console.log(td.path, ' → ', path_) // td.path = path_ } /** 根据 pattern 查找首个匹配的种子,不传 pattern 参数时返回最近添加的种子 */ find_torrent(pattern) { if (!pattern) return sortBy_js_1.default(this.torrents, 'added_on').last; return this.torrents.find(torrent => torrent.caption.match(pattern)); } find_torrents(pattern) { return this.torrents.filter(torrent => torrent.caption.match(pattern)); } [xshell_1.inspect.custom]() { return 'ResumeData {\n' + [ `fp_resume_dat: ${this.fp_resume_dat}\n`, ...this.torrents.map(torrent => { return [ ...torrent.to_str().split_lines(), '' ]; }) ].flat().indent(2).join_lines() + '\n' + '}'; } } exports.ResumeData = ResumeData; exports.default = ResumeData; async function repl_rename_onepice() { const dir = 'G:/ACGN/海贼王/'; // ------------ 选择种子任务 let resume_data = await ResumeData.parse('C:/Users/shf/AppData/Roaming/uTorrent/resume.dat'); let torrents = sortBy_js_1.default(resume_data.torrents, 'added_on').reverse().slice(0, 5); let torrent = torrents[0]; // let torrent = resume_data.get_torrent('[Skytree][海贼王]'.to_regx()) // 种子文件 const fp_torrent = torrent.fp_torrent; // episode const episode = torrent.caption.find('[Skytree][海贼王][One_Piece][{episode}][GB_BIG5_JP][X264_AAC][1080P][CRRIP][天空树双语字幕组]').episode; // episode = 921 // 下载的文件 const fp_mkv = torrent.path; // 新的种子文件路径 const fp_torrent_ = `${dir}${episode}.torrent`; // 移动种子文件 await xshell_1.fmove(fp_torrent, fp_torrent_); // 移动下载好的 mkv await xshell_1.fmove(fp_mkv, `${dir}${episode}.mkv`); // ------------ 更新任务 torrent.caption = `海贼王 ${episode}`; torrent.fp_torrent = `${dir}${episode}.torrent`; torrent.path = `${dir}${episode}.mkv`; torrent.labels = ['海贼王']; await resume_data.save(); } async function repl_resume_dat() { // 加载数据 let resume_data = await ResumeData.parse('C:/Users/shf/AppData/Roaming/uTorrent/resume.dat'); // ------------ 选择要修改的种子 let torrents = resume_data.torrents.filter(({ caption, labels }) => labels.includes('电影')); let torrent = torrents[0]; // let torrents = resume_data.torrents.filter(({ started, labels, targets, caption }) => labels.includes('ACGN') && started === 4 && !targets && caption.includes('Tom')) // let torrent = torrents[0] // let torrent = resume_data.find_torrent('Tom') // 按顺序 // let torrent = resume_data.torrents[3] // 按名称 // let torrent = resume_data.find_torrent('君の') // let torrents = resume_data.find_torrents('〈物語〉シリーズ') // 最近添加 // let torrent = sort_by(resume_data.torrents, 'added_on').reverse()[0] // let torrents = sort_by(resume_data.torrents, 'added_on').reverse().slice(0, 5) // 私有种子 let torrent_pt = sortBy_js_1.default(resume_data.torrents, 'added_on').reverse()[0]; // ------------ 指定存放目录及名称 // 获取当前种子路径等信息 const { fp_torrent, caption } = torrent; // 设置主标题 ( 注意日文空格 ' '.charCodeAt(0) ) const name = '妹さえいればいい。'; // 设置 & 打开 存放目录 const dir = `G:/ACGN/${name}/`; // const dir = 'G:/ACGN/WHITE ALBUM/1.anime/' // 设置版本 let version = ''; // 设置压制信息 const info = 'BDRip 1080p x265 Ma10p FLAC'; // const info = 'BDRip 1080p x265 Ma10p FLACx2' // const info = 'BDRip 1080p x265 Ma10p FLACx3' // const info = 'BDRip 1080p H.265.10bit FLACx2' // const info = 'BDRip 1080p x265 Ma10p FLAC+AAC' // const info = 'BDRip 1080p H.265 YUV444P10 MKV FLAC Chap' // const info = 'BDRip 1080p H.265 YUV420P10 QAAC' // const info = 'BDRip 1080p x264 Hi10p FLAC' // const info = 'BDRip 1080p H.264 Hi10p FLAC' // const info = 'BDRip 1080p H.264 YUV420P10 MKV FLAC' // const info = 'BDRip 1080p H.264 FLAC' // const info = 'BDRip 1080p x264 8bit FLAC' // const info = 'BDRip 1080p x264 Hi10p 2FLAC' // const info = 'BDRip 720p x264 Hi10p FLAC' // const info = 'BDRip 816p x265 Ma10p FLAC' // const info = '1080p MP4' // const info = '' // 设置字幕组/来源 const origin = 'VCB-Studio'; // const origin = 'DHR & MakariHoshiyume & VCB-Studio' // const origin = 'Snow-Raws' // 生成 caption const caption_ = name + (version ? ` ${version.bracket('ROUND')}` : '') + (info ? ` ${info.bracket('SQUARE')}` : '') + (origin ? ` ${origin.bracket('SQUARE')}` : ''); // 生成种子路径 const fp_torrent_ = `${dir}${caption_}.torrent`; // ------------ 保存修改到 torrent // 移动种子 await xshell_1.fmove(fp_torrent, fp_torrent_); // 修改任务标题 torrent.caption = caption_; // 修改种子路径 torrent.fp_torrent = fp_torrent_; // 修改任务文件夹 torrent.path = dir; // 修改任务标签 torrent.labels = ['ACGN']; // torrent.labels = ['电影'] // 参考原 torrent 中的 files // const ftorrent = await TorrentFile.parse(fp_torrent_) const ftorrent = await TorrentFile.parse(torrent.fp_torrent); const ftorrent_pt = await TorrentFile.parse(fp_torrent.replace('.torrent', '.pt.torrent')); // 查看种子中的文件列表 ftorrent.info.files.forEach(({ path }, i) => { console.log(`${String(i).pad(3, { character: '0', position: 'left' }).green} ${path}`); }); // (optional) 修改文件名 // [[58, 'BDRips/01.mkv'], [59, 'BDRips/01.sc.ass'], ... ] torrent.targets = [ ...ftorrent.get_fps().slice(58).map((fp, index) => ([58 + index, fp.refmt('[VCB-S]Hanamonogatari[1080p]/{any}', '花物語/{any}')])), ]; let fps = await xshell_1.flist('G:/ACGN/猫和老鼠/', { absolute: false }); fps = fps.slice(37); torrent.targets = fps.map((fp, i) => ([37 + i, fp])); // (optional) 重命名文件 ① await Promise.all([ ftorrent.get_fps().slice(0, 37).map(async (fp, index) => { const fp_ = fp.refmt('TOM AND JERRY EP{episode} {title}.mkv', '{episode} {title}.mkv'); // console.log(fp_) await xshell_1.frename(fp, fp_, { dir }); }) ]); // (optional) 重命名文件 ② const text = ''; // paste() await Promise.all([ text.split_lines().trim_lines().map(async (fp) => { const fp_ = fp.refmt('{episode} {title}.mkv', '0{episode} {title}.mkv'); // console.log(fp_) await xshell_1.frename(fp, fp_, { dir }); }) ]); // (optional) 应用到 .pt.torrent torrent_pt.caption = torrent.caption + '.pt'; torrent_pt.fp_torrent = torrent.fp_torrent.replace('.torrent', '.pt.torrent'); torrent_pt.path = torrent.path; torrent_pt.labels = torrent.labels; torrent_pt.targets = torrent.targets; // ------------ 确认修改后的种子 torrent; torrent_pt; resume_data; sortBy_js_1.default(resume_data.torrents, 'added_on').reverse().slice(0, 5); // ------------ 保存修改到 resume.dat await resume_data.save(); } //# sourceMappingURL=resume-data.js.map