io-client.ts 6.0 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1
/**
2
 *   Wechaty - https://github.com/wechaty/wechaty
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
3
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
4
 *   @copyright 2016-2018 Huan LI <zixia@zixia.net>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
5
 *
6 7 8
 *   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
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
9
 *
10 11 12 13 14 15 16
 *       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.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
17 18 19 20 21 22
 *
 */
/**
 * DO NOT use `require('../')` here!
 * because it will casue a LOOP require ERROR
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
23
import { StateSwitch }  from 'state-switch'
24

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
25
import {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
26 27 28
  PuppetServer,
  PuppetServerOptions,
}                       from 'wechaty-puppet-hostie'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
29

30
import { Message }      from './user'
31

32
import {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
33
  log,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
34
}                      from './config'
35 36
import { Io }           from './io'
import { Wechaty }      from './wechaty'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
37

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
38
export interface IoClientOptions {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
39 40
  token   : string,
  wechaty : Wechaty,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
41 42
}

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
45 46 47 48 49
  /**
   * Huan(20161026): keep io `null-able` or not?
   * Huan(202002): make it optional.
   */
  private io?: Io
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
50
  private puppetServer?: PuppetServer
51

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
52
  private state: StateSwitch
53

54
  constructor (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
55
    public options: IoClientOptions,
56
  ) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
57 58 59
    log.verbose('IoClient', 'constructor({token: %s})',
      options.token
    )
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
60

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
61
    this.state = new StateSwitch('IoClient', log)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
62
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
63

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
64 65 66
  private async startHostie () {
    log.verbose('IoClient', 'startHostie()')

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
67
    if (this.puppetServer) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
68 69 70
      throw new Error('hostie server exists')
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
71
    const options: PuppetServerOptions = {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
72 73 74 75
      endpoint : '0.0.0.0:8788',
      puppet   : this.options.wechaty.puppet,
      token    : this.options.token,
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
76 77
    this.puppetServer = new PuppetServer(options)
    await this.puppetServer.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
78 79 80 81 82
  }

  private async stopHostie () {
    log.verbose('IoClient', 'stopHostie()')

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
83
    if (!this.puppetServer) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
84 85
      throw new Error('hostie server does not exist')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
86

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
87 88
    await this.puppetServer.stop()
    this.puppetServer = undefined
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
89 90
  }

91
  public async start (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
92
    log.verbose('IoClient', 'start()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
93

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
94
    if (this.state.pending()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
95 96
      log.warn('IoClient', 'start() with a pending state, not the time')
      const e = new Error('state.pending() when start()')
97
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
98 99
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
100
    this.state.on('pending')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
101

102
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
103
      await this.hookWechaty(this.options.wechaty)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
104 105

      await this.startIo()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
106 107

      await this.options.wechaty.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
108

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
109
      await this.startHostie()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
110

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
111
      this.state.on(true)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
112

113
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
114
      log.error('IoClient', 'start() exception: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
115
      this.state.off(true)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
116
      throw e
117
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
118 119
  }

120
  private async hookWechaty (wechaty: Wechaty): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
121
    log.verbose('IoClient', 'initWechaty()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
122

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
123 124
    if (this.state.off()) {
      const e = new Error('state.off() is true, skipped')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
125
      log.warn('IoClient', 'initWechaty() %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
126
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
127 128
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
129
    wechaty
130 131 132 133
      .on('login',    user => log.info('IoClient', `${user.name()} logined`))
      .on('logout',   user => log.info('IoClient', `${user.name()} logouted`))
      .on('scan',     (url, code) => log.info('IoClient', `[${code}] ${url}`))
      .on('message',  msg => this.onMessage(msg))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
134 135
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
136 137
  private async startIo (): Promise<void> {
    log.verbose('IoClient', 'startIo() with token %s', this.options.token)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
138

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
139
    if (this.state.off()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
140
      const e = new Error('startIo() state.off() is true, skipped')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
141
      log.warn('IoClient', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
142
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
143 144
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
145 146 147 148 149 150 151 152 153
    if (this.io) {
      throw new Error('io exists')
    }

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
154
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
155
      await this.io.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
156
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
157
      log.verbose('IoClient', 'startIo() init fail: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
158 159
      throw e
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
160 161
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
162 163 164 165 166 167 168 169 170 171 172 173
  private async stopIo () {
    log.verbose('IoClient', 'stopIo()')

    if (!this.io) {
      log.warn('IoClient', 'stopIo() io does not exist')
      return
    }

    await this.io.stop()
    this.io = undefined
  }

174 175
  private async onMessage (msg: Message) {
    log.verbose('IoClient', 'onMessage(%s)', msg)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
176

177 178 179 180
    // const from = m.from()
    // const to = m.to()
    // const content = m.toString()
    // const room = m.room()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
181

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
182
    // log.info('Bot', '%s<%s>:%s'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
183
    //               , (room ? '['+room.topic()+']' : '')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
184 185 186 187
    //               , from.name()
    //               , m.toStringDigest()
    //         )

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
188 189 190 191
    // if (/^wechaty|chatie|botie/i.test(m.text()) && !m.self()) {
    //   await m.say('https://www.chatie.io')
    //     .then(_ => log.info('Bot', 'REPLIED to magic word "chatie"'))
    // }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
192
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
193

194
  public async stop (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
195
    log.verbose('IoClient', 'stop()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
196

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
197
    this.state.off('pending')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
198

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
199 200
    await this.stopIo()
    await this.stopHostie()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
201
    await this.options.wechaty.stop()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
202

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
203
    this.state.off(true)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
204 205

    // XXX 20161026
206
    // this.io = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
207 208
  }

209
  public async restart (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
210
    log.verbose('IoClient', 'restart()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
211

212 213 214 215
    try {
      await this.stop()
      await this.start()
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
216
      log.error('IoClient', 'restart() exception %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
217
      throw e
218
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
219 220
  }

221
  public async quit (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
222
    log.verbose('IoClient', 'quit()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
223

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
224 225 226
    if (this.state.off() === 'pending') {
      log.warn('IoClient', 'quit() with state.off() = `pending`, skipped')
      throw new Error('quit() with state.off() = `pending`')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
227 228
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
229
    this.state.off('pending')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
230

231
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
232
      if (this.options.wechaty) {
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
233
        await this.options.wechaty.stop()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
234
        // this.wechaty = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
235
      } else { log.warn('IoClient', 'quit() no this.wechaty') }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
236 237

      if (this.io) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
238
        await this.io.stop()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
239
        // this.io = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
240
      } else { log.warn('IoClient', 'quit() no this.io') }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
241

242
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
243
      log.error('IoClient', 'exception: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
244
      throw e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
245 246
    } finally {
      this.state.off(true)
247
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
248
  }
249

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