wechaty.ts 6.6 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1 2
/**
 *
Huan (李卓桓)'s avatar
comment  
Huan (李卓桓) 已提交
3
 * wechaty: Wechat for ChatBots.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
4
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
5
 * Class Wechaty
6
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
7 8 9 10
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */
11 12
import { EventEmitter }  from 'events'
import * as path         from 'path'
13
// const co            = require('co')
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
import * as fs          from'fs'

import Config       from './config'
import Contact      from './contact'
import Message      from './message'
import Puppet       from './puppet'
import PuppetWeb    from './puppet-web'
import UtilLib      from './util-lib'
import WechatyEvent from './wechaty-event'

import log          from './brolog-env'

type WechatySetting = {
  head?:       'chrome' | 'phantomjs'
  // port?:       number
  profile?:    string
  type?:       'web'
}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
32

33 34
class Wechaty extends EventEmitter {
  private static _instance: Wechaty
35

36
  public puppet: Puppet
37

38 39
  private inited:     boolean = false
  private npmVersion: string
40 41

  public uuid:        string
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
42

43
  constructor(private setting: WechatySetting) {
44
    super()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
45 46
    log.verbose('Wechaty', 'contructor()')

47
    if (Wechaty._instance instanceof Wechaty) {
48 49 50
      throw new Error('Wechaty must be singleton')
    }

51 52 53 54
    setting.type    = setting.type    || Config.puppet
    setting.head    = setting.head    || Config.head
    // setting.port    = setting.port    || Config.port
    setting.profile = setting.profile || Config.profile  // no profile, no session save/restore
55

56 57 58
    setting.profile  = /\.wechaty\.json$/i.test(setting.profile)
                        ? setting.profile
                        : setting.profile + '.wechaty.json'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
59

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
60
    this.npmVersion = require('../package.json').version
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
61

62
    this.uuid = UtilLib.guid()
63

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
64
    this.inited = false
65

66 67 68 69 70
    Wechaty._instance = this
  }

  public static instance() {
    return this._instance
71
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
72

73
  public toString() { return 'Class Wechaty(' + this.setting.type + ')'}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
74

75
  public version(forceNpm = false) {
76 77 78
    const dotGitPath  = path.join(__dirname, '..', '.git')
    const gitLogCmd   = 'git'
    const gitLogArgs  = ['log', '--oneline', '-1']
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
79

80 81
    if (!forceNpm) {
      try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
82 83
        /**
         * Synchronous version of fs.access().
84
         * This throws if any accessibility checks fail, and does nothing otherwise.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
85
         */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
86
        fs.accessSync(dotGitPath, fs.F_OK)
87 88 89 90 91 92 93 94 95 96

        const ss = require('child_process')
                    .spawnSync(gitLogCmd, gitLogArgs, { cwd:  __dirname })
        if (ss.status !== 0) {
          throw new Error(ss.error)
        }

        const revision = ss.stdout
                          .toString()
                          .trim()
97
        return `#git[${revision}]`
98
      } catch (e) { /* fall safe */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
99 100 101 102
        /**
         *  1. .git not exist
         *  2. git log fail
         */
103
        log.silly('Wechaty', 'version() test %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
104
      }
105 106 107
    }

    return this.npmVersion
108
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
109

110
  public user(): Contact { return this.puppet && this.puppet.user() }
111

112
  public reset(reason?: string) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
113
    log.verbose('Wechaty', 'reset() because %s', reason)
114
    return this.puppet.reset(reason)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
115
  }
116

117
  public init() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
118
    log.info('Wechaty', 'v%s initializing...' , this.version())
119 120 121
    log.verbose('Wechaty', 'puppet: %s'       , this.setting.type)
    log.verbose('Wechaty', 'head: %s'         , this.setting.head)
    log.verbose('Wechaty', 'profile: %s'      , this.setting.profile)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
122
    log.verbose('Wechaty', 'uuid: %s'         , this.uuid)
123

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
124 125 126 127 128
    if (this.inited) {
      log.error('Wechaty', 'init() already inited. return and do nothing.')
      return Promise.resolve(this)
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
129
    return co.call(this, function* () {
130
      yield this.initPuppet()
131

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
132
      this.inited = true
133
      return this // for chaining
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
134 135
    })
    .catch(e => {
136
      log.error('Wechaty', 'init() exception: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
137
      throw e
138
    })
139
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
140

141 142
  public on(event: string, listener: Function) {
    log.verbose('Wechaty', 'on(%s, %s)', event, typeof listener)
143

144 145
    const wrapListener = WechatyEvent.wrap.call(this, event, listener)
    super.on(event, wrapListener)
146

147
    return this
148 149
  }

150
  public initPuppet() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
151
    let puppet
152
    switch (this.setting.type) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
153
      case 'web':
154 155 156 157
        puppet = new PuppetWeb(
            this.setting.head
          , this.setting.profile
        )
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
158 159
        break
      default:
160
        throw new Error('Puppet unsupport(yet): ' + this.setting.type)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
161
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
162

163
    WechatyEvent.list().map(e => {
164 165 166 167 168
      // https://strongloop.com/strongblog/an-introduction-to-javascript-es6-arrow-functions/
      // We’ve lost () around the argument list when there’s just one argument (rest arguments are an exception, eg (...args) => ...)
      puppet.on(e, (...args) => {
        // this.emit(e, data)
        this.emit.apply(this, [e, ...args])
169
      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
170
    })
171 172 173 174 175 176 177 178
    /**
     * TODO: support more events:
     * 2. send
     * 3. reply
     * 4. quit
     * 5. ...
     */

179
    // set puppet before init, because we need this.puppet if we quit() before init() finish
180 181 182 183
    this.puppet     = puppet

    // set puppet instance to Wechaty Static variable, for using by Contact/Room/Message/FriendRequest etc.
    Config.puppetInstance(puppet)
184

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
185
    return puppet.init()
186 187
  }

188
  public quit() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
189
    log.verbose('Wechaty', 'quit()')
190

191 192
    if (!this.puppet) {
      log.warn('Wechaty', 'quit() without this.puppet')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
193 194 195
      return Promise.resolve()
    }

196
    const puppetBeforeDie = this.puppet
197 198
    this.puppet     = null
    Config.puppetInstance(null)
199 200
    this.inited = false

201
    return puppetBeforeDie.quit()
202 203 204 205 206
    .catch(e => {
      log.error('Wechaty', 'quit() exception: %s', e.message)
      throw e
    })
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
207

208
  public logout()  {
209
    return this.puppet.logout()
210 211 212 213
                      .catch(e => {
                        log.error('Wechaty', 'logout() exception: %s', e.message)
                        throw e
                      })
214
  }
215

216
  public self(message?: Message): boolean | Contact {
217 218
    return this.puppet.self(message)
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
219

220
  public send(message) {
221
    return this.puppet.send(message)
222 223 224 225
                      .catch(e => {
                        log.error('Wechaty', 'send() exception: %s', e.message)
                        throw e
                      })
226
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
227

228
  public reply(message, reply) {
229 230 231 232 233 234
    return this.puppet.reply(message, reply)
    .catch(e => {
      log.error('Wechaty', 'reply() exception: %s', e.message)
      throw e
    })
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
235

236
  public ding(data: string) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
237 238 239 240
    if (!this.puppet) {
      return Promise.reject(new Error('wechaty cant ding coz no puppet'))
    }

241
    return this.puppet.ding(data)
242 243 244 245
                      .catch(e => {
                        log.error('Wechaty', 'ding() exception: %s', e.message)
                        throw e
                      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
246
  }
247 248
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
249 250 251
/**
 * Expose `Wechaty`.
 */
252 253
// module.exports = Wechaty.default = Wechaty.Wechaty = Wechaty
export default Wechaty