Este script não deve ser instalado diretamente. Este script é uma biblioteca de outros scripts para incluir com o diretório meta // @require https://update.greasyfork.org/scripts/401726/813599/imageHostUploader.js
// ==UserScript==
// @name imageHostUploader
// @namespace https://greasyfork.org/cs/users/321857-anakunda
// @version 1.62
// @author Anakunda
// @description ×
// @require https://greasyfork.org/scripts/404642-js-xhr/code/js-xhr.js
// @require https://greasyfork.org/scripts/404516-progressbars/code/progressBars.js
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
'use strict';
if (document.getElementById('upload assistant') != null) return; // don't clash with Upload Assistant
const ulTimeFactor = GM_getValue('upload_speed', 16);
const rehostTimeout = 30000;
const urlParser = /^\s*(https?:\/\/\S+)\s*$/i;
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tif', 'tiff', 'heic'];
class PTPimg {
constructor() {
this.alias = 'PTPimg';
this.origin = 'https://ptpimg.me';
this.types = ['png', 'jpg', 'jpeg', 'gif', 'bmp'];
this.whitelist = [
'passthepopcorn.me', 'redacted.ch', 'orpheus.network',
'notwhat.cd', 'dicmusic.club', 'broadcasthe.net',
];
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (Array.isArray(this.types))
images = images.filter(image => this.types.some(type => image.type == 'image/'.concat(type.toLowerCase())));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(apiKey => new Promise((resolve, reject) => {
const boundary = '----WebKitFormBoundary'.concat(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.concat('/upload.php'),
responseType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
},
data: formData,
binary: true,
//timeout: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
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.concat('/', 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 parameter');
if (urls.length <= 0) return Promise.reject('Nothing to rehost');
return Promise.all(urls.map(url => {
if (!urlParser.test(url)) return Promise.reject('URL not valid ('.concat(url, ')'));
var hostname = new URL(url).hostname;
if (hostname == 'img.discogs.com' || hostname.endsWith('omdb.org')) {
return verifyImageUrl('https://reho.st/'.concat(url))
.catch(reason => imageHostHandlers['catbox'].rehost([url]).then(imgUrls => imgUrls[0]))
.catch(reason => imageHostHandlers['pixhost'].rehost([url]).then(imgUrls => imgUrls[0]))
.catch(reason => this.reupload(url));
} else if (!this.types.some(ext => url.toLowerCase().endsWith('.'.concat(ext)))) {
return verifyImageUrl(url.concat('#.jpg'))
//.catch(reason => imageHostHandlers['malzo'].rehost([url]).then(imgUrls => imgUrls[0]))
.catch(reason => 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]))
.catch(reason => imageHostHandlers['picload'].rehost([url]).then(imgUrls => imgUrls[0]));
//.catch(reason => imageHostHandlers['imgur'].rehost([url]).then(imgUrls => imgUrls[0]));
}
return verifyImageUrl(url);
})).then(imageUrls => this.setSession().then(apiKey => {
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.concat('/', 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() {
if (this.apiKey || (this.apiKey = GM_getValue('ptpimg_api_key'))) return Promise.resolve(this.apiKey);
try {
this.apiKey = JSON.parse(window.localStorage.ptpimg_it).api_key;
if (this.apiKey) {
GM_setValue('ptpimg_api_key', this.apiKey);
return Promise.resolve(this.apiKey);
}
} catch(e) { console.debug('PTPimg.setSession():', e) }
return 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, consider to remove PtpImg from upload_hosts and rehost_hosts entries in local storage.
Uploading and rehosting is still available to fallback image hosts.
`);
GM_setValue('ptpimg_reminder_read', ++counter);
}
return Promise.reject('PTPIMG API key not configured');
}
if (!apiKey.value) return Promise.reject('Assertion failed: missing PTPIMG API key');
Promise.resolve(this.apiKey)
.then(apiKey => { alert(`Your PTPIMG API key [${apiKey}] was successfully configured`) });
GM_setValue('ptpimg_api_key', this.apiKey = apiKey.value);
return this.apiKey;
});
}
}
class Chevereto {
constructor(hostName, alias = undefined, types = undefined, sizeLimitAnonymous = undefined, sizeLimit = undefined, configPrefix = undefined) {
if (!hostName) throw 'Chevereto adapter: missing host name';
this.hostName = hostName;
if (alias) this.alias = alias;
if (Array.isArray(types)) this.types = types;
this.sizeLimitAnonymous = sizeLimitAnonymous;
this.sizeLimit = sizeLimit;
if (!configPrefix && /^([\w\-]+)(?:\.[\w\-]+)+$/.test(hostName)) configPrefix = RegExp.$1.toLowerCase();
if (!configPrefix) console.warn('Chevereto adapter: config prefix could not be determined');
this.configPrefix = configPrefix;
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (Array.isArray(this.types))
images = images.filter(image => this.types.some(type => image.type == 'image/'.concat(type.toLowerCase())));
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
|| !session.username && this.sizeLimitAnonymous > 0 && image.size > this.sizeLimitAnonymous * 2**20)
return Promise.reject(`image size exceeds upload limit (${image.size})`);
const boundary = '----WebKitFormBoundary'.concat(Date.now().toString(16).toUpperCase());
var formData = '--' + boundary + '\r\n', params = Object.assign({
action: 'upload',
type: 'file',
nsfw: 0,
}, 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="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: 'https://'.concat(this.hostName, '/json'),
responseType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': 'https://'.concat(this.hostName, '/'),
},
data: formData,
binary: true,
//timeout: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
onload: response => {
if (response.status >= 200 && response.status < 400) {
if (response.response.success) resolve(response.response.image.url);
else reject((response.response.error ? response.response.error.message : response.response.status_txt)
.concat(' (', response.response.status_code, ')'));
} 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) {
return this.setSession().then(session => Promise.all(urls.map(url => verifyImageUrl(url).then(imageUrl => {
var formData = new URLSearchParams(Object.assign({
action: 'upload',
type: 'url',
nsfw: 0,
source: imageUrl,
}, session));
return globalFetch('https://'.concat(this.hostName, '/json'), {
responseType: 'json',
headers: { 'Referer': 'https://'.concat(this.hostName, '/') },
timeout: urls.length * rehostTimeout,
}, formData).then(response => {
if (!response.response.success)
return Promise.reject(`${this.hostName}: ${response.response.error.message} (${response.response.status_code})`);
if (typeof progressHandler == 'function') progressHandler(true);
return response.response.image.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().then(session => {
var formData = new URLSearchParams(Object.assign({
action: 'get-album-contents',
albumid: albumId,
}, session));
return globalFetch('https://'.concat(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.hostName.concat(': ', response.response.error.message,' (', response.response.status_code, ')'));
});
}).catch(reason => {
console.warn(this.hostName, '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() {
const index = 'https://'.concat(this.hostName, '/');
return globalFetch(index).then(response => {
if (!/\b(?:auth_token)\s*=\s*"(\w+)"/.test(response.responseText))
return Promise.reject('Auth token detection failure');
var session = {
auth_token: RegExp.$1,
timestamp: Date.now(),
};
if (getUser(response) || !this.configPrefix) return session;
if (!this.login) this.login = GM_getValue(this.configPrefix.concat('_uid'));
if (!this.password) this.password = GM_getValue(this.configPrefix.concat('_password'));
if (!this.login || !this.password) return session;
var formData = new URLSearchParams({
'login-subject': this.login,
'password': this.password,
'auth_token': session.auth_token,
});
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({ method: 'POST', url: index.concat('login'),
headers: {
'Accept': '*/*',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': formData.toString().length,
'Referer': index.concat('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(index, { responseType: 'text' }).then(response => {
if (getUser(response)) console.debug(this.hostName, 'authorized session:', session);
else console.warn(this.hostName, 'authorization failed:', status, '(continuing anonymous)');
})).catch(reason => { console.warn('Chevereto login failed:', reason) }).then(() => session);
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';
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (Array.isArray(this.types))
images = images.filter(image => this.types.some(type => image.type == 'image/'.concat(type.toLowerCase())));
if (images.length <= 0) return Promise.reject('nothing to upload');
return Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
if (!['png', 'jpg', 'jpeg', 'gif'].some(type => image.type == 'image/'.concat(type.toLowerCase())))
throw 'MIME type not supported: '.concat(image.type);
const boundary = '----WebKitFormBoundary'.concat(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: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
onload: response => {
if (response.status >= 200 && response.status < 400) resolve(response.response.show_url);
else reject(defaultErrorHandler(response));
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
}).then(imageUrlResolver)));
}
rehost(urls) {
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(image => imageUrlResolver(image.show_url)));
});
});
}
}
class Catbox {
constructor() {
this.alias = 'Catbox';
this.origin = 'https://catbox.moe';
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('Invalid argument');
if (Array.isArray(this.types))
images = images.filter(image => this.types.some(type => image.type == 'image/'.concat(type.toLowerCase())));
if (images.length <= 0) return Promise.reject('Nothing to upload or format not supported');
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'.concat(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: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
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) {
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() {
if (!this.userHash) this.userHash = GM_getValue('catbox_userhash');
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('Catbox.moe: not logged in or userhash not found');
GM_setValue('catbox_userhash', this.userHash = userHash.value);
return this.userHash;
});
}
}
class ImageVenue {
constructor() {
this.alias = 'ImageVenue';
this.origin = 'http://www.imagevenue.com';
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (Array.isArray(this.types))
images = images.filter(image => this.types.some(type => image.type == 'image/'.concat(type.toLowerCase())));
if (images.length <= 0) return Promise.reject('nothing to upload');
return this.setSession().then(session => new Promise((resolve, reject) => {
var now = Date.now();
const boundary = '----WebKitFormBoundary'.concat(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: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
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 links = response.document.querySelectorAll('div.row > div > a');
return Promise.all(Array.from(links).map(a => imageUrlResolver(a.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;
var uid = GM_getValue('imagevenue_uid'), password = GM_getValue('imagevenue_password');
if (!uid || !password) return csrfToken.content;
var formData = new URLSearchParams({
'_token': csrfToken.content,
'email': uid,
'password': 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 ImgBox {
constructor() {
this.alias = 'ImgBox';
this.origin = 'https://imgbox.com';
this.types = ['jpg', 'jpeg', 'gif', 'png'];
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (Array.isArray(this.types))
images = images.filter(image => this.types.some(type => image.type == 'image/'.concat(type.toLowerCase())));
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 (image.size > 10 * 2**20) throw 'Size limit exceeded: '.concat(image.name);
var now = Date.now();
const boundary = '----WebKitFormBoundary'.concat(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: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
resolve(response.response.files[0].original_url);
},
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;
var uid = GM_getValue('imgbox_uid'), password = GM_getValue('imgbox_password');
if (!uid || !password) return csrfToken.content;
var formData = new URLSearchParams({
"utf8": "✓",
"authenticity_token": csrfToken.content,
"user[login]": uid,
"user[password]": 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: '100c',
gallery_id: null,
gallery_secret: null,
comments_enabled: 0,
},
})));
}
}
class Imgur {
constructor() {
this.alias = 'Imgur';
this.origin = 'https://imgur.com';
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (images.length <= 0) return Promise.reject('nothing to upload');
return Promise.all(images.map((image, index) => new Promise((resolve, reject) => {
var now = Date.now();
const boundary = '----WebKitFormBoundary'.concat(now.toString(16).toUpperCase());
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="Filedata"; 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',
responseType: 'json',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': formData.length,
'Referer': this.origin + '/upload',
},
data: formData,
binary: true,
//timeout: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
onload: response => {
if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response));
resolve('https://i.imgur.com/'.concat(response.response.data.hash, response.response.data.ext));
},
onprogress: typeof progressHandler == 'function' ? progress => progressHandler(progress, index) : undefined,
onerror: response => { reject(defaultErrorHandler(response)) },
ontimeout: response => { reject(defaultTimeoutHandler(response)) },
});
})));
}
rehost(urls, progressHandler = null) {
return 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/'.concat(result.response.data.hash, result.response.data.ext);
});
})));
}
}
class PostImage {
constructor() {
this.alias = 'PostImage';
this.origin = 'https://postimages.org';
}
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) => {
var now = Date.now();
const boundary = '----WebKitFormBoundary'.concat(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: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
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(imageUrlResolver))));
}
rehost(urls, progressHandler = null) {
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 imageUrlResolver(response.response.url);
});
}))));
}
galleryResolver(url) {
return globalFetch(url, { responseType: 'text' }).then(function(response) {
if (/\bvar\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 notFound;
});
}
setSession() {
return globalFetch(this.origin + '/').then(response => {
var session = {
session_upload: Date.now(),
upload_session: rand_string(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;
var uid = GM_getValue('postimg_uid'), password = GM_getValue('postimg_password');
if (!uid || !password) return session;
var formData = new URLSearchParams({
'email': uid,
'password': 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)) },
}));
});
function rand_string(e) {
for (var t = "", i = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", n = 0; n < e; n++)
t += i.charAt(Math.floor(Math.random()*i.length));
return t;
}
}
}
class FastPic {
constructor() {
this.alias = 'FastPic';
this.origin = 'https://fastpic.ru';
}
upload(images, progressHandler = null) {
if (!Array.isArray(images)) return Promise.reject('invalid argument');
if (images.length <= 0) return Promise.reject('nothing to upload');
return new Promise((resolve, reject) => {
const boundary = '----WebKitFormBoundary'.concat(Date.now().toString(16).toUpperCase());
var formData = '--' + boundary + '\r\n';
images.forEach(image => {
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: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000),
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 directLinks = response.document.querySelectorAll('ul.codes-list > li:first-of-type > input');
if (directLinks.length >= images.length) return Array.from(directLinks).map(directLink => directLink.value);
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');
this.loadJS();
return Promise.all(files.map(uploadToImagehost)).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');
this.loadJS();
return Promise.all(urls.map(url => verifyImageUrl(url).then(uploadToImagehost).then(result => {
if (typeof progressHandler == 'function') progressHandler(true);
return result.url;
})));
}
static loadJS() {
if (document.domain == 'notwhat.cd' && typeof uploadToImagehost != 'function') {
let imageUpload = document.createElement('script');
imageUpload.src = '/static/functions/image_upload.js';
imageUpload.type = 'text/javascript';
document.head.append(imageUpload);
}
}
}
var imageHostHandlers = {
'ptpimg': new PTPimg,
'malzo': new Chevereto('malzo.com', 'Malzo', ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'], 2, 2),
'imgbb': new Chevereto('imgbb.com', 'ImgBB', ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tif', 'tiff', 'heic'], 32, 32),
'imgbox': new ImgBox,
'pixhost': new PixHost,
'catbox': new Catbox,
'jerking': new Chevereto('jerking.empornium.ph', 'Jerking', ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'], 5, 5),
'picload': new Chevereto('free-picload.com', 'PicLoad', ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'], 50 , 96, 'picload'),
'fastpic': new FastPic,
'24a': new Chevereto('z4a.net', '24A', ['jpg', 'jpeg', 'png', 'bmp', 'gif'], 50),
'postimage': new PostImage,
'imgur': new Imgur,
'imagevenue': new ImageVenue,
'nwcd' : new NWCD,
};
const siteWhitelists = {
'notwhat.cd': ['nwcd'],
};
const siteBlacklists = {
'passthepopcorn.me': ['imgbox', 'picload', 'postimage', 'imgur', 'tinypic', 'imageshack', 'imagebam'],
};
let cheveretoCustomHosts = GM_getValue('chevereto_custom_hosts');
if (cheveretoCustomHosts) try {
cheveretoCustomHosts = JSON.parse();
Object.keys(cheveretoCustomHosts).forEach(function(key) {
imageHostHandlers[key] = new Chevereto(cheveretoCustomHosts[key].host_name, cheveretoCustomHosts[key].alias,
cheveretoCustomHosts[key].types, cheveretoCustomHosts[key].limit_anon,
cheveretoCustomHosts[key].limit_authorized, cheveretoCustomHosts[key].config_prefix);
});
} catch (e) { console.warn(e) }
[
'ptpimg_api_key',
'malzo_uid', 'malzo_password',
'imgbb_uid', 'imgbb_password', 'imgbb_api_key',
'catbox_userhash',
'imgbox_uid', 'imgbox_password',
'jerking_uid', 'jerking_password',
//'picload_uid', 'picload_password',
//'24a_uid', '24a_password',
'postimg_uid', 'postimg_password',
'imagevenue_uid', 'imagevenue_password',
'chevereto_custom_hosts',
].forEach(propName => { if (!GM_getValue(propName)) GM_setValue(propName, '') });
['upload_hosts', 'rehost_hosts'].forEach(propName => { if (!GM_getValue(propName)) GM_setValue(propName, [
'PtpImg', 'NWCD', 'PixHost', 'ImgBB', 'ImgBox', 'Catbox', 'Jerking',
'PicLoad', 'FastPic', '24A', 'PostImage', /*'Malzo', */'Imgur', 'ImageVenue',
].join(', ')) });
function processLists(alias) {
alias = alias.toLowerCase();
return (!Array.isArray(siteWhitelists[document.domain])
|| siteWhitelists[document.domain].find(whiteAlias => whiteAlias.toLowerCase() == alias) != undefined)
&& (!Array.isArray(siteBlacklists[document.domain])
|| siteBlacklists[document.domain].find(blackAlias => blackAlias.toLowerCase() == alias) == undefined);
}
var ulHostChain = (GM_getValue(document.domain) || GM_getValue('upload_hosts')).split(/\s*[\,\;\|\/]\s*/)
.filter(processLists)
.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 + ':', ulHostChain.map(handler => handler.alias).join(', '));
var rhHostChain = (GM_getValue(document.domain) || GM_getValue('rehost_hosts')).split(/\s*[\,\;\|\/]\s*/)
.filter(processLists)
.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 + ':', rhHostChain.map(handler => handler.alias).join(', '));
String.prototype.toASCII = function() {
return this.normalize("NFKD").replace(/[\x00-\x1F\u0080-\uFFFF]/g, '');
};
Array.prototype.flatten = function() {
return this.reduce(function(flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? toFlatten.flatten() : toFlatten);
}, []);
}
function uploadImages(files, progressHandler) {
if (typeof files != 'object') return Promise.reject('Invalid argument');
if (!Array.isArray(files)) files = Array.from(files);
//if (files.length > 1) files.push(files.shift());
if (ulHostChain.length <= 0) return Promise.reject('No hosts where to upload');
files = files.filter(file => file instanceof File && file.size && file.type && file.type.startsWith('image/'));
if (files.length <= 0) return Promise.reject('Nothing to upload');
return Promise.all(files.map(file => new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() {
if (reader.result.length != file.size) console.warn('FileReader: binary string read length mismatch (',
reader.result.length, '≠', file.size, ')');
resolve({ name: file.name, type: file.type, size: file.size, data: reader.result });
};
reader.onerror = reader.ontimeout = function() { reject('FileReader error (' + file.name + ')') };
reader.readAsBinaryString(file);
}))).then(function(images) {
return uploadInternal();
function uploadInternal(hostIndex = 0) {
return hostIndex >= 0 && hostIndex < ulHostChain.length ? (function() {
if (typeof progressHandler == 'function') {
progressHandler(hostIndex, null);
var _progressHandler = (param = null, index = undefined) => progressHandler(hostIndex, param, index);
}
if (Array.isArray(ulHostChain[hostIndex].types)
&& !files.every(file => ulHostChain[hostIndex].types.some(type => file.type == 'image/'.concat(type.toLowerCase()))))
return Promise.reject('MIME type not accepted for all files');
if (ulHostChain[hostIndex].sizeLimit && files.some(file => file.size > ulHostChain[hostIndex].sizeLimit * 2**20))
return Promise.reject('one or more images exceed size limit (' + ulHostChain[hostIndex].sizeLimit + 'MB)');
return ulHostChain[hostIndex].upload(ulHostChain[hostIndex].upload.acceptFiles ? files : images, _progressHandler);
})().catch(function(reason) {
console.warn('Upload to', ulHostChain[hostIndex].alias, 'failed:', reason);
var msg = 'Upload to '.concat(ulHostChain[hostIndex].alias, ' failed (', reason, ')');
if (++hostIndex < ulHostChain.length) {
logFail(msg.concat(', falling back to ', ulHostChain[hostIndex].alias));
return uploadInternal(hostIndex);
}
logFail(msg);
return Promise.reject('Upload failed to all hosts');
}) : Promise.reject('host index out of bounds ('.concat(hostIndex, ')'));
}
});
}
function rehostImages(urls, progressHandler) {
if (!Array.isArray(urls)) return Promise.reject('Invalid parameter');
if (urls.length <= 0) return Promise.reject('Nothing to rehost');
return rhHostChain.length > 0 ? rehostInternal() : Promise.resolve(urls);
function rehostInternal(hostIndex = 0) {
if (hostIndex < 0 || hostIndex >= rhHostChain.length) return Promise.reject('Host index out of bounds ('.concat(hostIndex, ')'));
if (typeof progressHandler == 'function') {
var _progressHandler = (param = true) => progressHandler(hostIndex, param);
_progressHandler(false);
}
return rhHostChain[hostIndex].rehost(urls, _progressHandler).catch(function(reason) {
console.warn('Rehost to', rhHostChain[hostIndex].alias, 'failed:', reason);
var msg = 'Rehost to '.concat(rhHostChain[hostIndex].alias, ' failed (', reason, ')');
if (++hostIndex < rhHostChain.length) {
logFail(msg.concat(', falling back to ', rhHostChain[hostIndex].alias));
return rehostInternal(hostIndex);
}
logFail(msg);
return Promise.reject('Rehost failed to all hosts');
});
}
}
function urlResolver(url) {
if (!urlParser.test(url)) return Promise.reject('Invalid URL:\n\n'.concat(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.concat(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':
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) {
//if (!strict && imageExtensions.some(ext => url.toLowerCase().endsWith('.'.concat(ext)))) return Promise.resolve(url); // weak
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: '.concat(url));
};
img.ontimeout = timeout => { reject('Image load timed out:\n\n'.concat(url)) };
img.src = url;
});
});
}
function verifyImageUrls(urls) {
return Array.isArray(urls) ? Promise.all(urls.map(verifyImageUrl)) : Promise.reject('URLs not an array');
}
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].concat('=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].concat('=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.concat('/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: '.concat(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/'.concat(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'.concat(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'.concat(url)))));
}
function getRemoteFileSize(url) {
return 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('Image not accessible'));
},
onerror: response => { reject('Image not accessible') },
ontimeout: response => { reject('Image not accessible') },
});
});
}
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 imageDropHandler(evt) { return !evt.shiftKey ? imageDataHandler(evt, evt.dataTransfer) : true }
function imagePasteHandler(evt) { return imageDataHandler(evt, evt.clipboardData) }
function imageClear(evt) {
evt.target.value = '';
coverPreview(evt.target, null);
}
function setInputHandlers(node) {
node.ondragover = voidDragHandler0;
node.ondblclick = imageClear;
node.ondrop = imageDropHandler;
node.onpaste = imagePasteHandler;
node.placeholder = 'Paste/drop local or remote image';
}
function setTextAreahandlers(node) {
node.ondragover = voidDragHandler0;
node.ondrop = descDropHandler;
node.onpaste = descPasteHandler;
};
function logFail(message) {
var log = document.getElementById('ihh-console');
if (log == null) {
log = document.createElement('div');
log.id = 'ihh-console';
log.style = 'position: fixed; bottom: 20px; right: 20px; width: 64em; border: solid lightsalmon 4px;' +
' background-color: antiquewhite; padding: 10px; opacity: 1;' +
' transition: opacity 1000ms linear; -webkit-transition: opacity 1000ms linear;';
document.body.append(log);
} else if (log.hTimer) {
clearTimeout(log.hTimer);
log.style.opacity = 1;
}
var div = document.createElement('div');
div.style = 'font: 600 9pt Verdana, sans-serif; color: red;';
div.textContent = message;
log.append(div);
log.hTimer = setTimeout(function(node) {
node.style.opacity = 0;
node.hTimer = setTimeout(function(node) { node.remove() }, 1000, node);
}, 30000, log);
}