]> git.aero2k.de Git - dfde/quickmods.git/commitdiff
JTH: ban user, close mod notification
authorThorsten <mail@aero2k.de>
Sat, 3 Sep 2022 10:30:08 +0000 (12:30 +0200)
committerThorsten <mail@aero2k.de>
Sat, 3 Sep 2022 10:30:08 +0000 (12:30 +0200)
quickmod.user.js

index d5c53e481f2cef76fc4b6b691b85597662904223..8905d6ab22b37ef3c34231381f140f60e2fbaf84 100644 (file)
 // @match         https://debianforum.de/forum/viewtopic.php*
 // @author        Thorsten Sperber
 // @author        JTH
-// @version       1.2
+// @version       1.3
 // ==/UserScript==
 
 const ARCHIVFORUMID = 35;
-const boardurl = "/forum/";
 
-let mod_user_id;
+async function banUser(username, reason) {
+    /* The URL to the ban form does not need any IDs or hidden inputs. We
+     * hardcode it here.
+     */
+    let resp = await fetch("./mcp.php?i=ban&mode=user");
+    if (!resp.ok) {
+        throw "Konnte Formular zum Sperren von Benutzern nicht laden.";
+    }
+
+    let form, formData;
+    try {
+        [form, formData] = await openForm(resp, "form#mcp_ban");
+    } catch {
+        throw "Konnte Formular zum Sperren von Benutzern nicht öffnen.";
+    }
+
+    formData.set("ban", username);
+    formData.set("banreason", reason);
+    //formData.set("bangivereason", reason);
+    resp = await postForm(form, formData, "bansubmit");
+    if (!resp.ok) {
+        throw "Konnte Sperrung des Benutzers nicht anfragen.";
+    }
+
+    try {
+        await confirmAction(resp);
+    } catch (err) {
+        throw `Konnte Sperrung des Benutzers nicht bestätigen: ${err}`;
+    }
+}
+
+async function closeReport(post) {
+    const reportLink = post.querySelector(".post-notice.reported a");
+
+    let resp = await fetch(reportLink.href);
+    if (!resp.ok) {
+        throw "Konnte Meldung nicht öffnen.";
+    }
+
+    let form, formData;
+    try {
+        [form, formData] = await openForm(resp, "form#mcp_report");
+    } catch {
+        throw "Konnte Formular zum Schließen der Meldung nicht öffnen.";
+    }
+
+    resp = await postForm(form, formData, "action[close]");
+    if (!resp.ok) {
+        throw "Konnte Schließen der Meldung nicht anfragen.";
+    }
+
+    try {
+        await confirmAction(resp);
+    } catch (err) {
+        throw `Konnte Schließen der Meldung nicht bestätigen: ${err}`;
+    }
+}
+
+async function confirmAction(response) {
+    const [form, formData] = await openForm(response, "form#confirm");
+
+    /* Have to use explicit getAttribute() below since there is an input with
+     * name="action" which would be accessed with `form.action` :-/
+     */
+    const resp = await postForm(form, formData, "confirm");
+    if (!resp.ok) {
+        throw `${resp.url}: ${resp.status}`;
+    }
+    return await resp.text();
+}
 
 function ellipsify(str, maxlen) {
-       const ell = str.length > maxlen ? " […]" : "";
+    const ell = str.length > maxlen ? " […]" : "";
     return str.substring(0, maxlen - ell.length) + ell;
 }
 
-function get_thread_id() {
-    return new URL(document.head.querySelector("link[rel=canonical]").href).searchParams.get("t");
+function isPostReported(post) {
+    return post.querySelector(".post-notice.reported a") !== null;
 }
 
-function get_forum_id() {
-    return new URLSearchParams(document.querySelector('a[href*="f="]').search).get("f");
+async function openForm(response, selector) {
+    const parser = new DOMParser();
+    const txt = await response.text();
+    const doc = parser.parseFromString(txt, "text/html");
+    const form = doc.querySelector(selector);
+    return [form, new FormData(form)];
 }
 
-function redirect_to_new_topic(redirDoc) {
-    const links = redirDoc.querySelectorAll("#message a");
-    window.location = links[links.length-1].href;
+function postForm(form, formData, submitName) {
+    /* "Press" the right submit button. */
+    const submitBtn = form.elements[submitName];
+    formData.set(submitBtn.name, submitBtn.value);
+
+    /* Have to use explicit getAttribute() below since there is an input with
+     * name="action" which would be accessed with `form.action` :-/
+     */
+    return fetch(form.getAttribute("action"),
+        { body: new URLSearchParams(formData), method: "POST" });
 }
 
-function remove_post_handler(event) {
+async function remove_post_handler(event) {
     const post = event.currentTarget.closest('.post');
-    const post_id = post.id.slice(1);
     const username = post.querySelector(".author .username,.author .username-coloured").text;
     const thread_title = document.querySelector('.topic-title a').text;
     const content = ellipsify(post.querySelector(".content").innerText, 250);
-    if (localStorage.getItem("deleteWithoutConfirmation") ?? window.confirm(`Folgenden Beitrag von „${username}“ im Thema „${ellipsify(thread_title, 100)}“ wirklich als Spam archivieren?\n\n„${content}“`)) {
-        send_mcp_request_archival(post_id, get_thread_id(), thread_title);
+
+    const splitReason = window.prompt(`Folgenden Beitrag von „${username}“ im Thema „${ellipsify(thread_title, 100)}“ wirklich als Spam archivieren?\n\n„${content}“\n\nGrund:`, "Spam");
+    if (splitReason === null) {
+        /* Don't do any of the other actions if splitting was cancelled. */
+        return;
     }
-}
 
-async function send_mcp_request_confirmation(post_id, confirmDoc) {
-    const form = confirmDoc.querySelector("form#confirm");
-    const data = new FormData(form);
-    const confirmInput = form.elements["confirm"];
-    data.set(confirmInput.name, confirmInput.value); // Press "Yes"
-    /* Have to use explicit getAttribute() below since there is an input with
-     * name="action" which would be accessed with `form.action` :-/
+    /* Prompting for a separate ban reason in case there is something more
+     * specific to note here.
+     */
+    const banReason = window.prompt(`Benutzer „${username}“ sperren?\n\nGrund:`, "Spam");
+    const shouldCloseReport = isPostReported(post) && window.confirm("Meldung zum Beitrag schließen?");
+
+    /* Initially, I wanted to use Promise.allSettled() below to trigger and wait
+     * for all actions in parallel. But that made at least one of them fail in
+     * most cases. Not sure, if there was a mistake in here somewhere (totally
+     * not impossible) or if phpBB just does not cope well with parallel mod
+     * actions (there are some session IDs and confirm_keys involved when a mod
+     * action is executed and confirmed).
+     *
+     * So essentially, we do not have any asynchronous execution here,
+     * unfortunately.
      */
-    const resp = await fetch(form.getAttribute("action"),
-        { body: new URLSearchParams(data), method: "POST" });
-    if (resp.ok) {
-        if (localStorage.getItem("redirectToNewTopic") ?? true) {
-            const respTxt = await resp.text();
-            const respDoc = new DOMParser().parseFromString(respTxt, "text/html");
-            if (!respDoc.querySelector("parsererror")) {
-                redirect_to_new_topic(respDoc);
-            }
-        } else {
-            // hide the freshly archived post
-            document.querySelector('.post#p' + post_id).style.display = "none";
+    const errors = new Array();
+    try {
+        await send_mcp_request_archival(post, splitReason);
+    } catch (err) {
+        errors.push(err);
+    }
+
+    if (banReason !== null) {
+        try {
+            await banUser(username, banReason);
+        } catch (err) {
+            errors.push(err);
         }
     }
-}
 
-async function send_mcp_request_archival(post_id, thread_id, thread_title) {
-    const url = boardurl + "mcp.php?f=" + get_forum_id() + "&t=" + thread_id + "&i=main&mode=topic_view&action=split&start=0";
-    const data = new URLSearchParams({
-        "action": "split_all",
-        "mcp_topic_submit": "Absenden",
-        "post_id_list[]": post_id,
-        "posts_per_page": "15",
-        "st": "0",
-        "sk": "t",
-        "sd": "a",
-        "icon": "1",
-        "subject": thread_title.toLowerCase().includes("spam") ? thread_title : "[spam] " + thread_title,
-        "to_forum_id": ARCHIVFORUMID,
-        "to_topic_id": "0",
-        "st_old": "0",
-        "post_ids[0]": post_id
-    });
-    const resp = await fetch(url, { body: data, method: "POST" });
-    if (resp.ok) {
-        const respTxt = await resp.text();
-        const respDoc = new DOMParser().parseFromString(respTxt, "text/html");
-        if (!respDoc.querySelector("parsererror")) {
-            send_mcp_request_confirmation(post_id, respDoc);
+    if (shouldCloseReport) {
+        try {
+            await closeReport(post);
+        } catch (err) {
+            errors.push(err);
         }
     }
+
+    for (const error of errors) {
+        console.log(error);
+        window.alert(`ACHTUNG!\n\n${error}`);
+    }
+
+    if (errors.length === 0) {
+        updatePageAfterSplit(post);
+    }
 }
 
-function add_buttons() {
-    const postbuttons = document.querySelectorAll(".postbody .post-buttons");
-    postbuttons.forEach(function(el) {
-        // well...
-        el.style.maxWidth = '41%';
+async function send_mcp_request_archival(post, reason) {
+    const splitLink = document.querySelector("#quickmod .dropdown-contents a[href*='action=split']");
+    let resp = await fetch(splitLink.href);
+    if (!resp.ok) {
+        throw "Konnte Formular zum Aufteilen des Themas nicht laden.";
+    }
+
+    let form, formData;
+    try {
+        [form, formData] = await openForm(resp, "form#mcp");
+    } catch {
+        throw "Konnte Formular zum Aufteilen des Themas nicht öffnen.";
+    }
+
+    const post_id = post.id.slice(1);
+    const thread_title = (() => {
+        const prefix = `[${reason}]`;
+        let title = formData.get("subject");
+        if (reason && !title.toLowerCase().includes(prefix.toLowerCase())) {
+            title = `${prefix} ${title}`;
+        }
+        return title.slice(0, form.elements["subject"].maxLength);
+    })();
+    formData.set("post_id_list[]", post_id);
+    formData.set("subject", thread_title);
+    formData.set("to_forum_id", ARCHIVFORUMID);
 
-        const del_post_btn_outer = document.createElement('li');
-        const del_post_btn = document.createElement('a');
-        del_post_btn.className = 'button button-icon-only';
-        del_post_btn.innerHTML = '<i class="icon fa-fire-extinguisher fa-fw" aria-hidden="true"></i><span class="sr-only">Abfall</span>';
+    resp = await postForm(form, formData, "mcp_topic_submit");
+    if (!resp.ok) {
+        throw "Konnte Aufteilen des Themas nicht anfragen.";
+    }
 
-        del_post_btn.addEventListener("click", remove_post_handler);
+    try {
+        await confirmAction(resp);
+    } catch (err) {
+        throw `Konnte Aufteilen des Themas nicht bestätigen: ${err}`;
+    }
+}
 
-        del_post_btn_outer.append(del_post_btn);
-        el.insertBefore(del_post_btn_outer, el.querySelector('.dropdown-container'));
-    });
+function updatePageAfterSplit(post) {
+    if (document.querySelectorAll(".post").length > 1) {
+        post.parentNode.removeChild(post);
+    } else {
+        /* TODO: Make the location configurable, redirect to homepage, "Aktive
+         * Themen", "Neue Beiträge", or other?
+         */
+        window.location = "/";
+    }
 }
 
-function find_mod_user_id() {
-    const profile_link = document.querySelector("#username_logged_in a[title^='Profil']");
-    return Number.parseInt(new URLSearchParams(profile_link?.search).get("u"));
+function add_buttons() {
+    const del_post_btn_outer = document.createElement('li');
+    const del_post_btn = document.createElement('a');
+    del_post_btn.className = 'button button-icon-only';
+    del_post_btn.innerHTML = '<i class="icon fa-fire-extinguisher fa-fw" aria-hidden="true"></i><span class="sr-only">Abfall</span>';
+    del_post_btn.addEventListener("click", remove_post_handler);
+    del_post_btn.title = "Als Spam archivieren";
+    del_post_btn_outer.append(del_post_btn);
+
+    for (const postButtons of document.querySelectorAll(".post-buttons")) {
+        const del_post_btn_outer_clone = del_post_btn_outer.cloneNode(true);
+        del_post_btn_outer_clone.addEventListener("click", remove_post_handler);
+        postButtons.appendChild(del_post_btn_outer_clone);
+    }
 }
 
-mod_user_id ??= find_mod_user_id();
-if (Number.isInteger(mod_user_id)) {
-    add_buttons();
-} else {
-    console.error("mod_user_id unset and could not find user ID");
-}
\ No newline at end of file
+add_buttons();
\ No newline at end of file