diff --git a/example/ding-dong-bot.ts b/example/ding-dong-bot.ts index bcdf5f10034220ee0dde88848d486b55ee8cc952..280014060b34949814c39f7ac75a6acc2f27b3d4 100644 --- a/example/ding-dong-bot.ts +++ b/example/ding-dong-bot.ts @@ -15,6 +15,7 @@ import { Config, Wechaty, log, + MediaMessage, } from '../' const welcome = ` @@ -71,6 +72,9 @@ bot if (/^(ding|ping|bing)$/i.test(m.content()) && !m.self()) { m.say('dong') log.info('Bot', 'REPLY: dong') + } else if (/^code$/i.test(m.content()) && !m.self()) { + m.say(new MediaMessage(__dirname + '/../image/BotQrcode.png')) + log.info('Bot', 'REPLY: Img') } } catch (e) { log.error('Bot', 'on(message) exception: %s' , e) diff --git a/package.json b/package.json index d9c0440f25a64d3e4c74deeafe9bc053642829b4..72b6c70c30fa6c5318f0347c79ff11a0492c3000 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "retry-promise": "1.0.0", "socket.io": "1.7.3", "selenium-webdriver": "3.3.0", + "bl": "^1.2.0", "ws": "2.2.2" }, "devDependencies": { @@ -167,4 +168,4 @@ "publishConfig": { "tag": "next" } -} +} \ No newline at end of file diff --git a/src/contact.ts b/src/contact.ts index af7668e50dc4f989f547b73bdfe8129a56c53b20..98d1e64066a8f50fa2913a9da0f453a6af7527f6 100644 --- a/src/contact.ts +++ b/src/contact.ts @@ -2,7 +2,10 @@ import { Config, Sayable, } from './config' -import { Message } from './message' +import { + Message, + MediaMessage, +} from './message' import { PuppetWeb } from './puppet-web' import { UtilLib } from './util-lib' import { Wechaty } from './wechaty' @@ -578,7 +581,11 @@ export class Contact implements Sayable { * await contact.say('welcome to wechaty!') * ``` */ - public async say(content: string): Promise { + public async say(text: string) + public async say(mediaMessage: MediaMessage) + + public async say(textOrMedia: string | MediaMessage): Promise { + const content = textOrMedia instanceof MediaMessage ? textOrMedia.filename() : textOrMedia log.verbose('Contact', 'say(%s)', content) const wechaty = Wechaty.instance() @@ -587,11 +594,17 @@ export class Contact implements Sayable { if (!user) { throw new Error('no user') } - const m = new Message() + let m + if (typeof textOrMedia === 'string') { + m = new Message() + m.content(textOrMedia) + } else if (textOrMedia instanceof MediaMessage) { + m = textOrMedia + } else { + throw new Error('not support args') + } m.from(user) m.to(this) - m.content(content) - log.silly('Contact', 'say() from: %s to: %s content: %s', user.name(), this.name(), content) await wechaty.send(m) diff --git a/src/message-media.ts b/src/message-media.ts deleted file mode 100644 index a0e889215a85dddb9810db641f20ea5a8a0f4abe..0000000000000000000000000000000000000000 --- a/src/message-media.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * - * wechaty: Wechat for Bot. and for human who talk to bot/robot - * - * Licenst: ISC - * https://github.com/zixia/wechaty - * - */ -import * as moment from 'moment' - -import { - Config, - log, -} from './config' -import { - AppMsgType, - Message, - MsgType, -} from './message' -import { UtilLib } from './util-lib' -import { PuppetWeb } from './puppet-web/puppet-web' -import { Bridge } from './puppet-web/bridge' - -export class MediaMessage extends Message { - private bridge: Bridge - - constructor(rawObj) { - super(rawObj) - // FIXME: decoupling needed - this.bridge = (Config.puppetInstance() as PuppetWeb) - .bridge - } - - public async ready(): Promise { - log.silly('MediaMessage', 'ready()') - - try { - await super.ready() - - let url: string|null = null - switch (this.type()) { - case MsgType.EMOTICON: - url = await this.bridge.getMsgEmoticon(this.id) - break - case MsgType.IMAGE: - url = await this.bridge.getMsgImg(this.id) - break - case MsgType.VIDEO: - case MsgType.MICROVIDEO: - url = await this.bridge.getMsgVideo(this.id) - break - case MsgType.VOICE: - url = await this.bridge.getMsgVoice(this.id) - break - - case MsgType.APP: - if (!this.rawObj) { - throw new Error('no rawObj') - } - switch (this.typeApp()) { - case AppMsgType.ATTACH: - if (!this.rawObj.MMAppMsgDownloadUrl) { - throw new Error('no MMAppMsgDownloadUrl') - } - // had set in Message - // url = this.rawObj.MMAppMsgDownloadUrl - break - - case AppMsgType.URL: - case AppMsgType.READER_TYPE: - if (!this.rawObj.Url) { - throw new Error('no Url') - } - // had set in Message - // url = this.rawObj.Url - break - - default: - const e = new Error('ready() unsupported typeApp(): ' + this.typeApp()) - log.warn('MediaMessage', e.message) - this.dumpRaw() - throw e - } - break - - case MsgType.TEXT: - if (this.typeSub() === MsgType.LOCATION) { - url = await this.bridge.getMsgPublicLinkImg(this.id) - } - break - - default: - throw new Error('not support message type for MediaMessage') - } - - if (!url) { - if (!this.obj.url) { - throw new Error('no obj.url') - } - url = this.obj.url - } - - this.obj.url = url - - } catch (e) { - log.warn('MediaMessage', 'ready() exception: %s', e.message) - throw e - } - } - - private ext(): string { - switch (this.type()) { - case MsgType.EMOTICON: - return 'gif' - - case MsgType.IMAGE: - return 'jpg' - - case MsgType.VIDEO: - case MsgType.MICROVIDEO: - return 'mp4' - - case MsgType.VOICE: - return 'mp3' - - case MsgType.APP: - switch (this.typeApp()) { - case AppMsgType.URL: - return 'url' // XXX - } - break - - case MsgType.TEXT: - if (this.typeSub() === MsgType.LOCATION) { - return 'jpg' - } - break - } - throw new Error('not support type: ' + this.type()) - } - - public filename(): string { - if (!this.rawObj) { - throw new Error('no rawObj') - } - - const objFileName = this.rawObj.FileName || this.rawObj.MediaId || this.rawObj.MsgId - - let filename = moment().format('YYYY-MM-DD HH:mm:ss') - + ' #' + this._counter - + ' ' + this.getSenderString() - + ' ' + objFileName - - filename = filename.replace(/ /g, '_') - - const re = /\.[a-z0-9]{1,7}$/i - if (!re.test(filename)) { - const ext = this.rawObj.MMAppMsgFileExt || this.ext() - filename += '.' + ext - } - return filename - } - - // private getMsgImg(id: string): Promise { - // return this.bridge.getMsgImg(id) - // .catch(e => { - // log.warn('MediaMessage', 'getMsgImg(%d) exception: %s', id, e.message) - // throw e - // }) - // } - - public async readyStream(): Promise { - try { - await this.ready() - // FIXME: decoupling needed - const cookies = await (Config.puppetInstance() as PuppetWeb).browser.readCookie() - if (!this.obj.url) { - throw new Error('no url') - } - return UtilLib.urlStream(this.obj.url, cookies) - } catch (e) { - log.warn('MediaMessage', 'stream() exception: %s', e.stack) - throw e - } - } -} diff --git a/src/message.ts b/src/message.ts index 61766fa2e7f1c79a93f53e03febba67536b4781f..4ae248ed5c694187453776d7561609355a2a2e31 100644 --- a/src/message.ts +++ b/src/message.ts @@ -6,6 +6,10 @@ * https://github.com/wechaty/wechaty * */ +import * as moment from 'moment' +import * as fs from 'fs' +import * as path from 'path' + import { Config, RecommendInfo, @@ -16,6 +20,8 @@ import { import { Contact } from './contact' import { Room } from './room' import { UtilLib } from './util-lib' +import { PuppetWeb } from './puppet-web/puppet-web' +import { Bridge } from './puppet-web/bridge' export type MsgRawObj = { MsgId: string, @@ -492,31 +498,47 @@ export class Message implements Sayable { }) } - public say(content: string, replyTo?: Contact|Contact[]): Promise { - log.verbose('Message', 'say(%s, %s)', content, replyTo) - - const m = new Message() - const room = this.room() - if (room) { - m.room(room) - } + public say(text: string, replyTo?: Contact | Contact[]): Promise + public say(mediaMessage: MediaMessage, replyTo?: Contact | Contact[]): Promise - if (!replyTo) { - m.to(this.from()) - m.content(content) + public say(textOrMedia: string | MediaMessage, replyTo?: Contact|Contact[]): Promise { + const content = textOrMedia instanceof MediaMessage ? textOrMedia.filename() : textOrMedia + log.verbose('Message', 'say(%s, %s)', content, replyTo) + let m + if (typeof textOrMedia === 'string') { + m = new Message() + const room = this.room() + if (room) { + m.room(room) + } - } else if (this.room()) { - let mentionList - if (Array.isArray(replyTo)) { - m.to(replyTo[0]) - mentionList = replyTo.map(c => '@' + c.name()).join(' ') - } else { - m.to(replyTo) - mentionList = '@' + replyTo.name() + if (!replyTo) { + m.to(this.from()) + m.content(textOrMedia) + + } else if (this.room()) { + let mentionList + if (Array.isArray(replyTo)) { + m.to(replyTo[0]) + mentionList = replyTo.map(c => '@' + c.name()).join(' ') + } else { + m.to(replyTo) + mentionList = '@' + replyTo.name() + } + m.content(mentionList + ' ' + textOrMedia) + } + } else if (textOrMedia instanceof MediaMessage) { + m = textOrMedia + const room = this.room() + if (room) { + m.room(room) } - m.content(mentionList + ' ' + content) + if (!replyTo) { + m.to(this.from()) + } } + return Config.puppetInstance() .send(m) } @@ -525,7 +547,197 @@ export class Message implements Sayable { Message.initType() -export * from './message-media' +export class MediaMessage extends Message { + private bridge: Bridge + private fileStream: NodeJS.ReadableStream + private fileName: string // 'music' + private fileExt: string // 'mp3' + + constructor(rawObj: object) + constructor(filePath: string) + + constructor(rawObjOrFilePath: object | string) { + if (typeof rawObjOrFilePath === 'string') { + super() + this.fileStream = fs.createReadStream(rawObjOrFilePath) + + const pathInfo = path.parse(rawObjOrFilePath) + this.fileName = pathInfo.name + this.fileExt = pathInfo.ext.replace(/^\./, '') + } else if (rawObjOrFilePath instanceof Object) { + super(rawObjOrFilePath as any) + } else { + throw new Error('not supported construct param') + } + + // FIXME: decoupling needed + this.bridge = (Config.puppetInstance() as PuppetWeb) + .bridge + } + + public async ready(): Promise { + log.silly('MediaMessage', 'ready()') + + try { + await super.ready() + + let url: string|null = null + switch (this.type()) { + case MsgType.EMOTICON: + url = await this.bridge.getMsgEmoticon(this.id) + break + case MsgType.IMAGE: + url = await this.bridge.getMsgImg(this.id) + break + case MsgType.VIDEO: + case MsgType.MICROVIDEO: + url = await this.bridge.getMsgVideo(this.id) + break + case MsgType.VOICE: + url = await this.bridge.getMsgVoice(this.id) + break + + case MsgType.APP: + if (!this.rawObj) { + throw new Error('no rawObj') + } + switch (this.typeApp()) { + case AppMsgType.ATTACH: + if (!this.rawObj.MMAppMsgDownloadUrl) { + throw new Error('no MMAppMsgDownloadUrl') + } + // had set in Message + // url = this.rawObj.MMAppMsgDownloadUrl + break + + case AppMsgType.URL: + case AppMsgType.READER_TYPE: + if (!this.rawObj.Url) { + throw new Error('no Url') + } + // had set in Message + // url = this.rawObj.Url + break + + default: + const e = new Error('ready() unsupported typeApp(): ' + this.typeApp()) + log.warn('MediaMessage', e.message) + this.dumpRaw() + throw e + } + break + + case MsgType.TEXT: + if (this.typeSub() === MsgType.LOCATION) { + url = await this.bridge.getMsgPublicLinkImg(this.id) + } + break + + default: + throw new Error('not support message type for MediaMessage') + } + + if (!url) { + if (!this.obj.url) { + throw new Error('no obj.url') + } + url = this.obj.url + } + + this.obj.url = url + + } catch (e) { + log.warn('MediaMessage', 'ready() exception: %s', e.message) + throw e + } + } + + public ext(): string { + if (this.fileExt) + return this.fileExt + + switch (this.type()) { + case MsgType.EMOTICON: + return 'gif' + + case MsgType.IMAGE: + return 'jpg' + + case MsgType.VIDEO: + case MsgType.MICROVIDEO: + return 'mp4' + + case MsgType.VOICE: + return 'mp3' + + case MsgType.APP: + switch (this.typeApp()) { + case AppMsgType.URL: + return 'url' // XXX + } + break + + case MsgType.TEXT: + if (this.typeSub() === MsgType.LOCATION) { + return 'jpg' + } + break + } + throw new Error('not support type: ' + this.type()) + } + + public filename(): string { + if (this.fileName && this.fileExt) { + return this.fileName + '.' + this.fileExt + } + + if (!this.rawObj) { + throw new Error('no rawObj') + } + + const objFileName = this.rawObj.FileName || this.rawObj.MediaId || this.rawObj.MsgId + + let filename = moment().format('YYYY-MM-DD HH:mm:ss') + + ' #' + this._counter + + ' ' + this.getSenderString() + + ' ' + objFileName + + filename = filename.replace(/ /g, '_') + + const re = /\.[a-z0-9]{1,7}$/i + if (!re.test(filename)) { + const ext = this.rawObj.MMAppMsgFileExt || this.ext() + filename += '.' + ext + } + return filename + } + + // private getMsgImg(id: string): Promise { + // return this.bridge.getMsgImg(id) + // .catch(e => { + // log.warn('MediaMessage', 'getMsgImg(%d) exception: %s', id, e.message) + // throw e + // }) + // } + + public async readyStream(): Promise { + if (this.fileStream) + return this.fileStream + + try { + await this.ready() + // FIXME: decoupling needed + const cookies = await (Config.puppetInstance() as PuppetWeb).browser.readCookie() + if (!this.obj.url) { + throw new Error('no url') + } + return UtilLib.urlStream(this.obj.url, cookies) + } catch (e) { + log.warn('MediaMessage', 'stream() exception: %s', e.stack) + throw e + } + } +} /* * join room in mac client: https://support.weixin.qq.com/cgi-bin/ diff --git a/src/puppet-web/bridge.ts b/src/puppet-web/bridge.ts index 1510080b2f04b3e67c83c4793c94fae7d3cfbe75..07af4455e8141c479cea02fe886a3a5ba33cae48 100644 --- a/src/puppet-web/bridge.ts +++ b/src/puppet-web/bridge.ts @@ -351,6 +351,43 @@ export class Bridge { ///////////////////////////////// } + public async getBaseRequest(): Promise { + log.verbose('PuppetWebBridge', 'getBaseRequest()') + + try { + return await this.proxyWechaty('getBaseRequest') + } catch (e) { + log.silly('PuppetWebBridge', 'proxyWechaty(getBaseRequest) exception: %s', e.message) + throw e + } + } + + public async getPassticket(): Promise { + log.verbose('PuppetWebBridge', 'getPassticket()') + + try { + return await this.proxyWechaty('getPassticket') + } catch (e) { + log.silly('PuppetWebBridge', 'proxyWechaty(getPassticket) exception: %s', e.message) + throw e + } + } + + public sendMedia(toUserName: string, mediaId: string, type: number): Promise { + if (!toUserName) { + throw new Error('UserName not found') + } + if (!mediaId) { + throw new Error('cannot say nothing') + } + + return this.proxyWechaty('sendMedia', toUserName, mediaId, type) + .catch(e => { + log.error('PuppetWebBridge', 'sendMedia() exception: %s', e.message) + throw e + }) + } + /** * Proxy Call to Wechaty in Bridge */ diff --git a/src/puppet-web/puppet-web.ts b/src/puppet-web/puppet-web.ts index 7edb40cd93974950671392127b9fb27a54cf65dd..aa3635d2fc2bedcbdcc45d8d6794f701885efee8 100644 --- a/src/puppet-web/puppet-web.ts +++ b/src/puppet-web/puppet-web.ts @@ -22,7 +22,10 @@ import { } from '../config' import { Contact } from '../contact' -import { Message } from '../message' +import { + Message, + MediaMessage, + } from '../message' import { Puppet } from '../puppet' import { Room } from '../room' import { UtilLib } from '../util-lib' @@ -33,6 +36,17 @@ import { Event } from './event' import { Server } from './server' import { Watchdog } from './watchdog' +import * as request from 'request' +import * as bl from 'bl' + +type MediaType = 'pic' | 'video' | 'doc' + +const enum UploadMediaType { + IMAGE = 1, + VIDEO = 2, + AUDIO = 3, + ATTACHMENT = 4, +} export type PuppetWebSetting = { head?: HeadName, profile?: string, @@ -296,11 +310,116 @@ export class PuppetWeb extends Puppet { throw new Error('PuppetWeb.self() no this.user') } - public async send(message: Message): Promise { - const to = message.to() - const room = message.room() + private async getBaseRequest(): Promise { + try { + let json = await this.bridge.getBaseRequest(); + let obj = JSON.parse(json) + return obj.BaseRequest + } catch (e) { + log.error('PuppetWeb', 'send() exception: %s', e.message) + throw e + } + } + + private async uploadMedia(mediaMessage: MediaMessage, toUserName: string): Promise { + if (!mediaMessage) + throw new Error('require mediaMessage') + + let filename = mediaMessage.filename() + let ext = mediaMessage.ext() + + let contentType = UtilLib.mime(ext) + let mediatype: MediaType + + switch (ext) { + case 'bmp': + case 'jpeg': + case 'jpg': + case 'png': + mediatype = 'pic' + break + case 'mp4': + mediatype = 'video' + break + default: + mediatype = 'doc' + } + + let readStream = await mediaMessage.readyStream() + let buffer = await new Promise((resolve, reject) => { + readStream.pipe(bl((err, data) => { + if (err) reject(err) + else resolve(data) + })) + }) + + let md5 = UtilLib.md5(buffer) + + let baseRequest = await this.getBaseRequest() + let passTicket = await this.bridge.getPassticket() + let cookie = await this.browser.readCookie() + let first = cookie.find(c => c.name === 'webwx_data_ticket') + let webwxDataTicket = first && first.value + let size = buffer.length + + let hostname = this.browser.hostname + let uploadMediaRequest = { + BaseRequest: baseRequest, + FileMd5: md5, + FromUserName: this.self().id, + ToUserName: toUserName, + UploadType: 2, + ClientMediaId: +new Date, + MediaType: UploadMediaType.ATTACHMENT, + StartPos: 0, + DataLen: size, + TotalLen: size, + } + + let formData = { + id: 'WU_FILE_1', + name: filename, + type: contentType, + lastModifiedDate: Date().toString(), + size: size, + mediatype, + uploadmediarequest: JSON.stringify(uploadMediaRequest), + webwx_data_ticket: webwxDataTicket, + pass_ticket: passTicket || '', + filename: { + value: buffer, + options: { + filename, + contentType, + size, + }, + }, + } + + let mediaId = await new Promise((resolve, reject) => { + request.post({ + url: `https://file.${hostname}/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json`, + headers: { + Referer: `https://${hostname}`, + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36', + }, + formData, + }, function (err, res, body) { + if (err) reject(err) + else { + let obj = JSON.parse(body) + resolve(obj.MediaId) + } + }) + }) + if (!mediaId) + throw new Error('upload fail') + return mediaId as string + } - const content = message.content() + public async sendMedia(message: MediaMessage): Promise { + const to = message.to() + const room = message.room() let destinationId @@ -313,19 +432,57 @@ export class PuppetWeb extends Puppet { destinationId = to.id } - log.silly('PuppetWeb', 'send() destination: %s, content: %s)', - destinationId, - content, + const mediaId = await this.uploadMedia(message, destinationId) + let msgType = UtilLib.msgType(message.ext()) + + log.silly('PuppetWeb', 'send() destination: %s, mediaId: %s)', + destinationId, + mediaId, ) try { - await this.bridge.send(destinationId, content) + await this.bridge.sendMedia(destinationId, mediaId, msgType) } catch (e) { log.error('PuppetWeb', 'send() exception: %s', e.message) throw e } return -} + } + + public async send(message: Message | MediaMessage): Promise { + const to = message.to() + const room = message.room() + + let destinationId + + if (room) { + destinationId = room.id + } else { + if (!to) { + throw new Error('PuppetWeb.send(): message with neither room nor to?') + } + destinationId = to.id + } + + if (message instanceof MediaMessage) { + await this.sendMedia(message) + } else { + const content = message.content() + + log.silly('PuppetWeb', 'send() destination: %s, content: %s)', + destinationId, + content, + ) + + try { + await this.bridge.send(destinationId, content) + } catch (e) { + log.error('PuppetWeb', 'send() exception: %s', e.message) + throw e + } + } + return + } /** * Bot say... diff --git a/src/puppet-web/wechaty-bro.js b/src/puppet-web/wechaty-bro.js index e051938d5f39d0c77963f2e91f4210daeea37d1d..3e8472b61b6e55b71125fd586df761f061a0736d 100644 --- a/src/puppet-web/wechaty-bro.js +++ b/src/puppet-web/wechaty-bro.js @@ -441,6 +441,36 @@ return location + path } + function getBaseRequest() { + var accountFactory = WechatyBro.glue.accountFactory + var BaseRequest = accountFactory.getBaseRequest() + + return JSON.stringify(BaseRequest) + } + + function getPassticket() { + var accountFactory = WechatyBro.glue.accountFactory + return accountFactory.getPassticket() + } + + function sendMedia(ToUserName, MediaId,Type) { + var chatFactory = WechatyBro.glue.chatFactory + var confFactory = WechatyBro.glue.confFactory + + if (!chatFactory || !confFactory) { + log('send() chatFactory or confFactory not exist.') + return false + } + + var m = chatFactory.createMessage({ + ToUserName: ToUserName + , MediaId: MediaId + , MsgType: Type + }) + chatFactory.appendMessage(m) + return chatFactory.sendMessage(m) + } + function send(ToUserName, Content) { var chatFactory = WechatyBro.glue.chatFactory var confFactory = WechatyBro.glue.confFactory @@ -826,6 +856,9 @@ , getMsgVideo: getMsgVideo , getMsgVoice: getMsgVoice , getMsgPublicLinkImg: getMsgPublicLinkImg + , getBaseRequest: getBaseRequest + , getPassticket: getPassticket + , sendMedia: sendMedia // for Wechaty Contact Class , contactFindAsync: contactFindAsync diff --git a/src/puppet.ts b/src/puppet.ts index 6edd7723a6ac76a0e1a9e47c0e37ed344ca63407..e69147c849328b043f1d37d2165667a77dd7b73d 100644 --- a/src/puppet.ts +++ b/src/puppet.ts @@ -4,7 +4,10 @@ import { Sayable, } from './config' import { Contact } from './contact' -import { Message } from './message' +import { + Message, + MediaMessage, +} from './message' import { StateMonitor } from './state-monitor' import { Room } from './room' @@ -30,7 +33,7 @@ export abstract class Puppet extends EventEmitter implements Sayable { public abstract self(): Contact - public abstract send(message: Message): Promise + public abstract send(message: Message | MediaMessage): Promise public abstract say(content: string): Promise public abstract reset(reason?: string): void diff --git a/src/util-lib.ts b/src/util-lib.ts index eaad65567d18f92d8ab46b124245cf47ad9c052d..3d5657826e84d82f42351fed5af58063442a59c4 100644 --- a/src/util-lib.ts +++ b/src/util-lib.ts @@ -8,7 +8,9 @@ import * as https from 'https' import * as http from 'http' import * as url from 'url' +import * as crypto from 'crypto' +import { MsgType } from './message' import { log } from './config' /** @@ -241,4 +243,43 @@ export class UtilLib { return currentPort + n } } + + public static md5(buffer: Buffer): string { + let md5sum = crypto.createHash('md5') + md5sum.update(buffer) + return md5sum.digest('hex') + } + + public static msgType(ext): MsgType { + switch (ext) { + case 'bmp': + case 'jpeg': + case 'jpg': + case 'png': + return MsgType.IMAGE + case 'mp4': + return MsgType.VIDEO + default: + return MsgType.APP + } + } + + public static mime(ext): string { + switch (ext) { + case 'pdf': + return 'application/pdf' + case 'bmp': + return 'image/bmp' + case 'jpeg': + return 'image/jpeg' + case 'jpg': + return 'image/jpeg' + case 'png': + return 'image/png' + case 'mp4': + return 'video/mp4' + default: + return 'application/octet-stream' + } + } } diff --git a/src/wechaty.ts b/src/wechaty.ts index 2bdc4505cef86133a242299e4bef7477874e6fd7..0eba17875387363e1f46d3ced39857573f2b5d8a 100644 --- a/src/wechaty.ts +++ b/src/wechaty.ts @@ -12,7 +12,10 @@ import { import { Contact } from './contact' import { FriendRequest } from './friend-request' -import { Message } from './message' +import { + Message, + MediaMessage, +} from './message' import { Puppet } from './puppet' import { PuppetWeb } from './puppet-web/' import { Room } from './room' @@ -420,7 +423,7 @@ export class Wechaty extends EventEmitter implements Sayable { /** * @todo document me */ - public async send(message: Message): Promise { + public async send(message: Message | MediaMessage): Promise { if (!this.puppet) { throw new Error('no puppet') }