zerebos / BetterDiscordAddons

A series of plugins and themes for BetterDiscord.
Other
596 stars 407 forks source link

[Bug] Permissionsviewer not working #794

Open lee61 opened 2 weeks ago

lee61 commented 2 weeks ago

Which plugin/theme is this about? PermissionsViewer

Describe the Bug I can't see the permissions even tho permissionviewer is up and running

To Reproduce try and see their permissions

Expected Behavior should be able to see the behavior

Screenshots image image

Discord Version

Additional Context everything should be updated ive also read Duplicate of https://github.com/zerebos/BetterDiscordAddons/issues/424, but the thread seems very confusing, please break it down for me here

dedArkash commented 1 week ago

True

iDeparture commented 1 week ago

image

ColonelGerdauf commented 1 week ago

What would we be able to do as an interim fix? What needs to be replaced?

Pharaoh2k commented 5 days ago

I've made some changes to PermissionsViewer.plugin.js and it seems to work for me now. Please let me know if this works for you. EDIT 11/11/2024: Removed this version as it was only partially working. Please use the version in the next message. Should work fine, fully.

Pharaoh2k commented 5 days ago

Oops, this version should be better:

/**
 * @name PermissionsViewer
 * @description Allows you to view a user's permissions. Thanks to Noodlebox for the idea!
 * @version 0.2.11
 * @author Zerebos
 * @authorId 249746236008169473
 * @website https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer
 * @source https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js
 */
/*@cc_on
@if (@_jscript)

    // Offer to self-install for clueless users that try to run this directly.
    var shell = WScript.CreateObject("WScript.Shell");
    var fs = new ActiveXObject("Scripting.FileSystemObject");
    var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins");
    var pathSelf = WScript.ScriptFullName;
    // Put the user at ease by addressing them in the first person
    shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30);
    if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
        shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
    } else if (!fs.FolderExists(pathPlugins)) {
        shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
    } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
        fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
        // Show the user where to put plugins in the future
        shell.Exec("explorer " + pathPlugins);
        shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
    }
    WScript.Quit();

@else@*/
const config = {
    info: {
        name: "PermissionsViewer",
        authors: [
            {
                name: "Zerebos",
                discord_id: "249746236008169473",
                github_username: "zerebos",
                twitter_username: "IAmZerebos"
            }
        ],
        version: "0.2.11",
        description: "Allows you to view a user's permissions. Thanks to Noodlebox for the idea!",
        github: "https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer",
        github_raw: "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js"
    },
    changelog: [
        {
            title: "Fixes",
            type: "fixed",
            items: [
                "Quick fix for displaying permissions in user popouts!"
            ]
        }
    ],
    defaultConfig: [
        {
            type: "switch",
            id: "contextMenus",
            name: "Context Menus",
            value: true
        },
        {
            type: "switch",
            id: "popouts",
            name: "Popouts",
            value: true
        },
        {
            type: "radio",
            id: "displayMode",
            name: "Modal Display Mode",
            value: "compact",
            options: [
                {
                    name: "Cozy",
                    value: "cozy"
                },
                {
                    name: "Compact",
                    value: "compact"
                }
            ]
        }
    ],
    strings: {
        es: {
            contextMenuLabel: "Permisos",
            popoutLabel: "Permisos",
            modal: {
                header: "Permisos de {{name}}",
                rolesLabel: "Roles",
                permissionsLabel: "Permisos",
                owner: "@propietario"
            },
            settings: {
                popouts: {
                    name: "Mostrar en Popouts",
                    note: "Mostrar los permisos de usuario en popouts como los roles."
                },
                contextMenus: {
                    name: "Botón de menú contextual",
                    note: "Añadir un botón para ver permisos en los menús contextuales."
                }
            }
        },
        pt: {
            contextMenuLabel: "Permissões",
            popoutLabel: "Permissões",
            modal: {
                header: "Permissões de {{name}}",
                rolesLabel: "Cargos",
                permissionsLabel: "Permissões",
                owner: "@dono"
            },
            settings: {
                popouts: {
                    name: "Mostrar em Popouts",
                    note: "Mostrar as permissões em popouts como os cargos."
                },
                contextMenus: {
                    name: "Botão do menu de contexto",
                    note: "Adicionar um botão parar ver permissões ao menu de contexto."
                }
            }
        },
        de: {
            contextMenuLabel: "Berechtigungen",
            popoutLabel: "Berechtigungen",
            modal: {
                header: "{{name}}s Berechtigungen",
                rolesLabel: "Rollen",
                permissionsLabel: "Berechtigungen",
                owner: "@eigentümer"
            },
            settings: {
                popouts: {
                    name: "In Popouts anzeigen",
                    note: "Zeigt die Gesamtberechtigungen eines Benutzers in seinem Popup ähnlich den Rollen an."
                },
                contextMenus: {
                    name: "Kontextmenü-Schaltfläche",
                    note: "Fügt eine Schaltfläche hinzu, um die Berechtigungen mithilfe von Kontextmenüs anzuzeigen."
                }
            }
        },
        en: {
            contextMenuLabel: "Permissions",
            popoutLabel: "Permissions",
            modal: {
                header: "{{name}}'s Permissions",
                rolesLabel: "Roles",
                permissionsLabel: "Permissions",
                owner: "@owner"
            },
            settings: {
                popouts: {
                    name: "Show In Popouts",
                    note: "Shows a user's total permissions in their popout similar to roles."
                },
                contextMenus: {
                    name: "Context Menu Button",
                    note: "Adds a button to view the permissions modal to select context menus."
                },
                displayMode: {
                    name: "Modal Display Mode"
                }
            }
        },
        ru: {
            contextMenuLabel: "Полномочия",
            popoutLabel: "Полномочия",
            modal: {
                header: "Полномочия {{name}}",
                rolesLabel: "Роли",
                permissionsLabel: "Полномочия",
                owner: "Владелец"
            },
            settings: {
                popouts: {
                    name: "Показать во всплывающих окнах",
                    note: "Отображает полномочия пользователя в их всплывающем окне, аналогичном ролям."
                },
                contextMenus: {
                    name: "Кнопка контекстного меню",
                    note: "Добавить кнопку для отображения полномочий с помощью контекстных меню."
                }
            }
        }
    },
    main: "index.js"
};
class Dummy {
    constructor() {this._config = config;}
    start() {}
    stop() {}
}

if (!global.ZeresPluginLibrary) {
    BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.name ?? config.info.name} is missing. Please click Download Now to install it.`, {
        confirmText: "Download Now",
        cancelText: "Cancel",
        onConfirm: () => {
            require("request").get("https://betterdiscord.app/gh-redirect?id=9", async (err, resp, body) => {
                if (err) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9");
                if (resp.statusCode === 302) {
                    require("request").get(resp.headers.location, async (error, response, content) => {
                        if (error) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9");
                        await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), content, r));
                    });
                }
                else {
                    await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
                }
            });
        }
    });
}

module.exports = !global.ZeresPluginLibrary ? Dummy : (([Plugin, Api]) => {
     const plugin = (Plugin, Api) => {
    const {ContextMenu, DOM, Utils} = window.BdApi;
    const {DiscordModules, WebpackModules, Toasts, DiscordClasses, Utilities, DOMTools, ColorConverter, ReactTools} = Api;

    const GuildStore = DiscordModules.GuildStore;
    const SelectedGuildStore = DiscordModules.SelectedGuildStore;
    const MemberStore = DiscordModules.GuildMemberStore;
    const UserStore = DiscordModules.UserStore;
    const DiscordPerms = Object.assign({}, DiscordModules.DiscordPermissions);
    const AvatarDefaults = WebpackModules.getByProps("DEFAULT_AVATARS");
    const ModalClasses = WebpackModules.getByProps("root", "header", "small");

    const Strings = (() => {
    try {
        const messagesModule = WebpackModules.getModule(m => m?.Messages?.COPY_ID) 
            || WebpackModules.getModule(m => m?.default?.Messages?.COPY_ID)?.default 
            || WebpackModules.getModule(m => m?.COPY_ID);

        if (!messagesModule) {
            return {
                COPY_ID: "Copy ID",
                ADMINISTRATOR: "Administrator",
                MANAGE_GUILD: "Manage Server",
                MANAGE_ROLES: "Manage Roles",
                MANAGE_CHANNELS: "Manage Channels",
                KICK_MEMBERS: "Kick Members",
                BAN_MEMBERS: "Ban Members",
                CREATE_INSTANT_INVITE: "Create Invite",
                CHANGE_NICKNAME: "Change Nickname",
                MANAGE_NICKNAMES: "Manage Nicknames",
                MANAGE_EMOJIS: "Manage Emojis",
                MANAGE_WEBHOOKS: "Manage Webhooks",
                VIEW_AUDIT_LOG: "View Audit Log",
                VIEW_CHANNEL: "View Channel",
                SEND_MESSAGES: "Send Messages",
                SEND_TTS_MESSAGES: "Send TTS Messages",
                EMBED_LINKS: "Embed Links",
                ATTACH_FILES: "Attach Files",
                READ_MESSAGE_HISTORY: "Read Message History",
                MENTION_EVERYONE: "Mention Everyone",
                USE_EXTERNAL_EMOJIS: "Use External Emojis",
                CONNECT: "Connect",
                SPEAK: "Speak",
                MUTE_MEMBERS: "Mute Members",
                DEAFEN_MEMBERS: "Deafen Members",
                MOVE_MEMBERS: "Move Members",
                USE_VAD: "Use Voice Activity",
                PRIORITY_SPEAKER: "Priority Speaker",
                STREAM: "Video",
                VIEW_GUILD_ANALYTICS: "View Server Analytics"
            };
        }

        return messagesModule.Messages || messagesModule;
    } catch (e) {
        console.error("PermissionsViewer: Error initializing Strings", e);
        return {};
    }
})();

    const UserPopoutClasses = Object.assign({section: "section_ba4d80", heading: "heading_ba4d80", root: "root_c83b44"}, WebpackModules.getByProps("userPopoutOuter"), WebpackModules.getByProps("defaultColor", "eyebrow"), DiscordClasses.PopoutRoles, WebpackModules.getByProps("root", "expandButton"), WebpackModules.getModule(m => m?.heading && m?.section && Object.keys(m)?.length === 2));
    const RoleClasses = Object.assign({}, DiscordClasses.PopoutRoles, WebpackModules.getByProps("defaultColor", "eyebrow"), WebpackModules.getByProps("role", "roleName", "roleCircle"));

    const getRoles = (guild) => {
    try {
        if (!guild) return {};
        // First try the direct roles property
        if (guild.roles) return guild.roles;

        // Try to get roles through the GuildStore
        const guildData = GuildStore.getGuild(guild.id);
        if (guildData?.roles) return guildData.roles;

        // Try alternative methods to get roles
        const rolesModule = WebpackModules.getByProps('getRole', 'getRoles');
        if (rolesModule?.getRoles) {
            const roles = rolesModule.getRoles(guild.id);
            if (roles) return roles;
        }

        // Final fallback: try to get roles from the guild members
        if (guild.members) {
            const roles = {};
            for (const member of Object.values(guild.members)) {
                if (member.roles) {
                    for (const role of member.roles) {
                        if (!roles[role.id]) {
                            roles[role.id] = role;
                        }
                    }
                }
            }
            return roles;
        }

        console.warn('PermissionsViewer: Could not find roles through any method');
        return {};
    } catch (e) {
        console.error('PermissionsViewer: Error getting roles', e);
        return {};
    }
};

    if (DiscordPerms.STREAM) {
        DiscordPerms.VIDEO = DiscordPerms.STREAM;
        delete DiscordPerms.STREAM;
    }
    if (DiscordPerms.MANAGE_GUILD) {
        DiscordPerms.MANAGE_SERVER = DiscordPerms.MANAGE_GUILD;
        delete DiscordPerms.MANAGE_GUILD;
    }

    return class PermissionsViewer extends Plugin {
        constructor() {
            super();
            this.css = `.perm-user-avatar {
    border-radius: 50%;
    width: 16px;
    height: 16px;
    margin-right: 3px;
}

.member-perms-header {
    color: var(--header-secondary);
    display: flex;
    justify-content: space-between;
}

.member-perms {
    display: flex;
    flex-wrap: wrap;
    margin-top: 2px;
    max-height: 160px;
    overflow-y: auto;
    overflow-x: hidden;
}

.member-perms .member-perm .perm-circle {
    border-radius: 50%;
    height: 12px;
    margin-right: 4px;
    width: 12px;
}

.member-perms .member-perm .name {
    margin-right: 4px;
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.perm-details-button {
    cursor: pointer;
    height: 12px;
}

.perm-details {
    display: flex;
    justify-content: flex-end;
}

.member-perm-details {
    cursor: pointer;
}

.member-perm-details-button {
    fill: #72767d;
    height: 10px;
}

/* Modal */

@keyframes permissions-backdrop {
    to { opacity: 0.85; }
}

@keyframes permissions-modal-wrapper {
    to { transform: scale(1); opacity: 1; }
}

@keyframes permissions-backdrop-closing {
    to { opacity: 0; }
}

@keyframes permissions-modal-wrapper-closing {
    to { transform: scale(0.7); opacity: 0; }
}

#permissions-modal-wrapper {
    z-index: 100;
}

#permissions-modal-wrapper .callout-backdrop {
    animation: permissions-backdrop 250ms ease;
    animation-fill-mode: forwards;
    opacity: 0;
    background-color: rgb(0, 0, 0);
    transform: translateZ(0px);
}

#permissions-modal-wrapper.closing .callout-backdrop {
    animation: permissions-backdrop-closing 200ms linear;
    animation-fill-mode: forwards;
    animation-delay: 50ms;
    opacity: 0.85;
}

#permissions-modal-wrapper.closing .modal-wrapper {
    animation: permissions-modal-wrapper-closing 250ms cubic-bezier(0.19, 1, 0.22, 1);
    animation-fill-mode: forwards;
    opacity: 1;
    transform: scale(1);
}

#permissions-modal-wrapper .modal-wrapper {
    animation: permissions-modal-wrapper 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
    animation-fill-mode: forwards;
    transform: scale(0.7);
    transform-origin: 50% 50%;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    contain: content;
    justify-content: center;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    opacity: 0;
    pointer-events: none;
    position: absolute;
    user-select: none;
    z-index: 1000;
}

#permissions-modal-wrapper .modal-body {
    background-color: #36393f;
    height: 440px;
    width: auto;
    /*box-shadow: 0 0 0 1px rgba(32,34,37,.6), 0 2px 10px 0 rgba(0,0,0,.2);*/
    flex-direction: row;
    overflow: hidden;
    display: flex;
    flex: 1;
    contain: layout;
    position: relative;
}

#permissions-modal-wrapper #permissions-modal {
    contain: layout;
    flex-direction: column;
    pointer-events: auto;
    border: 1px solid rgba(28,36,43,.6);
    border-radius: 5px;
    box-shadow: 0 2px 10px 0 rgba(0,0,0,.2);
    overflow: hidden;
}

#permissions-modal-wrapper .header {
    background-color: #35393e;
    box-shadow: 0 2px 3px 0 rgba(0,0,0,.2);
    padding: 12px 20px;
    z-index: 1;
    color: #fff;
    font-size: 16px;
    font-weight: 700;
    line-height: 19px;
}

.role-side, .perm-side {
    flex-direction: column;
    padding-left: 6px;
}

.role-scroller, .perm-scroller {
    contain: layout;
    flex: 1;
    min-height: 1px;
    overflow-y: scroll;
}

#permissions-modal-wrapper .scroller-title {
    color: #fff;
    padding: 8px 0 4px 4px;
    margin-right: 8px;
    border-bottom: 1px solid rgba(0,0,0,0.3);
    display: none;
}

#permissions-modal-wrapper .role-side {
    width: auto;
    min-width: 150px;
    background: #2f3136;
    flex: 0 0 auto;
    overflow: hidden;
    display: flex;
    min-height: 1px;
    position: relative;
}

#permissions-modal-wrapper .role-scroller {
    contain: layout;
    flex: 1;
    min-height: 1px;
    overflow-y: scroll;
    padding-top: 8px;
}

#permissions-modal-wrapper .role-item {
    display: flex;
    border-radius: 2px;
    padding: 6px;
    margin-bottom: 5px;
    cursor: pointer;
    color: #dcddde;
}

#permissions-modal-wrapper .role-item:hover {
    background-color: rgba(0,0,0,0.1);
}

#permissions-modal-wrapper .role-item.selected {
    background-color: rgba(0,0,0,0.2);
}

#permissions-modal-wrapper .perm-side {
    width: 273px;
    background-color: #36393f;
    flex: 0 0 auto;
    display: flex;
    min-height: 1px;
    position: relative;
    padding-left: 10px;
}

#permissions-modal-wrapper .perm-item {
    box-shadow: inset 0 -1px 0 rgba(79,84,92,.3);
    box-sizing: border-box;
    height: 44px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    display: flex;
}

#permissions-modal-wrapper .perm-item.allowed svg {
    fill: #43B581;
}

#permissions-modal-wrapper .perm-item.denied svg {
    fill: #F04747;
}

#permissions-modal-wrapper .perm-name {
    display: inline;
    flex: 1;
    font-size: 16px;
    font-weight: 400;
    overflow: hidden;
    text-overflow: ellipsis;
    user-select: text;
    color: #dcddde;
    margin-left: 10px;
}

.member-perms::-webkit-scrollbar-thumb, .member-perms::-webkit-scrollbar-track,
#permissions-modal-wrapper *::-webkit-scrollbar-thumb, #permissions-modal-wrapper *::-webkit-scrollbar-track {
    background-clip: padding-box;
    border-radius: 7.5px;
    border-style: solid;
    border-width: 3px;
    visibility: hidden;
}

.member-perms:hover::-webkit-scrollbar-thumb, .member-perms:hover::-webkit-scrollbar-track,
#permissions-modal-wrapper *:hover::-webkit-scrollbar-thumb, #permissions-modal-wrapper *:hover::-webkit-scrollbar-track {
    visibility: visible;
}

.member-perms::-webkit-scrollbar-track,
#permissions-modal-wrapper *::-webkit-scrollbar-track {
    border-width: initial;
    background-color: transparent;
    border: 2px solid transparent;
}

.member-perms::-webkit-scrollbar-thumb,
#permissions-modal-wrapper *::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    border-radius: 4px;
    cursor: move;
    background-color: rgba(32,34,37,.6);
}

.member-perms::-webkit-scrollbar,
#permissions-modal-wrapper *::-webkit-scrollbar {
    height: 8px;
    width: 8px;
}

.theme-light #permissions-modal-wrapper #permissions-modal {
    background: #fff;
}

.theme-light #permissions-modal-wrapper .modal-body {
    background: transparent;
}

.theme-light #permissions-modal-wrapper .header {
    background: transparent;
    color: #000;
}

.theme-light #permissions-modal-wrapper .role-side {
    background: rgba(0,0,0,.2);
}

.theme-light #permissions-modal-wrapper .perm-side {
    background: rgba(0,0,0,.1);
}

.theme-light #permissions-modal-wrapper .role-item,
.theme-light #permissions-modal-wrapper .perm-name {
    color: #000;
}

#permissions-modal-wrapper #permissions-modal {
    width: auto;
}`;
            this.jumbo = `#permissions-modal-wrapper #permissions-modal {
    height: 840px;
}

#permissions-modal-wrapper #permissions-modal .perm-side {
    width: 500px;
}

#permissions-modal .perm-scroller {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
}

#permissions-modal .perm-item {
    width: 50%;
}`;
            this.sectionHTML = `<div class="{{section}}" id="permissions-popout">
    <h1 class="member-perms-header {{text-xs/semibold}} {{defaultColor_e9e35f}} {{heading}}" data-text-variant="text-xs/semibold" style="color: var(--header-secondary);">
        <div class="member-perms-title">{{sectionTitle}}</div>
        <span class="perm-details">
            <svg name="Details" viewBox="0 0 24 24" class="perm-details-button" fill="currentColor">
                <path d="M0 0h24v24H0z" fill="none"/>
                <path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
            </svg>
        </span>
    </h1>
    <div class="member-perms {{root}}"></div>
</div>`;
            this.itemHTML = `<div class="member-perm {{role}}">
    <div class="perm-circle {{roleCircle}}"></div>
    <div class="name {{roleName}} {{defaultColor}}"></div>
</div>`;
            this.modalHTML = `<div id="permissions-modal-wrapper">
        <div class="callout-backdrop {{backdrop}}"></div>
        <div class="modal-wrapper">
            <div id="permissions-modal" class="{{root}} {{small}}">
                <div class="header"><div class="title">{{header}}</div></div>
                <div class="modal-body">
                    <div class="role-side">
                        <span class="scroller-title role-list-title">{{rolesLabel}}</span>
                        <div class="role-scroller">

                        </div>
                    </div>
                    <div class="perm-side">
                        <span class="scroller-title perm-list-title">{{permissionsLabel}}</span>
                        <div class="perm-scroller">

                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>`;
            this.modalItem = `<div class="perm-item"><span class="perm-name"></span></div>`;
            this.modalButton = `<div class="role-item"><span class="role-name"></span></div>`;
            this.modalButtonUser = `<div class="role-item"><div class="wrapper_de5239 xsmall_d82b57"><div class="image__25781 xsmall_d82b57 perm-user-avatar" style="background-image: url('\\{{avatarUrl}}');"></div></div><span class="role-name marginLeft8_ff311d"></span></div>`;
            this.permAllowedIcon = `<svg height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`;
            this.permDeniedIcon = `<svg height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8 0-1.85.63-3.55 1.69-4.9L16.9 18.31C15.55 19.37 13.85 20 12 20zm6.31-3.1L7.1 5.69C8.45 4.63 10.15 4 12 4c4.42 0 8 3.58 8 8 0 1.85-.63 3.55-1.69 4.9z"/></svg>`;

            this.cancelUserPopout = () => {};
            this.contextMenuPatches = [];
        }

        onStart() {
            DOM.addStyle(this.name, this.css);

            this.sectionHTML = Utilities.formatString(this.sectionHTML, DiscordClasses.UserPopout);
            this.sectionHTML = Utilities.formatString(this.sectionHTML, RoleClasses);
            this.sectionHTML = Utilities.formatString(this.sectionHTML, UserPopoutClasses);
            this.itemHTML = Utilities.formatString(this.itemHTML, RoleClasses);
            this.modalHTML = Utilities.formatString(this.modalHTML, DiscordClasses.Backdrop);
            this.modalHTML = Utilities.formatString(this.modalHTML, {root: ModalClasses.root, small: ModalClasses.small});

            this.promises = {state: {cancelled: false}, cancel() {this.state.cancelled = true;}};
            if (this.settings.popouts) this.bindPopouts();
            if (this.settings.contextMenus) this.bindContextMenus();
            this.setDisplayMode(this.settings.displayMode);
        }

        onStop() {
            DOM.removeStyle(this.name);
            this.promises.cancel();
            this.unbindPopouts();
            this.unbindContextMenus();
        }

        setDisplayMode(mode) {
            if (mode === "cozy") DOM.addStyle(this.name + "-jumbo", this.jumbo);
            else DOM.removeStyle(this.name + "-jumbo");
        }

        patchPopouts(e) {
            const popoutMount = (props) => {
                const popout = document.querySelector(`[class*="userPopout_"], [class*="userPopoutOuter_"]`);
                if (!popout || popout.querySelector("#permissions-popout")) return;
                const user = MemberStore.getMember(props.displayProfile.guildId, props.user.id);
                const guild = GuildStore.getGuild(props.displayProfile.guildId);
                const name = MemberStore.getNick(props.displayProfile.guildId, props.user.id) ?? props.user.username;
                if (!user || !guild || !name) return;

                const userRoles = user.roles.slice(0);
                userRoles.push(guild.id);
                userRoles.reverse();
                let perms = 0n;
                const permBlock = DOMTools.createElement(Utilities.formatString(this.sectionHTML, {sectionTitle: this.strings.popoutLabel}));
                const memberPerms = permBlock.querySelector(".member-perms");
                const strings = Strings;

                const referenceRoles = getRoles(guild);
                for (let r = 0; r < userRoles.length; r++) {
                    const role = userRoles[r];
                    if (!referenceRoles[role]) continue;
                    perms = perms | referenceRoles[role].permissions;
                    for (const perm in DiscordPerms) {
                        const permName = strings[perm] || perm.split("_").map(n => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" ");
                        const hasPerm = (perms & DiscordPerms[perm]) == DiscordPerms[perm];
                        if (hasPerm && !memberPerms.querySelector(`[data-name="${permName}"]`)) {
                            const element = DOMTools.createElement(this.itemHTML);
                            // element.classList.add(RoleClasses.rolePill);
                            let roleColor = referenceRoles[role].colorString;
                            element.querySelector(".name").textContent = permName;
                            element.setAttribute("data-name", permName);
                            if (!roleColor) roleColor = "#B9BBBE";
                            element.querySelector(".perm-circle").style.backgroundColor = ColorConverter.rgbToAlpha(roleColor, 1);
                            // element.style.borderColor = ColorConverter.rgbToAlpha(roleColor, 0.6);
                            memberPerms.prepend(element);
                        }
                    }
                }

                permBlock.querySelector(".perm-details").addEventListener("click", () => {
                    this.showModal(this.createModalUser(name, user, guild));
                });
                let roleList = popout.querySelector(`[class*="section_"]`);
                roleList = roleList?.parentElement;
                roleList?.parentNode?.append(permBlock);

                const popoutInstance = ReactTools.getOwnerInstance(popout, {include: ["Popout"]});
                if (!popoutInstance || !popoutInstance.updateOffsets) return;
                popoutInstance.updateOffsets();
            };

            if (!e.addedNodes.length || !(e.addedNodes[0] instanceof Element)) return;
            const element = e.addedNodes[0];
            const popout = element.querySelector(`[class*="userPopout_"], [class*="userPopoutOuter_"]`) ?? element;
            if (!popout || !popout.matches(`[class*="userPopout_"], [class*="userPopoutOuter_"]`)) return;
            const props = Utilities.findInTree(ReactTools.getReactInstance(popout), m => m && m.user, {walkable: ["memoizedProps", "return"]});
            popoutMount(props);
        }

        bindPopouts() {
    this.observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            this.patchPopouts(mutation);
        }
    });
    const app = document.querySelector("#app-mount");
    if (app) this.observer.observe(app, { childList: true, subtree: true });
}

unbindPopouts() {
    if (this.observer) {
        this.observer.disconnect();
        this.observer = null;
    }
}

async bindContextMenus() {
    try {
        this.contextMenuPatches = [];
        await Promise.all([
            this.patchChannelContextMenu(),
            this.patchGuildContextMenu(),
            this.patchUserContextMenu()
        ]);
    } catch (e) {
        console.error("PermissionsViewer: Error binding context menus", e);
    }
}

patchGuildContextMenu() {
    this.contextMenuPatches.push(ContextMenu.patch("guild-context", (retVal, props) => {
        try {
            if (!props?.guild) return retVal;

            const newItem = ContextMenu.buildItem({
                label: this.strings.contextMenuLabel,
                action: () => {
                    this.showModal(this.createModalGuild(props.guild.name, props.guild));
                }
            });

            if (Array.isArray(retVal?.props?.children)) {
                const insertIndex = retVal.props.children.findIndex(item => 
                    item?.props?.id === "devmode-copy-id" || 
                    item?.props?.children === "Copy ID"
                ) + 1;
                retVal.props.children.splice(insertIndex >= 0 ? insertIndex : 1, 0, newItem);
            }
        } catch (e) {
            console.error("PermissionsViewer: Error patching guild context menu", e);
        }
        return retVal;
    }));
}

patchChannelContextMenu() {
    this.contextMenuPatches.push(ContextMenu.patch("channel-context", (retVal, props) => {
        try {
            const newItem = ContextMenu.buildItem({
                label: this.strings.contextMenuLabel,
                action: () => {
                    if (!Object.keys(props.channel.permissionOverwrites).length) {
                        return Toasts.info(`#${props.channel.name} has no permission overrides`);
                    }
                    this.showModal(this.createModalChannel(props.channel.name, props.channel, props.guild));
                }
            });

            if (Array.isArray(retVal?.props?.children)) {
                const insertIndex = retVal.props.children.findIndex(item => 
                    item?.props?.id === "devmode-copy-id" || 
                    item?.props?.children === "Copy ID"
                ) + 1;
                retVal.props.children.splice(insertIndex >= 0 ? insertIndex : 1, 0, newItem);
            }
        } catch (e) {
            console.error("PermissionsViewer: Error patching channel context menu", e);
        }
        return retVal;
    }));
}

patchUserContextMenu() {
    this.contextMenuPatches.push(ContextMenu.patch("user-context", (retVal, props) => {
        try {
            console.log("User context menu patch triggered", {props, retVal});

            let guildId = props.guildId;
            if (!guildId) {
                const match = window.location.href.match(/channels\/(\d+)/);
                guildId = match?.[1];
            }

            console.log("Found guildId:", guildId);

            if (!guildId) {
                console.log("No guildId found, returning");
                return retVal;
            }

            // Try multiple methods to get guild data
            let guild = null;
            try {
                // Method 1: Direct GuildStore
                guild = GuildStore.getGuild(guildId);

                // Method 2: Try through DiscordModules
                if (!guild) {
                    const GuildStore2 = WebpackModules.getByProps('getGuild');
                    guild = GuildStore2?.getGuild(guildId);
                }

                // Method 3: Try through BdApi
                if (!guild) {
                    guild = BdApi.Webpack.getStore("GuildStore").getGuild(guildId);
                }

                console.log("Found guild through alternative methods:", guild);
            } catch (err) {
                console.error("Error getting guild:", err);
            }

            if (!guild) {
                console.log("No guild found after all attempts, returning");
                return retVal;
            }

            const newItem = ContextMenu.buildItem({
                label: this.strings.contextMenuLabel,
                action: () => {
                    const user = MemberStore.getMember(guildId, props.user.id);
                    if (!user) return;
                    const name = user.nick || props.user.username;
                    this.showModal(this.createModalUser(name, user, guild));
                }
            });

            console.log("Created new menu item:", newItem);

            if (Array.isArray(retVal?.props?.children)) {
                let targetArray = retVal.props.children;
                console.log("Initial target array:", targetArray);

                // If the first item is a group/section, try to target its children
                if (targetArray[0]?.props?.children) {
                    console.log("Found menu group, targeting its children");
                    targetArray = targetArray[0].props.children;
                }

                // Try to insert after roles section if it exists
                let rolesIndex = targetArray.findIndex(item => 
                    item?.props?.id === "roles" || 
                    item?.type === "separator"
                );

                console.log("Found roles index:", rolesIndex);

                if (rolesIndex !== -1) {
                    console.log("Inserting after roles");
                    targetArray.splice(rolesIndex + 1, 0, newItem);
                } else {
                    console.log("Inserting at beginning");
                    targetArray.splice(1, 0, newItem);
                }
            }

            return retVal;
        } catch (e) {
            console.error("PermissionsViewer: Error patching user context menu", e);
            return retVal;
        }
    }));
}

        showModal(modal) {
            const popout = document.querySelector(`[class*="userPopoutOuter-"]`);
            if (popout) popout.style.display = "none";
            const app = document.querySelector(".app-19_DXt");
            if (app) app.append(modal);
            else document.querySelector("#app-mount").append(modal);

            const closeModal = (event) => {
                if (!event.key === "Escape") return;
                modal.classList.add("closing");
                setTimeout(() => {modal.remove();}, 300);
            };
            document.addEventListener("keydown", closeModal, true);
            DOMTools.onRemoved(modal, () => document.removeEventListener("keydown", closeModal, true));
        }

        createModalChannel(name, channel, guild) {
            return this.createModal(`#${name}`, channel.permissionOverwrites, getRoles(guild), true);
        }

        createModalUser(name, user, guild) {
            const guildRoles = Object.assign({}, getRoles(guild));
            const userRoles = user.roles.slice(0).filter(r => typeof(guildRoles[r]) !== "undefined");

            userRoles.push(guild.id);
            userRoles.sort((a, b) => {return guildRoles[b].position - guildRoles[a].position;});

            if (user.userId == guild.ownerId) {
                const ALL_PERMISSIONS = Object.values(DiscordModules.DiscordPermissions).reduce((all, p) => all | p);
                userRoles.push(user.userId);
                guildRoles[user.userId] = {name: this.strings.modal.owner, permissions: ALL_PERMISSIONS};
            }
            return this.createModal(name, userRoles, guildRoles);
        }

        createModalGuild(name, guild) {
            return this.createModal(name, getRoles(guild));
        }

        createModal(title, displayRoles, referenceRoles, isOverride = false) {
            if (!referenceRoles) referenceRoles = displayRoles;
            const modal = DOMTools.createElement(Utilities.formatString(Utilities.formatString(this.modalHTML, this.strings.modal), {name: Utils.escapeHTML(title)}));
            const closeModal = () => {
                modal.classList.add("closing");
                setTimeout(() => {modal.remove();}, 300);
            };
            modal.querySelector(".callout-backdrop").addEventListener("click", closeModal);

            const strings = Strings || {};
            for (const r in displayRoles) {
                const role = Array.isArray(displayRoles) ? displayRoles[r] : r;
                const user = UserStore.getUser(role) || {getAvatarURL: () => AvatarDefaults.DEFAULT_AVATARS[Math.floor(Math.random() * AvatarDefaults.DEFAULT_AVATARS.length)], username: role};
                const member = MemberStore.getMember(SelectedGuildStore.getGuildId(), role) || {colorString: ""};
                const item = DOMTools.createElement(!isOverride || displayRoles[role].type == 0 ? this.modalButton : Utilities.formatString(this.modalButtonUser, {avatarUrl: user.getAvatarURL(null, 16, true)})); // getAvatarURL(guildId, size, canAnimate);
                if (!isOverride || displayRoles[role].type == 0) item.style.color = referenceRoles[role].colorString;
                else item.style.color = member.colorString;
                if (isOverride) item.querySelector(".role-name").innerHTML = Utils.escapeHTML(displayRoles[role].type == 0 ? referenceRoles[role].name : user.username);
                else item.querySelector(".role-name").innerHTML = Utils.escapeHTML(referenceRoles[role].name);
                modal.querySelector(".role-scroller").append(item);
                item.addEventListener("click", () => {
                    modal.querySelectorAll(".role-item.selected").forEach(e => e.classList.remove("selected"));
                    item.classList.add("selected");
                    const allowed = isOverride ? displayRoles[role].allow : referenceRoles[role].permissions;
                    const denied = isOverride ? displayRoles[role].deny : null;

                    const permList = modal.querySelector(".perm-scroller");
                    permList.innerHTML = "";
                    for (const perm in DiscordPerms) {
                        const element = DOMTools.createElement(this.modalItem);
                        const permAllowed = (allowed & DiscordPerms[perm]) == DiscordPerms[perm];
                        const permDenied = isOverride ? (denied & DiscordPerms[perm]) == DiscordPerms[perm] : !permAllowed;
                        if (!permAllowed && !permDenied) continue;
                        if (permAllowed) {
                            element.classList.add("allowed");
                            element.prepend(DOMTools.createElement(this.permAllowedIcon));
                        }
                        if (permDenied) {
                            element.classList.add("denied");
                            element.prepend(DOMTools.createElement(this.permDeniedIcon));
                        }
                        element.querySelector(".perm-name").textContent = strings[perm] || perm.split("_").map(n => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" ");
                        permList.append(element);
                    }
                });
                item.addEventListener("contextmenu", (e) => {
                    ContextMenu.open(e, ContextMenu.buildMenu([
                        {label: Strings.COPY_ID ?? "Copy Id", action: () => {DiscordModules.ElectronModule.copy(role);}}
                    ]));
                });
            }

            modal.querySelector(".role-item").click();

            return modal;
        }

        getSettingsPanel() {
            const panel = this.buildSettingsPanel();
            panel.addListener((id, checked) => {
                if (id == "popouts") {
                    if (checked) this.bindPopouts();
                    else this.unbindPopouts();
                }
                if (id == "contextMenus") {
                    if (checked) this.bindContextMenus();
                    this.unbindContextMenus();
                }
                if (id == "displayMode") this.setDisplayMode(checked);
            });
            return panel.getElement();
        }

    };
};
     return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
/*@end@*/
DaddyBoard commented 3 days ago

https://github.com/zerebos/BetterDiscordAddons/pull/792 I have updated my previously-working PR, you just need to change 1 line to fix the issue, do not use the one above as its changed a range of unneccesary lines (AI hallucination)

XeFunky commented 2 days ago

The code above allows you to enable PermissionsViewer plugin but it can't be disabled. "PermissionsViewer could not be stopped." shows instead of "PermissionsViewer has been disabled." Also I get 6 Permissions is that normal?

Screenshot 2024-11-12 154153

Screenshot 2024-11-12 154120

Pharaoh2k commented 2 days ago

@XeFunky I will look into this. Meanwhile I suggest you use the version with @DaddyBoard PRs: https://github.com/zerebos/BetterDiscordAddons/pull/792

It is much closer to the original code and should work fine.

Pharaoh2k commented 2 days ago

For ease, just copy and paste the following. It's the original code with @DaddyBoard fixes. Works fine for me:

/**
 * @name PermissionsViewer
 * @description Allows you to view a user's permissions. Thanks to Noodlebox for the idea!
 * @version 0.2.12
 * @author Zerebos
 * @authorId 249746236008169473
 * @website https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer
 * @source https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js
 */
/*@cc_on
@if (@_jscript)

    // Offer to self-install for clueless users that try to run this directly.
    var shell = WScript.CreateObject("WScript.Shell");
    var fs = new ActiveXObject("Scripting.FileSystemObject");
    var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins");
    var pathSelf = WScript.ScriptFullName;
    // Put the user at ease by addressing them in the first person
    shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30);
    if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
        shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
    } else if (!fs.FolderExists(pathPlugins)) {
        shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
    } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
        fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
        // Show the user where to put plugins in the future
        shell.Exec("explorer " + pathPlugins);
        shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
    }
    WScript.Quit();

@else@*/
const config = {
    info: {
        name: "PermissionsViewer",
        authors: [
            {
                name: "Zerebos",
                discord_id: "249746236008169473",
                github_username: "zerebos",
                twitter_username: "IAmZerebos"
            }
        ],
        version: "0.2.12",
        description: "Allows you to view a user's permissions. Thanks to Noodlebox for the idea!",
        github: "https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer",
        github_raw: "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js"
    },
    changelog: [
        {
            title: "Fixes",
            type: "fixed",
            items: [
                "Quick fix for displaying permissions in user popouts!"
            ]
        }
    ],
    defaultConfig: [
        {
            type: "switch",
            id: "contextMenus",
            name: "Context Menus",
            value: true
        },
        {
            type: "switch",
            id: "popouts",
            name: "Popouts",
            value: true
        },
        {
            type: "radio",
            id: "displayMode",
            name: "Modal Display Mode",
            value: "compact",
            options: [
                {
                    name: "Cozy",
                    value: "cozy"
                },
                {
                    name: "Compact",
                    value: "compact"
                }
            ]
        }
    ],
    strings: {
        es: {
            contextMenuLabel: "Permisos",
            popoutLabel: "Permisos",
            modal: {
                header: "Permisos de {{name}}",
                rolesLabel: "Roles",
                permissionsLabel: "Permisos",
                owner: "@propietario"
            },
            settings: {
                popouts: {
                    name: "Mostrar en Popouts",
                    note: "Mostrar los permisos de usuario en popouts como los roles."
                },
                contextMenus: {
                    name: "Botón de menú contextual",
                    note: "Añadir un botón para ver permisos en los menús contextuales."
                }
            }
        },
        pt: {
            contextMenuLabel: "Permissões",
            popoutLabel: "Permissões",
            modal: {
                header: "Permissões de {{name}}",
                rolesLabel: "Cargos",
                permissionsLabel: "Permissões",
                owner: "@dono"
            },
            settings: {
                popouts: {
                    name: "Mostrar em Popouts",
                    note: "Mostrar as permissões em popouts como os cargos."
                },
                contextMenus: {
                    name: "Botão do menu de contexto",
                    note: "Adicionar um botão parar ver permissões ao menu de contexto."
                }
            }
        },
        de: {
            contextMenuLabel: "Berechtigungen",
            popoutLabel: "Berechtigungen",
            modal: {
                header: "{{name}}s Berechtigungen",
                rolesLabel: "Rollen",
                permissionsLabel: "Berechtigungen",
                owner: "@eigentümer"
            },
            settings: {
                popouts: {
                    name: "In Popouts anzeigen",
                    note: "Zeigt die Gesamtberechtigungen eines Benutzers in seinem Popup ähnlich den Rollen an."
                },
                contextMenus: {
                    name: "Kontextmenü-Schaltfläche",
                    note: "Fügt eine Schaltfläche hinzu, um die Berechtigungen mithilfe von Kontextmenüs anzuzeigen."
                }
            }
        },
        en: {
            contextMenuLabel: "Permissions",
            popoutLabel: "Permissions",
            modal: {
                header: "{{name}}'s Permissions",
                rolesLabel: "Roles",
                permissionsLabel: "Permissions",
                owner: "@owner"
            },
            settings: {
                popouts: {
                    name: "Show In Popouts",
                    note: "Shows a user's total permissions in their popout similar to roles."
                },
                contextMenus: {
                    name: "Context Menu Button",
                    note: "Adds a button to view the permissions modal to select context menus."
                },
                displayMode: {
                    name: "Modal Display Mode"
                }
            }
        },
        ru: {
            contextMenuLabel: "Полномочия",
            popoutLabel: "Полномочия",
            modal: {
                header: "Полномочия {{name}}",
                rolesLabel: "Роли",
                permissionsLabel: "Полномочия",
                owner: "Владелец"
            },
            settings: {
                popouts: {
                    name: "Показать во всплывающих окнах",
                    note: "Отображает полномочия пользователя в их всплывающем окне, аналогичном ролям."
                },
                contextMenus: {
                    name: "Кнопка контекстного меню",
                    note: "Добавить кнопку для отображения полномочий с помощью контекстных меню."
                }
            }
        }
    },
    main: "index.js"
};
class Dummy {
    constructor() {this._config = config;}
    start() {}
    stop() {}
}

if (!global.ZeresPluginLibrary) {
    BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.name ?? config.info.name} is missing. Please click Download Now to install it.`, {
        confirmText: "Download Now",
        cancelText: "Cancel",
        onConfirm: () => {
            require("request").get("https://betterdiscord.app/gh-redirect?id=9", async (err, resp, body) => {
                if (err) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9");
                if (resp.statusCode === 302) {
                    require("request").get(resp.headers.location, async (error, response, content) => {
                        if (error) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9");
                        await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), content, r));
                    });
                }
                else {
                    await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
                }
            });
        }
    });
}

module.exports = !global.ZeresPluginLibrary ? Dummy : (([Plugin, Api]) => {
     const plugin = (Plugin, Api) => {
    const {ContextMenu, DOM, Utils} = window.BdApi;
    const {DiscordModules, WebpackModules, Toasts, DiscordClasses, Utilities, DOMTools, ColorConverter, ReactTools} = Api;

    const { Webpack } = BdApi;
    const GuildStore = Webpack.getStore("GuildStore");
    const SelectedGuildStore = DiscordModules.SelectedGuildStore;
    const MemberStore = DiscordModules.GuildMemberStore;
    const UserStore = DiscordModules.UserStore;
    const DiscordPerms = Object.assign({}, DiscordModules.DiscordPermissions);
    const AvatarDefaults = WebpackModules.getByProps("DEFAULT_AVATARS");
    const ModalClasses = WebpackModules.getByProps("root", "header", "small");
    const Strings = WebpackModules.getByProps("COPY_ID");
    const UserPopoutClasses = Object.assign({section: "section_ba4d80", heading: "heading_ba4d80", root: "root_c83b44"}, WebpackModules.getByProps("userPopoutOuter"), WebpackModules.getByProps("defaultColor", "eyebrow"), DiscordClasses.PopoutRoles, WebpackModules.getByProps("root", "expandButton"), WebpackModules.getModule(m => m?.heading && m?.section && Object.keys(m)?.length === 2));
    const RoleClasses = Object.assign({}, DiscordClasses.PopoutRoles, WebpackModules.getByProps("defaultColor", "eyebrow"), WebpackModules.getByProps("role", "roleName", "roleCircle"));

    const getRoles = (guild) => guild?.roles ?? GuildStore.getRoles(guild?.id);

    if (DiscordPerms.STREAM) {
        DiscordPerms.VIDEO = DiscordPerms.STREAM;
        delete DiscordPerms.STREAM;
    }
    if (DiscordPerms.MANAGE_GUILD) {
        DiscordPerms.MANAGE_SERVER = DiscordPerms.MANAGE_GUILD;
        delete DiscordPerms.MANAGE_GUILD;
    }

    return class PermissionsViewer extends Plugin {
        constructor() {
            super();
            this.css = `.perm-user-avatar {
    border-radius: 50%;
    width: 16px;
    height: 16px;
    margin-right: 3px;
}

.member-perms-header {
    color: var(--header-secondary);
    display: flex;
    justify-content: space-between;
}

.member-perms {
    display: flex;
    flex-wrap: wrap;
    margin-top: 2px;
    max-height: 160px;
    overflow-y: auto;
    overflow-x: hidden;
}

.member-perms .member-perm .perm-circle {
    border-radius: 50%;
    height: 12px;
    margin-right: 4px;
    width: 12px;
}

.member-perms .member-perm .name {
    margin-right: 4px;
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.perm-details-button {
    cursor: pointer;
    height: 12px;
}

.perm-details {
    display: flex;
    justify-content: flex-end;
}

.member-perm-details {
    cursor: pointer;
}

.member-perm-details-button {
    fill: #72767d;
    height: 10px;
}

/* Modal */

@keyframes permissions-backdrop {
    to { opacity: 0.85; }
}

@keyframes permissions-modal-wrapper {
    to { transform: scale(1); opacity: 1; }
}

@keyframes permissions-backdrop-closing {
    to { opacity: 0; }
}

@keyframes permissions-modal-wrapper-closing {
    to { transform: scale(0.7); opacity: 0; }
}

#permissions-modal-wrapper {
    z-index: 100;
}

#permissions-modal-wrapper .callout-backdrop {
    animation: permissions-backdrop 250ms ease;
    animation-fill-mode: forwards;
    opacity: 0;
    background-color: rgb(0, 0, 0);
    transform: translateZ(0px);
}

#permissions-modal-wrapper.closing .callout-backdrop {
    animation: permissions-backdrop-closing 200ms linear;
    animation-fill-mode: forwards;
    animation-delay: 50ms;
    opacity: 0.85;
}

#permissions-modal-wrapper.closing .modal-wrapper {
    animation: permissions-modal-wrapper-closing 250ms cubic-bezier(0.19, 1, 0.22, 1);
    animation-fill-mode: forwards;
    opacity: 1;
    transform: scale(1);
}

#permissions-modal-wrapper .modal-wrapper {
    animation: permissions-modal-wrapper 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
    animation-fill-mode: forwards;
    transform: scale(0.7);
    transform-origin: 50% 50%;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    contain: content;
    justify-content: center;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    opacity: 0;
    pointer-events: none;
    position: absolute;
    user-select: none;
    z-index: 1000;
}

#permissions-modal-wrapper .modal-body {
    background-color: #36393f;
    height: 440px;
    width: auto;
    /*box-shadow: 0 0 0 1px rgba(32,34,37,.6), 0 2px 10px 0 rgba(0,0,0,.2);*/
    flex-direction: row;
    overflow: hidden;
    display: flex;
    flex: 1;
    contain: layout;
    position: relative;
}

#permissions-modal-wrapper #permissions-modal {
    contain: layout;
    flex-direction: column;
    pointer-events: auto;
    border: 1px solid rgba(28,36,43,.6);
    border-radius: 5px;
    box-shadow: 0 2px 10px 0 rgba(0,0,0,.2);
    overflow: hidden;
}

#permissions-modal-wrapper .header {
    background-color: #35393e;
    box-shadow: 0 2px 3px 0 rgba(0,0,0,.2);
    padding: 12px 20px;
    z-index: 1;
    color: #fff;
    font-size: 16px;
    font-weight: 700;
    line-height: 19px;
}

.role-side, .perm-side {
    flex-direction: column;
    padding-left: 6px;
}

.role-scroller, .perm-scroller {
    contain: layout;
    flex: 1;
    min-height: 1px;
    overflow-y: scroll;
}

#permissions-modal-wrapper .scroller-title {
    color: #fff;
    padding: 8px 0 4px 4px;
    margin-right: 8px;
    border-bottom: 1px solid rgba(0,0,0,0.3);
    display: none;
}

#permissions-modal-wrapper .role-side {
    width: auto;
    min-width: 150px;
    background: #2f3136;
    flex: 0 0 auto;
    overflow: hidden;
    display: flex;
    min-height: 1px;
    position: relative;
}

#permissions-modal-wrapper .role-scroller {
    contain: layout;
    flex: 1;
    min-height: 1px;
    overflow-y: scroll;
    padding-top: 8px;
}

#permissions-modal-wrapper .role-item {
    display: flex;
    border-radius: 2px;
    padding: 6px;
    margin-bottom: 5px;
    cursor: pointer;
    color: #dcddde;
}

#permissions-modal-wrapper .role-item:hover {
    background-color: rgba(0,0,0,0.1);
}

#permissions-modal-wrapper .role-item.selected {
    background-color: rgba(0,0,0,0.2);
}

#permissions-modal-wrapper .perm-side {
    width: 273px;
    background-color: #36393f;
    flex: 0 0 auto;
    display: flex;
    min-height: 1px;
    position: relative;
    padding-left: 10px;
}

#permissions-modal-wrapper .perm-item {
    box-shadow: inset 0 -1px 0 rgba(79,84,92,.3);
    box-sizing: border-box;
    height: 44px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    display: flex;
}

#permissions-modal-wrapper .perm-item.allowed svg {
    fill: #43B581;
}

#permissions-modal-wrapper .perm-item.denied svg {
    fill: #F04747;
}

#permissions-modal-wrapper .perm-name {
    display: inline;
    flex: 1;
    font-size: 16px;
    font-weight: 400;
    overflow: hidden;
    text-overflow: ellipsis;
    user-select: text;
    color: #dcddde;
    margin-left: 10px;
}

.member-perms::-webkit-scrollbar-thumb, .member-perms::-webkit-scrollbar-track,
#permissions-modal-wrapper *::-webkit-scrollbar-thumb, #permissions-modal-wrapper *::-webkit-scrollbar-track {
    background-clip: padding-box;
    border-radius: 7.5px;
    border-style: solid;
    border-width: 3px;
    visibility: hidden;
}

.member-perms:hover::-webkit-scrollbar-thumb, .member-perms:hover::-webkit-scrollbar-track,
#permissions-modal-wrapper *:hover::-webkit-scrollbar-thumb, #permissions-modal-wrapper *:hover::-webkit-scrollbar-track {
    visibility: visible;
}

.member-perms::-webkit-scrollbar-track,
#permissions-modal-wrapper *::-webkit-scrollbar-track {
    border-width: initial;
    background-color: transparent;
    border: 2px solid transparent;
}

.member-perms::-webkit-scrollbar-thumb,
#permissions-modal-wrapper *::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    border-radius: 4px;
    cursor: move;
    background-color: rgba(32,34,37,.6);
}

.member-perms::-webkit-scrollbar,
#permissions-modal-wrapper *::-webkit-scrollbar {
    height: 8px;
    width: 8px;
}

.theme-light #permissions-modal-wrapper #permissions-modal {
    background: #fff;
}

.theme-light #permissions-modal-wrapper .modal-body {
    background: transparent;
}

.theme-light #permissions-modal-wrapper .header {
    background: transparent;
    color: #000;
}

.theme-light #permissions-modal-wrapper .role-side {
    background: rgba(0,0,0,.2);
}

.theme-light #permissions-modal-wrapper .perm-side {
    background: rgba(0,0,0,.1);
}

.theme-light #permissions-modal-wrapper .role-item,
.theme-light #permissions-modal-wrapper .perm-name {
    color: #000;
}

#permissions-modal-wrapper #permissions-modal {
    width: auto;
}`;
            this.jumbo = `#permissions-modal-wrapper #permissions-modal {
    height: 840px;
}

#permissions-modal-wrapper #permissions-modal .perm-side {
    width: 500px;
}

#permissions-modal .perm-scroller {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
}

#permissions-modal .perm-item {
    width: 50%;
}`;
            this.sectionHTML = `<div class="{{section}}" id="permissions-popout">
    <h1 class="member-perms-header {{text-xs/semibold}} {{defaultColor_e9e35f}} {{heading}}" data-text-variant="text-xs/semibold" style="color: var(--header-secondary);">
        <div class="member-perms-title">{{sectionTitle}}</div>
        <span class="perm-details">
            <svg name="Details" viewBox="0 0 24 24" class="perm-details-button" fill="currentColor">
                <path d="M0 0h24v24H0z" fill="none"/>
                <path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
            </svg>
        </span>
    </h1>
    <div class="member-perms {{root}}"></div>
</div>`;
            this.itemHTML = `<div class="member-perm {{role}}">
    <div class="perm-circle {{roleCircle}}"></div>
    <div class="name {{roleName}} {{defaultColor}}"></div>
</div>`;
            this.modalHTML = `<div id="permissions-modal-wrapper">
        <div class="callout-backdrop {{backdrop}}"></div>
        <div class="modal-wrapper">
            <div id="permissions-modal" class="{{root}} {{small}}">
                <div class="header"><div class="title">{{header}}</div></div>
                <div class="modal-body">
                    <div class="role-side">
                        <span class="scroller-title role-list-title">{{rolesLabel}}</span>
                        <div class="role-scroller">

                        </div>
                    </div>
                    <div class="perm-side">
                        <span class="scroller-title perm-list-title">{{permissionsLabel}}</span>
                        <div class="perm-scroller">

                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>`;
            this.modalItem = `<div class="perm-item"><span class="perm-name"></span></div>`;
            this.modalButton = `<div class="role-item"><span class="role-name"></span></div>`;
            this.modalButtonUser = `<div class="role-item"><div class="wrapper_de5239 xsmall_d82b57"><div class="image__25781 xsmall_d82b57 perm-user-avatar" style="background-image: url('\\{{avatarUrl}}');"></div></div><span class="role-name marginLeft8_ff311d"></span></div>`;
            this.permAllowedIcon = `<svg height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`;
            this.permDeniedIcon = `<svg height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8 0-1.85.63-3.55 1.69-4.9L16.9 18.31C15.55 19.37 13.85 20 12 20zm6.31-3.1L7.1 5.69C8.45 4.63 10.15 4 12 4c4.42 0 8 3.58 8 8 0 1.85-.63 3.55-1.69 4.9z"/></svg>`;

            this.cancelUserPopout = () => {};
            this.contextMenuPatches = [];
        }

        onStart() {
            DOM.addStyle(this.name, this.css);

            this.sectionHTML = Utilities.formatString(this.sectionHTML, DiscordClasses.UserPopout);
            this.sectionHTML = Utilities.formatString(this.sectionHTML, RoleClasses);
            this.sectionHTML = Utilities.formatString(this.sectionHTML, UserPopoutClasses);
            this.itemHTML = Utilities.formatString(this.itemHTML, RoleClasses);
            this.modalHTML = Utilities.formatString(this.modalHTML, DiscordClasses.Backdrop);
            this.modalHTML = Utilities.formatString(this.modalHTML, {root: ModalClasses.root, small: ModalClasses.small});

            this.promises = {state: {cancelled: false}, cancel() {this.state.cancelled = true;}};
            if (this.settings.popouts) this.bindPopouts();
            if (this.settings.contextMenus) this.bindContextMenus();
            this.setDisplayMode(this.settings.displayMode);
        }

        onStop() {
            DOM.removeStyle(this.name);
            this.promises.cancel();
            this.unbindPopouts();
            this.unbindContextMenus();
        }

        setDisplayMode(mode) {
            if (mode === "cozy") DOM.addStyle(this.name + "-jumbo", this.jumbo);
            else DOM.removeStyle(this.name + "-jumbo");
        }

        patchPopouts(e) {
            const popoutMount = (props) => {
                const popout = document.querySelector(`[class*="userPopout_"], [class*="userPopoutOuter_"]`);
                if (!popout || popout.querySelector("#permissions-popout")) return;
                const user = MemberStore.getMember(props.displayProfile.guildId, props.user.id);
                const guild = GuildStore.getGuild(props.displayProfile.guildId);
                const name = MemberStore.getNick(props.displayProfile.guildId, props.user.id) ?? props.user.username;
                if (!user || !guild || !name) return;

                const userRoles = user.roles.slice(0);
                userRoles.push(guild.id);
                userRoles.reverse();
                let perms = 0n;
                const permBlock = DOMTools.createElement(Utilities.formatString(this.sectionHTML, {sectionTitle: this.strings.popoutLabel}));
                const memberPerms = permBlock.querySelector(".member-perms");
                const strings = Strings;

                const referenceRoles = getRoles(guild);
                for (let r = 0; r < userRoles.length; r++) {
                    const role = userRoles[r];
                    if (!referenceRoles[role]) continue;
                    perms = perms | referenceRoles[role].permissions;
                    for (const perm in DiscordPerms) {
                        const permName = strings[perm] || perm.split("_").map(n => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" ");
                        const hasPerm = (perms & DiscordPerms[perm]) == DiscordPerms[perm];
                        if (hasPerm && !memberPerms.querySelector(`[data-name="${permName}"]`)) {
                            const element = DOMTools.createElement(this.itemHTML);
                            // element.classList.add(RoleClasses.rolePill);
                            let roleColor = referenceRoles[role].colorString;
                            element.querySelector(".name").textContent = permName;
                            element.setAttribute("data-name", permName);
                            if (!roleColor) roleColor = "#B9BBBE";
                            element.querySelector(".perm-circle").style.backgroundColor = ColorConverter.rgbToAlpha(roleColor, 1);
                            // element.style.borderColor = ColorConverter.rgbToAlpha(roleColor, 0.6);
                            memberPerms.prepend(element);
                        }
                    }
                }

                permBlock.querySelector(".perm-details").addEventListener("click", () => {
                    this.showModal(this.createModalUser(name, user, guild));
                });
                let roleList = popout.querySelector(`[class*="section_"]`);
                roleList = roleList?.parentElement;
                roleList?.parentNode?.append(permBlock);

                const popoutInstance = ReactTools.getOwnerInstance(popout, {include: ["Popout"]});
                if (!popoutInstance || !popoutInstance.updateOffsets) return;
                popoutInstance.updateOffsets();
            };

            if (!e.addedNodes.length || !(e.addedNodes[0] instanceof Element)) return;
            const element = e.addedNodes[0];
            const popout = element.querySelector(`[class*="userPopout_"], [class*="userPopoutOuter_"]`) ?? element;
            if (!popout || !popout.matches(`[class*="userPopout_"], [class*="userPopoutOuter_"]`)) return;
            const props = Utilities.findInTree(ReactTools.getReactInstance(popout), m => m && m.user, {walkable: ["memoizedProps", "return"]});
            popoutMount(props);
        }

        bindPopouts() {
            this.observer = this.patchPopouts.bind(this);
        }

        unbindPopouts() {
            this.observer = undefined;
        }

        async bindContextMenus() {
            this.patchChannelContextMenu();
            this.patchGuildContextMenu();
            this.patchUserContextMenu();
        }

        unbindContextMenus() {
            for (const cancel of this.contextMenuPatches) cancel();
        }

        patchGuildContextMenu() {
            this.contextMenuPatches.push(ContextMenu.patch("guild-context", (retVal, props) => {
                if (!props?.guild) return retVal; // Ignore non-guild items
                const guild = GuildStore.getGuild(props.guild.id);
                if (!guild) return retVal;
                const newItem = ContextMenu.buildItem({
                    label: this.strings.contextMenuLabel,
                    action: () => {
                        this.showModal(this.createModalGuild(guild.name, guild));
                    }
                });
                retVal.props.children.splice(1, 0, newItem);
            }));
        }

        patchChannelContextMenu() {
            this.contextMenuPatches.push(ContextMenu.patch("channel-context", (retVal, props) => {
                const newItem = ContextMenu.buildItem({
                    label: this.strings.contextMenuLabel,
                    action: () => {
                        if (!Object.keys(props.channel.permissionOverwrites).length) return Toasts.info(`#${props.channel.name} has no permission overrides`);
                        this.showModal(this.createModalChannel(props.channel.name, props.channel, props.guild));
                    }
                });
                retVal.props.children.splice(1, 0, newItem);
            }));
        }

        patchUserContextMenu() {
            this.contextMenuPatches.push(ContextMenu.patch("user-context", (retVal, props) => {
                const guild = GuildStore.getGuild(props.guildId);
                if (!guild) return;

                const newItem = ContextMenu.buildItem({
                    label: this.strings.contextMenuLabel,
                    action: () => {
                        const user = MemberStore.getMember(props.guildId, props.user.id);
                        const name = user.nick ? user.nick : props.user.username;
                        this.showModal(this.createModalUser(name, user, guild));
                    }
                });
                retVal?.props?.children[0]?.props?.children.splice(2, 0, newItem);
            }));
        }

        showModal(modal) {
            const popout = document.querySelector(`[class*="userPopoutOuter-"]`);
            if (popout) popout.style.display = "none";
            const app = document.querySelector(".app-19_DXt");
            if (app) app.append(modal);
            else document.querySelector("#app-mount").append(modal);

            const closeModal = (event) => {
                if (!event.key === "Escape") return;
                modal.classList.add("closing");
                setTimeout(() => {modal.remove();}, 300);
            };
            document.addEventListener("keydown", closeModal, true);
            DOMTools.onRemoved(modal, () => document.removeEventListener("keydown", closeModal, true));
        }

        createModalChannel(name, channel, guild) {
            return this.createModal(`#${name}`, channel.permissionOverwrites, getRoles(guild), true);
        }

        createModalUser(name, user, guild) {
            const guildRoles = Object.assign({}, getRoles(guild));
            const userRoles = user.roles.slice(0).filter(r => typeof(guildRoles[r]) !== "undefined");

            userRoles.push(guild.id);
            userRoles.sort((a, b) => {return guildRoles[b].position - guildRoles[a].position;});

            if (user.userId == guild.ownerId) {
                const ALL_PERMISSIONS = Object.values(DiscordModules.DiscordPermissions).reduce((all, p) => all | p);
                userRoles.push(user.userId);
                guildRoles[user.userId] = {name: this.strings.modal.owner, permissions: ALL_PERMISSIONS};
            }
            return this.createModal(name, userRoles, guildRoles);
        }

        createModalGuild(name, guild) {
            return this.createModal(name, getRoles(guild));
        }

        createModal(title, displayRoles, referenceRoles, isOverride = false) {
            if (!referenceRoles) referenceRoles = displayRoles;
            const modal = DOMTools.createElement(Utilities.formatString(Utilities.formatString(this.modalHTML, this.strings.modal), {name: Utils.escapeHTML(title)}));
            const closeModal = () => {
                modal.classList.add("closing");
                setTimeout(() => {modal.remove();}, 300);
            };
            modal.querySelector(".callout-backdrop").addEventListener("click", closeModal);

            const strings = Strings || {};
            for (const r in displayRoles) {
                const role = Array.isArray(displayRoles) ? displayRoles[r] : r;
                const user = UserStore.getUser(role) || {getAvatarURL: () => AvatarDefaults.DEFAULT_AVATARS[Math.floor(Math.random() * AvatarDefaults.DEFAULT_AVATARS.length)], username: role};
                const member = MemberStore.getMember(SelectedGuildStore.getGuildId(), role) || {colorString: ""};
                const item = DOMTools.createElement(!isOverride || displayRoles[role].type == 0 ? this.modalButton : Utilities.formatString(this.modalButtonUser, {avatarUrl: user.getAvatarURL(null, 16, true)})); // getAvatarURL(guildId, size, canAnimate);
                if (!isOverride || displayRoles[role].type == 0) item.style.color = referenceRoles[role].colorString;
                else item.style.color = member.colorString;
                if (isOverride) item.querySelector(".role-name").innerHTML = Utils.escapeHTML(displayRoles[role].type == 0 ? referenceRoles[role].name : user.username);
                else item.querySelector(".role-name").innerHTML = Utils.escapeHTML(referenceRoles[role].name);
                modal.querySelector(".role-scroller").append(item);
                item.addEventListener("click", () => {
                    modal.querySelectorAll(".role-item.selected").forEach(e => e.classList.remove("selected"));
                    item.classList.add("selected");
                    const allowed = isOverride ? displayRoles[role].allow : referenceRoles[role].permissions;
                    const denied = isOverride ? displayRoles[role].deny : null;

                    const permList = modal.querySelector(".perm-scroller");
                    permList.innerHTML = "";
                    for (const perm in DiscordPerms) {
                        const element = DOMTools.createElement(this.modalItem);
                        const permAllowed = (allowed & DiscordPerms[perm]) == DiscordPerms[perm];
                        const permDenied = isOverride ? (denied & DiscordPerms[perm]) == DiscordPerms[perm] : !permAllowed;
                        if (!permAllowed && !permDenied) continue;
                        if (permAllowed) {
                            element.classList.add("allowed");
                            element.prepend(DOMTools.createElement(this.permAllowedIcon));
                        }
                        if (permDenied) {
                            element.classList.add("denied");
                            element.prepend(DOMTools.createElement(this.permDeniedIcon));
                        }
                        element.querySelector(".perm-name").textContent = strings[perm] || perm.split("_").map(n => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" ");
                        permList.append(element);
                    }
                });
                item.addEventListener("contextmenu", (e) => {
                    ContextMenu.open(e, ContextMenu.buildMenu([
                        {label: Strings.COPY_ID ?? "Copy Id", action: () => {DiscordModules.ElectronModule.copy(role);}}
                    ]));
                });
            }

            modal.querySelector(".role-item").click();

            return modal;
        }

        getSettingsPanel() {
            const panel = this.buildSettingsPanel();
            panel.addListener((id, checked) => {
                if (id == "popouts") {
                    if (checked) this.bindPopouts();
                    else this.unbindPopouts();
                }
                if (id == "contextMenus") {
                    if (checked) this.bindContextMenus();
                    this.unbindContextMenus();
                }
                if (id == "displayMode") this.setDisplayMode(checked);
            });
            return panel.getElement();
        }

    };
};
     return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
/*@end@*/
XeFunky commented 2 days ago

Thank you for supplying the code. I couldn't find it in the #792 link you sent. Works fine now

Pharaoh2k commented 18 hours ago

This is an alternative version that might work in case the current one I pasted above breaks again. Just putting it here:

/**
 * @name PermissionsViewer
 * @description Allows you to view a user's permissions. Thanks to Noodlebox for the idea!
 * @version 0.2.12
 * @author Zerebos
 * @authorId 249746236008169473
 * @website https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer
 * @source https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js
 */
/*@cc_on
@if (@_jscript)

    // Offer to self-install for clueless users that try to run this directly.
    var shell = WScript.CreateObject("WScript.Shell");
    var fs = new ActiveXObject("Scripting.FileSystemObject");
    var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins");
    var pathSelf = WScript.ScriptFullName;
    // Put the user at ease by addressing them in the first person
    shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30);
    if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
        shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
    } else if (!fs.FolderExists(pathPlugins)) {
        shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
    } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
        fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
        // Show the user where to put plugins in the future
        shell.Exec("explorer " + pathPlugins);
        shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
    }
    WScript.Quit();

@else@*/
const config = {
    info: {
        name: "PermissionsViewer",
        authors: [
            {
                name: "Zerebos",
                discord_id: "249746236008169473",
                github_username: "zerebos",
                twitter_username: "IAmZerebos"
            }
        ],
        version: "0.2.12",
        description: "Allows you to view a user's permissions. Thanks to Noodlebox for the idea!",
        github: "https://github.com/zerebos/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer",
        github_raw: "https://raw.githubusercontent.com/zerebos/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js"
    },
    changelog: [
        {
            title: "Fixes",
            type: "fixed",
            items: [
                "Quick fix for displaying permissions in user popouts!"
            ]
        }
    ],
    defaultConfig: [
        {
            type: "switch",
            id: "contextMenus",
            name: "Context Menus",
            value: true
        },
        {
            type: "switch",
            id: "popouts",
            name: "Popouts",
            value: true
        },
        {
            type: "radio",
            id: "displayMode",
            name: "Modal Display Mode",
            value: "compact",
            options: [
                {
                    name: "Cozy",
                    value: "cozy"
                },
                {
                    name: "Compact",
                    value: "compact"
                }
            ]
        }
    ],
    strings: {
        es: {
            contextMenuLabel: "Permisos",
            popoutLabel: "Permisos",
            modal: {
                header: "Permisos de {{name}}",
                rolesLabel: "Roles",
                permissionsLabel: "Permisos",
                owner: "@propietario"
            },
            settings: {
                popouts: {
                    name: "Mostrar en Popouts",
                    note: "Mostrar los permisos de usuario en popouts como los roles."
                },
                contextMenus: {
                    name: "Botón de menú contextual",
                    note: "Añadir un botón para ver permisos en los menús contextuales."
                }
            }
        },
        pt: {
            contextMenuLabel: "Permissões",
            popoutLabel: "Permissões",
            modal: {
                header: "Permissões de {{name}}",
                rolesLabel: "Cargos",
                permissionsLabel: "Permissões",
                owner: "@dono"
            },
            settings: {
                popouts: {
                    name: "Mostrar em Popouts",
                    note: "Mostrar as permissões em popouts como os cargos."
                },
                contextMenus: {
                    name: "Botão do menu de contexto",
                    note: "Adicionar um botão parar ver permissões ao menu de contexto."
                }
            }
        },
        de: {
            contextMenuLabel: "Berechtigungen",
            popoutLabel: "Berechtigungen",
            modal: {
                header: "{{name}}s Berechtigungen",
                rolesLabel: "Rollen",
                permissionsLabel: "Berechtigungen",
                owner: "@eigentümer"
            },
            settings: {
                popouts: {
                    name: "In Popouts anzeigen",
                    note: "Zeigt die Gesamtberechtigungen eines Benutzers in seinem Popup ähnlich den Rollen an."
                },
                contextMenus: {
                    name: "Kontextmenü-Schaltfläche",
                    note: "Fügt eine Schaltfläche hinzu, um die Berechtigungen mithilfe von Kontextmenüs anzuzeigen."
                }
            }
        },
        en: {
            contextMenuLabel: "Permissions",
            popoutLabel: "Permissions",
            modal: {
                header: "{{name}}'s Permissions",
                rolesLabel: "Roles",
                permissionsLabel: "Permissions",
                owner: "@owner"
            },
            settings: {
                popouts: {
                    name: "Show In Popouts",
                    note: "Shows a user's total permissions in their popout similar to roles."
                },
                contextMenus: {
                    name: "Context Menu Button",
                    note: "Adds a button to view the permissions modal to select context menus."
                },
                displayMode: {
                    name: "Modal Display Mode"
                }
            }
        },
        ru: {
            contextMenuLabel: "Полномочия",
            popoutLabel: "Полномочия",
            modal: {
                header: "Полномочия {{name}}",
                rolesLabel: "Роли",
                permissionsLabel: "Полномочия",
                owner: "Владелец"
            },
            settings: {
                popouts: {
                    name: "Показать во всплывающих окнах",
                    note: "Отображает полномочия пользователя в их всплывающем окне, аналогичном ролям."
                },
                contextMenus: {
                    name: "Кнопка контекстного меню",
                    note: "Добавить кнопку для отображения полномочий с помощью контекстных меню."
                }
            }
        }
    },
    main: "index.js"
};
class Dummy {
    constructor() {this._config = config;}
    start() {}
    stop() {}
}

if (!global.ZeresPluginLibrary) {
    BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.name ?? config.info.name} is missing. Please click Download Now to install it.`, {
        confirmText: "Download Now",
        cancelText: "Cancel",
        onConfirm: () => {
            require("request").get("https://betterdiscord.app/gh-redirect?id=9", async (err, resp, body) => {
                if (err) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9");
                if (resp.statusCode === 302) {
                    require("request").get(resp.headers.location, async (error, response, content) => {
                        if (error) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9");
                        await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), content, r));
                    });
                }
                else {
                    await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
                }
            });
        }
    });
}

module.exports = !global.ZeresPluginLibrary ? Dummy : (([Plugin, Api]) => {
     const plugin = (Plugin, Api) => {
    const {ContextMenu, DOM, Utils} = window.BdApi;
    const {DiscordModules, WebpackModules, Toasts, DiscordClasses, Utilities, DOMTools, ColorConverter, ReactTools} = Api;

    const GuildStore = DiscordModules.GuildStore;
    const SelectedGuildStore = DiscordModules.SelectedGuildStore;
    const MemberStore = DiscordModules.GuildMemberStore;
    const UserStore = DiscordModules.UserStore;
    const DiscordPerms = Object.assign({}, DiscordModules.DiscordPermissions);
    const AvatarDefaults = WebpackModules.getByProps("DEFAULT_AVATARS");
    const ModalClasses = WebpackModules.getByProps("root", "header", "small");
    const Strings = {COPY_ID: "Copy ID"}; // Simple fallback
    const UserPopoutClasses = Object.assign({section: "section_ba4d80", heading: "heading_ba4d80", root: "root_c83b44"}, WebpackModules.getByProps("userPopoutOuter"), WebpackModules.getByProps("defaultColor", "eyebrow"), DiscordClasses.PopoutRoles, WebpackModules.getByProps("root", "expandButton"), WebpackModules.getModule(m => m?.heading && m?.section && Object.keys(m)?.length === 2));
    const RoleClasses = Object.assign({}, DiscordClasses.PopoutRoles, WebpackModules.getByProps("defaultColor", "eyebrow"), WebpackModules.getByProps("role", "roleName", "roleCircle"));

    const getRoles = (guild) => {
    try {
        // If we have a guild ID instead of guild object
        const guildId = typeof guild === 'string' ? guild : guild?.id;
        if (!guildId) return null;

        // Use GuildStore directly to get roles
        const store = BdApi.Webpack.getStore("GuildStore");
        if (!store) return null;

        return store.getRoles(guildId);
    } catch (e) {
        console.error("Failed to get roles:", e);
        return null;
    }
};

    if (DiscordPerms.STREAM) {
        DiscordPerms.VIDEO = DiscordPerms.STREAM;
        delete DiscordPerms.STREAM;
    }
    if (DiscordPerms.MANAGE_GUILD) {
        DiscordPerms.MANAGE_SERVER = DiscordPerms.MANAGE_GUILD;
        delete DiscordPerms.MANAGE_GUILD;
    }

    return class PermissionsViewer extends Plugin {
        constructor() {
            super();
            this.css = `.perm-user-avatar {
    border-radius: 50%;
    width: 16px;
    height: 16px;
    margin-right: 3px;
}

.member-perms-header {
    color: var(--header-secondary);
    display: flex;
    justify-content: space-between;
}

.member-perms {
    display: flex;
    flex-wrap: wrap;
    margin-top: 2px;
    max-height: 160px;
    overflow-y: auto;
    overflow-x: hidden;
}

.member-perms .member-perm .perm-circle {
    border-radius: 50%;
    height: 12px;
    margin-right: 4px;
    width: 12px;
}

.member-perms .member-perm .name {
    margin-right: 4px;
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.perm-details-button {
    cursor: pointer;
    height: 12px;
}

.perm-details {
    display: flex;
    justify-content: flex-end;
}

.member-perm-details {
    cursor: pointer;
}

.member-perm-details-button {
    fill: #72767d;
    height: 10px;
}

/* Modal */

@keyframes permissions-backdrop {
    to { opacity: 0.85; }
}

@keyframes permissions-modal-wrapper {
    to { transform: scale(1); opacity: 1; }
}

@keyframes permissions-backdrop-closing {
    to { opacity: 0; }
}

@keyframes permissions-modal-wrapper-closing {
    to { transform: scale(0.7); opacity: 0; }
}

#permissions-modal-wrapper {
    z-index: 100;
}

#permissions-modal-wrapper .callout-backdrop {
    animation: permissions-backdrop 250ms ease;
    animation-fill-mode: forwards;
    opacity: 0;
    background-color: rgb(0, 0, 0);
    transform: translateZ(0px);
}

#permissions-modal-wrapper.closing .callout-backdrop {
    animation: permissions-backdrop-closing 200ms linear;
    animation-fill-mode: forwards;
    animation-delay: 50ms;
    opacity: 0.85;
}

#permissions-modal-wrapper.closing .modal-wrapper {
    animation: permissions-modal-wrapper-closing 250ms cubic-bezier(0.19, 1, 0.22, 1);
    animation-fill-mode: forwards;
    opacity: 1;
    transform: scale(1);
}

#permissions-modal-wrapper .modal-wrapper {
    animation: permissions-modal-wrapper 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
    animation-fill-mode: forwards;
    transform: scale(0.7);
    transform-origin: 50% 50%;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    contain: content;
    justify-content: center;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    opacity: 0;
    pointer-events: none;
    position: absolute;
    user-select: none;
    z-index: 1000;
}

#permissions-modal-wrapper .modal-body {
    background-color: #36393f;
    height: 440px;
    width: auto;
    /*box-shadow: 0 0 0 1px rgba(32,34,37,.6), 0 2px 10px 0 rgba(0,0,0,.2);*/
    flex-direction: row;
    overflow: hidden;
    display: flex;
    flex: 1;
    contain: layout;
    position: relative;
}

#permissions-modal-wrapper #permissions-modal {
    contain: layout;
    flex-direction: column;
    pointer-events: auto;
    border: 1px solid rgba(28,36,43,.6);
    border-radius: 5px;
    box-shadow: 0 2px 10px 0 rgba(0,0,0,.2);
    overflow: hidden;
}

#permissions-modal-wrapper .header {
    background-color: #35393e;
    box-shadow: 0 2px 3px 0 rgba(0,0,0,.2);
    padding: 12px 20px;
    z-index: 1;
    color: #fff;
    font-size: 16px;
    font-weight: 700;
    line-height: 19px;
}

.role-side, .perm-side {
    flex-direction: column;
    padding-left: 6px;
}

.role-scroller, .perm-scroller {
    contain: layout;
    flex: 1;
    min-height: 1px;
    overflow-y: scroll;
}

#permissions-modal-wrapper .scroller-title {
    color: #fff;
    padding: 8px 0 4px 4px;
    margin-right: 8px;
    border-bottom: 1px solid rgba(0,0,0,0.3);
    display: none;
}

#permissions-modal-wrapper .role-side {
    width: auto;
    min-width: 150px;
    background: #2f3136;
    flex: 0 0 auto;
    overflow: hidden;
    display: flex;
    min-height: 1px;
    position: relative;
}

#permissions-modal-wrapper .role-scroller {
    contain: layout;
    flex: 1;
    min-height: 1px;
    overflow-y: scroll;
    padding-top: 8px;
}

#permissions-modal-wrapper .role-item {
    display: flex;
    border-radius: 2px;
    padding: 6px;
    margin-bottom: 5px;
    cursor: pointer;
    color: #dcddde;
}

#permissions-modal-wrapper .role-item:hover {
    background-color: rgba(0,0,0,0.1);
}

#permissions-modal-wrapper .role-item.selected {
    background-color: rgba(0,0,0,0.2);
}

#permissions-modal-wrapper .perm-side {
    width: 273px;
    background-color: #36393f;
    flex: 0 0 auto;
    display: flex;
    min-height: 1px;
    position: relative;
    padding-left: 10px;
}

#permissions-modal-wrapper .perm-item {
    box-shadow: inset 0 -1px 0 rgba(79,84,92,.3);
    box-sizing: border-box;
    height: 44px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    display: flex;
}

#permissions-modal-wrapper .perm-item.allowed svg {
    fill: #43B581;
}

#permissions-modal-wrapper .perm-item.denied svg {
    fill: #F04747;
}

#permissions-modal-wrapper .perm-name {
    display: inline;
    flex: 1;
    font-size: 16px;
    font-weight: 400;
    overflow: hidden;
    text-overflow: ellipsis;
    user-select: text;
    color: #dcddde;
    margin-left: 10px;
}

.member-perms::-webkit-scrollbar-thumb, .member-perms::-webkit-scrollbar-track,
#permissions-modal-wrapper *::-webkit-scrollbar-thumb, #permissions-modal-wrapper *::-webkit-scrollbar-track {
    background-clip: padding-box;
    border-radius: 7.5px;
    border-style: solid;
    border-width: 3px;
    visibility: hidden;
}

.member-perms:hover::-webkit-scrollbar-thumb, .member-perms:hover::-webkit-scrollbar-track,
#permissions-modal-wrapper *:hover::-webkit-scrollbar-thumb, #permissions-modal-wrapper *:hover::-webkit-scrollbar-track {
    visibility: visible;
}

.member-perms::-webkit-scrollbar-track,
#permissions-modal-wrapper *::-webkit-scrollbar-track {
    border-width: initial;
    background-color: transparent;
    border: 2px solid transparent;
}

.member-perms::-webkit-scrollbar-thumb,
#permissions-modal-wrapper *::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    border-radius: 4px;
    cursor: move;
    background-color: rgba(32,34,37,.6);
}

.member-perms::-webkit-scrollbar,
#permissions-modal-wrapper *::-webkit-scrollbar {
    height: 8px;
    width: 8px;
}

.theme-light #permissions-modal-wrapper #permissions-modal {
    background: #fff;
}

.theme-light #permissions-modal-wrapper .modal-body {
    background: transparent;
}

.theme-light #permissions-modal-wrapper .header {
    background: transparent;
    color: #000;
}

.theme-light #permissions-modal-wrapper .role-side {
    background: rgba(0,0,0,.2);
}

.theme-light #permissions-modal-wrapper .perm-side {
    background: rgba(0,0,0,.1);
}

.theme-light #permissions-modal-wrapper .role-item,
.theme-light #permissions-modal-wrapper .perm-name {
    color: #000;
}

#permissions-modal-wrapper #permissions-modal {
    width: auto;
}`;
            this.jumbo = `#permissions-modal-wrapper #permissions-modal {
    height: 840px;
}

#permissions-modal-wrapper #permissions-modal .perm-side {
    width: 500px;
}

#permissions-modal .perm-scroller {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
}

#permissions-modal .perm-item {
    width: 50%;
}`;
            this.sectionHTML = `<div class="{{section}}" id="permissions-popout">
    <h1 class="member-perms-header {{text-xs/semibold}} {{defaultColor_e9e35f}} {{heading}}" data-text-variant="text-xs/semibold" style="color: var(--header-secondary);">
        <div class="member-perms-title">{{sectionTitle}}</div>
        <span class="perm-details">
            <svg name="Details" viewBox="0 0 24 24" class="perm-details-button" fill="currentColor">
                <path d="M0 0h24v24H0z" fill="none"/>
                <path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
            </svg>
        </span>
    </h1>
    <div class="member-perms {{root}}"></div>
</div>`;
            this.itemHTML = `<div class="member-perm {{role}}">
    <div class="perm-circle {{roleCircle}}"></div>
    <div class="name {{roleName}} {{defaultColor}}"></div>
</div>`;
            this.modalHTML = `<div id="permissions-modal-wrapper">
        <div class="callout-backdrop {{backdrop}}"></div>
        <div class="modal-wrapper">
            <div id="permissions-modal" class="{{root}} {{small}}">
                <div class="header"><div class="title">{{header}}</div></div>
                <div class="modal-body">
                    <div class="role-side">
                        <span class="scroller-title role-list-title">{{rolesLabel}}</span>
                        <div class="role-scroller">

                        </div>
                    </div>
                    <div class="perm-side">
                        <span class="scroller-title perm-list-title">{{permissionsLabel}}</span>
                        <div class="perm-scroller">

                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>`;
            this.modalItem = `<div class="perm-item"><span class="perm-name"></span></div>`;
            this.modalButton = `<div class="role-item"><span class="role-name"></span></div>`;
            this.modalButtonUser = `<div class="role-item"><div class="wrapper_de5239 xsmall_d82b57"><div class="image__25781 xsmall_d82b57 perm-user-avatar" style="background-image: url('\\{{avatarUrl}}');"></div></div><span class="role-name marginLeft8_ff311d"></span></div>`;
            this.permAllowedIcon = `<svg height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`;
            this.permDeniedIcon = `<svg height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8 0-1.85.63-3.55 1.69-4.9L16.9 18.31C15.55 19.37 13.85 20 12 20zm6.31-3.1L7.1 5.69C8.45 4.63 10.15 4 12 4c4.42 0 8 3.58 8 8 0 1.85-.63 3.55-1.69 4.9z"/></svg>`;

            this.cancelUserPopout = () => {};
            this.contextMenuPatches = [];
        }

        onStart() {
            DOM.addStyle(this.name, this.css);

            this.sectionHTML = Utilities.formatString(this.sectionHTML, DiscordClasses.UserPopout);
            this.sectionHTML = Utilities.formatString(this.sectionHTML, RoleClasses);
            this.sectionHTML = Utilities.formatString(this.sectionHTML, UserPopoutClasses);
            this.itemHTML = Utilities.formatString(this.itemHTML, RoleClasses);
            this.modalHTML = Utilities.formatString(this.modalHTML, DiscordClasses.Backdrop);
            this.modalHTML = Utilities.formatString(this.modalHTML, {root: ModalClasses.root, small: ModalClasses.small});

            this.promises = {state: {cancelled: false}, cancel() {this.state.cancelled = true;}};
            if (this.settings.popouts) this.bindPopouts();
            if (this.settings.contextMenus) this.bindContextMenus();
            this.setDisplayMode(this.settings.displayMode);
        }

        onStop() {
            DOM.removeStyle(this.name);
            this.promises.cancel();
            this.unbindPopouts();
            this.unbindContextMenus();
        }

        setDisplayMode(mode) {
            if (mode === "cozy") DOM.addStyle(this.name + "-jumbo", this.jumbo);
            else DOM.removeStyle(this.name + "-jumbo");
        }

        patchPopouts(e) {
            const popoutMount = (props) => {
                const popout = document.querySelector(`[class*="userPopout_"], [class*="userPopoutOuter_"]`);
                if (!popout || popout.querySelector("#permissions-popout")) return;
                const user = MemberStore.getMember(props.displayProfile.guildId, props.user.id);
                const guild = GuildStore.getGuild(props.displayProfile.guildId);
                const name = MemberStore.getNick(props.displayProfile.guildId, props.user.id) ?? props.user.username;
                if (!user || !guild || !name) return;

                const userRoles = user.roles.slice(0);
                userRoles.push(guild.id);
                userRoles.reverse();
                let perms = 0n;
                const permBlock = DOMTools.createElement(Utilities.formatString(this.sectionHTML, {sectionTitle: this.strings.popoutLabel}));
                const memberPerms = permBlock.querySelector(".member-perms");
                const strings = Strings;

                const referenceRoles = getRoles(guild);
                for (let r = 0; r < userRoles.length; r++) {
                    const role = userRoles[r];
                    if (!referenceRoles[role]) continue;
                    perms = perms | referenceRoles[role].permissions;
                    for (const perm in DiscordPerms) {
                        const permName = strings[perm] || perm.split("_").map(n => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" ");
                        const hasPerm = (perms & DiscordPerms[perm]) == DiscordPerms[perm];
                        if (hasPerm && !memberPerms.querySelector(`[data-name="${permName}"]`)) {
                            const element = DOMTools.createElement(this.itemHTML);
                            // element.classList.add(RoleClasses.rolePill);
                            let roleColor = referenceRoles[role].colorString;
                            element.querySelector(".name").textContent = permName;
                            element.setAttribute("data-name", permName);
                            if (!roleColor) roleColor = "#B9BBBE";
                            element.querySelector(".perm-circle").style.backgroundColor = ColorConverter.rgbToAlpha(roleColor, 1);
                            // element.style.borderColor = ColorConverter.rgbToAlpha(roleColor, 0.6);
                            memberPerms.prepend(element);
                        }
                    }
                }

                permBlock.querySelector(".perm-details").addEventListener("click", () => {
                    this.showModal(this.createModalUser(name, user, guild));
                });
                let roleList = popout.querySelector(`[class*="section_"]`);
                roleList = roleList?.parentElement;
                roleList?.parentNode?.append(permBlock);

                const popoutInstance = ReactTools.getOwnerInstance(popout, {include: ["Popout"]});
                if (!popoutInstance || !popoutInstance.updateOffsets) return;
                popoutInstance.updateOffsets();
            };

            if (!e.addedNodes.length || !(e.addedNodes[0] instanceof Element)) return;
            const element = e.addedNodes[0];
            const popout = element.querySelector(`[class*="userPopout_"], [class*="userPopoutOuter_"]`) ?? element;
            if (!popout || !popout.matches(`[class*="userPopout_"], [class*="userPopoutOuter_"]`)) return;
            const props = Utilities.findInTree(ReactTools.getReactInstance(popout), m => m && m.user, {walkable: ["memoizedProps", "return"]});
            popoutMount(props);
        }

        bindPopouts() {
            this.observer = this.patchPopouts.bind(this);
        }

        unbindPopouts() {
            this.observer = undefined;
        }

        async bindContextMenus() {
            this.patchChannelContextMenu();
            this.patchGuildContextMenu();
            this.patchUserContextMenu();
        }

        unbindContextMenus() {
            for (const cancel of this.contextMenuPatches) cancel();
        }

        patchGuildContextMenu() {
            this.contextMenuPatches.push(ContextMenu.patch("guild-context", (retVal, props) => {
                if (!props?.guild) return retVal; // Ignore non-guild items
                const newItem = ContextMenu.buildItem({
                    label: this.strings.contextMenuLabel,
                    action: () => {
                        this.showModal(this.createModalGuild(props.guild.name, props.guild));
                    }
                });
                retVal.props.children.splice(1, 0, newItem);
            }));
        }

        patchChannelContextMenu() {
            this.contextMenuPatches.push(ContextMenu.patch("channel-context", (retVal, props) => {
                const newItem = ContextMenu.buildItem({
                    label: this.strings.contextMenuLabel,
                    action: () => {
                        if (!Object.keys(props.channel.permissionOverwrites).length) return Toasts.info(`#${props.channel.name} has no permission overrides`);
                        this.showModal(this.createModalChannel(props.channel.name, props.channel, props.guild));
                    }
                });
                retVal.props.children.splice(1, 0, newItem);
            }));
        }

        patchUserContextMenu() {
    this.contextMenuPatches.push(ContextMenu.patch("user-context", (retVal, props) => {
        if (!props.guildId) return;

        const newItem = ContextMenu.buildItem({
            label: this.strings.contextMenuLabel,
            action: () => {
                // Get stores using BdApi like we did for GuildStore
                const MemberStore = BdApi.Webpack.getStore("GuildMemberStore");
                const GuildStore = BdApi.Webpack.getStore("GuildStore");

                const user = MemberStore.getMember(props.guildId, props.user.id);
                const guild = GuildStore.getGuild(props.guildId);
                if (!user || !guild) return;
                const name = user.nick ? user.nick : props.user.username;
                this.showModal(this.createModalUser(name, user, guild));
            }
        });

        // Find the first group in the menu
        const firstGroup = retVal?.props?.children?.find(c => Array.isArray(c?.props?.children));
        if (firstGroup?.props?.children) {
            // Insert after basic user actions
            firstGroup.props.children.splice(2, 0, newItem);
        }
    }));
}

        showModal(modal) {
            const popout = document.querySelector(`[class*="userPopoutOuter-"]`);
            if (popout) popout.style.display = "none";
            const app = document.querySelector(".app-19_DXt");
            if (app) app.append(modal);
            else document.querySelector("#app-mount").append(modal);

            const closeModal = (event) => {
                if (!event.key === "Escape") return;
                modal.classList.add("closing");
                setTimeout(() => {modal.remove();}, 300);
            };
            document.addEventListener("keydown", closeModal, true);
            DOMTools.onRemoved(modal, () => document.removeEventListener("keydown", closeModal, true));
        }

        createModalChannel(name, channel, guild) {
            return this.createModal(`#${name}`, channel.permissionOverwrites, getRoles(guild), true);
        }

        createModalUser(name, user, guild) {
            const guildRoles = Object.assign({}, getRoles(guild));
            const userRoles = user.roles.slice(0).filter(r => typeof(guildRoles[r]) !== "undefined");

            userRoles.push(guild.id);
            userRoles.sort((a, b) => {return guildRoles[b].position - guildRoles[a].position;});

            if (user.userId == guild.ownerId) {
                const ALL_PERMISSIONS = Object.values(DiscordModules.DiscordPermissions).reduce((all, p) => all | p);
                userRoles.push(user.userId);
                guildRoles[user.userId] = {name: this.strings.modal.owner, permissions: ALL_PERMISSIONS};
            }
            return this.createModal(name, userRoles, guildRoles);
        }

        createModalGuild(name, guild) {
            return this.createModal(name, getRoles(guild));
        }

        createModal(title, displayRoles, referenceRoles, isOverride = false) {
            if (!referenceRoles) referenceRoles = displayRoles;
            const modal = DOMTools.createElement(Utilities.formatString(Utilities.formatString(this.modalHTML, this.strings.modal), {name: Utils.escapeHTML(title)}));
            const closeModal = () => {
                modal.classList.add("closing");
                setTimeout(() => {modal.remove();}, 300);
            };
            modal.querySelector(".callout-backdrop").addEventListener("click", closeModal);

            const strings = Strings || {};
            for (const r in displayRoles) {
                const role = Array.isArray(displayRoles) ? displayRoles[r] : r;
                const user = UserStore.getUser(role) || {getAvatarURL: () => AvatarDefaults.DEFAULT_AVATARS[Math.floor(Math.random() * AvatarDefaults.DEFAULT_AVATARS.length)], username: role};
                const member = MemberStore.getMember(SelectedGuildStore.getGuildId(), role) || {colorString: ""};
                const item = DOMTools.createElement(!isOverride || displayRoles[role].type == 0 ? this.modalButton : Utilities.formatString(this.modalButtonUser, {avatarUrl: user.getAvatarURL(null, 16, true)})); // getAvatarURL(guildId, size, canAnimate);
                if (!isOverride || displayRoles[role].type == 0) item.style.color = referenceRoles[role].colorString;
                else item.style.color = member.colorString;
                if (isOverride) item.querySelector(".role-name").innerHTML = Utils.escapeHTML(displayRoles[role].type == 0 ? referenceRoles[role].name : user.username);
                else item.querySelector(".role-name").innerHTML = Utils.escapeHTML(referenceRoles[role].name);
                modal.querySelector(".role-scroller").append(item);
                item.addEventListener("click", () => {
                    modal.querySelectorAll(".role-item.selected").forEach(e => e.classList.remove("selected"));
                    item.classList.add("selected");
                    const allowed = isOverride ? displayRoles[role].allow : referenceRoles[role].permissions;
                    const denied = isOverride ? displayRoles[role].deny : null;

                    const permList = modal.querySelector(".perm-scroller");
                    permList.innerHTML = "";
                    for (const perm in DiscordPerms) {
                        const element = DOMTools.createElement(this.modalItem);
                        const permAllowed = (allowed & DiscordPerms[perm]) == DiscordPerms[perm];
                        const permDenied = isOverride ? (denied & DiscordPerms[perm]) == DiscordPerms[perm] : !permAllowed;
                        if (!permAllowed && !permDenied) continue;
                        if (permAllowed) {
                            element.classList.add("allowed");
                            element.prepend(DOMTools.createElement(this.permAllowedIcon));
                        }
                        if (permDenied) {
                            element.classList.add("denied");
                            element.prepend(DOMTools.createElement(this.permDeniedIcon));
                        }
                        element.querySelector(".perm-name").textContent = strings[perm] || perm.split("_").map(n => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" ");
                        permList.append(element);
                    }
                });
                item.addEventListener("contextmenu", (e) => {
                    ContextMenu.open(e, ContextMenu.buildMenu([
                        {label: Strings.COPY_ID ?? "Copy Id", action: () => {DiscordModules.ElectronModule.copy(role);}}
                    ]));
                });
            }

            modal.querySelector(".role-item").click();

            return modal;
        }

        getSettingsPanel() {
            const panel = this.buildSettingsPanel();
            panel.addListener((id, checked) => {
                if (id == "popouts") {
                    if (checked) this.bindPopouts();
                    else this.unbindPopouts();
                }
                if (id == "contextMenus") {
                    if (checked) this.bindContextMenus();
                    this.unbindContextMenus();
                }
                if (id == "displayMode") this.setDisplayMode(checked);
            });
            return panel.getElement();
        }

    };
};
     return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
/*@end@*/