// ==UserScript== // @name Extract images for pixiv // @name:zh P站原图收割机 // @namespace https://github.com/cmheia/extract-images-for-pixiv // @description Adds a button that get all attached images as original size to every post. // @include http://www.pixiv.net/member_illust.php* // @include https://www.pixiv.net/member_illust.php* // @author cmheia // @version 1.3.2 // @icon http://www.pixiv.net/favicon.ico // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @license MPL // @downloadURL https://update.greasyfork.icu/scripts/20730/Extract%20images%20for%20pixiv.user.js // @updateURL https://update.greasyfork.icu/scripts/20730/Extract%20images%20for%20pixiv.meta.js // ==/UserScript== (function () { 'use strict'; /********************************************************************** * 长得像库 **********************************************************************/ var $id = function (o) { // return document.getElementById(o); return document.querySelector(`#${o}`); }; var $class = function (o) { // return document.getElementsByClassName(o); return document.querySelector(`.${o}`); }; // 去重 var unique = function (arr) { var result = [], hash = {}; for (let i = 0, elem; (elem = arr[i]) !== undefined; i++) { if (!hash[elem]) { result.push(elem); hash[elem] = true; } } return result; }; // 插入样式表 var apendStyle = function (cssText) { var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; var textNode = document.createTextNode(cssText); style.appendChild(textNode); head.appendChild(style); }; // 增加 class var addClassName = function (elem, clas) { var current = elem.className; if (current) { current += " "; current += clas; current = current.split(' ').filter(function (v, i) { if (v) { return v; } }); current = unique(current); elem.className = current.join(" "); } else { elem.className = clas; } }; // 移除 class var removeClassName = function (elem, clas) { var current = elem.className; if (current) { current = current.split(' ').filter(function (v, i) { if (clas != v) { return v; } }); current = unique(current); elem.className = current.join(" "); } }; // 增加/移除 class var toggleClassName = function (elem, clas) { var current = elem.className; if (current) { if (-1 === current.split(' ').indexOf(clas)) { addClassName(elem, clas); } else { removeClassName(elem, clas); } } else { elem.className = clas; } }; // 伤脑筋! function illustCollector() { function tergetContainer() { // illust_id this.id = "-1"; // 取得的原图链接 this.result = []; // 最终的原图链接,1 -> yes,0 -> no,-1 -> failed this.final = []; } this.illust = []; // 删除重复目标 this.shrinkTarget = function () { var elem, hash = {}, duplicate = []; // 第一步:找出需要删除的重复 id for (let i = 0; (elem = this.illust[i]) !== undefined; i++) { if (hash[elem.id]) { duplicate.push(i); // 重复 } else { hash[elem.id] = true; } } // 第二步:删除的重复 id for (let i = duplicate.length - 1; i >= 0; i--) { this.illust.splice(duplicate[i], 1); } // console.log("删除重复 id", duplicate.length, "个"); return duplicate.length; }; // 增加新目标 this.addTarget = function (illust_id) { // console.group("addTarget", illust_id, this.illust.length); var i, index; for (i = 0; i < this.illust.length; i++) { if (illust_id === this.illust[i].id) { // console.log("目标重复了"); index = -1; break; } } if (-1 !== index) { this.shrinkTarget(); index = this.illust.length; // console.log("新增目标", index, this.illust.length); this.illust.push(new tergetContainer()); // illust_id this.illust[index].id = illust_id; } // console.log(index, this.illust); // console.groupEnd(); return index; }; // 删除目标 // type: true -> target is id; false -> target is index (default) this.removeTarget = function (target, type) { // console.group("removeTarget", target, type); var index = -1; if (type) { for (let i = 0; i < this.illust.length; i++) { if (target === this.illust[i].id) { index = i; break; } } } else { index = target; } if (index > -1) { this.illust.splice(index, 1); } // console.groupEnd(); }; // 记录指定 illust_id 包含的图片数量(取得目标 html 后调用) // count: -1 -> 记录为失败 // type: true -> target is index; false -> target is id (default) this.recordTargetLength = function (target, count, type) { // console.group("recordTargetLength", target, count, type); var index = -1; if (type && this.illust[target] && this.illust[target].id) { index = target; } else { for (let i = 0; i < this.illust.length; i++) { if (target === this.illust[i].id) { index = i; break; } } } if (index > -1) { if (0 > count) { // 记录为失败 // 取得的原图链接 // this.illust[index].result[0] = ""; // 最终的原图链接,1 -> yes,0 -> no,-1 -> failed this.illust[index].final[0] = -1; // console.log(target, "被标记为获取失败,index =", index); } else { // 取得的原图链接 this.illust[index].result = new Array(count); // 最终的原图链接,1 -> yes,0 -> no,-1 -> failed this.illust[index].final = new Array(count); // console.log("初始化 illust[", index, "] 为", count, "个原图存放区"); } } // console.groupEnd(); }; // 记录指定 illust_id 的原图 URL (取得目标的原图后调用, 每次调用添加一个 URL, 多图多调) // type: true -> target is index; false -> target is id (default) this.setTarget = function (target, content, offset, status, type) { // console.group("setTarget", target, content, offset, status, type); var index = -1, result = false; if (type && this.illust[target] && this.illust[target].id) { index = target; } else { for (let i = 0; i < this.illust.length; i++) { if (target === this.illust[i].id) { index = i; break; } } } if (index > -1) { if (offset < this.illust[index].final.length) { // console.log("记录第", offset, "个原图", content, "到", index); // 取得的原图链接 this.illust[index].result[offset] = content; // 最终的原图链接,1 -> yes,0 -> no,-1 -> failed this.illust[index].final[offset] = parseInt(status); result = true; } else { // console.log(offset, "已越界"); } } // console.groupEnd(); return result; }; // 完工? // final[],1 -> yes,0 -> no,-1 -> failed // 遍历所有 final, 发现 0 即为未完成 this.isAllDone = function () { // console.group("isAllDone", this.illust.length); var working = false; // console.group("loop illust[]"); for (let i = 0; i < this.illust.length && !working; i++) { // console.log("illust[", i, "]: id =", this.illust[i].id, ", final.length =", this.illust[i].final.length); if (0 === this.illust[i].final.length) { working = true; // console.warn("final.length=0, 即还未记录结果, 属未完成"); break; } for (let j = 0; j < this.illust[i].final.length && !working; j++) { // console.log("\tfinal[", j, "] =", this.illust[i].final[j]); if (0 === this.illust[i].final[j]) { working = true; // console.warn("illust[", i, "].final[", j, "] = 0, 还未完成"); break; } } } // console.groupEnd(); if (working) { // console.warn("在忙"); } else { // console.warn("完工!!!"); } // console.groupEnd(); return !working; }; // 导出结果 this.exportAll = function () { // console.group("exportAll"); var j, k, total = 0, failed = new Array(this.illust.length), src = [], result = {}; for (let i = 0; i < this.illust.length; i++) { for (j = 0, k = 0; j < this.illust[i].final.length; j++) { if (1 === this.illust[i].final[j]) { src[total++] = this.illust[i].result[j]; k++; } } failed[i] = j - k; // console.log("illust[", i, "]导出", k, "个,失败", failed, "个"); } // console.log("共导出", total, "个"); result.fail = failed; result.done = src; // console.groupEnd(); return result; }; // 导出 ID this.getID = function () { // console.group("getID"); var result = []; for (let i = 0; i < this.illust.length; i++) { result[i] = this.illust[i].id; } // console.groupEnd(); return result; }; } /********************************************************************** * 基础设施 **********************************************************************/ // 页面显示信息 var msg = function (msg) { $id("extracted").innerHTML = msg; }; // 创建样式表 var addStyle = function () { apendStyle(".cmheia_checkbox {position:absolute;left:0;} .cmheia_item {padding:1px;} .cmheia_item_unselect {background-color:pink;}"); }; // 作品目录? // 综合 // http://www.pixiv.net/member_illust.php?id=xxxxxxxx // 插画 // http://www.pixiv.net/member_illust.php?type=illust&id=xxxxxxxx // 漫画 // http://www.pixiv.net/member_illust.php?type=manga&id=xxxxxxxx // 动图 // http://www.pixiv.net/member_illust.php?type=ugoira&id=xxxxxxxx // 小说 // http://www.pixiv.net/novel/member.php?id=xxxxxxxx var isWorksList = function () { // console.group('页面类型'); var userId, workId; userId = window.location.search.match(/[^_]id=(\d+)/); workId = window.location.search.match(/illust_id=(\d+)/); if (userId) { // console.log("作品目录,USER ID:", userId[1]); } if (workId) { // console.log("作品页面,WORK ID:", workId[1]); } // console.groupEnd(); return null !== userId && null === workId; }; // 匹配单个图片链接 var parseImageUrl = function (src) { var result = src.match(/((http|https):\/\/)+(\w+\.)+(\w+)[\w\/\.\-]*(jpg|jpeg|gif|png|webp)/gi); if (null === result || 1 !== result.length) { return null; } return result[0]; }; // 提取多图页面原图链接 var parseMultiImageUrl = function (target, callback) { // console.group("parseMultiImageUrl", target); var num = target.length, parsed = 0, result = {}; var referer = target[0].replace(/big/, "medium"); // console.warn('Referer :', referer); result.done = new Array(num); result.fail = new Array(num); for (let i = 0; i < num; i++) { // console.log(target[i]); // 下面闭包的 index 无实际必要, // xhr.finalUrl.replace(/.*(page=\d+)/, "$1") 可取得相同的值, // 然而 // 听说闭包很深奥,那就多练练 GM_xmlhttpRequest({ method : 'GET', url : target[i], headers: { 'Referer': referer }, onload : (function (xhr) { var index = i; return function (xhr) { var src; if (200 === xhr.status) { src = parseImageUrl(xhr.response); if (null !== src) { result.done[index] = src; } } // console.log("parseMultiImageUrl:onload", xhr.finalUrl.replace(/.*(page=\d+)/, "$1"), parsed, src, result); if (++parsed === num) { callback(result); } }; })(), onerror: (function (xhr) { var index = i; return function (xhr) { // console.log("parseMultiImageUrl:onerror", xhr.finalUrl.replace(/.*(page=\d+)/, "$1")); result.fail[index] = xhr.finalUrl; if (++parsed === num) { callback(result); } }; })() }); } // console.groupEnd(); return num; }; /********************************************************************** * 作品目录页面功能 **********************************************************************/ // 解析详情页链接 var extractIllustUrl = function () { // console.group("extractIllustUrl"); var id = [], itemList = $class('_image-items').children; if (itemList) { for (let i = 0; i < itemList.length; i++) { let cmheia_checkbox = itemList[i].children[0].children[0].getElementsByTagName('input')[0]; // console.log(cmheia_checkbox); if (cmheia_checkbox && cmheia_checkbox.checked) { let href = itemList[i].children[1].getAttribute('href'); if (href && href.match(/.*illust_id=(\d+).*/)) { // id.push(href.replace(/.*illust_id=(\d+).*/, "$1") || ""); id.push(href); } } } } // console.log(id); // console.groupEnd(); return id; }; // 选中全部图片 var ctrlSelectAll = function () { // console.group("ctrlSelectAll"); var itemList = $class('_image-items').children; if (itemList) { for (let i = 0; i < itemList.length; i++) { let index = itemList[i].children[0].children[0].children.length - 1; let bt = itemList[i].children[0].children[0].children[index]; bt.checked = true; removeClassName(itemList[i].children[0], 'cmheia_item_unselect'); } } // console.groupEnd(); }; // 反选 var ctrlSelectInvert = function () { // console.group("ctrlSelectInvert"); var itemList = $class('_image-items').children; if (itemList) { for (let i = 0; i < itemList.length; i++) { let index = itemList[i].children[0].children[0].children.length - 1; let bt = itemList[i].children[0].children[0].children[index]; let x = bt.checked; bt.checked = !x; toggleClassName(itemList[i].children[0], 'cmheia_item_unselect'); } } // console.groupEnd(); }; // 提取指定页面 var fetchPageContent = function (arr, prefix, onload, onerror, referer) { // console.group('fetchPageContent'); // console.warn('Referer :', referer); for (let i of arr) { // 听说闭包很深奥,那就多练练 var target = i.replace(/.*illust_id=(\d+).*/, "$1"); // console.log(prefix + i); GM_xmlhttpRequest({ method : 'GET', url : prefix + i, headers: { 'Referer': referer }, onload : (function (xhr) { var id = target; return function (xhr) { onload(id, xhr); }; })(), onerror: (function (xhr) { var id = target; return function (xhr) { onerror(id, xhr); }; })() }); } // console.groupEnd(); }; // 从 html 源码提取原图链接 // 先尝试作为单图解析,解析失败再作为图集解析,解析再次失败再作为动图解析 // 返回: // 单图 -> 原图链接(57565823); // -> html 中包含字符串 "original-image" // 多图 -> 包含原图的目标页面链接(第二个参数为此而生)(56207143); // -> html 中包含字符串 "multiple" // 动图 -> 原图压缩包链接(44588377,56083603)(动图仅包含单个 zip , 使用与单图相同的方法处理) // -> html 中包含字符串 "ugoira_view" var parseWorkPage = function (html, url) { // console.group("parseWorkPage"); // 2016-07-18 更新特征: // 单图 -> 原图链接(57565823); // -> html 中包含字符串 "original-image" // -> document.querySelector('.works_display').innerHTML.indexOf('manga') === -1 // -> html 中仅字符串 "manga" 仅出现一次 // -> 即 XMLHttpRequest.responseText.match(/manga/gi).length === 1 // 多图 -> 包含原图的目标页面链接(第二个参数为此而生)(56207143); // -> html 中包含字符串 "multiple" // -> html 中仅字符串 "manga" 仅出现一次 // -> document.querySelector('.works_display').innerHTML.indexOf('manga') !== -1 // -> 即 XMLHttpRequest.responseText.match(/manga/gi).length > 1 // 动图 -> 原图压缩包链接(44588377,56083603)(动图仅包含单个 zip , 使用与单图相同的方法处理) // -> html 中包含字符串 "ugoira_view" // -> document.querySelector('.works_display').innerHTML.indexOf('manga') === -1 // -> html 中仅字符串 "manga" 仅出现一次 // -> 即 XMLHttpRequest.responseText.match(/manga/gi).length === 1 // 实例: // 单图 //
Верный
// 多图(伪) //
COMITIA114
// 多图(真) //
シャロ生誕祭
// 动图 //
// 对应正则: // /]*class=\"works_display\">[^<>]*<(\w*)[^<>]*class=\"([\w\s\-\_]*)\"[^<>]*>/ // /]*class[^<>]*=[^<>]*\"[^<>]*works_display[^<>]*\">[^<>]*<(\w*)[^<>]*class[^<>]*=[^<>]*\"([\w\s\-\_]*)\"[^<>]*>/ // 上述实例 match 结果: // 单图 // ["
", "div", "_layout-thumbnail ui-modal-trigger"] // 多图(伪) // ["
", "a", " _work manga "] // 多图(真) // ["
", "a", " _work multiple "] // 动图 // ["
", "div", "_ugoku-illust-player-container ready playing"] var result = [], target = html.match(/]*class=\"works_display\">[^<>]*<(\w*)[^<>]*class=\"([\w\s\-\_]*)\"[^<>]*>/); if (target && 3 === target.length) { // 先尝试用类名判断 if (-1 !== target[2].indexOf("trigger")) { // 单图 target = html.match(//); if (target && target[1]) { result.push(target[1]); } // console.log("单图", result); // console.log(target); } else if (-1 !== target[2].indexOf("multiple")) { // 多图(真) target = html.match(/