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.2
  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 installBtns = document.querySelectorAll('a.install-link');
  55. const installArea = document.querySelector('div#install-area');
  56. const installHelpLinks = document.querySelectorAll('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/style is detected
  61. if (
  62. installArea &&
  63. (installBtns.length > 0) &&
  64. (installBtns.length === installHelpLinks.length)
  65. ) {
  66. for (let i = 0; i < installBtns.length; i++) {
  67. mountScriptDownloadButton(installBtns[i], installArea, installHelpLinks[i]);
  68. }
  69. }
  70. // or maybe a library
  71. else if (suggestions && libraryRequire) {
  72. mountLibraryDownloadButton(suggestions, libraryRequire);
  73. }
  74.  
  75. function mountScriptDownloadButton(
  76. installBtn,
  77. installArea,
  78. installHelpLink,
  79. ) {
  80. if (!installBtn.href) throw new Error('script href is not found');
  81.  
  82. // https://img.icons8.com/pastel-glyph/64/ffffff/download.png
  83. // array to fold the string in a code editor
  84. const downloadIconBase64 = [
  85. 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaX',
  86. 'HeAAAABmJLR0QA/wD/AP+gvaeTAAABgUlEQVR4nO3ZTU6DUAAE4HnEk+jWG3TrHV',
  87. 'wY3XoEt23cGleamtRtTbyPS3sCV0bXjptHRAIEsM/hZ76kCZRHGaZAGwDMzMzMbJ',
  88. '6CasMkMwBncXYbQvhSZZEgecEf56ocmWrDAA4L00eqEMoCBsEFqAOouQB1ADUXoA',
  89. '6g5gLUAdRcgDqAmgtQB1BzAeoAakkLIHlN8pPkDcnWd59IBpK3cd1VyoxJkfwo3P',
  90. 'V5KJZAcllYtiy8H+LY3HvKjKlPgU1h+hLAuulIiMvWcWzVZ4xL/Dbv+Nsjyax8BM',
  91. 'Sx96Wxm3jzdLwaSliVCpjezucqzmuSfKuZJkvXi0moORKqTOebL2tRwnR3PtdQwv',
  92. 'R3PldRgmznlc8GA4DTOPscQqAqy6x1+X8+6Ke5yfNxIE9z6/TN1+XCM4inuQ165Z',
  93. 'vHz04DF6AOoOYC1AHUXIA6gNpBz/UWJK/2muTvFn1W6lvASXyNXpdTYJcsxf69th',
  94. '3Y5QjYAiCA485x/tcLgCd1CDMzMzMbum8+xtkWw6QCvwAAAABJRU5ErkJggg==',
  95. ].join('');
  96.  
  97. GM_addStyle([`
  98. .GF-DSB__script-download-button {
  99. position: relative;
  100. padding: 8px 22px;
  101. cursor: pointer;
  102. border: none;
  103. background: #0F750F;
  104. transition: box-shadow 0.2s;
  105. }
  106.  
  107. .GF-DSB__script-download-button:hover,
  108. .GF-DSB__script-download-button:focus {
  109. box-shadow: 0 8px 16px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
  110. }
  111.  
  112.  
  113. .GF-DSB__script-download-icon {
  114. position: absolute;
  115. }
  116.  
  117. .GF-DSB__script-download-icon--download {
  118. width: 30px;
  119. height: 30px;
  120. top: 4px;
  121. left: 7px;
  122. }
  123.  
  124. .GF-DSB__script-download-icon--loading,
  125. .GF-DSB__script-download-icon--loading:after {
  126. border-radius: 50%;
  127. width: 16px;
  128. height: 16px;
  129. }
  130.  
  131. .GF-DSB__script-download-icon--loading {
  132. top: 8px;
  133. left: 11px;
  134. border-top: 3px solid rgba(255, 255, 255, 0.2);
  135. border-right: 3px solid rgba(255, 255, 255, 0.2);
  136. border-bottom: 3px solid rgba(255, 255, 255, 0.2);
  137. border-left: 3px solid #ffffff;
  138. transform: translateZ(0);
  139. object-position: -99999px;
  140. animation: GF-DSB__script-download-loading-icon 1.1s infinite linear;
  141. }
  142.  
  143. @keyframes GF-DSB__script-download-loading-icon {
  144. 0% {
  145. transform: rotate(0deg);
  146. }
  147. 100% {
  148. transform: rotate(360deg);
  149. }
  150. }
  151. `][0]);
  152.  
  153. const b = document.createElement('a');
  154. const bIcon = document.createElement('img');
  155.  
  156. b.href = '#';
  157. b.title = translate(i18n.downloadWithoutInstalling);
  158. b.draggable = false;
  159. b.className = 'GF-DSB__script-download-button';
  160.  
  161. bIcon.src = downloadIconBase64;
  162. bIcon.draggable = false;
  163. bIcon.className =
  164. 'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
  165.  
  166. installHelpLink.style.position = 'relative'; // shadows bugfix
  167.  
  168. b.appendChild(bIcon);
  169. installArea.insertBefore(b, installHelpLink);
  170.  
  171. // against doubleclicks
  172. let isFetchingAllowed = true;
  173.  
  174. async function clicksHandler(ev) {
  175. ev.preventDefault();
  176.  
  177. setTimeout(() => b === document.activeElement && b.blur(), 250);
  178.  
  179. if (isFetchingAllowed === false) return;
  180.  
  181. isFetchingAllowed = false;
  182. bIcon.className =
  183. 'GF-DSB__script-download-icon GF-DSB__script-download-icon--loading';
  184.  
  185. try {
  186. await downloadScript({
  187. fileExt: `.user.${installBtn.dataset.installFormat || 'txt'}`,
  188. href: installBtn.href,
  189. name: installBtn.dataset.scriptName,
  190. });
  191. } catch (e) {
  192. console.error(e);
  193. alert(`${translate(i18n.failedToDownload)}: \n${e}`);
  194. } finally {
  195. setTimeout(() => {
  196. isFetchingAllowed = true;
  197. bIcon.className =
  198. 'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
  199. }, 300);
  200. }
  201. }
  202.  
  203. b.addEventListener('click', clicksHandler);
  204. b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
  205. }
  206.  
  207. function mountLibraryDownloadButton(suggestions, libraryRequire) {
  208. const [
  209. libraryHref,
  210. libraryName,
  211. ] = libraryRequire.innerText.match(
  212. /\/\/ @require (https:\/\/.+\/scripts\/\d+.+\/code\/(.*)\.js\?version=\d+)/
  213. ).slice(1);
  214.  
  215. // this probably is completely useless but whatever
  216. if (!libraryHref) throw new Error('library href is not found');
  217.  
  218. GM_addStyle([`
  219. .GF-DSB__library-download-button {
  220. transition: box-shadow 0.2s;
  221. }
  222.  
  223. .GF-DSB__library-download-button--loading {
  224. animation: GF-DSB__loading-text 1s infinite linear;
  225. }
  226.  
  227. @keyframes GF-DSB__loading-text {
  228. 50% {
  229. opacity: 0.4;
  230. }
  231. }
  232. `][0]);
  233.  
  234. const b = document.createElement('a');
  235.  
  236. b.href = '#';
  237. b.draggable = false;
  238. b.innerText = translate(i18n.download);
  239. b.className = 'GF-DSB__library-download-button';
  240.  
  241. suggestions.appendChild(b);
  242.  
  243. // against doubleclicks
  244. let isFetchingAllowed = true;
  245.  
  246. async function clicksHandler(ev) {
  247. ev.preventDefault();
  248.  
  249. setTimeout(() => b === document.activeElement && b.blur(), 250);
  250.  
  251. if (isFetchingAllowed === false) return;
  252.  
  253. isFetchingAllowed = false;
  254. b.className =
  255. 'GF-DSB__library-download-button GF-DSB__library-download-button--loading';
  256.  
  257. try {
  258. await downloadScript({
  259. fileExt: '.js',
  260. href: libraryHref,
  261. name: libraryName,
  262. });
  263. } catch (e) {
  264. console.error(e);
  265. alert(`${translate(i18n.failedToDownload)}: \n${e}`);
  266. } finally {
  267. setTimeout(() => {
  268. isFetchingAllowed = true;
  269. b.className = 'GF-DSB__library-download-button';
  270. }, 300);
  271. }
  272. }
  273.  
  274. b.addEventListener('click', clicksHandler);
  275. b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
  276. }
  277.  
  278. // utils --------------------------------------------------------------------
  279.  
  280. async function downloadScript({
  281. fileExt = '.txt',
  282. href,
  283. name = Date.now(),
  284. } = {}) {
  285. if (!href) throw new Error('href is missing');
  286.  
  287. const blob = await (await fetch(href)).blob();
  288. const url = window.URL.createObjectURL(blob);
  289. const a = document.createElement('a');
  290.  
  291. a.href = url;
  292. a.download = `${name}${fileExt}`;
  293. document.body.appendChild(a); // is needed due to firefox bug
  294. a.click();
  295. a.remove();
  296.  
  297. window.URL.revokeObjectURL(url);
  298. }
  299. }());

QingJ © 2025

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