Greasy Fork is available in English.

LZT Summarize

Summarize a large topic to find out if you need it

// ==UserScript==
// @name         LZT Summarize
// @namespace    lzt-summarize
// @version      1.0.0
// @description  Summarize a large topic to find out if you need it
// @author       Toil
// @license      MIT
// @match        https://zelenka.guru/threads/*
// @match        https://lolz.live/threads/*
// @match        https://lolz.guru/threads/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=zelenka.guru
// @supportURL   https://zelenka.guru/toil/
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  "use strict";

  GM_addStyle(`
    .LZTSummarizeThreadBar {
      background: rgb(39, 39, 39);
      padding: 15px 20px;
      margin-top: 15px;
      border-radius: 10px;
      height: auto;
    }

    .LZTSummarizeThreadBarTitle {
      font-weight: bold;
      font-size: 18px;
      padding: 0;
      margin: 0 0 2px;
      line-height: 33px;
      overflow: hidden;
    }

    .LZTSummarizeThreadBarContent {
      font-size: 14px;
    }

    .LZTSummarizeThesises {
      margin-left: 16px;
    }

    .LZTSummarizeThesises li {
      list-style: decimal;
      margin: 2px 0;
      line-height: 20px;
    }
  `);

  const SUMMARIZE_URL = "https://summarize.toil.cc/generation";
  const SUMMARIZE_TITLE = "<i class='fas fa-sparkles'></i> Суммаризатор тем";

  const yandexStatus = {
    StatusInProgress: 1,
    StatusSuccess: 2,
    StatusError: 3,
    StatusFetchError: 999,
  };

  class SummarizeStatus {
    static Waiting = new SummarizeStatus("waiting").name;
    static Error = new SummarizeStatus("error").name;
    static Success = new SummarizeStatus("success").name;

    constructor(name) {
      this.name = name;
    }
  }

  function checkSummarizeCode(res) {
    switch (res.status_code) {
      case yandexStatus.StatusInProgress:
        return {
          status: SummarizeStatus.Waiting,
          title: "Суммаризация...",
          desc: [
            {
              id: 0,
              content: `Ожидание окончания суммаризации текста`,
            },
          ],
        };
      case yandexStatus.StatusFetchError:
        return {
          status: SummarizeStatus.Error,
          title: "Ошибка запроса",
          desc: [
            {
              id: 0,
              content: `Не удалось совершить запрос к Yandex Summarize API`,
            },
          ],
        };
      case yandexStatus.StatusError:
        return {
          status: SummarizeStatus.Error,
          title: "Ошибка YandexGPT",
          desc: [
            {
              id: 0,
              content: "Возникла ошибка при суммаризации текста",
            },
          ],
        };
      case yandexStatus.StatusSuccess:
        return {
          status: SummarizeStatus.Success,
          title: "Успех",
          desc: res.thesis,
        };
      default:
        return {
          status: SummarizeStatus.Error,
          title: "Неизвестная ошибка",
          desc: [
            {
              id: 0,
              content:
                "Во время выполнения что-то пошло не так и из-за этого не удалось определить результат суммаризации",
            },
          ],
        };
    }
  }

  async function genSummarize(text, session_id) {
    return await fetch(SUMMARIZE_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        text,
        session_id,
      }),
    })
      .then((res) => res.json())
      .catch((err) => {
        console.error(
          "[LZT Summarize] Failed to generate a summarize of the text",
          err
        );
        return false;
      });
  }

  function getThreadContent() {
    return document
      .querySelector(".message.firstPost > .messageInfo article")
      ?.textContent?.trim();
  }

  async function getThreadContentByAjax(threadId) {
    try {
      const res = await XenForo.ajax(`/threads/${threadId}`);
      const resHTML = res.templateHtml;

      const parser = new DOMParser();
      const parsedHTML = parser.parseFromString(resHTML, "text/html");
      const text = parsedHTML.querySelector(
        ".message.firstPost > .messageInfo article"
      )?.innerText;

      return text;
    } catch {
      return undefined;
    }
  }

  function clearSummarizeContent(text) {
    // replace \n, \t, \r to basic spaces
    // replace ip to void (many ips in text = server error)
    return text
      .replaceAll(/\s/g, " ")
      .replaceAll(/((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}/g, "");
  }

  function createThreadBar(title, content) {
    const container = document.createElement("div");
    container.classList.add("LZTSummarizeThreadBar");

    const titleEl = document.createElement("h2");
    titleEl.classList.add("LZTSummarizeThreadBarTitle");
    titleEl.innerHTML = title;
    container.appendChild(titleEl);

    const contentEl = document.createElement("p");
    contentEl.classList.add("LZTSummarizeThreadBarContent", "muted");
    contentEl.innerHTML = content;
    container.appendChild(contentEl);

    return {
      container,
      title: titleEl,
      content: contentEl,
    };
  }

  async function summarizeThreadBlock() {
    let threadContent = getThreadContent();

    const summarizeBlock = createThreadBar(
      SUMMARIZE_TITLE,
      "Получение данных..."
    );
    const pageNavLinkGroup = document.querySelector(".pageNavLinkGroup");
    pageNavLinkGroup.before(summarizeBlock.container);

    if (threadContent == undefined) {
      // getting content about a topic if not 1 page is open
      const threadId =
        Number(window.location.pathname.match(/^\/threads\/([^d]+)\//)?.[1]) ||
        undefined;
      threadContent = await getThreadContentByAjax(threadId);
    }

    if (!(threadContent?.length >= 300)) {
      summarizeBlock.title.innerHTML = `${SUMMARIZE_TITLE} (Ошибка валидации)`;
      summarizeBlock.content.innerText =
        "Не удалось выполнить суммаризацию темы. Содержимое темы не найдено или содержит менее 300 символов.";
      return false;
    }

    let summarizeInterval;
    let sessionId = "";
    threadContent = clearSummarizeContent(threadContent);
    summarizeInterval = setInterval(async () => {
      const generatedInfo = await genSummarize(threadContent, sessionId);
      console.debug("[LZT Summarize] Summarize Generated Info", generatedInfo);

      if (!generatedInfo) {
        console.error("[LZT Summarize] Clear summarize interval (ext error)");
        clearInterval(summarizeInterval);
        summarizeBlock.title.innerHTML = `${SUMMARIZE_TITLE} (Внутренняя ошибка)`;
        summarizeBlock.content.innerText =
          "Не удалось выполнить суммаризацию темы. Произошла внутренняя ошибка при запросе к Summarize API. Для детальной информации смотри консоль.";
        return false;
      }

      sessionId = generatedInfo.session_id;
      const result = checkSummarizeCode(generatedInfo);
      if (result.status !== SummarizeStatus.Waiting) {
        clearInterval(summarizeInterval);
      }

      const contentEl = document.createElement("ul");
      if (result.desc.length > 1) {
        contentEl.classList.add("LZTSummarizeThesises");
      }

      for (const thesis of result.desc) {
        const thesisEl = document.createElement("li");
        thesisEl.innerText = thesis.content;
        contentEl.appendChild(thesisEl);
      }

      summarizeBlock.title.innerHTML = `${SUMMARIZE_TITLE} (${result.title})`;
      summarizeBlock.content.innerHTML = contentEl.outerHTML;
    }, 500);

    return true;
  }

  summarizeThreadBlock();
})();