Closed erosman closed 1 year ago
https://github.com/syhyz1990/baiduyun This doesn't work with FireMonkey. I guess you don't have a baiduyun account so if there is any info needed I'm more than happy to provide it. I guess GM_cookie
is the problem. Thanks!
https://github.com/syhyz1990/baiduyun This doesn't work with FireMonkey. I guess you don't have a baiduyun account so if there is any info needed I'm more than happy to provide it. I guess GM_cookie is the problem. Thanks!
GM_cookie
is used by 49 / 63,435 (0.07%) scripts and only supported by Tampermonkey.
The code should work even without it.
https://github.com/syhyz1990/baiduyun/blob/c8bdfc76656d2a00f4684fc462efbe35a0189b75/baiduyun.user.js#L525-L531
Has the developer commented about compatibility with FM? Does it work with GM or VM?
It works with VM but not with GM. In the README they say that VM or TM is required.
VM doesn't support GM_cookie
, so that should not be the problem. At a glance, I dont see any issues. Have you asked them?
If they point out the incompatibility, I will try to work on it.
I'll report this issue there and come back. Thanks!
https://greasyfork.org/en/scripts/432387-general-url-cleaner-revived doesn't work as intended. It might be a problem of how Firemonkey doesn't inject on all frames or whatever. Developer of this script is willing to make it Firemonkey compatible. ReCAPTCHA Solver, Hcaptcha Solver and other like them also victim of this. https://greasyfork.org/en/scripts/428651-tabview-youtube does not work with Firemonkey. https://greasyfork.org/en/scripts/433360-nova-youtube doesn't work before the last update but now looks like this load but doesn't show its icon in YouTube header. So, you can check it. https://greasyfork.org/en/scripts/405614-youtube-polymer-engine-fixes doesn't save user settings. https://greasyfork.org/en/scripts/429143-auto-set-youtube-volume can't set volume. Is there any option in Firemonkey to manually set value in Firemonkey like this script require? If not, I hope you will provide one. Because I have another problem with Nova YouTube that it stuck in loading options page with any user script manager in Firefox (might need to report there). So, I set its settings value manually in TM and VM but cannot in FM. Also, you said to consider UserCSS custom settings if there is a popular demand, then consider me as one. Firemonkey is an incredibly good user script and user styles manager. I like it and hope this to get recognition it deserves. No need to scratch your head over thus, just reported as you seem to seek to make Firemonky compatible with all user scripts which is very good. Spare me for my bad English. Thank you for your good work.
https://greasyfork.org/en/scripts/432387-general-url-cleaner-revived doesn't work as intended. It might be a problem of how Firemonkey doesn't inject on all frames or whatever. Developer of this script is willing to make it Firemonkey compatible.
Replied to the topic
ReCAPTCHA Solver, Hcaptcha Solver and other like them also victim of this.
At a glance, I dont see any issues. It has to be checked by the developers.
https://greasyfork.org/en/scripts/433360-nova-youtube doesn't work before the last update but now looks like this load but doesn't show its icon in YouTube header. So, you can check it.
At a glance, I dont see any issues. Too large to debug. Although not a problem ... dev may consider
// ==/UserScript==
/*jshint esversion: 6 */
window.nova_plugins = [];
Results in:
'Optional chaining' is only available in ES11 (use 'esversion: 11').
Declares but never uses
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_getResourceURL
// @grant GM_addValueChangeListener
// @grant GM_removeValueChangeListener
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_unregisterMenuCommand
// @grant GM_openInTab
https://greasyfork.org/en/scripts/405614-youtube-polymer-engine-fixes doesn't save user settings.
At a glance, I dont see any issues. Does it work with Greasemonkey?
https://greasyfork.org/en/scripts/429143-auto-set-youtube-volume can't set volume. Is there any option in Firemonkey to manually set value in Firemonkey like this script require? If not, I hope you will provide one. Because I have another problem with Nova YouTube that it stuck in loading options page with any user script manager in Firefox (might need to report there). So, I set its settings value manually in TM and VM but cannot in FM.
Userscript should have the UI for the users tos et values. TM has the UI to view/update storage values, but that should not be a replaemnet for userscript's own UI.
Anyway, since the userscript is very small, users can manually set values here, but the code doesnt updates its value, so it has to be uninstalled and reinstalled.
GM_setValue('Default_Volume', 20); //Save the Default YT Volume as 20%
TBH, the code uses static value which makes GM_setValue/GM_getValue
redundant. It is the same as:
// ==UserScript==
// @name Auto Set Youtube Volume
// @namespace YTVol
// @version 0.2
// @description Choose the default volume for YouTube videos!
// @author hacker09
// @match https://www.youtube.com/embed/*
// @match https://www.youtube.com/watch?v=*
// @icon https://www.youtube.com/s/desktop/03f86491/img/favicon.ico
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const defaultVolume = 20;
const data = {
data: {
volume: defaultVolume,
muted: false,
creation: Date.now()
}
};
window.sessionStorage.setItem('yt-player-volume', JSON.stringify(data)); //Set the Default YT Volume
})();
User can manually change the 20
by editing the userscript.
:pushpin: AFA feature to edit userscript storage, let me think about it :thinking:
Also, you said to consider UserCSS custom settings if there is a https://github.com/erosman/support/issues/293, then consider me as one.
As per that topic, there is way to have custom setting by using @require
to import them.
You can also use @import
in CSS which should work fine.
Creating the User Interface will involve more work. let's see.
Sorry I might not be clear first time. For general URL cleaner revived, if you go any site where they use disqus.com for comment, FM doesn't inject that script where TM and VM do. In case of ReCAPTCHA Solver, when you trigger captcha in sites TM and VM run that script but not FM. This happens with similar scripts across different websites. So, reporting all of them to make Firemonkey compatible might not be a viable solution and all developers might not be friendly like you. Instead, if you inspect the problem in your spare time to make Firemonkey support most script might be better.
.
https://greasyfork.org/en/scripts/428651-tabview-youtube doesn't work at all. https://greasyfork.org/en/scripts/405614-youtube-polymer-engine-fixes doesn't work properly. It doesn't act according to user setting instead always load default settings (try to change any setting). Looks like doesn't work with Greasemonkey either. At this point I can't say this script works properly with TM or VM 😊 as I don't use it anymore (use Nova YouTube now). "Userscript should have the UI for the users to set values."- Totally agree with you. As you see not all userscript have this and had to search long for that one to find as an example 😰. Thanks for your consideration to implement userscript storage. Yep, asking about UserCSS custom settings 'User Interface' if you can manage all of this. Thank you again.
For general URL cleaner revived, if you go any site where they use disqus.com for comment, FM doesn't inject that script where TM and VM do. In case of ReCAPTCHA Solver, when you trigger captcha in sites TM and VM run that script but not FM.
I see ... in that case, it seems to be the issue of @allFrames
. FireMonkey by default adheres to Firefox API defaults which is to inject into top frame only, while GM|VM|TM by default inject into all frames. (for more info check Help under defaults).
In majority of cases, the top frame is enough and avoids extra overheads of injecting into sub-frames unnecessarily. However, captcha, disqus, etc are usually included as iframes. Therefore, userscript dealing with them should be set to inject into all frames.
Set @allFrames true
in User Metadata and try.
Already tried that with URL cleaner from another issue you solved but doesn't work here. Solved ReCAPTCHA Solver though.
Already tried that with URL cleaner from another issue you solved but doesn't work here. Solved ReCAPTCHA Solver though.
URL cleaner issue doesn't relate to @allFrames
as per my reply to the mentioned topic.
Ok then, hope he will solve the issue. Looking forward to seeing future feature implementation and better scripts compatibility in firemonkey. Thank you.
:pushpin: AFA feature to edit userscript storage, let me think about it :thinking:
v2.45
Added storage view/edit feature
https://greasyfork.org/en/scripts/7543-google-search-extra-buttons
Doesn't load unless explicitly asked to via the run command, probably something to do with run-on but beyond my ken I'm sorry
https://greasyfork.org/en/scripts/7543-google-search-extra-buttons Doesn't load unless explicitly asked to via the run command,
The patterns have issues e.g.
https://www.google.*/*
also matches https://www.google.*/search*
so no need for https://www.google.*/search*
https://encrypted.google.*/*
also matches https://encrypted.google.*/search*
so no need for https://encrypted.google.*/search*
I found the reason for not injecting which is due to the processing order when patterns like https://www.google.*/*
are used. I will fix it in v2.45.
For now, if you only use google.com
, add this to the User Metadata
@disable-include
@match *://www.google.com/*
@match https://encrypted.google.com/*
@match https://spmbt.github.io/googleSearchExtraButtons/saveYourLocalStorage.html
@match https://www.gstatic.com/sites/p/b9356d/system/services/test.html
@match https://www.gstatic.com/index.html
https://greasyfork.org/en/scripts/436115-return-youtube-dislike
The userscript is not working in FireMonkey v2.44.
https://greasyfork.org/en/scripts/436115-return-youtube-dislike
The userscript in not working in FireMonkey v2.44.
Unfortunately it is due to the CORS restriction Bug 1715249 in userScript
context when it is using fetch
on line 219.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://returnyoutubedislikeapi.com/votes?videoId=DkYQksqpFyg. (Reason: CORS request did not succeed). Status code: (null).
GM.fetch
/GM.xmlHttpRequest
would work if developers decides to update it. I will see if I can work out anything until the Firefox bug is fixed.
Unfortunately it is due to the CORS restriction Bug 1715249 in userScript context when it is using fetch on line 219.
Just for reference, ViolentMonkey and TamperMonkey are working with the script at https://greasyfork.org/en/scripts/436115-return-youtube-dislike
Just for reference, ViolentMonkey and TamperMonkey are working with the script at https://greasyfork.org/en/scripts/436115-return-youtube-dislike
GM|TM|VM inject into content
context while FireMonkey injects into secure userScript
context. The bug relates to userScript
context.
Note: Script declares @grant GM.xmlHttpRequest
but never uses it.
Here is the same script, with added compatibility with FireMonkey as well (line 219-234 no other changes):
// ==UserScript==
// @name Return YouTube Dislike
// @namespace https://www.returnyoutubedislike.com/
// @homepage https://www.returnyoutubedislike.com/
// @version 0.9.0
// @encoding utf-8
// @description Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/
// @icon https://github.com/Anarios/return-youtube-dislike/raw/main/Icons/Return%20Youtube%20Dislike%20-%20Transparent.png
// @author Anarios & JRWR
// @match *://*.youtube.com/*
// @exclude *://music.youtube.com/*
// @exclude *://*.music.youtube.com/*
// @compatible chrome
// @compatible firefox
// @compatible opera
// @compatible safari
// @compatible edge
// @grant GM.xmlHttpRequest
// @connect youtube.com
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
const LIKED_STATE = "LIKED_STATE";
const DISLIKED_STATE = "DISLIKED_STATE";
const NEUTRAL_STATE = "NEUTRAL_STATE";
let previousState = 3; //1=LIKED, 2=DISLIKED, 3=NEUTRAL
let likesvalue = 0;
let dislikesvalue = 0;
let isMobile = location.hostname == "m.youtube.com";
let mobileDislikes = 0;
function cLog(text, subtext = "") {
subtext = subtext.trim() === "" ? "" : `(${subtext})`;
console.log(`[Return YouTube Dislikes] ${text} ${subtext}`);
}
function getButtons() {
if (isMobile) {
return document.querySelector(".slim-video-action-bar-actions");
}
if (document.getElementById("menu-container")?.offsetParent === null) {
return document.querySelector("ytd-menu-renderer.ytd-watch-metadata > div");
} else {
return document
.getElementById("menu-container")
?.querySelector("#top-level-buttons-computed");
}
}
function getLikeButton() {
return getButtons().children[0];
}
function getDislikeButton() {
return getButtons().children[1];
}
function isVideoLiked() {
if (isMobile) {
return (
getLikeButton().querySelector("button").getAttribute("aria-label") ==
"true"
);
}
return getLikeButton().classList.contains("style-default-active");
}
function isVideoDisliked() {
if (isMobile) {
return (
getDislikeButton().querySelector("button").getAttribute("aria-label") ==
"true"
);
}
return getDislikeButton().classList.contains("style-default-active");
}
function isVideoNotLiked() {
if (isMobile) {
return !isVideoLiked();
}
return getLikeButton().classList.contains("style-text");
}
function isVideoNotDisliked() {
if (isMobile) {
return !isVideoDisliked();
}
return getDislikeButton().classList.contains("style-text");
}
function checkForUserAvatarButton() {
if (isMobile) {
return;
}
if (document.querySelector('#avatar-btn')) {
return true
} else {
return false
}
}
function getState() {
if (isVideoLiked()) {
return LIKED_STATE;
}
if (isVideoDisliked()) {
return DISLIKED_STATE;
}
return NEUTRAL_STATE;
}
function setLikes(likesCount) {
if (isMobile) {
getButtons().children[0].querySelector(".button-renderer-text").innerText =
likesCount;
return;
}
getButtons().children[0].querySelector("#text").innerText = likesCount;
}
function setDislikes(dislikesCount) {
if (isMobile) {
mobileDislikes = dislikesCount;
return;
}
getButtons().children[1].querySelector("#text").innerText = dislikesCount;
}
(typeof GM_addStyle != "undefined"
? GM_addStyle
: (styles) => {
let styleNode = document.createElement("style");
styleNode.type = "text/css";
styleNode.innerText = styles;
document.head.appendChild(styleNode);
})(`
#return-youtube-dislike-bar-container {
background: var(--yt-spec-icon-disabled);
border-radius: 2px;
}
#return-youtube-dislike-bar {
background: var(--yt-spec-text-primary);
border-radius: 2px;
transition: all 0.15s ease-in-out;
}
.ryd-tooltip {
position: relative;
display: block;
height: 2px;
top: 9px;
}
.ryd-tooltip-bar-container {
width: 100%;
height: 2px;
position: absolute;
padding-top: 6px;
padding-bottom: 28px;
top: -6px;
}
`);
function createRateBar(likes, dislikes) {
if (isMobile) {
return;
}
let rateBar = document.getElementById("return-youtube-dislike-bar-container");
const widthPx =
getButtons().children[0].clientWidth +
getButtons().children[1].clientWidth +
8;
const widthPercent =
likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
if (!rateBar && !isMobile) {
document.getElementById("menu-container").insertAdjacentHTML(
"beforeend",
`
<div class="ryd-tooltip" style="width: ${widthPx}px">
<div class="ryd-tooltip-bar-container">
<div
id="return-youtube-dislike-bar-container"
style="width: 100%; height: 2px;"
>
<div
id="return-youtube-dislike-bar"
style="width: ${widthPercent}%; height: 100%"
></div>
</div>
</div>
<tp-yt-paper-tooltip position="top" id="ryd-dislike-tooltip" class="style-scope ytd-sentiment-bar-renderer" role="tooltip" tabindex="-1">
<!--css-build:shady-->${likes.toLocaleString()} / ${dislikes.toLocaleString()}
</tp-yt-paper-tooltip>
</div>
`
);
} else {
document.getElementById(
"return-youtube-dislike-bar-container"
).style.width = widthPx + "px";
document.getElementById("return-youtube-dislike-bar").style.width =
widthPercent + "%";
document.querySelector(
"#ryd-dislike-tooltip > #tooltip"
).innerHTML = `${likes.toLocaleString()} / ${dislikes.toLocaleString()}`;
}
}
function setState() {
cLog("Fetching votes...");
let statsSet = false;
if (GM_info.scriptHandler === 'FireMonkey') {
GM.fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`, {responseType: 'json'})
.then((response) => response.json)
.then((json) => {
if (json && !("traceId" in json) && !statsSet) {
const { dislikes, likes } = json;
cLog(`Received count: ${dislikes}`);
likesvalue = likes;
dislikesvalue = dislikes;
setDislikes(numberFormat(dislikes));
createRateBar(likes, dislikes);
}
});
setState = function(){};
return;
}
fetch(
`https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`
).then((response) => {
response.json().then((json) => {
if (json && !("traceId" in response) && !statsSet) {
const { dislikes, likes } = json;
cLog(`Received count: ${dislikes}`);
likesvalue = likes;
dislikesvalue = dislikes;
setDislikes(numberFormat(dislikes));
createRateBar(likes, dislikes);
}
});
});
setState = function(){};
}
function likeClicked() {
if (checkForUserAvatarButton() == true) {
if (previousState == 1) {
likesvalue--;
createRateBar(likesvalue, dislikesvalue);
setDislikes(numberFormat(dislikesvalue));
previousState = 3
} else if (previousState == 2) {
likesvalue++;
dislikesvalue--;
setDislikes(numberFormat(dislikesvalue))
createRateBar(likesvalue, dislikesvalue);
previousState = 1
} else if (previousState == 3) {
likesvalue++;
createRateBar(likesvalue, dislikesvalue)
previousState = 1
}
}
}
function dislikeClicked() {
if (checkForUserAvatarButton() == true) {
if (previousState == 3) {
dislikesvalue++;
setDislikes(numberFormat(dislikesvalue));
createRateBar(likesvalue, dislikesvalue);
previousState = 2
} else if (previousState == 2) {
dislikesvalue--;
setDislikes(numberFormat(dislikesvalue));
createRateBar(likesvalue, dislikesvalue);
previousState = 3
} else if (previousState == 1) {
likesvalue--;
dislikesvalue++;
setDislikes(numberFormat(dislikesvalue));
createRateBar(likesvalue, dislikesvalue);
previousState = 2
}
}
}
function setInitialState() {
setState();
}
function getVideoId() {
const urlObject = new URL(window.location.href);
const pathname = urlObject.pathname;
if (pathname.startsWith("/clip")) {
return document.querySelector("meta[itemprop='videoId']").content;
} else {
return urlObject.searchParams.get("v");
}
}
function isVideoLoaded() {
if (isMobile) {
return document.getElementById("player").getAttribute("loading") == "false";
}
const videoId = getVideoId();
return (
document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null
);
}
function roundDown(num) {
if (num < 1000) return num;
const int = Math.floor(Math.log10(num) - 2);
const decimal = int + (int % 3 ? 1 : 0);
const value = Math.floor(num / 10 ** decimal);
return value * 10 ** decimal;
}
function numberFormat(numberState) {
let userLocales;
try {
userLocales = new URL(
Array.from(document.querySelectorAll("head > link[rel='search']"))
?.find((n) => n?.getAttribute("href")?.includes("?locale="))
?.getAttribute("href")
)?.searchParams?.get("locale");
} catch {}
const formatter = Intl.NumberFormat(
document.documentElement.lang || userLocales || navigator.language,
{
notation: "compact",
}
);
return formatter.format(roundDown(numberState));
}
function setEventListeners(evt) {
let jsInitChecktimer;
function checkForJS_Finish(check) {
console.log();
if (getButtons()?.offsetParent && isVideoLoaded()) {
clearInterval(jsInitChecktimer);
const buttons = getButtons();
if (!window.returnDislikeButtonlistenersSet) {
cLog("Registering button listeners...");
buttons.children[0].addEventListener("click", likeClicked);
buttons.children[1].addEventListener("click", dislikeClicked);
window.returnDislikeButtonlistenersSet = true;
}
setInitialState();
}
}
cLog("Setting up...");
jsInitChecktimer = setInterval(checkForJS_Finish, 111);
}
(function () {
"use strict";
window.addEventListener("yt-navigate-finish", setEventListeners, true);
setEventListeners();
})();
if (isMobile) {
let originalPush = history.pushState;
history.pushState = function (...args) {
window.returnDislikeButtonlistenersSet = false;
setEventListeners(args[2]);
return originalPush.apply(history, args);
};
setInterval(() => {
getDislikeButton().querySelector(".button-renderer-text").innerText =
mobileDislikes;
}, 1000);
}
I write some scripts that use GM_openInTab and window.focus and window.close: GM_openIntab return the opened tab (A), addEventLinstener to focus A and close it auto. I'm using Violentmonkey, I know FireMonkey don't plan to suppot window.focus and window.close, however, what can I do?
I write some scripts that use GM_openInTab and window.focus and window.close: GM_openIntab return the opened tab (A), addEventLinstener to focus A and close it auto. I'm using Violentmonkey, I know FireMonkey don't plan to suppot window.focus and window.close, however, what can I do?
GM_openIntab -> focus
What focus does it to make the tab active. GM.openInTab(url)
creates a tab and makes it active.
Unless the script wants to make tab active later, or on some condition, that should do the job.
window.close
I have updated GM.openIntab/GM_openIntab
which should allow JavaScript window.close()
to work when the API is used.
It will be in v2.45
It is not fully tested but I tried it with the following test userscript and it works.
// ==UserScript==
// @name window.close test
// @match *://*.example.com/*
// @match *://github.com/*
// ==/UserScript==
if (location.hostname.endsWith('github.com')) {
window.setTimeout(() => GM.openInTab('https://example.com/'), 2000);
}
else {
window.setTimeout(() => window.close(), 2000);
}
Unless the script wants to make tab active later
Yes, need later.
https://greasyfork.org/en/scripts/428651-tabview-youtube
The userscript is not working in FireMonkey v2.45.
https://greasyfork.org/en/scripts/428651-tabview-youtube
The userscript is not working in FireMonkey v2.45.
At first glance, I don't see any issues. The script to too large and has large minified @require
to debug properly.
If the developer points out the issue, I will see what can be done.
PS. I posted to Return YouTube Dislike with the code but there is no response so far.
@erosman,
The script at https://greasyfork.org/en/scripts/436115-return-youtube-dislike with your changes is working in FireMonkey v2.45. Thank you.
Just for reference, ViolentMonkey and TamperMonkey are working with the script at https://greasyfork.org/en/scripts/428651-tabview-youtube
https://greasyfork.org/en/scripts/419825-opera-browser-rocker-mouse-gestures-search-highlight script does not work as expected. Units and currency converters of this script not working with FM. You can use this page to check currency conversion. Thanks.
https://greasyfork.org/en/scripts/419825-opera-browser-rocker-mouse-gestures-search-highlight script does not work as expected. Units and currency converters of this script not working with FM. You can use this page to check currency conversion. Thanks.
It could the be due to Bug 1715249 as the script uses fetch
.
I am waiting for the bug to be fixed.
Maybe. But no CORS errors are displayed in the console and enabling CORS with extension doesn't help. Also i do not understand a thing about those (or any of those hard code you write😯). So I am leaving all the hard work upon you🤣. Thanks again.
If there is no CORS error, then I didn't notice anything else. I tried it a bit but the script is too large to debug. If the dev point out the issue, I can work on it.
He specifically mentioned "Scripts for tampermonkey". Works fine with VM. I will ask him in greasyfork if he can help.
Actually, I get an error with that script when selecting a currently. The popup comes up but doesn't show exchange. Note: TM alters page CSP so you wont get that error with TM.
TypeError: NetworkError when attempting to fetch resource. line 332
When trying it directly e.g.
fetch('https://api.allorigins.win/get?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3D205%20EUR%20in%20CAD')
.then(response => response.json())
.then(data => console.log(data))
.catch(console.error);
GET https://api.allorigins.win/get?url=https://www.google.com/search?q=205 EUR in CAD TypeError: NetworkError when attempting to fetch resource. Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.allorigins.win/get?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3D205%20EUR%20in%20CAD. (Reason: CORS request did not succeed). Status code: (null). Content Security Policy: The page’s settings blocked the loading of a resource at https://api.allorigins.win/get?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3D205%20EUR%20in%20CAD (“default-src”).
Looks like some fetch related CORS error indeed. Unit converter works fine only problem is to convert currency cause script needs to fetch data from google. Here is what the dev had to say about this. Might have to wait for Mozilla to fix Firefox bug. Thanks.
I probably will not waste time changing the script from fetch to XMLHttpRequest just for the script to work on that extension.
fetch
and XMLHttpRequest
(any HTTP request)GM_xmlhttpRequest
was created specifically due to CORS restriction and has no other purpose (FireMonkey also has GM.fetch
)GM_xmlhttpRequest
to overcome CORS restrictionGM_xmlhttpRequest
does
Pull contents from any page via API (as JSON/P or raw) and avoid Same-origin policy problems.
content-security-policy: frame-ancestors 'self' *.gsmarena.com;
Add-on Policies Add-ons must not relax web page security headers, such as the Content Security Policy.
The issue of CORS affects all userscript managers (FM|GM|VM) except TM since TM changes page CSP
GM|TM|VM inject into content context while FireMonkey injects into secure userScript context. The bug relates to userScript context.
I was under the impression that CORS only affect scripts running using userScripts API as you mentioned previously and also VM runs the script fine.
CORS on this page affects the XHR to https://api.allorigins.win/ (nothing to do with google)
For this purpose, https://api.allorigins.win/ is doing exactly what GM_xmlhttpRequest does
Pull contents from any page via API (as JSON/P or raw) and avoid Same-origin policy problems.
If I understood correctly https://api.allorigins.win/ solution plagued itself with same problem it try to solve.
Changing page CSP is not allowed by Mozilla
I thought CSP is some form of telemetry for web sites also there is an option to disable CSP reporting in ublock origin.
I was under the impression that CORS only affect scripts running using userScripts API as you mentioned previously and also VM runs the script fine.
CORS is a safeguard that affects all HTTP comminations. The issue of userScripts context bug adds to the complications.
page
context but provides GM API in page
context
page
context but changes the CSPAbove measures circumvent CORS protection against XSS.
The difference with GM_xmlhttpRequest
API is that it is less visible to users.
CORS is there to safeguard users' security. For example, CORS prevents a userscript that runs everywhere (too many of those with @include *
), to run in users' bank login page and grab user/pass and send it to a remote location.
Cross-Origin Resource Sharing (CORS) For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example, XMLHttpRequest and the Fetch API follow the same-origin policy. This means that a web application using those APIs can only request resources from the same origin the application was loaded from unless the response from other origins includes the right CORS headers.
Content Security Policy (CSP) Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft, to site defacement, to malware distribution.
For best security, CORS & CSP should not be bypassed.
If I understood correctly https://api.allorigins.win/ solution plagued itself with same problem it try to solve.
Above API aims to bypass CORS restrictions for scripts, same as GM_xmlhttpRequest
.
I thought CSP is some form of telemetry for web sites also there is an option to disable CSP reporting in ublock origin.
Not at all .... Users sometimes want to bypass CORS as some features that they want might not be possible (e.g. some userscripts), however that is their own personal choice.
Block CSP reports blocks the feedback of the browser response on CSP violation. It does not block the CSP itself.
Thanks for detailed explanation of CORS & CSP. As it stands, TM alters CSP and VM temper CORS somehow. Best way for background HTTP request is using GM_xmlhttpRequest which mitigate CORS error. FM also support fetch but now bugged. If it get more complicated and technical then this that will surely go over my head. So if script author don't want to change from fetch to GM_xmlhttpRequest that's fine as FM supports both.
FM also support fetch but now bugged.
fetch
is standard JavaScript. All browsers, extension, userscripts use and support JavaScript.
Bug 1715249 is related to CORS. ATM, standard fetch
in userScripts
context (FM) is more restrictive than standard fetch
in content
context (GM|TM|VM).
So if script author don't want to change from fetch to GM_xmlhttpRequest that's fine as FM supports both.
fetch
is the modern method of HTTP request over the older method of xmlHttpRequest
fetch
or xmlHttpRequest
can run into CORS issuefetch
or xmlHttpRequest
is not advisedGM_xmlhttpRequest|GM.xmlHttpRequest
or GM_fetch|GM.fetch
(FM only)fetch
or xmlHttpRequest
from the page
contextThat means currency conversion option of that script out reach for now. Maybe I shall ask him one more time to use GM_xmlhttpRequest|GM.xmlHttpRequest
as this will only leads better compatibility. Looked at Return YouTube Dislike, doesn't need GM.fetch
if @inject-into page
user metadata used. So FM can also inject scripts in page context like VM? But same isn't true for Opera Browser Rocker. It gets totally broken with @inject-into page
. Now if I use your modified Return YouTube Dislike with GM.fetch
, then there is no update to newer version. So this is not a permanent solution until script author support the change. We can not force them to do that. You are doing a great work considering the limitations in this area.
Here is another script that does not work with FM. Take your time, i don't use it. Thank you.
Maybe I shall ask him one more time to use GM_xmlhttpRequest|GM.xmlHttpRequest as this will only leads better compatibility.
GM_xmlhttpRequest|GM.xmlHttpRequest
is supported by all userscript managers
So FM can also inject scripts in page context like VM?
Yes.. but VM allows GM API in page
context while FM & TM don't. If a script doesn't use any GM API, then that option can be used.
Here is a copy of Opera Browser Rocker+Mouse that works with FireMonkey as well as TM & VM.
I only updated the fetch
lines 322-325 (1.5 lines of extra code)
// ==UserScript==
// @name Opera Browser Rocker+Mouse Gestures + Search HighLight
// @namespace OperaBrowserGestures
// @description This script works on any browser and simulates the Opera Browser Mouse+Rocker Gestures, along with the Search HighLight and Units+Currency Converters, but with this script you can modify or disable them as you want.
// @version 0.0.48
// @author hacker09
// @include *
// @icon https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://www.opera.com/&size=64
// @require https://code.jquery.com/jquery-3.5.1.min.js
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @grant window.close
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// ==/UserScript==
// *** Mouse Gesture Settings Below *****************************************************************************************************************************************
GM_registerMenuCommand("Enable/Disable Mouse Gestures", MouseGestures); //Adds an option to the tampermonkey menu
if (GM_getValue("MouseGestures") !== true && GM_getValue("MouseGestures") !== false) { //If the value doesn't exist define as true
GM_setValue("MouseGestures", true); //Defines the variable as true
} //Finishes the if condition
function MouseGestures() //Function to enable or disable the MouseGestures
{ //Starts the function MouseGestures
if (GM_getValue("MouseGestures") === true) { //If the last config was true, set as false
GM_setValue("MouseGestures", false); //Defines the variable as false
} //Finishes the if condition
else { //If the last config was false, set as true
GM_setValue("MouseGestures", true); //Defines the variable as true
location.reload(); //Reloads the page
} //Finishes the else condition
} //Finishes the function MouseGestures
if (GM_getValue("MouseGestures") === true) //If the MouseGestures is enabled
{ //Starts the if condition
const SENSITIVITY = 3; // Adjust the script mouse senvity here between 1 ~ 5
const TOLERANCE = 3; // Adjust the script mouse tolerance here between 1 ~ 5
const funcs = { //Variable to store the functions
'L': function() { //Function that will run when the mouse movement Left is performed
window.history.back(); //Go Back
}, //Finishes the mouse movement Left
'R': function() { //Function that will run when the mouse movement Right is performed
window.history.forward(); //Go Forward
}, //Finishes the mouse movement Right
'D': function() { //Function that will run when the mouse movement Down is performed
if (IsShiftNotPressed === true) { //If the shift key isn't being pressed
GM_openInTab(link, { //Open the link on a new tab
active: true, //Focus on the new tab
insert: true, //Insert the new tab after the actual tab
setParent: true //Return to the tab the user was in
}); //Open the link that was hovered
} //Finishes the if condition
}, //Finishes the mouse movement Down
'UD': function() { //Function that will run when the mouse movement Up+Down is performed
window.location.reload(); //Reload the Tab
}, //Finishes the mouse movement Up+Down
'DR': function(e) { //Function that will run when the mouse movement Down+Right is performed
window.top.close(); //Close the tab
e.preventDefault(); //Prevent the default context menu from being opened
e.stopPropagation(); //Prevent the default context menu from being opened
}, //Finishes the mouse movement Down+Right
'DU': function() { //Function that will run when the mouse movement Down+Up is performed
GM_openInTab(link, { //Open the link that was hovered
active: false, //Don't focus on the new tab
insert: true, //Insert the new tab after the actual tab
setParent: true //Return to the tab the user was in
}); //Open the link that was hovered on a new background tab
} //Finishes the mouse movement Down+Up
}; //Finishes the variable to store the functions
// *** Below this line is the math codes that track the mouse movement gestures *******************************************************************************************
const s = 1 << ((7 - SENSITIVITY) << 1);
const t1 = Math.tan(0.15708 * TOLERANCE),
t2 = 1 / t1;
let x, y, path;
const tracer = function(e) { //Starts the const tracer
let cx = e.clientX,
cy = e.clientY,
deltaX = cx - x,
deltaY = cy - y,
distance = deltaX * deltaX + deltaY * deltaY;
if (distance > s) {
let slope = Math.abs(deltaY / deltaX),
direction = '';
if (slope > t1) {
direction = deltaY > 0 ? 'D' : 'U';
} else if (slope <= t2) {
direction = deltaX > 0 ? 'R' : 'L';
}
if (path.charAt(path.length - 1) !== direction) {
path += direction;
}
x = cx;
y = cy;
}
}; //Finishes the const tracer
window.addEventListener('mousedown', function(e) { //Add an advent listener to the page to detect when the mouse is clicked
if (e.which === 3) { //Starts the if condition
x = e.clientX;
y = e.clientY;
path = "";
window.addEventListener('mousemove', tracer, false); //Add an advent listener to the page to detect the mouse position
} //Finishes the if condition
}, false); //Finishes the advent listener
var IsShiftNotPressed = true; //Variable to hold the shift key status
window.addEventListener("contextmenu", function(e) { //Adds an advent listener to the page to know when the shift key is pressed or not
if (e.shiftKey) { //If the shift key was pressed
IsShiftNotPressed = false; //Variable to hold the shift key status
window.open(link, '_blank', 'height=' + window.screen.height + ',width=' + window.screen.width); //Open the link on a new window
} //Finishes the if condition
if (LeftClicked === true) { //If the Left Click was released when the Rocker Mouse Gestures are activated
e.preventDefault(); //Prevent the default context menu from being opened
e.stopPropagation(); //Prevent the default context menu from being opened
} //Finishes the if condition
setTimeout(function() { //Starts the settimeout function
IsShiftNotPressed = true; //Variable to hold the shift key status
}, 500); //Finishes the settimeout function
}, false); //Finishes the advent listener
window.addEventListener('contextmenu', function(e) { //When the right click button is released
window.removeEventListener('mousemove', tracer, false); //Stop tracking the mouse movements
if (path !== "") { //Starts the if condition
e.preventDefault(); //Prevent the default context menu from being opened
if (funcs.hasOwnProperty(path)) { //Starts the if condition
funcs[path]();
} //Finishes the if condition
} //Finishes the if condition
}, false); //Finishes the advent listener
var link; //Make the variable global
Array.from(document.querySelectorAll('a')).forEach(Element => Element.onmouseover = function() { //Get all the a link elements and add an advent listener to the link element
link = this.href; //Store the actual hovered link to a variable
}); //Finishes the forEach
Array.from(document.querySelectorAll('a')).forEach(Element => Element.onmouseout = function() { //Get all the a link elements and add an advent listener to the link element
var PreviousLink = link; //Save the actual hovered link to another variable
setTimeout(function() { //Starts the settimeout function
if (PreviousLink === link) //If he actual hovered link is still the same as the Previously hovered Link
{ //Starts the if condition
link = 'about:newtab'; //Make the script open a new browser tab when the mouse leaves any link that was hovered
} //Finishes the if condition
}, 200); //Finishes the settimeout function
}); //Finishes the forEach
} //Finishes the if condition
// *** Rocker Mouse Gesture Settings Below ***********************************************************************************************************************************
GM_registerMenuCommand("Enable/Disable Rocker Mouse Gestures", RockerMouseGestures); //Adds an option to the tampermonkey menu
if (GM_getValue("RockerMouseGestures") !== true && GM_getValue("RockerMouseGestures") !== false) { //If the value doesn't exist define as false
GM_setValue("RockerMouseGestures", false); //Defines the variable as false
} //Finishes the if condition
function RockerMouseGestures() //Function to enable or disable the RockerMouseGestures
{ //Starts the function RockerMouseGestures
if (GM_getValue("RockerMouseGestures") === true) { //If the last config was true, set as false
GM_setValue("RockerMouseGestures", false); //Defines the variable as false
} //Finishes the if condition
else { //If the last config was false, set as true
GM_setValue("RockerMouseGestures", true); //Defines the variable as true
location.reload(); //Reloads the page
} //Finishes the else condition
} //Finishes the function RockerMouseGestures
if (GM_getValue("RockerMouseGestures") === true || GM_getValue("SearchHiLight") === true) //If the RockerMouseGestures or the SearchHiLight is enabled
{ //Starts the if condition
var LeftClicked, RightClicked; //Make these variables global
window.addEventListener("mousedown", function(e) { //Detect the right and left mouse clicks presses on the page
switch (e.button) { //Start the switch condition
case 0: //If Left Click was Pressed
LeftClicked = true; //Set the variable LeftClicked as true
break; //Don't execute the lines below if the Left Key was Pressed
case 2: //If Right Click was Pressed
RightClicked = true; //Set the variable RightClicked as true
break; //Don't execute the lines below if the Right Key was Pressed
} //Finishes the switch condition
}, false); //Finishes the adventlistener mousedown
window.addEventListener("mouseup", function(e) { //Detect the right and left mouse clicks releases on the page
switch (e.button) { //Start the switch condition
case 0: //If Left Click was released
LeftClicked = false; //Set the variable LeftClicked as false
break; //Don't execute the lines below if the Left Key was Pressed
case 2: //If Right Click was released
RightClicked = false; //Set the variable RightClicked as false
break; //Don't execute the lines below if the Left Key was Pressed
} //Finishes the switch condition
if (LeftClicked && RightClicked === false) { //If Left was Clicked and then Right Click was released
window.history.back(); //Go Back
} //Finishes the if condition
if (RightClicked && LeftClicked === false) { //If Right was Clicked and then Left Click was released
window.history.forward(); //Go Forward
} //Finishes the if condition
}, false); //Finishes the adventlistener mouseup
} //Finishes the if condition
// *** SearchHighLight Below *************************************************************************************************************************************************
GM_registerMenuCommand("Enable/Disable SearchHiLight", SearchHiLight); //Adds an option to the tampermonkey menu
if (GM_getValue("SearchHiLight") !== true && GM_getValue("SearchHiLight") !== false) { //If the value doesn't exist define as true
GM_setValue("SearchHiLight", true); //Defines the variable as true
} //Finishes the if condition
if (GM_getValue("CurrenciesConverter") !== true && GM_getValue("CurrenciesConverter") !== false) { //If the value doesn't exist define as true
GM_setValue("CurrenciesConverter", true); //Defines the variable as true
} //Finishes the if condition
if (GM_getValue("UnitsConverter") !== true && GM_getValue("UnitsConverter") !== false) { //If the value doesn't exist define as true
GM_setValue("UnitsConverter", true); //Defines the variable as true
} //Finishes the if condition
function SearchHiLight() //Function to enable or disable the SearchHiLight and the Currency/Unit converters
{ //Starts the function SearchHiLight
if (GM_getValue("SearchHiLight") === true) { //If the last config was true, set as false
GM_setValue("SearchHiLight", false); //Defines the variable as false
GM_setValue("CurrenciesConverter", false); //Defines the variable as false
GM_setValue("UnitsConverter", false); //Defines the variable as false
} //Finishes the if condition
else { //If the last config was false, set as true
GM_setValue("SearchHiLight", true); //Defines the variable as true
if (confirm('If you want to enable the Currency Converter press OK.')) //Show the confimation alert box text
{ //Starts the if condition
GM_setValue("CurrenciesConverter", true); //Defines the variable as true
} //Finishes the if condition
else //If the user pressed cancel
{ //Starts the else condition
GM_setValue("CurrenciesConverter", false); //Defines the variable as false
} //Finishes the else condition
if (confirm('If you want to enable the Units Converter press OK.')) //Show the confimation alert box text
{ //Starts the if condition
GM_setValue("UnitsConverter", true); //Defines the variable as true
} //Finishes the if condition
else //If the user pressed cancel
{ //Starts the else condition
GM_setValue("UnitsConverter", false); //Defines the variable as false
} //Finishes the else condition
location.reload(); //Reloads the page
} //Finishes the else condition
} //Finishes the function SearchHiLight
if (GM_getValue("SearchHiLight") === true) //If the SearchHiLight is enabled
{ //Starts the if condition
var $ = window.jQuery; //Defines That The Symbol $ Is A jQuery
var SelectedTextIsLink; //Creates a new global variable
var Links = new RegExp(/\.org|\.ly|\.net|\.co|\.tv|\.me|\.biz|\.club|\.site|\.br|\.gov|\.io|\.jp|\.edu|\.au|\.in|\.it|\.ca|\.mx|\.fr|\.tw|\.il|\.uk|\.zoom\.us/i); //Creates a global variable to check if a link is matched
var FinalCurrency, SelectedText, SelectedTextSearch = ''; //Make these variables global
$(function() { //Starts the function
var menu = $("#highlight_menu", $("#highlight_menu_div")[0].shadowRoot); //Creates a variable to hold the menu element
$(document.body).on('mouseup', function() { //When the user releases the mouse click after selecting something
SelectedText = window.getSelection().toString(); //Creates a variable to store the selected text
SelectedTextSearch = window.getSelection().toString().replaceAll('&', '%26'); //Creates a variable to store the selected text to be opened on google
if (GM_getValue("CurrenciesConverter") === true) { //If the Currencies Converter option is activated
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ''; //Remove the previous Currency text
var Currencies = new RegExp(/^[ \t\xA0]*(?=.*?(\d+(?:.\d+)?))(?=(?:\1[ \t\xA0]*)?(Dólares|dolares|dólares|dollars|AUD|BGN|BRL|BCH|BTC|BYN|CAD|CHF|CNY|CZK|DKK|EUR|EGP|ETH|GBP|GEL|HKD|HRK|HUF|IDR|ILS|INR|JPY|LTC|KRW|MXN|MYR|NOK|NZD|PHP|PLN|RON|RM|RUB|SEK|SGD|THB|TRY|USD|UAH|ZAR|KZT|YTL|\$|R\$|HK\$|US\$|\$US|¥|€|Rp|kn|Kč|kr|zł|£|฿|₩))(?:\1[ \t\xA0]*\2|\2[ \t\xA0]*\1)[ \t\xA0]*$/i); //Adds an regex expression to the Currencies variable
if (SelectedText.match(Currencies) !== null) //If the selected text is a currency
{ //Starts the if condition
if (GM_getValue("YourLocalCurrency") === undefined) { //If the value is undefined
var UserInput = prompt('This is the first time that you selected a currency.\nThis is the first and last time this popup will appear, so please write your local currency so that the script will always use your local currency to make exchange rate conversions.\n*Example of what you should write: BRL\nCAD\nUSD\netc...\n*Press OK'); //Gets the user input
GM_setValue("YourLocalCurrency", UserInput); //Defines the variable as the UserInput
} //Finishes the if condition
(async () => { //Creates a function to get the final value
var CurrencySymbol = SelectedText.match(Currencies)[2]; //Get the actual currency symbol supposing "it's correct"
var CurrencySymbols = new RegExp(/\$|R\$|HK\$|US\$|\$US|¥|€|Rp|kn|Kč|kr|zł|£|฿|₩/i); //Create a variable to check for the selected currency symbols
if (SelectedText.match(Currencies)[2].match(CurrencySymbols) !== null) //If the selected currency contains a symbol
{ //Starts the if condition
switch (SelectedText.match(CurrencySymbols)[0].toLowerCase()) { //If the selected currency constains a symbol
case '$': //Get the actual selected currency symbol
case 'us$': //Get the actual selected currency symbol
case '$us': //Get the actual selected currency symbol
CurrencySymbol = 'USD'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case 'r$': //Get the actual selected currency symbol
CurrencySymbol = 'BRL'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case 'hk$': //Get the actual selected currency symbol
CurrencySymbol = 'HKD'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case "¥": //Get the actual selected currency symbol
CurrencySymbol = 'JPY'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case '€': //Get the actual selected currency symbol
CurrencySymbol = 'EUR'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case 'rp': //Get the actual selected currency symbol
CurrencySymbol = 'IDR'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case 'kn': //Get the actual selected currency symbol
CurrencySymbol = 'HRK'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case 'kč': //Get the actual selected currency symbol
CurrencySymbol = 'CZK'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case 'kr': //Get the actual selected currency symbol
CurrencySymbol = 'DKK'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case 'zł': //Get the actual selected currency symbol
CurrencySymbol = 'PLN'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case '£': //Get the actual selected currency symbol
CurrencySymbol = 'GBP'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case '฿': //Get the actual selected currency symbol
CurrencySymbol = 'THB'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
case '₩': //Get the actual selected currency symbol
CurrencySymbol = 'KRW'; //"Convert" the symbol to the Currency Letters
break; //Stop trying to get the correct Currency Letters
} //Finishes the switch condition
} //Finishes the if condition
const url = `https://api.allorigins.win/get?url=${encodeURIComponent('https://www.google.com/search?q=' + SelectedText.match(Currencies)[1] + ' ' + CurrencySymbol + ' in ' + GM_getValue("YourLocalCurrency"))}`;
const responsehtml = GM_info.scriptHandler === 'FireMonkey' ? (await GM.fetch(url, {responseType: 'json'})).json :
await (await fetch(url)).json(); //Fetch
const newDocument = new DOMParser().parseFromString(responsehtml.contents, 'text/html'); //Parses the fetch response
FinalCurrency = parseFloat(newDocument.querySelector("div.BNeawe.iBp4i.AP7Wnd").innerText.split(' ')[0].replaceAll(',', '')); //Gets the final amount of money
if (SelectedText.match(Currencies)[2].match(CurrencySymbols) !== null) //If the selected currency contains a symbol
{ //Starts the if condition
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = CurrencySymbol + ' 🠂 ' + Intl.NumberFormat(navigator.language, {
style: 'currency',
currency: GM_getValue("YourLocalCurrency")
}).format(FinalCurrency) + ' | '; //Show the FinalCurrency on the menu
} //Finishes the if condition
else //If the selected currency contains no symbol
{ //Starts the else condition
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = Intl.NumberFormat(navigator.language, {
style: 'currency',
currency: GM_getValue("YourLocalCurrency")
}).format(FinalCurrency) + ' | '; //Show the FinalCurrency on the menu
} //Finishes the else condition
var text = document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText; //Save the actual currency text
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onmousemove = function() { //When the mouse is hovering the currency
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = "Copy | "; //Change the element text to copy
}; //Finishes the onmousemove advent listener
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseout = function() { //When the mouse leaves the button
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = text; //Return the previous text
}; //Finishes the onmouseout advent listener
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onclick = function() { //When the user clicks on the currency
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = "Done | "; //Change the element text to copy
navigator.clipboard.writeText(Intl.NumberFormat(navigator.language, {
style: 'currency',
currency: GM_getValue("YourLocalCurrency")
}).format(FinalCurrency)); //Copy the Final Currency
}; //Finishes the onclick advent listener
})(); //Finishes the async function
} //Finishes the if condition
} //Finishes the if condition
//___________________________________________________________________________________________________________________________________________________________________________
if (GM_getValue("UnitsConverter") === true) { //If the Units Converter option is activated
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ''; //Remove the previous Units text
var Units = new RegExp(/^[ \t\xA0]*(-?\d+(?:[., ]\d+)?)[ \t\xA0]*(inch|inches|in|cms?|centimeters?|meters?|ft|kg|lbs?|pounds?|kilograms?|ounces?|g|ozs?|fl oz|fl oz (us)|fluid ounces?|kphs?|km\/h|kilometers per hours?|mphs?|meters per hours?|°[CF]|km\/hs?|ml|milliliters?|l|liters?|litres?|gal|gallons?|yards?|yd|Millimeter|millimetre|kilometers?|mi|mm|miles?|km|ft|fl|feets?|mts?|grams?|kilowatts?|kws?|brake horsepower|mechanical horsepower|hps?|bhps?|miles per gallons?|mpgs?|liters per 100 kilometers?|l\/100km|liquid quarts?|lqs?|foot-?pounds?|ft-?lbs?|lb fts?|newton-?meters?|nm)[ \t\xA0]*(?:\(\w+\)[ \t\xA0]*)?$/i); //Adds an regex expression to the Units variable
if (SelectedText.match(Units) !== null) //If the selected text is an unit
{ //Starts the if condition
var SelectedUnitValue = SelectedText.match(Units)[1].replaceAll(',', '.'); //Get the selected unit value and store the value to a variable
switch (SelectedText.match(Units)[2].toLowerCase()) { //Set all letters to Lower Case and convert the selected unit to another unit the same way the Opera Browser does
case 'inch': //Get the actual selected unit type
case 'inches': //Get the actual selected unit type
case 'in': //Get the actual selected unit type
var NewUnit = 'cm'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 2.54).toFixed(2); //Gets the converted unit result
break; //Stop
case 'centimeter': //Get the actual selected unit type
case 'centimeters': //Get the actual selected unit type
case 'cm': //Get the actual selected unit type
case 'cms': //Get the actual selected unit type
NewUnit = 'in'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 2.54).toFixed(2); //Gets the converted unit result
break; //Stop
case 'mt': //Get the actual selected unit type
case 'mts': //Get the actual selected unit type
case 'meters': //Get the actual selected unit type
case 'meter': //Get the actual selected unit type
NewUnit = 'ft'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 3.281).toFixed(2); //Gets the converted unit result
break; //Stop
case 'kg': //Get the actual selected unit type
case 'kilograms': //Get the actual selected unit type
case 'kilogram': //Get the actual selected unit type
NewUnit = 'lb'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 2.205).toFixed(2); //Gets the converted unit result
break; //Stop
case 'pound': //Get the actual selected unit type
case 'pounds': //Get the actual selected unit type
case 'lb': //Get the actual selected unit type
case 'lbs': //Get the actual selected unit type
NewUnit = 'kg'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 2.205).toFixed(2); //Gets the converted unit result
break; //Stop
case 'ounce': //Get the actual selected unit type
case 'ounces': //Get the actual selected unit type
case 'oz': //Get the actual selected unit type
case 'ozs': //Get the actual selected unit type
NewUnit = 'g'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 28.35).toFixed(2); //Gets the converted unit result
break; //Stop
case 'g': //Get the actual selected unit type
case 'gram': //Get the actual selected unit type
case 'grams': //Get the actual selected unit type
NewUnit = 'oz'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 28.35).toFixed(2); //Gets the converted unit result
break; //Stop
case 'kilometer': //Get the actual selected unit type
case 'kilometers': //Get the actual selected unit type
NewUnit = 'mi'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 1.609).toFixed(2); //Gets the converted unit result
break; //Stop
case 'kph': //Get the actual selected unit type
case 'kphs': //Get the actual selected unit type
case 'km/h': //Get the actual selected unit type
case 'km/hs': //Get the actual selected unit type
case 'kilometers per hour': //Get the actual selected unit type
case 'kilometers per hours': //Get the actual selected unit type
NewUnit = 'mph'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 1000).toFixed(2); //Gets the converted unit result
break; //Stop
case 'mph': //Get the actual selected unit type
case 'mphs': //Get the actual selected unit type
case 'meters per hour': //Get the actual selected unit type
case 'meters per hours': //Get the actual selected unit type
NewUnit = 'km/h'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 1.000).toFixed(2); //Gets the converted unit result
break; //Stop
case 'mi': //Get the actual selected unit type
case 'mile': //Get the actual selected unit type
case 'miles': //Get the actual selected unit type
NewUnit = 'km'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 1.609).toFixed(2); //Gets the converted unit result
break; //Stop
case '°c': //Get the actual selected unit type
NewUnit = '°F'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat((SelectedUnitValue * 9 / 5) + 32).toFixed(2); //Gets the converted unit result
break; //Stop
case '°f': //Get the actual selected unit type
NewUnit = '°C'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat((SelectedUnitValue - 32) * 5 / 9).toFixed(2); //Gets the converted unit result
break; //Stop
case 'ml': //Get the actual selected unit type
case 'milliliter': //Get the actual selected unit type
case 'milliliters': //Get the actual selected unit type
NewUnit = 'fl oz (US)'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 29.574).toFixed(2); //Gets the converted unit result
break; //Stop
case 'fl oz (US)': //Get the actual selected unit type
case 'fl oz': //Get the actual selected unit type
case 'fl': //Get the actual selected unit type
case 'fluid ounce': //Get the actual selected unit type
case 'fluid ounces': //Get the actual selected unit type
NewUnit = 'ml'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 29.574).toFixed(2); //Gets the converted unit result
break; //Stop
case 'l': //Get the actual selected unit type
case 'litre': //Get the actual selected unit type
case 'liter': //Get the actual selected unit type
case 'litres': //Get the actual selected unit type
case 'liters': //Get the actual selected unit type
NewUnit = 'gal (US)'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 3.785).toFixed(2); //Gets the converted unit result
break; //Stop
case 'gal': //Get the actual selected unit type
case 'gallon': //Get the actual selected unit type
case 'gallons': //Get the actual selected unit type
NewUnit = 'lt'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 3.785).toFixed(2); //Gets the converted unit result
break; //Stop
case 'yard': //Get the actual selected unit type
case 'yards': //Get the actual selected unit type
case 'yd': //Get the actual selected unit type
NewUnit = 'm'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 1.094).toFixed(2); //Gets the converted unit result
break; //Stop
case 'mm': //Get the actual selected unit type
case 'millimetre': //Get the actual selected unit type
case 'Millimeters': //Get the actual selected unit type
NewUnit = 'in'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 25.4).toFixed(2); //Gets the converted unit result
break; //Stop
case 'ft': //Get the actual selected unit type
case 'feet': //Get the actual selected unit type
case 'feets': //Get the actual selected unit type
NewUnit = 'mt'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 0.3048).toFixed(2); //Gets the converted unit result
break; //Stop
case 'kw': //Get the actual selected unit type
case 'kws': //Get the actual selected unit type
case 'kilowatt': //Get the actual selected unit type
case 'kilowatts': //Get the actual selected unit type
NewUnit = 'mhp'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 1.341).toFixed(2); //Gets the converted unit result
break; //Stop
case 'mhp': //Get the actual selected unit type
case 'mhps': //Get the actual selected unit type
case 'hp': //Get the actual selected unit type
case 'hps': //Get the actual selected unit type
case 'brake horsepower': //Get the actual selected unit type
case 'mechanical horsepower': //Get the actual selected unit type
NewUnit = 'kw'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 1.341).toFixed(2); //Gets the converted unit result
break; //Stop
case 'mpg': //Get the actual selected unit type
case 'mpgs': //Get the actual selected unit type
case 'miles per gallon': //Get the actual selected unit type
case 'miles per gallons': //Get the actual selected unit type
NewUnit = 'l/100km'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(235.215 / SelectedUnitValue).toFixed(2); //Gets the converted unit result
break; //Stop
case 'l/100km': //Get the actual selected unit type
case 'liters per 100 kilometer': //Get the actual selected unit type
case 'liters per 100 kilometers': //Get the actual selected unit type
NewUnit = 'US mpg'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(235.215 / SelectedUnitValue).toFixed(2); //Gets the converted unit result
break; //Stop
case 'lq': //Get the actual selected unit type
case 'lqs': //Get the actual selected unit type
case 'liquid quart': //Get the actual selected unit type
case 'liquid quarts': //Get the actual selected unit type
NewUnit = 'l'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 1.057).toFixed(2); //Gets the converted unit result
break; //Stop
case 'liter': //Get the actual selected unit type
case 'liters': //Get the actual selected unit type
NewUnit = 'lqs'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 1.057).toFixed(2); //Gets the converted unit result
break; //Stop
case 'foot-pound': //Get the actual selected unit type
case 'foot-pounds': //Get the actual selected unit type
case 'foot pound': //Get the actual selected unit type
case 'foot pounds': //Get the actual selected unit type
case 'ft-lbs': //Get the actual selected unit type
case 'ft-lb': //Get the actual selected unit type
case 'ft lbs': //Get the actual selected unit type
case 'ft lb': //Get the actual selected unit type
case 'lb ft': //Get the actual selected unit type
case 'lb fts': //Get the actual selected unit type
NewUnit = 'Nm'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue * 1.3558179483).toFixed(2); //Gets the converted unit result
break; //Stop
case 'newton-meter': //Get the actual selected unit type
case 'newton-meters': //Get the actual selected unit type
case 'newton meter': //Get the actual selected unit type
case 'newton meters': //Get the actual selected unit type
case 'nm': //Get the actual selected unit type
NewUnit = 'ft lb'; //"Convert" the current unit to another unit
ConvertedUnit = parseFloat(SelectedUnitValue / 1.3558179483).toFixed(2); //Gets the converted unit result
break; //Stop
} //Finishes the switch condition
setTimeout(function() { //Starts the settimeout function
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ConvertedUnit + ' ' + NewUnit + ' | '; //Display the converted unit results
var text = document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText; //Save the actual converted unit value
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onmousemove = function() { //When the mouse is hovering the converted unit value
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = "Copy | "; //Change the element text to copy
}; //Finishes the onmousemove advent listener
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseout = function() { //When the mouse leaves the button
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = text; //Return the previous text
}; //Finishes the onmouseout advent listener
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onclick = function() { //When the user clicks on the converted unit value
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = "Done | "; //Change the element text to copy
navigator.clipboard.writeText(ConvertedUnit); //Copy the Final converted unit value
}; //Finishes the onclick advent listener
}, 0); //Finishes the settimeout
} //Finishes the if condition
} //Finishes the if condition
//___________________________________________________________________________________________________________________________________________________________________________
if (document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#SearchBTN").innerText === 'Open') //If the Search butotn text is 'Open'
{ //Starts the if condition
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#highlight_menu > ul").style.paddingLeft = '7px'; //Change the ul menu element padding left css style
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#SearchBTN").innerText = 'Search'; //The next time that the menu is shown display the button text as Search again
SelectedTextIsLink = false; //Add the value false to the variable to make common words searchable again
} //Finishes the if condition
if (SelectedText.match(Links) !== null) //If the selected text is a link
{ //Starts the if condition
SelectedTextIsLink = true; //Add the value true to the variable
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#highlight_menu > ul").style.paddingLeft = '7px'; //Change the ul menu element padding left css style
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#SearchBTN").innerText = 'Open'; //Change the button text to Open
} //Finishes the if condition
//___________________________________________________________________________________________________________________________________________________________________________
if (document.getSelection().toString().trim() !== '') { //If the user selected something
var p = document.getSelection().getRangeAt(0).getBoundingClientRect(); //Create a new variable to get the positions later
menu.css({ //Set the menu css
left: (p.left + (p.width / 2)) - (menu.width() / 2), //Make the menu show on the correct left position
top: (p.top - menu.height() - 10), //Make the menu show on the correct top position
display: '' //Show the menu on the page
}).animate({ //Creates the animation
opacity: 1 //Set the animation opacity
}, 0); //Add an animation to the menu
menu.addClass('highlight_menu_animate'); //Add the class to animate the menu
//$('head').append('<style>.highlight_menu_animate .popuptext::after { content: ""; top: 100%; left: 50%; margin-left: -10px; position: absolute; border-style: solid; border-color: #292929 transparent transparent transparent; }</style>'); //Create a class to animate the menu
return; //Keep displaying the menu box
} //Finishes the if condition
menu.animate({ //Creates the animation
opacity: 0 //Set the animation opacity
}); //Hide the menu If the user clicked on any of the options
setTimeout(function() { //Hide the menu after some time
menu.css({ //Set the menu css
display: 'none' //Hide the menu on the page
}); //Hide the menu If the user clicked on any of the options
}, 300); //Finishes the setTimeout function
}); //Finishes the mouseup advent listener
}); //Finishes the function
//___________________________________________________________________________________________________________________________________________________________________________
var HtmlMenu = document.createElement('div'); //Creates a variable
HtmlMenu.setAttribute("id", "highlight_menu_div"); //Set the div id to the HtmlMenu variable
HtmlMenu.attachShadow({
mode: 'open'
}).innerHTML = '<style>.highlight_menu_animate .popuptext::after { content: ""; top: 100%; left: 50%; margin-left: -10px; border-top-width: 10px; border-right-width: 10px; border-left-width: 10px; position: absolute; border-style: solid; border-color: #292929 transparent transparent transparent; }</style><div id="highlight_menu" style="display:none; color: #fff; position: fixed; background-color: #292929; font-size: 13.4px; font-family: monospace; z-index: 9999;"> <ul style="margin-block-end: 10px; padding-left: 7px; padding-right: 7px; margin-top:10px;"><li class="popuptext" id="ShowCurrencyORUnits" style="cursor: pointer; display: inline;">' + FinalCurrency + '</li><li class="popuptext" id="SearchBTN" style="cursor: pointer; display: inline;">Search</li><li class="popuptext" id="CopyBTN" style="cursor: pointer; display: inline;"> | Copy</li></ul></div>'; //Set the HtmlMenu div html
if (document.body.textContent !== '' || document.body.innerText !== '') //If the body has any text
{ //Starts the if condition
document.body.appendChild(HtmlMenu); //Append the HtmlMenu div to the page
} //Finishes the if condition
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#SearchBTN").onmousedown = function() { //When the user clicks on the Search button
var LinkfyOrSearch = 'https://www.google.com/search?q='; //Creates a variable to open google
if (SelectedTextIsLink === true) //If the selected text is a link
{ //Starts the if condition
LinkfyOrSearch = 'https://'; //Make the non http and non https links able to be opened
} //Finishes the if condition
if (SelectedText.match(/http:|https:/) !== null) //If the selected text is a link that already has http or https
{ //Starts the if condition
LinkfyOrSearch = ''; //Remove the https:// that was previsouly added to this variable
} //Finishes the if condition
window.open(LinkfyOrSearch + SelectedTextSearch); //Open google and search for the selected word(s)
window.getSelection().removeAllRanges(); //UnSelect the selected text after the search button is clicked so that if the user clicks on the past selected text the menu won't show up again.
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#highlight_menu").style.display = 'none'; //Hide the menu
}; //Finishes the onmousedown advent listener
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#CopyBTN").onmousedown = function() { //When the user clicks on the copy button
navigator.clipboard.writeText(SelectedText); //Copy the selected word(s)
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#CopyBTN").innerText = ' | Done'; //Change the button text to Done
setTimeout(function() { //Starts the setTimeout function
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#CopyBTN").innerText = ' | Copy'; //The next time that the menu is shown display the button text as Copy again, instead of Done
}, 400); //Finishes the setTimeout function
}; //Finishes the onmousedown advent listener
//___________________________________________________________________________________________________________________________________________________________________________
setTimeout(function() { //Starts the setTimeout function
var AllIframes = document.querySelectorAll("iframe"); //Get all iframes on the page
for (var i = AllIframes.length; i--;) { //Starts the for condition
if (AllIframes[i].allow.match('clipboard-write;') === null && AllIframes[i].src.match(Links) !== null && AllIframes[i].src.match(/youtube|dailymotion|vimeo|streamtape|mcloud|vidstream|mp4upload|googlevideo|kaltura|crunchyroll|animesup|google.com\/recaptcha\/|blank.html|\.mp4/) === null && location.href.match(/animeshouse.net|nowanimes.com\/play/) === null) //If the iframe doesn't have the clipboard-write attribute yet and the iframed source attribute has a link. And if the iframe isn't an YT/dailymotion or vimeo video.
{ //Starts the if condition
AllIframes[i].allow = AllIframes[i].allow + 'clipboard-write;'; //Add the permission to copy the iframe text
AllIframes[i].src = AllIframes[i].src; //Reload the iframe to make the iframe have the new permission
} //Finishes the if condition
} //Finishes the for condition
}, 4000); //Finishes the setTimeout function
window.addEventListener('scroll', async function() { //When the page is scrolled
document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#highlight_menu").style.display = 'none'; //Hide the menu
if (LeftClicked === false && SelectedText !== '') { //If the Left Click isn't being holded, and if something is currently selected
window.getSelection().removeAllRanges(); //UnSelect the selected text when scrolling the page down so that if the user clicks on the past selected text the menu won't show up again.
} //Finishes the if condition
}); //Finishes the onscroll advent listener
} //Finishes the if condition
Here is another script that does not work with FM
I will check it out .
Works properly with your changes. Thanks.
@Ghost-BD FireMonkey v2.53 should work with original Opera Browser Rocker+Mouse without the need to change for FM. Try it and let me know.
How do i make https://sleazyfork.org/en/scripts/109-handy-image work with firemonkey? In tampermonkey it works without any issue.
How do i make https://sleazyfork.org/en/scripts/109-handy-image work with firemonkey?
Which version of FireMonkey are you using?
There is a workaround for XMLHttpRequest
in v2.53 which applies to this script.
What happens?
Which version of FireMonkey are you using? There is a workaround for
XMLHttpRequest
in v2.53 which applies to this script. What happens?
Version 2.53 Opening image on supported page and nothing happens (for example https://imagetwist.com/tg41qn3lphx4/1.png) it's supposed to strip advertisement, if I try to run script by hand i get a "Failed to insert return not in function" error.
if I try to run script by hand i get a "Failed to insert return not in function" error.
It doesn't even get injected because the userscript has parse error. :man_shrugging:
This can be checked in Browser Console (Ctrl+Shift+J)
SyntaxError: return not in function line 878
Tampermonkey, Violentmonkey & Greasemonkey wrap the userscripts in an anonymous function in order to limit its exposure to the page JavaScript.
As a result, such JavaScript parse errors do not cause a problem but that created a situation where developers write JavaScript with errors
The code has many invalid return
e.g. lines: 878, 883, 890, 901, 912
As a workaround, you can add the following to the start and end of the userscript:
At the start
(() => {
At the end
})();
This issue is similar to https://github.com/erosman/support/issues/429#issuecomment-1063692016 as I have posted to the "General URL Cleaner Revived" topic.
At the start
(() => {
At the end
})();
Hey thanks, this indeed makes it work. And also somehow makes it run on imdb too, i only see a pic when opening imdb, but if I disable handy userscript then imdb is back to normal, but firemonkey is not even shows it running. https://www.imdb.com/title/tt11138512/
And also somehow makes it run on imdb too, i only see a pic when opening imdb, but if I disable handy userscript then imdb is back to normal, but firemonkey is not even shows it running.
That is due to the mix of @match
& regular expression.
Regular Expression support is experimental & limited in FireMonkey.
Quick temporary fix Add the following to the User Metadata
@disable-exclude /^https://(.*\.)?xhamster(\d{1,2})?\.(com|desi)/photos/gallery/.*/\d{1,3}$/
@include
Convert deprecated @include with regular expression to @match in userscript
eslint: userscripts/better-use-match - Using
@include
is potentially unsafe and may be obsolete in Manifest v3 in early 2023. Please switch to@match
.ref: Tampermonkey lint.js & /_locales/en/messages.json
@disable-exclude /^https://(.*\.)?xhamster(\d{1,2})?\.(com|desi)/photos/gallery/.*/\d{1,3}$/ Indeed, this makes it work, many thanks!
Is there any way to make these changes stick, even after a script update?
Is there any way to make these changes stick, even after a script update?
User Metadata stays after script update. There is no function in FM to wrap script in an anonymous function at the moment.
TBH, it is an easy fix for userscript developers to write JavaScript without parse error. I use a template like the following for all my userscripts.
// ==UserScript==
// @name
// @match
// @author
// @version
// ==/UserScript==
(async () => { // anonymous function wrapper, for error checking & limiting scope, async FF52+
'use strict';
// end of anonymous function
})();
FireMonkey 2.44 should be compatible with 98% of userscripts. Please read Help for more information.
Please post any userscript issues here for further investigation.