Craigslist locations filter

By Kuzmeech Last update Jan 15, 2011 — Installed 1,343 times.

There are 41 previous versions of this script.

// GreaseMonkey exclude filter + Google Maps highlighter
// Copyright (c) 2011, Kuzmeech, version 1.226
// Released under the GNU General Public Licence.
// Visit http://www.gnu.org/copyleft/gpl.html for a copy of said licence.

// Image Preview functionality has been borrowed from "Craigslist image preview 2" 
// filter by Kite who reworked Jeffrey Palm's original script. Thank you, guys!

// hides images less than 150px tall or wide as irrelevant

/*
** Changelog **
2011.01.15:
1.226 - Google chrome compatibility fixes: loose "debug" statement and GM_ functions by James Campos, a good samaritan. However it still doesn't allow pictures fetch from other domains under Chrome (GM_xmlhttpRequest implementation sucks) so better continue using FireFox...
1.225- images were not showing!
1.224- fix for jumping ads due to loaded images - now ads with images are moved to the top. Removed "show real pics" option due to that.
1.223- logic fixes: includes filter wasn't working, "next 100" known issue fixed
1.222- fix for ads with duplicate images - they were not filtered, hide empty ads overrides "has image"
1.221- fix for small images which were not hidden
1.21 - downscale image popups which do not fit the screen
1.20 - dual monitor & horizontal alignment fix.
1.19 - horizontally - shows smaller pics closer to cursor
1.18 - hides all extremely wide images and ones that are smaller than 150px height or width
	 - hides duplicate links that once were shown somewhere on the page
	 - added "td background=" support
	 - fix for & in url
1.17 - thanks for the tip from user "Perplexed Guide" - now full size popup picture previews with idea borrowed from CLPicView FF plugin
1.16 - fixed include logic, right align for labels on top
1.15 - image preview - fetches images only for _shown_ ad blocks
1.14 - include location filter added
1.13 - checkbox added to enable/disable exclusion filter 
1.12 - performance imrovements in RE preparation, debug output removed
1.11 - added "oneshot" timer for 300ms quiesce time after typing stopped, because "onkeyup" in its' pure state was blocking the browser
1.1 - added "exclude" input element at top with onkeyup event handler
1.0 - initial versoin where exclusions were defined in script array
*/

// ==UserScript==
// @name           Craigslist locations filter
// @include       http://*.craigslist.org/*
// @include       http://*.craigslist.ca/*
// @exclude       http://*.craigslist.org/
// @exclude       http://*.craigslist.ca/
// @namespace      http://kuzmeech.blogspot.com/
// ==/UserScript==

String.prototype.trim = function(symbol) {
	return this.replace(/^\s+|\s+$/g,"");
}

function $n(tag, on) {
	var e = document.createElement(tag);
	if (on) on.appendChild(e);
	return e;
}

function $t(text, on) {
	var e = document.createTextNode(text);
	if (on) on.appendChild(e);
	return e;
}

function $e(id) {
	return document.getElementById(id);
}

// called once when loading - converts location into G.maps link
function mapsLink() {
	ps = document.getElementsByTagName('p');
	for (var i = 0; i < ps.length; i++)  							// for each posting
	{
		if (ps[i].innerHTML.match(/index\d+\.html/)) continue;
		var font = ps[i].getElementsByTagName( 'font' )[0];
		if (font) {
			var location = font.innerHTML.trim().replace(/\(/, '').replace(/\)/, '');
			// replace with href to google maps
			font.innerHTML = "&nbsp;&nbsp;&nbsp;(<a href=\"http://maps.google.com/?q=" + location + "&z=8\">" 
					+ location + "</a>)";
		}	
	}
}

function oneshot() { 
	var timer; 
	return function( fun, time ) { 
		clearTimeout( timer ); 
		timer = setTimeout( fun, time ); 
	}; 
} 

var processAdsOneShot = oneshot(); 

/** converts array of patterns to array of RegExps + trims patterns */
function prepareRexs(array) {
	var rexs = new Array(); // prepare RE objects
	for (var j = 0; j < array.length; j++) {
		var pattern = array[j].trim();
		if (pattern != '') {
			rexs.push(new RegExp(pattern, 'i'));
		}	
	}
	return rexs;
}

var alphaDiv; // like alpha-male

/** main piece that walks through ads */
function processAds() {
	var excludeFiltersArray = $e("excludeLocationsInput").value.split(',');
	var includeFiltersArray = $e("includeLocationsInput").value.split(',');

	var rexsInclude = prepareRexs(includeFiltersArray);
	var rexsExclude = prepareRexs(excludeFiltersArray);

	ps = document.getElementsByTagName('p');
	
	for (var i = 0; i < ps.length; i++)  							// for each posting
	{
		var p = ps[i];
		if (p.innerHTML.match(/index\d+\.html/)) continue;
		
		if (!alphaDiv) {
			alphaDiv = $n('div');
			alphaDiv.id = 'alphaDiv';
			p.parentNode.insertBefore(alphaDiv, p);
		}
		
		var font = ps[i].getElementsByTagName( 'font' )[0];
		var visible = true;
		var noPics = false;
		if (font) {
			var location = font.innerHTML;
					
			if ($e('includeEnabled').checked) {
				visible = false;
				for (j = 0; j < rexsInclude.length; j++) {
					var re = rexsInclude[j];
					if (re.test(location)) {
						visible = true;
						break;
					}
				}
			}
			if ($e('excludeEnabled').checked) {
				for (j = 0; j < rexsExclude.length; j++) {
					var re = rexsExclude[j];
					if (re.test(location)) {
						visible = false;
						break;
					}
				}
			}
		} else { // no <font> - locatin is unspecified - exclude if "only include" checked 
			visible = ! $e('includeEnabled').checked; 
		}
		
		ps[i].style.display = visible ? 'block' : 'none';
		
		if (visible) { // images only for visible
			var pclass = ps[i].className;
			if (!pclass || !pclass.match(/thumbnails\-loaded/)) { // mark ads which we've added thumbnails to
				if (pclass) { ps[i].className += " "; }
				ps[i].className += "thumbnails-loaded";
				// show image previews
				showImages(ps[i]);
			} else {
				ps[i].style.display = noPics ? 'block' : 'none';
			}
		}	
	}
}

var EXCLUDE_LOCATIONS = "exclude.locations";

saveChangesAndRefresh = function() {
	GM_setValue("excludeLocationsInput", $e("excludeLocationsInput").value);
	GM_setValue("includeLocationsInput", $e("includeLocationsInput").value);
	GM_setValue("excludeEnabled", $e("excludeEnabled").checked);
	GM_setValue("includeEnabled", $e("includeEnabled").checked);
//	GM_setValue("hideBullshit", $e("hideBullshit").checked);

	updateElementsVisibility();
	processAdsOneShot( processAds, 200 ); 
};

function updateElementsVisibility() {
	$e("excludeLocationsInput").style.display = $e("excludeEnabled").checked ? '' : 'none';
	$e("includeLocationsInput").style.display = $e("includeEnabled").checked ? '' : 'none';
	$e("includeLabel").style.color = $e("includeEnabled").checked ? 'red' : 'black';
	$e("excludeLabel").style.color = $e("excludeEnabled").checked ? 'red' : 'black';
//	$e("hideBullshitCheckbox").style.color = $e("hideBullshit").checked ? 'red' : 'black';
	
}

function addInput(name, defVal) {
	var input = $n("input");
	input.id = name;
	input.size = 70;
	input.addEventListener("keyup", saveChangesAndRefresh, true);
	input.value = GM_getValue(name, defVal);
	return input;
}

function addCheckbox(name) {
	var checkbox = $n("input");
	checkbox.type = "checkbox";
	checkbox.id = name;
	checkbox.addEventListener("change", saveChangesAndRefresh, true);
	checkbox.checked = GM_getValue(name, true);
	return checkbox;
}

var imagesOnly = false;

function addLinkAtTop() {
	// find top FORM with TABLE inside
	var form = document.getElementsByTagName("form");
	if (form && form[0].action.search(/search/)) {
		
		var tbody = form[0].getElementsByTagName('tbody')[0];
		var tr = $n("tr",tbody); 
		var td = $n("td", tr);
			td.align = "right";
			var label = $n("label", td); 
			label.innerHTML = "Only following locations:";
			label.id = "includeLabel";
		
		td = $n("td", tr);
			var input = addInput("includeLocationsInput", 'cambridge, belmont');
			td.appendChild(addCheckbox("includeEnabled"));
			td.appendChild(input);
//			td.colspan = 2;
		td = $n("td", tr);
/*			var label = $n("label", td);
			label.id = "hideBullshitCheckbox";
			label.innerHTML = '<input type="checkbox" id="hideBullshit"/> really hide no-pics ads';
			$e("hideBullshit").addEventListener("change", saveChangesAndRefresh, true);
			$e("hideBullshit").checked = GM_getValue("hideBullshit", true);
			label.title = "Hide ads without qualifying pictures which are showing like empty line";
*/
		tr = $n("tr",tbody); 
		td = $n("td", tr);
			td.align = "right";
			label = $n("label", td); 
			label.innerHTML = "Exclude locations:";
			label.id = "excludeLabel";
		
		td = $n("td", tr);
			excludeInput = addInput("excludeLocationsInput", 
				GM_getValue(EXCLUDE_LOCATIONS, 'malden, everett')); // for backwards compatibility, remove after march 2009
			td.appendChild(addCheckbox("excludeEnabled"));
			td.appendChild(excludeInput);
			td.colspan = 2;			
	}
	updateElementsVisibility();
}

/** finds all child <A/>  and fetches images for it */
function showImages(element) {
	var links = element.getElementsByTagName("a");
	for (i=0; i<links.length; i++) {
		var link = links[i];
		if (link.href && link.href.match(/.*craigslist.*\/\d+\.html$/)) {
		  GM_xmlhttpRequest({
			method:"GET",
				url: link.href,
				headers:{
				"User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6",
				  "Accept":"text/html,text/monkey,text/xml,text/plain",
				  },
				onload: processAd(link)
			});
		}
	}
}

var bigPictureDivCounter = 0;
var knownPictures = {}
var size = 60;

// ajax callback for each ad text
function processAd(_a) {
	var a = _a;
	
	return function(details) {
		if (details.responseText) {
			var div;
			if (m = details.responseText.match(/<img ([^>]+)>/gi)) {
				processImgs(a, div, m, "src");
			}
			if (m = details.responseText.match(/<td ([^>]+)>/gi)) {
				processImgs(a, div, m, "background");
			}
		}
	};
}

var counter = 0;
function processImgs(a, div, m, word) {
	for (j=0; j<m.length; j++) {
		s = m[j];
		if (!s) continue;
		s = s.split(word + '="')[1];
		if (!s) continue;
		s = s.split('"')[0];
		s = s.replace(/amp;/gi, ''); // some strange people post &amp; in URL parameters - fix for that
		
		if (knownPictures[s]) { // don't show duplicate pictures
			continue;
		} else {
			knownPictures[s] = s;
		}
		
		if (!div) {
			var d = $n("div",a.parentNode);
			var br = $t(" ",a.parentNode);
			div = $n("div",a.parentNode);
		}
		// small thumbnails shown
		var newA = $n("a", div);
		var img = $n("img", newA);
		div.style.display = 'none';
		img.src = s;
		img.style.maxHeight = size + "px";
//		img.title = "Thumbnail shown by Craigslist location filter Greasemonkey Script";
		$t(" ", div);
		newA.href = s;

		addBigPictureDiv(a, s, img);

		// remove irrelevant bull%%it
		img.addEventListener("load", function() { 
				var bigImage = $e("div" + this.id).firstChild;
				if ((this.height < size)
						|| bigImage.height < 150 || bigImage.width < 150
							) { // non-proportional pictures and small images are ignored
					bigImage.parentNode.className = 'bullshitPic';
					this.parentNode.style.display = 'none';
				} else {
					this.parentNode.parentNode.style.display = 'block';
					bigImage.parentNode.className = 'realPic';
					if (this.parentNode.parentNode.parentNode.className != 'realAd') {
						this.parentNode.parentNode.parentNode.className = 'realAd';
						counter ++;
						$e('alphaDiv').appendChild(this.parentNode.parentNode.parentNode); // zz
//						this.parentNode.parentNode.parentNode.innerHTML = counter + ": "  + this.parentNode.parentNode.parentNode.innerHTML;
						
					}
					
					
				}
		}, true);
	}
};

// adds div with big picture popup + handlers
function addBigPictureDiv(a, s, img) {
	// big picture hidden
	var fullSizeImgDiv = $n("div", a.parentNode);
	var fullSizeImg = $n("img", fullSizeImgDiv);
	fullSizeImgDiv.style.display = 'none';
	fullSizeImgDiv.style.position = 'absolute';
	fullSizeImgDiv.style.left = '150px';
	fullSizeImgDiv.style.border = '2px dashed blue';
	fullSizeImg.src = s;
	fullSizeImgDiv.id = "div" + bigPictureDivCounter;
	img.id = "p" + bigPictureDivCounter;
	fullSizeImgDiv.id = "divp" + bigPictureDivCounter;

	bigPictureDivCounter ++; 
	
	img.addEventListener("mouseover", function(event) { 
			var div = $e("div" + this.id);
			
			// for extremely tall or wide images
			if (window.innerWidth < div.firstChild.width * 1.05) {
					var originalWidth = div.firstChild.width;
					div.firstChild.width = window.innerWidth * 0.85;
					div.firstChild.height = div.firstChild.height * (div.firstChild.width/originalWidth);
			}
			
			if (window.innerHeight < div.firstChild.height * 1.05) { // if still..
					var originalHeight = div.firstChild.height;
					div.firstChild.height = window.innerHeight * 0.85;
					div.firstChild.width = div.firstChild.width * (div.firstChild.height/originalHeight);
			}

			var cursorDown = (event.screenY / screen.height) >  0.54; // vertically - above or below?
			div.style.top = cursorDown 
					? (event.pageY - div.firstChild.height - 5)
					: event.pageY + 5;
			
			div.style.left = 
				(div.firstChild.width > screen.width / 2) ? 
					(screen.width - div.firstChild.width) / 2 // horizontally - centered
					: (((event.screenX - window.screenX)/ window.innerWidth) < 0.54 ? (event.pageX + 15) : (event.pageX - div.firstChild.width - 10));

					

					
			div.style.display = "block";
			
			}, 
			true);
	img.addEventListener("mouseout", function(evt) { $e("div" + this.id).style.display = "none"; }, true);
}

// @copyright      2009, 2010 James Campos
// @license        cc-by-3.0; http://creativecommons.org/licenses/by/3.0/
if (typeof GM_deleteValue == 'undefined') {

    GM_addStyle = function(css) {
        var style = document.createElement('style');
        style.textContent = css;
        document.getElementsByTagName('head')[0].appendChild(style);
    }

    GM_deleteValue = function(name) {
        localStorage.removeItem(name);
    }

    GM_getValue = function(name, defaultValue) {
        var value = localStorage.getItem(name);
        if (!value)
            return defaultValue;
        var type = value[0];
        value = value.substring(1);
        switch (type) {
            case 'b':
                return value == 'true';
            case 'n':
                return Number(value);
            default:
                return value;
        }
    }

    GM_log = function(message) {
        console.log(message);
    }

    GM_openInTab = function(url) {
        return window.open(url, "_blank");
    }

     GM_registerMenuCommand = function(name, funk) {
    //todo
    }

    GM_setValue = function(name, value) {
        value = (typeof value)[0] + value;
        localStorage.setItem(name, value);
    }
}

addLinkAtTop();
mapsLink();
processAds();