Greasy Fork镜像 支持简体中文。

Pixiv 增強

專注沉浸式體驗,

目前為 2019-11-10 提交的版本,檢視 最新版本

  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.7.0
  7. // @icon http://www.pixiv.net/favicon.ico
  8. // @description Focus on immersive experience,
  9. // @description:zh-CN 专注沉浸式体验,
  10. // @description:zh-TW 專注沉浸式體驗,
  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. // @grant GM.setValue
  21. // @grant GM.getValue
  22. // @grant GM_addStyle
  23. // @grant GM_xmlhttpRequest
  24. // @grant GM_setClipboard
  25. // @grant GM_setValue
  26. // @grant GM_getValue
  27. // @require https://code.jquery.com/jquery-2.2.4.min.js
  28. // @require https://cdn.bootcss.com/jszip/3.1.4/jszip.min.js
  29. // @require https://cdn.bootcss.com/FileSaver.js/1.3.2/FileSaver.min.js
  30. // @require https://gf.qytechs.cn/scripts/2963-gif-js/code/gifjs.js?version=8596
  31. // @require https://gf.qytechs.cn/scripts/375359-gm4-polyfill-1-0-1/code/gm4-polyfill-101.js?version=652238
  32. // @run-at document-end
  33. // @noframes
  34. // ==/UserScript==
  35. jQuery(function ($) {
  36. 'use strict';
  37. // 加载依赖
  38. // ============================ jQuery插件 ====================================
  39. $.fn.extend({
  40. fitWindow: function () {
  41. this.css('width', 'auto').css('height', 'auto')
  42. .css('max-width', '').css('max-height', $(window).height());
  43. },
  44. replaceTagName: function (replaceWith) {
  45. var tags = [],
  46. i = this.length;
  47. while (i--) {
  48. var newElement = document.createElement(replaceWith),
  49. thisi = this[i],
  50. thisia = thisi.attributes;
  51. for (var a = thisia.length - 1; a >= 0; a--) {
  52. var attrib = thisia[a];
  53. newElement.setAttribute(attrib.name, attrib.value);
  54. }
  55. newElement.innerHTML = thisi.innerHTML;
  56. $(thisi).after(newElement).remove();
  57. tags[i] = newElement;
  58. }
  59. return $(tags);
  60. },
  61. getBackgroundUrl: function () {
  62. let imgUrls = [];
  63. this.each(function (index, ele) {
  64. let bgUrl = $(this).css('background-image') || ele.style.backgroundImage || 'url("")';
  65. let matchArr = bgUrl.match(/url\((['"])(.*?)\1\)/);
  66. bgUrl = matchArr && matchArr.length >= 2 ? matchArr[2] : '';
  67. imgUrls.push(bgUrl);
  68. });
  69. return imgUrls.length === 1 ? imgUrls[0] : imgUrls;
  70. }
  71. });
  72.  
  73. // ============================ 全局参数 ====================================
  74. let globalData, preloadData;
  75. $.ajax({ url: location.href, async: false,
  76. success: response => {
  77. let html = document.createElement( 'html' );
  78. html.innerHTML = response;
  79. globalData = JSON.parse($(html).find('meta[name="global-data"]').attr('content') || '{}');
  80. preloadData = JSON.parse($(html).find('meta[name="preload-data"]').attr('content') || '{}');
  81. }
  82. });
  83. let lang = (document.documentElement.getAttribute('lang') || 'en').toLowerCase(),
  84. illustJson = {};
  85.  
  86. console.log(globalData);
  87. console.log(preloadData);
  88. let illust = function () {
  89. // 1. 判断是否已有作品id(兼容按左右方向键翻页的情况)
  90. let preIllustId = $('body').attr('ahao_illust_id');
  91. let paramRegex = location.href.match(/artworks\/(\d*)$/);
  92. let urlIllustId = !!paramRegex && paramRegex.length > 0 ? paramRegex[1] : '';
  93. // 2. 如果illust_id没变, 则不更新json
  94. if (parseInt(preIllustId) === parseInt(urlIllustId)) {
  95. return illustJson;
  96. }
  97. // 3. 如果illust_id变化, 则持久化illust_id, 且同步更新json
  98. if (!!urlIllustId) {
  99. $('body').attr('ahao_illust_id', urlIllustId);
  100. $.ajax({
  101. url: '/ajax/illust/' + urlIllustId,
  102. dataType: 'json',
  103. async: false,
  104. success: response => illustJson = response.body
  105. });
  106. }
  107. return illustJson;
  108. };
  109. let uid = preloadData && preloadData.user && Object.keys(preloadData.user)[0];
  110. let observerFactory = function (option) {
  111. let options;
  112. if (typeof option === 'function') {
  113. options = {
  114. callback: option,
  115. node: document.getElementsByTagName('body')[0],
  116. option: {childList: true, subtree: true}
  117. };
  118. } else {
  119. options = $.extend({
  120. callback: () => {
  121. },
  122. node: document.getElementsByTagName('body')[0],
  123. option: {childList: true, subtree: true}
  124. }, option);
  125. }
  126. let MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
  127. observer = new MutationObserver((mutations, observer) => {
  128. options.callback.call(this, mutations, observer);
  129. // GM.getValue('MO', true).then(function (v) { if(!v) observer.disconnect(); });
  130. });
  131. observer.observe(options.node, options.option);
  132. return observer;
  133. };
  134. let isLogin = function () {
  135. let status = 0;
  136. $.ajax({url: 'https://www.pixiv.net/setting_user.php', async: false})
  137. .done((data, statusText, xhr) => status = xhr.status);
  138. return status === 200;
  139. };
  140.  
  141. // ============================ 配置信息 ====================================
  142. let GMkeys = {
  143. MO: 'MO', // MutationObserver 的开关
  144. selectorShareBtn: 'selectorShareBtn', // 下载按钮的selector
  145. selectorRightColumn: 'selectorRightColumn', // 作品页面的作者信息selector
  146.  
  147. switchImgSize: 'switch-img-size', // 是否显示图片大小的开关
  148. switchImgPreload: 'switch-img-preload', // 是否预下载的开关
  149. switchComment: 'switch-comment', // 是否自动加载评论的开关
  150. switchOrderByPopular: 'switch-order-by-popular',// 是否按收藏数排序的开关(单页排序)
  151.  
  152. downloadName: 'download-name', // 下载名pattern
  153. };
  154.  
  155. // ============================ i18n 国际化 ===============================
  156. let i18nLib = {
  157. ja: {
  158. favorites: 'users入り',
  159. },
  160. en: {
  161. favorites: 'favorites',
  162. illegal: 'illegal',
  163. download: 'download',
  164. download_wait: 'please wait download completed',
  165. copy_to_clipboard: 'copy to Clipboard',
  166. background: 'background',
  167. background_not_found: 'no-background',
  168. loginWarning: 'Pixiv Plus Script Warning! Please login to Pixiv for a better experience! Failure to login may result in unpredictable bugs!',
  169. illust_type_single: '[single pic]',
  170. illust_type_multiple: '[multiple pic]',
  171. illust_type_gif: '[gif pic]',
  172. sort_by_popularity: 'Sort_by_popularity(single page)'
  173. },
  174. ko: {},
  175. zh: {
  176. favorites: '收藏人数',
  177. illegal: '不合法',
  178. download: '下载',
  179. download_wait: '请等待下载完成',
  180. copy_to_clipboard: '已复制到剪贴板',
  181. background: '背景图',
  182. background_not_found: '无背景图',
  183. loginWarning: 'Pixiv增强 脚本警告! 请登录(不可用)Pixiv获得更好的体验! 未登录(不可用)可能产生不可预料的bug!',
  184. illust_type_single: '[单图]',
  185. illust_type_multiple: '[多图]',
  186. illust_type_gif: '[gif图]',
  187. sort_by_popularity: '按收藏数搜索(单页)'
  188. },
  189. 'zh-cn': {},
  190. 'zh-tw': {
  191. favorites: '收藏人數',
  192. illegal: '不合法',
  193. download: '下載',
  194. download_wait: '請等待下載完成',
  195. copy_to_clipboard: '已復製到剪貼板',
  196. background: '背景圖',
  197. background_not_found: '無背景圖',
  198. loginWarning: 'Pixiv增強 腳本警告! 請登錄Pixiv獲得更好的體驗! 未登錄可能產生不可預料的bug!',
  199. illust_type_single: '[單圖]',
  200. illust_type_multiple: '[多圖]',
  201. illust_type_gif: '[gif圖]',
  202. sort_by_popularity: '按收藏數搜索(單頁)'
  203. }
  204. };
  205. i18nLib['zh-cn'] = $.extend({}, i18nLib.zh);
  206. // TODO 待翻译
  207. i18nLib.ja = $.extend({}, i18nLib.en, i18nLib.ja);
  208. i18nLib.ko = $.extend({}, i18nLib.en, i18nLib.ko);
  209. let i18n = key => i18nLib[lang][key] || `i18n[${lang}][${key}] not found`;
  210.  
  211. // ============================ url 页面判断 ==============================
  212. let isArtworkPage = () => /.+artworks\/\d+.*/.test(location.href);
  213.  
  214. let isMemberIndexPage = () => /.+member.php.*id=\d+.*/.test(location.href);
  215. let isMemberIllustPage = () => /.+\/member_illust\.php\?id=\d+/.test(location.href);
  216. let isMemberBookmarkPage = () => /.+\/bookmark\.php\?id=\d+/.test(location.href);
  217. let isMemberFriendPage = () => /.+\/mypixiv_all\.php\?id=\d+/.test(location.href);
  218. let isMemberDynamicPage = () => /.+\/stacc.+/.test(location.href);
  219. let isMemberPage = () => isMemberIndexPage() || isMemberIllustPage() || isMemberBookmarkPage() || isMemberFriendPage(),
  220. isSearchPage = () => /.+\/search\.php.*/.test(location.href);
  221.  
  222. // 判断是否登录(不可用)
  223. if (!isLogin()) {
  224. alert(i18n('loginWarning'));
  225. }
  226.  
  227. // 1. 屏蔽广告, 全局进行css处理
  228. (function () {
  229. // 1. 删除静态添加的广告
  230. $('.ad').remove();
  231. $('._premium-lead-tag-search-bar').hide();
  232. $('.popular-introduction-overlay').hide();// 移除热门图片遮罩层
  233. $('.ad-footer').remove();//移除页脚广告
  234.  
  235. // 2. 删除动态添加的广告
  236. let adSelectors = ['iframe', '._premium-lead-promotion-banner'];
  237.  
  238. observerFactory(function (mutations, observer) {
  239. mutations.forEach(function (mutation) {
  240. if (mutation.type !== 'childList') {
  241. return;
  242. }
  243. let $parent = $(mutation.target).parent();
  244. // 2.1. 隐藏广告
  245. let $ad = $parent.find(adSelectors.join(','));
  246. $ad.hide();
  247. });
  248. });
  249. })();
  250.  
  251. // 2. 使用users入り的方式进行搜索, 优先显示高质量作品
  252. (function () {
  253. let label = i18n('favorites'); // users入り
  254. let $select = $(`
  255. <select id="select-ahao-favorites">
  256. <option value=""></option>
  257. <option value="20000users入り">20000users入り</option>
  258. <option value="10000users入り">10000users入り</option>
  259. <option value="5000users入り" > 5000users入り</option>
  260. <option value="1000users入り" > 1000users入り</option>
  261. <option value="500users入り" > 500users入り</option>
  262. <option value="300users入り" > 300users入り</option>
  263. <option value="100users入り" > 100users入り</option>
  264. <option value="50users入り" > 50users入り</option>
  265. </select>`);
  266.  
  267. // 1. 初始化通用页面UI
  268. (function () {
  269. let enable = (isArtworkPage() || isMemberPage() || isSearchPage());
  270. if (enable) {
  271. return;
  272. }
  273. console.log("初始化通用页面 按收藏数搜索");
  274. let icon = $('._discovery-icon').attr('src');
  275. let $menu = $(`
  276. <div class="menu-group">
  277. <a class="menu-item js-click-trackable-later">
  278. <img class="_howto-icon" src="${icon}">
  279. <span class="label">${label}:</span>
  280. <!--select-->
  281. </a>
  282. </div>`);
  283. $menu.find('span.label').after($select);
  284. $('.navigation-menu-right').append($menu);
  285.  
  286. })();
  287.  
  288. // 2. 初始化作品页面和画师页面UI
  289. (function () {
  290. let enable = !(isArtworkPage() || isMemberPage() || isSearchPage());
  291. if (enable) {
  292. return;
  293. }
  294. console.log("初始化作品页面 按收藏数搜索");
  295. let discoverySelector = 'a[href="/discovery"]';
  296. observerFactory(function (mutations, observer) {
  297. for (let i = 0, len = mutations.length; i < len; i++) {
  298. let mutation = mutations[i];
  299.  
  300. // 1. 判断是否改变节点, 或者是否有[发现]节点
  301. // let $discovery = $(mutation.target).find(discoverySelector);
  302. let $discovery = $(discoverySelector);
  303. if (mutation.type !== 'childList' || $discovery.length <= 0) {
  304. continue;
  305. }
  306.  
  307. // 2. clone [发现]节点, 移除href属性, 避免死循环
  308. let $tabGroup = $discovery.closest('div');
  309. let $tab = $discovery.closest('ul').clone();
  310. $tab.find(discoverySelector).attr('href', 'javascript:void(0)');
  311.  
  312. // 3. 加入dom中
  313. $tabGroup.prepend($tab);
  314. $tab.find('a').contents().last()[0].textContent = label;
  315. $tab.find('a').after($select);
  316.  
  317. observer.disconnect();
  318. break;
  319. }
  320. });
  321. })();
  322.  
  323. // 3. 如果已经有搜索字符串, 就在改变选项时直接搜索
  324. $('body').on('change', '#select-ahao-favorites', function () {
  325. if (!!$('input[name="word"]').val()) {
  326. $('form[action="/search.php"]').submit();
  327. }
  328. });
  329.  
  330. // 4. 在提交搜索前处理搜索关键字
  331. $('form[action="/search.php"]').submit(function () {
  332. let $text = $(this).find('input[name="word"]');
  333. let $favorites = $('#select-ahao-favorites');
  334. // 2.4.1. 去除旧的搜索选项
  335. $text.val((index, val) => val.replace(/\d*users入り/g, ''));
  336. // 2.4.2. 去除多余空格
  337. $text.val((index, val) => val.replace(/\s\s+/g, ' '));
  338. // 2.4.3. 添加新的搜索选项
  339. $text.val((index, val) => `${val} ${$favorites.val()}`);
  340. });
  341. })();
  342.  
  343. // 3. 追加搜索pid和uid功能
  344. (function () {
  345. let enable = (isArtworkPage() || isMemberPage() || isSearchPage());
  346. if (enable) {
  347. return;
  348. }
  349. console.log("初始化通用页面 搜索UID和PID");
  350. let initSearch = function (option) {
  351. let options = $.extend({right: '0px', placeholder: '', url: ''}, option);
  352.  
  353. // 1. 初始化表单UI
  354. let $form = $(`<form class="ui-search" style="position: static;width: 100px;">
  355. <div class="container" style="width:80%;">
  356. <input class="ahao-input" placeholder="${options.placeholder}" style="width:80%;"/>
  357. </div>
  358. <input type="submit" class="submit sprites-search-old" value="">
  359. </form>`);
  360. let $div = $('<div class="ahao-search"></div>').css('position', 'absolute')
  361. .css('bottom', '44px').css('height', '30px').css('right', options.right);
  362. $div.append($form);
  363. $('#suggest-container').before($div);
  364.  
  365. // 2. 绑定submit事件
  366. $form.submit(function (e) {
  367. e.preventDefault();
  368.  
  369. let $input = $(this).find('.ahao-input');
  370. let id = $input.val();
  371. // 2.1. ID 必须为纯数字
  372. if (!/^[0-9]+$/.test(id)) {
  373. let label = options.placeholder + i18n('illegal');
  374. alert(label);
  375. return;
  376. }
  377. // 2.2. 新窗口打开url
  378. let url = option.url + id;
  379. window.open(url);
  380. // 2.3. 清空input等待下次输入
  381. $input.val('');
  382. });
  383. };
  384. // 1. UID搜索
  385. initSearch({right: '235px', placeholder: 'UID', url: 'https://www.pixiv.net/member.php?id='});
  386. // 2. PID搜索
  387. initSearch({
  388. right: '345px',
  389. placeholder: 'PID',
  390. url: 'https://www.pixiv.net/member_illust.php?mode=medium&illust_id='
  391. });
  392. })(); // 初始化通用页面UI
  393. (function () {
  394. let enable = !(isArtworkPage() || isMemberPage() || isSearchPage());
  395. if (enable) {
  396. return;
  397. }
  398. console.log("初始化作品页面 搜索UID和PID");
  399.  
  400. observerFactory(function (mutations, observer) {
  401. for (let i = 0, len = mutations.length; i < len; i++) {
  402. let mutation = mutations[i];
  403. // 1. 判断是否改变节点, 或者是否有[form]节点
  404. // let $form = $(mutation.target).find(formSelector);
  405. let $form = $('input[name="word"]').parent('form');
  406. if (mutation.type !== 'childList' || !$form.length) {
  407. continue;
  408. }
  409.  
  410. // 2. 使用flex布局包裹
  411. let $flexBox = $('<div></div>').css('display', 'flex');
  412. $form.wrap($flexBox);
  413. $flexBox = $form.closest('div');
  414.  
  415. let initSearch = function (option) {
  416. let options = $.extend({placeholder: '', url: ''}, option);
  417.  
  418. // 1. clone form表单
  419. let $cloneForm = $form.clone();
  420. $cloneForm.attr('action', '').addClass('ahao-search').css('margin-right', '15px').css('width', '126px');
  421. $cloneForm.find('input[name="s_mode"]').remove(); // 只保留一个input
  422. $cloneForm.find('input:first').attr('placeholder', options.placeholder).attr('name', options.placeholder).css('width', '64px').val('');
  423. $flexBox.prepend($cloneForm);
  424.  
  425. // 2. 绑定submit事件
  426. $cloneForm.submit(function (e) {
  427. e.preventDefault();
  428.  
  429. let $input = $(this).find(`input[name="${options.placeholder}"]`);
  430. let id = $input.val();
  431. // ID 必须为纯数字
  432. if (!/^[0-9]+$/.test(id)) {
  433. var label = options.placeholder + i18n('illegal');
  434. alert(label);
  435. return;
  436. }
  437. // 新窗口打开url
  438. let url = option.url + id;
  439. window.open(url);
  440. // 清空input等待下次输入
  441. $input.val('');
  442. });
  443. };
  444. // 3. UID搜索
  445. initSearch({placeholder: 'UID', url: 'https://www.pixiv.net/member.php?id='});
  446. // 4. PID搜索
  447. initSearch({placeholder: 'PID', url: 'https://www.pixiv.net/member_illust.php?mode=medium&illust_id='});
  448. observer.disconnect();
  449. break;
  450. }
  451. });
  452. })(); // 初始化作品页面和画师页面UI
  453.  
  454. // 4. 单张图片替换为原图格式. 追加下载按钮, 下载gif图、gif的帧压缩包、多图
  455. (async function () {
  456. if (!isArtworkPage()) {
  457. return;
  458. }
  459. // 1. 初始化方法
  460. let initDownloadBtn = function (option) {
  461. // 下载按钮, 复制分享按钮并旋转180度
  462. let options = $.extend({ $shareButtonContainer: undefined, id: '', text: '', clickFun: () => {} }, option);
  463. let $downloadButtonContainer = options.$shareButtonContainer.clone();
  464. $downloadButtonContainer.addClass('ahao-download-btn')
  465. .attr('id', options.id)
  466. .removeClass(options.$shareButtonContainer.attr('class'))
  467. .css('margin-right', '10px')
  468. .css('position', 'relative')
  469. .css('border', '1px solid')
  470. .css('padding', '1px 10px')
  471. .append(`<p style="display: inline">${options.text}</p>`);
  472. $downloadButtonContainer.find('button').css('transform', 'rotate(180deg)')
  473. .on('click', options.clickFun);
  474. options.$shareButtonContainer.after($downloadButtonContainer);
  475. return $downloadButtonContainer;
  476. };
  477. let addImgSize = async function (option) {
  478. // 从 $img 获取图片大小, after 到 $img
  479. let options = $.extend({
  480. $img: undefined,
  481. position: 'absolute',
  482. }, option);
  483. let $img = options.$img, position = options.position;
  484. if ($img.length !== 1) {
  485. return;
  486. }
  487. GM.getValue(GMkeys.switchImgSize, true).then(open => {
  488. if (!!open) {
  489. // 1. 找到 显示图片大小 的 span, 没有则添加
  490. let $span = $img.next('span');
  491. if ($span.length <= 0) {
  492. // 添加前 去除失去依赖的 span
  493. $('body').find('.ahao-img-size').each(function () {
  494. let $this = $(this), $prev = $this.prev('canvas, img');
  495. if ($prev.length <= 0) {
  496. $this.remove();
  497. }
  498. });
  499. $img.after(`<span class="ahao-img-size" style="position: ${position}; right: 0; top: 28px;
  500. color: #ffffff; font-size: x-large; font-weight: bold; -webkit-text-stroke: 1.0px #000000;"></span>`);
  501. }
  502. // 2. 根据标签获取图片大小, 目前只有 canvas 和 img 两种
  503. if ($img.prop('tagName') === 'IMG') {
  504. let img = new Image();
  505. img.src = $img.attr('src');
  506. img.onload = function () {
  507. $span.text(`${this.width}x${this.height}`);
  508. };
  509. } else {
  510. let width = $img.attr('width') || $img.css('width').replace('px', '') || $img.css('max-width').replace('px', '') || 0;
  511. let height = $img.attr('height') || $img.css('height').replace('px', '') || $img.css('max-height').replace('px', '') || 0;
  512. $span.text(`${width}x${height}`);
  513. }
  514. }
  515. });
  516. };
  517. let mimeType = suffix => {
  518. let lib = {png: "image/png", jpg: "image/jpeg", gif: "image/gif"};
  519. return lib[suffix] || `mimeType[${suffix}] not found`;
  520. };
  521. let getDownloadName = (name) => {
  522. name = name.replace('{pid}', illust().illustId);
  523. name = name.replace('{uid}', illust().userId);
  524. name = name.replace('{pname}', illust().illustTitle);
  525. name = name.replace('{uname}', illust().userName);
  526. return name;
  527. };
  528. let isMoreMode = () => illust().pageCount > 1,
  529. isGifMode = () => illust().illustType === 2,
  530. isSingleMode = () => (illust().illustType === 0 || illust().illustType === 1) && illust().pageCount === 1;
  531. let selectorShareBtn = await GM.getValue(GMkeys.selectorShareBtn, '.UXmvz'); // section 下的 div
  532.  
  533. // 热修复下载按钮的className
  534. observerFactory(function (mutations, observer) {
  535. for (let i = 0, len = mutations.length; i < len; i++) {
  536. let mutation = mutations[i], $target = $(mutation.target);
  537. if($target.prop('tagName').toLowerCase() !== 'section') continue;
  538. let $section = $target.find('section');
  539. if($section.length <= 0) continue;
  540. let className = $section.eq(0).children('div').eq(1).attr('class').split(' ')[1];
  541. GM.setValue(GMkeys.selectorShareBtn, `.${className}`);
  542. observer.disconnect();
  543. return;
  544. }
  545. });
  546. // 显示单图、多图原图
  547. observerFactory({
  548. callback: function (mutations, observer) {
  549. for (let i = 0, len = mutations.length; i < len; i++) {
  550. let mutation = mutations[i], $target = $(mutation.target);
  551. let replaceImg = function ($target, attr, value) {
  552. let oldValue = $target.attr(attr);
  553. if (new RegExp(`.*i\.pximg\.net.*\/${illust().id}_.*`).test(oldValue) && !/.+original.+/.test(oldValue)) {
  554. $target.attr(attr, value).css('filter', 'none');
  555. $target.fitWindow();
  556. }
  557. };
  558.  
  559. // 1. 单图、多图 DOM 结构都为 <a href=""><img/></a>
  560. let $link = $target.find('img[srcset]');
  561. $link.each(function () {
  562. let $this = $(this);
  563. let href = $this.parent('a').attr('href');
  564. if(!!href) {
  565. replaceImg($this, 'src', href);
  566. replaceImg($this, 'srcset', href);
  567. addImgSize({$img: $this}); // 显示图片大小
  568. }
  569. });
  570.  
  571. // 2. 移除马赛克遮罩, https://www.pixiv.net/member_illust.php?mode=medium&illust_id=50358638
  572. // $('.e2p8rxc2').hide(); // 懒得适配了, 自行去个人资料设置 https://www.pixiv.net/setting_user.php
  573. }
  574. },
  575. option: {attributes: true, childList: true, subtree: true, attributeFilter: ['src', 'srcset', 'href']}
  576. });
  577. // 下载动图帧zip, gif图
  578. observerFactory(function (mutations, observer) {
  579. for (let i = 0, len = mutations.length; i < len; i++) {
  580. let mutation = mutations[i], $target = $(mutation.target);
  581.  
  582. // 1. 单图、多图、gif图三种模式
  583. let $shareBtn = $target.find(selectorShareBtn), $canvas = $target.find('canvas');
  584. // 2. 显示图片大小
  585. addImgSize({$img: $canvas})
  586. if (!isGifMode() || mutation.type !== 'childList' ||
  587. $shareBtn.length <= 0 ||
  588. $target.find('#ahao-download-zip').length > 0) {
  589. continue
  590. }
  591. console.log('下载gif图');
  592.  
  593.  
  594.  
  595. // 3. 初始化 下载按钮
  596. let $zipBtn = initDownloadBtn({
  597. $shareButtonContainer: $shareBtn,
  598. id: 'ahao-download-zip',
  599. text: 'zip',
  600. });
  601. let $gifBtn = initDownloadBtn({
  602. $shareButtonContainer: $shareBtn,
  603. id: 'ahao-download-gif',
  604. text: 'gif',
  605. clickFun: function () {
  606. // 从 pixiv 官方 api 获取 gif 的数据
  607. $.ajax({
  608. url: `/ajax/illust/${illust().illustId}/ugoira_meta`, dataType: 'json',
  609. success: response => {
  610. // 1. 初始化 gif 下载按钮 点击事件
  611. // GIF_worker_URL 来自 https://gf.qytechs.cn/scripts/2963-gif-js/code/gifjs.js?version=8596
  612. let gifUrl, gifFrames = [],
  613. gifFactory = new GIF({workers: 1, quality: 10, workerScript: GIF_worker_URL});
  614.  
  615. for (let frameIdx = 0, frames = response.body.frames, framesLen = frames.length; frameIdx < framesLen; frameIdx++) {
  616. let frame = frames[i],
  617. url = illust().urls.original.replace('ugoira0.', `ugoira${frameIdx}.`);
  618. GM.xmlHttpRequest({
  619. method: 'GET', url: url,
  620. headers: {referer: 'https://www.pixiv.net/'},
  621. overrideMimeType: 'text/plain; charset=x-user-defined',
  622. onload: function (xhr) {
  623. // 2. 转为blob类型
  624. let r = xhr.responseText, data = new Uint8Array(r.length), i = 0;
  625. while (i < r.length) {
  626. data[i] = r.charCodeAt(i);
  627. i++;
  628. }
  629. let suffix = url.split('.').splice(-1);
  630. let blob = new Blob([data], {type: mimeType(suffix)});
  631.  
  632. // 3. 压入gifFrames数组中, 手动同步sync
  633. let img = document.createElement('img');
  634. img.src = URL.createObjectURL(blob);
  635. img.width = illust().width;
  636. img.height = illust().height;
  637. img.onload = function () {
  638. gifFrames[frameIdx] = {frame: img, option: {delay: frame.delay}};
  639. if (Object.keys(gifFrames).length >= framesLen) {
  640. $.each(gifFrames, (i, f) => gifFactory.addFrame(f.frame, f.option));
  641. gifFactory.render();
  642. }
  643. };
  644. }
  645. });
  646. }
  647. gifFactory.on('progress', function (pct) {
  648. $gifBtn.find('p').text(`gif ${parseInt(pct * 100)}%`);
  649. });
  650. gifFactory.on('finished', function (blob) {
  651. gifUrl = URL.createObjectURL(blob);
  652. GM.getValue(GMkeys.downloadName, `{pid}`).then(name => {
  653. let $a = $(`<a href="${gifUrl}" download="${getDownloadName(name)}"></a>`);
  654. $gifBtn.find('button').wrap($a);
  655. });
  656.  
  657. });
  658. $gifBtn.find('button').off('click').on('click', () => {
  659. if (!gifUrl) {
  660. alert('Gif未加载完毕, 请稍等片刻!');
  661. return;
  662. }
  663. // Adblock 禁止直接打开 blob url, https://github.com/jnordberg/gif.js/issues/71#issuecomment-367260284
  664. // window.open(gifUrl);
  665. });
  666. }
  667. });
  668. }
  669. });
  670.  
  671. // 4. 控制是否预下载, 避免多个页面导致爆内存, 直接下载 zip
  672. $.ajax({
  673. url: `/ajax/illust/${illust().illustId}/ugoira_meta`, dataType: 'json',
  674. success: response => {
  675. GM.getValue(GMkeys.downloadName, `{pid}`).then(name => {
  676. let $a = $(`<a href="${response.body.originalSrc}" download="${getDownloadName(name)}"></a>`);
  677. $zipBtn.find('button').wrap($a);
  678. });
  679. }
  680. });
  681. GM.getValue(GMkeys.switchImgPreload, true).then(open => { if(open) { $gifBtn.find('button').click(); } });
  682.  
  683. // 5. 取消监听
  684. GM.getValue(GMkeys.MO, true).then(function (v) { if(!v) observer.disconnect(); });
  685. }
  686. });
  687. // 下载多图zip
  688. observerFactory(function (mutations, observer) {
  689. for (let i = 0, len = mutations.length; i < len; i++) {
  690. let mutation = mutations[i], $target = $(mutation.target);
  691.  
  692. // 1. 单图、多图、gif图三种模式
  693. let $shareBtn = $target.find(selectorShareBtn);
  694. if (!isMoreMode() || mutation.type !== 'childList' || !$shareBtn.length || !!$target.find('#ahao-download-zip').length) {
  695. continue
  696. }
  697. console.log('下载多图');
  698.  
  699. // 3. 初始化 图片数量, 图片url
  700. let zip = new JSZip();
  701. let downloaded = 0; // 下载完成数量
  702. let num = illust().pageCount; // 下载目标数量
  703. let url = illust().urls.original;
  704. let imgUrls = Array(parseInt(num)).fill()
  705. .map((value, index) => url.replace(/_p\d\./, `_p${index}.`));
  706.  
  707. // 4. 初始化 下载按钮, 复制分享按钮并旋转180度
  708. let $zipBtn = initDownloadBtn({
  709. $shareButtonContainer: $shareBtn,
  710. id: 'ahao-download-zip',
  711. text: `${i18n('download')}`,
  712. clickFun: function () {
  713. // 3.1. 下载图片, https://wiki.greasespot.net/GM.xmlHttpRequest
  714. if($(this).attr('start') !== 'true') {
  715. $(this).attr('start', true);
  716. $.each(imgUrls, function (index, url) {
  717. GM.xmlHttpRequest({
  718. method: 'GET', url: url,
  719. headers: {referer: 'https://www.pixiv.net/'},
  720. overrideMimeType: 'text/plain; charset=x-user-defined',
  721. onload: function (xhr) {
  722. // 4.1. 转为blob类型
  723. let r = xhr.responseText, data = new Uint8Array(r.length), i = 0;
  724. while (i < r.length) {
  725. data[i] = r.charCodeAt(i);
  726. i++;
  727. }
  728. let suffix = url.split('.').splice(-1);
  729. let blob = new Blob([data], {type: mimeType(suffix)});
  730.  
  731. // 4.2. 压缩图片
  732. GM.getValue(GMkeys.downloadName, `{pid}`).then(name => {
  733. zip.file(`${getDownloadName(name)}_${index}.${suffix}`, blob, {binary: true});
  734. });
  735.  
  736.  
  737. // 4.3. 手动sync, 避免下载不完全的情况
  738. downloaded++;
  739. $zipBtn.find('p').html(`${i18n('download')}${downloaded}/${num}`);
  740. }
  741. });
  742. });
  743. return;
  744. }
  745.  
  746. // 3.2. 手动sync, 避免下载不完全
  747. if (downloaded < num) {
  748. alert(i18n('download_wait'));
  749. return;
  750. }
  751. // 3.3. 使用jszip.js和FileSaver.js压缩并下载图片
  752. GM.getValue(GMkeys.downloadName, `{pid}`).then(name => {
  753. zip.generateAsync({type: 'blob', base64: true})
  754. .then(content => saveAs(content, getDownloadName(name)));
  755. });
  756. }
  757. });
  758.  
  759. // 4. 控制是否预下载, 避免多个页面导致爆内存
  760. GM.getValue(GMkeys.switchImgPreload, true).then(open => { if(open) { $zipBtn.find('button').click(); } });
  761.  
  762. // 5. 取消监听
  763. GM.getValue(GMkeys.MO, true).then(function (v) { if(!v) observer.disconnect(); });
  764. }
  765. });
  766. })();
  767.  
  768. // 5. 在画师页面和作品页面显示画师id、画师背景图, 用户头像允许右键保存
  769. observerFactory(function (mutations, observer) {
  770. if (!isMemberIndexPage()) {
  771. return;
  772. }
  773. for (let i = 0, len = mutations.length; i < len; i++) {
  774. let mutation = mutations[i];
  775. // 1. 判断是否改变节点, 或者是否有[section]节点
  776. let $target = $(mutation.target), externalLinksContainer = '_2AOtfl9'; // 多个反混淆externalLinksContainer
  777. let $row = $(`ul.${externalLinksContainer}`).parent();
  778. if (mutation.type !== 'childList' || $row.length <= 0 || $('body').find('#uid').length > 0) {
  779. continue;
  780. }
  781. // 1. 添加新的一行的div
  782. let $ahaoRow = $row.clone(), $ul = $ahaoRow.find('ul');
  783. $ul.empty();
  784. $row.before($ahaoRow);
  785.  
  786. // 2. 显示画师id, 点击自动复制到剪贴板
  787. let $uid = $(`<li id="uid"><div style="font-size: 20px;font-weight: 700;color: #333;margin-right: 8px;line-height: 1">UID:${uid}</div></li>`)
  788. .on('click', function () {
  789. let $this = $(this);
  790. $this.html(`<span>UID${i18n('copy_to_clipboard')}</span>`);
  791. GM.setClipboard(uid);
  792. setTimeout(function () {
  793. $this.html(`<span>UID${uid}</span>`);
  794. }, 2000);
  795. });
  796. $ul.append($uid);
  797.  
  798. // 3. 显示画师背景图
  799. let background = preloadData.user[uid].background;
  800. let url = (background && background.url) || '';
  801. let $bgli = $('<li><div style="font-size: 20px;font-weight: 700;color: #333;margin-right: 8px;line-height: 1"></div></li>'),
  802. $bg = $bgli.find('div');
  803. if (!!url && url !== 'none') {
  804. $bg.append(`<img src="${url}" width="30px"><a target="_blank" href="${url}">${i18n('background')}</a>`);
  805. } else {
  806. $bg.append(`<span>${i18n('background_not_found')}</span>`);
  807. }
  808. $ul.append($bgli);
  809.  
  810. // 4. 取消监听
  811. GM.getValue(GMkeys.MO, true).then(function (v) { if(!v) observer.disconnect(); });
  812. }
  813. }); // 画师页面UI
  814. observerFactory(function (mutations, observer) {
  815. if (!isArtworkPage()) {
  816. return;
  817. }
  818.  
  819. for (let i = 0, len = mutations.length; i < len; i++) {
  820. let mutation = mutations[i];
  821. // 1. 判断是否改变节点, 或者是否有[section]节点
  822. let $aside = $(mutation.target).parent().find('main').next('aside');
  823. if (mutation.type !== 'childList' || $aside.length <= 0) {
  824. continue;
  825. }
  826.  
  827. let $row = $aside.find('section:first').find('h2');
  828. if ($row.length <= 0 || $aside.find('#ahao-background').length > 0) {
  829. continue;
  830. }
  831.  
  832. // 2. 显示画师背景图
  833. let background = preloadData.user[uid].background;
  834. let url = (background && background.url) || '';
  835. let $bgDiv = $row.clone().attr('id', 'ahao-background');
  836. $bgDiv.children('a').remove();
  837. $bgDiv.children('div').children('div').remove();
  838. $bgDiv.prepend(`<img src="${url}" width="10%"/>`);
  839. $bgDiv.find('div a').attr('href', !!url ? url : 'javascript:void(0)').attr('target', '_blank')
  840. .text(!!url ? i18n('background') : i18n('background_not_found'));
  841. $row.after($bgDiv);
  842.  
  843. // 3. 显示画师id, 点击自动复制到剪贴板
  844. let $uid = $row.clone();
  845. $uid.children('a').remove();
  846. $uid.children('div').children('div').remove();
  847. $uid.find('a').attr('href', 'javascript:void(0)').attr('id', 'ahao-uid').text('UID: ' + uid);
  848. $uid.on('click', function () {
  849. let $this = $(this);
  850. $this.find('a').text('UID' + i18n('copy_to_clipboard'));
  851. GM.setClipboard(uid);
  852. setTimeout(function () {
  853. $this.find('a').text('UID: ' + uid);
  854. }, 2000);
  855. });
  856. $bgDiv.after($uid);
  857.  
  858. // 4. 取消监听
  859. GM.getValue(GMkeys.MO, true).then(function (v) { if(!v) observer.disconnect(); });
  860. }
  861. }); // 作品页面UI
  862. // 解除 用户头像 的background 限制, 方便保存用户头像
  863. observerFactory(function (mutations, observer) {
  864. for (let i = 0, len = mutations.length; i < len; i++) {
  865. let mutation = mutations[i];
  866. // 1. 判断是否改变节点
  867. if (mutation.type !== 'childList') {
  868. continue;
  869. }
  870.  
  871. // 2. 将作者头像由 background 转为 <img>
  872. let $target = $(mutation.target);
  873. $target.find('div[role="img"]').each(function () {
  874. let $this = $(this);
  875. let tagName = $this.prop('tagName');
  876.  
  877. let imgUrl = $this.getBackgroundUrl();
  878. if (!imgUrl) {
  879. return;
  880. }
  881.  
  882. let $userImg = $('<img class="ahao-user-img" src=""/>').attr('src', imgUrl);
  883. $userImg.css('width', $this.css('width'))
  884. .css('height', $this.css('height'));
  885.  
  886. // if(tagName.toLowerCase() === 'a') {
  887. // $this.html($userImg);
  888. // $this.css('background-image', '');
  889. // return;
  890. // }
  891.  
  892. if (tagName.toLowerCase() === 'div') {
  893. $userImg.attr('class', $this.attr('class'));
  894. $userImg.html($this.html());
  895. $this.replaceWith(() => $userImg);
  896. return;
  897. }
  898. });
  899.  
  900. // 3. 将评论头像由 background 转为 <img>
  901. $target.find('a[data-user_id][data-src]').each(function () {
  902. let $this = $(this), $div = $this.find('div'),
  903. $img = $('<img/>');
  904. $img.attr('src', $this.attr('data-src'));
  905. if (!!$div.length) {
  906. $img.attr('class', $div.attr('class'))
  907. .css('width', $div.css('width'))
  908. .css('height', $div.css('height'));
  909. $this.html($img);
  910. }
  911. });
  912. }
  913. });
  914.  
  915. // 6. 自动加载评论
  916. GM.getValue(GMkeys.switchComment, true).then(open => {
  917. if(!open || !isArtworkPage()){
  918. return;
  919. }
  920. let moreCommentSelector = '._1Hom0qN';
  921. let moreReplaySelector = '._28zR1MQ';
  922. observerFactory(function (mutations, observer) {
  923. for (let i = 0, len = mutations.length; i < len; i++) {
  924. let mutation = mutations[i];
  925. // 1. 判断是否改变节点
  926. if (mutation.type !== 'childList') {
  927. continue;
  928. }
  929. // 2. 模拟点击加载按钮
  930. let $moreCommentBtn = $(mutation.target).find(moreCommentSelector);
  931. $moreCommentBtn.click();
  932.  
  933. let $moreReplayBtn = $(mutation.target).find(moreReplaySelector);
  934. $moreReplayBtn.click();
  935. }
  936. });
  937. });
  938.  
  939. // 7. 对主页动态中的图片标记作品类型
  940. (function () {
  941. if (!isMemberDynamicPage()) {
  942. return;
  943. }
  944.  
  945. let illustTitleSelector = '.stacc_ref_illust_title';
  946. observerFactory(function (mutations, observer) {
  947. for (let i = 0, len = mutations.length; i < len; i++) {
  948. let mutation = mutations[i];
  949. // 1. 判断是否改变节点
  950. let $title = $(mutation.target).find(illustTitleSelector);
  951. if (mutation.type !== 'childList' || !$title.length) {
  952. continue;
  953. }
  954.  
  955. $title.each(function () {
  956. let $a = $(this).find('a');
  957. // 1. 已经添加过标记的就不再添加
  958. if (!!$a.attr('ahao-illust-id')) {
  959. return;
  960. }
  961. // 2. 获取pid, 设置标记避免二次生成
  962. let illustId = new URL(location.origin + '/' + $a.attr('href')).searchParams.get('illust_id');
  963. $a.attr('ahao-illust-id', illustId);
  964. // 3. 调用官方api, 判断作品类型
  965. $.ajax({
  966. url: '/ajax/illust/' + illustId, dataType: 'json',
  967. success: response => {
  968. let illustType = parseInt(response.body.illustType);
  969. let isMultiPic = parseInt(response.body.pageCount) > 1;
  970. switch (illustType) {
  971. case 0:
  972. case 1:
  973. $a.after('<p>' + (isMultiPic ? i18n('illust_type_multiple') : i18n('illust_type_single')) + '</p>');
  974. break;
  975. case 2:
  976. $a.after('<p>' + i18n('illust_type_gif') + '</p>');
  977. break;
  978. }
  979. }
  980. });
  981. })
  982. }
  983. });
  984. })();
  985.  
  986. // 8. 对jump.php取消重定向
  987. (function () {
  988. let jumpSelector = 'a[href*="jump.php"]';
  989.  
  990. observerFactory(function (mutations, observer) {
  991. for (let i = 0, len = mutations.length; i < len; i++) {
  992. let mutation = mutations[i];
  993. // 1. 判断是否改变节点
  994. if (mutation.type !== 'childList') {
  995. continue;
  996. }
  997. // 2. 修改href
  998. let $jump = $(mutation.target).find(jumpSelector);
  999. $jump.each(function () {
  1000. let $this = $(this), url = $this.attr('href').match(/jump\.php\?(url=)?(.*)$/)[2];
  1001. $this.attr('href', decodeURIComponent(url));
  1002. });
  1003. }
  1004. });
  1005. })();
  1006.  
  1007. // 9. 单页排序
  1008. (function () {
  1009. if (!isSearchPage() || true) {
  1010. return;
  1011. }
  1012. // 9.1. 生成按收藏数排序的按钮
  1013. observerFactory(function (mutations, observer) {
  1014. for (let i = 0, len = mutations.length; i < len; i++) {
  1015. let mutation = mutations[i];
  1016. // 1. 判断是否改变节点
  1017. let $section = $('section');
  1018. if (mutation.type !== 'childList' || $section.length <= 0) {
  1019. continue;
  1020. }
  1021. let $div = $section.prev().find('div').eq(0);
  1022.  
  1023. // 2. 添加按收藏数排序的按钮
  1024. let $sort = $(`<span class="sc-LzLvL">${i18n('sort_by_popularity')}</span>`);
  1025. $sort.on('click', function () {
  1026. var value = !$(this).hasClass('bNPzQX');
  1027. console.log(value);
  1028. GM.setValue(GMkeys.switchOrderByPopular, value);
  1029. if (value) {
  1030. $sort.attr('class', 'sc-LzLvL bNPzQX');
  1031. } else {
  1032. $sort.attr('class', 'sc-LzLvL lfAMBc');
  1033. }
  1034. });
  1035. $div.prepend($sort);
  1036. GM.getValue(GMkeys.switchOrderByPopular, true).then(value => {
  1037. if (value) {
  1038. $sort.attr('class', 'sc-LzLvL bNPzQX');
  1039. } else {
  1040. $sort.attr('class', 'sc-LzLvL lfAMBc');
  1041. }
  1042. });
  1043.  
  1044. observer.disconnect();
  1045. break;
  1046. }
  1047. });
  1048.  
  1049. // 9.2. 按收藏数排序 // TODO 页面没有展示收藏数, 关闭单页排序
  1050. observerFactory(function (mutations, observer) {
  1051. for (let i = 0, len = mutations.length; i < len; i++) {
  1052. let mutation = mutations[i];
  1053. // 1. 判断是否改变节点
  1054. let $div = $(mutation.target);
  1055. if (mutation.type !== 'childList' || $div.find('.count-list').length > 0) {
  1056. continue;
  1057. }
  1058.  
  1059. // 2. 获取所有的item, 排序并填充
  1060. GM.getValue(GMkeys.switchOrderByPopular, true).then(value => {
  1061. if(!value) {
  1062. return;
  1063. }
  1064. let $container = $('section#js-react-search-mid').find('div:first');
  1065. let $list = $container.children();
  1066. let getCount = $ => parseInt($.find('ul.count-list a').text()) || 0;
  1067. $list.sort((a, b) => getCount($(b)) - getCount($(a)));
  1068. $container.html($list);
  1069.  
  1070. });
  1071. return; // 本次变更只排序一次
  1072. }
  1073. });
  1074. })();
  1075.  
  1076. // 10. 兼容模式检测是否PJAX并刷新页面, https://stackoverflow.com/a/4585031/6335926
  1077. (function(history){
  1078. let pushState = history.pushState;
  1079. history.pushState = function(state) {
  1080. if (typeof history.onpushstate == "function") {
  1081. history.onpushstate({state: state});
  1082. }
  1083. GM.getValue(GMkeys.MO, true).then(function (enableMO) {
  1084. if(enableMO) { return; }
  1085. location.reload();
  1086. });
  1087. return pushState.apply(history, arguments);
  1088. };
  1089. })(window.history);
  1090.  
  1091. // 11. 控制面板
  1092. (function () {
  1093. if(!/.+setting_user\.php.*/.test(location.href)) {
  1094. return;
  1095. }
  1096.  
  1097. let $table = $(`<table style="width: 700px;">
  1098. <tbody>
  1099. <tr><th width="185">Pixiv增强配置</th><td width="500">
  1100. <label><input type="checkbox" name="${GMkeys.MO}">兼容PJAX(推荐)</label><br/>
  1101. <label><input type="checkbox" name="${GMkeys.switchComment}">自动加载评论</label><br/>
  1102. <label><input type="checkbox" name="${GMkeys.switchImgSize}">显示图片尺寸大小</label><br/>
  1103. <label><input type="checkbox" name="${GMkeys.switchImgPreload}">预下载GifZip(耗流量)</label><br/>
  1104.  
  1105. <label>下载文件名: <input type="text" name="${GMkeys.downloadName}" placeholder="{pid}-{uid}-{pname}-{uname}"></label>
  1106. <a>保存</a>
  1107. <a onclick="alert('{pid}是作品id--------{uid}是画师id\\n{pname}是作品名--------{uname}是画师名\\n注意, 多图情况下, 会自动填充index索引编号\\n目前只支持GIF和多图的重命名');">说明</a>
  1108. </td></tr>
  1109. </tbody>
  1110. </table>`);
  1111. $('.settingContent table:first').after($table);
  1112.  
  1113. $table.find('input[type="checkbox"]').each(function () {
  1114. let $checkbox = $(this), name = $checkbox.attr('name');
  1115. GM.getValue(name, true).then(function (value) { $checkbox.prop('checked', value); });
  1116. $checkbox.on('change', function () {
  1117. let checked = $checkbox.prop('checked');
  1118. $checkbox.prop(checked, checked);
  1119. GM.setValue(name, checked);
  1120. });
  1121. });
  1122. $table.find('input[type="text"]').each(function () {
  1123. let $input = $(this), name = $input.attr('name');
  1124. GM.getValue(name).then(function (value) { $input.val(value); });
  1125. $input.on('change', () => {
  1126. GM.setValue(name, $input.val());
  1127. });
  1128. });
  1129. })();
  1130.  
  1131. //TODO 增强新页面fanbox https://www.pixiv.net/fanbox/creator/22926661?utm_campaign=www_profile&utm_medium=site_flow&utm_source=pixiv
  1132. //TODO 日语化
  1133. //TODO 搜索框ui混乱 https://www.pixiv.net/member_illust.php?mode=medium&illust_id=899657
  1134. });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址