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 Image Boards // @namespace https://github.com/hybridsong/imageboards // @version 2018.12.12.2017-9-19 // @author hybridsong // @license GPL-3.0-only // @icon https://secure.gravatar.com/avatar/187e65a2ca69335f7483a4f19812050a // @match *://danbooru.donmai.us/* // @match *://www.pixiv.net/* // @run-at document-idle // ==/UserScript== /* * Enhanced Image Boards * \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; } }); (function() { 'use strict'; var globals = typeof global === 'undefined' ? self : global; if (typeof globals.require === 'function') return; var modules = {}; var cache = {}; var aliases = {}; var has = {}.hasOwnProperty; var expRe = /^\.\.?(\/|$)/; var expand = function(root, name) { var results = [], part; var parts = (expRe.test(name) ? root + '/' + name : name).split('/'); for (var i = 0, length = parts.length; i < length; i++) { part = parts[i]; if (part === '..') { results.pop(); } else if (part !== '.' && part !== '') { results.push(part); } } return results.join('/'); }; var dirname = function(path) { return path.split('/').slice(0, -1).join('/'); }; var localRequire = function(path) { return function expanded(name) { var absolute = expand(dirname(path), name); return globals.require(absolute, path); }; }; var initModule = function(name, definition) { var hot = hmr && hmr.createHot(name); var module = {id: name, exports: {}, hot: hot}; cache[name] = module; definition(module.exports, localRequire(name), module); return module.exports; }; var expandAlias = function(name) { return aliases[name] ? expandAlias(aliases[name]) : name; }; var _resolve = function(name, dep) { return expandAlias(expand(dirname(name), dep)); }; var require = function(name, loaderPath) { if (loaderPath == null) loaderPath = '/'; var path = expandAlias(name); if (has.call(cache, path)) return cache[path].exports; if (has.call(modules, path)) return initModule(path, modules[path]); throw new Error("Cannot find module '" + name + "' from '" + loaderPath + "'"); }; require.alias = function(from, to) { aliases[to] = from; }; var extRe = /\.[^.\/]+$/; var indexRe = /\/index(\.[^\/]+)?$/; var addExtensions = function(bundle) { if (extRe.test(bundle)) { var alias = bundle.replace(extRe, ''); if (!has.call(aliases, alias) || aliases[alias].replace(extRe, '') === alias + '/index') { aliases[alias] = bundle; } } if (indexRe.test(bundle)) { var iAlias = bundle.replace(indexRe, ''); if (!has.call(aliases, iAlias)) { aliases[iAlias] = bundle; } } }; require.register = require.define = function(bundle, fn) { if (bundle && typeof bundle === 'object') { for (var key in bundle) { if (has.call(bundle, key)) { require.register(key, bundle[key]); } } } else { modules[bundle] = fn; delete cache[bundle]; addExtensions(bundle); } }; require.list = function() { var list = []; for (var item in modules) { if (has.call(modules, item)) { list.push(item); } } return list; }; var hmr = globals._hmr && new globals._hmr(_resolve, require, modules, cache); require._cache = cache; require.hmr = hmr && hmr.wrap; require.brunch = true; globals.require = require; })(); 'use strict'; (function () { 'use strict'; var BASE_64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz0123456789+/'; // Namespace var Rand = { // Function to use for random values, available to overwrite randomGenerator: Math.random, // This is seen as a random number dependent on the distribution seed: function seed() { var distribution = arguments.length <= 0 || arguments[0] === undefined ? 'constant' : arguments[0]; switch (distribution) { // Constant distribution (random) -> .:.:..:..: case 'constant':case 'random': return Rand.randomGenerator(); // Stable distribution -> ..:|:.. // Sharp edge in the middle case 'double-exponential':case 'stable': return (Rand.randomGenerator() - 0.5) * Rand.randomGenerator() + 0.5; // Like the stable distribution but // only in one direction (outdying) -> |:.... case 'exponential': return Rand.randomGenerator() * Rand.randomGenerator(); // Linear curve decresing case 'linear-decrease': return 1 - Math.sqrt(Rand.randomGenerator()); // Linear curve incresing case 'linear-increase': return Math.sqrt(Rand.randomGenerator()); // Birnbaum-Saunders survival function ish case 'survival': var r = Rand.randomGenerator(); return Math.pow(r, 2); case 'normal': var u = Rand.randomGenerator(); var v = Rand.randomGenerator(); return Math.sqrt(-2 * Math.log(u)) * (Math.cos(2 * Math.PI * v) / 8) + 0.5; } }, // Check that value is between min and max sanitize: function sanitize(value) { var min = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; var max = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2]; if (value < min) { return min; } if (value > max) { return max; } return value; }, // Return a random value between min and max random: function random() { var min = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0]; var max = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1]; var distribution = arguments.length <= 2 || arguments[2] === undefined ? 'constant' : arguments[2]; var interval = max - min; // There are some different distributions var seed = Rand.seed(distribution); return Rand.sanitize(min + seed * interval, min, max); }, // Return a boolean depending on the probability // returns false with probability bool: function bool() { var probability = arguments.length <= 0 || arguments[0] === undefined ? 0.5 : arguments[0]; return Rand.randomGenerator() >= probability; }, // Return random int between min and max // Just an alias for parseInt(random()) int: function int() { return parseInt(Rand.random.apply(Rand, arguments)); }, // A hash string values 0-f hash: function hash() { var length = arguments.length <= 0 || arguments[0] === undefined ? 40 : arguments[0]; var str = ''; while (str.length < length) { str += Rand.int(0, 16).toString(16); } return str.substr(0, length); }, // Return a base36 string base36: function base36() { var length = arguments.length <= 0 || arguments[0] === undefined ? 40 : arguments[0]; var str = ''; while (str.length < length) { str += Rand.int(0, 36 * length).toString(36); } return str.substr(0, length); }, // Return a base64 string base64: function base64() { var length = arguments.length <= 0 || arguments[0] === undefined ? 40 : arguments[0]; var str = ''; var b = BASE_64_CHARS; while (str.length < length) { str += b[Rand.int(0, 64)]; } return str.substr(0, length); }, // Return random character char: function char() { return Rand.int(10, 36).toString(36); }, // Return random hexadecimal color color: function color() { return '#' + Rand.hash(6); }, // Return random rgba color rgba: function rgba() { var int = Rand.int; return 'rgba(' + int(0, 255) + ', ' + int(0, 255) + ', ' + int(0, 255) + (', ' + Rand.randomGenerator() + ')'); }, // Return random date between min and max (1 year) date: function date() { var min = arguments.length <= 0 || arguments[0] === undefined ? new Date() : arguments[0]; var max = arguments[1]; // Check if min is object or not if (typeof min === 'number') { min = new Date(min); } if (!max) { // One year max = new Date(); max.setFullYear(min.getFullYear() + 1); } return new Date(Rand.int(min.getTime(), max.getTime())); }, // Return random item from array choose: function choose() { var arr = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; var distribution = arguments.length <= 1 || arguments[1] === undefined ? 'constant' : arguments[1]; if (arr.length > 0) { return arr[Rand.int(0, arr.length, distribution)]; } return null; }, // Generate password password: function password() { var length = arguments.length <= 0 || arguments[0] === undefined ? 8 : arguments[0]; var numbers = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1]; var str = ''; while (str.length < length) { if (str.length % Math.floor(length / numbers) === 0) { str += Rand.int(0, 9); continue; } if (Rand.bool(0.3)) { str += Rand.char().toUpperCase(); } else { str += Rand.char(); } } return str; } }; // Export /* eslint no-undef: [0] */ if (typeof module !== "undefined" && module !== null && module.exports != null) { module.exports = Rand; } else { window.Rand = Rand; } })(); require.register("common.js", function(exports, require, module){ exports.clean_array = function (arr) { let tmp = [] for (let idx in arr) { let val = arr[idx] if (val != undefined) tmp.push(val) } return tmp } exports.index_array = function (arr) { let tmp = {} for (let idx = 0; idx < arr.length; idx++) { let val = arr[idx] if (!tmp[val]) tmp[val] = [] tmp[val].push(idx) } return tmp } exports.get_index = function (object, key, idx) { if (object[key] && object[key][idx] !== undefined) { return object[key][idx] } } exports.rename = function (basename) { let name = basename[0].split('_'), ext = basename[1], idx = index_array(name) if (debug_rename) console.log(name, idx, ext) name[get_index(idx, '', 2)] = ['danbooru', postid].join(' ') name[get_index(idx, 'original', 0)] = undefined name[get_index(idx, '', 2) + 1] = undefined if (debug_rename) console.log(name) return clean_array(name).join(' ').replace(/ +/, '') + '.' + ext } exports.get_name = function (doc) { const tags = (list, { unwanted, prepend = '', excludes } = {}) => { const tags = [], isneeded = str => { for (let i in unwanted) if (str == unwanted[i]) return false return true } for (let i=0; i<list.length; i++) { let tag = list[i].textContent for (let i in excludes) tag = tag.replace(excludes[i], '') tag = tag .trim() .replace(/,/g, '+') .replace(/\//g, '-') .replace(/ /g, '_') if (isneeded(tag) && tag) tags.push(prepend + tag) } return tags.length ? tags.join(' ') : '0' } const brackets = /\(.+\)/, name = [ tags(doc.querySelectorAll('#tag-list .category-4 .search-tag'), { excludes: [brackets] }), // characters tags(doc.querySelectorAll('#tag-list .category-3 .search-tag'), { unwanted: ['original'] }), // copyrights tags(doc.querySelectorAll('#tag-list .category-1 .search-tag')), // artists ['danbooru', doc.querySelector('meta[name="post-id"]').content].join(' ') ].join(', ') if (debug_get_name) console.log(name) return name } }); 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), debug_rename = 0, debug_get_name = 0 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); } function insert (target, el, after) { let insert = (el) => { if (el) { if (after) { if (target.nextElementSibling) target.parentElement.insertBefore(el, target.nextElementSibling) else target.parentElement.append(el) } else target.parentElement.insertBefore(el, target) } } if (Array.isArray(el)) for (let i=0; i<el.length; ++i) insert(el[i]) else insert(el) } function check (fn, success) { let t = setInterval(() => { if (fn()) { clearInterval(t) success() } }, 2000) } const common = require('common.js') for (let name in common) window[name] = common[name] // Danbooru Sector function danbooru () { function part_posts_id () { // posts id let options = document.getElementById("post-options").getElementsByTagName("ul")[0], image = document.getElementById("image-container").querySelector("#image"), image_src = image.src, original_src = null, postid = document.getElementsByName("post-id")[0].content let save = function (original, el) { let url = original ? original_src : image_src, action = el.parentElement.childNodes[1], improved_name = get_name(document) + '.' + extname(url) var xhr = new XMLHttpRequest() xhr.open('GET', url, true) xhr.responseType = 'arraybuffer' xhr.onprogress = function (evt) { let percentComplete = (evt.loaded / evt.total) * 100 action.innerText = `${Math.floor(percentComplete)}%` } xhr.onload = function () { if (this.status === 200) { var blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') }) var URL = window.URL || window.webkitURL var downloadUrl = URL.createObjectURL(blob) // use HTML5 a[download] attribute to specify filename var a = document.createElement("a") a.href = downloadUrl a.download = improved_name document.body.appendChild(a) a.click() action.innerText = null setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup } } xhr.send() action.onclick = function () { xhr.abort() action.innerText = null } } let info = document.querySelector('#post-information ul li:last-child') let download = document.createElement('li'), download_a = document.createElement('a'), download_action = document.createElement('a') download_action.style.marginLeft = '5px' download_a.append("Download") download_a.href = "javascript:" download_a.onclick = function () { save(false, this) } download.append(download_a) download.append(download_action) let download_original = document.createElement('li'), download_original_a = document.createElement('a'), download_original_action = document.createElement('a') download_original_action.style.marginLeft = '5px' download_original_a.append("Download-Original") download_original_a.href = "javascript:" download_original_a.onclick = function () { save(true, this) } download_original.append(download_original_a) download_original.append(download_original_action) insert(info, download_original, true) insert(info, download, true) ;(() => { let list = options.getElementsByTagName("li") setTimeout(() => { for (idx in list) { if ('View original' === list[idx].innerText) original_src = list[idx].getElementsByTagName('a')[0].href } if (!original_src) { download_original.remove() } let image = document.getElementById("image-container").querySelector("#image") image.onmouseup = function () { window.open(image_src) } }, 0) })() } // part posts id if (document.location.pathname.match(/posts\/\d+/)) part_posts_id() function part_posts () { let sidebar = document.querySelector('#a-index #sidebar'), sidebar_style = sidebar.style sidebar_style.cssFloat = 'right' document.querySelector('#a-index #content').style.marginLeft = '0px' let show = document.createElement('div') show.innerHTML = '<h1>Sidebar</h1>' sidebar.append(show) let sections = document.querySelectorAll('#a-index #sidebar section') for (let i=0; i<sections.length; i++) sections[i].style.display = 'none' sidebar.onmouseover = () => { for (let i=0; i<sections.length; i++) sections[i].style.display = 'block' } sidebar.onmouseout = () => { for (let i=0; i<sections.length; i++) sections[i].style.display = 'none' } let more = document.querySelector('.paginator menu .more'), num = 0 if (more) { num = more.nextElementSibling.querySelector('a').innerText if (num == '>>') num = '...' } else { num = document.querySelector('.paginator menu').lastChild.previousElementSibling.querySelector('a').innerText } let floatbar = document.createElement('div') floatbar.style.position = 'fixed' floatbar.style.top = '50%' floatbar.style.listStyleType = 'none' floatbar.style.color = '#111' let li = document.createElement('li'), a = document.createElement('a') li.append(a) a.append('Top') a.onclick = () => { window.scroll(0,0) } floatbar.append(li) floatbar.append("Pgc " + num) document.body.append(floatbar) } // part posts if (document.location.pathname == '/posts') part_posts() function part_artists () { // Find Artists let tr = document.getElementsByTagName('table')[0].querySelector('tbody').querySelectorAll('tr') let add_links = () => { for (let i=0; i<tr.length; i++) { let td = tr[i].querySelector('td') let tag = td.querySelector('a').innerText let list = document.createElement('a') list.append('(posts)') list.href = 'https://danbooru.donmai.us/posts?tags=' + tag td.append(list) } } if (doc_url.searchParams.get('listposts') == '✓') { if (tr.length == 1) { let tag = tr[0].querySelector('td').querySelector('a').innerText window.location.replace('https://danbooru.donmai.us/posts?tags=' + tag) } else if (tr.length > 1) add_links () } else if (tr.length > 0) add_links() } // part artists if (document.location.pathname.match(/artists$/)) part_artists() } // danbooru // Danbooru main() if (window.location.host == 'danbooru.donmai.us') danbooru() // End of Danbooru Sector // Pixiv Sector var pixiv_li function pixiv () { let check_timer doc_url = new URL(document.location.href) // clean if (pixiv_li) pixiv_li.remove() function danbooru_posts () { // Test index used // var i=0; for (let a of document.querySelectorAll("a")) { if (a.href.match(/\/member.php\?id\=/)) { console.log (i,a)} ++i} // Condition Artist. let skip = 0, all = document.querySelectorAll("a"), artist for (let a of all) { if (a.href.match(/\/member.php\?id\=/)) ++skip // Use a skip(number) to select a target link element in all links. if ( document.location.pathname == '/member_illust.php' && skip == 2 ) { artist = a // We just need a username to modify. break } if ( document.location.pathname == '/member.php' && skip == 1 ) { artist = a.parentNode.nextSibling.firstElementChild.firstElementChild break } } if (!artist) return // Passed! Cancel timer. clearInterval(check_timer) let username = artist.innerText let li = document.createElement('li'), a = document.createElement('a') li.append(a) a.append('Danbooru Posts') a.target = '_blank' insert(artist, li, true) pixiv_li = li // Member.php needed. a.href = 'https://danbooru.donmai.us/artists' + encodeURI("?commit=Search&search[is_active]=true&search[any_name_matches]=" + username + "&utf8=✓&listposts=✓") // Using Pixiv Url to search on Member_illust.php. if (document.location.pathname == '/member_illust.php' && doc_url.searchParams.get('illust_id')) { // Change URL. a.href = 'https://danbooru.donmai.us/artists' + encodeURI("?commit=Search&search[is_active]=true&search[url_matches]=") + encodeURIComponent(document.location.href) + encodeURI("&utf8=✓&listposts=✓") } } // part danbooru_posts if (document.location.pathname.match(/member_illust.php|member.php/)) // A check used to test some condition of each second before doing something. check_timer = setInterval(danbooru_posts, 1000) } // pixiv // Pixiv main() if (window.location.host == 'www.pixiv.net') { let current_page = window.location.href // Back, Forward window.onpopstate = history.onpushstate = pixiv // On url changed window.onclick = () => { setTimeout(() => { if (current_page != window.location.href) { current_page = window.location.href pixiv() } }, 1000) } // Idle pixiv() } // End of Pixiv Sector