Greasy Fork

Heise.de Forum: all comments on one page v2

All comments on one page, iReply, quick-vote, user-scores.

目前为 2015-10-16 提交的版本。查看 最新版本

// ==UserScript==
// @name          Heise.de Forum: all comments on one page v2
// @namespace     Vorticon
// @author        marrr, edited by commander_keen, jn
// @version       2.3.2
// @description   All comments on one page, iReply, quick-vote, user-scores.
// @include       http://www.heise.de/*/foren/*
// @include       http://www.heise.de/foren/*
// @include       http://www.heise.de/forum/*
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_xmlhttpRequest
// @grant         GM_log
// @grant         GM_addStyle
// ==/UserScript==
// known issues: Greasemonkey 3.1 GM_xmlhttpRequest doesn't send Cookies, so inline voting is not possible

(function() { // Opera wrapper
/********************************************
 * USER SETTINGS
 ********************************************/
var overviewPagePostCount  = 16;
var maxJoinedPosts         = overviewPagePostCount * 10; // in overview, add x posts * y pages
var maxJoinedPostsInThread = 80;

// user score is scaled by this value and then fitted into 0-10
var posterScoreScaleFactor = 0.3;

// here you can enable joining on different overview levels
/*var joinTopLevelForen = 1; this is nowhere to be found*/
var joinSubLevelForen = 1;

var enableThreadView = 1;

// reply stuff
var enableIReply    = 1;
var enableAutoQuote = 1;
var enableQuickVote = 1;

// "plain" for disabling colored bar indentation
var displayMode = "colored";

// defines the colors used for colored indentation
var branchBorderStyle = "1px dashed";

function getBranchColor(lvl) {
	switch(lvl % 8) {
		case 0:  return "#999999";
		case 1:  return "#445599";
		case 2:  return "#995544";
		case 3:  return "#449955";
		case 4:  return "#994455";
		case 5:  return "#554499";
		case 6:  return "#CC77CC";
		case 7:  return "#554499";
	}
}

function getQuoteColor(lvl) {
	switch(lvl) {
		case 2:  return "#668811";
		case 3:  return "#445599";
		case 4:  return "#995544";
		case 5:  return "#449955";
		case 6:  return "#994455";
		case 7:  return "#554499";
		case 8:  return "#CC77CC";
		case 9:  return "#554499";
		// ...
		default: return "";
	}
}


/********************************************
 * BROWSER DEPENDENT
 ********************************************/
function isOpera() {
	return typeof(opera) != "undefined";
}

function isSafari() {
	return typeof(safari) != "undefined" || /apple/i.test(navigator.vendor) || /safari/i.test(navigator.userAgent);
}

function isChrome() {
	return typeof(chrome) != "undefined";
}


function log(msg) {
	if (isOpera()) opera.postError(msg);
	// else if (isChrome()) console.log(msg); // chrome supports GM_log
	else if(isSafari())
	{
		// according to the docs, GM_log is supported
		if(typeof(GM_log) != undefined)
			GM_log(msg);
	}
	else GM_log(msg);
}

function requestHTML(fileUrl, callback, nr, div) {
	fileUrl = ensureAbsoluteUrl(fileUrl);
	if (isOpera() || isSafari()) {
		var xmlHttp = new XMLHttpRequest();
		xmlHttp.open('GET', fileUrl, true);
		xmlHttp.onreadystatechange = function () {
			if(xmlHttp.readyState == 4 &&
			   xmlHttp.status     == 200)
				callback(xmlHttp.responseText, nr, div, fileUrl);
		};
		xmlHttp.send(null);
	}
	else { // maybe the opera way works here, but this one contains more GM_s
		GM_xmlhttpRequest(
		{
			method: 'GET',
			url: fileUrl,
			onload: function(resp) {
				if(resp.status == 200)
					callback(resp.responseText, nr, div, fileUrl);
			}
		});
	}
}

// TODO: this is limited to simple values, yet sufficient
function setLocalValue(name, val) {
    if (isOpera() || isChrome() || isSafari()) {
		var lifeTime = 31536000;
		document.cookie = escape(name) + "=" + escape(val) +
			";expires=" + (new Date((new Date()).getTime() + (1000 * lifeTime))).toGMTString() + ";path=/";
	}
	else
		GM_setValue(name, val);
}

function getLocalValue(name, def ) {
	if(isOpera() || isChrome() || isSafari()) {
		var cookieJar = document.cookie.split("; ");
		for(var x = 0; x < cookieJar.length; x++) {
			var oneCookie = cookieJar[x].split( "=" );
			if( oneCookie[0] == escape(name) ) {
				try {
					eval('var footm = '+unescape(oneCookie[1]));
					return footm;
				} catch(e) { return def; }
			}
		}
		return def;
	}
	else
		return GM_getValue(name, def);
}



/********************************************
 * THE CODE
 ********************************************/
var baseUrl = document.location.protocol + '//' + document.location.host;
var postingRegExp;

var isNewForum = /^\/forum\//.test(document.location.pathname) ? true : false;
if(isNewForum)
    postingRegExp = /((<div class="userbar[\s\S]*?)(?=(<div class="metabar">))\3[\s\S]*?<\/div>\s*<\/div>)(?=\s*<div class="thread_view_switch">)/;
else
	postingRegExp = /((<div class="vote_posting">[\s\S]*?)?(?=(<div class="posting_date">))\3[\s\S]*?(?=(<div class="tovote_links">))\4[\s\S]*?<\/div>)/;

var searchUrl = baseUrl + '/foren/suche?q=';

function xpath(xp, root)
{
	if(root === null) return null;
	if(root === undefined) root = document;
	return document.evaluate(xp, root, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
}
function xpath1(xp, root)
{
	var res = xpath(xp, root);
	return res ? ((res.snapshotLength > 0) ? res.snapshotItem(0) : null) : null;
}

function ensureAbsoluteUrl(url)
{
	if(url.match(/^\//))
		url = baseUrl + url;
	else if(url.match(/^\?/))
		url = document.location.href.replace(document.location.search, "") + url;
	return url;
}

function defineScriptInPageContext(code)
{
	var script = document.createElement("script");
	script.type = "application/javascript";
	script.innerHTML = code;

	document.body.appendChild(script);
}

function getElementsByClassName(oElm, strTagName, strClassName) {

	var arrElements = (strTagName == "*" && document.all) ?
		document.all : oElm.getElementsByTagName(strTagName);

	var arrReturnElements = new Array();
	strClassName = strClassName.replace(/\-/g, "\\-");

	var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");

	for(var i = 0; i < arrElements.length; i++) {

		var oElement = arrElements[i];
		if(oRegExp.test(oElement.className))
			arrReturnElements.push(oElement);

	}
	return (arrReturnElements);
}

function grepTitleLinkURL(html)
{
	if(isNewForum)
		var res = html.match(/<a[^>]*? href="([^"]*?)"[^>]*? class="posting_subject">/);
	else
		var res = html.match(/<a[^>]*? href="([\s\S]*?)"[^>]*? title=(["'])[\s\S]*?\2>/i);
	if(!res) return null;
	return res[1];
}

function joinThreadPosts()
{
	if(isNewForum) {
		joinThreadPostsNew();
		return;
	}
	// do it this way to respect priorities
	var rootPostDiv = xpath1("//div[@class='forum_content']");
	if(rootPostDiv == null)
		rootPostDiv = xpath1("//div[@id='mitte_forum']");
	if(rootPostDiv == null)
		rootPostDiv = xpath1("//div[@id='mitte']");
	if(rootPostDiv == null)
		rootPostDiv = xpath1("//td[@class='f-content']");
	if(rootPostDiv == null)
		rootPostDiv = xpath1("//*[@id='parttime-forum']");
	if(rootPostDiv == null)
		log("Root div not found!");

	var rootPostText = xpath1("p[@class='posting_text']", rootPostDiv);
	appendReplyFrame(rootPostText, -1, rootPostDiv);

	var replyLinkA = xpath1(".//a[text() = 'Beantworten']", rootPostDiv);
	var replyLink = replyLinkA == null ? "" : replyLinkA.href;
	processMessageDiv(rootPostDiv, document.location.href, replyLink);

	var threadsList = xpath1("//ul[@class='thread_tree']");
	if(threadsList == null)
	{
		log("Thread tree not found - activate it!");
		return;
	}

	var divStack  = new Array();
	divStack.peek = function() { return this[this.length - 1]; }

	var answerDiv = document.createElement('div');
	threadsList.parentNode.insertBefore(answerDiv, threadsList);
	divStack.push(answerDiv);

	var threadMsgs = xpath(".//div[@class='thread_title']", threadsList);
	var maxJoinCnt  = Math.min(threadMsgs.snapshotLength, maxJoinedPostsInThread);
	var afterActive = 0;
	var cntJoined   = 0;
	var rootAbsoluteDepth = 0;

	for(var i = 0; i < threadMsgs.snapshotLength && cntJoined <= maxJoinCnt; i++) {
		var msgDiv   = threadMsgs.snapshotItem(i);
		var isActive = 0;

		// search the currently selected beitrag
		if(msgDiv.innerHTML.match("beitrag_aktiv") ||
		   msgDiv.innerHTML.match("active_post")) {
			afterActive = 1;
			isActive = 1;
			rootAbsoluteDepth = countLevel(msgDiv);
			continue;
		}
		else if(afterActive == 0) continue;

		// count the number of next_levels upwards
		var curRelativeDepth = countLevel(msgDiv) - rootAbsoluteDepth;

		// only show current subnode
		if(curRelativeDepth <= 0) break;

		// find the URL
		var url = grepTitleLinkURL(msgDiv.innerHTML);
		if(!url) {
			log("Error parsing: " + msgDiv.innerHTML);
			continue;
		}

		// create div for the branch
		var divBranch = document.createElement('div');
		if(!isActive)
			divBranch.style.marginLeft = "20px";
		if(displayMode == "colored")
			divBranch.style.borderLeft = branchBorderStyle + " " + getBranchColor(curRelativeDepth);


//		divBranch.innerHTML = '<a style="display: block; font-size: 6px">^^^</a>';

		// create div for the post
		var divPost = document.createElement('div');
		divPost.style.border = "1px dashed #DDDDDD";
		divPost.style.marginLeft = "8px"; // some space from the border

		// decend down to current level
		while(curRelativeDepth < divStack.length) divStack.pop();

		// add it
		divStack.peek().appendChild(divBranch);
		divBranch.appendChild(divPost);

		// create the ireply frame
		appendReplyFrame(divBranch, cntJoined, divPost);

		// remember current branch
		divStack.push(divBranch);

		// grep it
		requestHTML(url, callbackThread, i, divPost);

		cntJoined++;
	}

	if(afterActive == 0) {
		log("found no active post!");
		return;
	}
}

function joinThreadPostsNew()
{
	// do it this way to respect priorities
	var rootPostDiv = xpath1(".//div[@id='forum_wrap']", document.getElementById("mitte"));
	if(rootPostDiv == null)
		rootPostDiv = xpath1("//div[@id='mitte']");
	if(rootPostDiv == null)
		log("Root div not found! (joinThreadPostsNew)");

	var rootPostText = xpath1(".//div[@class='post']", rootPostDiv);
	appendReplyFrame(rootPostText, -1, rootPostDiv);

	var replyLinkA = xpath1(".//a[@class='reply']", rootPostDiv);
	var replyLink = replyLinkA === null ? "" : replyLinkA.href;
	var quoteLinkA = xpath1(".//a[@class='quote']", rootPostDiv);
	var quoteLink = quoteLinkA === null ? "" : quoteLinkA.href;
	processMessageDiv(rootPostDiv, document.location.href, replyLink, quoteLink);

	var threadsList = document.getElementById('tree_thread_list');
	if(threadsList == null) {
		log("Thread tree not found - activate it! (joinThreadPostsNew)");
		return;
	}

	// apply stylesheet once
	addStyles();

	var divStack  = new Array();
	divStack.peek = function() { return this[this.length - 1]; }

	var answerDiv = document.createElement('div');
	threadsList.parentNode.insertBefore(answerDiv, threadsList);
	divStack.push(answerDiv);

	var threadMsgs  = xpath(".//li[contains(@class, 'posting_element')]", threadsList);
	var maxJoinCnt  = Math.min(threadMsgs.snapshotLength, maxJoinedPostsInThread);
	var afterActive = false;
	var cntJoined   = 0;
	var rootAbsoluteDepth = 0;

	for(var i = 0; i < threadMsgs.snapshotLength && cntJoined <= maxJoinCnt; i++) {
		var msgDiv   = threadMsgs.snapshotItem(i);
		var isActive = false;

		// search the currently selected beitrag
		if(msgDiv.className.match("current_posting")) {
			afterActive       = true;
			isActive          = true;
			rootAbsoluteDepth = countLevel(msgDiv);
			continue;
		}
		else if(afterActive === false) continue;

		// count the number of next_levels upwards
		var curRelativeDepth = countLevel(msgDiv) - rootAbsoluteDepth;

		// only show current subnode
		if(curRelativeDepth <= 0) break;

		// find the URL
		var url = grepTitleLinkURL(msgDiv.innerHTML);
		if(!url) {
			log("Error parsing (joinThreadPostsNew): " + msgDiv.innerHTML);
			continue;
		}

		// create div for the branch
		var divBranch = document.createElement('div');
		if(!isActive)
			divBranch.style.marginLeft = "20px";
		if(displayMode == "colored")
			divBranch.style.borderLeft = branchBorderStyle + " " + getBranchColor(curRelativeDepth);

		// create div for the post
		var divPost = document.createElement('div');
		divPost.style.border = "1px dashed #DDDDDD";
		divPost.style.marginLeft = "8px"; // some space from the border

		// descend down to current level
		while(curRelativeDepth < divStack.length) divStack.pop();

		// add it
		divStack.peek().appendChild(divBranch);
		divBranch.appendChild(divPost);

		// create the ireply frame
		appendReplyFrame(divBranch, cntJoined, divPost);

		// remember current branch
		divStack.push(divBranch);

		// grep it
		requestHTML(url, callbackThread, i, divPost);

		cntJoined++;
	}

	if(afterActive === false) {
		log("found no active post! (joinThreadPostsNew)");
		return;
	}
}

function appendReplyFrame(div, id, divPost)
{
	if(div == null)
	{
		log("div to attach reply frame to is null");
		return;
	}

	var iReplyFrame = document.createElement('iframe');
	iReplyFrame.id = "reply" + id;
	iReplyFrame.style.display = 'none';
	iReplyFrame.style.width   = '100%';
	iReplyFrame.style.height  = '35em';
	divPost.setAttribute("replyFrameID", iReplyFrame.id);

	var divReply = document.createElement('div');
	divReply.style.marginLeft = divPost.style.marginLeft;
	divReply.appendChild(iReplyFrame);

	div.appendChild(divReply);
}

function callbackThread(txt, nr, div, url)
{
	if(isNewForum) {
		callbackThreadNew(txt, nr, div, url);
		return;
	}
	try
	{
		// pre-check the posting for performance issues
		// else match can lock up
		if(txt.indexOf('<div class="posting_date">') == -1 ||
		   txt.indexOf('<div class="tovote_links">') == -1) {
			log("No known posting: " + url);
			return;
		}

		var mtchs = txt.match(postingRegExp);
		if(mtchs == null) {
			div.innerHTML = "<i>Could not load comment</i>";
			return;
		}

		var html = mtchs[1];
		div.innerHTML = html;

		var replyLinks = txt.match(/<a href="([^"]*)"\s*>Beantworten<\/a>/);
		var replyLink  = (replyLinks != null) ? replyLinks[1] : "";

		processMessageDiv(div, url, replyLink);
	}
	catch(e)
	{
		log('Error processing post: ' + e);
	}
}

function callbackThreadNew(txt, nr, div, url)
{
	try
	{
		// pre-check the posting for performance issues
		// else match can lock up
		if(txt.indexOf('<div class="thread_view_switch">') === -1) {
			log("No known posting (callbackThreadNew): " + url);
			return;
		}

		var mtchs = txt.match(postingRegExp);
		if(mtchs == null) {
			div.innerHTML = "<i>Could not load comment (new forum)</i>";
			return;
		}

		var html = mtchs[1];
		div.innerHTML = html;

		var replyLinks = txt.match(/<a href="([^"]*)" class="reply"[^>]*>[\s]*Antworten[\s]*<\/a>/);
		var replyLink  = (replyLinks !== null) ? replyLinks[1] : "";

		var quoteLinks = txt.match(/<a href="([^"]*)" class="quote"[^>]*>[\s]*Zitieren[\s]*<\/a>/);
		var quoteLink  = (quoteLinks !== null) ? quoteLinks[1] : "";

		processMessageDiv(div, url, replyLink, quoteLink);
	}
	catch(e)
	{
		log('Error processing post (callbackThreadNew): ' + e);
	}
}

function processMessageDiv(div, messageUrl, replyLink, quoteLink) {
	// parameter quoteLink is only used in the new forum
	if(isNewForum) {
		processMessageDivNew(div, messageUrl, replyLink, quoteLink);
		return;
	}

	// link username to search
	var userI = xpath1(".//div[@class='user_info']/i", div);
	var userName;
	if(userI != null)
	{
		userName = userI.innerHTML.replace(/^(.*?),&nbsp;.*$/, "$1");
		userI.innerHTML = "<a href=\"" + searchUrl + escape(userName) + "\">" + userI.innerHTML + "</a>";

		userI.innerHTML += getPosterScoreBarCode(userName);
	}
	else
	{
		userName = "?";
		log("Could not find username div!");
	}

	// colorize quote
	var quotes = xpath(".//p/span[@class='quote']", div);
	for(var i = 0; i < quotes.snapshotLength; i++) {
		var e = quotes.snapshotItem(i);

		var patternLength = 10;
		var m = e.innerHTML.match(/^((?:&gt;&nbsp;)+)/);
		if(m != null) {
			var color = getQuoteColor(m[0].length / patternLength);
			if(color != "")
				e.innerHTML = "<span style=\"color: " + color + "\">" + e.innerHTML + "</span>";
		}
	}

	// set the reply links
	if(replyLink != "") {
		replyLink       = ensureAbsoluteUrl(replyLink);
		replyLinkInline = "<a style=\"color: #6673DD\" onclick=\"iReply('" + div.getAttribute ("replyFrameID") +
			"', '" + replyLink + "')\">iReply</a>";
		replyLink       = "<a href=\"" + replyLink + "\">Beantworten</a>";

		if(userI != null)
			userI.innerHTML += " --- " + replyLink + (enableIReply ? " / " + replyLinkInline : "");
	}

	// link posting title to posting
	var postingSubject = xpath1(".//h3[@class='posting_subject']", div);
	if(postingSubject != null)
		postingSubject.innerHTML = "<a href=\"" + messageUrl + "\">" + postingSubject.innerHTML + "</a>";
	else
		log("Posting subject not found!");

	// relink voting buttons
	if(enableQuickVote) {
		var voteLinks = xpath(".//div[@class='tovote_links']/a", div);
		for(var i = 0; i < voteLinks.snapshotLength; i++) {
			var voteLink = voteLinks.snapshotItem(i);
			var url = voteLink.href;

			voteLink.removeAttribute("href");
			voteLink.addEventListener("click",
				quickVoteFunctionBuilder(voteLink, url, userName), true);

			voteLink.setAttribute("onclick", "sendVote(this, '" + url + "');");
		}
	}
}

function processMessageDivNew(div, messageUrl, replyLink, quoteLink) {
	// link username to search
	var userI = xpath1(".//span[@class='realname']|.//span[@class='pseudonym']/a|.//span[@class='pseudonym']", div);
	var userName;
	if(userI !== null) {
		userName = userI.textContent;
		userI.innerHTML = "<a href=\"" + searchUrl + escape(userName) + "\">" + userI.innerHTML + "</a>";

		userI.innerHTML += getPosterScoreBarCode(userName);
	}
	else {
		userName = "?";
		log("Could not find username div! (processMessageDivNew)");
	}

	/*
	// colorize quote
	// not necessary any more
	var quotes = xpath(".//blockquote", div);
	for(var i = 0; i < quotes.snapshotLength; i++) {
		var e = quotes.snapshotItem(i);

		var patternLength = 10;
		var lineLength    = 80;
		var m = e.innerHTML;
		if(m != null) {
			var colorNumber = parseInt((m.length % lineLength) / patternLength);
			var color = getQuoteColor(colorNumber);
			if(color != "") {
				// add @grant GM_addStyle to UserScript header !!!
				GM_addStyle(".quotecolor" + colorNumber + " { border-left:2px solid "+color+ "; color: " + color + " }");
				e.setAttribute("className", "quotecolor"+colorNumber);
			}
		}
	}*/

	// set the reply links
	if(replyLink != "") {
		replyLink       = ensureAbsoluteUrl(replyLink);
		var replyLinkInline = "<a style=\"color: #6673DD\" onclick=\"iReply('" + div.getAttribute ("replyFrameID") +
			"', '" + replyLink + "')\">iReply</a>";
		replyLink       = "<a href=\"" + replyLink + "\">Beantworten</a>";

		if(quoteLink != "") {
			quoteLink  = ensureAbsoluteUrl(quoteLink);
			quoteLink  = " | <a href=\"" + quoteLink + "\">Zitieren</a>";
			replyLink += quoteLink;
		}

		if(userI !== null)
			userI.innerHTML += " --- " + replyLink + (enableIReply ? " / " + replyLinkInline : "");
	}

	// link posting title to posting
	var postingSubject = xpath1(".//h1[@class='thread_title']", div);
	if(postingSubject !== null)
		postingSubject.innerHTML = "<a href=\"" + messageUrl + "\">" + postingSubject.innerHTML + "</a>";
	else
		log("Posting subject not found!(processMessageDivNew)");

	// remove vote buttons when not logged in
	var votebuttonLoggedOutSpan = xpath1(".//span[contains(@class, 'rate_post')][@title='Bitte loggen Sie sich ein, um den Beitrag zu bewerten.']", document.getElementById("forum_wrap"));
	if(votebuttonLoggedOutSpan !== null) {
		var votebuttonWrapper = xpath1(".//div[@class='posting_options_wrapper']", div);
		votebuttonWrapper.parentNode.removeChild(votebuttonWrapper);
	}
	// relink voting buttons
	else if(enableQuickVote) {
		var voteLinks = xpath(".//a[contains(@class, 'rate_post')]", div);
		for(var i = 0; i < voteLinks.snapshotLength; i++) {
			var voteLink = voteLinks.snapshotItem(i);
			var url = voteLink.href;
			voteLink.removeAttribute("href");
			/* does it work yet?
 			voteLink.addEventListener("click", function() {
										quickVoteFunctionBuilder(voteLink, url, userName);
										sendVote(this, '' + url + '');
										log("vote sent");
			                          }, true);*/
			voteLink.addEventListener("click",
				quickVoteFunctionBuilder(voteLink, url, userName), true);

			voteLink.setAttribute("onclick", "sendVote(this, '" + url + "');");
		}
	}
}

function quickVoteFunctionBuilder(voteLink, url, author) {
	if(isNewForum)
		return quickVoteFunctionBuilderNew(voteLink, url, author);

	return function() {
		log("voted for author: " + author);

		// mark as voted
		voteLink.style.backgroundColor = "yellow";

		// extract score
		var matches = url.match(/postvote-(-?\d)/);
		if(!matches) return;

		var score = parseInt(matches[1]);
		log("score: " + score);

		// score the author
		scorePoster(author, score);
	};
}

function quickVoteFunctionBuilderNew(voteLink, url, author) {
	return function() {
		log("voted for author (New): " + author);

		// mark as voted
		voteLink.style.backgroundColor = "yellow";

		// extract score
		var matches = url.match(/\/rate\((positive|negative)\/$/);
		if(!matches) return;

		var score = 0;
		if(     matches[1] === "postive")  score = 1;
		else if(matches[1] === "negative") score = -1;

		log("score: " + score);

		// score the author
		scorePoster(author, score);
	};
}

function addStyles() {
	if(top !== self) return;
	// apply style rules from the first post to all posts
	// fixes look and positioning of the voting buttons
	// this is very messy but I want to avoid an external file
	var styleText = "\
	/*#thread #replys li .post .posting_options,#thread .first_posting*/ .post .posting_options{\
		line-height:10px;\
		position:absolute;\
		right:0\
	}\
	/*#thread #replys li .post .posting_options .thanks,#thread .first_posting*/ .post .posting_options .thanks{\
		display:inline-block;\
		margin:-22px 0 0 10px;\
		float:left;\
		line-height:120%;\
		text-align:center\
	}\
	/*#thread #replys li .post .posting_options .thanks .rate_post,#thread .first_posting*/ .post .posting_options .thanks .rate_post{\
		float:left;\
		width:12px;\
		height:12px;\
		overflow:hidden;\
		color:#039;\
		cursor:pointer;\
		text-align:center;\
		font-size:18px;\
		line-height:14px;\
		border:1px solid #ccc;\
		background:#fff;\
		padding:4px\
	}\
	/*#thread #replys li .post .posting_options .thanks .rate_post:hover,#thread .first_posting*/ .post .posting_options .thanks .rate_post:hover{\
		opacity:.8;\
		text-decoration:none\
	}\
	/*#thread #replys li .post .posting_options .thanks .rate_post.negative,#thread .first_posting*/ .post .posting_options .thanks .rate_post.negative{\
		color:#c30000;\
		margin-right:5px;\
		border:1px solid #c30000;\
		line-height:10px\
	}\
	/*#thread #replys li .post .posting_options .thanks .rate_post.negative:hover,#thread #replys li .post .posting_options .thanks .rate_post.negative:visited,#thread .first_posting*/ .post .posting_options .thanks .rate_post.negative:hover,/*#thread .first_posting*/ .post .posting_options .thanks .rate_post.negative:visited{\
		background-color:#c30000;\
		color:#fff\
	}\
	/*#thread #replys li .post .posting_options .thanks .positive,#thread .first_posting*/ .post .posting_options .thanks .positive{\
		color:#2b8f00;\
		border:1px solid #2b8f00\
	}\
	/*#thread #replys li .post .posting_options .thanks .positive:hover,#thread #replys li .post .posting_options .thanks .positive:visited,#thread .first_posting*/ .post .posting_options .thanks .positive:hover,/*#thread .first_posting*/ .post .posting_options .thanks .positive:visited{\
		background-color:#2b8f00;\
		color:#fff\
	}";
	if(typeof GM_addStyle !== "undefined") {
		GM_addStyle(styleText);
	}
	else {
		var copyStyles = document.createElement("style");
		copyStyles.setAttribute("type", "text/css");
		copyStyles.appendChild(document.createTextNode(styleText));
		document.getElementsByTagName("head")[0].appendChild(copyStyles);
	}
}

function getPosterScoreBarCode(author) {
	var absScore = getPosterScore(author);
	if (absScore === undefined) return "";

	// TODO: think about making it logarithmical
	var score = absScore * posterScoreScaleFactor;
	score += 5;
	if(score >= 4 && score <= 6) return "";

	score = Math.min(score, 10);
	score = Math.max(score, 0);
	score += 1;
	score = Math.round(score);

	return "&nbsp;&nbsp;<img src=\"/icons/forum/wertung_" + score + ".gif\" title=\"User-Score: " + absScore + "\"/>";
}

function getPosterScore(author) {
	return getLocalValue("score_" + author, 0);
}
function scorePoster(author, score) {
	var oldScore = getPosterScore(author);
	setLocalValue("score_" + author, oldScore + score);
}

function countLevel(el)
{
	if(isNewForum) {
		return countLevelNew(el);
	}
	var lvl = 1;

	// limit loop, just to be sure
	for(var i=0; i < 10000; i++) {
		var par = el.parentNode;
		el = par;

		if(par == null) break;
		if(par.className == "thread_tree") break;
		if(par.className == "nextlevel" ||
		   par.className == "nextlevel_line")
			lvl++;
	}

	return lvl;
}

function countLevelNew(el)
{
	var lvl = 1;

	// limit loop, just to be sure
	for(var i=0; i < 10000; i++) {
		var par = el.parentNode;
		el = par;

		if(par == null) break;
		if(!par.className) continue;
		if(par.className == "tree_thread_list") break;
		if(par.className.indexOf("posting_element") === 0)
			lvl++;
	}

	return lvl;
}

function insertPostStart(url, nr)
{
	if(isNewForum)
		return url.replace(/(\/page-|_page=)\d+/, "$1" + nr);
	else {
		var eall = "";
		if(document.location.href.match(/\/e-all/)) eall = "/e-all";

		return url.replace(/(\/(list|foren)\/hs)-\d+/, "$1-" + nr + eall);
	}
}

function extractPostStart(url)
{
	var matches;
	if(isNewForum) {
		matches = url.match(/\/page-(\d+)/);
		if(!matches) {
			matches = url.match(/\?forum_page=(\d+)/);
		}
	}
	else {
		matches = url.match(/\/hs-(\d+)/);
	}

	if(!matches) return -1;

	return parseInt(matches[1]);
}

function joinOverviewPages()
{
	if(isNewForum) {
		joinOverviewPagesNew();
		return;
	}

	showOverviewPosterScores(document);

	var pageNrUls   = getElementsByClassName(document, "ul", "forum_navi");
	var threadTrees = getElementsByClassName(document, "ul", "thread_tree");
	if(threadTrees.length == 0)
		threadTrees = getElementsByClassName(document, "ul", "fora_list");

	if(pageNrUls.length   == 0 ||
	   threadTrees.length == 0) {
		log("no forum_navi or thread_tree");
		return;
	}

	var firstPageURL = "";
	var lastPageURL  = "";

	// find the first and last of the page URLs
	var pageLinks = pageNrUls[0].getElementsByTagName("li");
	for(var i = 0; i < pageLinks.length; i++) {
		var pageLink = pageLinks[i];

		if(pageLink.innerHTML.match(/>Neuere</)) break;
		if(pageLink.innerHTML.match(/^\d+$/))    firstPageURL = "";

		// find the URL
		var url = grepTitleLinkURL(pageLink.innerHTML);
		if(!url) continue;

		if(firstPageURL == "")
			firstPageURL = url;
		lastPageURL = url;
	}
	if(firstPageURL == "" || lastPageURL == "") {
		log("found no page URLs");
		return;
	}

	// extract the post numbers
	var firstPostNr = extractPostStart(firstPageURL);
	var lastPostNr  = extractPostStart(lastPageURL);
	if(firstPostNr == -1 || lastPostNr == -1) {
		log("found no post numbers");
		return;
	}

	// limit pages to users setting
	var limited = false;
	if(lastPostNr - firstPostNr > maxJoinedPosts) {
		lastPostNr = firstPostNr + maxJoinedPosts;
		limited = true;
	}

	// add list items and load the overview pages into them
	var threadTree = threadTrees[0];
	for(var j = firstPostNr; j <= lastPostNr; j += overviewPagePostCount) {
		var url = ensureAbsoluteUrl(insertPostStart(lastPageURL, j));

		var li = document.createElement('li');
		li.innerHTML = "<b>Beitr&auml;ge ab Nr. " + j + "</b>";
		threadTree.appendChild(li);

		li = document.createElement('li');
		li.innerHTML = "<i>Lade...</i>";
		threadTree.appendChild(li);

		requestHTML(url, callbackOverviewPage, j, li);
	}

	// add links to navigate
	if(firstPostNr > overviewPagePostCount) {
		var li = document.createElement('li');
		li.innerHTML = "<a href=\"" + ensureAbsoluteUrl(insertPostStart(lastPageURL, firstPostNr - maxJoinedPosts - 3 * overviewPagePostCount)) + "\"><b>Vorwärts...</b></a>";
		threadTree.insertBefore(li, threadTree.childNodes[0]);
	}
	if(limited) {
		var li = document.createElement('li');
		li.innerHTML = "<a href=\"" + ensureAbsoluteUrl(insertPostStart(lastPageURL, lastPostNr + overviewPagePostCount)) + "\"><b>Weiter...</b></a>";
		threadTree.appendChild(li);
	}
}

function joinOverviewPagesNew()
{
	showOverviewPosterScores(document);

	var pageNrElmFirst = xpath1(".//span[@class='seiten']/a[position()=1]",      document.getElementById("thread_sortierung"));
	var pageNrElmLast  = xpath1(".//span[@class='seiten']/a[position()=last()]", document.getElementById("thread_sortierung"));
	if(pageNrElmFirst == null)
		pageNrElmFirst = xpath1(".//a[@class='page'][starts-with(@href, '?forum_page=')][position()=1]",      document.getElementById("forum_wrap"));
	if(pageNrElmLast  == null)
		pageNrElmLast  = xpath1(".//a[@class='page'][starts-with(@href, '?forum_page=')][position()=last()]", document.getElementById("forum_wrap"));
	// only one page, nothing to join
	if(pageNrElmFirst == null || pageNrElmLast == null)
		return;
	var pageNrElmCurr  = xpath1(".//span[@class='seiten']/em/text()", document.getElementById("thread_sortierung"));
	if(pageNrElmCurr === null)
		pageNrElmCurr  = xpath1("./span[@class='active']/text()", document.getElementById("paginierung"));
	if(pageNrElmCurr === null) {
		log("current page element not found joinOverviewPagesNew()");
		return;
	}

	var threadTree = document.getElementById("tree_thread_list");
	if(!threadTree)
		var threadTree = xpath1(".//div[@class='inner_forum_list']/ul[@class='forum_list']", document.getElementById("forum_wrap"));
	if(threadTree == null) {
		log("no tree_thread_list or forum_list (joinOverviewPagesNew)");
		return;
	}

	// the "first page" is actually the first with a link in the navigation bar
	// so when you are on the first page, firstPage is 2 ("Seite 2")
	var firstPageURL = "";
	var lastPageURL  = "";

	// find the first and last of the page URLs
	firstPageURL = ensureAbsoluteUrl(pageNrElmFirst.getAttribute("href"));
	lastPageURL  = ensureAbsoluteUrl(pageNrElmLast.getAttribute("href"));

	if(firstPageURL == "" || lastPageURL == "") {
		log("found no page URLs (joinOverviewPagesNew)");
		return;
	}

	// extract the post numbers
	var firstPageNr   = extractPostStart(firstPageURL);
	var lastPageNr    = extractPostStart(lastPageURL);
	var currentPageNr = parseInt(pageNrElmCurr.textContent);
	if(firstPageNr == -1 || lastPageNr == -1) {
		log("found no page numbers (joinOverviewPagesNew)");
		return;
	}
	if(Number.isNaN(currentPageNr)) {
		log("could not find current page number");
		return;
	}

	// limit pages to users setting
	var limited = false;
	if(lastPageNr - currentPageNr > maxJoinedPosts / overviewPagePostCount) {
		lastPageNr = currentPageNr + maxJoinedPosts / overviewPagePostCount;
		limited = true;
	}
	// add list items and load the overview pages into them
	for(var j = currentPageNr+1; j <= lastPageNr; j++) {
		var url = ensureAbsoluteUrl(insertPostStart(lastPageURL, j));

		var li = document.createElement('li');
		li.innerHTML = "<b>Beitr&auml;ge ab Seite " + j + "</b>";
		threadTree.appendChild(li);

		li = document.createElement('li');
		li.innerHTML = "<i>Lade...</i>";
		threadTree.appendChild(li);

		requestHTML(url, callbackOverviewPage, j, li);
	}

	// add links to navigate
	if(currentPageNr > 1) {
		var li = document.createElement('li');
		li.innerHTML = "<a href=\"" + ensureAbsoluteUrl(insertPostStart(lastPageURL, currentPageNr > (maxJoinedPosts / overviewPagePostCount) ? currentPageNr - maxJoinedPosts / overviewPagePostCount - 1 : 1)) + "\"><b>Vorwärts...</b></a>";
		threadTree.insertBefore(li, threadTree.childNodes[0]);
	}
	if(limited) {
		var li = document.createElement('li');
		li.innerHTML = "<a href=\"" + ensureAbsoluteUrl(insertPostStart(lastPageURL, lastPageNr + 1)) + "\"><b>Weiter...</b></a>";
		threadTree.appendChild(li);
	}
}

function callbackOverviewPage(txt, nr, startli, url)
{
	if(isNewForum) {
		callbackOverviewPageNew(txt, nr, startli, url);
		return;
	}

	var matches = txt.match(/<ul class=\"(thread_tree|fora_list)\">([\s\S]*)<\/ul>[\s\S]*?<ul class="forum_navi">/i);
	if(!matches) {
		startli.innerHTML = "<b><i>Fehler beim Laden</i></b>";
		return;
	}

	var lis = matches[2];
	lis = lis.replace(/\/read(?!\/showthread-1)/g, "/read/showthread-1");
	startli.innerHTML = "<ul style=\"padding-left: 0px; list-style-type: none\">" + lis + "</ul>";

	showOverviewPosterScores(startli);
}
function callbackOverviewPageNew(txt, nr, startli, url)
{
	var matches = txt.match(/<ol id="tree_thread_list">([\s\S]*)<\/ol>[\s\S]*?<!-- Seitenzahl [\d ]+ -->[\s\S]*?<div class="paginierung(?:_thread)?">/i);
	if(!matches)
		matches = txt.match(/<ul class="forum_list">\s*(<li class="forum[\s\S]*)<\/ul>\s*<\/div>\s*<div id="paginierung">/i);
	if(!matches) {
		startli.innerHTML = "<b><i>Fehler beim Laden der Seite</i></b>";
		return;
	}
	var lis = matches[1];
	startli.innerHTML = "" + lis + "";

	showOverviewPosterScores(startli);
}

function showOverviewPosterScores(root) {
	if(isNewForum)
		var userdivs = xpath(".//span[@class='written_by_user']", root);
	else
		var userdivs = xpath(".//div[@class='thread_user']", root);

	for(var i = 0; i < userdivs.snapshotLength; i++) {
		var div = userdivs.snapshotItem(i);
		div.innerHTML += getPosterScoreBarCode(div.innerHTML.trim());
	}
}

function cleanUpReplyPage()
{
	if(isNewForum) {
		cleanUpReplyPageNew();
		return;
	}

	if(enableAutoQuote && document.getElementsByName("message")[0].value == "") {
		// select the right button the ultra hacky way
		document.getElementsByName("quote")[0].click();
		return;
	}

	var form = xpath1("//div[@class='forum_content' or @id='mitte_forum']");
	var html = form.innerHTML;

	// messy but working
	html = html.replace(/(?:Unsere Foren|Dieses Forum)[\s\S]*<textarea/i, "<textarea");
	html = html.replace(/<i>\([^)]+\)<\/i>/ig, "");

	document.body.innerHTML = html;
}

function cleanUpReplyPageNew()
{
	// do not crop the regular answer page (e.g. after click on "Zitat einfügen")
	if(top === self && /\/save\/$/.test(document.location.href))
		return;

	if(enableAutoQuote && document.getElementById("msg_body").value == "") {
		// select the right button the ultra hacky way
		document.getElementsByName("insert_quote")[0].click();
		return;
	}

	var noticep = xpath1(".//p[@class='hinweis']", document.getElementById("composer"));
	noticep.parentNode.removeChild(noticep);
	var form = document.getElementById("reply_create_posting");
	document.body.innerHTML = form.innerHTML;
}

function isWriteUrl(url)
{
	if(isNewForum)
		return url.match(/\/reply\/$/);
	return url.match(/\/write\/$/);
}

function ensureShowThreadLinks()
{
	//there is no equivalent for this in the new forum?
	if(isNewForum) return;

	var links = xpath("//a[contains(@href, '/read/') and not(contains(@href, '/read/showthread-1'))]");

	// we need the tree enabled on all links
	for (var i = 0; i < links.snapshotLength; i++) {
		var link = links.snapshotItem(i);
		link.href = link.href.replace(/\/read\/(?!showthread-1)/, "/read/showthread-1/");
	}
}

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

	ensureShowThreadLinks();

	if(enableIReply) {
		defineScriptInPageContext(
		'function iReply(frameId, replyUrl) {' +
		'var frm = document.getElementById(frameId);' +
		'frm.src = replyUrl;' +
		'frm.style.display = ""' +
		'}');

		// is reply page?
		if((isWriteUrl(document.location.href) || document.body.innerHTML.match(/<textarea name="message"/i) || document.body.innerHTML.match(/<textarea name="body" id="msg_body"/i)) &&
		  (top === undefined || !isWriteUrl(top.location.href))) {
			cleanUpReplyPage();
			return;
		}
	}

	if(enableQuickVote) {
		defineScriptInPageContext(
		'function sendVote(target, voteUrl) {' +
		'var xmlHttp = new XMLHttpRequest();' +
		'xmlHttp.open(\'GET\', voteUrl, true);' +
		'xmlHttp.send(null);' +
		'target.style.backgroundColor = "yellow";' +
		'}');
	}

	// is board overview?
	if(joinSubLevelForen === 1 && (
	   document.location.href.match(/\/forum-\d+\/list/) ||
	   document.location.href.match(/\/foren\/(?:hs-\d+\/)?$/) ||
	   document.location.href.match(/\/forum-\d+(?:\/comment)?\/$/) ||
	   document.location.href.match(/\/forum-\d+\/page-\d+\/$/) ||
	   document.location.href.match(/\/forum-\d+\/\?$/) ||
	   document.location.href.match(/\?forum_page=\d+$/))) {
		joinOverviewPages();
		return;
	}

	// else must be a thread view
	// except the reply site has no thread tree but has comments embedded
	if(!/\/reply\/$/.test(document.location.href))
		joinThreadPosts();
}
main();
})();