User:DVRTed/RecordSpam.js

/* * notes: ** prettier with default config is used to format this code; */

(async => {  // Page where the logs will be stored  const LOG_PAGE = "User:DVRTed/SpamLog";  /* global mw, $ */  const API = new mw.Api;

const sign = "(via RecordSpam)";

/**  * "Send" a message, i.e. write on the popup's info section * @param {string} message * @param {string} type Default is `error` that results in a red-background, otherwise blue-ish color is used * @returns */ function send_message(message, type = "error") { if (!message) { $(".script_message").addClass("hidden"); $(".script_message").html(""); return; }

$(".script_message").removeClass("hidden"); $(".script_message").html(message); $(".script_message").removeClass("error"); if (type == "error") { $(".script_message").addClass("error"); } }

/**  * Get a list of sections * @returns array of sections w/ sub-sections */ async function get_existing_sections { // parse headings (sections) from `LOG_PAGE` const { parse } = await API.get({     action: "parse",      prop: ["sections"],      page: LOG_PAGE,      format: "json",    });

const main_sections = parse.sections // select level 2 sections, i.e. == heading == .filter((sec) => parseInt(sec.level) === 2) .map((sec) => {       return {          ...sec,          sub_sections: parse.sections.filter((s) => s.number.startsWith(`${sec.number}.`) ),       };      });

return main_sections; }

/**  * Checks if user is already exists under the relevant section * @param {int} section Section index * @param {string} user User/IP * @returns boolean */ async function check_user_exists(section, user) { const { parse } = await API.get({     action: "parse",      prop: ["wikitext"],      page: LOG_PAGE,      section,      format: "json",    }); const entries = parse.wikitext["*"].split("\n");

const check_match = entries.find((entry) => {     // dont bother running regex on empty string      if (!entry) return false;

const regex_match = entry.match(/^\*\s\{\{User\|(.*)\}\}/i); return regex_match && user === regex_match[1]; });

if (check_match) { return true; }   return false; }

/**  * Adds new section for the spam hostname * If user is provided, adds user underneath the Users section * If user already exists under the relevant section, displays an error message * @param {string} spamlink * @param {string} user * @returns */ async function add_entry(spamlink, user) { user = user?.trim; user = user.replace(/^User\s*:\s*/i, ""); spamlink = spamlink?.trim;

if (!spamlink) { send_message("URL input is empty!"); return; }

const spam_URL = new URL(spamlink); const host_name = spam_URL.hostname;

const sections = await get_existing_sections; const relevant_section = sections.find((sec) => sec.line === host_name);

if (relevant_section) { // hostname section exists send_message(       "A section for the hostname   already exists." +          (user ? "Appending user entry inside the section..." : ""),       "info"      ); if (!user) { toggle_loading; toggle_buttons(false); return; }     const users_section = relevant_section.sub_sections[1];

if (await check_user_exists(users_section.index, user)) { send_message(         "An entry for user " +            user +            " already exists on the section for  ."        ); toggle_loading; toggle_buttons(false); return; }

const { edit } = await API.postWithToken("csrf", {       action: "edit",        title: LOG_PAGE,

section: users_section.index, appendtext: `\n* `, summary: `Adding entry for ${user} ${sign}`,

format: "json", }).always(function { toggle_loading; toggle_buttons(false); });

if (edit.result === "Success") { send_message(         "Successfully added entry for the user " + user + "",          "info"        ); } else { console.error("Something went wrong while performing the edit:"); console.error(edit); send_message("Something went wrong while performing the edit."); }

return; }

// hostname section doesn't exist; create a new one: const report_text = "=== Linkback ===\n" + `* \n` + `* HTTP: \n` + `* HTTPS: \n\n` + `=== Users ===\n` + // leave empty if no user is specified (user ? `* \n` : "");

let summary = `Creating new section with no user entry ${sign}`;

if (user) summary = `Creating new section with added entry for ${user} ${sign}`;

const { edit } = await API.postWithToken("csrf", {     action: "edit",      title: LOG_PAGE,

section: "new", sectiontitle: host_name, text: report_text, summary: summary,

format: "json", }).always(function { toggle_loading; toggle_buttons(false); });

if (edit.result === "Success") { send_message(       "Successfully created a new section" +          (user ? " and added the user entry" : "") +         "!",        "info"      ); } else { console.error("Something went wrong while performing the edit:"); console.error(edit); send_message("Something went wrong while performing the edit."); } }

/**  * Toggle loading animation on the submit button * @param {boolean} disable If set to true, disable loading animation else enable. */ const toggle_loading = (disable = true) => { if (disable) $(".SpamLogDialog").find("#SubmitBtn").removeClass("loading"); else $(".SpamLogDialog").find("#SubmitBtn").addClass("loading"); };

/**  * Toggle b/w disabled and enabled state of all buttons * @param {boolean} disable If set to true, disable buttons else enable. */ const toggle_buttons = (disable = true) => { if (disable) $(".SpamLogDialog").find("button").addClass("disabled"); else $(".SpamLogDialog").find("button").removeClass("disabled"); };

/**  * Toggle b/w disabled and enabled state of the submit button only * @param {boolean} disable If set to true, disable buttons else enable. */ const toggle_submit_button = (disable = true) => { if (disable) $(".SpamLogDialog").find("#SubmitBtn").addClass("disabled"); else $(".SpamLogDialog").find("#SubmitBtn").removeClass("disabled"); };

function handle_events { // Cancel button $(".SpamLogDialog") .find("#CancelBtn") .on("click", function {        toggle_buttons;        $(".SpamLogDialog").addClass("hidden");        setTimeout( => { $("body").find(".SpamLogDialog").remove; }, 400);     });

// Submit button $(".SpamLogDialog") .find("#SubmitBtn") .on("click", async function {        send_message;        toggle_buttons;        toggle_loading(false);        const cur_URL = $("#SpamURL").val;        const cur_user = $("#SpamUser").val;

await add_entry(cur_URL, cur_user); });

// parse and write to hostname input $("#SpamURL").on("input", function {      try {        const parse_url = new URL($("#SpamURL").val);        if (!parse_url.hostname) throw "No hostname!";        $("#SpamHostname").val(parse_url.hostname);        toggle_submit_button(false);      } catch {        $("#SpamHostname").val("Invalid URL");        toggle_submit_button;      }    }); }

const POPUP_HTML = ` Record Spam Cancel User or IP address (prefix "User:" is not necessary)   Spam URL (e.g. https://somewebsite.com)  Parsed hostname  No message.

Recorded at: ${LOG_PAGE} Submit `;

// Add link "Record spam" under personal content-actions (? lol) const node = mw.util.addPortletLink(   "p-cactions",    "#",    "Record spam",    "spamrecord-usc"  );

// when the aforementioned link is clicked, $(node).on("click", function (e) {   e.preventDefault;    // remove if there's an existing dialog box    $("body").find(".SpamLogDialog").remove;

// inject html for the popup display $("body").append(POPUP_HTML); handle_events;

const relevant_username = mw.config.get("wgRelevantUserName"); if (relevant_username && relevant_username !== mw.config.get("wgUserName")) $(".SpamLogDialog").find("#SpamUser").val(relevant_username);

// just for the kewl (cool) animation setTimeout( => {     $(".SpamLogDialog").removeClass("hidden");    }, 0); });

// CSS for the dialog box const CSS = ` .SpamLogDialog { font-family: Arial, Helvetica, sans-serif; background-color: white; position: fixed; border-radius: 10px; box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px; width: 800px; height: 400px; z-index: 99; left: 50%; transform: translate(-50%, 0); top: 10%; padding: 30px; opacity: 1; transition: all 0.2s ease-in-out; }

.SpamLogDialog.hidden { left: 40%; opacity: 0; }

.SpamLogDialog .mainContent { margin: 20 0px; }

.SpamLogDialog .heading { display: flex; justify-content: space-between; align-items: center; }

.SpamLogDialog .footer { display: flex; justify-content: space-between; align-items: center; margin: 10px;

}

.SpamLogDialog .txt { color: #808080; }

.SpamLogDialog h1 { font-size: 19pt; border: none; font-weight: normal; }

.SpamLogDialog .divider { border-bottom: 1px solid #e7e6e6; }

.SpamLogDialog .spacer { margin: 40px 0; }

.SpamLogDialog button { padding: 10px 20px; border-radius: 5px; position: relative; border: none; color: white; font-size: 16px; cursor: pointer; transition: all 0.2s ease-in-out; }

.SpamLogDialog button:hover { transform: translateY(-2px); box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.2); }

button.danger { background-color: #580234; }

button.danger:hover { background-color: #a0055f; }

button.primary { background-color: #027740; }

button.primary:hover { background-color: #08a15a; }

.SpamLogDialog button.loading, button.disabled { position: relative; pointer-events: none; background-color: rgb(143, 167, 167); }

.SpamLogDialog button.loading:before { content: ""; position: absolute; left: -30px; border: 2px solid rgb(0, 0, 0); border-top: 2px solid rgb(255, 0, 0); border-radius: 50%; width: 16px; height: 16px; animation: spinner 1s linear infinite; }

@keyframes spinner { 0% {     transform: rotate(0deg); }

100% {     transform: rotate(360deg); } }

.SpamLogDialog label { font-size: 12pt; display: block; margin: 10px 0; }

.SpamLogDialog .URL_detail { display: flex; justify-content: space-between; }

.SpamLogDialog input[type="text"] { padding: 10px; border-radius: 5px; border: none; border: 2px solid #ccc; font-size: 16px; width: 300px; transition: all 0.2s ease-in-out; }

.SpamLogDialog input[type="text"]:focus { border-color: #4caf50; outline: none; box-shadow: 0px 0px 10px #4caf50; }

.SpamLogDialog input#SpamURL { width: 400px; }

.SpamLogDialog input#SpamHostname { background: rgb(216, 214, 214); }

.SpamLogDialog .script_message { background: #1a6cb9; color: white; padding: 9px; width: 100%; margin: 20px 0; text-align: center; font-size: 14pt; }

.SpamLogDialog code { font-size: 10pt; }

.SpamLogDialog .script_message.hidden { visibility: hidden; }

.SpamLogDialog .script_message.error { background: #d10034; }`;

// Load CSS mw.loader.addStyleTag(CSS, "text/css"); });