Greasy Fork

来自缓存

Greasy Fork is available in English.

Roblox Role Dropdown

A simple tool that adds a dropdown menu to change the role of your group members directly from the group wall.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Roblox Role Dropdown
// @description  A simple tool that adds a dropdown menu to change the role of your group members directly from the group wall.
// @author       HotDog6400
// @copyright    Copyright (c) 2021, 2025 HotDog640
// @namespace    https://www.roblox.com/users/694999148/
// @license      MIT
// @version      0.9.6
// @match        https://*.roblox.com/groups/*
// @match        https://*.roblox.com/communities/*
// @grant        none
// ==/UserScript==

/* eslint-disable curly, no-multi-spaces, no-undef */
// @ts-check

// *******************
// * Editable config *
// *******************
const RRDConfig = {
    enabled: true,              // Set this to false if you want to disable RRD.
    dropdown_width: '12rem'     // The width of dropdown. Can be any CSS length.
};

// *********************************************************************
// * Don't edit beyond this point! (unless you know what you're doing) *
// *********************************************************************
if (
    window.location.href.match(/roblox\.com\/(groups|communities)\/\d/) && RRDConfig.enabled
) (async () => {
    'use strict';
    /** @typedef {{ id: number; name: string; rank: number; }[]} Roles */

    console.log('RRD: Starting Roblox Role Dropdown');

    const groupId = window.location.href.split('/')[4];

    /** @type {Roles} */
    const roles = (await $.ajax(`https://groups.roblox.com/v1/groups/${groupId}/roles`)).roles;
    const currentUser = await $.ajax(`https://groups.roblox.com/v1/groups/${groupId}/membership`);

    if (!roles || !currentUser)
        return console.error('RRD: Error: Could not load the required resources');
    if (currentUser.permissions.groupMembershipPermissions.changeRank === false)
        return console.log('RRD: Exiting: Authenticated user can\'t change roles in this group');

    ///////////////////////////////////////////////////////////////////////////

    let RBXAlertTimeout;

    /**
     * @param {string} message
     * @param {boolean} [success]
     */
    const RBXAlert = (message, success = true) => {
        const alertClasses = ['alert-warning', 'alert-success'];
        const alertBox = $('.alert')[0];
        const alertContent = $('.alert-content')[0];

        const resetAlert = () => {
            alertBox.classList.remove('on');
            alertBox.classList.add('alert-success');
        }
        resetAlert()

        clearTimeout(RBXAlertTimeout);

        setTimeout(() => {
            alertBox.classList.remove(alertClasses[success ? 0 : 1]);
            alertBox.classList.add(alertClasses[success ? 1 : 0]);
            alertBox.classList.add('on');
            alertContent.innerText = message;
            RBXAlertTimeout = setTimeout(resetAlert, 5000);
        }, 100);
    }

    /** Global function for the dropdowns to access
     * @param {HTMLAnchorElement} anchor
     * @param {number} userId
     * @param {number} roleId
     * @param {string} roleName
     */
    // @ts-ignore
    window.changeRole = async (anchor, userId, roleId, roleName) => {
        $.ajax({
            method:'PATCH',
            url: `https://groups.roblox.com/v1/groups/${groupId}/users/${userId}`,
            data: { roleId }
        }).done(() => {
            const button = $(anchor.parentElement.parentElement.parentElement).find('.input-dropdown-btn')[0];
            button.title = $(button).find('.rbx-selection-label')[0].innerHTML = roleName;
            RBXAlert('Successfully changed role.');
        }).fail(res => {
            RBXAlert(res.responseJSON.errors[0].userFacingMessage, false);
        });
    }

    const allowedRoles = roles.filter(role =>
        role.rank !== 0 && role.rank < currentUser.userRole.role.rank
    );

    /**
     * @param {string} currentRole
     * @param {string} userId
     * @param {boolean} disabled
     * @returns {ChildNode}
     */
    const generateDropdown = (currentRole, userId, disabled) => {
        const template = document.createElement('template');
        template.innerHTML =
            `<div class="input-group-btn">
                <button class="input-dropdown-btn" data-toggle="dropdown" title="${currentRole}" ${disabled ? 'disabled: "disabled"' : ''}>
                    <span class="rbx-selection-label">${currentRole}</span>
                    <span class="icon-down-16x16"></span>
                </button>
                <ul class="dropdown-menu" data-toggle="dropdown-menu" role="menu">
                    ${allowedRoles.map(role =>
                        `<li title="${role.name}">
                            <a onClick="changeRole(this, ${userId}, ${role.id}, '${role.name}')">
                                <span class="text-overflow">${role.name}</span>
                            </a>
                        </li>`
                    ).join('')}
                </ul>
            </div>`;
        return template.content.firstChild;
    }

    /**
     * @param {JQuery<Node>} $post
     */
    // TODO: Make the dropdown disabled if the target's role is the same or higher than the auth'd user
    const putDropdown = ($post) => {
        const userId = /** @type {HTMLAnchorElement} */ ($post.find(
            '.avatar-headshot .avatar-card-link'
        )[0]).href.split('/')[4];
        const footer = $post.find('.text-caption-medium')[0];
        const content = $post.find('.list-body .list-content')[0];

        const footerArr = footer.innerText.split('|');
        const date = footerArr.splice(footerArr.length - 2, 2).join('|').trim();
        const currentRole = footerArr.join('|').trim();

        // const currentRole = footer.innerText.split('|')[0].trim();

        const disabled = false;

        const dropdown = generateDropdown(currentRole, userId, disabled);

        content.style.margin = '0 0 6px';
        footer.style.display = 'inline-grid';
        footer.style.gridTemplateColumns = `${RRDConfig.dropdown_width} auto`;
        footer.style.gridColumnGap = '8px';
        footer.style.alignItems = 'center';

        footer.innerHTML = date;
        footer.prepend(dropdown);
    }

    /**
     * @param {Element} posts
     */
    const createObserver = (posts) => {
        const observer = new MutationObserver(mutationsList =>
            mutationsList.forEach(mutation => {
                const $post = $(mutation.addedNodes[0]);
                if ($post.hasClass('comment'))
                    putDropdown($post);
            })
        );
        observer.observe(posts, { childList: true });
    }

    const posts = document.querySelector('.group-comments');
    if (posts) createObserver(posts);
    else {
        // Wait for posts to exist
        const observer = new MutationObserver(() => {
            const posts = document.querySelector('.group-comments');
            if (posts) {
                createObserver(posts);
                observer.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }
})();