From b556881eaf89c3fd4d1a1b44edb3c0234e4b4e48 Mon Sep 17 00:00:00 2001 From: JTH Date: Sat, 24 Sep 2022 22:07:52 +0200 Subject: [PATCH] Add a quickmod link for archiving a whole thread --- quickmod.user.js | 150 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 3 deletions(-) diff --git a/quickmod.user.js b/quickmod.user.js index 2c06dde..5f8c885 100644 --- a/quickmod.user.js +++ b/quickmod.user.js @@ -10,6 +10,138 @@ const ARCHIVFORUMID = 35; +async function archiveThread(firstPage, reason) { + const moveProm = (async () => { + const moveLink = firstPage.querySelector("#quickmod .dropdown-contents a[href*='action=move']"); + + let form, formData; + try { + [form, formData] = await openForm(toAbsoluteURL(moveLink.href), "form#confirm"); + } catch (err) { + throw `Konnte Formular zum Verschieben des Themas nicht öffnen: ${err}`; + } + + formData.set("to_forum_id", ARCHIVFORUMID); + try { + /* Unlike splitting a thread, moving does not have a second + * confirmation step. + */ + await postForm(form, formData, "confirm"); + } catch (err) { + throw `Konnte Thema nicht verschieben: ${err}`; + } + })(); + + const editProm = (async () => { + const editLink = firstPage.querySelector(".post .post-buttons a[href*='mode=edit']"); + + let form, formData; + try { + [form, formData] = await openForm(toAbsoluteURL(editLink.href), "form#postform"); + } catch (err) { + throw `Konnte Formular zum Bearbeiten des ersten Beitrags nicht öffnen: ${err}`; + } + + formData.set("subject", prefixSubject(form.elements["subject"], reason)); + + /* All "altering actions not secured by confirm_box" require a non-zero + * time difference between opening and submitting a form. See + * check_form_key() in phpBB/includes/functions.php. + * + * So we artificially delay the postForm() for a second. + */ + await new Promise((resolve) => { + setTimeout(async () => { + try { + await postForm(form, formData, "post"); + } catch (err) { + throw `Konnte Thema nicht umbenennen: ${err}`; + } + + resolve(); + }, 1001); + }); + })(); + + /* An mcp action and a post edit can actually be done concurrently! :-) */ + await Promise.all([moveProm, editProm]); +} + +async function archiveThreadQuickmod() { + const canonicalURL = new URL(document.querySelector("link[rel='canonical']").href); + const firstPage = await openDoc(canonicalURL); + const firstPost = firstPage.querySelector(".post"); + const usernameElem = firstPost.querySelector(".author .username,.author .username-coloured"); + const username = usernameElem.textContent; + const thread_title = firstPage.querySelector('.topic-title a').text; + + const archiveReason = await asyncPrompt(`Thema „${ellipsify(thread_title, 100)}“ eröffnet von „${username}“ wirklich als Spam archivieren?\n\nGrund:`, "Spam"); + if (archiveReason === null) { + /* Don't do any of the other actions if moving was cancelled. */ + return; + } + + const archivingThread = archiveThread(firstPage, archiveReason); + + /* Prompting for a separate ban reason in case there is something more + * specific to note here. + */ + const userStillExists = usernameElem.nodeName === "A"; + const banReasonPrompt = userStillExists && + asyncPrompt(`Benutzer „${username}“, der das Thema eröffnet hat, sperren?\n\nGrund:`, "Spam"); + + /* Mod actions via mcp.php involve a confirm_key which is stored in the + * database when an action is requested until it is confirmed. There can only + * be one confirm_key stored at a time---meaning there cannot be multiple mcp + * actions executed concurrently. See confirm_box() in + * phpBB/includes/functions.php. + * + * This means we cannot really execute the actions concurrently here, + * unfortunately. User interaction is still done in parallel to one action at + * a time, though. + */ + const errors = []; + try { + await archivingThread; + } catch (err) { + errors.push(err); + } + + let banningUser; + const banReason = await banReasonPrompt; + if (banReason) { + banningUser = banUser(username, banReason); + } else if (!userStillExists) { + await asyncAlert(`Benutzer „${username}“ wurde schon gelöscht und kann nicht mehr gesperrt werden.`); + } + + const shouldCloseReport = isPostReported(firstPost) && + asyncConfirm("Meldung zum ersten Beitrag schließen?"); + + try { + await banningUser; + } catch (err) { + errors.push(err); + } + + if (await shouldCloseReport) { + try { + await closeReport(firstPost); + } catch (err) { + errors.push(err); + } + } + + for (const error of errors) { + console.log(error); + window.alert(`ACHTUNG!\n\n${error}`); + } + + if (errors.length === 0) { + redirectToArchive(); + } +} + async function asyncAlert(message) { window.alert(message); } @@ -77,6 +209,12 @@ function isPostReported(post) { } async function openForm(urlOrResponse, selector) { + const doc = await openDoc(urlOrResponse); + const form = doc.querySelector(selector); + return [form, new FormData(form)]; +} + +async function openDoc(urlOrResponse) { const resp = urlOrResponse instanceof Response ? urlOrResponse : await fetch(urlOrResponse); if (!resp.ok) { @@ -85,9 +223,7 @@ async function openForm(urlOrResponse, selector) { const parser = new DOMParser(); const txt = await resp.text(); - const doc = parser.parseFromString(txt, "text/html"); - const form = doc.querySelector(selector); - return [form, new FormData(form)]; + return parser.parseFromString(txt, "text/html"); } async function postForm(form, formData, submitName, requiresConfirmation = false) { @@ -255,6 +391,14 @@ function add_buttons() { del_post_btn_outer_clone.addEventListener("click", remove_post_handler); postButtons.appendChild(del_post_btn_outer_clone); } + + const quickmodLinks = document.querySelector("#quickmod .dropdown-contents"); + const archiveThreadLink = quickmodLinks + .insertBefore(document.createElement("li"), quickmodLinks.firstChild) + .appendChild(document.createElement("a")); + archiveThreadLink.addEventListener("click", archiveThreadQuickmod); + archiveThreadLink.innerText = "Thema als Spam archivieren"; + archiveThreadLink.style.cursor = "pointer"; } add_buttons(); -- 2.39.2