您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
An image searching/browsing tool on Pixiv
当前为
// ==UserScript== // @name Patchouli // @description An image searching/browsing tool on Pixiv // @namespace https://github.com/FlandreDaisuki // @include http://www.pixiv.net/* // @require https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.25/vue.js // @require https://cdnjs.cloudflare.com/ajax/libs/URI.js/1.18.1/URI.min.js // @version 2017.02.06 // @author FlandreDaisuki // @grant none // @noframes // ==/UserScript== /* jshint esnext: true */ function fetchWithcookie(url) { return fetch(url, {credentials: 'same-origin'}) .then(response => response.text()) .catch(err => { console.error(err); }); } function getBookmarkCountAndTags(illust_id) { const url = `http://www.pixiv.net/bookmark_detail.php?illust_id=${illust_id}`; return fetchWithcookie(url) .then(html => parseToDOM(html)) .then(doc => $(doc)) .then($doc => { let m = $doc.find('a.bookmark-count').text(); const bookmark_count = m ? parseInt(m) : 0; const tags = Array.from($doc.find('ul.tags:first a')).map(x => x.innerText); return { bookmark_count, illust_id, tags, }; }) .catch(err => { console.error(err); }); } function getBatch(url) { return fetchWithcookie(url) .then(html => parseToDOM(html)) .then(doc => $(doc)) .then($doc => { removeAnnoyance($doc); const next = $doc.find('.next a').attr('href'); const nextLink = (next) ? new URI(BASE.baseURI).query(next).toString() : null; const illust_ids = $doc .find('li.image-item > a.work') .toArray() .map(x => URI.parseQuery($(x).attr('href')).illust_id); return { nextLink, illust_ids, }; }) .catch(err => { console.error(err); }); } /** * return object which key is illust_id */ function getIllustsDetails(illust_ids) { const api = `http://www.pixiv.net/rpc/index.php?mode=get_illust_detail_by_ids&illust_ids=${illust_ids.join(',')}&tt=${BASE.tt}`; return fetchWithcookie(api).then(json => JSON.parse(json).body).catch(err => { console.error(err); }); } /** * return an array */ function getUsersDetails(user_ids) { const api = `http://www.pixiv.net/rpc/get_profile.php?user_ids=${user_ids.join(',')}&tt=${BASE.tt}`; return fetchWithcookie(api).then(json => JSON.parse(json).body).catch(err => { console.error(err); }); } function parseDataFromBatch(batch) { const illust_d = batch.illust_d; const user_d = batch.user_d; const bookmark_d = batch.bookmark_d; return batch.illust_ids .filter(x => x) .map(x => { const iinfo = illust_d[x]; const uinfo = user_d[iinfo.user_id]; const binfo = bookmark_d[x]; const is_ugoira = iinfo.illust_type === '2'; const is_manga = iinfo.illust_type === '1'; const src150 = (is_ugoira) ? iinfo.url.big.replace(/([^-]+)(?:-original)([^_]+)(?:[^\.]+)(.+)/,'$1-inf$2_s$3') : iinfo.url.m.replace(/600x600/,'150x150'); return { is_ugoira, is_manga, src150, srcbig: iinfo.url.big, is_multiple: iinfo.is_multiple, illust_id: iinfo.illust_id, illust_title: iinfo.illust_title, user_id: uinfo.user_id, user_name: uinfo.user_name, is_follow: uinfo.is_follow, tags: binfo.tags, bookmark_count: binfo.bookmark_count, }; }); } function parseToDOM(html) { return (new DOMParser()).parseFromString(html, 'text/html'); } function removeAnnoyance($doc = $(document)) { [ 'iframe', //Ad '.ad', '.ads_area', '.ad-footer', '.ads_anchor', '.ads-top-info', '.comic-hot-works', '.user-ad-container', '.ads_area_no_margin', //Premium '.ad-printservice', '.bookmark-ranges', '.require-premium', '.showcase-reminder', '.sample-user-search', '.popular-introduction', ].forEach((e) => { $doc.find(e).remove(); }); } const BASE = (() => { const bu = new URI(document.baseURI); const pn = bu.pathname(); const ss = URI.parseQuery(bu.query()); const baseURI = bu.toString(); const tt = $('input[name="tt"]').val(); const container = $('li.image-item').parent()[0]; const $fullwidthElement = $('#wrapper div:first'); let supported = true; let li_type = 'search'; /** li_type - the DOM type to show li.image-item * * 'search'(default) : illust_150 + illust_title + user_name + bookmark_count * 'member-illust' : illust_150 + illust_title + + bookmark_count * 'mybookmark' : illust_150 + illust_title + user_name + bookmark_count + checkbox + editlink */ if (pn === '/member_illust.php' && ss.id) { li_type = 'member-illust'; } else if (pn === '/search.php') { } else if (pn === '/bookmark.php' && !ss.type) { if (!ss.id) { li_type = 'mybookmark'; } } else if (pn === '/bookmark_new_illust.php') { } else if (pn === '/new_illust.php') { } else if (pn === '/mypixiv_new_illust.php') { } else if (pn === '/new_illust_r18.php') { } else if (pn === '/bookmark_new_illust_r18.php') { } else { supported = false; } return { tt, baseURI, li_type, supported, container, $fullwidthElement, }; })(); Vue.filter('illust_href', function(illust_id) { return 'http://www.pixiv.net/member_illust.php?mode=medium&illust_id='+illust_id; }); Vue.component('img150', { props:['thd'], template: `<a class="work _work" :href="thd.illust_id | illust_href" :class="{ 'ugoku-illust': thd.is_ugoira, 'manga': thd.is_manga, 'multiple': thd.is_multiple}"> <div class="_layout-thumbnail"><img class="_thumbnail" :src="thd.src150"></img></div></a>`, }); Vue.component('illust-title', { props:['thd'], template: '<a :href="thd.illust_id | illust_href"><h1 class="title" :title="thd.illust_title">{{thd.illust_title}}</h1></a>', }); Vue.component('user-name', { props:['thd'], template: `<a class="user ui-profile-popup" :href="thd.user_id | user_href" :title="thd.user_name" :data-user_id="thd.user_id" :data-user_name="thd.user_name" :class="{'followed': thd.is_follow}"> {{thd.user_name}}</a>`, filters: { user_href: function(user_id) { return 'http://www.pixiv.net/member_illust.php?id='+user_id; }, }, }); Vue.component('count-list', { props:['thd'], template: `<ul class="count-list"> <li v-if="thd.bookmark_count > 0"> <a class="bookmark-count _ui-tooltip" :href="thd.illust_id | bookmark_detail_href" :data-tooltip="thd.bookmark_count | datatooltip"> <i class="_icon sprites-bookmark-badge"></i>{{thd.bookmark_count}}</a></li></ul>`, filters: { datatooltip: function(bookmark_count) { return bookmark_count+'件のブックマーク'; }, bookmark_detail_href: function(illust_id) { return 'http://www.pixiv.net/bookmark_detail.php?illust_id='+illust_id; }, }, }); Vue.component('edit-link', { props:['thd'], template: '<a :href="thd.illust_id | edit_href" class="edit-work"><span class="edit-bookmark edit_link">編集</span></a>', filters: { edit_href: function(illust_id) { return `http://www.pixiv.net/bookmark_add.php?type=illust&illust_id=${ illust_id }&tag=&rest=show&p=1`; }, }, }); Vue.component('imageitem-search', { props:['thdata'], template: `<li class="image-item"> <img150 :thd="thdata"></img150> <illust-title :thd="thdata"></illust-title> <user-name :thd="thdata"></user-name> <count-list :thd="thdata"></count-list> </li>`, }); Vue.component('imageitem-member-illust', { props:['thdata'], template: `<li class="image-item"> <img150 :thd="thdata"></img150> <illust-title :thd="thdata"></illust-title> <count-list :thd="thdata"></count-list> </li>`, }); Vue.component('imageitem-mybookmark', { props:['thdata'], template: `<li class="image-item"> <bookmark-checkbox v-if="false" :thd="thdata"></bookmark-checkbox> <img150 :thd="thdata"></img150> <illust-title :thd="thdata"></illust-title> <user-name :thd="thdata"></user-name> <count-list :thd="thdata"></count-list> <edit-link :thd="thdata"></edit-link> </li>`, }); function setupHTML() { $(` <div id="Koa-controller" class="tachi"> <div id="Koa-controller-child"> <div id="Koa-found"><span id="Koa-found-value">0</span></div> <div id="Koa-bookmark"> <span>★書籤</span>: <input id="Koa-bookmark-input" type="number" min="0" step="1" value="0" required/> </div> <div id="Koa-btn"> <input id="Koa-btn-input" type="button" value="找"/> </div> <div id="Koa-options"> 全<input id="Koa-fullwidth-input" type="checkbox"/> 排序<input id="Koa-ordering-input" type="checkbox"/> </div> </div> </div>`).appendTo('body'); $(` <style> #Koa-controller.tachi { background-color: #8F2019; position: fixed; bottom: 0px; left: 0px; margin: 10px 30px; padding: 15px 8px 0px; border-radius: 15px 15px 8px 8px; font-size: 16px; cursor: default; z-index: 10; } #Koa-controller::before { content: ''; position: absolute; width: 0; height: 0; border-style: solid; border-width: 30px 0 0 100px; border-color: transparent transparent transparent #000000; left: 0px; top: 0px; z-index: -1; transform-origin: 50% 50%; transform: translate(-45px,40px) skew(20deg, 0deg) rotate(-20deg); } #Koa-controller::after { border-width: 0 0 30px 100px; border-color: transparent transparent #000000 transparent; content: ''; position: absolute; width: 0; height: 0; border-style: solid; right: 0px; top: 0px; z-index: -1; transform-origin: 50% 50%; transform: translate(45px,40px) skew(-20deg, 0deg) rotate(20deg); } #Koa-controller.chibi { width: 60px; background-color: #8F2019; position: fixed; bottom: 0px; left: 0px; margin: 10px 30px; padding: 10px 4px 15px; border-radius: 50%; font-size: 16px; cursor: default; z-index: 10; color: white; } #Koa-controller.chibi::before { transform: translate(-45px,10px) skew(20deg, 0deg) rotate(-10deg) scale(0.4); } #Koa-controller.chibi::after { transform: translate(45px,10px) skew(-20deg, 0deg) rotate(10deg) scale(0.4); } #Koa-controller.tachi #Koa-controller-child { background-color: #FEE6CA; border-bottom: 30px solid black; border-radius: 10px 10px 20px 20px; } #Koa-controller.chibi #Koa-controller-child { background-color: #8F2019; width: 100%; height: 30px; border-radius: 50%; margin-top: -2px; } #Koa-controller.chibi #Koa-controller-child::after { content: " ' ' "; display: block; text-align: center; font-size: 32px; padding-right: 5px; transform: rotate(10deg); } #Koa-controller.chibi #Koa-controller-child > div { display: none; } #Koa-found, #Koa-btn { text-align: center; } #Koa-controller.tachi #Koa-btn { border-left: 15px solid white; border-right: 15px solid white; background-color: black; color: white; } #Koa-controller.tachi #Koa-options { border-left: 15px solid white; border-right: 15px solid white; background-color: black; color: white; } #Koa-bookmark > span { color: #0069b1; background-color: #cceeff; } input#Koa-bookmark-input::-webkit-inner-spin-button, input#Koa-bookmark-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } input#Koa-bookmark-input { -moz-appearance: textfield; } input#Koa-bookmark-input { border: none; background-color: transparent; padding: 0px; color: blue; font-size: 16px; display: inline-block; width: 50px; cursor: ns-resize; text-align: center; } input#Koa-bookmark-input:focus { cursor: initial; } #Koa-controller.tachi #Koa-btn-input { border: none; background-color: #FFFF00; height: 100%; margin: 0px auto; display: block; border-radius: 0% 0% 50% 50%; font-size: 22px; } #Koa-container { display: flex; flex-wrap: wrap; justify-content: space-around; margin-left: initial; } .fullwidth { width: initial; } .layout-a.fullwidth { display: flex; flex-direction: row-reverse; } .layout-a.fullwidth .layout-column-2{ flex: 1; margin-left: 20px; } .layout-body.fullwidth, .layout-a.fullwidth { margin: 10px 20px; } .followed.followed.followed { font-weight: bold; color: red; } </style>`).appendTo('head'); } function setupEvent() { const $KoaController = $('#Koa-controller'); const $KoaBookmarkInput = $('#Koa-bookmark-input'); const $KoaBtnInput = $('#Koa-btn-input'); const $KoaFullwidthInput = $('#Koa-fullwidth-input'); const $KoaOrderingInput = $('#Koa-ordering-input'); $KoaController .on('click', function() { if($(this).hasClass('chibi')){ $(this).removeClass('chibi'); $(this).addClass('tachi'); } }) .on('mouseleave', function() { $(this).addClass('chibi'); $(this).removeClass('tachi'); $KoaBookmarkInput.focusout(); }); $KoaBookmarkInput .on('wheel', function(event) { this.blur(); if(event.originalEvent.deltaY > 0) { this.stepDown(20); } else { this.stepUp(20); } MuQ.Koakuma.$data.limit = parseInt(this.value); return false; }) .on('focusout', function(event) { this.blur(); if(!this.validity.valid){ console.log(this.validationMessage); } else { MuQ.Koakuma.$data.limit = parseInt(this.value); } }); $KoaBtnInput.click(function(event) { if(!$KoaBookmarkInput[0].validity.valid){ console.log($KoaBookmarkInput[0].validationMessage); } else { if (MuQ.intervalID) { clearInterval(MuQ.intervalID); MuQ.intervalID = null; this.value = '找'; $KoaController.removeClass('working'); } else { MuQ.intervalID = setInterval(function(){ MuQ.nextPage(); }, 1000); this.value = '停'; $KoaController.addClass('working'); } } return false; }); $KoaFullwidthInput.click(function(event){ const node = BASE.$fullwidthElement; if (this.checked) { node.addClass('fullwidth'); } else { node.removeClass('fullwidth'); } }); $KoaOrderingInput.click(function(event){ const order_t = this.checked ? 'bookmark_count' : ''; MuQ.Koakuma.$data.order_t = order_t; }); } const MuQ = { nextLink: location.href, intervalID: null, init: function() { if(BASE.supported){ if(BASE.container) { BASE.container.id = 'Koa-container'; } $('#wrapper').width('initial'); setupHTML(); setupEvent(); this.Koakuma.$watch('thumbs', function(newVal, oldVal) { $('#Koa-found-value').text(newVal.length); }); this.page = this.np_gen(); this.np = this.page.next().value.then(v => { this.Koakuma.$mount('#Koa-container'); return v; }); } }, nextPage: function() { this.np = this.np .then(n => this.page.next(n.nextLink).value ) .catch(err => { console.error(err); clearInterval(this.intervalID); this.intervalID = null; $('#Koa-btn-input').attr('disabled', true).val('完'); }); }, np_gen: function* () { while(this.nextLink) { this.nextLink = yield getBatch(this.nextLink) .then(bat => { return getIllustsDetails(bat.illust_ids) .then(illust_d => { bat.illust_d = illust_d; return bat; }); }) .then(bat => { return getUsersDetails(Object.keys(bat.illust_d) .map((k) => bat.illust_d[k].user_id)) .then(user_d => { bat.user_d = {}; user_d.forEach(x => bat.user_d[x.user_id] = x); return bat; }); }) .then(bat => { return Promise.all(Object.keys(bat.illust_d) .map((k) => bat.illust_d[k]) .map(x => getBookmarkCountAndTags(x.illust_id))) .then(bookmark_d => { bat.bookmark_d = {}; bookmark_d.forEach(x => bat.bookmark_d[x.illust_id] = x); return bat; }); }) .then(bat => { this.Koakuma.$data.thumbs.push(...parseDataFromBatch(bat)); return bat; }).catch(err => { console.error(err); }); } }, Koakuma: new Vue({ template: `<ul><component :is="li_type" v-for="th in thumbs | bookmark_gt limit | orderBy order_t -1" :thdata="th"></component></ul>`, data: { thumbs: [], order_t: '', limit: 0, }, computed: { li_type: function() { return 'imageitem-' + BASE.li_type; }, }, filters: { bookmark_gt: function(data, limit) { return data.filter(x => x.bookmark_count >= limit); }, }, }), }; removeAnnoyance(); MuQ.init(); //Debugging window.fetchWithcookie = fetchWithcookie; window.getBookmarkCountAndTags = getBookmarkCountAndTags; window.getBatch = getBatch; window.getIllustsDetails = getIllustsDetails; window.getUsersDetails = getUsersDetails; window.parseToDOM = parseToDOM; window.parseDataFromBatch = parseDataFromBatch; window.removeAnnoyance = removeAnnoyance; window.BASE = BASE; window.MuQ = MuQ; window.Vue = Vue; window.URI = URI;