2023-05-14 21:33:04 -03:00
|
|
|
/*
|
|
|
|
* Vencord, a modification for Discord's desktop app
|
|
|
|
* Copyright (c) 2023 Vendicated and contributors
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import "./styles.css";
|
|
|
|
|
2024-03-07 11:06:24 +01:00
|
|
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
2023-05-14 21:33:04 -03:00
|
|
|
import { definePluginSettings } from "@api/Settings";
|
2024-06-26 14:38:50 +02:00
|
|
|
import ErrorBoundary from "@components/ErrorBoundary";
|
|
|
|
import { SafetyIcon } from "@components/Icons";
|
2023-05-14 21:33:04 -03:00
|
|
|
import { Devs } from "@utils/constants";
|
2024-06-26 14:38:50 +02:00
|
|
|
import { classes } from "@utils/misc";
|
2023-05-14 21:33:04 -03:00
|
|
|
import definePlugin, { OptionType } from "@utils/types";
|
2024-06-26 14:38:50 +02:00
|
|
|
import { findByPropsLazy } from "@webpack";
|
2024-09-02 06:42:52 +03:00
|
|
|
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common";
|
2023-05-14 21:33:04 -03:00
|
|
|
import type { Guild, GuildMember } from "discord-types/general";
|
|
|
|
|
|
|
|
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions";
|
|
|
|
import UserPermissions from "./components/UserPermissions";
|
2023-05-19 21:24:56 -03:00
|
|
|
import { getSortedRoles, sortPermissionOverwrites } from "./utils";
|
2023-05-14 21:33:04 -03:00
|
|
|
|
2024-06-26 14:38:50 +02:00
|
|
|
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
|
2024-07-10 04:33:03 -03:00
|
|
|
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "banner");
|
2024-06-26 14:38:50 +02:00
|
|
|
|
2023-05-14 21:33:04 -03:00
|
|
|
export const enum PermissionsSortOrder {
|
|
|
|
HighestRole,
|
|
|
|
LowestRole
|
|
|
|
}
|
|
|
|
|
|
|
|
const enum MenuItemParentType {
|
|
|
|
User,
|
|
|
|
Channel,
|
|
|
|
Guild
|
|
|
|
}
|
|
|
|
|
|
|
|
export const settings = definePluginSettings({
|
|
|
|
permissionsSortOrder: {
|
|
|
|
description: "The sort method used for defining which role grants an user a certain permission",
|
|
|
|
type: OptionType.SELECT,
|
|
|
|
options: [
|
|
|
|
{ label: "Highest Role", value: PermissionsSortOrder.HighestRole, default: true },
|
|
|
|
{ label: "Lowest Role", value: PermissionsSortOrder.LowestRole }
|
2024-09-02 06:42:52 +03:00
|
|
|
]
|
2023-05-14 21:33:04 -03:00
|
|
|
},
|
|
|
|
defaultPermissionsDropdownState: {
|
|
|
|
description: "Whether the permissions dropdown on user popouts should be open by default",
|
|
|
|
type: OptionType.BOOLEAN,
|
2024-09-02 06:42:52 +03:00
|
|
|
default: false
|
2023-05-14 21:33:04 -03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
|
2023-05-16 00:19:20 +02:00
|
|
|
if (type === MenuItemParentType.User && !GuildMemberStore.isMember(guildId, id!)) return null;
|
|
|
|
|
2023-05-14 21:33:04 -03:00
|
|
|
return (
|
|
|
|
<Menu.MenuItem
|
|
|
|
id="perm-viewer-permissions"
|
|
|
|
label="Permissions"
|
|
|
|
action={() => {
|
|
|
|
const guild = GuildStore.getGuild(guildId);
|
|
|
|
|
2024-09-02 01:54:16 -03:00
|
|
|
const { permissions, header } = match(type)
|
|
|
|
.returnType<{ permissions: RoleOrUserPermission[], header: string; }>()
|
2024-09-02 06:42:52 +03:00
|
|
|
.with(MenuItemParentType.User, () => {
|
2023-05-14 21:33:04 -03:00
|
|
|
const member = GuildMemberStore.getMember(guildId, id!);
|
|
|
|
|
2024-09-02 06:42:52 +03:00
|
|
|
const permissions: RoleOrUserPermission[] = getSortedRoles(guild, member)
|
2023-05-14 21:33:04 -03:00
|
|
|
.map(role => ({
|
|
|
|
type: PermissionType.Role,
|
|
|
|
...role
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (guild.ownerId === id) {
|
|
|
|
permissions.push({
|
|
|
|
type: PermissionType.Owner,
|
|
|
|
permissions: Object.values(PermissionsBits).reduce((prev, curr) => prev | curr, 0n)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-09-02 06:42:52 +03:00
|
|
|
return {
|
|
|
|
permissions,
|
|
|
|
header: member.nick ?? UserStore.getUser(member.userId).username
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.with(MenuItemParentType.Channel, () => {
|
2023-05-14 21:33:04 -03:00
|
|
|
const channel = ChannelStore.getChannel(id!);
|
|
|
|
|
2024-09-02 06:42:52 +03:00
|
|
|
const permissions = sortPermissionOverwrites(Object.values(channel.permissionOverwrites).map(({ id, allow, deny, type }) => ({
|
2023-05-14 21:33:04 -03:00
|
|
|
type: type as PermissionType,
|
|
|
|
id,
|
|
|
|
overwriteAllow: allow,
|
|
|
|
overwriteDeny: deny
|
2023-05-19 21:24:56 -03:00
|
|
|
})), guildId);
|
2023-05-14 21:33:04 -03:00
|
|
|
|
2024-09-02 06:42:52 +03:00
|
|
|
return {
|
|
|
|
permissions,
|
|
|
|
header: channel.name
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.otherwise(() => {
|
|
|
|
const permissions = Object.values(GuildStore.getRoles(guild.id)).map(role => ({
|
2023-05-14 21:33:04 -03:00
|
|
|
type: PermissionType.Role,
|
|
|
|
...role
|
|
|
|
}));
|
|
|
|
|
2024-09-02 06:42:52 +03:00
|
|
|
return {
|
|
|
|
permissions,
|
|
|
|
header: guild.name
|
|
|
|
};
|
|
|
|
});
|
2023-05-14 21:33:04 -03:00
|
|
|
|
|
|
|
openRolesAndUsersPermissionsModal(permissions, guild, header);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-05-16 00:19:20 +02:00
|
|
|
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
|
2024-03-07 11:06:24 +01:00
|
|
|
return (children, props) => {
|
2024-09-02 06:42:52 +03:00
|
|
|
if (
|
|
|
|
!props ||
|
|
|
|
(type === MenuItemParentType.User && !props.user) ||
|
|
|
|
(type === MenuItemParentType.Guild && !props.guild) ||
|
|
|
|
(type === MenuItemParentType.Channel && (!props.channel || !props.guild))
|
|
|
|
) {
|
2024-03-07 11:06:24 +01:00
|
|
|
return;
|
2024-09-02 06:42:52 +03:00
|
|
|
}
|
2023-05-14 21:33:04 -03:00
|
|
|
|
|
|
|
const group = findGroupChildrenByChildId(childId, children);
|
|
|
|
|
2024-09-02 06:42:52 +03:00
|
|
|
const item = match(type)
|
|
|
|
.with(MenuItemParentType.User, () => MenuItem(props.guildId, props.user.id, type))
|
|
|
|
.with(MenuItemParentType.Channel, () => MenuItem(props.guild.id, props.channel.id, type))
|
|
|
|
.with(MenuItemParentType.Guild, () => MenuItem(props.guild.id))
|
|
|
|
.otherwise(() => null);
|
|
|
|
|
2023-05-16 00:19:20 +02:00
|
|
|
|
|
|
|
if (item == null) return;
|
|
|
|
|
2024-09-02 06:42:52 +03:00
|
|
|
if (group) {
|
|
|
|
return group.push(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
// "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID"
|
|
|
|
if (childId === "roles" && props.guildId) {
|
2023-05-16 00:19:20 +02:00
|
|
|
children.splice(-1, 0, <Menu.MenuGroup>{item}</Menu.MenuGroup>);
|
2024-09-02 06:42:52 +03:00
|
|
|
}
|
2023-05-14 21:33:04 -03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export default definePlugin({
|
|
|
|
name: "PermissionsViewer",
|
|
|
|
description: "View the permissions a user or channel has, and the roles of a server",
|
|
|
|
authors: [Devs.Nuckyz, Devs.Ven],
|
|
|
|
settings,
|
|
|
|
|
|
|
|
patches: [
|
2024-06-26 14:38:50 +02:00
|
|
|
{
|
|
|
|
find: ".VIEW_ALL_ROLES,",
|
|
|
|
replacement: {
|
2024-10-04 05:28:36 -04:00
|
|
|
match: /\.expandButton,.+?null,/,
|
2024-06-26 14:38:50 +02:00
|
|
|
replace: "$&$self.ViewPermissionsButton(arguments[0]),"
|
|
|
|
}
|
2023-05-14 21:33:04 -03:00
|
|
|
}
|
|
|
|
],
|
|
|
|
|
2024-06-26 14:38:50 +02:00
|
|
|
ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => (
|
|
|
|
<Popout
|
|
|
|
position="bottom"
|
|
|
|
align="center"
|
|
|
|
renderPopout={() => (
|
|
|
|
<Dialog className={PopoutClasses.container} style={{ width: "500px" }}>
|
2024-08-22 20:29:39 -03:00
|
|
|
<UserPermissions guild={guild} guildMember={guildMember} forceOpen />
|
2024-06-26 14:38:50 +02:00
|
|
|
</Dialog>
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
{popoutProps => (
|
|
|
|
<TooltipContainer text="View Permissions">
|
|
|
|
<Button
|
|
|
|
{...popoutProps}
|
|
|
|
color={Button.Colors.CUSTOM}
|
|
|
|
look={Button.Looks.FILLED}
|
|
|
|
size={Button.Sizes.NONE}
|
|
|
|
innerClassName={classes(RoleButtonClasses.buttonInner, RoleButtonClasses.icon)}
|
|
|
|
className={classes(RoleButtonClasses.button, RoleButtonClasses.icon, "vc-permviewer-role-button")}
|
|
|
|
>
|
|
|
|
<SafetyIcon height="16" width="16" />
|
|
|
|
</Button>
|
|
|
|
</TooltipContainer>
|
|
|
|
)}
|
|
|
|
</Popout>
|
|
|
|
), { noop: true }),
|
2023-05-14 21:33:04 -03:00
|
|
|
|
2024-03-07 11:06:24 +01:00
|
|
|
contextMenus: {
|
|
|
|
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User),
|
|
|
|
"channel-context": makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
|
|
|
|
"guild-context": makeContextMenuPatch("privacy", MenuItemParentType.Guild),
|
|
|
|
"guild-header-popout": makeContextMenuPatch("privacy", MenuItemParentType.Guild)
|
|
|
|
}
|
2023-05-14 21:33:04 -03:00
|
|
|
});
|