Neoboards User Buttons

Inserts “Neomail User” and “Copy Username” buttons to the left of report buttons on Neoboards posts with custom styling.

// ==UserScript==
// @name         Neoboards User Buttons
// @namespace    http://snotspoon.neocities.com
// @version      1.3
// @description  Inserts “Neomail User” and “Copy Username” buttons to the left of report buttons on Neoboards posts with custom styling.
// @match        *://www.neopets.com/neoboards/*
// @grant        none
// @author       @nadinejun0
// @license      MIT
// ==/UserScript==

(() => {
    'use strict';

    // Inject custom CSS
    const css = `
    /* Container for our extra buttons */
    #boardTopic ul li .np-extra-buttons {
      margin-bottom: 5px;
      clear: both;
    }
    /* Extra button style */
    #boardTopic ul li .np-extra-buttons .np-extra-button {
      border: 1px solid #CACACA;
      font-family: "Palanquin", "Arial Bold", sans-serif;
      font-size: 9pt;
      line-height: 9pt;
      padding: 5px 10px;
      box-sizing: border-box;
      color: #CACACA;
      background-color: #FFF;
      border-radius: 10px;
      margin: 5px 5px 5px 0;
      display: inline-block;
      float: left;
      position: relative;
    }
    /* Hover state */
    #boardTopic ul li .np-extra-buttons .np-extra-button:hover {
      background-color: #CACACA;
      color: #FFF;
      cursor: pointer;
    }
    /* Active state: while pressed, show "Copied!" overlay */
    #boardTopic ul li .np-extra-buttons .np-extra-button:active::after {
      content: "Copied!";
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #FFF;
      background-color: #CACACA;
      border-radius: 10px;
    }
    `;
    const styleElem = document.createElement('style');
    styleElem.textContent = css;
    document.head.appendChild(styleElem);

    // extract username from userlookup link
    const getUsername = link => {
        try {
            const url = new URL(link.href, window.location.href);
            return url.searchParams.get('user');
        } catch {
            const match = link.href.match(/[?&]user=([^&#]+)/);
            return match ? decodeURIComponent(match[1]) : null;
        }
    };

    // create button element
    const createExtraButton = (label, clickHandler) => {
        const btn = document.createElement('button');
        btn.textContent = label;
        btn.className = 'np-extra-button';
        btn.addEventListener('click', clickHandler);
        return btn;
    };

    // insert extra buttons into the post byline if not already added.
    const processByline = byline => {
        if (byline.dataset.npExtraAdded) return;
        const link = byline.querySelector('a[href*="userlookup.phtml?user="]');
        if (!link) return;
        const username = getUsername(link);
        if (!username) return;
        const container = document.createElement('div');
        container.className = 'np-extra-buttons';

        // "Neomail User" button: opens neomail in a new tab.
        container.appendChild(createExtraButton('Neomail User', () => {
            window.open(`https://www.neopets.com/neomessages.phtml?type=send&recipient=${encodeURIComponent(username)}`, '_blank');
        }));
        // "Copy Username" button: copies username to clipboard.
        container.appendChild(createExtraButton('Copy Username', e => {
            e.preventDefault();
            if (navigator.clipboard && window.isSecureContext) {
                navigator.clipboard.writeText(username);
            } else {
                const tempInput = document.createElement('input');
                tempInput.value = username;
                document.body.appendChild(tempInput);
                tempInput.select();
                document.execCommand('copy');
                document.body.removeChild(tempInput);
            }
        }));

        byline.dataset.npExtraAdded = 'true';
        return container;
    };

    // process each report button and insert our extra buttons before it - float:left
    const processReportButtons = () => {
        document.querySelectorAll('.reportButton-neoboards').forEach(reportBtn => {
            const byline = reportBtn.closest('li')?.querySelector('.boardPostByline');
            if (!byline) return;
            const extraButtons = processByline(byline);
            if (extraButtons) reportBtn.parentNode.insertBefore(extraButtons, reportBtn);
        });
    };

    // DOM
    if (document.readyState !== 'loading') processReportButtons();
    else document.addEventListener('DOMContentLoaded', processReportButtons);
})();