SpamCop Held Mail Cleanup
By Daniel Einspanjer
—
Last update Mar 11, 2007
—
Installed
399 times.
// SpamCop Held Mail Cleanup
// version 0.3
// 2007-03-11
// Copyright (c) 2007, Daniel Einspanjer
// Released under the MIT license
// http://www.opensource.org/licenses/mit-license.php
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script. To install it, you need
// Greasemonkey 0.3 or later: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "SpamCop Held Mail Cleanup" then click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name SpamCop Held Mail Cleanup
// @namespace http://www.yipyip.com/greasemonkey/
// @description Converts the SpamCop Held Mail <dl> list into a table with sortable columns
// @include http://mailsc.spamcop.net/reportheld*
// ==/UserScript==
GM_addStyle("\
a.sortheader {\
background-color:#eee;\
color:#666666;\
font-weight: bold;\
text-decoration: none;\
display: block;\
}\
span.sortarrow {\
color: black;\
text-decoration: none;\
}\
caption {\
padding: 1em 0 .25em .5em;\
text-align: left;\
}\
table {\
font-family: monospace;\
font-size: smaller !important;\
}\
th {\
text-align: center;\
}\
td.X {\
text-align: center;\
}\
td.ID {\
text-align: center;\
}\
td.From {\
text-align: right;\
padding-right: 1em;\
}\
td.Subject {\
padding: 0 1em;\
}\
td.Received {\
padding: 0 1em;\
white-space: nowrap;\
text-align: right;\
}\
td.Filter {\
padding: 0 1em;\
white-space: nowrap;\
}");
// This regex captures the SpamCop time format and the blocking reason.
const ddRegex = /\s*(.+?(?: [-+]\d{4})?(?: \(?[A-Z]{3}(?: ?[-+]\d{2}:?\d{2})?\)?)*)? \((Blocked .*)\)\s*/;
// Captures the e-mail address from the trailing cruft
const fromRegex = /\s*([^ (]*)/;
// Captures the ID without the brackets
const idRegex = /\s*\[(\d+)\]/;
// This is very VBish, but I just have a bunch of variables that get reused a lot in loops and such.
var tr, td, tdX, tdID, tdFrom, tdSubject, tdReceived, tdFilter, stringNode, regexResult, nodes1, nodes2;
// The list of column headers and the sort function to use on them
var columnDefinitions = [
["X", ts_null_sort],
["ID", ts_sort_numeric],
["From", ts_sort_email],
["Subject", ts_sort_caseinsensitive],
["Received", ts_sort_parsed_date],
["Filter", ts_sort_blocked] ];
var table = document.createElement('table');
table.border = '1';
// cellSpacing gets bunched up at the top of the table when reordering rows. blech!
table.cellSpacing = '0';
// First, create the header row
tr = document.createElement('tr');
for (var i = 0; i < columnDefinitions.length; i++)
{
var methodHolder = columnDefinitions[i][1];
td = document.createElement('th');
td.appendChild(document.createTextNode(columnDefinitions[i][0]));
tr.appendChild(td);
}
table.appendChild(tr);
var dlElement = document.getElementsByTagName('dl')[0];
// Note that I assume paired dt/dd elements.
// The code in the loopwould break if they weren''t paired.
var dtElements = dlElement.getElementsByTagName('dt');
var ddElements = dlElement.getElementsByTagName('dd');
for (var i = 0; i < dtElements.length; i++)
{
tr = document.createElement('tr');
tdX = document.createElement('td');
tdX.className = 'X';
tdID = document.createElement('td');
tdID.className = 'ID';
tdFrom = document.createElement('td');
tdFrom.className = 'From';
tdSubject = document.createElement('td');
tdSubject.className = 'Subject';
tdReceived = document.createElement('td');
tdReceived.className = 'Received';
tdFilter = document.createElement('td');
tdFilter.className = 'Filter';
// Stuff all the input elements into the X field
nodes1 = dtElements[i].getElementsByTagName('input');
while (nodes1.length > 0)
{
tdX.appendChild(nodes1[0]);
}
// The ID and the Subject are both in <strong> elements.
nodes1 = dtElements[i].getElementsByTagName('strong');
// The ID
stringNode = nodes1[0].childNodes[0];
regexResult = stringNode.textContent.match(idRegex);
if (regexResult != null)
{
tdID.appendChild(document.createTextNode(regexResult[1]));
}
// The subject and preview link
nodes2 = nodes1[1].childNodes;
while (nodes2.length > 0)
{
tdSubject.appendChild(nodes2[0]);
}
// Remove the two now-empty strong elements.
dtElements[i].removeChild(nodes1[1]);
dtElements[i].removeChild(nodes1[0]);
// All the darn anonymous whitespace nodes.
// I know the From address is the fifth element at this point.
stringNode = dtElements[i].childNodes[4];
regexResult = stringNode.textContent.match(fromRegex);
if (regexResult != null)
{
tdFrom.appendChild(document.createTextNode(regexResult[1]));
}
// The date and blocked strings are first in the <dd>
stringNode = ddElements[i].childNodes[0]
regexResult = stringNode.textContent.match(ddRegex);
if (regexResult != null)
{
// Rewrite the date as a local string so sorting makes visible sense.
tdReceived.appendChild(document.createTextNode(new Date(Date.parse(regexResult[1])).toLocaleString()));
tdFilter.appendChild(document.createTextNode(regexResult[2]));
ddElements[i].removeChild(stringNode);
}
tr.appendChild(tdX);
tr.appendChild(tdID);
tr.appendChild(tdFrom);
tr.appendChild(tdSubject);
tr.appendChild(tdReceived);
tr.appendChild(tdFilter);
table.appendChild(tr);
}
var caption = document.createElement('caption');
// Now that we''ve gotten rid of all the other stuff, the only <a> elements in <dl> are the widgets.
var aElements = dlElement.getElementsByTagName('a');
while (aElements.length > 0)
{
caption.appendChild(aElements[0]);
}
caption.insertBefore(document.createTextNode(' | '), caption.childNodes[1]);
// -1 for the header row
caption.appendChild(document.createTextNode(' ' + (table.childNodes.length-1) + ' items'));
table.appendChild(caption);
dlElement.parentNode.insertBefore(table, dlElement);
// Clean up the now defunct <dl>.
dlElement.parentNode.removeChild(dlElement);
String.prototype.trim=function(){
return this.replace(/^\s*|\s*$/g,'');
}
// The code below was tweaked from:
// http://www.kryogenix.org/code/browser/sorttable/
// and falls under the MIT license:
// http://www.kryogenix.org/code/browser/licence.html
//
// My major changes were:
// To replace the use of table.rows and row.cells with .childNodes because the
// former methods didn''t work with my generated table.
// To replace the use of innerHTML for creating the sort links because I needed
// to use .addEventListener instead of the onclick attribute.
var SORT_COLUMN_INDEX;
ts_makeSortable(table);
function ts_makeSortable(theTable) {
var firstRow = theTable.getElementsByTagName('tr')[0];
if (!firstRow) return;
// We have a first row: assume it''s the header, and make its contents clickable links
for (var i=0;i<firstRow.childNodes.length;i++) {
var cell = firstRow.childNodes[i];
var aLink = document.createElement('a');
aLink.href = '#';
aLink.className = 'sortheader';
aLink.addEventListener('click', function(event) { ts_resortTable(event.target); event.preventDefault(); }, true);
aLink.appendChild(cell.childNodes[0]);
var span = document.createElement('span');
span.className = 'sortarrow';
span.innerHTML = ' ';
aLink.appendChild(span);
cell.appendChild(aLink);
}
}
function ts_getInnerText(el) {
if (typeof el == "string") return el;
if (typeof el == "undefined") { return el };
if (el.innerText) return el.innerText.trim(); //Not needed but it is faster
var str = "";
var cs = el.childNodes;
var l = cs.length;
for (var i = 0; i < l; i++) {
switch (cs[i].nodeType) {
case 1: //ELEMENT_NODE
str += ts_getInnerText(cs[i]);
break;
case 3: //TEXT_NODE
str += cs[i].nodeValue;
break;
}
}
return str.trim();
}
function ts_resortTable(lnk) {
// get the span
var span;
for (var ci=0;ci<lnk.childNodes.length;ci++) {
if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
}
var spantext = ts_getInnerText(span);
var td = lnk.parentNode;
var column = td.cellIndex;
var sortMethod = columnDefinitions[column][1];
var theTable = getParent(td,'TABLE');
var rows = theTable.getElementsByTagName('tr');
if (rows.length <= 1) return;
SORT_COLUMN_INDEX = column;
var firstRow = new Array();
var newRows = new Array();
for (var i=0;i<rows[0].childNodes.length;i++) { firstRow[i] = rows[0].childNodes[i]; }
for (var i=1;i<rows.length;i++) { newRows[i-1] = rows[i]; }
newRows.sort(sortMethod);
if (span.getAttribute("sortdir") == 'down') {
ARROW = ' ↑';
newRows.reverse();
span.setAttribute('sortdir','up');
} else {
ARROW = ' ↓';
span.setAttribute('sortdir','down');
}
// Sortbottom isn''t used in my implementation.
// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
// don''t do sortbottom rows
for (i=0;i<newRows.length;i++) { if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) theTable.appendChild(newRows[i]);}
// do sortbottom rows only
for (i=0;i<newRows.length;i++) { if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) theTable.appendChild(newRows[i]);}
// Delete any other arrows there may be showing
var allspans = document.getElementsByTagName("span");
for (var ci=0;ci<allspans.length;ci++) {
if (allspans[ci].className == 'sortarrow') {
if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
allspans[ci].innerHTML = ' ';
}
}
}
span.innerHTML = ARROW;
}
function getParent(el, pTagName) {
if (el == null) return null;
else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) // Gecko bug, supposed to be uppercase
return el;
else
return getParent(el.parentNode, pTagName);
}
function ts_sort_date(a,b) {
// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
if (aa.length == 10) {
dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
} else {
yr = aa.substr(6,2);
if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
}
if (bb.length == 10) {
dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
} else {
yr = bb.substr(6,2);
if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
}
if (dt1==dt2) return 0;
if (dt1<dt2) return -1;
return 1;
}
function ts_sort_currency(a,b) {
aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
return parseFloat(aa) - parseFloat(bb);
}
function ts_sort_numeric(a,b) {
aa = parseFloat(ts_getInnerText(a.childNodes[SORT_COLUMN_INDEX]));
if (isNaN(aa)) aa = 0;
bb = parseFloat(ts_getInnerText(b.childNodes[SORT_COLUMN_INDEX]));
if (isNaN(bb)) bb = 0;
return aa-bb;
}
function ts_sort_caseinsensitive(a,b) {
aa = ts_getInnerText(a.childNodes[SORT_COLUMN_INDEX]).toLowerCase();
bb = ts_getInnerText(b.childNodes[SORT_COLUMN_INDEX]).toLowerCase();
if (aa==bb) return 0;
if (aa<bb) return -1;
return 1;
}
function ts_sort_default(a,b) {
aa = ts_getInnerText(a.childNodes[SORT_COLUMN_INDEX]);
bb = ts_getInnerText(b.childNodes[SORT_COLUMN_INDEX]);
if (aa==bb) return 0;
if (aa<bb) return -1;
return 1;
}
// My own sort date function takes advantage of Date.parse.
function ts_sort_parsed_date(a,b) {
aa = Date.parse(ts_getInnerText(a.childNodes[SORT_COLUMN_INDEX]));
bb = Date.parse(ts_getInnerText(b.childNodes[SORT_COLUMN_INDEX]));
if (aa==bb) return 0;
if (aa<bb) return -1;
return 1;
}
// I try to compare the blocked column as numbers because I want to order
// The Blocked SpamAssassin = 5 etc.
function ts_sort_blocked(a,b) {
aa = ts_getInnerText(a.childNodes[SORT_COLUMN_INDEX]).replace(/[^0-9]/g,'');
bb = ts_getInnerText(b.childNodes[SORT_COLUMN_INDEX]).replace(/[^0-9]/g,'');
aaa = parseInt(aa);
bbb = parseInt(bb);
if (!isNaN(aaa) && !isNaN(bbb))
return aaa - bbb;
else if (isNaN(aaa) && !isNaN(bbb))
return -1;
else if (!isNaN(aaa) && isNaN(bbb))
return 1;
else
return ts_sort_default(a,b);
}
function ts_sort_email(a,b) {
aa = ts_getInnerText(a.childNodes[SORT_COLUMN_INDEX]).toLowerCase();
bb = ts_getInnerText(b.childNodes[SORT_COLUMN_INDEX]).toLowerCase();
var order = 0;
aaa = aa.split('@', 2)[1];
bbb = bb.split('@', 2)[1];
if (aaa==bbb) order = 0;
else if (aaa<bbb) order = -1;
else order = 1;
if (order == 0)
{
aaa = aa.split('@', 2)[0];
bbb = bb.split('@', 2)[0];
if (aaa==bbb) order = 0;
else if (aaa<bbb) order = -1;
else order = 1;
}
//var debug = ['<', '=', '>'];
//GM_log(aaa + ' ' + debug[order+1] + ' ' + bbb);
return order;
}
// Just to formally declare that you can''t really sort the X column.
function ts_null_sort(a,b) {
return 0;
}
function displayArray()
{
var a = displayArray.array;
var str = '';
for (var i = 0; i < a.length; i++)
{
str += ts_getInnerText(a[i].childNodes[SORT_COLUMN_INDEX]) + '\n';
}
alert(str);
}