未验证 提交 43f87702 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓) 提交者: GitHub

Merge branch 'master' into greenkeeper/update-to-node-10

language: node_js
node_js:
- "9"
- "8"
- "10"
os:
......@@ -40,17 +39,17 @@ jobs:
- npm test || travis_terminate 1
after_success:
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then npm run coverage; fi
- if [ "$TRAVIS_OS_NAME" == 'linux' ]; then npm run coverage; fi
- stage: pack
script:
- npm run test:pack && echo 'Npm pack testing is passed'
- npm run test:pack && echo 'Npm pack testing is passed' || travis_terminate 1
- stage: deploy
script:
- echo "Deploying to NPM ..."
- npm version
- if ./scripts/development-release.ts; then ./scripts/package-public-config-tag-next.ts; fi
- if ./scripts/development-release.ts; then ./scripts/package-publish-config-tag-next.ts; fi
- npm run dist
deploy:
......
......@@ -2,11 +2,9 @@
# https://help.github.com/articles/about-codeowners/
#
/docs/ @lijiarui @hczhcz @TingYinHelen @ax4
/examples/ @Gcaufy @hczhcz
/src/puppet-web/bridge.*.ts @mukaiu
/src/puppet-web/browser.ts @binsee
/src/puppet-web/browser-driver.*.ts @xjchengo
/src/puppet-web/event.ts @binsee
/src/puppet-web/firer.*.ts @xinbenlv
/src/puppet-web/wechaty-bro.js @binsee @mukaiu @hczhcz @cherry-geqi @zhenyong
/docs/ @lijiarui @hczhcz @TingYinHelen @ax4
/examples/ @Gcaufy @hczhcz
/src/puppet-puppeteer/bridge.*.ts @mukaiu @xjchengo
/src/puppet-puppeteer/event.ts @binsee
/src/puppet-puppeteer/firer.*.ts @xinbenlv
/src/puppet-puppeteer/wechaty-bro.js @binsee @mukaiu @hczhcz @cherry-geqi @zhenyong
......@@ -6,18 +6,18 @@ Thank you for your time on Wechaty.
Contribute by marketing: Add **Powered by Wechaty** Badge to your Project Homepage:
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/wechaty/wechaty)
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/Chatie/wechaty)
## Markdown
```markdown
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/wechaty/wechaty)
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/Chatie/wechaty)
```
## Html
```html
<a href="https://github.com/wechaty/wechaty" target="_blank">
<a href="https://github.com/Chatie/wechaty" target="_blank">
<img src="https://img.shields.io/badge/Powered%20By-Wechaty-green.svg" alt="Powered by Wechaty" border="0">
</a>
```
......
......@@ -72,11 +72,11 @@ LABEL org.label-schema.license="ISC" \
org.label-schema.schema-version="$(wechaty-version)" \
org.label-schema.name="Wechaty" \
org.label-schema.description="Wechat for Bot" \
org.label-schema.usage="https://github.com/wechaty/wechaty/wiki/Docker" \
org.label-schema.usage="https://github.com/Chatie/wechaty/wiki/Docker" \
org.label-schema.url="https://www.chatie.io" \
org.label-schema.vendor="AKA Mobi" \
org.label-schema.vcs-ref="$SOURCE_COMMIT" \
org.label-schema.vcs-url="https://github.com/wechaty/wechaty" \
org.label-schema.vcs-url="https://github.com/Chatie/wechaty" \
org.label-schema.docker.cmd="docker run -ti --rm zixia/wechaty <code.js>" \
org.label-schema.docker.cmd.test="docker run -ti --rm zixia/wechaty test" \
org.label-schema.docker.cmd.help="docker run -ti --rm zixia/wechaty help" \
......
......@@ -4,12 +4,11 @@
## CONNECTING CHATBOTS
Wechaty is a Bot Framework for Wechat **Personal** Account which can help you create a bot in 6 lines of javascript, with cross-platform support include [Linux](https://travis-ci.org/chatie/wechaty), [Windows](https://ci.appveyor.com/project/chatie/wechaty), [Darwin(OSX/Mac)](https://travis-ci.org/chatie/wechaty) and [Docker](https://app.shippable.com/github/Chatie/wechaty).
Wechaty is a Bot SDK for Wechat **Personal** Account which can help you create a bot in 6 lines of javascript, with cross-platform support include [Linux](https://travis-ci.com/chatie/wechaty), [Windows](https://ci.appveyor.com/project/chatie/wechaty), [Darwin(OSX/Mac)](https://travis-ci.com/chatie/wechaty) and [Docker](https://app.shippable.com/github/Chatie/wechaty).
[![node](https://img.shields.io/node/v/wechaty.svg?maxAge=604800)](https://nodejs.org/)
[![NPM Version](https://badge.fury.io/js/wechaty.svg)](https://badge.fury.io/js/wechaty)
[![Docker Pulls](https://img.shields.io/docker/pulls/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-blue.svg)](https://www.typescriptlang.org/)
[![Repo Size](https://reposs.herokuapp.com/?path=Chatie/wechaty)](https://github.com/chatie/wechaty)
[![Donate Wechaty](https://img.shields.io/badge/Donate-Wechaty%20$-green.svg)](https://salt.bountysource.com/checkout/amount?team=chatie)
:octocat: <https://github.com/chatie/wechaty>
:beetle: <https://github.com/chatie/wechaty/issues>
......@@ -54,6 +53,9 @@ You can find more examples from [Wiki](https://github.com/chatie/wechaty/wiki/Ex
## GETTING STARTED
[![node](https://img.shields.io/node/v/wechaty.svg?maxAge=604800)](https://nodejs.org/)
[![Repo Size](https://reposs.herokuapp.com/?path=Chatie/wechaty)](https://github.com/chatie/wechaty)
### A Great Live Coding Tutorial
<div align="center">
......@@ -77,7 +79,9 @@ Notice: The published versions have always passed the CI tests. We highly recomm
#### Docker
[![Docker Pulls](https://img.shields.io/docker/pulls/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/) [![Docker Stars](https://img.shields.io/docker/stars/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/) [![Docker Layers](https://images.microbadger.com/badges/image/zixia/wechaty.svg)](https://microbadger.com/#/images/zixia/wechaty)
[![Docker Pulls](https://img.shields.io/docker/pulls/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/)
[![Docker Stars](https://img.shields.io/docker/stars/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/)
[![Docker Layers](https://images.microbadger.com/badges/image/zixia/wechaty.svg)](https://microbadger.com/#/images/zixia/wechaty)
The **best practice** to use Wechaty is running with docker, because it's not only the most easy way to get started, but also protects you from the troubles of dependency problems.
......@@ -120,7 +124,7 @@ Get to know more about NPM at [Wiki:NPM](https://github.com/chatie/wechaty/wiki/
## TEST
[![Linux/Mac Build Status](https://img.shields.io/travis/Chatie/wechaty.svg?label=Linux/Mac)](https://travis-ci.org/Chatie/wechaty)
[![Linux/Mac Build Status](https://travis-ci.com/Chatie/wechaty.svg?branch=master)](https://travis-ci.com/Chatie/wechaty)
[![Windows Build Status](https://img.shields.io/appveyor/ci/chatie/wechaty/master.svg?label=Windows)](https://ci.appveyor.com/project/chatie/wechaty)
[![Docker Build Status](https://img.shields.io/shippable/5aaf8667ec373f17004dcb66.svg?label=Docker&color=brightgreen)](https://app.shippable.com/github/Chatie/wechaty)
......@@ -151,6 +155,7 @@ See: [Official API Reference](https://chatie.github.io/wechaty/)
## POWERED BY WECHATY
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/chatie/wechaty)
[![Donate Wechaty](https://img.shields.io/badge/Donate-Wechaty%20$-green.svg)](https://salt.bountysource.com/checkout/amount?team=chatie)
### Wechaty Badge
......@@ -221,8 +226,7 @@ Scan now, because other Wechaty developers want to talk with you too! (secret co
Wechaty is far from perfect. The following things should be addressed in the future:
* [ ] PuppetWine - Use DLL Inject to hook Windows Wechat Application, run from wine inside docker.
* [ ] PuppetAndroid - Use Xposed to Hook Android Pad version of Wechat App, run from android emulator inside docker.
* [Create New Puppets for Wechaty #1167](https://github.com/Chatie/wechaty/issues/1167)
## AUTHOR
......@@ -253,3 +257,9 @@ At last, It's built for my personal study purpose of Automatically Testing.
[downloads-image]: http://img.shields.io/npm/dm/wechaty.svg?style=flat-square
[downloads-url]: https://npmjs.org/package/wechaty
## NOTES
* github.com/chatie-oos: Open Open Source for Chatie Community.
* wechaty-puppet-abstract Plugin Design: strong support wechaty.
* list all donation sponsors on README, and create a wechat group for sponsors.
......@@ -2,7 +2,7 @@
#
# Wechaty - Connect ChatBots
#
# https://github.com/wechaty/wechaty
# https://github.com/Chatie/wechaty
#
set -e
......
......@@ -64,7 +64,10 @@ client.init()
client.initWeb()
.catch(onError.bind(client))
function onError(e) {
function onError(
this : IoClient,
e : Error,
) {
log.error('Client', 'initWeb() fail: %s', e)
this.quit()
process.exit(-1)
......
docs/images/bot-qr-code.png

380.7 KB | W: | H:

docs/images/bot-qr-code.png

71.2 KB | W: | H:

docs/images/bot-qr-code.png
docs/images/bot-qr-code.png
docs/images/bot-qr-code.png
docs/images/bot-qr-code.png
  • 2-up
  • Swipe
  • Onion skin
此差异已折叠。
......@@ -43,7 +43,8 @@ import { EventEmitter } from 'events'
import {
config,
Wechaty,
} from '../'
Message,
} from '../src/'
// log.level = 'verbose'
// log.level = 'silly'
......@@ -77,7 +78,7 @@ bot
console.log(`${url}\n[${code}] Scan QR Code in above url to login: `)
})
.on('login' , user => log.info('Bot', `bot login: ${user}`))
.on('logout' , e => log.info('Bot', 'bot logout.'))
.on('logout' , user => log.info('Bot', 'bot %s logout.', user))
.on('message', m => {
if (m.self()) { return }
......@@ -103,11 +104,16 @@ bot.start()
})
class Talker extends EventEmitter {
private thinker
private obj
private timer
private thinker: (text: string) => Promise<string>
private obj: {
text: string[],
time: number[],
}
private timer?: number
constructor(thinker) {
constructor(
thinker: (text: string) => Promise<string>,
) {
log.verbose('Talker()')
super()
this.thinker = thinker
......@@ -115,10 +121,9 @@ class Talker extends EventEmitter {
text: [],
time: [],
}
this.timer = null
}
public save(text) {
public save(text: string) {
log.verbose('Talker', 'save(%s)', text)
this.obj.text.push(text)
this.obj.time.push(Date.now())
......@@ -131,7 +136,7 @@ class Talker extends EventEmitter {
return text
}
public updateTimer(delayTime?) {
public updateTimer(delayTime?: number) {
delayTime = delayTime || this.delayTime()
log.verbose('Talker', 'updateTimer(%s)', delayTime)
......@@ -139,7 +144,7 @@ class Talker extends EventEmitter {
this.timer = setTimeout(this.say.bind(this), delayTime)
}
public hear(text) {
public hear(text: string) {
log.verbose('Talker', `hear(${text})`)
this.save(text)
this.updateTimer()
......@@ -149,7 +154,7 @@ class Talker extends EventEmitter {
const text = this.load()
this.thinker(text)
.then(reply => this.emit('say', reply))
this.timer = null
this.timer = undefined
}
public delayTime() {
......@@ -161,19 +166,22 @@ class Talker extends EventEmitter {
}
/* tslint:disable:variable-name */
const Talkers: Talker[] = []
const Talkers: {
[index: string]: Talker,
} = {}
function talk(m) {
function talk(m: Message) {
const fromId = m.from().id
const roomId = m.room().id
const content = m.content()
const room = m.room()
const roomId = room && room.id
const content = m.text()
const talkerName = fromId + roomId
if (!Talkers[talkerName]) {
Talkers[talkerName] = new Talker(function(text) {
Talkers[talkerName] = new Talker(function(text: string) {
return new Promise((resolve, reject) => {
brainApiAi.textRequest(text)
.on('response', function(response) {
.on('response', function(response: any) {
console.log(response)
/*
{ id: 'a09381bb-8195-4139-b49c-a2d03ad5e014',
......@@ -188,7 +196,7 @@ function talk(m) {
score: 0 },
status: { code: 200, errorType: 'success' } }
*/
const reply = response.result.fulfillment.speech
const reply: string = response.result.fulfillment.speech
if (!reply) {
log.info('ApiAi', `Talker do not want to talk for "${text}"`)
return reject()
......@@ -196,7 +204,7 @@ function talk(m) {
log.info('ApiAi', 'Talker reply:"%s" for "%s" ', reply, text)
return resolve(reply)
})
.on('error', function(error) {
.on('error', function(error: Error) {
log.error('ApiAi', error)
reject(error)
})
......
......@@ -9,7 +9,7 @@ import * as qrcode from 'qrcode-terminal'
import {
Wechaty,
} from '../../index'
} from '../../src/'
const screen = blessed.screen({
smartCSR: true,
......@@ -249,7 +249,7 @@ setInterval(function() {
screen.render()
}, 500)
function setLineData(mockData, line) {
function setLineData(mockData: any, line: any) {
for (let i = 0; i < mockData.length; i++) {
const last = mockData[i].y[mockData[i].y.length - 1]
mockData[i].y.shift()
......@@ -261,6 +261,7 @@ function setLineData(mockData, line) {
}
screen.key(['escape', 'q', 'C-c'], function(ch, key) {
console.log(ch, key)
return process.exit(0)
})
......@@ -321,7 +322,7 @@ function startBot(bot: Wechaty, logElement: any) {
{
small: true,
},
qrData => logElement.setContent(qrData),
(qrData: string) => logElement.setContent(qrData),
)
}
// logElement.log(`${url}\n[${code}] Scan QR Code above url to log in: `)
......
......@@ -28,15 +28,13 @@ const QrcodeTerminal = require('qrcode-terminal')
* when you are runing with Docker or NPM instead of Git Source.
*/
import {
config,
// Contact,
Wechaty,
log,
} from '../'
} from '../src/'
const welcome = `
=============== Powered by Wechaty ===============
-------- https://github.com/wechaty/wechaty --------
-------- https://github.com/Chatie/wechaty --------
Hello,
......@@ -54,7 +52,7 @@ Please wait... I'm trying to login in...
`
console.log(welcome)
const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE })
const bot = Wechaty.instance()
bot
.on('login' , function(this, user) {
......@@ -103,16 +101,16 @@ async function onLogin() {
}
}
/**
* Special contact list
*/
// /**
// * Special contact list
// */
for (let i = 0; i < contactList.length; i++) {
const contact = contactList[i]
if (contact.special()) {
log.info('Bot', `special ${i}: ${contact.name()}`)
}
}
// for (let i = 0; i < contactList.length; i++) {
// const contact = contactList[i]
// if (contact.special()) {
// log.info('Bot', `special ${i}: ${contact.name()}`)
// }
// }
/**
* personal contact list
......@@ -121,7 +119,7 @@ async function onLogin() {
for (let i = 0; i < contactList.length; i++) {
const contact = contactList[i]
if (contact.personal()) {
log.info('Bot', `personal ${i}: ${contact.get('name')} : ${contact.id}`)
log.info('Bot', `personal ${i}: ${contact.name()} : ${contact.id}`)
}
}
......
......@@ -19,8 +19,9 @@
import * as path from 'path'
/* tslint:disable:variable-name */
const QrcodeTerminal = require('qrcode-terminal')
const finis = require('finis')
import * as QrcodeTerminal from 'qrcode-terminal'
import finis from 'finis'
import { FileBox } from 'file-box'
/**
* Change `import { ... } from '../'`
......@@ -28,11 +29,9 @@ const finis = require('finis')
* when you are runing with Docker or NPM instead of Git Source.
*/
import {
config,
Wechaty,
log,
MediaMessage,
} from '../index'
} from '../src/'
const BOT_QR_CODE_IMAGE_FILE = path.join(
__dirname,
......@@ -63,7 +62,7 @@ Please wait... I'm trying to login in...
`
console.log(welcome)
const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE })
const bot = Wechaty.instance()
bot
.on('logout' , user => log.info('Bot', `${user.name()} logouted`))
......@@ -74,43 +73,50 @@ bot
.on('scan', (url, code) => {
if (!/201|200/.test(String(code))) {
const loginUrl = url.replace(/\/qrcode\//, '/l/')
QrcodeTerminal.generate(loginUrl)
QrcodeTerminal.generate(loginUrl, { small: true }, (qrcode: string) => {
console.log(qrcode)
console.log(url)
console.log(`[${code}] Scan QR Code above url to log in: `)
})
}
console.log(`${url}\n[${code}] Scan QR Code above url to log in: `)
})
.on('message', async m => {
.on('message', async msg => {
try {
const room = m.room()
console.log(
(room ? `${room}` : '')
+ `${m.from()}:${m}`,
)
if (/^(ding|ping|bing|code)$/i.test(m.content()) && !m.self()) {
m.say('dong')
console.log(msg.toString())
if (/^(ding|ping|bing|code)$/i.test(msg.text()) && !msg.self()) {
/**
* 1. reply 'dong'
*/
log.info('Bot', 'REPLY: dong')
msg.say('dong')
const joinWechaty = `Join Wechaty Developers' Community\n\n` +
`Wechaty is used in many ChatBot projects by hundreds of developers.\n\n` +
`If you want to talk with other developers, just scan the following QR Code in WeChat with secret code: wechaty,\n\n` +
`you can join our Wechaty Developers' Home at once`
await m.say(joinWechaty)
await m.say(new MediaMessage(BOT_QR_CODE_IMAGE_FILE))
await m.say('Scan now, because other Wechaty developers want to talk with you too!\n\n(secret code: wechaty)')
log.info('Bot', 'REPLY: Image')
await msg.say(joinWechaty)
/**
* 2. reply qrcode image
*/
const fileBox = FileBox.fromLocal(BOT_QR_CODE_IMAGE_FILE)
log.info('Bot', 'REPLY: %s', fileBox)
await msg.say(fileBox)
/**
* 3. reply 'scan now!'
*/
await msg.say('Scan now, because other Wechaty developers want to talk with you too!\n\n(secret code: wechaty)')
}
} catch (e) {
log.error('Bot', 'on(message) exception: %s' , e)
console.error(e)
}
})
bot.start()
.catch(e => {
log.error('Bot', 'start() fail: %s', e)
bot.stop()
process.exit(-1)
})
bot.on('error', async e => {
log.error('Bot', 'error: %s', e)
if (bot.logonoff()) {
......@@ -119,31 +125,80 @@ bot.on('error', async e => {
// await bot.stop()
})
let killChrome: NodeJS.SignalsListener
bot.start()
.then(() => {
const listenerList = process.listeners('SIGINT')
for (const listener of listenerList) {
if (listener.name === 'killChrome') {
process.removeListener('SIGINT', listener)
killChrome = listener
}
}
})
.catch(e => {
log.error('Bot', 'start() fail: %s', e)
bot.stop()
process.exit(-1)
})
let quiting = false
finis(async (code, signal) => {
finis((code, signal) => {
log.info('Bot', 'finis(%s, %s)', code, signal)
if (!bot.logonoff()) {
log.info('Bot', 'finis() bot had been already stopped')
doExit(code)
}
if (quiting) {
log.warn('Bot', 'finis(%s, %s) called when quiting... just wait...', code, signal)
log.warn('Bot', 'finis() already quiting... return and wait...')
return
}
quiting = true
log.info('Bot', 'finis(%s, %s)', code, signal)
let done = false
// let checkNum = 0
const exitMsg = `Wechaty will exit ${code} because of ${signal} `
if (bot.logonoff()) {
log.info('Bot', 'finis() stoping bot')
await bot.say(exitMsg).catch(console.error)
} else {
log.info('Bot', 'finis() bot had been already stopped')
}
setTimeout(async () => {
log.info('Bot', 'finis() setTimeout() going to exit with %d', code)
try {
if (bot.logonoff()) {
await bot.stop()
}
} catch (e) {
log.error('Bot', 'finis() setTimeout() exception: %s', e)
} finally {
process.exit(code)
log.info('Bot', 'finis() broadcast quiting message for bot')
bot.say(exitMsg)
// .then(() => bot.stop())
.catch(e => log.error('Bot', 'finis() catch rejection: %s', e))
.then(() => done = true)
setImmediate(checkForExit)
function checkForExit() {
// if (checkNum++ % 100 === 0) {
log.info('Bot', 'finis() checkForExit() checking done: %s', done)
// }
if (done) {
log.info('Bot', 'finis() checkForExit() done!')
setTimeout(() => doExit(code), 1000) // delay 1 second
return
}
}, 3 * 1000)
// death loop to wait for `done`
// process.nextTick(checkForExit)
// setImmediate(checkForExit)
setTimeout(checkForExit, 100)
}
})
function doExit(code: number): void {
log.info('Bot', 'doExit(%d)', code)
if (killChrome) {
killChrome('SIGINT')
}
process.exit(code)
}
// process.on('SIGINT', function() {
// console.log('Nice SIGINT-handler')
// const listeners = process.listeners('SIGINT')
// for (let i = 0; i < listeners.length; i++) {
// console.log(listeners[i].toString())
// }
// })
......@@ -30,11 +30,12 @@ import {
// Contact,
log,
Wechaty,
} from '../'
FriendRequest,
} from '../src/'
const welcome = `
=============== Powered by Wechaty ===============
-------- https://github.com/wechaty/wechaty --------
-------- https://github.com/Chatie/wechaty --------
Hello,
......@@ -75,16 +76,16 @@ bot
* Wechaty Event: `friend`
*
*/
.on('friend', async (contact, request) => {
.on('friend', async request => {
let logMsg
const fileHelper = bot.Contact.load('filehelper')
try {
logMsg = 'received `friend` event from ' + contact.get('name')
logMsg = 'received `friend` event from ' + request.contact().name()
fileHelper.say(logMsg)
console.log(logMsg)
if (request) {
switch (request.type()) {
/**
*
* 1. New Friend Request
......@@ -92,20 +93,24 @@ bot
* when request is set, we can get verify message from `request.hello`,
* and accept this request by `request.accept()`
*/
if (request.hello === 'ding') {
logMsg = 'accepted because verify messsage is "ding"'
request.accept()
} else {
logMsg = 'not auto accepted, because verify message is: ' + request.hello
}
} else {
/**
*
* 2. Friend Ship Confirmed
*
*/
logMsg = 'friend ship confirmed with ' + contact.get('name')
case FriendRequest.Type.Receive:
if (request.hello() === 'ding') {
logMsg = 'accepted because verify messsage is "ding"'
request.accept()
} else {
logMsg = 'not auto accepted, because verify message is: ' + request.hello
}
break
/**
*
* 2. Friend Ship Confirmed
*
*/
case FriendRequest.Type.Confirm:
logMsg = 'friend ship confirmed with ' + request.contact().name()
break
}
} catch (e) {
logMsg = e.message
......
......@@ -26,7 +26,7 @@ import {
config,
Wechaty,
log,
} from '../../'
} from '../../src/'
import { onMessage } from './on-message'
import { onFriend } from './on-friend'
......@@ -34,7 +34,7 @@ import { onRoomJoin } from './on-room-join'
const welcome = `
=============== Powered by Wechaty ===============
-------- https://github.com/wechaty/wechaty --------
-------- https://github.com/Chatie/wechaty --------
Please wait... I'm trying to login in...
......
......@@ -23,18 +23,23 @@
* when you are runing with Docker or NPM instead of Git Source.
*/
import {
Contact,
FriendRequest,
Wechaty,
// Room,
} from '../../'
} from '../../src/'
export async function onFriend(this: Wechaty, contact: Contact, request?: FriendRequest): Promise<void> {
export async function onFriend(
this: Wechaty,
request: FriendRequest,
): Promise<void> {
try {
if (!request) {
const contact = request.contact()
if (request.type() === FriendRequest.Type.Confirm) {
console.log('New friend ' + contact.name() + ' relationship confirmed!')
return
}
/********************************************
*
* 从这里开始修改 vvvvvvvvvvvv
......@@ -49,7 +54,7 @@ export async function onFriend(this: Wechaty, contact: Contact, request?: Friend
3000,
)
if (request.hello === 'ding') {
if (request.hello() === 'ding') {
const myRoom = await this.Room.find({ topic: 'ding' })
if (!myRoom) return
setTimeout(
......
......@@ -25,17 +25,17 @@
import {
Message,
Wechaty,
} from '../../'
} from '../../src/'
export async function onMessage(this: Wechaty, message: Message): Promise<void> {
try {
const room = message.room()
const sender = message.from()
const content = message.content()
const content = message.text()
console.log((room ? '[' + room.topic() + ']' : '')
+ '<' + sender.name() + '>'
+ ':' + message.toStringDigest(),
+ ':' + message,
)
if (message.self() || room) {
......
......@@ -25,14 +25,14 @@
import {
Contact,
Room,
Sayable,
} from '../../'
Wechaty,
} from '../../src/'
export async function onRoomJoin(
this: Sayable,
room: Room,
inviteeList: Contact[],
inviter: Contact,
this: Wechaty,
room: Room,
inviteeList: Contact[],
inviter: Contact,
): Promise<void> {
try {
const inviteeName = inviteeList.map(c => c.name()).join(', ')
......
......@@ -19,7 +19,7 @@
exports = module.exports = async function onMessage (message) {
const room = message.room();
const sender = message.from();
const content = message.content();
const content = message.text();
const topic = room ? '[' + room.topic() + ']' : '';
......
......@@ -34,10 +34,11 @@ import * as qrcodeTerminal from 'qrcode-terminal'
*/
import {
config,
MediaMessage,
Message,
// MsgType,
Wechaty,
} from '../'
} from '../src/'
const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE })
bot
......@@ -49,8 +50,8 @@ bot
console.log(`${url}\n[${code}] Scan QR Code in above url to login: `)
})
.on('login' , user => console.log(`${user} logined`))
.on('message', m => {
console.log(`RECV: ${m}`)
.on('message', msg => {
console.log(`RECV: ${msg}`)
// console.log(inspect(m))
// saveRawObj(m.rawObj)
......@@ -63,26 +64,31 @@ bot
// || m.type() === MsgType.APP
// || (m.type() === MsgType.TEXT && m.typeSub() === MsgType.LOCATION) // LOCATION
// ) {
if (m instanceof MediaMessage) {
saveMediaFile(m)
if (msg.type() !== Message.Type.Text) {
saveMediaFile(msg)
}
// }
})
.start()
.catch(e => console.error('bot.start() error: ' + e))
async function saveMediaFile(message: MediaMessage) {
const filename = message.filename()
async function saveMediaFile(message: Message) {
const fileBox = message.file()
const filename = fileBox.name
console.log('IMAGE local filename: ' + filename)
if (!filename) {
throw new Error('no filename found for media file')
}
const fileStream = createWriteStream(filename)
console.log('start to readyStream()')
try {
const netStream = await message.readyStream()
netStream
fileBox
.pipe(fileStream)
.on('close', _ => {
.on('close', () => {
const stat = statSync(filename)
console.log('finish readyStream() for ', filename, ' size: ', stat.size)
})
......
......@@ -34,7 +34,7 @@ export default async function onMessage (message) {
try {
const room = message.room()
const sender = message.from()
const content = message.content()
const content = message.text()
const roomName = room ? `[${room.topic()}] ` : ''
process.stdout.write(
......@@ -44,11 +44,11 @@ export default async function onMessage (message) {
saveMediaFile(message)
return
}
console.log(`${Misc.digestEmoji(message)}`)
// add an extra CR if too long
if (content.length > 80) console.log("")
const config = await hotImport('config.js')
// Hot import! Try to change the msgKW1&2 to 'ping' & 'pong'
// after the bot has already started!
......@@ -62,7 +62,7 @@ export default async function onMessage (message) {
}
} catch (e) {
log.error('Bot', 'on(message) exception: %s' , e)
}
}
}
async function saveMediaFile(message) {
......
......@@ -25,9 +25,9 @@ const qrcodeTerminal = require('qrcode-terminal')
* to `import { ... } from 'wechaty'`
* when you are runing with Docker or NPM instead of Git Source.
*/
import { Wechaty } from '../'
import { Wechaty } from '../src/'
const bot = Wechaty.instance(/* no profile here because roger bot is too noisy */)
const bot = Wechaty.instance()
bot
.on('scan', (url, code) => {
......@@ -41,7 +41,7 @@ bot
if (m.self()) {
return // skip self
}
await m.say('roger') // 1. reply others' msg
await m.say('roger') // 1. reply others' msg
console.log(`RECV: ${m}, REPLY: "roger"`) // 2. log message
})
.start()
......
......@@ -58,11 +58,11 @@ import {
Room,
Wechaty,
log,
} from '../'
} from '../src/'
const welcome = `
=============== Powered by Wechaty ===============
-------- https://github.com/wechaty/wechaty --------
-------- https://github.com/Chatie/wechaty --------
Hello,
......@@ -144,10 +144,11 @@ bot
*/
.on('room-topic', function(this, room, topic, oldTopic, changer) {
try {
log.info('Bot', 'EVENT: room-topic - Room %s change topic to %s by member %s',
log.info('Bot', 'EVENT: room-topic - Room %s change topic from %s to %s by member %s',
room,
oldTopic,
topic,
changer.name(),
changer,
)
} catch (e) {
log.error('Bot', 'room-topic event exception: %s', e.stack)
......@@ -160,11 +161,11 @@ bot
.on('message', async function(this: Wechaty, message) {
const room = message.room()
const sender = message.from()
const content = message.content()
const content = message.text()
console.log((room ? '[' + room.topic() + ']' : '')
+ '<' + sender.name() + '>'
+ ':' + message.toStringDigest(),
+ ':' + message,
)
if (message.self()) {
......@@ -264,7 +265,7 @@ async function manageDingRoom() {
/**
* Event: Join
*/
room.on('join', function(this, inviteeList, inviter) {
room.on('join', function(inviteeList, inviter) {
log.verbose('Bot', 'Room EVENT: join - %s, %s',
inviteeList.map(c => c.name()).join(', '),
inviter.name(),
......@@ -303,11 +304,9 @@ async function checkRoomJoin(room: Room, inviteeList: Contact[], inviter: Contac
try {
// let to, content
const user = bot.self()
if (!user) {
throw new Error('no user')
}
if (inviter.id !== user.id) {
const userSelf = bot.userSelf()
if (inviter.id !== userSelf.id) {
await room.say('RULE1: Invitation is limited to me, the owner only. Please do not invit people without notify me.',
inviter,
......@@ -337,7 +336,7 @@ async function checkRoomJoin(room: Room, inviteeList: Contact[], inviter: Contac
}
async function putInRoom(contact, room) {
async function putInRoom(contact: Contact, room: Room) {
log.info('Bot', 'putInRoom(%s, %s)', contact.name(), room.topic())
try {
......@@ -366,10 +365,10 @@ function getHelperContact() {
log.info('Bot', 'getHelperContact()')
// create a new room at least need 3 contacts
return Contact.find({ name: HELPER_CONTACT_NAME })
return bot.Contact.find({ name: HELPER_CONTACT_NAME })
}
async function createDingRoom(contact): Promise<any> {
async function createDingRoom(contact: Contact): Promise<any> {
log.info('Bot', 'createDingRoom(%s)', contact)
try {
......@@ -385,7 +384,7 @@ async function createDingRoom(contact): Promise<any> {
const contactList = [contact, helperContact]
log.verbose('Bot', 'contactList: %s', contactList.join(','))
const room = await Room.create(contactList, 'ding')
const room = await bot.Room.create(contactList, 'ding')
log.info('Bot', 'createDingRoom() new ding room created: %s', room)
await room.topic('ding - created')
......
......@@ -17,7 +17,7 @@
*
*/
import { createWriteStream } from 'fs'
import { createWriteStream, createReadStream } from 'fs'
import {
PassThrough,
Readable,
......@@ -37,10 +37,9 @@ const qrcodeTerminal = require('qrcode-terminal')
*/
import {
config,
MediaMessage,
MsgType,
Message,
Wechaty,
} from '../'
} from '../src/'
const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE })
......@@ -53,18 +52,24 @@ bot
console.log(`${url}\n[${code}] Scan QR Code in above url to login: `)
})
.on('login' , user => console.log(`${user} logined`))
.on('message', async function(this, msg) {
.on('message', async function(msg) {
console.log(`RECV: ${msg}`)
if (msg.type() !== MsgType.VOICE) {
if (msg.type() !== Message.Type.Audio) {
return // skip no-VOICE message
}
const mp3Stream = await (msg as MediaMessage).readyStream()
// const mp3Stream = await msg.readyStream()
const file = createWriteStream(msg.filename())
mp3Stream.pipe(file)
const filename = msg.file().name
if (!filename) {
throw new Error('no filename for media message')
}
const file = createWriteStream(filename)
msg.file().pipe(file)
const mp3Stream = createReadStream(filename)
const text = await speechToText(mp3Stream)
console.log('VOICE TO TEXT: ' + text)
......@@ -118,7 +123,7 @@ function mp3ToWav(mp3Stream: Readable): NodeJS.ReadableStream {
// .on('end', function() {
// console.log('Finished processing');
// })
.on('error', function(err, stdout, stderr) {
.on('error', function(err: Error /*, stdout, stderr */) {
console.log('Cannot process video: ' + err.message)
})
......@@ -162,7 +167,7 @@ async function wavToText(wavStream: NodeJS.ReadableStream): Promise<string> {
}
return new Promise<string>((resolve, reject) => {
wavStream.pipe(request.post(apiUrl, options, (err, httpResponse, body) => {
wavStream.pipe(request.post(apiUrl, options, (err, _ /* httpResponse */, body) => {
// "err_msg":"success.","err_no":0,"result":["这是一个测试测试语音转文字,"]
if (err) {
return reject(err)
......
......@@ -36,10 +36,9 @@ const Tuling123 = require('tuling123-client')
* when you are runing with Docker or NPM instead of Git Source.
*/
import {
config,
Wechaty,
log,
} from '../'
} from '../src/'
// log.level = 'verbose'
// log.level = 'silly'
......@@ -53,7 +52,7 @@ import {
const TULING123_API_KEY = '18f25157e0446df58ade098479f74b21'
const tuling = new Tuling123(TULING123_API_KEY)
const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE })
const bot = Wechaty.instance()
console.log(`
Welcome to Tuling Wechaty Bot.
......@@ -67,7 +66,7 @@ Loading...
bot
.on('login' , user => log.info('Bot', `bot login: ${user}`))
.on('logout' , e => log.info('Bot', 'bot logout.'))
.on('logout' , user => log.info('Bot', 'bot %s logout.', user))
.on('scan', (url, code) => {
if (!/201|200/.test(String(code))) {
const loginUrl = url.replace(/\/qrcode\//, '/l/')
......@@ -82,10 +81,10 @@ bot
log.info('Bot', 'talk: %s' , msg)
try {
const {text: reply} = await tuling.ask(msg.content(), {userid: msg.from()})
const {text: reply} = await tuling.ask(msg.text(), {userid: msg.from()})
log.info('Tuling123', 'Talker reply:"%s" for "%s" ',
reply,
msg.content(),
msg.text(),
)
msg.say(reply)
} catch (e) {
......
export {
config,
log,
Sayable,
VERSION,
} from './src/config'
export { Contact } from './src/contact'
// ISSUE #70 import { FriendRequest } from './src/friend-request'
export {
PuppetWebFriendRequest as FriendRequest,
} from './src/puppet-web/friend-request'
export {
MsgType,
} from './src/puppet-web/schema'
export { IoClient } from './src/io-client'
export {
Message,
MediaMessage,
} from './src/message'
export { Profile } from './src/profile'
export { Puppet } from './src/puppet'
export { PuppetWeb } from './src/puppet-web/'
export { Room } from './src/room'
export { Misc } from './src/misc'
import Wechaty from './src/wechaty'
export { Wechaty }
export default Wechaty
{
"name": "wechaty",
"version": "0.15.11",
"version": "0.15.47",
"description": "Wechat for Bot(Personal Account)",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"wechaty": {
"DEFAULT_HEAD": 0,
"DEFAULT_PORT": 8080,
"DEFAULT_PUPPET": "web",
"DEFAULT_PROFILE": "demo",
"DEFAULT_PUPPET": "puppeteer",
"DEFAULT_PROFILE": "default",
"DEFAULT_PROTOCOL": "io|0.0.1",
"DEFAULT_TOKEN": "WECHATY_IO_TOKEN",
"DEFAULT_APIHOST": "api.chatie.io"
},
"scripts": {
"clean": "shx rm -fr dist/*",
"dist": "npm run clean && tsc && shx cp src/puppet-web/*.js dist/src/puppet-web/",
"doc": "npm run dist && echo '# Wechaty v'$(jq -r .version package.json)' Documentation\n* https://blog.chatie.io\n' > docs/index.md && jsdoc2md dist/src/{wechaty,room,contact,friend-request,message}.js dist/src/puppet-web/{friend-request,schema}.js>> docs/index.md",
"dist": "npm run clean && tsc && shx cp src/puppet-puppeteer/*.js dist/src/puppet-puppeteer/",
"doc": "bash -x scripts/generate-docs.sh",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"changelog": "github_changelog_generator -u chatie -p wechaty && sed -i'.bak' /greenkeeper/d CHANGELOG.md && sed -i'.bak' '/An in-range update of/d' CHANGELOG.md && ts-node scripts/sort-contributiveness.ts < CHANGELOG.md > CHANGELOG.new.md 2>/dev/null && cat CHANGELOG.md >> CHANGELOG.new.md && mv CHANGELOG.new.md CHANGELOG.md",
"doctor": "npm run check-node-version && ts-node bin/doctor",
......@@ -24,13 +24,13 @@
"lint": "npm run check-node-version && npm run lint:ts && npm run lint:es && npm run lint:sh",
"lint:es": "eslint \"{bin,examples,scripts,src,tests}/**/*.js\" --ignore-pattern=\"tests/fixtures/**\"",
"lint:md": "markdownlint README.md",
"lint:ts": "npm run clean && echo tslint v`tslint --version` && tslint --project tsconfig.json \"{bin,examples,scripts,src,tests}/**/*.ts\" --exclude \"tests/fixtures/**\" --exclude \"dist/\" && tsc --noEmit",
"lint:ts": "tslint --project tsconfig.json && tsc --noEmit",
"lint:sh": "bash -n bin/*.sh",
"sloc": "sloc bin examples scripts src tests index.ts --details --format cli-table --keys total,source,comment && sloc bin examples scripts src tests index.ts",
"sloc": "sloc bin examples scripts src tests --details --format cli-table --keys total,source,comment && sloc bin examples scripts src tests",
"ts-node": "ts-node",
"test": "npm run clean && npm run lint && npm run test:unit:retry && npm run test:shell && npm run sloc",
"test:linux": "npm run pretest && parallel ts-node -- ./src/**/*.spec.ts ./tests/**/*.spec.ts && npm run posttest",
"test:pack": "npm run dist && export TMPDIR=/tmp/wechaty.$$ && npm pack && mkdir $TMPDIR && mv wechaty-*.*.*.tgz $TMPDIR && cp tests/fixtures/smoke-testing.js $TMPDIR && cd $TMPDIR && npm init -y && npm i wechaty-*.*.*.tgz && (for i in {1..3}; do node smoke-testing.js && break || sleep 1; done)",
"test:pack": "bash -x scripts/npm-pack-testing.sh",
"test:shell": "shellcheck bin/*.sh",
"test:unit": "blue-tape -r ts-node/register \"src/**/*.spec.ts\" \"src/*.spec.ts\" \"tests/*.spec.ts\" \"tests/**/*.spec.ts\"",
"test:unit:retry": "ts-node scripts/retry-unit-tests",
......@@ -40,11 +40,6 @@
"demo": "ts-node examples/ding-dong-bot.ts",
"start": "npm run demo"
},
"git": {
"scripts": {
"pre-push": "./scripts/pre-push.sh"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/chatie/wechaty.git"
......@@ -97,39 +92,47 @@
"node": ">= 8.5"
},
"dependencies": {
"@types/node": "^9.4.0",
"@types/ws": "^4.0.1",
"bl": "^1.2.0",
"bl": "^2.0.0",
"brolog": "^1.2.0",
"clone-class": "^0.6.11",
"cuid": "^2.1.1",
"hot-import": "^0.1.0",
"express": "^4.16.3",
"file-box": "^0.2.1",
"hot-import": "^0.2.1",
"mime": "^2.2.0",
"normalize-package-data": "^2.4.0",
"puppeteer": "^1.2.0",
"raven": "^2.2.0",
"read-pkg-up": "^3.0.0",
"request": "^2.83.0",
"retry-promise": "^1.0.0",
"rx-queue": "^0.3.1",
"rxjs": "^5.5.0",
"rx-queue": "^0.4.7",
"rxjs": "^6.1.0",
"state-switch": "^0.4.5",
"watchdog": "^0.6.3",
"vinyl": "^2.1.0",
"watchdog": "^0.8.1",
"wechat4u": "^0.7.6",
"ws": "^5.1.0",
"xml2js": "^0.4.0"
},
"devDependencies": {
"@types/blessed": "^0.1.10",
"@types/blue-tape": "^0.1.0",
"@types/cuid": "^1.3.0",
"@types/express": "^4.0.0",
"@types/express": "^4.11.1",
"@types/fluent-ffmpeg": "^2.1.0",
"@types/glob": "^5.0.0p",
"@types/mime": "^2.0.0",
"@types/node": "^10.0.3",
"@types/normalize-package-data": "^2.4.0",
"@types/puppeteer": "^1.0.0",
"@types/raven": "^2.1.0",
"@types/read-pkg-up": "^3.0.0",
"@types/request": "^2.0.0",
"@types/semver": "^5.5.0",
"@types/sinon": "^4.3.0",
"@types/sinon": "^5.0.0",
"@types/vinyl": "^2.0.2",
"@types/ws": "^5.1.0",
"@types/xml2js": "^0.4.0",
"apiai": "^4.0.0",
"babel-cli": "^6.26.0",
......@@ -143,26 +146,25 @@
"coveralls": "^3.0.0",
"cross-env": "^5.0.0",
"eslint": "^4.8.0",
"express": "^4.15.0",
"finis": "^0.4.1",
"fluent-ffmpeg": "^2.1.0",
"git-scripts": "git+https://github.com/nkzawa/git-scripts.git",
"glob": "^7.1.0",
"jsdoc-to-markdown": "^4.0.0",
"markdownlint-cli": "^0.8.1",
"markdownlint-cli": "^0.9.0",
"nyc": "^11.2.0",
"qrcode-terminal": "^0.12.0",
"semver": "^5.5.0",
"shx": "^0.2.0",
"sinon": "^5.0.0",
"sinon": "^5.0.2",
"sinon-test": "^2.1.2",
"sloc": "^0.2.0",
"ts-node": "^6.0.0",
"tslint": "^5.9.0",
"tslint": "^5.9.1",
"tslint-eslint-rules": "^5.1.0",
"tslint-jsdoc-rules": "^0.1.0",
"tuling123-client": "^0.0.1",
"typescript": "^2.6.1"
"typescript": "^2.9.0-dev.20180509"
},
"files_comment__whitelist_npm_publish": "http://stackoverflow.com/a/8617868/1123955",
"files": [
......@@ -176,9 +178,13 @@
"dist/src",
"src"
],
"git": {
"scripts": {
"pre-push": "./scripts/pre-push.sh"
}
},
"publishConfig": {
"access": "public",
"tagBak": "next",
"tag": "latest"
}
}
#!/usr/bin/env bash
set -e
npm version
if ./scripts/development-release.ts; then
echo "Current release is a development release, please only update the docs when there's a stable release."
exit 1
else
echo "Generating docs ..."
npm run dist
echo '# Wechaty v'$(jq -r .version package.json)' Documentation\n* https://blog.chatie.io\n' > docs/index.md
jsdoc2md dist/src/{wechaty,room,contact,friend-request,message}.js dist/src/puppet-puppeteer/schema.js >> docs/index.md
fi
#!/usr/bin/env bash
set -e
npm run dist
npm pack
TMPDIR="/tmp/npm-pack-testing.$$"
mkdir "$TMPDIR"
mv *-*.*.*.tgz "$TMPDIR"
cp tests/fixtures/smoke-testing.ts "$TMPDIR"
cd $TMPDIR
npm init -y
npm install *-*.*.*.tgz \
@types/node \
rxjs \
brolog \
typescript@latest
./node_modules/.bin/tsc \
--lib esnext,dom \
--noEmitOnError \
--noImplicitAny \
--skipLibCheck \
smoke-testing.ts
node smoke-testing.js
# (for i in {1..3}; do node smoke-testing.js && break || sleep 1; done)
......@@ -50,7 +50,7 @@ _STR_
}
# must run this after the above `test` ([ -z ...]),
# or will whow a error: error: failed to push some refs to 'git@github.com:wechaty/wechaty.git'
# or will whow a error: error: failed to push some refs to 'git@github.com:Chatie/wechaty.git'
echo "PRE-PUSH HOOK PASSED"
echo
......@@ -2,7 +2,9 @@
import * as readline from 'readline'
const contributeMap = {}
const contributeMap: {
[contributor: string]: string[],
} = {}
function parseLine(line: string): string[] | null {
// [\#264](https://github.com/Chatie/wechaty/pull/264) ([lijiarui](https://github.com/lijiarui))
......
......@@ -45,7 +45,7 @@ class LicenseTransformer extends Transform {
super(options)
}
public _transform(chunk, encoding, done) {
public _transform(chunk: any, _: string /* encoding: string */, done: Function) {
if (this.updated) {
this.push(chunk)
} else {
......@@ -56,7 +56,7 @@ class LicenseTransformer extends Transform {
done()
}
private updateChunk(chunk): string {
private updateChunk(chunk: any): string {
const buffer = this.lineBuf + chunk.toString()
this.lineBuf = ''
......@@ -106,7 +106,7 @@ class LicenseTransformer extends Transform {
return updatedLineList.join('\n')
}
public _flush(done) {
public _flush(done: Function) {
if (this.lineBuf) {
this.push(this.lineBuf)
this.lineBuf = ''
......@@ -127,13 +127,14 @@ async function updateLicense(file: string): Promise<void> {
.pipe(tranStream)
.pipe(writeStream)
.on('close', resolve)
.on('error', reject)
})
await promisify(unlinkCallback)(file)
await promisify(linkCallback)(tmpFile, file)
await promisify(unlinkCallback)(tmpFile)
}
async function glob(pattern): Promise<string[]> {
async function glob(pattern: string): Promise<string[]> {
return promisify<string, string[]>(globCallback as any)(pattern)
}
......
......@@ -10,6 +10,8 @@ python:
branches:
only:
- master
- v0.14
- v0.12
env:
global:
......
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// tslint:disable:no-shadowed-variable
import * as test from 'blue-tape'
// import * as sinon from 'sinon'
import cloneClass from './clone-class'
class FixtureClass {
public static staticNumber: number
public static staticMethod(n: number) {
this.staticNumber = n
}
constructor(
public i: number,
public j: number,
) {
//
}
public sum() {
return this.i + this.j + (this.constructor as any).staticNumber
}
}
const EXPECTED_NUMBER1 = 1
const EXPECTED_NUMBER2 = 2
test('cloneClass smoke testing', async t => {
// tslint:disable-next-line:variable-name
const NewClass1 = cloneClass(FixtureClass)
// tslint:disable-next-line:variable-name
const NewClass2 = cloneClass(FixtureClass)
t.notEqual(NewClass1, NewClass2, 'NewClass1 should different with NewClass2')
t.notEqual(NewClass1, FixtureClass, 'NewClass1 should different with FixtureClass')
NewClass1.staticMethod(EXPECTED_NUMBER1)
t.equal(NewClass1.staticNumber, EXPECTED_NUMBER1, 'should set static number to EXPECTED_NUMBER1')
NewClass2.staticMethod(EXPECTED_NUMBER2)
t.equal(NewClass2.staticNumber, EXPECTED_NUMBER2, 'should set static number to EXPECTED_NUMBER2')
const nc1 = new NewClass1(EXPECTED_NUMBER1, EXPECTED_NUMBER2)
const nc2 = new NewClass2(EXPECTED_NUMBER1, EXPECTED_NUMBER2)
t.ok(nc1 instanceof FixtureClass, 'nc1 should instanceof FixtureClass')
t.ok(nc1 instanceof NewClass1, 'nc1 should instanceof NewClass1')
t.equal(nc1.sum(), EXPECTED_NUMBER1 + EXPECTED_NUMBER1 + EXPECTED_NUMBER2, 'should sum right for 1 + 1 + 2')
t.equal(nc2.sum(), EXPECTED_NUMBER2 + EXPECTED_NUMBER1 + EXPECTED_NUMBER2, 'should sum right for 2 + 1 + 2')
})
/**
* Clone Class for easy savig Information into Static Properties
* https://github.com/Chatie/wechaty/issues/518
*/
// https://github.com/Microsoft/TypeScript/issues/10262
// https://github.com/Microsoft/TypeScript/pull/13743
export type Constructor<T> = new(...args: any[]) => T
// tslint:disable-next-line:variable-name
export function cloneClass<T extends Constructor<{}>>(OrignalClass: T): T {
class NewClass extends OrignalClass {
constructor(...args: any[]) {
super(...arguments)
}
}
return NewClass as any as T
}
export default cloneClass
......@@ -24,7 +24,9 @@ import * as readPkgUp from 'read-pkg-up'
import * as Raven from 'raven'
import { log } from 'brolog'
// import Puppet from './puppet'
import {
PuppetName,
} from './puppet-config'
const pkg = readPkgUp.sync({ cwd: __dirname }).pkg
export const VERSION = pkg.version
......@@ -84,15 +86,6 @@ if (log.level() === 'verbose' || log.level() === 'silly') {
})
}
export type PuppetName = 'android-pad'
| 'android-phone'
| 'cat-king'
| 'hostie'
| 'ios-app-phone'
| 'ios-app-pad'
| 'web'
| 'win32'
export interface DefaultSetting {
DEFAULT_HEAD : number,
DEFAULT_PORT : number,
......@@ -197,24 +190,9 @@ export class Config {
}
export interface Sayable {
say(content: string, replyTo?: any|any[]): Promise<boolean>
say(text: string, replyTo?: any|any[]): Promise<void>
}
export type WechatEvent = 'friend'
| 'login'
| 'logout'
| 'message'
| 'room-join'
| 'room-leave'
| 'room-topic'
| 'scan'
export type WechatyEvent = WechatEvent
| 'error'
| 'heartbeat'
| 'start'
| 'stop'
export {
log,
Raven,
......
#!/usr/bin/env ts-node
import * as test from 'blue-tape'
// import * as sinon from 'sinon'
import { cloneClass } from 'clone-class'
import { Contact as GlobalContact } from './contact'
// tslint:disable-next-line:variable-name
const Contact = cloneClass(GlobalContact)
test('Should not be able to instanciate directly', async t => {
// tslint:disable-next-line:variable-name
const MyContact = cloneClass(Contact)
t.throws(() => {
const c = MyContact.load('xxx')
t.fail(c.name())
}, 'should throw when `Contact.load()`')
t.throws(() => {
const c = MyContact.load('xxx')
t.fail(c.name())
}, 'should throw when `Contact.load()`')
})
test('Should not be able to instanciate through cloneClass without puppet', async t => {
// tslint:disable-next-line:variable-name
const MyContact = cloneClass(Contact)
t.throws(() => {
const c = MyContact.load('xxx')
t.fail(c.name())
}, 'should throw when `MyContact.load()` without puppet')
t.throws(() => {
const c = MyContact.load('xxx')
t.fail(c.name())
}, 'should throw when `MyContact.load()` without puppet')
})
test('should be able to instanciate through cloneClass with puppet', async t => {
// tslint:disable-next-line:variable-name
const MyContact = cloneClass(Contact)
MyContact.puppet = {} as any
t.doesNotThrow(() => {
const c = MyContact.load('xxx')
t.ok(c, 'should get contact instance from `MyContact.load()')
}, 'should not throw when `MyContact().load`')
t.doesNotThrow(() => {
const c = MyContact.load('xxx')
t.ok(c, 'should get contact instance from `MyContact.load()`')
}, 'should not throw when `MyContact.load()`')
})
test('should throw when instanciate the global class', async t => {
t.throws(() => {
const c = GlobalContact.load('xxx')
t.fail('should not run to here')
t.fail(c.toString())
}, 'should throw when we instanciate a global class')
})
此差异已折叠。
export class MediaMessage {
constructor(..._: any[]) {
const msg = '`MediaMessage` is deprecated. Please use `Message` instead. See: https://github.com/Chatie/wechaty/issues/1164'
throw new Error(msg)
}
}
......@@ -69,7 +69,7 @@ export class Doctor {
client.write('ding')
})
client.on('data', function(data) {
client.on('data', function() {
/**
* Promise Resolve
*/
......@@ -82,7 +82,7 @@ export class Doctor {
*/
client.on('error', reject)
client.on('close', err => server.close())
client.on('close', _ => server.close())
})
})
}
......
......@@ -18,12 +18,32 @@
*
*/
/* tslint:disable:no-var-requires */
const retryPromise = require('retry-promise').default
import { instanceToClass } from 'clone-class'
import { PuppetAccessory } from './puppet-accessory'
import { Contact } from './contact'
import {
// config,
log,
} from './config'
import Contact from './contact'
import PuppetAccessory from './puppet-accessory'
export enum FriendRequestType {
Unknown = 0,
Send,
Receive,
Confirm,
}
export interface FriendRequestPayload {
contact? : Contact,
hello? : string,
ticket? : string
type? : FriendRequestType,
}
/**
* Send, receive friend request, and friend confirmation events.
......@@ -34,23 +54,173 @@ import PuppetAccessory from './puppet-accessory'
*
* [Examples/Friend-Bot]{@link https://github.com/Chatie/wechaty/blob/master/examples/friend-bot.ts}
*/
export abstract class FriendRequest extends PuppetAccessory {
export class FriendRequest extends PuppetAccessory {
// tslint:disable-next-line:variable-name
public static Type = FriendRequestType
public contact: Contact
public hello: string
public type: 'send' | 'receive' | 'confirm'
public static createSend(
contact : Contact,
hello : string,
): FriendRequest {
log.verbose('PuppeteerFriendRequest', 'createSend(%s, %s)',
contact,
hello,
)
constructor() {
const sentRequest = new this({
type: FriendRequestType.Send,
contact,
hello,
})
return sentRequest
}
public static createConfirm(
contact: Contact,
): FriendRequest {
log.verbose('PuppeteerFriendRequest', 'createConfirm(%s)',
contact,
)
const confirmedRequest = new this({
type: FriendRequestType.Confirm,
contact,
})
return confirmedRequest
}
public static createReceive(
contact : Contact,
hello : string,
ticket : string,
): FriendRequest {
log.verbose('PuppeteerFriendRequest', 'createReceive(%s, %s, %s)',
contact,
hello,
ticket,
)
const receivedRequest = new this({
type: FriendRequestType.Receive,
contact,
hello,
ticket,
})
return receivedRequest
}
/**
*
* Instance Properties
*
*/
constructor(
protected payload?: FriendRequestPayload,
) {
super()
log.verbose('FriendRequest', 'constructor()')
log.verbose('PuppeteerFriendRequest', 'constructor(%s)', payload)
// tslint:disable-next-line:variable-name
const MyClass = instanceToClass(this, FriendRequest)
// if (!config.puppetInstance()) {
// throw new Error('no Config.puppetInstance() instanciated')
// }
if (MyClass === FriendRequest) {
throw new Error('FriendRequest class can not be instanciated directly! See: https://github.com/Chatie/wechaty/issues/1217')
}
if (!this.puppet) {
throw new Error('FriendRequest class can not be instanciated without a puppet!')
}
}
public async send(): Promise<void> {
if (!this.payload) {
throw new Error('no payload')
} else if (!this.payload.contact) {
throw new Error('no contact')
} else if (this.payload.type !== FriendRequest.Type.Send) {
throw new Error('not a send request')
}
log.verbose('PuppeteerFriendRequest', 'send() to %s', this.payload.contact)
await this.puppet.friendRequestSend(
this.payload.contact,
this.payload.hello,
)
}
public abstract send(contact: Contact, hello: string): Promise<boolean>
public abstract accept(): Promise<boolean>
public async accept(): Promise<void> {
if (!this.payload) {
throw new Error('no payload')
} else if (!this.payload.contact) {
throw new Error('no contact')
} else if (!this.payload.ticket) {
throw new Error('no ticket')
} else if (this.payload.type !== FriendRequest.Type.Receive) {
throw new Error('not a receive request, its a ' + FriendRequest.Type[this.payload.type!])
}
log.verbose('FriendRequest', 'accept() to %s', this.payload.contact)
await this.puppet.friendRequestAccept(this.payload.contact, this.payload.ticket)
const max = 20
const backoff = 300
const timeout = max * (backoff * max) / 2
// 20 / 300 => 63,000
// max = (2*totalTime/backoff) ^ (1/2)
// timeout = 11,250 for {max: 15, backoff: 100}
// refresh to wait contact ready
await retryPromise({ max: max, backoff: backoff }, async (attempt: number) => {
log.silly('PuppeteerFriendRequest', 'accept() retryPromise() attempt %d with timeout %d', attempt, timeout)
await this.contact().ready()
if (this.contact().isReady()) {
log.verbose('PuppeteerFriendRequest', 'accept() with contact %s ready()', this.contact().name())
return
}
throw new Error('FriendRequest.accept() content.ready() not ready')
}).catch((e: Error) => {
log.warn('PuppeteerFriendRequest', 'accept() rejected for contact %s because %s', this.contact, e && e.message || e)
})
}
public hello(): string {
if (!this.payload) {
throw new Error('no payload')
}
return this.payload.hello || ''
}
public contact(): Contact {
if (!this.payload) {
throw new Error('no payload')
} else if (!this.payload.contact) {
throw new Error('no contact')
}
return this.payload.contact
}
public async reject(): Promise<void> {
log.warn('PuppeteerFriendRequest', 'reject() not necessary, NOP.')
return
}
public type(): FriendRequestType {
if (!this.payload) {
throw new Error('no payload')
} else if (!this.payload.type) {
throw new Error('no type')
}
return this.payload.type
}
}
......
export {
config,
log,
VERSION,
} from './config'
/**
* We need to put `Wechaty` at the beginning of this file for import
* because we have circluar dependencies between `Puppet` & `Wechaty`
*/
import {
Wechaty,
} from './wechaty'
export {
Contact,
ContactPayload,
ContactQueryFilter,
ContactType,
Gender,
} from './contact'
export {
FriendRequest,
FriendRequestPayload,
FriendRequestType,
} from './friend-request'
export {
Message,
} from './message'
export {
Room,
RoomMemberQueryFilter,
RoomPayload,
RoomQueryFilter,
} from './room'
export {
MediaMessage,
} from './deprecated'
export { IoClient } from './io-client'
export { Misc } from './misc'
export { Profile } from './profile'
export {
PuppetPuppeteer,
} from './puppet-puppeteer/'
export {
Wechaty,
}
export default Wechaty
......@@ -16,7 +16,7 @@
* limitations under the License.
*
*/
import * as express from 'express'
/**
* DO NOT use `require('../')` here!
* because it will casue a LOOP require ERROR
......@@ -24,13 +24,14 @@
// import Brolog from 'brolog'
import StateSwitch from 'state-switch'
import { Message } from './message'
import {
config,
log,
} from './config'
import { Io } from './io'
import { Wechaty } from './wechaty'
export interface IoClientOptions {
token : string,
wechaty? : Wechaty,
......@@ -120,9 +121,9 @@ export class IoClient {
public initWeb(port = config.httpPort) {
// if (process.env.DYNO) {
// }
const app = require('express')()
const app = express()
app.get('/', function (req, res) {
app.get('/', function (_ /* req */, res) {
res.send('Wechaty IO Bot Alive!')
})
......@@ -136,7 +137,7 @@ export class IoClient {
})
}
private onMessage(m) {
private onMessage(m: Message) {
// const from = m.from()
// const to = m.to()
// const content = m.toString()
......@@ -148,7 +149,7 @@ export class IoClient {
// , m.toStringDigest()
// )
if (/^wechaty|chatie|botie/i.test(m.content()) && !m.self()) {
if (/^wechaty|chatie|botie/i.test(m.text()) && !m.self()) {
m.say('https://www.chatie.io')
.then(_ => log.info('Bot', 'REPLIED to magic word "chatie"'))
}
......
......@@ -19,13 +19,21 @@
import * as WebSocket from 'ws'
import StateSwitch from 'state-switch'
import PuppetWeb from './puppet-web/'
import {
Message,
} from './message'
import {
ScanData,
} from './puppet/'
import {
config,
log,
} from './config'
import Wechaty from './wechaty'
import {
Wechaty,
} from './wechaty'
export interface IoOptions {
wechaty: Wechaty,
......@@ -34,18 +42,22 @@ export interface IoOptions {
protocol?: string,
}
type IoEventName = 'botie'
| 'error'
| 'heartbeat'
| 'login'
| 'logout'
| 'message'
| 'update'
| 'raw'
| 'reset'
| 'scan'
| 'sys'
| 'shutdown'
export const IO_EVENT_DICT = {
botie: 'tbw',
error: 'tbw',
heartbeat: 'tbw',
login: 'tbw',
logout: 'tbw',
message: 'tbw',
update: 'tbw',
raw: 'tbw',
reset: 'tbw',
scan: 'tbw',
sys: 'tbw',
shutdown: 'tbw',
}
type IoEventName = keyof typeof IO_EVENT_DICT
interface IoEvent {
name: IoEventName,
......@@ -53,19 +65,20 @@ interface IoEvent {
}
export class Io {
public cuid: string
private protocol : string
private readonly cuid : string
private readonly protocol : string
private eventBuffer : IoEvent[] = []
private ws : WebSocket
private state = new StateSwitch('Io', log)
private readonly state = new StateSwitch('Io', log)
private reconnectTimer: NodeJS.Timer | null
private reconnectTimeout: number | null
private reconnectTimer? : NodeJS.Timer
private reconnectTimeout? : number
private onMessage: Function
private scanData: ScanData
constructor(
private options: IoOptions,
) {
......@@ -74,6 +87,8 @@ export class Io {
this.cuid = options.wechaty.cuid
this.scanData = {} as any
this.protocol = options.protocol + '|' + options.wechaty.cuid
log.verbose('Io', 'instantiated with apihost[%s], token[%s], protocol[%s], cuid[%s]',
options.apihost,
......@@ -99,7 +114,10 @@ export class Io {
try {
await this.initEventHook()
this.ws = this.initWebSocket()
this.options.wechaty.on('scan', (url, code) => {
this.scanData.url = url
this.scanData.code = code
})
this.state.on(true)
return
......@@ -215,7 +233,7 @@ export class Io {
// FIXME: how to keep alive???
// ws._socket.setKeepAlive(true, 100)
this.reconnectTimeout = null
this.reconnectTimeout = undefined
const name = 'sys'
const payload = 'Wechaty version ' + this.options.wechaty.version() + ` with CUID: ${this.cuid}`
......@@ -280,27 +298,27 @@ export class Io {
break
case 'update':
log.verbose('Io', 'on(report): %s', ioEvent.payload)
const user = this.options.wechaty.puppet ? this.options.wechaty.puppet.user : null
log.verbose('Io', 'on(update): %s', ioEvent.payload)
const user = this.options.wechaty.puppet.userSelf()
if (user) {
const loginEvent: IoEvent = {
name : 'login',
payload : user.obj,
payload : {
id: user.id,
name: user.name(),
},
}
this.send(loginEvent)
}
const puppet = this.options.wechaty.puppet
if (puppet instanceof PuppetWeb) {
const scanInfo = puppet.scanInfo
if (scanInfo) {
const scanEvent: IoEvent = {
name: 'scan',
payload: scanInfo,
}
this.send(scanEvent)
if (this.scanData) {
const scanEvent: IoEvent = {
name: 'scan',
payload: this.scanData,
}
this.send(scanEvent)
}
break
......@@ -368,7 +386,7 @@ export class Io {
log.warn('Io', 'reconnect() will reconnect after %d s', Math.floor(this.reconnectTimeout / 1000))
this.reconnectTimer = setTimeout(_ => {
this.reconnectTimer = null
this.reconnectTimer = undefined
this.initWebSocket()
}, this.reconnectTimeout)// as any as NodeJS.Timer
}
......@@ -415,7 +433,7 @@ export class Io {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
this.reconnectTimer = undefined
}
this.ws.close()
......@@ -429,7 +447,7 @@ export class Io {
* Prepare to be overwriten by server setting
*
*/
private async ioMessage(m): Promise<void> {
private async ioMessage(m: Message): Promise<void> {
log.silly('Io', 'ioMessage() is a nop function before be overwriten from cloud')
if (typeof this.onMessage === 'function') {
await this.onMessage(m)
......
此差异已折叠。
此差异已折叠。
import {
FileBox,
} from 'file-box'
import {
Contact,
} from './contact'
import {
Room,
} from './room'
export enum MessageDirection {
MO,
MT,
}
export enum MessageType {
Unknown = 0,
Attachment,
Audio,
Image,
Text,
Video,
}
/**
*
* MessageMOOptions
*
*
*/
export interface MessageMOOptionsText {
text : string,
}
export interface MessageMOOptionsFile {
file : FileBox,
}
export interface MessageMOOptionsRoom {
room : Room,
to? : null | Contact,
}
export interface MessageMOOptionsTo {
room? : null | Room,
to : Contact,
}
export type MessageMOOptions = (
MessageMOOptionsText
| MessageMOOptionsFile
) & (
| MessageMOOptionsRoom
| MessageMOOptionsTo
)
/**
*
* MessagePayload
*
*/
export interface MessagePayload {
type : MessageType,
text? : string,
file? : FileBox,
direction : MessageDirection,
from? : Contact,
date : Date,
to? : null | Contact, // if to is not set, then room must be set
room? : null | Room,
}
......@@ -22,6 +22,7 @@ import * as test from 'blue-tape'
// import * as sinon from 'sinon'
// const sinonTest = require('sinon-test')(sinon)
import * as http from 'http'
import * as express from 'express'
import Misc from './misc'
......@@ -121,9 +122,9 @@ test('downloadStream() for media', async t => {
res.end('dong')
})
const server = require('http').createServer(app)
const server = http.createServer(app)
server.on('clientError', (err, socket) => {
t.fail('server on clientError')
t.fail('server on clientError' + err)
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(65534)
......
......@@ -19,13 +19,13 @@
import * as crypto from 'crypto'
import * as https from 'https'
import * as http from 'http'
import * as net from 'net'
import {
Readable,
} from 'stream'
import * as url from 'url'
import { log } from './config'
import { MsgType } from './message'
export class Misc {
public static stripHtml(html?: string): string {
......@@ -112,22 +112,22 @@ export class Misc {
// const myurl = 'http://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmsgimg?&MsgID=3080011908135131569&skey=%40crypt_c117402d_53a58f8fbb21978167a3fc7d3be7f8c9'
href = href.replace(/^https/i, 'http') // use http instead of https, because https will only success on the very first request!
const u = url.parse(href)
const protocol = u.protocol as 'https:'|'http:'
const parsedUrl = url.parse(href)
const protocol = parsedUrl.protocol as 'https:'|'http:'
let options
let options: http.RequestOptions | https.RequestOptions
// let request
let get
let get: typeof https.get
if (protocol === 'https:') {
// request = https.request.bind(https)
get = https.get
options = u as any as https.RequestOptions
options = parsedUrl as any as https.RequestOptions
options.agent = https.globalAgent
} else if (protocol === 'http:') {
// request = http.request.bind(http)
get = http.get
options = u as any as http.RequestOptions
options = parsedUrl as any as http.RequestOptions
options.agent = http.globalAgent
} else {
throw new Error('protocol unknown: ' + protocol)
......@@ -199,16 +199,16 @@ export class Misc {
log.silly('UtilLib', 'getPort(%d)', port)
let tryPort = nextPort(port || 38788)
return new Promise((resolve, reject) => {
return new Promise(resolve => {
// https://gist.github.com/mikeal/1840641
function _getPort(cb) {
const server = require('net').createServer()
function _getPort(cb: (port: number) => void) {
const server = net.createServer()
server.on('error', function(err) {
if (err) {/* fail safe */ }
tryPort = nextPort(port)
_getPort(cb)
})
server.listen(tryPort, function(err) {
server.listen(tryPort, function(err: any) {
if (err) {/* fail safe */}
server.once('close', function() {
cb(tryPort)
......@@ -249,22 +249,6 @@ export class Misc {
return md5sum.digest('hex')
}
public static msgType(ext: string): MsgType {
switch (ext) {
case 'bmp':
case 'jpeg':
case 'jpg':
case 'png':
return MsgType.IMAGE
case 'gif':
return MsgType.EMOTICON
case 'mp4':
return MsgType.VIDEO
default:
return MsgType.APP
}
}
// public static mime(ext): string {
// switch (ext) {
// case 'pdf':
......
......@@ -24,15 +24,15 @@ import {
log,
} from './config'
export type ProfileSection = 'cookies' | 'ToBeAdded'
export interface ProfileSchema {
cookies?: any[]
cookies?: any[]
}
export type ProfileSection = keyof ProfileSchema
export class Profile {
private obj : ProfileSchema
private file : string | null
private obj : ProfileSchema
private file? : string
constructor(
public name = config.profile,
......@@ -40,7 +40,7 @@ export class Profile {
log.verbose('Profile', 'constructor(%s)', name)
if (!name) {
this.file = null
this.file = undefined
} else {
this.file = path.isAbsolute(name)
? name
......@@ -58,7 +58,7 @@ export class Profile {
return `Profile<${this.name}>`
}
public load(): void {
public async load(): Promise<void> {
log.verbose('Profile', 'load() file: %s', this.file)
this.obj = {}
......@@ -80,7 +80,7 @@ export class Profile {
}
}
public save(): void {
public async save(): Promise<void> {
log.verbose('Profile', 'save() file: %s', this.file)
if (!this.file) {
log.verbose('Profile', 'save() no file, NOOP')
......@@ -100,15 +100,15 @@ export class Profile {
}
}
public get(section: ProfileSection): null | any {
public async get<T = any>(section: ProfileSection): Promise<null | T> {
log.verbose('Profile', 'get(%s)', section)
if (!this.obj) {
return null
}
return this.obj[section]
return this.obj[section] as any as T
}
public set(section: ProfileSection, data: any): void {
public async set(section: ProfileSection, data: any): Promise<void> {
log.verbose('Profile', 'set(%s, %s)', section, data)
if (!this.obj) {
this.obj = {}
......@@ -116,12 +116,12 @@ export class Profile {
this.obj[section] = data
}
public destroy(): void {
public async destroy(): Promise<void> {
log.verbose('Profile', 'destroy() file: %s', this.file)
this.obj = {}
if (this.file && fs.existsSync(this.file)) {
fs.unlinkSync(this.file)
this.file = null
this.file = undefined
}
}
}
......
......@@ -21,14 +21,21 @@
import * as test from 'blue-tape'
// import * as sinon from 'sinon'
import Puppet from './puppet'
import {
cloneClass,
} from 'clone-class'
import PuppetAccessory from './puppet-accessory'
import { Puppet } from './puppet/'
const EXPECTED_PUPPET1 = {p: 1} as any as Puppet
const EXPECTED_PUPPET2 = {p: 2} as any as Puppet
test('PuppetAccessory smoke testing', async t => {
class FixtureClass extends PuppetAccessory {}
t.throws(() => FixtureClass.puppet, 'should throw if read static puppet before initialize')
const c = new FixtureClass()
......@@ -42,3 +49,22 @@ test('PuppetAccessory smoke testing', async t => {
t.equal(FixtureClass.puppet, EXPECTED_PUPPET1, 'should get EXPECTED_PUPPET1 from static puppet after set instance puppet to EXPECTED_PUPPET2')
t.equal(c.puppet, EXPECTED_PUPPET2, 'should get EXPECTED_PUPPET2 from instance puppet after set instance puppet to EXPECTED_PUPPET2')
})
test('Two clone-ed classes', async t => {
class FixtureClass extends PuppetAccessory {}
// tslint:disable-next-line:variable-name
const ClonedClass1 = cloneClass(FixtureClass)
// tslint:disable-next-line:variable-name
const ClonedClass2 = cloneClass(FixtureClass)
ClonedClass1.puppet = EXPECTED_PUPPET1
ClonedClass2.puppet = EXPECTED_PUPPET2
const c1 = new ClonedClass1()
const c2 = new ClonedClass2()
t.equal(c1.puppet, EXPECTED_PUPPET1, 'should get the puppet as 1 from 1st cloned class')
t.equal(c2.puppet, EXPECTED_PUPPET2, 'should get the puppet as 2 from 2nd cloned class')
})
import { EventEmitter } from 'events'
import { EventEmitter } from 'events'
import { log } from './config'
import { Puppet } from './puppet'
import { instanceToClass } from 'clone-class'
import { log } from './config'
import { Puppet } from './puppet/'
// use Symbol to prevent conflicting with the child class properties
// This symbol must be exported (for now).
// See: https://github.com/Microsoft/TypeScript/issues/20080
export const SYMBOL_NAME = Symbol('name')
export const SYMBOL_COUNTER = Symbol('counter')
let COUNTER = 0
export abstract class PuppetAccessory extends EventEmitter {
// Not work???
// private static readonly PUPPET_ACCESSORY_NAME = Symbol('name')
private [SYMBOL_NAME] : string
private [SYMBOL_COUNTER] : number
/**
* 1. Static puppet property
*
* 1. Static Properties & Methods
*
*/
private static _puppet?: Puppet
public static set puppet(puppet: Puppet) {
log.silly('PuppetAssessory', 'static set puppet()')
log.silly('PuppetAccessory', '<%s> static set puppet(%s)',
this.name,
puppet,
)
this._puppet = puppet
}
public static get puppet(): Puppet {
log.silly('PuppetAssessory', 'static get puppet()')
log.silly('PuppetAccessory', '<%s> static get puppet()',
this.name,
)
if (this._puppet) {
log.silly('PuppetAssessory', 'static get puppet() from instance properties')
return this._puppet
}
throw new Error('static puppet not found')
throw new Error('static puppet not found for '
+ this.name,
)
}
/**
* 2. Instance puppet property
*
* 2. Instance Properties & Methods
*
*/
private _puppet?: Puppet
public set puppet(puppet: Puppet) {
log.silly('PuppetAssessory', 'set puppet()')
log.silly('PuppetAccessory', '<%s> set puppet(%s)',
this[SYMBOL_NAME] || this,
puppet,
)
this._puppet = puppet
}
public get puppet(): Puppet {
log.silly('PuppetAssessory', 'get puppet()')
log.silly('PuppetAccessory', '#%d<%s> get puppet()',
this[SYMBOL_COUNTER],
this[SYMBOL_NAME] || this,
)
if (this._puppet) {
log.silly('PuppetAssessory', 'get puppet() from instance properties')
return this._puppet
}
return (this.constructor as any).puppet
/**
* Get `puppet` from Class Static puppet property
* note: use `instanceToClass` at here is because
* we might have many copy/child of `PuppetAccessory` Classes
*/
return instanceToClass(this, PuppetAccessory).puppet
}
constructor(
name?: string,
) {
super()
this[SYMBOL_NAME] = name || this.toString()
this[SYMBOL_COUNTER] = COUNTER++
log.silly('PuppetAccessory', '#%d<%s> constructor(%s)',
this[SYMBOL_COUNTER],
this[SYMBOL_NAME],
name || '',
)
}
}
......
import PuppetPuppeteer from './puppet-puppeteer/'
import PuppetMock from './puppet-mock/'
// import PuppetWechat4u from './puppet-wechat4u/'
/**
* Wechaty Official Puppet Plugins List
*/
export const PUPPET_DICT = {
mock: PuppetMock,
puppeteer: PuppetPuppeteer,
// wechat4u: PuppetWechat4u,
}
export type PuppetName = keyof typeof PUPPET_DICT
// 'android-pad'
// | 'android-phone'
// | 'cat-king'
// | 'hostie'
// | 'ios-app-phone'
// | 'ios-app-pad'
// | 'mock'
// | 'web'
// | 'win32'
# PUPPET-MOCK
```ts
import PuppetMock from 'wechaty-puppet-mock'
const puppet = new PuppetMock({
profile,
})
const wechaty = new Wechaty({
puppet,
})
```
## HELPER UTILITIES
### StateSwitch
```ts
this.state.on('pending')
this.state.on(true)
this.state.off('pending')
this.state.off(true)
await this.state.ready('on')
await this.state.ready('off')
```
### Watchdog
```ts
```
### Profile
```ts
this.profile.set('config', { id: 1, key: 'xxx' })
const config = await this.profile.get('config')
```
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册