Orbitz.com flight search

By niknah Last update Aug 2, 2009 — Installed 122 times.

There are 3 previous versions of this script.

// ==UserScript==
// @name           Orbitz.com flight search
// @namespace      OrbitzSearch
// @version_timestamp 1242917549147
// @description    Search orbitz.com plane tickets with different dates.
// @include        http://www.orbitz.com/*
// ==/UserScript==

/*

To use:
Do search for flights on orbitz.com
Click "start search" on the results page.

The search will start around the departure/return dates you have picked until orbitz returns an error when the search reaches today.


To try to guess the best season of a year...
* Change the "move ... per search" box to 30 days.
* Change departure/return dates to 6 months away.
* Click "start search"
This will do searches in 30 day gaps.

* Note: if you search too much as a non-member eventually they'll ask you to join.

*/


var version_scriptNum = 44038; // Change this to the number given to the script by userscripts.org (check the address bar)
var version_timestamp = 1242917549147; // Used to differentiate one version of the script from an older one. Use the Date.getTime() function to get a value for this.
//function updateCheck(forced) {if((forced)||(parseInt(GM_getValue("lastUpdate", "0")) + 86400000 <= (new Date().getTime()))) {try {GM_xmlhttpRequest({method: "GET",url: "http://userscripts.org/scripts/source/" + version_scriptNum +".meta.js" + "?" + new Date().getTime(),headers: {'Cache-Control': 'no-cache'},onload: function(xhrResponse) {GM_setValue("lastUpdate", new Date().getTime() + ""); var rt = xhrResponse.responseText; var scriptName = (/@name\s*(.*?)\s*$/m.exec(rt))[1]; GM_setValue("targetScriptName", scriptName); if (parseInt(/@version_timestamp\s+([0-9]+)\s*$/m.exec(rt)[1]) > version_timestamp) {if (confirm("There is an update available for the Greasemonkey script \"" + scriptName + ".\"\nWould you like to go to the install page now?")) {GM_openInTab("http://userscripts.org/scripts/show/" + version_scriptNum);}} else if (forced) {alert("No update is available for \"" + scriptName + ".\"");}}});} catch (err) {if (forced) {alert("An error occurred while checking for updates:\n" + err);}}}} GM_registerMenuCommand(GM_getValue("targetScriptName", "???") + " - Manual Update Check", function() {updateCheck(true);}); updateCheck(false);

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


if(!this.JSON){JSON={};}
(function(){function f(n){return n<10?'0'+n:n;}
if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z';};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+
partial.join(',\n'+gap)+'\n'+
mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+
mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});};}
if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');};}}());

nHtml={
Log:function(str) {
	if(GM_log) {
		GM_log('OrbitzSearch:'+str);
	}
},
FindByXPath:function(obj,xp) {
	var q=null;
	try {
		q=document.evaluate(xp,obj,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null);
	} catch(err) {
		this.Log("XPath Failed:"+tag+","+className);
	}
	if(q && q.singleNodeValue) { return q.singleNodeValue; }
	return null;
},
Click2:function(obj,evtName) {
	var evt = document.createEvent("MouseEvents");
	evt.initMouseEvent(evtName, true, true, window,
		0, 0, 0, 0, 0, false, false, false, false, 0, null);
	return !obj.dispatchEvent(evt);
},
Click:function(obj) {
	this.Click2(obj,"click");
}
};


OrbitzSearch={

SaveSearch:function(search) {
	GM_setValue('search',JSON.stringify(search));
},

LoadSearch:function() {
	return JSON.parse(GM_getValue('search','{}'));
},

SortableDateToOrbitzDate:function(str) {
	str=str.trim();
	return str.substring(5,7)+"/"+str.substring(8,10)+"/"+str.substring(2,4);
},

ClickDate:function(dateStr) {
	var startEnd=dateStr.split('-');
	
	var inps=this.GetStartEndInputs();
	if(inps.start) {
		inps.start.value=this.SortableDateToOrbitzDate(startEnd[0]);
	}
	inps.end.value=this.SortableDateToOrbitzDate(startEnd[1]);
},

minFare:0,
GetCurrentResultsDiv:function(search) {
	var div=document.createElement('div');
	if(!search.ByDate) { return div; }

	var dates=[];
	var byCost=[];
	for(var d in search.ByDate) {
		var cost=search.ByDate[d];
		byCost.push(cost);
		dates.push(d);
	}
	byCost=byCost.sort(function(a,b) { return a-b; });
	this.minFare=byCost[0];
	dates=dates.sort();
	for(var dUpto=0; dUpto<dates.length; dUpto++) {
		var dStr=dates[dUpto];
		var t=document.createElement('a');
		var cost=search.ByDate[dStr];
		var fontOpt='';
		if(cost==byCost[0]) {
			t.style.fontWeight='bold';
		}
		t.innerHTML=dStr+" :	 $"+cost;
		t.href='javascript:void();';
		t.addEventListener('click',function(e) {
			var dateStr=e.target.textContent.split(':')[0];
			OrbitzSearch.ClickDate(dateStr);
			
		},false);

		div.appendChild(t);
		div.appendChild(document.createElement('br'));
	}
	return div;
},

GetStartEndInputs:function() {
	var start=document.getElementById('airchgStartDate');
	var end=document.getElementById('airchgEndDate');
	if(!start) { start=document.getElementById('amcchgStartDate1'); }
	if(!end) { 
		var endNum=1;
		while(true) {
			var e=document.getElementById('amcchgStartDate'+endNum); 
			if(!e || e.value=="mm/dd/yy") {break; }
			end=e;
			endNum++;
		}
	}
	return {
		'start':start,
		'end':end
	};
},

AddATag:function(txt,func,title) {
	var stopA=document.createElement('a');
	stopA.innerHTML=txt;
	stopA.href='javascript:void();';
	if(title) stopA.title=title;
	stopA.addEventListener('click',func,false);
	return stopA;
},

stopA:null,
startA:null,
continueA:null,
//stopOnBestA:null,
belowInp:null,

ToggleSearchVis:function(started) {
	if(this.stopA)
		this.stopA.style.display=(started?'inline':'none');
//	this.stopOnBestA.style.display=(started?'inline':'none');
	if(this.continueA)
		this.continueA.style.display=(started?'none':'inline');
	if(this.startA)
		this.startA.style.display=(started?'none':'inline');
},

SearchForm:function() {
	var chgBotBox=document.getElementById('sort_tabs');
	if(!chgBotBox) { return; }
	var search=this.LoadSearch();
	var div=document.createElement('div');
	div.innerHTML="Move <input id='dateShiftAdd' size='3' value='"+(search.dateShiftAdd==undefined?1:search.dateShiftAdd)+"' /> days per search<br />"+
		"<input type='checkbox' id='noStartDate' "+(search.noStartDate?"checked":"")+" />Don't change the start date<br />"+
		"<input type='checkbox' id='noEndDate' "+(search.noEndDate?"checked":"")+" />Don't change the end date<br />";

	div.appendChild(this.stopA=this.AddATag('Stop Search',function() {
		OrbitzSearch.StopSearch();
		OrbitzSearch.ToggleSearchVis(false);
	}));
	this.stopA.style.fontSize='14pt';
	div.appendChild(document.createElement('br'));
/*
	div.appendChild(this.stopOnBestA=this.AddATag('Stop when a price below $',function() {
		OrbitzSearch.StopOnBestSearch();
	}));
	this.stopOnBestA.style.fontSize='14pt';
*/
	div.appendChild(document.createTextNode('Stop when price is below $'));
	var belowInp=document.createElement('input');
	belowInp.id='belowInp';
	belowInp.size='6';
	belowInp.value=search.belowCost>0?search.belowCost:-1;
	div.appendChild(belowInp);
	this.belowInp=belowInp;
	div.appendChild(document.createElement('br'));


	div.appendChild(this.continueA=this.AddATag('Continue Search',function() {
		OrbitzSearch.ContinueSearch();
		OrbitzSearch.ToggleSearchVis(true);
	}));
	div.appendChild(document.createElement('br'));
	div.appendChild(this.startA=this.AddATag('Start Search',function() {
		OrbitzSearch.StartSearch();
		OrbitzSearch.ToggleSearchVis(true);
	},"This will take a while, leave it running in the background.  The search will stop when we reach a date before today."));
	div.appendChild(document.createElement('br'));
	OrbitzSearch.ToggleSearchVis(search.startSearch);

	
	var currentResults=null;
	div.appendChild(this.AddATag('Hide/Show Results',function() {
		currentResults.style.display=currentResults.style.display=='none'?'block':'none';
	},""));
	div.appendChild(document.createElement('br'));
	
	currentResults=this.GetCurrentResultsDiv(search);
	div.appendChild(currentResults);
	
	chgBotBox.insertBefore(div,chgBotBox.childNodes[0]);
},

StopSearch:function() {
	var search=this.LoadSearch();
	if(!search.startSearch) { return false; }
	search.startSearch=undefined;
	this.SaveSearch(search);
	return true;
},
/*
StopOnBestSearch:function() {
	var search=this.LoadSearch();
	search.stopOnBest=search.stopOnBest?false:true;
	this.SaveSearch(search);
},

*/

Get2Digits:function(num) {
	if(num<10) { num="0"+num.toString(); }
	return num;
},
DateToDateStr:function(d) {
	return this.Get2Digits(d.getMonth()+1)+"/"+this.Get2Digits(d.getDate())+"/"+this.Get2Digits(d.getFullYear()-2000);
},

DateToSortableStr:function(d) {
	return d.getFullYear()+"/"+this.Get2Digits(d.getMonth()+1)+"/"+this.Get2Digits(d.getDate());
},

toInt:function(str) {
	var num=str.replace(/^0*/,'');
	return parseInt(num);
},

DateStrToDate:function(str) {
	var i=str.split('/');
	var d=new Date();
	d.setMonth(this.toInt(i[0])-1);
	d.setFullYear(2000+this.toInt(i[2]));
	d.setDate(i[1]);

	return d;
},

ContinueSearch:function() {
	var search=this.LoadSearch();
	search.startSearch=true;
	this.SaveSearch(search);
	this.Search();
},
StartSearch:function() {
	var search=this.LoadSearch();
	search.startSearch=true;
//	search.stopOnBest=false;
	search.dateShift=0;
	search.belowCost=parseInt(this.belowInp.value);
	search.dateShiftAdd=parseInt(document.getElementById('dateShiftAdd').value);
	search.noStartDate=document.getElementById('noStartDate').checked;
	search.noEndDate=document.getElementById('noEndDate').checked;
	search.ByDate={};
	if(!search.dateShiftAdd || isNaN(search.dateShiftAdd)) { search.dateShiftAdd=1; }

	var inps=this.GetStartEndInputs();

	var startDate=this.DateStrToDate(inps.start.value);
	var endDate=this.DateStrToDate(inps.end.value);
	search.startDate=startDate.getTime();
	search.endDate=endDate.getTime();
	nHtml.Log('Start/end date:'+startDate.toLocaleString()+" to "+endDate.toLocaleString());
	this.SaveSearch(search);
	
	this.Search();
},

GetBestFare:function() {
	var nonNumberRe=new RegExp('[^0-9]','g');
	var ss=document.evaluate(".//div[@class='matrixTotal']",document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
	var bestFare=-1;
	for(var s=0; s<ss.snapshotLength; s++) {
		var priceDiv=ss.snapshotItem(s)
		var num=parseInt(priceDiv.textContent.replace(nonNumberRe,''));
		if(bestFare<0 || num<bestFare) { bestFare=num; }
	}
	return bestFare;
},

Search:function() {
	var search=this.LoadSearch();
	if(!search.startSearch) { return; }
	
//	var airOrigin=document.getElementById('airOrigin');
//	var airDestination=document.getElementById('airDestination');
	var inps=this.GetStartEndInputs();

	var buttonDiv=nHtml.FindByXPath(document,".//div[contains(@class,'geckoButtonFix') and contains(string(),'Find')]");
	if(!inps.start || !inps.end || !buttonDiv) {
		// we're in the intermediate page?
//		window.setTimeout(function() { OrbitzSearch.Search(); },5000);
//		nHtml.Log("Could not find date inputs");
		return;
	}

	// add best fare
	var bestFare=this.GetBestFare();
	var d=new Date();
	if(search.noStartDate) {
		d.setTime(search.startDate);
	} else {
		d.setTime(search.startDate+(search.dateShift*86400*1000));
	}
	var dend=new Date();
	if(search.noEndDate) {
		dend.setTime(search.endDate);
	} else {
		dend.setTime(search.endDate+(search.dateShift*86400*1000));
	}
	var currentDStr=this.DateToSortableStr(d)+' - '+this.DateToSortableStr(dend);
	if(bestFare>0) {
		search.ByDate[currentDStr]=bestFare;
	}
	
	nHtml.Log("Bestfare:"+currentDStr+"="+bestFare);

	// search for the next date
	search.dateShift=(0-search.dateShift);
	if(search.dateShift>=0) {
		search.dateShift+=search.dateShiftAdd;
	}
	
	if(search.dateShift>365) {
		nHtml.Log("Reached 365 days, quitting");
		return;
	}
	
	this.SaveSearch(search);

	if(bestFare>0 && bestFare<search.belowCost) {
		nHtml.Log("Found beter fare:"+bestFare);
		this.minFare=bestFare;
		this.StopSearch();
		this.ToggleSearchVis(false);
		return;
	}

	if(!search.noStartDate) {
		d.setTime(search.startDate+(search.dateShift*86400*1000));
		inps.start.value=this.DateToDateStr(d);
	}
	if(!search.noEndDate) {
		dend=new Date();
		dend.setTime(search.endDate+(search.dateShift*86400*1000));
		inps.end.value=this.DateToDateStr(dend);
	}


	nHtml.Click(buttonDiv.childNodes[0]);
}

};

if(GM_registerMenuCommand) {
	GM_registerMenuCommand('Orbitz - stop searching',function() {
		OrbitzSearch.StopSearch();
	});
}

window.addEventListener('load',function() {
	var perror=nHtml.FindByXPath(document,"//p[@class='error']");
	if(perror) {
		nHtml.Log('Search stopped on error');
		if(OrbitzSearch.StopSearch()) {
			window.history.go(-1);
		}
	} else {
		OrbitzSearch.SearchForm();
		window.setTimeout(function() {
			OrbitzSearch.Search();
		},3000);
	}
},false);