NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // %genload_req% // @name Enhanced SauceNao // @namespace https://github.com/hybridsong/saucenao // @version 2018.12.12.2017.10.04 // @author hybridsong // @license GPL-3.0-only // @icon https://secure.gravatar.com/avatar/187e65a2ca69335f7483a4f19812050a // @match *://saucenao.com/* // @run-at document-end // @grant GM_listValues // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // ==/UserScript== /* * Enhanced SauceNao * \hybridsong/ */ (function (global, factory) { if (typeof define === "function" && define.amd) { define([], factory); } else if (typeof exports !== "undefined") { factory(); } else { var mod = { exports: {} }; factory(); global.FileSaver = mod.exports; } })(this, function () { "use strict"; /* * FileSaver.js * A saveAs() FileSaver implementation. * * By Eli Grey, http://eligrey.com * * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) * source : http://purl.eligrey.com/github/FileSaver.js */ // The one and only way of getting global scope in all environments // https://stackoverflow.com/q/3277182/1008999 var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : void 0; function bom(blob, opts) { if (typeof opts === 'undefined') opts = { autoBom: false };else if (typeof opts !== 'object') { console.warn('Depricated: Expected third argument to be a object'); opts = { autoBom: !opts }; } // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type }); } return blob; } function download(url, name, opts) { var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = 'blob'; xhr.onload = function () { saveAs(xhr.response, name, opts); }; xhr.onerror = function () { console.error('could not download file'); }; xhr.send(); } function corsEnabled(url) { var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker xhr.open('HEAD', url, false); xhr.send(); return xhr.status >= 200 && xhr.status <= 299; } // `a.click()` doesn't work for all browsers (#465) function click(node) { try { node.dispatchEvent(new MouseEvent('click')); } catch (e) { var evt = document.createEvent('MouseEvents'); evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); node.dispatchEvent(evt); } } var saveAs = _global.saveAs || // probably in some web worker typeof window !== 'object' || window !== _global ? function saveAs() {} /* noop */ // Use download attribute first if possible (#193 Lumia mobile) : 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) { var URL = _global.URL || _global.webkitURL; var a = document.createElement('a'); name = name || blob.name || 'download'; a.download = name; a.rel = 'noopener'; // tabnabbing // TODO: detect chrome extensions & packaged apps // a.target = '_blank' if (typeof blob === 'string') { // Support regular links a.href = blob; if (a.origin !== location.origin) { corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank'); } else { click(a); } } else { // Support blobs a.href = URL.createObjectURL(blob); setTimeout(function () { URL.revokeObjectURL(a.href); }, 4E4); // 40s setTimeout(function () { click(a); }, 0); } } // Use msSaveOrOpenBlob as a second approach : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) { name = name || blob.name || 'download'; if (typeof blob === 'string') { if (corsEnabled(blob)) { download(blob, name, opts); } else { var a = document.createElement('a'); a.href = blob; a.target = '_blank'; setTimeout(function () { click(a); }); } } else { navigator.msSaveOrOpenBlob(bom(blob, opts), name); } } // Fallback to using FileReader and a popup : function saveAs(blob, name, opts, popup) { // Open a popup immediately do go around popup blocker // Mostly only avalible on user interaction and the fileReader is async so... popup = popup || open('', '_blank'); if (popup) { popup.document.title = popup.document.body.innerText = 'downloading...'; } if (typeof blob === 'string') return download(blob, name, opts); var force = blob.type === 'application/octet-stream'; var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari; var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); if ((isChromeIOS || force && isSafari) && typeof FileReader === 'object') { // Safari doesn't allow downloading of blob urls var reader = new FileReader(); reader.onloadend = function () { var url = reader.result; url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;'); if (popup) popup.location.href = url;else location = url; popup = null; // reverse-tabnabbing #460 }; reader.readAsDataURL(blob); } else { var URL = _global.URL || _global.webkitURL; var url = URL.createObjectURL(blob); if (popup) popup.location = url;else location.href = url; popup = null; // reverse-tabnabbing #460 setTimeout(function () { URL.revokeObjectURL(url); }, 4E4); // 40s } }; _global.saveAs = saveAs.saveAs = saveAs; if (typeof module !== 'undefined') { module.exports = saveAs; } }); let main_style = "\ #ES-images { position: fixed; top: 0; right: 5px; width: 125px; height: 100%; overflow-y: auto; z-index:1; display:none }\ #ES-total { position: fixed; bottom: 5; right: 50px; background: #1d1d1d; padding: 0 5px; opacity: 0.5; z-index:1 }\ #ES-namefix { position: fixed; bottom: 25; right: 50px; background: #1d1d1d; padding: 0 5px; opacity: 0.5; z-index:1 }\ #ES-results { position: absolute; top:0; height: 100%; background: #1d1d1d; display: none }\ " let head = document.querySelector('head'), style = document.createElement("style") style.type = "text/css" style.append(main_style) head.append(style) 'use strict' let doc_url = new URL(document.location.href), NF_useboard = "danbooru", NF_delimiter = "_", NF_debug = true const hosts = [ 'www.pixiv.net', 'danbooru.donmai.us', 'chan.sankakucomplex.com', 'yande.re', ] function createObjectURL(object) { return (window.URL) ? window.URL.createObjectURL(object) : window.webkitURL.createObjectURL(object); } function revokeObjectURL(url) { return (window.URL) ? window.URL.revokeObjectURL(url) : window.webkitURL.revokeObjectURL(url); } function filename (str) { var name = new String(str).substring(str.lastIndexOf('/') + 1); return name; } function extname (path) { var filename = path.split('\\').pop().split('/').pop(); return filename.substr(( Math.max(0, filename.lastIndexOf(".")) || Infinity) + 1); } if (doc_url.pathname == '/') { // root let base = document.createElement("base") base.target = '_self' document.head.append(base) let mount_scripts = () => { if (!document.getElementById('mounted_scripts')) { let scripts = results_container.querySelectorAll('script') for (let i=0; i<scripts.length; i++) { let script = document.createElement("script") script.textContent = scripts[i].innerText script.id = 'mounted_scripts' document.body.append(script) scripts[i].remove() } downloadJSAtOnload() // load jQuery } } let adjust_results_page = () => { base.target = '_blank' results_container.querySelector('#Search').remove() } const get_link = (host) => { let links = results_container.querySelectorAll('\ .result:not(.hidden) .resultmiscinfo a,\ .result:not(.hidden) .resultcontentcolumn>a\ ') for (let link of links) { let m = link.href.match(/\:\/\/(.+?)\//) if (m && m[1]) { if (host == m[1]) return link } } } let xhr = new XMLHttpRequest(), get_results = (file, img_btn, next) => { //return formData = new FormData() formData.append('file', file) formData.append('url', '') formData.append('frame', 1) formData.append('hide', 0) formData.append('database', 999) xhr.open("POST", '/search.php') xhr.onreadystatechange = function() { if(xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) { if (!img_btn.searched) { img_btn.searched = 1 total_searched++ total.update() } img_btn.style.opacity = 0.5 results.style.display = 'block' GM_setValue(file.name, true) results_container.innerHTML = xhr.response mount_scripts() adjust_results_page() if (next) next() } messages.innerText = `${xhr.statusText} (${xhr.status})` } xhr.send(formData) } let total_searched = 0, total_images = 0 let image_list = null, load_images = (files) => { images.style.display = 'block' total_images = 0 total_searched = 0 image_list = [] next_state.i = 0 if(files.length) { for(let i=0; i<files.length; i++) { var src = createObjectURL(files[i]) var image = new Image() image.src = src image.style.width = 100 image.searched = 0 if (GM_getValue(files[i].name)) { total_searched++ total.update() image.searched = 1 image.style.opacity = 0.5 } image.onclick = function () { next_state.i = i+1 this.style.opacity = 0.9 get_results(files[i], this) } images.append(image) total_images++ total.update() image_list[i] = { file: files[i], image:image } } } } let namefix_state = { i: 0, save: function (id, file) { if (id) saveAs(file, `${file.name.split('.')[0]}${NF_delimiter}${NF_useboard}${NF_delimiter}${id}.${extname(file.name)}`) else saveAs(file, `${file.name.split('.')[0]}${NF_delimiter}search${NF_delimiter}failed.${extname(file.name)}`) }, proc: function () { if (this.i == image_list.length) return let { file, image } = image_list[this.i] if (!image.searched) { image.style.opacity = 0.9 get_results(file, image, () => { let links = results_container.querySelectorAll('.result:not(.hidden) .resultmiscinfo a'), urls = [], id = null links.forEach((link, i) => { let m = link.href.match(/\:\/\/(.+?)\//) if (m && m[1]) { let host = m[1] if (host == 'danbooru.donmai.us') urls.push(link.href) } }) if (urls.length) { let m = urls[0].match(/\/(\d+)/) id = m ? m[1] : null } if (NF_debug) console.log(urls, links, id) this.save(id, file) setTimeout(() => this.proc(), 2000) }) } else { if (this.i < image_list.length) setTimeout(() => this.proc(), 0) } this.i++ } } let next_state = { i: 0, loading: 0, load: function () { if (this.i == image_list.length || this.loading) return let { file, image } = image_list[this.i] images.scrollTop = image.offsetTop image.style.opacity = 0.9 this.loading = 1 get_results(file, image, () => { this.i++ this.loading = 0 }) } } let select_button = document.createElement('input') select_button.multiple = true select_button.type = 'file' select_button.style.width = '80px' select_button.onchange = function () { load_images(this.files) } document.querySelector('form').insertBefore(select_button, document.querySelector('form p')) document.querySelector('#Search').style.width = '465px' let images = document.createElement('div') images.id = "ES-images" let total = document.createElement('div') total.id = "ES-total" total.update = () => { total.innerText = `${total_images}/${total_searched}` } total.onclick = () => { if (confirm("Clean")) clean() } images.append(total) let namefix = document.createElement('div') namefix.id = "ES-namefix" namefix.innerText = 'NF' namefix.onclick = () => { if (confirm("Name Fix")) { namefix.onclick = null namefix_state.proc() } } images.append(namefix) document.body.append(images) let results = document.createElement('div') results.id = 'ES-results' let widget = document.createElement('div') widget.style.position = 'fixed' widget.style.bottom = 0 widget.style.right = '135px' widget.style.color = '#666' results.append(widget) let messages = document.createElement('div') widget.append(messages) let back = document.createElement('div') back.append('Back') back.onclick = () => { results.style.display = 'none' } widget.append(back) let results_container = document.createElement('div') results.append(results_container) document.body.append(results) window.onbeforeunload = () => { if (total_searched == total_images && image_list.length) clean() } let clean = () => { let vals = GM_listValues() for (let i=0; i<vals.length; i++) GM_deleteValue(vals[i]) } document.onpaste = e => { load_images(e.clipboardData.files) next_state.load() } document.onkeydown = (e) => { let key = String.fromCharCode(e.keyCode) switch (key) { case 'N': next_state.load() break } if (key.match(/\d/)) { const link = get_link(hosts[key-1]) if (link) link.click() } } } // root