GreasyFork: download script button

If you have a script manager and you want to download some script without installing it, this script will help

当前为 2023-11-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GreasyFork: download script button
  3. // @description If you have a script manager and you want to download some script without installing it, this script will help
  4. // @author Konf
  5. // @version 2.1.1
  6. // @namespace https://gf.qytechs.cn/users/424058
  7. // @icon https://gf.qytechs.cn/vite/assets/blacklogo96-e0c2c761.png
  8. // @match https://gf.qytechs.cn/*/scripts/*
  9. // @match https://sleazyfork.org/*/scripts/*
  10. // @compatible Chrome
  11. // @compatible Opera
  12. // @compatible Firefox
  13. // @run-at document-end
  14. // @grant GM_addStyle
  15. // @noframes
  16. // ==/UserScript==
  17.  
  18. /* jshint esversion: 8 */
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23. const i18n = {
  24. download: 'download',
  25. downloadWithoutInstalling: 'downloadWithoutInstalling',
  26. failedToDownload: 'failedToDownload',
  27. };
  28.  
  29. const translate = (function() {
  30. const userLang = location.pathname.split('/')[1];
  31. const strings = {
  32. 'en': {
  33. [i18n.download]: 'Download ⇩',
  34. [i18n.downloadWithoutInstalling]: 'Download without installing',
  35. [i18n.failedToDownload]:
  36. 'Failed to download the script. There is might be more info in the browser console',
  37. },
  38. 'ru': {
  39. [i18n.download]: 'Скачать ⇩',
  40. [i18n.downloadWithoutInstalling]: 'Скачать не устанавливая',
  41. [i18n.failedToDownload]:
  42. 'Не удалось скачать скрипт. Больше информации может быть в консоли браузера',
  43. },
  44. 'zh-CN': {
  45. [i18n.download]: '下载 ⇩',
  46. [i18n.downloadWithoutInstalling]: '下载此脚本',
  47. [i18n.failedToDownload]: '无法下载此脚本',
  48. },
  49. };
  50.  
  51. return id => (strings[userLang] || strings.en)[id] || strings.en[id];
  52. }());
  53.  
  54. const installBtn = document.querySelector('a.install-link');
  55. const installArea = document.querySelector('div#install-area');
  56. const installHelpLink = document.querySelector('a.install-help-link');
  57. const suggestions = document.querySelector('div#script-feedback-suggestion');
  58. const libraryRequire = document.querySelector('div#script-content > p > code');
  59.  
  60. // if a script is detected
  61. if (installBtn && installArea && installHelpLink) {
  62. mountScriptDownloadButton(installBtn, installArea, installHelpLink);
  63. }
  64. // or maybe a library
  65. else if (suggestions && libraryRequire) {
  66. mountLibraryDownloadButton(suggestions, libraryRequire);
  67. }
  68.  
  69. function mountScriptDownloadButton(
  70. installBtn,
  71. installArea,
  72. installHelpLink,
  73. ) {
  74. if (!installBtn.href) throw new Error('script href is not found');
  75.  
  76. // https://img.icons8.com/pastel-glyph/64/ffffff/download.png
  77. // array to fold the string in a code editor
  78. const downloadIconBase64 = [
  79. 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaX',
  80. 'HeAAAABmJLR0QA/wD/AP+gvaeTAAABgUlEQVR4nO3ZTU6DUAAE4HnEk+jWG3TrHV',
  81. 'wY3XoEt23cGleamtRtTbyPS3sCV0bXjptHRAIEsM/hZ76kCZRHGaZAGwDMzMzMbJ',
  82. '6CasMkMwBncXYbQvhSZZEgecEf56ocmWrDAA4L00eqEMoCBsEFqAOouQB1ADUXoA',
  83. '6g5gLUAdRcgDqAmgtQB1BzAeoAakkLIHlN8pPkDcnWd59IBpK3cd1VyoxJkfwo3P',
  84. 'V5KJZAcllYtiy8H+LY3HvKjKlPgU1h+hLAuulIiMvWcWzVZ4xL/Dbv+Nsjyax8BM',
  85. 'Sx96Wxm3jzdLwaSliVCpjezucqzmuSfKuZJkvXi0moORKqTOebL2tRwnR3PtdQwv',
  86. 'R3PldRgmznlc8GA4DTOPscQqAqy6x1+X8+6Ke5yfNxIE9z6/TN1+XCM4inuQ165Z',
  87. 'vHz04DF6AOoOYC1AHUXIA6gNpBz/UWJK/2muTvFn1W6lvASXyNXpdTYJcsxf69th',
  88. '3Y5QjYAiCA485x/tcLgCd1CDMzMzMbum8+xtkWw6QCvwAAAABJRU5ErkJggg==',
  89. ].join('');
  90.  
  91. GM_addStyle([`
  92. .GF-DSB__script-download-button {
  93. position: relative;
  94. padding: 8px 22px;
  95. cursor: pointer;
  96. border: none;
  97. background: #0F750F;
  98. transition: box-shadow 0.2s;
  99. }
  100.  
  101. .GF-DSB__script-download-button:hover,
  102. .GF-DSB__script-download-button:focus {
  103. box-shadow: 0 8px 16px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
  104. }
  105.  
  106.  
  107. .GF-DSB__script-download-icon {
  108. position: absolute;
  109. }
  110.  
  111. .GF-DSB__script-download-icon--download {
  112. width: 30px;
  113. height: 30px;
  114. top: 4px;
  115. left: 7px;
  116. }
  117.  
  118. .GF-DSB__script-download-icon--loading,
  119. .GF-DSB__script-download-icon--loading:after {
  120. border-radius: 50%;
  121. width: 16px;
  122. height: 16px;
  123. }
  124.  
  125. .GF-DSB__script-download-icon--loading {
  126. top: 8px;
  127. left: 11px;
  128. border-top: 3px solid rgba(255, 255, 255, 0.2);
  129. border-right: 3px solid rgba(255, 255, 255, 0.2);
  130. border-bottom: 3px solid rgba(255, 255, 255, 0.2);
  131. border-left: 3px solid #ffffff;
  132. transform: translateZ(0);
  133. object-position: -99999px;
  134. animation: GF-DSB__script-download-loading-icon 1.1s infinite linear;
  135. }
  136.  
  137. @keyframes GF-DSB__script-download-loading-icon {
  138. 0% {
  139. transform: rotate(0deg);
  140. }
  141. 100% {
  142. transform: rotate(360deg);
  143. }
  144. }
  145. `][0]);
  146.  
  147. const b = document.createElement('a');
  148. const bIcon = document.createElement('img');
  149.  
  150. b.href = '#';
  151. b.title = translate(i18n.downloadWithoutInstalling);
  152. b.draggable = false;
  153. b.className = 'GF-DSB__script-download-button';
  154.  
  155. bIcon.src = downloadIconBase64;
  156. bIcon.draggable = false;
  157. bIcon.className =
  158. 'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
  159.  
  160. installHelpLink.style.position = 'relative'; // shadows bugfix
  161.  
  162. b.appendChild(bIcon);
  163. installArea.insertBefore(b, installHelpLink);
  164.  
  165. // against doubleclicks
  166. let isFetchingAllowed = true;
  167.  
  168. async function clicksHandler(ev) {
  169. ev.preventDefault();
  170.  
  171. setTimeout(() => b === document.activeElement && b.blur(), 250);
  172.  
  173. if (isFetchingAllowed === false) return;
  174.  
  175. isFetchingAllowed = false;
  176. bIcon.className =
  177. 'GF-DSB__script-download-icon GF-DSB__script-download-icon--loading';
  178.  
  179. try {
  180. await downloadScript({
  181. fileExt: `.user.${installBtn.dataset.installFormat || 'txt'}`,
  182. href: installBtn.href,
  183. name: installBtn.dataset.scriptName,
  184. });
  185. } catch (e) {
  186. console.error(e);
  187. alert(`${translate(i18n.failedToDownload)}: \n${e}`);
  188. } finally {
  189. setTimeout(() => {
  190. isFetchingAllowed = true;
  191. bIcon.className =
  192. 'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
  193. }, 300);
  194. }
  195. }
  196.  
  197. b.addEventListener('click', clicksHandler);
  198. b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
  199. }
  200.  
  201. function mountLibraryDownloadButton(suggestions, libraryRequire) {
  202. const [
  203. libraryHref,
  204. libraryName,
  205. ] = libraryRequire.innerText.match(
  206. /\/\/ @require (https:\/\/.+\/scripts\/\d+.+\/code\/(.*)\.js\?version=\d+)/
  207. ).slice(1);
  208.  
  209. // this probably is completely useless but whatever
  210. if (!libraryHref) throw new Error('library href is not found');
  211.  
  212. GM_addStyle([`
  213. .GF-DSB__library-download-button {
  214. transition: box-shadow 0.2s;
  215. }
  216.  
  217. .GF-DSB__library-download-button--loading {
  218. animation: GF-DSB__loading-text 1s infinite linear;
  219. }
  220.  
  221. @keyframes GF-DSB__loading-text {
  222. 50% {
  223. opacity: 0.4;
  224. }
  225. }
  226. `][0]);
  227.  
  228. const b = document.createElement('a');
  229.  
  230. b.href = '#';
  231. b.draggable = false;
  232. b.innerText = translate(i18n.download);
  233. b.className = 'GF-DSB__library-download-button';
  234.  
  235. suggestions.appendChild(b);
  236.  
  237. // against doubleclicks
  238. let isFetchingAllowed = true;
  239.  
  240. async function clicksHandler(ev) {
  241. ev.preventDefault();
  242.  
  243. setTimeout(() => b === document.activeElement && b.blur(), 250);
  244.  
  245. if (isFetchingAllowed === false) return;
  246.  
  247. isFetchingAllowed = false;
  248. b.className =
  249. 'GF-DSB__library-download-button GF-DSB__library-download-button--loading';
  250.  
  251. try {
  252. await downloadScript({
  253. fileExt: '.js',
  254. href: libraryHref,
  255. name: libraryName,
  256. });
  257. } catch (e) {
  258. console.error(e);
  259. alert(`${translate(i18n.failedToDownload)}: \n${e}`);
  260. } finally {
  261. setTimeout(() => {
  262. isFetchingAllowed = true;
  263. b.className = 'GF-DSB__library-download-button';
  264. }, 300);
  265. }
  266. }
  267.  
  268. b.addEventListener('click', clicksHandler);
  269. b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
  270. }
  271.  
  272. // utils --------------------------------------------------------------------
  273.  
  274. async function downloadScript({
  275. fileExt = '.txt',
  276. href,
  277. name = Date.now(),
  278. } = {}) {
  279. if (!href) throw new Error('href is missing');
  280.  
  281. const blob = await (await fetch(href)).blob();
  282. const url = window.URL.createObjectURL(blob);
  283. const a = document.createElement('a');
  284.  
  285. a.href = url;
  286. a.download = `${name}${fileExt}`;
  287. document.body.appendChild(a); // is needed due to firefox bug
  288. a.click();
  289. a.remove();
  290.  
  291. window.URL.revokeObjectURL(url);
  292. }
  293. }());

QingJ © 2025

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