Last active
August 11, 2018 00:54
-
-
Save duzun/234bd3ca69b243bb32bb to your computer and use it in GitHub Desktop.
Compresses images using on-line services.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# https://gist.github.com/duzun/234bd3ca69b243bb32bb | |
basedir=`dirname "$0"` | |
optimagurl=https://duzun.me/optimag.js | |
optimagjs=$basedir/optimag.js | |
case `uname` in | |
*CYGWIN*) optimagjs=`cygpath -w "$optimagjs"`;; | |
*MINGW64*) optimagjs=`cygpath -w "$optimagjs"`;; | |
esac | |
if [ ! -f "$optimagjs" ]; then | |
curl $optimagurl > $optimagjs | |
fi | |
if [ ! -f "$optimagjs" ]; then | |
wget $optimagurl -O $optimagjs | |
fi | |
if [ ! -x "$optimagjs" ]; then | |
chmod +x $optimagjs | |
fi | |
node "$optimagjs" $@ | |
ret=$? | |
exit $ret |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@ECHO off | |
REM https://gist.github.com/duzun/234bd3ca69b243bb32bb | |
set optimagurl=https://duzun.me/optimag.js | |
if not exist %~dp0\optimag.js ( | |
curl %optimagurl% > %~dp0\optimag.js | |
) | |
if not exist %~dp0\optimag.js ( | |
wget %optimagurl% -O %~dp0\optimag.js | |
) | |
node %~dp0\optimag.js %* | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/** | |
* Compresses images using on-line services. | |
* | |
* Usage: | |
* node optimag.js [-t|-k|-c] source-pic.png [compressed-pic.png] | |
* | |
* Auto-update: | |
* node optimag.js -u [<url-to-optimag-source.js>] | |
* | |
* Install: | |
* wget -O /usr/local/lib/optimag.js https://duzun.me/optimag.js && chmod +x /usr/local/lib/optimag.js && ln -si /usr/local/lib/optimag.js /usr/local/bin/optimag | |
* or | |
* curl https://duzun.me/optimag.js > /usr/local/lib/optimag.js && chmod +x /usr/local/lib/optimag.js && ln -si /usr/local/lib/optimag.js /usr/local/bin/optimag | |
* | |
* | |
* Online services: | |
* https://tinypng.com/ | |
* https://kraken.io/web-interface | |
* https://compressor.io/compress | |
* | |
* @author Dumitru Uzun (DUzun.Me) | |
* @version 2.2.2 | |
*/ | |
var VERSION = '2.2.2'; | |
// For PNG tinypng & kraken are good, compressor and sometimes might be good | |
// For JPG tinypng is the best, kraken helps a little bit, compressor is useless | |
// compressor: Doesn't display errors, max 10Mb | |
// kraken: max 1Mb, supports CMYK | |
var https = require('https'); | |
var http = require('http'); | |
var fs = require('fs'); | |
var url = require('url'); | |
var path = require('path'); | |
var zlib = require('zlib'); | |
var log = console.log.bind(console); | |
var TB = "\t"; | |
var CR = "\r"; | |
var LF = "\n"; | |
var CRLF = CR+LF; | |
var update_url = 'https://duzun.me/optimag.js'; | |
tinypng.id = 'tinypng'; | |
tinypng.url = 'https://tinypng.com/web/shrink'; | |
tinypng.fileExts = ['.jpg', '.jpeg', '.png'] | |
kraken.id = 'kraken'; | |
kraken.url = 'https://kraken.io/uploader'; | |
kraken.Referer = 'https://kraken.io/web-interface'; | |
kraken.maxSize = 1024*1024; | |
tinypng.fileExts = ['.jpg', '.jpeg', '.png'] | |
compressor.id = 'compressor'; | |
compressor.url = 'https://compressor.io/server/Lossless.php'; | |
compressor.Referer = 'https://compressor.io/compress'; | |
compressor.maxSize = 10*1024*1024; | |
tinypng.fileExts = ['.jpg', '.jpeg', '.png'] | |
if ( !module.parent ) { | |
var processor = tinypng; | |
var src; | |
var dest; | |
for(var i=2, l=process.argv.length; i<l; i++) { | |
var a = process.argv[i]; | |
switch(a) { | |
case '--update': | |
case '-u': { | |
autoUpdate(process.argv[i+1] || update_url); | |
i = l; | |
processor = false; | |
} break; | |
case '-v': { | |
log('v' + VERSION); | |
processor = false; | |
} break; | |
case '-t': { | |
processor = tinypng; | |
} break; | |
case '-k': { | |
processor = kraken; | |
} break; | |
case '-c': { | |
processor = compressor; | |
} break; | |
default: { | |
if ( !src ) { | |
src = a; | |
} | |
else { | |
dest = a; | |
} | |
} | |
} | |
} | |
if ( !src ) src = '.'; | |
if ( !dest ) dest = src; | |
if ( processor ) { | |
log('Processor: ' + (processor.id || processor.name)); | |
processor(src, dest, function (err, info, contents, resp) { | |
if ( err ) { | |
console.error(err); | |
process.exit(1); | |
} | |
else { | |
if ( info ) { | |
var ratio = info.processedSize ? info.outputSize / info.processedSize : 1; | |
log(['Totals:' | |
, 'ratio ' + fmtPercent(ratio || 1) | |
, 'saved ' + fmtSize(info.saved || 0) | |
, 'processed ' + fmtSize(info.processedSize) + '/' + fmtSize(info.totalSize) + ' in ' + fmtSize(info.filesCount) + ' files' | |
].join(LF+TB)); | |
} | |
process.exit(0); | |
} | |
}); | |
} | |
} | |
else { | |
module.exports.tinypng = tinypng; | |
module.exports.kraken = kraken; | |
module.exports.compressor = compressor; | |
} | |
// - Compressors ----------------------------------------------------------- | |
function tinypng(src, dest, cb, isRec) { | |
var _src = path.resolve(src); | |
var _dest = src != dest ? path.resolve(dest) : _src; | |
var isOtherDest = _src != _dest; | |
var srcStat = fs.statSync(_src); | |
if ( srcStat.isDirectory() ) { | |
// Don't go deeper then one level | |
if ( isRec ) { | |
cb(); | |
} | |
else { | |
processDir(_src, _dest, tinypng, cb); | |
} | |
return ; | |
} | |
// Browser state object | |
var state = {}; | |
var ext = path.extname(src).replace(/^\.+/, ''); | |
ext = {jpg:'jpeg'}[ext] || ext; | |
var data = fs.readFileSync(_src); | |
var fileSize = data.length; | |
out('"' + path.basename(_src) + '"' + TB + fmtSize(fileSize) + ' >>t'); | |
// log('t<- ' + fmtSize(fileSize) + ' of "'+path.basename(_src)+'"'); | |
var req = request({ | |
url: tinypng.url | |
, data: data | |
, headers: { | |
"Content-Type" : "image/" + ext | |
, "Pragma" : "no-cache" | |
, "Cache-Control" : "no-cache" | |
, "Connection" : "close" | |
} | |
}, function (err, data, resp, state) { | |
if ( err ) { cb(err); return; } | |
next(data.toString('utf-8'), resp); | |
}, state); | |
function next(json, res) { | |
var obj = JSON.parse(json); | |
if ( !obj.output ) { | |
var err = new Error(obj.message); | |
err.error = obj.error; | |
err.message = obj.message; | |
out('>x '+err.error + ':' + err.message, true); | |
// log(err.error + ':', err.message); | |
cb(err); | |
} | |
else { | |
obj.saved = obj.input.size - obj.output.size; | |
// log('Ratio:' | |
// , obj.output | |
// ? obj.output.size + ':' + obj.input.size + | |
// ' = ' + obj.output.ratio + | |
// ' < ' + fmtSize(obj.saved) | |
// : obj | |
// ); | |
if ( obj.output.ratio < 1 ) { | |
var url = obj.output.url; | |
out('>> '+fmtSize(obj.output.size)); | |
if ( isOtherDest ) { | |
out(' to "' + path.basename(_dest) + '"'); | |
} | |
else { | |
if ( obj.output ) { | |
out(' = '+fmtPercent(obj.output.ratio)); | |
out(' < '+fmtSize(obj.saved)); | |
} | |
} | |
// log('t-> '+fmtSize(obj.output.size)+' to "' + path.basename(_dest) + '"'); | |
var req = request({ | |
url: obj.output.url | |
, filename: _dest | |
, filesize: obj.output.size | |
}, function (err, data, resp, state) { | |
out(' .', true); | |
cb(err, obj, data, resp); | |
}, state); | |
} | |
else { | |
// log('No compression'); | |
out('>| < 0b'); | |
if ( isOtherDest ) { | |
out(', original to "' + path.basename(_dest) + '"') | |
// log('Writing original file to "'+path.basename(_dest)+'"'); | |
fs.writeFile(_dest, data, function (err) { | |
out(' .', true); | |
cb(err, obj, data); | |
}); | |
} | |
else { | |
out(' .', true); | |
cb(null, obj) | |
} | |
} | |
} | |
} | |
return req; | |
} | |
function kraken(src, dest, cb, isRec) { | |
var _src = path.resolve(src); | |
var _dest = src == dest ? _src : path.resolve(dest); | |
var srcStat = fs.statSync(_src); | |
if ( srcStat.isDirectory() ) { | |
// Don't go deeper then one level | |
if ( isRec ) { | |
cb(); | |
} | |
else { | |
processDir(_src, _dest, kraken, cb); | |
} | |
return ; | |
} | |
var ext = path.extname(src).replace(/^\.+/, ''); | |
ext = {jpg:'jpeg'}[ext] || ext; | |
var boundary = '----WebKitFormBoundary' + randStr(16); | |
var beginData = '--' + boundary + CRLF + | |
'Content-Disposition: form-data; name="files"; filename="'+path.basename(_src)+'"' + CRLF + | |
"Content-Type: image/" + ext + CRLF + CRLF | |
; | |
var endData = CRLF + '--' + boundary + CRLF + | |
'Content-Disposition: form-data; name="lossy"' + CRLF + CRLF + | |
'false' + | |
CRLF + '--' + boundary + '--' + CRLF; | |
var data = fs.readFileSync(_src); | |
var fileSize = data.length; | |
data = Buffer.concat([new Buffer(beginData, 'utf8'), data, new Buffer(endData, 'utf8')]); | |
// Browser state object | |
var state = {}; | |
log('k<- ' + fmtSize(fileSize) + ' of "'+path.basename(_src)+'"'); | |
var req = request({ | |
url: kraken.url | |
, data: data | |
, headers: { | |
"Content-Type" : "multipart/form-data; boundary=" + boundary | |
, "Referer" : kraken.Referer | |
, "Pragma" : "no-cache" | |
, "Cache-Control" : "no-cache" | |
// , "Connection" : "close" | |
} | |
}, function (err, data, resp, state) { | |
if ( err ) { cb(err); return; } | |
next(data.toString('utf-8'), resp); | |
}, state); | |
function next(json, res) { | |
var _obj = JSON.parse(json); | |
if ( !_obj.originalSize || _obj.status == 'error' ) { | |
var err = new Error(_obj.err); | |
err.error = _obj.status; | |
err.message = _obj.err; | |
log(err.error + ':', err.message); | |
cb(err); | |
} | |
else { | |
// Convert response to something similar to kraken's response | |
var obj = {input:{}, output:{}}; | |
obj.input.size = _obj.originalSize; | |
obj.output.size = _obj.krakedSize; | |
obj.output.ratio = Math.round(obj.output.size / obj.input.size * 1e4) / 1e4; | |
obj.output.url = _obj.url; | |
obj.saved = obj.input.size - obj.output.size; | |
log('Ratio:' | |
, obj.output | |
? obj.output.size + ':' + obj.input.size + | |
' = ' + obj.output.ratio + | |
' < ' + fmtSize(obj.saved) | |
: obj | |
); | |
if ( obj.output.ratio < 1 ) { | |
log('k-> '+fmtSize(obj.output.size)+' to "' + path.basename(_dest) + '"'); | |
var req = request({ | |
url: obj.output.url | |
, filename: _dest | |
, filesize: obj.output.size | |
, headers: { | |
"Referer": kraken.Referer | |
, "Pragma" : "no-cache" | |
, "Cache-Control" : "no-cache" | |
} | |
}, function (err, data, resp, state) { | |
cb(err, obj, data, resp); | |
}, state); | |
} | |
else { | |
log('No compression'); | |
if ( _src != _dest ) { | |
log('Writing original file to "'+path.basename(_dest)+'"'); | |
fs.writeFile(_dest, data, function (err) { | |
cb(err, obj, data); | |
}); | |
} | |
else { | |
cb(null, obj) | |
} | |
} | |
} | |
} | |
return req; | |
} | |
function compressor(src, dest, cb, isRec) { | |
var _src = path.resolve(src); | |
var _dest = src == dest ? _src : path.resolve(dest); | |
var srcStat = fs.statSync(_src); | |
if ( srcStat.isDirectory() ) { | |
// Don't go deeper then one level | |
if ( isRec ) { | |
cb(); | |
} | |
else { | |
processDir(_src, _dest, compressor, cb); | |
} | |
return ; | |
} | |
var ext = path.extname(src).replace(/^\.+/, ''); | |
ext = {jpg:'jpeg'}[ext] || ext; | |
var boundary = '----WebKitFormBoundary' + randStr(16); | |
var beginData = '--' + boundary + CRLF + | |
'Content-Disposition: form-data; name="files[]"; filename="'+path.basename(_src)+'"' + CRLF + | |
"Content-Type: image/" + ext + CRLF + CRLF | |
; | |
var endData = CRLF + '--' + boundary + '--' + CRLF; | |
var data = fs.readFileSync(_src); | |
var fileSize = data.length; | |
data = Buffer.concat([new Buffer(beginData, 'utf8'), data, new Buffer(endData, 'utf8')]); | |
// Browser state object | |
var state = {}; | |
log('c<- ' + fmtSize(fileSize) + ' of "'+path.basename(_src)+'"'); | |
var req = request({ | |
url: compressor.url | |
, data: data | |
, headers: { | |
"Content-Type" : "multipart/form-data; boundary=" + boundary | |
, "Pragma" : "no-cache" | |
, "Cache-Control" : "no-cache" | |
, "Referer" : compressor.Referer | |
, "Accept" : "application/json, text/javascript, */*; q=0.01" | |
, "X-Requested-With" : "XMLHttpRequest" | |
// , "Connection" : "close" | |
} | |
}, function (err, data, resp, state) { | |
if ( err ) { cb(err); return; } | |
next(data.toString('utf-8'), resp); | |
}, state); | |
function next(json, res) { | |
var _obj = JSON.parse(json); | |
var _files = _obj.files; | |
var _file = _files[0]; | |
// Convert response to something similar to compressor's response | |
var obj = {input:{}, output:{}}; | |
obj.input.size = _file.size; | |
obj.input.name = _file.name; | |
obj.output.size = _file.sizeAfter; | |
obj.output.ratio = Math.round(obj.output.size / obj.input.size * 1e4) / 1e4; | |
obj.output.url = _file.url; | |
if ( !obj.output.size || _obj.error ) { | |
var err = new Error(_obj.error); | |
err.error = _obj.error; | |
err.message = _obj.message; | |
log(err.error + ':', err.message, _obj); | |
cb(err); | |
} | |
else { | |
obj.saved = obj.input.size - obj.output.size; | |
log('Ratio:' | |
, obj.output | |
? obj.output.size + ':' + obj.input.size + | |
' = ' + obj.output.ratio + | |
' < ' + fmtSize(obj.saved) | |
: obj | |
); | |
if ( obj.output.ratio < 1 ) { | |
log('c-> '+fmtSize(obj.output.size)+' to "' + path.basename(_dest) + '"'); | |
var req = request({ | |
url: obj.output.url | |
, filename: _dest | |
, filesize: obj.output.size | |
, headers: { | |
"Referer": compressor.Referer | |
} | |
}, function (err, data, resp, state) { | |
cb(err, obj, data, resp); | |
}, state); | |
} | |
else { | |
log('No compression'); | |
if ( _src != _dest ) { | |
log('Writing original file to "'+path.basename(_dest)+'"'); | |
fs.writeFile(_dest, data, function (err) { | |
cb(err, obj, data); | |
}); | |
} | |
else { | |
cb(null, obj) | |
} | |
} | |
} | |
} | |
return req; | |
} | |
// - Auto-update ----------------------------------------------------------- | |
function autoUpdate(url, cb) { | |
fs.realpath(process.argv[1], function (err, filename) { | |
if ( err ) if ( cb ) cb(err); else throw err; | |
request({ | |
url: url | |
// , filename: filename | |
}, function (err, data, resp, state) { | |
if ( err ) if ( cb ) cb(err); else throw err; | |
var _str = data.toString('utf8'); | |
var _ver = _str.match(/\*\s+\@version\s+([0-9]+\.[0-9]+\.[0-9]+)/i); | |
if ( !_ver ) { | |
var err = new Error("Can't find version tag in the received file :-("); | |
if ( cb ) cb(err); else throw err; | |
} | |
_ver = _ver[1]; | |
if ( VERSION == _ver ) { | |
if ( cb ) { | |
cb(null, {version: VERSION, filename: filename, remote_ver: _ver}); | |
} | |
else { | |
log("No update available, version " + VERSION); | |
} | |
} | |
else fs.writeFile(filename, data, function (err) { | |
if ( cb ) { | |
cb(err, {version: _ver, filename: filename, data: data}); | |
} | |
else { | |
log('New version ' + _ver + ' in ' + filename); | |
} | |
}); | |
}); | |
}); | |
} | |
// - Helpers ----------------------------------------------------------- | |
function processDir(dirName, dest, processor, cb) { | |
fs.readdir(dirName, function (err, files) { | |
var fileExts = processor.fileExts || ['.jpg', '.jpeg', '.png']; | |
files = files | |
.filter(function (f) { | |
var e = path.extname(f).toLowerCase(); | |
var isImg = ~fileExts.indexOf(e); | |
return isImg; | |
}) | |
// Sort files by size - bigger files first: | |
.map(function (f) { | |
var fn = path.join(dirName, f); | |
var stat = fs.statSync(fn); | |
return {n:fn, s:stat.size}; | |
}) | |
; | |
if ( processor.maxSize ) { | |
var maxSize = processor.maxSize; | |
files = files | |
.filter(function (o) { return o.s <= maxSize; }) | |
; | |
} | |
var processedSize = 0; | |
var outputSize = 0; | |
var savedSize = 0; | |
var totalSize = 0; | |
files = files | |
.sort(function (a, b) {return b.s - a.s;}) | |
.map(function (o) { | |
totalSize += o.s; | |
return o.n; | |
}) | |
; | |
var idx = 0; | |
function log_report() { | |
var progress = (idx / files.length + processedSize / totalSize) / 2; | |
var ratio = processedSize ? outputSize / processedSize : 1; | |
log([ | |
fmtPercent(progress) | |
, idx + '/' + files.length | |
, lpad(fmtSize(processedSize), 7) + '/' + lpad(fmtSize(totalSize), 7) | |
, 'ratio ' + fmtPercent(ratio) | |
, 'saved ' + fmtSize(savedSize) | |
].join(TB)); | |
} | |
;(function process(err, obj) { | |
if ( err && err.error == 'TooManyRequests' ) { | |
out('Retrying in '); | |
count_back(3, function () { | |
out(LF); | |
log_report(); | |
var fn = files[idx-1]; | |
processor(fn, path.join(dest, path.basename(fn)), process, true); | |
}); | |
return; | |
} | |
if ( obj ) { | |
if ( obj.saved ) savedSize += obj.saved; | |
if ( obj.output.size ) outputSize += obj.output.size; | |
if ( obj.input.size ) processedSize += obj.input.size; | |
} | |
var fn = files[idx]; | |
if ( idx >= files.length ) { | |
if ( obj ) { | |
obj.saved = savedSize; | |
obj.savedSize = savedSize; | |
obj.outputSize = outputSize; | |
obj.processedSize = processedSize; | |
obj.totalSize = totalSize; | |
obj.filesCount = files.length; | |
} | |
cb.apply(this, arguments); | |
return; | |
} | |
idx++; | |
// log((savedSize ? 'Saved ' + fmtSize(savedSize) + ' in ' : 'Processing ') + idx + ' of ' + files.length); | |
log_report(); | |
processor(fn, path.join(dest, path.basename(fn)), process, true); | |
} | |
()); | |
}); | |
} | |
/** | |
* Make an http(s) request. | |
* | |
* Defaults to GET if no .data, and POST for .data. | |
* Can have .cookie. | |
* The .data is automatically .write()en. | |
* If .filename present, downloads contents to it. | |
* Additionally .filesize can be checked before writing to .filename. | |
* | |
* @param (object|string)opt - url or options object | |
* @param (function)cb(err, data, resp, state) | |
* @param (object)state - to store cookie so far | |
* | |
* @return (Request) .end()ed | |
*/ | |
function request(opt, cb, state) { | |
// Swap cb with state, if state is the cb: | |
if ( typeof state == 'function' && typeof cb != 'function' ) { | |
var t = cb; | |
cb = state; | |
state = t; | |
} | |
if ( typeof opt == 'string' ) opt = { url: opt }; | |
var protocol; | |
if ( opt.url ) { | |
var _purl = url.parse(opt.url); | |
opt.hostname = _purl.hostname | |
opt.path = _purl.path | |
protocol = _purl.protocol; | |
if ( !('port' in opt) ) opt.port = protocol == 'https:' ? 443 : 80; | |
} | |
else { | |
protocol = opt.port == 443 ? 'https:' : 'http:'; | |
} | |
// Default headers | |
var _headers = { | |
// "User-Agent" : "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36" | |
"User-Agent" : "Mozilla/5.0 (Windows NT 6.3)" | |
, "Accept-Encoding" : "gzip, deflate" | |
, "Accept-Language" : "en-US,en;q=0.8" | |
, "Accept" : "*/*" | |
}; | |
_headers['Origin'] = protocol + '//' + (opt.hostname || opt.host); | |
_headers['Referer'] = _headers['Origin'] + '/'; | |
var data = opt.data; | |
if ( 'data' in opt ) { | |
if ( !opt.method ) opt.method = 'POST'; | |
// Default method for data requests is GET | |
_headers['Content-Length'] = data && data.length || 0; | |
} | |
// Default method is GET | |
if ( !opt.method ) opt.method = 'GET'; | |
var cookie = state && state.cookie; | |
// opt.cookie can overwrite state.cookie; | |
if ( 'cookie' in opt ) { | |
cookie = opt.cookie; | |
} | |
if ( !isEmptyObject(cookie) ) { | |
_headers['Cookie'] = cookies2header(cookie); | |
} | |
// Headers in opt.headers are more important then the dynamic ones | |
if ( opt.headers ) { | |
Object.keys(opt.headers).forEach(function (n) { | |
_headers[n] = opt.headers[n]; | |
}); | |
} | |
opt.headers = _headers; | |
// File download | |
var filename = opt.filename; | |
var filesize = opt.filesize; // expected filesize | |
// Remove special options: | |
delete opt.url; | |
delete opt.data; | |
delete opt.cookie; | |
delete opt.filename; | |
delete opt.filesize; | |
var hlib = protocol == 'https:' ? https : http; | |
var req = hlib.request(opt, function(resp) { | |
// log('STATUS: ' + resp.statusCode); | |
if ( state ) { | |
state.cookie = getCookies(resp, state.cookie); | |
} | |
// File download request: | |
if ( filename ) { | |
if ( resp.statusCode == 200 ) { | |
onHttpResponse(resp, function (err, data) { | |
// Check filesize | |
if ( !err && filesize != undefined && filesize != data.length ) { | |
err = new Error('Data doesn\'t have the expected size of "'+filesize+'"'); | |
err.expectedSize = filesize; | |
err.actualSize = data.length; | |
} | |
if ( err ) { | |
cb(err, data, resp, state); | |
} | |
else { | |
fs.writeFile(filename, data, function (err) { | |
cb(err, data, resp, state); | |
}); | |
} | |
}); | |
} | |
else { | |
var err = new Error('Can\'t download file: ' + resp.statusMessage); | |
err.code = resp.statusCode; | |
err.status = resp.statusMessage; | |
cb(err, null, resp, state); | |
} | |
} | |
// Data request: | |
else { | |
onHttpResponse(resp, function (err, data) { | |
cb(err, data, resp, state); | |
}); | |
} | |
}); | |
req.on('error', function (err) { | |
cb(err, null, null, state); | |
}); | |
if ( data ) req.write(data); | |
req.end(); | |
return req; | |
} | |
function onHttpResponse(resp, cb) { | |
var body = []; | |
resp.on('data', function (chunk) { body.push(chunk); }); | |
resp.on('end', function () { | |
body = Buffer.concat(body); | |
var encoding = resp.headers['content-encoding']; | |
if (encoding && encoding.indexOf('gzip') >= 0) { | |
zlib.gunzip(body, function(err, dezipped) { | |
if ( err ) { | |
cb(err, body, resp); | |
return; | |
} | |
cb(null, dezipped, resp); | |
}); | |
} | |
else { | |
cb(null, body, resp); | |
} | |
}); | |
} | |
function getCookies(resp, list) { | |
if ( !list ) list = {}; | |
var cooks = resp.headers['set-cookie']; | |
if ( cooks ) { | |
cooks.forEach(function (c) { | |
var t = c.split(';'); | |
var n = t[0].split('='); | |
list[n[0]] = n[1]; | |
}); | |
} | |
return list; | |
} | |
function cookies2header(list) { | |
if ( list ) { | |
return Object.keys(list) | |
.map(function (n) { | |
var v = list[n]; | |
return n + '=' + v; | |
}) | |
.join('; ') | |
; | |
} | |
} | |
function out(str, lf) { | |
process.stdout.write(str); | |
if ( lf ) process.stdout.write(LF); | |
} | |
function count_back(sec, cb) { | |
var _a = []; | |
(function _to_(sec) { | |
var msg = sec.toFixed(0) + ' sec'; | |
out(msg); | |
setTimeout(function () { | |
_a.length = msg.length + 1; | |
out(_a.join('\x08')); | |
if ( sec <= 1 ) { | |
cb && cb(); | |
} | |
else { | |
_to_(sec-1); | |
} | |
}, Math.min(1, sec) * 1e3); | |
}(parseFloat(sec))); | |
} | |
/** | |
* Generate a random alphanumeric string of given length. | |
*/ | |
function randStr(len) { | |
if ( len == undefined ) len = 16; | |
var ret = []; | |
for(var i = 0, c; i < len; i++) { | |
c = Math.floor(Math.random() * 62) + 48; | |
if ( 57 < c ) c += 7; | |
if ( 90 < c ) c += 6; | |
ret.push(c); | |
} | |
return String.fromCharCode.apply(String, ret); | |
} | |
function isEmptyObject(obj) { | |
if ( obj ) for ( var i in obj ) if ( Object.prototype.hasOwnProperty.call(obj, i) ) return false; | |
return true; | |
} | |
function fmtSize(size) { | |
var ret = Number(size) | |
, u = 'b' | |
; | |
if ( ret > 1048576 ) { | |
ret = (ret / 1048576).toFixed(2); | |
u = 'Mb'; | |
} | |
else | |
if ( ret > 1024 ) { | |
ret = (ret / 1024).toFixed(2); | |
u = 'Kb'; | |
} | |
return ret + u; | |
} | |
function fmtPercent(abs) { | |
return lpad((abs*100).toFixed(2), 6) + '%'; | |
} | |
function lpad(str, size, chr) { | |
str = String(str); | |
var len = str.length; | |
var dif = size - len; | |
if ( dif > 0 ) { | |
if ( !chr ) chr = ' '; | |
var a = []; | |
a.length = dif + 1; | |
str = a.join(chr) + str; | |
} | |
return str; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Please note that the tinypng.url ('https://tinypng.com/web/shrink') is not the official endpoint for using the TinyPNG/TinyJPG API.
Read https://tinypng.com/developers for more information on our official API.