Greasy Fork is available in English.
If you have a script manager and you want to download some script without installing it, this script will help
< Opinie na GreasyFork: download script button
I'll check it out. Is there are any messenger I can reach you to debug the problem faster?
Nevermind. It seems like the problem is general, I also have it. It's because of changes on the website... I'll see what I can do
Uhmm... I did nothing and it works for me again. I'm confused
Incredible, I just tested again and it's always the same on my side 😲
Do you use discord or something where I can text you? As long as I can't reproduce the problem on my side, I can't fix it
Yes by e-mail, because I don't have a social network.
Ok let's stick with this place for now. I need some screenshots or recording that is showing what is the reason of the error. I need something like this: https://streamable.com/fj29le
It's my video where I'm just offline trying to download something. It would be great if you can make something similar and upload it using the same video share service or maybe some better one because this is kinda shit, loses video quality
Show the exact things I'm showing on the video
I've made some changes, but I have no idea if they would have any impact or not. Test this:
// ==UserScript==
// @name GreasyFork: download script button
// @description If you have a script manager and you want to download some script without installing it, this script will help
// @author Konf
// @version 2.1.5a
// @namespace https://greasyfork.org/users/424058
// @icon https://greasyfork.org/vite/assets/blacklogo96-e0c2c761.png
// @match https://greasyfork.org/*/scripts/*
// @match https://sleazyfork.org/*/scripts/*
// @connect update.greasyfork.org
// @compatible Chrome
// @compatible Opera
// @compatible Firefox
// @run-at document-end
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @noframes
// ==/UserScript==
/* jshint esversion: 8 */
(function() {
'use strict';
const i18n = {
download: 'download',
downloadWithoutInstalling: 'downloadWithoutInstalling',
failedToDownload: 'failedToDownload',
};
const translate = (function() {
const userLang = location.pathname.split('/')[1];
const strings = {
'en': {
[i18n.download]: 'Download ⇩',
[i18n.downloadWithoutInstalling]: 'Download without installing',
[i18n.failedToDownload]:
'Failed to download the script. There is might be more info in the browser console',
},
'ru': {
[i18n.download]: 'Скачать ⇩',
[i18n.downloadWithoutInstalling]: 'Скачать не устанавливая',
[i18n.failedToDownload]:
'Не удалось скачать скрипт. Больше информации может быть в консоли браузера',
},
'zh-CN': {
[i18n.download]: '下载 ⇩',
[i18n.downloadWithoutInstalling]: '下载此脚本',
[i18n.failedToDownload]: '无法下载此脚本',
},
};
return id => (strings[userLang] || strings.en)[id] || strings.en[id];
}());
const installBtns = document.querySelectorAll('a.install-link');
const installArea = document.querySelector('div#install-area');
const installHelpLinks = document.querySelectorAll('a.install-help-link');
const suggestion = document.querySelector('div#script-feedback-suggestion');
const libraryRequire = document.querySelector('div#script-content > p > code');
// if a script/style is detected
if (
installArea &&
(installBtns.length > 0) &&
(installBtns.length === installHelpLinks.length)
) {
for (let i = 0; i < installBtns.length; i++) {
mountScriptDownloadButton(installBtns[i], installArea, installHelpLinks[i]);
}
}
// or maybe a library
else if (suggestion && libraryRequire) {
mountLibraryDownloadButton(suggestion, libraryRequire);
}
function mountScriptDownloadButton(
installBtn,
installArea,
installHelpLink,
) {
if (!installBtn.href) throw new Error('script href is not found');
// https://img.icons8.com/pastel-glyph/64/ffffff/download.png
// array to fold the string in a code editor
const downloadIconBase64 = [
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaX',
'HeAAAABmJLR0QA/wD/AP+gvaeTAAABgUlEQVR4nO3ZTU6DUAAE4HnEk+jWG3TrHV',
'wY3XoEt23cGleamtRtTbyPS3sCV0bXjptHRAIEsM/hZ76kCZRHGaZAGwDMzMzMbJ',
'6CasMkMwBncXYbQvhSZZEgecEf56ocmWrDAA4L00eqEMoCBsEFqAOouQB1ADUXoA',
'6g5gLUAdRcgDqAmgtQB1BzAeoAakkLIHlN8pPkDcnWd59IBpK3cd1VyoxJkfwo3P',
'V5KJZAcllYtiy8H+LY3HvKjKlPgU1h+hLAuulIiMvWcWzVZ4xL/Dbv+Nsjyax8BM',
'Sx96Wxm3jzdLwaSliVCpjezucqzmuSfKuZJkvXi0moORKqTOebL2tRwnR3PtdQwv',
'R3PldRgmznlc8GA4DTOPscQqAqy6x1+X8+6Ke5yfNxIE9z6/TN1+XCM4inuQ165Z',
'vHz04DF6AOoOYC1AHUXIA6gNpBz/UWJK/2muTvFn1W6lvASXyNXpdTYJcsxf69th',
'3Y5QjYAiCA485x/tcLgCd1CDMzMzMbum8+xtkWw6QCvwAAAABJRU5ErkJggg==',
].join('');
GM_addStyle([`
.GF-DSB__script-download-button {
position: relative;
padding: 8px 22px;
cursor: pointer;
border: none;
background: #0F750F;
transition: box-shadow 0.2s;
}
.GF-DSB__script-download-button:hover,
.GF-DSB__script-download-button:focus {
box-shadow: 0 8px 16px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
}
.GF-DSB__script-download-icon {
position: absolute;
}
.GF-DSB__script-download-icon--download {
width: 30px;
height: 30px;
top: 4px;
left: 7px;
}
.GF-DSB__script-download-icon--loading,
.GF-DSB__script-download-icon--loading:after {
border-radius: 50%;
width: 16px;
height: 16px;
}
.GF-DSB__script-download-icon--loading {
top: 8px;
left: 11px;
border-top: 3px solid rgba(255, 255, 255, 0.2);
border-right: 3px solid rgba(255, 255, 255, 0.2);
border-bottom: 3px solid rgba(255, 255, 255, 0.2);
border-left: 3px solid #ffffff;
transform: translateZ(0);
object-position: -99999px;
animation: GF-DSB__script-download-loading-icon 1.1s infinite linear;
}
@keyframes GF-DSB__script-download-loading-icon {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`][0]);
const b = document.createElement('a');
const bIcon = document.createElement('img');
b.href = '#';
b.title = translate(i18n.downloadWithoutInstalling);
b.draggable = false;
b.className = 'GF-DSB__script-download-button';
bIcon.src = downloadIconBase64;
bIcon.draggable = false;
bIcon.className =
'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
installHelpLink.style.position = 'relative'; // shadows bugfix
b.appendChild(bIcon);
installArea.insertBefore(b, installHelpLink);
// against doubleclicks
let isFetchingAllowed = true;
async function clicksHandler(ev) {
ev.preventDefault();
setTimeout(() => b === document.activeElement && b.blur(), 250);
if (isFetchingAllowed === false) return;
isFetchingAllowed = false;
bIcon.className =
'GF-DSB__script-download-icon GF-DSB__script-download-icon--loading';
try {
await downloadScript({
fileExt: `.user.${installBtn.dataset.installFormat || 'txt'}`,
href: installBtn.href,
name: installBtn.dataset.scriptName,
});
} catch (e) {
console.error(e);
alert(`${translate(i18n.failedToDownload)}: \n${e}`);
} finally {
setTimeout(() => {
isFetchingAllowed = true;
bIcon.className =
'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
}, 300);
}
}
b.addEventListener('click', clicksHandler);
b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
}
function mountLibraryDownloadButton(suggestion, libraryRequire) {
const [
libraryHref,
libraryName,
] = libraryRequire.innerText.match(
/\/\/ @require (https:\/\/.+\/scripts\/\d+\/\d+\/(.*)\.js)/
).slice(1);
// this probably is completely useless but whatever
if (!libraryHref) throw new Error('library href is not found');
GM_addStyle([`
.GF-DSB__library-download-button {
transition: box-shadow 0.2s;
}
.GF-DSB__library-download-button--loading {
animation: GF-DSB__loading-text 1s infinite linear;
}
@keyframes GF-DSB__loading-text {
50% {
opacity: 0.4;
}
}
`][0]);
const b = document.createElement('a');
b.href = '#';
b.draggable = false;
b.innerText = translate(i18n.download);
b.className = 'GF-DSB__library-download-button';
suggestion.appendChild(b);
// against doubleclicks
let isFetchingAllowed = true;
async function clicksHandler(ev) {
ev.preventDefault();
setTimeout(() => b === document.activeElement && b.blur(), 250);
if (isFetchingAllowed === false) return;
isFetchingAllowed = false;
b.className =
'GF-DSB__library-download-button GF-DSB__library-download-button--loading';
try {
await downloadScript({
fileExt: '.js',
href: libraryHref,
name: libraryName,
});
} catch (e) {
console.error(e);
alert(`${translate(i18n.failedToDownload)}: \n${e}`);
} finally {
setTimeout(() => {
isFetchingAllowed = true;
b.className = 'GF-DSB__library-download-button';
}, 300);
}
}
b.addEventListener('click', clicksHandler);
b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
}
// utils --------------------------------------------------------------------
async function downloadScript({
fileExt = '.txt',
href,
name = Date.now(),
} = {}) {
if (!href) throw new Error('href is missing');
const result = await GM.xmlHttpRequest({ url: href });
if (result.status !== 200) {
throw new Error(`Bad response: ${result.status}`);
}
const blob = new Blob([result.response], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${name}${fileExt}`;
document.body.appendChild(a); // is needed due to firefox bug
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
}());
Hello,
Sorry for my late reply.
Unfortunately, there are the problems in this version 2.1.5a on my side :-(
Was that Tampermonkey? If it was not, could you try it? Also what is the Chrome version?
This Tampermonkey version 5 for Google Chrome -> https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo
As I'm running Windows 8.1, Google Chrome forced me to stay on their version 109 :-(
version 109
Ok, I'll check this out a bit later. I'll make one more script version to test
However the versions 1.2.3 and 2.0.0 work always very well :-)
Got it. I'll get this 109 and would fix the script latest version for it
This is so weird that I don't have any of these errors on 109 as well. I guess I'll just try something in blind
Try this. If this works, I'll release it then, and you would need to reinstall it from the script page in order to be able to receive a next updates automatically
// ==UserScript==
// @name GreasyFork: download script button
// @description If you have a script manager and you want to download some script without installing it, this script will help
// @author Konf
// @version 2.1.5b
// @namespace https://greasyfork.org/users/424058
// @icon https://greasyfork.org/vite/assets/blacklogo96-e0c2c761.png
// @match https://greasyfork.org/*/scripts/*
// @match https://sleazyfork.org/*/scripts/*
// @compatible Chrome
// @compatible Opera
// @compatible Firefox
// @run-at document-end
// @grant GM_addStyle
// @noframes
// ==/UserScript==
/* jshint esversion: 8 */
(function() {
'use strict';
const i18n = {
download: 'download',
downloadWithoutInstalling: 'downloadWithoutInstalling',
failedToDownload: 'failedToDownload',
};
const translate = (function() {
const userLang = location.pathname.split('/')[1];
const strings = {
'en': {
[i18n.download]: 'Download ⇩',
[i18n.downloadWithoutInstalling]: 'Download without installing',
[i18n.failedToDownload]:
'Failed to download the script. There is might be more info in the browser console',
},
'ru': {
[i18n.download]: 'Скачать ⇩',
[i18n.downloadWithoutInstalling]: 'Скачать не устанавливая',
[i18n.failedToDownload]:
'Не удалось скачать скрипт. Больше информации может быть в консоли браузера',
},
'zh-CN': {
[i18n.download]: '下载 ⇩',
[i18n.downloadWithoutInstalling]: '下载此脚本',
[i18n.failedToDownload]: '无法下载此脚本',
},
};
return id => (strings[userLang] || strings.en)[id] || strings.en[id];
}());
const installBtns = document.querySelectorAll('a.install-link');
const installArea = document.querySelector('div#install-area');
const installHelpLinks = document.querySelectorAll('a.install-help-link');
const suggestion = document.querySelector('div#script-feedback-suggestion');
const libraryRequire = document.querySelector('div#script-content > p > code');
// if a script/style is detected
if (
installArea &&
(installBtns.length > 0) &&
(installBtns.length === installHelpLinks.length)
) {
for (let i = 0; i < installBtns.length; i++) {
mountScriptDownloadButton(installBtns[i], installArea, installHelpLinks[i]);
}
}
// or maybe a library
else if (suggestion && libraryRequire) {
mountLibraryDownloadButton(suggestion, libraryRequire);
}
function mountScriptDownloadButton(
installBtn,
installArea,
installHelpLink,
) {
if (!installBtn.href) throw new Error('script href is not found');
// https://img.icons8.com/pastel-glyph/64/ffffff/download.png
// array to fold the string in a code editor
const downloadIconBase64 = [
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaX',
'HeAAAABmJLR0QA/wD/AP+gvaeTAAABgUlEQVR4nO3ZTU6DUAAE4HnEk+jWG3TrHV',
'wY3XoEt23cGleamtRtTbyPS3sCV0bXjptHRAIEsM/hZ76kCZRHGaZAGwDMzMzMbJ',
'6CasMkMwBncXYbQvhSZZEgecEf56ocmWrDAA4L00eqEMoCBsEFqAOouQB1ADUXoA',
'6g5gLUAdRcgDqAmgtQB1BzAeoAakkLIHlN8pPkDcnWd59IBpK3cd1VyoxJkfwo3P',
'V5KJZAcllYtiy8H+LY3HvKjKlPgU1h+hLAuulIiMvWcWzVZ4xL/Dbv+Nsjyax8BM',
'Sx96Wxm3jzdLwaSliVCpjezucqzmuSfKuZJkvXi0moORKqTOebL2tRwnR3PtdQwv',
'R3PldRgmznlc8GA4DTOPscQqAqy6x1+X8+6Ke5yfNxIE9z6/TN1+XCM4inuQ165Z',
'vHz04DF6AOoOYC1AHUXIA6gNpBz/UWJK/2muTvFn1W6lvASXyNXpdTYJcsxf69th',
'3Y5QjYAiCA485x/tcLgCd1CDMzMzMbum8+xtkWw6QCvwAAAABJRU5ErkJggg==',
].join('');
GM_addStyle([`
.GF-DSB__script-download-button {
position: relative;
padding: 8px 22px;
cursor: pointer;
border: none;
background: #0F750F;
transition: box-shadow 0.2s;
}
.GF-DSB__script-download-button:hover,
.GF-DSB__script-download-button:focus {
box-shadow: 0 8px 16px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
}
.GF-DSB__script-download-icon {
position: absolute;
}
.GF-DSB__script-download-icon--download {
width: 30px;
height: 30px;
top: 4px;
left: 7px;
}
.GF-DSB__script-download-icon--loading,
.GF-DSB__script-download-icon--loading:after {
border-radius: 50%;
width: 16px;
height: 16px;
}
.GF-DSB__script-download-icon--loading {
top: 8px;
left: 11px;
border-top: 3px solid rgba(255, 255, 255, 0.2);
border-right: 3px solid rgba(255, 255, 255, 0.2);
border-bottom: 3px solid rgba(255, 255, 255, 0.2);
border-left: 3px solid #ffffff;
transform: translateZ(0);
object-position: -99999px;
animation: GF-DSB__script-download-loading-icon 1.1s infinite linear;
}
@keyframes GF-DSB__script-download-loading-icon {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`][0]);
const b = document.createElement('a');
const bIcon = document.createElement('img');
b.href = '#';
b.title = translate(i18n.downloadWithoutInstalling);
b.draggable = false;
b.className = 'GF-DSB__script-download-button';
bIcon.src = downloadIconBase64;
bIcon.draggable = false;
bIcon.className =
'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
installHelpLink.style.position = 'relative'; // shadows bugfix
b.appendChild(bIcon);
installArea.insertBefore(b, installHelpLink);
// against doubleclicks
let isFetchingAllowed = true;
async function clicksHandler(ev) {
ev.preventDefault();
setTimeout(() => b === document.activeElement && b.blur(), 250);
if (isFetchingAllowed === false) return;
isFetchingAllowed = false;
bIcon.className =
'GF-DSB__script-download-icon GF-DSB__script-download-icon--loading';
try {
await downloadScript({
fileExt: `.user.${installBtn.dataset.installFormat || 'txt'}`,
href: installBtn.href,
name: installBtn.dataset.scriptName,
});
} catch (e) {
console.error(e);
alert(`${translate(i18n.failedToDownload)}: \n${e}`);
} finally {
setTimeout(() => {
isFetchingAllowed = true;
bIcon.className =
'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
}, 300);
}
}
b.addEventListener('click', clicksHandler);
b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
}
function mountLibraryDownloadButton(suggestion, libraryRequire) {
const [
libraryHref,
libraryName,
] = libraryRequire.innerText.match(
/\/\/ @require (https:\/\/.+\/scripts\/\d+\/\d+\/(.*)\.js)/
).slice(1);
// this probably is completely useless but whatever
if (!libraryHref) throw new Error('library href is not found');
GM_addStyle([`
.GF-DSB__library-download-button {
transition: box-shadow 0.2s;
}
.GF-DSB__library-download-button--loading {
animation: GF-DSB__loading-text 1s infinite linear;
}
@keyframes GF-DSB__loading-text {
50% {
opacity: 0.4;
}
}
`][0]);
const b = document.createElement('a');
b.href = '#';
b.draggable = false;
b.innerText = translate(i18n.download);
b.className = 'GF-DSB__library-download-button';
suggestion.appendChild(b);
// against doubleclicks
let isFetchingAllowed = true;
async function clicksHandler(ev) {
ev.preventDefault();
setTimeout(() => b === document.activeElement && b.blur(), 250);
if (isFetchingAllowed === false) return;
isFetchingAllowed = false;
b.className =
'GF-DSB__library-download-button GF-DSB__library-download-button--loading';
try {
await downloadScript({
fileExt: '.js',
href: libraryHref,
name: libraryName,
});
} catch (e) {
console.error(e);
alert(`${translate(i18n.failedToDownload)}: \n${e}`);
} finally {
setTimeout(() => {
isFetchingAllowed = true;
b.className = 'GF-DSB__library-download-button';
}, 300);
}
}
b.addEventListener('click', clicksHandler);
b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
}
// utils --------------------------------------------------------------------
// Is needed because you can't fetch a new format script link
// due to different domain cors restriction...
function convertScriptHrefToAnOldFormat(href) {
const regex = /https:\/\/update\.(\w+\.org)\/scripts\/(\d+)\/(\d+\/)?(.+)/;
const match = href.match(regex);
if (!match) throw new Error("can't convert href to an old format");
const domain = match[1];
const scriptId = match[2];
const version = match[3] ? `?version=${match[3]}` : '';
const scriptName = match[4];
return `https://${domain}/scripts/${scriptId}/code/${scriptName}${version}`;
}
async function downloadScript({
fileExt = '.txt',
href,
name = Date.now(),
} = {}) {
if (!href) throw new Error('Script href is missing');
const fetchErrors = [];
let url;
// Consider first attempt as a main one. Second one is
// needed just for some unknown edge case scenarios. See link:
// https://greasyfork.org/scripts/420872/discussions/216921
for (const scriptHref of [
convertScriptHrefToAnOldFormat(href),
href,
]) {
try {
const response = await fetch(scriptHref);
if (response.status !== 200) {
throw new Error(`Bad response: ${response.status}`);
}
url = window.URL.createObjectURL(await response.blob());
} catch (e) {
fetchErrors.push(e);
}
}
if (!url) {
fetchErrors.forEach(e => console.error(e));
throw new Error('Failed to fetch. See console');
}
const a = document.createElement('a');
a.href = url;
a.download = `${name}${fileExt}`;
document.body.appendChild(a); // is needed due to firefox bug
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
}());
Finally it works very well with this version 2.1.5b, here's the proof in a screenshot :-)
In additon, do you think that it's also possible to add the version number directly to the backup please ?
-> Remove YouTube Movie Purchase 1.0.1.user.js instead of Remove YouTube Movie Purchase.user.js
-> Remove YouTube Movie Purchase 1.0.1.user.css instead of Remove YouTube Movie Purchase.user.css
This way it's direct and you don't have to do it manually every time because it saves time...
Yeah, makes sense. I'll see how this could be done
2.2.6 has been released. Thanks for your help
Very good, your script is really much better and efficace now and I hope it helps everyone including you.
Thank you very much for your time, work, understanding and kindness ;-)
Hello again,
Sorry for the inconvenience, but do you think that it's possible to run your script on the web archive pages https://archive.org/ or https://web.archive.org/ also please ?
Because some scripts are suddenly removed on Greasy Fork...
For example, I wanted to install this script again, but I have just seen that it removed O_O
However, it's still in the web archive and it can be installed from there :
-> http://web.archive.org/web/20240000000000*/https://greasyfork.org/scripts/456108
-> http://web.archive.org/web/20240302053247/https://greasyfork.org/scripts/456108
In Violentmonkey, there is no option to save scripts unless I'm mistaken and it saves time to save it directly from the web archive instead of always using Tampermonkey :-)
Thank you for your answer.
I'll take a look... If it's not too complicated I'll make it
Okay, because it can really be useful for everyone :-)
I'm not sure how reliable and bugless is this, but it's done
Didn't want to spend a lot of time on it
After spending some time for testing, it's worked perfectly so far (even with the link quoted above this morning) for the archive web links which were updated after the Greasy Fork changes of November 2023 -> https://github.com/Tampermonkey/tampermonkey/issues/1904.
But for the others archive web links before that date, it doesn't work.
I wish to respect your choice, so I don't want to bother you more.
You can indicate on the info page of your script that it also works for the archive web links, because it's really very useful and it can really help everyone.
Thank you very much for all and good courage for the future ;-)
Tested these two and it worked:
If you can't download these it's probably because of your browser weird behavior again. I can see why this might be happening but need to test it from your side, then think how to make it using another approach.. Ugh
Yes, these 2 links worked very well, but not this one on my side -> https://web.archive.org/web/20220827221543/https://greasyfork.org/en/scripts/369400-local-youtube-downloader
Yeah this one doesn't work indeed. There was a different website layout at that time. Looking into a solution
Fixed. The 2.3.0 version you've been using today has been downloading scripts with a WebArchive metadata. I think the WebArchive is, by default, appending some code to all the files it hosts. In order to get a pure file a bit different request is needed. This was fixed in 2.3.1 and all the scripts you've downloaded with 2.3.0 are might be broken and not work properly
Remember this new download from WebArchive feature has been done hastily and might not work well
But if it works now, it's good. You can't really guarantee for a long time a proper work of ANY userscript anyways
But if it works now, it's good. You can't really guarantee for a long time a proper work of ANY userscript anyways
Thank you so much again for all your work and yes I understand perfectly :-)
Hello,
With the latest version 2.1.4 of your script, I always get this error message to download a userscript or a userstyle like the screenshots show :-(
Thank you for your answer.
Google Chrome Browser with Tampermonkey and Violentmonkey