/**
* Wechaty - Wechat for Bot. Connecting ChatBots
*
* Licenst: ISC
* https://github.com/wechaty/wechaty
*
*/
import * as https from 'https'
import * as http from 'http'
import * as url from 'url'
import * as crypto from 'crypto'
import { MsgType } from './message'
import { log } from './config'
/**
* bug compatible with:
* https://github.com/wechaty/wechaty/issues/40#issuecomment-252802084
*/
// import * as ws from 'ws'
export class UtilLib {
public static stripHtml(html?: string): string {
if (!html) {
return ''
}
return html.replace(/(<([^>]+)>)/ig, '')
}
public static unescapeHtml(str?: string): string {
if (!str) {
return ''
}
return str
.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/&/g, '&')
}
public static digestEmoji(html?: string): string {
if (!html) {
return ''
}
return html
.replace(/]+>/g,
'$3',
) //
.replace(/<\/span>/g,
'[$2]',
) // ''
}
/**
* unifyEmoji: the same emoji will be encoded as different xml code in browser. unify them.
*
* from:
* to:
*
*/
public static unifyEmoji(html?: string): string {
if (!html) {
return ''
}
return html
.replace(/]+>/g,
'',
) //
.replace(/<\/span>/g,
'',
) // ''
}
public static stripEmoji(html?: string): string {
if (!html) {
return ''
}
return html
.replace(/]+>/g,
'',
) //
.replace(/<\/span>/g,
'',
) // ''
}
public static plainText(html?: string): string {
if (!html) {
return ''
}
return UtilLib.stripHtml(
UtilLib.unescapeHtml(
UtilLib.stripHtml(
UtilLib.digestEmoji(
html,
),
),
),
)
}
public static urlStream(href: string, cookies: any[]): Promise {
// const myurl = 'http://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmsgimg?&MsgID=3080011908135131569&skey=%40crypt_c117402d_53a58f8fbb21978167a3fc7d3be7f8c9'
href = href.replace(/^https/i, 'http') // use http instead of https, because https will only success on the very first request!
const u = url.parse(href)
const protocol: 'https:'|'http:' = u.protocol as any
let options
// let request
let get
if (protocol === 'https:') {
// request = https.request.bind(https)
get = https.get
options = u as any as https.RequestOptions
options.agent = https.globalAgent
} else if (protocol === 'http:') {
// request = http.request.bind(http)
get = http.get
options = u as any as http.RequestOptions
options.agent = http.globalAgent
} else {
throw new Error('protocol unknown: ' + protocol)
}
options.headers = {
'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',
// Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', // MsgType.IMAGE | VIDEO
Accept: '*/*',
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',
// 'Accept-Encoding': 'gzip, deflate, sdch, br', // MsgType.IMAGE | VIDEO
'Accept-Encoding': 'identity;q=1, *;q=0',
'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',
}
/**
* pgv_pvi=6639183872; pgv_si=s8359147520; webwx_data_ticket=gSeBbuhX+0kFdkXbgeQwr6Ck
*/
options.headers['Cookie'] = cookies.map(c => `${c['name']}=${c['value']}`).join('; ')
// log.verbose('Util', 'Cookie: %s', options.headers.Cookie)
// console.log(options)
return new Promise((resolve, reject) => {
// const req = request(options, (res) => {
const req = get(options, (res) => {
// 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}`)
reject(e)
})
// req.end(() => {
// log.verbose('UtilLib', 'urlStream() req.end() request sent')
// })
})
}
// credit - http://stackoverflow.com/a/2117523/1123955
public static guid(): string {
/* 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)
})
}
/**
*
* @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}
*
*/
public static getPort(port: number): Promise {
log.silly('UtilLib', 'getPort(%d)', port)
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)
})
server.listen(tryPort, function(err) {
if (err) {/* fail safe */}
server.once('close', function() {
cb(tryPort)
})
server.close()
})
}
_getPort(okPort => {
log.silly('UtilLib', 'getPort(%d) return: %d',
port,
okPort,
)
resolve(okPort)
})
})
function nextPort(currentPort: number): number {
const RANGE = 1733
// 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
// const n = Math.floor(Math.random() * BETWEEN_RANGE)
/**
* nano seconds from node: http://stackoverflow.com/a/18197438/1123955
*/
const [, nanoSeed] = process.hrtime()
const n = 1 + nanoSeed % RANGE // +1 to prevent same port
if (currentPort + n > 65000) {
return currentPort + n - RANGE
}
return currentPort + n
}
}
public static md5(buffer: Buffer): string {
let md5sum = crypto.createHash('md5')
md5sum.update(buffer)
return md5sum.digest('hex')
}
public static msgType(ext): MsgType {
switch (ext) {
case 'bmp':
case 'jpeg':
case 'jpg':
case 'png':
return MsgType.IMAGE
case 'mp4':
return MsgType.VIDEO
default:
return MsgType.APP
}
}
public static mime(ext): string {
switch (ext) {
case 'pdf':
return 'application/pdf'
case 'bmp':
return 'image/bmp'
case 'jpeg':
return 'image/jpeg'
case 'jpg':
return 'image/jpeg'
case 'png':
return 'image/png'
case 'mp4':
return 'video/mp4'
default:
return 'application/octet-stream'
}
}
}