hybridsong / Enhance SauceNao

// ==UserScript==
// @name           Enhance SauceNao
// @version        2017.10.04
// @author         hybridsong
// @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
// ==/UserScript==

/*
 *  Enhance SauceNao
 *  by hybridsong
 */


/* FileSaver.js
 * A saveAs() FileSaver implementation.
 * 1.3.2
 * 2016-06-16 18:25:19
 *
 * By Eli Grey, http://eligrey.com
 * License: MIT
 *   See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
 */

/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */

/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

var saveAs = saveAs || (function(view) {
	"use strict";
	// IE <10 is explicitly unsupported
	if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
		return;
	}
	var
		  doc = view.document
		  // only get URL when necessary in case Blob.js hasn't overridden it yet
		, get_URL = function() {
			return view.URL || view.webkitURL || view;
		}
		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
		, can_use_save_link = "download" in save_link
		, click = function(node) {
			var event = new MouseEvent("click");
			node.dispatchEvent(event);
		}
		, is_safari = /constructor/i.test(view.HTMLElement) || view.safari
		, is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
		, throw_outside = function(ex) {
			(view.setImmediate || view.setTimeout)(function() {
				throw ex;
			}, 0);
		}
		, force_saveable_type = "application/octet-stream"
		// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
		, arbitrary_revoke_timeout = 1000 * 40 // in ms
		, revoke = function(file) {
			var revoker = function() {
				if (typeof file === "string") { // file is an object URL
					get_URL().revokeObjectURL(file);
				} else { // file is a File
					file.remove();
				}
			};
			setTimeout(revoker, arbitrary_revoke_timeout);
		}
		, dispatch = function(filesaver, event_types, event) {
			event_types = [].concat(event_types);
			var i = event_types.length;
			while (i--) {
				var listener = filesaver["on" + event_types[i]];
				if (typeof listener === "function") {
					try {
						listener.call(filesaver, event || filesaver);
					} catch (ex) {
						throw_outside(ex);
					}
				}
			}
		}
		, auto_bom = function(blob) {
			// 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 (/^\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;
		}
		, FileSaver = function(blob, name, no_auto_bom) {
			if (!no_auto_bom) {
				blob = auto_bom(blob);
			}
			// First try a.download, then web filesystem, then object URLs
			var
				  filesaver = this
				, type = blob.type
				, force = type === force_saveable_type
				, object_url
				, dispatch_all = function() {
					dispatch(filesaver, "writestart progress write writeend".split(" "));
				}
				// on any filesys errors revert to saving with object URLs
				, fs_error = function() {
					if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
						// Safari doesn't allow downloading of blob urls
						var reader = new FileReader();
						reader.onloadend = function() {
							var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
							var popup = view.open(url, '_blank');
							if(!popup) view.location.href = url;
							url=undefined; // release reference before dispatching
							filesaver.readyState = filesaver.DONE;
							dispatch_all();
						};
						reader.readAsDataURL(blob);
						filesaver.readyState = filesaver.INIT;
						return;
					}
					// don't create more object URLs than needed
					if (!object_url) {
						object_url = get_URL().createObjectURL(blob);
					}
					if (force) {
						view.location.href = object_url;
					} else {
						var opened = view.open(object_url, "_blank");
						if (!opened) {
							// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
							view.location.href = object_url;
						}
					}
					filesaver.readyState = filesaver.DONE;
					dispatch_all();
					revoke(object_url);
				}
			;
			filesaver.readyState = filesaver.INIT;

			if (can_use_save_link) {
				object_url = get_URL().createObjectURL(blob);
				setTimeout(function() {
					save_link.href = object_url;
					save_link.download = name;
					click(save_link);
					dispatch_all();
					revoke(object_url);
					filesaver.readyState = filesaver.DONE;
				});
				return;
			}

			fs_error();
		}
		, FS_proto = FileSaver.prototype
		, saveAs = function(blob, name, no_auto_bom) {
			return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
		}
	;
	// IE 10+ (native saveAs)
	if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
		return function(blob, name, no_auto_bom) {
			name = name || blob.name || "download";

			if (!no_auto_bom) {
				blob = auto_bom(blob);
			}
			return navigator.msSaveOrOpenBlob(blob, name);
		};
	}

	FS_proto.abort = function(){};
	FS_proto.readyState = FS_proto.INIT = 0;
	FS_proto.WRITING = 1;
	FS_proto.DONE = 2;

	FS_proto.error =
	FS_proto.onwritestart =
	FS_proto.onprogress =
	FS_proto.onwrite =
	FS_proto.onabort =
	FS_proto.onerror =
	FS_proto.onwriteend =
		null;

	return saveAs;
}(
	   typeof self !== "undefined" && self
	|| typeof window !== "undefined" && window
	|| this.content
));
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window

if (typeof module !== "undefined" && module.exports) {
  module.exports.saveAs = saveAs;
} else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) {
  define("FileSaver.js", function() {
    return saveAs;
  });
}


function rand (min, max) {
  //  discuss at: http://locutus.io/php/rand/
  // original by: Leslie Hoare
  // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)
  //      note 1: See the commented out code below for a version which
  //      note 1: will work with our experimental (though probably unnecessary)
  //      note 1: srand() function)
  //   example 1: rand(1, 1)
  //   returns 1: 1

  var argc = arguments.length
  if (argc === 0) {
    min = 0
    max = 2147483647
  } else if (argc === 1) {
    throw new Error('Warning: rand() expects exactly 2 parameters, 1 given')
  }
  return Math.floor(Math.random() * (max - min + 1)) + min
}

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 = false

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 = '450px'

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