mark.taiwangmail.com / 4chan Manual (You)

// ==UserScript==
// @name         4chan Manual (You)
// @description  Add arbitary posts to 4chan's reply tracking
// @author       Marker
// @license      MIT
// @namespace    https://gist.github.com/marktaiwan
// @version      1.0
// @match        https://boards.4chan.org/*
// @match        https://boards.4channel.org/*
// @grant        none
// @noframes
// @require      https://openuserjs.org/src/libs/soufianesakhi/node-creation-observer.js
// @icon         https://s.4cdn.org/image/favicon-ws.ico
// ==/UserScript==

/* global Main, Parser, NodeCreationObserver */

(function () {
'use strict';

const $ = (selector, parent = document) => parent.querySelector(selector);
const $$ = (selector, parent = document) => parent.querySelectorAll(selector);

const ADD_STRING = ' [Start tracking]';
const REM_STRING = ' [<b>Tracked</b>]';

function processPost(postEle) {
  Parser.trackedReplies = Parser.getTrackedReplies(Main.board, Main.tid) ?? {};

  const postId = $('.postInfo input[type="checkbox"]', postEle).name;
  const postNum = $('.postInfo .postNum', postEle);

  const isOwnPost = Object.keys(Parser.trackedReplies).some(key => key == '>>' + postId);

  const button = document.createElement('a');
  button.style.cursor = 'pointer';
  button.innerHTML = isOwnPost ? REM_STRING : ADD_STRING;
  button.dataset.ownPost = isOwnPost ? '1' : '0';
  button.addEventListener('click', handleClick);

  postNum.after(button);
}

function handleClick(e) {
  const button = e.target;
  const postEle = button.closest('.postContainer');
  const threadId = Main.tid;
  const postId = $('.postInfo input[type="checkbox"]', postEle).name;

  if (button.dataset.ownPost == '1') {
    delete Parser.trackedReplies['>>' + postId];
    Parser.saveTrackedReplies(threadId, Parser.trackedReplies);
    button.innerHTML = ADD_STRING;
    button.dataset.ownPost = '0';
  } else {
    Parser.trackedReplies['>>' + postId] = '1';
    Parser.saveTrackedReplies(threadId, Parser.trackedReplies);
    button.innerHTML = REM_STRING;
    button.dataset.ownPost = '1';

    // Update replied posts by following the backlinks
    const backlinks = $$('.backlink .quotelink', postEle);
    for (const backlink of backlinks) {
      const replyId = backlink.innerText.slice(2);
      const replyEle = $('#pc' + replyId);
      parseTrackedReplies(replyEle);
    }
  }
}

function parseTrackedReplies(postEle) {
  for (const link of $$('.quotelink', postEle)) {
    if (!Parser.trackedReplies[link.textContent] || link.closest('.backlink')) continue;
    link.classList.add('ql-tracked');
    link.textContent += ' (You)';
    Parser.hasYouMarkers = true;
  }
}

if (Main.tid) NodeCreationObserver.onCreation('.postContainer', processPost);
})();