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);
}
}
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) {
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) {
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();