wechaty-bro.js 27.8 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1
/**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
2 3 4
 *
 * Wechaty - Wechat for Bot, and human who talk to bot.
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
5
 * Class PuppetWebInjectio
6
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
7
 * Inject this js code to browser,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
8 9
 * in order to interactive with wechat web program.
 *
10 11
 * Licenst: ISC
 * https://github.com/wechaty/wechaty
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
12
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *
 * ATTENTION:
 *
 * JAVASCRIPT IN THIS FILE
 * IS RUN INSIDE
 *
 *    BROWSER
 *
 * INSTEAD OF
 *
 *    NODE
 *
 * read more about this in puppet-web-bridge.js
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
27
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
28 29 30

/*global angular*/

31
(function(port) {
32

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
33
  function init() {
34
    if (!angularIsReady()) {
35 36 37
      retObj.code = 503 // 503 SERVICE UNAVAILABLE https://httpstatuses.com/503
      retObj.message = 'init() without a ready angular env'
      return retObj
38 39
    }

40 41 42 43
    if (!initClog(false)) { // make console.log work (wxapp disabled the console.log)
      retObj.code = 503 // 503 Service Unavailable http://www.restapitutorial.com/httpstatuscodes.html
      retObj.message = 'initClog fail'
      return retObj
44 45
    }

46
    if (WechatyBro.vars.initStatus === true) {
47 48 49 50
      log('WechatyBro.init() called twice: already inited')
      retObj.code = 304 // 304 Not Modified https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
      retObj.message = 'init() already inited before. returned with do nothing'
      return retObj
51 52
    }

53
    clog('init on port:' + port)
54 55

    if (MMCgiLogined()) {
56
      login('page refresh')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
57
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
58

59 60 61
    glueToAngular()
    connectSocket()
    hookEvents()
62

63
    checkScan()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
64

65
    heartBeat(true)
66

67 68
    clog('inited!. ;-D')
    WechatyBro.vars.initStatus = true
69

70 71 72
    retObj.code = 200
    retObj.message = 'WechatyBro Init Succ on port: ' + port
    return retObj
73 74
  }

75 76 77 78
  /**
  * Log to console
  * http://stackoverflow.com/a/7089553/1123955
  */
79 80
  function initClog(enabled) {
    if (!enabled) {
81
      return true
82 83
    }

84
    if (WechatyBro.vars.iframe) {
85 86
      log('initClog() again? there is already a iframe')
      return true
87 88
    }

89
    if (!document.body) { // Javascript Error Null is not an Object
90
      // log('initClog() not ready because document.body not ready')
91
      return false
92 93
    }

94
    var i = document.createElement('iframe')
95
    if (!i) {
96
      // log('initClog() not ready because document.createElement fail')
97
      return false
98 99
    }

100
    // slog('initClog got iframe element')
101 102 103
    i.style.display = 'none'
    document.body.appendChild(i)
    WechatyBro.vars.iframe = i
104
    // if (!WechatyBro.vars.iframe) {
105 106 107
    //   throw new Error('iframe gone after appendChild, WTF???')
    // }
    // slog('initClog done')
108
    return true
109 110 111
  }

  function clog(s) {
112
    if (!WechatyBro.vars.iframe) {
113
      // throw new Error('clog() iframe not found when be invocked')
114
      return
115 116
    }

117 118
    var d = new Date()
    s = d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() + ' <WechatyBro> ' + s
119

120
    WechatyBro.vars.iframe.contentWindow.console.log(s)
121 122
  }

123 124
  function slog(msg)  { WechatyBro.emit('log', msg) }
  function log(s)     { clog(s); slog(s) }
125

126
  /**
127
   * WechatyBro.emit, will save event & data when there's no socket io connection to prevent event lost
128 129 130
   * NOTICE: only clog available here, because slog & log will call emit, death loop
   */
  function emit(event, data) {
131
    var eventsBuf = WechatyBro.vars.eventsBuf
132
    if (!eventsBuf.map) {
133
      throw new Error('WechatyBro.vars.eventsBuf must be a Array')
134 135
    }
    if (event) {
136
      eventsBuf.unshift([event, data])
137
    }
138
    var socket = WechatyBro.vars.socket
139
    if (!socket) {
140 141
      clog('WechatyBro.vars.socket not ready')
      return setTimeout(emit, 1000) // resent eventsBuf after 1000ms
142
    }
143
    var bufLen = eventsBuf.length
144
    if (bufLen) {
145
      if (bufLen > 1) { clog('WechatyBro.vars.eventsBuf has ' + bufLen + ' unsend events') }
146 147

      while (eventsBuf.length) {
148 149 150 151 152
        var eventData = eventsBuf.pop()
        if (eventData && eventData.map && eventData.length===2) {
          clog('emiting ' + eventData[0])
          socket.emit(eventData[0], eventData[1])
        } else { clog('WechatyBro.emit() got invalid eventData: ' + eventData[0] + ', ' + eventData[1] + ', length: ' + eventData.length) }
153 154
      }

155
      if (bufLen > 1) { clog('WechatyBro.vars.eventsBuf[' + bufLen + '] all sent') }
156 157 158
    }
  }

159 160 161 162 163
  /**
  *
  * Functions that Glued with AngularJS
  *
  */
164
  function MMCgiLogined() { return !!(window.MMCgi && window.MMCgi.isLogin) }
165

166
  function angularIsReady() {
167
    // don't log inside, because we has not yet init clog here.
168 169 170 171 172 173
    return !!(
      (typeof angular) !== 'undefined'
      && angular.element
      && angular.element('body')
      && angular.element(document).injector()
    )
174 175
  }

176
  function heartBeat(firstTime) {
177
    var TIMEOUT = 15000 // 15s
178
    if (firstTime && WechatyBro.vars.heartBeatTimmer) {
179 180
      WechatyBro.log('heartBeat timer exist when 1st time is true? return for do nothing')
      return
181
    }
182 183 184
    WechatyBro.emit('ding', 'heartbeat@browser')
    WechatyBro.vars.heartBeatTimmer = setTimeout(heartBeat, TIMEOUT)
    return TIMEOUT
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
185 186
  }

187
  function glueToAngular() {
188
    var injector  = angular.element(document).injector()
189
    if (!injector) {
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
      throw new Error('glueToAngular cant get injector(right now)')
    }

    var accountFactory  = injector.get('accountFactory')
    var appFactory      = injector.get('appFactory')
    var chatroomFactory = injector.get('chatroomFactory')
    var chatFactory     = injector.get('chatFactory')
    var contactFactory  = injector.get('contactFactory')
    var confFactory     = injector.get('confFactory')
    var emojiFactory    = injector.get('emojiFactory')
    var loginFactory    = injector.get('loginFactory')

    var http            = injector.get('$http')
    var state           = injector.get('$state')
    var mmHttp          = injector.get('mmHttp')

    var appScope    = angular.element('[ng-controller="appController"]').scope()
    var rootScope   = injector.get('$rootScope')
    var loginScope  = angular.element('[ng-controller="loginController"]').scope()

/*
    // method 1
    appFactory.syncOrig = appFactory.sync
    appFactory.syncCheckOrig = appFactory.syncCheck
    appFactory.sync = function() { WechatyBro.log('appFactory.sync() !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); return appFactory.syncOrig(arguments) }
    appFactory.syncCheck = function() { WechatyBro.log('appFactory.syncCheck() !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); return appFactory.syncCheckOrig(arguments) }

    // method 2
    $.ajaxOrig = $.ajax
    $.ajax = function() { WechatyBro.log('$.ajax() !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); return $.ajaxOrig(arguments) }

    $.ajax({
      url: "https://wx.qq.com/zh_CN/htmledition/v2/images/webwxgeticon.jpg"
      , type: "GET"
    }).done(function (response) {
      alert("success");
    })

    // method 3 - mmHttp
    mmHttp.getOrig = mmHttp.get
    mmHttp.get = function() { WechatyBro.log('mmHttp.get() !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); return mmHttp.getOrig(arguments) }
*/
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
232

233 234 235 236
    /**
     * generate $scope with a contoller (as it is not assigned in html staticly)
     * https://github.com/angular/angular.js/blob/a4e60cb6970d8b6fa9e0af4b9f881ee3ba7fdc99/test/ng/controllerSpec.js#L24
     */
237 238
    var contentChatScope  = rootScope.$new()
    injector.get('$controller')('contentChatController', {$scope: contentChatScope })
239

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
240
    // get all we need from wx in browser(angularjs)
241
    WechatyBro.glue = {
242 243 244 245
      injector:       injector
      , http:         http
      , mmHttp:       mmHttp
      , state:        state
246

247 248 249 250 251 252 253
      , accountFactory:  accountFactory
      , chatroomFactory: chatroomFactory
      , chatFactory:     chatFactory
      , confFactory:     confFactory
      , contactFactory:  contactFactory
      , emojiFactory:    emojiFactory
      , loginFactory:    loginFactory
254

255 256 257
      , rootScope:    rootScope
      , appScope:     appScope
      , loginScope:   loginScope
258

259 260
      , contentChatScope: contentChatScope
    }
261

262
    return true
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
263 264
  }

265
  function checkScan() {
266
    clog('checkScan()')
267
    if (isLogin()) {
268
      log('checkScan() - already login, no more check, and return(only)') //but I will emit a login event')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
269
      // login('checkScan found already login')
270
      return
271
    }
272
    if (!WechatyBro.glue.loginScope) {
273 274 275
      log('checkScan() - loginScope disappeared, no more check')
      login('loginScope disappeared')
      return
276 277
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
278 279
    // loginScope.code:
    // 0:   显示二维码
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
280
    // 408: 未确认(显示二维码后30秒触发)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
281 282
    // 201: 扫描,未确认
    // 200: 登录成功
283 284
    var code  = +WechatyBro.glue.loginScope.code
    var url   =  WechatyBro.glue.loginScope.qrcodeUrl
285
    if (url && code !== WechatyBro.vars.scanCode) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
286

287 288 289 290 291
      log('checkScan() - code change detected: from '
        + WechatyBro.vars.scanCode
        + ' to '
        + code
      )
292
      WechatyBro.emit('scan', {
293 294 295 296
        code:   code
        , url:  url
      })
      WechatyBro.vars.scanCode = code
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
297
    }
298 299

    if (code === 200) {
300
      login('scan code 200')
301
    } else {
302
      setTimeout(checkScan, 1000)
303
    }
304
    return
305 306
  }

307
  function isLogin() { return !!WechatyBro.vars.loginStatus }
308
  function login(data) {
309
    log('login(' + data + ')')
310
    if (!WechatyBro.vars.loginStatus) {
311
      WechatyBro.vars.loginStatus = true
312
    }
313
    WechatyBro.emit('login', data)
314 315
  }
  function logout(data) {
316 317
    log('logout(' + data + ')')
    WechatyBro.vars.loginStatus = false
318
    // WechatyBro.emit('logout', data)
319
    if (WechatyBro.glue.loginFactory) {
320
      WechatyBro.glue.loginFactory.loginout()
321
    }
322
    checkScan()
323 324
  }
  function quit() {
325 326
    log('quit()')
    logout('quit()')
327
    if (WechatyBro.vars.socket) {
328 329
      WechatyBro.vars.socket.close()
      WechatyBro.vars.socket = null
330
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
331
  }
332

333
  function ding(data) { log('recv ding'); return data || 'dong' }
334
  function hookEvents() {
335 336
    var rootScope = WechatyBro.glue.rootScope
    var appScope = WechatyBro.glue.appScope
337
    if (!rootScope || !appScope) {
338 339
      log('hookEvents() no rootScope')
      return false
340
    }
341 342 343
    rootScope.$on('message:add:success', function(event, data) {
      if (!isLogin()) { // in case of we missed the pageInit event
        login('by event[message:add:success]')
344
      }
345 346
      WechatyBro.emit('message', data)
    })
347
    rootScope.$on('root:pageInit:success'), function (event, data) {
348 349
      login('by event[root:pageInit:success]')
    }
350 351 352 353
    // newLoginPage seems not stand for a user login action
    // appScope.$on("newLoginPage", function(event, data) {
    //   login('by event[newLoginPage]')
    // })
354
    window.addEventListener('unload', function(e) {
355
      // XXX only 1 event can be emitted here???
356
      WechatyBro.emit('unload', String(e))
357 358 359 360
      // WechatyBro.slog('emit unload')
      // WechatyBro.emit('logout', e)
      // WechatyBro.slog('emit logout')
      // WechatyBro.slog('emit logout&unload over')
361 362
    })
    return true
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
363
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
364
  function connectSocket() {
365
    log('connectSocket()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
366
    if (typeof io !== 'function') {
367
      log('connectSocket: io not found. loading lib...')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
368
      // http://stackoverflow.com/a/3248500/1123955
369 370 371 372 373 374 375 376
      var script = document.createElement('script')
      script.onload = function() {
        log('socket io lib loaded.')
        connectSocket()
      }
      script.src = '//cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js'
      document.getElementsByTagName('head')[0].appendChild(script)
      return // wait to be called via script.onload()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
377
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
378

379
    /*global io*/ // WechatyBro global variable: socket
380
    var socket  = WechatyBro.vars.socket = io.connect('wss://127.0.0.1:' + port/*, {transports: ['websocket']}*/)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
381

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
382 383
    // ding -> dong. for test & live check purpose
    // ping/pong are reserved by socket.io https://github.com/socketio/socket.io/issues/2414
384 385 386 387
    socket.on('ding', function(data) {
      log('received socket io event: ding(' + data + '). emit dong...')
      socket.emit('dong', data)
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
388

389 390
    socket.on('connect'   , function(e) { clog('connected to server:' + e) })
    socket.on('disconnect', function(e) { clog('socket disconnect:'   + e) })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
391
  }
392

393
  /**
394 395
   *
   * Help Functions which Proxy to WXAPP AngularJS Scope & Factory
396 397
   *  getMsgImg(message.MsgId,'slave')
   *  getMsgImg(message.MsgId,'big',message)
398
   */
399
  function getMsgImg(id, type, message) {
400
    var contentChatScope = WechatyBro.glue.contentChatScope
401
    if (!contentChatScope) {
402
      throw new Error('getMsgImg() contentChatScope not found')
403
    }
404 405 406
    var location = window.location.href.replace(/\/$/, '')
    var path = contentChatScope.getMsgImg(id, type, message)
    return location + path
407 408 409
  }

  function getMsgEmoticon(id) {
410
    var chatFactory = WechatyBro.glue.chatFactory
411

412 413
    var message = chatFactory.getMsg(id)
    return message.MMPreviewSrc || getMsgImg(message.MsgId,'big',message)  || message.MMThumbSrc
414 415 416
  }

  function getMsgVideo(id) {
417
    var contentChatScope = WechatyBro.glue.contentChatScope
418
    if (!contentChatScope) {
419
      throw new Error('getMsgVideo() contentChatScope not found')
420
    }
421 422 423
    var location = window.location.href.replace(/\/$/, '')
    var path = contentChatScope.getMsgVideo(id)
    return location + path
424 425 426 427 428 429
  }

  /**
   * from playVoice()
   */
  function getMsgVoice(id) {
430 431
    var confFactory     = WechatyBro.glue.confFactory
    var accountFactory  = WechatyBro.glue.accountFactory
432

433 434 435
    var location = window.location.href.replace(/\/$/, '')
    var path = confFactory.API_webwxgetvoice + "?msgid=" + id + "&skey=" + accountFactory.getSkey()
    return location + path
436
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
437

438
  function getMsgPublicLinkImg(id) {
439 440 441
    var location = window.location.href.replace(/\/$/, '')
    var path = '/cgi-bin/mmwebwx-bin/webwxgetpubliclinkimg?url=xxx&msgid=' + id + '&pictype=location'
    return location + path
442 443
  }

M
Mukaiu 已提交
444 445 446
  function getBaseRequest() {
    var accountFactory = WechatyBro.glue.accountFactory
    var BaseRequest = accountFactory.getBaseRequest()
447

M
Mukaiu 已提交
448 449 450 451 452 453 454 455
    return JSON.stringify(BaseRequest)
  }

  function getPassticket() {
    var accountFactory = WechatyBro.glue.accountFactory
    return accountFactory.getPassticket()
  }

M
Mukaiu 已提交
456 457 458 459 460
  function getUploadMediaUrl() {
    var confFactory = WechatyBro.glue.confFactory
    return confFactory.API_webwxuploadmedia
  }

M
Mukaiu 已提交
461 462 463 464 465
  function sendMedia(ToUserName, MediaId,Type) {
    var chatFactory = WechatyBro.glue.chatFactory
    var confFactory = WechatyBro.glue.confFactory

    if (!chatFactory || !confFactory) {
466
      log('sendMedia() chatFactory or confFactory not exist.')
M
Mukaiu 已提交
467 468 469
      return false
    }

470 471 472 473 474 475 476 477 478 479 480 481 482
    try {
      var m = chatFactory.createMessage({
        ToUserName: ToUserName
        , MediaId: MediaId
        , MsgType: Type
      })
      chatFactory.appendMessage(m)
      chatFactory.sendMessage(m)
    } catch (e) {
      log('sendMedia() exception: ' + e.message)
      return false
    }
    return true
M
Mukaiu 已提交
483 484
  }

485
  function send(ToUserName, Content) {
486 487
    var chatFactory = WechatyBro.glue.chatFactory
    var confFactory = WechatyBro.glue.confFactory
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
488

489
    if (!chatFactory || !confFactory) {
490 491
      log('send() chatFactory or confFactory not exist.')
      return false
492
    }
493 494 495 496 497 498 499 500 501 502 503 504 505
    try {
      var m = chatFactory.createMessage({
        ToUserName: ToUserName
        , Content: Content
        , MsgType: confFactory.MSGTYPE_TEXT
      })
      chatFactory.appendMessage(m)
      chatFactory.sendMessage(m)
    } catch (e) {
      log('send() exception: ' + e.message)
      return false
    }
    return true
506
  }
507
  function getContact(id) {
508
    var contactFactory = WechatyBro.glue.contactFactory
509
    if (!contactFactory) {
510 511
      log('contactFactory not inited')
      return null
512
    }
513 514
    var c = contactFactory.getContact(id)
    var contactWithoutFunction = {}
515 516 517

    if (c) {
      if (c.isContact) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
518
        // extend rawObj to identify `stranger`
519
        c.stranger = !(c.isContact())
520 521
      }

522
      Object.keys(c).forEach(function(k) {
523
        if (typeof c[k] !== 'function') {
524
          contactWithoutFunction[k] = c[k]
525
        }
526 527
      })

528 529 530 531 532 533
    } else {

      /**
       * when `id` does not exist in _contact Array, maybe it is belongs to a stranger in a room.
       * try to find in room's member list for this `id`, and return the contact info, if any.
       */
534 535 536 537 538 539 540 541 542 543 544 545 546
      c = Object.keys(_contacts)
                .filter(function(id) { return id.match(/^@@/) })    // only search in room
                .map(function(id) { return _contacts[id] })         // map to room array
                .filter(function(r) { return r.MemberList.length }) // get rid of room without member list
                .filter(function(r) { return r.MemberList
                                              .filter(function(m) { return m.UserName === id })
                                              .length
                })
                .map(function(c) { return c.MemberList
                                           .filter(function(m) { return m.UserName === id })
                                           [0]
                })
                [0]
547 548

      if (c) {
549
        c.stranger = true
550

551
        Object.keys(c).forEach(function(k) {
552
          if (typeof c[k] !== 'function') {
553
            contactWithoutFunction[k] = c[k]
554
          }
555
        })
556
      }
557

558
    }
559

560
    return contactWithoutFunction
561
  }
562

563
  function getUserName() {
564 565 566 567
    var accountFactory = WechatyBro.glue.accountFactory
    return accountFactory
            ? accountFactory.getUserName()
            : null
568 569
  }

570
  function contactFindAsync(filterFunction) {
571
    var callback = arguments[arguments.length - 1]
572 573
    if (typeof callback !== 'function') {
      // here we should in sync mode, because there's no callback
574
      throw new Error('async method need to be called via webdriver.executeAsyncScript')
575 576
    }

577
    var contactFactory = WechatyBro.glue.contactFactory
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
578

579
    var match
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
580
    if (!filterFunction) {
581
      match = function() { return true }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
582
    } else {
583
      match = eval(filterFunction)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
584
    }
585

586 587 588
    retryFind(0)

    return
589 590

    // retry 3 times, sleep 300ms between each time
591
    function retryFind(attempt) {
592
      attempt = attempt || 0;
593

594 595
      var contactList = contactFactory
                          .getAllFriendContact()
596
                          .filter(function(c) { return match(c) })
597
                          .map(function(c) { return c.UserName })
598 599

      if (contactList && contactList.length) {
600
        callback(contactList)
601
      } else if (attempt > 3) {
602
        callback([])
603
      } else {
604 605
        attempt++
        setTimeout(function() { return retryFind(attempt) }, 300)
606
      }
607

608
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
609 610
  }

611
  function contactRemarkAsync(UserName, remark) {
612
    var callback = arguments[arguments.length - 1]
613 614
    if (typeof callback !== 'function') {
      // here we should in sync mode, because there's no callback
615
      throw new Error('async method need to be called via webdriver.executeAsyncScript')
616 617
    }

618
    if (remark === null || remark === undefined) {
619 620 621
      remark = ''
    }

622 623 624 625 626
    var contact = _contacts[UserName]
    if (!contact) {
      throw new Error('contactRemarkAsync() can not found UserName ' + UserName)
    }

627 628 629 630
    var accountFactory  = WechatyBro.glue.accountFactory
    var confFactory     = WechatyBro.glue.confFactory
    var emojiFactory    = WechatyBro.glue.emojiFactory
    var mmHttp          = WechatyBro.glue.mmHttp
631 632 633 634 635 636 637 638 639 640 641 642 643 644

    mmHttp({
      method: "POST",
      url: confFactory.API_webwxoplog,
      data: angular.extend({
        UserName: UserName,
        CmdId: confFactory.oplogCmdId.MODREMARKNAME,
        RemarkName: emojiFactory.formatHTMLToSend(remark)
      }, accountFactory.getBaseRequest()),
      MMRetry: {
        count: 3,
        timeout: 1e4,
        serial: !0
      }
645
    }).success(function() {
646
      contact.RemarkName = remark
647 648 649 650
      callback(true)
    }).error(function() {
      callback(false)
    })
651 652
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
653
  function roomFind(filterFunction) {
654
    var contactFactory = WechatyBro.glue.contactFactory
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
655

656
    var match
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
657
    if (!filterFunction) {
658
      match = function() { return true }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
659
    } else {
660
      match = eval(filterFunction)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
661 662
    }
    // log(match.toString())
663 664 665
    return contactFactory.getAllChatroomContact()
                         .filter(function(r) { return match(r.NickName) })
                         .map(function(r) { return r.UserName })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
666 667 668
  }

  function roomDelMember(ChatRoomName, UserName) {
669 670
    var chatroomFactory = WechatyBro.glue.chatroomFactory
    return chatroomFactory.delMember(ChatRoomName, UserName)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
671 672
  }

Huan (李卓桓)'s avatar
#119  
Huan (李卓桓) 已提交
673 674 675 676 677 678 679
  function roomAddMemberAsync(ChatRoomName, UserName) {
    var callback = arguments[arguments.length - 1]
    if (typeof callback !== 'function') {
      // here we should in sync mode, because there's no callback
      throw new Error('async method need to be called via webdriver.executeAsyncScript')
    }

680
    var chatroomFactory = WechatyBro.glue.chatroomFactory
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
681 682
    // log(ChatRoomName)
    // log(UserName)
Huan (李卓桓)'s avatar
#119  
Huan (李卓桓) 已提交
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699

    // There's no return value of addMember :(
    // https://github.com/wechaty/webwx-app-tracker/blob/f22cb043ff4201ee841990dbeb59e22643092f92/formatted/webwxApp.js#L2404-L2413
    var timer = setTimeout(function() {
      log('roomAddMemberAsync() timeout')
      callback(0)
    }, 10 * 1000)

    chatroomFactory.addMember(ChatRoomName, UserName, function(result) {

      clearTimeout(timer)
      callback(1)

      log('roomAddMemberAsync() return: ')
      log(result)

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
702
  function roomModTopic(ChatRoomName, topic) {
703 704
    var chatroomFactory = WechatyBro.glue.chatroomFactory
    return chatroomFactory.modTopic(ChatRoomName, topic)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
705 706
  }

707
  function roomCreateAsync(UserNameList, topic) {
708
    var callback = arguments[arguments.length - 1]
709 710
    if (typeof callback !== 'function') {
      // here we should in sync mode, because there's no callback
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
      throw new Error('async method need to be called via webdriver.executeAsyncScript')
    }

    var UserNameListArg = UserNameList.map(function(n) { return { UserName: n } })

    var chatroomFactory = WechatyBro.glue.chatroomFactory
    var state           = WechatyBro.glue.state
    chatroomFactory.create(UserNameListArg)
                    .then(function(r) {
                      if (r.BaseResponse && 0 == r.BaseResponse.Ret || -2013 == r.BaseResponse.Ret) {
                        state.go('chat', { userName: r.ChatRoomName }) // BE CAREFUL: key name is userName, not UserName! 20161001
                        // if (topic) {
                        //   setTimeout(_ => roomModTopic(r.ChatRoomName, topic), 3000)
                        // }
                        if (!r.ChatRoomName) {
                          throw new Error('chatroomFactory.create() got empty r.ChatRoomName')
                        }
                        callback(r.ChatRoomName)
                      } else {
                        throw new Error('chatroomFactory.create() error with Ret: '
                                          + r && r.BaseResponse.Ret
                                          + 'with ErrMsg: '
                                          + r && r.BaseResponse.ErrMsg
                                      )
                      }
                    })
                    .catch(function(e) {
                      // Async can only return by call callback
                      callback(
                        JSON.parse(
                          JSON.stringify(
                            e
                            , Object.getOwnPropertyNames(e)
                          )
                        )
                      )
                    })
  }

750 751 752 753 754 755 756
  function verifyUserRequestAsync(UserName, VerifyContent) {
    var callback = arguments[arguments.length - 1]
    if (typeof callback !== 'function') {
      // here we should in sync mode, because there's no callback
      throw new Error('async method need to be called via webdriver.executeAsyncScript')
    }

757 758 759 760 761 762
    VerifyContent = VerifyContent || '';

    var contactFactory = WechatyBro.glue.contactFactory
    var confFactory = WechatyBro.glue.confFactory

    var Ticket = '' // what's this?
763 764

    contactFactory.verifyUser({
765 766 767 768 769 770 771
      UserName:        UserName
      , Opcode:        confFactory.VERIFYUSER_OPCODE_SENDREQUEST
      , Scene:         confFactory.ADDSCENE_PF_WEB
      , Ticket:        Ticket
      , VerifyContent: VerifyContent
    })
    .then(function() {  // succ
772
      // alert('ok')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
773
      // log('friendAdd(' + UserName + ', ' + VerifyContent + ') done')
774
      callback(true)
775
    }, function(t) {    // fail
776
      // alert('not ok')
777
      log('friendAdd(' + UserName + ', ' + VerifyContent + ') fail: ' + t)
778
      callback(false)
779
    })
780 781
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
782
  function verifyUserOkAsync(UserName, Ticket) {
783 784 785 786 787 788
    var callback = arguments[arguments.length - 1]
    if (typeof callback !== 'function') {
      // here we should in sync mode, because there's no callback
      throw new Error('async method need to be called via webdriver.executeAsyncScript')
    }

789 790
    var contactFactory  = WechatyBro.glue.contactFactory
    var confFactory     = WechatyBro.glue.confFactory
791 792

    contactFactory.verifyUser({
793 794 795 796 797
      UserName:   UserName
      , Opcode:   confFactory.VERIFYUSER_OPCODE_VERIFYOK
      , Scene:    confFactory.ADDSCENE_PF_WEB
      , Ticket:   Ticket
    }).then(function() {  // succ
798
      // alert('ok')
799
      log('friendVerify(' + UserName + ', ' + Ticket + ') done')
800 801
      callback(true)
    }, function(err) {       // fail
802
      // alert('err')
803
      log('friendVerify(' + UserName + ', ' + Ticket + ') fail')
804
      callback(false)
805
    })
806 807
  }

808 809 810



Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
811 812 813 814
  /////////////////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////

815

816 817 818 819



  port = port || 8788
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
820 821 822 823 824 825 826

  /*
   * WechatyBro injectio must return this object.
   * PuppetWebBridge need this to decide if injection is successful.
   */
  var retObj = {
    code: 200 // 2XX ok, 4XX/5XX error. HTTP like
827 828 829
    , message: 'any message'
    , port: port
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
830 831

  if (typeof this.WechatyBro !== 'undefined') {
832 833 834
    retObj.code = 201
    retObj.message = 'WechatyBro already injected?'
    return retObj
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
835 836 837
  }

  var WechatyBro = {
838 839 840
    glue: {
      // will be initialized by glueToAngular() function
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
841 842 843 844

    // glue funcs
    // , getLoginStatusCode: function() { return WechatyBro.glue.loginScope.code }
    // , getLoginQrImgUrl:   function() { return WechatyBro.glue.loginScope.qrcodeUrl }
845
    , angularIsReady:    angularIsReady
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
846 847 848

    // variable
    , vars: {
849 850
      loginStatus:      false
      , initStatus:     false
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
851

852 853 854 855
      , socket:     null
      , eventsBuf:  []
      , scanCode:   null
      , heartBeatTimmer:   null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
856 857 858
    }

    // funcs
859 860 861 862 863 864 865 866
    , init: init  // initialize WechatyBro @ Browser
    , send: send  // send message to wechat user
    , clog: clog  // log to Console
    , slog: slog  // log to SocketIO
    , log:  log   // log to both Console & SocketIO
    , ding: ding  // simple return 'dong'
    , quit: quit  // quit wechat
    , emit: emit  // send event to server
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
867 868
    , logout: logout // logout current logined user

869 870 871 872 873 874 875
    , getContact:          getContact
    , getUserName:         getUserName
    , getMsgImg:           getMsgImg
    , getMsgEmoticon:      getMsgEmoticon
    , getMsgVideo:         getMsgVideo
    , getMsgVoice:         getMsgVoice
    , getMsgPublicLinkImg: getMsgPublicLinkImg
M
Mukaiu 已提交
876 877
    , getBaseRequest:      getBaseRequest
    , getPassticket:       getPassticket
M
Mukaiu 已提交
878
    , getUploadMediaUrl:   getUploadMediaUrl
M
Mukaiu 已提交
879
    , sendMedia:           sendMedia
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
880

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
881
    // for Wechaty Contact Class
882 883
    , contactFindAsync:   contactFindAsync
    , contactRemarkAsync: contactRemarkAsync
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
884

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
885
    // for Wechaty Room Class
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
886
    , roomCreateAsync:    roomCreateAsync
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
887
    , roomAddMemberAsync: roomAddMemberAsync
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
888 889 890
    , roomFind:           roomFind
    , roomDelMember:      roomDelMember
    , roomModTopic:       roomModTopic
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
891

892
    // for Friend Request
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
893 894
    , verifyUserRequestAsync: verifyUserRequestAsync
    , verifyUserOkAsync:      verifyUserOkAsync
895 896 897
    // , friendAdd
    // , friendVerify

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
898
    // test purpose
899 900 901
    , isLogin: isLogin
    , initClog: initClog
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
902

903 904 905
  this.WechatyBro = WechatyBro
  retObj.code = 200
  retObj.message = 'WechatyBro Inject Done'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
906 907 908 909 910 911

  /**
   * Two return mode of WebDriver (should be one of them at a time)
   * 1. a callback. return a value by call callback with args
   * 2. direct return
   */
912
  var callback = arguments[arguments.length - 1]
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
913
  if (typeof callback === 'function') {
914
    return callback(retObj)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
915
  }
916
  return retObj
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
917 918 919 920

  // retObj.code = 500
  // retObj.message = 'SHOULD NOT RUN TO HERE'
  // return retObj
921 922

}.apply(window, arguments))