Skip to content

Instantly share code, notes, and snippets.

@tb148
Created December 10, 2022 12:54
Show Gist options
  • Save tb148/dc694e9a7ff609ddfa8d6d3c1f4fa114 to your computer and use it in GitHub Desktop.
Save tb148/dc694e9a7ff609ddfa8d6d3c1f4fa114 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name CABS-TB148
// @namespace http://tampermonkey.net/
// @version 0.739085
// @description This description is self-referential.
// @author PkmnQ & TonyBrown148
// @match https://tbgforums.com/forums/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=tbgforums.com
// @grant none
// ==/UserScript==
(function () {
"use strict";
let insert_text = window.insert_text;
let customStyle = document.createElement("style");
customStyle.innerHTML = `
.pun details {
margin: 0.75em !important;
}
.pun button.runjs {
margin: 0.5em !important;
}
.pun .blink {
animation: blink 1s step-end infinite;
}
@keyframes blink {
67% {
opacity: 0;
}
}
.pun .smileydropdown .smileydropdownhover {
border: 1px outset #000;
background-color: #fafafa;
padding: 2px;
}
.pun .smileydropdown .smileydropdowncontent {
display: none;
position: absolute;
background-color: #fff;
border: 1px solid #888;
width: 102px;
padding: 3px;
}
.pun .smileydropdown:hover .smileydropdowncontent {
display: block;
}
.pun .smileydropdown .smileydropdowncontent img {
margin: 1px;
}
.pun .align-center {
text-align: center;
}
.pun .align-right {
text-align: right;
}
.pun .align-justify {
text-align: justify;
}
`;
document.head.appendChild(customStyle);
const separateTags = {
h: {
tag: "h5",
content(data, content) {
return createParagraph(content).childNodes;
},
},
quote: {
tag: "div",
params: { class: "quotebox" },
content(data, content) {
let quote = document.createElement("blockquote");
blocksToHTML(content).forEach((element) => quote.appendChild(element));
if (data) {
let cite = document.createElement("cite");
cite.innerText = `${data} wrote:`;
return [cite, quote];
} else {
return [quote];
}
},
},
code: {
tag: "div",
params: { class: "codebox" },
content(data, content) {
let codeTag = document.createElement("code");
codeTag.innerText = blocksToString(content).trim();
let preTag = document.createElement("pre");
preTag.appendChild(codeTag);
return [preTag];
},
},
list: {
tag(data, content) {
return data === "1" || data === "a" ? "ol" : "ul";
},
params: {
class(data, content) {
return data === "1" ? "decimal" : data === "a" ? "alpha" : "";
},
},
subTags: {
"*": { tag: "li" },
},
},
marquee: { tag: "marquee" },
js: {
tag: "div",
params: { class: "quotebox" },
content(data, content) {
const jsCode = blocksToString(content).trim();
let codeTag = document.createElement("code");
codeTag.innerText = jsCode;
let preTag = document.createElement("pre");
preTag.appendChild(codeTag);
let codebox = document.createElement("div");
codebox.setAttribute("class", "codebox");
codebox.appendChild(preTag);
let summary = document.createElement("summary");
summary.innerText = data ?? "Click to reveal script";
let clickToReveal = document.createElement("details");
clickToReveal.appendChild(summary);
clickToReveal.appendChild(codebox);
let runButton = document.createElement("button");
runButton.innerText = "Run Script";
runButton.setAttribute("class", "runjs");
runButton.onclick = Function(jsCode);
return [clickToReveal, runButton];
},
},
center: { tag: "div", params: { class: "align-center" } },
right: { tag: "div", params: { class: "align-right" } },
justify: { tag: "div", params: { class: "align-justify" } },
div: { tag: "div" },
};
const inlineTags = {
b: { tag: "b" },
u: { tag: "u" },
i: { tag: "i" },
s: { tag: "s" },
c: {
tag: "code",
content(data, content) {
return [document.createTextNode(blocksToString(content))];
},
},
del: { tag: "del" },
ins: { tag: "ins" },
em: { tag: "em" },
sup: { tag: "sup" },
sub: { tag: "sub" },
color: {
tag: "span",
params: {
style(data, content) {
return /^#?\w+$/.test(data) ? `color: ${data}` : "";
},
},
},
bgcolor: {
tag: "span",
params: {
style(data, content) {
return /^#?\w+$/.test(data) ? `background-color: ${data}` : "";
},
},
},
font: {
tag: "span",
params: {
style(data, content) {
return /^[\w ]+$/.test(data) ? `font-family: "${data}"` : "";
},
},
},
size: {
tag: "span",
params: {
style(data, content) {
return /^\d+(?:\.\d+)?$/.test(data) ? `font-size: ${data}pt` : "";
},
},
},
url: {
tag: "a",
params: {
href(data, content) {
let url = data ?? blocksToString(content);
if (url.startsWith("/")) return `https://tbgforums.com/forums${url}`;
else if (/^\w+:/.test(url)) return url;
else return `https://${url}`;
},
},
content(data, content) {
if (data === null) {
let contentText = blocksToString(content);
return [
document.createTextNode(
contentText.length > 55
? contentText.slice(0, 39) + " … " + contentText.slice(-10)
: contentText
),
];
} else return createParagraph(content).childNodes;
},
},
email: {
tag: "a",
params: {
href(data, content) {
return `mailto:${data ?? blocksToString(content)}`;
},
},
},
topic: {
tag: "a",
params: {
href(data, content) {
return `https://tbgforums.com/forums/viewtopic.php?id=${
data ?? blocksToString(content)
}`;
},
},
},
post: {
tag: "a",
params: {
href(data, content) {
return `https://tbgforums.com/forums/viewtopic.php?pid=${
data ?? blocksToString(content)
}#p${data ?? blocksToString(content)}`;
},
},
},
forum: {
tag: "a",
params: {
href(data, content) {
return `https://tbgforums.com/forums/viewforum.php?id=${
data ?? blocksToString(content)
}`;
},
},
},
user: {
tag: "a",
params: {
href(data, content) {
return `https://tbgforums.com/forums/profile.php?id=${
data ?? blocksToString(content)
}`;
},
},
},
img: {
tag: "img",
params: {
src(data, content) {
return blocksToString(content);
},
alt(data, content) {
return data ?? blocksToString(content);
},
title(data, content) {
return data ?? "";
},
},
content: "",
},
abbr: {
tag: "abbr",
params: {
title(data, content) {
return data;
},
},
},
ruby: {
tag: "ruby",
content(data, content) {
let annotation = document.createElement("rt");
annotation.innerText = data;
return blocksToHTML(content).concat([annotation]);
},
},
audio: {
tag: "audio",
params: {
src(data, content) {
return blocksToString(content);
},
controls: "",
},
content(data, content) {
let link = document.createElement("a");
link.innerText = data ?? blocksToString(content);
link.src = blocksToString(content);
return [link];
},
},
video: {
tag: "video",
params: {
src(data, content) {
return blocksToString(content);
},
controls: "",
},
content(data, content) {
let link = document.createElement("a");
link.innerText = data ?? blocksToString(content);
link.src = blocksToString(content);
return [link];
},
},
blink: { tag: "span", params: { class: "blink" } },
};
const smileySrc = {
":)": "https://tbgforums.com/forums/img/smilies/smile.png",
":|": "https://tbgforums.com/forums/img/smilies/neutral.png",
":(": "https://tbgforums.com/forums/img/smilies/sad.png",
":o": "https://tbgforums.com/forums/img/smilies/yikes.png",
":D": "https://tbgforums.com/forums/img/smilies/big_smile.png",
":lol:": "https://tbgforums.com/forums/img/smilies/lol.png",
":/": "https://tbgforums.com/forums/img/smilies/hmm.png",
"D:<": "https://tbgforums.com/forums/img/smilies/mad.png",
";)": "https://tbgforums.com/forums/img/smilies/wink.png",
":P": "https://tbgforums.com/forums/img/smilies/tongue.png",
":roll:": "https://tbgforums.com/forums/img/smilies/roll.png",
"B)": "https://tbgforums.com/forums/img/smilies/cool.png",
":surprised:":
"https://assets.scratch.mit.edu/get_image/.%2E/36df4e46910544fa949032c62774924c.png",
"(rofl)":
"https://assets.scratch.mit.edu/get_image/.%2E/40b7b45901a4f637bd192ee8986f014c.png",
x_x: "https://github.com/lopste/forumoji/blob/main/resources/forumoji/dizzy-face.png?raw=true",
"8D": "https://assets.scratch.mit.edu/get_image/.%2E/6238e33a8e7a31861bec6092e09e19d3.png",
"(puke)":
"https://assets.scratch.mit.edu/get_image/.%2E/d78bcd6a5a85ecc67f66469466c37e5a.png",
":))":
"https://assets.scratch.mit.edu/get_image/.%2E/0cfa01a3f7b92532fd2df975289b5e5d.png",
O_o: "https://assets.scratch.mit.edu/get_image/.%2E/fb97067ee481713737b064921bcd904e.png",
">:D":
"https://github.com/lopste/forumoji/blob/main/resources/forumoji/enraged-face.png?raw=true",
":-X":
"https://assets.scratch.mit.edu/get_image/.%2E/d2841ed560090f302d65d70e30c2effb.png",
":-*":
"https://assets.scratch.mit.edu/get_image/.%2E/def815f4d6bed0165a09e7fabfd28e65.png",
"8-|":
"https://assets.scratch.mit.edu/get_image/.%2E/fd90844a51ec241663fcbd8b34843741.png",
"B-|":
"https://assets.scratch.mit.edu/get_image/.%2E/ad2f6c05182543c78b6d4e5f18d1b36c.png",
":-/":
"https://assets.scratch.mit.edu/get_image/.%2E/22c8e26d86e5c2f01ebecb687dfe7ad0.png",
"(zzz)":
"https://assets.scratch.mit.edu/get_image/.%2E/28dd890aa68f5229ab40da6c76b5a866.png",
":-[":
"https://assets.scratch.mit.edu/get_image/.%2E/aa4f4aba3e137242f2904d28c82743f5.png",
":-S":
"https://assets.scratch.mit.edu/get_image/.%2E/a82d8582a0f3bbe933d1a575e8d1cb6e.png",
"P-)":
"https://assets.scratch.mit.edu/get_image/.%2E/de83178e913758dca54bca6f61737211.png",
"(:))":
"https://assets.scratch.mit.edu/get_image/.%2E/30342ebf7d9921c62d64e96e01d67bee.png",
};
const duplicateSmilies = {
"=)": ":)",
"=|": ":|",
"=(": ":(",
":O": ":o",
"=D": ":D",
":mad:": "D:<",
":p": ":P",
":rolleyes": ":roll:",
":cool:": "B)",
":rofl:": "(rofl)",
":puke:": "(puke)",
":zzz:": "(zzz)",
":sleep:": "(zzz)",
};
function quickInput(event) {
let txtarea = event.target;
let beforeSelection = txtarea.value.slice(0, txtarea.selectionStart);
let selection = txtarea.value.slice(
txtarea.selectionStart,
txtarea.selectionEnd
);
let afterSelection = txtarea.value.slice(txtarea.selectionEnd);
if (event.altKey)
switch (event.key) {
case "Enter": {
// Auto-close tag
let parsedPost = parseBBCode(beforeSelection);
let openTag = parsedPost.findLast(
(block) => block.type === "openTag"
);
if (openTag !== undefined) beforeSelection += `[/${openTag.tagType}]`;
selection = "";
break;
}
}
txtarea.value = beforeSelection + selection + afterSelection;
txtarea.setSelectionRange(
beforeSelection.length,
(beforeSelection + selection).length,
txtarea.selectionDirection
);
}
function parseSmileys(text) {
let nodeList = [];
let smileyRegex = new RegExp(
"(?<=[>\\s]|^)(" +
Object.keys(smileySrc)
.concat(Object.keys(duplicateSmilies))
.map((smiley) => smiley.replace(/([$()*+.\/?[\\\]^\{|\}])/g, "\\$1"))
.join("|") +
")(?=[\\p{Z}]|$)",
"gum"
);
let smileyMatch;
let lastIndex = 0;
while ((smileyMatch = smileyRegex.exec(text)) !== null) {
nodeList.push(
document.createTextNode(text.slice(lastIndex, smileyMatch.index))
);
let smileyStr = smileyMatch[0];
lastIndex = smileyMatch.index + smileyStr.length;
smileyStr =
smileyStr in duplicateSmilies ? duplicateSmilies[smileyStr] : smileyStr;
let smileyImg = document.createElement("img");
smileyImg.setAttribute("src", smileySrc[smileyStr]);
smileyImg.setAttribute("alt", smileyStr);
smileyImg.setAttribute("title", smileyStr);
smileyImg.setAttribute("width", 15);
smileyImg.setAttribute("height", 15);
nodeList.push(smileyImg);
}
nodeList.push(document.createTextNode(text.slice(lastIndex)));
return nodeList;
}
function parseBBCode(bbcode) {
let tagStack = [];
let tagRegex = /\[(\/?[^\[=\]]+)(=[^\[\]]+?)?\]/g;
let tagMatch;
let lastIndex = 0;
while ((tagMatch = tagRegex.exec(bbcode)) !== null) {
if (bbcode.slice(lastIndex, tagMatch.index))
tagStack.push({
type: "text",
content: bbcode.slice(lastIndex, tagMatch.index),
pos: [lastIndex, tagMatch.index],
});
lastIndex = tagMatch.index + tagMatch[0].length;
let tagName = tagMatch[1];
let tagData = tagMatch[2] ? tagMatch[2].slice(1) : null;
if (tagName[0] === "/") {
tagName = tagMatch[0].slice(2, -1);
if (tagStack.length === 0)
tagStack.push({ type: "text", content: tagMatch[0] });
else {
let tagIndex = tagStack.findLastIndex(
(block) => block.type === "openTag" && block.tagType === tagName
);
if (tagIndex === -1)
tagStack.push({ type: "text", content: tagMatch[0] });
else {
let tagBlock = { type: "tag", tagType: tagName };
tagBlock.content = fixTags(tagStack.splice(tagIndex + 1));
let openTag = tagStack.pop();
tagBlock.tagData = openTag.tagData;
tagBlock.index = [openTag.index, [tagMatch.index, lastIndex]];
if (
tagBlock.tagType == "quote" &&
tagBlock.tagData == "TonyBrown148"
) {
console.log("HACKED!");
tagBlock.tagType = "div";
}
tagStack.push(tagBlock);
}
}
} else
tagStack.push({
type: "openTag",
tagType: tagName,
tagData: tagData,
index: [tagMatch.index, lastIndex],
});
}
if (bbcode.slice(lastIndex))
tagStack.push({
type: "text",
content: bbcode.slice(lastIndex),
pos: [lastIndex, null],
});
return tagStack;
}
function fixTags(blocks) {
return blocks.map((block) =>
block.type === "openTag"
? {
type: "text",
content:
block.tagData === null
? `[${block.tagType}]`
: `[${block.tagType}=${block.tagData}]`,
}
: block
);
}
function blocksToString(blockStack) {
let blockString = "";
for (let block of blockStack) {
if (block.type === "text") blockString += block.content;
else if (block.tagData)
blockString += `[${block.tagType}=${block.tagData}]${blocksToString(
block.content
)}[/${block.tagType}]`;
else
blockString += `[${block.tagType}]${blocksToString(block.content)}[/${
block.tagType
}]`;
}
return blockString;
}
function blocksToHTML(blockStack, subTags = {}) {
let paragraph = [];
let htmlNodes = [];
for (let block of blockStack) {
if (
block.type === "tag" &&
(block.tagType in separateTags || block.tagType in subTags)
) {
if (/\S/.test(blocksToString(paragraph)))
htmlNodes.push(createParagraph(paragraph));
paragraph = [];
let tagObj = subTags[block.tagType] || separateTags[block.tagType];
let blockTag = document.createElement(
typeof tagObj.tag === "function"
? tagObj.tag(block.tagData, block.content)
: tagObj.tag
);
if (tagObj.params)
for (let paramName of Object.keys(tagObj.params)) {
let paramVal = tagObj.params[paramName];
if (typeof paramVal === "string")
blockTag.setAttribute(paramName, paramVal);
else
blockTag.setAttribute(
paramName,
paramVal(block.tagData, block.content)
);
}
let blockContent = tagObj.content;
switch (typeof blockContent) {
case "undefined":
blocksToHTML(block.content, tagObj.subTags ?? {}).forEach(
(element) => blockTag.appendChild(element)
);
break;
case "string":
blockTag.innerText = blockContent;
break;
case "function":
blockContent(block.tagData, block.content).forEach((element) =>
blockTag.appendChild(element)
);
break;
}
htmlNodes.push(blockTag);
} else {
paragraph.push(block);
}
}
if (/\S/.test(blocksToString(paragraph)))
htmlNodes.push(createParagraph(paragraph));
return htmlNodes;
}
function createParagraph(blockStack) {
let paragraph = document.createElement("p");
if (blockStack[0]?.type === "text")
blockStack[0].content = blockStack[0].content.trimStart();
if (blockStack[blockStack.length - 1]?.type === "text")
blockStack[blockStack.length - 1].content =
blockStack[blockStack.length - 1].content.trimEnd();
for (let block of blockStack) {
if (block.type === "tag" && block.tagType in inlineTags) {
let blockTag = document.createElement(inlineTags[block.tagType].tag);
if (inlineTags[block.tagType].params)
for (let paramName of Object.keys(inlineTags[block.tagType].params)) {
let paramVal = inlineTags[block.tagType].params[paramName];
if (typeof paramVal === "string")
blockTag.setAttribute(paramName, paramVal);
else
blockTag.setAttribute(
paramName,
paramVal(block.tagData, block.content)
);
}
let blockContent = inlineTags[block.tagType].content;
switch (typeof blockContent) {
case "undefined":
[...createParagraph(block.content).childNodes].forEach((element) =>
blockTag.appendChild(element)
);
break;
case "string":
blockTag.innerText = blockContent;
break;
case "function":
[...blockContent(block.tagData, block.content)].forEach((element) =>
blockTag.appendChild(element)
);
break;
}
paragraph.appendChild(blockTag);
} else {
let blockText = blocksToString([block]);
let notFirstLine = false;
for (let line of blockText.split("\n")) {
if (notFirstLine) paragraph.appendChild(document.createElement("br"));
else notFirstLine = true;
parseSmileys(line).forEach((element) =>
paragraph.appendChild(element)
);
}
}
}
return paragraph;
}
function smileyDropdown(element) {
element.innerHTML = "";
element.setAttribute("class", "smileydropdown");
let dropdownHover = document.createElement("img");
dropdownHover.setAttribute(
"src",
"https://tbgforums.com/forums/img/smilies/smile.png"
);
dropdownHover.setAttribute("class", "smileydropdownhover");
element.appendChild(dropdownHover);
let smileyList = document.createElement("div");
smileyList.setAttribute("class", "smileydropdowncontent");
for (let smiley in smileySrc) {
let smileyOption = document.createElement("img");
smileyOption.setAttribute("alt", smiley);
smileyOption.setAttribute("title", smiley);
smileyOption.onclick = () => insert_text(smiley, "");
smileyOption.setAttribute("src", smileySrc[smiley]);
smileyOption.setAttribute("width", 15);
smileyOption.setAttribute("height", 15);
smileyList.appendChild(smileyOption);
}
element.appendChild(smileyList);
}
switch (window.location.pathname) {
case "/forums/viewtopic.php": {
for (let post of document.querySelectorAll(".blockpost")) {
let postOnclick = post
.querySelector(".postquickquote a")
.getAttribute("onclick");
let postBBCode = JSON.parse(
`"${
postOnclick.match(/^Quote\('.+?', '(.+?)'\); return false;$/)[1]
}"`
.replace(/\\'/g, "'")
.replace(/\t/g, "\\t")
);
let postMsg = post.querySelector(".postmsg");
try {
let postEdit = post.querySelector(".postedit");
postMsg.innerHTML = "";
blocksToHTML(fixTags(parseBBCode(postBBCode))).forEach((element) =>
postMsg.appendChild(element)
);
if (postEdit && postEdit.nodeName === "P")
postMsg.appendChild(postEdit);
} catch (e) {
console.error(
e.lineNumber
? `Could not parse post id ${post.id}. Error on line ${
e.lineNumber - 2
}: ${e}`
: `Could not parse post id ${post.id}. ${e}`
);
postMsg.innerHTML = "";
let cnp = document.createElement("p");
cnp.appendChild(
document.createTextNode("(could not convert to HTML)")
);
postMsg.appendChild(cnp);
let error = document.createElement("pre");
error.appendChild(
document.createTextNode(
e.lineNumber ? `Error on line ${e.lineNumber - 2}.\n${e}` : `${e}`
)
);
let errorBox = document.createElement("div");
errorBox.setAttribute("class", "codebox");
errorBox.appendChild(error);
postMsg.appendChild(errorBox);
let postContent = document.createElement("pre");
postContent.appendChild(document.createTextNode(postBBCode));
let postContentBox = document.createElement("div");
postContentBox.setAttribute("class", "codebox");
postContentBox.appendChild(postContent);
postMsg.appendChild(postContentBox);
}
}
smileyDropdown(document.querySelector(".infldset div"));
document.querySelector("textarea").onkeydown = quickInput;
if (location.hash !== "") {
document.querySelector(location.hash).scrollIntoView();
}
break;
}
case "/forums/post.php":
case "/forums/edit.php": {
let post = document.querySelector(".postmsg");
let postBBCode = document.querySelector("textarea").value;
if (post)
try {
post.innerHTML = "";
blocksToHTML(fixTags(parseBBCode(postBBCode))).forEach((element) =>
post.appendChild(element)
);
} catch (e) {
console.error(
e.lineNumber
? `Could not parse preview post. Error on line ${
e.lineNumber - 2
}: ${e}`
: `Could not parse preview post. ${e}`
);
post.innerHTML = "";
let cnp = document.createElement("p");
cnp.appendChild(
document.createTextNode("(could not convert to HTML)")
);
post.appendChild(cnp);
let error = document.createElement("pre");
error.appendChild(
document.createTextNode(
e.lineNumber ? `Error on ${e.lineNumber - 2}.\n${e}` : `${e}`
)
);
let errorBox = document.createElement("div");
errorBox.setAttribute("class", "codebox");
errorBox.appendChild(error);
post.appendChild(errorBox);
let postContent = document.createElement("pre");
postContent.appendChild(document.createTextNode(postBBCode));
let postContentBox = document.createElement("div");
postContentBox.setAttribute("class", "codebox");
postContentBox.appendChild(postContent);
post.appendChild(postContentBox);
}
smileyDropdown(document.querySelector(".infldset div"));
document.querySelector("textarea").onkeydown = quickInput;
break;
}
}
window.parseBBCode = parseBBCode;
window.blocksToHTML = blocksToHTML;
window.createParagraph = createParagraph;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment