提交 aed688cb 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓)

change class name from Group to Room

上级 cbbcf63d
![Wechaty](https://raw.githubusercontent.com/zixia/wechaty/master/image/wechaty-logo-en.png)
# Wechaty [![Circle CI](https://circleci.com/gh/zixia/wechaty.svg?style=svg)](https://circleci.com/gh/zixia/wechaty) [![Build Status](https://travis-ci.org/zixia/wechaty.svg?branch=master)](https://travis-ci.org/zixia/wechaty) [![Build status](https://ci.appveyor.com/api/projects/status/60fgkemki7e6upb9?svg=true)](https://ci.appveyor.com/project/zixia/wechaty)
# Wechaty [![Linux Circle CI](https://circleci.com/gh/zixia/wechaty.svg?style=svg)](https://circleci.com/gh/zixia/wechaty) [![Linux Build Status](https://travis-ci.org/zixia/wechaty.svg?branch=master)](https://travis-ci.org/zixia/wechaty) [![Win32 Build status](https://ci.appveyor.com/api/projects/status/60fgkemki7e6upb9?svg=true)](https://ci.appveyor.com/project/zixia/wechaty)
Wechaty is a Chatbot Library for Wechat **Personal** Account.
> Easy creating personal account wechat robot in 9 lines of code.
**Connecting Bots**
**Connecting ChatBots**. Support [Linux](https://travis-ci.org/zixia/wechaty), [Win32](https://ci.appveyor.com/project/zixia/wechaty) and OSX(mac).
[![Join the chat at https://gitter.im/zixia/wechaty](https://badges.gitter.im/zixia/wechaty.svg)](https://gitter.im/zixia/wechaty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![node](https://img.shields.io/node/v/wechaty.svg?maxAge=2592000)](https://nodejs.org/)
......@@ -17,11 +17,11 @@ Wechaty is a Chatbot Library for Wechat **Personal** Account.
# Why
My daily life/work depends on too much chat on wechat.
* I almost have 14,000 wechat friends till May 2014, before wechat restricts a total number of friends to 5,000.
* I almost have 400 wechat groups that most of them have more than 400 members.
* I almost have 400 wechat rooms that most of them have more than 400 members.
Can you image that? I'm dying...
So a tireless bot working for me 24x7 on wechat, moniting/filtering the most important message is badly needed. For example: highlights discusstion which contains the KEYWORDS I want to follow up(especially in a noisy group). ;-)
So a tireless bot working for me 24x7 on wechat, moniting/filtering the most important message is badly needed. For example: highlights discusstion which contains the KEYWORDS I want to follow up(especially in a noisy room). ;-)
# Examples
Wechaty is super easy to use: 10 lines of javascript is enough for your first wechat robot.
......@@ -35,9 +35,9 @@ const bot = new Wechaty()
bot.init().on('scan', ({url, code}) => {
console.log(`Use Wechat to scan qrcode in url to login: ${code}\n${url}`)
}).on('message', m => {
bot.send(m.reply('roger')) // 1. reply
.then(() => console.log(`RECV: ${m}, REPLY: "roger"`)) // 2. log message
.catch(e => console.error(e)) // 3. catch exception
!m.self() && bot.send(m.reply('roger')) // 1. reply
.then(() => console.log(`RECV: ${m}, REPLY: "roger"`)) // 2. log message
.catch(e => console.error(e)) // 3. catch exception
})
```
......@@ -190,7 +190,7 @@ Supported prop list:
1. `from` :Contact
1. `to` :Contact
1. `content` :String
1. `group` :Group
1. `room` :Room
1. `date` :Date
```javascript
......@@ -226,6 +226,17 @@ const replyMessage = message.reply('roger!')
bot.send(replyMessage)
```
### Message.self()
Check if message is send by self.
Return `true` for send from self, `false` for send from others.
```javascript
if (m.self()) {
console.log('this message is sent by myself!')
}
```
## Class Contact
### Contact.get(prop)
......@@ -258,11 +269,11 @@ contact.ready()
})
```
## Class Group
## Class Room
### Group.get(prop)
Get prop from a group.
### Room.get(prop)
Get prop from a room.
Supported prop list:
......@@ -273,15 +284,15 @@ Supported prop list:
1. `name` :String
```javascript
group.get('members').length
room.get('members').length
```
### Group.ready()
A group may be not fully initialized yet. Call `ready()` to confirm we get all the data needed.
### Room.ready()
A room may be not fully initialized yet. Call `ready()` to confirm we get all the data needed.
Return a Promise, will be resolved when all data is ready.
```javascript
group.ready()
room.ready()
.then(() => {
// Here we can be sure all the data is ready for use.
})
......@@ -308,7 +319,7 @@ Know more about TAP: [Why I use Tape Instead of Mocha & So Should You](https://m
## v0.0.5 (2016/5/11)
1. Receive & send message
1. Show contacts info
1. Show groups info
1. Show rooms info
1. 1st usable version
1. Start coding from May 1st 2016
......@@ -318,10 +329,10 @@ Know more about TAP: [Why I use Tape Instead of Mocha & So Should You](https://m
- [ ] Accept a friend request
- [ ] Send a friend request
- [ ] Delete a contact
- [ ] Chat Group
- [ ] Create a new chat group
- [ ] Invite people to join a existing chat group
- [ ] Rename a Chat Group
- [ ] Chat Room
- [ ] Create a new chat room
- [ ] Invite people to join a existing chat room
- [ ] Rename a Chat Room
- [ ] Events
- [ ] Use EventEmitter2 to emit message events, so we can use wildcard
1. `message`
......
......@@ -2,7 +2,7 @@
*
* Wechaty bot use a ApiAi.com brain
*
* Apply Your Own ApiAi Developer API_KEY at:
* Apply Your Own ApiAi Developer API_KEY at:
* http://www.api.ai
*
* Enjoy!
......@@ -20,9 +20,9 @@ const Wechaty = require('../src/wechaty')
// log.level = 'silly'
/**
*
*
* `7217d7bce18c4bcfbe04ba7bdfaf9c08` for Wechaty demo
*
*
*/
const APIAI_API_KEY = '7217d7bce18c4bcfbe04ba7bdfaf9c08'
const brainApiAi = ApiAi(APIAI_API_KEY)
......@@ -33,8 +33,8 @@ console.log(`
Welcome to api.AI Wechaty Bot.
Api.AI Doc: https://docs.api.ai/v16/docs/get-started
Notice: This bot will only active in the group which name contains 'wechaty'.
/* if (m.group() && /Wechaty/i.test(m.group().name())) { */
Notice: This bot will only active in the room which name contains 'wechaty'.
/* if (m.room() && /Wechaty/i.test(m.room().name())) { */
Loading... please wait for QrCode Image Url and then scan to login.
`)
......@@ -46,10 +46,13 @@ bot
.on('login' , user => log.info('Bot', `bot login: ${user}`))
.on('logout' , e => log.info('Bot', 'bot logout.'))
.on('message', m => {
if (m.self()) { return }
co(function* () {
const msg = yield m.ready()
const room = Wechaty.Room.load(m.room())
if (m.group() && /Wechaty/i.test(m.group().name())) {
if (room && /Wechaty/i.test(room.name())) {
log.info('Bot', 'talk: %s' , msg)
talk(m)
} else {
......@@ -90,15 +93,15 @@ class Talker extends EventEmitter2 {
this.obj.time = []
return text
}
updateTimer(delayTime) {
delayTime = delayTime || this.delayTime()
log.verbose('Talker', 'updateTimer(%s)', delayTime)
if (this.timer) { clearTimeout(this.timer) }
this.timer = setTimeout(this.say.bind(this), delayTime)
}
hear(text) {
log.verbose('Talker', `hear(${text})`)
this.save(text)
......@@ -111,7 +114,7 @@ class Talker extends EventEmitter2 {
.then(reply => this.emit('say', reply))
this.timer = null
}
delayTime() {
const minDelayTime = 5000
const maxDelayTime = 15000
......@@ -123,11 +126,11 @@ class Talker extends EventEmitter2 {
var Talkers = []
function talk(m) {
const fromId = m.from().id
const groupId = m.group().id
const content = m.content()
const talkerName = fromId + groupId
const fromId = m.get('from')
const roomId = m.get('room')
const content = m.get('content')
const talkerName = fromId + roomId
if (!Talkers[talkerName]) {
Talkers[talkerName] = new Talker(function(text) {
return new Promise((resolve, reject) => {
......@@ -137,11 +140,11 @@ function talk(m) {
/*
{ id: 'a09381bb-8195-4139-b49c-a2d03ad5e014',
timestamp: '2016-05-27T17:22:46.597Z',
result:
result:
{ source: 'domains',
resolvedQuery: 'hi',
action: 'smalltalk.greetings',
parameters: { simplified: 'hello' },
parameters: { simplified: 'hello' },w
metadata: {},
fulfillment: { speech: 'Hi there.' },
score: 0 },
......
......@@ -42,7 +42,7 @@ bot
log.info('Bot', 'recv: %s', msg.toStringEx())
// logToFile(JSON.stringify(msg.rawObj))
if (/^(ding|ping|bing)$/i.test(m.get('content'))) {
if (/^(ding|ping|bing)$/i.test(m.get('content')) && !m.self()) {
const replyMsg = m.reply('dong')
bot.send(replyMsg)
.then(() => { log.warn('Bot', 'REPLY: dong') })
......
......@@ -6,7 +6,7 @@ bot.init()
console.log(`Use Wechat to scan qrcode in url to login: ${code}\n${url}`)
})
.on('message', m => {
bot.send(m.reply('roger')) // 1. reply
(!m.self()) && bot.send(m.reply('roger')) // 1. reply others' msg
.then(() => console.log(`RECV: ${m}, REPLY: "roger"`)) // 2. log message
.catch(e => console.error(e)) // 3. catch exception
})
......
......@@ -20,10 +20,10 @@ const Wechaty = require('../src/wechaty')
// log.level = 'silly'
/**
*
* Apply Your Own Tuling123 Developer API_KEY at:
*
* Apply Your Own Tuling123 Developer API_KEY at:
* http://www.tuling123.com
*
*
*/
const TULING123_API_KEY = '18f25157e0446df58ade098479f74b21'
const brain = new Tuling123(TULING123_API_KEY)
......@@ -34,8 +34,8 @@ console.log(`
Welcome to Tuling Wechaty Bot.
Tuling API: http://www.tuling123.com/html/doc/api.html
Notice: This bot will only active in the group whose name contains 'wechaty'.
/* if (m.group() && /Wechaty/i.test(m.group().name())) { */
Notice: This bot will only active in the room which name contains 'wechaty'.
/* if (/Wechaty/i.test(room.get('name'))) { */
Loading...
`)
......@@ -47,10 +47,13 @@ bot
console.log(`[${code}]Scan qrcode in url to login:\n${url}`)
})
.on('message', m => {
if (m.self()) return
co(function* () {
const msg = yield m.ready()
const room = Wechaty.Room.load(m.get('room'))
if (m.group() && /Wechaty/i.test(m.group().name())) {
if (room && /Wechaty/i.test(room.get('name'))) {
log.info('Bot', 'talk: %s' , msg)
talk(m)
} else {
......@@ -91,15 +94,15 @@ class Talker extends EventEmitter2 {
this.obj.time = []
return text
}
updateTimer(delayTime) {
delayTime = delayTime || this.delayTime()
log.verbose('Talker', 'updateTimer(%s)', delayTime)
if (this.timer) { clearTimeout(this.timer) }
this.timer = setTimeout(this.say.bind(this), delayTime)
}
hear(text) {
log.verbose('Talker', `hear(${text})`)
this.save(text)
......@@ -112,7 +115,7 @@ class Talker extends EventEmitter2 {
.then(reply => this.emit('say', reply))
this.timer = null
}
delayTime() {
const minDelayTime = 5000
const maxDelayTime = 15000
......@@ -124,11 +127,11 @@ class Talker extends EventEmitter2 {
var Talkers = []
function talk(m) {
const fromId = m.from().id
const groupId = m.group().id
const content = m.content()
const talkerName = fromId + groupId
const fromId = m.get('from')
const roomId = m.get('room')
const content = m.get('content')
const talkerName = fromId + roomId
if (!Talkers[talkerName]) {
Talkers[talkerName] = new Talker(function(text) {
return brain.ask(text, {userid: talkerName})
......
{
"name": "wechaty",
"version": "0.0.11",
"version": "0.1.0",
"description": "Wechat for Bot. (Personal Account, NOT Official Account)",
"main": "index.js",
"scripts": {
......
......@@ -97,6 +97,8 @@ class Contact {
Contact.init = function() { Contact.pool = {} }
Contact.init()
Contact.load = function(id) {
if (!id) { return null }
if (id in Contact.pool) {
return Contact.pool[id]
}
......
......@@ -8,7 +8,7 @@
*/
const Contact = require('./contact')
const Group = require('./group')
const Room = require('./room')
const log = require('npmlog')
class Message {
......@@ -27,15 +27,17 @@ class Message {
, type: rawObj.MsgType
, from: rawObj.MMActualSender
, to: rawObj.ToUserName
, group: rawObj.MMIsChatRoom ? rawObj.FromUserName : null // MMPeerUserName always eq FromUserName ?
, room: rawObj.MMIsChatRoom ? rawObj.FromUserName : null // MMPeerUserName always eq FromUserName ?
, content: rawObj.MMActualContent // Content has @id prefix added by wx
, status: rawObj.Status
, digest: rawObj.MMDigest
, date: rawObj.MMDisplayTime // Javascript timestamp of milliseconds
, self: undefined // to store the logined user id
// , from: Contact.load(rawObj.MMActualSender)
// , to: Contact.load(rawObj.ToUserName)
// , group: rawObj.MMIsChatRoom ? Group.load(rawObj.FromUserName) : null
// , room: rawObj.MMIsChatRoom ? Room.load(rawObj.FromUserName) : null
// , date: new Date(rawObj.MMDisplayTime*1000)
}
}
......@@ -48,8 +50,8 @@ class Message {
}
getSenderString() {
const name = Contact.load(this.obj.from).toStringEx()
const group = this.obj.group ? Group.load(this.obj.group).toStringEx() : null
return '<' + name + (group ? `@${group}` : '') + '>'
const room = this.obj.room ? Room.load(this.obj.room).toStringEx() : null
return '<' + name + (room ? `@${room}` : '') + '>'
}
getContentString() {
let content = this.unescapeHtml(this.stripHtml(this.obj.content))
......@@ -69,44 +71,50 @@ class Message {
from() { return this.obj.from }
to() { return this.obj.to }
content() { return this.obj.content }
group() { return this.obj.group }
room() { return this.obj.room }
self() {
if (!this.obj.self) {
log.warn('Message', 'self not set')
return false
} else {
return this.obj.self === this.obj.from
}
}
reply(replyContent) {
if (this.self()) {
throw new Error('dont reply message send by myself')
}
const m = new Message()
.set('from' , this.to())
.set('group' , this.group())
.set('to' , (this.group() ? this.group() : this.from()))
.set('content' , replyContent)
console.log(m)
.set('from' , this.obj.to)
.set('to' , this.obj.from)
.set('room' , this.obj.room)
// FIXME: find a alternate way to check a message create by `self`
.set('self' , this.obj.self)
// console.log(m)
return m
}
ready() {
log.silly('Message', 'ready()')
const f = Contact.load(this.obj.from)
const t = Contact.load(this.obj.to)
const g = this.obj.group ? Group.load(this.obj.group) : null
const from = Contact.load(this.obj.from)
const to = Contact.load(this.obj.to)
const room = this.obj.room ? Room.load(this.obj.room) : null
return f.ready() // Contact from
.then(r => t.ready()) // Contact to
.then(r => g && g.ready()) // Group member list
.then(r => this) // return this for chain
return from.ready() // Contact from
.then(() => to.ready()) // Contact to
.then(() => room && room.ready()) // Room member list
.then(() => this) // return this for chain
.catch(e => { // REJECTED
log.error('Message', 'ready() rejected: %s', e)
throw e
})
// return this.obj.from.ready() // Contact from
// .then(r => this.obj.to.ready()) // Contact to
// .then(r => this.obj.group && this.obj.group.ready()) // Group member list
// .then(r => this) // return this for chain
// .catch(e => { // REJECTED
// log.error('Message', 'ready() rejected: %s', e)
// throw new Error(e)
// })
}
get(prop) {
......
......@@ -23,7 +23,7 @@ const co = require('co')
const Puppet = require('./puppet')
const Message = require('./message')
const Contact = require('./contact')
const Group = require('./group')
const Room = require('./room')
const Server = require('./puppet-web-server')
const Browser = require('./puppet-web-browser')
......@@ -36,7 +36,7 @@ class PuppetWeb extends Puppet {
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
this.head = options.head
this.user = null // <Contact>
this.user = null // <Contact> of user self
}
toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` }
......@@ -74,7 +74,7 @@ class PuppetWeb extends Puppet {
initAttach() {
log.verbose('PuppetWeb', 'initAttach()')
Contact.attach(this)
Group.attach(this)
Room.attach(this)
return Promise.resolve(true)
}
initBrowser() {
......@@ -147,17 +147,15 @@ class PuppetWeb extends Puppet {
.catch(e => log.error('PuppetWeb', 'onServerLogin rejected: %s', e))
}
onServerLogout(data) {
this.emit('logout', this.user)
this.user = null
this.emit('logout', data)
}
onServerMessage(data) {
const m = new Message(data)
if (!this.user) {
log.warn('PuppetWeb', 'onServerMessage() without this.user')
} else if (this.user.id===m.get('from')) {
log.silly('PuppetWeb', 'onServerMessage skip msg send by self')
return
}
m.set('self', this.user.id)
this.emit('message', m)
}
onServerUnload(data) {
......@@ -183,19 +181,21 @@ class PuppetWeb extends Puppet {
}
send(message) {
const userName = message.get('to')
const content = message.get('content')
const to = message.get('to')
const room = message.get('room')
log.silly('PuppetWeb', `send(${userName}, ${content})`)
return this.bridge.send(userName, content)
}
reply(recvMsg, replyMsg) {
var contact = recvMsg.group()
if (!contact) { contact = recvMsg.from() }
const contactId = contact.id
let content = message.get('content')
let destination = to
if (room) {
destination = room
if (to && to!==room) {
content = `@[${to}] ${content}`
}
}
log.silly('PuppetWeb', `reply(${contact}, ${replyMsg})`)
return this.bridge.send(contactId, replyMsg)
log.silly('PuppetWeb', `send(${destination}, ${content})`)
return this.bridge.send(destination, content)
}
logout() { return this.bridge.logout() }
......
......@@ -2,7 +2,7 @@
* Wechat for Bot. and for human who can talk with bot/robot
*
* Interface for puppet
*
*
* Class Puppet
*
* Licenst: ISC
......@@ -40,7 +40,7 @@ class Puppet extends EventEmitter {
Object.assign(Puppet, {
Message: require('./message')
, Contact: require('./contact')
, Group: require('./group')
, Room: require('./room')
})
module.exports = Puppet
......@@ -9,37 +9,37 @@
const log = require('npmlog')
const Contact = require('./contact')
class Group {
class Room {
constructor(id) {
log.silly('Group', `constructor(${id})`)
log.silly('Room', `constructor(${id})`)
this.id = id
this.obj = {}
if (!Group.puppet) {
throw new Error('no puppet attached to Group')
if (!Room.puppet) {
throw new Error('no puppet attached to Room')
}
}
toString() { return this.id }
toStringEx() { return `Group(${this.obj.name}[${this.id}])` }
toStringEx() { return `Room(${this.obj.name}[${this.id}])` }
ready(contactGetter) {
log.silly('Group', `ready(${contactGetter})`)
log.silly('Room', `ready(${contactGetter})`)
if (!this.id) {
log.warn('Group', 'ready() on a un-inited group')
log.warn('Room', 'ready() on a un-inited Room')
return Promise.resolve(this)
} else if (this.obj.id) {
return Promise.resolve(this)
}
contactGetter = contactGetter || Group.puppet.getContact.bind(Group.puppet)
contactGetter = contactGetter || Room.puppet.getContact.bind(Room.puppet)
return contactGetter(this.id)
.then(data => {
log.silly('Group', `contactGetter(${this.id}) resolved`)
log.silly('Room', `contactGetter(${this.id}) resolved`)
this.rawObj = data
this.obj = this.parse(data)
return this
}).catch(e => {
log.error('Group', `contactGetter(${this.id}) rejected: ` + e)
log.error('Room', `contactGetter(${this.id}) rejected: ` + e)
throw new Error('contactGetter: ' + e)
})
}
......@@ -68,11 +68,11 @@ class Group {
}
dumpRaw() {
console.error('======= dump raw group =======')
console.error('======= dump raw Room =======')
Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
}
dump() {
console.error('======= dump group =======')
console.error('======= dump Room =======')
Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj[k]}`))
}
......@@ -85,14 +85,16 @@ class Group {
}
}
Group.init = function() { Group.pool = {} }
Group.init()
Group.load = function(id) {
if (id in Group.pool) {
return Group.pool[id]
Room.init = function() { Room.pool = {} }
Room.init()
Room.load = function(id) {
if (!id) { return null }
if (id in Room.pool) {
return Room.pool[id]
}
return Group.pool[id] = new Group(id)
return Room.pool[id] = new Room(id)
}
Group.attach = function(puppet) { Group.puppet = puppet }
Room.attach = function(puppet) { Room.puppet = puppet }
module.exports = Group
module.exports = Room
......@@ -17,7 +17,7 @@ const PuppetWeb = require('./puppet-web')
const Message = require('./message')
const Contact = require('./contact')
const Group = require('./group')
const Room = require('./room')
class Wechaty extends EventEmitter {
constructor(options) {
......@@ -101,7 +101,7 @@ Object.assign(Wechaty, {
Puppet: Puppet
, Message: Message
, Contact: Contact
, Group: Group
, Room: Room
})
/**
......
const test = require('tap').test
const Message = require('../src/message')
const Group = require('../src/group')
const Room = require('../src/room')
const Puppet = require('../src/puppet')
const log = require('npmlog')
//log.level = 'silly'
//log.enableColor()
Group.attach(new Puppet())
Room.attach(new Puppet())
test('Group smoke testing', t => {
test('Room smoke testing', t => {
const UserName = '@0bb3e4dd746fdbd4a80546aef66f4085'
const NickName = 'Nick Name Test'
const EncryChatRoomId = '123456abcdef'
......@@ -27,7 +27,7 @@ test('Group smoke testing', t => {
})
}
const g = new Group(UserName)
const g = new Room(UserName)
t.equal(g.id, UserName, 'id/UserName right')
g.ready(mockContactGetter)
......@@ -49,7 +49,7 @@ test('Group smoke testing', t => {
id: '1120003476579027592'
, from: '@0bb3e4dd746fdbd4a80546aef66f4085'
}
const g = new Group(rawData)
const g = new Room(rawData)
t.equal(g.id , EXPECTED.id , 'id right')
t.equal(g.from.id , EXPECTED.from , 'from right')
......
......@@ -5,10 +5,10 @@ test('Wechaty Library', function(t) {
t.ok(Wechaty , 'should have Wechaty exports')
t.ok(Wechaty.Message , 'should have Wechaty.Message exports')
t.ok(Wechaty.Contact , 'should have Wechaty.Contact exports')
t.ok(Wechaty.Group , 'should have Wechaty.Group exports')
t.ok(Wechaty.Room , 'should have Wechaty.Room exports')
t.ok(Wechaty.Puppet , 'should have Wechaty.Puppet exports')
t.ok(Wechaty.Puppet.Web , 'should have Wechaty.Puppet.Web exports')
t.end()
})
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册