代码格式化
This commit is contained in:
247
util/apicache.js
247
util/apicache.js
@@ -13,19 +13,19 @@ var t = {
|
||||
|
||||
var instances = []
|
||||
|
||||
var matches = function(a) {
|
||||
return function(b) {
|
||||
var matches = function (a) {
|
||||
return function (b) {
|
||||
return a === b
|
||||
}
|
||||
}
|
||||
|
||||
var doesntMatch = function(a) {
|
||||
return function(b) {
|
||||
var doesntMatch = function (a) {
|
||||
return function (b) {
|
||||
return !matches(a)(b)
|
||||
}
|
||||
}
|
||||
|
||||
var logDuration = function(d, prefix) {
|
||||
var logDuration = function (d, prefix) {
|
||||
var str = d > 1000 ? (d / 1000).toFixed(2) + 'sec' : d + 'ms'
|
||||
return '\x1b[33m- ' + (prefix ? prefix + ' ' : '') + str + '\x1b[0m'
|
||||
}
|
||||
@@ -68,10 +68,12 @@ function ApiCache() {
|
||||
this.id = instances.length
|
||||
|
||||
function debug(a, b, c, d) {
|
||||
var arr = ['\x1b[36m[apicache]\x1b[0m', a, b, c, d].filter(function(arg) {
|
||||
var arr = ['\x1b[36m[apicache]\x1b[0m', a, b, c, d].filter(function (arg) {
|
||||
return arg !== undefined
|
||||
})
|
||||
var debugEnv = process.env.DEBUG && process.env.DEBUG.split(',').indexOf('apicache') !== -1
|
||||
var debugEnv =
|
||||
process.env.DEBUG &&
|
||||
process.env.DEBUG.split(',').indexOf('apicache') !== -1
|
||||
|
||||
return (globalOptions.debug || debugEnv) && console.log.apply(null, arr)
|
||||
}
|
||||
@@ -86,9 +88,17 @@ function ApiCache() {
|
||||
return false
|
||||
}
|
||||
|
||||
if (codes.exclude && codes.exclude.length && codes.exclude.indexOf(response.statusCode) !== -1)
|
||||
if (
|
||||
codes.exclude &&
|
||||
codes.exclude.length &&
|
||||
codes.exclude.indexOf(response.statusCode) !== -1
|
||||
)
|
||||
return false
|
||||
if (codes.include && codes.include.length && codes.include.indexOf(response.statusCode) === -1)
|
||||
if (
|
||||
codes.include &&
|
||||
codes.include.length &&
|
||||
codes.include.indexOf(response.statusCode) === -1
|
||||
)
|
||||
return false
|
||||
|
||||
return true
|
||||
@@ -108,10 +118,10 @@ function ApiCache() {
|
||||
|
||||
function filterBlacklistedHeaders(headers) {
|
||||
return Object.keys(headers)
|
||||
.filter(function(key) {
|
||||
.filter(function (key) {
|
||||
return globalOptions.headerBlacklist.indexOf(key) === -1
|
||||
})
|
||||
.reduce(function(acc, header) {
|
||||
.reduce(function (acc, header) {
|
||||
acc[header] = headers[header]
|
||||
return acc
|
||||
}, {})
|
||||
@@ -135,7 +145,7 @@ function ApiCache() {
|
||||
try {
|
||||
redis.hset(key, 'response', JSON.stringify(value))
|
||||
redis.hset(key, 'duration', duration)
|
||||
redis.expire(key, duration / 1000, expireCallback || function() {})
|
||||
redis.expire(key, duration / 1000, expireCallback || function () {})
|
||||
} catch (err) {
|
||||
debug('[apicache] error in redis.hset()')
|
||||
}
|
||||
@@ -144,7 +154,7 @@ function ApiCache() {
|
||||
}
|
||||
|
||||
// add automatic cache clearing from duration, includes max limit on setTimeout
|
||||
timers[key] = setTimeout(function() {
|
||||
timers[key] = setTimeout(function () {
|
||||
instance.clear(key, true)
|
||||
}, Math.min(duration, 2147483647))
|
||||
}
|
||||
@@ -157,7 +167,9 @@ function ApiCache() {
|
||||
var oldContent = res._apicache.content
|
||||
|
||||
if (typeof oldContent === 'string') {
|
||||
oldContent = !Buffer.from ? new Buffer(oldContent) : Buffer.from(oldContent)
|
||||
oldContent = !Buffer.from
|
||||
? new Buffer(oldContent)
|
||||
: Buffer.from(oldContent)
|
||||
}
|
||||
|
||||
if (!oldContent) {
|
||||
@@ -166,7 +178,7 @@ function ApiCache() {
|
||||
|
||||
res._apicache.content = Buffer.concat(
|
||||
[oldContent, content],
|
||||
oldContent.length + content.length
|
||||
oldContent.length + content.length,
|
||||
)
|
||||
} else {
|
||||
res._apicache.content = content
|
||||
@@ -174,7 +186,15 @@ function ApiCache() {
|
||||
}
|
||||
}
|
||||
|
||||
function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) {
|
||||
function makeResponseCacheable(
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
key,
|
||||
duration,
|
||||
strDuration,
|
||||
toggle,
|
||||
) {
|
||||
// monkeypatch res.end to create cache object
|
||||
res._apicache = {
|
||||
write: res.write,
|
||||
@@ -185,15 +205,18 @@ function ApiCache() {
|
||||
}
|
||||
|
||||
// append header overwrites if applicable
|
||||
Object.keys(globalOptions.headers).forEach(function(name) {
|
||||
Object.keys(globalOptions.headers).forEach(function (name) {
|
||||
res.setHeader(name, globalOptions.headers[name])
|
||||
})
|
||||
|
||||
res.writeHead = function() {
|
||||
res.writeHead = function () {
|
||||
// add cache control headers
|
||||
if (!globalOptions.headers['cache-control']) {
|
||||
if (shouldCacheResponse(req, res, toggle)) {
|
||||
res.setHeader('cache-control', 'max-age=' + (duration / 1000).toFixed(0))
|
||||
res.setHeader(
|
||||
'cache-control',
|
||||
'max-age=' + (duration / 1000).toFixed(0),
|
||||
)
|
||||
} else {
|
||||
res.setHeader('cache-control', 'no-cache, no-store, must-revalidate')
|
||||
}
|
||||
@@ -204,13 +227,13 @@ function ApiCache() {
|
||||
}
|
||||
|
||||
// patch res.write
|
||||
res.write = function(content) {
|
||||
res.write = function (content) {
|
||||
accumulateContent(res, content)
|
||||
return res._apicache.write.apply(this, arguments)
|
||||
}
|
||||
|
||||
// patch res.end
|
||||
res.end = function(content, encoding) {
|
||||
res.end = function (content, encoding) {
|
||||
if (shouldCacheResponse(req, res, toggle)) {
|
||||
accumulateContent(res, content)
|
||||
|
||||
@@ -221,13 +244,16 @@ function ApiCache() {
|
||||
res.statusCode,
|
||||
headers,
|
||||
res._apicache.content,
|
||||
encoding
|
||||
encoding,
|
||||
)
|
||||
cacheResponse(key, cacheObject, duration)
|
||||
|
||||
// display log entry
|
||||
var elapsed = new Date() - req.apicacheTimer
|
||||
debug('adding cache entry for "' + key + '" @ ' + strDuration, logDuration(elapsed))
|
||||
debug(
|
||||
'adding cache entry for "' + key + '" @ ' + strDuration,
|
||||
logDuration(elapsed),
|
||||
)
|
||||
debug('_apicache.headers: ', res._apicache.headers)
|
||||
debug('res.getHeaders(): ', getSafeHeaders(res))
|
||||
debug('cacheObject: ', cacheObject)
|
||||
@@ -240,31 +266,46 @@ function ApiCache() {
|
||||
next()
|
||||
}
|
||||
|
||||
function sendCachedResponse(request, response, cacheObject, toggle, next, duration) {
|
||||
function sendCachedResponse(
|
||||
request,
|
||||
response,
|
||||
cacheObject,
|
||||
toggle,
|
||||
next,
|
||||
duration,
|
||||
) {
|
||||
if (toggle && !toggle(request, response)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
var headers = getSafeHeaders(response)
|
||||
|
||||
Object.assign(headers, filterBlacklistedHeaders(cacheObject.headers || {}), {
|
||||
// set properly-decremented max-age header. This ensures that max-age is in sync with the cache expiration.
|
||||
'cache-control':
|
||||
'max-age=' +
|
||||
Math.max(
|
||||
0,
|
||||
(duration / 1000 - (new Date().getTime() / 1000 - cacheObject.timestamp)).toFixed(0)
|
||||
),
|
||||
})
|
||||
Object.assign(
|
||||
headers,
|
||||
filterBlacklistedHeaders(cacheObject.headers || {}),
|
||||
{
|
||||
// set properly-decremented max-age header. This ensures that max-age is in sync with the cache expiration.
|
||||
'cache-control':
|
||||
'max-age=' +
|
||||
Math.max(
|
||||
0,
|
||||
(
|
||||
duration / 1000 -
|
||||
(new Date().getTime() / 1000 - cacheObject.timestamp)
|
||||
).toFixed(0),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
// only embed apicache headers when not in production environment
|
||||
|
||||
|
||||
// unstringify buffers
|
||||
var data = cacheObject.data
|
||||
if (data && data.type === 'Buffer') {
|
||||
data =
|
||||
typeof data.data === 'number' ? new Buffer.alloc(data.data) : new Buffer.from(data.data)
|
||||
typeof data.data === 'number'
|
||||
? new Buffer.alloc(data.data)
|
||||
: new Buffer.from(data.data)
|
||||
}
|
||||
|
||||
// test Etag against If-None-Match for 304
|
||||
@@ -283,18 +324,22 @@ function ApiCache() {
|
||||
|
||||
function syncOptions() {
|
||||
for (var i in middlewareOptions) {
|
||||
Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions)
|
||||
Object.assign(
|
||||
middlewareOptions[i].options,
|
||||
globalOptions,
|
||||
middlewareOptions[i].localOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.clear = function(target, isAutomatic) {
|
||||
this.clear = function (target, isAutomatic) {
|
||||
var group = index.groups[target]
|
||||
var redis = globalOptions.redisClient
|
||||
|
||||
if (group) {
|
||||
debug('clearing group "' + target + '"')
|
||||
|
||||
group.forEach(function(key) {
|
||||
group.forEach(function (key) {
|
||||
debug('clearing cached entry for "' + key + '"')
|
||||
clearTimeout(timers[key])
|
||||
delete timers[key]
|
||||
@@ -312,7 +357,13 @@ function ApiCache() {
|
||||
|
||||
delete index.groups[target]
|
||||
} else if (target) {
|
||||
debug('clearing ' + (isAutomatic ? 'expired' : 'cached') + ' entry for "' + target + '"')
|
||||
debug(
|
||||
'clearing ' +
|
||||
(isAutomatic ? 'expired' : 'cached') +
|
||||
' entry for "' +
|
||||
target +
|
||||
'"',
|
||||
)
|
||||
clearTimeout(timers[target])
|
||||
delete timers[target]
|
||||
// clear actual cached entry
|
||||
@@ -330,8 +381,10 @@ function ApiCache() {
|
||||
index.all = index.all.filter(doesntMatch(target))
|
||||
|
||||
// remove target from each group that it may exist in
|
||||
Object.keys(index.groups).forEach(function(groupName) {
|
||||
index.groups[groupName] = index.groups[groupName].filter(doesntMatch(target))
|
||||
Object.keys(index.groups).forEach(function (groupName) {
|
||||
index.groups[groupName] = index.groups[groupName].filter(
|
||||
doesntMatch(target),
|
||||
)
|
||||
|
||||
// delete group if now empty
|
||||
if (!index.groups[groupName].length) {
|
||||
@@ -345,7 +398,7 @@ function ApiCache() {
|
||||
memCache.clear()
|
||||
} else {
|
||||
// clear redis keys one by one from internal index to prevent clearing non-apicache entries
|
||||
index.all.forEach(function(key) {
|
||||
index.all.forEach(function (key) {
|
||||
clearTimeout(timers[key])
|
||||
delete timers[key]
|
||||
try {
|
||||
@@ -381,7 +434,7 @@ function ApiCache() {
|
||||
return defaultDuration
|
||||
}
|
||||
|
||||
this.getDuration = function(duration) {
|
||||
this.getDuration = function (duration) {
|
||||
return parseDuration(duration, globalOptions.defaultDuration)
|
||||
}
|
||||
|
||||
@@ -393,13 +446,13 @@ function ApiCache() {
|
||||
* })
|
||||
* </code>
|
||||
*/
|
||||
this.getPerformance = function() {
|
||||
return performanceArray.map(function(p) {
|
||||
this.getPerformance = function () {
|
||||
return performanceArray.map(function (p) {
|
||||
return p.report()
|
||||
})
|
||||
}
|
||||
|
||||
this.getIndex = function(group) {
|
||||
this.getIndex = function (group) {
|
||||
if (group) {
|
||||
return index.groups[group]
|
||||
} else {
|
||||
@@ -407,7 +460,11 @@ function ApiCache() {
|
||||
}
|
||||
}
|
||||
|
||||
this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
|
||||
this.middleware = function cache(
|
||||
strDuration,
|
||||
middlewareToggle,
|
||||
localOptions,
|
||||
) {
|
||||
var duration = instance.getDuration(strDuration)
|
||||
var opt = {}
|
||||
|
||||
@@ -415,9 +472,9 @@ function ApiCache() {
|
||||
options: opt,
|
||||
})
|
||||
|
||||
var options = function(localOptions) {
|
||||
var options = function (localOptions) {
|
||||
if (localOptions) {
|
||||
middlewareOptions.find(function(middleware) {
|
||||
middlewareOptions.find(function (middleware) {
|
||||
return middleware.options === opt
|
||||
}).localOptions = localOptions
|
||||
}
|
||||
@@ -433,7 +490,7 @@ function ApiCache() {
|
||||
* A Function for non tracking performance
|
||||
*/
|
||||
function NOOPCachePerformance() {
|
||||
this.report = this.hit = this.miss = function() {} // noop;
|
||||
this.report = this.hit = this.miss = function () {} // noop;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,7 +544,7 @@ function ApiCache() {
|
||||
/**
|
||||
* Return performance statistics
|
||||
*/
|
||||
this.report = function() {
|
||||
this.report = function () {
|
||||
return {
|
||||
lastCacheHit: this.lastCacheHit,
|
||||
lastCacheMiss: this.lastCacheMiss,
|
||||
@@ -507,7 +564,7 @@ function ApiCache() {
|
||||
* @param {Uint8Array} array An array representing hits and misses.
|
||||
* @returns a number between 0 and 1, or null if the array has no hits or misses
|
||||
*/
|
||||
this.hitRate = function(array) {
|
||||
this.hitRate = function (array) {
|
||||
var hits = 0
|
||||
var misses = 0
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
@@ -540,7 +597,7 @@ function ApiCache() {
|
||||
* 01 encodes a hit
|
||||
* 10 encodes a miss
|
||||
*/
|
||||
this.recordHitInArray = function(array, hit) {
|
||||
this.recordHitInArray = function (array, hit) {
|
||||
var arrayIndex = ~~(this.callCount / 4) % array.length
|
||||
var bitOffset = (this.callCount % 4) * 2 // 2 bits per record, 4 records per uint8 array element
|
||||
var clearMask = ~(3 << bitOffset)
|
||||
@@ -552,7 +609,7 @@ function ApiCache() {
|
||||
* Records the hit or miss in the tracking arrays and increments the call count.
|
||||
* @param {boolean} hit true records a hit, false records a miss
|
||||
*/
|
||||
this.recordHit = function(hit) {
|
||||
this.recordHit = function (hit) {
|
||||
this.recordHitInArray(this.hitsLast100, hit)
|
||||
this.recordHitInArray(this.hitsLast1000, hit)
|
||||
this.recordHitInArray(this.hitsLast10000, hit)
|
||||
@@ -565,7 +622,7 @@ function ApiCache() {
|
||||
* Records a hit event, setting lastCacheMiss to the given key
|
||||
* @param {string} key The key that had the cache hit
|
||||
*/
|
||||
this.hit = function(key) {
|
||||
this.hit = function (key) {
|
||||
this.recordHit(true)
|
||||
this.lastCacheHit = key
|
||||
}
|
||||
@@ -574,17 +631,19 @@ function ApiCache() {
|
||||
* Records a miss event, setting lastCacheMiss to the given key
|
||||
* @param {string} key The key that had the cache miss
|
||||
*/
|
||||
this.miss = function(key) {
|
||||
this.miss = function (key) {
|
||||
this.recordHit(false)
|
||||
this.lastCacheMiss = key
|
||||
}
|
||||
}
|
||||
|
||||
var perf = globalOptions.trackPerformance ? new CachePerformance() : new NOOPCachePerformance()
|
||||
var perf = globalOptions.trackPerformance
|
||||
? new CachePerformance()
|
||||
: new NOOPCachePerformance()
|
||||
|
||||
performanceArray.push(perf)
|
||||
|
||||
var cache = function(req, res, next) {
|
||||
var cache = function (req, res, next) {
|
||||
function bypass() {
|
||||
debug('bypass detected, skipping cache.')
|
||||
return next()
|
||||
@@ -592,7 +651,11 @@ function ApiCache() {
|
||||
|
||||
// initial bypass chances
|
||||
if (!opt.enabled) return bypass()
|
||||
if (req.headers['x-apicache-bypass'] || req.headers['x-apicache-force-fetch']) return bypass()
|
||||
if (
|
||||
req.headers['x-apicache-bypass'] ||
|
||||
req.headers['x-apicache-force-fetch']
|
||||
)
|
||||
return bypass()
|
||||
|
||||
// REMOVED IN 0.11.1 TO CORRECT MIDDLEWARE TOGGLE EXECUTE ORDER
|
||||
// if (typeof middlewareToggle === 'function') {
|
||||
@@ -631,19 +694,34 @@ function ApiCache() {
|
||||
// send if cache hit from memory-cache
|
||||
if (cached) {
|
||||
var elapsed = new Date() - req.apicacheTimer
|
||||
debug('sending cached (memory-cache) version of', key, logDuration(elapsed))
|
||||
debug(
|
||||
'sending cached (memory-cache) version of',
|
||||
key,
|
||||
logDuration(elapsed),
|
||||
)
|
||||
|
||||
perf.hit(key)
|
||||
return sendCachedResponse(req, res, cached, middlewareToggle, next, duration)
|
||||
return sendCachedResponse(
|
||||
req,
|
||||
res,
|
||||
cached,
|
||||
middlewareToggle,
|
||||
next,
|
||||
duration,
|
||||
)
|
||||
}
|
||||
|
||||
// send if cache hit from redis
|
||||
if (redis && redis.connected) {
|
||||
try {
|
||||
redis.hgetall(key, function(err, obj) {
|
||||
redis.hgetall(key, function (err, obj) {
|
||||
if (!err && obj && obj.response) {
|
||||
var elapsed = new Date() - req.apicacheTimer
|
||||
debug('sending cached (redis) version of', key, logDuration(elapsed))
|
||||
debug(
|
||||
'sending cached (redis) version of',
|
||||
key,
|
||||
logDuration(elapsed),
|
||||
)
|
||||
|
||||
perf.hit(key)
|
||||
return sendCachedResponse(
|
||||
@@ -652,7 +730,7 @@ function ApiCache() {
|
||||
JSON.parse(obj.response),
|
||||
middlewareToggle,
|
||||
next,
|
||||
duration
|
||||
duration,
|
||||
)
|
||||
} else {
|
||||
perf.miss(key)
|
||||
@@ -663,18 +741,34 @@ function ApiCache() {
|
||||
key,
|
||||
duration,
|
||||
strDuration,
|
||||
middlewareToggle
|
||||
middlewareToggle,
|
||||
)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
// bypass redis on error
|
||||
perf.miss(key)
|
||||
return makeResponseCacheable(req, res, next, key, duration, strDuration, middlewareToggle)
|
||||
return makeResponseCacheable(
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
key,
|
||||
duration,
|
||||
strDuration,
|
||||
middlewareToggle,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
perf.miss(key)
|
||||
return makeResponseCacheable(req, res, next, key, duration, strDuration, middlewareToggle)
|
||||
return makeResponseCacheable(
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
key,
|
||||
duration,
|
||||
strDuration,
|
||||
middlewareToggle,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,18 +777,23 @@ function ApiCache() {
|
||||
return cache
|
||||
}
|
||||
|
||||
this.options = function(options) {
|
||||
this.options = function (options) {
|
||||
if (options) {
|
||||
Object.assign(globalOptions, options)
|
||||
syncOptions()
|
||||
|
||||
if ('defaultDuration' in options) {
|
||||
// Convert the default duration to a number in milliseconds (if needed)
|
||||
globalOptions.defaultDuration = parseDuration(globalOptions.defaultDuration, 3600000)
|
||||
globalOptions.defaultDuration = parseDuration(
|
||||
globalOptions.defaultDuration,
|
||||
3600000,
|
||||
)
|
||||
}
|
||||
|
||||
if (globalOptions.trackPerformance) {
|
||||
debug('WARNING: using trackPerformance flag can cause high memory usage!')
|
||||
debug(
|
||||
'WARNING: using trackPerformance flag can cause high memory usage!',
|
||||
)
|
||||
}
|
||||
|
||||
return this
|
||||
@@ -703,14 +802,14 @@ function ApiCache() {
|
||||
}
|
||||
}
|
||||
|
||||
this.resetIndex = function() {
|
||||
this.resetIndex = function () {
|
||||
index = {
|
||||
all: [],
|
||||
groups: {},
|
||||
}
|
||||
}
|
||||
|
||||
this.newInstance = function(config) {
|
||||
this.newInstance = function (config) {
|
||||
var instance = new ApiCache()
|
||||
|
||||
if (config) {
|
||||
@@ -720,7 +819,7 @@ function ApiCache() {
|
||||
return instance
|
||||
}
|
||||
|
||||
this.clone = function() {
|
||||
this.clone = function () {
|
||||
return this.newInstance(this.options())
|
||||
}
|
||||
|
||||
@@ -728,4 +827,4 @@ function ApiCache() {
|
||||
this.resetIndex()
|
||||
}
|
||||
|
||||
module.exports = new ApiCache()
|
||||
module.exports = new ApiCache()
|
||||
|
||||
@@ -3,7 +3,8 @@ const iv = Buffer.from('0102030405060708')
|
||||
const presetKey = Buffer.from('0CoJUm6Qyw8W8jud')
|
||||
const linuxapiKey = Buffer.from('rFgB&h#%2?^eDg:Q')
|
||||
const base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----'
|
||||
const publicKey =
|
||||
'-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----'
|
||||
const eapiKey = 'e82ckenh8dichen8'
|
||||
|
||||
const aesEncrypt = (buffer, mode, key, iv) => {
|
||||
@@ -13,38 +14,54 @@ const aesEncrypt = (buffer, mode, key, iv) => {
|
||||
|
||||
const rsaEncrypt = (buffer, key) => {
|
||||
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
|
||||
return crypto.publicEncrypt({key: key, padding: crypto.constants.RSA_NO_PADDING}, buffer)
|
||||
return crypto.publicEncrypt(
|
||||
{ key: key, padding: crypto.constants.RSA_NO_PADDING },
|
||||
buffer,
|
||||
)
|
||||
}
|
||||
|
||||
const weapi = (object) => {
|
||||
const text = JSON.stringify(object)
|
||||
const secretKey = crypto.randomBytes(16).map(n => (base62.charAt(n % 62).charCodeAt()))
|
||||
const secretKey = crypto
|
||||
.randomBytes(16)
|
||||
.map((n) => base62.charAt(n % 62).charCodeAt())
|
||||
return {
|
||||
params: aesEncrypt(Buffer.from(aesEncrypt(Buffer.from(text), 'cbc', presetKey, iv).toString('base64')), 'cbc', secretKey, iv).toString('base64'),
|
||||
encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex')
|
||||
params: aesEncrypt(
|
||||
Buffer.from(
|
||||
aesEncrypt(Buffer.from(text), 'cbc', presetKey, iv).toString('base64'),
|
||||
),
|
||||
'cbc',
|
||||
secretKey,
|
||||
iv,
|
||||
).toString('base64'),
|
||||
encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'),
|
||||
}
|
||||
}
|
||||
|
||||
const linuxapi = (object) => {
|
||||
const text = JSON.stringify(object)
|
||||
return {
|
||||
eparams: aesEncrypt(Buffer.from(text), 'ecb', linuxapiKey, '').toString('hex').toUpperCase()
|
||||
eparams: aesEncrypt(Buffer.from(text), 'ecb', linuxapiKey, '')
|
||||
.toString('hex')
|
||||
.toUpperCase(),
|
||||
}
|
||||
}
|
||||
|
||||
const eapi = (url, object) => {
|
||||
const text = typeof object === 'object' ? JSON.stringify(object) : object;
|
||||
const text = typeof object === 'object' ? JSON.stringify(object) : object
|
||||
const message = `nobody${url}use${text}md5forencrypt`
|
||||
const digest = crypto.createHash('md5').update(message).digest('hex')
|
||||
const data = `${url}-36cd479b6b5-${text}-36cd479b6b5-${digest}`
|
||||
return {
|
||||
params: aesEncrypt(Buffer.from(data), 'ecb', eapiKey, '').toString('hex').toUpperCase()
|
||||
params: aesEncrypt(Buffer.from(data), 'ecb', eapiKey, '')
|
||||
.toString('hex')
|
||||
.toUpperCase(),
|
||||
}
|
||||
}
|
||||
|
||||
const decrypt = cipherBuffer => {
|
||||
const decrypt = (cipherBuffer) => {
|
||||
const decipher = crypto.createDecipheriv('aes-128-ecb', eapiKey, '')
|
||||
return Buffer.concat([decipher.update(cipherBuffer), decipher.final()])
|
||||
}
|
||||
|
||||
module.exports = {weapi, linuxapi, eapi, decrypt}
|
||||
module.exports = { weapi, linuxapi, eapi, decrypt }
|
||||
|
||||
@@ -4,13 +4,13 @@ module.exports = {
|
||||
return val === 'true' || val == '1'
|
||||
},
|
||||
cookieToJson(cookie) {
|
||||
if (!cookie ) return {}
|
||||
let cookieArr = cookie.split(';');
|
||||
let obj = {}
|
||||
if (!cookie) return {}
|
||||
let cookieArr = cookie.split(';')
|
||||
let obj = {}
|
||||
cookieArr.forEach((i) => {
|
||||
let arr = i.split('=');
|
||||
obj[arr[0]] = arr[1];
|
||||
});
|
||||
let arr = i.split('=')
|
||||
obj[arr[0]] = arr[1]
|
||||
})
|
||||
return obj
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,58 +2,62 @@ function MemoryCache() {
|
||||
this.cache = {}
|
||||
this.size = 0
|
||||
}
|
||||
|
||||
MemoryCache.prototype.add = function(key, value, time, timeoutCallback) {
|
||||
|
||||
MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
|
||||
var old = this.cache[key]
|
||||
var instance = this
|
||||
|
||||
|
||||
var entry = {
|
||||
value: value,
|
||||
expire: time + Date.now(),
|
||||
timeout: setTimeout(function() {
|
||||
timeout: setTimeout(function () {
|
||||
instance.delete(key)
|
||||
return timeoutCallback && typeof timeoutCallback === 'function' && timeoutCallback(value, key)
|
||||
}, time)
|
||||
return (
|
||||
timeoutCallback &&
|
||||
typeof timeoutCallback === 'function' &&
|
||||
timeoutCallback(value, key)
|
||||
)
|
||||
}, time),
|
||||
}
|
||||
|
||||
|
||||
this.cache[key] = entry
|
||||
this.size = Object.keys(this.cache).length
|
||||
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
MemoryCache.prototype.delete = function(key) {
|
||||
|
||||
MemoryCache.prototype.delete = function (key) {
|
||||
var entry = this.cache[key]
|
||||
|
||||
|
||||
if (entry) {
|
||||
clearTimeout(entry.timeout)
|
||||
}
|
||||
|
||||
|
||||
delete this.cache[key]
|
||||
|
||||
|
||||
this.size = Object.keys(this.cache).length
|
||||
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
MemoryCache.prototype.get = function(key) {
|
||||
|
||||
MemoryCache.prototype.get = function (key) {
|
||||
var entry = this.cache[key]
|
||||
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
MemoryCache.prototype.getValue = function(key) {
|
||||
|
||||
MemoryCache.prototype.getValue = function (key) {
|
||||
var entry = this.get(key)
|
||||
|
||||
|
||||
return entry && entry.value
|
||||
}
|
||||
|
||||
MemoryCache.prototype.clear = function() {
|
||||
Object.keys(this.cache).forEach(function(key) {
|
||||
|
||||
MemoryCache.prototype.clear = function () {
|
||||
Object.keys(this.cache).forEach(function (key) {
|
||||
this.delete(key)
|
||||
}, this)
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = MemoryCache
|
||||
|
||||
module.exports = MemoryCache
|
||||
|
||||
143
util/request.js
143
util/request.js
@@ -16,7 +16,7 @@ const chooseUserAgent = (ua = false) => {
|
||||
// iOS with qq micromsg
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML like Gecko) Mobile/14A456 QQ/6.5.7.408 V1_IPH_SQ_6.5.7_1_APP_A Pixel/750 Core/UIWebView NetType/4G Mem/103',
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.15(0x17000f27) NetType/WIFI Language/zh',
|
||||
// Android -> Huawei Xiaomi
|
||||
// Android -> Huawei Xiaomi
|
||||
'Mozilla/5.0 (Linux; Android 9; PCT-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.64 HuaweiBrowser/10.0.3.311 Mobile Safari/537.36',
|
||||
'Mozilla/5.0 (Linux; U; Android 9; zh-cn; Redmi Note 8 Build/PKQ1.190616.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.141 Mobile Safari/537.36 XiaoMi/MiuiBrowser/12.5.22',
|
||||
// Android + qq micromsg
|
||||
@@ -31,12 +31,15 @@ const chooseUserAgent = (ua = false) => {
|
||||
// Windows 10 Firefox / Chrome / Edge
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.30 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586'
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586',
|
||||
// Linux 就算了
|
||||
]
|
||||
],
|
||||
}
|
||||
let realUserAgentList = userAgentList[ua] || (userAgentList.mobile).concat(userAgentList.pc)
|
||||
return (['mobile', 'pc', false].indexOf(ua) > -1) ? realUserAgentList[Math.floor(Math.random() * realUserAgentList.length)] : ua
|
||||
let realUserAgentList =
|
||||
userAgentList[ua] || userAgentList.mobile.concat(userAgentList.pc)
|
||||
return ['mobile', 'pc', false].indexOf(ua) > -1
|
||||
? realUserAgentList[Math.floor(Math.random() * realUserAgentList.length)]
|
||||
: ua
|
||||
}
|
||||
const createRequest = (method, url, data, options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -45,16 +48,15 @@ const createRequest = (method, url, data, options) => {
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
if (url.includes('music.163.com'))
|
||||
headers['Referer'] = 'https://music.163.com'
|
||||
if (options.realIP)
|
||||
headers['X-Real-IP'] = options.realIP
|
||||
if (options.realIP) headers['X-Real-IP'] = options.realIP
|
||||
// headers['X-Real-IP'] = '118.88.88.88'
|
||||
if (typeof options.cookie === 'object')
|
||||
headers['Cookie'] = Object.keys(options.cookie)
|
||||
.map(
|
||||
key =>
|
||||
(key) =>
|
||||
encodeURIComponent(key) +
|
||||
'=' +
|
||||
encodeURIComponent(options.cookie[key])
|
||||
encodeURIComponent(options.cookie[key]),
|
||||
)
|
||||
.join('; ')
|
||||
else if (options.cookie) headers['Cookie'] = options.cookie
|
||||
@@ -71,35 +73,35 @@ const createRequest = (method, url, data, options) => {
|
||||
data = encrypt.linuxapi({
|
||||
method: method,
|
||||
url: url.replace(/\w*api/, 'api'),
|
||||
params: data
|
||||
params: data,
|
||||
})
|
||||
headers['User-Agent'] =
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
|
||||
url = 'https://music.163.com/api/linux/forward'
|
||||
} else if (options.crypto === 'eapi') {
|
||||
const cookie = options.cookie || {};
|
||||
const cookie = options.cookie || {}
|
||||
const csrfToken = cookie['__csrf'] || ''
|
||||
const header = {
|
||||
'osver': cookie.osver, //系统版本
|
||||
'deviceId': cookie.deviceId, //encrypt.base64.encode(imei + '\t02:00:00:00:00:00\t5106025eb79a5247\t70ffbaac7')
|
||||
'appver': cookie.appver || '6.1.1', // app版本
|
||||
'versioncode': cookie.versioncode || '140', //版本号
|
||||
'mobilename': cookie.mobilename, //设备model
|
||||
'buildver': cookie.buildver || Date.now().toString().substr(0, 10),
|
||||
'resolution': cookie.resolution || '1920x1080', //设备分辨率
|
||||
'__csrf': csrfToken,
|
||||
'os': cookie.os || 'android',
|
||||
'channel': cookie.channel,
|
||||
'requestId': `${Date.now()}_${Math.floor(Math.random() * 1000).toString().padStart(4, '0')}`
|
||||
osver: cookie.osver, //系统版本
|
||||
deviceId: cookie.deviceId, //encrypt.base64.encode(imei + '\t02:00:00:00:00:00\t5106025eb79a5247\t70ffbaac7')
|
||||
appver: cookie.appver || '6.1.1', // app版本
|
||||
versioncode: cookie.versioncode || '140', //版本号
|
||||
mobilename: cookie.mobilename, //设备model
|
||||
buildver: cookie.buildver || Date.now().toString().substr(0, 10),
|
||||
resolution: cookie.resolution || '1920x1080', //设备分辨率
|
||||
__csrf: csrfToken,
|
||||
os: cookie.os || 'android',
|
||||
channel: cookie.channel,
|
||||
requestId: `${Date.now()}_${Math.floor(Math.random() * 1000)
|
||||
.toString()
|
||||
.padStart(4, '0')}`,
|
||||
}
|
||||
if (cookie.MUSIC_U) header['MUSIC_U'] = cookie.MUSIC_U
|
||||
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
||||
headers['Cookie'] = Object.keys(header)
|
||||
.map(
|
||||
key =>
|
||||
encodeURIComponent(key) +
|
||||
'=' +
|
||||
encodeURIComponent(header[key])
|
||||
(key) =>
|
||||
encodeURIComponent(key) + '=' + encodeURIComponent(header[key]),
|
||||
)
|
||||
.join('; ')
|
||||
data.header = header
|
||||
@@ -112,7 +114,7 @@ const createRequest = (method, url, data, options) => {
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
body: queryString.stringify(data)
|
||||
body: queryString.stringify(data),
|
||||
}
|
||||
|
||||
if (options.crypto === 'eapi') settings.encoding = null
|
||||
@@ -123,62 +125,55 @@ const createRequest = (method, url, data, options) => {
|
||||
settings.proxy = options.proxy
|
||||
}
|
||||
|
||||
request(
|
||||
settings,
|
||||
(err, res, body) => {
|
||||
if (err) {
|
||||
answer.status = 502
|
||||
answer.body = { code: 502, msg: err.stack }
|
||||
reject(answer)
|
||||
} else {
|
||||
answer.cookie = (res.headers['set-cookie'] || []).map(x =>
|
||||
x.replace(/\s*Domain=[^(;|$)]+;*/, '')
|
||||
)
|
||||
try {
|
||||
if (options.crypto === 'eapi') {
|
||||
|
||||
zlib.unzip(body, function (err, buffer) {
|
||||
const _buffer = err ? body : buffer
|
||||
request(settings, (err, res, body) => {
|
||||
if (err) {
|
||||
answer.status = 502
|
||||
answer.body = { code: 502, msg: err.stack }
|
||||
reject(answer)
|
||||
} else {
|
||||
answer.cookie = (res.headers['set-cookie'] || []).map((x) =>
|
||||
x.replace(/\s*Domain=[^(;|$)]+;*/, ''),
|
||||
)
|
||||
try {
|
||||
if (options.crypto === 'eapi') {
|
||||
zlib.unzip(body, function (err, buffer) {
|
||||
const _buffer = err ? body : buffer
|
||||
try {
|
||||
try {
|
||||
try {
|
||||
answer.body = JSON.parse(encrypt.decrypt(_buffer).toString())
|
||||
answer.status = answer.body.code || res.statusCode
|
||||
} catch (e) {
|
||||
answer.body = JSON.parse(_buffer.toString())
|
||||
answer.status = res.statusCode
|
||||
}
|
||||
answer.body = JSON.parse(encrypt.decrypt(_buffer).toString())
|
||||
answer.status = answer.body.code || res.statusCode
|
||||
} catch (e) {
|
||||
answer.body = _buffer.toString()
|
||||
answer.body = JSON.parse(_buffer.toString())
|
||||
answer.status = res.statusCode
|
||||
}
|
||||
answer.status =
|
||||
100 < answer.status && answer.status < 600 ? answer.status : 400
|
||||
if (answer.status === 200) resolve(answer)
|
||||
else reject(answer)
|
||||
});
|
||||
return false
|
||||
|
||||
} else {
|
||||
|
||||
answer.body = JSON.parse(body)
|
||||
answer.status = answer.body.code || res.statusCode
|
||||
if (answer.body.code === 502) {
|
||||
answer.status = 200
|
||||
} catch (e) {
|
||||
answer.body = _buffer.toString()
|
||||
answer.status = res.statusCode
|
||||
}
|
||||
answer.status =
|
||||
100 < answer.status && answer.status < 600 ? answer.status : 400
|
||||
if (answer.status === 200) resolve(answer)
|
||||
else reject(answer)
|
||||
})
|
||||
return false
|
||||
} else {
|
||||
answer.body = JSON.parse(body)
|
||||
answer.status = answer.body.code || res.statusCode
|
||||
if (answer.body.code === 502) {
|
||||
answer.status = 200
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
answer.body = body
|
||||
answer.status = res.statusCode
|
||||
}
|
||||
|
||||
answer.status =
|
||||
100 < answer.status && answer.status < 600 ? answer.status : 400
|
||||
if (answer.status == 200) resolve(answer)
|
||||
else reject(answer)
|
||||
} catch (e) {
|
||||
answer.body = body
|
||||
answer.status = res.statusCode
|
||||
}
|
||||
|
||||
answer.status =
|
||||
100 < answer.status && answer.status < 600 ? answer.status : 400
|
||||
if (answer.status == 200) resolve(answer)
|
||||
else reject(answer)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user