您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
×
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/401726/806453/imageHostUploader.js
// ==UserScript== // @name imageHostUploader // @namespace https://greasyfork.org/cs/users/321857-anakunda // @version 1.30 // @author Anakunda // @description × // @require https://greasyfork.org/scripts/401725-globalfetch/code/globalFetch.js // @require https://greasyfork.org/scripts/394414-ua-resource/code/UA-resource.js // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // ==/UserScript== 'use strict'; const ulTimeFactor = GM_getValue('upload_speed', 128); const rehostTimeout = 30000; const urlParser = /^\s*(https?:\/\/\S+)\s*$/i; const ptpimgOrigin = 'https://ptpimg.me'; const ptpSiteWhitelist = ['passthepopcorn.me', 'redacted.ch', 'orpheus.network', 'notwhat.cd', 'dicmusic.club', 'broadcasthe.net']; const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tif', 'tiff', 'heic']; const imageHostHandlers = { 'ptpimg': ['PtpImg', upload2PTPIMG, rehost2PTPIMG], 'malzo': ['Malzo', (images, elem) => upload2Chevereto('malzo.com', images, elem), urls => rehost2Chevereto('malzo.com', urls)], 'imgbb': ['ImgBB', (images, elem) => upload2Chevereto('imgbb.com', images, elem), urls => rehost2Chevereto('imgbb.com', urls)], 'imgbox': ['ImgBox', upload2ImgBox, null], 'pixhost': ['PixHost', upload2PixHost, rehost2PixHost], 'catbox': ['Catbox', upload2Catbox, rehost2Catbox], 'jerking': ['Jerking', (images, elem) => upload2Chevereto('jerking.empornium.ph', images, elem), urls => rehost2Chevereto('jerking.empornium.ph', urls)], 'picload': ['PicLoad', (images, elem) => upload2Chevereto('free-picload.com', images, elem), urls => rehost2Chevereto('free-picload.com', urls)], 'fastpic': ['FastPic', upload2FastPic, null], '24a': ['24A', (images, elem) => upload2Chevereto('z4a.net', images, elem), urls => rehost2Chevereto('z4a.net', urls)], 'postimage': ['PostImage', upload2PostImg, rehost2PostImg], 'imgur': ['Imgur', upload2Imgur, rehost2Imgur], 'imagevenue': ['ImageVenue', upload2ImageVenue, null], }; [ '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', ].forEach(propName => { if (!GM_getValue(propName)) GM_setValue(propName, '') }); ['upload_hosts', 'rehost_hosts'].forEach(propName => { if (!GM_getValue(propName)) GM_setValue(propName, [ 'PtpImg', 'Malzo', 'PixHost', 'ImgBB', 'ImgBox', 'Catbox', 'Jerking', 'PicLoad', 'FastPic', '24A', 'PostImage', 'Imgur', 'ImageVenue', ].join(', ')) }); var ulHostChain = (GM_getValue(document.domain) || GM_getValue('upload_hosts')).split(/\s*[\,\;\|\/]\s*/) .map(alias => imageHostHandlers[alias.toLowerCase()]) .filter(handler => Array.isArray(handler) && typeof handler[1] == 'function' && (handler[0].toLowerCase() != 'ptpimg' || ptpSiteWhitelist.includes(document.domain))); var rhHostChain = (GM_getValue(document.domain) || GM_getValue('rehost_hosts')) .split(/\s*[\,\;\|\/]\s*/).map(alias => imageHostHandlers[alias.toLowerCase()]) .filter(handler => Array.isArray(handler) && typeof handler[2] == 'function' && (handler[0].toLowerCase() == 'ptpimg' ? ptpSiteWhitelist.includes(document.domain) : document.domain != 'redacted.ch')); 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, elem) { 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'); var frs = files.filter(function(file) { return file instanceof File && imageExtensions.some(ext => file.type == 'image/' + ext); })/*.sort((file1, file2) => file1.name.localeCompare(file2.name))*/.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); })); if (frs.length <= 0) return Promise.reject('Nothing to upload'); return Promise.all(frs).then(function(images) { return uploadInternal(); function uploadInternal(hostIndex = 0) { return hostIndex >= 0 && hostIndex < ulHostChain.length ? ulHostChain[hostIndex][1](images, elem).catch(function(reason) { if (++hostIndex >= ulHostChain.length) return Promise.reject('Upload failed to all hosts'); console.warn('Upload to', ulHostChain[hostIndex - 1][0], 'failed (' + reason + '), falling back to', ulHostChain[hostIndex][0]); return uploadInternal(hostIndex); }) : Promise.reject('Host index out of bounds ('.concat(hostIndex, ')')); } }); } function rehostImages(urls) { 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) { return hostIndex >= 0 && hostIndex < rhHostChain.length ? rhHostChain[hostIndex][2](urls).catch(function(reason) { if (++hostIndex >= rhHostChain.length) return Promise.reject('Rehost failed to all hosts'); console.warn('Rehost to', rhHostChain[hostIndex - 1][0], 'failed (' + reason + '), falling back to', rhHostChain[hostIndex][0]); return rehostInternal(hostIndex); }) : Promise.reject('Host index out of bounds ('.concat(hostIndex, ')')); } } 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 'goo.gl': case 'apple.co': case 'flic.kr': case 'rebrand.ly': case 'b.link': case 't2m.io': case 'zpr.io': case 'yourls.org': 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:\n\n'.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 upload2PTPIMG(images, elem) { if (!Array.isArray(images)) return Promise.reject('invalid argument'); if (images.length <= 0) return Promise.reject('nothing to upload'); return getPTPIMGapiKey().then(apiKey => new Promise(function(resolve, reject) { var now = Date.now(); const boundary = '----WebKitFormBoundary'.concat(now.toString(16).toUpperCase()); var formData = '--' + boundary + '\r\n'; images.filter(function(image) { return image.data && image.name && ['png', 'jpg', 'jpeg', 'gif', 'bmp'].some(ext => image.type == 'image/'.concat(ext)); }).forEach(function(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: ptpimgOrigin + '/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: function(response) { if (response.status >= 200 && response.status < 400) { if (response.response) resolve(response.response.map(item => ptpimgOrigin + '/' + item.code + '.' + item.ext)); else reject('void response'); } else reject(defaultErrorHandler(response)); }, onprogress: elem instanceof HTMLElement && 'value' in elem ? function(progress) { var pct = progress.position * 100 / progress.total; //elem.value = 'Uploading... (' + Math.round(pct) + '%)'; } : undefined, onerror: error => reject(defaultErrorHandler(error)), ontimeout: timeout => reject(defaultTimeoutHandler(timeout)), }); })); } function rehost2PTPIMG(urls) { if (!Array.isArray(urls)) return Promise.reject('Invalid parameter'); if (urls.length <= 0) return Promise.resolve([]); //Promise.reject('Nothing to rehost'); return Promise.all(urls.map(function(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 => rehost2Catbox([url]).then(imgUrls => imgUrls[0])) .catch(reason => rehost2PixHost([url]).then(imgUrls => imgUrls[0])) .catch(reason => reupload2PTPIMG(url)); } else if (!['png', 'jpg', 'jpeg', 'gif', 'bmp'].some(ext => url.toLowerCase().endsWith('.'.concat(ext)))) { return verifyImageUrl(url.concat('#.jpg')) .catch(reason => rehost2Chevereto('malzo.com', [url]).then(imgUrls => imgUrls[0])) .catch(reason => rehost2PixHost([url]).then(imgUrls => imgUrls[0])) .catch(reason => rehost2Chevereto('imgbb.com', [url]).then(imgUrls => imgUrls[0])) .catch(reason => rehost2Chevereto('jerking.empornium.ph', [url]).then(imgUrls => imgUrls[0])) .catch(reason => rehost2Chevereto('free-picload.com', [url]).then(imgUrls => imgUrls[0])); //.catch(reason => rehost2Imgur([url]).then(imgUrls => imgUrls[0])); } return verifyImageUrl(url); })).then(imageUrls => getPTPIMGapiKey().then(function(apiKey) { console.debug('rehost2PTPIMG(...) input:', imageUrls); var formData = new URLSearchParams({ 'link-upload': imageUrls.join('\r\n'), 'api_key': apiKey, }); return globalFetch(ptpimgOrigin + '/upload.php', { responseType: 'json', timeout: imageUrls.length * rehostTimeout, }, formData).then(function(response) { if (!response.response) return Promise.reject('PTPIMG void response'); if (response.response.length < imageUrls.length) return Promise.reject(`not all images rehosted (${response.response.length}/${imageUrls.length})`); return response.response.map(item => ptpimgOrigin.concat('/', item.code, '.', item.ext)); }); })); } function reupload2PTPIMG(imgUrl) { console.warn('PTPIMG rehoster fallback to local reupload'); return globalFetch(imgUrl, { responseType: 'blob' }).then(function(response) { var image = { name: imgUrl.replace(/^.*\//, ''), data: response.responseText, }; 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 upload2PTPIMG([image]).then(imgUrls => imgUrls[0]); }); } function getPTPIMGapiKey() { var ptpimg_api_key = GM_getValue('ptpimg_api_key'); if (ptpimg_api_key) return Promise.resolve(ptpimg_api_key); try { var apiKey = JSON.parse(window.localStorage.ptpimg_it).api_key; if (apiKey) { GM_setValue('ptpimg_api_key', ptpimg_api_key = apiKey); return Promise.resolve(apiKey); } } catch(e) { console.debug('getPTPIMGapiKey():', e) } return globalFetch(ptpimgOrigin).then(function(response) { if ((apiKey = response.document.getElementById('api_key')) == null) { let counter = GM_getValue('ptpimg_reminder_read', 0); if (counter < 3) { alert(` PTPIMG API key could not be captured. Please login to ${ptpimgOrigin}/ 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'); GM_setValue('ptpimg_api_key', ptpimg_api_key = apiKey.value); Promise.resolve(ptpimg_api_key).then(apiKey => { alert(`Your PTPIMG API key [${apiKey}] was successfully configured`) }); return ptpimg_api_key; }); } function upload2Chevereto(hostname, images, elem) { if (!Array.isArray(images)) return Promise.reject('invalid argument'); if (images.length <= 0) return Promise.reject('nothing to upload'); const anonSessionLimits = { 'malzo.com': 2, 'imgbb.com': 2, 'jerking.empornium.ph': 5, 'free-picload.com': 50, }; return setCheveretoSession(hostname).then(session => Promise.all(images.map(image => new Promise(function(resolve, reject) { switch (hostname) { case 'malzo.com': case 'jerking.empornium.ph': case 'free-picload.com': if (!['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].some(ext => image.type == 'image/'.concat(ext))) throw 'MIME type not supported: '.concat(image.type); break; case 'imgbb.com': if (!['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tif', 'tiff', 'heic'].some(ext => image.type == 'image/'.concat(ext))) throw 'MIME type not supported: '.concat(image.type); break; } var anonSessionLimit = anonSessionLimits[hostname.toLowerCase()]; if (!session.username && typeof anonSessionLimit == 'number' && image.size > anonSessionLimit * 2**20) throw 'image size exceeds anonymous upload limit'; 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(function(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'; GM_xmlhttpRequest({ method: 'POST', url: 'https://'.concat(hostname, '/json'), responseType: 'json', headers: { 'Accept': 'application/json', 'Content-Type': 'multipart/form-data; boundary=' + boundary, 'Content-Length': formData.length, 'Referer': 'https://'.concat(hostname, '/'), }, data: formData, binary: true, timeout: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000), onload: function(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)); }, onerror: error => reject(defaultErrorHandler(error)), ontimeout: timeout => reject(defaultTimeoutHandler(timeout)), }); function formField(key, value) { return 'Content-Disposition: form-data; name="' + key + '"\r\n\r\n' + value + '\r\n--' + boundary; } })))); } function rehost2Chevereto(hostname, urls) { if (hostname.toLowerCase() == 'free-picload.com' && ['passthepopcorn.me'].some(domain => document.domain == domain)) return Promise.reject(hostname.concat(' blacklisted for this site')); return setCheveretoSession(hostname).then(session => Promise.all(urls.map(url => verifyImageUrl(url).then(function(imageUrl) { var formData = new URLSearchParams(Object.assign({ action: 'upload', type: 'url', nsfw: 0, source: imageUrl, }, session)); return globalFetch('https://'.concat(hostname, '/json'), { responseType: 'json', headers: { 'Referer': 'https://'.concat(hostname, '/') }, timeout: urls.length * rehostTimeout, }, formData).then(function(response) { return response.response.success ? response.response.image.url : Promise.reject(hostname.concat(': ', response.response.error.message,' (', response.response.status_code, ')')); }); })))); } function cheveretoGalleryResolver(hostname, url) { var albumId = /^\/(?:album|al)\/(\w+)\b/.test(url.pathname) && RegExp.$1; if (!albumId) return Promise.reject('Invlaid gallery URL'); return setCheveretoSession(hostname).then(function(session) { var formData = new URLSearchParams(Object.assign({ action: 'get-album-contents', albumid: albumId, }, session)); return globalFetch(url.origin.concat('/json'), { responseType: 'json', headers: { 'Referer': url }, }, formData).then(function(response) { return response.response.status_txt == 'OK' && Array.isArray(response.response.contents) ? response.response.contents.map(image => image.url) : Promise.reject(hostname.concat(': ', response.response.error.message,' (', response.response.status_code, ')')); }); }).catch(function(reason) { console.warn(hostname, 'gallery couldn\'t be resolved via API:', reason, '(falling back to HTML parser)'); return new Promise(function(resolve, reject) { var urls = [], domParser = new DOMParser; getPage(url); function getPage(url) { GM_xmlhttpRequest({ method: 'GET', url: url, headers: { Referer: url }, onload: function(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))); }); } function setCheveretoSession(hostname) { const index = 'https://'.concat(hostname, '/'); return globalFetch(index).then(function(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)) return session; if (hostname.toLowerCase() == 'free-picload.com') var hostPrefix = 'picload_'; else if (/^([\w\-]+)(?:\.[\w\-]+)+$/.test(hostname)) hostPrefix = RegExp.$1.toLowerCase().concat('_'); if (!hostPrefix) return session; var login = GM_getValue(hostPrefix.concat('uid')), password = GM_getValue(hostPrefix.concat('password')); if (!login || !password) return session; var formData = new URLSearchParams({ 'login-subject': login, 'password': password, 'auth_token': session.auth_token, }); return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: 'POST', url: 'https://'.concat(hostname, '/login'), headers: { 'Accept': '*/*', 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': formData.toString().length, 'Referer': 'https://'.concat(hostname, '/login'), }, data: formData.toString(), onload: function(response) { if (response.status < 200 || response.status > 400) defaultErrorHandler(response); resolve(response.status); }, onerror: function(response) { reject(defaultErrorHandler(response)); //resolve(response.status); }, ontimeout: function(response) { reject(defaultTimeoutHandler(response)); //resolve(response.status); }, }); }).then(status => globalFetch(index, { responseType: 'text' }).then(function(response) { if (getUser(response)) console.debug(hostname, 'authorized session:', session); else console.warn(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; } }); } function upload2PixHost(images, elem) { 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 => new Promise(function(resolve, reject) { if (!['png', 'jpg', 'jpeg', 'gif'].some(ext => image.type == 'image/'.concat(ext))) throw 'MIME type not supported: '.concat(image.type); var now = Date.now(); const boundary = '----WebKitFormBoundary'.concat(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'; 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: function(response) { if (response.status >= 200 && response.status < 400) resolve(response.response.show_url); else reject(defaultErrorHandler(response)); }, onerror: error => reject(defaultErrorHandler(error)), ontimeout: timeout => reject(defaultTimeoutHandler(timeout)), }); }).then(imageUrlResolver))); } function rehost2PixHost(urls) { return verifyImageUrls(urls).then(function(imageUrls) { //console.debug('rehost2PixHost(...) input:', imageUrls.join('\n')); var formData = new URLSearchParams({ imgs: imageUrls.join('\r\n'), content_type: 0, tos: 'on', }); return globalFetch('https://pixhost.to/remote/', { responseType: 'text', timeout: imageUrls.length * rehostTimeout, }, formData).then(function(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))); }); }); } function upload2Catbox(images, elem) { if (!Array.isArray(images)) return Promise.reject('Invalid argument'); if (images.length <= 0) return Promise.reject('Nothing to upload or format not supported'); return getCatboxUserHash().catch(reason => undefined).then(userHash => Promise.all(images.map(image => new Promise(function(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'; GM_xmlhttpRequest({ method: 'POST', url: 'https://catbox.moe/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: function(response) { if (response.status >= 200 && response.status < 400) resolve(response.responseText); else reject(defaultErrorHandler(response)); }, onerror: error => reject(defaultErrorHandler(error)), ontimeout: timeout => reject(defaultTimeoutHandler(timeout)), }); })))); } function rehost2Catbox(urls) { return getCatboxUserHash().catch(reason => undefined).then(userHash => Promise.all(urls.map(url => verifyImageUrl(url).then(function(imageUrl) { var formData = new URLSearchParams({ reqtype: 'urlupload', url: imageUrl, }); if (userHash) formData.set('userhash', userHash); return globalFetch('https://catbox.moe/user/api.php', { responseType: 'text', timeout: urls.length * rehostTimeout, }, formData).then(response => response.responseText); })))); } function getCatboxUserHash() { var catbox_userhash = GM_getValue('catbox_userhash'); return catbox_userhash ? Promise.resolve(catbox_userhash) : globalFetch('https://catbox.moe/').then(function(response) { catbox_userhash = response.document.querySelector('input[name="userhash"][value]'); return catbox_userhash != null && catbox_userhash.value || Promise.reject('Catbox.moe: not logged in or userhash not found'); }); } function upload2ImageVenue(images, elem) { if (!Array.isArray(images)) return Promise.reject('invalid argument'); if (images.length <= 0) return Promise.reject('nothing to upload'); return setImageVenueSession().then(session => new Promise(function(resolve, reject) { var now = Date.now(); const boundary = '----WebKitFormBoundary'.concat(now.toString(16).toUpperCase()); var formData = '--' + boundary + '\r\n'; Object.keys(session).forEach(function(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(function(image, index, arr) { if (!['png', 'jpg', 'jpeg', 'gif'].some(ext => image.type == 'image/'.concat(ext))) throw 'MIME type not supported: '.concat(image.type); 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: 'http://www.imagevenue.com/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: function(response) { if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response)); resolve(response.response.success); }, onerror: error => reject(defaultErrorHandler(error)), ontimeout: timeout => reject(defaultTimeoutHandler(timeout)), }); })).then(resultUrl => globalFetch(resultUrl)).then(function(response) { var links = response.document.querySelectorAll('div.row > div > a'); return Promise.all(Array.from(links).map(a => imageUrlResolver(a.href))); }); } function setImageVenueSession() { return globalFetch('http://www.imagevenue.com/').then(function(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: 'http://www.imagevenue.com/auth/login', headers: { 'Referer': 'http://www.imagevenue.com/auth/login', 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Content-Length': formData.toString().length, }, data: formData.toString() }); return new Promise(function(resolve, reject) { setTimeout(function() { globalFetch('http://www.imagevenue.com/').then(function(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('http://www.imagevenue.com/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, }))); } function upload2ImgBox(images, elem) { if (['passthepopcorn.me'].some(domain => document.domain == domain)) return Promise.reject('ImgBox blacklisted for this site'); if (!Array.isArray(images)) return Promise.reject('invalid argument'); if (images.length <= 0) return Promise.reject('nothing to upload'); return setImgBoxSession().then(session => Promise.all(images.map(image => new Promise(function(resolve, reject) { if (!['png', 'jpg', 'jpeg', 'gif'].some(ext => image.type == 'image/'.concat(ext))) throw 'MIME type not supported: '.concat(image.type); var now = Date.now(); const boundary = '----WebKitFormBoundary'.concat(now.toString(16).toUpperCase()); var formData = '--' + boundary + '\r\n'; Object.keys(session.params).forEach(function(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'; GM_xmlhttpRequest({ method: 'POST', url: 'https://imgbox.com/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: function(response) { if (response.status < 200 || response.status >= 400) return reject(defaultErrorHandler(response)); resolve(response.response.files[0].original_url); }, onerror: error => reject(defaultErrorHandler(error)), ontimeout: timeout => reject(defaultTimeoutHandler(timeout)), }); })))); } function setImgBoxSession() { return globalFetch('https://imgbox.com/').then(function(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(function(resolve, reject) { setTimeout(() => { globalFetch('http://imgbox.com/').then(function(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('https://imgbox.com/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, }, }))); } function upload2Imgur(images, elem) { if (['passthepopcorn.me'].some(domain => document.domain == domain)) return Promise.reject('Imgur blacklisted for this site'); 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 => new Promise(function(resolve, reject) { const requestUrl = 'https://imgur.com/upload'; 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'; GM_xmlhttpRequest({ method: 'POST', url: requestUrl, responseType: 'json', headers: { 'Content-Type': 'multipart/form-data; boundary=' + boundary, 'Content-Length': formData.length, 'Referer': requestUrl, }, data: formData, binary: true, timeout: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000), onload: function(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)); }, onerror: error => reject(defaultErrorHandler(error)), ontimeout: timeout => reject(defaultTimeoutHandler(timeout)), }); }))); } function rehost2Imgur(urls) { if (['passthepopcorn.me'].some(domain => document.domain == domain)) return Promise.reject('Imgur blacklisted for this site'); return Promise.all(urls => urls.map(url => verifyImageUrl(url).then(function(imageUrl) { const requestUrl = 'https://imgur.com/upload'; var formData = new URLSearchParams({ url: imageUrl }); return globalFetch(requestUrl, { responseType: 'json', headers: { Referer: requestUrl }, timeout: urls.length * rehostTimeout, }, formData).then(function(result) { if (!result.response.success) return Promise.reject(result.response.status); return 'https://i.imgur.com/'.concat(result.response.data.hash, result.response.data.ext); }); }))); } function upload2PostImg(images, elem) { if (['passthepopcorn.me'].some(domain => document.domain == domain)) return Promise.reject('PostImages blacklisted for this site'); if (!Array.isArray(images)) return Promise.reject('invalid argument'); if (images.length <= 0) return Promise.reject('nothing to upload'); return setPostImgSession().then(session => Promise.all(images.map(image => new Promise(function(resolve, reject) { var now = Date.now(); const boundary = '----WebKitFormBoundary'.concat(now.toString(16).toUpperCase()); var formData = '--' + boundary + '\r\n'; Object.keys(session).forEach(function(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'; GM_xmlhttpRequest({ method: 'POST', url: 'https://postimages.org/json/rr', responseType: 'json', headers: { 'Content-Type': 'multipart/form-data; boundary=' + boundary, 'Content-Length': formData.length, //'Referer': 'https://postimages.org/', }, data: formData, binary: true, timeout: Math.max(Math.ceil(formData.length / ulTimeFactor), 10000), onload: function(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); }, onerror: response => { reject(defaultErrorHandler(response)) }, ontimeout: response => { reject(defaultTimeoutHandler(response)) }, }); }).then(imageUrlResolver)))); } function rehost2PostImg(urls) { if (['passthepopcorn.me'].some(domain => document.domain == domain)) return Promise.reject('PostImages blacklisted for this site'); return setPostImgSession().then(session => Promise.all(urls => urls.map(url => verifyImageUrl(url).then(function(imageUrl) { var formData = new URLSearchParams(Object.assign({ url: imageUrl }, session)); return globalFetch('https://postimages.org/json/rr', { responseType: 'json', timeout: urls.length * rehostTimeout, }, formData).then(function(response) { if (response.status < 200 || response.status >= 400) return Promise.reject(defaultErrorHandler(response)); if (response.response.status != 'OK') return Promise.reject(response.response.status); return imageUrlResolver(response.response.url); }); })))); } function setPostImgSession() { return globalFetch('https://postimages.org/').then(function(response) { var session = { session_upload: Date.now(), upload_session: rand_string(32), optsize: 0, expire: 0, numfiles: 1, upload_referer: btoa('https://postimages.org/'), }; 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: 'https://postimages.org/login', data: formData.toString(), headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Content-Length': formData.toString().length, //'Referer': 'https://postimages.org/login', }, onload: function(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; } } function upload2FastPic(images, elem) { if (!Array.isArray(images)) return Promise.reject('invalid argument'); if (images.length <= 0) return Promise.reject('nothing to upload'); const boundary = '----WebKitFormBoundary'.concat(Date.now().toString(16).toUpperCase()); var formData = '--' + boundary + '\r\n'; images.forEach(function(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'; return new Promise((resolve, reject) => GM_xmlhttpRequest({ method: 'POST', url: 'https://fastpic.ru/uploadmulti', 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: function(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'); } }, onerror: response => { reject(defaultErrorHandler(response)) }, ontimeout: response => { reject(defaultTimeoutHandler(response)) }, })).then(resultUrl => globalFetch(resultUrl).then(function(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})`); })); } 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; };