Bu script direkt olarak kurulamaz. Başka scriptler için bir kütüphanedir ve meta yönergeleri içerir // @require https://update.greasyfork.org/scripts/401726/852379/imageHostUploader.js
// ==UserScript==
// @name imageHostUploader
// @namespace https://greasyfork.org/cs/users/321857-anakunda
// @version 2.07
// @author Anakunda
// @require https://greasyfork.org/scripts/408084-xhrlib/code/xhrLib.js
// @require https://greasyfork.org/scripts/404516-progressbars/code/progressBars.js
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
// https://funkyimg.com/ (4MB limit)
// http://ge.tt/ (no HTTPS)
// http://savephoto.ru/ (no HTTPS)
'use strict';
const minUploadSpeed = parseFloat(GM_getValue('min_upload_speed')); // in Mbit/s
const rehostTimeout = 30000;
const urlParser = /^\s*(https?:\/\/\S+)\s*$/i;
const nonWordStripper = /[\x00-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\xFF]+/g;
var testRemoteSizes = GM_getValue('test_remote_sizes');
if (testRemoteSizes === undefined) GM_setValue('test_remote_sizes', (testRemoteSizes = false)); // time consuming
var inputDataHandler, textAreaDropHandler, textAreaPasteHandler, imageUrlResolver;
function imageHostUploaderInit(_inputDataHandler, _textAreaDropHandler, _textAreaPasteHandler, _imageUrlResolver) {
inputDataHandler = _inputDataHandler;
textAreaDropHandler = _textAreaDropHandler;
textAreaPasteHandler = _textAreaPasteHandler;
imageUrlResolver = _imageUrlResolver;
}
String.prototype.toASCII = function() {
return this.normalize("NFKD").replace(/[\x00-\x1F\u0080-\uFFFF]/g, '');
};
File.prototype.toContent = function() {
return new Promise((resolve, reject) => {
var reader = new FileReader();
reader.onload = () => {
if (reader.result.length != this.size)
console.warn(`FileReader: binary string read length mismatch (${reader.result.length} ≠ ${this.size})`);
resolve({ name: this.name, type: this.type, size: reader.result.length, data: reader.result });
};
reader.onerror = reader.ontimeout = () => { reject(`FileReader error (${this.name})`) };
reader.readAsBinaryString(this);
});
};
function getUploadTimeout(totalSize) {
return minUploadSpeed > 0 && totalSize > 0 ?
Math.max(Math.ceil(totalSize * 8000 / minUploadSpeed / 2**20), 10000) : undefined;
}
class PTPimg {
constructor() {
this.alias = 'PTPimg';
this.origin = 'https://ptpimg.me';
this.types = ['png', 'jpeg', 'gif', 'bmp'];
this.whitelist = [
'passthepopcorn.me', 'redacted.ch', 'orpheus.network', 'notwhat.cd', 'dicmusic.club', 'broadcasthe.net',
];
if (!(this.apiKey = GM_getValue('ptpimg_api_key')) && window.localStorage.ptpimg_it) try {
if (this.apiKey = JSON.parse(window.localStorage.ptpimg_it).api_key) GM_setValue('ptpimg_api_key', this.apiKey);
} catch(e) { console.debug(e) }
if (this.apiKey === undefined) GM_setValue('ptpimg_api_key', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
if (this.batchLimit && images.length > this.batchLimit)
return Promise.reject('batch limit exceeded (' + this.batchLimit + ')');
return this.setSession().then(apiKey => new Promise((resolve, reject) => {
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
images.forEach((image, ndx) => {
formData += 'Content-Disposition: form-data; name="file-upload[' + ndx + ']"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="api_key"\r\n\r\n';
formData += apiKey + '\r\n';
formData += '--' + boundary + '--\r\n';
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload.php',
responseType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
},
data: formData,
binary: true,
timeout: getUploadTimeout(formData.length),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (!response.response) return reject('void response');
if (response.response.length < images.length) {
console.warn('PTPimg returning incomplete list of images (', response.response, ')');
return reject(`not all images uploaded (${response.response.length}/${images.length})`);
}
if (response.response.length > images.length)
console.warn('PTPimg returns more links than expected (', response.response, images, ')');
resolve(response.response.map((item, ndx) => {
if (!item.ext && /\.([a-z]+)(?=$|[\#\?])/i.test(images[ndx].name)) item.ext = RegExp.$1;
return this.origin + '/' + item.code + '.' + item.ext;
}));
},
onprogress: typeof progressHandler == 'function' ? progressHandler : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}));
}
rehost(urls) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
if (this.batchLimit && urls.length > this.batchLimit)
return Promise.reject('batch limit exceeded (' + this.batchLimit + ')');
return this.setSession().then(apiKey => Promise.all(urls.map(url => {
if (!urlParser.test(url)) return Promise.reject('URL not valid (' + url + ')');
var hostname = new URL(url).hostname;
if (hostname == 'img.discogs.com' || hostname.endsWith('omdb.org')) {
return verifyImageUrl('https://reho.st/' + url)
.catch(reason => imageHostHandlers.catbox.rehost([url]).then(imgUrls => imgUrls[0]))
.catch(reason => imageHostHandlers.pixhost.rehost([url]).then(imgUrls => imgUrls[0].original))
.catch(reason => this.reupload(url));
} else if (!['png', 'jpg', 'jpeg', 'jfif', 'gif', 'bmp'].some(ext => url.toLowerCase().endsWith('.' + ext))) {
return verifyImageUrl(url + '#.jpg')
.catch(reason => imageHostHandlers.imgbb.rehost([url]).then(imgUrls => imgUrls[0].original))
.catch(reason => imageHostHandlers.jerking.rehost([url]).then(imgUrls => imgUrls[0].original))
.catch(reason => imageHostHandlers.pixhost.rehost([url]).then(imgUrls => imgUrls[0]).original)
}
return verifyImageUrl(url);
})).then(imageUrls => {
console.debug('PTPimg.rehost(...) input:', imageUrls);
var formData = new URLSearchParams({
'link-upload': imageUrls.join('\r\n'),
'api_key': apiKey,
});
return globalFetch(this.origin + '/upload.php', {
responseType: 'json',
timeout: imageUrls.length * rehostTimeout,
}, formData).then(response => {
if (!response.response) return Promise.reject('void response');
if (response.response.length < imageUrls.length) {
console.warn('PTPimg returning incomplete list of images (', response.response, ')');
return Promise.reject(`not all images rehosted to (${response.response.length}/${imageUrls.length})`)
}
if (response.response.length > imageUrls.length)
console.warn('PTPimg returns more links than expected (', response.response, imageUrls, ')');
return response.response.map((item, ndx) => {
if (!item.ext && /\.([a-z]+)(?=$|[\#\?])/i.test(imageUrls[ndx])) item.ext = RegExp.$1;
return this.origin + '/' + item.code + '.' + item.ext;
});
});
}));
}
reupload(imgUrl) {
console.warn('PTPimg rehoster fallback to local reupload');
return globalFetch(imgUrl, { responseType: 'blob' }).then(response => {
var image = {
name: imgUrl.replace(/^.*\//, ''),
data: response.responseText,
size: response.responseText.length,
};
switch (imgUrl.replace(/^.*\./, '').toLowerCase()) {
case 'jpg': case 'jpeg': case 'jfif': image.type = 'image/jpeg'; break;
case 'png': image.type = 'image/png'; break;
case 'gif': image.type = 'image/gif'; break;
case 'bmp': image.type = 'image/bmp'; break;
default: return Promise.reject('Unsupported extension');
}
return this.upload([image]).then(imgUrls => imgUrls[0]);
});
}
setSession() {
return this.apiKey ? Promise.resolve(this.apiKey) : globalFetch(this.origin).then(response => {
var apiKey = response.document.getElementById('api_key');
if (apiKey == null) {
let counter = GM_getValue('ptpimg_reminder_read', 0);
if (counter < 3) {
alert(`
PTPimg API key could not be captured. Please login to ${this.origin}/ and redo the action.
If you don\'t have PTPimg account or don\'t want to use it, consider to remove PTPimg from
upload_hosts and rehost_hosts local storage entries.
`);
GM_setValue('ptpimg_reminder_read', ++counter);
}
return Promise.reject('API key not configured');
} else if (!(this.apiKey = apiKey.value))
return Promise.reject('assertion failed: empty PTPimg API key');
GM_setValue('ptpimg_api_key', this.apiKey);
Promise.resolve(this.apiKey)
.then(apiKey => { alert(`Your PTPimg API key [${apiKey}] was successfully configured`) });
return this.apiKey;
});
}
}
class Chevereto {
constructor(hostName, alias = undefined, types = undefined, sizeLimit = undefined, params = undefined) {
if (typeof hostName != 'string' || !hostName) throw 'Chevereto adapter: missing mandatory host name';
this.origin = 'https://' + hostName;
this.alias = alias;
if (Array.isArray(types)) this.types = types;
if (typeof params != 'object') params = { };
this.sizeLimit = sizeLimit || params.sizeLimitAnonymous;
if (!(this.sizeLimit > 0)) this.sizeLimit = undefined;
if (params.sizeLimitAnonymous < this.sizeLimit) this.sizeLimitAnonymous = params.sizeLimitAnonymous;
if (alias) var al = alias.replace(nonWordStripper, '');
if (!params.configPrefix && al) params.configPrefix = al.toLowerCase();
if (!params.configPrefix && /^(?:www\.)?([\w\-]+)(?:\.[\w\-]+)+$/.test(hostName))
params.configPrefix = RegExp.$1.toLowerCase();
if (this.configPrefix = params.configPrefix) {
this.uid = GM_getValue(this.configPrefix + '_uid');
if (this.uid === undefined && alias) GM_setValue(this.configPrefix + '_uid', '');
this.password = GM_getValue(this.configPrefix + '_password');
if (this.password === undefined && alias) GM_setValue(this.configPrefix + '_password', '');
this.apiKey = GM_getValue(this.configPrefix + '_api_key');
if (this.apiKey === undefined && alias && params.apiEndpoint) GM_setValue(this.configPrefix + '_api_key', '');
} else console.warn('Chevereto adapter: config prefix could not be evaluated, authorized operations not available');
this.jsonEndpoint = params.jsonEndpoint;
this.apiEndpoint = params.apiEndpoint;
this.apiFieldName = params.apiFieldName;
this.apiResultKey = params.apiResultKey;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession(false).then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20
|| this.sizeLimitAnonymous >= 0 && !session.username && !session.key
&& image.size > this.sizeLimitAnonymous * 2**20)
return Promise.reject(`image size exceeds site limit (${image.size})`);
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n', params = Object.assign({
action: 'upload',
type: 'file',
nsfw: 0,
thumb_width: 200,
//thumb_height: 200,
format: 'json',
}, session);
Object.keys(params).forEach((field, index, arr) => {
formData += 'Content-Disposition: form-data; name="' + field + '"\r\n\r\n';
formData += params[field] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="' + (session.key && this.apiFieldName || 'source') +
'"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n' + image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: session.key ? (this.apiEndpoint || this.origin + '/api/1') + '/upload'
: this.jsonEndpoint || this.origin + '/json',
responseType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status >= 200 && response.status < 400) try {
if (response.response.success)
resolve(this.resultHandler(response.response[session.key && this.apiResultKey || 'image']));
else reject((response.response.error ? response.response.error.message
: response.response.status_txt) + ' (' + response.response.status_code + ')');
} catch(e) { reject(e) } else reject(defaultErrorHandler(response));
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
return this.setSession(false).then(session => Promise.all(urls.map(url => verifyImageUrl(url).then(imageUrl => {
var formData = new URLSearchParams(Object.assign({
action: 'upload',
type: 'url',
nsfw: 0,
thumb_width: 200,
//thumb_height: 200,
format: 'json',
}, session));
formData.set(session.key && this.apiFieldName || 'source', imageUrl);
return globalFetch(session.key ? (this.apiEndpoint || this.origin + '/api/1') + '/upload'
: this.jsonEndpoint || this.origin + '/json', {
responseType: 'json',
headers: { 'Referer': this.origin },
timeout: urls.length * rehostTimeout,
}, formData).then(response => {
if (!response.response.success)
return Promise.reject(`${this.alias || this.origin}: ${response.response.error.message} (${response.response.status_code})`);
if (typeof progressHandler == 'function') progressHandler(true);
return this.resultHandler(response.response[session.key && this.apiResultKey || 'image']);
});
}))));
}
resultHandler(result) {
try {
return {
original: result.image && result.image.url || result.url,
thumb: result.thumb.url,
share: result.url_viewer,
};
} catch(e) { return result.url }
}
galleryResolver(url) {
var albumId = /^\/(?:album|al)\/(\w+)\b/.test(url.pathname) && RegExp.$1;
if (!albumId) return Promise.reject('Invlaid gallery URL');
return this.setSession(true, false).then(session => {
var formData = new URLSearchParams(Object.assign({
action: 'get-album-contents',
albumid: albumId,
}, session));
return globalFetch(this.origin + '/json', {
responseType: 'json',
headers: { 'Referer': url },
}, formData).then(response => {
return response.response.status_txt == 'OK' && Array.isArray(response.response.contents) ?
response.response.contents.map(image => image.url)
: Promise.reject(`${this.alias || this.origin}: ${response.response.error.message} (${response.response.status_code})`);
});
}).catch(reason => {
console.warn(this.alias || this.origin, 'gallery couldn\'t be resolved via API:', reason, '(falling back to HTML parser)');
return new Promise((resolve, reject) => {
var urls = [], domParser = new DOMParser;
getPage(url);
function getPage(url) {
GM_xmlhttpRequest({ method: 'GET', url: url, headers: { Referer: url },
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
var dom = domParser.parseFromString(response.responseText, 'text/html');
Array.prototype.push.apply(urls,
Array.from(dom.querySelectorAll('div.list-item-image > a.image-container')).map(a => a.href));
var next = dom.querySelector('a[data-pagination="next"][href]');
if (next == null || !next.href) resolve(urls); else getPage(next.href);
},
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}
}).then(urls => Promise.all(urls.map(imageUrlResolver)));
});
}
setSession(requireToken = true, requireLogin = false) {
var session = { timestamp: Date.now() };
if (this.uid) session.login = this.uid;
if (this.password) session.password = this.password;
if (this.apiKey) {
session.key = this.apiKey;
if (!requireToken && !requireLogin) return Promise.resolve(session);
}
return globalFetch(this.origin).then(response => {
if (/\b(?:auth_token)\s*=\s*"(\w+)"/m.test(response.responseText)) var authToken = RegExp.$1;
if (!authToken) {
authToken = response.document.querySelector('input[name="auth_token"][value]');
if (authToken != null) authToken = authToken.value;
}
if (authToken) session.auth_token = authToken; else {
console.warn('Chevereto auth_token detection failure:', this.alias || this.origin, '\n\n', response.responseText);
return Promise.reject('auth_token detection failure');
}
if (getUser(response)) return session;
if (!this.configPrefix || !this.uid || !this.password)
return !requireLogin ? session : Promise.reject('not logged in');
var formData = new URLSearchParams({
'login-subject': this.uid,
'password': this.password,
'auth_token': session.auth_token,
});
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({ method: 'POST', url: this.origin + '/login',
headers: {
'Accept': '*/*',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': formData.toString().length,
'Referer': this.origin + '/login',
}, data: formData.toString(),
onload: response => {
if (response.status < 200 || response.status > 400) defaultErrorHandler(response);
resolve(response.status);
},
onerror: response => {
reject(defaultErrorHandler(response));
//resolve(response.status);
},
ontimeout: response => {
reject(defaultTimeoutHandler(response));
//resolve(response.status);
},
});
}).then(status => globalFetch(this.origin, { responseType: 'text' }).then(response => {
if (!getUser(response)) return Promise.reject('unknown reason');
console.debug(this.alias || this.origin, 'login session:', session);
return session;
})).catch(reason => {
console.warn('Chevereto login failed:', reason);
return !requireLogin ? session : Promise.reject('login failed (' + reason + ')');
});
function getUser(response) {
if (/\b(?:logged_user)\s*=\s*(\{.*\});/.test(response.responseText)) try {
let logged_user = JSON.parse(RegExp.$1);
session.username = logged_user.username;
session.userid = logged_user.id;
return Boolean(logged_user.username || logged_user.id);
} catch(e) { console.warn(e) }
return false;
}
});
}
}
class PixHost {
constructor() {
this.alias = 'PixHost';
this.origin = 'https://pixhost.to';
this.types = ['png', 'jpeg', 'gif'];
this.sizeLimit = 10;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="img"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="content_type"\r\n\r\n';
formData += '0\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: 'https://api.pixhost.to/images',
responseType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status >= 200 && response.status < 400) resolve(PixHost.resultHandler(response.response));
else reject(defaultErrorHandler(response));
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
})));
}
rehost(urls) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
if (this.batchLimit && urls.length > this.batchLimit)
return Promise.reject('batch limit exceeded (' + this.batchLimit + ')');
return verifyImageUrls(urls).then(imageUrls => {
//console.debug('rehost2PixHost(...) input:', imageUrls.join('\n'));
var formData = new URLSearchParams({
imgs: imageUrls.join('\r\n'),
content_type: 0,
tos: 'on',
});
return globalFetch(this.origin + '/remote/', {
responseType: 'text',
timeout: imageUrls.length * rehostTimeout,
}, formData).then(response => {
if (!/\b(?:upload_results)\s*=\s*(\{.*\});$/m.test(response.responseText))
return Promise.reject('page parsing error');
var images = JSON.parse(RegExp.$1).images;
if (images.length < imageUrls.length)
return Promise.reject(`not all images rehosted (${images.length}/${imageUrls.length})`);
return Promise.all(images.map(PixHost.resultHandler));
});
});
}
static resultHandler(result) {
try {
return imageUrlResolver(result.show_url).then(imgUrl => ({
original: imgUrl,
thumb: result.th_url,
share: result.show_url,
}));
} catch(e) { return Promise.reject(e) }
}
}
class Catbox {
constructor() {
this.alias = 'Catbox';
this.origin = 'https://catbox.moe';
this.sizeLimit = 200;
if ((this.userHash = GM_getValue('catbox_userhash')) === undefined) GM_setValue('catbox_userhash', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().catch(reason => undefined).then(userHash => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
var now = Date.now();
const boundary = '--------WebKitFormBoundary-' + now.toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="reqtype"\r\n\r\n';
formData += 'fileupload\r\n';
formData += '--' + boundary + '\r\n';
if (userHash) {
formData += 'Content-Disposition: form-data; name="userhash"\r\n\r\n';
formData += userHash + '\r\n';
formData += '--' + boundary + '\r\n';
}
formData += 'Content-Disposition: form-data; name="fileToUpload"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/user/api.php',
responseType: 'text',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status >= 200 && response.status < 400) resolve(response.responseText);
else reject(defaultErrorHandler(response));
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
return this.setSession().catch(reason => undefined).then(userHash => Promise.all(urls.map(url => verifyImageUrl(url).then(imageUrl => {
var formData = new URLSearchParams({
reqtype: 'urlupload',
url: imageUrl,
});
if (userHash) formData.set('userhash', userHash);
return globalFetch(this.origin + '/user/api.php', {
responseType: 'text',
timeout: urls.length * rehostTimeout,
}, formData).then(response => {
if (typeof progressHandler == 'function') progressHandler(true);
return response.responseText;
});
}))));
}
setSession() {
return this.userHash ? Promise.resolve(this.userHash) : globalFetch(this.origin).then(response => {
var userHash = response.document.querySelector('input[name="userhash"][value]');
if (userHash == null) return Promise.reject('userhash not configured; please log-in to Catbox.moe to autodetect it');
if (!(this.userHash = userHash.value)) return Promise.reject('assertion failed: empty userhash value');
GM_setValue('catbox_userhash', this.userHash);
return this.userHash;
});
}
}
class ImgBox {
constructor() {
this.alias = 'ImgBox';
this.origin = 'https://imgbox.com';
this.types = ['jpeg', 'gif', 'png'];
this.sizeLimit = 10;
if ((this.uid = GM_getValue('imgbox_uid')) === undefined) GM_setValue('imgbox_uid', '');
if ((this.password = GM_getValue('imgbox_password')) === undefined) GM_setValue('imgbox_password', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
var now = Date.now();
const boundary = '--------WebKitFormBoundary-' + now.toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
Object.keys(session.params).forEach((field, index, arr) => {
formData += 'Content-Disposition: form-data; name="' + field + '"\r\n\r\n';
formData += session.params[field] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="files[]"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload/process',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'X-CSRF-Token': session.csrf_token,
},
data: formData,
responseType: 'json',
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status >= 200 && response.status < 400) resolve({
original: response.response.files[0].original_url,
thumb: response.response.files[0].thumbnail_url,
share: response.response.files[0].url,
}); else reject(defaultErrorHandler(response));
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
setSession() {
return globalFetch(this.origin + '/').then(response => {
var csrfToken = response.document.querySelector('meta[name="csrf-token"]');
if (csrfToken == null) return Promise.reject('ImgBox.com session token not found');
console.debug('ImgBox.com session token:', csrfToken.content);
if (response.document.querySelector('div.btn-group > ul.dropdown-menu') != null) return csrfToken.content;
if (!this.uid || !this.password) return csrfToken.content;
var formData = new URLSearchParams({
"utf8": "✓",
"authenticity_token": csrfToken.content,
"user[login]": this.uid,
"user[password]": this.password,
});
GM_xmlhttpRequest({ method: 'POST', url: 'https://imgbox.com/login', headers: {
'Referer': 'https://imgbox.com/login',
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Content-Length': formData.toString().length,
}, data: formData.toString() });
return new Promise((resolve, reject) => {
setTimeout(() => { globalFetch('http://imgbox.com/').then(response => {
if (response.document.querySelector('div.btn-group > ul.dropdown-menu') == null)
console.warn('ImgBox.com login failed, continuing as anonymous', response);
if ((csrfToken = response.document.querySelector('meta[name="csrf-token"]')) != null) {
console.debug('ImgBox.com session token after login:', csrfToken.content);
resolve(csrfToken.content);
} else reject('ImgBox.com session token not found');
}) }, 1000);
});
}).then(csrfToken => globalFetch(this.origin + '/ajax/token/generate', {
method: 'POST',
responseType: 'json',
headers: { 'X-CSRF-Token': csrfToken },
}).then(response => ({
csrf_token: csrfToken,
params: {
token_id: response.response.token_id,
token_secret: response.response.token_secret,
content_type: 1,
thumbnail_size: '150r',
gallery_id: null,
gallery_secret: null,
comments_enabled: 0,
},
})));
}
}
class Imgur {
constructor() {
this.alias = 'Imgur';
this.origin = 'https://imgur.com';
this.types = ['jpeg', 'png', 'gif', 'apng', 'tiff', 'bmp', 'icf', 'webp'];
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(clientId => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="type"\r\n\r\n';
formData += 'file\r\n';
formData += '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="name"\r\n\r\n';
formData += image.name + '\r\n';
formData += '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="Filedata"; filename="' + image.name.toASCII() + '"\r\n';
//formData += 'Content-Disposition: form-data; name="image"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload',
//url: 'https://api.imgur.com/3/image?client_id=' + clientId,
responseType: 'json',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin + '/upload',
//'Referer': this.origin + '/upload?beta',
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
resolve('https://i.imgur.com/' + response.response.data.hash + response.response.data.ext);
//if (!response.response.success) return reject('status:' + response.response.status);
//resolve(response.response.link);
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
return this.setSession().then(session => Promise.all(urls.map(url => verifyImageUrl(url).then(imageUrl => {
var formData = new URLSearchParams({ url: imageUrl });
return globalFetch(this.origin + '/upload', {
responseType: 'json',
headers: { Referer: this.origin + '/upload' },
timeout: urls.length * rehostTimeout,
}, formData).then(result => {
if (!result.response.success) return Promise.reject(result.response.status);
if (typeof progressHandler == 'function') progressHandler(true);
return 'https://i.imgur.com/' + result.response.data.hash + result.response.data.ext;
});
}))));
}
setSession() {
return Promise.resolve('');
}
}
class PostImage {
constructor() {
this.alias = 'PostImage';
this.origin = 'https://postimages.org';
this.sizeLimit = 24;
if ((this.uid = GM_getValue('postimg_uid')) === undefined) GM_setValue('postimg_uid', '');
if ((this.password = GM_getValue('postimg_password')) === undefined) GM_setValue('postimg_password', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
Object.keys(session).forEach(field => {
formData += 'Content-Disposition: form-data; name="' + field + '"\r\n\r\n';
formData += session[field] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="file"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/json/rr',
responseType: 'json',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (response.response.status != 'OK') return reject(response.response.status);
resolve(response.response.url);
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}).then(PostImage.resultHandler))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
return this.setSession().then(session => Promise.all(urls.map(url => verifyImageUrl(url).then(imageUrl => {
var formData = new URLSearchParams(Object.assign({ url: imageUrl }, session));
return globalFetch(this.origin + '/json/rr', {
responseType: 'json',
timeout: urls.length * rehostTimeout,
}, formData).then(response => {
if (response.status < 200 || response.status >= 400) return Promise.reject(defaultErrorHandler(response));
if (response.response.status != 'OK') return Promise.reject(response.response.status);
if (typeof progressHandler == 'function') progressHandler(true);
return PostImage.resultHandler(response.response.url);
});
}))));
}
static resultHandler(resultUrl) {
return globalFetch(resultUrl).then(function(response) {
var thumb = response.document.querySelector('div.thumb > a.img');
if (thumb == null || !/\b(?:url)\("(.+)"\)/.test(thumb.style.backgroundImage)) throw 'Page parsing error';
thumb = RegExp.$1;
return {
original: response.document.querySelector('meta[property="og:image"][content]').content,
thumb: thumb,
share: response.document.querySelector('meta[property="og:url"][content]').content,
}
}).catch(reason => imageUrlResolver(resultUrl))
}
static galleryResolver(url) {
return globalFetch(url, { responseType: 'text' }).then(function(response) {
if (/\b(?:var\s+embed_value)=(\{[\S\s]+?\});/.test(response.responseText)) try {
let embed_value = JSON.parse(RegExp.$1);
return Object.keys(embed_value).map(key => 'https://i.postimg.cc/'
.concat(embed_value[key][2], '/', embed_value[key][0], '.', embed_value[key][1]))
} catch(e) { console.warn(e) }
return Promise.reject('URL not resolvable');
});
}
setSession() {
return globalFetch(this.origin + '/').then(response => {
var session = {
session_upload: Date.now(),
upload_session: randomString(32),
optsize: 0,
expire: 0,
numfiles: 1,
upload_referer: btoa(this.origin + '/'),
};
if (/"token","(\w+)"/.test(response.responseText)) session.token = RegExp.$1;
if (response.document.querySelector('nav.authorized') != null) return session;
if (!this.uid || !this.password) return session;
var formData = new URLSearchParams({
'email': this.uid,
'password': this.password,
});
return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'POST', url: this.origin + '/login',
data: formData.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Content-Length': formData.toString().length,
'Referer': this.origin + '/login',
},
onload: response => {
if (response.status >= 200 && response.status <= 400) resolve(session);
else reject(defaultErrorHandler(response));
},
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
}));
});
}
}
class ImageVenue {
constructor() {
this.alias = 'ImageVenue';
this.origin = 'https://www.imagevenue.com';
this.types = ['jpeg', 'png', 'gif'];
if ((this.uid = GM_getValue('imagevenue_uid')) === undefined) GM_setValue('imagevenue_uid', '');
if ((this.password = GM_getValue('imagevenue_password')) === undefined) GM_setValue('imagevenue_password', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
if (this.batchLimit && images.length > this.batchLimit)
return Promise.reject('batch limit exceeded (' + this.batchLimit + ')');
return this.setSession().then(session => new Promise((resolve, reject) => {
var now = Date.now();
const boundary = '--------WebKitFormBoundary-' + now.toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
Object.keys(session).forEach((field, index, arr) => {
formData += 'Content-Disposition: form-data; name="' + field + '"\r\n\r\n';
formData += session[field] + '\r\n';
formData += '--' + boundary + '\r\n';
});
images.forEach((image, index, arr) => {
formData += 'Content-Disposition: form-data; name="files[' + index + ']"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary;
if (index + 1 >= arr.length) formData += '--';
formData += '\r\n';
});
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
},
data: formData,
responseType: 'json',
binary: true,
timeout: getUploadTimeout(formData.length),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
resolve(response.response.success);
},
onprogress: typeof progressHandler == 'function' ? progressHandler : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
})).then(resultUrl => globalFetch(resultUrl)).then(response => {
var thumbs = response.document.querySelectorAll('div.row > div > a > img');
return Promise.all(Array.from(thumbs).map(img => imageUrlResolver(img.parentNode.href).then(imgUrl => ({
original: imgUrl,
thumb: img.src,
share: img.parentNode.href,
}))));
});
}
setSession() {
return globalFetch(this.origin + '/').then(response => {
var csrfToken = response.document.querySelector('meta[name="csrf-token"]');
if (csrfToken == null) return Promise.reject('ImageVenue.com session token not found');
console.debug('ImageVenue.com session token:', csrfToken.content);
if (response.document.getElementById('navbarDropdown') != null) return csrfToken.content;
if (!this.uid || !this.password) return csrfToken.content;
var formData = new URLSearchParams({
'_token': csrfToken.content,
'email': this.uid,
'password': this.password,
});
GM_xmlhttpRequest({ method: 'POST', url: this.origin + '/auth/login', headers: {
'Referer': this.origin + '/auth/login',
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Content-Length': formData.toString().length,
}, data: formData.toString() });
return new Promise((resolve, reject) => {
setTimeout(() => {
globalFetch(this.origin + '/').then(response => {
if (response.document.getElementById('navbarDropdown') == null)
console.warn('ImageVenue.com login failed, continuing as anonymous', response);
if ((csrfToken = response.document.querySelector('meta[name="csrf-token"]')) != null) {
console.debug('ImageVenue.com session token after login:', csrfToken.content);
resolve(csrfToken.content);
} else reject('ImageVenue.com session token not found');
});
}, 1000);
});
}).then(csrfToken => globalFetch(this.origin + '/upload/session', {
responseType: 'json',
headers: { 'X-CSRF-TOKEN': csrfToken },
}, new URLSearchParams({
thumbnail_size: 2,
content_type: 'sfw',
comments_enabled: false,
})).then(response => ({
data: response.response.data,
_token: csrfToken,
})));
}
}
class FastPic {
constructor() {
this.alias = 'FastPic';
this.origin = 'https://fastpic.ru';
this.type = ['jpeg', 'png', 'gif'];
this.sizeLimit = 25;
this.batchLimit = 30;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
if (this.batchLimit && images.length > this.batchLimit)
return Promise.reject('batch limit exceeded (' + this.batchLimit + ')');
return new Promise((resolve, reject) => {
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
images.forEach(image => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
formData += 'Content-Disposition: form-data; name="file[]"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="uploading"\r\n\r\n';
formData += '1\r\n';
formData += '--' + boundary + '--\r\n';
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/uploadmulti',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
timeout: getUploadTimeout(formData.length),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (/^\s*(?:Refresh)\s*:\s*(\d+);url=(\S+)\s*$/im.test(response.responseHeaders)) resolve(RegExp.$2); else {
console.warn('FastPic.ru invalid response header:', response.responseHeaders);
reject('invalid response header');
}
},
onprogress: typeof progressHandler == 'function' ? progressHandler : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}).then(resultUrl => globalFetch(resultUrl).then(response => {
var thumbs = Array.from(response.document.querySelectorAll('div.picinfo > div.dCenter > a > img')).map(img => img.src);
return Promise.all(Array.from(response.document.querySelectorAll('ul.codes-list > li:first-of-type > input'))
.map((input, index) => globalFetch(input.value).then(response => ({
original: response.document.querySelector('img.image').src,
thumb: thumbs[index],
share: response.finalUrl,
}))));
console.warn(`FastPic.ru: not all images uploaded (${directLinks.length}/${images.length})`, response.finalUrl);
return Promise.reject(`not all images uploaded (${directLinks.length}/${images.length})`);
}));
}
}
class NWCD {
constructor() {
this.alias = 'NotWhatCd';
this.whitelist = ['notwhat.cd'];
this.upload.acceptFiles = true;
}
upload(files) {
if (!Array.isArray(files)) return Promise.reject('invalid argument');
if (files.length <= 0) return Promise.reject('nothing to upload');
return NWCD.loadJS().then(upload => Promise.all(files.map(upload)).then(results => results.map(result => result.url)));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
return NWCD.loadJS().then(upload => Promise.all(urls.map(url => verifyImageUrl(url).then(upload).then(result => {
if (typeof progressHandler == 'function') progressHandler(true);
return result.url;
}))));
}
static loadJS() {
if (document.domain != 'notwhat.cd') return Promise.reject('uploadToImagehost not available');
return typeof uploadToImagehost == 'function' ? Promise.resolve(uploadToImagehost) : new Promise((resolve, reject) => {
var imageUpload = document.createElement('script');
imageUpload.type = 'text/javascript';
imageUpload.src = '/static/functions/image_upload.js';
imageUpload.onload = evt => {
if (typeof uploadToImagehost == 'function') resolve(uploadToImagehost);
else reject('uploadToImagehost() not loaded'); // assertion fail
};
imageUpload.onerror = evt => { reject('Script load error: ' + evt.message) };
document.head.append(imageUpload);
});
}
}
class Abload {
constructor() {
this.alias = 'Abload';
this.origin = 'https://abload.de';
this.types = ['bmp', 'bmp2', 'bmp3', 'gif', 'jpeg', 'png'];
this.sizeLimit = 10;
this.batchLimit = 20;
if ((this.uid = GM_getValue('abload_uid')) === undefined) GM_setValue('abload_uid', '');
if ((this.password = GM_getValue('abload_password')) === undefined) GM_setValue('abload_password', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var id = 'o_' + (index + 1).toString().padStart(2, '0') + randomString(28).toLowerCase(), params = {
name: id, // + image.type.replace('image/', '.'),
chunk: 0,
chunks: 1,
}, formData = '--' + boundary + '\r\n';
Object.keys(params).forEach(field => {
formData += 'Content-Disposition: form-data; name="' + field + '"\r\n\r\n';
formData += params[field] + '\r\n';
formData += '--' + boundary + '\r\n';
});
Object.keys(session).forEach(field => {
formData += 'Content-Disposition: form-data; name="' + field + '"\r\n\r\n';
formData += session[field] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="file"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: 'https://' + session.server + '.abload.de/calls/newUpload.php',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin + '/',
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status >= 200 && response.status < 400) resolve({ id: id, name: image.name });
else reject(defaultErrorHandler(response));
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))).then(uploadMapping => {
var formData = new URLSearchParams(Object.assign({}, session, {
resize: 'none',
rules: 'on',
gallery: '',
upload_mapping: JSON.stringify(uploadMapping).replace(/"/g, '\\"'),
}));
return globalFetch('https://' + session.server + '.abload.de/flashUploadFinished.php?server=' + session.server, {
headers: { Referer: this.origin + '/' },
}, formData).then(Abload.resolveRedirect);
}));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
if (this.batchLimit && urls.length > this.batchLimit)
return Promise.reject('batch limit exceeded (' + this.batchLimit + ')');
return this.setSession().then(session => verifyImageUrls(urls).then(imageUrls => {
var formData = {};
imageUrls.forEach((imageUrl, index) => { formData['img' + index] = imageUrl });
formData = new URLSearchParams(Object.assign(formData, session, {
resize: 'none',
rules: 'on',
gallery: '',
upload_mapping: JSON.stringify([]),
}));
return globalFetch('https://' + session.server + '.abload.de/flashUploadFinished.php?server=' + session.server, {
headers: { Referer: this.origin + '/' },
timeout: imageUrls.length * rehostTimeout,
}, formData).then(Abload.resolveRedirect);
}));
}
static resolveRedirect(response) {
var form = response.document.querySelector('form#weiter');
if (form == null) return Promise.reject(response.responseText);
var formData = new FormData(form);
formData = new URLSearchParams(formData);
return globalFetch(form.action, { headers: { Referer: response.finalUrl } }, formData).then(response =>
Array.from(response.document.querySelectorAll('table.image_links > tbody > tr > td > input[type="text"]'))
.filter(input => urlParser.test(input.value)
&& input.parentNode.previousElementSibling.textContent.startsWith('Dire'))
.map(input => ({
original: input.value.trim(),
thumb: input.value.trim().replace('/img/', '/thumb/'),
share: input.value.trim().replace('/img/', '/image.php?img='),
})));
}
setSession() {
return globalFetch(this.origin).then(response => {
var session = { userID: randomUser(32) };
if (!/^Server\s*:\s*Abload\s+(\w+)\b/im.test(response.responseHeaders))
return Promise.reject('Invalid response header');
session.server = RegExp.$1;
if (/\b(?:user_logged_in)\s*=\s*true\b/.test(response.responseText)) return session;
if (!this.uid || !this.password) return session;
var formData = new URLSearchParams({ name: this.uid, password: this.password });
return globalFetch(this.origin + '/login.php', {
method: 'HEAD',
headers: { 'Referer': this.origin },
}, formData).catch(reason => { console.warn(reason) }).then(response => session);
function randomUser(length) {
const possible = "abcdefABCDEF0123456789";
var text = "";
for (var i = 0; i < length; ++i) text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
});
}
}
class Radikal {
constructor() {
this.alias = 'Radikal';
this.origin = 'https://radikal.ru';
this.sizeLimit = 40;
if ((this.uid = GM_getValue('radikal_uid')) === undefined) GM_setValue('radikal_uid', '');
if ((this.password = GM_getValue('radikal_password')) === undefined) GM_setValue('radikal_password', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n', params = {
OriginalFileName: image.name,
MaxSize: 99999,
PrevMaxSize: 500,
IsPublic: false,
NeedResize: false,
Rotate: 0,
RotateMetadataRelative: false,
};
Object.keys(params).forEach(key => {
formData += 'Content-Disposition: form-data; name="' + key + '"\r\n\r\n';
formData += params[key] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="File"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/Img/SaveImg2',
responseType: 'json',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin + '/',
'Cookie': 'USER_ID=' + session.Id || '',
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (response.response.IsError)
return reject(`${response.response.ErrorSrvMsg} (${response.response.Errors._allerrors_.join(' / ')})`);
resolve({
original: response.response.Url,
thumb: response.response.PublicPrevUrl,
share: response.response.PrevPageUrl,
});
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
return this.setSession().then(session => Promise.all(urls.map(url => verifyImageUrl(url).then(imageUrl => {
var formData = new URLSearchParams({
OriginalFileName: imageUrl,
MaxSize: 99999,
PrevMaxSize: 500,
IsPublic: false,
NeedResize: false,
Rotate: 0,
RotateMetadataRelative: false,
Url: imageUrl,
});
return globalFetch(this.origin + '/Img/SaveImg2', {
responseType: 'json',
headers: { 'Referer': this.origin },
cookie: 'USER_ID=' + session.Id || '',
timeout: urls.length * rehostTimeout,
}, formData).then(response => {
if (response.status < 200 || response.status >= 400) return Promise.reject(defaultErrorHandler(response));
if (response.response.IsError)
return Promise.reject(`${response.response.ErrorSrvMsg} (${response.response.Errors._allerrors_.join(' / ')})`);
if (typeof progressHandler == 'function') progressHandler(true);
return {
original: response.response.Url,
thumb: response.response.PublicPrevUrl,
share: response.response.PrevPageUrl,
};
});
}))));
}
setSession() {
return globalFetch(this.origin, { responseType: 'text' }).then(response => {
var session = getUserInfo(response);
if (!session) return Promise.reject('Invalida page format');
if (!session.IsAnonym) return session;
if (!this.uid || !this.password) return session;
var formData = new URLSearchParams({
Login: this.uid,
Password: this.password,
IsRemember: false,
ReturnUrl: '/',
});
return globalFetch(this.origin + '/Auth/Login', {
responseType: 'json',
headers: { 'Referer': this.origin },
}, formData).then(response => response.response.IsError ? session
: globalFetch(this.origin, { responseType: 'text' }).then(response => getUserInfo(response) || session),
reason => { console.warn(reason) });
});
function getUserInfo(response) {
if (/\b(?:var\s+serverVm)\s*=\s*(\{.*\});$/m.test(response.responseText)) try {
return JSON.parse(RegExp.$1).CommonUserData;
} catch(e) { console.warn(e) }
return null;
}
}
}
class SVGshare {
constructor() {
this.alias = 'SVGshare';
this.origin = 'https://svgshare.com';
this.types = ['svg+xml'];
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(submitUrl => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n', params = {
name: image.name,
submit: 'Share',
};
Object.keys(params).forEach(key => {
formData += 'Content-Disposition: form-data; name="' + key + '"\r\n\r\n';
formData += params[key] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="file"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: submitUrl,
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
fetch: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
let domParser = new DOMParser;
domParser.parseFromString(response.responseText, 'text/html')
.querySelectorAll('ul#shares > li > input[type="text"]')
.forEach(input => { if (/^(?:https?:\/\/.+\.svg)$/.test(input.value)) resolve(input.value) });
reject('image URL could not be found');
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
setSession() {
return globalFetch(this.origin).then(response => {
var form = response.document.getElementById('filereader');
return form != null && form.action || Promise.reject('Invalid document format');
});
}
}
class GeekPic {
constructor() {
this.alias = 'GeekPic';
this.origin = 'https://geekpic.net';
//this.types = [];
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="file"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/ajax.php?PHPSESSID=' + randomString(26).toLowerCase(),
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
//fetch: true,
responseType: 'json',
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (!response.response.success) return reject(response.response.msg);
resolve(this.origin + response.response.img);
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}).then(imageUrlResolver)));
}
}
class LightShot {
constructor() {
this.alias = 'LightShot';
this.origin = 'https://prntscr.com';
//this.types = [];
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(userInfo => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="image"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload.php',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
//fetch: true,
responseType: 'json',
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (response.response.status == 'success') resolve(response.response.data);
else reject(response.response.status);
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}).then(imageUrlResolver))));
}
setSession() {
var params = {
id: 1,
jsonrpc: '2.0',
method: 'get_userinfo',
params: {},
};
return globalFetch('https://api.prntscr.com/v1/', { responseType: 'json' }, JSON.stringify(params)).then(response => {
if (response.response.result.success) return response.response.result;
// TODO: login
return response.response.result;
});
}
}
class ImageBan {
constructor() {
this.alias = 'ImageBan';
this.origin = 'https://imageban.ru';
this.types = ['jpeg', 'png', 'gif', 'webp'];
this.sizeLimit = 10;
this.batchLimit = 100;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="Filedata"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/up',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
responseType: 'json',
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (response.response.files[0].error) return reject(response.response.files[0].error);
resolve({
original: response.response.files[0].link,
thumb: response.response.files[0].thumbs,
share: response.response.files[0].piclink,
});
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
if (this.batchLimit && urls.length > this.batchLimit)
return Promise.reject(`batch limit exceeded (${this.batchLimit})`);
return this.setSession().then(session => verifyImageUrls(urls).then(imageUrls => {
var formData = new URLSearchParams(Object.assign({
u_url: imageUrls.join('\n'),
}, session));
return globalFetch(this.origin + '/urlup', {
headers: { 'Referer': this.origin },
timeout: imageUrls.length * rehostTimeout,
}, formData).then(response => Array.from(response.document.querySelectorAll('div.container > div[align="left"] ~ div.row')).map(row => ({
original: row.querySelector('div.input-group > input[id^="g"]').value,
thumb: row.querySelector(':scope > a > img').src,
share: row.querySelector('div.input-group > input[id^="a"]').value,
})));
}));
}
setSession() {
return Promise.resolve({});
}
}
class PicaBox {
constructor() {
this.alias = 'PicaBox';
this.origin = 'https://picabox.ru';
//this.types = ['jpeg', 'png', 'gif', 'webp'];
//this.sizeLimit = 10;
//this.batchLimit = 100;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
Object.keys(session).forEach(key => {
formData += 'Content-Disposition: form-data; name="' + key + '"\r\n\r\n';
formData += session[key] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="ImagesForm[imageFiles][]"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/image/load',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin + '/image/load',
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
fetch: true,
cookie: '_csrf=' + session._csrf,
onload: response => {
if (response.status >= 200 && response.status < 400) resolve(this.extractLinks(response));
else reject(defaultErrorHandler(response));
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}).then(results => results[0]))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
if (this.batchLimit && urls.length > this.batchLimit)
return Promise.reject(`batch limit exceeded (${this.batchLimit})`);
return this.setSession().then(session => Promise.all(urls.map(url => verifyImageUrl(url).then(imageUrl => {
var formData = new URLSearchParams(session);
formData.set('ImagesForm[imageFiles][]', '');
formData.set('ImagesForm[file_url]', imageUrl);
return globalFetch(this.origin + '/image/load', {
responseType: 'text',
fetch: true,
headers: { 'Referer': this.origin + '/image/load' },
timeout: urls.length * rehostTimeout,
cookie: '_csrf=' + session._csrf,
}, formData).then(PicaBox.extractLinks.bind(this)).then(results => {
if (typeof progressHandler == 'function') progressHandler(true);
return results[0];
});
}))));
}
extractLinks(response) {
var domParser = new DOMParser;
return Promise.all(Array.from(domParser.parseFromString(response.responseText, 'text/html').querySelectorAll('input[name="url"]'))
.map(input => imageUrlResolver(input.value).then(imgUrl => ({
original: imgUrl,
thumb: this.origin + '/img_small/' + input.value.replace(/^.*\//, ''),
share: input.value,
}))));
}
setSession() {
return globalFetch(this.origin + '/image/load').then(response => {
var formData = response.document.querySelector('form[name="form_image"]');
if (formData == null) return Promise.reject('Invalid document format');
formData = new FormData(formData);
var session = { }, val, it = formData.entries();
while (!(val = it.next()).done) session[val.value[0]] = val.value[1];
['ImagesForm[file_url]', 'ImagesForm[imageFiles][]', 'imagesform-text_color-source']
.forEach(key => { delete session[key] });
return session;
});
}
}
class PimpAndHost {
constructor() {
this.alias = 'PimpAndHost';
this.origin = 'https://pimpandhost.com';
this.types = ['jpeg', 'png', 'gif'];
this.sizeLimit = 5 * 1000 / 2**10;
this.batchLimit = 100;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n', params = {
fileId: `${index.toString().padStart(3, '0')}_${session.albumId}`,
albumId: session.albumId,
};
Object.keys(params).forEach(key => {
formData += 'Content-Disposition: form-data; name="' + key + '"\r\n\r\n';
formData += params[key] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="files"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/image/upload-file',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin + '/album/' + session.albumId,
'X-CSRF-Token': session['csrf-token'],
},
data: formData,
binary: true,
//fetch: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
responseType: 'json',
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
resolve({
original: 'https:' + response.response.files[0].image[0],
thumb: 'https:' + response.response.files[0].image[180],
share: response.response.files[0].pageUrl,
});
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
if (this.batchLimit && urls.length > this.batchLimit)
return Promise.reject(`batch limit exceeded (${this.batchLimit})`);
return this.setSession().then(session => Promise.all(urls.map((url, index) => (() => {
return['png', 'jpg', 'jpeg', 'jfif', 'gif'].some(ext => url.toLowerCase().endsWith('.' + ext)) ?
verifyImageUrl(url) : imageHostHandlers.imgbb.rehost([url]).then(imgUrls => imgUrls[0])
.catch(reason => imageHostHandlers.jerking.rehost([url]).then(imgUrls => imgUrls[0]))
.catch(reason => imageHostHandlers.pixhost.rehost([url]).then(imgUrls => imgUrls[0]))
})().then(imageUrl => {
var formData = new URLSearchParams({
url: imageUrl,
field: `${index.toString().padStart(3, '0')}_${session.albumId}`,
albumId: session.albumId,
});
return globalFetch(this.origin + '/image/upload-by-url', {
responseType: 'json',
headers: {
'Referer': this.origin + '/album/' + session.albumId,
'X-CSRF-Token': session['csrf-token'],
},
timeout: urls.length * rehostTimeout,
}, formData).then(response => {
if (response.response.status != 'ok') return Promise.reject(response.response.message);
if (response.response.file.error) return Promise.reject(response.response.file.error.title);
if (typeof progressHandler == 'function') progressHandler(true);
return {
original: 'https:' + response.response.file.image[0],
thumb: 'https:' + response.response.file.image[180],
share: response.response.file.pageUrl,
};
});
}))));
}
setSession() {
return globalFetch(this.origin).then(response => {
var meta = response.document.querySelector('meta[name="csrf-token"][content]');
if (meta == null) return Promise.reject('Invalid document structure');
var session = { 'csrf-token': meta.content };
return globalFetch(this.origin + '/album/create-by-uploading', {
headers: { 'X-CSRF-Token': session['csrf-token'] },
responseType: 'json',
}).then(response => {
session.albumId = response.response.albumId;
return session;
});
});
}
}
class ScreenCast {
constructor() {
this.alias = 'ScreenCast';
this.origin = 'https://www.screencast.com';
//this.types = [];
//this.sizeLimit = 10;
//this.batchLimit = 100;
}
upload(images, progressHandler = null) {
}
rehost(urls, progressHandler = null) {
}
setSession() {
}
}
class GoogleAPI {
constructor(scope) {
this.origin = 'https://www.googleapis.com';
this.clientId = '241768952066-r0pojdg0l8m4nqr31psf8rb01btt43c4.apps.googleusercontent.com';
this.apiKey = 'lk9MZc7eSYzi6tDQ-H6jeC-2';
this.scope = scope;
}
setSession() {
return this.isTokenValid() ? Promise.resolve(this.token)
: (this.auth ? Promise.resolve(this.auth) : (typeof gapi == 'object' ? Promise.resolve(gapi) : new Promise((resolve, reject) => {
var gApi = document.createElement('script');
gApi.type = 'text/javascript';
gApi.src = 'https://apis.google.com/js/api.js';
gApi.onload = evt => { if (typeof gapi == 'object') resolve(gapi); else reject('Google API loading error') };
gApi.onerror = evt => { reject('Script load error: ' + evt.message ) };
document.head.append(gApi);
})).then(gapi => new Promise((resolve, reject) => gapi.load('client:auth2', {
callback: () => {
gapi.client.init({
clientId: this.clientId,
//apiKey: this.apiKey,
scope: this.scope,
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'],
}).then(() => { resolve(this.auth = gapi.auth2.getAuthInstance()) }, error => reject(JSON.stringify(error)));
},
onerror: () => { reject('Google API loading error') },
})))).then(auth => auth.isSignedIn.get() ? auth.currentUser.get() : auth.signIn().catch(e => Promise.reject(e.error)))
.then(user => (this.token = gapi.client.getToken()));
}
isTokenValid() {
if (!this.token || typeof this.token != 'object' || !this.token.token_type || !this.token.access_token) return false;
var now = new Date();
return this.token.expires_at >= now.getTime() + now.getTimezoneOffset() * 60000 + 30000;
}
}
class GoogleDrive extends GoogleAPI {
constructor() {
super('https://www.googleapis.com/auth/drive.file');
this.alias = 'GoogleDrive';
//this.types = [];
//this.sizeLimit = 10;
//this.batchLimit = 100;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(token => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n', metadata = {
'name' : image.name,
'mimeType' : image.type,
};
formData += 'Content-Disposition: form-data; name="metadata"\r\n';
formData += 'Content-Type: application/json; charset=UTF-8\r\n\r\n';
formData += JSON.stringify(metadata) + '\r\n';
formData += '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="file"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload/drive/v3/files?uploadType=multipart&fields=id,webContentLink',
headers: {
'Content-Type': 'multipart/related; boundary=' + boundary,
'Content-Length': formData.length,
'Authorization': token.token_type + ' ' + token.access_token,
},
data: formData,
binary: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
responseType: 'json',
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
gapi.client.drive.permissions.create({
fileId: response.response.id,
resource: { role: 'reader', type: 'anyone' },
}).execute(result => {
if (result.id == 'anyoneWithLink') resolve(response.response.webContentLink.replace(/&.*$/i, ''));
else reject('failed to enable sharing for this file');
}, error => { reject(JSON.stringify(error)) });
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
}
class GooglePhotos extends GoogleAPI {
constructor() {
super('https://www.googleapis.com/auth/photoslibrary.sharing');
this.alias = 'GooglePhotos';
//this.types = [];
//this.sizeLimit = 10;
//this.batchLimit = 100;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(token => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
// TODO
}))));
}
}
class DropBox {
constructor() {
this.alias = 'DropBox';
this.origin = 'https://www.dropbox.com';
//this.types = [];
//this.sizeLimit = 10;
//this.batchLimit = 100;
}
upload(images, progressHandler = null) {
}
rehost(urls, progressHandler = null) {
}
setSession() {
}
}
class OneDrive {
constructor() {
this.alias = 'OneDrive';
this.origin = 'https://onedrive.live.com';
//this.types = [];
//this.sizeLimit = 10;
//this.batchLimit = 100;
}
upload(images, progressHandler = null) {
}
rehost(urls, progressHandler = null) {
}
setSession() {
}
}
class VgyMe {
constructor() {
this.alias = 'Vgy.me';
this.origin = 'https://vgy.me';
this.types = ['jpeg', 'png', 'gif'];
this.sizeLimit = 20;
if ((this.userKey = GM_getValue('vgyme_user_key')) === undefined) GM_setValue('vgyme_user_key', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
if (this.sizeLimit > 0 && images.some(image => image.size > this.sizeLimit * 2**20))
return Promise.reject('size limit exceeded by one or more images');
return this.setSession().then(userKey => new Promise((resolve, reject) => {
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
images.forEach((image, index) => {
formData += 'Content-Disposition: form-data; name="file[' + index + ']"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="userkey"\r\n\r\n';
formData += userKey + '\r\n';
formData += '--' + boundary + '--\r\n';
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
//fetch: true,
responseType: 'json',
timeout: getUploadTimeout(formData.length),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (!response.response.error) {
if (Array.isArray(response.response.upload_list)) return resolve(response.response.upload_list);
if (response.response.image) return resolve([response.response.image]);
reject('Invalid response');
} else reject('Error');
},
onprogress: typeof progressHandler == 'function' ? progressHandler : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}));
}
setSession() {
return this.userKey ? Promise.resolve(this.userKey)
: Promise.reject('user key not configured (https://vgy.me/account/details#userkeys)');
}
}
class ImgURL {
constructor() {
this.alias = 'ImgURL';
this.origin = 'https://www.png8.com';
//this.origin = 'https://imgurl.org';
this.types = ['jpeg', 'png', 'gif', 'bmp'];
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="file"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload/localhost',
//url: this.origin + '/upload/ftp',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
//fetch: true,
responseType: 'json',
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (response.response.code != 200) return reject('status: ' + response.response.code);
resolve({
original: response.response.url,
thumb: response.response.thumbnail_url,
share: this.origin + '/img/' + response.response.imgid,
});
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
setSession() {
return Promise.resolve({});
}
}
class Slowpoke {
constructor() {
this.alias = 'Slowpoke';
this.origin = 'https://slow.pics';
//this.types = ['jpeg', 'png', 'gif', 'bmp'];
if ((this.uid = GM_getValue('slowpoke_uid')) === undefined) GM_setValue('slowpoke_uid', '');
if ((this.password = GM_getValue('slowpoke_password')) === undefined) GM_setValue('slowpoke_password', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(csrfToken => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const now = Date.now(), boundary = '--------WebKitFormBoundary-' + now.toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n', params = {
collectionName: new Date(now).toISOString(),
public: false,
thumbnailSize: 180,
};
Object.keys(params).forEach(key => {
formData += 'Content-Disposition: form-data; name="' + key + '"\r\n\r\n';
formData += params[key] + '\r\n';
formData += '--' + boundary + '\r\n';
});
images.forEach((image, index) => {
formData += 'Content-Disposition: form-data; name="images[' + index + '].name"\r\n\r\n';
formData += image.name + '\r\n';
formData += '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="images[' + index + '].file"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary;
if (index >= images.length - 1) formData += '--';
formData += '\r\n';
});
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/api/collection',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
'X-XSRF-TOKEN': csrfToken,
},
data: formData,
binary: true,
//fetch: true,
responseType: 'text',
timeout: getUploadTimeout(formData.length),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
var shareUrl = this.origin + '/c/' + response.responseText;
console.log('Slowpoke upload gallery link:', shareUrl);
imageUrlResolver(shareUrl).then(result => resolve(Array.isArray(result) ? result : [result]));
},
onprogress: typeof progressHandler == 'function' ? progressHandler : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}));
}
setSession() {
return globalFetch(this.origin + '/login').then(response => {
if (response.finalUrl.includes(this.origin)) return response;
if (!this.uid || !this.password) return globalFetch(this.origin);
var token = response.document.querySelector('input[name="_csrf"][value]');
if (token == null) return Promise.reject('invlid page structure');
return new Promise((resolve, reject) => {
var formData = new URLSearchParams({
_csrf: token.value,
username: this.uid,
password: this.password,
});
GM_xmlhttpRequest({ method: 'POST', url: response.finalUrl,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': formData.toString().length,
},
data: formData.toString(),
onload: response => {
if (['my', 'exit'].some(p => response.finalUrl.endsWith('/' + p))) {
console.log('Slowpoke successfull login:', response.finalUrl);
resolve(globalFetch(this.origin + '/login'));
} else if (response.finalUrl.endsWith('/login?credentials')) reject('invalid userid or password'); else {
console.warn('Slowpoke unhandled redirect:', response);
reject('unexpected redirect: ' + response.finalUrl);
}
},
onerror: response => { reject(defaultErrorHandler(response)) },
});
}).catch(reason => {
console.warn('Slowpoke login failed:', reason);
return globalFetch(this.origin);
});
}).then(response => {
var token = response.document.querySelector('input[name="_csrf"][value]');
return token != null ? token.value : Promise.reject('invlid page structure (' + response.finalUrl + ')');
});
}
}
class FunkyIMG {
constructor() {
this.alias = 'FunkyIMG';
this.origin = 'https://funkyimg.com';
this.sizeLimit = 4;
this.types = ['jpeg', 'png', 'gif', 'bmp', 'tiff'];
if ((this.uid = GM_getValue('funkyimg_uid')) === undefined) GM_setValue('funkyimg_uid', '');
if ((this.password = GM_getValue('funkyimg_password')) === undefined) GM_setValue('funkyimg_password', '');
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
images = images.filter(isSupportedType.bind(this));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (this.sizeLimit > 0 && image.size > this.sizeLimit * 2**20) throw 'size limit exceeded: ' + image.name;
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
var formData = '--' + boundary + '\r\n', params = {
wmText: '',
wmPos: 'TOPRIGHT',
wmLayout: 2,
wmFontSize: 14,
wmTransparency: 50,
addInfoType: 'res',
labelText: '',
_images: image.name,
};
Object.keys(params).forEach(key => {
formData += 'Content-Disposition: form-data; name="' + key + '"\r\n\r\n';
formData += params[key] + '\r\n';
formData += '--' + boundary + '\r\n';
});
formData += 'Content-Disposition: form-data; name="images"; filename="' + image.name + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n';
formData += image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
if (typeof progressHandler == 'function') progressHandler(formData.length - image.size);
GM_xmlhttpRequest({
method: 'POST',
url: this.origin + '/upload/?' + session,
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin,
},
data: formData,
binary: true,
//fetch: true,
timeout: getUploadTimeout(images.reduce((acc, image) => acc + image.size, images.length * 1024)),
responseType: 'json',
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (response.response.success) resolve(this.resultHandler(response.response.jid));
else reject('failure');
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}))));
}
rehost(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('invalid argument');
if (urls.length <= 0) return Promise.reject('nothing to rehost');
if (this.batchLimit && urls.length > this.batchLimit)
return Promise.reject(`batch limit exceeded (${this.batchLimit})`);
return this.setSession().then(session => Promise.all(urls.map(url => verifyImageUrl(url).then(imageUrl => {
var formData = new URLSearchParams({
wmText: '',
wmPos: 'TOPRIGHT',
wmLayout: 2,
wmFontSize: 14,
wmTransparency: 50,
addInfoType: 'res',
labelText: '',
url: imageUrl,
});
return globalFetch(this.origin + '/upload/?' + session, {
responseType: 'json',
headers: { 'Referer': this.origin },
timeout: urls.length * rehostTimeout,
}, formData).then(response => response.response.success ? this.resultHandler(response.response.jid)
: Promise.reject('failure'));
}))));
}
resultHandler(jid) {
return new Promise((resolve, reject) => {
//var queries = 0;
check.call(this);
function check() {
globalFetch(this.origin + '/upload/check/' + jid + '?_=' + Date.now(), {
headers: { 'Referer': this.origin },
responseType: 'json',
}).then(response => {
//++queries;
if (response.response.success) try {
//console.debug('FunkyIMG queries to success:', queries, jid);
var dom = domParser.parseFromString(response.response.bit, 'text/html');
resolve({
original: dom.querySelector('ul > li:nth-of-type(2) > input').value.trim(),
thumb: dom.querySelector('ul > li:nth-of-type(2) > input').value.trim().replace('/i/', '/p/'),
share: dom.querySelector('ul > li:nth-of-type(1) > input').value.trim(),
});
} catch(e) { reject(e) } else setTimeout(check.bind(this), 250);
}, reject);
}
});
}
setSession() {
return Promise.resolve('fileapi' + Date.now());
}
}
function singleImageGetter(results) {
if (!Array.isArray(results)) throw 'Invalid result format';
if (results.length <= 0) return null;
if (typeof results[0] == 'string' && urlParser.test(results[0])) return results[0];
if (typeof results[0] == 'object' && urlParser.test(results[0].original)) return results[0].original;
throw 'Invalid result format';
}
var imageHostHandlers = {
'abload' : new Abload,
'catbox': new Catbox,
//'dropbox': new DropBox,
'fastpic': new FastPic,
'funkyimg': new FunkyIMG,
'geekpic': new GeekPic,
'gifyu': new Chevereto('gifyu.com', 'Gifyu', ['jpeg', 'png', 'gif', 'bmp', 'webp'], 100, { sizeLimitAnonymous: 50 }),
'googledrive': new GoogleDrive,
//'googlephotos': new GooglePhotos,
'imageban': new ImageBan,
'imagevenue': new ImageVenue,
'imgbb': new Chevereto('imgbb.com', 'ImgBB', ['jpeg', 'png', 'bmp', 'gif', 'webp', 'tiff', 'heic', 'heif'], 32,
{ apiEndpoint: 'https://api.imgbb.com/1', apiFieldName: 'image', apiResultKey: 'data' }),
'imgbox': new ImgBox,
'imgur': new Imgur,
'imgurl': new ImgURL,
'jerking': new Chevereto('jerking.empornium.ph', 'Jerking', ['jpeg', 'png', 'bmp', 'gif', 'webp'], 5),
'lightshot': new LightShot,
'nwcd' : new NWCD,
//'onedrive': new OneDrive,
'picabox': new PicaBox,
'pimpandhost': new PimpAndHost,
'pixhost': new PixHost,
'postimage': new PostImage,
'ptpimg': new PTPimg,
'radikal': new Radikal,
//'screencast': new ScreenCast,
'slowpoke': new Slowpoke,
'svgshare': new SVGshare,
'vgyme': new VgyMe,
'z4a': new Chevereto('z4a.net', 'Z4A', ['jpeg', 'png', 'bmp', 'gif'], 50),
};
var siteWhitelists = {
'notwhat.cd': ['nwcd'],
};
var siteBlacklists = {
'passthepopcorn.me': ['imgbox', 'postimage', 'imgur', 'tinypic', 'imageshack', 'imagebam'],
};
class ImageHostManager {
constructor(messageHandler = null, UlHostList = undefined, rhHostList = undefined) {
this.messageHandler = messageHandler;
if (UlHostList) this.buildUploadChain(UlHostList); else this.ulHostChain = [];
if (rhHostList) this.buildRehostChain(rhHostList); else this.rhHostChain = [];
}
processLists(alias) {
alias = alias.replace(nonWordStripper, '').toLowerCase();
return (!Array.isArray(siteWhitelists[document.domain])
|| siteWhitelists[document.domain].some(whiteAlias => alias == whiteAlias.toLowerCase()))
&& (!Array.isArray(siteBlacklists[document.domain])
|| siteBlacklists[document.domain].every(blackAlias => alias != blackAlias.toLowerCase()));
}
buildUploadChain(list) {
this.ulHostChain = (Array.isArray(list) ? list : typeof list == 'string' ? list.split(/\s*[\,\;\|\/]\s*/) : [])
.filter(ImageHostManager.prototype.processLists.bind(this)).map(alias => imageHostHandlers[alias.toLowerCase()])
.filter(handler => typeof handler == 'object' && typeof handler.upload == 'function'
&& (!Array.isArray(handler.whitelist) || handler.whitelist.includes(document.domain))
&& (!Array.isArray(handler.blacklist) || !handler.blacklist.includes(document.domain)));
console.debug('Local upload hosts for ' + document.domain + ':',
this.ulHostChain.map(handler => handler.alias).join(', '));
}
buildRehostChain(list) {
this.rhHostChain = (Array.isArray(list) ? list : typeof list == 'string' ? list.split(/\s*[\,\;\|\/]\s*/) : [])
.filter(ImageHostManager.prototype.processLists.bind(this)).map(alias => imageHostHandlers[alias.toLowerCase()])
.filter(handler => typeof handler == 'object' && typeof handler.rehost == 'function'
&& (document.domain != 'redacted.ch' || handler.alias.toLowerCase() == 'ptpimg')
&& (!Array.isArray(handler.whitelist) || handler.whitelist.includes(document.domain))
&& (!Array.isArray(handler.blacklist) || !handler.blacklist.includes(document.domain)));
console.debug('Remote upload hosts for ' + document.domain + ':',
this.rhHostChain.map(handler => handler.alias).join(', '));
}
uploadImages(files, progressHandler = null) {
if (!Array.isArray(this.ulHostChain) || this.ulHostChain.length <= 0) return Promise.reject('No hosts where to upload');
if (typeof files != 'object') return Promise.reject('Invalid argument');
if (!Array.isArray(files)) files = Array.from(files);
if (files.length > 1) files.sort((a, b) => a.name.localeCompare(b.name));
files = files.filter(file => file instanceof File && file.size > 0 && (!file.type || file.type.startsWith('image/')));
if (files.length <= 0) return Promise.reject('Nothing to upload');
return Promise.all(files.map(file => file.toContent())).then(images => {
return uploadInternal.call(this);
function uploadInternal(hostIndex = 0) {
return hostIndex >= 0 && hostIndex < this.ulHostChain.length ? (() => {
if (!files.every(isSupportedType.bind(this.ulHostChain[hostIndex])))
return Promise.reject('one or more files of unsupported format');
if (this.ulHostChain[hostIndex].sizeLimit > 0 && files.some(file => file.size > this.ulHostChain[hostIndex].sizeLimit * 2**20))
return Promise.reject(`one or more images exceed size limit (${this.ulHostChain[hostIndex].sizeLimit}MiB)`);
// if (this.ulHostChain[hostIndex].batchLimit && files.length > this.ulHostChain[hostIndex].batchLimit)
// return Promise.reject(`batch limit exceeded (${this.ulHostChain[hostIndex].batchLimit})`);
if (typeof progressHandler == 'function') {
progressHandler(hostIndex, null);
var _progressHandler = (param = null, index = undefined) => progressHandler(hostIndex, param, index);
}
return this.ulHostChain[hostIndex].upload(this.ulHostChain[hostIndex].upload.acceptFiles ?
files : images, _progressHandler);
})().catch(reason => {
console.warn('Upload to', this.ulHostChain[hostIndex].alias, 'failed:', reason);
var msg = `Upload to ${this.ulHostChain[hostIndex].alias} failed (${reason})`;
if (++hostIndex < this.ulHostChain.length) {
if (typeof this.messageHandler == 'function')
this.messageHandler(`${msg}, falling back to ${this.ulHostChain[hostIndex].alias}`);
return uploadInternal.call(this, hostIndex);
}
if (typeof this.messageHandler == 'function') this.messageHandler(msg);
return Promise.reject('Upload failed to all hosts');
}) : Promise.reject(`Host index out of bounds (${hostIndex})`);
}
});
}
rehostImages(urls, progressHandler = null) {
if (!Array.isArray(urls)) return Promise.reject('Invalid argument');
urls = urls.filter(url => urlParser.test(url));
if (urls.length <= 0) return Promise.reject('Nothing to rehost');
if (!Array.isArray(this.rhHostChain) || this.rhHostChain.length <= 0) return Promise.resolve(urls);
if (testRemoteSizes) var start = Date.now();
return (testRemoteSizes ? Promise.all(urls.map(url => getRemoteFileSize(url).catch(reason => undefined)))
: Promise.resolve('Size tests skipped')).then(lengths => {
if (testRemoteSizes) console.debug('Size analysis time:', (Date.now() - start) / 1000, 's');
try { var h2 = urls.map(url => new URL(url).hostname) } catch(e) { console.error('Assertion failed: ' + e) }
return rehostInternal.call(this);
function rehostInternal(hostIndex = 0) {
if (hostIndex < 0 || hostIndex >= this.rhHostChain.length)
return Promise.reject(`Host index out of bounds (${hostIndex})`);
try {
let h1 = new URL(this.rhHostChain[hostIndex].origin).hostname;
if (h1 && Array.isArray(h2) && h2.every(h2 => h2.includes(h1) || h1.includes(h2))) return Promise.resolve(urls);
} catch(e) { }
// if (this.rhHostChain[hostIndex].batchLimit && urls.length > this.rhHostChain[hostIndex].batchLimit)
// return Promise.reject('batch limit exceeded (' + this.rhHostChain[hostIndex].batchLimit + ')');
if (this.rhHostChain[hostIndex].sizeLimit > 0 && Array.isArray(lengths)
&& !lengths.every(length => !length || length <= this.rhHostChain[hostIndex].sizeLimit * 2**20))
return Promise.reject(`one or more images exceed size limit (${this.rhHostChain[hostIndex].sizeLimit}MiB)`);
if (typeof progressHandler == 'function') {
progressHandler(hostIndex, false);
var _progressHandler = (param = true) => progressHandler(hostIndex, param);
}
return this.rhHostChain[hostIndex].rehost(urls, _progressHandler).catch(reason => {
console.warn('Rehost to', this.rhHostChain[hostIndex].alias, 'failed:', reason);
var msg = `Rehost to ${this.rhHostChain[hostIndex].alias} failed (${reason})`;
if (++hostIndex < this.rhHostChain.length) {
if (typeof this.messageHandler == 'function')
this.messageHandler(`${msg}, falling back to ${this.rhHostChain[hostIndex].alias}`);
return rehostInternal.call(this, hostIndex);
}
if (typeof this.messageHandler == 'function') this.messageHandler(msg);
return Promise.reject('Rehost failed to all hosts');
});
}
});
}
}
function urlResolver(url) {
if (!urlParser.test(url)) return Promise.reject('Invalid URL:\n\n' + url);
try { if (!(url instanceof URL)) url = new URL(url) } catch(e) { return Promise.reject(e) }
switch (url.hostname) {
case 'rutracker.org':
if (url.pathname != '/forum/out.php') break;
return globalFetch(url, { method: 'HEAD' }).then(response => urlResolver(response.finalUrl));
case 'www.anonymz.com': case 'anonymz.com': case 'anonym.to': case 'dereferer.me':
var resolved = decodeURIComponent(url.search.slice(1));
return urlParser.test(resolved) ? urlResolver(resolved) : genericResolver();
// case 'reho.st':
// resolved = (url.pathname + url.search + url.hash).slice(1);
// if (/\b(?:https?):\/\/(?:\w+\.)*(?:discogs\.com|omdb\.org)\//i.test(resolved)) break;
// return urlParser.test(resolved) ? urlResolver(resolved) : genericResolver();
// URL shorteners
case 'tinyurl.com': case 'bit.ly': case 'j.mp': case 't.co': case 'apple.co': case 'flic.kr':
case 'rebrand.ly': case 'b.link': case 't2m.io': case 'zpr.io': case 'yourls.org': case 'ibn.im':
return genericResolver();
}
if (/\b(?:goo\.gl)$/i.test(url.hostname)) return genericResolver();
return Promise.resolve(url.href);
function genericResolver() {
return globalFetch(url).then(function(response) {
var redirect = response.document.querySelector('meta[http-equiv="refresh"]');
if (redirect != null && (redirect = redirect.content.replace(/^.*?\b(?:URL)\s*=\s*/i, '')) != url.href
|| /^ *(?:Location) *: *(\S+) *$/im.test(response.responseHeaders) && (redirect = RegExp.$1) != url.href
|| /^ *(?:Refresh) *: *(\d+); *url=(\S+) *$/im.test(response.responseHeaders) && (redirect = RegExp.$2) != url.href
|| (redirect = response.finalUrl) != url.href) return urlResolver(redirect);
return Promise.resolve(url.href);
});
}
}
function verifyImageUrl(url) {
return urlResolver(url).then(function(url) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = load => { resolve(url) };
img.onerror = function(error) {
if (img.src.includes('?')) img.src = url.replace(/\?.*?(?=\#|$)/, '');
else reject('not valid image: ' + url);
};
img.ontimeout = timeout => { reject('image load timed out:\n\n' + url) };
img.src = url;
});
});
}
function verifyImageUrls(urls) {
return Array.isArray(urls) ? Promise.all(urls.map(verifyImageUrl)) : Promise.reject('argument not an array');
}
function reduceImageSize(image, maxImageHeight, jpegQuality, progressHandler) {
const checkInterval = 250; // in ms
const baseUrl = 'https://dragon.img2go.com/api/jobs';
const referer = 'https://www.img2go.com/convert-image-to-image';
const params = () => ({
responseType: 'json',
headers: { 'Referer': referer },
});
return setJob('convert image to image').then(function(job) {
return job.id ? (function() {
return urlParser.test(image) ? setRemoteInput(image) : (function() {
if (typeof progressHandler == 'function') progressHandler(-1, null);
if (image instanceof File) return image.toContent();
if (image && typeof image == 'object' && /^(?:image)\//.test(image.type) && image.size >= 0 && image.data)
return image;
return Promise.reject('reduceImageSize: invalid input object');
})().then(image => new Promise(function(resolve, reject) {
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
let formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="file[]"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n' + image.data + '\r\n';
formData += '--' + boundary + '--\r\n';
GM_xmlhttpRequest({
method: 'POST',
url: job.server + '/upload-file/' + job.id,
responseType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': referer,
'X-Oc-Token': job.token,
'X-Oc-Upload-Uuid': uuid(),
},
data: formData,
binary: true,
timeout: getUploadTimeout(image.size),
onload: response => {
if (response.status >= 200 && response.status < 400) {
if (!response.response.completed) console.warn('img2go upload not completed:', response.response);
resolve(response);
} else reject(defaultErrorHandler(response));
},
onprogress: typeof progressHandler == 'function' ? response => { progressHandler(-1, response) } : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}));
})().then(getInputStatus).then(function(input) {
if (!(maxImageHeight >= 0)) maxImageHeight = 1500;
if (maxImageHeight > 0 && input.metadata.image_width > maxImageHeight) {
var imageHeight = input.metadata.image_width;
while (imageHeight > maxImageHeight) imageHeight = Math.floor(imageHeight / 2);
}
if (!(jpegQuality > 0)) jpegQuality = 85;
return doConversion({ category: 'image', options: {
allow_multiple_outputs: false,
height: imageHeight,
quality: jpegQuality,
}, target: 'jpg' });
}) : Promise.reject('invalid response (job id)');
function setRemoteInput(imageUrl) {
return globalFetch(baseUrl + '/' + job.id + '/input', params(), {
type: 'remote',
source: imageUrl,
engine: 'auto',
});
}
function getInputStatus() {
return new Promise(function(resolve, reject) {
function waitInput() {
globalFetch(baseUrl + '/' + job.id, params()).then(function(response) {
if (response.response.input[0].status == 'ready') resolve(response.response.input[0]);
else setTimeout(waitInput, checkInterval);
}).catch(reject);
}
waitInput();
});
}
function doConversion(formData) {
return globalFetch(baseUrl + '/' + job.id + '/conversions', params(), formData).then(response => new Promise(function(resolve, reject) {
function waitResult() {
globalFetch(baseUrl + '/' + job.id, params()).then(function(response) {
if (response.response.status.code == 'completed') {
resolve(response.response.output[0]);
console.debug('img2go conversion result:', response.response.output[0]);
} else setTimeout(waitResult, checkInterval);
}).catch(reject);
}
waitResult();
}));
}
function uuid() {
let dt = new Date().getTime();
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (dt + Math.random() * 16) % 16 | 0;
dt = Math.floor(dt / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
}
});
function setJob(operation) {
return globalFetch(baseUrl, params(), { operation: operation }).then(response => response.response);
}
}
function optiPNG(urlOrFile) {
return (function() {
const url = 'https://ezgif.com/optipng';
if (urlOrFile instanceof File) {
if (!['image/png', 'image/apng'].includes(urlOrFile.type)) return Promise.reject('invalid format');
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() {
if (reader.result.length != urlOrFile.size)
console.warn(`FileReader: binary string read length mismatch (${reader.result.length} ≠ ${urlOrFile.size})`);
resolve({ name: urlOrFile.name, type: urlOrFile.type, size: reader.result.length, data: reader.result });
};
reader.onerror = reader.ontimeout = function() { reject(`FileReader error (${urlOrFile.name})`) };
reader.readAsBinaryString(urlOrFile);
}).then(image => new Promise(function(resolve, reject) {
const boundary = '--------WebKitFormBoundary-' + Date.now().toString(16).toUpperCase();
let formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="new-image"; filename="' + image.name.toASCII() + '"\r\n';
formData += 'Content-Type: ' + image.type + '\r\n\r\n' + image.data + '\r\n';
formData += '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="new-image-url"\r\n\r\n';
formData += '\r\n';
formData += '--' + boundary + '--\r\n';
GM_xmlhttpRequest({ method: 'POST', url: url,
headers: {
'Accept': 'text/html',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'X-Requested-With': 'XMLHttpRequest',
},
data: formData,
fetch: true,
binary: true,
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
let domParser = new DOMParser;
response.document = domParser.parseFromString(response.responseText, 'text/html');
resolve(response);
},
//onprogress: typeof progressHandler == 'function' ? progressHandler : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}));
} else if (urlParser.test(urlOrFile)) return (getRemoteFileType(urlOrFile)).then(function(mimeType) {
if (!['image/png', 'image/apng'].includes(mimeType)) Promise.reject('not PNG');
let payLoad = new URLSearchParams({ 'new-image-url': urlOrFile });
return globalFetch(url, { fetch: true }, payLoad);
}); else return Promise.reject('optiPNG: invalid argument');
})().then(function(response) {
let srcImg = response.document.querySelector('img#target');
if (srcImg != null) srcImg = srcImg.src;
let form = response.document.querySelector('form.ajax-form');
if (form == null) {
console.warn('ezgif.com form not found');
return srcImg || Promise.reject('invalid page structure');
}
let action = new URL(form.action);
return globalFetch('https://ezgif.com' + action.pathname, { fetch: true }, new FormData(form)).then(function(response) {
var optImg = response.document.querySelector('p.outfile > img');
if (optImg != null) optImg = optImg.src; else {
console.warn('ezgif.com unexpected result format, not optimized');
return srcImg || Promise.reject('unexpected result format, not optimized');
}
return urlOrFile instanceof File ? getRemoteFileSize(optImg).then(function(optSize) {
let delta = optSize - urlOrFile.size, sign = ['-', '', '+'][Math.sign(delta) + 1],
saving = delta * 100 / urlOrFile.size;
console.log('OptiPNG ' + (optSize < urlOrFile.size ? 'success' : 'fail') + ':', urlOrFile, optImg, optSize,
'(' + sign + Math.abs(optSize - urlOrFile.size) + ' = ' + sign + Math.abs((Math.round(saving * 10) / 10)) + '%)');
return delta < 0 ? optImg : srcImg || urlOrFile;
}, function(reason) {
console.warn('ezgif.com failed to get result image size', optImg)
return optImg;
}) : optImg;
});
});
}
function googlePhotosResolver(url) {
return globalFetch(url).then(function(response) {
var result;
response.document.querySelectorAll('body > script[nonce]').forEach(function(script) {
if (result) return;
if (!/^(?:AF_initDataCallback)\(\{\s*key\s*:\s*'ds:(\d+)'.+\b(?:data:function)\(\)\s*\{\s*(?:return)\s*(\[[\S\s]+?\])\s*\}\}\);$/
.test(script.text)) return;
var AF_initDataCallback = eval(RegExp.$2);
if (AF_initDataCallback.length == 14 && Array.isArray(AF_initDataCallback[0])) try {
if (urlParser.test(AF_initDataCallback[0][1][0])) result = AF_initDataCallback[0][1][0] + '=s0';
} catch(e) { console.warn(e, AF_initDataCallback) }
else if (AF_initDataCallback.length == 6 && Array.isArray(AF_initDataCallback[1])) try {
result = AF_initDataCallback[1].map(photo => photo[1][0] + '=s0');
} catch(e) { console.warn(e, AF_initDataCallback) }
});
return result || Promise.reject('No image content for this URL');
});
}
function pinterestResolver(url) {
return globalFetch(url).then(function(response) {
var initialState = response.document.querySelector('script#initial-state');
if (initialState != null) try {
initialState = JSON.parse(initialState.text);
let images = Object.keys(initialState.pins).map(pin => initialState.pins[pin].images.orig.url);
if (images.length == 1) return images[0]; else if (images.length > 1) return images;
let boards = Object.keys(initialState.boards.content);
if (boards.length > 0) {
return Promise.all(boards.map(function(board) {
var params = new URLSearchParams({
source_url: response.finalUrl,
data: JSON.stringify({ options: {
board_id: initialState.boards.content[board].id,
board_url: initialState.boards.content[board].url,
} }),
_: Date.now(),
});
return globalFetch(url.origin + '/resource/BoardFeedResource/get/?' + params, {
responseType: 'json',
headers: { Referer: response.finalUrl },
}).then(function(response) {
if (response.response.resource_response.status != 'success') {
console.warn('Pinterest:', response.response.resource_response, response.finalUrl);
return Promise.reject('Pinterest: ' + response.response.resource_response.status);
}
return response.response.resource_response.data.filter(it => it.type == 'pin').map(it => it.images.orig.url);
});
}));
}
} catch(e) { console.warn(e, initialState) }
return Promise.reject('No title image for this URL');
});
}
function _500pxUrlHandler(path) {
return globalFetch('https://api.500px.com/v1/' + path + '&image_size[]=4096', { responseType: 'json' }).then(function(response) {
var results = Object.keys(response.response.photos).map(id => response.response.photos[id].image_url[0]);
return results.length == 1 ? results[0] : results.length > 1 ? results
: Promise.reject('No image content found on this UIRL');
});
}
function pxhereCollectionResolver(url) {
if (!/\/collection\/(\d+)\b/i.test(url.pathname)) return Promise.reject('Invalid URL');
var collectionId = parseInt(RegExp.$1);
return new Promise(function(resolve, reject) {
var domParser = new DOMParser(), photos = [];
loadPage();
function loadPage(page = 1) {
GM_xmlhttpRequest({ method: 'GET', url: `https://pxhere.com/en/collection/${collectionId}?page=${page}&format=json`,
responseType: 'json',
onload: function(response) {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (response.response._msg != 'success') return reject(response.response._msg);
if (!response.response.data) return resolve(photos);
var dom = domParser.parseFromString(response.response.data, 'text/html');
Array.prototype.push.apply(photos, Array.from(dom.querySelectorAll('div.item > a')).map(a => a.pathname));
loadPage(page + 1);
},
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}
}).then(urls => Promise.all(urls.map(url => imageUrlResolver('https://pxhere.com' + url))));
}
function unsplashCollectionResolver(url) {
if (!/\/collections\/(\d+)\b/i.test(url.pathname)) return Promise.reject('Invalid URL');
var collectionId = parseInt(RegExp.$1);
return new Promise(function(resolve, reject) {
var urls = [];
loadPage();
function loadPage(page = 1) {
GM_xmlhttpRequest({ method: 'GET', url: `https://unsplash.com/napi/collections/${collectionId}/photos?page=${page}&per_page=999`,
responseType: 'json',
onload: function(response) {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
if (response.response.length <= 0) return resolve(urls);
Array.prototype.push.apply(urls, response.response.map(photo => photo.urls.raw.replace(/\?.*$/, '')));
loadPage(page + 1);
},
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}
});
}
function pexelsCollectionResolver(url) {
if (!/\/collections\/([\w\-]+)\//i.test(url.pathname)) return Promise.reject('Invalid URL');
var collectionId = RegExp.$1;
return new Promise(function(resolve, reject) {
var domParser = new DOMParser, urls = [];
loadPage();
function loadPage(page = 1) {
GM_xmlhttpRequest({ method: 'GET', url: `https://www.pexels.com/collections/${collectionId}/?format=html&page=${page}`,
onload: function(response) {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
var dom = domParser.parseFromString(response.responseText, 'text/html');
var photos = dom.querySelectorAll('article.photo-item > a.js-photo-link');
if (photos.length <= 0) return resolve(urls);
Array.prototype.push.apply(urls, Array.from(photos).map(a => a.pathname));
loadPage(page + 1);
},
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}
}).then(urls => Promise.all(urls.map(url => imageUrlResolver('https://www.pexels.com' + url))));
}
function getRemoteFileType(url) {
return urlParser.test(url) ? new Promise(function(resolve, reject) {
var contentType, abort = GM_xmlhttpRequest({ method: 'HEAD', url: url,
onreadystatechange: function(response) {
if (contentType || response.readyState < XMLHttpRequest.HEADERS_RECEIVED) return;
if (/^(?:Content-Type):\s*(.+)$/im.test(response.responseHeaders) && (contentType = RegExp.$1))
resolve(contentType.toLowerCase());
else reject('empty header');
abort.abort();
},
onerror: response => { reject('File not accessible') },
ontimeout: response => { reject('File not accessible') },
});
}) : Promise.reject('getRemoteFileType: parameter not valid URL');
}
function getRemoteFileSize(url) {
return urlParser.test(url) ? new Promise(function(resolve, reject) {
var imageSize, abort = GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'arraybuffer',
onreadystatechange: function(response) {
if (imageSize || response.readyState < XMLHttpRequest.HEADERS_RECEIVED
|| !/^(?:Content-Length):\s*(\d+)\b/im.test(response.responseHeaders)) return;
if (!(imageSize = parseInt(RegExp.$1))) return;
resolve(imageSize);
abort.abort();
},
onload: function(response) { // fail-safe
if (imageSize) return;
if (response.status >= 200 && response.status < 400) resolve(response.responseText.length /*response.response.byteLength*/);
else reject(new Error('File not accessible'));
},
onerror: response => { reject('File not accessible') },
ontimeout: response => { reject('File not accessible') },
});
}) : Promise.reject('getRemoteFileSize: parameter not valid URL');
}
function formattedSize(size) {
return size < 1024**1 ? Math.round(size) + ' B'
: size < 1024**2 ? (Math.round(size * 10 / 2**10) / 10) + ' KiB'
: size < 1024**3 ? (Math.round(size * 100 / 2**20) / 100) + ' MiB'
: size < 1024**4 ? (Math.round(size * 100 / 2**30) / 100) + ' GiB'
: size < 1024**5 ? (Math.round(size * 100 / 2**40) / 100) + ' TiB'
: (Math.round(size * 100 / 2**50) / 100) + ' PiB';
}
function voidDragHandler0(evt) { return false }
function inputDropHandler(evt) { return !evt.shiftKey ? inputDataHandler(evt, evt.dataTransfer) : true }
function inputPasteHandler(evt) { return inputDataHandler(evt, evt.clipboardData) }
function inputClear(evt) {
evt.target.value = '';
coverPreview(evt.target, null);
}
function setInputHandlers(node) {
node.ondragover = voidDragHandler0;
node.ondblclick = inputClear;
node.ondrop = inputDropHandler;
node.onpaste = inputPasteHandler;
node.placeholder = 'Paste/drop local or remote image';
}
function setTextAreahandlers(node) {
node.ondragover = voidDragHandler0;
node.ondrop = textAreaDropHandler;
node.onpaste = textAreaPasteHandler;
};
function randomString(length) {
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var text = "";
for (var i = 0; i < length; ++i) text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
function isSupportedType(image) {
if (!this || typeof this != 'object' || !image || typeof image != 'object') return false;
if (!Array.isArray(this.types) || this.types.length <= 0) return !image.type || image.type.startsWith('image/');
return this.types.some(function(mimeType) {
if (!mimeType) return false;
if (image.type) return image.type == 'image/' + mimeType;
return image.name && testExt([mimeType]);
function testExt(extensions) { return extensions.some(ext => image.name.toLowerCase().endsWith('.' + ext)) }
});
}