diff --git a/RoleMembersModal.tsx b/RoleMembersModal.tsx index 76c5756..3ef6cb4 100644 --- a/RoleMembersModal.tsx +++ b/RoleMembersModal.tsx @@ -4,225 +4,40 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; -import { InfoIcon } from "@components/Icons"; -import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { findByCodeLazy, findExportedComponentLazy } from "@webpack"; -import { Constants, GuildChannelStore, GuildMemberStore, GuildStore, Parser, RestAPI, ScrollerThin, showToast, Text, Tooltip, useEffect, UserStore, useState } from "@webpack/common"; -import { UnicodeEmoji } from "@webpack/types"; -import type { Role } from "discord-types/general"; +import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { Forms, Parser } from "@webpack/common"; +import { GuildMember } from "discord-types/general"; -import { cl, GuildUtils } from "./utils"; +const cl = classNameFactory("vc-inrole-"); -type GetRoleIconData = (role: Role, size: number) => { customIconSrc?: string; unicodeEmoji?: UnicodeEmoji; }; -const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots"); -const getRoleIconData: GetRoleIconData = findByCodeLazy("convertSurrogateToName", "customIconSrc", "unicodeEmoji"); - -let rolesFetched2; -let members2; - -function getRoleIconSrc(role: Role) { - const icon = getRoleIconData(role, 20); - if (!icon) return; - - const { customIconSrc, unicodeEmoji } = icon; - return customIconSrc ?? unicodeEmoji?.url; -} - -function MembersContainer({ guildId, roleId }: { guildId: string; roleId: string; }) { - - const channelId = GuildChannelStore.getChannels(guildId).SELECTABLE[0].channel.id; - - // RMC: RoleMemberCounts - const [RMC, setRMC] = useState({}); - useEffect(() => { - let loading = true; - const interval = setInterval(async () => { - try { - await RestAPI.get({ - url: Constants.Endpoints.GUILD_ROLE_MEMBER_COUNTS(guildId) - }).then(x => { - if (x.ok) setRMC(x.body); clearInterval(interval); - }); - } catch (error) { console.error("Error fetching member counts", error); } - }, 1000); - return () => { loading = false; }; - }, []); - - let usersInRole = []; - const [rolesFetched, setRolesFetched] = useState(Array); - useEffect(() => { - if (!rolesFetched.includes(roleId)) { - const interval = setInterval(async () => { - try { - const response = await RestAPI.get({ - url: Constants.Endpoints.GUILD_ROLE_MEMBER_IDS(guildId, roleId), - }); - ({ body: usersInRole } = response); - await GuildUtils.requestMembersById(guildId, usersInRole, !1); - setRolesFetched([...rolesFetched, roleId]); - rolesFetched2 = rolesFetched; - clearInterval(interval); - } catch (error) { console.error("Error fetching members:", error); } - }, 1200); - return () => clearInterval(interval); - } - }, [roleId]); // Fetch roles - - const [members, setMembers] = useState(GuildMemberStore.getMembers(guildId)); - useEffect(() => { - const interval = setInterval(async () => { - if (usersInRole) { - const guildMembers = GuildMemberStore.getMembers(guildId); - const storedIds = guildMembers.map(user => user.userId); - usersInRole.every(id => storedIds.includes(id)) && clearInterval(interval); - if (guildMembers !== members) { - setMembers(GuildMemberStore.getMembers(guildId)); - } - } - }, 500); - return () => clearInterval(interval); - }, [roleId, rolesFetched]); - - const roleMembers = members.filter(x => x.roles.includes(roleId)).map(x => UserStore.getUser(x.userId)); - - return ( -
-
-
- - {roleMembers.length} loaded / {RMC[roleId] || 0} members with this role
-
- - {props => } - -
- -
- - {roleMembers.map(x => { - return ( -
- - {Parser.parse(`<@${x.id}>`, true, { channelId, viewingChannelId: channelId })} -
- ); - })} - { - (Object.keys(RMC).length === 0) ? ( -
- -
- ) : !RMC[roleId] ? ( - No member found with this role - ) : RMC[roleId] === roleMembers.length ? ( - <> -
- All members loaded - - ) : rolesFetched.includes(roleId) ? ( - <> -
- All cached members loaded - - ) : ( -
- -
- ) - } - -
- ); -} - -function InRoleModal({ guildId, props, roleId }: { guildId: string; props: ModalProps; roleId: string; }) { - const roleObj = GuildStore.getRoles(guildId); - const roles = Object.keys(roleObj).map(key => roleObj[key]).sort((a, b) => b.position - a.position); - - const [selectedRole, selectRole] = useState(roles.find(x => x.id === roleId) || roles[0]); - - let cooldown; - useEffect(() => { - const timeout = setTimeout(() => cooldown = false, 1000); - return () => clearTimeout(timeout); - }, [selectedRole]); - - return ( - - - - View members with role - - - -
- - {roles.map((role, index) => { - - if (role.id === guildId) return; - - const roleIconSrc = role != null ? getRoleIconSrc(role) : undefined; - - return ( -
{ - if (selectedRole.id === roles[index].id) return; - cooldown && !rolesFetched2.includes(roles[index].id) ? showToast("To limit ratelimiting, please wait at least a second before switching roles.") - : (selectRole(roles[index]), cooldown = true); - }} - role="button" - tabIndex={0} - key={role.id} - > -
- - { - roleIconSrc != null && ( - - ) - - } - - {role?.name || "Unknown role"} - -
-
- ); - })} -
-
- -
- - - - ); -} - -export function showInRoleModal(guildId: string, roleId: string) { +export function showInRoleModal(members: GuildMember[], roleId: string, channelId: string) { openModal(props => - + <> + + + + Members of role { + Parser.parse(`<@&${roleId}>`, true, { channelId, viewingChannelId: channelId }) + } ({members.length}) + + + +
+ { + members.length !== 0 ? members.map(member => + <> + + {Parser.parse(`<@${member.userId}>`, true, { channelId, viewingChannelId: channelId })} + + + ) : Looks like no online cached members with that role were found. Try scrolling down on your member list to cache more users! + } +
+
+
+
+ ); } - diff --git a/icons.tsx b/icons.tsx deleted file mode 100644 index 26d432e..0000000 --- a/icons.tsx +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -export function MemberIcon() { - return ( - - - - ); -} diff --git a/index.tsx b/index.tsx index 7d858d5..37c0b37 100644 --- a/index.tsx +++ b/index.tsx @@ -7,29 +7,33 @@ import "./style.css"; import { ApplicationCommandInputType, ApplicationCommandOptionType, sendBotMessage } from "@api/Commands"; -import { findGroupChildrenByChildId } from "@api/ContextMenu"; import { getUserSettingLazy } from "@api/UserSettings"; +import { InfoIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { getCurrentChannel, getCurrentGuild } from "@utils/discord"; import definePlugin from "@utils/types"; import { Forms, GuildMemberStore, GuildStore, Menu, Parser } from "@webpack/common"; -import { Guild, GuildMember } from "discord-types/general"; +import { GuildMember } from "discord-types/general"; -import { MemberIcon } from "./icons"; import { showInRoleModal } from "./RoleMembersModal"; const DeveloperMode = getUserSettingLazy("appearance", "developerMode")!; +function getMembersInRole(roleId: string, guildId: string) { + const members = GuildMemberStore.getMembers(guildId); + const membersInRole: GuildMember[] = []; + members.forEach(member => { + if (member.roles.includes(roleId)) { + membersInRole.push(member); + } + }); + return membersInRole; +} + export default definePlugin({ name: "InRole", description: "Know who is in a role with the role context menu or /inrole command (read plugin info!)", - authors: [ - Devs.nin0dev, - { - name: "Ryfter", - id: 898619112350183445n, - }, - ], + authors: [Devs.nin0dev], dependencies: ["UserSettingsAPI"], start() { // DeveloperMode needs to be enabled for the context menu to be shown @@ -40,8 +44,9 @@ export default definePlugin({ <> {Parser.parse(":warning:")} Limitations If you don't have mod permissions on the server, and that server is large (over 100 members), the plugin may be limited in the following ways: - • Up to 100 members will be listed by default for each role. To get more, scroll down in the member list to cache more members. - • However, friends will always be shown. + • Offline members won't be listed + • Up to 100 members will be listed by default. To get more, scroll down in the member list to load more members. + • However, friends will always be shown regardless of their status. ); }, @@ -65,7 +70,7 @@ export default definePlugin({ return sendBotMessage(ctx.channel.id, { content: "Make sure that you are in a server." }); } const role = args[0].value; - showInRoleModal(ctx.guild.id, role); + showInRoleModal(getMembersInRole(role, ctx.guild.id), role, ctx.channel.id); } } ], @@ -85,21 +90,9 @@ export default definePlugin({ id="vc-view-inrole" label="View Members in Role" action={() => { - showInRoleModal(guild.id, role.id); + showInRoleModal(getMembersInRole(role.id, guild.id), role.id, channel.id); }} - icon={MemberIcon} - /> - ); - }, - "guild-header-popout"(children, { guild }: { guild: Guild, onClose(): void; }) { - if (!guild) return; - const group = findGroupChildrenByChildId("privacy", children); - group?.push( - showInRoleModal(guild.id, "0")} + icon={InfoIcon} /> ); } diff --git a/style.css b/style.css index 1eed2d3..b3184ea 100644 --- a/style.css +++ b/style.css @@ -1,139 +1,29 @@ -.vc-inrole-modal-content { - padding: 16px 4px 16px 16px; +.vc-inrole-member-list { + max-height: 400px; + margin-top: 10px; + margin-bottom: 13px; + overflow-x: hidden; } -.vc-inrole-modal-title { - flex-grow: 1; +.vc-inrole-member-list::-webkit-scrollbar { + background-color: #fff1; + border-radius: 100px; + width: 10px; } -.vc-inrole-modal-container { - width: 100%; - height: 100%; - display: flex; - gap: 8px; +.vc-inrole-member-list::-webkit-scrollbar-thumb { + background-color: #fff3; + border-radius: 100px; } -.vc-inrole-modal-list { - display: flex; - flex-direction: column; - gap: 2px; - padding-right: 8px; - max-width: 300px; - min-width: 300px; +.vc-inrole-modal-member { + margin: 11px 0; } -.vc-inrole-modal-list-item-btn { - cursor: pointer; +.vc-inrole-header { + padding-top: "15px"; } -.vc-inrole-modal-list-item { - display: flex; - align-items: center; - gap: 8px; - padding: 8px; - border-radius: 5px; -} - -.vc-inrole-modal-list-item:hover { - background-color: var(--background-modifier-hover); -} - -.vc-inrole-modal-list-item-active { - background-color: var(--background-modifier-selected); -} - -.vc-inrole-modal-list-item > div { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.vc-inrole-modal-role-circle { - border-radius: 50%; - width: 12px; - height: 12px; - flex-shrink: 0; -} - -.vc-inrole-modal-role-image { - width: 20px; - height: 20px; - object-fit: contain; -} - -.vc-inrole-modal-divider { - width: 2px; - background-color: var(--background-modifier-active); -} - -.vc-inrole-role-button { - border-radius: var(--radius-xs); - background: var(--bg-mod-faint); - color: var(--interactive-normal); - border: 1px solid var(--border-faint); - /* stylelint-disable-next-line value-no-vendor-prefix */ - width: -moz-fit-content; - width: fit-content; - height: 24px; - padding: 4px -} - -.custom-profile-theme .vc-inrole-role-button { - background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6)); - border-color: var(--profile-body-border-color) -} - -.vc-inrole-user-div{ - display: flex; - align-items: center; - gap: 0.2em; -} - -.vc-inrole-modal-members { - display: flex; - flex-direction: column; - width: 100%; -} - - -.vc-inrole-user-avatar { - border-radius: 50%; - padding: 5px; - width: 30px; - height: 30px; -} - -.vc-inrole-member-list-header { - background-color: var(--background-secondary); - padding: 5px; - border-radius: 5px; -} - -.vc-inrole-member-list-header-text { - display: flex; - align-items: center; - gap: 5px; -} - -.vc-inrole-member-list-header-text .vc-info-icon { - color: var(--interactive-muted); +.vc-inrole-close { margin-left: auto; - cursor: pointer; - transition: color ease-in 0.1s; -} - -.vc-inrole-member-list-header-text .vc-info-icon:hover { - color: var(--interactive-active); -} - -.vc-inrole-member-list-footer { - padding: 5px; - text-align: center; - font-style: italic; -} - -.vc-inrole-divider { - height: 2px; - width: 100%; - background-color: var(--background-modifier-active); } diff --git a/utils.ts b/utils.ts deleted file mode 100644 index b8c765d..0000000 --- a/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { classNameFactory } from "@api/Styles"; -import { findByPropsLazy } from "@webpack"; - -export const cl = classNameFactory("vc-inrole-"); -export const GuildUtils = findByPropsLazy("requestMembersById");