// ==UserScript==
// @name LZT Summarize
// @namespace lztup-summarize
// @version 1.1.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://cdn.lztup.net/brand/logo-mini.png
// @supportURL https://lolz.live/threads/6149539
// @homepageURL https://github.com/lzt-upgrade/lzt-summarize
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect summarize.toil.cc
// ==/UserScript==
(function () {
"use strict";
GM_addStyle(`
.LZTUpSummarizeThreadBar {
background: rgb(39, 39, 39);
padding: 15px 20px;
margin-top: 15px;
border-radius: 10px;
height: auto;
}
.LZTUpSummarizeThreadBarTitle {
font-weight: bold;
font-size: 18px;
padding: 0;
margin: 0 0 2px;
line-height: 33px;
overflow: hidden;
}
.LZTUpSummarizeThreadBarContent {
font-size: 14px;
}
.LZTUpSummarizeThesises {
margin-left: 16px;
}
.LZTUpSummarizeThesises li {
list-style: decimal;
margin: 2px 0;
line-height: 20px;
}`);
const SUMMARIZE_URL = "https://summarize.toil.cc/v2/summarize/text";
const SUMMARIZE_TITLE = "<i class='fas fa-sparkles'></i> Суммаризатор тем";
const yandexStatus = {
StatusInProgress: 1,
StatusSuccess: 2,
StatusError: 3,
};
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;
}
}
async function GM_fetch(url, opts = {}) {
const {
timeout = 15000, ...fetchOptions
} = opts;
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: fetchOptions.method || "GET",
url,
data: fetchOptions.body,
timeout,
headers: fetchOptions.headers || {},
onload: (resp) => {
const headers = resp.responseHeaders
.split(/\r?\n/)
.reduce((acc, line) => {
const [, key, value] = line.match(/^([\w-]+): (.+)$/) || [];
if (key) {
acc[key] = value;
}
return acc;
}, {});
const response = new Response(resp.response, {
status: resp.status,
headers: headers,
});
// Response have empty url by default
// this need to get same response url as in classic fetch
Object.defineProperty(response, "url", {
value: resp.finalUrl ?? "",
});
resolve(response);
},
ontimeout: () => reject(new Error("Timeout")),
onerror: (error) => reject(new Error(error)),
onabort: () => reject(new Error("AbortError")),
});
});
}
function checkSummarizeCode(res) {
switch (res.statusCode) {
case yandexStatus.StatusInProgress:
return {
status: SummarizeStatus.Waiting,
title: "Суммаризация...",
thesis: res.thesis.length ?
res.thesis : [{
id: 0,
content: `Ожидание окончания суммаризации текста`,
}],
};
case yandexStatus.StatusError:
return {
status: SummarizeStatus.Error,
title: "Ошибка YandexGPT",
thesis: [{
id: 0,
content: "Возникла ошибка при суммаризации текста",
}],
};
case yandexStatus.StatusSuccess:
return {
status: SummarizeStatus.Success,
title: "Успех",
thesis: res.thesis,
};
default:
return {
status: SummarizeStatus.Error,
title: "Неизвестная ошибка",
thesis: [{
id: 0,
content: "Во время выполнения что-то пошло не так и из-за этого не удалось определить результат суммаризации",
}],
};
}
}
async function genSummarize(text, sessionId) {
try {
const res = await GM_fetch(SUMMARIZE_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
text,
sessionId,
}),
});
if (!res.ok) {
throw new Error(await res.text());
}
return await 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 .messageText")
?.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 .messageText",
)?.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("LZTUpSummarizeThreadBar");
const titleEl = document.createElement("h2");
titleEl.classList.add("LZTUpSummarizeThreadBarTitle");
titleEl.innerHTML = title;
const contentEl = document.createElement("p");
contentEl.classList.add("LZTUpSummarizeThreadBarContent", "muted");
contentEl.innerHTML = content;
container.append(titleEl, contentEl);
return {
container,
title: titleEl,
content: contentEl,
};
}
async function summarize(
summarizeBlock,
threadContent,
sessionId,
timer = null,
) {
clearTimeout(timer);
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)");
summarizeBlock.title.innerHTML = `${SUMMARIZE_TITLE} (Внутренняя ошибка)`;
summarizeBlock.content.innerText =
"Не удалось выполнить суммаризацию темы. Произошла внутренняя ошибка при запросе к Summarize API. Для детальной информации смотри консоль.";
return false;
}
sessionId = generatedInfo.sessionId;
const result = checkSummarizeCode(generatedInfo);
const contentEl = document.createElement("ul");
if (result.thesis.length > 1) {
contentEl.classList.add("LZTUpSummarizeThesises");
}
for (const thesis of result.thesis) {
const thesisEl = document.createElement("li");
thesisEl.innerText = thesis.content;
contentEl.appendChild(thesisEl);
}
summarizeBlock.title.innerHTML = `${SUMMARIZE_TITLE} (${result.title})`;
summarizeBlock.content.innerHTML = contentEl.outerHTML;
if (result.status !== SummarizeStatus.Waiting) {
return true;
}
return new Promise((resolve) => {
timer = setTimeout(async () => {
resolve(
await summarize(summarizeBlock, threadContent, sessionId, timer),
);
}, generatedInfo.pollIntervalMs);
});
}
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 current page isn't 1st page
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;
}
threadContent = clearSummarizeContent(threadContent);
return await summarize(summarizeBlock, threadContent);
}
summarizeThreadBlock();
})();