io-client.ts 7.9 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class IoClient
 * http://www.wechaty.io
 *
 * Licenst: ISC
 * https://github.com/wechaty/wechaty
 *
 */

/**
 * DO NOT use `require('../')` here!
 * because it will casue a LOOP require ERROR
 */
17 18
// import Brolog   from 'brolog'

19 20 21 22 23
import { Config }       from './config'
import { Io }           from './io'
import { StateMonitor } from './state-monitor'
import { Wechaty }      from './wechaty'
import { Brolog }       from './brolog-env'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
24

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
25
export class IoClient {
26

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
27 28
  private wechaty: Wechaty
  private io: Io // XXX keep io `null-able` or not? 20161026
29

30 31
  private state = new StateMonitor<'online', 'offline'>('IoClient', 'offline')

32 33
  constructor(
      private token: string = Config.token || Config.DEFAULT_TOKEN
34
    , private log: any = new Brolog()
35
  ) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
36 37 38 39
    if (!log) {
      const e = new Error('constructor() log(npmlog/brolog) must be set')
      throw e
    }
40
    this.log.verbose('IoClient', 'constructor() with token: %s', token)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
41

42
    if (!token) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
43 44 45 46
      const e = new Error('constructor() token must be set')
      this.log.error('IoClient', e.message)
      throw e
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
47 48 49 50 51 52 53 54 55 56

    this.wechaty = Wechaty.instance({
      profile: token
    })

    this.io = new Io({
      wechaty: this.wechaty
      , token: this.token
    })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
57 58
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
59
  public async init(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
60 61
    this.log.verbose('IoClient', 'init()')

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
62 63
    // if (/connecting|disconnecting/.test(this.currentState())) {
    if (this.state.inprocess()) {
64 65 66
      const e = new Error('state.inprocess(), skip init')
      this.log.warn('IoClient', 'init() with %s', e.message)
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
67 68
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
69 70 71 72
    // this.targetState('connected')
    // this.currentState('connecting')
    this.state.target('online')
    this.state.current('online', false)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
73

74
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
75 76
      await this.initIo()
      await this.initWechaty()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
77 78
      // this.currentState('connected')
      this.state.current('online')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
79
      return
80
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
81
      this.log.error('IoClient', 'init() exception: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
82 83
      // this.currentState('disconnected')
      this.state.current('offline')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
84
      throw e
85
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
86 87
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
88
  private async initWechaty(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
89 90
    this.log.verbose('IoClient', 'initWechaty()')

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
91 92
    // if (this.targetState() !== 'connected') {
    if (this.state.target() !== 'online') {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
93 94 95
      const e = new Error('state.target() is not `online`, skipped')
      this.log.warn('IoClient', 'initWechaty() %s', e.message)
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
96 97 98 99
    }

    const wechaty = this.wechaty

100 101 102 103
    if (!wechaty) {
      throw new Error('no Wechaty')
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
104
    wechaty
105 106 107 108
    .on('login'	     , user => this.log.info('IoClient', `${user.name()} logined`))
    .on('logout'	   , user => this.log.info('IoClient', `${user.name()} logouted`))
    .on('scan', (url, code) => this.log.info('IoClient', `[${code}] ${url}`))
    .on('message' , message => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
109 110 111 112 113
      message.ready()
            .then(this.onMessage.bind(this))
            .catch(e => this.log.error('IoClient', 'message.ready() %s' , e))
    })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
114
    await wechaty.init()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
115
                  .then(_ => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
116
                    this.log.verbose('IoClient', 'wechaty.init() done')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
117
                    return wechaty
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
118 119 120 121 122 123
                  })
                  .catch(e => {
                    this.log.error('IoClient', 'init() init fail: %s', e)
                    wechaty.quit()
                    throw e
                  })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
124
    return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
125 126
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
127
  private async initIo(): Promise<void> {
128
    this.log.verbose('IoClient', 'initIo() with token %s', this.token)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
129

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
130 131
    // if (this.targetState() !== 'connected') {
    if (this.state.target() !== 'online') {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
132 133 134
      const e = new Error('initIo() targetState is not `connected`, skipped')
      this.log.warn('IoClient', e.message)
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
135 136
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
137 138 139 140
    // const io = this.io = new Io({
    //   wechaty: this.wechaty
    //   , token: this.token
    // })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
141

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
142
    const io = this.io
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
143

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
144
    await io.init()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
145
            .catch(e => {
146
              this.log.verbose('IoClient', 'initIo() init fail: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
147 148
              throw e
            })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
149
    return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
150 151
  }

152
  public initWeb(port = Config.httpPort) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
153 154 155 156 157 158 159 160
//    if (process.env.DYNO) {
//    }
    const app = require('express')()

    app.get('/', function (req, res) {
      res.send('Wechaty IO Bot Alive!')
    })

161
    return new Promise((resolve) => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
162 163 164 165 166 167 168 169 170
      app.listen(port, () => {
        this.log.verbose('IoClient', 'initWeb() Wechaty IO Bot listening on port ' + port + '!')

        return resolve(this)

      })
    })
  }

171 172 173 174 175
  private onMessage(m) {
    // const from = m.from()
    // const to = m.to()
    // const content = m.toString()
    // const room = m.room()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
176

177
    // this.log.info('Bot', '%s<%s>:%s'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
178
    //               , (room ? '['+room.topic()+']' : '')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
179 180 181
    //               , from.name()
    //               , m.toStringDigest()
    //         )
182
    const puppet = Config.puppetInstance()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
183

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
184
    if (/^wechaty|botie/i.test(m.content()) && !m.self()) {
185
      puppet.reply(m, 'https://www.wechaty.io')
186
        .then(_ => this.log.info('Bot', 'REPLIED to magic word "wechaty"'))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
187 188
    }
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
189

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
190
  public async start(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
191 192
    this.log.verbose('IoClient', 'start()')

193 194 195
    if (!this.wechaty) {
      return this.init()
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
196

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
197 198
    // if (/connecting|disconnecting/.test(this.currentState())) {
    if (this.state.inprocess()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
199 200 201
      this.log.warn('IoClient', 'start() with a pending state, not the time')
      return Promise.reject('pending')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
202

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
203 204 205 206
    // this.targetState('connected')
    // this.currentState('connecting')
    this.state.target('online')
    this.state.current('online', false)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
207

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
208 209 210 211 212 213 214 215 216 217 218 219 220
    try {
      await this.initIo()
              // .then(() => {
              //   // this.io = io
              //   // this.currentState('connected')
      this.state.current('online')
      return
    } catch (e) {
      this.log.error('IoClient', 'start() exception: %s', e.message)
      // this.currentState('disconnected')
      this.state.current('offline')
      throw e
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
221 222
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
223
  public async stop(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
224
    this.log.verbose('IoClient', 'stop()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
225 226 227 228
    // this.targetState('disconnected')
    // this.currentState('disconnecting')
    this.state.target('offline')
    this.state.current('offline', false)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
229

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
230
    // XXX
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
231 232
    if (!this.io) {
      this.log.warn('IoClient', 'stop() without this.io')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
233 234
      // this.currentState('connected')
      this.state.current('online')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
235 236 237
      return Promise.resolve()
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
238
    await this.io.quit()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
239
                    // .then(_ => this.currentState('disconnected'))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
240 241 242 243
                    // .then(_ => this.state.current('offline'))
    this.state.current('offline')

    // XXX 20161026
244
    // this.io = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
245
    return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
246 247
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
248
  public async restart(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
249 250
    this.log.verbose('IoClient', 'restart()')

251 252 253
    try {
      await this.stop()
      await this.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
254
      return
255
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
256 257
      this.log.error('IoClient', 'restart() exception %s', e.message)
      throw e
258
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
259 260
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
261
  public async quit(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
262
    this.log.verbose('IoClient', 'quit()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
263

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
264 265
    // if (this.currentState() === 'disconnecting') {
    if (this.state.current() === 'offline' && this.state.inprocess()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
266 267 268 269
      this.log.warn('IoClient', 'quit() with currentState() = `disconnecting`, skipped')
      return Promise.reject('quit() with currentState = `disconnecting`')
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
270 271 272 273
    // this.targetState('disconnected')
    // this.currentState('disconnecting')
    this.state.target('offline')
    this.state.current('offline', false)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
274

275
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
276
      if (this.wechaty) {
277
        await this.wechaty.quit()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
278
        // this.wechaty = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
279 280 281
      } else { this.log.warn('IoClient', 'quit() no this.wechaty') }

      if (this.io) {
282
        await this.io.quit()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
283
        // this.io = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
284 285
      } else { this.log.warn('IoClient', 'quit() no this.io') }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
286 287 288
      // this.currentState('disconnected')
      this.state.current('offline')

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
289 290
      return

291
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
292
      this.log.error('IoClient', 'exception: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
293

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
294
      // XXX fail safe?
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
295 296
      // this.currentState('disconnected')
      this.state.current('offline')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
297

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
298
      throw e
299
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
300
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
301
}