+580
| "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; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.createClient = exports.Client = void 0; | ||
| const fs = __importStar(require("fs")); | ||
| const path = __importStar(require("path")); | ||
| const log4js = __importStar(require("log4js")); | ||
| const core_1 = require("./core"); | ||
| const pkg = require("../package.json"); | ||
| const common_1 = require("./common"); | ||
| const internal_1 = require("./internal"); | ||
| const friend_1 = require("./friend"); | ||
| const group_1 = require("./group"); | ||
| const member_1 = require("./member"); | ||
| const message_1 = require("./message"); | ||
| /** 一个客户端 */ | ||
| class Client extends core_1.BaseClient { | ||
| //@ts-ignore ts2376?? | ||
| constructor(uin, conf) { | ||
| const config = { | ||
| log_level: "info", | ||
| platform: core_1.Platform.Android, | ||
| auto_server: true, | ||
| ignore_self: true, | ||
| resend: true, | ||
| cache_group_member: true, | ||
| reconn_interval: 5, | ||
| data_dir: path.join(require?.main?.path || process.cwd(), "data"), | ||
| ...conf, | ||
| }; | ||
| const dir = createDataDir(config.data_dir, uin); | ||
| const file = path.join(dir, `device-${uin}.json`); | ||
| try { | ||
| var device = require(file); | ||
| var _ = false; | ||
| } | ||
| catch { | ||
| var device = (0, core_1.generateShortDevice)(uin); | ||
| var _ = true; | ||
| fs.writeFile(file, JSON.stringify(device, null, 2), common_1.NOOP); | ||
| } | ||
| super(uin, config.platform, device); | ||
| /** 得到一个群对象, 每个群都是单例 */ | ||
| this.pickGroup = group_1.Group.as.bind(this); | ||
| /** 得到一个好友对象, 每个好友都是单例 */ | ||
| this.pickFriend = friend_1.Friend.as.bind(this); | ||
| /** 得到一个群员对象, 开启群员列表缓存时,每个群员都是单例 */ | ||
| this.pickMember = member_1.Member.as.bind(this); | ||
| /** 创建一个用户对象 */ | ||
| this.pickUser = friend_1.User.as.bind(this); | ||
| /** 创建一个讨论组对象 */ | ||
| this.pickDiscuss = group_1.Discuss.as.bind(this); | ||
| this._cache = new Map(); | ||
| /** 好友列表(务必以`ReadonlyMap`方式访问) */ | ||
| this.fl = new Map(); | ||
| /** 陌生人列表(务必以`ReadonlyMap`方式访问) */ | ||
| this.sl = new Map(); | ||
| /** 群列表(务必以`ReadonlyMap`方式访问) */ | ||
| this.gl = new Map(); | ||
| /** 群员列表缓存(务必以`ReadonlyMap`方式访问) */ | ||
| this.gml = new Map(); | ||
| /** 黑名单列表(务必以`ReadonlySet`方式访问) */ | ||
| this.blacklist = new Set(); | ||
| /** 好友分组 */ | ||
| this.classes = new Map(); | ||
| /** 勿手动修改这些属性 */ | ||
| this.status = 0; | ||
| this.nickname = ""; | ||
| this.sex = "unknown"; | ||
| this.age = 0; | ||
| this.bid = ""; | ||
| /** 漫游表情缓存 */ | ||
| this.stamp = new Set(); | ||
| this.cookies = new Proxy(this.pskey, { | ||
| get: (obj, domain) => { | ||
| const cookie = `uin=o${this.uin}; skey=${this.sig.skey};`; | ||
| if (!obj[domain]) | ||
| return cookie; | ||
| return `${cookie} p_uin=o${this.uin}; p_skey=${obj[domain]};`; | ||
| }, | ||
| set: () => { | ||
| return false; | ||
| } | ||
| }); | ||
| this.logger = log4js.getLogger(`[${this.apk.display}:${uin}]`); | ||
| this.logger.level = config.log_level; | ||
| if (_) | ||
| this.logger.mark("创建了新的设备文件:" + file); | ||
| this.logger.mark("----------"); | ||
| this.logger.mark(`Package Version: oicq@${pkg.version} (Released on ${pkg.upday})`); | ||
| this.logger.mark("View Changelogs:https://github.com/takayama-lily/oicq/releases"); | ||
| this.logger.mark("----------"); | ||
| this.dir = dir; | ||
| this.config = config; | ||
| internal_1.bindInternalListeners.call(this); | ||
| this.on("internal.verbose", (verbose, level) => { | ||
| const list = ["fatal", "mark", "error", "warn", "info", "trace"]; | ||
| this.logger[list[level]](verbose); | ||
| }); | ||
| (0, common_1.lock)(this, "dir"); | ||
| (0, common_1.lock)(this, "config"); | ||
| (0, common_1.lock)(this, "_cache"); | ||
| (0, common_1.lock)(this, "internal"); | ||
| (0, common_1.lock)(this, "pickUser"); | ||
| (0, common_1.lock)(this, "pickFriend"); | ||
| (0, common_1.lock)(this, "pickGroup"); | ||
| (0, common_1.lock)(this, "pickDiscuss"); | ||
| (0, common_1.lock)(this, "pickMember"); | ||
| (0, common_1.lock)(this, "cookies"); | ||
| (0, common_1.lock)(this, "fl"); | ||
| (0, common_1.lock)(this, "gl"); | ||
| (0, common_1.lock)(this, "sl"); | ||
| (0, common_1.lock)(this, "gml"); | ||
| (0, common_1.lock)(this, "blacklist"); | ||
| (0, common_1.hide)(this, "_sync_cookie"); | ||
| let n = 0; | ||
| this.heartbeat = () => { | ||
| this._calcMsgCntPerMin(); | ||
| n++; | ||
| if (n > 10) { | ||
| n = 0; | ||
| this.setOnlineStatus(); | ||
| } | ||
| }; | ||
| if (!this.config.auto_server) | ||
| this.setRemoteServer("msfwifi.3g.qq.com", 8080); | ||
| } | ||
| get [Symbol.toStringTag]() { | ||
| return "OicqClient"; | ||
| } | ||
| /** csrf token */ | ||
| get bkn() { | ||
| let bkn = 5381; | ||
| for (let v of this.sig.skey) | ||
| bkn = bkn + (bkn << 5) + v; | ||
| bkn &= 2147483647; | ||
| return bkn; | ||
| } | ||
| /** 数据统计 */ | ||
| get stat() { | ||
| this.statistics.msg_cnt_per_min = this._calcMsgCntPerMin(); | ||
| return this.statistics; | ||
| } | ||
| /** 修改日志级别 */ | ||
| set log_level(level) { | ||
| this.logger.level = level; | ||
| this.config.log_level = level; | ||
| } | ||
| /** | ||
| * 会优先尝试使用token登录 (token在上次登录成功后存放在`this.dir`下) | ||
| * | ||
| * 无token或token失效时: | ||
| * * 传了`password`则尝试密码登录 | ||
| * * 不传`password`则尝试扫码登录 | ||
| * | ||
| * 掉线重连时也是自动调用此函数,走相同逻辑 | ||
| * 你也可以在配置中修改`reconn_interval`,关闭掉线重连并自行处理 | ||
| * | ||
| * @param password 可以为密码原文,或密码的md5值 | ||
| */ | ||
| async login(password) { | ||
| if (password && password.length > 0) { | ||
| let md5pass; | ||
| if (typeof password === "string") | ||
| md5pass = Buffer.from(password, "hex"); | ||
| else | ||
| md5pass = password; | ||
| if (md5pass.length !== 16) | ||
| md5pass = (0, common_1.md5)(String(password)); | ||
| this.password_md5 = md5pass; | ||
| } | ||
| try { | ||
| const token = await fs.promises.readFile(path.join(this.dir, "token")); | ||
| return this.tokenLogin(token); | ||
| } | ||
| catch { | ||
| if (this.password_md5) | ||
| return this.passwordLogin(this.password_md5); | ||
| else | ||
| return this.sig.qrsig.length ? this.qrcodeLogin() : this.fetchQrcode(); | ||
| } | ||
| } | ||
| /** 设置在线状态 */ | ||
| setOnlineStatus(status = this.status || common_1.OnlineStatus.Online) { | ||
| return internal_1.setStatus.call(this, status); | ||
| } | ||
| /** 设置昵称 */ | ||
| async setNickname(nickname) { | ||
| return this._setProfile(0x14E22, Buffer.from(String(nickname))); | ||
| } | ||
| /** 设置性别(1男2女) */ | ||
| async setGender(gender) { | ||
| return this._setProfile(0x14E29, Buffer.from([gender])); | ||
| } | ||
| /** 设置生日(20201202) */ | ||
| async setBirthday(birthday) { | ||
| const birth = String(birthday).replace(/[^\d]/g, ""); | ||
| const buf = Buffer.allocUnsafe(4); | ||
| buf.writeUInt16BE(Number(birth.substr(0, 4))); | ||
| buf[2] = Number(birth.substr(4, 2)); | ||
| buf[3] = Number(birth.substr(6, 2)); | ||
| return this._setProfile(0x16593, buf); | ||
| } | ||
| /** 设置个人说明 */ | ||
| async setDescription(description = "") { | ||
| return this._setProfile(0x14E33, Buffer.from(String(description))); | ||
| } | ||
| /** 设置个性签名 */ | ||
| async setSignature(signature = "") { | ||
| return internal_1.setSign.call(this, signature); | ||
| } | ||
| /** 设置头像 */ | ||
| async setAvatar(file) { | ||
| return internal_1.setAvatar.call(this, new message_1.Image({ type: "image", file })); | ||
| } | ||
| /** 获取漫游表情 */ | ||
| getRoamingStamp(no_cache = false) { | ||
| return internal_1.getStamp.call(this, no_cache); | ||
| } | ||
| /** 删除表情(支持批量) */ | ||
| deleteStamp(id) { | ||
| return internal_1.delStamp.call(this, id); | ||
| } | ||
| /** 获取系统消息 */ | ||
| getSystemMsg() { | ||
| return internal_1.getSysMsg.call(this); | ||
| } | ||
| /** 添加好友分组 */ | ||
| addClass(name) { | ||
| return internal_1.addClass.call(this, name); | ||
| } | ||
| /** 删除好友分组 */ | ||
| deleteClass(id) { | ||
| return internal_1.delClass.call(this, id); | ||
| } | ||
| /** 重命名好友分组 */ | ||
| renameClass(id, name) { | ||
| return internal_1.renameClass.call(this, id, name); | ||
| } | ||
| /** 重载好友列表 */ | ||
| reloadFriendList() { | ||
| return internal_1.loadFL.call(this); | ||
| } | ||
| /** 重载陌生人列表 */ | ||
| reloadStrangerList() { | ||
| return internal_1.loadSL.call(this); | ||
| } | ||
| /** 重载群列表 */ | ||
| reloadGroupList() { | ||
| return internal_1.loadGL.call(this); | ||
| } | ||
| /** 重载黑名单 */ | ||
| reloadBlackList() { | ||
| return internal_1.loadBL.call(this); | ||
| } | ||
| /** 清空缓存文件 fs.rm need v14.14 */ | ||
| cleanCache() { | ||
| const dir = path.join(this.dir, "../image"); | ||
| fs.rm?.(dir, { recursive: true }, () => { | ||
| fs.mkdir(dir, common_1.NOOP); | ||
| }); | ||
| } | ||
| /** 获取视频下载地址 */ | ||
| getVideoUrl(fid, md5) { | ||
| return this.pickFriend(this.uin).getVideoUrl(fid, md5); | ||
| } | ||
| /** 获取转发消息 */ | ||
| getForwardMsg(resid) { | ||
| return this.pickFriend(this.uin).getForwardMsg(resid); | ||
| } | ||
| /** 制作转发消息 */ | ||
| makeForwardMsg(fake, dm = false) { | ||
| return (dm ? this.pickFriend : this.pickGroup)(this.uin).makeForwardMsg(fake); | ||
| } | ||
| /** @cqhttp (cqhttp遗留方法) use client.cookies[domain] */ | ||
| getCookies(domain = "") { | ||
| return this.cookies[domain]; | ||
| } | ||
| /** @cqhttp use client.bkn */ | ||
| getCsrfToken() { | ||
| return this.bkn; | ||
| } | ||
| /** @cqhttp use client.fl */ | ||
| getFriendList() { | ||
| return this.fl; | ||
| } | ||
| /** @cqhttp use client.gl */ | ||
| getGroupList() { | ||
| return this.gl; | ||
| } | ||
| /** @cqhttp use client.sl */ | ||
| getStrangerList() { | ||
| return this.sl; | ||
| } | ||
| /** @cqhttp use user.getSimpleInfo() */ | ||
| async getStrangerInfo(user_id) { | ||
| return this.pickUser(user_id).getSimpleInfo(); | ||
| } | ||
| /** @cqhttp use group.info or group.renew() */ | ||
| async getGroupInfo(group_id, no_cache = false) { | ||
| const group = this.pickGroup(group_id); | ||
| if (no_cache) | ||
| return group.renew(); | ||
| return group.info || group.renew(); | ||
| } | ||
| /** @cqhttp use group.getMemberList() */ | ||
| async getGroupMemberList(group_id, no_cache = false) { | ||
| return this.pickGroup(group_id).getMemberMap(no_cache); | ||
| } | ||
| /** @cqhttp use member.info or member.renew() */ | ||
| async getGroupMemberInfo(group_id, user_id, no_cache = false) { | ||
| if (no_cache || !this.gml.get(group_id)?.has(user_id)) | ||
| return this.pickMember(group_id, user_id).renew(); | ||
| return this.gml.get(group_id)?.get(user_id); | ||
| } | ||
| /** @cqhttp use friend.sendMsg() */ | ||
| async sendPrivateMsg(user_id, message, source) { | ||
| return this.pickFriend(user_id).sendMsg(message); | ||
| } | ||
| /** @cqhttp use group.sendMsg() */ | ||
| async sendGroupMsg(group_id, message, source) { | ||
| return this.pickGroup(group_id).sendMsg(message); | ||
| } | ||
| /** @cqhttp use discuss.sendMsg() */ | ||
| async sendDiscussMsg(discuss_id, message, source) { | ||
| return this.pickDiscuss(discuss_id).sendMsg(message); | ||
| } | ||
| /** @cqhttp use member.sendMsg() */ | ||
| async sendTempMsg(group_id, user_id, message) { | ||
| return this.pickMember(group_id, user_id).sendMsg(message); | ||
| } | ||
| /** @cqhttp use user.recallMsg() or group.recallMsg() */ | ||
| async deleteMsg(message_id) { | ||
| if (message_id.length > 24) { | ||
| const { group_id, seq, rand, pktnum } = (0, message_1.parseGroupMessageId)(message_id); | ||
| return this.pickGroup(group_id).recallMsg(seq, rand, pktnum); | ||
| } | ||
| else { | ||
| const { user_id, seq, rand, time } = (0, message_1.parseDmMessageId)(message_id); | ||
| return this.pickUser(user_id).recallMsg(seq, rand, time); | ||
| } | ||
| } | ||
| /** @cqhttp use user.markRead() or group.markRead() */ | ||
| async reportReaded(message_id) { | ||
| if (message_id.length > 24) { | ||
| const { group_id, seq } = (0, message_1.parseGroupMessageId)(message_id); | ||
| return this.pickGroup(group_id).markRead(seq); | ||
| } | ||
| else { | ||
| const { user_id, time } = (0, message_1.parseDmMessageId)(message_id); | ||
| return this.pickUser(user_id).markRead(time); | ||
| } | ||
| } | ||
| /** @cqhttp use user.getChatHistory() or group.getChatHistory() */ | ||
| async getMsg(message_id) { | ||
| return (await this.getChatHistory(message_id, 1)).pop(); | ||
| } | ||
| /** @cqhttp use user.getChatHistory() or group.getChatHistory() */ | ||
| async getChatHistory(message_id, count = 20) { | ||
| if (message_id.length > 24) { | ||
| const { group_id, seq } = (0, message_1.parseGroupMessageId)(message_id); | ||
| return this.pickGroup(group_id).getChatHistory(seq, count); | ||
| } | ||
| else { | ||
| const { user_id, time } = (0, message_1.parseDmMessageId)(message_id); | ||
| return this.pickUser(user_id).getChatHistory(time, count); | ||
| } | ||
| } | ||
| /** @cqhttp use group.muteAnony() */ | ||
| async setGroupAnonymousBan(group_id, flag, duration = 1800) { | ||
| return this.pickGroup(group_id).muteAnony(flag, duration); | ||
| } | ||
| /** @cqhttp use group.allowAnony() */ | ||
| async setGroupAnonymous(group_id, enable = true) { | ||
| return this.pickGroup(group_id).allowAnony(enable); | ||
| } | ||
| /** @cqhttp use group.muteAll() */ | ||
| async setGroupWholeBan(group_id, enable = true) { | ||
| return this.pickGroup(group_id).muteAll(enable); | ||
| } | ||
| /** @cqhttp use group.setName() */ | ||
| async setGroupName(group_id, name) { | ||
| return this.pickGroup(group_id).setName(name); | ||
| } | ||
| /** @cqhttp use group.announce() */ | ||
| async sendGroupNotice(group_id, content) { | ||
| return this.pickGroup(group_id).announce(content); | ||
| } | ||
| /** @cqhttp use group.setAdmin() or member.setAdmin() */ | ||
| async setGroupAdmin(group_id, user_id, enable = true) { | ||
| return this.pickMember(group_id, user_id).setAdmin(enable); | ||
| } | ||
| /** @cqhttp use group.setSpecialTitle() or member.setSpecialTitle() */ | ||
| async setGroupSpecialTitle(group_id, user_id, special_title, duration = -1) { | ||
| return this.pickMember(group_id, user_id).setTitle(special_title, duration); | ||
| } | ||
| /** @cqhttp use group.setCard() or member.setCard() */ | ||
| async setGroupCard(group_id, user_id, card) { | ||
| return this.pickMember(group_id, user_id).setCard(card); | ||
| } | ||
| /** @cqhttp use group.kickMember() or member.kick() */ | ||
| async setGroupKick(group_id, user_id, reject_add_request = false) { | ||
| return this.pickMember(group_id, user_id).kick(reject_add_request); | ||
| } | ||
| /** @cqhttp use group.muteMember() or member.mute() */ | ||
| async setGroupBan(group_id, user_id, duration = 1800) { | ||
| return this.pickMember(group_id, user_id).mute(duration); | ||
| } | ||
| /** @cqhttp use group.quit() */ | ||
| async setGroupLeave(group_id) { | ||
| return this.pickGroup(group_id).quit(); | ||
| } | ||
| /** @cqhttp use group.pokeMember() or member.poke() */ | ||
| async sendGroupPoke(group_id, user_id) { | ||
| return this.pickMember(group_id, user_id).poke(); | ||
| } | ||
| /** @cqhttp use member.addFriend() */ | ||
| async addFriend(group_id, user_id, comment = "") { | ||
| return this.pickMember(group_id, user_id).addFriend(comment); | ||
| } | ||
| /** @cqhttp use friend.delete() */ | ||
| async deleteFriend(user_id, block = true) { | ||
| return this.pickFriend(user_id).delete(block); | ||
| } | ||
| /** @cqhttp use group.invite() */ | ||
| async inviteFriend(group_id, user_id) { | ||
| return this.pickGroup(group_id).invite(user_id); | ||
| } | ||
| /** @cqhttp use friend.thumbUp() */ | ||
| async sendLike(user_id, times = 1) { | ||
| return this.pickFriend(user_id).thumbUp(times); | ||
| } | ||
| /** @cqhttp user client.setAvatar() */ | ||
| async setPortrait(file) { | ||
| return this.setAvatar(file); | ||
| } | ||
| /** @cqhttp use group.setAvatar() */ | ||
| async setGroupPortrait(group_id, file) { | ||
| return this.pickGroup(group_id).setAvatar(file); | ||
| } | ||
| /** @cqhttp use group.fs */ | ||
| acquireGfs(group_id) { | ||
| return this.pickGroup(group_id).fs; | ||
| } | ||
| /** @cqhttp use user.setFriendReq() or user.addFriendBack() */ | ||
| async setFriendAddRequest(flag, approve = true, remark = "", block = false) { | ||
| const { user_id, seq, single } = (0, internal_1.parseFriendRequestFlag)(flag); | ||
| const user = this.pickUser(user_id); | ||
| return single ? user.addFriendBack(seq, remark) : user.setFriendReq(seq, approve, remark, block); | ||
| } | ||
| /** @cqhttp use user.setGroupInvite() or user.setGroupReq() */ | ||
| async setGroupAddRequest(flag, approve = true, reason = "", block = false) { | ||
| const { group_id, user_id, seq, invite } = (0, internal_1.parseGroupRequestFlag)(flag); | ||
| const user = this.pickUser(user_id); | ||
| return invite ? user.setGroupInvite(group_id, seq, approve, block) : user.setGroupReq(group_id, seq, approve, reason, block); | ||
| } | ||
| /** dont use it if not clear the usage */ | ||
| sendOidb(cmd, body, timeout = 5) { | ||
| const sp = cmd //OidbSvc.0x568_22 | ||
| .replace("OidbSvc.", "") | ||
| .replace("oidb_", "") | ||
| .split("_"); | ||
| const type1 = parseInt(sp[0], 16), type2 = parseInt(sp[1]); | ||
| body = core_1.pb.encode({ | ||
| 1: type1, | ||
| 2: isNaN(type2) ? 1 : type2, | ||
| 3: 0, | ||
| 4: body, | ||
| 6: "android " + this.apk.ver, | ||
| }); | ||
| return this.sendUni(cmd, body, timeout); | ||
| } | ||
| /** emit an event */ | ||
| em(name = "", data) { | ||
| data = Object.defineProperty(data || {}, "self_id", { | ||
| value: this.uin, | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| }); | ||
| while (true) { | ||
| this.emit(name, data); | ||
| let i = name.lastIndexOf("."); | ||
| if (i === -1) | ||
| break; | ||
| name = name.slice(0, i); | ||
| } | ||
| } | ||
| _msgExists(from, type, seq, time) { | ||
| if ((0, common_1.timestamp)() - time >= 60 || time < this.stat.start_time) | ||
| return true; | ||
| const id = [from, type, seq].join("-"); | ||
| const set = this._cache.get(time); | ||
| if (!set) { | ||
| this._cache.set(time, new Set([id])); | ||
| return false; | ||
| } | ||
| else { | ||
| if (set.has(id)) | ||
| return true; | ||
| else | ||
| set.add(id); | ||
| return false; | ||
| } | ||
| } | ||
| _calcMsgCntPerMin() { | ||
| let cnt = 0; | ||
| for (let [time, set] of this._cache) { | ||
| if ((0, common_1.timestamp)() - time >= 60) | ||
| this._cache.delete(time); | ||
| else | ||
| cnt += set.size; | ||
| } | ||
| return cnt; | ||
| } | ||
| async _setProfile(k, v) { | ||
| const buf = Buffer.allocUnsafe(11 + v.length); | ||
| buf.writeUInt32BE(this.uin); | ||
| buf.writeUInt8(0, 4); | ||
| buf.writeInt32BE(k, 5); | ||
| buf.writeUInt16BE(v.length, 9); | ||
| buf.fill(v, 11); | ||
| const payload = await this.sendOidb("OidbSvc.0x4ff_9", buf); | ||
| const obj = core_1.pb.decode(payload); | ||
| return obj[3] === 0 || obj[3] === 34; | ||
| } | ||
| /** @deprecated use client.submitSlider() */ | ||
| sliderLogin(ticket) { | ||
| return this.submitSlider(ticket); | ||
| } | ||
| /** @deprecated use client.sendSmsCode() */ | ||
| sendSMSCode() { | ||
| return this.sendSmsCode(); | ||
| } | ||
| /** @deprecated use client.submitSmsCode() */ | ||
| submitSMSCode(code) { | ||
| return this.submitSmsCode(code); | ||
| } | ||
| /** @deprecated use client.status */ | ||
| get online_status() { | ||
| return this.status; | ||
| } | ||
| } | ||
| exports.Client = Client; | ||
| function createDataDir(dir, uin) { | ||
| if (!fs.existsSync(dir)) | ||
| fs.mkdirSync(dir, { mode: 0o755, recursive: true }); | ||
| const img_path = path.join(dir, "image"); | ||
| const uin_path = path.join(dir, String(uin)); | ||
| if (!fs.existsSync(img_path)) | ||
| fs.mkdirSync(img_path); | ||
| if (!fs.existsSync(uin_path)) | ||
| fs.mkdirSync(uin_path, { mode: 0o755 }); | ||
| return uin_path; | ||
| } | ||
| /** 创建一个客户端 (=new Client) */ | ||
| function createClient(uin, config) { | ||
| if (isNaN(Number(uin))) | ||
| throw new Error(uin + " is not an OICQ account"); | ||
| return new Client(Number(uin), config); | ||
| } | ||
| exports.createClient = createClient; |
+180
| "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 __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
| for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.OnlineStatus = exports.MAX_UPLOAD_SIZE = exports.TMP_DIR = exports.IS_WIN = exports.PB_CONTENT = exports.DownloadTransform = exports.log = exports.escapeXml = exports.parseFunString = exports.uin2code = exports.code2uin = exports.fileHash = exports.md5Stream = exports.uuid = void 0; | ||
| const fs = __importStar(require("fs")); | ||
| const crypto = __importStar(require("crypto")); | ||
| const stream = __importStar(require("stream")); | ||
| const util = __importStar(require("util")); | ||
| const os = __importStar(require("os")); | ||
| const core_1 = require("./core"); | ||
| function uuid() { | ||
| let hex = crypto.randomBytes(16).toString("hex"); | ||
| return hex.substr(0, 8) + "-" + hex.substr(8, 4) + "-" + hex.substr(12, 4) + "-" + hex.substr(16, 4) + "-" + hex.substr(20); | ||
| } | ||
| exports.uuid = uuid; | ||
| /** 计算流的md5 */ | ||
| function md5Stream(readable) { | ||
| return new Promise((resolve, reject) => { | ||
| readable.on("error", reject); | ||
| readable.pipe(crypto.createHash("md5") | ||
| .on("error", reject) | ||
| .on("data", resolve)); | ||
| }); | ||
| } | ||
| exports.md5Stream = md5Stream; | ||
| /** 计算文件的md5和sha */ | ||
| function fileHash(filepath) { | ||
| const readable = fs.createReadStream(filepath); | ||
| const sha = new Promise((resolve, reject) => { | ||
| readable.on("error", reject); | ||
| readable.pipe(crypto.createHash("sha1") | ||
| .on("error", reject) | ||
| .on("data", resolve)); | ||
| }); | ||
| return Promise.all([md5Stream(readable), sha]); | ||
| } | ||
| exports.fileHash = fileHash; | ||
| /** 群号转uin */ | ||
| function code2uin(code) { | ||
| let left = Math.floor(code / 1000000); | ||
| if (left >= 0 && left <= 10) | ||
| left += 202; | ||
| else if (left >= 11 && left <= 19) | ||
| left += 469; | ||
| else if (left >= 20 && left <= 66) | ||
| left += 2080; | ||
| else if (left >= 67 && left <= 156) | ||
| left += 1943; | ||
| else if (left >= 157 && left <= 209) | ||
| left += 1990; | ||
| else if (left >= 210 && left <= 309) | ||
| left += 3890; | ||
| else if (left >= 310 && left <= 334) | ||
| left += 3490; | ||
| else if (left >= 335 && left <= 386) //335 336不确定 | ||
| left += 2265; | ||
| else if (left >= 387 && left <= 499) | ||
| left += 3490; | ||
| return left * 1000000 + code % 1000000; | ||
| } | ||
| exports.code2uin = code2uin; | ||
| /** uin转群号 */ | ||
| function uin2code(uin) { | ||
| let left = Math.floor(uin / 1000000); | ||
| if (left >= 202 && left <= 212) | ||
| left -= 202; | ||
| else if (left >= 480 && left <= 488) | ||
| left -= 469; | ||
| else if (left >= 2100 && left <= 2146) | ||
| left -= 2080; | ||
| else if (left >= 2010 && left <= 2099) | ||
| left -= 1943; | ||
| else if (left >= 2147 && left <= 2199) | ||
| left -= 1990; | ||
| else if (left >= 2600 && left <= 2651) | ||
| left -= 2265; | ||
| else if (left >= 3800 && left <= 3989) | ||
| left -= 3490; | ||
| else if (left >= 4100 && left <= 4199) | ||
| left -= 3890; | ||
| return left * 1000000 + uin % 1000000; | ||
| } | ||
| exports.uin2code = uin2code; | ||
| /** 解析彩色群名片 */ | ||
| function parseFunString(buf) { | ||
| if (buf[0] === 0xA) { | ||
| let res = ""; | ||
| try { | ||
| let arr = core_1.pb.decode(buf)[1]; | ||
| if (!Array.isArray(arr)) | ||
| arr = [arr]; | ||
| for (let v of arr) { | ||
| if (v[2]) | ||
| res += String(v[2]); | ||
| } | ||
| } | ||
| catch { } | ||
| return res; | ||
| } | ||
| else { | ||
| return String(buf); | ||
| } | ||
| } | ||
| exports.parseFunString = parseFunString; | ||
| /** xml转义 */ | ||
| function escapeXml(str) { | ||
| return str.replace(/[&"><]/g, function (s) { | ||
| if (s === "&") | ||
| return "&"; | ||
| if (s === "<") | ||
| return "<"; | ||
| if (s === ">") | ||
| return ">"; | ||
| if (s === "\"") | ||
| return """; | ||
| return ""; | ||
| }); | ||
| } | ||
| exports.escapeXml = escapeXml; | ||
| function log(any) { | ||
| if (any instanceof Buffer) | ||
| any = any.toString("hex").replace(/(.)(.)/g, '$1$2 '); | ||
| console.log(util.inspect(any, { depth: 20, showHidden: false, maxArrayLength: 1000, maxStringLength: 20000 })); | ||
| } | ||
| exports.log = log; | ||
| /** 用于下载限量 */ | ||
| class DownloadTransform extends stream.Transform { | ||
| constructor() { | ||
| super(...arguments); | ||
| this._size = 0; | ||
| } | ||
| _transform(data, encoding, callback) { | ||
| this._size += data.length; | ||
| let error = null; | ||
| if (this._size <= exports.MAX_UPLOAD_SIZE) | ||
| this.push(data); | ||
| else | ||
| error = new Error("downloading over 30MB is refused"); | ||
| callback(error); | ||
| } | ||
| } | ||
| exports.DownloadTransform = DownloadTransform; | ||
| exports.PB_CONTENT = core_1.pb.encode({ 1: 1, 2: 0, 3: 0 }); | ||
| exports.IS_WIN = os.platform().includes("win"); | ||
| /** 系统临时目录,用于临时存放下载的图片等内容 */ | ||
| exports.TMP_DIR = os.tmpdir(); | ||
| /** 最大上传和下载大小,以图片上传限制为准:30MB */ | ||
| exports.MAX_UPLOAD_SIZE = 31457280; | ||
| /** 可设置的在线状态 */ | ||
| var OnlineStatus; | ||
| (function (OnlineStatus) { | ||
| OnlineStatus[OnlineStatus["Online"] = 11] = "Online"; | ||
| OnlineStatus[OnlineStatus["Absent"] = 31] = "Absent"; | ||
| OnlineStatus[OnlineStatus["Invisible"] = 41] = "Invisible"; | ||
| OnlineStatus[OnlineStatus["Busy"] = 50] = "Busy"; | ||
| OnlineStatus[OnlineStatus["Qme"] = 60] = "Qme"; | ||
| OnlineStatus[OnlineStatus["DontDisturb"] = 70] = "DontDisturb"; | ||
| })(OnlineStatus = exports.OnlineStatus || (exports.OnlineStatus = {})); | ||
| __exportStar(require("./core/constants"), exports); |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.FACE_OLD_BUF = exports.pokemap = exports.facemap = void 0; | ||
| exports.facemap = { | ||
| 0: "惊讶", | ||
| 1: "撇嘴", | ||
| 2: "色", | ||
| 3: "发呆", | ||
| 4: "得意", | ||
| 5: "流泪", | ||
| 6: "害羞", | ||
| 7: "闭嘴", | ||
| 8: "睡", | ||
| 9: "大哭", | ||
| 10: "尴尬", | ||
| 11: "发怒", | ||
| 12: "调皮", | ||
| 13: "呲牙", | ||
| 14: "微笑", | ||
| 15: "难过", | ||
| 16: "酷", | ||
| 18: "抓狂", | ||
| 19: "吐", | ||
| 20: "偷笑", | ||
| 21: "可爱", | ||
| 22: "白眼", | ||
| 23: "傲慢", | ||
| 24: "饥饿", | ||
| 25: "困", | ||
| 26: "惊恐", | ||
| 27: "流汗", | ||
| 28: "憨笑", | ||
| 29: "悠闲", | ||
| 30: "奋斗", | ||
| 31: "咒骂", | ||
| 32: "疑问", | ||
| 33: "嘘", | ||
| 34: "晕", | ||
| 35: "折磨", | ||
| 36: "衰", | ||
| 37: "骷髅", | ||
| 38: "敲打", | ||
| 39: "再见", | ||
| 41: "发抖", | ||
| 42: "爱情", | ||
| 43: "跳跳", | ||
| 46: "猪头", | ||
| 49: "拥抱", | ||
| 53: "蛋糕", | ||
| 54: "闪电", | ||
| 55: "炸弹", | ||
| 56: "刀", | ||
| 57: "足球", | ||
| 59: "便便", | ||
| 60: "咖啡", | ||
| 61: "饭", | ||
| 63: "玫瑰", | ||
| 64: "凋谢", | ||
| 66: "爱心", | ||
| 67: "心碎", | ||
| 69: "礼物", | ||
| 74: "太阳", | ||
| 75: "月亮", | ||
| 76: "赞", | ||
| 77: "踩", | ||
| 78: "握手", | ||
| 79: "胜利", | ||
| 85: "飞吻", | ||
| 86: "怄火", | ||
| 89: "西瓜", | ||
| 96: "冷汗", | ||
| 97: "擦汗", | ||
| 98: "抠鼻", | ||
| 99: "鼓掌", | ||
| 100: "糗大了", | ||
| 101: "坏笑", | ||
| 102: "左哼哼", | ||
| 103: "右哼哼", | ||
| 104: "哈欠", | ||
| 105: "鄙视", | ||
| 106: "委屈", | ||
| 107: "快哭了", | ||
| 108: "阴险", | ||
| 109: "亲亲", | ||
| 110: "吓", | ||
| 111: "可怜", | ||
| 112: "菜刀", | ||
| 113: "啤酒", | ||
| 114: "篮球", | ||
| 115: "乒乓", | ||
| 116: "示爱", | ||
| 117: "瓢虫", | ||
| 118: "抱拳", | ||
| 119: "勾引", | ||
| 120: "拳头", | ||
| 121: "差劲", | ||
| 122: "爱你", | ||
| 123: "不", | ||
| 124: "好", | ||
| 125: "转圈", | ||
| 126: "磕头", | ||
| 127: "回头", | ||
| 128: "跳绳", | ||
| 129: "挥手", | ||
| 130: "激动", | ||
| 131: "街舞", | ||
| 132: "献吻", | ||
| 133: "左太极", | ||
| 134: "右太极", | ||
| 136: "双喜", | ||
| 137: "鞭炮", | ||
| 138: "灯笼", | ||
| 140: "K歌", | ||
| 144: "喝彩", | ||
| 145: "祈祷", | ||
| 146: "爆筋", | ||
| 147: "棒棒糖", | ||
| 148: "喝奶", | ||
| 151: "飞机", | ||
| 158: "钞票", | ||
| 168: "药", | ||
| 169: "手枪", | ||
| 171: "茶", | ||
| 172: "眨眼睛", | ||
| 173: "泪奔", | ||
| 174: "无奈", | ||
| 175: "卖萌", | ||
| 176: "小纠结", | ||
| 177: "喷血", | ||
| 178: "斜眼笑", | ||
| 180: "惊喜", | ||
| 181: "骚扰", | ||
| 182: "笑哭", | ||
| 183: "我最美", | ||
| 184: "河蟹", | ||
| 185: "羊驼", | ||
| 187: "幽灵", | ||
| 188: "蛋", | ||
| 190: "菊花", | ||
| 192: "红包", | ||
| 193: "大笑", | ||
| 194: "不开心", | ||
| 197: "冷漠", | ||
| 198: "呃", | ||
| 199: "好棒", | ||
| 200: "拜托", | ||
| 201: "点赞", | ||
| 202: "无聊", | ||
| 203: "托脸", | ||
| 204: "吃", | ||
| 205: "送花", | ||
| 206: "害怕", | ||
| 207: "花痴", | ||
| 208: "小样儿", | ||
| 210: "飙泪", | ||
| 211: "我不看", | ||
| 212: "托腮", | ||
| 214: "啵啵", | ||
| 215: "糊脸", | ||
| 216: "拍头", | ||
| 217: "扯一扯", | ||
| 218: "舔一舔", | ||
| 219: "蹭一蹭", | ||
| 220: "拽炸天", | ||
| 221: "顶呱呱", | ||
| 222: "抱抱", | ||
| 223: "暴击", | ||
| 224: "开枪", | ||
| 225: "撩一撩", | ||
| 226: "拍桌", | ||
| 227: "拍手", | ||
| 228: "恭喜", | ||
| 229: "干杯", | ||
| 230: "嘲讽", | ||
| 231: "哼", | ||
| 232: "佛系", | ||
| 233: "掐一掐", | ||
| 234: "惊呆", | ||
| 235: "颤抖", | ||
| 236: "啃头", | ||
| 237: "偷看", | ||
| 238: "扇脸", | ||
| 239: "原谅", | ||
| 240: "喷脸", | ||
| 241: "生日快乐", | ||
| 242: "头撞击", | ||
| 243: "甩头", | ||
| 244: "扔狗", | ||
| 245: "加油必胜", | ||
| 246: "加油抱抱", | ||
| 247: "口罩护体", | ||
| 260: "/搬砖中", | ||
| 261: "/忙到飞起", | ||
| 262: "/脑阔疼", | ||
| 263: "/沧桑", | ||
| 264: "/捂脸", | ||
| 265: "/辣眼睛", | ||
| 266: "/哦哟", | ||
| 267: "/头秃", | ||
| 268: "/问号脸", | ||
| 269: "/暗中观察", | ||
| 270: "/emm", | ||
| 271: "/吃瓜", | ||
| 272: "/呵呵哒", | ||
| 273: "/我酸了", | ||
| 274: "/太南了", | ||
| 276: "/辣椒酱", | ||
| 277: "/汪汪", | ||
| 278: "/汗", | ||
| 279: "/打脸", | ||
| 280: "/击掌", | ||
| 281: "/无眼笑", | ||
| 282: "/敬礼", | ||
| 283: "/狂笑", | ||
| 284: "/面无表情", | ||
| 285: "/摸鱼", | ||
| 286: "/魔鬼笑", | ||
| 287: "/哦", | ||
| 288: "/请", | ||
| 289: "/睁眼", | ||
| 290: "/敲开心", | ||
| 291: "/震惊", | ||
| 292: "/让我康康", | ||
| 293: "/摸锦鲤", | ||
| 294: "/期待", | ||
| 295: "/拿到红包", | ||
| 296: "/真好", | ||
| 297: "/拜谢", | ||
| 298: "/元宝", | ||
| 299: "/牛啊", | ||
| 300: "/胖三斤", | ||
| 301: "/好闪", | ||
| 302: "/左拜年", | ||
| 303: "/右拜年", | ||
| 304: "/红包包", | ||
| 305: "/右亲亲", | ||
| 306: "/牛气冲天", | ||
| 307: "/喵喵", | ||
| 308: "/求红包", | ||
| 309: "/谢红包", | ||
| 310: "/新年烟花", | ||
| 311: "/打call", | ||
| 312: "/变形", | ||
| 313: "/嗑到了", | ||
| 314: "/仔细分析", | ||
| 315: "/加油", | ||
| 316: "/我没事", | ||
| 317: "/菜狗", | ||
| 318: "/崇拜", | ||
| 319: "/比心", | ||
| 320: "/庆祝", | ||
| 321: "/老色痞", | ||
| 322: "/拒绝", | ||
| 323: "/嫌弃", | ||
| 324: "/吃糖", | ||
| }; | ||
| exports.pokemap = { | ||
| 0: "回戳", | ||
| 1: "戳一戳", | ||
| 2: "比心", | ||
| 3: "点赞", | ||
| 4: "心碎", | ||
| 5: "666", | ||
| 6: "放大招", | ||
| 2000: "敲门", | ||
| 2001: "抓一下", | ||
| 2002: "碎屏", | ||
| 2003: "勾引", | ||
| 2004: "手雷", | ||
| 2005: "结印", | ||
| 2006: "召唤术", | ||
| 2007: "玫瑰花", | ||
| 2009: "让你皮", | ||
| 2011: "宝贝球", | ||
| }; | ||
| exports.FACE_OLD_BUF = Buffer.from([0x00, 0x01, 0x00, 0x04, 0x52, 0xCC, 0xF5, 0xD0]); |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.Image = exports.parseImageFileParam = exports.buildImageFileParam = void 0; | ||
| const stream_1 = require("stream"); | ||
| const fs_1 = __importDefault(require("fs")); | ||
| const path_1 = __importDefault(require("path")); | ||
| const crypto_1 = require("crypto"); | ||
| const probe_image_size_1 = __importDefault(require("probe-image-size")); | ||
| const axios_1 = __importDefault(require("axios")); | ||
| const common_1 = require("../common"); | ||
| const TYPE = { | ||
| jpg: 1000, | ||
| png: 1001, | ||
| webp: 1002, | ||
| bmp: 1005, | ||
| gif: 2000, | ||
| face: 4, | ||
| }; | ||
| const EXT = { | ||
| 3: "png", | ||
| 4: "face", | ||
| 1000: "jpg", | ||
| 1001: "png", | ||
| 1002: "webp", | ||
| 1003: "jpg", | ||
| 1005: "bmp", | ||
| 2000: "gif", | ||
| 2001: "png", | ||
| }; | ||
| /** 构造图片file */ | ||
| function buildImageFileParam(md5, size, width, height, type) { | ||
| size = size || 0; | ||
| width = width || 0; | ||
| height = height || 0; | ||
| const ext = EXT[type] || "jpg"; | ||
| return md5 + size + "-" + width + "-" + height + "." + ext; | ||
| } | ||
| exports.buildImageFileParam = buildImageFileParam; | ||
| /** 从图片的file中解析出图片属性参数 */ | ||
| function parseImageFileParam(file) { | ||
| let md5, size, width, height, ext; | ||
| let sp = file.split("-"); | ||
| md5 = sp[0].slice(0, 32); | ||
| size = Number(sp[0].slice(32)) | 0; | ||
| width = Number(sp[1]) | 0; | ||
| height = Number(sp[2]) | 0; | ||
| sp = file.split("."); | ||
| ext = sp[1] || "jpg"; | ||
| return { md5, size, width, height, ext }; | ||
| } | ||
| exports.parseImageFileParam = parseImageFileParam; | ||
| class Image { | ||
| /** @param dm 是否私聊图片 */ | ||
| constructor(elem, dm = false, cachedir) { | ||
| this.dm = dm; | ||
| this.cachedir = cachedir; | ||
| /** 最终用于发送的对象 */ | ||
| this.proto = {}; | ||
| /** 图片属性 */ | ||
| this.md5 = (0, crypto_1.randomBytes)(16); | ||
| this.size = 0xffff; | ||
| this.width = 320; | ||
| this.height = 240; | ||
| this.type = 1000; | ||
| let { file, cache, timeout, headers, asface, origin } = elem; | ||
| this.origin = origin; | ||
| this.asface = asface; | ||
| this.setProto(); | ||
| if (file instanceof Buffer) { | ||
| this.task = this.fromProbeSync(file); | ||
| } | ||
| else if (file instanceof stream_1.Readable) { | ||
| this.task = this.fromReadable(file); | ||
| } | ||
| else if (typeof file !== "string") { | ||
| throw new Error("bad file param: " + file); | ||
| } | ||
| else if (file.startsWith("base64://")) { | ||
| this.task = this.fromProbeSync(Buffer.from(file.slice(9), "base64")); | ||
| } | ||
| else if (file.startsWith("http://") || file.startsWith("https://")) { | ||
| this.task = this.fromWeb(file, cache, headers, timeout); | ||
| } | ||
| else { | ||
| this.task = this.fromLocal(file); | ||
| } | ||
| } | ||
| /** 从服务端拿到fid后必须设置此值,否则图裂 */ | ||
| set fid(val) { | ||
| this._fid = val; | ||
| if (this.dm) { | ||
| this.proto[3] = val; | ||
| this.proto[10] = val; | ||
| } | ||
| else { | ||
| this.proto[7] = val; | ||
| } | ||
| } | ||
| setProperties(dimensions) { | ||
| if (!dimensions) | ||
| throw new Error("bad image file"); | ||
| this.width = dimensions.width; | ||
| this.height = dimensions.height; | ||
| this.type = TYPE[dimensions.type] || 1000; | ||
| } | ||
| parseFileParam(file) { | ||
| const { md5, size, width, height, ext } = parseImageFileParam(file); | ||
| const hash = Buffer.from(md5, "hex"); | ||
| if (hash.length !== 16) | ||
| throw new Error("bad file param: " + file); | ||
| this.md5 = hash; | ||
| size > 0 && (this.size = size); | ||
| this.width = width; | ||
| this.height = height; | ||
| TYPE[ext] & (this.type = TYPE[ext]); | ||
| this.setProto(); | ||
| } | ||
| async fromProbeSync(buf) { | ||
| const dimensions = probe_image_size_1.default.sync(buf); | ||
| this.setProperties(dimensions); | ||
| this.md5 = (0, common_1.md5)(buf); | ||
| this.size = buf.length; | ||
| this.readable = stream_1.Readable.from(buf, { objectMode: false }); | ||
| this.setProto(); | ||
| } | ||
| async fromReadable(readable, timeout) { | ||
| try { | ||
| readable = readable.pipe(new common_1.DownloadTransform); | ||
| timeout = timeout > 0 ? timeout : 60; | ||
| this.tmpfile = path_1.default.join(common_1.TMP_DIR, (0, common_1.uuid)()); | ||
| var id = setTimeout(() => { | ||
| readable.destroy(); | ||
| }, timeout * 1000); | ||
| const [dimensions, md5] = await Promise.all([ | ||
| // @ts-ignore | ||
| (0, probe_image_size_1.default)(readable, true), | ||
| (0, common_1.md5Stream)(readable), | ||
| (0, common_1.pipeline)(readable, fs_1.default.createWriteStream(this.tmpfile)), | ||
| ]); | ||
| this.setProperties(dimensions); | ||
| this.md5 = md5; | ||
| this.size = (await fs_1.default.promises.stat(this.tmpfile)).size; | ||
| this.readable = fs_1.default.createReadStream(this.tmpfile, { highWaterMark: 1024 * 256 }); | ||
| this.setProto(); | ||
| } | ||
| catch (e) { | ||
| this.deleteTmpFile(); | ||
| throw e; | ||
| } | ||
| finally { | ||
| clearTimeout(id); | ||
| } | ||
| } | ||
| async fromWeb(url, cache, headers, timeout) { | ||
| if (this.cachedir) { | ||
| this.cachefile = path_1.default.join(this.cachedir, (0, common_1.md5)(url).toString("hex")); | ||
| if (cache) { | ||
| try { | ||
| this.parseFileParam(await fs_1.default.promises.readFile(this.cachefile, "utf8")); | ||
| return; | ||
| } | ||
| catch { } | ||
| } | ||
| } | ||
| const readable = (await axios_1.default.get(url, { | ||
| headers, | ||
| maxRedirects: 3, | ||
| responseType: "stream", | ||
| })).data; | ||
| await this.fromReadable(readable, timeout); | ||
| this.cachefile && fs_1.default.writeFile(this.cachefile, buildImageFileParam(this.md5.toString("hex"), this.size, this.width, this.height, this.type), common_1.NOOP); | ||
| } | ||
| async fromLocal(file) { | ||
| try { | ||
| //收到的图片 | ||
| this.parseFileParam(file); | ||
| } | ||
| catch { | ||
| //本地图片 | ||
| file.startsWith("file://") && (file = file.slice(7)); | ||
| common_1.IS_WIN && file.startsWith("/") && (file = file.slice(1)); | ||
| const stat = await fs_1.default.promises.stat(file); | ||
| if (stat.size <= 0 || stat.size > common_1.MAX_UPLOAD_SIZE) | ||
| throw new Error("bad file size: " + stat.size); | ||
| const readable = fs_1.default.createReadStream(file); | ||
| const [dimensions, md5] = await Promise.all([ | ||
| // @ts-ignore | ||
| (0, probe_image_size_1.default)(readable, true), | ||
| (0, common_1.md5Stream)(readable) | ||
| ]); | ||
| readable.destroy(); | ||
| this.setProperties(dimensions); | ||
| this.md5 = md5; | ||
| this.size = stat.size; | ||
| this.readable = fs_1.default.createReadStream(file, { highWaterMark: 1024 * 256 }); | ||
| this.setProto(); | ||
| } | ||
| } | ||
| setProto() { | ||
| let proto; | ||
| if (this.dm) { | ||
| proto = { | ||
| 1: this.md5.toString("hex"), | ||
| 2: this.size, | ||
| 3: this._fid, | ||
| 5: this.type, | ||
| 7: this.md5, | ||
| 8: this.height, | ||
| 9: this.width, | ||
| 10: this._fid, | ||
| 13: this.origin ? 1 : 0, | ||
| 16: this.type === 4 ? 5 : 0, | ||
| 24: 0, | ||
| 25: 0, | ||
| 29: { | ||
| 1: this.asface ? 1 : 0 | ||
| }, | ||
| }; | ||
| } | ||
| else { | ||
| proto = { | ||
| 2: this.md5.toString("hex") + ".gif", | ||
| 7: this._fid, | ||
| 8: 0, | ||
| 9: 0, | ||
| 10: 66, | ||
| 12: 1, | ||
| 13: this.md5, | ||
| // 17: 3, | ||
| 20: this.type, | ||
| 22: this.width, | ||
| 23: this.height, | ||
| 24: 200, | ||
| 25: this.size, | ||
| 26: this.origin ? 1 : 0, | ||
| 29: 0, | ||
| 30: 0, | ||
| 34: { | ||
| 1: this.asface ? 1 : 0 | ||
| }, | ||
| }; | ||
| } | ||
| Object.assign(this.proto, proto); | ||
| } | ||
| /** 服务端图片失效时建议调用此函数 */ | ||
| deleteCacheFile() { | ||
| this.cachefile && fs_1.default.unlink(this.cachefile, common_1.NOOP); | ||
| } | ||
| /** 图片上传完成后建议调用此函数(文件存在系统临时目录中) */ | ||
| deleteTmpFile() { | ||
| this.tmpfile && fs_1.default.unlink(this.tmpfile, common_1.NOOP); | ||
| this.readable?.destroy(); | ||
| } | ||
| } | ||
| exports.Image = Image; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.buildMusic = void 0; | ||
| const axios_1 = __importDefault(require("axios")); | ||
| async function getQQSong(id) { | ||
| let rsp = await axios_1.default.get(`https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data={"comm":{"ct":24,"cv":0},"songinfo":{"method":"get_song_detail_yqq","param":{"song_type":0,"song_mid":"","song_id":${id}},"module":"music.pf_song_detail_svr"}}`, { responseType: "json" }); | ||
| rsp = rsp.data.songinfo.data.track_info; | ||
| let mid = rsp.mid, title = rsp.name, album = rsp.album.mid, singer = rsp.singer?.[0]?.name || "unknown"; | ||
| rsp = await axios_1.default.get(`http://u.y.qq.com/cgi-bin/musicu.fcg?g_tk=2034008533&uin=0&format=json&data={"comm":{"ct":23,"cv":0},"url_mid":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"4311206557","songmid":["${mid}"],"songtype":[0],"uin":"0","loginflag":1,"platform":"23"}}}&_=1599039471576`, { responseType: "json" }); | ||
| rsp = rsp.data.url_mid.data.midurlinfo[0]; | ||
| return { | ||
| title: title, | ||
| singer: singer, | ||
| jumpUrl: `https://i.y.qq.com/v8/playsong.html?platform=11&appshare=android_qq&appversion=10030010&hosteuin=oKnlNenz7i-s7c**&songmid=${mid}&type=0&appsongtype=1&_wv=1&source=qq&ADTAG=qfshare`, | ||
| musicUrl: rsp.purl, | ||
| preview: `http://y.gtimg.cn/music/photo_new/T002R180x180M000${album}.jpg`, | ||
| }; | ||
| } | ||
| async function get163Song(id) { | ||
| let rsp = await axios_1.default.get(`http://music.163.com/api/song/detail/?id=${id}&ids=[${id}]`, { responseType: "json" }); | ||
| rsp = rsp.data.songs[0]; | ||
| return { | ||
| title: rsp.name, | ||
| singer: rsp.artists[0].name, | ||
| jumpUrl: "https://y.music.163.com/m/song/" + id, | ||
| musicUrl: "http://music.163.com/song/media/outer/url?id=" + id, | ||
| preview: rsp.album.picUrl, | ||
| }; | ||
| } | ||
| async function getMiGuSong(id) { | ||
| let rsp = await axios_1.default.get(`https://c.musicapp.migu.cn/MIGUM2.0/v1.0/content/resourceinfo.do?copyrightId=${id}&resourceType=2`, { responseType: "json" }); | ||
| rsp = rsp.data.resource[0]; | ||
| let preview = ""; | ||
| try { | ||
| let a = await axios_1.default.get(`https://music.migu.cn/v3/api/music/audioPlayer/getSongPic?songId=${rsp.songId}`, { responseType: "json", headers: { referer: "https://music.migu.cn/v3/music/player/audio" } }); | ||
| preview = a.data.smallPic || ""; | ||
| } | ||
| catch { } | ||
| let url = await axios_1.default.get(`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/shareInfo.do?contentId=${rsp.contentId}&contentName=${rsp.songName}&resourceType=2&targetUserName=${rsp.singer}`, { responseType: "json" }); | ||
| let jumpUrl = url.data.url || "http://c.migu.cn/"; | ||
| return { | ||
| title: rsp.songName, | ||
| singer: rsp.singer, | ||
| jumpUrl, | ||
| musicUrl: rsp.newRateFormats ? rsp.newRateFormats[0].url.replace(/ftp:\/\/[^/]+/, "https://freetyst.nf.migu.cn") : rsp.rateFormats[0].url.replace(/ftp:\/\/[^/]+/, "https://freetyst.nf.migu.cn"), | ||
| preview: preview || "", | ||
| }; | ||
| } | ||
| async function getKuGouSong(id) { | ||
| let url = `https://wwwapi.kugou.com/yy/index.php?r=play/getdata&callback=&hash=${id}&dfid=&mid=${id}&platid=4&_=${+new Date()}&album_id=`; | ||
| let rsp = await axios_1.default.get(url, { responseType: "json" }); | ||
| rsp = rsp.data.data; | ||
| url += rsp.album_id; | ||
| rsp = await axios_1.default.get(url, { responseType: "json" }); | ||
| rsp = rsp.data.data; | ||
| return { | ||
| title: rsp.audio_name, | ||
| singer: rsp.author_name, | ||
| jumpUrl: `https://www.kugou.com/song/#hash=${id}&album_id=${rsp.album_id}`, | ||
| musicUrl: rsp.play_url || "https://webfs.yun.kugou.com", | ||
| preview: rsp.img, | ||
| }; | ||
| } | ||
| async function getKuwoSong(id) { | ||
| let rsp = await axios_1.default.get(`http://yinyue.kuwo.cn/api/www/music/musicInfo?mid=${id}&httpsStatus=1`, { responseType: "json", headers: { csrf: id, cookie: " kw_token=" + id } }); | ||
| rsp = rsp.data.data; | ||
| let url = await axios_1.default.get(`http://yinyue.kuwo.cn/url?format=mp3&rid=${id}&response=url&type=convert_url3&from=web&t=${+new Date()}`, { responseType: "json" }); | ||
| return { | ||
| title: rsp.name, | ||
| singer: rsp.artist, | ||
| jumpUrl: "http://yinyue.kuwo.cn/play_detail/" + id, | ||
| musicUrl: url.data.url || "https://win-web-ra01-sycdn.kuwo.cn", | ||
| preview: rsp.pic, | ||
| }; | ||
| } | ||
| /** 构造b77音乐分享 */ | ||
| async function buildMusic(target, platform, id, bu) { | ||
| var appid, appname, appsign, style = 4; | ||
| try { | ||
| if (platform == "qq") { | ||
| appid = 100497308, appname = "com.tencent.qqmusic", appsign = "cbd27cd7c861227d013a25b2d10f0799"; | ||
| var { singer, title, jumpUrl, musicUrl, preview } = await getQQSong(id); | ||
| if (!musicUrl) | ||
| style = 0; | ||
| } | ||
| else if (platform == "163") { | ||
| appid = 100495085, appname = "com.netease.cloudmusic", appsign = "da6b069da1e2982db3e386233f68d76d"; | ||
| var { singer, title, jumpUrl, musicUrl, preview } = await get163Song(id); | ||
| } | ||
| else if (platform == "migu") { | ||
| appid = 1101053067, appname = "cmccwm.mobilemusic", appsign = "6cdc72a439cef99a3418d2a78aa28c73"; | ||
| var { singer, title, jumpUrl, musicUrl, preview } = await getMiGuSong(id); | ||
| } | ||
| else if (platform == "kugou") { | ||
| appid = 205141, appname = "com.kugou.android", appsign = "fe4a24d80fcf253a00676a808f62c2c6"; | ||
| var { singer, title, jumpUrl, musicUrl, preview } = await getKuGouSong(id); | ||
| } | ||
| else if (platform == "kuwo") { | ||
| appid = 100243533, appname = "cn.kuwo.player", appsign = "bf9ff4ffb4c558a34ee3fd52c223ebf5"; | ||
| var { singer, title, jumpUrl, musicUrl, preview } = await getKuwoSong(id); | ||
| } | ||
| else { | ||
| throw new Error("unknown music platform: " + platform); | ||
| } | ||
| } | ||
| catch (e) { | ||
| throw new Error("unknown music id: " + id + ", in platform: " + platform); | ||
| } | ||
| return { | ||
| 1: appid, | ||
| 2: 1, | ||
| 3: style, | ||
| 5: { | ||
| 1: 1, | ||
| 2: "0.0.0", | ||
| 3: appname, | ||
| 4: appsign | ||
| }, | ||
| 10: bu, | ||
| 11: target, | ||
| 12: { | ||
| 10: title, | ||
| 11: singer, | ||
| 12: "[分享]" + title, | ||
| 13: jumpUrl, | ||
| 14: preview, | ||
| 16: musicUrl, | ||
| } | ||
| }; | ||
| } | ||
| exports.buildMusic = buildMusic; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.getGroupImageUrl = exports.Parser = exports.parse = void 0; | ||
| const zlib_1 = require("zlib"); | ||
| const core_1 = require("../core"); | ||
| const face_1 = require("./face"); | ||
| const image_1 = require("./image"); | ||
| /** 解析消息 */ | ||
| function parse(rich, uin) { | ||
| return new Parser(rich, uin); | ||
| } | ||
| exports.parse = parse; | ||
| /** 消息解析器 */ | ||
| class Parser { | ||
| constructor(rich, uin) { | ||
| this.uin = uin; | ||
| this.message = []; | ||
| this.brief = ""; | ||
| this.content = ""; | ||
| this.atme = false; | ||
| this.atall = false; | ||
| this.exclusive = false; | ||
| if (Array.isArray(rich)) { | ||
| this.parseElems(rich); | ||
| } | ||
| else { | ||
| if (rich[4]) | ||
| this.parseExclusiveElem(0, rich[4]); | ||
| this.parseElems(Array.isArray(rich[2]) ? rich[2] : [rich[2]]); | ||
| } | ||
| } | ||
| /** 获取下一个节点的文本 */ | ||
| getNextText() { | ||
| try { | ||
| const elem = this.it?.next().value[1][1]; | ||
| return String(elem[1]); | ||
| } | ||
| catch { | ||
| return "[未知]"; | ||
| } | ||
| } | ||
| /** 解析: xml, json, ptt, video, flash, file, shake, poke */ | ||
| parseExclusiveElem(type, proto) { | ||
| let elem; | ||
| let brief; | ||
| switch (type) { | ||
| case 12: //xml | ||
| case 51: //json | ||
| const buf = proto[1].toBuffer(); | ||
| elem = { | ||
| type: type === 12 ? "xml" : "json", | ||
| data: String(buf[0] > 0 ? (0, zlib_1.unzipSync)(buf.slice(1)) : buf.slice(1)), | ||
| id: proto[2] | ||
| }; | ||
| brief = elem.type + "消息"; | ||
| this.content = elem.data; | ||
| break; | ||
| case 3: //flash | ||
| elem = this.parseImgElem(proto, "flash"); | ||
| brief = "闪照"; | ||
| this.content = `{flash:${elem.file.slice(0, 32).toUpperCase()}}`; | ||
| break; | ||
| case 0: //ptt | ||
| elem = { | ||
| type: "record", | ||
| file: "protobuf://" + proto.toBase64(), | ||
| url: "", | ||
| md5: proto[4].toHex(), | ||
| size: proto[6] || 0, | ||
| seconds: proto[19] || 0, | ||
| }; | ||
| if (proto[20]) { | ||
| const url = String(proto[20]); | ||
| elem.url = url.startsWith("http") ? url : "https://grouptalk.c2c.qq.com" + url; | ||
| } | ||
| brief = "语音"; | ||
| this.content = `{ptt:${elem.url}}`; | ||
| break; | ||
| case 19: //video | ||
| elem = { | ||
| type: "video", | ||
| file: "protobuf://" + proto.toBase64(), | ||
| name: proto[3]?.toString() || "", | ||
| fid: String(proto[1]), | ||
| md5: proto[2].toBase64(), | ||
| size: proto[6] || 0, | ||
| seconds: proto[5] || 0, | ||
| }; | ||
| brief = "视频"; | ||
| this.content = `{video:${elem.fid}}`; | ||
| break; | ||
| case 5: //transElem | ||
| const trans = core_1.pb.decode(proto[2].toBuffer().slice(3))[7][2]; | ||
| elem = { | ||
| type: "file", | ||
| name: String(trans[4]), | ||
| fid: String(trans[2]).replace("/", ""), | ||
| md5: String(trans[8]), | ||
| size: trans[3], | ||
| duration: trans[5], | ||
| }; | ||
| brief = "群文件"; | ||
| this.content = `{file:${elem.fid}}`; | ||
| break; | ||
| case 126: //poke | ||
| if (!proto[3]) | ||
| return; | ||
| const pokeid = proto[3] === 126 ? proto[2][4] : proto[3]; | ||
| elem = { | ||
| type: "poke", | ||
| id: pokeid, | ||
| text: face_1.pokemap[pokeid] | ||
| }; | ||
| brief = face_1.pokemap[pokeid]; | ||
| this.content = `{poke:${elem.id}}`; | ||
| break; | ||
| default: | ||
| return; | ||
| } | ||
| this.message = [elem]; | ||
| this.brief = "[" + brief + "]"; | ||
| this.exclusive = true; | ||
| } | ||
| /** 解析: text, at, face, bface, sface, image, mirai */ | ||
| parsePartialElem(type, proto) { | ||
| let elem; | ||
| let brief = ""; | ||
| let content = ""; | ||
| switch (type) { | ||
| case 1: //text&at | ||
| brief = String(proto[1]); | ||
| const buf = proto[3]?.toBuffer(); | ||
| if (buf && buf[1] === 1) { | ||
| elem = { | ||
| type: "at", | ||
| qq: 0, | ||
| text: brief | ||
| }; | ||
| if (buf[6] === 1) { | ||
| elem.qq = "all"; | ||
| this.atall = true; | ||
| } | ||
| else { | ||
| elem.qq = buf.readUInt32BE(7); | ||
| if (elem.qq === this.uin) | ||
| this.atme = true; | ||
| } | ||
| brief = brief || ("@" + elem.qq); | ||
| content = `{at:${elem.qq}}`; | ||
| } | ||
| else { | ||
| if (!brief) | ||
| return; | ||
| content = brief; | ||
| elem = { | ||
| type: "text", | ||
| text: brief | ||
| }; | ||
| } | ||
| break; | ||
| case 2: //face | ||
| elem = { | ||
| type: "face", | ||
| id: proto[1], | ||
| text: face_1.facemap[proto[1]] || "表情", | ||
| }; | ||
| brief = `[${elem.text}]`; | ||
| content = `{face:${elem.id}}`; | ||
| break; | ||
| case 33: //face(id>255) | ||
| elem = { | ||
| type: "face", | ||
| id: proto[1], | ||
| text: face_1.facemap[proto[1]], | ||
| }; | ||
| if (!elem.text) | ||
| elem.text = proto[2] ? String(proto[2]) : ("/" + elem.id); | ||
| brief = `[${elem.text}]`; | ||
| content = `{face:${elem.id}}`; | ||
| break; | ||
| case 6: //bface | ||
| brief = this.getNextText(); | ||
| if (brief.includes("骰子") || brief.includes("猜拳")) { | ||
| elem = { | ||
| type: brief.includes("骰子") ? "dice" : "rps", | ||
| id: proto[12].toBuffer()[16] - 0x30 + 1 | ||
| }; | ||
| content = `{${elem.type}:${elem.id}}`; | ||
| } | ||
| else { | ||
| elem = { | ||
| type: "bface", | ||
| file: proto[4].toHex() + proto[7].toHex() + proto[5], | ||
| text: brief.replace(/[[\]]/g, "") | ||
| }; | ||
| content = `{bface:${elem.text}}`; | ||
| } | ||
| break; | ||
| case 4: | ||
| case 8: | ||
| elem = this.parseImgElem(proto, "image"); | ||
| brief = elem.asface ? "[动画表情]" : "[图片]"; | ||
| content = `{image:${elem.file.slice(0, 32).toUpperCase()}}`; | ||
| break; | ||
| case 34: //sface | ||
| brief = this.getNextText(); | ||
| elem = { | ||
| type: "sface", | ||
| id: proto[1], | ||
| text: brief.replace(/[[\]]/g, ""), | ||
| }; | ||
| content = `{sface:${elem.id}}`; | ||
| break; | ||
| case 31: //mirai | ||
| if (proto[3] === 103904510) { | ||
| brief = content = String(proto[2]); | ||
| elem = { | ||
| type: "mirai", | ||
| data: brief, | ||
| }; | ||
| } | ||
| else { | ||
| return; | ||
| } | ||
| break; | ||
| default: | ||
| return; | ||
| } | ||
| // 删除回复中多余的AT元素 | ||
| if (this.message.length === 2 && elem.type === "at" && this.message[0]?.type === "at" && this.message[1]?.type === "text") { | ||
| if (this.message[0].qq === elem.qq && this.message[1].text === " ") { | ||
| this.message.splice(0, 2); | ||
| this.brief = ""; | ||
| } | ||
| } | ||
| this.brief += brief; | ||
| this.content += content; | ||
| if (!Array.isArray(this.message)) | ||
| this.message = []; | ||
| const prev = this.message[this.message.length - 1]; | ||
| if (elem.type === "text" && prev?.type === "text") | ||
| prev.text += elem.text; | ||
| else | ||
| this.message.push(elem); | ||
| } | ||
| parseElems(arr) { | ||
| this.it = arr.entries(); | ||
| while (true) { | ||
| let wrapper = this.it.next().value?.[1]; | ||
| if (!wrapper) | ||
| break; | ||
| const type = Number(Object.keys(Reflect.getPrototypeOf(wrapper))[0]); | ||
| const proto = wrapper[type]; | ||
| if (type === 16) { //extraInfo | ||
| this.extra = proto; | ||
| } | ||
| else if (type === 21) { //anonGroupMsg | ||
| this.anon = proto; | ||
| } | ||
| else if (type === 45) { //sourceMsg | ||
| this.quotation = proto; | ||
| } | ||
| else if (!this.exclusive) { | ||
| switch (type) { | ||
| case 1: //text | ||
| case 2: //face | ||
| case 4: //notOnlineImage | ||
| case 6: //bface | ||
| case 8: //customFace | ||
| case 31: //mirai | ||
| case 34: //sface | ||
| this.parsePartialElem(type, proto); | ||
| break; | ||
| case 5: //transElem | ||
| case 12: //xml | ||
| case 19: //video | ||
| case 51: //json | ||
| this.parseExclusiveElem(type, proto); | ||
| break; | ||
| case 53: //commonElem | ||
| if (proto[1] === 3) { //flash | ||
| this.parseExclusiveElem(3, proto[2][1] ? proto[2][1] : proto[2][2]); | ||
| } | ||
| else if (proto[1] === 33) { //face(id>255) | ||
| this.parsePartialElem(33, proto[2]); | ||
| } | ||
| else if (proto[1] === 2) { //poke | ||
| this.parseExclusiveElem(126, proto); | ||
| } | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| parseImgElem(proto, type) { | ||
| let elem; | ||
| if (proto[7]?.toHex) { | ||
| elem = { | ||
| type, | ||
| file: (0, image_1.buildImageFileParam)(proto[7].toHex(), proto[2], proto[9], proto[8], proto[5]), | ||
| url: "", | ||
| }; | ||
| if (proto[15]) | ||
| elem.url = `https://c2cpicdw.qpic.cn${proto[15]}`; | ||
| else if (proto[10]) | ||
| elem.url = `https://c2cpicdw.qpic.cn/offpic_new/0/${proto[10]}/0`; | ||
| if (elem.type === "image") | ||
| elem.asface = proto[29]?.[1] === 1; | ||
| } | ||
| else { //群图 | ||
| elem = { | ||
| type, | ||
| file: (0, image_1.buildImageFileParam)(proto[13].toHex(), proto[25], proto[22], proto[23], proto[20]), | ||
| url: proto[16] ? `https://gchat.qpic.cn${proto[16]}` : getGroupImageUrl(proto[13].toHex()), | ||
| }; | ||
| if (elem.type === "image") | ||
| elem.asface = proto[34]?.[1] === 1; | ||
| } | ||
| return elem; | ||
| } | ||
| } | ||
| exports.Parser = Parser; | ||
| function getGroupImageUrl(md5) { | ||
| return `https://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.toUpperCase()}/0`; | ||
| } | ||
| exports.getGroupImageUrl = getGroupImageUrl; |
+1
-1
| { | ||
| "name": "oicq", | ||
| "version": "2.1.7", | ||
| "version": "2.1.8", | ||
| "upday": "2021/12/08", | ||
@@ -5,0 +5,0 @@ "description": "QQ protocol!", |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
521379
13.26%82
7.89%13173
15.41%10
66.67%