Auto-select unwatched

This automatically selects submission notifications from unwatched users on the current FurAffinity notifications page.

// ==UserScript==
// @name         Auto-select unwatched
// @namespace    https://github.com/f1r3w4rr10r/fa-utils
// @version      1.0.0
// @description  This automatically selects submission notifications from unwatched users on the current FurAffinity notifications page.
// @author       f1r3w4rr10r
// @match        https://www.furaffinity.net/msg/submissions/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @license      MIT
// @grant        none
// ==/UserScript==

(async function () {
  "use strict";

  /**
   * @param {string} url
   * @returns {Promise<[string[], string | null]>}
   */
  async function getWatchedFromPage(url) {
    const result = await fetch(url);
    if (!result.ok) {
      throw new Error(
        `Could not get watchlist: ${result.status} ${result.statusText} ${url}`,
      );
    }
    const doc = new DOMParser().parseFromString(
      await result.text(),
      "text/html",
    );

    const userLinks = Array.from(doc.querySelectorAll("a")).map((a) => a.href);

    const nextForm = doc.querySelector(".floatright form");
    if (!(nextForm instanceof HTMLFormElement))
      throw new Error(`Could not find the next page form on: ${url}`);

    const nextUrl = nextForm.action;

    return [userLinks, nextUrl === url ? null : nextUrl];
  }

  /**
   * @returns {Promise<string[]>}
   */
  async function getWatched() {
    const watchList = [];

    const userAnchor = document.querySelector("article.mobile-menu a");
    if (!(userAnchor instanceof HTMLAnchorElement))
      throw new Error("Could not get the user anchor.");

    const userName = userAnchor.href.match(/user\/(.+?)$/)[1];

    let nextUrl = `https://www.furaffinity.net/watchlist/by/${userName}/`;

    while (nextUrl !== null) {
      let pageWatchList = [];
      [pageWatchList, nextUrl] = await getWatchedFromPage(nextUrl);
      watchList.push(...pageWatchList);
    }

    return watchList;
  }

  /**
   * @param {string[]} watched
   * @returns {Promise<number>}
   */
  async function iterateLabels(watched) {
    const figures = Array.from(
      document.querySelectorAll("section.gallery figure"),
    );
    let selected = 0;

    for (const figure of figures) {
      const userAnchor = figure.querySelector(
        "figcaption label p:last-child a",
      );
      if (!(userAnchor instanceof HTMLAnchorElement))
        throw new Error("Could not find a user anchor.");

      const userLink = userAnchor.href;

      if (watched.includes(userLink)) continue;

      figure.classList.add("unwatched");
      figure.querySelector("input").checked = true;
      selected += 1;
    }

    return selected;
  }

  const sheet = new CSSStyleSheet();
  sheet.replaceSync("figure.unwatched { outline: red 3px solid; }");
  document.adoptedStyleSheets.push(sheet);

  const sectionHeader = document.querySelector(".section-header");

  const unwatchedSelectMessage = document.createElement("p");
  unwatchedSelectMessage.textContent =
    "Checking for submissions from unwatched users…";
  sectionHeader.appendChild(unwatchedSelectMessage);

  const watched = await getWatched();

  const selected = await iterateLabels(watched);

  const message = `Selected ${selected} unwatched submissions.`;

  unwatchedSelectMessage.textContent = message;
})();