I am unable to retrieve a username from a dynamic webpage using the wait function. However when I execute $("#label301354000").text(); in Chrome Developer tool, I get my username. Any idea how to tweak the function to capture the id value?
// ==UserScript==
// #name UI
// #namespace http://tampermonkey.net/
// #version 0.1
// #description Test
// #author Dejan
// #match http://url/*
// #require https://ajax.googleapis.com/ajax/libs/jquery/2.2.0
// #require https://gist.githubusercontent.com/raw/2625891/waitForKeyElements.js
// #grant GM_addStyle
// ==/UserScript==
/* jshint -W097 */
'use strict';
waitForKeyElements("#label301354000", getUser);
function getUser(jNode) {
console.log("User is "+ jNode.text() );
}
The question omits critical details, see the comments above.
But in general, if $("#label301354000").text(); always works and
function getUser(jNode) {
console.log("User is "+ jNode.text() );
}
Comes up empty in the Tampermonkey script, it suggests that the target node is not the best choice or that the node is created at some point and filled in by the page later.
waitForKeyElements checks the callback return for cases like that. Change getUser() to:
function getUser(jNode) {
var usrText = jNode.text ().trim ();
if (usrText) {
console.log ("User is " + usrText);
}
else
return true; //-- Keep waiting.
}
If that doesn't work, provide a proper MCVE and/or link to the target page. (Per SO guidelines, you should always provide one or both.)
Related
document.querySelector('div.su-input-group:nth-child(2) > input:nth-child(2)').value = ('abc');
document.querySelector("#password").value = "abc";
document.querySelector("#dob").value = "abc";
document.querySelector(".button-orange").click();
So far this is the code that I was able to write and it does not work on https://kite3.zerodha.com/. The script fails after line number 3. BTW same code works for me on other sites.
The reason this code is not working on kite3 website is that controls that need to be filled are animated, and they are not available for selection until animation finishes.
Solution for this is usage of waitForKeyElements js library. When these elements become available values can be set, buttons clicked.
Only one criteria is used for wait just to prove the concept.
This code is working for login on that website, just replace username and password with correct values.
// ==UserScript==
// #name Login to Kite
// #namespace http://tampermonkey.net/
// #version 0.1
// #match *kite3.zerodha.com
// #require https://gist.github.com/raw/2625891/waitForKeyElements.js
// #require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// ==/UserScript==
waitForKeyElements(":password", login);
function login() {
document.querySelector("[type=text]").value = 'yourUserId';
document.querySelector("[type=password]").value = 'yourPassword';
document.querySelector("button").click();
}
I'm trying to get Tampermonkey to complete an online form.
It works every 1 out of 4 times, all I want it to do is a simple check out process on a bigcartel store. Can anyone help?
It should work on any shop using their platform as they are all quite generic, i.e http://groundup.bigcartel.com
my code;
// ==UserScript==
// #name New Userscript
// #namespace http://tampermonkey.net/
// #version 0.1
// #description try to take over the world!
// #author You
// #include https://checkout.bigcartel.com/*
// #include https://*.bigcartel.com/product
// #include https://*.bigcartel.com/cart
// #grant none
// ==/UserScript==
// on "/cart" page click checkout button
document.getElementByName("checkout").click();
// fill first three form fields
document.getElementById("buyer_first_name").value = "John";
document.getElementById("buyer_last_name").value = "Smith";
document.getElementById("buyer_email").value = "john#doe.com";
// click "next" button
document.getElementByType("submit").click();
There are four major issues with your TM script.
1.) Your include tags use https instead of http
2.) document.getElementByName doesn't exist.
Fix: Use document.getElementsByName("checkout")[0]
3.) Once you click the checkout button, the script immediately try to set the values of the input fields, you must wait for the page to load.
4.) document.getElementByType doesn't exist either.
Here is the working script:
// ==UserScript==
// #name Script
// #version 0.1
// #description try to take over the world!
// #author You
// #include https://checkout.bigcartel.com/*
// #include http://*.bigcartel.com/product
// #include http://*.bigcartel.com/cart
// #grant none
// ==/UserScript==
// on "/cart" page click checkout button
if (window.location.origin !== "https://checkout.bigcartel.com") document.getElementsByName("checkout")[0].click();
else {
// fill first three form fields
document.getElementById("buyer_first_name").value = "John";
document.getElementById("buyer_last_name").value = "Smith";
document.getElementById("buyer_email").value = "john#doe.com";
// click "next" button
document.getElementsByTagName("button")[0].click();
}
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.
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.
I am writing a script that changes up the message system for a website I use a lot. It adds a button to the top of each message thread. When clicked, it automatically moves to the most recent message in the thread.
The issue I am having is that the messages are loaded by php and the script fires before the messages are loaded. The site also doesn't load all of the message threads at the same time. It will load 10 or so, and then as you scroll down it loads more. Is there a way to get the button to load for each of the message threads, or am I shooting for an unreachable goal?
// ==UserScript==
// #name Imgur - Message Viewer
// #namespace Basion
// #description It skips to the bottom of the most recent message
// #author Basion
// #include http://imgur.com/*
// #include https://imgur.com/*
// #include http://*.imgur.com/*
// #include https://*.imgur.com/*
// #run-at document-end
// ==/UserScript==
var messages = document.getElementsByClassName("thread-wrapper");
var newA = document.createElement('a');
for (var i = 0; i < messages.length; i++) {
newA.addEventListener("click", scrollToMessage(i), true);
newA.innerText = "Go to Newest Message";
messages[i].appendChild(newA);
}
function scrollToMessage(id) {
var newMessages = document.getElementsByClassName('thread-wrapper');
newMessages[id].scrollIntoView(false);
}
To handle elements added via AJAX, use jQuery and waitForKeyElements as shown in "Run Greasemonkey script on the same page, multiple times?".
Here's a handy jQuery reference.
Also, you can't send id's to event listeners that way.
Here's a complete working, post-jump script, adapted to the question's code and the Imgur target page(s):
// ==UserScript==
// #name Imgur - Message Viewer
// #namespace Basion
// #description It skips to the bottom of the most recent message
// #author Basion
// #include http://imgur.com/*
// #include https://imgur.com/*
// #include http://*.imgur.com/*
// #include https://*.imgur.com/*
// #run-at document-end
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js
// #require https://gist.github.com/raw/2625891/waitForKeyElements.js
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
waitForKeyElements ("div.thread-wrapper", addJumpButton);
/*-- Prepend a button to the top of each thread. Don't add in the header, to
avoid conflicts with the open/close toggle.
*/
function addJumpButton (jNode) {
jNode.prepend (
'Go to Newest Message in thread'
);
}
//-- Activate *all* the jump buttons with one jQuery function.
$("div.notifications").on (
"click", "div.thread-wrapper a.gmThreadJmpBtn", jumpToEndOfThread
);
function jumpToEndOfThread (zEvent) {
//-- The last (newest) message will be just above the "reply bar".
var thisThreadHdr = $(zEvent.target);
var targetDiv = thisThreadHdr.nextAll ("div.reply.striped");
targetDiv[0].scrollIntoView (false);
//-- Block the page handlers from also firing for this link.
zEvent.stopPropagation ();
zEvent.preventDefault ();
}