// ==UserScript== // @name Patchouli // @name:ja パチュリー // @name:zh-CN 帕秋莉 // @name:zh-TW 帕秋莉 // @namespace https://github.com/FlandreDaisuki // @description An image searching/browsing tool on Pixiv // @description:ja Pixiv 検索機能強化 // @description:zh-CN Pixiv 搜寻/浏览 工具 // @description:zh-TW Pixiv 搜尋/瀏覽 工具 // @include *://www.pixiv.net/* // @require https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/vue-i18n/7.4.2/vue-i18n.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/axios/0.17.1/axios.min.js // @icon http://i.imgur.com/VwoYc5w.png // @noframes // @author FlandreDaisuki // @license The MIT License (MIT) Copyright (c) 2016-2018 FlandreDaisuki // @compatible firefox >=52 // @compatible chrome >=55 // @version 4.0.3 // @grant none // @downloadURL none // ==/UserScript== (function (Vue,Vuex,VueI18n) { 'use strict'; function __$styleInject( css ) { if(!css) return ; if(typeof(window) == 'undefined') return ; let style = document.createElement('style'); style.innerHTML = css; document.head.appendChild(style); return css; } Vue = Vue && Vue.hasOwnProperty('default') ? Vue['default'] : Vue; Vuex = Vuex && Vuex.hasOwnProperty('default') ? Vuex['default'] : Vuex; VueI18n = VueI18n && VueI18n.hasOwnProperty('default') ? VueI18n['default'] : VueI18n; __$styleInject("#koakuma[data-v-430ffdfb] {\n display: flex;\n justify-content: center;\n position: sticky;\n top: 0;\n z-index: 3;\n background-color: #e77;\n box-shadow: 0 1px 3px #000000;\n padding: 4px;\n color: #fff;\n font-size: 16px;\n}\n#koakuma > div[data-v-430ffdfb] {\n margin: 0 10px;\n}\n.bookmark-count[data-v-430ffdfb] {\n display: inline-flex !important;\n align-items: center;\n}\n#koakuma-bookmark-sort-input[data-v-430ffdfb] {\n -moz-appearance: textfield;\n border: none;\n background-color: transparent;\n padding: 0px;\n color: inherit;\n font-size: 16px;\n display: inline-block;\n cursor: ns-resize;\n text-align: center;\n max-width: 50px;\n}\n#koakuma-bookmark-sort-input[data-v-430ffdfb]::-webkit-inner-spin-button,\n#koakuma-bookmark-sort-input[data-v-430ffdfb]::-webkit-outer-spin-button {\n /* https://css-tricks.com/numeric-inputs-a-comparison-of-browser-defaults/ */\n -webkit-appearance: none;\n margin: 0;\n}\n.tags-filter[data-v-430ffdfb] {\n min-width: 300px;\n}\n.main-button[data-v-430ffdfb] {\n border: none;\n padding: 2px 14px;\n border-radius: 3px;\n font-size: 16px;\n}\n.main-button[data-v-430ffdfb]:enabled:hover {\n box-shadow: 1px 1px;\n}\n.main-button[data-v-430ffdfb]:enabled:active {\n box-shadow: 1px 1px inset;\n}\n.go .main-button[data-v-430ffdfb] {\n background-color: #64ffda;\n}\n.paused .main-button[data-v-430ffdfb] {\n background-color: #ffd600;\n}\n.end .main-button[data-v-430ffdfb] {\n background-color: #455a64;\n color: #fff;\n opacity: 0.9;\n}\n#patchouli[data-v-39c8e0ab] {\n display: flex;\n flex-flow: wrap;\n justify-content: space-around;\n}\n.image-item[data-v-f6c8e106] {\n display: flex;\n justify-content: center;\n margin: 0 0 30px 0;\n padding: 10px;\n height: auto;\n width: 200px;\n}\n.image-item-inner[data-v-f6c8e106] {\n display: flex;\n flex-flow: column;\n max-width: 100%;\n max-height: 300px;\n}\n.image-item-image[data-v-3c187ee4] {\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n}\n.image-flexbox[data-v-3c187ee4] {\n display: -webkit-box;\n display: -webkit-flex;\n display: flex;\n -webkit-box-orient: vertical;\n -webkit-box-direction: normal;\n -webkit-flex-flow: column;\n flex-flow: column;\n -webkit-box-pack: center;\n -webkit-justify-content: center;\n justify-content: center;\n -webkit-box-align: center;\n -webkit-align-items: center;\n align-items: center;\n z-index: 0;\n border: 1px solid rgba(0, 0, 0, 0.04);\n position: relative;\n height: 200px;\n}\n.top-right-slot[data-v-3c187ee4] {\n -webkit-box-flex: 0;\n -webkit-flex: none;\n flex: none;\n display: -webkit-box;\n display: -webkit-flex;\n display: flex;\n -webkit-box-align: center;\n -webkit-align-items: center;\n align-items: center;\n z-index: 1;\n box-sizing: border-box;\n margin: 0 0 -24px auto;\n padding: 6px;\n height: 24px;\n background: #000;\n background: rgba(0, 0, 0, 0.4);\n border-radius: 0 0 0 4px;\n color: #fff;\n font-size: 12px;\n line-height: 1;\n font-weight: 700;\n}\n.multiple-icon[data-v-3c187ee4] {\n display: inline-block;\n margin-right: 4px;\n width: 10px;\n height: 10px;\n background: url(https://source.pixiv.net/www/js/bundle/3b9b0b9e331e13c46aeadaea83132203.svg);\n}\n.ugoira-icon[data-v-3c187ee4] {\n position: absolute;\n -webkit-box-flex: 0;\n -webkit-flex: none;\n flex: none;\n width: 40px;\n height: 40px;\n background: url(https://source.pixiv.net/www/js/bundle/f608d897f389e8161e230b817068526d.svg)\n 50% no-repeat;\n top: 50%;\n left: 50%;\n margin: -20px 0 0 -20px;\n}\nimg[data-v-3c187ee4] {\n max-height: 100%;\n max-width: 100%;\n}\n._one-click-bookmark[data-v-3c187ee4] {\n right: 0;\n width: 24px;\n height: 24px;\n line-height: 24px;\n z-index: 2;\n text-align: center;\n cursor: pointer;\n background: url(https://source.pixiv.net/www/images/bookmark-heart-off.svg)\n center transparent;\n background-repeat: no-repeat;\n background-size: cover;\n opacity: 0.8;\n filter: alpha(opacity=80);\n transition: opacity 0.2s ease-in-out;\n}\n._one-click-bookmark.on[data-v-3c187ee4] {\n background-image: url(https://source.pixiv.net/www/images/bookmark-heart-on.svg);\n}\n.bookmark-input-container[data-v-3c187ee4] {\n position: absolute;\n left: 0;\n top: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 6px;\n border-radius: 0 0 4px 0;\n}\n.image-item-title[data-v-d20319ea] {\n max-width: 100%;\n margin: 8px auto;\n text-align: center;\n color: #333;\n font-size: 12px;\n line-height: 1;\n}\n.title-text[data-v-d20319ea] {\n margin: 4px 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 700;\n}\n.user-info[data-v-d20319ea] {\n display: inline-flex;\n align-items: center;\n}\n.user-link[data-v-d20319ea] {\n font-size: 12px;\n display: inline-flex;\n align-items: center;\n}\n.user-img[data-v-d20319ea] {\n width: 20px;\n height: 20px;\n display: inline-block;\n background-size: cover;\n border-radius: 50%;\n margin-right: 4px;\n}\n.follow-icon[data-v-d20319ea] {\n display: inline-block;\n margin-left: 4px;\n width: 14px;\n height: 14px;\n background-color: dodgerblue;\n -webkit-mask-image: url(https://cdnjs.cloudflare.com/ajax/libs/simple-icons/3.0.1/rss.svg);\n mask-image: url(https://cdnjs.cloudflare.com/ajax/libs/simple-icons/3.0.1/rss.svg);\n}"); __$styleInject(".ω.↔,\n.ω.↔ .layout-a,\n.ω.↔ .layout-body {\n width: initial !important;\n}\n.ω.↔ .layout-a {\n display: flex;\n flex-direction: row-reverse;\n}\n.ω.↔ .layout-column-2{\n flex: 1;\n margin-left: 20px;\n}\n.ω.↔ .layout-body,\n.ω.↔ .layout-a {\n margin: 10px 20px;\n}\n"); function $(selector) { return document.querySelector(selector); } function $$(selector) { return [...document.querySelectorAll(selector)]; } function $$find(doc, selector) { return [...doc.querySelectorAll(selector)]; } function $el(tag, attr = {}, cb = () => {}) { const el = document.createElement(tag); Object.assign(el, attr); cb(el); return el; } function $error(...args) { console.error.apply(console, args); } (() => { Math.clamp = (val, min, max) => Math.min(Math.max(min, val), max); Number.toInt = (s) => (isNaN(~~s) ? 0 : ~~s); (function(arr) { arr.forEach(function(item) { if (item.hasOwnProperty('after')) { return; } Object.defineProperty(item, 'after', { configurable: true, enumerable: true, writable: true, value: function after() { const argArr = Array.prototype.slice.call(arguments); const docFrag = document.createDocumentFragment(); argArr.forEach(function(argItem) { const isNode = argItem instanceof Node; docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem))); }); this.parentNode.insertBefore(docFrag, this.nextSibling); } }); }); })([Element.prototype, CharacterData.prototype, DocumentType.prototype]); })(); class Pixiv { constructor() { this.tt = $('input[name="tt"]').value; } async fetch(url) { try { if (url) { const res = await axios.get(url); if (res.statusText !== 'OK') { throw new Error(res.statusText); } else { return res.data; } } else { $error('Pixiv#fetch has no url'); } } catch (error) { $error('Pixiv#fetch: error:', error); } } async getLegacyPageHTMLIllustIds(url, { needBookmarkId } = { needBookmarkId: false }) { try { const html = await this.fetch(url); const nextTag = html.match(/class="next"[^/]*/); let nextUrl = ''; if (nextTag) { const nextHref = nextTag[0].match(/href="([^"]+)"/); if (nextHref) { const query = nextHref[1].replace(/&/g, '&'); if (query) { nextUrl = `${location.pathname}${query}`; } } } const iidHTMLs = html.match(/;illust_id=\d+"\s*class="work/g) || []; const illustIds = []; for (const dataid of iidHTMLs) { const iid = dataid.replace(/\D+(\d+).*/, '$1'); if (!illustIds.includes(iid) && iid !== '0') { illustIds.push(iid); } } const ret = { nextUrl, illustIds }; if (needBookmarkId) { ret.bookmarkIds = {}; const bimHTMLs = html.match(/name="book_id[^;]+;illust_id=\d+/g) || []; for (const bim of bimHTMLs) { const [illustId, bookmarkId] = bim.replace(/\D+(\d+)\D+(\d+)/, '$2 $1').split(' '); if (illustIds.includes(illustId)) { ret.bookmarkIds[illustId] = { illustId, bookmarkId }; } } } return ret; } catch (error) { $error('Pixiv#getLegacyPageHTMLIllustIds: error:', error); } } async getPageHTMLIllustIds(url) { try { const html = await this.fetch(url); const nextTag = html.match(/class="next"[^/]*/); let nextUrl = ''; if (nextTag) { const nextHref = nextTag[0].match(/href="([^"]+)"/); if (nextHref) { const query = nextHref[1].replace(/&/g, '&'); if (query) { nextUrl = `${location.pathname}${query}`; } } } const iidHTMLs = html.match(/illustId":"(\d+)"/g) || []; const illustIds = []; for (const dataid of iidHTMLs) { const iid = dataid.replace(/\D+(\d+).*/, '$1'); if (!illustIds.includes(iid) && iid !== '0') { illustIds.push(iid); } } const ret = { nextUrl, illustIds }; return ret; } catch (error) { $error('Pixiv#getPageHTMLIllustIds: error:', error); } } async getBookmarkHTMLDetails(illustIds) { const bookmarkHTMLDetails = illustIds.map(id => this.getBookmarkHTMLDetail(id)); const bookmarkDetails = await Promise.all(bookmarkHTMLDetails); const detail = {}; for (const d of bookmarkDetails) { detail[d.illustId] = d; } return detail; } async getBookmarkHTMLDetail(illustId) { const url = `/bookmark_detail.php?illust_id=${illustId}`; try { const html = await this.fetch(url); const bkMatches = html.match(/<\/i>(\d+)/); const bookmarkCount = bkMatches ? parseInt(bkMatches[1]) : 0; const tagsListHTML = html.match(/