Why doesn't this script work with successive page clicks? - javascript

I am currently using the following script in Tampermonkey in Google Chrome:
// ==UserScript==
// #name Youtube opt in Ads per channel
// #namespace schippi
// #include http://www.youtube.com/watch*
// #version 1
// ==/UserScript==
var u = window.location.href;
if (u.search("user=") == -1) {
var cont = document.getElementById("watch7-user-header").innerHTML;
var user=cont.replace(/.+\/user\//i,'').replace(/\?(?:.|\s)*/m,'');
window.location.href = u+"&user="+user;
}
It seems to work perfectly in Firefox with Greasemonkey but in Google Chrome, it seems that it only works on the first click on a YouTube video.
More specifically, if I click on a YouTube video:
   youtube.com/watch?v=MijmeoH9LT4,
it redirects me to:
   youtube.com/watch?v=MijmeoH9LT4&user=Computerphile
However, if I click on a video from the related videos vertical bar, it doesn't seem to do any further redirection.

Alas, there is still no really "neat" way to do this in Chrome. (Firefox has more options.)
Your best bet is just to poll location.search; see below.
Your other options in Chrome, currently, are not recommended -- but here they are for reference:
Hack into the history.pushState function. This gives faster notice of page changes, BUT fires before you can run your code, so it will still need a timer. Plus, it brings in cross-scope problems, in a userscript environment.
Use Mutation Observers to monitor changes to the <title> tag. this may work okay, but may fire after you want it to, causing delays and pronounced "flicker". Also may not work on poorly designed pages (YouTube is okay).
Also note that the replace() statements, from the question, will blow up the URL and 404 the script in several cases. Use DOM methods to get the user (see below).
Polling code (Simple, robust, cross-browser):
// ==UserScript==
// #name Youtube opt in Ads per channel
// #namespace schippi
// #include http://www.youtube.com/watch*
// #version 1
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var elemCheckTimer = null;
var pageURLCheckTimer = setInterval (
function () {
if (this.lastQueryStr !== location.search) {
this.lastQueryStr = location.search;
gmMain ();
}
}
, 111 //-- Nine times a second. Plenty fast w/o bogging page
);
function gmMain () {
if ( ! /user=/.test (window.location.href) ) {
elemCheckTimer = setInterval (checkUserAndRelocate, 24);
}
}
function checkUserAndRelocate () {
var elem = document.querySelector (
"#watch7-user-header a[href*='/user/']"
);
if (elem) {
clearInterval (elemCheckTimer);
var user = elem.href.match (/\/user\/(\w+)\W?/);
if (user && user.length > 1) {
location.replace (location.href + "&user=" + user[1]);
}
}
}

Related

querySelector incorrectly returning null on specific website [duplicate]

The website is: lexin.nada.kth.se/lexin/#searchinfo=both,swe_gre,hej;
My script is:
function main(){
var links=document.getElementsByTagName("a");
alert("There are " + links.length + "links.");
}
main();
Running the script gives me two alert messages saying
There are 0 links.
Any ideas why I can't get the right amount of links from the document? And why do I get the alert twice?
The alert fires more than once because that page contains iFrames (with, de facto, the same URL as the main page). Greasemonkey treats iFrames as if they were standalone web pages. Use #noframes to stop that.
The script is not finding the links because they are added, by javascript, long after the page loads and the GM script fires. This is a common problem with scripts and AJAX. A simple, robust solution is use waitForKeyElements() (and jQuery).
Here is a complete sample script that avoids the iFrames and shows how to get dynamic links:
// ==UserScript==
// #name _Find elements added by AJAX
// #include http://YOUR_SERVER.COM/YOUR_PATH/*
// #match http://stackoverflow.com/questions/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// #require https://gist.github.com/raw/2625891/waitForKeyElements.js
// #noframes
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var totalUsrLinks = 0;
waitForKeyElements ("a[href*='/users/']", listLinks);
function listLinks (jNode) {
var usrMtch = jNode.attr ("href").match (/^.*\/users\/(\d+)\/.*$/);
if (usrMtch && usrMtch.length > 1) {
totalUsrLinks++;
var usrId = usrMtch[1];
console.log ("Found link for user: ", usrId, "Total links = ", totalUsrLinks);
}
}
It's returning an HTMLcollection because of .getElementsByTagName and because of that, you will have to state the HTMLcollection with .getElementsByTagName and then find the length, and alert it. It will look like this...
(function main(){
var links = document.getElementsByTagName("a").length
alert("There are "+ links + " links.");
})()
I added an IIFE or an Immediately-Invoked-Function-Expression more on IIFEs here, so you don't have to call the function, and so the code is small and able to be "swallowed". lastly, it's alerting 2 alert boxes, because there's one[alert box] in the function and you're calling that function so it's going to do the same thing.

Grease/tampermonkey script not working unless page refreshed

I frequent a forum that has a horrible way of ignoring users. If you place someone on ignore, it almost makes that users presence more prevalent.
So I wrote this to hide them completely:
// ==UserScript==
// #name Freddie
// #namespace http://tampermonkey.net/
// #version 0.1
// #description hide annoying forum users
// #author You
// #match http://www.scout.com/college/kansas/forums/*
// #grant none
// ==/UserScript==
/* jshint -W097 */
'use strict';
function checkForDiv() { // crappy workaround function to wait for AJAX content
if (!document.getElementById("wrapper")) {
setTimeout(checkForDiv, 300);
} else {
checkNames();
}
}
function checkNames() {
var mybannedList = ["Pyros", "GOHawksGators", "th30r3o"]; // add usernames here
var nms = document.body.querySelectorAll('a.authName'), i = 0, len = nms.length;
for (i; i < len; i++) {
if (mybannedList.indexOf(nms[i].innerHTML) != -1) {
nms[i].parentNode.parentNode.style.display = "none";
}
}
}
checkForDiv();
But when you go to the page with the ignored users they still appear, upon refreshing, the script runs and they disappear.
Please good sirs, what do I do?
The site uses AJAX for navigation so the page address changes without reloading, that's why Tampermonkey doesn't inject your script when you navigate from another page on that site.
The simplest solution would be to include the entire site: // #match http://www.scout.com/*
There are other more advanced methods of detecting page transitions based on MutationObserver or some DOM event or property change that occurs on navigation.
Also beware of #grant none with jQuery loaded via #require: it breaks sites that also load jQuery unless you use jQuery.noConflict. The simplest solution is to remove that line as you don't need to access the web page variables.
P.S. There's a known timer-based wrapper: waitForKeyElements.

waitForKeyElements not waiting for ajax loaded data on some browsers?

In my script for Greasemonkey/Tampermonkey is working perfectly in Google Chrome but in Firefox this:
waitForKeyElements ("table#ID-rowTable tr td span._GAmD", replaceAffid);
is not waiting for AJAX loaded content.
Here is the relevant part of my script:
// ==UserScript==
// #name ChangeProvider
// #description Change Provider Name
// #include https://analytics.google.com/analytics/web/*
// #version 1
// #grant GM_xmlhttpRequest
// ==/UserScript==
<snip>...
waitForKeyElements ("table#ID-rowTable tr td span._GAmD", replaceAffid);
<snip>...
function waitForKeyElements (
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined")
targetNodes = $(selectorTxt);
else
targetNodes = $(iframeSelector).contents ()
.find (selectorTxt);
<snip>...
The full code is at pastebin.com.
The problem(s) is/are:
waitForKeyElements() requires jQuery.
Your script must either provide jQuery (recommended), or use #grant none mode and be running on a page that already uses jQuery (a brittle way to do things, AKA "time-bomb code").
Tampermonkey has a bug and possible security weakness whereby it doesn't always
sandbox properly. This means that the script can (sometimes) see the page's jQuery, even when #grant is not none. This allowed the script to run in Chrome (for now) but is a very dicey thing to depend on.
Firefox properly sandboxes the scope when you use #grant GM_ ... so the script cannot see the page's jQuery.
If you had looked at Firefox's Browser Console, you would have seen error messages pointing you to the problem.
The solution is: Don't use waitForKeyElements without #requireing jQuery!
In fact, you should require both libraries, as shown in this answer, as it (A) Runs faster, (B) Only fetches the code once, when you install/edit the userscript, and (C) makes for cleaner, easier to grok code.
So, your entire script would become something like:
// ==UserScript==
// #name GoogleAnalytics Change Provider
// #description Change Provider Name
// #include https://analytics.google.com/analytics/web/*
// #version 1
// #require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// #require https://gist.github.com/raw/2625891/waitForKeyElements.js
// #grant GM_xmlhttpRequest
// ==/UserScript==
var providers = new Array ();
GM_xmlhttpRequest ( {
method: "GET",
url: "http://localhost:3000/api/t2_provider_list",
onload: function (response) {
var provider_list = JSON.parse (response.responseText);
for (i = 0; i < provider_list.length; i++) {
providers[provider_list[i].analytics_prefix] = provider_list[i].provider_name;
}
waitForKeyElements ("table#ID-rowTable tr td span._GAmD", replaceAffid);
}
} );
/*--- replaceAffid (): Match the fields with a given pattern
and replace with the provider name and affid
*/
function replaceAffid () {
console.log (providers);
var regExp = /([a-z,A-Z,0-9]+)---([a-z,A-Z,0-9,_,-]+)/g;
var spans = document.evaluate ("//span[contains(#class, '_GAmD') and not(contains(#class, '_GAe'))]/text()", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
console.log (spans);
for (var i = 0; i < spans.snapshotLength; i++) {
match = regExp.exec (spans.snapshotItem (i).textContent);
if (match != null) {
if (typeof providers[match[1]] === undefined) {
// do nothing
} else {
spans.snapshotItem (i).textContent = "Provider: " + providers[match[1]] + " \n\r Affid: " + match[2];
}
}
}
}
Finally, it looks like you pasted in an old version of waitForKeyElements.
Since May of 2012, that function has had this text near the top:
IMPORTANT: This function requires your script to have loaded jQuery.
If you grabbed your copy of the function from one of my old answers, I apologize. I just updated it to avoid a repeat of this confusion.

Userscript that requires microphone access prompts for permissions in Chrome multiple times

I wrote a userscript that runs on the page for a game called TagPro that allows for voice recognition. It listens for key words followed by phrases, and depending on the word it puts the phrase into a different chat channel.
I'm running the script in Chrome's Tampermonkey extension. The speech recognition library I'm using is Annyang.
At the beginning of every game, Chrome confirms that you want to allow the site to use your microphone with a prompt like this:
The problem I'm having is that sometimes, in the middle of a game, the prompt will come up again. It doesn't happen in every game, but when it does happen it usually happens more than once. I haven't noticed any patterns that would make it reproducible. That makes it very hard to diagnose, let alone fix. Is there an error with my script that could be causing this?
// ==UserScript==
// #name TagPro Speech To Text
// #namespace http://www.reddit.com/u/undergroundmonorail
// #description Say a message out loud to say it into chat.
// #include http://tagpro-*.koalabeast.com:*
// #include http://tangent.jukejuice.com:*
// #include http://maptest.newcompte.fr:*
// #include http://justletme.be*
// #license MIT
// #author monorail
// #version 0.2
// ==/UserScript==
(function() {
// https://github.com/TalAter/annyang
// I couldn't figure out how to load it dynamically, so I just copypasted
// the minified version.
// #require works in Firefox, but not Chrome, and this is way easier than
// any alternative I found.
(function(a){"use strict";var b=this,c=b.SpeechRecognition||b.webkitSpeechRecognition||b.mozSpeechRecognition||b.msSpeechRecognition||b.oSpeechRecognition;if(!c)return b.annyang=null,a;var d,e,f=[],g={start:[],error:[],end:[],result:[],resultMatch:[],resultNoMatch:[],errorNetwork:[],errorPermissionBlocked:[],errorPermissionDenied:[]},h=0,i=!1,j="font-weight: bold; color: #00f;",k=/\s*\((.*?)\)\s*/g,l=/(\(\?:[^)]+\))\?/g,m=/(\(\?)?:\w+/g,n=/\*\w+/g,o=/[\-{}\[\]+?.,\\\^$|#]/g,p=function(a){return a=a.replace(o,"\\$&").replace(k,"(?:$1)?").replace(m,function(a,b){return b?a:"([^\\s]+)"}).replace(n,"(.*?)").replace(l,"\\s*$1?\\s*"),new RegExp("^"+a+"$","i")},q=function(a){a.forEach(function(a){a.callback.apply(a.context)})},r=function(){d===a&&b.annyang.init({},!1)};b.annyang={init:function(k,l){l=l===a?!0:!!l,d&&d.abort&&d.abort(),d=new c,d.maxAlternatives=5,d.continuous=!0,d.lang="en-US",d.onstart=function(){q(g.start)},d.onerror=function(a){switch(q(g.error),a.error){case"network":q(g.errorNetwork);break;case"not-allowed":case"service-not-allowed":e=!1,(new Date).getTime()-h<200?q(g.errorPermissionBlocked):q(g.errorPermissionDenied)}},d.onend=function(){if(q(g.end),e){var a=(new Date).getTime()-h;1e3>a?setTimeout(b.annyang.start,1e3-a):b.annyang.start()}},d.onresult=function(a){q(g.result);for(var c,d=a.results[a.resultIndex],e=0;e<d.length;e++){c=d[e].transcript.trim(),i&&b.console.log("Speech recognized: %c"+c,j);for(var h=0,k=f.length;k>h;h++){var l=f[h].command.exec(c);if(l){var m=l.slice(1);return i&&(b.console.log("command matched: %c"+f[h].originalPhrase,j),m.length&&b.console.log("with parameters",m)),f[h].callback.apply(this,m),q(g.resultMatch),!0}}}return q(g.resultNoMatch),!1},l&&(f=[]),k.length&&this.addCommands(k)},start:function(b){r(),b=b||{},e=b.autoRestart!==a?!!b.autoRestart:!0,h=(new Date).getTime(),d.start()},abort:function(){r(),e=!1,d.abort()},debug:function(a){i=arguments.length>0?!!a:!0},setLanguage:function(a){r(),d.lang=a},addCommands:function(a){var c,d;r();for(var e in a)if(a.hasOwnProperty(e)){if(c=b[a[e]]||a[e],"function"!=typeof c)continue;d=p(e),f.push({command:d,callback:c,originalPhrase:e})}i&&b.console.log("Commands successfully loaded: %c"+f.length,j)},removeCommands:function(a){a=Array.isArray(a)?a:[a],f=f.filter(function(b){for(var c=0;c<a.length;c++)if(a[c]===b.originalPhrase)return!1;return!0})},addCallback:function(c,d,e){if(g[c]!==a){var f=b[d]||d;"function"==typeof f&&g[c].push({callback:f,context:e||this})}}}}).call(this);
// The following code is the function for sending a chat message. This is
// how every userscript that touches chat does it. It's almost definitely
// not related to the problem.
var lastMessage = 0;
var chat = function(message, all) {
var limit = 500 + 10;
var now = new Date();
var timeDiff = now - lastMessage;
if (timeDiff > limit) {
tagpro.socket.emit("chat", {
message: message,
toAll: all
});
lastMessage = new Date();
} else if (timeDiff >= 0) {
setTimeout(chat, limit - timeDiff, chatMessage)
}
}
// Code that I wrote begins here.
var team = function(message) { chat(message, 0); };
var all = function(message) { chat(message, 1); };
var group = function(message) {
if (tagpro.group.socket) {tagpro.group.socket.emit('chat', message);}
};
commands = { 'say *message': all,
'team *message': team,
'group *message': group };
annyang.addCommands(commands);
annyang.start();
})();
Chrome's speech recognition implementation simply stops from time-to-time.
To handle this, annyang restarts it (unless it is stopped manually by you or the user). Since your page doesn't use HTTPS, the permission you gave Chrome to use speech recognition on that page doesn't persist, and it asks the user for permission again and again.
This is why it is recommended to use HTTPS whenever using speech recognition on the page.
This was a funny one.
So I unminified annyang.js to find the onend and onerror functions. In the onerror call I console logged the error to get:
Note error: "no-speech".
You see where this is going...
Looking up the W3 spec: https://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html#dfn-sre.nospeech
It reads:
"no-speech"
No speech was detected.
Long story short, you're being too quiet.
As for PREVENTING it - dig into annyang.js, particularly the onerror event. Or sing the ABCs repeatedly while you need it on.
You are probably getting the extra prompts due to AJAX loading in iframes for various purposes.
Wrap your code in a frame check:
if (window.top == window.self) {
//-- Only run on the master (top) page...
(function() {
// https://github.com/TalAter/annyang
// I couldn't figure out how to load it dynamically, so I just copypasted
// the minified version.
// #require works in Firefox, but not Chrome, and this is way easier than
// any alternative I found.
(function(a){"use strict";var b=this,c=b.SpeechRecognition||b.webkitSpeechRecognition|| ...
// etc...
}
To eliminate the prompt completely, you have to use a full-blown extension. Then you can use techniques like in this other answer. (But then you also have to hassle with the Chrome store if you want to share the extension easily.)

How to change the script to load the page content only without images etc.?

I have the following greasemonkey script currently:
// ==UserScript==
// #name test_script
// #namespace my.example.com
// #include http://example.com/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js
// #version 1
// #grant none
// ==/UserScript==
(function(window, undefined) {
// normalized window
var w;
if (unsafeWindow != "undefined"){
w = unsafeWindow
} else {
w = window;
}
// You can inject almost any javascript library here.
// Just pass the w as the window reference,
// e.g. jquery.min.js embedding:
// (function(a,b){function ci(a) ... a.jQuery=a.$=d})(w);
// do not run in frames
if (w.self != w.top){
return;
}
// additional url check.
// Google Chrome do not treat #match as intended sometimes.
if (/http:\/\/example.com\//.test(w.location.href)){
var link = $('span.link').text();
if (link) {
location.replace(link);
}
}
})(window);
Once I open example.com page, it looks for the URL here and redirects me. It works well, but redirection happens only when all images, style sheets etc. are loaded. How should I fix that? Looks like it should happen before.
The code you've shown, will fire before images are loaded, but not necessarily before CSS.
From Mozilla's DOMContentLoaded doc (and verified in Gecko, Webkit and IE):
Stylesheet loads block script execution, so if you have a <script> after a <link rel="stylesheet" ...>, the page will not finish parsing - and DOMContentLoaded will not fire - until the stylesheet is loaded.
Obviously, if the CSS and/or images are small or cached, they might also appear to load before the script fires.
You might be able to work around this using #run-at document-start. See this answer, and similar, for example.

Categories

Resources