8chan.moe Penis Grabber

love from /vyt/ <3

// ==UserScript==
// @name        8chan.moe Penis Grabber
// @namespace   gock
// @match       https://8chan.moe/*
// @match       https://8chan.se/*
// @grant       none
// @version     0.01
// @author      gock
// @license     MIT
// @description love from /vyt/ <3
// @run-at      document-start
// @noframes
// ==/UserScript==



//copied and modified from thread.js to close qr after posting (closes on error too?)
function closeQrAfterPosting()
{
  window.addEventListener("load", () => {
    //clobbers
    thread.replyCallback.stop = function() {

      thread.replyButton.value = thread.originalButtonText;

      qr.setQRReplyText(thread.originalButtonText);

      thread.replyButton.disabled = false;
      qr.setQRReplyEnabled(true);
      qr.removeQr();
    };
  });
}


//move qr button to above everything else so you just need to press tab once
function moveReplyButton()
{
  document.addEventListener("DOMContentLoaded", () => {
    const qrButton = document.getElementById("quick-reply").childNodes[2].childNodes[13];
    const target = document.getElementById("qrFilesBody");
    const parent = document.getElementById("quick-reply").childNodes[2];
    parent.insertBefore(qrButton, target);
  });
}

//move qr button to above everything else so you just need to press tab once
function moveThreadInfo()
{
  document.addEventListener("DOMContentLoaded", () => {
    const threadInfo = document.getElementById("postCount").parentNode;
    const target = document.getElementById("navOptionsSpan");
    const parent = document.getElementById("dynamicHeaderThread");
    parent.insertBefore(threadInfo, target);

    document.getElementById("fileCount").setAttribute("title", "files");
    document.getElementById("idCount").setAttribute("title", "IDs");
    document.getElementById("postCount").setAttribute("title", "Posts");

    var style = document.createElement("style");
    style.textContent = `
    .threadInfo {
      color: var(--text-color);
      user-select: none;
    }
    .threadInfo::before {
      content: "[";
      color: var(--navbar-text-color);
      padding-right: 5px;
    }
    .threadInfo::after {
      content: "]";
      color: var(--navbar-text-color);
      padding-left: 5px;
      padding-right: 3px;
    }

    .threadInfo #postCount::before {
      content: "" ;
    }
    .threadInfo #idCount::before {
      content: "/" ;
      color: var(--navbar-text-color);
      padding-left: 5px;
      padding-right: 5px;
    }
    .threadInfo #fileCount::before {
      content: "/" ;
      color: var(--navbar-text-color);
      padding-left: 5px;
      padding-right: 5px;
    }
    `;

    document.head.appendChild(style);
  });
}

//counterpart to posting.markPostAsYou()
function unmarkPostAsYou(id, obj)
{
  var post = obj || document.getElementById(+id);
  if (!post) return;

  var author = post.querySelector(".linkName");
  if (!author) return;

  author.classList.remove("youName");
}

//counterpart to api.addYou()
function removeYou(boardUri, postId) //(string, int)
{
  var yous = JSON.parse(localStorage[boardUri + "-yous"] || "[]");
  var index = yous.indexOf(postId);
  var indexPosting = posting.yous.indexOf(postId);
  if (index === -1 || indexPosting === -1) {
    console.log("tried to remove nonexistant (you)");
    return;
  }
  yous.splice(index, 1);
  posting.yous.splice(indexPosting, 1); //in place
  localStorage.setItem(boardUri + "-yous", JSON.stringify(yous));
}


//fixes clicking on IDs to highlight them
//also adds nicknames
function fixClickOnId()
{
  new MutationObserver((_, observer) => {
    const scriptTag = document.querySelector("script[src*='multiboardMenu.js']");
    if (scriptTag) {
      observer.disconnect();

      //copypaste and modified
      posting.processIdLabel = function(label) {
        if (label === undefined)
          return;

        var id = label.innerText;
        var array = posting.idsRelation[id] || [];
        var cell = label.parentNode.parentNode.parentNode;

        //add nickname   probably best to untie this from this method

        //end nickname

        if (cell.parentNode.className === 'inlineQuote'
            || cell.parentNode.className === 'quoteTooltip') {
        } else {
          posting.idsRelation[id] = array;
          //bad performance ?
          if (array.indexOf(cell) === -1) {
            array.push(cell);
          }
        }

        label.onmouseover = function() {
          label.innerText = id + ' (' + array.length + ')';
        }

        label.onmouseout = function() {
          label.innerText = id;
        }

        label.onclick = function() {
          var index = posting.highLightedIds.indexOf(id);
          //window.location.hash = '_'; // whats the point?

          if (index > -1) {
            posting.highLightedIds.splice(index, 1);
          } else {
            posting.highLightedIds.push(id);
          }

          for (var i = 0; i < array.length; i++) {
            var cellToChange = array[i];

            if (cellToChange.className === 'innerOP') {
              continue;
            }

            if (index > -1) { /*? 'innerPost' : 'markedPost';*/
              cellToChange.classList.remove("markedPost");
            }

            if (index === -1) { /*? 'innerPost' : 'markedPost';*/
              cellToChange.classList.add("markedPost");
            }
          }

        };

      };
      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", () => {
          document.querySelectorAll(".linkSelf").forEach(e => posting.parseExistingPost(e, true, false, true, true));
        });
      } else {
        document.querySelectorAll(".linkSelf").forEach(e => posting.parseExistingPost(e, true, false, true, true));

      }
    }
  }).observe(document.documentElement, {childList: true, subtree:true });
}


function addWatcherShortcut()
{
  document.addEventListener("DOMContentLoaded", () => {
    document.addEventListener("keydown", (e) => {
      if (e.key === "w") {
        //copypasted from watcher.processOP
          var op = document.querySelector(".opHead.title");
          var checkBox = op.getElementsByClassName('deletionCheckBox')[0];

          var nameParts = checkBox.name.split('-');

          var board = nameParts[0];
          var thread = nameParts[1];

          var storedWatchedData = watcher.getStoredWatchedData();

          var boardThreads = storedWatchedData[board] || {};

          if (boardThreads[thread]) {
            return;
          }

          var subject = op.getElementsByClassName('labelSubject');
          var message = op.getElementsByClassName('divMessage')[0];

          var label = (subject.length ? subject[0].innerText : null)
              || message.innerHTML.replace(/(<([^>]+)>)/ig, "").substr(0, 16).trim();

          if (!label.length) {
            label = null;
          } else {
            label = label.replace(/[<>"']/g, function(match) {
              return api.htmlReplaceTable[match]
            });
          }

          boardThreads[thread] = {
            lastSeen : new Date().getTime(),
            lastReplied : new Date().getTime(),
            label : label
          };

          storedWatchedData[board] = boardThreads;

          localStorage.watchedData = JSON.stringify(storedWatchedData);

          watcher.addWatchedCell(board, thread, boardThreads[thread]);
      }
    });
  });

}


function keepWatcherOpen()
{
  document.addEventListener("DOMContentLoaded", () => {
    document.getElementById("watchedMenu").style.display = "flex";
  });
}


function toggleYou(post)
{
  if (posting.yous.indexOf(parseInt(post.postInfo.post)) === -1) {
    //not you
    api.addYou(post.postInfo.board, parseInt(post.postInfo.post));
    posting.markPostAsYou(post.postInfo.post, document.getElementById(post.postInfo.post));
  } else {
    //you
    removeYou(post.postInfo.board, parseInt(post.postInfo.post));
    unmarkPostAsYou(post.postInfo.post, document.getElementById(post.postInfo.post));
  }

  reloadPosts(true, true, false, false, false, true);
}


function showNickname(cell)
{
  var id = cell.querySelector(".title").querySelector(".spanId").querySelector(".labelId").innerText;
  //cell is innerPost


  let board = cell.parentNode.getAttribute("data-boarduri");
  //this is requiered as inlinequotes dont have a parent with data-boarduri
  //recursively searching until you reach an element with data-boarduri doesn't
  //work either because it isn't placed in the document when this is called
  //also hover quote things are placed at the very bottom as a child of body
  if (board === null) {
    board = window.location.pathname.split("/")[1];
  }


  var title = cell.querySelector(".title");
  let nicknames= JSON.parse(localStorage.PG_nicknames || null);
  if (nicknames !== null) {
    if (nicknames[board] !== undefined) {
      if (nicknames[board][id] !== undefined) {
        let nickname = nicknames[board][id];
        let currentNickElement = title.querySelector(".nickname");

        if (currentNickElement == undefined) {
          var nickElement = document.createElement("span");
          nickElement.className = "nickname";
          nickElement.innerHTML = nickname + " ";

          title.insertBefore(nickElement, title.querySelector(".labelCreated"));
        } else if (currentNickElement.innerHTML !== nickname + " ") {
          currentNickElement.innerHTML = nickname + " ";
        }
      }
    }
  }
}


function addNickname(post)
{

  var board = post.postInfo.board.toString();
  var id = post.postInfo.id.toString();
  var name = prompt("Enter Nickname").toString();
  //if cancelled
  if (name === null) {
    return;
  }

  var nicknames = JSON.parse(localStorage.PG_nicknames || "{}");

  if (nicknames[board] === undefined) {
    nicknames[board] = {};
  }

  nicknames[board][id] = name;
  localStorage.setItem("PG_nicknames", JSON.stringify(nicknames));

  //only Nicknames
  reloadPosts(true, true, true, false, true);
}

//modified to spaghetti mode + number signs after quotelinks
function overwriteParseExistingPost()
{
  new MutationObserver((_, observer) => {
    const scriptTag = document.querySelector("script[src*='multiboardMenu.js']");
    if (scriptTag) {
      observer.disconnect();

      posting.parseExistingPost = function(linkSelf, noExtras, noModify, noTooltips, onlyIds, onlyNicknames, onlyTooltips, onlyPG_hideButton) {
        if (posting.postCellTemplate.template.contains(linkSelf)) {
          return;
        }
        var innerPost = linkSelf.parentNode.parentNode;
        var postInfo = api.parsePostLink(linkSelf.href);
        posting.getExtraInfo(innerPost, postInfo);

        var ret = {};
        ret.postInfo = postInfo;
        ret.linkSelf = linkSelf;
        ret.innerPost = innerPost;
        ret.files = innerPost.getElementsByClassName('panelUploads')[0];
        ret.message = innerPost.getElementsByClassName("divMessage")[0];

        //used to apply the ID fix to posts already loaded at the start
        //new posts have it already applied
        if (onlyIds) {
          posting.processIdLabel(ret.innerPost.getElementsByClassName("labelId")[0]);
          return;
        }

        if (onlyNicknames) {
          showNickname(ret.innerPost);
          return;
        }

        if (onlyTooltips) {
          posting.addExternalExtras(ret, noTooltips, onlyTooltips);
          return;
        }

        //this "onlyxyz" shit is stupid. replace with enums or something

        //new
        if (PG_settings.addHideButton)
          addPG_hideButton(ret.innerPost);
        //end new

        showNickname(ret.innerPost);

        //quotelink # link thing
        //ret.innerPost.querySelectorAll(".quoteLink, .panelBacklinks>a").forEach((e) => {
        ret.innerPost.querySelectorAll(".quoteLink").forEach((e) => {
          let linkTarget = e.getAttribute("href");
          let noSign = document.createElement("a");
          noSign.setAttribute("href", linkTarget);
          noSign.setAttribute("class", "numberSign");
          noSign.innerText = "#";
          let spacer = document.createTextNode(" ");
          e.after(spacer);
          spacer.after(noSign);

        });

        if (noModify)
          return;

        //cache a clone of the node with alt before it gets additional backlinks
        if (typeof tooltips !== "undefined" && !noTooltips) {
          tooltips.addToKnownPostsForBackLinks(innerPost);
          tooltips.postCache[linkSelf.href] = innerPost.cloneNode(true);
        }

        //update with local times
        var labelCreated = innerPost.getElementsByClassName('labelCreated')[0];
        if (posting.localTime) {
          posting.setLocalTime(labelCreated);
        }

        if (posting.relativeTime) {
          posting.addRelativeTime(labelCreated);
        }

        //thumbnail hovering/hiding
        if (typeof thumbs !== "undefined") {
          Array.from(innerPost.getElementsByClassName('uploadCell'))
            .forEach((cell) => thumbs.processUploadCell(cell));
        }

        if (typeof embed !== "undefined") {
          Array.from(ret.message.getElementsByTagName("a"))
            .forEach((embedLink) => embed.processLinkForEmbed(embedLink));
        }

        if (typeof hiding !== "undefined") {
          hiding.hideIfHidden(ret, hiding.checkFilterHiding(linkSelf));
        }

        if (!noExtras)
          posting.addExternalExtras(ret, noTooltips, onlyTooltips);



        return ret;
      };
    }
  }).observe(document.documentElement, {childList: true, subtree:true });
}


function addPG_hideButton(innerPost)
{
  if (innerPost.parentNode.classList.contains("inlineQuote"))
    return;

  postInfo = innerPost.getElementsByClassName("postInfo")[0];
  if (postInfo === undefined) {
    return; //OP
  }

  let linkSelf = postInfo.getElementsByClassName("linkSelf")[0];

  var PG_hideButton = document.createElement("span");
  PG_hideButton.innerText = "[−]"; //minus sign
  PG_hideButton.classList.add("glowOnHover");
  PG_hideButton.addEventListener("click", function () {
    hiding.hidePost(linkSelf);
  });

  //try to position the [-] after the checkbox but before everything else
  //breaks on new posts because of the fucked up overwriteParseExistingPost()
  //the hide button (no smoking sign) is created after this function is called
  //postInfo.insertBefore(PG_hideButton, postInfo.getElementsByClassName("hideButton")[0]);
  //if (postInfo.getElementsByClassName("deletionCheckBox")[0] === undefined) {
    //no deletioncheckbox > inline
  //  postInfo.insertBefore(PG_hideButton, postInfo.getElementsByClassName("hideButton")[0]);
  //} else {
  //  //yes deletioncheckbox > not inline
  //  postInfo.getElementsByClassName("deletionCheckBox")[0].after(PG_hideButton);
  //}
  // I give up. I hate the checkbox anyway
  postInfo.prepend(PG_hideButton);
}

//modified to spaghetti mode + remove you class after you unmark as you
function overwriteAddExternalExtras()
{
  new MutationObserver((_, observer) => {
    const scriptTag = document.querySelector("script[src*='multiboardMenu.js']");
    if (scriptTag) {
      observer.disconnect();
      posting.addExternalExtras = function(ret, noTooltips, onlyTooltips) {
        var innerPost = ret.innerPost;
        var linkSelf = ret.linkSelf;
        var postInfo = ret.postInfo;

        if (!onlyTooltips) {

        posting.processIdLabel(innerPost.getElementsByClassName("labelId")[0]);

        //(You)s
        if (posting.yous && posting.yous.indexOf(+postInfo.post) !== -1) {
          posting.markPostAsYou(postInfo.post, innerPost);
        }

        //load posting menu, hiding menu, and watcher
        //TODO: coalesce files?
        if (typeof postingMenu !== "undefined") {
          interfaceUtils.addMenuDropdown(ret, "Post Menu",
            "extraMenuButton", postingMenu.buildMenu);
        }

        if (typeof hiding !== "undefined") {
          interfaceUtils.addMenuDropdown(ret, "Hide",
            "hideButton", hiding.buildMenu);
        }

        if (typeof watcher !== "undefined") {
          if (postInfo.op)
            watcher.processOP(innerPost);
        }

        if (typeof qr !== "undefined") {
          var linkQuote = innerPost.getElementsByClassName('linkQuote')[0];

          linkQuote.onclick = function() {
            qr.showQr(linkQuote.href.match(/#q(\d+)/)[1]);
          };
        }

        }//endif !onlytooltips

        if (typeof tooltips !== "undefined") {
          Array.from(innerPost.getElementsByClassName('quoteLink'))
            .forEach((quote) => {
              var target = api.parsePostLink(quote.href);
              tooltips.processQuote(quote, false, noTooltips);

              if (!posting.yous) {
                return;
              }

              if (api.boardUri === target.board && posting.yous.indexOf(+target.post) !== -1) {
                quote.classList.add("you");
              } else {
                quote.classList.remove("you");
              }

            });
        }
      };
    }
  }).observe(document.documentElement, {childList: true, subtree:true });
}

//modified for number signs on backlinks
function overwriteAddBackLink()
{
    new MutationObserver((_, observer) => {
      const scriptTag = document.querySelector("script[src*='hiding.js']");
      if (scriptTag) {
        observer.disconnect();

        tooltips.addBackLink = function(quote, quoteTarget, containerPost) {

          var knownBoard = tooltips.knownPosts[quoteTarget.board];

          if (!knownBoard)
            return;

          var knownBackLink = knownBoard[quoteTarget.post];

          if (!knownBackLink)
            return;

          var sourceBoard = containerPost.dataset.boarduri;
          var sourcePost = containerPost.id;

          var sourceId = sourceBoard + '_' + sourcePost;

          if (knownBackLink.added.indexOf(sourceId) > -1) {
            return;
          } else {
            knownBackLink.added.push(sourceId);
          }

          var text = '>>';

          if (sourceBoard != quoteTarget.board) {
            text += '/' + containerPost.dataset.boarduri + '/';
          }

          text += sourcePost;

          var backLink = document.createElement('a');
          backLink.innerText = text;

          backLink.href = '/' + sourceBoard + '/res/' + quoteTarget.thread + '.html#'
              + sourcePost;

          var backLinkNoSign = document.createElement("a");

          backLinkNoSign.setAttribute("href", backLink.href);
          backLinkNoSign.setAttribute("class", "numberSign");
          backLinkNoSign.innerText = "#";
          var spacer = document.createTextNode(" ");

          knownBackLink.container.appendChild(backLink);
          knownBackLink.container.appendChild(spacer.cloneNode(true));
          knownBackLink.container.appendChild(backLinkNoSign);
          knownBackLink.container.appendChild(spacer.cloneNode(true));

          tooltips.processQuote(backLink, true);

          var dupe = backLink.cloneNode(true);
          var dupe2 = backLinkNoSign.cloneNode(true);
          knownBackLink.altContainer.appendChild(dupe);
          knownBackLink.altContainer.appendChild(spacer.cloneNode(true));
          knownBackLink.altContainer.appendChild(dupe2);
          knownBackLink.altContainer.appendChild(spacer.cloneNode(true));

          tooltips.processQuote(dupe, true);

        };
     }
  }).observe(document.documentElement, {childList: true, subtree:true });
}

//fix recursive backlinks + remove X from inlines
function overwriteAddInlineClick()
{
  new MutationObserver((_, observer) => {
    const scriptTag = document.querySelector("script[src*='hiding.js']");
    if (scriptTag) {
      observer.disconnect();

      tooltips.addInlineClick = function(quote, innerPost, isBacklink, quoteTarget, sourceId) {

        quote.addEventListener("click", function(e) {
            if (!tooltips.inlineReplies)
              return;

            e.preventDefault();
            var replyPreview = Array.from(innerPost.children)
              .find((a) => a.className === "replyPreview");
            var divMessage = innerPost.getElementsByClassName("divMessage")[0];

            if (tooltips.loadingPreviews[quoteTarget.quoteUrl] ||
            tooltips.quoteAlreadyAdded(quoteTarget.quoteUrl, innerPost))
              return;

            var placeHolder = document.createElement("div");
            placeHolder.style.whiteSpace = "normal";
            placeHolder.className = "inlineQuote";
            tooltips.loadTooltip(placeHolder, quoteTarget.quoteUrl, true, true);
            placeHolder.append

            if (!placeHolder.getElementsByClassName("linkSelf"))
              return;

            if (!PG_settings["removeXFromInlines"]) {
              var close = document.createElement("A");
              close.innerText = "X";
              close.onclick = function() {
                placeHolder.remove();
              }
              close.style.className = "closeInline";
              placeHolder.getElementsByClassName("postInfo")[0].prepend(close);
            }



            Array.from(placeHolder.getElementsByClassName("quoteLink"))
              .forEach((a) => tooltips.processQuote(a, false, true));

            //for some bizzaro reason they only processQuote()'d the bottom backlinks
            //new
            if (!tooltips.bottomBacklinks) {
              var stla = placeHolder.getElementsByClassName("panelBacklinks")[0]
              Array.from(stla.children)
              //Array.from(stla.querySelectorAll("a:not(.numberSign)"))
                .forEach((a) => tooltips.processQuote(a, true));
              Array.from(stla.getElementsByClassName("numberSign"))
              //Array.from(stla.querySelectorAll("a:not(.numberSign)"))
                .forEach((a) => stla.insertBefore(document.createTextNode(" "), a));
            }
            //end new

            if (tooltips.bottomBacklinks) {
              var alts = placeHolder.getElementsByClassName("altBacklinks")[0].firstChild
              Array.from(alts.children)
                .forEach((a) => tooltips.processQuote(a, true));
            }

            if (isBacklink) {
              //innerPost.append(placeHolder);
              //new
              if (!tooltips.bottomBacklinks) {
                // cant be bothered to troubleshoot
                if (innerPost === innerPost.getElementsByClassName("divMessage")[0].parentNode)
                  innerPost.insertBefore(placeHolder, innerPost.getElementsByClassName("divMessage")[0]);
                //innerPost.getElementsByClassName("panelUploads")[0].after(placeHolder);
              } else {
              //end new
                replyPreview.append(placeHolder);
              }
            } else {
              quote.insertAdjacentElement("afterEnd", placeHolder);
            }

          //broken?
          //false when opening, undefined when closing
            tooltips.removeIfExists();

            if (!quote.classList.contains("PG_quoteLinkClicked")) {
              //new
              quote.classList.add("PG_quoteLinkClicked");
              //end new

              //new
              quote.addEventListener("click", function() {
                placeHolder.remove();
                quote.classList.remove("PG_quoteLinkClicked");
              },{"once": true});
              //end new
            }
        })
      }
    }
  }).observe(document.documentElement, {childList: true, subtree:true });
}

//changed to fix recusive inlining
function overwriteAddLoadedTooltip()
{
  new MutationObserver((_, observer) => {
    const scriptTag = document.querySelector("script[src*='hiding.js']");
    if (scriptTag) {
      observer.disconnect();

      //add tooltip dynamic content: backlinks, link underlining, (you)s
      tooltips.addLoadedTooltip = function(htmlContents, tooltip, quoteUrl, replyId, isInline) {
        var quoteTarget = api.parsePostLink(quoteUrl);
        var board = quoteTarget.board;
        var thread = +quoteTarget.thread;
        var post = +quoteTarget.post;

        if (!htmlContents) {
          tooltip.innerText = 'Not found'; //TODO delete and disable hover?
          return;
        }

        Array.from(htmlContents.getElementsByClassName("inlineQuote")).forEach((q) => q.remove())

        var deletionCheckBox = htmlContents.getElementsByClassName('deletionCheckBox')[0];
        if (deletionCheckBox) {
          deletionCheckBox.remove();
        }

        // obtain the latest backlinks from the actual post
        // this has to be done now since backlinks change
        if (board === api.boardUri && thread === api.threadId) {
          var backContainer = tooltips.knownPosts[board][post ? post : thread];
          if (backContainer) {
            var contentBack = htmlContents.getElementsByClassName("panelBacklinks");
            Array.from(backContainer.container.children).forEach((backlink) => {
               Array.from(contentBack).forEach((panel) => {
                 panel.append(backlink.cloneNode(true));
               })
            });
          }
        }

        //new
        var altBacklinks = document.createElement("div");
        altBacklinks.setAttribute("class", "altBacklinks");
        altBacklinks.appendChild(htmlContents.getElementsByClassName("panelBacklinks")[0].cloneNode(true));
        htmlContents.appendChild(altBacklinks);

        var replyPreview = document.createElement("div");
        replyPreview.setAttribute("class", "replyPreview");
        htmlContents.appendChild(replyPreview);
        //end new


        tooltips.addReplyUnderline(htmlContents, board, replyId);

        //TODO move to HTML/node caching
        var yous = localStorage.getItem(board + "-yous");
        if (yous !== null && JSON.parse(yous).find((a) => a == post) !== undefined) {
          posting.markPostAsYou(undefined, htmlContents);
        }

        htmlContents.className = 'innerPost'; //for innerOPs
        if (tooltip.firstChild) {
          tooltip.firstChild.remove();
        }

        //new

        //end new

        tooltip.append(htmlContents); //htmlcontents is originally not complete hence recursion etc not working

        if (isInline) {
          posting.parseExistingPost(tooltip.getElementsByClassName('linkSelf')[0], false, false, true);
        } else {
          tooltips.checkHeight(tooltip);
        }
      }
    }
  }).observe(document.documentElement, {childList: true, subtree:true });
}


function buildMenu()
{
  new MutationObserver((_, observer) => {
  const scriptTag = document.querySelector("script[src*='posting.js']");
  if (scriptTag) {
    observer.disconnect();
    //clobbers. this is used to create the hide and triangle button and their menus
    postingMenu.buildMenu = function(post, extraMenu) {
      var hasFiles = post.files && post.files.children.length > 0;

      var menuCallbacks = [];

      menuCallbacks.push(
        {
          name: "Change Nickname",
          callback: () => { addNickname(post); }
        }
      );

      menuCallbacks.push(
        {
          name: "Toggle You",
          callback: () => { toggleYou(post); }
        }
      );

      menuCallbacks.push(
        {name: 'Report'
        ,callback: function() {
          postingMenu.showReport2(post.postInfo.board, post.postInfo.thread,
            post.postInfo.post);
          }
        }
      );

      menuCallbacks.push(
        {name: 'Delete Post'
        ,callback: function() {
          postingMenu.deleteSinglePost(post.postInfo.board, post.postInfo.thread,
            post.postInfo.post, null, null, null, post.innerPost);
          }
        }
      );

      if (postingMenu.loggedIn && (postingMenu.globalRole < 4
        || postingMenu.moddedBoards.indexOf(post.postInfo.board) >= 0)) {

        postingMenu.setExtraMenuMod(post, menuCallbacks, hasFiles);
      }

      return menuCallbacks;
    };
  }
  }).observe(document.documentElement, {childList: true, subtree:true });
}


function reloadPosts(noExtras = true, noModify = false, noTooltips = true, onlyIds = false, onlyNicknames = false, onlyTooltips = false, onlyPG_hideButton = false)
{
  runWhenDomLoaded(function(){
    document.querySelectorAll(".linkSelf").forEach(e => posting.parseExistingPost(e, noExtras, noModify, noTooltips, onlyIds, onlyNicknames, onlyTooltips, onlyPG_hideButton));
  });
}


function addSettings()
{
  function capitalise(s)
  {
    return s.charAt(0).toUpperCase() + s.slice(1);
  }

  function createCheckboxDiv(settingName, description)
  {
    let checkbox = document.createElement("input");
    checkbox.setAttribute("type", "checkbox");
    checkbox.setAttribute("id", "PG_checkbox" + capitalise(settingName));
    if (PG_settings[settingName]){
      checkbox.setAttribute("checked", "");
    }

    let label = document.createElement("label");
    label.setAttribute("class", "small");
    label.htmlFor = "PG_checkbox" + capitalise(settingName);
    label.innerText = description;

    let div = document.createElement("div");
    div.append(checkbox, label);
    return div;
  }

  document.addEventListener("DOMContentLoaded", () => {
    let settings = [
      {
        "name": "moveReplyButton",
        "description": "Move Reply Button to Above Everything Else"
      },
      {
        "name": "keepWatcherOpen",
        "description": "Open Threadwatcher on Load"
      },
      {
        "name": "closeQrAfterPosting",
        "description": "Close Quick Reply after Posting"
      },
      {
        "name": "moveThreadInfo",
        "description": "Move Thread Info to Header (posts/IDs/files)"
      },
      {
        "name": "addWatcherShortcut",
        "description": "Add \"Add to Threadwatcher\" Shortcut Key (w)"
      },
      {
        "name": "removeXFromInlines",
        "description": "Remove \"Remove\" Button (X) from Inlines (You can click on the quotelink)"
      },
      {
        "name": "addHideButton",
        "description": "Add a \"Hide this post\" Button"
      },
      {
        "name": "removeCheckbox",
        "description": "Remove Checkbox"
      }
    ];

    var settingsDiv = document.createElement("div");
    settingsDiv.setAttribute("id", "PG_checkboxes");

    var button = document.createElement("button");
    button.innerText = "Save";
    button.addEventListener("click", function () {
      settings.forEach(function(e) {
        PG_settings[e.name] = document.getElementById("PG_checkbox" + capitalise(e.name)).checked ? true : false;
      });

      localStorage.setItem("PG_settings", JSON.stringify(PG_settings));
    });

    var name = document.createElement("span");
    name.innerText = "Weanus Settings ⸜(* ॑꒳ ॑*  )⸝⋆*";

    // I learn about Element.prepend() and .append() at the end. figures
    settings.forEach(function(e) {
      settingsDiv.appendChild(createCheckboxDiv(e.name, e.description));
    });

    document.getElementById("settings-TG9jYWwgVGltZX").parentNode.parentNode.prepend(
      name,
      settingsDiv,
      button,
      document.createElement("hr")
    );
  });
}


function fixAltBacklinkCSS()
{
  runWhenDomLoaded(function() {
    if (tooltips.bottomBacklinks) {
      applyCSS(`
      .altBacklinks > .panelBacklinks {
        display: block !important;
      }
      .title > .panelBacklinks {
        display: none !important;
      }
      `);
    }
  });
}


function removeCheckbox()
{
  runWhenDomLoaded(function() {
    applyCSS(`
      .deletionCheckBox {
        display: none;
      }
    `);
  });
}


function addCSS()
{
  runWhenDomLoaded(function() {
    applyCSS(`
      .innerPost:has(.youName) {
        border-left: solid var(--link-hover-color);
        }
      .innerPost:has(.quoteLink.you) {
        border-left: dashed var(--link-hover-color);
      }
      .unhideButton {
        font-size: 90%;
      }
      .hideButton, .extraMenuButton {
        user-select: none;
      }
      .nickname {
        color: var(--link-hover-color);
        font-weight: bold;
      }
      .numberSign {
        text-decoration: none !important;
      }
      .altBacklinks {
        background-color: rgba(0, 0, 0, 0) !important;
      }
      a.quoteLink{
        color: var(--link-color);
      }
      #quick-reply table {
        width: 100%;
      }

      .postInfo .coloredIcon {
        top: 2px;
      }

      .inlineQuote {
        margin: 10px;
      }

      .PG_quoteLinkClicked {
        text-decoration-style: dashed !important;
      }
    `);
  });
}


function applyCSS(css)
{
  var style = document.createElement("style");
  style.textContent = css;

  document.head.appendChild(style);
}


function runWhenDomLoaded (f)
{
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", () => {
      f();
    });
  } else {
    f();
  }
}


/*main ===========================*/

const settingsName = "PG_settings";
const PG_settings = JSON.parse(localStorage.getItem(settingsName));

if (PG_settings === null) {
  var settings = {};
  settings["closeQrAfterPosting"] = true;
  settings["moveReplyButton"]     = true;
  settings["moveThreadInfo"]      = true;
  settings["keepWatcherOpen"]     = true;
  settings["addWatcherShortcut"]  = true;
  settings["removeXFromInlines"]  = true;
  settings["addHideButton"]       = true;
  settings["removeCheckbox"]      = true;
  localStorage.setItem(settingsName, JSON.stringify(settings));
  console.log("Initialised settings at localStorage.PG_settings");
  PG_settings = JSON.parse(localStorage.getItem(settingsName));
}

overwriteParseExistingPost();
overwriteAddExternalExtras();
overwriteAddBackLink();
overwriteAddInlineClick();
overwriteAddLoadedTooltip();

fixClickOnId();
fixAltBacklinkCSS();

buildMenu();
//reload ids to apply id fix
reloadPosts(true, true, true, true);
//reload nicknames to make them show (combine with above later, perhaps, I cant be arsed)
//reloadPosts(true, true, true, false, true);
//initialise posts. honestly I'm spagghing myself noModify is enough. I'll fix everything later...
reloadPosts(false, true, false);

addCSS();

addSettings();

if (PG_settings.closeQrAfterPosting)
  closeQrAfterPosting();
if (PG_settings.moveReplyButton)
  moveReplyButton();
if (PG_settings.moveThreadInfo)
  moveThreadInfo();
if (PG_settings.keepWatcherOpen)
  keepWatcherOpen();
if (PG_settings.addWatcherShortcut)
  addWatcherShortcut();
if (PG_settings.removeCheckbox)
  removeCheckbox();