Plugin

PermissionsViewer

View the permissions a user or channel has, and the roles of a server

Servers Roles Utility
index.tsx
Download

Source

src/plugins/permissionsViewer/index.tsx
1import "./styles.css";
2
3import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
4import { definePluginSettings } from "@api/Settings";
5import ErrorBoundary from "@components/ErrorBoundary";
6import { SafetyIcon } from "@components/Icons";
7import { TooltipContainer } from "@components/TooltipContainer";
8import { Devs } from "@utils/constants";
9import { classes } from "@utils/misc";
10import definePlugin, { OptionType } from "@utils/types";
11import type { Guild, RoleOrUserPermission } from "@vencord/discord-types";
12import { PermissionOverwriteType } from "@vencord/discord-types/enums";
13import { findCssClassesLazy } from "@webpack";
14import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, useEffect, useRef, UserStore } from "@webpack/common";
15
16import openRolesAndUsersPermissionsModal from "./components/RolesAndUsersPermissions";
17import UserPermissions from "./components/UserPermissions";
18import { getSortedRolesForMember, loadGetGuildPermissionSpecMap, sortPermissionOverwrites } from "./utils";
19
20const PopoutClasses = findCssClassesLazy("container", "popoutRoleDot");
21
22export const enum PermissionsSortOrder {
23 HighestRole,
24 LowestRole
25}
26
27const enum MenuItemParentType {
28 User,
29 Channel,
30 Guild
31}
32
33export 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
44function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
45 if (type === MenuItemParentType.User && !GuildMemberStore.isMember(guildId, id!)) return null;
46
47 return (
48 <Menu.MenuItem
49 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 ...role
63 }));
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).username
75 };
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: deny
85 })), guildId);
86
87 return {
88 permissions,
89 header: channel.name
90 };
91 })
92 .otherwise(() => {
93 const permissions = GuildRoleStore.getSortedRoles(guild.id).map(role => ({
94 type: PermissionOverwriteType.ROLE,
95 ...role
96 }));
97
98 return {
99 permissions,
100 header: guild.name
101 };
102 });
103
104 openRolesAndUsersPermissionsModal(permissions, guild, header);
105 }}
106 />
107 );
108}
109
110function 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
142export 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 <Popout
168 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 <Button
180 {...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