Pixiv 增强

专注沉浸式体验, 1. 屏蔽广告, 直接访问热门图片 2. 使用users入り的方式进行搜索 3. 搜索pid和uid 4. 下载原图|gif图|gif帧zip|多图zip 5. 显示画师id、画师背景图, 用户头像允许右键保存 6. 自动加载评论 7. 对动态标记作品类型 8. 去除重定向 github:https://github.com/Ahaochan/Tampermonkey,欢迎star和fork。

当前为 2018-08-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Pixiv Plus
  3. // @name:zh-CN Pixiv 增强
  4. // @name:zh-TW Pixiv 增強
  5. // @namespace https://github.com/Ahaochan/Tampermonkey
  6. // @version 0.3.4
  7. // @icon http://www.pixiv.net/favicon.ico
  8. // @description Focus on immersive experience, 1. Block ads, directly access popular images 2. Search using users to search for 3. Search pid and uid 4. Download original image | gif map | gif frame zip | multi-image zip 5. Display artist id , artist background image, user avatar allows right-click to save 6. Automatically load comments 7. Dynamically mark the work type 8. Remove the redirect github: https://github.com/Ahaochan/Tampermonkey, welcome star and fork.
  9. // @description:zh-CN 专注沉浸式体验, 1. 屏蔽广告, 直接访问热门图片 2. 使用users入り的方式进行搜索 3. 搜索pid和uid 4. 下载原图|gif图|gif帧zip|多图zip 5. 显示画师id、画师背景图, 用户头像允许右键保存 6. 自动加载评论 7. 对动态标记作品类型 8. 去除重定向 github:https://github.com/Ahaochan/Tampermonkey,欢迎star和fork。
  10. // @description:zh-TW 專注沉浸式體驗, 1. 屏蔽廣告, 直接訪問熱門圖片2. 使用users入り的方式進行搜索3. 搜索pid和uid 4. 下載原圖|gif圖|gif幀zip|多圖zip 5. 顯示畫師id 、畫師背景圖, 用戶頭像允許右鍵保存6. 自動加載評論7. 對動態標記作品類型8. 去除重定向github:https://github.com/Ahaochan/Tampermonkey,歡迎star和fork。
  11. // @author Ahaochan
  12. // @include http*://www.pixiv.net*
  13. // @match http://www.pixiv.net/
  14. // @connect i.pximg.net
  15. // @license GPL-3.0
  16. // @supportURL https://github.com/Ahaochan/Tampermonkey
  17. // @grant unsafeWindow
  18. // @grant GM.xmlHttpRequest
  19. // @grant GM.setClipboard
  20. // @require https://code.jquery.com/jquery-2.2.4.min.js
  21. // @require https://cdn.bootcss.com/jszip/3.1.4/jszip.min.js
  22. // @require https://cdn.bootcss.com/FileSaver.js/1.3.2/FileSaver.min.js
  23. // @require https://greasyfork.org/scripts/2963-gif-js/code/gifjs.js?version=8596
  24. // @run-at document-end
  25. // @noframes
  26. // ==/UserScript==
  27.  
  28. jQuery(function ($) {
  29. 'use strict';
  30. // 加载依赖
  31.  
  32. // ============================ 全局参数 ====================================
  33. let lang = document.documentElement.getAttribute('lang') || 'en',
  34. globalInitData = unsafeWindow.globalInitData,
  35. illustJson = {};
  36. let illust = function () {
  37. // 1. 判断是否已有作品id(兼容按左右方向键翻页的情况)
  38. let preIllustId = $('body').attr('ahao_illust_id');
  39. let urlIllustId = new URL(location.href).searchParams.get("illust_id");
  40. // 2. 如果illust_id没变, 则不更新json
  41. if (parseInt(preIllustId) === parseInt(urlIllustId)) {
  42. return illustJson;
  43. }
  44. // 3. 如果illust_id变化, 则持久化illust_id, 且同步更新json
  45. $('body').attr('ahao_illust_id', urlIllustId);
  46. $.ajax({url: '/ajax/illust/' + urlIllustId, dataType: 'json', async: false, success: response => illustJson = response.body});
  47. return illustJson;
  48. };
  49. let uid = illust().userId || (pixiv && pixiv.context.userId) || (globalInitData && Object.keys(globalInitData.preload.user)[0]) || 'unknown';
  50. let mimeType = suffix => {
  51. let lib = {png: "image/png", jpg: "image/jpeg", gif: "image/gif"};
  52. return lib[suffix] || 'mimeType[' + suffix + '] not found';
  53. };
  54. let executeMutationObserver = function (option) {
  55. var options = $.extend({
  56. type: 'childList',
  57. attributeName: [],
  58. isValid: () => false,
  59. execute: addedNodes => {}
  60. }, option);
  61.  
  62. // 2.1. MutationObserver 处理
  63. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  64. var mutationObserver = new MutationObserver(function (mutations) {
  65. mutations.forEach(function (mutation) {
  66. if (options.type !== mutation.type) {
  67. return;
  68. }
  69. if (options.type === 'attributes' && options.attributeName && options.attributeName.indexOf(mutation.attributeName) < 0) {
  70. return;
  71. }
  72. if (mutation.target && typeof mutation.target !== 'object') {
  73. return;
  74. }
  75. var $parent = $(mutation.target).parent();
  76. if (!options.isValid($parent)) {
  77. return;
  78. }
  79. options.execute($parent);
  80.  
  81. });
  82. });
  83. mutationObserver.observe(document.getElementsByTagName('body')[0], {
  84. childList: true,
  85. subtree: true,
  86. attributes: true
  87. });
  88.  
  89. // 2.2. 定时处理, 避免 MutationObserver 来不及加载的情况, 10秒后交由 MutationObserver 接管
  90. var intervalId = setInterval(function () {
  91. var $body = $('body');
  92. if (!options.isValid($body)) {
  93. return;
  94. }
  95. options.execute($body);
  96. }, 1000);
  97. setTimeout(function () {
  98. window.clearInterval(intervalId);
  99. }, 10000);
  100. };
  101.  
  102. // ============================ i18n 国际化 ===============================
  103. let i18nLib = {
  104. ja: {
  105. favorites: 'users入り',
  106. },
  107. en: {
  108. favorites: 'favorites',
  109. illegal: 'illegal',
  110. download: 'download',
  111. download_wait: 'please wait download completed',
  112. copy_to_clipboard: 'copy to Clipboard',
  113. background: 'background',
  114. background_not_found: 'no-background',
  115. loginWarning: 'Pixiv Plus Script Warning! Please login to Pixiv for a better experience! Failure to login may result in unpredictable bugs!',
  116. illust_type_single: '[single pic]',
  117. illust_type_multiple: '[multiple pic]',
  118. illust_type_gif: '[gif pic]',
  119. },
  120. ko: {},
  121. zh: {
  122. favorites: '收藏人数',
  123. illegal: '不合法',
  124. download: '下载',
  125. download_wait: '请等待下载完成',
  126. copy_to_clipboard: '已复制到剪贴板',
  127. background: '背景图',
  128. background_not_found: '无背景图',
  129. loginWarning: 'Pixiv增强 脚本警告! 请登录Pixiv获得更好的体验! 未登录可能产生不可预料的bug!',
  130. illust_type_single: '[单图]',
  131. illust_type_multiple: '[多图]',
  132. illust_type_gif: '[gif图]',
  133. },
  134. 'zh-CN': {},
  135. 'zh-tw': {
  136. favorites: '收藏人數',
  137. illegal: '不合法',
  138. download: '下載',
  139. download_wait: '請等待下載完成',
  140. copy_to_clipboard: '已復製到剪貼板',
  141. background: '背景圖',
  142. background_not_found: '無背景圖',
  143. loginWarning: 'Pixiv增強 腳本警告! 請登錄Pixiv獲得更好的體驗! 未登錄可能產生不可預料的bug!',
  144. illust_type_single: '[單圖]',
  145. illust_type_multiple: '[多圖]',
  146. illust_type_gif: '[gif圖]',
  147. }
  148. };
  149. i18nLib['zh-CN'] = $.extend({}, i18nLib.zh);
  150. // TODO 待翻译
  151. i18nLib.ja = $.extend({}, i18nLib.en, i18nLib.ja);
  152. i18nLib.ko = $.extend({}, i18nLib.en, i18nLib.ko);
  153. let i18n = key => i18nLib[lang][key] || 'i18n[' + lang + '][' + key + '] not found';
  154.  
  155. // ============================ url 页面判断 ==============================
  156. let isArtworkPage = /.+member_illust\.php\?.*illust_id=\d+.*/.test(location.href);
  157.  
  158. let isMemberIndexPage = /.+member.php.*id=\d+.*/.test(location.href);
  159. let isMemberIllustPage = /.+\/member_illust\.php\?id=\d+/.test(location.href);
  160. let isMemberBookmarkPage = /.+\/bookmark\.php\?id=\d+/.test(location.href);
  161. let isMemberFriendPage = /.+\/mypixiv_all\.php\?id=\d+/.test(location.href);
  162. let isMemberDynamicPage = /.+\/stacc.+/.test(location.href);
  163. let isMemberPage = isMemberIndexPage || isMemberIllustPage || isMemberBookmarkPage || isMemberFriendPage || isMemberDynamicPage;
  164.  
  165. // ============================ 反混淆 ====================================
  166. let unique = function(array) {
  167. let seen = {}, out = [], len = array.length, j = 0;
  168. for(let i = 0; i < len; i++) {
  169. let item = array[i];
  170. if(seen[item] !== 1) {
  171. seen[item] = 1;
  172. out[j++] = item;
  173. }
  174. }
  175. return out;
  176. };
  177. let classLib = {
  178. userIcon: ['_2lyPnMP'],
  179. rightColumn: ['_2e0p8Qb']
  180. };
  181. setInterval(function () {
  182. let webpackJsonp = unsafeWindow.webpackJsonp;
  183. // 1. 格式化 webpackJsonp 变量, 取出反混淆所需的变量
  184. let filter = webpackJsonp.map(value => value[1]).filter(value => value && !Array.isArray(value) && typeof value === 'object');
  185. $.each(filter, (index, obj) => {
  186. for (let key in obj) {
  187. if (!obj.hasOwnProperty(key)) {
  188. continue;
  189. }
  190. let tmp = {};
  191. // 2. 尝试导出反混淆变量到tmp
  192. try { obj[key](tmp); } catch(err) { continue; }
  193. // 3. 存在一个变量对应多个反混淆值的情况, 用数组存入
  194. if(tmp.hasOwnProperty('exports')) {
  195. $.each(tmp.exports, function (k, v) {
  196. classLib[k] = classLib[k] || [];
  197. classLib[k].push(v);
  198. classLib[k] = unique(classLib[k]); // 去重
  199. });
  200. }
  201. }
  202. });
  203. }, 1000);
  204. let clazz = function (option) {
  205. let options = $.extend({key: '', dot: true, tag: '', join: undefined,}, option);
  206. let classItem = classLib[options.key] || [];
  207. if(options.dot) {
  208. classItem = classItem.map((v)=>'.'+v); // 是否加上点, 用于类选择器
  209. }
  210. classItem = classItem.map((v)=>options.tag+v); // 加上 tag 名, 用于限制标签的选择器
  211. if(!!options.join) {
  212. classItem = classItem.join(options.join); // 转化为字符串
  213. }
  214. return classItem;
  215. };
  216.  
  217. // 判断是否登录
  218. if (dataLayer[0].login === 'no') {
  219. alert(i18n('loginWarning'));
  220. }
  221.  
  222. // 1. 屏蔽广告, 隐藏搜索页的热门图片遮罩层, 直接访问热门图片
  223. (function () {
  224. // 1. 删除静态添加的广告
  225. $('._premium-lead-tag-search-bar').hide();
  226. $('.popular-introduction-overlay').hide();// 移除热门图片遮罩层
  227.  
  228. // 2. 删除动态添加的广告
  229. executeMutationObserver({
  230. type: 'childList',
  231. isValid: () => true,
  232. execute: function ($parent) {
  233. // 2.1. 隐藏广告
  234. (function () {
  235. let adSelector = ['iframe', '._premium-lead-promotion-banner'];
  236. adSelector = adSelector.concat(clazz({key: 'alertContainer'}));
  237. adSelector = adSelector.concat(clazz({key: 'adContainer'}));
  238. let $ad = $parent.find(adSelector.join(','));
  239. if (!$ad.length) {
  240. return;
  241. }
  242. $ad.hide();
  243. })();
  244.  
  245. // 2.2. 移除class
  246. (function () {
  247. var $figure = $parent.find('figure');
  248. if (!$figure.length) {
  249. return false;
  250. }
  251. var $class = $parent.find(clazz({key: 'blur', join: ','}));
  252. if (!$class.length) {
  253. return;
  254. }
  255. $class.removeClass(clazz({key: 'blur', dot: false, join: ' '}));
  256. })();
  257. }
  258. });
  259. })();
  260.  
  261. // 2. 使用users入り的方式进行搜索, 优先显示高质量作品
  262. (function () {
  263. var label = i18n('favorites'); // users入り
  264.  
  265. // 1. 初始化通用页面UI
  266. (function () {
  267. if (isArtworkPage) {
  268. return;
  269. }
  270. console.log("初始化通用页面 按收藏数搜索");
  271. var icon = $('._discovery-icon').attr('src');
  272. $('.navigation-menu-right').append(
  273. '<div class="menu-group">' +
  274. ' <a class="menu-item js-click-trackable-later">' +
  275. ' <img class="_howto-icon" src="' + icon + '">' +
  276. ' <span class="label">' + label + ':</span>' +
  277. ' <select id="select-ahao-favorites">' +
  278. ' <option value=""></option>' +
  279. ' <option value="10000users入り">10000users入り</option>' +
  280. ' <option value="5000users入り" > 5000users入り</option>' +
  281. ' <option value="1000users入り" > 1000users入り</option>' +
  282. ' <option value="500users入り" > 500users入り</option>' +
  283. ' <option value="300users入り" > 300users入り</option>' +
  284. ' <option value="100users入り" > 100users入り</option>' +
  285. ' <option value="50users入り" > 50users入り</option>' +
  286. ' </select>' +
  287. ' </a>' +
  288. '</div>');
  289. })();
  290.  
  291. // 2. 初始化作品页面UI
  292. (function () {
  293. if (!isArtworkPage) {
  294. return;
  295. }
  296. console.log("初始化作品页面 按收藏数搜索");
  297. executeMutationObserver({
  298. type: 'childList',
  299. isValid: function ($parent) {
  300. // 1. 判断是否添加完毕
  301. if (!!$parent.find('#select-ahao-favorites').length) {
  302. return false;
  303. }
  304.  
  305. // 2. 判断[发现]节点是否加入dom
  306. var $discovery = $parent.find('a[href="/discovery"]');
  307. if (!$discovery.length) {
  308. return false;
  309. }
  310. return true;
  311. },
  312. execute: function ($parent) {
  313. var $discovery = $parent.find('a[href="/discovery"]');
  314. // 3. clone [发现]节点, 移除href属性, 避免死循环
  315. var $tabGroup = $discovery.closest('div');
  316. var $tab = $discovery.closest('ul').clone();
  317. $tab.find('a[href="/discovery"]').attr('href', 'javascript:void(0)');
  318.  
  319. // 4. 加入dom中
  320. $tabGroup.prepend($tab);
  321. $tab.find('a').contents().last()[0].textContent = label;
  322. $tab.find('a').after('' +
  323. '<select id="select-ahao-favorites">' +
  324. ' <option value=""></option>' +
  325. ' <option value="10000users入り">10000users入り</option>' +
  326. ' <option value="5000users入り" > 5000users入り</option>' +
  327. ' <option value="1000users入り" > 1000users入り</option>' +
  328. ' <option value="500users入り" > 500users入り</option>' +
  329. ' <option value="300users入り" > 300users入り</option>' +
  330. ' <option value="100users入り" > 100users入り</option>' +
  331. ' <option value="50users入り" > 50users入り</option>' +
  332. '</select>');
  333. }
  334. });
  335. })();
  336.  
  337. // 3. 如果已经有搜索字符串, 就在改变选项时直接搜索
  338. $('body').on('change', '#select-ahao-favorites', function () {
  339. if (!!$('input[name="word"]').val()) {
  340. $('form[action="/search.php"]').submit();
  341. }
  342. });
  343.  
  344. // 4. 在提交搜索前处理搜索关键字
  345. $('form[action="/search.php"]').submit(function () {
  346. var $text = $(this).find('input[name="word"]');
  347. var $favorites = $('#select-ahao-favorites');
  348. // 3.1. 去除旧的搜索选项
  349. $text.val((index, val) => val.replace(/\d*users入り/g, ''));
  350. // 3.2. 去除多余空格
  351. $text.val((index, val) => val.replace(/\s\s+/g, ' '));
  352. // 3.3. 添加新的搜索选项
  353. $text.val((index, val) => val + ' ' + $favorites.val());
  354. });
  355. })();
  356.  
  357. // 3. 追加搜索pid和uid功能
  358. (function () {
  359. if (isArtworkPage) {
  360. return;
  361. }
  362. console.log("初始化通用页面 搜索UID和PID");
  363. var initSearch = function (option) {
  364. var options = $.extend({right: '0px', placeholder: '', url: ''}, option);
  365.  
  366. // 1. 初始化表单UI
  367. var $form = $('<form class="ui-search" ' +
  368. ' style="position: static;width: 100px;">' +
  369. '<div class="container" style="width:80%;">' +
  370. ' <input class="ahao-input" placeholder="' + options.placeholder + '" style="width:80%;"/>' +
  371. '</div>' +
  372. '<input type="submit" class="submit sprites-search-old" value="">' +
  373. '</form>');
  374. var $div = $('<div class="ahao-search"></div>').css('position', 'absolute')
  375. .css('bottom', '44px')
  376. .css('height', '30px')
  377. .css('right', options.right);
  378. $div.append($form);
  379. $('#suggest-container').before($div);
  380.  
  381. // 2. 绑定submit事件
  382. $form.submit(function (e) {
  383. e.preventDefault();
  384.  
  385. var $input = $(this).find('.ahao-input');
  386. var id = $input.val();
  387. // 2.1. ID 必须为纯数字
  388. if (!/^[0-9]+$/.test(id)) {
  389. var label = options.placeholder + i18n('illegal');
  390. alert(label);
  391. return;
  392. }
  393. // 2.2. 新窗口打开url
  394. var url = option.url + id;
  395. window.open(url);
  396. // 2.3. 清空input等待下次输入
  397. $input.val('');
  398. });
  399. };
  400. // 1. UID搜索
  401. initSearch({right: '235px', placeholder: 'UID', url: 'https://www.pixiv.net/member.php?id='});
  402. // 2. PID搜索
  403. initSearch({right: '345px', placeholder: 'PID', url: 'https://www.pixiv.net/member_illust.php?mode=medium&illust_id='});
  404. })(); // 初始化通用页面UI
  405. (function () {
  406. if (!isArtworkPage) {
  407. return;
  408. }
  409. console.log("初始化作品页面 搜索UID和PID");
  410. executeMutationObserver({
  411. type: 'childList',
  412. isValid: function ($parent) {
  413. // 1. 判断是否添加完毕
  414. if (!!$parent.find('.ahao-search').length) {
  415. return false;
  416. }
  417.  
  418. // 2. 判断 form 节点是否加入dom
  419. var $form = $parent.find('form[action="/search.php"]');
  420. if (!$form.length) {
  421. return false;
  422. }
  423. return true;
  424. },
  425. execute: function ($parent) {
  426. var $form = $parent.find('form[action="/search.php"]');
  427. // 3. 使用flex布局包裹
  428. var $flexBox = $('<div></div>').css('display', 'flex');
  429. $form.wrap($flexBox);
  430. $flexBox = $form.closest('div');
  431.  
  432. var initSearch = function (option) {
  433. var options = $.extend({
  434. placeholder: '',
  435. url: ''
  436. }, option);
  437.  
  438. // 4. clone form表单
  439. var $cloneForm = $form.clone();
  440. $cloneForm.attr('action', '').addClass('ahao-search').css('margin-right', '15px').css('width', '126px');
  441. $cloneForm.find('input[name="s_mode"]').remove(); // 只保留一个input
  442. $cloneForm.find('input:first').attr('placeholder', options.placeholder).attr('name', options.placeholder).css('width', '64px');
  443. $flexBox.prepend($cloneForm);
  444.  
  445. // 5. 绑定submit事件
  446. $cloneForm.submit(function (e) {
  447. e.preventDefault();
  448.  
  449. var $input = $(this).find('input[name="' + options.placeholder + '"]');
  450. var id = $input.val();
  451. // ID 必须为纯数字
  452. if (!/^[0-9]+$/.test(id)) {
  453. var label = options.placeholder + i18n('illegal');
  454. alert(label);
  455. return;
  456. }
  457. // 新窗口打开url
  458. var url = option.url + id;
  459. window.open(url);
  460. // 清空input等待下次输入
  461. $input.val('');
  462. });
  463. };
  464. // 1. UID搜索
  465. initSearch({placeholder: 'UID', url: 'https://www.pixiv.net/member.php?id='});
  466. // 2. PID搜索
  467. initSearch({
  468. placeholder: 'PID',
  469. url: 'https://www.pixiv.net/member_illust.php?mode=medium&illust_id='
  470. });
  471. }
  472. });
  473. })(); // 初始化作品页面UI
  474.  
  475. // 4. 单张图片替换为原图格式. 追加下载按钮, 下载gif图、gif的帧压缩包、多图
  476. (function () {
  477. if (!isArtworkPage) {
  478. return;
  479. }
  480. // 1. 初始化 下载按钮, 复制分享按钮并旋转180度
  481. let initDownloadBtn = function (option) {
  482. let options = $.extend({$parent: undefined, id: '', text: '', clickFun: ()=>{}}, option);
  483. let $shareButtonContainer = options.$parent.find(clazz({key: 'shareButtonContainer', join: ','}));
  484. let $downloadButtonContainer = $shareButtonContainer.clone();
  485. $downloadButtonContainer.addClass('ahao-download-btn')
  486. .attr('id', options.id)
  487. .removeClass(clazz({key:'shareButtonContainer', dot: false, join: ' '}))
  488. .css('margin-right', '10px')
  489. .css('position', 'relative')
  490. .append('<p>'+options.text+'</p>');
  491. $downloadButtonContainer.find('button').css('transform', 'rotate(180deg)')
  492. .on('click', options.clickFun);
  493. $shareButtonContainer.after($downloadButtonContainer);
  494. };
  495.  
  496. executeMutationObserver({
  497. type: 'attributes',
  498. attributeName: ['src', 'srcset'],
  499. isValid: function ($parent) {
  500. // 1. 判断 执行完毕
  501. let $img = $parent.find(clazz({key: 'illust', tag: 'img', join: ','}));
  502. let isImgExist = !!$img.length;
  503. let isImgSrcOriginal = /.+original.+/.test($img.attr('src'));
  504. let isImgSrcsetEmpty = $img.attr('srcset');
  505. if (!isImgExist || (isImgSrcOriginal && !isImgSrcsetEmpty)) {
  506. return false;
  507. }
  508. return true;
  509. },
  510. execute: function ($parent) {
  511. // 1. 单图、多图、gif图三种模式
  512. let moreMode = !!$(clazz({key: 'mangaViewLink', tag: 'a', join: ','})).length;
  513. let gifMode = !!$(clazz({key: 'play', tag: 'button', join: ','})).length;
  514. let singleMode = !moreMode && !gifMode;
  515. if (!singleMode) {
  516. return;
  517. }
  518. console.log('下载单图');
  519.  
  520. // 2. 替换大图
  521. let $img = $parent.find(clazz({key: 'illust', tag: 'img', join: ','}));
  522. let url = illust().urls.original;
  523. $img.attr('src', url).attr('srcset', '');
  524.  
  525. }
  526. });
  527. executeMutationObserver({
  528. type: 'childList',
  529. isValid: function ($parent) {
  530. // 1. 判断 figure 节点是否加入dom
  531. var $figure = $parent.find('figure');
  532. if (!$figure.length) {
  533. return false;
  534. }
  535.  
  536. // 2. 判断 执行完毕
  537. var $sharBtn = $parent.find(clazz({key: 'shareButtonContainer', join: ','}));
  538. if (!$sharBtn.length || !!$parent.find('.ahao-download-btn').length) {
  539. return false;
  540. }
  541. return true;
  542. },
  543. execute: function ($parent) {
  544. // 1. 单图、多图、gif图三种模式
  545. let moreMode = !!$(clazz({key: 'mangaViewLink', tag: 'a', join: ','})).length;
  546. let gifMode = !!$(clazz({key: 'play', tag: 'button', join: ','})).length;
  547. var singleMode = !moreMode && !gifMode;
  548. if (!gifMode) {
  549. return;
  550. }
  551. console.log('下载gif图');
  552.  
  553. // 2. 从 pixiv 官方 api 获取 gif 的数据, 要求 async:false, 否则页面混乱
  554. $.ajax({url: '/ajax/illust/' + illust().illustId + '/ugoira_meta',
  555. dataType: 'json', async: false,
  556. success: response => {
  557. // 2.1. 初始化 zip 下载按钮
  558. initDownloadBtn({
  559. $parent: $parent,
  560. id: 'ahao-download-zip',
  561. text: 'zip',
  562. clickFun: () => window.open(response.body.originalSrc)
  563. });
  564.  
  565. // 2.2. 初始化 gif 下载按钮
  566. // GIF_worker_URL 来自 https://greasyfork.org/scripts/2963-gif-js/code/gifjs.js?version=8596
  567. let gifUrl;
  568. let gif = new GIF({workers: 2, quality: 10, workerScript: GIF_worker_URL});
  569. let gifFrames = [];
  570. $.each(response.body.frames, function (index, frame) {
  571. let url = illust().urls.original.replace('ugoira0.', 'ugoira'+index+'.');
  572. GM.xmlHttpRequest({
  573. method: 'GET', url: url,
  574. headers: {referer: 'https://www.pixiv.net/'},
  575. overrideMimeType: 'text/plain; charset=x-user-defined',
  576. onload: function (xhr) {
  577. // 2.1. 转为blob类型
  578. let r = xhr.responseText, data = new Uint8Array(r.length), i = 0;
  579. while (i < r.length) {
  580. data[i] = r.charCodeAt(i);
  581. i++;
  582. }
  583. let suffix = url.split('.').splice(-1);
  584. let blob = new Blob([data], {type: mimeType(suffix)});
  585.  
  586. // 2.2. 压入gifFrames数组中, 手动同步sync
  587. let img = document.createElement('img');
  588. img.src = URL.createObjectURL(blob);
  589. img.width = illust().width;
  590. img.height = illust().height;
  591. gifFrames[index] = {frame: img, option: {delay: frame.delay}};
  592.  
  593. let loadFramesLength = Object.keys(gifFrames).length;
  594. // 2.3. 若xhr执行完毕, 则加载gif
  595. if(loadFramesLength >= response.body.frames.length){
  596. $.each(gifFrames, function (index, frame) {
  597. gif.addFrame(frame.frame, frame.option);
  598. });
  599. gif.render();
  600. }
  601. }
  602. });
  603. });
  604. gif.on('progress', function (pct) {
  605. $('#ahao-download-gif').find('p').text('gif '+parseInt(pct*100)+'%');
  606. });
  607. gif.on('finished', function(blob) {
  608. gifUrl = URL.createObjectURL(blob);
  609.  
  610. let $a = $('<a id="ahao-download-gif" ></a>')
  611. .attr('href', gifUrl)
  612. .attr('download', illust().illustId+'.gif');
  613. $('#ahao-download-gif').find('button').wrap($a);
  614. });
  615.  
  616. initDownloadBtn({
  617. $parent: $parent,
  618. id: 'ahao-download-gif',
  619. text: 'gif 0%',
  620. clickFun: function () {
  621. if (!gifUrl) {
  622. alert('Gif未加载完毕, 请稍等片刻!');
  623. return;
  624. }
  625. // Adblock 禁止直接打开 blob url, https://github.com/jnordberg/gif.js/issues/71#issuecomment-367260284
  626. // window.open(gifUrl);
  627. }
  628. });
  629. }
  630. });
  631.  
  632.  
  633. }
  634. });
  635. executeMutationObserver({
  636. type: 'childList',
  637. isValid: function ($parent) {
  638. // 1. 判断 figure 节点是否加入dom
  639. let $figure = $parent.find('figure');
  640. if (!$figure.length) {
  641. return false;
  642. }
  643.  
  644. // 2. 判断 执行完毕
  645. let $sharBtn = $parent.find(clazz({key: 'shareButtonContainer', join: ','}));
  646. if (!$sharBtn.length || !!$parent.find('.ahao-download-btn').length) {
  647. return false;
  648. }
  649. return true;
  650. },
  651. execute: function ($parent) {
  652. // 1. 单图、多图、gif图三种模式
  653. let moreMode = !!$(clazz({key: 'mangaViewLink', tag: 'a', join: ','})).length;
  654. let gifMode = !!$(clazz({key: 'play', tag: 'button', join: ','})).length;
  655. var singleMode = !moreMode && !gifMode;
  656. if (!moreMode) {
  657. return;
  658. }
  659. console.log('下载多图');
  660.  
  661. // 2. 初始化 图片数量, 图片url
  662. let zip = new JSZip();
  663. let downloaded = 0; // 下载完成数量
  664. let num = illust().pageCount; // 下载目标数量
  665. let url = illust().urls.original;
  666. let imgUrls = Array(parseInt(num)).fill()
  667. .map((value, index) => url.replace(/_p\d\./, '_p' + index + '.'));
  668.  
  669. // 3. 初始化 下载按钮, 复制分享按钮并旋转180度
  670. initDownloadBtn({
  671. $parent: $parent,
  672. id: 'ahao-download-gif',
  673. text: i18n('download') + '0/' + num,
  674. clickFun: function () {
  675. // 3.1. 手动sync, 避免下载不完全
  676. if (downloaded < num) {
  677. alert(i18n('download_wait'));
  678. return;
  679. }
  680. // 3.2. 使用jszip.js和FileSaver.js压缩并下载图片
  681. zip.generateAsync({type: 'blob', base64: true})
  682. .then(content => saveAs(content, illust().illustId + '.zip'));
  683. }
  684. });
  685.  
  686. // 4. 下载图片, https://wiki.greasespot.net/GM.xmlHttpRequest
  687. $.each(imgUrls, function (index, url) {
  688. GM.xmlHttpRequest({
  689. method: 'GET', url: url,
  690. headers: {referer: 'https://www.pixiv.net/'},
  691. overrideMimeType: 'text/plain; charset=x-user-defined',
  692. onload: function (xhr) {
  693. // 4.1. 转为blob类型
  694. let r = xhr.responseText, data = new Uint8Array(r.length), i = 0;
  695. while (i < r.length) {
  696. data[i] = r.charCodeAt(i);
  697. i++;
  698. }
  699. let suffix = url.split('.').splice(-1);
  700. let blob = new Blob([data], {type: mimeType(suffix)});
  701.  
  702. // 4.2. 压缩图片
  703. zip.file(illust().illustId + '_' + index + '.' + suffix, blob, {binary: true});
  704.  
  705. // 4.3. 手动sync, 避免下载不完全的情况
  706. downloaded++;
  707. $('.ahao-download-btn').find('p').html(i18n('download') + '' + downloaded + '/' + num);
  708. }
  709. });
  710. });
  711. }
  712. });
  713. })();
  714.  
  715. // 5. 在画师页面和作品页面显示画师id、画师背景图, 用户头像允许右键保存
  716. (function () {
  717. if (!isMemberPage) {
  718. return;
  719. }
  720.  
  721. // 1. 获取用户名的元素
  722. var $username = $('a.user-name');
  723.  
  724. // 2. 显示画师背景图
  725. var url = $('body').css('background-image').replace('url(', '').replace(')', '').replace(/"/gi, "");
  726. var $div = $('<div style="text-align: center"></div>');
  727. if (!!url && url !== 'none') {
  728. $div.append('<img src="' + url + '" width="10%">' +
  729. '<a target="_blank" href="' + url + ' ">' + i18n('background') + '</a>');
  730. } else {
  731. $div.append('<span>' + i18n('background_not_found') + '</span>');
  732. }
  733. $username.after($div);
  734.  
  735. // 3. 显示画师id, 点击自动复制到剪贴板
  736. var $uid = $('<span>UID: ' + uid + '</span>')
  737. .on('click', function () {
  738. var $this = $(this);
  739. $this.text('UID' + i18n('copy_to_clipboard'));
  740. GM.setClipboard(uid);
  741. setTimeout(function () {
  742. $this.text('UID: ' + uid);
  743. }, 2000);
  744. });
  745. $username.after($uid);
  746. })(); // 画师页面UI
  747. (function () {
  748. if (!isArtworkPage) {
  749. return;
  750. }
  751. executeMutationObserver({
  752. type: 'childList',
  753. isValid: function ($parent) {
  754. // 1. 判断 rightColumn 节点是否加入dom
  755. let $rightColumn = $parent.find(clazz({key: 'rightColumn', join: ','}));
  756. if (!$rightColumn.length) {
  757. return false;
  758. }
  759.  
  760. // 2. 判断 执行完毕
  761. let $uid = $parent.find('#ahao-uid');
  762. if (!!$uid.length) {
  763. return false;
  764. }
  765. return true;
  766. },
  767. execute: function ($parent) {
  768. console.log("显示画师id和背景图");
  769. let $rightColumn = $parent.find(clazz({key: 'rightColumn', join: ','}));
  770. let $userIcon = $rightColumn.find(clazz({key: 'userIcon', tag: 'a', join: ','}));
  771.  
  772.  
  773. let $row = $userIcon.closest('div');
  774. let $firstDiv = $row.find('div:first');
  775.  
  776. // 1. 显示画师背景图
  777. var background = globalInitData.preload.user[uid].background;
  778. var url = (background && background.url) || '';
  779. var $bgDiv = $row.clone().attr('id', 'ahao-background');
  780. $bgDiv.children('a').remove();
  781. $bgDiv.prepend('<img src="' + url + '" width="10%"/>');
  782. $bgDiv.find('div a').attr('href', !!url ? url : 'javascript:void(0)').attr('target', '_blank')
  783. .text(!!url ? i18n('background') : i18n('background_not_found'));
  784. $row.after($bgDiv);
  785.  
  786. // 2. 显示画师id, 点击自动复制到剪贴板
  787. let $uid = $firstDiv.clone();
  788. $uid.find('a').attr('href', 'javascript:void(0)').attr('id', 'ahao-uid').text('UID: ' + uid);
  789. $uid.on('click', function () {
  790. var $this = $(this);
  791. $this.find('a').text('UID' + i18n('copy_to_clipboard'));
  792. GM.setClipboard(uid);
  793. setTimeout(function () {
  794. $this.find('a').text('UID: ' + uid);
  795. }, 2000);
  796. });
  797. $row.append($uid);
  798. }
  799. });
  800. })(); // 作品页面UI
  801. (function () {
  802. if(!isArtworkPage) {
  803. return;
  804. }
  805.  
  806. executeMutationObserver({
  807. type: 'childList',
  808. isValid: function ($parent) {
  809. // 1. 判断 用户icon 节点是否加入dom
  810. let $userIcon = $parent.find(clazz({key: 'userIcon', join: ','}));
  811. if (!$userIcon.length) {
  812. return false;
  813. }
  814.  
  815. // 2. 判断 执行完毕
  816. let $userImg = $parent.find('img.ahao-user-img');
  817. if (!!$userImg.length) {
  818. return false;
  819. }
  820. return true;
  821. },
  822. execute: function ($parent) {
  823. let $userIcon = $parent.find(clazz({key: 'userIcon', join: ','}));
  824. $userIcon.each(function () {
  825. let $this = $(this);
  826. let tagName = $this.prop('tagName');
  827.  
  828. let imgUrl = $this.css('background-image').replace('url(','').replace(')','').replace(/\"/gi, "");
  829. let $userImg = $('<img class="ahao-user-img" src=""/>').attr('src', imgUrl);
  830. $userImg.css('width', $this.css('width'))
  831. .css('height', $this.css('height'));
  832.  
  833. if(tagName.toLowerCase() === 'a') {
  834. $this.append($userImg);
  835. $this.css('background-image', '');
  836. return;
  837. }
  838.  
  839. if(tagName.toLowerCase() === 'div') {
  840. $userImg.attr('class', $this.attr('class'));
  841. $userImg.html($this.html());
  842. $this.replaceWith(()=>$userImg);
  843. return;
  844. }
  845. });
  846. }
  847. });
  848. })(); // 解除 用户头像 的background 限制, 方便保存用户头像
  849.  
  850. // 6. 自动加载评论
  851. (function () {
  852. if (!isArtworkPage) {
  853. return;
  854. }
  855. executeMutationObserver({
  856. type: 'childList',
  857. isValid: function ($parent) {
  858. // 1. 判断 查看更多评论 节点是否加入dom
  859. let $showMoreButton = $parent.find(clazz({key: 'showMoreButton', join: ','}));
  860. if (!$showMoreButton.length) {
  861. return false;
  862. }
  863. return true;
  864. },
  865. execute: function ($parent) {
  866. $parent.find(clazz({key: 'showMoreButton', join: ','})).click();
  867. }
  868. });
  869. })();
  870.  
  871. // 7. 对画师页动态中的图片标记作品类型
  872. (function () {
  873. if(!isMemberDynamicPage) {
  874. return;
  875. }
  876.  
  877. executeMutationObserver({
  878. type: 'childList',
  879. isValid: function ($parent) {
  880. // 1. 判断 figure 节点是否加入dom
  881. let $title = $parent.find('.stacc_ref_illust_title');
  882. if (!$title.length) {
  883. return false;
  884. }
  885. return true;
  886. },
  887. execute: function ($parent) {
  888. $parent.find('.stacc_ref_illust_title').each(function () {
  889. let $a = $(this).find('a');
  890. // 1. 已经添加过标记的就不再添加
  891. if(!!$a.attr('ahao-illust-id')){
  892. return;
  893. }
  894. // 2. 获取pid, 设置标记避免二次生成
  895. let illustId = new URL(location.origin + '/' + $a.attr('href')).searchParams.get('illust_id');
  896. $a.attr('ahao-illust-id', illustId);
  897. // 3. 调用官方api, 判断作品类型
  898. $.ajax({
  899. url: '/ajax/illust/' + illustId, dataType: 'json',
  900. success: response => {
  901. let illustType = parseInt(response.body.illustType);
  902. let isMultiPic = parseInt(response.body.pageCount) > 1;
  903. switch (illustType) {
  904. case 0:
  905. case 1:$a.after('<p>' + (isMultiPic ? i18n('illust_type_multiple') : i18n('illust_type_single')) + '</p>');break;
  906. case 2:$a.after('<p>'+i18n('illust_type_gif')+'</p>');break;
  907. }
  908. }
  909. });
  910. })
  911.  
  912. }
  913. });
  914. })();
  915.  
  916. // 8. 对jump.php取消重定向
  917. $('a[href*="jump.php"]').each(function () {
  918. let $this = $(this);
  919. let href = $this.attr('href').replace(/\/?jump.php\?/, '');
  920. $this.attr('href', decodeURIComponent(href));
  921. });
  922. });