// ==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 // @downloadURL none // ==/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: `
`, }); Vue.component('illust-title', { props:['thd'], template: '

{{thd.illust_title}}

', }); Vue.component('user-name', { props:['thd'], template: ` {{thd.user_name}}`, filters: { user_href: function(user_id) { return 'http://www.pixiv.net/member_illust.php?id='+user_id; }, }, }); Vue.component('count-list', { props:['thd'], template: ``, 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: '編集', 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: `
  • `, }); Vue.component('imageitem-member-illust', { props:['thdata'], template: `
  • `, }); Vue.component('imageitem-mybookmark', { props:['thdata'], template: `
  • `, }); function setupHTML() { $(`
    0
    ★書籤
    排序
    `).appendTo('body'); $(` `).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: ``, 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;