Greasy Fork镜像 tweaks

various tweaks for gf.qytechs.cn site for enhanced usability and additional features

  1. // ==UserScript==
  2. // @name Greasy Fork镜像 tweaks
  3. // @namespace almaceleste
  4. // @version 0.6.3
  5. // @description various tweaks for gf.qytechs.cn site for enhanced usability and additional features
  6. // @description:ru различные твики для сайта gf.qytechs.cn для повышения удобства использования и дополнительных функций
  7. // @author (ɔ) almaceleste (https://almaceleste.github.io)
  8. // @license AGPL-3.0-or-later; http://www.gnu.org/licenses/agpl.txt
  9. // @icon https://gf.qytechs.cn/assets/blacklogo16-bc64b9f7afdc9be4cbfa58bdd5fc2e5c098ad4bca3ad513a27b15602083fd5bc.png
  10. // @icon64 https://gf.qytechs.cn/assets/blacklogo96-e0c2c76180916332b7516ad47e1e206b42d131d36ff4afe98da3b1ba61fd5d6c.png
  11.  
  12. // @homepageURL https://gf.qytechs.cn/en/users/174037-almaceleste
  13. // @homepageURL https://openuserjs.org/users/almaceleste
  14. // @homepageURL https://github.com/almaceleste/userscripts
  15. // @supportURL https://github.com/almaceleste/userscripts/issues
  16.  
  17. // @require https://code.jquery.com/jquery-3.3.1.js
  18. // @require https://code.jquery.com/ui/1.12.1/jquery-ui.js
  19. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  20. // @grant GM_getValue
  21. // @grant GM_setValue
  22. // @grant GM_registerMenuCommand
  23. // @grant GM_openInTab
  24. // @grant GM_getResourceText
  25.  
  26. // @resource css https://github.com/almaceleste/userscripts/raw/master/css/default.css
  27.  
  28. // @match https://gf.qytechs.cn/*/users/*
  29. // @match https://gf.qytechs.cn/*/scripts*
  30. // ==/UserScript==
  31.  
  32. // ==OpenUserJS==
  33. // @author almaceleste
  34. // ==/OpenUserJS==
  35.  
  36. const route = {};
  37. route.userpage = /^\/.*\/users\/.*/;
  38. route.scriptpage = /^\/.*\/scripts\/[^\/]*$/; //(?!\/)*/; //.*/;
  39. route.searchpage = /^\/.*\/scripts$/;
  40.  
  41. const maincontainer = 'body > .width-constraint';
  42. const listitem = '.script-list > li';
  43. const separator = '.name-description-separator';
  44. const scriptversion = 'data-script-version';
  45. const scriptrating = 'dd.script-list-ratings';
  46. const scriptstats = '.inline-script-stats';
  47. const dailyinstalls = '.script-list-daily-installs';
  48. const totalinstalls = '.script-list-total-installs';
  49. const createddate = '.script-list-created-date';
  50. const updateddate = '.script-list-updated-date';
  51.  
  52. const scripturl = 'article h2 a';
  53.  
  54. const userprofile = {};
  55. userprofile.path = '#user-profile';
  56. userprofile.header = 'body > div.width-constraint > section:first-of-type > h2:first-of-type';
  57.  
  58. const sections = {};
  59. sections.controlpanel = '#control-panel';
  60. sections.discussions = '#user-discussions-on-scripts-written';
  61. sections.scriptsets = 'section:has(h3:contains("Script Sets"))';
  62.  
  63. const configId = 'greasyforktweaksCfg';
  64. const iconUrl = GM_info.script.icon64;
  65. const pattern = {};
  66. pattern[`#${configId}`] = /#configId/g;
  67. pattern[`${iconUrl}`] = /iconUrl/g;
  68.  
  69. let css = GM_getResourceText('css');
  70. Object.keys(pattern).forEach((key) => {
  71. css = css.replace(pattern[key], key);
  72. });
  73. const windowcss = css;
  74. const iframecss = `
  75. height: 590px;
  76. width: 435px;
  77. border: 1px solid;
  78. border-radius: 3px;
  79. position: fixed;
  80. z-index: 9999;
  81. `;
  82.  
  83. GM_registerMenuCommand(`${GM_info.script.name} Settings`, () => {
  84. GM_config.open();
  85. GM_config.frame.style = iframecss;
  86. });
  87.  
  88. GM_config.init({
  89. id: `${configId}`,
  90. title: `${GM_info.script.name} ${GM_info.script.version}`,
  91. fields: {
  92. width: {
  93. section: ['', 'All pages options'],
  94. label: 'page width',
  95. labelPos: 'left',
  96. type: 'text',
  97. default: '70%',
  98. },
  99. version: {
  100. label: 'add script version number',
  101. labelPos: 'right',
  102. type: 'checkbox',
  103. default: true,
  104. },
  105. ratingscore: {
  106. label: 'display script rating score',
  107. labelPos: 'right',
  108. type: 'checkbox',
  109. default: true,
  110. },
  111. updates: {
  112. label: 'display update checks information',
  113. labelPos: 'right',
  114. type: 'checkbox',
  115. default: true,
  116. },
  117. updatesperiods: {
  118. type: 'multiselect',
  119. options: {
  120. daily: 1,
  121. weekly: 7,
  122. monthly: 30,
  123. total: 0
  124. },
  125. default: {daily: 1, weekly: 7, monthly: 30, total: 0},
  126. },
  127. installs: {
  128. label: 'display alternative installs information',
  129. labelPos: 'right',
  130. type: 'checkbox',
  131. default: true,
  132. },
  133. installsperiods: {
  134. type: 'multiselect',
  135. options: {
  136. daily: 1,
  137. weekly: 7,
  138. monthly: 30,
  139. total: 0
  140. },
  141. default: {daily: 1, weekly: 7, monthly: 30, total: 0},
  142. },
  143. compact: {
  144. label: 'compact script information',
  145. labelPos: 'right',
  146. type: 'checkbox',
  147. default: true,
  148. },
  149. userprofile: {
  150. section: ['', 'User page options (own page and other users`)'],
  151. label: 'collapse user profile info on user page',
  152. labelPos: 'right',
  153. type: 'checkbox',
  154. default: true,
  155. },
  156. controlpanel: {
  157. label: 'collapse control panel on user page',
  158. labelPos: 'right',
  159. type: 'checkbox',
  160. default: true,
  161. },
  162. discussions: {
  163. label: 'collapse discussions on user page',
  164. labelPos: 'right',
  165. type: 'checkbox',
  166. default: true,
  167. },
  168. scriptsets: {
  169. label: 'collapse script sets on user page',
  170. labelPos: 'right',
  171. type: 'checkbox',
  172. default: true,
  173. },
  174. displayimage: {
  175. label: 'display script image (experimental)',
  176. labelPos: 'right',
  177. type: 'checkbox',
  178. default: true,
  179. },
  180. newtab: {
  181. section: ['', 'Other options'],
  182. label: 'open script page in new tab',
  183. labelPos: 'right',
  184. type: 'checkbox',
  185. default: true,
  186. },
  187. background: {
  188. label: 'open new tab in background',
  189. labelPos: 'right',
  190. type: 'checkbox',
  191. default: false,
  192. },
  193. insert: {
  194. label: 'insert new tab next to the current instead of the right end',
  195. labelPos: 'right',
  196. type: 'checkbox',
  197. default: true,
  198. },
  199. setParent: {
  200. label: 'return to the current tab after new tab closed',
  201. labelPos: 'right',
  202. type: 'checkbox',
  203. default: true,
  204. },
  205. support: {
  206. section: ['', 'Support'],
  207. label: 'almaceleste.github.io',
  208. title: 'more info on almaceleste.github.io',
  209. type: 'button',
  210. click: () => {
  211. GM_openInTab('https://almaceleste.github.io', {
  212. active: true,
  213. insert: true,
  214. setParent: true
  215. });
  216. }
  217. },
  218. },
  219. types: {
  220. multiselect: {
  221. default: {},
  222. toNode: function() {
  223. let field = this.settings,
  224. value = this.value,
  225. options = field.options,
  226. id = this.id,
  227. configId = this.configId,
  228. labelPos = field.labelPos,
  229. create = this.create;
  230.  
  231. // console.log('toNode:', field, value, options);
  232. function addLabel(pos, labelEl, parentNode, beforeEl) {
  233. if (!beforeEl) beforeEl = parentNode.firstChild;
  234. switch (pos) {
  235. case 'right': case 'below':
  236. if (pos == 'below')
  237. parentNode.appendChild(create('br', {}));
  238. parentNode.appendChild(labelEl);
  239. break;
  240. default:
  241. if (pos == 'above')
  242. parentNode.insertBefore(create('br', {}), beforeEl);
  243. parentNode.insertBefore(labelEl, beforeEl);
  244. }
  245. }
  246.  
  247. let retNode = create('div', {
  248. className: 'config_var multiselect',
  249. id: `${configId}_${id}_var`,
  250. title: field.title || ''
  251. }),
  252. firstProp;
  253. // Retrieve the first prop
  254. for (let i in field) { firstProp = i; break; }
  255. let label = field.label ? create('label', {
  256. className: 'field_label',
  257. id: `${configId}_${id}_field_label`,
  258. for: `${configId}_field_${id}`,
  259. }, field.label) : null;
  260. let wrap = create('ul', {
  261. id: `${configId}_field_${id}`
  262. });
  263. this.node = wrap;
  264.  
  265. for (const key in options) {
  266. // console.log('toNode:', key);
  267. const inputId = `${configId}_${id}_${key}_checkbox`;
  268. const li = wrap.appendChild(create('li', {
  269. }));
  270. li.appendChild(create('input', {
  271. checked: value.hasOwnProperty(key),
  272. id: inputId,
  273. type: 'checkbox',
  274. value: options[key],
  275. }));
  276. li.appendChild(create('label', {
  277. className: 'option_label',
  278. for: inputId,
  279. }, key));
  280. }
  281.  
  282. retNode.appendChild(wrap);
  283.  
  284. if (label) {
  285. // If the label is passed first, insert it before the field
  286. // else insert it after
  287. if (!labelPos)
  288. labelPos = firstProp == "label" ? "left" : "right";
  289. addLabel(labelPos, label, retNode);
  290. }
  291. return retNode;
  292. },
  293. toValue: function() {
  294. let node = this.node,
  295. id = node.id,
  296. options = this.settings.options,
  297. rval = {};
  298.  
  299. // console.log('toValue:', node, options, this);
  300.  
  301. if (!node) return rval;
  302.  
  303. let nodelist = node.querySelectorAll(`#${id} input:checked`);
  304. // console.log('nodelist:', document.querySelectorAll(`#${id} input:checked`), nodelist);
  305. nodelist.forEach((input) => {
  306. // console.log('toValue:', input);
  307. const value = input.value;
  308. const key = Object.keys(options).find((key) => options[key] == value);
  309. rval[key] = value;
  310. });
  311.  
  312. // console.log('toValue:', rval);
  313. return rval;
  314. },
  315. reset: function() {
  316. let node = this.node,
  317. values = this.default;
  318.  
  319. // console.log('reset:', node, values, Object.values(values));
  320. const inputs = node.getElementsByTagName('input');
  321. for (const index in inputs) {
  322. const input = inputs[index];
  323. // console.log('reset:', input.value, Object.values(values).includes(input.value) || Object.values(values).includes(+input.value));
  324. if (Object.values(values).includes(input.value) || Object.values(values).includes(+input.value)) {
  325. if (!input.checked) input.click();
  326. }
  327. else {
  328. if (input.checked) input.click();
  329. }
  330. }
  331. }
  332. }
  333. },
  334. css: windowcss,
  335. events: {
  336. save: function() {
  337. GM_config.close();
  338. }
  339. },
  340. });
  341.  
  342. function arrow(element){
  343. const arrow = $(`
  344. <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
  345. <style>
  346. .collapsed {
  347. transform: rotate(0deg);
  348. }
  349. .expanded {
  350. transform: rotate(180deg);
  351. }
  352. </style>
  353. <text x='0' y='18'>▼</text>
  354. </svg>
  355. `).css({
  356. fill: 'whitesmoke',
  357. height: '20px',
  358. width: '30px',
  359. });
  360. $(element).append(arrow);
  361. }
  362.  
  363. function collapse(element, header){
  364. $(element).css({
  365. cursor: 'pointer',
  366. });
  367. arrow($(element).find(header));
  368. $(element).accordion({
  369. collapsible: true,
  370. active: false,
  371. beforeActivate: () => {
  372. rotate($(element).find('svg'));
  373. }
  374. });
  375. }
  376.  
  377. function rotate(element){
  378. if ($(element).hasClass('expanded')) {
  379. $(element).animate({
  380. transform: 'rotate(0deg)',
  381. });
  382. }
  383. else {
  384. $(element).animate({
  385. transform: 'rotate(180deg)',
  386. });
  387. }
  388. $(element).toggleClass('expanded');
  389. }
  390.  
  391. function compact(first, second){
  392. $('dt' + first).each(function(){
  393. $(this).css('display','none');
  394. $(this).siblings('dt' + second).find('span').append(' (' + $(this).find('span').text() + ')');
  395. });
  396. $('dd' + first).each(function(){
  397. $(this).css('display','none');
  398. $(this).siblings('dd' + second).find('span').append(' (' + $(this).find('span').text() + ')');
  399. });
  400. }
  401.  
  402. function newtaber(e){
  403. const options = {active: !GM_config.get('background'), insert: GM_config.get('insert'), setParent: GM_config.get('setParent')};
  404. e.preventDefault();
  405. e.stopPropagation();
  406. GM_openInTab(e.target.href, options);
  407. }
  408.  
  409. function getjson(url){
  410. fetch(url).then((response) => {
  411. // console.log('getjson:', response);
  412. response.json().then((json) => {
  413. console.log('getjson:', json);
  414. });
  415. });
  416. }
  417.  
  418. function sumlast(array, number, prop){
  419. if (number != 0) {
  420. array = array.slice(-number);
  421. }
  422. let result = array.reduce((sum, next) => {
  423. return sum + next[prop];
  424. }, 0);
  425. return result;
  426. }
  427.  
  428. function getjsondata(url, prop, periods, target){
  429. fetch(url).then((response) => {
  430. response.json().then((json) => {
  431. const data = Object.values(json);
  432.  
  433. for (const period in periods) {
  434. const result = sumlast(data, periods[period], prop);
  435. $('<span></span>', {
  436. title: period,
  437. }).text(result).appendTo(target);
  438. }
  439. });
  440. });
  441. }
  442.  
  443. function doCompact(){
  444. if (GM_config.get('compact')){
  445. $(scriptstats).children().css('width','auto');
  446. compact(totalinstalls, dailyinstalls);
  447. compact(updateddate, createddate);
  448. }
  449. }
  450.  
  451. function doRating(page){
  452. switch (page) {
  453. case 'user':
  454. case 'search':
  455. $(scriptrating).each(function(){
  456. let rating = $(this).attr('data-rating-score');
  457. $(this).children('span').after(` - ${rating}`);
  458. });
  459. break;
  460. case 'script':
  461. $(scriptrating).each(function(){
  462. const author = '#script-stats > .script-show-author > span > a';
  463. const url = `${window.location.origin}${$(author).attr('href')}`;
  464. const scriptId = '#script-content > .script-in-sets > input[name="script_id"]';
  465. const id = $(scriptId).val();
  466.  
  467. fetch(url).then((response) => {
  468. response.text().then((data) => {
  469. const parser = new DOMParser();
  470. const doc = parser.parseFromString(data, 'text/html');
  471. const el = doc.querySelector(`#user-script-list li[data-script-id="${id}"]`);
  472.  
  473. $(this).children('span').after(` - ${el.dataset.scriptRatingScore}`);
  474. });
  475. });
  476. });
  477. break;
  478. default:
  479. break;
  480. }
  481. }
  482.  
  483. function doCollapse(){
  484. Object.keys(sections).forEach((section) => {
  485. if (GM_config.get(section)) {
  486. collapse(sections[section], 'header h3');
  487. }
  488. });
  489. }
  490.  
  491. function doProfile(){
  492. $(userprofile.path).slideUp();
  493. arrow($(userprofile.header));
  494. $(userprofile.header).css({
  495. cursor: 'pointer',
  496. })
  497. .click(function(){
  498. $(userprofile.path).slideToggle();
  499. rotate($(this).find('svg'));
  500. });
  501. }
  502.  
  503. function doList(){
  504. const version = GM_config.get('version');
  505. const newtab = GM_config.get('newtab');
  506. $(listitem).each(function(){
  507. if (version){
  508. $(this).find(separator).before(` ${$(this).attr(scriptversion)}`);
  509. }
  510. if (newtab){
  511. $(this).find(separator).prev('a').click(newtaber);
  512. }
  513. });
  514. }
  515.  
  516. function doUpdates(page){
  517. let parent, target, url;
  518. switch (page) {
  519. case 'user':
  520. case 'search':
  521. parent = listitem;
  522. target = scriptstats;
  523. break;
  524. case 'script':
  525. parent = `#script-meta`;
  526. target = `#script-stats`;
  527. url = `${window.location.href}/stats.json`;
  528. break;
  529. default:
  530. break;
  531. }
  532. $(parent).each((index, item) => {
  533. $(item).css({
  534. maxWidth: 'unset',
  535. });
  536. const stats = $(item).find(target);
  537. if (page != 'script') url = `${$(item).find(scripturl).attr('href')}/stats.json`;
  538.  
  539. const updatesperiods = GM_config.get('updatesperiods');
  540. if (Object.keys(updatesperiods).length > 0) {
  541. const dt = $('<dt></dt>', {
  542. class: 'script-list-update-checks',
  543. style: 'cursor: default',
  544. width: 'auto',
  545. });
  546. let text = 'Updates (';
  547. let title = 'Update checks (';
  548. for (const period in updatesperiods) {
  549. text += `${period.charAt(0)}|`;
  550. title +=`${period}|`;
  551. };
  552. text = text.replace(/\|$/, '):');
  553. title = title.replace(/\|$/, ')');
  554. dt.text(text).attr('title', title).append(`
  555. <style>
  556. .inline-script-stats dt,
  557. .inline-script-stats dd,
  558. .inline-script-stats span {
  559. cursor: default;
  560. width: auto !important;
  561. }
  562. .script-list-update-checks span {
  563. padding: 0 5px;
  564. }
  565. .script-list-update-checks span:not(:last-child) {
  566. border-right: 1px dotted whitesmoke;
  567. }
  568. </style>`).appendTo($(stats));
  569.  
  570. const updatechecks = $('<dd></dd>', {
  571. class: 'script-list-update-checks',
  572. });
  573. $(stats).append(updatechecks);
  574.  
  575. getjsondata(url, 'update_checks', updatesperiods, $(updatechecks));
  576. }
  577. });
  578. }
  579.  
  580. function doInstalls(page){
  581. let daily, parent, target, total, url;
  582. switch (page) {
  583. case 'user':
  584. case 'search':
  585. daily = dailyinstalls;
  586. parent = listitem;
  587. target = scriptstats;
  588. total = totalinstalls;
  589. break;
  590. case 'script':
  591. daily = '.script-show-daily-installs';
  592. parent = `#script-meta`;
  593. target = `#script-stats`;
  594. total = '.script-show-total-installs';
  595. url = `${window.location.href}/stats.json`;
  596. break;
  597. default:
  598. break;
  599. }
  600. $(daily).css({
  601. display: 'none',
  602. });
  603. $(total).css({
  604. display: 'none',
  605. });
  606.  
  607. $(parent).each((index, item) => {
  608. $(item).css({
  609. maxWidth: 'unset',
  610. });
  611. const stats = $(item).find(target);
  612. if (page != 'script') url = `${$(item).find(scripturl).attr('href')}/stats.json`;
  613.  
  614. const installsperiods = GM_config.get('installsperiods');
  615. if (Object.keys(installsperiods).length > 0) {
  616. const dt = $('<dt></dt>', {
  617. class: 'script-list-installs',
  618. style: 'cursor: default',
  619. width: 'auto',
  620. });
  621. let text = 'Installs (';
  622. let title = 'Installs (';
  623. for (const period in installsperiods) {
  624. text += `${period.charAt(0)}|`;
  625. title +=`${period}|`;
  626. };
  627. text = text.replace(/\|$/, '):');
  628. title = title.replace(/\|$/, ')');
  629. dt.text(text).attr('title', title).append(`
  630. <style>
  631. .inline-script-stats dt,
  632. .inline-script-stats dd,
  633. .inline-script-stats span {
  634. cursor: default;
  635. width: auto !important;
  636. }
  637. .script-list-installs span {
  638. padding: 0 5px;
  639. }
  640. .script-list-installs span:not(:last-child) {
  641. border-right: 1px dotted whitesmoke;
  642. }
  643. </style>`).appendTo($(stats));
  644.  
  645. const installs = $('<dd></dd>', {
  646. class: 'script-list-installs',
  647. });
  648. $(stats).append(installs);
  649.  
  650. getjsondata(url, 'installs', installsperiods, $(installs));
  651. }
  652. });
  653. }
  654.  
  655. function isImage(url){
  656. const types = ['apng', 'bmp', 'gif', 'ico', 'jfi', 'jfif', 'jif', 'jpe', 'jpeg', 'jpg', 'pjp', 'pjpeg', 'png', 'psd', 'svg', 'tif', 'tiff', 'webp'];
  657. const ext = url.split('/').pop().split('#').shift().split('?').shift().split('.').pop();
  658. return types.includes(ext);
  659. }
  660.  
  661. function displayImage(){
  662. $(listitem).each((index, item) => {
  663. const url = `${$(item).find(scripturl).attr('href')}`;
  664. const height = $(item).height();
  665.  
  666. $(item).children('article').css({
  667. display: 'inline-block',
  668. margin: '0',
  669. width: '75%',
  670. });
  671. const div = $('<div></div>').appendTo($(item)).css({
  672. display: 'inline-block',
  673. height: `${height}px`,
  674. float: 'right',
  675. margin: '0',
  676. overflow: 'hidden',
  677. padding: '5px',
  678. width: '20%',
  679. });
  680. fetch(url).then((response) => {
  681. response.text().then((data) => {
  682. const parser = new DOMParser();
  683. const doc = parser.parseFromString(data, 'text/html');
  684. const el = doc.querySelector(`#additional-info img:first-child`);
  685. let src = el.getAttribute('src');
  686.  
  687. if (el.parentElement.hasAttribute('href')) {
  688. const href = el.parentElement.getAttribute('href');
  689. if (isImage(href)) src = href;
  690. }
  691.  
  692. const width = $(div).width();
  693. const img = $('<img/>', {
  694. src: src,
  695. width: `${width}px`,
  696. });
  697. $(div).append(`
  698. <style>
  699. ${listitem}::after {
  700. clear: both;
  701. }
  702. </style>
  703. `).append(img);
  704. });
  705. });
  706. });
  707. }
  708.  
  709. function router(path){
  710. const ratingscore = GM_config.get('ratingscore');
  711. const displayimage = GM_config.get('displayimage');
  712. const installs = GM_config.get('installs');
  713. const updates = GM_config.get('updates');
  714. const userprofile = GM_config.get('userprofile');
  715.  
  716. switch (true) {
  717. case route.userpage.test(path):
  718. console.log('router:', 'user', path);
  719. if (userprofile) doProfile();
  720. doCollapse();
  721. doCompact();
  722. if (ratingscore) doRating('user');
  723. doList();
  724. if (installs) doInstalls('user');
  725. if (updates) doUpdates('user');
  726. if (displayimage) displayImage();
  727. break;
  728. case route.searchpage.test(path):
  729. console.log('router:', 'search', path);
  730. if (ratingscore) doRating('search');
  731. doCompact();
  732. doList();
  733. if (installs) doInstalls('search');
  734. if (updates) doUpdates('search');
  735. if (displayimage) displayImage();
  736. break;
  737. case route.scriptpage.test(path):
  738. console.log('router:', 'script', path);
  739. if (ratingscore) doRating('script');
  740. if (installs) doInstalls('script');
  741. if (updates) doUpdates('script');
  742. break;
  743. default:
  744. console.log('router:', 'default', path);
  745. break;
  746. }
  747. }
  748.  
  749. (function() {
  750. 'use strict';
  751.  
  752. $(document).ready(() => {
  753. const width = GM_config.get('width');
  754. $(maincontainer).css({
  755. maxWidth: width,
  756. });
  757.  
  758. router(window.location.pathname);
  759. });
  760. })();

QingJ © 2025

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