diff --git a/.github/workflows/pack.yml b/.github/workflows/pack.yml new file mode 100644 index 0000000..8b9fb44 --- /dev/null +++ b/.github/workflows/pack.yml @@ -0,0 +1,55 @@ +name: Package and Release + +on: + push: + tags: + - 'v*.*.*' + +jobs: + build: + strategy: + matrix: + os: + - ubuntu-latest + node-version: + - 16 + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '16' + + - name: Install dependencies + run: npm install + + - name: Run pack script + run: | + tarball=$(npm pack) + echo "TARBALL_NAME=$tarball" >> $GITHUB_ENV + + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Upload binaries to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ${{ env.TARBALL_NAME }} + asset_name: ${{ env.TARBALL_NAME }} + tag: ${{ github.ref }} + overwrite: true + body: ${{ github.ref }} diff --git a/package.json b/package.json index d0adb94..ba8f29f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wechat4u", - "version": "0.7.14", + "version": "0.7.16", "description": "web wechat lib for user", "main": "lib/wechat.js", "scripts": { diff --git a/run-core.js b/run-core.js index b17f933..5d6ed70 100644 --- a/run-core.js +++ b/run-core.js @@ -59,7 +59,7 @@ bot.on('logout', () => { * 联系人更新事件,参数为被更新的联系人列表 */ bot.on('contacts-updated', contacts => { - console.log(contacts) + // console.log(contacts) console.log('联系人数量:', Object.keys(bot.contacts).length) }) /** @@ -143,16 +143,27 @@ bot.on('login', () => { bot.emit('error', err) }) + /** + * 发送大文件 测试 + */ + // bot.sendMsg({ + // file: fs.createReadStream('./media/21mb.zip'), + // filename: '21mb.zip' + // }, ToUserName) + // .catch(err => { + // bot.emit('error', err) + // }) + /** * 发送文件 */ - bot.sendMsg({ - file: fs.createReadStream('./media/test.txt'), - filename: 'test.txt' - }, ToUserName) - .catch(err => { - bot.emit('error', err) - }) + // bot.sendMsg({ + // file: fs.createReadStream('./media/2mb.mp4'), + // filename: '2mb.mp4' + // }, ToUserName) + // .catch(err => { + // bot.emit('error', err) + // }) /** * 发送撤回消息请求 diff --git a/src/core.js b/src/core.js index aac2d0d..c258b80 100644 --- a/src/core.js +++ b/src/core.js @@ -3,16 +3,10 @@ import bl from 'bl' import _debug from 'debug' import FormData from 'form-data' import mime from 'mime' -import { - getCONF, - Request, - isStandardBrowserEnv, - assert, - getClientMsgId, - getDeviceID -} from './util' +import { assert, getClientMsgId, getCONF, getDeviceID, isStandardBrowserEnv, randomUuid, Request } from './util' const debug = _debug('core') + export class AlreadyLogoutError extends Error { constructor (message = 'already logout') { super(message) @@ -21,18 +15,12 @@ export class AlreadyLogoutError extends Error { this.__proto__ = AlreadyLogoutError.prototype } } + const CHUNK_SIZE = 0.5 * 1024 * 1024 // 0.5 MB export default class WechatCore { constructor (data) { this.PROP = { - uuid: '', - uin: '', - sid: '', - skey: '', - passTicket: '', - formatedSyncKey: '', - webwxDataTicket: '', - syncKey: { + uuid: '', uin: '', sid: '', skey: '', passTicket: '', formatedSyncKey: '', webwxDataTicket: '', syncKey: { List: [] } } @@ -50,10 +38,7 @@ export default class WechatCore { get botData () { return { - PROP: this.PROP, - CONF: this.CONF, - COOKIE: this.COOKIE, - user: this.user + PROP: this.PROP, CONF: this.CONF, COOKIE: this.COOKIE, user: this.user } } @@ -66,8 +51,7 @@ export default class WechatCore { getUUID () { return Promise.resolve().then(() => { return this.request({ - method: 'POST', - url: this.CONF.API_jsLogin + method: 'POST', url: this.CONF.API_jsLogin }).then(res => { let window = { QRLogin: {} @@ -90,15 +74,10 @@ export default class WechatCore { checkLogin () { return Promise.resolve().then(() => { let params = { - 'tip': 0, - 'uuid': this.PROP.uuid, - 'loginicon': true, - 'r': ~new Date() + 'tip': 0, 'uuid': this.PROP.uuid, 'loginicon': true, 'r': ~new Date() } return this.request({ - method: 'GET', - url: this.CONF.API_login, - params: params + method: 'GET', url: this.CONF.API_login, params: params }).then(res => { let window = {} @@ -130,10 +109,7 @@ export default class WechatCore { headers['referer'] = 'https://wx.qq.com/?&lang=zh_CN&target=t' headers['extspam'] = 'Go8FCIkFEokFCggwMDAwMDAwMRAGGvAESySibk50w5Wb3uTl2c2h64jVVrV7gNs06GFlWplHQbY/5FfiO++1yH4ykCyNPWKXmco+wfQzK5R98D3so7rJ5LmGFvBLjGceleySrc3SOf2Pc1gVehzJgODeS0lDL3/I/0S2SSE98YgKleq6Uqx6ndTy9yaL9qFxJL7eiA/R3SEfTaW1SBoSITIu+EEkXff+Pv8NHOk7N57rcGk1w0ZzRrQDkXTOXFN2iHYIzAAZPIOY45Lsh+A4slpgnDiaOvRtlQYCt97nmPLuTipOJ8Qc5pM7ZsOsAPPrCQL7nK0I7aPrFDF0q4ziUUKettzW8MrAaiVfmbD1/VkmLNVqqZVvBCtRblXb5FHmtS8FxnqCzYP4WFvz3T0TcrOqwLX1M/DQvcHaGGw0B0y4bZMs7lVScGBFxMj3vbFi2SRKbKhaitxHfYHAOAa0X7/MSS0RNAjdwoyGHeOepXOKY+h3iHeqCvgOH6LOifdHf/1aaZNwSkGotYnYScW8Yx63LnSwba7+hESrtPa/huRmB9KWvMCKbDThL/nne14hnL277EDCSocPu3rOSYjuB9gKSOdVmWsj9Dxb/iZIe+S6AiG29Esm+/eUacSba0k8wn5HhHg9d4tIcixrxveflc8vi2/wNQGVFNsGO6tB5WF0xf/plngOvQ1/ivGV/C1Qpdhzznh0ExAVJ6dwzNg7qIEBaw+BzTJTUuRcPk92Sn6QDn2Pu3mpONaEumacjW4w6ipPnPw+g2TfywJjeEcpSZaP4Q3YV5HG8D6UjWA4GSkBKculWpdCMadx0usMomsSS/74QgpYqcPkmamB4nVv1JxczYITIqItIKjD35IGKAUwAA==' return this.request({ - method: 'GET', - url: this.rediUri, - maxRedirects: 0, - headers: headers + method: 'GET', url: this.rediUri, maxRedirects: 0, headers: headers }).then(res => { }).catch(error => { @@ -175,17 +151,13 @@ export default class WechatCore { let t = Date.now() let r = t / -1579 let params = { - 'pass_ticket': this.PROP.passTicket, - 'r': Math.ceil(r) + 'pass_ticket': this.PROP.passTicket, 'r': Math.ceil(r) } let data = { BaseRequest: this.getBaseRequest() } return this.request({ - method: 'POST', - url: this.CONF.API_webwxinit, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxinit, params: params, data: data }).then(res => { let data = res.data if (data.BaseResponse.Ret === this.CONF.SYNCCHECK_RET_LOGOUT) { @@ -207,8 +179,7 @@ export default class WechatCore { notifyMobile (to) { return Promise.resolve().then(() => { let params = { - pass_ticket: this.PROP.passTicket, - lang: 'zh_CN' + pass_ticket: this.PROP.passTicket, lang: 'zh_CN' } let data = { 'BaseRequest': this.getBaseRequest(), @@ -218,10 +189,7 @@ export default class WechatCore { 'ClientMsgId': Date.now() } return this.request({ - method: 'POST', - url: this.CONF.API_webwxstatusnotify, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxstatusnotify, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -237,14 +205,10 @@ export default class WechatCore { return Promise.resolve().then(() => { let params = { // 'pass_ticket': this.PROP.passTicket, - 'seq': seq, - 'skey': this.PROP.skey, - 'r': +new Date() + 'seq': seq, 'skey': this.PROP.skey, 'r': +new Date() } return this.request({ - method: 'GET', - url: this.CONF.API_webwxgetcontact, - params: params + method: 'GET', url: this.CONF.API_webwxgetcontact, params: params }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -260,21 +224,13 @@ export default class WechatCore { batchGetContact (contacts) { return Promise.resolve().then(() => { let params = { - 'pass_ticket': this.PROP.passTicket, - 'type': 'ex', - 'r': +new Date(), - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'type': 'ex', 'r': +new Date(), 'lang': 'zh_CN' } let data = { - 'BaseRequest': this.getBaseRequest(), - 'Count': contacts.length, - 'List': contacts + 'BaseRequest': this.getBaseRequest(), 'Count': contacts.length, 'List': contacts } return this.request({ - method: 'POST', - url: this.CONF.API_webwxbatchgetcontact, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxbatchgetcontact, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -291,34 +247,23 @@ export default class WechatCore { statReport (text) { return Promise.resolve().then(() => { text = text || { - 'type': '[action-record]', - 'data': { + 'type': '[action-record]', 'data': { 'actions': [{ - 'type': 'click', - 'action': '发送框', - 'time': +new Date() + 'type': 'click', 'action': '发送框', 'time': +new Date() }] } } text = JSON.stringify(text) let params = { - 'pass_ticket': this.PROP.passTicket, - 'fun': 'new', - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'fun': 'new', 'lang': 'zh_CN' } let data = { - 'BaseRequest': this.getBaseRequest(), - 'Count': 1, - 'List': [{ - 'Text': text, - 'Type': 1 + 'BaseRequest': this.getBaseRequest(), 'Count': 1, 'List': [{ + 'Text': text, 'Type': 1 }] } return this.request({ - method: 'POST', - url: this.CONF.API_webwxreport, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxreport, params: params, data: data }) }).catch(err => { debug(err) @@ -338,9 +283,7 @@ export default class WechatCore { 'synckey': this.PROP.formatedSyncKey } return this.request({ - method: 'GET', - url: this.CONF.API_synccheck, - params: params + method: 'GET', url: this.CONF.API_synccheck, params: params }).then(res => { let window = { synccheck: {} @@ -368,21 +311,13 @@ export default class WechatCore { sync () { return Promise.resolve().then(() => { let params = { - 'sid': this.PROP.sid, - 'skey': this.PROP.skey, - 'pass_ticket': this.PROP.passTicket, - 'lang': 'zh_CN' + 'sid': this.PROP.sid, 'skey': this.PROP.skey, 'pass_ticket': this.PROP.passTicket, 'lang': 'zh_CN' } let data = { - 'BaseRequest': this.getBaseRequest(), - 'SyncKey': this.PROP.syncKey, - 'rr': ~new Date() + 'BaseRequest': this.getBaseRequest(), 'SyncKey': this.PROP.syncKey, 'rr': ~new Date() } return this.request({ - method: 'POST', - url: this.CONF.API_webwxsync, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxsync, params: params, data: data }).then(res => { let data = res.data if (data.BaseResponse.Ret === this.CONF.SYNCCHECK_RET_LOGOUT) { @@ -423,10 +358,7 @@ export default class WechatCore { logout () { return Promise.resolve().then(() => { let params = { - redirect: 1, - type: 0, - skey: this.PROP.skey, - lang: 'zh_CN' + redirect: 1, type: 0, skey: this.PROP.skey, lang: 'zh_CN' } // data加上会出错,不加data也能登出 @@ -435,9 +367,7 @@ export default class WechatCore { // uin: this.PROP.uin // } return this.request({ - method: 'POST', - url: this.CONF.API_webwxlogout, - params: params + method: 'POST', url: this.CONF.API_webwxlogout, params: params }).then(res => { return '登出成功' }).catch(err => { @@ -450,14 +380,11 @@ export default class WechatCore { sendText (msg, to) { return Promise.resolve().then(() => { let params = { - 'pass_ticket': this.PROP.passTicket, - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'lang': 'zh_CN' } let clientMsgId = getClientMsgId() let data = { - 'BaseRequest': this.getBaseRequest(), - 'Scene': 0, - 'Msg': { + 'BaseRequest': this.getBaseRequest(), 'Scene': 0, 'Msg': { 'Type': this.CONF.MSGTYPE_TEXT, 'Content': msg, 'FromUserName': this.user['UserName'], @@ -467,10 +394,7 @@ export default class WechatCore { } } return this.request({ - method: 'POST', - url: this.CONF.API_webwxsendmsg, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxsendmsg, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -486,15 +410,11 @@ export default class WechatCore { sendEmoticon (id, to) { return Promise.resolve().then(() => { let params = { - 'fun': 'sys', - 'pass_ticket': this.PROP.passTicket, - 'lang': 'zh_CN' + 'fun': 'sys', 'pass_ticket': this.PROP.passTicket, 'lang': 'zh_CN' } let clientMsgId = getClientMsgId() let data = { - 'BaseRequest': this.getBaseRequest(), - 'Scene': 0, - 'Msg': { + 'BaseRequest': this.getBaseRequest(), 'Scene': 0, 'Msg': { 'Type': this.CONF.MSGTYPE_EMOTICON, 'EmojiFlag': 2, 'FromUserName': this.user['UserName'], @@ -511,10 +431,7 @@ export default class WechatCore { } return this.request({ - method: 'POST', - url: this.CONF.API_webwxsendemoticon, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxsendemoticon, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -538,7 +455,7 @@ export default class WechatCore { MediaType: 4, UploadType: 2, FromUserName: this.user.UserName, - ToUserName: toUserName || this.user.UserName + ToUserName: toUserName || this.user.UserName, }) // 小于0.5mb的文件直接返回form @@ -553,9 +470,7 @@ export default class WechatCore { form.append('webwx_data_ticket', this.PROP.webwxDataTicket) form.append('pass_ticket', encodeURI(this.PROP.passTicket)) form.append('filename', data, { - filename: name, - contentType: type, - knownLength: size + filename: name, contentType: type, knownLength: size }) return form @@ -565,36 +480,37 @@ export default class WechatCore { const totalChunksNum = Math.ceil(size / CHUNK_SIZE) const formList = [] - for (let i = 0; i < totalChunksNum; i++) { - let startPos = i * CHUNK_SIZE - let endPos = Math.min(size, startPos + CHUNK_SIZE) - let chunk = data.slice(startPos, endPos) + const getFormList = () => { + for (let i = 0; i < totalChunksNum; i++) { + let startPos = i * CHUNK_SIZE + let endPos = Math.min(size, startPos + CHUNK_SIZE) + let chunk = data.slice(startPos, endPos) + + // 创建每个块的 FormData + const form = new FormData() + form.append('name', name) + form.append('type', type) + form.append('lastModifiedDate', new Date().toGMTString()) + form.append('size', size) + form.append('mediatype', mediatype) + form.append('uploadmediarequest', uploadMediaRequest) + form.append('webwx_data_ticket', this.PROP.webwxDataTicket) + form.append('pass_ticket', encodeURI(this.PROP.passTicket)) + form.append('id', 'WU_FILE_0') + form.append('chunk', i) + form.append('chunks', totalChunksNum) + form.append('filename', chunk, { + filename: name, contentType: type, knownLength: chunk.length + }) + formList.push({ + data: form, headers: form.getHeaders() + }) + } - // 创建每个块的 FormData - const form = new FormData() - form.append('name', name) - form.append('type', type) - form.append('lastModifiedDate', new Date().toGMTString()) - form.append('size', size) - form.append('mediatype', mediatype) - form.append('uploadmediarequest', uploadMediaRequest) - form.append('webwx_data_ticket', this.PROP.webwxDataTicket) - form.append('pass_ticket', encodeURI(this.PROP.passTicket)) - form.append('id', 'WU_FILE_0') - form.append('chunk', i) - form.append('chunks', totalChunksNum) - form.append('filename', chunk, { - filename: name, - contentType: type, - knownLength: chunk.length - }) - formList.push({ - data: form, - headers: form.getHeaders() - }) + return formList } - return formList + return getFormList() } // file: Stream, Buffer, File, Blob @@ -602,8 +518,7 @@ export default class WechatCore { return Promise.resolve().then(() => { let name, type, size, ext, mediatype, data return new Promise((resolve, reject) => { - if ((typeof (File) !== 'undefined' && file.constructor === File) || - (typeof (Blob) !== 'undefined' && file.constructor === Blob)) { + if ((typeof (File) !== 'undefined' && file.constructor === File) || (typeof (Blob) !== 'undefined' && file.constructor === Blob)) { name = file.name || 'file' type = file.type size = file.size @@ -660,8 +575,7 @@ export default class WechatCore { return new Promise((resolve, reject) => { if (isStandardBrowserEnv) { return resolve({ - data: formOrFormList, - headers: {} + data: formOrFormList, headers: {} }) } else if (Array.isArray(formOrFormList)) { const bufferList = formOrFormList.reduce((arr, formObj) => { @@ -682,8 +596,7 @@ export default class WechatCore { return reject(err) } return resolve({ - data: buffer, - headers: formOrFormList.getHeaders() + data: buffer, headers: formOrFormList.getHeaders() }) })) } @@ -696,11 +609,7 @@ export default class WechatCore { // 单块文件上传 if (!Array.isArray(data.data)) { return this.request({ - method: 'POST', - url: this.CONF.API_webwxuploadmedia, - headers: data.headers, - params, - data: data.data + method: 'POST', url: this.CONF.API_webwxuploadmedia, headers: data.headers, params, data: data.data }) } @@ -708,7 +617,7 @@ export default class WechatCore { let currentChunkIndex = 0 // 分块上传逻辑 - const processChunk = res => { + const processChunk = (res, signature) => { if (currentChunkIndex < bufferList.length) { const chunkObj = bufferList[currentChunkIndex] return this.request({ @@ -720,29 +629,96 @@ export default class WechatCore { }).then(res => { currentChunkIndex++ // 递归处理下一个块 - return processChunk(res) + return processChunk(res, signature) }) } else { // 所有块上传完成 - return Promise.resolve({data: { - MediaId: res.data.MediaId - }}) + return Promise.resolve({ + data: { + MediaId: res.data.MediaId, + Signature: signature + } + }) } } - // 开始处理第一个块 - return processChunk() + // 大于大概22MB的文件上传会失败 具体没测试 + if (size > 22 * 1024 * 1024) { + + const fileMd5 = randomUuid() + let assign = { + BaseRequest: this.getBaseRequest(), + FromUserName: this.user.UserName, + ToUserName: toUserName || this.user.UserName, + FileSize: size, + FileMd5: fileMd5, + FileName: name, + FileType: 7, + } + + console.log('checkupload', JSON.stringify(assign)) + let headers = Object.assign({}, bufferList[0].headers, { + 'Content-Type': 'application/json; charset=UTF-8', + 'extspam': 'Go8FCIkFEokFCggwMDAwMDAwMRAGGvAESySibk50w5Wb3uTl2c2h64jVVrV7gNs06GFlWplHQbY/5FfiO++1yH4ykCyNPWKXmco+wfQzK5R98D3so7rJ5LmGFvBLjGceleySrc3SOf2Pc1gVehzJgODeS0lDL3/I/0S2SSE98YgKleq6Uqx6ndTy9yaL9qFxJL7eiA/R3SEfTaW1SBoSITIu+EEkXff+Pv8NHOk7N57rcGk1w0ZzRrQDkXTOXFN2iHYIzAAZPIOY45Lsh+A4slpgnDiaOvRtlQYCt97nmPLuTipOJ8Qc5pM7ZsOsAPPrCQL7nK0I7aPrFDF0q4ziUUKettzW8MrAaiVfmbD1/VkmLNVqqZVvBCtRblXb5FHmtS8FxnqCzYP4WFvz3T0TcrOqwLX1M/DQvcHaGGw0B0y4bZMs7lVScGBFxMj3vbFi2SRKbKhaitxHfYHAOAa0X7/MSS0RNAjdwoyGHeOepXOKY+h3iHeqCvgOH6LOifdHf/1aaZNwSkGotYnYScW8Yx63LnSwba7+hESrtPa/huRmB9KWvMCKbDThL/nne14hnL277EDCSocPu3rOSYjuB9gKSOdVmWsj9Dxb/iZIe+S6AiG29Esm+/eUacSba0k8wn5HhHg9d4tIcixrxveflc8vi2/wNQGVFNsGO6tB5WF0xf/plngOvQ1/ivGV/C1Qpdhzznh0ExAVJ6dwzNg7qIEBaw+BzTJTUuRcPk92Sn6QDn2Pu3mpONaEumacjW4w6ipPnPw+g2TfywJjeEcpSZaP4Q3YV5HG8D6UjWA4GSkBKculWpdCMadx0usMomsSS/74QgpYqcPkmamB4nVv1JxczYITIqItIKjD35IGKAUwAA==' + }) + return this.request({ + method: 'POST', url: this.CONF.API_checkupload, headers: headers, params: { + 'lang': 'zh_CN', 'pass_ticket': this.PROP.passTicket, + }, data: assign + }).then(res => { + assert.equal(res.data.BaseResponse.Ret, 0, res) + bufferList.forEach(it => { + let buffer = Buffer.from(it.data) + let number = buffer.indexOf('Content-Disposition: form-data; name="uploadmediarequest"') + if (number) { + let start = number + let end = start + let endFlagCount = 0 + while (start < buffer.length) { + if (buffer[start] === '{'.charCodeAt(0)) { + while (end < buffer.length) { + if (buffer[end] === '}'.charCodeAt(0)) { + end++ + endFlagCount++ + if (endFlagCount === 2) { + break + } + } else { + end++ + } + } + break + } else { + start++ + } + } + let oldJson = JSON.parse(buffer.toString('utf8', start, end)) + let startBuff = buffer.subarray(0, start) + let endBuff = buffer.subarray(end) + oldJson.AESKey = res.data.AESKey + oldJson.Signature = res.data.Signature + oldJson.FileMd5 = fileMd5 + let newBuffer = Buffer.from(JSON.stringify(oldJson), 'utf8') + it.data = Buffer.concat([startBuff, newBuffer, endBuff]) + } + }) + return processChunk(null, res.data.Signature) + }).catch(err => { + debug(err) + err.tips = '上传媒体文件失败' + throw err + }) + } else { + // 开始处理第一个块 + return processChunk() + } }).then(res => { let data = res.data let mediaId = data.MediaId assert.ok(mediaId, res) return { - name: name, - size: size, - ext: ext, - mediatype: mediatype, - mediaId: mediaId + name: name, size: size, ext: ext, mediatype: mediatype, mediaId: mediaId, signature: data.Signature, } }) }).catch(err => { @@ -755,16 +731,11 @@ export default class WechatCore { sendPic (mediaId, to) { return Promise.resolve().then(() => { let params = { - 'pass_ticket': this.PROP.passTicket, - 'fun': 'async', - 'f': 'json', - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'fun': 'async', 'f': 'json', 'lang': 'zh_CN' } let clientMsgId = getClientMsgId() let data = { - 'BaseRequest': this.getBaseRequest(), - 'Scene': 0, - 'Msg': { + 'BaseRequest': this.getBaseRequest(), 'Scene': 0, 'Msg': { 'Type': this.CONF.MSGTYPE_IMAGE, 'MediaId': mediaId, 'FromUserName': this.user.UserName, @@ -774,10 +745,7 @@ export default class WechatCore { } } return this.request({ - method: 'POST', - url: this.CONF.API_webwxsendmsgimg, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxsendmsgimg, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -793,16 +761,11 @@ export default class WechatCore { sendVideo (mediaId, to) { return Promise.resolve().then(() => { let params = { - 'pass_ticket': this.PROP.passTicket, - 'fun': 'async', - 'f': 'json', - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'fun': 'async', 'f': 'json', 'lang': 'zh_CN' } let clientMsgId = getClientMsgId() let data = { - 'BaseRequest': this.getBaseRequest(), - 'Scene': 0, - 'Msg': { + 'BaseRequest': this.getBaseRequest(), 'Scene': 0, 'Msg': { 'Type': this.CONF.MSGTYPE_VIDEO, 'MediaId': mediaId, 'FromUserName': this.user.UserName, @@ -812,10 +775,7 @@ export default class WechatCore { } } return this.request({ - method: 'POST', - url: this.CONF.API_webwxsendmsgvedio, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxsendmsgvedio, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -828,32 +788,25 @@ export default class WechatCore { }) } - sendDoc (mediaId, name, size, ext, to) { + sendDoc (mediaId, name, size, ext, to, signature) { return Promise.resolve().then(() => { let params = { - 'pass_ticket': this.PROP.passTicket, - 'fun': 'async', - 'f': 'json', - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'fun': 'async', 'f': 'json', 'lang': 'zh_CN' } let clientMsgId = getClientMsgId() let data = { - 'BaseRequest': this.getBaseRequest(), - 'Scene': 0, - 'Msg': { + 'BaseRequest': this.getBaseRequest(), 'Scene': 0, 'Msg': { 'Type': this.CONF.APPMSGTYPE_ATTACH, 'Content': `${name}6${size}${mediaId}${ext}`, 'FromUserName': this.user.UserName, 'ToUserName': to, 'LocalID': clientMsgId, - 'ClientMsgId': clientMsgId + 'ClientMsgId': clientMsgId, + 'Signature': signature } } return this.request({ - method: 'POST', - url: this.CONF.API_webwxsendappmsg, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxsendappmsg, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -869,16 +822,11 @@ export default class WechatCore { forwardMsg (msg, to) { return Promise.resolve().then(() => { let params = { - 'pass_ticket': this.PROP.passTicket, - 'fun': 'async', - 'f': 'json', - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'fun': 'async', 'f': 'json', 'lang': 'zh_CN' } let clientMsgId = getClientMsgId() let data = { - 'BaseRequest': this.getBaseRequest(), - 'Scene': 2, - 'Msg': { + 'BaseRequest': this.getBaseRequest(), 'Scene': 2, 'Msg': { 'Type': msg.MsgType, 'MediaId': '', 'Content': msg.Content.replace(/</g, '<').replace(/>/g, '>').replace(/^.*:\n/, ''), @@ -920,18 +868,13 @@ export default class WechatCore { case this.CONF.MSGTYPE_APP: url = this.CONF.API_webwxsendappmsg data.Msg.Type = msg.AppMsgType - data.Msg.Content = data.Msg.Content.replace( - /^[\s\S]*?()[\s\S]*?(<\/attachid>[\s\S]*?<\/appmsg>)[\s\S]*?$/, - `$1${msg.MediaId}$2`) + data.Msg.Content = data.Msg.Content.replace(/^[\s\S]*?()[\s\S]*?(<\/attachid>[\s\S]*?<\/appmsg>)[\s\S]*?$/, `$1${msg.MediaId}$2`) break default: throw new Error('该消息类型不能直接转发') } return this.request({ - method: 'POST', - url: url, - params: params, - data: data + method: 'POST', url: url, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -947,20 +890,14 @@ export default class WechatCore { getMsgImg (msgId) { return Promise.resolve().then(() => { let params = { - MsgID: msgId, - skey: this.PROP.skey, - type: 'big' + MsgID: msgId, skey: this.PROP.skey, type: 'big' } return this.request({ - method: 'GET', - url: this.CONF.API_webwxgetmsgimg, - params: params, - responseType: 'arraybuffer' + method: 'GET', url: this.CONF.API_webwxgetmsgimg, params: params, responseType: 'arraybuffer' }).then(res => { return { - data: res.data, - type: res.headers['content-type'] + data: res.data, type: res.headers['content-type'] } }) }).catch(err => { @@ -973,22 +910,16 @@ export default class WechatCore { getVideo (msgId) { return Promise.resolve().then(() => { let params = { - MsgID: msgId, - skey: this.PROP.skey + MsgID: msgId, skey: this.PROP.skey } return this.request({ - method: 'GET', - url: this.CONF.API_webwxgetvideo, - headers: { + method: 'GET', url: this.CONF.API_webwxgetvideo, headers: { 'Range': 'bytes=0-' - }, - params: params, - responseType: 'arraybuffer' + }, params: params, responseType: 'arraybuffer' }).then(res => { return { - data: res.data, - type: res.headers['content-type'] + data: res.data, type: res.headers['content-type'] } }) }).catch(err => { @@ -1001,19 +932,14 @@ export default class WechatCore { getVoice (msgId) { return Promise.resolve().then(() => { let params = { - MsgID: msgId, - skey: this.PROP.skey + MsgID: msgId, skey: this.PROP.skey } return this.request({ - method: 'GET', - url: this.CONF.API_webwxgetvoice, - params: params, - responseType: 'arraybuffer' + method: 'GET', url: this.CONF.API_webwxgetvoice, params: params, responseType: 'arraybuffer' }).then(res => { return { - data: res.data, - type: res.headers['content-type'] + data: res.data, type: res.headers['content-type'] } }) }).catch(err => { @@ -1027,13 +953,10 @@ export default class WechatCore { return Promise.resolve().then(() => { let url = this.CONF.origin + HeadImgUrl return this.request({ - method: 'GET', - url: url, - responseType: 'arraybuffer' + method: 'GET', url: url, responseType: 'arraybuffer' }).then(res => { return { - data: res.data, - type: res.headers['content-type'] + data: res.data, type: res.headers['content-type'] } }) }).catch(err => { @@ -1054,14 +977,10 @@ export default class WechatCore { webwx_data_ticket: this.PROP.webwxDataTicket } return this.request({ - method: 'GET', - url: this.CONF.API_webwxdownloadmedia, - params: params, - responseType: 'arraybuffer' + method: 'GET', url: this.CONF.API_webwxdownloadmedia, params: params, responseType: 'arraybuffer' }).then(res => { return { - data: res.data, - type: res.headers['content-type'] + data: res.data, type: res.headers['content-type'] } }) }).catch(err => { @@ -1074,27 +993,15 @@ export default class WechatCore { verifyUser (UserName, Ticket) { return Promise.resolve().then(() => { let params = { - 'pass_ticket': this.PROP.passTicket, - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'lang': 'zh_CN' } let data = { - 'BaseRequest': this.getBaseRequest(), - 'Opcode': 3, - 'VerifyUserListSize': 1, - 'VerifyUserList': [{ - 'Value': UserName, - 'VerifyUserTicket': Ticket - }], - 'VerifyContent': '', - 'SceneListCount': 1, - 'SceneList': [33], - 'skey': this.PROP.skey + 'BaseRequest': this.getBaseRequest(), 'Opcode': 3, 'VerifyUserListSize': 1, 'VerifyUserList': [{ + 'Value': UserName, 'VerifyUserTicket': Ticket + }], 'VerifyContent': '', 'SceneListCount': 1, 'SceneList': [33], 'skey': this.PROP.skey } return this.request({ - method: 'POST', - url: this.CONF.API_webwxverifyuser, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxverifyuser, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -1115,29 +1022,17 @@ export default class WechatCore { */ addFriend (UserName, content = '我是' + this.user.NickName) { let params = { - 'pass_ticket': this.PROP.passTicket, - 'lang': 'zh_CN' + 'pass_ticket': this.PROP.passTicket, 'lang': 'zh_CN' } let data = { - 'BaseRequest': this.getBaseRequest(), - 'Opcode': 2, - 'VerifyUserListSize': 1, - 'VerifyUserList': [{ - 'Value': UserName, - 'VerifyUserTicket': '' - }], - 'VerifyContent': content, - 'SceneListCount': 1, - 'SceneList': [33], - 'skey': this.PROP.skey + 'BaseRequest': this.getBaseRequest(), 'Opcode': 2, 'VerifyUserListSize': 1, 'VerifyUserList': [{ + 'Value': UserName, 'VerifyUserTicket': '' + }], 'VerifyContent': content, 'SceneListCount': 1, 'SceneList': [33], 'skey': this.PROP.skey } return this.request({ - method: 'POST', - url: this.CONF.API_webwxverifyuser, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxverifyuser, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -1158,21 +1053,13 @@ export default class WechatCore { createChatroom (Topic, MemberList) { return Promise.resolve().then(() => { let params = { - 'pass_ticket': this.PROP.passTicket, - 'lang': 'zh_CN', - 'r': ~new Date() + 'pass_ticket': this.PROP.passTicket, 'lang': 'zh_CN', 'r': ~new Date() } let data = { - BaseRequest: this.getBaseRequest(), - MemberCount: MemberList.length, - MemberList: MemberList, - Topic: Topic + BaseRequest: this.getBaseRequest(), MemberCount: MemberList.length, MemberList: MemberList, Topic: Topic } return this.request({ - method: 'POST', - url: this.CONF.API_webwxcreatechatroom, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxcreatechatroom, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -1192,8 +1079,7 @@ export default class WechatCore { fun: fun } let data = { - BaseRequest: this.getBaseRequest(), - ChatRoomName: ChatRoomUserName + BaseRequest: this.getBaseRequest(), ChatRoomName: ChatRoomUserName } if (fun === 'addmember') { data.AddMemberList = MemberList.toString() @@ -1203,10 +1089,7 @@ export default class WechatCore { data.InviteMemberList = MemberList.toString() } return this.request({ - method: 'POST', - url: this.CONF.API_webwxupdatechatroom, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxupdatechatroom, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -1227,17 +1110,10 @@ export default class WechatCore { pass_ticket: this.PROP.passTicket } let data = { - BaseRequest: this.getBaseRequest(), - CmdId: 3, - OP: OP, - RemarkName: RemarkName, - UserName: UserName + BaseRequest: this.getBaseRequest(), CmdId: 3, OP: OP, RemarkName: RemarkName, UserName: UserName } return this.request({ - method: 'POST', - url: this.CONF.API_webwxoplog, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxoplog, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -1253,20 +1129,13 @@ export default class WechatCore { updateRemarkName (UserName, RemarkName) { return Promise.resolve().then(() => { let params = { - pass_ticket: this.PROP.passTicket, - 'lang': 'zh_CN' + pass_ticket: this.PROP.passTicket, 'lang': 'zh_CN' } let data = { - BaseRequest: this.getBaseRequest(), - CmdId: 2, - RemarkName: RemarkName, - UserName: UserName + BaseRequest: this.getBaseRequest(), CmdId: 2, RemarkName: RemarkName, UserName: UserName } return this.request({ - method: 'POST', - url: this.CONF.API_webwxoplog, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxoplog, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -1285,15 +1154,10 @@ export default class WechatCore { 'fun': 'modtopic' } let data = { - BaseRequest: this.getBaseRequest(), - ChatRoomName: ChatRoomUserName, - NewTopic: NewName + BaseRequest: this.getBaseRequest(), ChatRoomName: ChatRoomUserName, NewTopic: NewName } return this.request({ - method: 'POST', - url: this.CONF.API_webwxupdatechatroom, - params: params, - data: data + method: 'POST', url: this.CONF.API_webwxupdatechatroom, params: params, data: data }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -1307,19 +1171,13 @@ export default class WechatCore { revokeMsg (msgId, toUserName) { return Promise.resolve().then(() => { let data = { - BaseRequest: this.getBaseRequest(), - SvrMsgId: msgId, - ToUserName: toUserName, - ClientMsgId: getClientMsgId() + BaseRequest: this.getBaseRequest(), SvrMsgId: msgId, ToUserName: toUserName, ClientMsgId: getClientMsgId() } let headers = {} headers['ContentType'] = 'application/json; charset=UTF-8' headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' return this.request({ - method: 'POST', - url: this.CONF.API_webwxrevokemsg, - data: data, - headers: headers + method: 'POST', url: this.CONF.API_webwxrevokemsg, data: data, headers: headers }).then(res => { let data = res.data assert.equal(data.BaseResponse.Ret, 0, res) @@ -1333,10 +1191,7 @@ export default class WechatCore { getBaseRequest () { return { - Uin: parseInt(this.PROP.uin), - Sid: this.PROP.sid, - Skey: this.PROP.skey, - DeviceID: getDeviceID() + Uin: parseInt(this.PROP.uin), Sid: this.PROP.sid, Skey: this.PROP.skey, DeviceID: getDeviceID() } } } diff --git a/src/util/global.js b/src/util/global.js index 1f9d037..897531f 100644 --- a/src/util/global.js +++ b/src/util/global.js @@ -1,6 +1,8 @@ 'use strict' import Assert from 'assert' import _debug from 'debug' +import crypto from 'crypto' + const debug = _debug('util') export const isStandardBrowserEnv = ( @@ -87,3 +89,7 @@ export function getClientMsgId () { export function getDeviceID () { return 'e' + ('' + Math.random().toFixed(15)).substring(2, 17) } + +export function randomUuid () { + return crypto.randomUUID().replace(/-/g, '') +} diff --git a/src/wechat.js b/src/wechat.js index 5b1a2b2..4401010 100644 --- a/src/wechat.js +++ b/src/wechat.js @@ -65,7 +65,7 @@ class Wechat extends WechatCore { case 'mp4': return this.sendVideo(res.mediaId, toUserName) default: - return this.sendDoc(res.mediaId, res.name, res.size, res.ext, toUserName) + return this.sendDoc(res.mediaId, res.name, res.size, res.ext, toUserName, res.signature) } }) }