Plugin
PermissionsViewer
View the permissions a user or channel has, and the roles of a server
1
import "./styles.css";2
3
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";4
import { definePluginSettings } from "@api/Settings";5
import ErrorBoundary from "@components/ErrorBoundary";6
import { SafetyIcon } from "@components/Icons";7
import { TooltipContainer } from "@components/TooltipContainer";8
import { Devs } from "@utils/constants";9
import { classes } from "@utils/misc";10
import definePlugin, { OptionType } from "@utils/types";11
import type { Guild, RoleOrUserPermission } from "@vencord/discord-types";12
import { PermissionOverwriteType } from "@vencord/discord-types/enums";13
import { findCssClassesLazy } from "@webpack";14
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, useEffect, useRef, UserStore } from "@webpack/common";15
16
import openRolesAndUsersPermissionsModal from "./components/RolesAndUsersPermissions";17
import UserPermissions from "./components/UserPermissions";18
import { getSortedRolesForMember, loadGetGuildPermissionSpecMap, sortPermissionOverwrites } from "./utils";19
20
const PopoutClasses = findCssClassesLazy("container", "popoutRoleDot");21
22
export const enum PermissionsSortOrder {23
HighestRole,24
LowestRole25
}26
27
const enum MenuItemParentType {28
User,29
Channel,30
Guild31
}32
33
export const settings = definePluginSettings({34
permissionsSortOrder: {35
description: "The sort method used for defining which role grants an user a certain permission",36
type: OptionType.SELECT,37
options: [38
{ label: "Highest Role", value: PermissionsSortOrder.HighestRole, default: true },39
{ label: "Lowest Role", value: PermissionsSortOrder.LowestRole }40
]41
},42
});43
44
function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {45
if (type === MenuItemParentType.User && !GuildMemberStore.isMember(guildId, id!)) return null;46
47
return (48
<Menu.MenuItem49
id="perm-viewer-permissions"50
label="Permissions"51
action={() => {52
const guild = GuildStore.getGuild(guildId);53
54
const { permissions, header } = match(type)55
.returnType<{ permissions: RoleOrUserPermission[], header: string; }>()56
.with(MenuItemParentType.User, () => {57
const member = GuildMemberStore.getMember(guildId, id!)!;58
59
const permissions: RoleOrUserPermission[] = getSortedRolesForMember(guild, member)60
.map(role => ({61
type: PermissionOverwriteType.ROLE,62
...role63
}));64
65
if (guild.ownerId === id) {66
permissions.push({67
type: PermissionOverwriteType.OWNER,68
permissions: Object.values(PermissionsBits).reduce((prev, curr) => prev | curr, 0n)69
});70
}71
72
return {73
permissions,74
header: member.nick ?? UserStore.getUser(member.userId).username75
};76
})77
.with(MenuItemParentType.Channel, () => {78
const channel = ChannelStore.getChannel(id!);79
80
const permissions = sortPermissionOverwrites(Object.values(channel.permissionOverwrites).map(({ id, allow, deny, type }) => ({81
type,82
id,83
overwriteAllow: allow,84
overwriteDeny: deny85
})), guildId);86
87
return {88
permissions,89
header: channel.name90
};91
})92
.otherwise(() => {93
const permissions = GuildRoleStore.getSortedRoles(guild.id).map(role => ({94
type: PermissionOverwriteType.ROLE,95
...role96
}));97
98
return {99
permissions,100
header: guild.name101
};102
});103
104
openRolesAndUsersPermissionsModal(permissions, guild, header);105
}}106
/>107
);108
}109
110
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {111
return (children, props) => {112
if (113
!props ||114
(type === MenuItemParentType.User && !props.user) ||115
(type === MenuItemParentType.Guild && !props.guild) ||116
(type === MenuItemParentType.Channel && (!props.channel || !props.guild))117
) {118
return;119
}120
121
const group = findGroupChildrenByChildId(childId, children);122
123
const item = match(type)124
.with(MenuItemParentType.User, () => MenuItem(props.guildId, props.user.id, type))125
.with(MenuItemParentType.Channel, () => MenuItem(props.guild.id, props.channel.id, type))126
.with(MenuItemParentType.Guild, () => MenuItem(props.guild.id))127
.otherwise(() => null);128
129
if (item == null) return;130
131
if (group) {132
return group.push(item);133
}134
135
// "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID"136
if (childId === "roles" && props.guildId) {137
children.splice(-1, 0, <Menu.MenuGroup>{item}</Menu.MenuGroup>);138
}139
};140
}141
142
export default definePlugin({143
name: "PermissionsViewer",144
description: "View the permissions a user or channel has, and the roles of a server",145
tags: ["Servers", "Roles", "Utility"],146
authors: [Devs.Nuckyz, Devs.Ven],147
settings,148
149
patches: [150
{151
find: "#{intl::COLLAPSE_ROLES}",152
replacement: {153
match: /(?<=\i\.id\)\),\i\(\))(?=,\i\?)/,154
replace: ",$self.ViewPermissionsButton(arguments[0])"155
}156
}157
],158
159
ViewPermissionsButton: ErrorBoundary.wrap(({ className, guild, userId }: { className: string; guild: Guild; userId: string; }) => {160
const buttonRef = useRef(null);161
useEffect(() => void loadGetGuildPermissionSpecMap(), []);162
163
const guildMember = GuildMemberStore.getMember(guild.id, userId);164
if (!guildMember) return null;165
166
return (167
<Popout168
position="bottom"169
align="center"170
targetElementRef={buttonRef}171
renderPopout={({ closePopout }) => (172
<Dialog className={PopoutClasses.container} style={{ width: "500px" }}>173
<UserPermissions guild={guild} guildMember={guildMember} closePopout={closePopout} />174
</Dialog>175
)}176
>177
{popoutProps => (178
<TooltipContainer text="View Permissions">179
<Button180
{...popoutProps}181
ref={buttonRef}182
color={Button.Colors.CUSTOM}183
look={Button.Looks.FILLED}184
size={Button.Sizes.NONE}185
className={classes(className, "vc-permviewer-role-button")}186
>187
<SafetyIcon height="16" width="16" />188
</Button>189
</TooltipContainer>190
)}191
</Popout>192
);193
}, { noop: true }),194
195
contextMenus: {196
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User),197
"channel-context": makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),198
"guild-context": makeContextMenuPatch("privacy", MenuItemParentType.Guild),199
"guild-header-popout": makeContextMenuPatch("privacy", MenuItemParentType.Guild)200
}201
});202