// ==UserScript==
// @name          Monkey Do
// @description   A del.icio.us assistant
// @version       2005-09-08
// @namespace     http://diveintomark.org/projects/greasemonkey/
// @include       http*://*
// ==/UserScript==
//
// Tested with
// - Firefox 1.0 and 1.5, Greasemonkey 0.5 <http://greasemonkey.mozdev.org/>
//

/* BEGIN LICENSE BLOCK
Copyright (C) 2005 Mark Pilgrim

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You can download a copy of the GNU General Public License at
http://diveintomark.org/projects/greasemonkey/COPYING
or get a free printed copy by writing to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

This program includes ISBN detection code from BookBurro
Copyright (C) 2005 Jesse Andrews and Britt Selvitelle
and relicensed by the authors under the GNU General Public License.

This program includes code from Greasemonkey
Copyright (C) 2004-5 Aaron Boodman
and included here in accordance with the MIT License.

This program includes original code for setting default CSS styles
that constitutes a derived work of certain data files in Firefox
(res/html.css and res/forms.css).  The code is included here in
accordance with the GNU General Public License.

END LICENSE BLOCK */

var DEBUG = 0;
var DEBUG_SIMULATION_ONLY = 0;
var gOptions = [];
var gSettingHotkey = false;

Array.prototype.contains = function(sString) {
    for (var i = 0; i < this.length; i++) {
	if (this[i] == sString) {
	    return true;
	}
    }
    return false;
}

String.prototype.contains = function(sMatch) {
    return this.indexOf(sMatch) != -1;
}

String.prototype.startswith = function(sMatch) {
    return this.indexOf(sMatch) == 0;
}

String.prototype.endswith = function(sMatch) {
    var iPos = this.lastIndexOf(sMatch);
    return (iPos != -1 && (iPos == this.length - sMatch.length));
}

String.prototype.trim = function() {
    return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
}

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

String.prototype.normalizeTags = function() {
    return this.toLowerCase().normalize().split(' ').sort().join(' ');
}

String.prototype.lpad = function(cPadder, iMaxLen) {
    var s = this;
    for (var i = s.length; i < iMaxLen; i++) {
	s = cPadder + s;
    }
    return s;
}

String.prototype.unquote = function() {
    return unescape(this.replace(/\+/g, ' '));
}

String.prototype.substringBefore = function(sDelimeter) {
    var s = this;
    var iPos = s.indexOf(sDelimeter);
    if (iPos != -1) {
	s = s.substring(0, iPos);
    }
    return s;
}

String.prototype.substringAfterFirst = function(sDelimeter) {
    var s = this;
    var iPos = s.indexOf(sDelimeter);
    if (iPos != -1) {
	s = s.substring(iPos + sDelimeter.length);
    }
    return s;
}

String.prototype.substringAfterLast = function(sDelimeter) {
    var s = this;
    var iPos = s.lastIndexOf(sDelimeter);
    if (iPos != -1) {
	s = s.substring(iPos + sDelimeter.length);
    }
    return s;
}

String.prototype.replaceString = function(sOld, sNew) {
    var re = '';
    var arSpecialChars = ['\\', '[', ']', '(', ')', '.', '*', '+', 
			  '^', '$', '?', '|', '{', '}'];
    for (var i = 0; i < sOld.length; i++) {
	var c = sOld.charAt(i);
	if (arSpecialChars.contains(c)) {
	    re += '\\' + c;
	} else {
	    re += c;
	}
    }
    var oRegExp = new RegExp('(' + re + ')', 'gim');
    return this.replace(oRegExp, sNew);
}

String.prototype.containsAll = function(arKeywords) {
    var s = this.toLowerCase();
    for (var i = 0; i < arKeywords.length; i++) {
	var sKeyword = arKeywords[i].toLowerCase();
	if (s.indexOf(sKeyword) == -1) {
	    return false;
	}
    }
    return true;
}

String.prototype.containsAny = function(arKeywords) {
    var s = this.toLowerCase();
    for (var i = 0; i < arKeywords.length; i++) {
	var sKeyword = arKeywords[i].toLowerCase();
	if (s.indexOf(sKeyword) != -1) {
	    return true;
	}
    }
    return false;
}

String.prototype.capitalize = function() {
    if (this.length == 0) { return this; }
    return this.substring(0, 1).toUpperCase() + this.substring(1);
}

RegExp.prototype.count = function(s) {
    return s.split(this).length - 1;
}

XMLDocument.prototype.NSResolver = function(prefix) {
    return '';
}

Event.prototype.holdEverything = function() {
    if (this.target) {
	this.target.blur();
    }
    this.preventDefault();
    this.stopPropagation();
    return false;
}

function displayKeyFromEvent(e) {
    var bCtrlKey = e.ctrlKey;
    var bAltKey = e.altKey;
    var bShiftKey = e.shiftKey;
    var bCmdKey = e.metaKey;
    var sDisplayKey = (bCtrlKey ? 'Ctrl + ' : '') +
	(bCmdKey ? 'Cmd + ' : '') +
	(bAltKey ? 'Alt + ' : '') +
	(bShiftKey ? 'Shift + ' : '');
    var sKey = String.fromCharCode(e.which);
    switch (e.keyCode) {
    case e.DOM_VK_BACK_SPACE: return sDisplayKey + 'Backspace';
    case e.DOM_VK_TAB: return sDisplayKey + 'Tab';
    case e.DOM_VK_CLEAR: return sDisplayKey + 'Clear';
    case e.DOM_VK_RETURN: return sDisplayKey + 'Return';
    case e.DOM_VK_ENTER: return sDisplayKey + 'Enter';
    case e.DOM_VK_PAUSE: return sDisplayKey + 'Pause';
    case e.DOM_VK_ESCAPE: return sDisplayKey + 'Esc';
    case e.DOM_VK_SPACE: return sDisplayKey + 'Space';
    case e.DOM_VK_PAGE_UP: return sDisplayKey + 'PgUp';
    case e.DOM_VK_PAGE_DOWN: return sDisplayKey + 'PgDn';
    case e.DOM_VK_END: return sDisplayKey + 'End';
    case e.DOM_VK_HOME: return sDisplayKey + 'Home';
    case e.DOM_VK_LEFT: return sDisplayKey + 'Left Arrow';
    case e.DOM_VK_UP: return sDisplayKey + 'Up Arrow';
    case e.DOM_VK_RIGHT: return sDisplayKey + 'Right Arrow';
    case e.DOM_VK_DOWN: return sDisplayKey + 'Down Arrow';
    case e.DOM_VK_PRINTSCREEN: return sDisplayKey + 'PrtSc';
    case e.DOM_VK_INSERT: return sDisplayKey + 'Ins';
    case e.DOM_VK_DELETE: return sDisplayKey + 'Del';
    case e.DOM_VK_NUMPAD0: return sDisplayKey + 'NumPad 0';
    case e.DOM_VK_NUMPAD1: return sDisplayKey + 'NumPad 1';
    case e.DOM_VK_NUMPAD2: return sDisplayKey + 'NumPad 2';
    case e.DOM_VK_NUMPAD3: return sDisplayKey + 'NumPad 3';
    case e.DOM_VK_NUMPAD4: return sDisplayKey + 'NumPad 4';
    case e.DOM_VK_NUMPAD5: return sDisplayKey + 'NumPad 5';
    case e.DOM_VK_NUMPAD6: return sDisplayKey + 'NumPad 6';
    case e.DOM_VK_NUMPAD7: return sDisplayKey + 'NumPad 7';
    case e.DOM_VK_NUMPAD8: return sDisplayKey + 'NumPad 8';
    case e.DOM_VK_NUMPAD9: return sDisplayKey + 'NumPad 9';
    case e.DOM_VK_MULTIPLY: return sDisplayKey + 'NumPad *';
    case e.DOM_VK_ADD: return sDisplayKey + 'NumPad +';
    case e.DOM_VK_SEPARATOR: return sDisplayKey + 'NumPad Sep';
    case e.DOM_VK_SUBTRACT: return sDisplayKey + 'NumPad -';
    case e.DOM_VK_DECIMAL: return sDisplayKey + 'NumPad .';
    case e.DOM_VK_DIVIDE: return sDisplayKey + 'NumPad /';
    case e.DOM_VK_F1: return sDisplayKey + 'F1';
    case e.DOM_VK_F2: return sDisplayKey + 'F2';
    case e.DOM_VK_F3: return sDisplayKey + 'F3';
    case e.DOM_VK_F4: return sDisplayKey + 'F4';
    case e.DOM_VK_F5: return sDisplayKey + 'F5';
    case e.DOM_VK_F6: return sDisplayKey + 'F6';
    case e.DOM_VK_F7: return sDisplayKey + 'F7';
    case e.DOM_VK_F8: return sDisplayKey + 'F8';
    case e.DOM_VK_F9: return sDisplayKey + 'F9';
    case e.DOM_VK_F10: return sDisplayKey + 'F10';
    case e.DOM_VK_F11: return sDisplayKey + 'F11';
    case e.DOM_VK_F12: return sDisplayKey + 'F12';
    case e.DOM_VK_F13: return sDisplayKey + 'F13';
    case e.DOM_VK_F14: return sDisplayKey + 'F14';
    case e.DOM_VK_F15: return sDisplayKey + 'F15';
    case e.DOM_VK_F16: return sDisplayKey + 'F16';
    case e.DOM_VK_F17: return sDisplayKey + 'F17';
    case e.DOM_VK_F18: return sDisplayKey + 'F18';
    case e.DOM_VK_F19: return sDisplayKey + 'F19';
    case e.DOM_VK_F20: return sDisplayKey + 'F20';
    case e.DOM_VK_F21: return sDisplayKey + 'F21';
    case e.DOM_VK_F22: return sDisplayKey + 'F22';
    case e.DOM_VK_F23: return sDisplayKey + 'F23';
    case e.DOM_VK_F24: return sDisplayKey + 'F24';
    case e.DOM_VK_NUM_LOCK: return sDisplayKey + 'NumLk';
    case e.DOM_VK_SCROLL_LOCK: return sDisplayKey + 'ScrLk';
    }
    if (/^[a-zA-z0-9;=,\`\.\/;\'\[\]\\]$/.test(sKey)) {
	return sDisplayKey + sKey.toUpperCase();
    }
    return '';
}

// bug: this is incredibly naive and wrong in many cases
// see http://www.mozilla.org/access/keyboard/
// and http://www.mozilla.org/unix/customizing.html
var kAccelKey = navigator.platform.toLowerCase().startswith('mac') ? 'Cmd' : 'Ctrl';
var kIllegalHotkey = ['Alt + Left Arrow', kAccelKey + ' + B', kAccelKey + ' + I', 'F7', kAccelKey + ' + W', kAccelKey + ' + F4', kAccelKey + ' + Shift + W', 'Alt + F4', kAccelKey + ' + Enter', 'Shift + Enter', kAccelKey + ' + Shift + Enter', kAccelKey + ' + C', kAccelKey + ' + X', kAccelKey + ' + -', 'Del', 'Shift + Del', kAccelKey + ' + Shift + I', kAccelKey + ' + J', kAccelKey + ' + G', 'F3', "'", '/', kAccelKey + ' + Shift + G', 'Shift + F3', kAccelKey + ' + F', 'Shift + Backspace', 'Down Arrow', 'Up Arrow', 'PgUp', 'PgDn', 'End', 'Home', 'F11', 'F1', kAccelKey + ' + H', 'Alt + Home', kAccelKey + ' + +', 'F6', 'Shift + F6', kAccelKey + ' + M', kAccelKey + ' + T', 'Tab', 'Shift + Tab', kAccelKey + ' + Tab', kAccelKey + ' + PgDn', kAccelKey + ' + N', kAccelKey + ' + O', 'Enter', kAccelKey + ' + U', kAccelKey + ' + V', kAccelKey + ' + Shift + Tab', kAccelKey + ' + PgUp', kAccelKey + ' + P', kAccelKey + ' + Shift + Z', kAccelKey + ' + Y', 'F5', kAccelKey + ' + R', kAccelKey + ' + F5', kAccelKey + ' + Shift + R', kAccelKey + ' + O', kAccelKey + ' + S', kAccelKey + ' + A', kAccelKey + ' + L', 'Alt + D', kAccelKey + ' + Down Arrow', kAccelKey + ' + Up Arrow', kAccelKey + ' + 1', kAccelKey + ' + 2', kAccelKey + ' + 3', kAccelKey + ' + 4', kAccelKey + ' + 5', kAccelKey + ' + 6', kAccelKey + ' + 7', kAccelKey + ' + 8', kAccelKey + ' + 9', 'Esc', kAccelKey + ' + Z', kAccelKey + ' + K'];

function getCurrentSearchText() {
    var elmForm = document.forms.namedItem('gs') ||
        document.forms.namedItem('s') ||
        document.forms.namedItem('f') ||
        document.forms.namedItem('qf') ||
        document.forms.namedItem('sf1') ||
        document.forms.namedItem('frmS') ||
        document.forms.namedItem('lookup') ||
        document.forms.namedItem('sf');
    if (!elmForm) { return ''; }
    var elmSearchBox = elmForm.elements.namedItem('q') || 
        elmForm.elements.namedItem('p') ||
        elmForm.elements.namedItem('query') ||
        elmForm.elements.namedItem('qu') ||
        elmForm.elements.namedItem('s');
    if (!elmSearchBox) { return ''; }
    var sKeywords = elmSearchBox.value;
    if (!sKeywords) { return ''; }
    sKeywords = sKeywords.toLowerCase();
    sKeywords = sKeywords.normalize();
    return sKeywords;
}

function checkTitle(arPosts, sTitle) {
    sTitle = sTitle.toLowerCase().normalize();
    var bDuplicate = false;
    for (var i = 0; i < arPosts.length; i++) {
	var oParams = arPosts[i];
	if (oParams.title.toLowerCase().normalize() == sTitle) {
	    bDuplicate = true;
	    break;
	}
    }
    return bDuplicate;
}

function checkTags(arPosts, sTags) {
    var sTags = (sTags || '').normalizeTags();
    var bDuplicate = false;
    for (var i = 0; i < arPosts.length; i++) {
	var oParams = arPosts[i];
	var sPostTags = (oParams.tags || '').normalizeTags();
	if (sPostTags.contains(sTags) || sTags.contains(sPostTags)) {
	    bDuplicate = true;
	    break;
	}
    }
    return bDuplicate;
}

function getHeaderText() {
    var sHeaderText = '';
    var snapHeaders = document.evaluate("//h1|//h2|//h3|//h4|//h5|//h6",
        window.top.document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0; i < snapHeaders.snapshotLength; i++) {
	sHeaderText += snapHeaders.snapshotItem(i).textContent + ', ';
    }
    return sHeaderText.normalize().toLowerCase();
}

function removeMessage() {
    var elmMessage = window.top.document.getElementById('monkeydo');
    if (elmMessage) {
	elmMessage.parentNode.removeChild(elmMessage);
    }
}

function showConfirmation(elmConfirmation, oParams, _this) {
    var doc = window.top.document;
    doc.body.appendChild(elmConfirmation);
    forceStyle(elmConfirmation);
    doc.getElementById('monkeydook').addEventListener('click', function(e) {
	e.holdEverything();
	removeMessage();
    }, true);
    doc.getElementById('monkeydooptions').addEventListener('click', function(e) {
	e.holdEverything();
	showOptions();
    }, true);
}

function showMessage(elmMessage, _this) {
    var doc = window.top.document;
    doc.body.appendChild(elmMessage);
    forceStyle(elmMessage);
    doc.getElementById('monkeydoform').addEventListener('submit', function(e) {
	var sTags = GM_getValue(_this.option + '.tags', '');
	var sValue = _this.getValue();
	var oParams = {title: doc.title,
                       link: doc.location.href,
                       description: '',
                       tags: sTags};
	oParams = _this.buildPost(oParams, sValue);
	if (DEBUG) { if (!window.confirm('Post this?\n\n' + uneval(oParams))) return; }
	Delicious.post(oParams, _this);
	Cache.save({date: new Date(), key: _this.option + '-' + sValue});
	e.holdEverything();
	removeMessage();
    }, true);
    doc.getElementById('monkeydoauto').addEventListener('click', function(e) {
	GM_setValue(_this.option + '.enabled', e.target.checked ? 2 : 1);
    }, true);
    doc.getElementById('monkeydono').addEventListener('click', function(e) {
	e.holdEverything();
	removeMessage();
    }, true);
    doc.getElementById('monkeydonever').addEventListener('click', function(e) {
	GM_setValue(sOption + '.enabled', 0);
	e.holdEverything();
	removeMessage();
    }, true);
}

function showProgress() {
    var doc = window.top.document;
    var elmMessage = buildProgress();
    if (!elmMessage) { return; }
    window.setTimeout(function(){
	removeMessage();
	doc.body.appendChild(elmMessage);
	forceStyle(elmMessage);
    }, 0);
}

function buildProgress() {
    var elmWrapper = window.top.document.createElement('div');
    elmWrapper.id = 'monkeydo';
    elmWrapper.innerHTML = '<div style="margin: 0; padding: 0; z-index: 99999; position: fixed; top: 10px; right: 10px; width: 200px; border: 1px solid black; background: #ffffce; color: #000; -moz-border-radius: 10px; opacity: 0.9"><p style="font: 12px/16px Verdana, sans-serif; margin: 1em auto 1em auto; text-align: center">Posting to del.icio.us...</p></div>';
    return elmWrapper;
}

function registerOption(oParams) {
    if (gOptions[oParams.option]) { return; }
    gOptions[oParams.option] = oParams.name;
}

function showOptions() {
    var doc = window.top.document;
    var elmOptions = buildOptions();
    if (!elmOptions) { return; }
    window.setTimeout(function(){
	removeMessage();
	doc.body.appendChild(elmOptions);
	forceStyle(elmOptions);
	var elmTable = doc.getElementById('monkeydooptionstable');
	var arTableRows = elmTable.getElementsByTagName('tr');
	for (var i = arTableRows.length - 1; i >= 0; i--) {
	    var elmRow = arTableRows[i];
	    elmRow.addEventListener('mouseover', function() {
		this.style.backgroundColor = '#ddd';
	    }, true);
	    elmRow.addEventListener('mouseout', function() {
		this.style.backgroundColor = '#ffffce';
	    }, true);
	}
	doc.getElementById('monkeydoform').addEventListener('submit', function(e) {
	    e.holdEverything();
	    removeMessage();
	}, true);
	doc.getElementById('monkeydook').addEventListener('click', function(e) {
	    e.holdEverything();
	    removeMessage();
	}, true);
	doc.getElementById('monkeydouser').addEventListener('blur', function(e) {
	    GM_setValue('username', e.target.value);
	}, true);
	doc.getElementById('monkeydopass').addEventListener('blur', function(e) {
	    GM_setValue('password', e.target.value);
	}, true);
	var elmPostHotkey = doc.getElementById('monkeydoposthotkey');
	elmPostHotkey.addEventListener('keypress', function(e) {
	    var sKey = displayKeyFromEvent(e);
	    if (sKey.contains('Tab')) { return true; }
	    e.holdEverything();
	    if (sKey.contains('Backspace') || (sKey.contains('Del'))) { 
		e.target.value = '';
	    } else if (sKey && sKey.contains(' + ') && !kIllegalHotkey.contains(sKey)) {
		e.target.value = sKey;
	    }
	    GM_setValue('hotkey.post', e.target.value);
	    e.target.select();
	    return false;
	}, true);
	elmPostHotkey.addEventListener('focus', function(e) {
	    e.target.select();
	    gSettingHotkey = true;
	}, true);
	elmPostHotkey.addEventListener('blur', function(e) {
	    GM_setValue('hotkey.post', e.target.value);
	    gSettingHotkey = false;
	}, true);
	for (var option in gOptions) {
	    if (typeof gOptions[option] != 'string') { continue; }
	    doc.getElementById(option + '-auto').addEventListener('click', function(e) {
		var option = e.target.id.substringBefore('-') + '.enabled';
		GM_setValue(option, 2);
	    }, true);
	    doc.getElementById(option + '-ignore').addEventListener('click', function(e) {
		var option = e.target.id.substringBefore('-') + '.enabled';
		GM_setValue(option, 0);
	    }, true);
	    doc.getElementById(option + '-ask').addEventListener('click', function(e) {
		var option = e.target.id.substringBefore('-') + '.enabled';
		GM_setValue(option, 1);
	    }, true);
	    doc.getElementById(option + '-tags').addEventListener('blur', function(e) {
		var option = e.target.id.substringBefore('-') + '.tags';
		GM_setValue(option, e.target.value);
	    }, true);
	}
    }, 0);
}

function buildOptions() {
    const kHighlightColor = '#ccffff';
    const kBackgroundColor = '#ffffce';
    var elmWrapper = window.top.document.createElement('div');
    elmWrapper.id = 'monkeydo';
    var s = '<div style="margin: 0; padding: 0; z-index: 99999; position: fixed; top: 10px; right: 10px; width: 700px; border: 1px solid black; background: ' + kBackgroundColor + '; color: #000; -moz-border-radius: 10px; opacity: 0.9; font: 12px/16px Verdana, sans-serif">' +
'<form style="margin: 0; padding: 0;" id="monkeydoform" method="GET" action=".">' +
'<br style="margin: 1em 0 0 0" />' +
'<table id="monkeydooptionstable" style="margin: 5px auto 0 auto; padding: 5px 0 0 0; border-spacing: 0;" summary="">'+
'<caption style="margin: 0 auto 0 auto;"><strong>Monkey Do options</strong></caption>';
    for (var option in gOptions) {
	if (typeof gOptions[option] != 'string') { continue; }
	var iValue = GM_getValue(option + '.enabled', 1);
	s += '<tr><th style="text-align:left">' + gOptions[option] + '</th>';
	s += '<td style="padding: 4px;"><input type="radio" name="' + option + '" id="' + option + '-ask" value="1"';
	if (iValue == 1) { s += ' checked="checked"'; }
	s += ' /><label for="' + option + '-ask">ask</label></td>';
	s += '<td style="padding: 4px;"><input type="radio" name="' + option + '" id="' + option + '-ignore" value="0"';
	if (iValue == 0) { s += ' checked="checked"'; }
	s += ' /><label for="' + option + '-ignore">ignore</label></td>';
	s += '<td style="padding: 4px;"><input type="radio" name="' + option + '" id="' + option + '-auto" value="2"';
	if (iValue == 2) { s += ' checked="checked"'; }
	s += ' /><label for="' + option + '-auto">auto-post</label></td>';
	s += '<td style="padding: 0 0 0 20px"><label for="' + option + '-tags">tags: </label>';
	s += '<input style="width: 184px; font: 12px/16px monospace; border: 1px solid #aaa; background: transparent; color: black;" type="text" id="' + option + '-tags" value="' + GM_getValue(option + '.tags', '') + '" /></td>';
	s += '</tr>';
    }
    s += '</table><table style="margin: 0 auto 0 auto;" summary="">' +
'<tr><td>&#160;</td></tr>' +
'<tr>' +
'<td><label for="monkeydouser"><strong>(optional)</strong> del.icio.us login: </label></td>' +
'<td><input style="background: transparent; color: black; border: 1px solid #aaa;" type="text" id="monkeydouser" value="' + GM_getValue('username', '') + '" />' + 
'<label style="padding: 0 0 0 10px;" for="monkeydopass">password: </label>' +
'<input style="background: transparent; color: black; border: 1px solid #aaa;" type="password" id="monkeydopass" value="' + GM_getValue('password', '') + '" /></td>' +
'</tr>' +
'<tr>' +
'<td style="padding: 10px 0 0 0;"><label for="monkeydoposthotkey">Hotkey to post current page: </label></td>' +
'<td style="padding: 10px 0 0 0;"><input style="background: transparent; color: black; border: 1px solid #aaa;" type="text" id="monkeydoposthotkey" value="' + GM_getValue('hotkey.post', '') + '" /></td>' +
'</tr>' +
'</table>' +
'<a style="display: block; margin: 15px 5px; padding: 0; text-align: center; border: 0; color: blue; font: 11px/16px Verdana, sans-serif;" href="#" id="monkeydook">OK</a>' +
'</form>' +
'</div>';
    elmWrapper.innerHTML = s;
    return elmWrapper;
}

function getFilenameFromURL(urlTarget) {
    var sFilename = urlTarget.substringBefore('?');
    sFilename = sFilename.substringBefore('#');
    sFilename = sFilename.substringAfterLast('/');
    return sFilename;
}

function isBinaryFile(sFilename) {
    var sFileExt = sFilename.substringAfterLast('.').toLowerCase();
    return (['zip','rar','exe','gz','tar','tgz','dmg',
	     'img','sit','sitx','hqx','deb','rpm','bz2',
	     'jar','iso','bin','msi'].contains(sFileExt));
}

function forceStyle(elmTarget) {
    // Apply styles to an element on a foreign page whose styles are unknown in advance.
    // Note: always use fully-defined shorthand properties (background, margin, padding,
    // border, font) if they exist.  This includes line-height within font, etc.
    var oStyle = elmTarget.style;
    var sNodeName = elmTarget.nodeName.toLowerCase();
    var sType = '';
    if (sNodeName == 'input') {
	var sType = (elmTarget.getAttribute('type') || '').normalize().toLowerCase() || 'text';
    }
    var sBackground = 'transparent';
    if (elmTarget.disabled && (!['option','optgroup'].contains(sNodeName))) {
	sBackground = 'ThreeDFace';
    }
    else {
	switch (sNodeName+sType) {
	case 'inputradio':
	case 'inputcheckbox':
	case 'inputpassword':
	case 'inputtext': 
	case 'select':
	case 'textarea': sBackground = '-moz-Field'; break;
	case 'button': 
	case 'inputreset':
	case 'inputbutton':
	case 'inputsubmit': sBackground = 'ButtonFace'; break;
	case 'option': if (elmTarget.checked) { sBackground = 'Highlight' }; break;
	}
    }
    elmTarget.style.background = (oStyle.background || sBackground) + ' ! important';
    var sBorder = '0';
    switch (sNodeName+sType) {
    case 'fieldset': sBorder = '2px groove ThreeDFace'; break;
    case 'iframe': sBorder = '2px inset'; break;
    case 'textarea':
    case 'select': 
    case 'inputtext':
    case 'inputpassword':
    case 'inputradio':
    case 'inputcheckbox': sBorder = '2px inset ThreeDFace'; break;
    case 'inputreset': 
    case 'inputbutton': 
    case 'inputsubmit': sBorder = '2px outset ButtonFace'; break;
    case 'abbr':
    case 'acronym': if (elmTarget.title) { sBorder = '0 0 1px 0 dotted black' }; break;
    }
    elmTarget.style.border = (oStyle.border || sBorder) + ' ! important';
    var sBorderSpacing = (sNodeName == 'table') ? '2px' : '0';
    elmTarget.style.borderSpacing = (oStyle.borderSpacing || sBorderSpacing) + ' ! important';
    var sBorderCollapse = (sNodeName == 'table' && elmTarget.rules) ? 'collapse' : 'separate';
    elmTarget.style.borderCollapse = (oStyle.borderCollapse || sBorderCollapse) + ' ! important';
    elmTarget.style.bottom = (oStyle.bottom || 'auto') + ' ! important';
    elmTarget.style.captionSide = (oStyle.captionSide || 'top') + ' ! important';
    elmTarget.style.clear = (oStyle.clear || 'none') + ' ! important';
    elmTarget.style.clip = (oStyle.clip || 'auto') + ' ! important';
    var sColor = 'inherit';
    if (elmTarget.disabled) {
	sBackground = 'GrayText';
    }
    else {
	switch (sNodeName+sType) {
	case 'button': 
	case 'inputreset':
	case 'inputbutton':
	case 'inputsubmit': sBackground = 'ButtonText'; break;
	case 'inputradio':
	case 'inputcheckbox':
	case 'inputpassword':
	case 'inputtext': 
	case 'select':
	case 'textarea': sBackground = '-moz-FieldText'; break;
	case 'option': if (elmTarget.checked) { sBackground = 'HighlightText' }; break;
	}
    }
    elmTarget.style.color = (oStyle.color || sColor) + ' ! important';
    var sCursor = 'auto';
    switch (sNodeName+sType) {
    case 'textarea': 
    case 'inputtext':
    case 'inputpassword': sCursor = 'text'; break;
    case 'label':
    case 'scrollbar':
    case 'select': 
    case 'inputfile': 
    case 'inputradio': 
    case 'inputcheckbox': 
    case 'inputreset':
    case 'inputsubmit':
    case 'button': sCursor = 'default'; break;
    case 'inputimage': 
    case 'a': sCursor = 'pointer'; break;
    case 'img': 
    case 'object': if (elmTarget.getAttribute('usemap')) { sCursor = 'pointer' }; break;
    }
    elmTarget.style.cursor = (oStyle.cursor || sCursor) + ' ! important';
    elmTarget.style.direction = (oStyle.direction || 'ltr') + ' ! important';
    sDisplay = 'inline';
    if (['form','fieldset','option','optgroup','div','map','dt','isindex','p','dl','multicol','dd','blockquote','address','center','h1','h2','h3','h4','h5','h6','listing','plaintext','xmp','pre','ul','menu','dir','ol','hr','frameset','marquee','br'].contains(sNodeName)) {
	sDisplay = 'block';
    } else if (['noframes','area','base','basefont','head','meta','script','style','title','noembed','noscript','param'].contains(sNodeName)) {
	sDisplay = 'none';
    } else switch (sNodeName) {
    case 'table': sDisplay = 'table'; break;
    case 'caption': sDisplay = 'table-caption'; break;
    case 'tr': sDisplay = 'table-row'; break;
    case 'col': sDisplay = 'table-column'; break;
    case 'colgroup': sDisplay = 'table-column-group'; break;
    case 'tbody': sDisplay = 'table-row-group'; break;
    case 'thead': sDisplay = 'table-header-group'; break;
    case 'tfoot': sDisplay = 'table-footer-group'; break;
    case 'td':
    case 'th': sDisplay = 'table-cell'; break;
    case 'li': sDisplay = 'list-item'; break;
    }
    elmTarget.style.display = (oStyle.display || sDisplay) + ' ! important';
    elmTarget.style.emptyCells = (oStyle.emptyCells || 'show') + ' ! important';
    elmTarget.style.cssFloat = (oStyle.cssFloat || 'none') + ' ! important';
    var sFont = 'inherit';
    switch (sNodeName+sType) {
    case 'h1': sFont = '2em bold'; break;
    case 'h2': sFont = '1.5em bold'; break;
    case 'h3': sFont = '1.17em bold'; break;
    case 'h4': sFont = 'bold'; break;
    case 'h5': sFont = '0.83em bold'; break;
    case 'h6': sFont = '0.67em bold'; break;
    case 'listing': sFont = 'medium -moz-fixed'; break;
    case 'plaintext':
    case 'xmp':
    case 'pre': 
    case 'tt':
    case 'code':
    case 'kbd':
    case 'samp': sFont = 'inherit -moz-fixed'; break;
    case 'big': sFont = 'larger inherit'; break;
    case 'sub':
    case 'sup':
    case 'small': sFont = 'smaller'; break;
    case 'inputtext': 
    case 'inputpassword': sFont = '-moz-field'; break;
    case 'inputimage': sFont = 'small sans-serif'; break;
    case 'button':
    case 'inputreset':
    case 'inputbutton':
    case 'inputsubmit': sFont = '-moz-button'; break;
    case 'textarea': sFont = 'medium -moz-field'; break;
    case 'select': 
    case 'optgroup': sFont = 'italic bold -moz-list'; break;
    case 'b':
    case 'strong': sFont = 'bolder'; break;
    case 'th': sFont = 'bold'; break;
    case 'address':
    case 'i':
    case 'cite':
    case 'em':
    case 'var':
    case 'dfn': sFont = 'italic'; break;
    }
    //GM_log(sFont);
    elmTarget.style.font = (oStyle.font || sFont) + ' ! important';
    var sHeight = 'auto';
    switch (sNodeName+sType) {
    case 'hr': sHeight = '2px'; break;
    case 'inputradio': 
    case 'inputcheckbox': sHeight = '13px'; break;
    }
    elmTarget.style.height = (oStyle.height || sHeight) + ' ! important';
    elmTarget.style.left = (oStyle.left || 'auto') + ' ! important';
    elmTarget.style.letterSpacing = (oStyle.letterSpacing || 'normal') + ' ! important';
    var sListStyle = 'none';
    if (['ul','menu','dir'].contains(sNodeName)) {
	sListStyle = 'disc';
    } else if (['ol'].contains(sNodeName)) {
	sListStyle = 'decimal';
    } // bug: improperly styles nested lists
    elmTarget.style.listStyle = (oStyle.listStyle || sListStyle) + ' ! important';
    var sMargin = '0';
    switch (sNodeName+sType) {
    case 'form': sMargin = '0 0 1em 0'; break;
    case 'fieldset': sMargin = '0 2px 0 2px'; break;
    case 'textarea': sMargin = '1px 0 1px 0'; break;
    case 'inputradio': sMargin = '3px 3px 0 5px'; break;
    case 'inputcheckbox': sMargin = '3px 3px 3px 4px'; break;
    case 'p': 
    case 'dl': 
    case 'h3': 
    case 'listing': 
    case 'plaintext': 
    case 'xmp': 
    case 'pre': 
    case 'ul': 
    case 'menu': 
    case 'dir': 
    case 'ol': 
    case 'multicol': sMargin = '1em 0'; break;
    case 'blockquote': sMargin = '1em 40px'; break;
    case 'h1': sMargin = '.67em 0'; break;
    case 'h2': sMargin = '.83em 0'; break;
    case 'h4': sMargin = '1.33em 0'; break;
    case 'h5': sMargin = '1.67em 0'; break;
    case 'h6': sMargin = '2.33em 0'; break;
    case 'hr': sMargin = '0.5em auto 0.5em auto'; break;
    }
    elmTarget.style.margin = (oStyle.margin || sMargin) + ' ! important';
    elmTarget.style.maxHeight = (oStyle.maxHeight || '9999px') + ' ! important';
    elmTarget.style.maxWidth = (oStyle.maxWidth || '9999px') + ' ! important';
    var sMinHeight = (sNodeName == 'option') ? '1em' : '0';
    elmTarget.style.minHeight = (oStyle.minHeight || sMinHeight) + ' ! important';
    elmTarget.style.minWidth = (oStyle.minWidth || '0') + ' ! important';
    elmTarget.style.opacity = (oStyle.opacity || '1') + ' ! important';
    elmTarget.style.outline = (oStyle.outline || '0') + ' ! important';
    var sOverflow = (sNodeName == 'frameset' ? '-moz-hidden-unscrollable' : 'visible');
    elmTarget.style.overflow = (oStyle.overflow || sOverflow) + ' ! important';
    var sPadding = '0';
    switch (sNodeName+sType) {
    case 'td': 
    case 'th': sPadding = '1px'; break;
    case 'legend': sPadding = '0 2px 0 2px'; break;
    case 'fieldset': sPadding = '0.35em 0.625em 0.75em'; break;
    case 'inputreset': 
    case 'inputbutton': 
    case 'inputsubmit': sPadding = '0 6px 0 6px'; break;
    case 'inputtext':
    case 'inputpassword':
    case 'inputfile': sPadding = '1px 0 1px 0'; break;
    case 'option': sPadding = '0 5px 0 3px'; break;
    }
    elmTarget.style.padding = (oStyle.padding || sPadding) + ' ! important';
    elmTarget.style.position = (oStyle.position || 'static') + ' ! important';
    elmTarget.style.right = (oStyle.right || 'auto') + ' ! important';
    elmTarget.style.tableLayout = (oStyle.tableLayout || 'auto') + ' ! important';
    var sTextAlign = 'start';
    switch (sNodeName+sType) {
    case 'center': sTextAlign = '-moz-center'; break;
    case 'caption': sTextAlign = 'center'; break;
    case 'td': sTextAlign = 'inherit'; break;
    }
    elmTarget.style.textAlign = (oStyle.textAlign || sTextAlign) + ' ! important';
    var sTextDecoration = 'none';
    switch (sNodeName) {
    case 'u':
    case 'ins':
    case 'a': sTextDecoration = 'underline'; break;
    case 's':
    case 'strike':
    case 'del': sTextDecoration = 'line-through'; break;
    case 'blink': sTextDecoration = 'blink'; break;
    }
    elmTarget.style.textDecoration = (oStyle.textDecoration || sTextDecoration) + ' ! important';
    elmTarget.style.textIndent = (oStyle.textIndent || '0px') + ' ! important';
    elmTarget.style.textShadow = (oStyle.textShadow || 'none') + ' ! important';
    elmTarget.style.textTransform = (oStyle.textTransform || 'none') + ' ! important';
    elmTarget.style.top = (oStyle.top || 'auto') + ' ! important';
    elmTarget.style.unicodeBidi = (oStyle.unicodeBidi || 'normal') + ' ! important';
    var sVerticalAlign = 'baseline';
    switch (sNodeName+sType) {
    case 'textarea': 
    case 'inputcheckbox': sVerticalAlign = 'text-bottom'; break;
    case 'tr': 
    case 'td': 
    case 'th': sVerticalAlign = 'inherit'; break;
    case 'tbody': 
    case 'thead': 
    case 'tfoot': sVerticalAlign = 'middle'; break;
    case 'sub': sVerticalAlign = 'sub'; break;
    case 'sup': sVerticalAlign = 'super'; break;
    }
    elmTarget.style.verticalAlign = (oStyle.verticalAlign || sVerticalAlign) + ' ! important';
    elmTarget.style.visibility = (oStyle.visibility || 'visible') + ' ! important';
    var sWhiteSpace = 'normal';
    switch (sNodeName+sType) {
    case 'select': 
    case 'nobr': 
    case 'inputfile': sWhiteSpace = 'nowrap'; break;
    case 'listing':
    case 'plaintext':
    case 'xmp':
    case 'pre':
    case 'inputreset':
    case 'inputsubmit':
    case 'inputbutton': sWhiteSpace = 'pre'; break;
    }
    elmTarget.style.whiteSpace = (oStyle.whiteSpace || sWhiteSpace) + ' ! important';
    var sWidth = 'auto';
    switch (sNodeName+sType) {
    case 'inputradio': 
    case 'inputcheckbox': sWidth = '13px'; break;
    }
    elmTarget.style.width = (oStyle.width || sWidth) + ' ! important';
    elmTarget.style.wordSpacing = (oStyle.wordSpacing || 'normal') + ' ! important';
    elmTarget.style.zIndex = (oStyle.zIndex || 'auto') + ' ! important';
    var sMozAppearance = 'none';
    switch (sNodeName+sType) {
    case 'inputtext':
    case 'inputpassword': 
    case 'textarea': sMozAppearance = 'textfield'; break;
    case 'inputradio': sMozAppearance = 'radio'; break;
    case 'inputcheckbox': sMozAppearance = 'checkbox'; break;
    case 'button':
    case 'inputreset':
    case 'inputbutton':
    case 'inputsubmit': sMozAppearance = 'button'; break;
    case 'select': sMozAppearance = (elmTarget.multiple || (elmTarget.size && parseInt(elmTarget.size) > 1)) ? 'listbox' : 'menulist'; break;
    }
    elmTarget.style.MozAppearance = (oStyle.MozAppearance || sMozAppearance) + ' ! important';
    elmTarget.style.MozBorderRadius = (oStyle.MozBorderRadius || '0') + ' ! important';
    if (elmTarget.childNodes) {
	for (var i = 0; i < elmTarget.childNodes.length; i++) {
	    var elmChild = elmTarget.childNodes[i];
	    if (elmChild.nodeType == 1) {
		forceStyle(elmChild, elmChild.style);
	    }
	}
    }
}

var Authentication = {
    basic: function(username, password) {
	if (username && password) {
	    return {'Authorization': 'Basic ' + window.btoa(username + ':' + password)};
	} else {
	    return {};
	};
    }
}

var Delicious = {
    get: function(usTag, fCallback) {
	var urlGet = 'http://del.icio.us/api/posts/recent?count=100';
	if (usTag) {
	    urlGet += '&tag=' + escape(usTag);
	}
	if (DEBUG_SIMULATION_ONLY) {
	    fCallback([]);
	    return;
	}
	GM_xmlhttpRequest({
            method: 'GET',
            url: urlGet,
            headers: Authentication.basic(GM_getValue('username', ''), GM_getValue('password', '')),
            onload: function(oResponseDetails) {
		var arPosts = [];
		var oParser = new DOMParser();
		var elmPosts = oParser.parseFromString(
                    oResponseDetails.responseText, 'application/xml');
		var arAll = elmPosts.getElementsByTagName('post');
		var arPosts = [];
		for (var i = 0; i < arAll.length; i++) {
		    var elmPost = arAll[i];
		    arPosts.push({title: elmPost.getAttribute('description') || '',
                                  link: elmPost.getAttribute('href') || '',
                                  description: elmPost.getAttribute('extended') || '',
                                  tags: elmPost.getAttribute('tags') || ''
                                 }
                    );
		}
		fCallback(arPosts);
	    }
	});
	return true;
    },

    post: function(oParams, _this) {
	var urlPost = 'http://del.icio.us/api/posts/add?';
	urlPost += 'url=' + escape(oParams.link);
	if (oParams.title) {
	    urlPost += '&description=' + escape(oParams.title);
	}
	if (oParams.description) {
	    urlPost += '&extended=' + escape(oParams.description);
	}
	if (oParams.tags) {
	    urlPost += '&tags=' + escape(oParams.tags);
	}
	if (DEBUG) { GM_log('posting to ' + urlPost); }
	showProgress();
	if (DEBUG_SIMULATION_ONLY) {
	    var elmConfirmation = _this.buildConfirmation(oParams, _this);
	    if (elmConfirmation) {
		window.setTimeout(function(){showConfirmation(elmConfirmation, oParams, _this)}, 0);
	    }
	    return;
	}
	GM_xmlhttpRequest({
            method: 'GET',
            url: urlPost,
            headers: Authentication.basic(GM_getValue('username', ''), GM_getValue('password', '')),
            onload: function(r) {
                if (DEBUG) { GM_log(r.status + '\n' + r.responseText); }
		removeMessage();
		var elmConfirmation = _this.buildConfirmation(oParams, _this);
		if (elmConfirmation) {
		    window.setTimeout(function(){showConfirmation(elmConfirmation, oParams, _this)}, 0);
		}
		Cache.save({date: new Date(), key: _this.option + '-' + _this.getValue()});
	    }
	});
    }
};

var Cache = {
    count: function() {
	return GM_getValue('cache.count', 0);
    },

    load: function(iIndex) {
	var oCache = {};
	if (iIndex < 0) { return oCache; }
	var sRawValue = GM_getValue('cache.' + iIndex);
	if (!sRawValue) { return oCache; }
	var arVal = sRawValue.split('|');
	if (arVal.length < 3) { return oCache; }
	var dateCache = new Date();
	dateCache.setTime(Date.parse(arVal[0]));
	oCache.date = dateCache;
	oCache.key = arVal[1];
	if (arVal.length > 3) {
	    var sValue = arVal.slice(2).join('|');
	} else {
	    var sValue = arVal[2];
	}
	oCache.value = sValue;
	oCache.index = iIndex;
	return oCache;
    },

    save: function(oCache) {
	if (!oCache || !oCache.key) { return -1; }
	if (oCache.key.indexOf('|')) {
	    oCache.key = oCache.key.split('|')[0];
	}
	if (!oCache.value) {
	    oCache.value = '';
	}
	if (typeof oCache.value != 'string') {
	    oCache.value = oCache.value.toString();
	}
	if (!oCache.date) {
	    oCache.date = new Date();
	}
	if (!oCache.index) {
	    oFoundCache = Cache.find(oCache.key);
	    if (oFoundCache && oFoundCache.index) {
		oCache.index = oFoundCache.index;
	    } else {
		oCache.index = Cache.count();
	    }
	}
	GM_setValue('cache.' + oCache.index,
		    oCache.date.toUTCString() + '|' +
		    oCache.key + '|' +
		    oCache.value);
	if (oCache.index == Cache.count()) {
	    GM_setValue('cache.count', Cache.count() + 1);
	    oCache.index = Cache.count();
	}
	return oCache.index;
    },
    
    find: function(sKey) {
	if (sKey.contains('|')) {
	    sKey = sKey.split('|')[0];
	}
	var iCount = Cache.count();
	for (var i = 0; i < iCount; i++) {
	    var oCache = Cache.load(i);
	    if (oCache.key == sKey) {
		oCache.index = i;
		return oCache;
	    }
	}
	return {};
    },

    flush: function() {
	var dateNow = new Date();
	var iNow = dateNow.getTime();
	var iCount = Cache.count();
	var iNewCount = iCount;
	for (var i = 0; i < iCount; i++) {
	    var oCache = Cache.load(i);
	    if (oCache.date) {
		var iCacheAge = iNow - oCache.date.getTime();
	    }
	    else {
		var iCacheAge = -1;
	    }
	    if ((iCacheAge > 604800000 /* 1 week */) || (iCacheAge < 0)) {
		for (var j = 0; j < iCount - 1; j++) {
		    GM_setValue('cache.' + j,
                        GM_getValue('cache.' + (j+1), ''));
		}
		iCount = iCount - 1;
		GM_setValue('cache.' + iCount, '');
	    }
	}
    }
};

function AutoList() {
    this.whatToPost = 'this page';
}

AutoList.prototype.run = function() {
    // save self-reference for use in callback functions
    var _this = this;

    // if this option is totally off, bail
    var iEnabled = GM_getValue(_this.option + '.enabled', 1);
    if (iEnabled <= 0) { return; }

    // if something else has already posted a prompt on this page, bail
    var elmMessage = window.top.document.getElementById('monkeydo');
    if (elmMessage) { return; }

    // if this page doesn't have what we're looking for, bail
    var sValue = _this.getValue();
    if (!sValue) { return; }

    // if target has been posted recently, bail
    var oCache = Cache.find(_this.option + '-' + sValue);
    if (oCache.date) {
	if (DEBUG) { GM_log(_this.option + ': ' + sValue + ' found in local cache, skipping'); }
	return false;
    }

    var sTags = GM_getValue(_this.option + '.tags', '');

    // if this option is on but auto-posting is off, prompt the user and await further instructions
    if (GM_getValue(_this.option + '.enabled', 1) == 1) {
	var elmMessage = _this.buildMessage(sTags, _this);
	if (elmMessage) {
	    window.setTimeout(function(){showMessage(elmMessage, _this)}, 0);
	}
	Cache.save({date: new Date(), key: _this.option + '-' + sValue});
	return;
    }

    // we're auto-posting, so double-check that this isn't a duplicate
    Delicious.get(sTags, function(arPosts) {
	if (_this.checkDuplicates(arPosts, sValue)) {
	    Cache.save({date: new Date(), key: _this.option + '-' + sValue});
	    if (DEBUG) { GM_log(_this.option + ': ' + sValue + ' has been posted already, skipping'); }
	    return;
	}
	var oParams = {title: window.top.document.title,
                       link: window.top.location.href,
                       description: '',
                       tags: sTags};
	oParams = _this.buildPost(oParams, sValue);
	if (DEBUG) { if (!window.confirm('Post this?\n\n' + uneval(oParams))) return; }
	Delicious.post(oParams, _this);
	if (DEBUG) { GM_log(_this.option + ': posted ' + sValue + '.'); }
    });
}

AutoList.prototype.getValue = function() {
    return window.top.location.href;
}

AutoList.prototype.checkDuplicates = function(arPosts, url) {
    var bDuplicate = false;
    for (var i = 0; i < arPosts.length; i++) {
	var oParams = arPosts[i];
	if (oParams.link.startswith(url)) {
	    bDuplicate = true;
	    break;
	}
    }
    return bDuplicate;
}

AutoList.prototype.buildPost = function(oParams) {
    return oParams;
}

AutoList.prototype.buildMessage = function(sTags, _this) {
    var elmWrapper = window.top.document.createElement('div');
    elmWrapper.id = 'monkeydo';
    elmWrapper.innerHTML = '<div style="margin: 0; padding: 0; z-index: 99999; position: fixed; top: 10px; right: 10px; width: 200px; border: 1px solid black; background: #ffffce; color: #000; -moz-border-radius: 10px; opacity: 0.9">' +
'<form style="margin: 0; padding: 0;" id="monkeydoform" method="GET" action=".">' +
'<p style="margin: 5px; padding: 0; font: 12px/16px Verdana, sans-serif;">It looks like ' + _this.action + '.' +
'<br style="margin: 1em 0 0 0"/>' +
'Would you like to post ' + _this.whatToPost + ' to del.icio.us?' +
'<br style="margin: 1em 0 0 0"/>' +
'<label for="monkeydotags">Tags:</label>' +
'<br />' +
'<input style="width: 184px; margin: 5px 0 5px 0; font: 12px/16px monospace" type="text" id="monkeydotags" name="monkeydotags" value="' + sTags + '" />' +
'<br />' +
'<input style="float: right;" type="submit" id="monkeydosubmit" name="monkeydosubmit" value="Post" />' +
'<br style="clear: both; margin: 10px 0 0 0;" />' +
'<input style="float: left; vertical-align: top; cursor: pointer; margin: 0 6px 2em 0px;" type="checkbox" name="monkeydoauto" id="monkeydoauto" />' +
'<label for="monkeydoauto" style="margin: 0; padding: 0; font: 11px/16px Verdana, sans-serif; cursor: pointer;">Always post ' + _this.plural + ' without asking</label>' +
'<hr style="clear: both; background: #ccc; color: #ccc; height: 1px; margin: 10px 5px; padding: 0" />' + 
'<a style="display: block; margin: 15px 5px; padding: 0; text-align: center; border: 0; color: blue; font: 11px/16px Verdana, sans-serif;" href="#" id="monkeydono">No, don&#8217;t post this</a>' +
'<a style="display: block; margin: 15px 5px; padding: 0; text-align: center; border: 0; color: blue; font: 11px/16px Verdana, sans-serif;" href="#" id="monkeydonever">Never offer to post ' + _this.plural + '</a>' +
'</p>' +
'</form>' +
'</div>';
    return elmWrapper;
}

AutoList.prototype.buildConfirmation = function(oParams, _this) {
    var elmWrapper = window.top.document.createElement('div');
    elmWrapper.id = 'monkeydo';
    elmWrapper.innerHTML = '<div style="margin: 0; padding: 0; z-index: 99999; position: fixed; top: 10px; right: 10px; width: 200px; border: 1px solid black; background: #ffffce; color: #000; -moz-border-radius: 10px; opacity: 0.9">' +
'<form style="margin: 0; padding: 0;" id="monkeydoform" method="GET" action=".">' +
'<p style="margin: 5px; padding: 0; font: 12px/16px Verdana, sans-serif;">This page has been posted to your del.icio.us bookmarks.' +
'<br style="margin: 1em 0 0 0"/>' +
(oParams.tags ? 'Tags: <strong>' + oParams.tags + '</strong>' : '&#160;') +
'<br style="margin: 1em 0 0 0"/>' +
'<a style="display: block; margin: 0 5px 15px 0; padding: 0; text-align: center; text-decoration: underline; border: 0; color: blue; font-weight: bold; font: 11px/16px Verdana, sans-serif;" href="#" id="monkeydook">OK</a>' +
'<hr style="visibility: visible; display: block; clear: both; background: #ccc; color: #ccc; height: 1px; margin: 10px 5px; padding: 0" />' + 
'<a style="display: block; margin: 15px 5px; padding: 0; text-align: center; text-decoration: underline; border: 0; color: blue; font-weight: normal; font: 11px/16px Verdana, sans-serif;" href="#" id="monkeydooptions">more options</a>' +
'</p>' +
'</form>' +
'</div>';
    return elmWrapper;
}

function AutoTopTen() {
    this.option = 'autotopten';
    this.action = 'you&#8217;re reading a Top 10 list';
    this.plural = 'top 10 lists';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoTopTen.prototype = new AutoList;
AutoTopTen.prototype.getValue = function() {
    if (window.location.href.contains('?')) { return ''; }
    var sTitle = (window.top.document.title || '').normalize().toLowerCase();
    if (sTitle.containsAny(['top 10', 'top ten', 'top 11'])) {
	return window.location.href;
    }
    return '';	
}

function AutoUseful() {
    this.option = 'autouseful';
    this.action = 'you find this page useful';
    this.plural = 'useful pages';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoUseful.prototype = new AutoList;
AutoUseful.prototype.getValue = function() {
    if (window.top.location.host.endswith('del.icio.us')) { return ''; }
    return window.top.location.href;
}

function AutoResearch() {
    this.option = 'autoresearch';
    this.action = 'you&#8217;re doing research';
    this.plural = 'research';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoResearch.prototype = new AutoList;
AutoResearch.prototype.getValue = function() {
    var sTags = getCurrentSearchText().normalizeTags();
    if (sTags.containsAny([':','-'])) { return ''; }
    return sTags;
}
AutoResearch.prototype.checkDuplicates = checkTags;
AutoResearch.prototype.buildMessage = function(sTags, _this) {
    sTags = getCurrentSearchText().normalizeTags();
    return AutoList.prototype.buildMessage(sTags, _this);
}
AutoResearch.prototype.buildPost = function(oParams) {
    oParams.tags = getCurrentSearchText();
}

var gClickCount = 0;
var bAskUseful = false;
function trackUseful(e) {
    if (bAskUseful) { return true; }
    const kArbitraryCutoff = 4;
    var elmTarget = e.target;
    while (!['A', 'BODY', 'HTML'].contains(elmTarget.nodeName.toUpperCase())) {
	elmTarget = elmTarget.parentNode;
    }
    if (elmTarget.nodeName.toUpperCase() != 'A') { return true; }
    var urlTarget = elmTarget.href;
    if (!urlTarget) { return false; }
    gClickCount += 1;
    if (gClickCount >= kArbitraryCutoff) {
	bAskUseful = true;
	gClickCount = -999;
	if (getCurrentSearchText()) {
	    new AutoResearch().run();
	} else {
	    new AutoUseful().run();
	}
    }
    return true;
}


function AutoSpec() {
    this.option = 'autospec';
    this.action = 'you&#8217;re reading a specification';
    this.plural = 'specifications';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoSpec.prototype = new AutoList;
AutoSpec.prototype.getValue = function() {
    const kArbitraryCutoff = 7;
    var iScore = 0;
    var url = window.top.location.href.toLowerCase();
    var sTitle = (window.top.document.title || '').toLowerCase();
    var sBody = window.top.document.body.textContent.normalize().toLowerCase();
    iScore -= 7 * (/del\.icio\.us/.count(url));
    iScore += /w3\.org/.count(url);
    iScore += /rfc/.count(url);
    iScore += /specification/.count(sTitle);
    iScore += /network working group/.count(sBody);
    iScore += /request for comments:/.count(sBody);
    iScore += /obsoletes: [1-9]/.count(sBody);
    iScore += /updates: [1-9]/.count(sBody);
    iScore += /category: standards track/.count(sBody);
    iScore += /status of this (memo|document)/.count(sBody);
    iScore += /notational conventions/.count(sBody);
    iScore += /the key words "must", "must not",/.count(sBody);
    iScore += /rfc ?2119/.count(sBody);
    iScore += /iana considerations/.count(sBody);
    iScore += /copyright (c) the internet society/.count(sBody);
    iScore += /funding for the rfc editor/.count(sBody);
    iScore += /the latest status of this document is maintained at the w3c/.count(sBody);
    iScore += /a list of current w3c recommendations and other technical documents can be found at/.count(sBody);
    iScore += /the english version of this specification is the only normative version/.count(sBody);
    iScore += /this (appendix|section) is (normative|informative)/.count(sBody);
    iScore += /please refer to the errata for this document/.count(sBody);
    iScore += /the list of known errors in this specification/.count(sBody);
    iScore += /the scope of this specification/.count(sBody);
    iScore += /the latest version of this working draft/.count(sBody);
    iScore += /conformance requirements/.count(sBody);
    iScore += /this specification is a work in progress/.count(sBody);
    iScore += /the list of current internet-drafts/.count(sBody);
    iScore += /internet-drafts are working documents of the internet engineering task force/.count(sBody);
    if (DEBUG) { GM_log(sTitle + ' autospec score=' + iScore); }
    if (iScore >= kArbitraryCutoff) {
	return window.top.document.title;
    }
    return '';
}
AutoSpec.prototype.checkDuplicates = checkTitle;

function AutoHowto() {
    this.option = 'autohowto';
    this.action = 'you&#8217;re reading a tutorial';
    this.plural = 'tutorials';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoHowto.prototype = new AutoList;
AutoHowto.prototype.getValue = function() {
    if (getCurrentSearchText()) { return ''; }
    const kArbitraryCutoff = 24;
    var iScore = 0;
    var url = window.top.location.href.toLowerCase();
    var sTitle = window.top.document.title.toLowerCase();
    var sBody = window.top.document.body.textContent.normalize().toLowerCase();
    var sHeaderText = getHeaderText();
    iScore -= 24 * (/del\.icio\.us/.count(url));
    iScore += 9 * (/howto/.count(url));
    iScore += 9 * (/tldp\.org/.count(url));
    iScore += 4 * (/(how[- ]to|tutorial)/.count(url));
    iScore += 3 * (/\/(tut|toc)\//.count(url));
    iScore += 3 * (/\/\/(www\.)?diveinto/.count(url));
    iScore -= 3 * (/diveintomark/.count(url));
    iScore += 2 * (/primer/.count(url));
    iScore += /introduction/.count(url);
    iScore += /guide/.count(url);

    iScore += 6 * (/(tutorial|howto|dive into)/.count(sTitle));
    iScore -= 6 * (/dive into mark/.count(sTitle));
    iScore += 4 * (/how[- ]to/.count(sTitle));
    iScore += 2 * (/(primer|introduction|table of contents)/.count(sTitle));
    iScore += /secrets of\b/.count(sTitle);
    iScore += /installation/.count(sTitle);
    iScore += /guide/.count(sTitle);
    iScore += /tips (and|&) tricks/.count(sTitle);

    iScore += 6 * (/\b(tutorial|howto|guide|dive into)\b/.count(sHeaderText));
    iScore -= 6 * (/dive into mark/.count(sHeaderText));
    iScore += 4 * (/how[- ]to/.count(sHeaderText));
    iScore += 3 * (/what is\b/.count(sHeaderText));
    iScore += 2 * (/installation/.count(sHeaderText));

    iScore += 9 * (/this book lives/.count(sBody));
    iScore += 6 * (/this how-?to/.count(sBody));
    iScore += 2 * (/this (tutorial|guide)/.count(sBody));
    iScore += /table of contents/.count(sBody);
    iScore += /secrets of\b/.count(sBody);
    iScore += /how[- ]?to/.count(sBody);
    iScore += /step [1-9]/.count(sBody);
    iScore += /(several|couple|these) steps\b/.count(sBody);
    iScore += /need the following/.count(sBody);
    iScore += /introduction to\b/.count(sBody);
    iScore += /instructions/.count(sBody);
    iScore += /first step/.count(sBody);
    iScore += /before (we|you) (start|get started|continue)/.count(sBody);
    if (!/\bpaint\b/.count(sBody)) {
	iScore += /primer/.count(sBody);
    }
    iScore += /in ([0-9]+|five|ten|thirty) minute/.count(sBody);
    iScore += window.top.document.getElementsByTagName('ol').length;
    if (DEBUG) { GM_log(url + ' autohowto score=' + iScore); }
    if (iScore >= kArbitraryCutoff) {
	return window.top.document.title;
    }
    return '';
}
AutoHowto.prototype.checkDuplicates = checkTitle;

function AutoGraham() {
    this.option = 'autograham';
    this.action = 'you&#8217;re reading an essay by Paul Graham';
    this.plural = 'essays by Paul Graham';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoGraham.prototype = new AutoList;
AutoGraham.prototype.getValue = function() {
    var doc = window.top.document;
    if (!window.top.location.host.endswith('paulgraham.com')) { return ''; }
    if (doc.body.textContent.contains('[1]')) { return doc.title; }
    var elmFootnote = doc.evaluate("//a[@name='f1n']", doc,
        null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (elmFootnote) { return doc.title; }
    return '';
}
AutoGraham.prototype.checkDuplicates = checkTitle;

function AutoRecipe() {
    this.option = 'autorecipe';
    this.action = 'you&#8217;re reading a recipe';
    this.plural = 'recipes';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoRecipe.prototype = new AutoList;
AutoRecipe.prototype.getValue = function() {
    const kArbitraryCutoff = 5;
    var doc = window.top.document;
    var iScore = 0;
    var url = window.location.href.toLowerCase();
    var sTitle = doc.title.toLowerCase();
    if (sTitle.contains('recipe')) { iScore += 1; }
    var sBody = doc.body.textContent.normalize().toLowerCase();
    if (sBody.contains('ingredients')) { iScore += 1; }
    if (sBody.contains('preheat')) { iScore += 1; }
    if (sBody.contains('soak overnight')) { iScore += 1; }
    if (sBody.contains('serve immediately')) { iScore += 1; }
    if (sBody.contains('or until done')) { iScore += 1; }
    iScore -= 7 * (/del\.icio\.us/.count(url));
    iScore += /[1-9] pounds?\b/.count(sBody);
    iScore += /[1-9] ?lbs?/.count(sBody);
    iScore += /[0-9] ?ounces?/.count(sBody);
    iScore += /[0-9] ?oz\.?/.count(sBody);
    iScore += /[1-9] ?cups?\b/.count(sBody);
    iScore += /[1-9] ?teaspoons?\b/.count(sBody);
    iScore += /[1-9] ?tsp?\b/.count(sBody);
    iScore += /[1-9] ?tablespoons?\b/.count(sBody);
    iScore += /[1-9] ?tbsp\b/.count(sBody);
    iScore += /[1-9] ?tb\b/.count(sBody);
    iScore += /[0-9] ?grams\b/.count(sBody);
    iScore += /[0-9] ?g\b/.count(sBody);
    iScore += /1 pinch\b/.count(sBody);
    iScore += /\b(simmer|boil)\b/.count(sBody);
    iScore += /\b(servings|serves)\b/.count(sBody);
    iScore += /[0-9] ?degrees\b/.count(sBody);
    iScore += /[0-9] ?\u00b0/.count(sBody);
    if (iScore >= kArbitraryCutoff) {
	return window.top.location.href;
    } else {
	return '';
    }
}

function AutoBook() {
    this.option = 'autobook';
    this.action = 'you&#8217;re considering buying a book';
    this.plural = 'books';
    registerOption({option: this.option, name: this.plural.capitalize()});
}

AutoBook.prototype = new AutoList;
AutoBook.prototype.getValue = function() {
    function checkISBN(isbn) {
	try {
	    isbn=isbn.toLowerCase().replace(/-/g,'').replace(/ /g,'');
	    if (isbn.length != 10) return false;
	    var checksum = 0;
	    for (var i=0; i<9; i++) {
		if (isbn[i] == 'x') {
		    checksum += 10 * (i+1);
		} else {
		    checksum += isbn[i] * (i+1);
		}
	    }
	    checksum = checksum % 11;
	    if (checksum == 10) checksum = 'x';
	    if (isbn[9] == checksum)
	    return isbn;
	    else
	    return false;
	} catch (e) { return false; }
    }

    var isbn = '';

    if (window.top.location.href.match('amazon.com') && 
	!window.top.location.href.match('rate-this')) {
	var match = window.top.location.href.match(/\/([0-9X]{10})(\/|\?|$)/);
	if (match) {
	    isbn = checkISBN(match[1]);
	}
    }

    if (window.top.location.href.match('barnesandnoble.com')) {
	var match = window.top.location.href.match(
            /[iI][sS][Bb][Nn]=([0-9X]{10})(\&|\?|$)/);
	if (match) {
	    isbn = checkISBN(match[1] );
	}
    }

    if (window.top.location.href.match('buy.com')) {
	var match = window.top.document.title.match(/ISBN ([0-9X]{10})/);
	if (match) {
	    isbn = checkISBN(match[1]);
	}
    }
    
    if (window.top.location.href.match('powells.com')) {
	var arBold = window.top.document.getElementsByTagName('b');
	for (var i=0; i<arBold.length; i++) {
	    var elmBold = arBold[i];
	    if (elmBold.innerHTML.match('ISBN:')) {
		if (elmBold.nextSibling && elmBold.nextSibling.nextSibling) {
		    isbn = checkISBN(elmBold.nextSibling.nextSibling.textContent);
		    if (isbn) { break; }
		}
	    }
	}
    }

    if (window.top.location.href.match('half.ebay.com')) {
	var arBold = window.top.document.getElementsByTagName('b');
	for (var i=0; i<arBold.length; i++) {
	    var elmBold = arBold[i];
	    if (elmBold.innerHTML.match('ISBN:')) {
		if (elmBold.nextSibling) {
		    isbn = checkISBN(elmBold.nextSibling.textContent);
		    if (isbn) { break; }
		}
	    }
	}
    }

    return isbn;
}
AutoBook.prototype.checkDuplicates = function(arPosts, isbn) {
    var bDuplicate = false;
    for (var i = 0; i < arPosts.length; i++) {
	var oParams = arPosts[i];
	if (oParams.link.contains(isbn)) {
	    bDuplicate = true;
	    break;
	}
    }
    return bDuplicate;
}
AutoBook.prototype.buildPost = function(oParams, isbn) {
    if (oParams.title.contains(' - ')) {
	oParams.title = oParams.title.substringAfterFirst(' - ');
    } else if (oParams.title.contains(': ')) {
	oParams.title = oParams.title.substringAfterFirst(': ');
    }
    oParams.link = 'http://www.amazon.com/exec/obidos/ASIN/' + isbn;
    var sAssociateID = GM_getValue('associate.amazon', '');
    if (sAssociateID) {
	oParams.link += '/ref=nosim/' + sAssociateID;
    }
    return oParams;
}

function AutoDownload() {
    this.option = 'autodownload';
    this.action = 'you&#8217;re down-<br />loading a file';
    this.plural = 'downloads';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoDownload.prototype = new AutoList;
AutoDownload.prototype.getValue = function() {
    return window.top.location.href;
}

function trackDownload(e) {
    var elmTarget = e.target;
    while (!['A', 'BODY', 'HTML'].contains(elmTarget.nodeName.toUpperCase())) {
	elmTarget = elmTarget.parentNode;
    }
    if (elmTarget.nodeName.toUpperCase() != 'A') { return true; }
    var urlTarget = elmTarget.href;
    if (!urlTarget) { return true; }
    if (!isBinaryFile(getFilenameFromURL(urlTarget))) { return true; }
    new AutoDownload().run();
    return true;
}

function AutoBug() {
    this.option = 'autobug';
    this.action = 'you&#8217;re tracking a bug report';
    this.plural = 'bug reports';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoBug.prototype = new AutoList;
AutoBug.prototype.getValue = function() {
    if (window.top.location.host.indexOf('bugzilla') == -1) { return ''; }
    if (window.top.location.pathname.indexOf('show_bug.cgi') == -1) { return ''; }
    if (window.top.location.search.indexOf('id=') == -1) { return ''; }
    return window.top.location.href;
}

function AutoAuction() {
    this.option = "autoauction";
    this.action = 'you&#8217;re tracking an auction';
    this.plural = 'auctions';
    registerOption({option: this.option, name: this.plural.capitalize()});
}
AutoAuction.prototype = new AutoList;
AutoAuction.prototype.getValue = function() {
    var sValue = '';
    switch (window.top.location.host) {
    case 'cgi.ebay.com':
	if (window.top.document.title.contains('(item ')) {
	    sValue = window.top.location.href;
	}
	break;
    case 'page.auctions.yahoo.com':
	if (window.top.location.pathname.startswith('/auction/')) {
	    sValue = window.top.location.href;
	}
    }
    return sValue;
}

function trackGreasemonkey(e) {
    var elmTarget = e.target;
    while (!['A', 'BODY', 'HTML'].contains(elmTarget.nodeName.toUpperCase())) {
	elmTarget = elmTarget.parentNode;
    }
    if (elmTarget.nodeName.toUpperCase() != 'A') { return true; }
    var urlTarget = elmTarget.href;
    if (!urlTarget) { return true; }
    if (!getFilenameFromURL(urlTarget).toLowerCase().endswith('.user.js')) { return true; }
    GM_xmlhttpRequest({method: 'GET', url: urlTarget, onload: function(oResponseDetails) {
	if (oResponseDetails.status.toString() != '200') { return; }
	var oMeta = parseUserScript(oResponseDetails.responseText);
	if (!oMeta.name) { return; }
	new AutoGreasemonkey(urlTarget, oMeta).run();
    }});
    return true;
}

function AutoGreasemonkey(url, oMeta) {
    this.option = 'autogreasemonkey';
    this.action = 'you&#8217;re installing a Greasemonkey script';
    this.plural = 'Greasemonkey scripts';
    registerOption({option: this.option, name: this.plural.capitalize()});
    this.whatToPost = 'the script';
    this.buildPost = function(oParams) {
	oParams.title = oMeta.name;
	oParams.link = url;
	oParams.description = oMeta.description || '';
	return oParams;
    }
    this.getValue = function() {
	return oMeta.name;
    }
}
AutoGreasemonkey.prototype = new AutoList;
AutoGreasemonkey.prototype.checkDuplicates = checkTitle;

// parseUserScript code lifted from Greasemonkey itself.
// Oh, the meta-ness of it all.
function parseUserScript(txt) {
    var lines = txt.match(/.+/g);
    var lnIdx = 0;
    var result = {};
    var foundMeta = false;
    var script = {};
    
    while (result = lines[lnIdx++]) {
	if (result.indexOf("// ==UserScript==") == 0) {
	    foundMeta = true;
	    break;
	}
    }
    
    // gather up meta lines
    if (foundMeta) {
	while (result = lines[lnIdx++]) {
	    if (result.indexOf("// ==/UserScript==") == 0) {
		break;
	    }
	    
	    var match = result.match(/\/\/ \@(\S+)\s+([^\n]+)/);
	    if (match != null) {
		switch (match[1]) {
		case "name":
		case "description":
		    script[match[1]] = match[2];
		    break;
		}
	    }
	}
    }

    return script;
}

function onkeypress(event) {
    if (gSettingHotkey) { return true; }
    var sDisplayKey = displayKeyFromEvent(event);
    var sPostHotkey = GM_getValue('hotkey.post', '');
    if (!sDisplayKey || sDisplayKey != sPostHotkey) {
	return true;
    }
    Delicious.post({link: window.top.location.href, title: window.top.document.title, description: '', tags: ''}, new AutoList());
    event.holdEverything();
    return false;
}

function setDefault(sKey, sValue) {
    GM_setValue(sKey, GM_getValue(sKey, sValue));
}
setDefault('autotopten.enabled', 1);
setDefault('autotopten.tags', 'topten list');
setDefault('autouseful.enabled', 1);
setDefault('autouseful.tags', 'useful links');
setDefault('autoresearch.enabled', 1);
setDefault('autoresearch.tags', 'research');
setDefault('autospec.enabled', 1);
setDefault('autospec.tags', 'specification');
setDefault('autohowto.enabled', 1);
setDefault('autohowto.tags', 'tutorial howto');
setDefault('autograham.enabled', 1);
setDefault('autograham.tags', 'paulgraham article');
setDefault('autorecipe.enabled', 1);
setDefault('autorecipe.tags', 'recipes');
setDefault('autobook.enabled', 1);
setDefault('autobook.tags', 'books');
setDefault('associate.amazon', '');
setDefault('autodownload.enabled', 1);
setDefault('autodownload.tags', 'download');
setDefault('autobug.enabled', 1);
setDefault('autobug.tags', 'bugs');
setDefault('autoauction.enabled', 1);
setDefault('autoauction.tags', 'auction');
setDefault('autogreasemonkey.enabled', 1);
setDefault('autogreasemonkey.tags', 'greasemonkey javascript');
setDefault('hotkey.post', '');
if (DEBUG_SIMULATION_ONLY) { GM_setValue('cache.count', 0); }
Cache.flush();

// sanity check, filter out bogus pages, text pages, etc.
var doc = window.top.document;
if (!doc) { return; }
if (!doc.title) { return; }
if (!doc.body) { return; }
if (!doc.body.childNodes) { return; }
if (doc.body.childNodes.length == 1 &&
    doc.body.firstChild.nodeName.toUpperCase() == 'PRE') { return; }

new AutoHowto().run();
new AutoBook().run();
new AutoAuction().run();
new AutoBug().run();
new AutoSpec().run();
new AutoTopTen().run();
new AutoRecipe().run();
new AutoUseful();
new AutoResearch();
new AutoDownload();
new AutoGreasemonkey();
new AutoGraham().run();
document.addEventListener('click', trackUseful, false);
document.addEventListener('click', trackDownload, false);
document.addEventListener('click', trackGreasemonkey, false);
document.addEventListener('keypress', onkeypress, true);
if (DEBUG_SIMULATION_ONLY) { showOptions(); }
GM_registerMenuCommand('Monkey Do options', showOptions);
if (GM_getValue('first.run', true)) {
    showOptions();
    GM_setValue('first.run', false);
}

//
// ChangeLog
// 2005-09-08 - MAP - removed unused functions
// 2005-09-07 - MAP - fixed "oCache.date has no properties" error in cache flushing
// 2005-08-18 - MAP - use auto height on all boxes, prefer Verdana, don't allow hotkeys without modifiers
// 2005-08-17 - MAP - initial release
//
