// @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