// $Id: ljfilter.user.js,v 1.6 2007-08-01 05:45:00+04 slobin Exp $
//
// ==UserScript==
// @name LiveJournal Filter
// @description filters out user entries based of tags present or absent
// @namespace http://wagner.pp.ru/~slobin/firefox/
// @include http://*.livejournal.com/friends
// @include http://*.livejournal.com/friends/*
// @include http://*.livejournal.com/friends?*
// ==/UserScript==
//
// Homepage: http://wagner.pp.ru/~slobin/firefox/
//
// Cyril Slobin <slobin@ice.ru> `When I use a word,' Humpty Dumpty said,
// http://wagner.pp.ru/~slobin/ `it means just what I choose it to mean'
//
// Public Domain
// Made on Earth
var DEBUG = false;
var DOCS = "# To view only certain tags:\n"
+ "# username: +tag1 +tag2\n"
+ "#\n"
+ "# To filter out certain tags:\n"
+ "# username: -tag1 -tag2\n"
+ "#\n"
+ "# To include spaces in tags:\n"
+ "# username: -tag~with~spaces\n";
var ITER = XPathResult.ORDERED_NODE_ITERATOR_TYPE;
var SNAP = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
var NODE = XPathResult.FIRST_ORDERED_NODE_TYPE;
var filter, errors, editWindow;
function debug(message)
{
if (DEBUG) alert(message);
}
function xpath(query, root, type)
{
return document.evaluate(query, root, null, type, null);
}
function setRules(rules)
{
GM_setValue("rules", escape(rules));
}
function getRules(defvalue)
{
return unescape(GM_getValue("rules", defvalue));
}
function parseRules(rules)
{
debug("parseRules");
filter = new Object();
errors = new Array();
rules = rules.split("\n");
for (var i = 0; i < rules.length; i++) {
var line = rules[i];
if (line.length && (line[0] != "#")) {
var pair = line.split(/:/);
if (pair.length == 2) {
var user = pair[0];
var tags = pair[1];
filter[user] = new Object();
var userFilter = filter[user];
userFilter.has_positive = false;
userFilter.positive = new Object();
userFilter.negative = new Object();
tags = tags.split(/ +/);
for (var j = 0; j < tags.length; j++) {
var tag = tags[j];
tag = tag.replace(/~/g, " ");
if (tag.length) {
switch (tag[0]) {
case "+":
userFilter.has_positive = true;
userFilter.positive[tag.slice(1)] = true;
break;
case "-":
userFilter.negative[tag.slice(1)] = true;
break;
default:
errors.push(i+1);
break;
}
}
}
} else {
errors.push(i+1);
}
}
}
}
function applyFilter()
{
debug("applyFilter");
var parents = Array();
var elements = Array();
var recognized = false;
function remember(parent, element)
{
parents.push(parent);
elements.push(element);
}
// Punquin Elegant
if (!recognized) {
debug("Punquin Elegant");
var allEntries = xpath("//table[@class='entry']", document, ITER);
var entryNode;
while (entryNode = allEntries.iterateNext()) {
recognized = true;
var userNode = xpath(".//a[1]", entryNode, NODE).singleNodeValue;
var user = userNode.innerHTML;
var userFilter = filter[user];
if (userFilter) {
var positive = !userFilter.has_positive;
var negative = false;
var allTags = xpath(".//a[@rel='tag']", entryNode, ITER);
var tagNode;
while (tagNode = allTags.iterateNext()) {
var tag = tagNode.innerHTML;
if (userFilter.positive[tag]) positive = true;
if (userFilter.negative[tag]) negative = true;
}
if (!positive || negative) {
var parentNode = entryNode.parentNode;
remember(parentNode, entryNode);
remember(parentNode, entryNode.nextSibling);
}
}
}
}
// A Sturdy Gesture
if (!recognized) {
debug("A Sturdy Gesture");
var allEntries = xpath("//div[@class='box']//div[@class='entry']",
document, ITER);
var entryNode;
while (entryNode = allEntries.iterateNext()) {
recognized = true;
var boxNode = entryNode.parentNode;
var userNode = xpath(".//a[2]", boxNode, NODE).singleNodeValue;
var user = userNode.innerHTML;
var userFilter = filter[user];
if (userFilter) {
var positive = !userFilter.has_positive;
var negative = false;
var allTags = xpath(".//a[@rel='tag']", entryNode, ITER);
var tagNode;
while (tagNode = allTags.iterateNext()) {
var tag = tagNode.innerHTML;
if (userFilter.positive[tag]) positive = true;
if (userFilter.negative[tag]) negative = true;
}
if (!positive || negative) {
var parentNode = boxNode.parentNode;
remember(parentNode, boxNode);
}
}
}
}
// Refried Paper
if (!recognized) {
debug("Refried Paper");
var allEntries = xpath("//div[@class='entry']", document, ITER);
var entryNode;
while (entryNode = allEntries.iterateNext()) {
recognized = true;
var userNode = xpath(".//a[2]/b", entryNode, NODE).singleNodeValue;
var user = userNode.innerHTML;
var userFilter = filter[user];
if (userFilter) {
var positive = !userFilter.has_positive;
var negative = false;
var allTags = xpath(".//table[1]//table[1]//td[last()]/a",
entryNode, ITER);
var tagNode;
while (tagNode = allTags.iterateNext()) {
var tag = tagNode.innerHTML;
if (userFilter.positive[tag]) positive = true;
if (userFilter.negative[tag]) negative = true;
}
if (!positive || negative) {
var parentNode = entryNode.parentNode;
remember(parentNode, entryNode);
var prevNode = entryNode;
do {
prevNode = prevNode.previousSibling;
remember(parentNode, prevNode);
} while (prevNode.nodeName != "A")
var nextNode = entryNode.nextSibling;
while (nextNode.nodeName != "A") {
remember(parentNode, nextNode);
nextNode = nextNode.nextSibling;
}
}
}
}
}
for (var i = 0; i < parents.length; i++) {
parents[i].removeChild(elements[i]);
}
}
function editRules()
{
debug("editRules");
editWindow = window.open("about:blank", "_blank",
"width=500, height=300, resizable=1");
editWindow.document.writeln("<form name='edit' action='about:blank'>");
editWindow.document.writeln("<textarea name='rules' cols='50' rows='12'>");
editWindow.document.writeln(getRules());
editWindow.document.writeln("</textarea>");
if (errors.length) {
editWindow.document.writeln("<P>Errors in lines: " + errors.join(" "));
}
editWindow.document.writeln("<p><input type='submit' value='Submit'>");
editWindow.document.writeln("</form>");
editWindow.document.close();
var form = editWindow.document.forms.namedItem("edit");
form.addEventListener("submit", processForm, true);
editWindow.focus();
}
function processForm()
{
debug("processForm");
var form = editWindow.document.forms.namedItem("edit");
var elem = form.elements.namedItem("rules");
var rules = elem.value;
rules = rules.replace(/\n*$/, "\n");
setRules(rules);
parseRules(rules);
editWindow.close();
if (errors.length) {
editRules();
} else {
location.reload();
}
}
GM_registerMenuCommand("Edit LiveJournal Filter Rules", editRules);
setRules(getRules(DOCS));
parseRules(getRules());
applyFilter();