Userscript stopped working with #grant and causes issues without #grant - greasemonkey

I've written an userscript for geocaching.com which switches the language automatically to a specified language (German), if English was set by an external app.
It worked fine until about 10 days ago, for no reason I could imagine.
If I remove #grant from the script, it works again, but causes several issues with the rest of the page.
Any #grant I tried, including none, breaks the script.
It's still working as it's supposed to do in Chrome. But I've already heard, Tampermonkey and Chrome are a little different than Greasemonkey on Firefox.
Any idea I can give a try is very welcome.
Here's the script:
// ==UserScript==
// #name c:geo LangFix Deutsch BETA
// #include https://www.geocaching.com/*/*/*
// #include https://www.geocaching.com/*/*
// #include https://www.geocaching.com/*
// #include https://www.geocaching.com
// #include http://www.geocaching.com/*/*/*
// #include http://www.geocaching.com/*/*
// #include http://www.geocaching.com/*
// #include http://www.geocaching.com
// #exclude http://www.geocaching.com/account/messagecenter
// #exclude https://www.geocaching.com/map/*
// #exclude https://www.geocaching.com/map
// #version 1.2
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// #grant GM_xmlhttpRequest
// ==/UserScript==
var TargetLink = $('a[href*="LocaleList$ctl04$uxLocaleItem"]');
var LanguageSwitch = $("div.LocaleText:contains('Choose Language')");
if (TargetLink.length && LanguageSwitch.length)
window.location.assign (TargetLink[0].href);

The main issue is that the href, that the script is trying to assign, is actually a javascript function. That is:
TargetLink[0].href has a value of javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl04$uxLocaleItem','').
Many browsers will still allow you to pass a javascript: URL to location.assign(), but Firefox has been restricting this practice of late. (See GM bug 2232, Firefox bug 1192821, etc.) This is especially true across sandboxes (#grant set to other than none).
A better practice is to click on such links by sending mouse event(s), see below.
Other minor issues with that script:
The #includes are overlapping, redundant, and possibly a performance drag. The #match statement, below, should be all you need.
Likewise, the #excludes can be condensed.
Some geocaching.com pages use a different structure for the language change mechanism. I added an extra selector to catch an additional scheme. (There is at least one more, rarer, scheme that is not caught by either script.)
Selector components like LocaleList$ctl04$uxLocaleItem are extremely brittle and liable to change on a whim. Avoid these as much as possible.
Under some circumstances, that site loads many iframes -- leading to jQuery errors on some of the frames. Use #noframes to block that and speed things up.
Given all that, this complete script should work much better for you than the old one did:
// ==UserScript==
// #name c:geo LangFix Deutsch BETA
// #match *://www.geocaching.com/*
// #exclude http://www.geocaching.com/account/messagecenter
// #exclude https://www.geocaching.com/map/*
// #version 2.0
// #require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// #noframes
// #grant GM_xmlhttpRequest
// ==/UserScript==
var EnglishPageIndicator = $(".selected-language > a:contains(English)");
if (EnglishPageIndicator.length) {
var GermanLnk = $(
".language-selector > .language-list > ul > li > a:contains(Deutsch)," +
".LocaleList > .language-list > li > a:contains(Deutsch)"
);
//-- Don't try to assign a JS location! Click the link instead.
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent ("click", true, true);
GermanLnk[0].dispatchEvent (clickEvent);
}

Related

duktape js - have multiple contexts with own global and reference to one common 'singleton'

We are in the process of embedding JS in our application, and we will use a few dozen scripts each assigned to an event. Inside these scripts we provide a minimal callback api,
function onevent(value)
{ // user javascript code here
}
which is called whenever that event happens. The scripts have to have their own global, since this funtion has always the same name and we access it from cpp code with
duk_get_global_string(js_context_duk, "onevent");
duk_push_number(js_context_duk, val);
if (duk_pcall(js_context_duk, 1) != 0)
{
printf("Duk error: %s\n", duk_safe_to_string(js_context_duk, -1));
}
duk_pop(js_context_duk); /* ignore result */
Then again we want to allow minimal communication between scripts, e.g.
Script 1
var a = 1;
function onevent(val)
{
log(a);
}
Script 2
function onevent(val)
{
a++;
}
Is there a way we achieve this? Maybe by introducing an own 'ueber-' global object, that is defined once and referencable everywhere? It should be possible to add properties to this 'ueber-global object' from any script like
Script 1
function onevent(val)
{
log(ueber.a);
}
Script 2
function onevent(val)
{
ueber.a=1;
}
Instead of simple JS files you could use modules. duktape comes with a code example to implement a module system (including its code isolation) like in Node.js. Having that in place you can export variables that should be sharable.
We have an approach that seems to work now. After creating the new context with
duk_push_thread_new_globalenv(master_ctx);
new_ctx = duk_require_context(master_ctx, -1);
duk_copy_element_reference(master_ctx, new_ctx, "ueber");
we issue this call sequence in for all properties/objects/functions created in the main context:
void duk_copy_element_reference(duk_context* src, duk_context* dst, const char* element)
{
duk_get_global_string(src, element);
duk_require_stack(dst, 1);
duk_xcopy_top(dst, src, 1);
duk_put_global_string(dst, element);
}
It seems to work (because everything is in the same heap and all is single threaded). Maybe someone with deeper insight into duktape can comment on this? Is this a feasible solution with no side effects?
edit: mark this as answer. works as expected, no memory leaks or other issues.

Greasemonkey - Browser redirect

So it's been a minute since I've used Greasemonkey scripts and I'm probably forgetting to do something basic but...
What I want to do is when ever I navigate to https://beta.crunchyroll.com to instead redirect me to https://beta.crunchyroll.com/simulcasts/seasons/spring-2021 and I think my current code should do that but isn't for some reason.
// ==UserScript==
// #name Crunchyroll Redirect
// #version 1
// #grant none
// #include http://*.crunchyroll.*/*
// #include https://*.crunchyroll.*/*
// ==/UserScript==
var current_location = content.document.location;
if(current_location == "https://beta.crunchyroll.com"){
window.location.replace("https://beta.crunchyroll.com/simulcasts/seasons/spring-2021")
}
So where did I mess up?
You can simplify the logic.
Since you only need to run it on https://beta.crunchyroll.com, there is no point on running it on other pages
#match is more robust than #include
Use #run-at to run at the earliest possible
Here is an example:
// ==UserScript==
// #name Crunchyroll Redirect
// #match *://beta.crunchyroll.com/*
// #version 1
// #grant none
// #run-at document-start
// ==/UserScript==
window.stop(); // stop loading
location.replace('https://beta.crunchyroll.com/simulcasts/seasons/spring-2021');

VIDEOJS: playing a pre-roll add before each playlist item

I'm using the videojs-playlist plugin along with Google's videojs-ima plugin. Everything works swimmingly except I am only getting a preload ad before the first video. I want one before each video in the playlist.
Basic setup is boilerplate, but for reference:
this.player = videojs('currentvideo', { autoplay : true, fluid : true });
this.player.playlist(this.playlist);
this.player.playlist.autoadvance(5);
const skippable_linear = {google's test ad};
const options = {
id: 'currentvideo',
adTagUrl: skippable_linear,
debug : true
};
this.player.ima(
options
);
this.player.ima.requestAds();
I have tried various ways of manually calling ads from inside an 'ended' event handler, such as calling requestAds again:
const _this = this;
this.player.on( 'ended', function(){
/* some other stuff */
_this.player.ima.requestAds();
});
This does play an ad where I want it, but
this breaks playlist's 'autoadvance' setting (next video doesn't start playing when the ad is finished), and
this puts the player into "ad display" mode (scrubber is unavailable, etc).
Is there a simple way to just say, "play an ad now" programmatically? I've tried, without joy, to use all of the seemingly applicable methods exposed by both the ima plugin and the contrib-ads plugin it relies on. I'll admit here that this is the first time I've ever had to deal with videos that run ads, so I'm kind of a noob.
I am trying to do the same thing. Just like you I failed when calling player.ima.requestAds() on events. I dug deeper and the best I could come up with is what I share bellow.
According to the videojs-ima API you have to use the setContentWithAdTag method instead of whatever you are using to switch the player content. In our case it is the player.playlist.next method.
I combined the code found in the videojs-ima examples with the original playlist.next to write my own next.
Then quite brutally I overrode the original plugin method.
Here's the code:
player.playlist(myPlayilst);
player.playlist.autoadvance(2);
player.playlistUi(); //videojs-playlist-ui
player.ima({
id: 'video5',
adTagUrl: 'thy adserver request'
});
//override playlist.next
player.playlist.next = function(){
var nextIndex = 0,
playlist = this.player_.playlist,
list = this.player_.playlist();
//everything below is copied directly from the original `next` (except for the "//load with ad")
// Repeat
if (playlist.repeat_) {
nextIndex = playlist.currentIndex_ + 1;
if (nextIndex > list.length - 1) {
nextIndex = 0;
}
} else {
// Don't go past the end of the playlist.
nextIndex = Math.min(playlist.currentIndex_ + 1, list.length - 1);
}
// Make the change
if (nextIndex !== playlist.currentIndex_) {
//load with ad
this.player_.playlist.currentItem(nextIndex);
this.player_.ima.setContentWithAdTag(
this.player_.playlist.currentItem(),
null,
true);
this.player_.ima.requestAds();
/////
return list[playlist.currentItem()];
}
}
You will probably need to override other methods that change the current playback, like playlist.previous.
I use videojs-playlist-ui so in my case it was neccessary to change the onclick handler called switchPlaylistItem_. I used some good old brute force to do that like this:
videojs.getComponent('PlaylistMenuItem').prototype.switchPlaylistItem_ = function(e){
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
this.player_.ima.setContentWithAdTag(
this.player_.playlist.currentItem(),
null,
true);
this.player_.ima.requestAds();
};
PlaylistMenuItem's prototype should be changed before initializing the player.
This solution works, but it feels hacky, so if anyone can come up with something cleaner, please share!
I ended up forking videojs-playlist, and adding the option to override the player.src method. Feel free to use it:
fw-videojs-playlist
Details on how to use it are all in the github readme (including an example with ima.setContentWithAdTag)

SAPI 5 TTS Events

I'm writing to ask you some advices for a particular problem regarding SAPI engine. I have an application that can speak both to the speakers and to a WAV file. I also need some events to be aware, i.e. word boundary and end input.
m_cpVoice->SetNotifyWindowMessage(m_hWnd, TTS_MSG, 0, 0);
hr = m_cpVoice->SetInterest(SPFEI_ALL_EVENTS, SPFEI_ALL_EVENTS);
Just for test I added all events! When the engine speaks to speakers all events are triggered and sent to the m_hWnd window, but when I set output to the WAV file, none of them are sent
CSpStreamFormat fmt;
CComPtr<ISpStreamFormat> pOld;
m_cpVoice->GetOutputStream(&pOld);
fmt.AssignFormat(pOld);
SPBindToFile(file, SPFM_CREATE_ALWAYS, &m_wavStream, &fmt.FormatId(), fmt.WaveFormatExPtr());
m_cpVoice->SetOutput(m_wavStream, false);
m_cpVoice->Speak(L"Test", SPF_ASYNC, 0);
Where file is a path passed as argument.
Really this code is taken from the TTS samples found on the SAPI SDK. It seems a little bit obscure the part setting the format...
Can you help me in finding the problem? Or does anyone of you know a better way to write TTS to WAV? I can not use manager code, it should be better to use the C++ version...
Thank you very much for help
EDIT 1
This seems to be a thread problem and searching in the spuihelp.h file, that contains the SPBindToFile helper I found that it uses the CoCreateInstance() function to create the stream. Maybe this is where the ISpVoice object looses its ability to send event in its creation thread.
What do you think about that?
I adopted an on-the-fly solution that I think should be acceptable in most of the cases, In fact when you write speech on files, the major event you would be aware is the "stop" event.
So... take a look a the class definition:
#define TTS_WAV_SAVED_MSG 5000
#define TTS_WAV_ERROR_MSG 5001
class CSpeech {
public:
CSpeech(HWND); // needed for the notifications
...
private:
HWND m_hWnd;
CComPtr<ISpVoice> m_cpVoice;
...
std::thread* m_thread;
void WriteToWave();
void SpeakToWave(LPCWSTR, LPCWSTR);
};
I implemented the method SpeakToWav as follows
// Global variables (***)
LPCWSTR tMsg;
LPCWSTR tFile;
long tRate;
HWND tHwnd;
ISpObjectToken* pToken;
void CSpeech::SpeakToWave(LPCWSTR file, LPCWSTR msg) {
// Using, for example wcscpy_s:
// tMsg <- msg;
// tFile <- file;
tHwnd = m_hWnd;
m_cpVoice->GetRate(&tRate);
m_cpVoice->GetVoice(&pToken);
if(m_thread == NULL)
m_thread = new std::thread(&CSpeech::WriteToWave, this);
}
And now... take a look at the WriteToWave() method:
void CSpeech::WriteToWav() {
// create a new ISpVoice that exists only in this
// new thread, so we need to
//
// CoInitialize(...) and...
// CoCreateInstance(...)
// Now set the voice, i.e.
// rate with global tRate,
// voice token with global pToken
// output format and...
// bind the stream using tFile as I did in the
// code listed in my question
cpVoice->Speak(tMsg, SPF_PURGEBEFORESPEAK, 0);
...
Now, because we did not used the SPF_ASYNC flag the call is blocking, but because we are on a separate thread the main thread can continue. After the Speak() method finished the new thread can continue as follow:
...
if(/* Speak is went ok */)
::PostMessage(tHwn, TTS_WAV_SAVED_MSG, 0, 0);
else
::PostMessage(tHwnd, TTS_WAV_ERROR_MSG, 0, 0);
}
(***) OK! using global variables is not quite cool :) but I was going fast. Maybe using a thread with the std::reference_wrapper to pass parameters would be more elegant!
Obviously, when receiving the TTS messages you need to clean the thread for a next time call! This can be done using a CSpeech::CleanThread() method like this:
void CSpeech::CleanThread() {
m_thread->join(); // I prefer to be sure the thread has finished!
delete m_thread;
m_thread = NULL;
}
What do you think about this solution? Too complex?

Browse a page with custom shortcut keys

I want to browse ragecollection.com site using the j and k keys [similar to 9gag.com or google reader] , the default is left right arrow which is not ideal when using a laptop ..
grease monkey script for firefox preffered.
You can't use greasemonkey/javascript to simulate the pressing of a key, as this would cause security problems. The approach in javascript would be to intercept a key press event and trigger the required action that would normally be triggered by a different key press.
To get you started, the following greasemonkey script intercepts g and h character presses on any page:
// ==UserScript==
// #name keytranslate
// #namespace ongar.org
// #description turns g or h keypress into left or right cursor press
// #include *
// ==/UserScript==
function keycheck(e)
{
if (e.keyCode == 71) // code for g
{
// trigger right-cursor event
}
else if (e.keyCode == 72) // code for h
{
// trigger left-cursor event
}
}
window.addEventListener('keydown', keycheck, true);

Resources