util-lib.ts 7.6 KB
Newer Older
1 2 3 4 5 6 7
/**
 * Wechaty - Wechat for Bot. Connecting ChatBots
 *
 * Licenst: ISC
 * https://github.com/wechaty/wechaty
 *
 */
8 9 10
import * as https from 'https'
import * as http  from 'http'
import * as url   from 'url'
11

12
import { log } from './config'
13

14 15 16 17
/**
 * bug compatible with:
 * https://github.com/wechaty/wechaty/issues/40#issuecomment-252802084
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
18
// import * as ws from 'ws'
19

20
export class UtilLib {
21 22 23 24 25
  public static stripHtml(html?: string): string {
    if (!html) {
      return ''
    }
    return html.replace(/(<([^>]+)>)/ig, '')
26
  }
27

28 29 30 31 32
  public static unescapeHtml(str?: string): string {
    if (!str) {
      return ''
    }
    return str
33 34 35 36 37 38
    .replace(/&apos;/g, "'")
    .replace(/&quot;/g, '"')
    .replace(/&gt;/g, '>')
    .replace(/&lt;/g, '<')
    .replace(/&amp;/g, '&')
  }
39

40 41 42 43 44
  public static digestEmoji(html?: string): string {
    if (!html) {
      return ''
    }
    return html
45 46 47 48 49 50 51
          .replace(/<img class="(\w*?emoji) (\w*?emoji[^"]+?)" text="(.*?)_web" src=[^>]+>/g
                 , '$3'
                 ) // <img class="emoji emoji1f4a4" text="[流汗]_web" src="/zh_CN/htmledition/v2/images/spacer.gif" />
          .replace(/<span class="(\w*?emoji) (\w*?emoji[^"]+?)"><\/span>/g
                 , '[$2]'
                 ) // '<span class="emoji emoji1f334"></span>'
  }
52

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
  /**
   * unifyEmoji: the same emoji will be encoded as different xml code in browser. unify them.
   *
   *  from: <img class="emoji emoji1f602" text="_web" src="/zh_CN/htmledition/v2/images/spacer.gif" />
   *  to:   <span class=\"emoji emoji1f602\"></span>
   *
   */
  public static unifyEmoji(html?: string): string {
    if (!html) {
      return ''
    }
    return html
          .replace(/<img class="(\w*?emoji) (\w*?emoji[^"]+?)" text="(.*?)_web" src=[^>]+>/g
                 , '<emoji code="$2"/>'
                 ) // <img class="emoji emoji1f4a4" text="[流汗]_web" src="/zh_CN/htmledition/v2/images/spacer.gif" />
          .replace(/<span class="(\w*?emoji) (\w*?emoji[^"]+?)"><\/span>/g
                 , '<emoji code="$2"/>'
                 ) // '<span class="emoji emoji1f334"></span>'
  }

73 74 75 76 77 78 79 80 81 82 83 84 85
  public static stripEmoji(html?: string): string {
    if (!html) {
      return ''
    }
    return html
          .replace(/<img class="(\w*?emoji) (\w*?emoji[^"]+?)" text="(.*?)_web" src=[^>]+>/g
                 , ''
                 ) // <img class="emoji emoji1f4a4" text="[流汗]_web" src="/zh_CN/htmledition/v2/images/spacer.gif" />
          .replace(/<span class="(\w*?emoji) (\w*?emoji[^"]+?)"><\/span>/g
                 , ''
                 ) // '<span class="emoji emoji1f334"></span>'
  }

86 87 88 89
  public static plainText(html?: string): string {
    if (!html) {
      return ''
    }
90 91 92 93 94 95
    return UtilLib.stripHtml(
      UtilLib.unescapeHtml(
        UtilLib.stripHtml(
          UtilLib.digestEmoji(
            html
          )
96 97 98 99 100
        )
      )
    )
  }

101
  public static urlStream(href: string, cookies: any[]): Promise<NodeJS.ReadableStream> {
102
    // const myurl = 'http://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmsgimg?&MsgID=3080011908135131569&skey=%40crypt_c117402d_53a58f8fbb21978167a3fc7d3be7f8c9'
103 104
    href = href.replace(/^https/i, 'http') // use http instead of https, because https will only success on the very first request!

105 106 107 108
    const u = url.parse(href)
    const protocol: 'https:'|'http:' = u.protocol as any

    let options
109 110
    // let request
    let get
111 112

    if (protocol === 'https:') {
113 114
      // request       = https.request.bind(https)
      get           = https.get
115 116 117
      options       = u as https.RequestOptions
      options.agent = https.globalAgent
    } else if (protocol === 'http:') {
118 119
      // request       = http.request.bind(http)
      get           = http.get
120 121 122 123 124
      options       = u as http.RequestOptions
      options.agent = http.globalAgent
    } else {
      throw new Error('protocol unknown: ' + protocol)
    }
125 126

    options.headers = {
127 128 129
      'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36',

      // Accept: 'image/webp,image/*,*/*;q=0.8',
130 131
      // Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', //  MsgType.IMAGE | VIDEO
      Accept: '*/*',
132 133 134 135 136 137 138 139 140 141

      Host: options.hostname, // 'wx.qq.com',  // MsgType.VIDEO | IMAGE

      Referer: protocol + '//wx.qq.com/',

      // 'Upgrade-Insecure-Requests': 1, // MsgType.VIDEO | IMAGE

      Range: 'bytes=0-',

      // 'Accept-Encoding': 'gzip, deflate, sdch',
142 143
      // 'Accept-Encoding': 'gzip, deflate, sdch, br', // MsgType.IMAGE | VIDEO
      'Accept-Encoding': 'identity;q=1, *;q=0',
144 145 146

      'Accept-Language': 'zh-CN,zh;q=0.8', // MsgType.IMAGE | VIDEO
      // 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.6,en-US;q=0.4,en;q=0.2',
147 148 149
    }

    /**
150
     * pgv_pvi=6639183872; pgv_si=s8359147520; webwx_data_ticket=gSeBbuhX+0kFdkXbgeQwr6Ck
151
     */
152
    options.headers['Cookie'] = cookies.map(c => `${c['name']}=${c['value']}`).join('; ')
153
    // log.verbose('Util', 'Cookie: %s', options.headers.Cookie)
154
// console.log(options)
155 156

    return new Promise((resolve, reject) => {
157 158
      // const req = request(options, (res) => {
      const req = get(options, (res) => {
159 160 161 162 163 164 165 166
        // console.log(`STATUS: ${res.statusCode}`);
        // console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
        // res.setEncoding('utf8');
        resolve(res)
      })

      req.on('error', (e) => {
        log.warn('WebUtil', `downloadStream() problem with request: ${e.message}`)
167
        reject(e)
168
      })
169

170 171 172
      // req.end(() => {
      //   log.verbose('UtilLib', 'urlStream() req.end() request sent')
      // })
173

174
    })
175
  }
176

177
  // credit - http://stackoverflow.com/a/2117523/1123955
178
  public static guid(): string {
179 180 181 182 183 184
    /* 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)
    })
  }
185

186 187 188 189 190 191 192 193 194 195 196
  /**
   *
   * @param port is just a suggestion.
   * there's no grantuee for the number
   *
   * The IANA suggested ephemeral port range.
   * @see http://en.wikipedia.org/wiki/Ephemeral_ports
   *
   * const DEFAULT_IANA_RANGE = {min: 49152, max: 65535}
   *
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
197
  public static getPort(port: number): Promise<number> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
198
    log.silly('UtilLib', 'getPort(%d)', port)
199 200 201 202 203 204 205 206 207 208
    let tryPort = nextPort(port || 38788)

    return new Promise((resolve, reject) => {
      // https://gist.github.com/mikeal/1840641
      function _getPort(cb) {
        const server = require('net').createServer()
        server.on('error', function(err) {
          if (err) {/* fail safe */ }
          tryPort = nextPort(port)
          _getPort(cb)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
209
        })
210 211 212 213 214 215 216 217 218
        server.listen(tryPort, function(err) {
          if (err) {/* fail safe */}
          server.once('close', function() {
            cb(tryPort)
          })
          server.close()
        })
      }
      _getPort(okPort => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
219
        log.silly('UtilLib', 'getPort(%d) return: %d'
220 221
                  , port
                  , okPort
222
        )
223 224
        resolve(okPort)
      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
225
    })
226

227 228
    function nextPort(currentPort: number): number {
      const RANGE = 719
229
      // do not use Math.random() here, because AVA will fork, then here will get the same random number, cause a race condition for socket listen
230 231 232 233 234 235
      // const n = Math.floor(Math.random() * BETWEEN_RANGE)

      /**
       * nano seconds from node: http://stackoverflow.com/a/18197438/1123955
       */
      const [, nanoSeed] = process.hrtime()
236
      const n = 1 + nanoSeed % RANGE // +1 to prevent same port
237 238 239 240 241

      if (currentPort + n > 65000) {
        return currentPort + n - RANGE
      }
      return currentPort + n
242
    }
243
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
244
}