UNPKG

oicq

Version:
443 lines (442 loc) 14.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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.Gfs = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const crypto_1 = require("crypto"); const stream_1 = require("stream"); const core_1 = require("./core"); const errors_1 = require("./errors"); const common = __importStar(require("./common")); const internal_1 = require("./internal"); function checkRsp(rsp) { if (!rsp[1]) return; (0, errors_1.drop)(rsp[1], rsp[2]); } /** * 群文件系统 * fid表示一个文件或目录的id,pid表示它所在目录的id * 根目录的id为"/" * 只能在根目录下创建目录 * 删除一个目录会删除下面的全部文本 */ class Gfs { constructor(c, gid) { this.c = c; this.gid = gid; common.lock(this, "c"); common.lock(this, "gid"); } /** `this.gid`的别名 */ get group_id() { return this.gid; } /** 返回所在群的实例 */ get group() { return this.c.pickGroup(this.gid); } /** 返回所属的客户端对象 */ get client() { return this.c; } /** 获取使用空间和文件数 */ async df() { const [a, b] = await Promise.all([(async () => { const body = core_1.pb.encode({ 4: { 1: this.gid, 2: 3 } }); const payload = await this.c.sendOidb("OidbSvc.0x6d8_3", body); const rsp = core_1.pb.decode(payload)[4][4]; const total = Number(rsp[4]), used = Number(rsp[5]), free = total - used; return { total, used, free }; })(), (async () => { const body = core_1.pb.encode({ 3: { 1: this.gid, 2: 2 } }); const payload = await this.c.sendOidb("OidbSvc.0x6d8_2", body); const rsp = core_1.pb.decode(payload)[4][3]; const file_count = Number(rsp[4]), max_file_count = Number(rsp[6]); return { file_count, max_file_count }; })()]); return Object.assign(a, b); } async _resolve(fid) { const body = core_1.pb.encode({ 1: { 1: this.gid, 2: 0, 4: String(fid) } }); const payload = await this.c.sendOidb("OidbSvc.0x6d8_0", body); const rsp = core_1.pb.decode(payload)[4][1]; checkRsp(rsp); return genGfsFileStat(rsp[4]); } /** 获取文件或目录属性 */ async stat(fid) { try { return await this._resolve(fid); } catch (e) { const files = await this.dir("/"); for (let file of files) { if (!file.is_dir) break; if (file.fid === fid) return file; } throw e; } } /** 列出目录下的所有文件和目录(默认pid为根目录`/`) */ async dir(pid = "/", start = 0, limit = 100) { const body = core_1.pb.encode({ 2: { 1: this.gid, 2: 1, 3: String(pid), 5: Number(limit) || 100, 13: Number(start) || 0 } }); const payload = await this.c.sendOidb("OidbSvc.0x6d8_1", body); const rsp = core_1.pb.decode(payload)[4][2]; checkRsp(rsp); const arr = []; if (!rsp[5]) return arr; const files = Array.isArray(rsp[5]) ? rsp[5] : [rsp[5]]; for (let file of files) { if (file[3]) arr.push(genGfsFileStat(file[3])); else if (file[2]) arr.push(genGfsDirStat(file[2])); } return arr; } /** `this.dir`的别名 */ ls(pid = "/", start = 0, limit = 100) { return this.dir(pid, start, limit); } /** 创建目录(只能在根目录下创建) */ async mkdir(name) { const body = core_1.pb.encode({ 1: { 1: this.gid, 2: 0, 3: "/", 4: String(name) } }); const payload = await this.c.sendOidb("OidbSvc.0x6d7_0", body); const rsp = core_1.pb.decode(payload)[4][1]; checkRsp(rsp); return genGfsDirStat(rsp[4]); } /** 删除文件或目录(删除目录会删除下面的所有文件) */ async rm(fid) { fid = String(fid); let rsp; if (!fid.startsWith("/")) { //rm file const file = await this._resolve(fid); const body = core_1.pb.encode({ 4: { 1: this.gid, 2: 3, 3: file.busid, 4: file.pid, 5: file.fid, } }); const payload = await this.c.sendOidb("OidbSvc.0x6d6_3", body); rsp = core_1.pb.decode(payload)[4][4]; } else { //rm dir const body = core_1.pb.encode({ 2: { 1: this.gid, 2: 1, 3: String(fid) } }); const payload = await this.c.sendOidb("OidbSvc.0x6d7_1", body); rsp = core_1.pb.decode(payload)[4][2]; } checkRsp(rsp); } /** 重命名文件或目录 */ async rename(fid, name) { fid = String(fid); let rsp; if (!fid.startsWith("/")) { //rename file const file = await this._resolve(fid); const body = core_1.pb.encode({ 5: { 1: this.gid, 2: 4, 3: file.busid, 4: file.fid, 5: file.pid, 6: String(name) } }); const payload = await this.c.sendOidb("OidbSvc.0x6d6_4", body); rsp = core_1.pb.decode(payload)[4][5]; } else { //rename dir const body = core_1.pb.encode({ 3: { 1: this.gid, 2: 2, 3: String(fid), 4: String(name) } }); const payload = await this.c.sendOidb("OidbSvc.0x6d7_2", body); rsp = core_1.pb.decode(payload)[4][3]; } checkRsp(rsp); } /** 移动文件 */ async mv(fid, pid) { const file = await this._resolve(fid); const body = core_1.pb.encode({ 6: { 1: this.gid, 2: 5, 3: file.busid, 4: file.fid, 5: file.pid, 6: String(pid) } }); const payload = await this.c.sendOidb("OidbSvc.0x6d6_5", body); const rsp = core_1.pb.decode(payload)[4][6]; checkRsp(rsp); } async _feed(fid, busid) { const body = core_1.pb.encode({ 5: { 1: this.gid, 2: 4, 3: { 1: busid, 2: fid, 3: (0, crypto_1.randomBytes)(4).readInt32BE(), 5: 1, } } }); const payload = await this.c.sendOidb("OidbSvc.0x6d9_4", body); let rsp = core_1.pb.decode(payload)[4][5]; checkRsp(rsp); rsp = rsp[4]; checkRsp(rsp); return await this._resolve(rsp[3]); } /** * 上传一个文件 * @param file string表示从该本地文件路径上传,Buffer表示直接上传这段内容 * @param pid 上传到此目录(默认根目录) * @param name file为Buffer时,若留空则自动以md5命名 * @param callback 监控上传进度的回调函数,拥有一个"百分比进度"的参数 */ async upload(file, pid = "/", name, callback) { let size, md5, sha1; if (file instanceof Uint8Array) { if (!Buffer.isBuffer(file)) file = Buffer.from(file); size = file.length; md5 = common.md5(file), sha1 = common.sha(file); name = name ? String(name) : ("file" + md5.toString("hex")); } else { file = String(file); size = (await fs_1.default.promises.stat(file)).size; [md5, sha1] = await common.fileHash(file); name = name ? String(name) : path_1.default.basename(file); } const body = core_1.pb.encode({ 1: { 1: this.gid, 2: 0, 3: 102, 4: 5, 5: String(pid), 6: name, 7: "/storage/emulated/0/Pictures/files/s/" + name, 8: size, 9: sha1, 11: md5, 15: 1, } }); const payload = await this.c.sendOidb("OidbSvc.0x6d6_0", body); const rsp = core_1.pb.decode(payload)[4][1]; checkRsp(rsp); if (!rsp[10]) { const ext = core_1.pb.encode({ 1: 100, 2: 1, 3: 0, 100: { 100: { 1: rsp[6], 100: this.c.uin, 200: this.gid, 400: this.gid, }, 200: { 100: size, 200: md5, 300: sha1, 600: rsp[7], 700: rsp[9], }, 300: { 100: 2, 200: String(this.c.apk.subid), 300: 2, 400: "9e9c09dc", 600: 4, }, 400: { 100: name, }, 500: { 200: { 1: { 1: 1, 2: rsp[12] }, 2: rsp[14] } }, } }); await internal_1.highwayUpload.call(this.c, Buffer.isBuffer(file) ? stream_1.Readable.from(file, { objectMode: false }) : fs_1.default.createReadStream(String(file), { highWaterMark: 1024 * 256 }), { cmdid: 71, callback, md5, size, ext }); } return await this._feed(String(rsp[7]), rsp[6]); } /** * 将文件转发到当前群 * @param stat 另一个群中的文件属性() * @param pid 转发后的目录(默认根目录) * @param name 转发后的文件名(默认不变) */ async forward(stat, pid = "/", name) { const body = core_1.pb.encode({ 1: { 1: this.gid, 2: 3, 3: 102, 4: 5, 5: String(pid), 6: String(name || stat.name), 7: "/storage/emulated/0/Pictures/files/s/" + (name || stat.name), 8: Number(stat.size), 9: Buffer.from(stat.sha1, "hex"), 11: Buffer.from(stat.md5, "hex"), 15: 1, } }); const payload = await this.c.sendOidb("OidbSvc.0x6d6_0", body); const rsp = core_1.pb.decode(payload)[4][1]; checkRsp(rsp); if (!rsp[10]) (0, errors_1.drop)(errors_1.ErrorCode.GroupFileNotExists, "文件不存在,无法被转发"); return await this._feed(String(rsp[7]), rsp[6]); } /** 获取文件下载地址 */ async download(fid) { const file = await this._resolve(fid); const body = core_1.pb.encode({ 3: { 1: this.gid, 2: 2, 3: file.busid, 4: file.fid, } }); const payload = await this.c.sendOidb("OidbSvc.0x6d6_2", body); const rsp = core_1.pb.decode(payload)[4][3]; checkRsp(rsp); return { name: file.name, url: `http://${rsp[4]}/ftn_handler/${rsp[6].toHex()}/?fname=${file.name}`, size: file.size, md5: file.md5, duration: file.duration, fid: file.fid, }; } } exports.Gfs = Gfs; function genGfsDirStat(file) { return { fid: String(file[1]), pid: String(file[2]), name: String(file[3]), create_time: file[4], user_id: file[6], file_count: file[8] || 0, is_dir: true, }; } function genGfsFileStat(file) { const stat = { fid: String(file[1]), pid: String(file[16]), name: String(file[2]), busid: file[4], size: file[5], md5: file[12].toHex(), sha1: file[10].toHex(), create_time: file[6], duration: file[7], user_id: file[15], download_times: file[9], is_dir: false, }; if (stat.fid.startsWith("/")) stat.fid = stat.fid.slice(1); return stat; }