MAL Conversation Button Adder (Async, DOMParser)

Adds a conversation button to each comment on a MAL profile using async/await and DOMParser to extract numerical user IDs.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MAL Conversation Button Adder (Async, DOMParser)
// @namespace    ConversationMAL
// @version      2
// @description  Adds a conversation button to each comment on a MAL profile using async/await and DOMParser to extract numerical user IDs.
// @author       Indochina
// @match        https://myanimelist.net/profile/*
// @match        https://myanimelist.net/comments.php?id=*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(async function() {
    'use strict';

    // --- STEP 1: Extract the Profile Owner’s Numerical ID ---
    // Look for the Report link and extract the "id" parameter.
    const reportLink = document.querySelector('a.header-right[href*="/modules.php?go=report"]');
    let ownerId = null;
    if (reportLink) {
        const match = reportLink.href.match(/id=(\d+)/);
        if (match) {
            ownerId = match[1];
            console.log(`Profile owner ID extracted: ${ownerId}`);
        }
    }
    if (!ownerId) {
        console.error("Could not determine the profile owner's numerical ID from the Report link.");
        return;
    }

    // --- STEP 2: Define a helper to fetch and extract a commenter's numerical ID ---
    // We use a cache to avoid refetching the same profile.
    const userIdCache = {};

    async function getUserId(username) {
        if (userIdCache[username]) return userIdCache[username];
        try {
            // Fetch the commenter's profile page
            const response = await fetch(`https://myanimelist.net/profile/${username}`);
            const text = await response.text();

            // Parse the HTML response to a document
            const parser = new DOMParser();
            const doc = parser.parseFromString(text, 'text/html');

            // Look for the Report link on the fetched profile page.
            // (This should work for profiles that include it.)
            const reportLink = doc.querySelector('a.header-right[href*="/modules.php?go=report"]');
            if (reportLink) {
                const match = reportLink.href.match(/id=(\d+)/);
                if (match) {
                    const userId = match[1];
                    userIdCache[username] = userId;
                    console.log(`Extracted user id for ${username} via report link: ${userId}`);
                    return userId;
                }
            }

            // If the Report link wasn't found, log an error.
            console.error(`Could not extract user_id for ${username}`);
            return null;
        } catch (error) {
            console.error(`Error fetching profile for ${username}:`, error);
            return null;
        }
    }

    // --- STEP 3: Process each comment ---
    // We target each comment container. In many MAL pages, the comment content is within a <div class="text"> element.
    const commentDivs = document.querySelectorAll('div.text');
    for (const commentDiv of commentDivs) {
        // Find the comment author’s profile link; usually an <a class="fw-b"> element.
        const profileLink = commentDiv.querySelector('a.fw-b[href*="/profile/"]');
        if (!profileLink) continue;

        // Extract the username from the URL.
        // For example: "https://myanimelist.net/profile/Indochina"
        const urlParts = profileLink.href.split('/profile/');
        if (urlParts.length < 2) continue;
        const username = urlParts[1].split('?')[0];
        console.log(`Processing comment by: ${username}`);

        // Find or create the container for action links.
        let actionsDiv = commentDiv.querySelector('div.postActions');
        if (!actionsDiv) {
            actionsDiv = document.createElement("div");
            actionsDiv.className = "postActions ar mt4";
            actionsDiv.style.clear = "both";
            actionsDiv.style.paddingTop = "10px";
            commentDiv.appendChild(actionsDiv);
        }

        // If a Conversation link already exists, skip this comment.
        if (actionsDiv.querySelector('a.ml8[href*="comtocom.php"]')) {
            console.log(`Conversation link already exists for ${username}`);
            continue;
        }

        // --- STEP 4: Fetch the commenter's numerical ID and create the button ---
        const commenterId = await getUserId(username);
        if (!commenterId) continue;

        // Create the Conversation button.
        // id1 is the profile owner's id and id2 is the commenter's id.
        const convoLink = document.createElement("a");
        convoLink.className = "ml8";
        convoLink.href = `https://myanimelist.net/comtocom.php?id1=${ownerId}&id2=${commenterId}`;
        convoLink.textContent = "Conversation";
        convoLink.style.marginLeft = "10px";

        // Append the button to the actions container.
        actionsDiv.appendChild(convoLink);
    }
})();