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

Merge pull request #1159 from Chatie/new_puppet

Multi-Instance Support #518 
......@@ -18,32 +18,29 @@ cache:
directories:
- node_modules
before_install:
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then brew update; fi
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then brew cleanup; fi
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then brew cask cleanup; fi
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then brew install jq; fi
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then brew install moreutils; fi
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then brew install shellcheck; fi
script:
- echo $TRAVIS_OS_NAME
- node --version
- npm --version
- npm test
after_success:
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then npm run coverage; fi
stages:
- test
- pack
- name: deploy
if: branch =~ ^(master|v\d+\.\d+)
if: branch =~ ^(master|v\d+\.\d+)$
AND (type NOT IN (cron, pull_request))
before_install:
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then ./scripts/prepare-osx.sh; fi
jobs:
include:
- stage: test
script:
- echo $TRAVIS_OS_NAME
- node --version
- npm --version
- echo "Testing started ..."
- npm test || travis_terminate 1
after_success:
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then npm run coverage; fi
- stage: pack
script:
- npm run test:pack && echo 'Npm pack testing is passed'
......@@ -52,6 +49,7 @@ jobs:
script:
- echo "Deploying to NPM ..."
- npm version
- if [ "$TRAVIS_BRANCH" = "master" -a ./scripts/development-release.ts ]; then ./scripts/package-public-config-tag-next.ts; fi
- npm run dist
deploy:
......@@ -59,6 +57,8 @@ jobs:
email: zixia@zixia.net
api_key: "$NPM_TOKEN"
skip_cleanup: true
on:
all_branches: true
notifications:
webhooks:
......
......@@ -20,5 +20,4 @@
import Wechaty from '../src/wechaty'
const w = Wechaty.instance()
console.log(w.version())
console.log(Wechaty.version())
......@@ -1079,7 +1079,7 @@ Initialize the bot, return Promise.
**Kind**: instance method of [<code>Wechaty</code>](#Wechaty)
**Example**
```js
await bot.init()
await bot.start()
// do other stuff with bot here
```
<a name="Wechaty+start"></a>
......@@ -1175,7 +1175,7 @@ Quit the bot
**Kind**: instance method of [<code>Wechaty</code>](#Wechaty)
**Example**
```js
await bot.quit()
await bot.stop()
```
<a name="Wechaty+stop"></a>
......
......@@ -95,10 +95,10 @@ bot
// .catch(e => log.error('Bot', 'on message rejected: %s' , e))
})
bot.init()
bot.start()
.catch(e => {
log.error('Bot', 'init() fail:' + e)
bot.quit()
bot.stop()
process.exit(-1)
})
......
# BLESSED TWINS BOT
![blessed twins bot](https://chatie.github.io/wechaty/images/blessed-twins-bot.png)
<script src="https://asciinema.org/a/177857.js" id="asciicast-177857" async></script>
/**
* Wechaty Twins Bot + Blessed Contrib Demo
* Credit: https://github.com/yaronn/blessed-contrib/blob/06a05f107a3b54c91b5200a041ad8c15e6489de9/examples/dashboard.js
*/
import * as blessed from 'blessed'
import * as contrib from 'blessed-contrib'
import * as qrcode from 'qrcode-terminal'
import {
Wechaty,
} from '../../index'
const screen = blessed.screen({
smartCSR: true,
fullUnicode: true, // https://github.com/chjj/blessed/issues/226#issuecomment-188777457
})
// create layout and widgets
const grid = new contrib.grid({rows: 12, cols: 12, screen: screen})
/**
* Donut Options
* self.options.radius = options.radius || 14; // how wide is it? over 5 is best
* self.options.arcWidth = options.arcWidth || 4; //width of the donut
* self.options.yPadding = options.yPadding || 2; //padding from the top
*/
const donut = grid.set(8, 8, 4, 2, contrib.donut,
{
label: 'Percent Donut',
radius: 16,
arcWidth: 4,
yPadding: 2,
data: [{label: 'Storage', percent: 87}],
})
// const latencyLine = grid.set(8, 8, 4, 2, contrib.line,
// { style:
// { line: "yellow"
// , text: "green"
// , baseline: "black"}
// , xLabelPadding: 3
// , xPadding: 5
// , label: 'Network Latency (sec)'})
const gauge = grid.set(8, 10, 2, 2, contrib.gauge, {label: 'Storage', percent: [80, 20]})
const gaugeTwo = grid.set(2, 9, 2, 3, contrib.gauge, {label: 'Deployment Progress', percent: 80})
const sparkline = grid.set(10, 10, 2, 2, contrib.sparkline,
{ label: 'Throughput (bits/sec)'
, tags: true
, style: { fg: 'blue', titleFg: 'white' }})
const bar = grid.set(4, 6, 4, 3, contrib.bar,
{ label: 'Server Utilization (%)'
, barWidth: 4
, barSpacing: 6
, xOffset: 2
, maxHeight: 9})
const table = grid.set(4, 9, 4, 3, contrib.table,
{ keys: true
, fg: 'green'
, label: 'Active Processes'
, columnSpacing: 1
, columnWidth: [24, 10, 10]})
/*
*
* LCD Options
//these options need to be modified epending on the resulting positioning/size
options.segmentWidth = options.segmentWidth || 0.06; // how wide are the segments in % so 50% = 0.5
options.segmentInterval = options.segmentInterval || 0.11; // spacing between the segments in % so 50% = 0.5
options.strokeWidth = options.strokeWidth || 0.11; // spacing between the segments in % so 50% = 0.5
//default display settings
options.elements = options.elements || 3; // how many elements in the display. or how many characters can be displayed.
options.display = options.display || 321; // what should be displayed before anything is set
options.elementSpacing = options.spacing || 4; // spacing between each element
options.elementPadding = options.padding || 2; // how far away from the edges to put the elements
//coloring
options.color = options.color || "white";
*/
const lcdLineOne = grid.set(0, 9, 2, 3, contrib.lcd,
{
label: 'LCD Test',
segmentWidth: 0.06,
segmentInterval: 0.11,
strokeWidth: 0.1,
elements: 5,
display: 3210,
elementSpacing: 4,
elementPadding: 2,
},
)
const errorsLine = grid.set(0, 6, 4, 3, contrib.line, {
style: {
line: 'red',
text: 'white',
baseline: 'black',
},
label: 'Errors Rate',
maxY: 60,
showLegend: true,
})
const boyConsole = grid.set(0, 0, 6, 6, contrib.log, {
fg: 'green',
selectedFg: 'green',
label: 'Boy Bot',
})
const girlConsole = grid.set(6, 0, 6, 6, contrib.log, {
fg: 'red',
selectedFg: 'red',
label: 'Girl Bot',
})
const log = grid.set(8, 6, 4, 2, contrib.log, {
fg: 'green',
selectedFg: 'green',
label: 'Server Log',
})
// dummy data
const servers = ['US1', 'US2', 'EU1', 'AU1', 'AS1', 'JP1']
const commands = ['grep', 'node', 'java', 'timer', '~/ls -l', 'netns', 'watchdog', 'gulp', 'tar -xvf', 'awk', 'npm install']
// set dummy data on gauge
let gaugePercent = 0
setInterval(function() {
gauge.setData([gaugePercent, 100 - gaugePercent])
gaugePercent++
if (gaugePercent >= 100) gaugePercent = 0
}, 200)
let gaugePercentTwo = 0
setInterval(function() {
gaugeTwo.setData(gaugePercentTwo)
gaugePercentTwo++
if (gaugePercentTwo >= 100) gaugePercentTwo = 0
}, 200)
// set dummy data on bar chart
function fillBar() {
const arr: number[] = []
for (let i = 0; i < servers.length; i++) {
arr.push(Math.round(Math.random() * 10))
}
bar.setData({titles: servers, data: arr})
}
fillBar()
setInterval(fillBar, 2000)
// set dummy data for table
function generateTable() {
const data: any[] = []
for (let i = 0; i < 30; i++) {
const row: any[] = []
row.push(commands[Math.round(Math.random() * (commands.length - 1))])
row.push(Math.round(Math.random() * 5))
row.push(Math.round(Math.random() * 100))
data.push(row)
}
table.setData({headers: ['Process', 'Cpu (%)', 'Memory'], data: data})
}
generateTable()
table.focus()
setInterval(generateTable, 3000)
// set log dummy data
setInterval(function() {
const rnd = Math.round(Math.random() * 2)
if (rnd === 0) log.log('starting process ' + commands[Math.round(Math.random() * (commands.length - 1))])
else if (rnd === 1) log.log('terminating server ' + servers[Math.round(Math.random() * (servers.length - 1))])
else if (rnd === 2) log.log('avg. wait time ' + Math.random().toFixed(2))
screen.render()
}, 500)
// set spark dummy data
const spark1 = [1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5, 4, 4, 5, 4, 1, 5, 1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5]
const spark2 = [4, 4, 5, 4, 1, 5, 1, 2, 5, 2, 1, 5, 4, 4, 5, 4, 1, 5, 1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5]
refreshSpark()
setInterval(refreshSpark, 1000)
function refreshSpark() {
spark1.shift()
spark1.push(Math.random() * 5 + 1)
spark2.shift()
spark2.push(Math.random() * 5 + 1)
sparkline.setData(['Server1', 'Server2'], [spark1, spark2])
}
// set line charts dummy data
const errorsData = {
title: 'server 1',
x: ['00:00', '00:05', '00:10', '00:15', '00:20', '00:25'],
y: [30, 50, 70, 40, 50, 20],
}
// const latencyData = {
// x: ['t1', 't2', 't3', 't4'],
// y: [5, 1, 7, 5],
// }
setLineData([errorsData], errorsLine)
// setLineData([latencyData], latencyLine)
setInterval(function() {
setLineData([errorsData], errorsLine)
}, 1500)
setInterval(function() {
const colors = ['green', 'magenta', 'cyan', 'red', 'blue']
const text = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L']
const value = Math.round(Math.random() * 100)
lcdLineOne.setDisplay(value + text[value % 12])
lcdLineOne.setOptions({
color: colors[value % 5],
elementPadding: 4,
})
screen.render()
}, 1500)
let pct = 0.00
function updateDonut() {
if (pct > 0.99) pct = 0.00
let color = 'green'
if (pct >= 0.25) color = 'cyan'
if (pct >= 0.5) color = 'yellow'
if (pct >= 0.75) color = 'red'
donut.setData([
{percent: parseFloat(((pct + 0.00) % 1) as any).toFixed(2), label: 'storage', 'color': color},
])
pct += 0.01
}
setInterval(function() {
updateDonut()
screen.render()
}, 500)
function setLineData(mockData, line) {
for (let i = 0; i < mockData.length; i++) {
const last = mockData[i].y[mockData[i].y.length - 1]
mockData[i].y.shift()
const num = Math.max(last + Math.round(Math.random() * 10) - 5, 10)
mockData[i].y.push(num)
}
line.setData(mockData)
}
screen.key(['escape', 'q', 'C-c'], function(ch, key) {
return process.exit(0)
})
// fixes https://github.com/yaronn/blessed-contrib/issues/10
screen.on('resize', function() {
donut.emit('attach')
gauge.emit('attach')
gaugeTwo.emit('attach')
sparkline.emit('attach')
bar.emit('attach')
table.emit('attach')
lcdLineOne.emit('attach')
errorsLine.emit('attach')
boyConsole.emit('attach')
girlConsole.emit('attach')
log.emit('attach')
})
screen.render()
// setInterval(() => {
// boyConsole.log('boy')
// girlConsole.log('girl')
// console.log('zixia')
// }, 500)
/**
*
*
*
* Wechaty multi instance support example:
* boy & girl twins
*
*
*
*/
const boy = new Wechaty({ profile: 'boy' })
const girl = new Wechaty({ profile: 'girl' })
startBot(boy, boyConsole)
startBot(girl, girlConsole)
function startBot(bot: Wechaty, logElement: any) {
// logElement.log('Initing...')
bot
.on('logout' , user => logElement.log(`${user.name()} logouted`))
.on('login' , user => {
logElement.setContent('')
logElement.log(`${user.name()} login`)
bot.say('Wechaty login').catch(console.error)
logElement.setLabel(logElement._label.content + ' - ' + user.name())
})
.on('scan', (url, code) => {
if (!/201|200/.test(String(code))) {
const loginUrl = url.replace(/\/qrcode\//, '/l/')
qrcode.generate(
loginUrl,
{
small: true,
},
qrData => logElement.setContent(qrData),
)
}
// logElement.log(`${url}\n[${code}] Scan QR Code above url to log in: `)
})
.on('message', async m => {
logElement.log(m.toString())
})
bot.start()
.catch(e => {
logElement.log(`start() fail: ${e}`)
bot.stop()
process.exit(-1)
})
bot.on('error', async e => {
logElement.log(`error: ${e}`)
if (bot.logonoff()) {
await bot.say('Wechaty error: ' + e.message).catch(console.error)
}
// await bot.stop()
})
}
......@@ -29,7 +29,7 @@ const QrcodeTerminal = require('qrcode-terminal')
*/
import {
config,
Contact,
// Contact,
Wechaty,
log,
} from '../'
......@@ -77,10 +77,10 @@ bot
console.log(`${url}\n[${code}] Scan QR Code in above url to login: `)
})
bot.init()
bot.start()
.catch(e => {
log.error('Bot', 'init() fail: %s', e)
bot.quit()
bot.stop()
process.exit(-1)
})
......@@ -88,7 +88,7 @@ bot.init()
* Main Contact Bot
*/
async function onLogin() {
const contactList = await Contact.findAll()
const contactList = await bot.Contact.findAll()
log.info('Bot', '#######################')
log.info('Bot', 'Contact number: %d\n', contactList.length)
......@@ -157,5 +157,5 @@ async function onLogin() {
// setTimeout(main, SLEEP * 1000)
await bot.logout()
await bot.quit()
await bot.stop()
}
......@@ -27,7 +27,7 @@ const QrcodeTerminal = require('qrcode-terminal')
*/
import {
config,
Contact,
// Contact,
log,
Wechaty,
} from '../'
......@@ -77,7 +77,7 @@ bot
*/
.on('friend', async (contact, request) => {
let logMsg
const fileHelper = Contact.load('filehelper')
const fileHelper = bot.Contact.load('filehelper')
try {
logMsg = 'received `friend` event from ' + contact.get('name')
......@@ -116,9 +116,9 @@ bot
})
bot.init()
bot.start()
.catch(e => {
log.error('Bot', 'init() fail: %s', e)
bot.quit()
bot.stop()
process.exit(-1)
})
......@@ -41,8 +41,9 @@ Please wait... I'm trying to login in...
`
console.log(welcome)
Wechaty.instance({ profile: config.default.DEFAULT_PROFILE })
const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE })
bot
.on('scan', (url, code) => {
if (!/201|200/.test(String(code))) {
const loginUrl = url.replace(/\/qrcode\//, '/l/')
......@@ -63,5 +64,5 @@ Wechaty.instance({ profile: config.default.DEFAULT_PROFILE })
.on('friend', onFriend)
.on('room-join', onRoomJoin)
.init()
.start()
.catch(e => console.error(e))
......@@ -25,10 +25,11 @@
import {
Contact,
FriendRequest,
Room,
Wechaty,
// Room,
} from '../../'
export async function onFriend(contact: Contact, request?: FriendRequest): Promise<void> {
export async function onFriend(this: Wechaty, contact: Contact, request?: FriendRequest): Promise<void> {
try {
if (!request) {
console.log('New friend ' + contact.name() + ' relationship confirmed!')
......@@ -49,7 +50,7 @@ export async function onFriend(contact: Contact, request?: FriendRequest): Promi
)
if (request.hello === 'ding') {
const myRoom = await Room.find({ topic: 'ding' })
const myRoom = await this.Room.find({ topic: 'ding' })
if (!myRoom) return
setTimeout(
async _ => {
......
......@@ -24,10 +24,10 @@
*/
import {
Message,
Room,
Wechaty,
} from '../../'
export async function onMessage(message: Message): Promise<void> {
export async function onMessage(this: Wechaty, message: Message): Promise<void> {
try {
const room = message.room()
const sender = message.from()
......@@ -52,7 +52,7 @@ export async function onMessage(message: Message): Promise<void> {
if (content === 'ding') {
await message.say('thanks for ding me')
const myRoom = await Room.find({ topic: 'ding' })
const myRoom = await this.Room.find({ topic: 'ding' })
if (!myRoom) return
if (myRoom.has(sender)) {
......
......@@ -117,4 +117,4 @@ Please see https://github.com/Chatie/wechaty/tree/master/examples/hot-reload-bot
`)
bot.init();
bot.start();
......@@ -68,8 +68,8 @@ bot
}
// }
})
.init()
.catch(e => console.error('bot.init() error: ' + e))
.start()
.catch(e => console.error('bot.start() error: ' + e))
async function saveMediaFile(message: MediaMessage) {
const filename = message.filename()
......
......@@ -44,5 +44,5 @@ bot
await m.say('roger') // 1. reply others' msg
console.log(`RECV: ${m}, REPLY: "roger"`) // 2. log message
})
.init()
.start()
.catch(e => console.error(e))
......@@ -157,7 +157,7 @@ bot
/**
* Global Event: message
*/
.on('message', async function(this, message) {
.on('message', async function(this: Wechaty, message) {
const room = message.room()
const sender = message.from()
const content = message.content()
......@@ -197,7 +197,7 @@ bot
* find room name start with "ding"
*/
try {
const dingRoom = await Room.find({ topic: /^ding/i })
const dingRoom = await this.Room.find({ topic: /^ding/i })
if (dingRoom) {
/**
* room found
......@@ -244,7 +244,7 @@ bot
}
}
})
.init()
.start()
.catch(e => console.error(e))
async function manageDingRoom() {
......@@ -254,7 +254,7 @@ async function manageDingRoom() {
* Find Room
*/
try {
const room = await Room.find({ topic: /^ding/i })
const room = await bot.Room.find({ topic: /^ding/i })
if (!room) {
log.warn('Bot', 'there is no room topic ding(yet)')
return
......
......@@ -75,8 +75,8 @@ bot
}
})
.init()
.catch(e => console.error('bot.init() error: ' + e))
.start()
.catch(e => console.error('bot.start() error: ' + e))
async function speechToText(mp3Stream: Readable): Promise<string> {
const wavStream = mp3ToWav(mp3Stream)
......
{
"name": "wechaty",
"version": "0.15.3",
"version": "0.15.9",
"description": "Wechat for Bot(Personal Account)",
"main": "dist/index.js",
"types": "dist/index.d.ts",
......@@ -32,10 +32,10 @@
"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:shell": "shellcheck bin/*.sh",
"test:unit": "blue-tape -r ts-node/register -r source-map-support/register \"src/**/*.spec.ts\" \"src/*.spec.ts\" \"tests/*.spec.ts\" \"tests/**/*.spec.ts\"",
"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",
"test:win32": "npm run test:unit:retry",
"test:debug": "blue-tape -r ts-node/register -r source-map-support/register src/puppet-web/bridge.spec.ts",
"test:debug": "blue-tape -r ts-node/register src/puppet-web/bridge.spec.ts",
"io-client": "ts-node bin/io-client",
"demo": "ts-node examples/ding-dong-bot.ts",
"start": "npm run demo"
......@@ -101,6 +101,7 @@
"@types/ws": "^4.0.1",
"bl": "^1.2.0",
"brolog": "^1.2.0",
"cuid": "^2.1.1",
"hot-import": "^0.1.0",
"mime": "^2.2.0",
"puppeteer": "^1.2.0",
......@@ -110,13 +111,15 @@
"retry-promise": "^1.0.0",
"rx-queue": "^0.3.1",
"rxjs": "^5.5.0",
"state-switch": "^0.4.1",
"watchdog": "^0.3.0",
"state-switch": "^0.4.5",
"watchdog": "^0.5.1",
"wechat4u": "^0.7.6",
"ws": "^5.1.0",
"xml2js": "^0.4.0"
},
"devDependencies": {
"@types/blue-tape": "^0.1.0",
"@types/cuid": "^1.3.0",
"@types/express": "^4.0.0",
"@types/fluent-ffmpeg": "^2.1.0",
"@types/glob": "^5.0.0p",
......@@ -125,12 +128,15 @@
"@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/xml2js": "^0.4.0",
"apiai": "^4.0.0",
"babel-cli": "^6.26.0",
"babel-eslint": "^8.0.0",
"babel-preset-env": "^1.6.0",
"blessed": "^0.1.81",
"blessed-contrib": "^4.8.5",
"blue-tape": "^1.0.0",
"check-node-version": "^3.0.0",
"cookie-parser": "^1.4.0",
......@@ -146,6 +152,7 @@
"markdownlint-cli": "^0.8.1",
"nyc": "^11.2.0",
"qrcode-terminal": "^0.12.0",
"semver": "^5.5.0",
"shx": "^0.2.0",
"sinon": "^5.0.0",
"sinon-test": "^2.1.2",
......@@ -170,6 +177,7 @@
"src"
],
"publishConfig": {
"access": "public",
"tagBak": "next",
"tag": "latest"
}
......
#!/usr/bin/env ts-node
import { minor } from 'semver'
const version: string = require('../package.json').version
if (minor(version) % 2 === 0) { // production release
console.log(`${version} is production release`)
process.exit(1) // exit 1 for not development
}
// development release
console.log(`${version} is development release`)
process.exit(0)
#!/usr/bin/env bash
set -e
# https://docs.npmjs.com/cli/deprecate
# $ npm deprecate <pkg>[@<version>] <message>
# npm semver calculator
# - https://semver.npmjs.com
if [ $# -eq 0 ]; then
echo
echo "Usage:"
echo
echo " deprecate all the 0.15.* versions."
echo " \$ $0 0.15"
echo " \$ $0 0.15.*"
echo " \$ $0 ^0.15.1"
echo
else
message="${2:-WARNING: this version(odd number) is coming from a developing branch which means it is very possible UNSTABLE. Please use the latest version with even number if you are in production. You can know more about the Wechaty version numbering design at https://github.com/chatie/wechaty/issues/905}"
npm deprecate "wechaty@$1" "$message"
fi
#!/usr/bin/env ts-node
import * as fs from 'fs'
import * as path from 'path'
const PACKAGE_JSON = path.join(__dirname, '../package.json')
const pkg = require(PACKAGE_JSON)
pkg.publishConfig.tag = 'next'
fs.writeFileSync(PACKAGE_JSON, JSON.stringify(pkg, null, 2))
// console.log(JSON.stringify(pkg, null, 2))
console.log('set package.json:publicConfig.tag to next.')
#!/usr/bin/env bash
set -e
brew update
brew cleanup
brew cask cleanup
brew install \
moreutils \
jq \
shellcheck
文件模式从 100644 更改为 100755
#!/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
......@@ -23,7 +23,7 @@ import * as test from 'blue-tape'
// const sinonTest = require('sinon-test')(sinon)
import { config } from './config'
import { Puppet } from './puppet'
// import { Puppet } from './puppet'
test('important variables', async t => {
t.true('puppet' in config, 'should exist `puppet` in Config')
......@@ -59,28 +59,28 @@ test('validApiHost()', async t => {
})
test('puppetInstance()', async t => {
// BUG Compitable with Win32 CI
// global instance infected across unit tests... :(
const bak = config.puppetInstance()
// test('puppetInstance()', async t => {
// // BUG Compitable with Win32 CI
// // global instance infected across unit tests... :(
// const bak = config.puppetInstance()
config.puppetInstance(null)
t.throws(() => {
config.puppetInstance()
}, Error, 'should throw when not initialized')
config.puppetInstance(bak)
// config.puppetInstance(null)
// t.throws(() => {
// config.puppetInstance()
// }, Error, 'should throw when not initialized')
// config.puppetInstance(bak)
const EXPECTED = <Puppet>{userId: 'test'}
const mockPuppet = EXPECTED
// const EXPECTED: Puppet = {userId: 'test'} as any
// const mockPuppet = EXPECTED
config.puppetInstance(mockPuppet)
const instance = config.puppetInstance()
t.deepEqual(instance, EXPECTED, 'should equal with initialized data')
// config.puppetInstance(mockPuppet)
// const instance = config.puppetInstance()
// t.deepEqual(instance, EXPECTED, 'should equal with initialized data')
config.puppetInstance(null)
t.throws(() => {
config.puppetInstance()
}, Error, 'should throw after set to null')
// config.puppetInstance(null)
// t.throws(() => {
// config.puppetInstance()
// }, Error, 'should throw after set to null')
config.puppetInstance(bak)
})
// config.puppetInstance(bak)
// })
......@@ -22,9 +22,9 @@ import * as path from 'path'
import * as readPkgUp from 'read-pkg-up'
import * as Raven from 'raven'
import Brolog from 'brolog'
import { log } from 'brolog'
import Puppet from './puppet'
// import Puppet from './puppet'
const pkg = readPkgUp.sync({ cwd: __dirname }).pkg
export const VERSION = pkg.version
......@@ -62,17 +62,16 @@ Raven.context(function () {
})
*/
export const log = new Brolog()
const logLevel = process.env['WECHATY_LOG'] || 'info'
const logLevel = process.env['WECHATY_LOG']
if (logLevel) {
log.level(logLevel.toLowerCase() as any)
log.silly('Brolog', 'WECHATY_LOG set level to %s', logLevel)
log.silly('Config', 'WECHATY_LOG set level to %s', logLevel)
}
/**
* to handle unhandled exceptions
*/
if (/verbose|silly/i.test(log.level())) {
if (log.level() === 'verbose' || log.level() === 'silly') {
log.info('Config', 'registering process.on("unhandledRejection") for development/debug')
process.on('unhandledRejection', (reason, promise) => {
log.error('Config', '###########################')
......@@ -101,7 +100,7 @@ export interface DefaultSetting {
/* tslint:disable:variable-name */
/* tslint:disable:no-var-requires */
export const DEFAULT_SETTING = pkg.wechaty as DefaultSetting
const DEFAULT_SETTING = pkg.wechaty as DefaultSetting
export class Config {
public default = DEFAULT_SETTING
......@@ -117,7 +116,7 @@ export class Config {
public httpPort = process.env['PORT'] || process.env['WECHATY_PORT'] || DEFAULT_SETTING.DEFAULT_PORT
public docker = !!(process.env['WECHATY_DOCKER'])
private _puppetInstance: Puppet | null = null
// private _puppetInstance: Puppet | null = null
constructor() {
log.verbose('Config', 'constructor()')
......@@ -127,29 +126,29 @@ export class Config {
/**
* 5. live setting
*/
public puppetInstance(): Puppet
public puppetInstance(empty: null): void
public puppetInstance(instance: Puppet): void
// public puppetInstance(): Puppet
// public puppetInstance(empty: null): void
// public puppetInstance(instance: Puppet): void
public puppetInstance(instance?: Puppet | null): Puppet | void {
// public puppetInstance(instance?: Puppet | null): Puppet | void {
if (typeof instance === 'undefined') {
if (!this._puppetInstance) {
throw new Error('no puppet instance')
}
return this._puppetInstance
// if (typeof instance === 'undefined') {
// if (!this._puppetInstance) {
// throw new Error('no puppet instance')
// }
// return this._puppetInstance
} else if (instance === null) {
log.verbose('Config', 'puppetInstance(null)')
this._puppetInstance = null
return
}
// } else if (instance === null) {
// log.verbose('Config', 'puppetInstance(null)')
// this._puppetInstance = null
// return
// }
log.verbose('Config', 'puppetInstance(%s)', instance.constructor.name)
this._puppetInstance = instance
return
// log.verbose('Config', 'puppetInstance(%s)', instance.constructor.name)
// this._puppetInstance = instance
// return
}
// }
public gitRevision(): string | null {
const dotGitPath = path.join(__dirname, '..', '.git') // only for ts-node, not for dist
......@@ -196,11 +195,23 @@ export interface Sayable {
say(content: string, replyTo?: any|any[]): Promise<boolean>
}
export interface Sleepable {
sleep(millisecond: number): 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,
}
......
......@@ -18,18 +18,20 @@
* @ignore
*/
import {
config,
// config,
Raven,
Sayable,
log,
} from './config'
} from './config'
import {
Message,
MediaMessage,
} from './message'
import Misc from './misc'
import PuppetWeb from './puppet-web'
import Wechaty from './wechaty'
} from './message'
import Misc from './misc'
import PuppetAccessory from './puppet-accessory'
// import Wechaty from './wechaty'
import PuppetWeb from './puppet-web/'
export interface ContactObj {
address: string,
......@@ -104,7 +106,7 @@ const specialContactList: string[] = [
* `Contact` is `Sayable`,
* [Examples/Contact-Bot]{@link https://github.com/Chatie/wechaty/blob/master/examples/contact-bot.ts}
*/
export class Contact implements Sayable {
export class Contact extends PuppetAccessory implements Sayable {
private static pool = new Map<string, Contact>()
public obj: ContactObj | null
......@@ -117,6 +119,7 @@ export class Contact implements Sayable {
constructor(
public readonly id: string,
) {
super()
log.silly('Contact', `constructor(${id})`)
if (typeof id !== 'string') {
......@@ -206,7 +209,7 @@ export class Contact implements Sayable {
public static async find(query: ContactQueryFilter): Promise<Contact | null> {
log.verbose('Contact', 'find(%s)', JSON.stringify(query))
const contactList = await Contact.findAll(query)
const contactList = await this.findAll(query)
if (!contactList || !contactList.length) {
return null
}
......@@ -277,7 +280,7 @@ export class Contact implements Sayable {
/**
* must be string because we need inject variable value
* into code as variable name
* into code as variable namespecialContactList
*/
let filterFunction: string
......@@ -291,7 +294,7 @@ export class Contact implements Sayable {
}
try {
const contactList = await config.puppetInstance()
const contactList = await this.puppet // config.puppetInstance()
.contactFind(filterFunction)
await Promise.all(contactList.map(c => c.ready()))
......@@ -332,8 +335,7 @@ export class Contact implements Sayable {
const content = textOrMedia instanceof MediaMessage ? textOrMedia.filename() : textOrMedia
log.verbose('Contact', 'say(%s)', content)
const bot = Wechaty.instance()
const user = bot.self()
const user = this.puppet.self()
if (!user) {
throw new Error('no user')
......@@ -341,6 +343,8 @@ export class Contact implements Sayable {
let m
if (typeof textOrMedia === 'string') {
m = new Message()
m.puppet = this.puppet
m.content(textOrMedia)
} else if (textOrMedia instanceof MediaMessage) {
m = textOrMedia
......@@ -351,7 +355,7 @@ export class Contact implements Sayable {
m.to(this)
log.silly('Contact', 'say() from: %s to: %s content: %s', user.name(), this.name(), content)
return await bot.send(m)
return await this.puppet.send(m)
}
/**
......@@ -406,25 +410,25 @@ export class Contact implements Sayable {
return this.obj && this.obj.alias || null
}
return config.puppetInstance()
.contactAlias(this, newAlias)
.then(ret => {
if (ret) {
if (this.obj) {
this.obj.alias = newAlias
} else {
log.error('Contact', 'alias() without this.obj?')
}
return this.puppet // config.puppetInstance()
.contactAlias(this, newAlias)
.then(ret => {
if (ret) {
if (this.obj) {
this.obj.alias = newAlias
} else {
log.warn('Contact', 'alias(%s) fail', newAlias)
log.error('Contact', 'alias() without this.obj?')
}
return ret
})
.catch(e => {
log.error('Contact', 'alias(%s) rejected: %s', newAlias, e.message)
Raven.captureException(e)
return false // fail safe
})
} else {
log.warn('Contact', 'alias(%s) fail', newAlias)
}
return ret
})
.catch(e => {
log.error('Contact', 'alias(%s) rejected: %s', newAlias, e.message)
Raven.captureException(e)
return false // fail safe
})
}
/**
......@@ -542,9 +546,9 @@ export class Contact implements Sayable {
}
try {
const hostname = await (config.puppetInstance() as PuppetWeb).hostname()
const hostname = await (/* config.puppetInstance() */ this.puppet as PuppetWeb ).hostname()
const avatarUrl = `http://${hostname}${this.obj.avatar}&type=big` // add '&type=big' to get big image
const cookies = await (config.puppetInstance() as PuppetWeb).cookies()
const cookies = await (/* config.puppetInstance() */ this.puppet as PuppetWeb).cookies()
log.silly('Contact', 'avatar() url: %s', avatarUrl)
return Misc.urlStream(avatarUrl, cookies)
......@@ -599,9 +603,9 @@ export class Contact implements Sayable {
}
if (!contactGetter) {
log.silly('Contact', 'get contact via ' + config.puppetInstance().constructor.name)
contactGetter = config.puppetInstance()
.getContact.bind(config.puppetInstance())
log.silly('Contact', 'get contact via ' + /* config.puppetInstance() */ this.puppet.constructor.name)
contactGetter = /* config.puppetInstance() */ this.puppet
.getContact.bind(/* config.puppetInstance() */ this.puppet)
}
if (!contactGetter) {
throw new Error('no contatGetter')
......@@ -648,8 +652,8 @@ export class Contact implements Sayable {
* const isSelf = contact.self()
*/
public self(): boolean {
const userId = config.puppetInstance()
.userId
const userId = this.puppet // config.puppetInstance()
.userId
const selfId = this.id
......
......@@ -19,10 +19,11 @@
*/
import {
config,
// config,
log,
} from './config'
import Contact from './contact'
} from './config'
import Contact from './contact'
import PuppetAccessory from './puppet-accessory'
/**
* Send, receive friend request, and friend confirmation events.
......@@ -33,18 +34,19 @@ import Contact from './contact'
*
* [Examples/Friend-Bot]{@link https://github.com/Chatie/wechaty/blob/master/examples/friend-bot.ts}
*/
export abstract class FriendRequest {
export abstract class FriendRequest extends PuppetAccessory {
public contact: Contact
public hello: string
public type: 'send' | 'receive' | 'confirm'
constructor() {
super()
log.verbose('FriendRequest', 'constructor()')
if (!config.puppetInstance()) {
throw new Error('no Config.puppetInstance() instanciated')
}
// if (!config.puppetInstance()) {
// throw new Error('no Config.puppetInstance() instanciated')
// }
}
public abstract send(contact: Contact, hello: string): Promise<boolean>
......
......@@ -227,7 +227,7 @@ export class IoClient {
try {
if (this.options.wechaty) {
await this.options.wechaty.quit()
await this.options.wechaty.stop()
// this.wechaty = null
} else { log.warn('IoClient', 'quit() no this.wechaty') }
......
......@@ -53,7 +53,7 @@ interface IoEvent {
}
export class Io {
public uuid: string
public cuid: string
private protocol : string
private eventBuffer : IoEvent[] = []
......@@ -72,14 +72,14 @@ export class Io {
options.apihost = options.apihost || config.apihost
options.protocol = options.protocol || config.default.DEFAULT_PROTOCOL
this.uuid = options.wechaty.uuid
this.cuid = options.wechaty.cuid
this.protocol = options.protocol + '|' + options.wechaty.uuid
log.verbose('Io', 'instantiated with apihost[%s], token[%s], protocol[%s], uuid[%s]',
this.protocol = options.protocol + '|' + options.wechaty.cuid
log.verbose('Io', 'instantiated with apihost[%s], token[%s], protocol[%s], cuid[%s]',
options.apihost,
options.token,
options.protocol,
this.uuid,
this.cuid,
)
}
......@@ -115,7 +115,7 @@ export class Io {
const wechaty = this.options.wechaty
wechaty.on('error' , error => this.send({ name: 'error', payload: error }))
wechaty.on('heartbeat', data => this.send({ name: 'heartbeat', payload: { uuid: this.uuid, data } }))
wechaty.on('heartbeat', data => this.send({ name: 'heartbeat', payload: { cuid: this.cuid, data } }))
wechaty.on('login', user => this.send({ name: 'login', payload: user }))
wechaty.on('logout' , user => this.send({ name: 'logout', payload: user }))
wechaty.on('message', message => this.ioMessage(message))
......@@ -150,7 +150,7 @@ export class Io {
// case 'heartbeat':
// ioEvent.payload = {
// uuid: this.uuid
// cuid: this.cuid
// , data: data
// }
// break
......@@ -218,7 +218,7 @@ export class Io {
this.reconnectTimeout = null
const name = 'sys'
const payload = 'Wechaty version ' + this.options.wechaty.version() + ` with UUID: ${this.uuid}`
const payload = 'Wechaty version ' + this.options.wechaty.version() + ` with CUID: ${this.cuid}`
const initEvent: IoEvent = {
name,
......
......@@ -23,12 +23,14 @@ import * as test from 'blue-tape'
// const sinonTest = require('sinon-test')(sinon)
import {
config,
// config,
log,
} from './config'
import Contact from './contact'
import Message from './message'
import Profile from './profile'
import PuppetWeb from './puppet-web/'
import Room from './room'
const MOCK_USER_ID = 'TEST-USER-ID'
......@@ -36,7 +38,8 @@ const puppet = new PuppetWeb({
profile: new Profile(),
})
puppet.userId = MOCK_USER_ID
config.puppetInstance(puppet)
// config.puppetInstance(puppet)
Message.puppet = puppet
test('constructor()', async t => {
/* tslint:disable:max-line-length */
......@@ -100,8 +103,12 @@ test('ready()', async t => {
})
}
config.puppetInstance()
.getContact = mockGetContact
// config.puppetInstance()
// .getContact = mockGetContact
Room.puppet = Contact.puppet = Message.puppet = {
...puppet,
getContact: mockGetContact,
} as any
const m = new Message(rawData)
......@@ -138,7 +145,8 @@ test('findAll()', async t => {
})
test('self()', async t => {
config.puppetInstance(puppet)
// config.puppetInstance(puppet)
Room.puppet = Contact.puppet = Message.puppet = puppet
const m = new Message()
m.from(MOCK_USER_ID)
......@@ -186,14 +194,17 @@ test('mentioned()', async t => {
})
}
let puppet1
try {
puppet1 = config.puppetInstance()
puppet1.getContact = mockContactGetter
} catch (err) {
puppet1 = { getContact: mockContactGetter }
config.puppetInstance(puppet1)
}
// let puppet1
// try {
// puppet1 = config.puppetInstance()
// puppet1.getContact = mockContactGetter
// } catch (err) {
// puppet1 = { getContact: mockContactGetter }
// config.puppetInstance(puppet1)
// }
Contact.puppet = Room.puppet = Message.puppet = {
getContact: mockContactGetter,
} as any
const msg11 = new Message(rawObj11)
const room11 = msg11.room()
if (room11) {
......
......@@ -25,15 +25,17 @@ import {
import * as mime from 'mime'
import {
config,
// config,
Raven,
Sayable,
log,
} from './config'
} from './config'
import Contact from './contact'
import Room from './room'
import Misc from './misc'
import PuppetAccessory from './puppet-accessory'
import Contact from './contact'
import Room from './room'
import Misc from './misc'
import PuppetWeb from './puppet-web/puppet-web'
import Bridge from './puppet-web/bridge'
......@@ -55,7 +57,7 @@ export type TypeName = 'attachment'
* `Message` is `Sayable`,
* [Examples/Ding-Dong-Bot]{@link https://github.com/Chatie/wechaty/blob/master/examples/ding-dong-bot.ts}
*/
export class Message implements Sayable {
export class Message extends PuppetAccessory implements Sayable {
/**
* @private
*/
......@@ -87,6 +89,7 @@ export class Message implements Sayable {
* @private
*/
constructor(public rawObj?: MsgRawObj) {
super()
this._counter = Message.counter++
log.silly('Message', 'constructor() SN:%d', this._counter)
......@@ -163,7 +166,10 @@ export class Message implements Sayable {
* @private
*/
public getSenderString() {
const fromName = Contact.load(this.obj.from).name()
const from = Contact.load(this.obj.from)
from.puppet = this.puppet
const fromName = from.name()
const roomTopic = this.obj.room
? (':' + Room.load(this.obj.room).topic())
: ''
......@@ -192,7 +198,7 @@ export class Message implements Sayable {
* @returns {Promise<any>}
*
* @example
* const bot = Wechaty.instance()
* const bot = new Wechaty()
* bot
* .on('message', async m => {
* if (/^ding$/i.test(m.content())) {
......@@ -210,6 +216,8 @@ export class Message implements Sayable {
let m
if (typeof textOrMedia === 'string') {
m = new Message()
m.puppet = this.puppet
const room = this.room()
if (room) {
m.room(room)
......@@ -243,8 +251,8 @@ export class Message implements Sayable {
}
}
return config.puppetInstance()
.send(m)
return this.puppet // config.puppetInstance()
.send(m)
}
/**
......@@ -276,9 +284,8 @@ export class Message implements Sayable {
}
const loadedContact = Contact.load(this.obj.from)
if (!loadedContact) {
throw new Error('no from')
}
loadedContact.puppet = this.puppet
return loadedContact
}
......@@ -312,7 +319,9 @@ export class Message implements Sayable {
return
}
if (this.obj.room) {
return Room.load(this.obj.room)
const r = Room.load(this.obj.room)
r.puppet = this.puppet
return r
}
return null
}
......@@ -403,7 +412,7 @@ export class Message implements Sayable {
* }
*/
public self(): boolean {
const userId = config.puppetInstance()
const userId = this.puppet // config.puppetInstance()
.userId
const fromId = this.obj.from
......@@ -491,15 +500,20 @@ export class Message implements Sayable {
try {
const from = Contact.load(this.obj.from)
from.puppet = this.puppet
await from.ready() // Contact from
if (this.obj.to) {
const to = Contact.load(this.obj.to)
to.puppet = this.puppet
await to.ready()
}
if (this.obj.room) {
const room = Room.load(this.obj.room)
room.puppet = this.puppet
await room.ready() // Room member list
}
......@@ -613,7 +627,10 @@ export class Message implements Sayable {
if (!this.obj.to) {
return null
}
return Contact.load(this.obj.to)
const to = Contact.load(this.obj.to)
to.puppet = this.puppet
return to
}
/**
......@@ -688,10 +705,6 @@ export class MediaMessage extends Message {
} else {
throw new Error('not supported construct param')
}
// FIXME: decoupling needed
this.bridge = (config.puppetInstance() as PuppetWeb)
.bridge
}
/**
......@@ -707,10 +720,15 @@ export class MediaMessage extends Message {
public async ready(): Promise<void> {
log.silly('MediaMessage', 'ready()')
// FIXME: decoupling needed
if (!this.bridge) {
this.bridge = (this.puppet as PuppetWeb).bridge
}
try {
await super.ready()
let url: string|null = null
let url: string | undefined
switch (this.type()) {
case MsgType.EMOTICON:
url = await this.bridge.getMsgEmoticon(this.id)
......@@ -886,7 +904,7 @@ export class MediaMessage extends Message {
try {
await this.ready()
// FIXME: decoupling needed
const cookies = await (config.puppetInstance() as PuppetWeb).cookies()
const cookies = await (/* config.puppetInstance() */ this.puppet as PuppetWeb).cookies()
if (!this.obj.url) {
throw new Error('no url')
}
......@@ -983,7 +1001,7 @@ export class MediaMessage extends Message {
*/
public async forward(to: Room|Contact): Promise<boolean> {
try {
const ret = await config.puppetInstance().forward(this, to)
const ret = await /* config.puppetInstance() */ this.puppet.forward(this, to)
return ret
} catch (e) {
log.error('Message', 'forward(%s) exception: %s', to, e)
......
......@@ -184,15 +184,6 @@ export class Misc {
})
}
// credit - http://stackoverflow.com/a/2117523/1123955
public static guid(): string {
/* tslint:disable:no-bitwise */
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
/**
*
* @param port is just a suggestion.
......
......@@ -24,7 +24,7 @@ import {
log,
} from './config'
export type ProfileSection = 'cookies'
export type ProfileSection = 'cookies' | 'ToBeAdded'
export interface ProfileSchema {
cookies?: any[]
......
#!/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 Puppet from './puppet'
import PuppetAccessory from './puppet-accessory'
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()
t.throws(() => c.puppet, 'should throw if read instance puppet before initialization')
FixtureClass.puppet = EXPECTED_PUPPET1
t.equal(FixtureClass.puppet, EXPECTED_PUPPET1, 'should get EXPECTED_PUPPET1 from static puppet after set static puppet')
t.equal(c.puppet, EXPECTED_PUPPET1, 'should get EXPECTED_PUPPET1 from instance puppet after set static puppet')
c.puppet = EXPECTED_PUPPET2
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')
})
import { EventEmitter } from 'events'
import { log } from './config'
import { Puppet } from './puppet'
export abstract class PuppetAccessory extends EventEmitter {
/**
* 1. Static puppet property
*/
private static _puppet?: Puppet
public static set puppet(puppet: Puppet) {
log.silly('PuppetAssessory', 'static set puppet()')
this._puppet = puppet
}
public static get puppet(): Puppet {
log.silly('PuppetAssessory', 'static get puppet()')
if (this._puppet) {
log.silly('PuppetAssessory', 'static get puppet() from instance properties')
return this._puppet
}
throw new Error('static puppet not found')
}
/**
* 2. Instance puppet property
*/
private _puppet?: Puppet
public set puppet(puppet: Puppet) {
log.silly('PuppetAssessory', 'set puppet()')
this._puppet = puppet
}
public get puppet(): Puppet {
log.silly('PuppetAssessory', 'get puppet()')
if (this._puppet) {
log.silly('PuppetAssessory', 'get puppet() from instance properties')
return this._puppet
}
return (this.constructor as any).puppet
}
}
export default PuppetAccessory
......@@ -29,7 +29,7 @@ import {
MediaMessage,
} from '../message'
import {
ScanInfo,
ScanData,
} from '../puppet'
import Firer from './firer'
......@@ -58,7 +58,7 @@ function onDing(this: PuppetWeb, data): void {
this.emit('watchdog', { data })
}
async function onScan(this: PuppetWeb, data: ScanInfo): Promise<void> {
async function onScan(this: PuppetWeb, data: ScanData): Promise<void> {
log.verbose('PuppetWebEvent', 'onScan({code: %d, url: %s})', data.code, data.url)
if (this.state.off()) {
......@@ -78,7 +78,7 @@ async function onScan(this: PuppetWeb, data: ScanInfo): Promise<void> {
log.verbose('PuppetWebEvent', 'onScan() there has user when got a scan event. emit logout and set it to null')
const bak = this.user || this.userId || ''
this.user = this.userId = null
this.user = this.userId = undefined
this.emit('logout', bak)
}
......@@ -135,6 +135,8 @@ async function onLogin(this: PuppetWeb, memo: string, ttl = 30): Promise<void> {
log.silly('PuppetWebEvent', 'bridge.getUserName: %s', this.userId)
this.user = Contact.load(this.userId)
this.user.puppet = this
await this.user.ready()
log.silly('PuppetWebEvent', `onLogin() user ${this.user.name()} logined`)
......@@ -171,15 +173,16 @@ function onLogout(this: PuppetWeb, data) {
}
const bak = this.user || this.userId || ''
this.userId = this.user = null
this.userId = this.user = undefined
this.emit('logout', bak)
}
async function onMessage(
this: PuppetWeb,
obj: MsgRawObj,
obj: MsgRawObj,
): Promise<void> {
let m = new Message(obj)
m.puppet = this
try {
await m.ready()
......@@ -222,6 +225,7 @@ async function onMessage(
case MsgType.APP:
log.verbose('PuppetWebEvent', 'onMessage() EMOTICON/IMAGE/VIDEO/VOICE/MICROVIDEO message')
m = new MediaMessage(obj)
m.puppet = this
break
case MsgType.TEXT:
......
......@@ -112,6 +112,8 @@ async function checkFriendRequest(m: Message) {
}
const request = new FriendRequest()
request.puppet = m.puppet
request.receive(info)
await request.contact.ready()
......@@ -145,6 +147,8 @@ async function checkFriendConfirm(m: Message) {
return
}
const request = new FriendRequest()
request.puppet = m.puppet
const contact = m.from()
request.confirm(contact)
......@@ -216,6 +220,7 @@ async function checkRoomJoin(m: Message): Promise<boolean> {
try {
if (inviter === 'You' || inviter === '' || inviter === 'you') {
inviterContact = Contact.load(this.userId)
inviterContact.puppet = m.puppet
}
const max = 20
......@@ -354,6 +359,8 @@ async function checkRoomLeave(m: Message): Promise<boolean> {
let leaverContact: Contact | null, removerContact: Contact | null
if (leaver === this.userId) {
leaverContact = Contact.load(this.userId)
leaverContact.puppet = m.puppet
// not sure which is better
// removerContact = room.member({contactAlias: remover}) || room.member({name: remover})
removerContact = room.member(remover)
......@@ -361,8 +368,11 @@ async function checkRoomLeave(m: Message): Promise<boolean> {
log.error('PuppetWebFirer', 'fireRoomLeave() bot is removed from the room, but remover %s not found, event `room-leave` & `leave` will not be fired', remover)
return false
}
} else {
removerContact = Contact.load(this.userId)
removerContact.puppet = m.puppet
// not sure which is better
// leaverContact = room.member({contactAlias: remover}) || room.member({name: leaver})
leaverContact = room.member(remover)
......@@ -418,6 +428,7 @@ async function checkRoomTopic(m: Message): Promise<boolean> {
let changerContact: Contact | null
if (/^You$/.test(changer) || /^你$/.test(changer)) {
changerContact = Contact.load(this.userId)
changerContact.puppet = m.puppet
} else {
changerContact = room.member(changer)
}
......
......@@ -22,15 +22,18 @@ import * as test from 'blue-tape'
// import * as sinon from 'sinon'
// const sinonTest = require('sinon-test')(sinon)
import config from '../config'
// import config from '../config'
import Contact from '../contact'
import Message from '../message'
import Puppet from '../puppet'
import PuppetWebFriendRequest from './friend-request'
config.puppetInstance({
PuppetWebFriendRequest.puppet = {
userId: 'xxx',
} as Puppet)
} as any as Puppet
// config.puppetInstance({
// userId: 'xxx',
// } as any as Puppet)
test('PuppetWebFriendRequest.receive smoke testing', async t => {
/* tslint:disable:max-line-length */
......@@ -56,6 +59,7 @@ test('PuppetWebFriendRequest.confirm smoke testing', async t => {
`
const rawObj = JSON.parse(rawMessageData)
const m = new Message(rawObj)
m.puppet = this.puppet
t.true(/^You have added (.+) as your WeChat contact. Start chatting!$/.test(m.content()), 'should match confirm message')
......
......@@ -31,7 +31,7 @@ const retryPromise = require('retry-promise').default
import { Contact } from '../contact'
import {
config,
// config,
log,
} from '../config'
import FriendRequest from '../friend-request'
......@@ -63,10 +63,8 @@ export class PuppetWebFriendRequest extends FriendRequest {
this.info = info
const contact = Contact.load(info.UserName)
if (!contact) {
log.warn('PuppetWebFriendRequest', 'receive() no contact found for "%s"', info.UserName)
throw new Error('no contact')
}
contact.puppet = this.puppet
this.contact = contact
this.hello = info.Content
this.ticket = info.Ticket
......@@ -114,7 +112,7 @@ export class PuppetWebFriendRequest extends FriendRequest {
this.hello = hello
}
return config.puppetInstance()
return this.puppet // config.puppetInstance()
.friendRequestSend(contact, hello)
}
......@@ -130,8 +128,8 @@ export class PuppetWebFriendRequest extends FriendRequest {
throw new Error('request is not a `receive` type. it is a ' + this.type + ' type')
}
const ret = await config.puppetInstance()
.friendRequestAccept(this.contact, this.ticket)
const ret = await this.puppet // config.puppetInstance()
.friendRequestAccept(this.contact, this.ticket)
const max = 20
const backoff = 300
......
......@@ -25,7 +25,7 @@ import {
Bridge,
Event,
PuppetWeb,
} from './index'
} from './'
test('PuppetWeb Module Exports', async t => {
t.ok(PuppetWeb , 'should export PuppetWeb')
......
......@@ -22,8 +22,9 @@ import {
} from 'watchdog'
import {
ThrottleQueue,
} from 'rx-queue'
} from 'rx-queue'
import cloneClass from '../clone-class'
import {
config,
log,
......@@ -38,16 +39,15 @@ import Profile from '../profile'
import {
Puppet,
PuppetOptions,
ScanInfo,
} from '../puppet'
import Room from '../room'
import Misc from '../misc'
ScanData,
} from '../puppet'
import Room from '../room'
import Misc from '../misc'
import {
Bridge,
Cookie,
} from './bridge'
import Event from './event'
} from './bridge'
import Event from './event'
import {
MediaData,
......@@ -63,7 +63,7 @@ export type ScanFoodType = 'scan' | 'login' | 'logout'
export class PuppetWeb extends Puppet {
public bridge : Bridge
public scanInfo : ScanInfo | null
public scanInfo : ScanData | null
public puppetWatchdog : Watchdog<PuppetFoodType>
public scanWatchdog : Watchdog<ScanFoodType>
......@@ -87,8 +87,8 @@ export class PuppetWeb extends Puppet {
return `PuppetWeb<${this.options.profile.name}>`
}
public async init(): Promise<void> {
log.verbose('PuppetWeb', `init() with ${this.options.profile}`)
public async start(): Promise<void> {
log.verbose('PuppetWeb', `start() with ${this.options.profile}`)
this.state.on('pending')
......@@ -114,19 +114,19 @@ export class PuppetWeb extends Puppet {
const throttleQueue = new ThrottleQueue(5 * 60 * 1000)
this.on('heartbeat', data => throttleQueue.next(data))
throttleQueue.subscribe(async data => {
log.verbose('Wechaty', 'init() throttleQueue.subscribe() new item: %s', data)
log.verbose('Wechaty', 'start() throttleQueue.subscribe() new item: %s', data)
await this.saveCookie()
})
log.verbose('PuppetWeb', 'init() done')
log.verbose('PuppetWeb', 'start() done')
return
} catch (e) {
log.error('PuppetWeb', 'init() exception: %s', e)
log.error('PuppetWeb', 'start() exception: %s', e)
this.state.off(true)
this.emit('error', e)
await this.quit()
await this.stop()
Raven.captureException(e)
throw e
......@@ -153,8 +153,8 @@ export class PuppetWeb extends Puppet {
log.warn('PuppetWeb', 'initWatchdogForPuppet() dog.on(reset) last food:%s, timeout:%s',
food.data, timeout)
try {
await this.quit()
await this.init()
await this.stop()
await this.start()
} catch (e) {
puppet.emit('error', e)
}
......@@ -217,18 +217,13 @@ export class PuppetWeb extends Puppet {
})
}
public async quit(): Promise<void> {
public async stop(): Promise<void> {
log.verbose('PuppetWeb', 'quit()')
const off = this.state.off()
if (off === 'pending') {
const e = new Error('quit() is called on a PENDING OFF PuppetWeb')
log.warn('PuppetWeb', e.message)
this.emit('error', e)
return
} else if (off === true) {
log.warn('PuppetWeb', 'quit() is called on a OFF puppet. return directly.')
return
if (this.state.off()) {
log.warn('PuppetWeb', 'quit() is called on a OFF puppet. await ready(off) and return.')
await this.state.ready('off')
return
}
log.verbose('PuppetWeb', 'quit() make watchdog sleep before do quit')
......@@ -290,18 +285,6 @@ export class PuppetWeb extends Puppet {
return this.bridge
}
public async reset(reason?: string): Promise<void> {
log.verbose('PuppetWeb', 'reset(%s)', reason)
try {
await this.bridge.quit()
await this.bridge.init()
log.silly('PuppetWeb', 'reset() done')
} catch (err) {
log.error('PuppetWeb', 'reset(%s) bridge.{quit,init}() exception: %s', reason, err)
this.emit('error', err)
}
}
public logined(): boolean {
log.warn('PuppetWeb', 'logined() DEPRECATED. use logonoff() instead.')
return this.logonoff()
......@@ -759,7 +742,7 @@ export class PuppetWeb extends Puppet {
log.verbose('PuppetWeb', 'logout()')
const data = this.user || this.userId || ''
this.userId = this.user = null
this.userId = this.user = undefined
try {
await this.bridge.logout()
......@@ -811,7 +794,11 @@ export class PuppetWeb extends Puppet {
public async contactFind(filterFunc: string): Promise<Contact[]> {
try {
const idList = await this.bridge.contactFind(filterFunc)
return idList.map(id => Contact.load(id))
return idList.map(id => {
const c = Contact.load(id)
c.puppet = this
return c
})
} catch (e) {
log.warn('PuppetWeb', 'contactFind(%s) rejected: %s', filterFunc, e.message)
Raven.captureException(e)
......@@ -822,7 +809,11 @@ export class PuppetWeb extends Puppet {
public async roomFind(filterFunc: string): Promise<Room[]> {
try {
const idList = await this.bridge.roomFind(filterFunc)
return idList.map(id => Room.load(id))
return idList.map(id => {
const r = Room.load(id)
r.puppet = this
return r
})
} catch (e) {
log.warn('PuppetWeb', 'roomFind(%s) rejected: %s', filterFunc, e.message)
Raven.captureException(e)
......@@ -881,7 +872,9 @@ export class PuppetWeb extends Puppet {
if (!roomId) {
throw new Error('PuppetWeb.roomCreate() roomId "' + roomId + '" not found')
}
return Room.load(roomId)
const r = Room.load(roomId)
r.puppet = this
return r
} catch (e) {
log.warn('PuppetWeb', 'roomCreate(%s, %s) rejected: %s', contactIdList.join(','), topic, e.message)
......@@ -929,9 +922,14 @@ export class PuppetWeb extends Puppet {
log.verbose('PuppetWeb', 'readyStable()')
let counter = -1
// tslint:disable-next-line:variable-name
const MyContact = cloneClass(Contact)
MyContact.puppet = this
async function stable(done: Function): Promise<void> {
log.silly('PuppetWeb', 'readyStable() stable() counter=%d', counter)
const contactList = await Contact.findAll()
const contactList = await MyContact.findAll()
if (counter === contactList.length) {
log.verbose('PuppetWeb', 'readyStable() stable() READY counter=%d', counter)
return done()
......@@ -958,6 +956,12 @@ export class PuppetWeb extends Puppet {
}
/**
* https://www.chatie.io:8080/api
* location.hostname = www.chatie.io
* location.host = www.chatie.io:8080
* See: https://stackoverflow.com/a/11379802/1123955
*/
public async hostname(): Promise<string> {
try {
const name = await this.bridge.hostname()
......
......@@ -25,6 +25,7 @@ import {
import {
Sayable,
WechatyEvent,
log,
} from './config'
import Contact from './contact'
......@@ -35,13 +36,11 @@ import {
} from './message'
import Profile from './profile'
import Room from './room'
import {
WechatyEvent,
} from './wechaty'
export interface ScanInfo {
url: string,
code: number,
export interface ScanData {
avatar: string, // Image Data URL
url: string, // QR Code URL
code: number, // Code
}
export type PuppetEvent = WechatyEvent
......@@ -54,11 +53,10 @@ export interface PuppetOptions {
* Abstract Puppet Class
*/
export abstract class Puppet extends EventEmitter implements Sayable {
public userId: string | null
public user: Contact | null
public abstract getContact(id: string): Promise<any>
public userId?: string
public user?: Contact
public state: StateSwitch
public state: StateSwitch
constructor(public options: PuppetOptions) {
super()
......@@ -94,7 +92,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public on(event: 'room-join', listener: (room: Room, inviteeList: Contact[], inviter: Contact) => void) : this
public on(event: 'room-leave', listener: (room: Room, leaverList: Contact[]) => void) : this
public on(event: 'room-topic', listener: (room: Room, topic: string, oldTopic: string, changer: Contact) => void) : this
public on(event: 'scan', listener: (info: ScanInfo) => void) : this
public on(event: 'scan', listener: (info: ScanData) => void) : this
public on(event: 'watchdog', listener: (data: WatchdogFood) => void) : this
public on(event: never, listener: never) : never
......@@ -106,10 +104,13 @@ export abstract class Puppet extends EventEmitter implements Sayable {
return this
}
public abstract async init() : Promise<void>
public abstract async start() : Promise<void>
public abstract async stop() : Promise<void>
public abstract self() : Contact
public abstract getContact(id: string): Promise<any>
/**
* Message
*/
......@@ -121,14 +122,12 @@ export abstract class Puppet extends EventEmitter implements Sayable {
* Login / Logout
*/
public abstract logonoff() : boolean
public abstract reset(reason?: string) : void
public abstract logout() : Promise<void>
public abstract quit() : Promise<void>
/**
* Misc
*/
public abstract ding() : Promise<string>
public abstract ding(data?: any) : Promise<string>
/**
* FriendRequest
......
......@@ -17,20 +17,19 @@
*
* @ignore
*/
import { EventEmitter } from 'events'
import {
config,
// config,
Raven,
Sayable,
log,
} from './config'
import Contact from './contact'
} from './config'
import Contact from './contact'
import {
Message,
MediaMessage,
} from './message'
import Misc from './misc'
} from './message'
import Misc from './misc'
import PuppetAccessory from './puppet-accessory'
interface RoomObj {
id: string,
......@@ -82,7 +81,7 @@ export interface MemberQueryFilter {
* `Room` is `Sayable`,
* [Examples/Room-Bot]{@link https://github.com/Chatie/wechaty/blob/master/examples/room-bot.ts}
*/
export class Room extends EventEmitter implements Sayable {
export class Room extends PuppetAccessory implements Sayable {
private static pool = new Map<string, Room>()
private dirtyObj: RoomObj | null // when refresh, use this to save dirty data for query
......@@ -120,6 +119,7 @@ export class Room extends EventEmitter implements Sayable {
private async readyAllMembers(memberList: RoomRawMember[]): Promise<void> {
for (const member of memberList) {
const contact = Contact.load(member.UserName)
contact.puppet = this.puppet
await contact.ready()
}
return
......@@ -141,8 +141,8 @@ export class Room extends EventEmitter implements Sayable {
}
if (!contactGetter) {
contactGetter = config.puppetInstance()
.getContact.bind(config.puppetInstance())
contactGetter = this.puppet // config.puppetInstance()
.getContact.bind(/* config.puppetInstance() */ this.puppet)
}
if (!contactGetter) {
throw new Error('no contactGetter')
......@@ -231,6 +231,7 @@ export class Room extends EventEmitter implements Sayable {
let m
if (typeof textOrMedia === 'string') {
m = new Message()
m.puppet = this.puppet
const replyToList: Contact[] = [].concat(replyTo as any || [])
......@@ -247,8 +248,8 @@ export class Room extends EventEmitter implements Sayable {
m.room(this)
return config.puppetInstance()
.send(m)
return this.puppet // config.puppetInstance()
.send(m)
}
public on(event: 'leave', listener: (this: Room, leaver: Contact) => void): this
......@@ -331,7 +332,11 @@ export class Room extends EventEmitter implements Sayable {
}
const memberList = (rawObj.MemberList || [])
.map(m => Contact.load(m.UserName))
.map(m => {
const c = Contact.load(m.UserName)
c.puppet = this.puppet
return c
})
const nameMap = this.parseMap('name', rawObj.MemberList)
const roomAliasMap = this.parseMap('roomAlias', rawObj.MemberList)
......@@ -358,6 +363,8 @@ export class Room extends EventEmitter implements Sayable {
memberList.forEach(member => {
let tmpName: string
const contact = Contact.load(member.UserName)
contact.puppet = this.puppet
switch (parseContent) {
case 'name':
tmpName = contact.name()
......@@ -427,8 +434,8 @@ export class Room extends EventEmitter implements Sayable {
throw new Error('contact not found')
}
const n = config.puppetInstance()
.roomAdd(this, contact)
const n = this.puppet // config.puppetInstance()
.roomAdd(this, contact)
return n
}
......@@ -455,9 +462,9 @@ export class Room extends EventEmitter implements Sayable {
if (!contact) {
throw new Error('contact not found')
}
const n = await config.puppetInstance()
.roomDel(this, contact)
.then(_ => this.delLocal(contact))
const n = await this.puppet // config.puppetInstance()
.roomDel(this, contact)
.then(_ => this.delLocal(contact))
return n
}
......@@ -536,14 +543,14 @@ export class Room extends EventEmitter implements Sayable {
return Misc.plainText(this.obj ? this.obj.topic : '')
}
config.puppetInstance()
.roomTopic(this, newTopic)
.catch(e => {
log.warn('Room', 'topic(newTopic=%s) exception: %s',
newTopic, e && e.message || e,
)
Raven.captureException(e)
})
this.puppet // config.puppetInstance()
.roomTopic(this, newTopic)
.catch(e => {
log.warn('Room', 'topic(newTopic=%s) exception: %s',
newTopic, e && e.message || e,
)
Raven.captureException(e)
})
if (!this.obj) {
this.obj = <RoomObj>{}
......@@ -717,7 +724,11 @@ export class Room extends EventEmitter implements Sayable {
log.silly('Room', 'memberAll() check %s from %s: %s', filterValue, filterKey, JSON.stringify(filterMap))
if (idList.length) {
return idList.map(id => Contact.load(id))
return idList.map(id => {
const c = Contact.load(id)
c.puppet = this.puppet
return c
})
} else {
return []
}
......@@ -820,13 +831,13 @@ export class Room extends EventEmitter implements Sayable {
throw new Error('contactList not found')
}
return config.puppetInstance()
.roomCreate(contactList, topic)
.catch(e => {
log.error('Room', 'create() exception: %s', e && e.stack || e.message || e)
Raven.captureException(e)
throw e
})
return this.puppet // config.puppetInstance()
.roomCreate(contactList, topic)
.catch(e => {
log.error('Room', 'create() exception: %s', e && e.stack || e.message || e)
Raven.captureException(e)
throw e
})
}
/**
......@@ -862,13 +873,13 @@ export class Room extends EventEmitter implements Sayable {
throw new Error('unsupport topic type')
}
const roomList = await config.puppetInstance()
.roomFind(filterFunction)
.catch(e => {
log.verbose('Room', 'findAll() rejected: %s', e.message)
Raven.captureException(e)
return [] as Room[] // fail safe
})
const roomList = await this.puppet // config.puppetInstance()
.roomFind(filterFunction)
.catch(e => {
log.verbose('Room', 'findAll() rejected: %s', e.message)
Raven.captureException(e)
return [] as Room[] // fail safe
})
await Promise.all(roomList.map(room => room.ready()))
// for (let i = 0; i < roomList.length; i++) {
......@@ -887,7 +898,7 @@ export class Room extends EventEmitter implements Sayable {
public static async find(query: RoomQueryFilter): Promise<Room | null> {
log.verbose('Room', 'find({ topic: %s })', query.topic)
const roomList = await Room.findAll(query)
const roomList = await this.findAll(query)
if (!roomList || roomList.length < 1) {
return null
} else if (roomList.length > 1) {
......@@ -919,7 +930,7 @@ export class Room extends EventEmitter implements Sayable {
public owner(): Contact | null {
const ownerUin = this.obj && this.obj.ownerUin
const user = config.puppetInstance()
const user = this.puppet // config.puppetInstance()
.user
if (user && user.get('uin') === ownerUin) {
......@@ -927,7 +938,9 @@ export class Room extends EventEmitter implements Sayable {
}
if (this.rawObj.ChatRoomOwner) {
return Contact.load(this.rawObj.ChatRoomOwner)
const c = Contact.load(this.rawObj.ChatRoomOwner)
c.puppet = this.puppet
return c
}
log.info('Room', 'owner() is limited by Tencent API, sometimes work sometimes not')
......@@ -942,10 +955,10 @@ export class Room extends EventEmitter implements Sayable {
throw new Error('Room.load() no id')
}
if (id in Room.pool) {
return Room.pool[id]
if (id in this.pool) {
return this.pool[id]
}
return Room.pool[id] = new Room(id)
return this.pool[id] = new this(id)
}
}
......
......@@ -17,7 +17,7 @@
*
* @ignore
*/
import { EventEmitter } from 'events'
import * as cuid from 'cuid'
import * as os from 'os'
import StateSwitch from 'state-switch'
......@@ -26,47 +26,36 @@ import {
hotImport,
} from 'hot-import'
import cloneClass from './clone-class'
import {
config,
log,
PuppetName,
Raven,
Sayable,
log,
VERSION,
} from './config'
import Contact from './contact'
import FriendRequest from './friend-request'
WechatyEvent,
} from './config'
import Contact from './contact'
import {
Message,
MediaMessage,
} from './message'
import Profile from './profile'
import Puppet from './puppet'
import PuppetWeb from './puppet-web/'
import Room from './room'
import Misc from './misc'
} from './message'
import Profile from './profile'
import Puppet from './puppet'
import PuppetAccessory from './puppet-accessory'
import {
FriendRequest,
PuppetWeb,
} from './puppet-web/'
import Room from './room'
// import Misc from './misc'
export interface WechatyOptions {
puppet?: PuppetName,
profile?: string,
}
export type WechatEvent = 'friend'
| 'login'
| 'logout'
| 'message'
| 'room-join'
| 'room-leave'
| 'room-topic'
| 'scan'
export type WechatyEvent = WechatEvent
| 'error'
| 'heartbeat'
| 'start'
| 'stop'
/**
* Main bot class.
*
......@@ -77,19 +66,13 @@ export type WechatyEvent = WechatEvent
* import { Wechaty } from 'wechaty'
*
*/
export class Wechaty extends EventEmitter implements Sayable {
export class Wechaty extends PuppetAccessory implements Sayable {
/**
* singleton _instance
* @private
*/
private static _instance: Wechaty
/**
* the puppet
* @private
*/
public puppet: Puppet | null
private profile: Profile
/**
......@@ -99,10 +82,19 @@ export class Wechaty extends EventEmitter implements Sayable {
private state = new StateSwitch('Wechaty', log)
/**
* the uuid
* the cuid
* @private
*/
public uuid: string
public cuid: string
// tslint:disable-next-line:variable-name
public Contact : typeof Contact
// tslint:disable-next-line:variable-name
public FriendRequest : typeof FriendRequest
// tslint:disable-next-line:variable-name
public Message : typeof Message
// tslint:disable-next-line:variable-name
public Room : typeof Room
/**
* get the singleton instance of Wechaty
......@@ -129,9 +121,9 @@ export class Wechaty extends EventEmitter implements Sayable {
}
/**
* @private
* @public
*/
private constructor(
constructor(
private options: WechatyOptions = {},
) {
super()
......@@ -141,7 +133,7 @@ export class Wechaty extends EventEmitter implements Sayable {
this.profile = new Profile(options.profile)
this.uuid = Misc.guid()
this.cuid = cuid()
}
/**
......@@ -176,20 +168,6 @@ export class Wechaty extends EventEmitter implements Sayable {
return Wechaty.version(forceNpm)
}
/**
* Initialize the bot, return Promise.
*
* @deprecated
* @returns {Promise<void>}
* @example
* await bot.init()
* // do other stuff with bot here
*/
public async init(): Promise<void> {
log.warn('Wechaty', 'init() DEPRECATED and will be removed after Jun 2018. Use start() instead.')
await this.start()
}
/**
* Start the bot, return Promise.
*
......@@ -200,15 +178,14 @@ export class Wechaty extends EventEmitter implements Sayable {
*/
public async start(): Promise<void> {
log.info('Wechaty', 'v%s starting...' , this.version())
log.verbose('Wechaty', 'puppet: %s' , this.options.puppet)
log.verbose('Wechaty', 'profile: %s' , this.options.profile)
log.verbose('Wechaty', 'uuid: %s' , this.uuid)
if (this.state.on() === true) {
log.error('Wechaty', 'start() already started. return and do nothing.')
return
} else if (this.state.on() === 'pending') {
log.error('Wechaty', 'start() another task is starting. return and do nothing.')
log.verbose('Wechaty', 'puppet: %s' , this.options.puppet)
log.verbose('Wechaty', 'profile: %s' , this.options.profile)
log.verbose('Wechaty', 'cuid: %s' , this.cuid)
if (this.state.on()) {
log.silly('Wechaty', 'start() on a starting/started instance')
await this.state.ready()
log.silly('Wechaty', 'start() state.ready() resolved')
return
}
......@@ -216,7 +193,21 @@ export class Wechaty extends EventEmitter implements Sayable {
try {
this.profile.load()
this.puppet = await this.initPuppet()
this.puppet = this.initPuppet()
// set puppet instance to Wechaty Static variable, for using by Contact/Room/Message/FriendRequest etc.
// config.puppetInstance(puppet)
this.Contact = cloneClass(Contact)
this.FriendRequest = cloneClass(FriendRequest)
this.Message = cloneClass(Message)
this.Room = cloneClass(Room)
this.Contact.puppet = this.puppet
this.FriendRequest.puppet = this.puppet
this.Message.puppet = this.puppet
this.Room.puppet = this.puppet
await this.puppet.start()
} catch (e) {
log.error('Wechaty', 'start() exception: %s', e && e.message)
......@@ -399,7 +390,7 @@ export class Wechaty extends EventEmitter implements Sayable {
/**
* @private
*/
public async initPuppet(): Promise<Puppet> {
public initPuppet(): Puppet {
log.verbose('Wechaty', 'initPuppet()')
let puppet: Puppet
......@@ -435,26 +426,9 @@ export class Wechaty extends EventEmitter implements Sayable {
})
}
// set puppet instance to Wechaty Static variable, for using by Contact/Room/Message/FriendRequest etc.
config.puppetInstance(puppet)
await puppet.init()
return puppet
}
/**
* Quit the bot
*
* @deprecated use stop() instead
* @returns {Promise<void>}
* @example
* await bot.quit()
*/
public async quit(): Promise<void> {
log.warn('Wechaty', 'quit() DEPRECATED and will be removed after Jun 2018. Use stop() instead.')
await this.stop()
}
/**
* Stop the bot
*
......@@ -466,29 +440,31 @@ export class Wechaty extends EventEmitter implements Sayable {
log.verbose('Wechaty', 'stop()')
if (this.state.off()) {
if (this.state.off() === 'pending') { // current() !== 'on' || !this.state.stable()) {
const err = new Error(`stop() on a pending stop instance.`)
log.error('Wechaty', err.message)
this.emit('error', err)
} else {
log.warn('Wechaty', 'stop() on an already stopped instance.')
}
log.silly('Wechaty', 'stop() on an stopping/stopped instance')
await this.state.ready('off')
log.silly('Wechaty', 'stop() state.ready(off) resolved')
return
}
this.state.off('pending')
if (!this.puppet) {
let puppet: Puppet
try {
puppet = this.puppet
} catch (e) {
log.warn('Wechaty', 'stop() without this.puppet')
return
}
const puppet = this.puppet
this.puppet = null
config.puppetInstance(null)
// this.puppet = null
// config.puppetInstance(null)
// this.Contact.puppet = undefined
// this.FriendRequest.puppet = undefined
// this.Message.puppet = undefined
// this.Room.puppet = undefined
try {
await puppet.quit()
await puppet.stop()
} catch (e) {
log.error('Wechaty', 'stop() exception: %s', e.message)
Raven.captureException(e)
......@@ -514,10 +490,6 @@ export class Wechaty extends EventEmitter implements Sayable {
public async logout(): Promise<void> {
log.verbose('Wechaty', 'logout()')
if (!this.puppet) {
throw new Error('no puppet')
}
try {
await this.puppet.logout()
} catch (e) {
......@@ -540,10 +512,11 @@ export class Wechaty extends EventEmitter implements Sayable {
* }
*/
public logonoff(): Boolean {
if (!this.puppet) {
try {
return this.puppet.logonoff()
} catch (e) {
return false
}
return this.puppet.logonoff()
}
/**
......@@ -555,9 +528,6 @@ export class Wechaty extends EventEmitter implements Sayable {
* console.log(`Bot is ${contact.name()}`)
*/
public self(): Contact {
if (!this.puppet) {
throw new Error('Wechaty.self() no puppet')
}
return this.puppet.self()
}
......@@ -565,9 +535,6 @@ export class Wechaty extends EventEmitter implements Sayable {
* @private
*/
public async send(message: Message | MediaMessage): Promise<boolean> {
if (!this.puppet) {
throw new Error('no puppet')
}
try {
return await this.puppet.send(message)
} catch (e) {
......@@ -585,10 +552,6 @@ export class Wechaty extends EventEmitter implements Sayable {
*/
public async say(content: string): Promise<boolean> {
log.verbose('Wechaty', 'say(%s)', content)
if (!this.puppet) {
throw new Error('no puppet')
}
return await this.puppet.say(content)
}
......@@ -605,10 +568,6 @@ export class Wechaty extends EventEmitter implements Sayable {
* @private
*/
public async ding(): Promise<string> {
if (!this.puppet) {
return Promise.reject(new Error('wechaty cant ding coz no puppet'))
}
try {
return await this.puppet.ding() // should return 'dong'
} catch (e) {
......@@ -638,10 +597,8 @@ export class Wechaty extends EventEmitter implements Sayable {
*/
public async reset(reason?: string): Promise<void> {
log.verbose('Wechaty', 'reset() because %s', reason)
if (!this.puppet) {
throw new Error('no puppet')
}
await this.puppet.reset(reason)
await this.puppet.stop()
await this.puppet.start()
return
}
......
......@@ -22,14 +22,17 @@
import * as test from 'blue-tape'
// import * as sinon from 'sinon'
import config from '../src/config'
// import config from '../src/config'
import Contact from '../src/contact'
import Profile from '../src/profile'
import PuppetWeb from '../src/puppet-web'
config.puppetInstance(new PuppetWeb({
// config.puppetInstance(new PuppetWeb({
// profile: new Profile(),
// }))
Contact.puppet = new PuppetWeb({
profile: new Profile(),
}))
})
test('Contact smoke testing', async t => {
/* tslint:disable:variable-name */
......
......@@ -35,9 +35,9 @@ test('Puppet Web Event smoke testing', async t => {
})
try {
await pw.init()
await pw.start()
t.pass('should be inited')
await pw.quit()
await pw.stop()
t.pass('should be quited')
} catch (e) {
t.fail('exception: ' + e.message)
......
......@@ -31,7 +31,7 @@ const sinonTest = require('sinon-test')(sinon, {
// log.level('silly')
import {
config,
// config,
Contact,
Profile,
} from '../../'
......@@ -60,9 +60,10 @@ test('login/logout events', sinonTest(async function (t: test.Test) {
const pw = new PuppetWeb({ profile })
t.ok(pw, 'should instantiated a PuppetWeb')
config.puppetInstance(pw)
// config.puppetInstance(pw)
Contact.puppet = pw
await pw.init()
await pw.start()
t.pass('should be inited')
t.is(pw.logonoff() , false , 'should be not logined')
......@@ -83,7 +84,7 @@ test('login/logout events', sinonTest(async function (t: test.Test) {
t.is(await logoutPromise, 'logoutFired', 'should fire logout event')
t.is(pw.logonoff(), false, 'should be logouted')
await pw.quit()
await pw.stop()
profile.destroy()
} catch (e) {
t.fail(e)
......
......@@ -22,15 +22,19 @@
import * as test from 'blue-tape'
// import * as sinon from 'sinon'
import config from '../src/config'
// import config from '../src/config'
import Contact from '../src/contact'
import Message from '../src/message'
import Profile from '../src/profile'
import PuppetWeb from '../src/puppet-web'
import Room from '../src/room'
config.puppetInstance(new PuppetWeb({
// config.puppetInstance(new PuppetWeb({
// profile: new Profile(),
// }))
Room.puppet = new PuppetWeb({
profile: new Profile(),
}))
})
// Room.attach(new PuppetWeb())
// test('Room smoke testing', async t => {
......@@ -110,14 +114,15 @@ test('Room smoking test', async t => {
t.is(r.id, EXPECTED.id, 'should set id/UserName right')
let puppet
try {
puppet = config.puppetInstance()
puppet.getContact = mockContactGetter
} catch (err) {
puppet = { getContact: mockContactGetter }
config.puppetInstance(puppet)
}
// let puppet
// try {
// puppet = config.puppetInstance()
// puppet.getContact = mockContactGetter
// } catch (err) {
// puppet = { getContact: mockContactGetter }
// config.puppetInstance(puppet)
// }
Contact.puppet = Message.puppet = Room.puppet = { getContact: mockContactGetter } as any
await r.ready()
t.is(r.get('id') , EXPECTED.id, 'should set id/UserName')
......@@ -175,6 +180,10 @@ test('Room smoking test', async t => {
})
test('Room static method', async t => {
Room.puppet = new PuppetWeb({
profile: new Profile(),
})
try {
const result = await Room.find({ topic: 'xxx' })
t.is(result, null, `should return null if cannot find the room`)
......
......@@ -18,7 +18,7 @@
, "noImplicitThis": false
, "traceResolution": false
, "lib": [
"es2016"
"esnext"
, "dom"
]
}
......@@ -27,7 +27,8 @@
, "dist/"
]
, "include": [
"bin/*.ts"
"*.ts"
, "bin/*.ts"
, "scripts/**/*.ts"
, "examples/**/*.ts"
, "src/**/*.ts"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册