diff --git a/README.md b/README.md index 223e440571d728d7a74b4ca08313fa4509620ca1..cc3cefcf431f986a26d14ba13d51406cc55ac2ed 100644 --- a/README.md +++ b/README.md @@ -327,12 +327,18 @@ Wechaty support the following 6 events: 8. room-leave 9. room-topic +### this.say(content: string) + +`this` is `Sayable` for all listeners. + +which means there will be a `this.say()` method inside listener call, you can use it sending message to `filehelper`, just for logging / reporting / any usage for your convienience + ### 1. Event: `scan` A `scan` event will be emitted when the bot need to show you a QrCode for scaning. ```typescript -wechaty.on('scan', (url: string, code: number) => { +wechaty.on('scan', (this: Sayable, url: string, code: number) => { console.log(`[${code}] Scan ${url} to login.` ) }) ``` @@ -350,7 +356,7 @@ wechaty.on('scan', (url: string, code: number) => { After the bot login full successful, the event `login` will be emitted, with a [Contact](#class-contact) of current logined user. ```javascript -wechaty.on('login', user => { +wechaty.on('login', (this: Sayable, user: Contact) => { console.log(`user ${user} login`) }) ``` @@ -360,7 +366,7 @@ wechaty.on('login', user => { `logout` will be emitted when bot detected it is logout, with a [Contact](#class-contact) of current logined user. ```javascript -wechaty.on('logout', user => { +wechaty.on('logout', (this: Sayable, user: Contact) => { console.log(`user ${user} logout`) }) ``` @@ -370,27 +376,29 @@ wechaty.on('logout', user => { Emit when there's a new message. ```javascript -wechaty.on('message', (message: Message) => { +wechaty.on('message', (this: Sayable, message: Message) => { console.log('message ${message} received') }) ``` ### 5. Event: `error` -Emit for error +Emit when there's a error occoured. -```ts -wechaty.on('error', (err: Error) => { - console.log(err.message) +```javascript +wechaty.on('error', (this: Sayable, err: Error) => { + console.log('error ${err.message} received') }) ``` +The `message` here is a [Message](#class-message). + ### 6. Event: `friend` Fired when we got new friend request, or confirm a friend ship. ```typescript -wechaty.on('friend', (contact: Contact, request: FriendRequest) => { +wechaty.on('friend', (this: Sayable, contact: Contact, request: FriendRequest) => { if (request) { // 1. request to be friend from new contact request.accept() console.log('auto accepted for ' + contact + ' with message: ' + request.hello) @@ -403,7 +411,7 @@ wechaty.on('friend', (contact: Contact, request: FriendRequest) => { ### 7. Event: `room-join` ```typescript -wechaty.on('room-join', (room: Room, invitee: Contact, inviter: Contact) => { +wechaty.on('room-join', (this: Sayable, room: Room, invitee: Contact, inviter: Contact) => { console.log(`Room ${room} got new member ${invitee}, invited by ${inviter}`) }) ``` @@ -411,7 +419,7 @@ wechaty.on('room-join', (room: Room, invitee: Contact, inviter: Contact) => { ### 8. Event: `room-leave` ```typescript -wechaty.on('room-leave', (room: Room, leaver: Contact) => { +wechaty.on('room-leave', (this: Sayable, room: Room, leaver: Contact) => { console.log(`Room ${room} lost member ${leaver}`) }) ``` @@ -419,7 +427,7 @@ wechaty.on('room-leave', (room: Room, leaver: Contact) => { ### 9. Event: `room-topic` ```typescript -wechaty.on('room-topic', (room: Room, topic: string, oldTopic: string, changer: Contact) => { +wechaty.on('room-topic', (this: Sayable, room: Room, topic: string, oldTopic: string, changer: Contact) => { console.log(`Room ${room} topic changed from ${oldTopic} to ${topic} by {changer}`) }) ``` @@ -482,6 +490,8 @@ wechaty.reply(message, 'roger') All wechat messages will be encaped as a Message. +`Message` is `Sayable` + ### Message.from(contact?: Contact|string): Contact get the sender from message, or set it. @@ -540,6 +550,8 @@ message.set('content', 'Hello, World!') ### Contact.name(): string +`Contact` is `Sayable` + ### Contact.ready(): Contact A Contact may be not fully initialized yet. Call `ready()` to confirm we get all the data needed. @@ -552,6 +564,10 @@ contact.ready() }) ``` +### Contact.say(content: string) + +say `content` to Contact + ### @deprecated Contact.get(prop): String|Number Get prop from a contact. @@ -572,7 +588,8 @@ contact.get('name') ## Class Room -Doc is cheap, read code: [Example/Room-Bot](https://github.com/wechaty/wechaty/blob/master/example/room-bot.js) +Doc is cheap, show you code: [Example/Room-Bot](https://github.com/wechaty/wechaty/blob/master/example/room-bot.js) +`Room` is `Sayable` ### Room.say(content: string, replyTo: Contact|Contact[]): Promise @@ -597,7 +614,13 @@ room.ready() force reload data for Room -### Event: `join` +### Room Events + +`this` is `Sayable` for all listeners. + +which means there will be a `this.say()` method inside listener call, you can use it sending message to `filehelper`, just for logging / reporting / any usage for your convienience + +#### Event: `join` ```javascript Room.on('join', (invitee, inviter) => void) @@ -611,13 +634,13 @@ room.on('join', (invitee, inviter) => { }) ``` -### Event: `leave` +#### Event: `leave` ```typescript Room.on('leave', (leaver) => void) ``` -### Event: `topic` +#### Event: `topic` ```typescript Room.on('topic', (topic, oldTopic, changer) => void) @@ -729,18 +752,18 @@ npm test # Version History ## v0.5.0 master (2016/10) The First Typescript Version -1. #40 Converted to Typescript (2016/10/11) -1. added `say()` method to Contact/Room instance, and to `this` inside wechaty event listeners +1. [#40](https://github.com/wechaty/wechaty/issues/40) Converted to Typescript (2016/10/11) +1. [#41](https://github.com/wechaty/wechaty/issues/41) added `say()` method to Contact/Room instance, and to `this` inside wechaty event listeners, to make them `Sayable` 1. BREAKING CHANGE: global event `scan` arguments changed from 1 to 2: now is (url: string, code: number) instead of {url, code} before. ## [v0.4.0](https://github.com/wechaty/wechaty/releases/tag/v0.4.0) (2016/10/9) The Latest Javascript Version -1. #32 Extend Room Class with: +1. [#32](https://github.com/wechaty/wechaty/issues/32) Extend Room Class with: 1. Global events: `room-join`, `room-leave`, `room-topic` 1. Room events: `join`, `leave`, `topic` 1. Create a new Room: `Room.create()` 1. Add/Del/Topic for Room 1. Other methods like nick/member/has/etc... -1. #33 New Class `FriendRequest` with: +1. [#33](https://github.com/wechaty/wechaty/issues/33) New Class `FriendRequest` with: 1. `Wechaty.on('friend', (contact, request) => {})` with Wechaty new Event `friend` 1. `accept()` to accept a friend request 1. `send()` to send new friend request diff --git a/src/config.ts b/src/config.ts index ec8ff95fd382b83386fdbfffdf7b732163ae4e17..1f9333aa667aefc8fc4af489d1525ecfaa8abbdf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -151,4 +151,8 @@ export type RecommendInfo = { VerifyFlag: number } +export interface Sayable { + say(content: string, replyTo?: any): Promise +} + export default Config diff --git a/src/event-scope.ts b/src/event-scope.ts deleted file mode 100644 index 6e76dac0b3e682fd0c2dc6bfb356e36377a69132..0000000000000000000000000000000000000000 --- a/src/event-scope.ts +++ /dev/null @@ -1,205 +0,0 @@ -/** - * - * Wechaty: Wechat for ChatBots. - * - * Class WechatyEvent - * - * Licenst: ISC - * https://github.com/wechaty/wechaty - * - * Events Function Wrapper - * - */ -import { - Config - , WechatyEventName -} from './config' - -import Contact from './contact' -import Message from './message' -import Room from './room' -import Wechaty from './wechaty' - -import log from './brolog-env' - -type WechatyEventScope = { - say: (content: string, replyTo?: Contact|Contact[]) => void -} - -const EVENT_CONFIG = { - error: wrapFilehelper - , friend: wrapFilehelper - , heartbeat: wrapFilehelper - , login: wrapFilehelper - , logout: null // NULL - , message: wrapMessage - , 'room-join': wrapRoom - , 'room-leave': wrapRoom - , 'room-topic': wrapRoom - , scan: null // NULL -} - -class EventScope { - public static list() { - return Object.keys(EVENT_CONFIG) - } - - public static wrap(this: Wechaty|Room, event: WechatyEventName, listener: Function): Function { - log.verbose('WechatyEvent', 'wrap(%s, %s)', event, typeof listener) - - if (typeof listener !== 'function') { - throw new Error('`listener` should be function') - } - - if (!(event in EVENT_CONFIG)) { - throw new Error('event not support: ' + event) - } - const wrapper = EVENT_CONFIG[event] - - /** - * We assign a empty object to each event listener, - * to carry the indenpendent scope - */ - if (wrapper) { - return wrapper(listener) - } else { - return listener - } - } -} - -function isContact(contact) { - if (contact.map && contact[0] instanceof Contact) { - return true - } else if (contact instanceof Contact) { - return true - } - return false -} - -function isRoom(room) { - /** - * here not use `instanceof Room` is because circular dependence problem... - */ - if (!room || !room.constructor - || room.constructor.name !== 'Room') { - return false - } - return true -} - -// function wrapContact(listener) { -// log.verbose('WechatyEvent', 'wrapContact()') - -// return (...argList) => { -// log.silly('WechatyEvent', 'wrapContact() listener') - -// if (!isContact(argList[0])) { -// throw new Error('contact not found in argList') -// } - -// const contact = argList[0] - -// const eventScope = {} -// eventScope.say = (content) => { -// const msg = new Message() -// msg.to(contact) -// msg.content(content) -// return Config.puppetInstance() -// .send(msg) -// } - -// return listener.apply(eventScope, argList) -// } -// } - -function wrapRoom(listener) { - log.verbose('WechatyEvent', 'wrapRoom()') - - return (room: Room, ...argList) => { - log.silly('WechatyEvent', 'wrapRoom(%s, %s, %s, %s) listener', room.topic(), argList[0], argList[1], argList[2]) - - let contact - for (let arg of argList) { - if (!contact && isContact(arg)) { - contact = arg - } - } - - if (!room || !isRoom(room) || !contact) { - throw new Error('room or contact not found') - } - - const eventScope = {} - eventScope.say = (content: string, replyTo?: Contact) => { - if (!replyTo) { - replyTo = contact - } else if (!isContact(replyTo)) { - throw new Error('replyTo is not Contact instance(s)') - } - return room.say(content, replyTo) - } - - return listener.apply(eventScope, [room, ...argList]) - } -} - -function wrapMessage(listener) { - log.verbose('WechatyEvent', 'wrapMessage()') - - return (...argList) => { - log.silly('WechatyEvent', 'wrapMessage() listener') - - // console.log('############### wrapped on message listener') - // console.log(typeof Message) - // console.log(argList) - if (!(argList[0] instanceof Message)) { - throw new Error('Message instance not found') - } - - const msg = argList[0] - - const sender = msg.from() - // const receiver = msg.to() - const room = msg.room() - - const eventScope = {} - eventScope.say = (content, replyTo) => { - log.silly('WechatyEvent', 'wrapMessage() say("%s", "%s")', content, replyTo) - - if (room) { - return room.say(content, replyTo) - } - - const m = new Message() - m.to(sender) - m.content(content) - - return Config.puppetInstance() - .send(m) - } - - return listener.apply(eventScope, argList) - } -} - -function wrapFilehelper(listener) { - log.verbose('WechatyEvent', 'wrapFilehelper()') - - return (...argList) => { - log.silly('WechatyEvent', 'wrapFilehelper() listener') - const eventScope = {} - eventScope.say = (content) => { - log.silly('WechatyEvent', 'wrapFilehelper() say(%s)', content) - const msg = new Message() - msg.to('filehelper') - msg.content(content) - return Config.puppetInstance() - .send(msg) - } - - return listener.apply(eventScope, argList) - } -} - -export default EventScope diff --git a/src/puppet-web/puppet-web.ts b/src/puppet-web/puppet-web.ts index 51f54c304bfb3b8528e7d59f8a7c785cc7c86b20..6400e4a2590643b77633d9e067394ee0e7606f86 100644 --- a/src/puppet-web/puppet-web.ts +++ b/src/puppet-web/puppet-web.ts @@ -37,13 +37,13 @@ import Event from './event' import Server from './server' import Watchdog from './watchdog' -type PuppetWebSetting = { +export type PuppetWebSetting = { head?: string profile?: string } const DEFAULT_PUPPET_PORT = 18788 // // W(87) X(88), ascii char code ;-] -class PuppetWeb extends Puppet { +export class PuppetWeb extends Puppet { public browser: Browser public bridge: Bridge @@ -318,9 +318,21 @@ class PuppetWeb extends Puppet { }) } + /** + * Bot say... + * send to `filehelper` for notice / log + */ + public say(content: string) { + const m = new Message() + m.to('filehelper') + m.content(content) + + return this.send(m) + } + + // @deprecated public reply(message: Message, replyContent: string) { log.warn('PuppetWeb', 'reply() @deprecated, please use Message.say()') - if (this.self(message)) { return Promise.reject(new Error('will not to reply message of myself')) } diff --git a/src/puppet.ts b/src/puppet.ts index d155e6e79b2378ac1d87b296c7fb198eefcc0398..c12b08c097966e06f421a433ecc4ac9552ed2575 100644 --- a/src/puppet.ts +++ b/src/puppet.ts @@ -71,6 +71,9 @@ abstract class Puppet extends EventEmitter { // } public abstract send(message: Message): Promise + public abstract say(content: string) + + // @deprecated public abstract reply(message: Message, reply): Promise public abstract reset(reason?: string) diff --git a/src/room.ts b/src/room.ts index 35feb94cbaf4c1b6917d68d43099051e6ef95f55..1d54577f6dd8d8013ad3159594ac17912c784e27 100644 --- a/src/room.ts +++ b/src/room.ts @@ -14,7 +14,7 @@ import Config from './config' import Contact from './contact' import Message from './message' import UtilLib from './util-lib' -import EventScope from './event-scope' +// import EventScope from './event-scope' import log from './brolog-env' @@ -116,26 +116,16 @@ export class Room extends EventEmitter { public on(event: string, listener: Function): this { log.verbose('Room', 'on(%s, %s)', event, typeof listener) - /** - * wrap a room event listener to a global event listener - */ - const listenerWithRoomArg = (room: Room, ...argList) => { - return listener.apply(this, argList) + const thisWithSay = { + say: (content: string) => { + return Config.puppetInstance() + .say(content) + } } + super.on(event, function() { + return listener.apply(thisWithSay, arguments) + }) - /** - * every room event must can be mapped to a global event. - * such as: `join` to `room-join` - */ - const globalEventName = 'room-' + event - const listenerWithScope = EventScope.wrap.call(this, globalEventName, listenerWithRoomArg) - - /** - * bind(listenerWithScope, this): - * the `this` is for simulate the global room-* event, - * provide the first argument `room` - */ - super.on(event, listenerWithScope.bind(listenerWithScope, this)) return this } diff --git a/src/wechaty.ts b/src/wechaty.ts index 0e04f003acfd2642c551c0cea65b4082c8c17679..e83a415e79745c68b18dc2a5a8b7bc853e45d0df 100644 --- a/src/wechaty.ts +++ b/src/wechaty.ts @@ -9,13 +9,14 @@ * */ import { EventEmitter } from 'events' -import * as path from 'path' import * as fs from 'fs' +import * as path from 'path' import { Config , HeadType , PuppetType + , Sayable , WechatyEventName } from './config' @@ -26,7 +27,7 @@ import Puppet from './puppet' import PuppetWeb from './puppet-web/' import Room from './room' import UtilLib from './util-lib' -import EventScope from './event-scope' +// import EventScope from './event-scope' import log from './brolog-env' @@ -155,23 +156,30 @@ export class Wechaty extends EventEmitter { return this } - public on(event: 'error' , listener: (error: Error) => void): this - public on(event: 'friend' , listener: (friend: Contact, request: FriendRequest) => void): this - public on(event: 'heartbeat' , listener: (data: any) => void): this - public on(event: 'logout' , listener: (user: Contact) => void): this - public on(event: 'login' , listener: (user: Contact) => void): this - public on(event: 'message' , listener: (message: Message, n: number) => void): this - public on(event: 'room-join' , listener: (room: Room, invitee: Contact, inviter: Contact) => void): this - public on(event: 'room-join' , listener: (room: Room, inviteeList: Contact, inviter: Contact) => void): this - public on(event: 'room-leave' , listener: (room: Room, leaver: Contact) => void): this - public on(event: 'room-topic' , listener: (room: Room, topic: string, oldTopic: string, changer: Contact) => void): this - public on(event: 'scan' , listener: (url: string, code: number) => void): this + public on(event: 'error' , listener: (this: Sayable, error: Error) => void): this + public on(event: 'friend' , listener: (this: Sayable, friend: Contact, request: FriendRequest) => void): this + public on(event: 'heartbeat' , listener: (this: Sayable, data: any) => void): this + public on(event: 'logout' , listener: (this: Sayable, user: Contact) => void): this + public on(event: 'login' , listener: (this: Sayable, user: Contact) => void): this + public on(event: 'message' , listener: (this: Sayable, message: Message, n: number) => void): this + public on(event: 'room-join' , listener: (this: Sayable, room: Room, invitee: Contact, inviter: Contact) => void): this + public on(event: 'room-join' , listener: (this: Sayable, room: Room, inviteeList: Contact, inviter: Contact) => void): this + public on(event: 'room-leave' , listener: (this: Sayable, room: Room, leaver: Contact) => void): this + public on(event: 'room-topic' , listener: (this: Sayable, room: Room, topic: string, oldTopic: string, changer: Contact) => void): this + public on(event: 'scan' , listener: (this: Sayable, url: string, code: number) => void): this public on(event: WechatyEventName, listener: Function): this { log.verbose('Wechaty', 'on(%s, %s)', event, typeof listener) - const listenerWithScope = EventScope.wrap.call(this, event, listener) - super.on(event, listenerWithScope) + const thisWithSay: Sayable = { + say: (content: string) => { + return Config.puppetInstance() + .say(content) + } + } + super.on(event, function() { + return listener.apply(thisWithSay, arguments) + }) return this } @@ -185,11 +193,23 @@ export class Wechaty extends EventEmitter { , profile: this.setting.profile }) break + default: throw new Error('Puppet unsupport(yet): ' + this.setting.type) } - EventScope.list().map(e => { + ; // must have a semicolon here to seperate the last line with `[]` + [ 'error' + , 'friend' + , 'heartbeat' + , 'login' + , 'logout' + , 'message' + , 'room-join' + , 'room-leave' + , 'room-topic' + , 'scan' + ].map(e => { // https://strongloop.com/strongblog/an-introduction-to-javascript-es6-arrow-functions/ // We’ve lost () around the argument list when there’s just one argument (rest arguments are an exception, eg (...args) => ...) puppet.on(e, (...args) => { @@ -206,7 +226,7 @@ export class Wechaty extends EventEmitter { */ // set puppet before init, because we need this.puppet if we quit() before init() finish - this.puppet = puppet + this.puppet = puppet // set puppet instance to Wechaty Static variable, for using by Contact/Room/Message/FriendRequest etc. Config.puppetInstance(puppet) @@ -254,6 +274,11 @@ export class Wechaty extends EventEmitter { }) } + public say(content: string) { + return this.puppet.say(content) + } + + /// @deprecated public reply(message: Message, reply: string) { log.warn('Wechaty', 'reply() @deprecated, please use Message.say()')