Zanoab's Torn Forum Blocker

Blocks forum communication from users

// ==UserScript==
// @name         Zanoab's Torn Forum Blocker
// @version      0.5.1
// @description  Blocks forum communication from users
// @author       Zanoab [1877054]
// @run-at       document-begin
// @namespace    TornCity
// @match        http://www.torn.com/forums.php*
// @match        https://www.torn.com/forums.php*
// @match        http://www.torn.com/laptop.php*
// @match        https://www.torn.com/laptop.php*
// @grant        none
// ==/UserScript==

(function() {
	var LS_KEY_BLOCK_LIST = "block_list";
	
	var blocked;
	var mapCount = 0;
	var entryCount = 0;
	
	function saveBlockList() {
		var data = JSON.stringify(blocked);
		localStorage[LS_KEY_BLOCK_LIST] = data;
	}
	
	function loadBlockList() {
		blocked = null;
		try {
			blocked = JSON.parse(localStorage[LS_KEY_BLOCK_LIST]);
		} catch(e) {}
		if (!blocked) {
			blocked = {user:{}, faction:{}};
		}
	}
	
	function injectForumWatcher() {
		var A = {
			forum_wrap: "#forums-page-wrap",
			top_links_list: "#top-page-links-list",
			
			hide_entry_message_var: {
				thread: "hide_entry_thread",
				post: "hide_entry_post",
			},
			hide_entry_message: {
				true: "Show block messages",
				false: "Hide block messages",
			},
		};
		
		var forumEle = document.querySelector(A.forum_wrap);
		if (!forumEle) return;
		
		var blockMap, linkBar;
		var _styleSheet, styleSheet;
		
		_styleSheet = document.createElement("style");
		_styleSheet.title = this.title;
		document.body.append(_styleSheet);
		styleSheet = _styleSheet.sheet;
		
		function _updateLinkBar() {
			if (!linkBar || !blockMap) return;
			
			var a, b, c, d;
			
			a = linkBar.querySelector(A.top_links_list);
			if (!a.querySelector(".block-hide-entry")) {
				b = document.createElement("a");
				a.insertBefore(b, a.querySelector(".links-footer"));
				b.classList.add("t-clear");
				b.classList.add("h");
				b.classList.add("c-pointer");
				b.classList.add("line-h24");
				b.classList.add("right");
				b.classList.add("m-icon");
				b.onclick = function(evt) {
					var a = !blocked[A.hide_entry_message_var[blockMap.type]];
					blocked[A.hide_entry_message_var[blockMap.type]] = a;
					c.innerText = A.hide_entry_message[a];
					for (var b of blockMap.entries) {
						if (a) {
							b.message.style.display = "none";
						} else {
							b.message.style.display = null;
						}
					}
					saveBlockList();
				};
				c = document.createElement("span");
				d = blocked[A.hide_entry_message_var[blockMap.type]] || false;
				c.innerText = A.hide_entry_message[d];
				b.append(c);
			}
		}
		
		function processThread(main) {
			function PostEntry(blockMap) {
				var entry = this;
				
				var a;
				
				this.blockMap = blockMap;
				this.id = entryCount++;
				blockMap.entries.push(this);
				this.posts = [];
				this.names = [];
				
				// Assign style rule
				// Create new style rule
				a = styleSheet.insertRule(["[block-id='", this.id, "'] {}"].join(""), styleSheet.rules.length);
				this.style = styleSheet.rules[a];
				
				// Create show/hide hidden posts message
				this.message = document.createElement("li");
				this.message.setAttribute("block-entry", this.id);
				if (blocked[A.hide_entry_message_var[blockMap.type]]) {
					this.message.style.display = "none";
				}
				a = document.createElement("div");
				a.classList.add("column-wrap");
				a.classList.add("white-grad");
				a.style.padding = "10px";
				a.style.fontWeight = "bold";
				a.innerHTML = "<div><span class='block-count'>N/A</span> <span class='block-noun'>posts</span> hidden from <span class='block-names'>users</span></div><div>Click here to <span class='block-state'>show</span> hidden <span class='block-noun'>posts</span>.</div>";
				this.message.append(a);
				a = document.createElement("div");
				a.classList.add("post-delimiter");
				this.message.append(a);
				this.message.onclick = function(evt) {
					entry.style.disabled = !entry.style.disabled;
					entry.updateStyle();
				};
				
				this.updateStyle();
			}
			
			PostEntry.prototype.updateStyle = function() {
				var rule = this.style;
				if (rule.disabled) {
					// console.debug("Style disabled");
					rule.style = {};
					this.message.querySelector(".block-state").innerText = "hide";
				} else {
					// console.debug("Style enabled");
					rule.style.display = "none";
					this.message.querySelector(".block-state").innerText = "show";
				}
			};
			
			PostEntry.prototype.addPost = function(post) {
				var a, b;
				this.posts.push(post);
				
				post.setAttribute("block-id", this.id);
				
				var name = post.getAttribute("user-name");
				if (name && !this.names.includes(name)) {
					this.names.push(name);
					this.message.querySelector(".block-names").innerText = this.names.join(", ");
				}
				
				this.message.querySelector(".block-count").innerText = this.posts.length;
				a = this.posts.length == 1;
				this.message.querySelectorAll(".block-noun").forEach(function(ele) {
					ele.innerText = a ? "post" : "posts";
				});
				// if (this.posts.length == 1) {
				// 	this.message.querySelector(".block-noun").innerText = "post";
				// } else {
				// 	this.message.querySelector(".block-noun").innerText = "posts";
				// }
			};

			function PostBlockMap(main) {
				var a;
				
				this.id = mapCount++;
				this.type = "post";
				this.title = ["block-id-", this.id].join("");
				this.main = main;
				this.postList = main.querySelector(".thread-list");
				this.entries = [];
				
				this.evaluate();
			}
			
			PostBlockMap.prototype.updateLinkBar = _updateLinkBar;
			
			PostBlockMap.prototype.updateUserBlocks = function(userId) {
				var userBlock = blocked.user[userId];
				var a = userBlock.block_forum_post;
				for (var b of [...this.postList.querySelectorAll(["[user-id='", userId, "'] .post-wrap .action-wrap .block-user .value"].join(""))]) {
					if (a) {
						b.innerText = "Unblock User";
					} else {
						b.innerText = "Block User";
					}
				}
			};
			
			PostBlockMap.prototype.evaluate = function() {
				// console.debug("Evaluating block map...");
				while(styleSheet.rules.length) {
					styleSheet.removeRule(0);
				}
				
				var a;
				a = this.main.querySelector("#top-page-links-button");
				
				this.entries = [];
				var posts = [...this.postList.children];
				
				var blockMap = this;
				var entry;
				posts.forEach(function(post) {
					if (post.getAttribute("block-entry") != null) {
						// Delete previous block entries
						post.remove();
						return;
					}
					
					if (post.classList.contains("parent-post")) {
						// Ignore original post
						return;
					}
					
					var userId, factionId, userName;
					var a, b;
					// Clean-up previous block
					post.removeAttribute("block-id");
					
					if (post.getAttribute("block-prepped") == null) {
						// Prepare post metadata
						// Extract user id
						userId = "";
						for (a of post.querySelectorAll(".poster > a.user.name[href^='/profiles.php?']")) {
							b = a.getAttribute("href").match(/[?&]XID=(\d+)/);
							if (b) {
								userId = b[1];
								break;
							}
						}
						post.setAttribute("user-id", userId);
						
						// Extract faction Id
						factionId = "";
						for (a of post.querySelectorAll(".poster > a.user.faction[href^='/factions.php?']")) {
							b = a.getAttribute("href").match(/[?&]ID=(\d+)/);
							if (b) {
								factionId = b[1];
								break;
							}
						}
						post.setAttribute("faction-id", factionId);
						
						// Extract user name
						userName = "";
						for (a of post.querySelectorAll(".poster > .user.name [alt]")) {
							b = a.getAttribute("alt").match(/(.*) \[\d+\]/);
							if (b) {
								userName = b[1];
								break;
							}
						}
						post.setAttribute("user-name", userName);
						
						post.setAttribute("block-prepped", "");
					} else {
						// Pull prepped metadata
						userId = post.getAttribute("user-id");
						factionId = post.getAttribute("faction-id");
					}
					
					// Check if blocked
					var userBlock = blocked.user[userId];
					if (!userBlock) {
						userBlock = {
							id: userId,
							name: userName,
						};
						blocked.user[userId] = userBlock;
					}
					var factionBlock = blocked.faction[factionId];
					if (!factionBlock) {
						factionBlock = {
							id: factionId,
						};
						blocked.faction[factionId] = factionBlock;
					}
					
					var block = userBlock.block_forum_post || factionBlock.block_forum_post;
					
					if (!post.querySelector(".block-user")) {
						// Add (un)block user button
						var actionBar = post.querySelector(".post-wrap .action-wrap .right-part > ul");
						a = document.createElement("li");
						a.classList.add("forum-button");
						a.classList.add("block-user");
						a.style.width = "auto";
						a.style.padding = "5px";
						a.style.lineHeight = "22px";
						b = document.createElement("span");
						b.classList.add("value");
						b.innerText = (userBlock && userBlock.block_forum_post) ? "Unblock Posts" : "Block Posts";
						a.append(b);
						var lastClick;
						a.onclick = function(evt) {
							console.debug("Click!");
							var b;
							b = blocked.user[userId];
							
							if (b.block_forum_post) {
								// Unblock posts
								console.debug("Unblocking posts");
								b.block_forum_post = false;
							} else {
								// Block posts
								console.debug("Blocking posts");
								b.block_forum_post = true;
							}
							
							blockMap.updateUserBlocks(userId);
							saveBlockList();
						};
						actionBar.prepend(a);
					}
					
					if (block) {
						// Block post
						if (!entry) {
							entry = new PostEntry(blockMap);
							// console.debug(post);
							blockMap.postList.insertBefore(entry.message, post);
						}
						entry.addPost(post);
					} else {
						// Flush existing entry
						entry = null;
					}
				});
			};
			
			blockMap = new PostBlockMap(main);
		}
		
		function processBoard(main) {
			function ThreadEntry(blockMap) {
				var entry = this;
				
				var a;
				
				this.blockMap = blockMap;
				this.id = entryCount++;
				blockMap.entries.push(this);
				this.threads = [];
				this.names = [];
				
				// Assign style rule
				// Create new style rule
				a = styleSheet.insertRule(["[block-id='", this.id, "'] {}"].join(""), styleSheet.rules.length);
				this.style = styleSheet.rules[a];
				
				// Create show/hide hidden threads message
				this.message = document.createElement("li");
				this.message.setAttribute("block-entry", this.id);
				if (blocked[A.hide_entry_message_var[blockMap.type]]) {
					this.message.style.display = "none";
				}
				a = document.createElement("div");
				a.style.padding = "10px";
				a.style.paddingTop = "5px";
				a.style.paddingBottom = "5px";
				// a.style.fontWeight = "bold";
				a.innerHTML = "<div><span class='block-count'>N/A</span> <span class='block-noun'>threads</span> hidden from <span class='block-names'>users</span></div>";
				this.message.append(a);
				this.message.onclick = function(evt) {
					entry.style.disabled = !entry.style.disabled;
					entry.updateStyle();
				};
				
				this.updateStyle();
			}
			
			ThreadEntry.prototype.addThread = function(thread) {
				var a, b;
				this.threads.push(thread);
				
				thread.setAttribute("block-id", this.id);
				
				var name = thread.getAttribute("user-name");
				if (name && !this.names.includes(name)) {
					this.names.push(name);
					this.message.querySelector(".block-names").innerText = this.names.join(", ");
				}
				
				this.message.querySelector(".block-count").innerText = this.threads.length;
				a = this.threads.length == 1;
				this.message.querySelectorAll(".block-noun").forEach(function(ele) {
					ele.innerText = a ? "thread": "threads";
				});
			};
			
			ThreadEntry.prototype.updateStyle = function() {
				var rule = this.style;
				if (rule.disabled) {
					// console.debug("Style disabled");
					rule.style = {};
					// this.message.querySelector(".block-state").innerText = "hide";
				} else {
					// console.debug("Style enabled");
					rule.style.display = "none";
					// this.message.querySelector(".block-state").innerText = "show";
				}
			};
			
			function ThreadBlockMap(main) {
				var a;
				
				this.id = mapCount++;
				this.type = "thread";
				this.title = ["block-id-", this.id].join("");
				this.main = main;
				this.threadList = main.querySelector(".threads-list");
				this.entries = [];
				
				this.evaluate();
			}
			
			ThreadBlockMap.prototype.updateLinkBar = _updateLinkBar;
			
			ThreadBlockMap.prototype.updateUserBlocks = function(userId) {
				// var userBlock = blocked.user[userId];
				// var a = userBlock.block_forum_post;
				// for (var b of [...this.postList.querySelectorAll(["[user-id='", userId, "'] .post-wrap .action-wrap .block-user .value"].join(""))]) {
				// 	if (a) {
				// 		b.innerText = "Unblock User";
				// 	} else {
				// 		b.innerText = "Block User";
				// 	}
				// }
			};
			
			ThreadBlockMap.prototype.evaluate = function() {
				while(styleSheet.rules.length) {
					styleSheet.removeRule(0);
				}
				
				this.entries = [];
				var threads = [...this.threadList.children];
				
				var blockMap = this;
				var entry;
				threads.forEach(function(thread) {
					if (thread.getAttribute("block-entry") != null) {
						// Delete previous block entries
						thread.remove();
						return;
					}
					
					var userId, userName;
					var a, b, c;
					// Clean-up previous block
					thread.removeAttribute("block-id");
					
					if (thread.getAttribute("block-prepped") == null) {
						// Prepare thread metadata
						// Extract user id and name
						userId = "";
						userName = "";
						a = thread.querySelector(".thread .starter > .user.name[href^='/profiles.php?']");
						if (a) {
							b = a.getAttribute("href").match(/[?&]XID=(\d+)/);
							if (b) {
								userId = b[1];
							}
							a = a.querySelector("[alt]");
							if (a) {
								b = a.getAttribute("alt").match(/(.*) \[\d+\]/);
								if (b) {
									userName = b[1];
								}
							}
						}
						thread.setAttribute("user-id", userId);
						thread.setAttribute("user-name", userName);
						
						thread.setAttribute("block-prepped", "");
					} else {
						// Pull prepped metadata
						thread.getAttribute("user-id");
						thread.getAttribute("user-name");
					}
					
					var userBlock = blocked.user[userId];
					if (!userBlock) {
						userBlock = {
							id: userId,
							name: userName,
						};
						blocked.user[userId] = userBlock;
					}
					
					var block = userBlock.block_forum_thread;
					
					if (!thread.querySelector(".block-user")) {
						// Add (un)block user button
						a = thread.querySelector(".thread-name-wrap");
						b = document.createElement("div");
						b.classList.add("icon-wrap");
						c = document.createElement("i");
						b.append(c);
						// c.setAttribute("title", "Block User");
						c.style.height = "15px";
						c.style.width = "16px";
						a.insertBefore(b, a.querySelector(".arrow-999"));
						b.onclick = function(evt) {
							console.debug("Click!");
							var b;
							b = blocked.user[userId];
							
							if (b.block_forum_thread) {
								// Unblock threads
								console.debug("Unblocking threads");
								b.block_forum_thread = false;
							} else {
								// Block threads
								console.debug("Blocking threads");
								b.block_forum_thread = true;
							}
							
							blockMap.updateUserBlocks(userId);
							saveBlockList();
						};
					}
					
					if (block) {
						// Block thread
						if (!entry) {
							entry = new ThreadEntry(blockMap);
							blockMap.threadList.insertBefore(entry.message, thread);
						}
						entry.addThread(thread);
					} else {
						// Flush existing entry
						entry = null;
					}
				});
			};
			
			blockMap = new ThreadBlockMap(main);
		}
		
		var mainObserver = new MutationObserver(function(evts) {
			evts.forEach(function(evt) {
				// console.debug(evt);
				if (evt.addedNodes.length && evt.addedNodes[0].nodeName == "TITLE") {
					// Filter out loading screen and minor changes
					// console.debug("Content load detected! Ponies!");
					
					loadBlockList();
					
					linkBar = null;
					blockMap = null;
					
					var a;
					for (a of [...evt.addedNodes]) {
						// console.debug(a);
						if (a.nodeType != 1) {
							// Not an element node
							continue;
						}
						
						if (a.classList.contains("content-title")) {
							// Link bar
							linkBar = a;
							if (blockMap) {
								blockMap.updateLinkBar();
							}
							continue;
						}
						if (a.classList.contains("forums-thread-wrap")) {
							// Forum thread view
							processThread(a);
							if (linkBar) {
								blockMap.updateLinkBar();
							}
							continue;
						}
						if (a.classList.contains("forums-committee-wrap")) {
							// Forum board view
							processBoard(a);
							if (linkBar) {
								blockMap.updateLinkBar();
							}
							continue;
						}
						if (a.classList.contains("forums-main-wrap")) {
							// Forum main view
							continue;
						}
					}
				}
			});
		});
		
		mainObserver.observe(forumEle, {
			"childList": true,
		});
	}
	
	function injectLaptopWatcher() {
		var laptopEle = document.querySelector("#computer-content-wrapper");
		if (!laptopEle) return;
		
		var mainObserver = new MutationObserver(function(evts) {
			evts.forEach(function(evt) {
				// console.debug(evt);
				if (evt.addedNodes.length) {
					// Filter out loading screen and minor changes
					if (evt.addedNodes[0].getAttribute && evt.addedNodes[0].getAttribute("id") == "forums-page-wrap") {
						console.debug("Trying to inject forum watcher");
						// Try injecting forum watcher
						injectForumWatcher();
					}
				}
			});
		});
		
		mainObserver.observe(laptopEle, {
			"childList": true,
		});
	}
	
	if (document.querySelector("#forums-page-wrap")) {
		injectForumWatcher();
	}
	if (document.querySelector("#computer-content-wrapper")) {
		injectLaptopWatcher();
	}
})();