Greasemonkey Hacks/Taking Back the Browser

From WikiContent

Jump to: navigation, search
Greasemonkey Hacks


Contents

Hacks 77–84: Introduction

Some days I miss NCSA Mosaic. No, really. For those readers who haven't been around the Web as long as I have, let me explain how it was in the good old days. (Good Lord, I can't believe I just said that. I'm only 32. Shoot me now.)

Anyway, in the early days of the Web, there was no Netscape. There was no Internet Explorer. There was no Flash. There was NCSA Mosaic, the first popular graphical web browser, and a few personal home pages cobbled together by physics professors. And we loved it. I mean, totally loved it.

The Web grew up, and everybody grew up with it. But along with the good stuff (Amazon.com, Google, and a million personal weblogs cobbled together by physics professors), there arose a class of web sites that treated you like dirt. They were developed by people who really wanted the Web to be more like television. I publish, you watch. Resize my layout? How dare you! Save my pages to your local hard drive? That's criminal! And don't even think of clicking your Back button.

Browsers have become steadily savvier about the tricks and traps that these publishers lay for unsuspecting visitors. Firefox blocks pop-up ads by default, and AdBlock (http://adblock.mozdev.org) can block almost any other advertisements. Extensions like FlashBlock (http://flashblock.mozdev.org) replace stupid Flash animations with a button so you see them only if you really want to.

But there is still a wide range of smaller annoyances that publishers try to get away with; for example, burying their content in frames that clutter the screen and break the Back button, requiring personal registration just to read an article, and disabling the context menu.

Enough. It's not their browser; it's your browser. It's time to take it back.

Reenable Context Menus on Sites That Disable Them

Tired of too-clever web developers disabling right-click on images? Reenable the full functionality of your browser.

Somewhere along the line, web developers got the impression that it was their Web. Unstoppable pop-up windows and scrolling status bar text were bad enough, but the stupid web trick that really drove me nuts was the way some sites tried to disable the right-click context menu. If I tried to right-click on an image, the site would pop up an alert saying that they had helpfully disabled that feature in a pathetic attempt to prevent me from saving the image to my hard drive or viewing it in a separate window.

Well, as you already know from using Firefox and Greasemonkey, it is most definitely your Web. This hack reenables the right-click context menu by nullifying all the JavaScript event handlers that sites use to try to disable it.

The Code

This user script runs on all pages. However, because it is so aggressive in trying to disable the disablers, it ends up breaking some sites that use those particular event handlers for different purposes. Google, for example, uses onmousedown handlers (in a good way) on Gmail and Google Maps. Those sites are excluded by default. If you find other problematic sites, you can add them to the exclusion list in the Manage User Scripts dialog [Hack #3].

Save the following user script as antidisabler.user.js:

	// ==UserScript==
	// @name          Anti-Disabler
	// @namespace	  http://diveintomark.org/projects/greasemonkey/
	// @description   restore context menus on sites that try to disable them
	// @include       *
	// @exclude       http*://mail.google.com/*
	// @exclude       http://maps.google.com/*
	// ==/UserScript==

	with (document.wrappedJSObject || document) {
	    onmouseup = null;
		onmousedown = null;
		oncontextmenu = null;
	}
	var arAllElements = document.getElementsByTagName('*');
	for (var i = arAllElements.length - 1; i >= 0; i--) {
		var elmOne = arAllElements[i];
		with (elmOne.wrappedJSObject || elmOne) {
		    onmouseup = null;

			context menusreenabling on disabled sitesonmousedown = null;
			oncontextmenu = null;
		}
	}

Running the Hack

Before installing the user script, go to http://www.dynamicdrive.com/dynamicindex9/noright2.htm. This is a page that demonstrates a particularly nasty right-click disabler script. Right-clicking on either image produces an alert that the function is disabled, as shown in Figure 9-1.

Figure 9-1. Dynamic Drive site with context menu disabled

Dynamic Drive site with context menu disabled

Now, install the user script from Tools → Install This User Script, and refresh the page. Right-clicking on either image bypasses the alert altogether and displays the standard image context menu, as shown in Figure 9-2.

Figure 9-2. Dynamic Drive site with context menu restored

Dynamic Drive site with context menu restored

If you find that this hack is interfering with too many sites you use (like Gmail), you can take a whitelist approach by including no sites by default, and then add individual sites (like Dynamic Drive) as you find them.

Bypass Weight Watchers' Browser Checker

For some reason, WeightWatchers.com doesn't like Firefox.

Sadly, in 2005, there are still many sites that intentionally discriminate against minority browsers. WeightWatchers.com is one such site. The developers think they have "optimized" it for Internet Explorer, and the site intentionally shunts all other browsers to a dead-end "site requirements" page. Now, if they were doing something like using ActiveX, we would have little recourse. But they're not; they're just being stubborn.

This hack bypasses their Site Requirements page and takes you to their home page, which works just fine in Firefox (and pretty much every other browser).

Tip

Some sites include a browser check on every page. You can usually access such stubborn sites by installing the User Agent Switcher extension from http://chrispederick.com/work/firefox/useragentswitcher/ and setting your User Agent to Internet Explorer.

The Code

This user script runs on all WeightWatchers.com pages, although it really does its thing only on the Site Requirements page. It parses the URL of the current page and replaces it with the actual home page that Weight Watchers takes you to when running an inferior browser.

Save the following user script as weightwatchers.user.js:

	// ==UserScript==
	// @name		   WeightWatchers SiteRequirements Bypass
	// @namespace	   http://docs.g-blog.net/code/greasemonkey
	// @description	   Move past Weight Watchers' ridiculous browser check
	// @include        http://weightwatchers.com/*
	// @include        http://www.weightwatchers.com/*
	// ==/UserScript==

	// based on code by Carlo Zottmann
	// and included here with his gracious permission

	if (window.location.href.match(/siteRequirements/i)) {
	    window.location.replace(
			location.href.match(/^(https?:\/\/[^\/]+)\//i)[1]+"/index.aspx");
    }

Running the Hack

Before running this hack, go to http://www.weightwatchers.com. The site will immediately redirect you to a page telling you that you are using an unsupported browser, despite listing instructions for Firefox, as shown in Figure 9-3.

Figure 9-3. WeightWatchers.com site requirements

WeightWatchers.com site requirements

Now, install the user script (Tools → Install This User Script), and revisit http://www.weightwatchers.com. Now the Unsupported Browser page redirects to the Weight Watchers home page, as shown in Figure 9-4.

Figure 9-4. WeightWatchers.com home page

WeightWatchers.com home page

As you can see, the site functions perfectly well in Firefox. Note to self: when you start your own company to take over the world, try not to upset 60 million potential customers.

Easily Download Embedded Movies

Add a download link next to movies and Flash animations in web pages.

A little-known feature of Firefox is the Page Info dialog (under the Tools menu). Most people who try it can't get past the first tab, which displays geeky technical details of how the page was served. But if you click over to the Media tab, Firefox displays the URLs of all the multimedia objects on the page: images, QuickTime movies, and Flash animations. You can select the URL of a movie, copy it to the clipboard, and then paste it into a download manager or command-line tool to download it to your local computer.

OK, I guess that's still pretty geeky. Here's a simpler solution: this hack adds a download link next to each inline movie. You can right-click the download link and save the movie to your local computer—you know, the way the Web is supposed to work.

The Code

This user script runs on all pages. It uses the document.getElementsByTagName function to find movies embedded in the page with an <embed> tag. Then, it creates a download link that points to the embedded object's source file.

Save the following user script as unembed.user.js:

	// ==UserScript==
	// @name		  Unembed
	// @namespace	  http://neugierig.org/software/greasemonkey
	// @description	  Adds a download link to web sitesembedded moviesembedded movies
	// @include		  *
	// ==/UserScript

	// based on code by Evan Martin
	// published here with his gracious permission

	var arEmbed = document.getElementsByTagName('embed');
	for (var i = arEmbed.length - 1; i >= 0; i--) {
		var elmEmbed = arEmbed[i];
		var elmLink = document.createElement('a');
		elmLink.href = elmEmbed.src;
		elmLink.appendChild(document.createTextNode('[download]'));
		elmEmbed.parentNode.insertBefore(elmLink, elmEmbed.nextSibling);
	}

Running the Hack

After installing the user script (Tools → Install This User Script), go to http://www.kiku.com/electric_samurai/virtual_mongol/four.html. Next to the embedded QuickTime movie is a link titled "[download]," as shown in Figure 9-5. You can right-click the download link and select Save Link As… to save the movie to your local computer.

Figure 9-5. Link to download embedded movie

Link to download embedded movie

The script also works for embedded Flash animations. Go to http://www.markfiore.com/animation.html, Mark Fiore's archive of animated political cartoons. Select a cartoon from the list. Next to the cartoon, you will see a link titled "[download]," as shown in Figure 9-6. As with the embedded QuickTime movie, you can right-click the download link and save the Flash animation to your local computer.

Because of the way some Flash animations work, the .swf file you download might not be complete. This is because a Flash file can be a stub: a small file that loads the rest of the animation from another URL. This second URL is embedded within the binary Flash object itself, so it is not easily accessible from JavaScript.

Figure 9-6. Link to download Flash animation

Link to download Flash animation

Break Out of Frames

Replace a framed document with the biggest single frame.

On some news sites, link collections or weblogs, and even web mail programs, external links are wrapped into a frame. This wastes your precious screen real estate to keep part of the original web site in view, usually with additional advertisements. We can take back control of our browsing experience and use the whole screen instead of wasting it on these framed wrappers.

The Code

This script takes a straightforward approach, which comes in three simple steps. First, via XPath, we find all the <frame> elements. Then, we check the site and location of each frame and save it as if it is the biggest frame so far.

Once the loop has evaluated all <frame> tags, one of two things happens. If we have a URL of the biggest frame recorded, the script redirects to that URL with the location.replace method. If no URL has been recorded because there are no frames on this page, we do nothing. This makes the script safe to include with wide-open wildcards that match an entire domain.

The default list of included pages is simply an example page. There are many legitimately frames-based pages, so a catchall @include * parameter would disrupt too many sites. You will need to manually add pages that you know have wrapped frames.

Save the following user script as frame-killer-plus.user.js:

	// ==UserScript==
	// @name		  Frame Killer Plus
	// @namespace	  http://www.arantius.com/
	// @description	  Replaces the current page with the biggest frame
	// @include       http://www.example.com/
	// ==/UserScript==

	// based on code by Anthony Lieuallen
	// and included here with his gracious permission
	
	var i=0,f,bigArea=-1,frameArea,newLoc='';
	// use xpath here to circumvent security restrictions that prevent
	// reading the src directly
	var frames=document.evaluate("//frame", document, null,
	    XPathResult.ANY_TYPE, null);
	while (f = frames.iterateNext()) {
	    frameArea = (parseInt(f.offsetWidth) *
            parseInt(f.offsetHeight));
		if (frameArea > bigArea) {
			bigArea = frameArea;
			newLoc = f.src;

		}
	}
	if (''!=newLoc) {
		document.location.replace(newLoc);
	}

Running the Hack

The web site About.com (http://www.about.com) places a large frame in the top of your window when you click through to an external page. The top frame includes some advertisements and a link back to About.com; in other words, it's useless.

Install the user script (Tools → Install This User Script), but don't change the @include parameter. (We'll change it in a minute.) Now, go to http://atheism.about.com/od/offsiteagnostic/, shown in Figure 9-7.

Figure 9-7. Offsite links on About.com

Offsite links on About.com

Click the link titled Agnostic Church Home Page, and you will see the external site wrapped in an About.com frame, as shown in Figure 9-8.

Now, go to Tools → Manage User Scripts, select Frame Killer Plus in the pane on the left, click Add… next to the list of included pages, and enter http://*.about.com/*offsite*. Click OK to exit the Manage User Scripts dialog, go back to http://atheism.about.com/od/offsiteagnostic/, and again click the link titled Agnostic Church Home Page. This time, the script will kill the useless About.com frame wrapper and redirect you straight to the external site.

—Anthony Lieuallen

Figure 9-8. External site wrapped in About.com frame

External site wrapped in About.com frame

Disable Targets for Downloads

Don't open a new window when downloading a file.

Here's something that annoys me. I click a link to download a file, and the site forces the link to open in a new window. Firefox begins to download the file, but the new window stays around. Seriously, what's up with that? Does anybody actually want this behavior?

This hack modifies links that point to known binary file types, so they just download the file and stay on the same page.

The Code

This user script runs on all web pages. For performance reasons, it creates a regular expression object in advance that matches known binary file types. (You can add your own if you like.) Then, it scans the page looking for links that match the file type expression and open a new window, and removes the target attribute to neutralize the extra blank window.

Save the following user script as disable-targets-for-downloads.user.js:

	// ==UserScript==
	// @name		 Disable Targets For Downloads
	// @namespace	 http://www.rhyley.org/
	// @description  Don't open a new window on links to binary files
	// @include      http://*
	// ==/UserScript==

	// based on code by Jason Rhyley
	// and included here with his gracious permission

	// Add other file extensions here as needed
	var oExp = new RegExp("(\.zip|\.rar|\.exe|\.tar|\.jar|\.xpi|\.gzip|" +
	    "\.gz|\.ace|\.bin|\.ico|\.jpg|\.gif|\.pdf)$", "i");
	var snapLinks = document.evaluate("//a[@onclick] | //a[@target windows for downloadstarget]",
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

	for (var i = 0; i < snapLinks.snapshotLength; i++) {
		var elmLink = snapLinks.snapshotItem(i);
		if (elmLink.href && oExp.exec(elmLink.href)) {
			elmLink.target = '';
			elmLink.addEventListener('click', function(e) {
			    e.stopPropagation();
				e.preventDefault();
			}, true);
		}
	}

Running the Hack

Before installing this script, go to http://www.techsmith.com/download/ensharpendefault.asp. Click one of the download links. The site opens a new window, and then Firefox proceeds to download the file and leave the blank window in the foreground, as shown in Figure 9-9.

Figure 9-9. Useless blank window

Useless blank window

Now, install the user script (Tools → Install This User Script), and go back to http://www.techsmith.com/download/ensharpendefault.asp. Click the download link again. Hooray! This time, Firefox just downloads the file. The extra blank window has been neutralized.

Hacking the Hack

I scoured my CVS repository configuration files to find a list of binary file extensions. Here is a more complete list of file types:

	var oExp = new RegExp("(\.zip|\.rar|\.exe|\.tar|\.jar|\.xpi|\.gzip|" +
	    "\.gz|\.ace|\.bin|\.ico|\.jpg|\.gif|\.pdf|\.ico|\.png|\.tgz|\.doc|" +
		"\.xls|\.ppt|\.dmg|\.img|\.sit|\.scc|\.dll|\.lib|\.exp|\.so|\.frm|" +
		"\.myd|\.myi|\.sys|\.pyd|\.pyc|\.pyo|\.dat|\.cache|\.swf)$", "i");

Although, in my opinion, anyone caught publishing .ppt (PowerPoint) files on the Web should be shot.

Automatically Link to Printer-Friendly Versions

Get the content without the cruft by changing selected article links to "printer-friendly" versions.

Most online news sites offer printer-friendly versions of their articles. Such pages usually contain the entire article text in one page, instead of forcing you to click through to read multiple pages. They also leave out the site's global navigation bar. They might also have fewer ads, or no ads at all.

Well, that all sounds appealing to me; why isn't that the default? This hack makes it the default, by changing links to selected news sites to point to the printer-friendly article instead.

The Code

This user script runs on all pages. It uses regular expressions to find specific patterns in link URLs and then performs site-specific alterations to change the link to point to the associated printer-friendly page instead. Of course, it works only on links to the sites it knows about, but it can easily be extended with knowledge of how other sites associate normal article pages and printer-friendly pages.

Save the following user script as bstopthepresses.user.js:

	// ==UserScript==
	// @name		  Stop The Presses!
	// @namespace	  http://diveintomark.org/projects/greasemonkey/
	// @description	  make links point to printer-friendly versions
	// @include       *
	// ==/UserScript==
	var urlPage = window.location.href;
	for (var i = document.links.length - 1; i >= 0; i--) {
	    var elmLink = document.links[i];
	    var urlHref = elmLink.href;

		// Yahoo News
		if ((urlHref.match(/\/\/(story\.)?news\.yahoo\.com\//i)) &&
		((urlHref.match(/sid=/i)) || (urlHref.match(/tmpl=story/i))) &&
			(!urlHref.match(/printer=1/i))) {
			if (urlHref.match(/\?/i)) {
				urlHref += '&printer=1';
			} else {
			    urlHref += '?printer=1';
			}
		}
		// NYTimes
		if ((urlHref.match(/nytimes\.com\/2/i)) &&
		    (!urlHref.match(/pagewanted=/i))) {
			if (urlHref.match(/\?/i)) {
				urlHref += '&pagewanted=print';
			} else {
				urlHref += '?pagewanted=print';
			}
		}
		// CNET
		if (((urlHref.match(/com\.com\//i)) ||
			 (urlHref.match(/cnet\.com\//i)) ||
			 (urlPage.match(/com\.com\//i)) ||
			 (urlPage.match(/cnet\.com\//i))) &&
			(urlHref != elmLink.printer-friendly contenttextContent)) {
			urlHref = urlHref.replace(/2100-/g, '2102-');
			urlHref = urlHref.replace(/2008-/g, '2102-');
		}

		// Washington Post
		if ((urlHref.match(/washingtonpost\.com\/wp\-dyn\/content\/article/i))
&&
		   (!urlHref.match(/_pf\./i))) {
		    urlHref = urlHref.replace(/.html/g, '_pf.html');
		}
		if (urlHref != elmLink.href) {
			elmLink.href = urlHref;
			elmLink.addEventListener('click', function(event) {
			    window.top.location.href = urlHref;
				event.stopPropagation();
				event.preventDefault();
				return false;
			}, true);
		}
	}

Running the Hack

After installing the user script (Tools → Install This User Script), go to http://news.com.com. (Yes, there are really two .coms in that URL. Don't ask.) Click any article link to read that article. Instead of the usual News.com article page, you will immediately go to the printer-friendly version, as shown in Figure 9-10.

Figure 9-10. News.com printer-friendly page

News.com printer-friendly page

As printer-friendly pages have become more popular, news sites have begun adding advertisements and other clutter to them, but they are still an improvement over the original article pages.

Restore Functionality in Google Print

Google Print goes to extraordinary lengths to keep you from downloading images, but you don't need to go to the same extraordinary lengths to get them anyway.

It's long been stated that if you put your images up on the Web, there's no real way of stopping people from downloading them and using them for their own purposes. That's still basically true, although one of the interesting things about the new Google Print service is the unusual lengths it goes to prevent the average web user from doing exactly that.

Tip

This hack is based on an article by Gervase Markham, who has graciously allowed me to include it here. The code is mine, but I couldn't have written it without his excellent and original research. You can read his article at http://weblogs.mozillazine.org/gerv/archives/006657.html, including comments from many other people who were collaboratively hacking Google Print on the day it was announced.

Google Print allows you to search printed books (although Google obviously has the data in electronic form). To see it in action, search Google for Romeo and Juliet and click the link under Book Results titled "Romeo and Juliet by William Shakespeare." You'll see an image of the first page of the book, but the page is specially crafted to prevent you from printing the image or saving it to your local computer.

The first thing that prevents you from saving the image of the printed page is that the right-click context menu is disabled. Google has used the standard JavaScript trick to disable the context menu for the entire page, by returning false from the oncontextmenu handler. This is no problem for those takingback the Web. Go to Tools → Options → Web Features → Advanced JavaScript and uncheck "Disable or replace context menus." Score one for Firefox.

The next obstacle is that selecting the View Image item in the newly enabled context menu seems to show you a blank page. The <img> element for the image of the printed page is actually a transparent GIF; the real book page is defined as a CSS background image on a container <div>. If you select View Image from the context menu, all you end up with is the transparent GIF, not the background image. And since there's a foreground image overlaying the background image, Firefox suppresses the View Background Image item in the context menu. Score one for Google.

OK, let's change tactics. Open the Page Info dialog under the Tools menu, and go to the Media tab. This lists all the media on the page, and it has a Save As… button next to each media file that allows you to save that file to disk—except that it doesn't work for the one image we're interested in. It works for images inserted using <img>, <input>, and <embed>, but not for background images inserted using a CSS background-image rule. Score: Google 2, hackers 1.

My next idea was to copy and paste the URL out of page source. However, Google likes to serve pages without newlines, and there are a lot of similar URLs in them, so it would seem virtually impossible to find the right URL in the View Source window scrolling two and a half miles to the right. Score: Google 3, hackers 1.

Let's change tactics again. Since the transparent GIF is in our way (literally, it's an <img> element that is obscuring the actual image of the printed page), we can try to delete the GIF altogether using DOM Inspector.

Tip

DOM Inspector is not installed by default. If you don't see a DOM Inspector item in your Tools menu, you'll need to reinstall Firefox, select Custom Install → Developer Tools. You can safely reinstall over your existing Firefox installation. This will not affect your existing bookmarks, preferences, extensions, or user scripts.

DOM Inspector displays a tree of all the elements on the current page. Changes you make in DOM Inspector are immediately reflected in the original page. So, theoretically, we can locate the GIF in the DOM Inspector tree and just press Delete. Bang! The entire book page image disappears along with it! How did this happen? Well, the transparent GIF <img> element was providing a size for the <div> that contains it. When we removed the transparent GIF, the <div> collapsed and we could no longer see the book page image, since it was now the background image of a 0x0 <div>. Another point for Google.

No problem. In DOM Inspector, we can select the container <div> (the one helpfully declared as class="theimg"), drop down the menu on the right to select CSS Style Rules, and then manually edit the CSS to give the <div> a real width and height. Right-click in the lower pane on the right and select New Property. Enter a property name of width and a value of 400. Repeat and enter a property name of height and a value of 400.

Success! This allows us to see the background image again on the original page, albeit only partially, since the image is larger than 400 x 400. But it's enough, because the transparent GIF is gone, so we can right-click the partial book page image and select View Background Image to display the image in isolation. From there, we can save the image to disk or print it. Final score: Google 4, hackers 8. Game, set, match.

Now that we've suffered through all the gory details of Google's attempts to make your browser less functional, let's automate the process with a 20-line Greasemonkey script.

The Code

This user script runs on Google Print pages. Right out of the gate, it reenables the right-click context menu by setting document.oncontextmenu=null. Then, it uses an XPath query to find all the transparent GIFs named cleardot.gif. These are the GIFs obscuring other images. For each one, it replaces the URL of the transparent GIF with the URL of the obscured image. For bonus points, it makes the image clickable by wrapping it in an <a> element that links to the image URL.

Save the following user script as nrestoregoogleprint.user.js:

	// ==UserScript==
	// @name		  Restore Google Print
	// @namespace	  http://diveintomark.org/projects/greasemonkey/
	// @description	  restore normal browser Googlerestoring functionality in Google Printfunctionality in Google Print
	// @include		  http://print.google.tld/print*
	// ==/UserScript==

	// restore context menu
	unsafeWindow.document.oncontextmenu = null;

	// remove clear GIFs that obscure divs with background images
	var snapDots = document.evaluate("//img[@src='images/cleardot.gif']",
	    document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for(var i = snapDots.snapshotLength - 1; i >= 0; i--) {
	    var elmDot = snapDots.snapshotItem(i);
		var elmWrapper = elmDot.parentNode;
		while (elmWrapper.nodeName.toLowerCase() != 'div') {
		    elmWrapper = elmWrapper.parentNode;
		}
		var urlImage = getComputedStyle(elmWrapper, '').backgroundImage;
		urlImage = urlImage.replace(/url\((.*?)\)/g, '$1');
		// make image clickable
		var elmClone = elmDot.cloneNode(true);
		elmClone.style.border = 'none';
		elmClone.src = urlImage;
		var elmLink = document.createElement('a');
		elmLink.href = urlImage;
		elmLink.appendChild(elmClone);
		elmDot.parentNode.insertBefore(elmLink, elmDot);
		elmDot.parentNode.removeChild(elmDot);
	}

Running the Hack

After installing the user script (Tools → Install This User Script), go to http://print.google.com and search for Romeo and Juliet. Click the link under Book Results titled "Romeo and Juliet by William Shakespeare." You will see the first page of Romeo and Juliet. Thanks to this hack, you can right-click the image of the printed page and do all the things you can normally do with an image (such as saving it to disk), as shown in Figure 9-11.

Figure 9-11. Restored context menu on Google Print

Restored context menu on Google Print

There are actually two protected images on each Google Print page: the image of the printed page and the smaller thumbnail image of the book cover. Google uses the same technique for both images, so this hack works on the cover thumbnail image as well.

Bypass Annoying Site Registrations

Autofill privacy-invading registration pages.

Many online newspapers require you to register with the site before being able to read online articles. This registration is annoying, invasive, and a serious privacy risk. (Several newspaper publishing companies have been caught selling their registration information to spammers.) A site called BugMeNot.com (http://www.bugmenot.com) has sprung up to aggregate fake logins for such sites. This hack takes BugMeNot one step further by integrating it into the login page itself.

The Code

This user script runs on all pages. It is most useful on online newspaper sites, such as The New York Times online, that require mandatory registration in order to read news articles, but the script is designed to work on any site.

Save the following user script as bugmenot.user.js:

	// ==UserScript==
	// @name		  Bug Me Not
	// @namespace	  http://www.reifysoft.com/?scr=BugMeNot
	// @description	  Bypass required registration using Bug Me Not
	// @include		  *
	// ==/UserScript==

	// based on code by Matt McCarthy
	// and included here with his gracious permission

	// new logins gotten from the current page (reset on every page load)
	var retrievals = 0;
	// millisecond delay between a field losing focus and checking to see
	// if any other of our fields has focus. If this is too low, the menu
	// won't work because it will get "display: none" and its onclick
	// won't fire.
	var BLUR_TIMEOUT = 150;

	var allInputs = null;
	var bmnView = "http://bugmenot.com/view.php";
	var bmnUri = bmnView + "?url=" + location.href;
	var bmnHomeUri = "http://bugmenot.com/";
	var DEBUG = false;
	var bmnWrappers = new Object();

	var Style = {
		menuLink: {
			border: "none",
			backgroundColor: "#fff",
			color: "#000",

			display: "block",
			padding: "2px",
			margin: "0",
			width: "12em",

			fontSize: "8pt",
			fontWeight: "normal",
			textDecoration: "none"
		},

		menuLinkHover: {
			backgroundColor: "#316AC5",

			color: "#fff"
		},

		menuLinkWrapper: {
			textAlign: "left",
			padding: "1px",
			margin: 0
		},
		bmnWrapper: {
			display: "none",
			fontFamily: "tahoma, verdana, arial, sans-serif",
			whiteSpace: "nowrap",

			position: "absolute",
			zIndex: 1000,

			padding: "2px",
			border: "1px solid #ACA899",
			backgroundColor: "#fff",

			opacity: "0.9",
			filter: "alpha(opacity=90)"
		}
	};
	function copyProperties(to, from) {
		for (var i in from) {
			to[i] = from[i];
		}
	}

	function main() {
		processPasswordFields();
	}
	function getBmnWrapper(pwFieldIndex) {
		return document.getElementById("reify-bugmenot-bmnWrapper" +
			pwFieldIndex);
	}

	function processPasswordFields() {
		var allInputs = document.getElementsByTagName("input");
		var bmnContainer = document.createElement("div");
		bmnContainer.id = "reify-bugmenot-container";

		var bodyEl = document.getElementsByTagName("body")[0];
		if (!bodyEl) return;

		for (var i = 0; i < allInputs.length; i++) {
			var pwField = allInputs[i];
			if (!(pwField.type && pwField.type.toLowerCase() == "password")) {
				continue;
			}
			var previousTextFieldInd = getPreviousTextField(i);
			if (previousTextFieldInd == -1) {
				if (DEBUG) {
				GM_log("Couldn't find text field before password input " +
				   i + ".");
				continue;
				}
			}
			var usernameField = allInputs[previousTextFieldInd];
			usernameField.setAttribute('usernameInputIndex',
						   previousTextFieldInd);
			usernameField.setAttribute('passwordInputIndex', i);
			Utility.addEventHandler(usernameField, "focus",
						usernameField_onfocus);
			Utility.addEventHandler(usernameField, "blur",
						usernameField_onblur);

			Utility.addEventHandler(pwField, "focus", pwField_onfocus);
			Utility.addEventHandler(pwField, "blur", pwField_onblur);
			pwField.setAttribute('usernameInputIndex', previousTextFieldInd);
			pwField.setAttribute('passwordInputIndex', i);

			var getLoginLink = menuLink(bmnUri, "Get login from Bug Me Not",
				"Get a login from Bug Me Not",
				getLoginLink_onclick, Style.menuLink, previousTextFieldInd,
				i, menuLink_onmouseover, menuLink_onmouseout);
			var getLoginLinkWrapper = menuEntry(getLoginLink,
				Style.menuLinkWrapper);

			var fullFormLink = menuLink(bmnUri, "More options",
				"See more options for getting logins from BugMeNot.com " +
				"(opens a new window)", openMenuLink_onclick,
				Style.menuLink, previousTextFieldInd, i,
				menuLink_onmouseover, menuLink_onmouseout);
			var fullFormLinkWrapper = menuEntry(fullFormLink,
				Style.menuLinkWrapper);

			var visitBmnLink = menuLink(bmnHomeUri, "Visit Bug Me Not",
				"Go to the Bug Me Not home page (opens a new window)",
				openMenuLink_onclick, Style.menuLink, previousTextFieldInd,
				i, menuLink_onmouseover, menuLink_onmouseout);
			var visitBmnLinkWrapper = menuEntry(visitBmnLink,
				Style.menuLinkWrapper);

			var bmnWrapper = document.createElement("div");
			bmnWrapper.id = "reify-bugmenot-bmnWrapper" + i;
			bmnWrapper.className = "reify-bugmenot-bmnWrapper";
			bmnWrapper.appendChild(getLoginLinkWrapper);
			bmnWrapper.appendChild(fullFormLinkWrapper);
			bmnWrapper.appendChild(visitBmnLinkWrapper);
			copyProperties(bmnWrapper.style, Style.bmnWrapper);

			bmnContainer.appendChild(bmnWrapper);
		}
		if (bmnContainer.hasChildNodes()) {
			bodyEl.appendChild(bmnContainer);
		}
	}
	function menuEntry(linkEl, styleObj) {
		var p = document.createElement("p");
		copyProperties(p.style, styleObj);	
		p.appendChild(linkEl); 
		return p;
	}
	function menuLink(href, text, title, onclick, styleObj,
		usernameInputIndex, passwordInputIndex, onmouseover, onmouseout) {
		var newMenuLink = document.createElement("a");
		newMenuLink.href = href;
		newMenuLink.appendChild(document.createTextNode(text));
		newMenuLink.title = title;
		newMenuLink.setAttribute('usernameInputIndex', usernameInputIndex);
		newMenuLink.setAttribute('passwordInputIndex', passwordInputIndex);

		Utility.addEventHandler(newMenuLink, "click", onclick);
		Utility.addEventHandler(newMenuLink, "mouseover", onmouseover);
		Utility.addEventHandler(newMenuLink, "mouseout", onmouseout);

		copyProperties(newMenuLink.style, styleObj);
		
		return newMenuLink;
	}
	function menuLink_onmouseover(event) {
		event = event || window.event;
		var target = event.currentTarget || event.srcElement;
		copyProperties(target.style, Style.menuLinkHover);
	}
	
	function menuLink_onmouseout(event) {
		event = event || window.event;
		var target = event.currentTarget || event.srcElement;
		copyProperties(target.style, Style.menuLink);
	}
	function getLoginLink_onclick(event) {
		if((!allInputs[this.getAttribute('passwordInputIndex')].value.length &&
			!allInputs[this.getAttribute('usernameInputIndex')].value.length) ||
			confirm("Overwrite the current login entry?")) {
			getLogin(bmnUri, this.getAttribute('usernameInputIndex'),
				this.getAttribute('passwordInputIndex'));
		}
		menuLink_onmouseout({currentTarget: this});
		event.preventDefault && event.preventDefault();
		return false;
	}
	
	function openMenuLink_onclick(event) {
		if (typeof GM_openInTab != 'undefined') {
			GM_openInTab(this.href);
		} else {
			window.open(this.href);
		}
		menuLink_onmouseout({currentTarget: this});
		event.preventDefault && event.preventDefault();
		return false;
	}

	function usernameField_onfocus(event) {
		event = event || window.event;
		var target = event.currentTarget || event.srcElement;
		target.setAttribute('hasFocus', true);
		showHideBmnWrapper(target, allInputs[target.
	getAttribute('passwordInputIndex')], true);
	}

	function usernameField_onblur(event) {
		event = event || window.event || this;
		var target = event.currentTarget || event.srcElement;
		target.setAttribute('hasFocus', false);
		var fRef = hideIfNoFocus(allInputs[target.
	getAttribute('usernameInputIndex')],
			allInputs[target.getAttribute('passwordInputIndex')]);
		// race condition: wait for other element's onfocus
		setTimeout(fRef, BLUR_TIMEOUT);
	}

	function pwField_onfocus(event) {
		event = event || window.event;
		var target = event.currentTarget || event.srcElement;
		target.setAttribute('hasFocus', true);
		showHideBmnWrapper(allInputs[target.getAttribute('usernameInputIndex')],
				target, true);
	}
	function pwField_onblur(event) {
		event = event || window.event;
		var target = event.currentTarget || event.srcElement;
		target.setAttribute('hasFocus', false);
		var fRef = hideIfNoFocus(allInputs[target.
	getAttribute('usernameInputIndex')],
			allInputs[target.getAttribute('passwordInputIndex')]);
		// race condition: wait for other element's onfocus
		setTimeout(fRef, BLUR_TIMEOUT);
	}

	function hideIfNoFocus(usernameField, pwField) {
		return (function() {
			var bUsernameFocus = usernameField.getAttribute('hasFocus');
			if (typeof bUsernameFocus == 'string') {
				bUsernameFocus = (bUsernameFocus && bUsernameFocus != 'false');
			}
			var bPasswordFocus = pwField.getAttribute('hasFocus');
			if (typeof bPasswordFocus == 'string') {
				bPasswordFocus = (bPasswordFocus && bPasswordFocus != 'false');
			}
			if ((!bUsernameFocus) && (!bPasswordFocus)) {
				GM_log('calling showHideBmnWrapper from hideIfNoFocus');
				showHideBmnWrapper(usernameField, pwField, false);
			}
		});
	}

	function showHideBmnWrapper(usernameField, pwField, show) {
		var bmnWrapper = getBmnWrapper(pwField.
	getAttribute('passwordInputIndex'));

		if (show) {
			bmnWrapper.style.display = "block";
			positionBmnWrapper(bmnWrapper, usernameField, pwField);
		} else {
			GM_log('hiding bugmenot wrapper');
			bmnWrapper.style.display = "none";

			// Menu links may not get onmouseout event, so they get
			// stuck with the hover style unless we do this.
			var menuLinks = bmnWrapper.getElementsByTagName("div");
			for (var i = 0; i < menuLinks.length; i++) {
				copyProperties(menuLinks[i].style, Style.menuLink);
			}
		}
	}
	function positionBmnWrapper(bmnWrapper, usernameField, pwField) {
		var pwLeft = Utility.elementLeft(pwField);
		if (pwLeft + pwField.offsetWidth + bmnWrapper.offsetWidth +
			Utility.scrollLeft() + 10 < Utility.viewportWidth()) {
			bmnWrapper.style.left = (pwLeft + pwField.offsetWidth + 2) + "px";
			bmnWrapper.style.top = Utility.elementTop(pwField) + "px";
		} else {
			bmnWrapper.style.left = (Utility.elementLeft(usernameField) -
				bmnWrapper.offsetWidth - 2) + "px";
			bmnWrapper.style.top = Utility.elementTop(usernameField) + "px";
		}
	}

	// We have a uri param rather than assuming it's for the current
	// page so this function can be modular and potentially used
	// for registration pagespages other than the current one.
	function getLogin(uri, usernameInputIndex, passwordInputIndex) {
		var usernameField = allInputs[usernameInputIndex];
		var pwField = allInputs[passwordInputIndex];
		waitOrRestoreFields(usernameField, pwField, false);
		
		var hostUri = location.hostname;
		var firstAttempt = retrievals == 0;
		var submitData = "submit=This+login+didn%27t+work&num=" + retrievals +
			"&site=" + encodeURI(location.hostname);

		GM_xmlhttpRequest({
			method: firstAttempt ? "get" : "post",
			headers: firstAttempt ? null :
				{"Content-type": "application/x-www-form-urlencoded"},
			data: firstAttempt ? null : submitData,
			url: firstAttempt ? uri : bmnView,
			onload: function(responseDetails) {
				waitOrRestoreFields(usernameField, pwField, true);
				var doc = textToXml(responseDetails.responseText);
				if (!(doc && doc.documentElement)) {
				return Errors.say(Errors.malformedResponse);
				}

				var accountInfo = doc.documentElement.
				getElementsByTagName("dd")[0];
				if (!(accountInfo && accountInfo.childNodes.length > 2)) {
				return Errors.say(Errors.noLoginAvailable);
				}

				usernameField.value = accountInfo.childNodes[0].nodeValue;
				pwField.value = accountInfo.childNodes[2].nodeValue;
				retrievals++;
			},
			onerror: function(responseDetails) {
				waitOrRestoreFields(usernameField, pwField, true);
				Errors.say(Errors.xmlHttpFailure);
			}
		});
	}

	function waitOrRestoreFields(usernameField, pwField, restore) {
		document.documentElement.style.cursor = restore ? "default" :
	"progress";
		usernameField.value = restore ? "" : "Loading…";
		usernameField.disabled = !restore;
		pwField.disabled = !restore;
	}

	function getPreviousTextField(pwFieldIndex) {
		for (var i = pwFieldIndex; i >= 0 && i <allInputs.length; i--)
			if (allInputs[i].type && allInputs[i].type.toLowerCase() == "text")
				return i;

		return -1;
	}

	function textToXml(t) {
		try {
			if (typeof DOMParser != undefined) {
				var dp = new DOMParser();
				return dp.parseFromString(t, "text/xml");
			}
			else {
				return null;
			}
		}
		catch (e) {
			return null;
		}
	}

	var Errors = {
		noLoginAvailable: "Sorry, but BugMeNot.com had no login available " +
			"for this site.\nIf you're feeling helpful, you can click \"More " +
			"options\" to provide a login for future visitors.",
		malformedResponse: "Sorry, but I couldn't understand the response " +
			"from BugMeNot.com.\nThe service might be unavailable.",
		xmlHttpFailure: "There was an error in contacting BugMeNot.com.\n" +
			"The server may be unavailable or having internal errors.",

		say: function(msg) { alert(msg); return false; }
	};

	var Utility = {
		elementTop: function(el) {
			return Utility.recursiveOffset(el, "offsetTop");
		},
		elementLeft: function(el) {
			return Utility.recursiveOffset(el, "offsetLeft");
		},

		recursiveOffset: function(el, prop) {
			var dist = 0;
			while (el.offsetParent)
			{
				dist += el[prop];
				el = el.offsetParent;
			}
			return dist;
		},

		viewportWidth: function() {
			return Utility.detectAndUseAppropriateObj("clientWidth");
		},

		viewportHeight: function() {
			return Utility.detectAndUseAppropriateObj("clientHeight");
		},

		scrollLeft: function() {
			return Utility.detectAndUseAppropriateObj("scrollLeft");
		},

		scrollTop: function() {
			return Utility.detectAndUseAppropriateObj("scrollTop");
		},

		detectAndUseAppropriateObj: function(prop) {
			if (document.documentElement && document.documentElement[prop]) {
				return document.documentElement[prop];
			}
			else if (document.body && document.body[prop]) {
				return document.body[prop];
			} else {
				return -1;
			}
		},

		addEventHandler: function(target, eventName, eventHandler) {
			if (target.addEventListener) {
				target.addEventListener(eventName, eventHandler, false);
			} else if (target.attachEvent) {
				target.attachEvent("on" + eventName, eventHandler);
			}
		}
	};
	main();

Running the Hack

After installing this script (Tools → Install This User Script), go to http://www.kansascity.com/ and click any article. The Kansas City Star will interrupt your reading with a registration page, as shown in Figure 9-12.

Figure 9-12. KansasCity.com registration page

KansasCity.com registration page

The script pops up a menu showing the available options. Select "Get login from BugMe Not" from the pop-up menu, and the script will contact BugMeNot.com and autofill the registration form, as shown in Figure 9-13.

Figure 9-13. Registration information autofilled from But MeNot.com

Registration information autofilled from But MeNot.com

Tip

Logins collected by Bug MeNot.com are generally valid only for a short time. Publishers have gotten wise to this technique, and they will invalidate logins once they are used by too many people. You might need to try several times to find a valid login.

Personal tools