diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 24c68c4ed..7a041f1ee 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -44,6 +44,11 @@ export interface ProfileBadge { position?: BadgePosition; /** The badge name to display, Discord uses this. Required for component badges */ key?: string; + + /** + * Allows dynamically returning multiple badges + */ + getBadges?(userInfo: BadgeUserArgs): ProfileBadge[]; } const Badges = new Set(); @@ -73,9 +78,16 @@ export function _getBadges(args: BadgeUserArgs) { const badges = [] as ProfileBadge[]; for (const badge of Badges) { if (!badge.shouldShow || badge.shouldShow(args)) { + const b = badge.getBadges + ? badge.getBadges(args).map(b => { + b.component &&= ErrorBoundary.wrap(b.component, { noop: true }); + return b; + }) + : [{ ...badge, ...args }]; + badge.position === BadgePosition.START - ? badges.unshift({ ...badge, ...args }) - : badges.push({ ...badge, ...args }); + ? badges.unshift(...b) + : badges.push(...b); } } const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId); diff --git a/src/components/ExpandableHeader.tsx b/src/components/ExpandableHeader.tsx index 84b065862..473dffaa0 100644 --- a/src/components/ExpandableHeader.tsx +++ b/src/components/ExpandableHeader.tsx @@ -31,10 +31,20 @@ export interface ExpandableHeaderProps { headerText: string; children: React.ReactNode; buttons?: React.ReactNode[]; + forceOpen?: boolean; } -export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) { - const [showContent, setShowContent] = useState(defaultState); +export function ExpandableHeader({ + children, + onMoreClick, + buttons, + moreTooltipText, + onDropDownClick, + headerText, + defaultState = false, + forceOpen = false, +}: ExpandableHeaderProps) { + const [showContent, setShowContent] = useState(defaultState || forceOpen); return ( <> @@ -90,6 +100,7 @@ export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipTe setShowContent(v => !v); onDropDownClick?.(showContent); }} + disabled={forceOpen} > ); } + +export function SafetyIcon(props: IconProps) { + return ( + + + + + ); +} + +export function NotesIcon(props: IconProps) { + return ( + + + + + ); +} diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index cb153c6a9..94dc673a5 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -136,6 +136,8 @@ export default definePlugin({ }, getBadges(props: { userId: string; user?: User; guildId: string; }) { + if (!props) return []; + try { props.userId ??= props.user?.id!; diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 31fc71a9e..85aadca13 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -109,9 +109,9 @@ interface ProfileModalProps { } const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); -const ProfileModal = findComponentByCodeLazy('"ProfileCustomizationPreview"'); +const ProfileModal = findComponentByCodeLazy("isTryItOutFlow:", "pendingThemeColors:", "avatarDecorationOverride:", ".CUSTOM_STATUS"); -const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("?(.+?)"?\).then\(\i\.bind\(\i,"?(.+?)"?\)\)/); +const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/); export default definePlugin({ name: "FakeProfileThemes", diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 23a40ac34..0fbf41e93 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -49,7 +49,7 @@ export default definePlugin({ find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Note: the module is lazy-loaded replacement: { match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/, - replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),' + replace: '$self.isBotOrSelf(arguments[0].user)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),' } }, { @@ -58,9 +58,24 @@ export default definePlugin({ match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/, replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});" } + }, + { + find: ".MUTUAL_FRIENDS?(", + replacement: [ + { + match: /(?<=onItemSelect:\i,children:)(\i)\.map/, + replace: "[...$1, ...($self.isBotOrSelf(arguments[0].user) ? [] : [{section:'MUTUAL_GDMS',text:'Mutual Groups'}])].map" + }, + { + match: /\(0,\i\.jsx\)\(\i,\{items:\i,section:(\i)/, + replace: "$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):$&" + } + ] } ], + isBotOrSelf: (user: User) => user.bot || user.id === UserStore.getCurrentUser().id, + renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => { const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => ( . */ +import { + findGroupChildrenByChildId, + NavContextMenuPatchCallback +} from "@api/ContextMenu"; import { definePluginSettings, migratePluginSettings } from "@api/Settings"; +import { CogWheel } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; +import { Menu } from "@webpack/common"; +import { Guild } from "discord-types/general"; const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", { @@ -73,48 +80,68 @@ const settings = definePluginSettings({ } }); +const makeContextMenuPatch: (shouldAddIcon: boolean) => NavContextMenuPatchCallback = (shouldAddIcon: boolean) => (children, { guild }: { guild: Guild, onClose(): void; }) => { + if (!guild) return; + + const group = findGroupChildrenByChildId("privacy", children); + group?.push( + applyDefaultSettings(guild.id)} + /> + ); +}; + +function applyDefaultSettings(guildId: string | null) { + if (guildId === "@me" || guildId === "null" || guildId == null) return; + updateGuildNotificationSettings(guildId, + { + muted: settings.store.guild, + suppress_everyone: settings.store.everyone, + suppress_roles: settings.store.role, + mute_scheduled_events: settings.store.events, + notify_highlights: settings.store.highlights ? 1 : 0 + }); + if (settings.store.messages !== 3) { + updateGuildNotificationSettings(guildId, + { + message_notifications: settings.store.messages, + }); + } + if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) { + toggleShowAllChannels(guildId); + } +} + + migratePluginSettings("NewGuildSettings", "MuteNewGuild"); export default definePlugin({ name: "NewGuildSettings", description: "Automatically mute new servers and change various other settings upon joining", tags: ["MuteNewGuild", "mute", "server"], authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi, Devs.GabiRP], + contextMenus: { + "guild-context": makeContextMenuPatch(false), + "guild-header-popout": makeContextMenuPatch(true) + }, patches: [ { find: ",acceptInvite(", replacement: { match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/, - replace: (m, guildId) => `${m}$self.handleMute(${guildId});` + replace: (m, guildId) => `${m}$self.applyDefaultSettings(${guildId});` } }, { find: "{joinGuild:", replacement: { match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/, - replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});` + replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.applyDefaultSettings(${guildId});` } } ], settings, - - handleMute(guildId: string | null) { - if (guildId === "@me" || guildId === "null" || guildId == null) return; - updateGuildNotificationSettings(guildId, - { - muted: settings.store.guild, - suppress_everyone: settings.store.everyone, - suppress_roles: settings.store.role, - mute_scheduled_events: settings.store.events, - notify_highlights: settings.store.highlights ? 1 : 0 - }); - if (settings.store.messages !== 3) { - updateGuildNotificationSettings(guildId, - { - message_notifications: settings.store.messages, - }); - } - if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) { - toggleShowAllChannels(guildId); - } - } + applyDefaultSettings }); diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index 869a6a1ee..49770bbe1 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -43,7 +43,7 @@ const Classes = proxyLazyWebpack(() => )) ) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>; -function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; }) { +function UserPermissionsComponent({ guild, guildMember, showBorder, forceOpen = false }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; forceOpen?: boolean; }) { const stns = settings.use(["permissionsSortOrder"]); const [rolePermissions, userPermissions] = useMemo(() => { @@ -95,6 +95,7 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G return ( diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index b27a3c2f9..6401d9450 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -20,15 +20,22 @@ import "./styles.css"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { SafetyIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; +import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common"; +import { findByPropsLazy } from "@webpack"; +import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common"; import type { Guild, GuildMember } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; import UserPermissions from "./components/UserPermissions"; import { getSortedRoles, sortPermissionOverwrites } from "./utils"; +const PopoutClasses = findByPropsLazy("container", "scroller", "list"); +const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text"); + export const enum PermissionsSortOrder { HighestRole, LowestRole @@ -168,10 +175,45 @@ export default definePlugin({ match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/, replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),` } + }, + { + find: ".VIEW_ALL_ROLES,", + replacement: { + match: /children:"\+"\.concat\(\i\.length-\i\.length\).{0,20}\}\),/, + replace: "$&$self.ViewPermissionsButton(arguments[0])," + } } ], - UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && , + UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBorder: boolean) => + !!guildMember && , + + ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => ( + ( + + + + )} + > + {popoutProps => ( + + + + )} + + ), { noop: true }), contextMenus: { "user-context": makeContextMenuPatch("roles", MenuItemParentType.User), diff --git a/src/plugins/permissionsViewer/styles.css b/src/plugins/permissionsViewer/styles.css index 1c60098f3..0ef961e5a 100644 --- a/src/plugins/permissionsViewer/styles.css +++ b/src/plugins/permissionsViewer/styles.css @@ -149,3 +149,21 @@ .vc-permviewer-perms-perms-item .vc-info-icon:hover { color: var(--interactive-active); } + +/* copy pasted from discord cause impossible to webpack find */ +.vc-permviewer-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-permviewer-role-button { + background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6)); + border-color: var(--profile-body-border-color) +} diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index eef74d65e..1dc76e9d3 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges"; +import "./style.css"; + +import { addBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeBadge } from "@api/Badges"; import { addDecorator, removeDecorator } from "@api/MemberListDecorators"; import { addDecoration, removeDecoration } from "@api/MessageDecorations"; import { Settings } from "@api/Settings"; @@ -27,7 +29,20 @@ import { findByPropsLazy, findStoreLazy } from "@webpack"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; import { User } from "discord-types/general"; -const SessionsStore = findStoreLazy("SessionsStore"); +export interface Session { + sessionId: string; + status: string; + active: boolean; + clientInfo: { + version: number; + os: string; + client: string; + }; +} + +const SessionsStore = findStoreLazy("SessionsStore") as { + getSessions(): Record; +}; function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) { return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => ( @@ -67,15 +82,11 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: return ; }; -const getStatus = (id: string): Record => PresenceStore.getState()?.clientStatuses?.[id]; - -const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => { - if (!user || user.bot) return null; - +function ensureOwnStatus(user: User) { if (user.id === UserStore.getCurrentUser().id) { const sessions = SessionsStore.getSessions(); if (typeof sessions !== "object") return null; - const sortedSessions = Object.values(sessions).sort(({ status: a }: any, { status: b }: any) => { + const sortedSessions = Object.values(sessions).sort(({ status: a }, { status: b }) => { if (a === b) return 0; if (a === "online") return 1; if (b === "online") return -1; @@ -84,7 +95,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma return 0; }); - const ownStatus = Object.values(sortedSessions).reduce((acc: any, curr: any) => { + const ownStatus = Object.values(sortedSessions).reduce((acc, curr) => { if (curr.clientInfo.client !== "unknown") acc[curr.clientInfo.client] = curr.status; return acc; @@ -93,6 +104,37 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma const { clientStatuses } = PresenceStore.getState(); clientStatuses[UserStore.getCurrentUser().id] = ownStatus; } +} + +function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] { + const user = UserStore.getUser(userId); + + if (!user || user.bot) return []; + + ensureOwnStatus(user); + + const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record; + if (!status) return []; + + return Object.entries(status).map(([platform, status]) => ({ + component: () => ( + + + + ), + key: `vc-platform-indicator-${platform}` + })); +} + +const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => { + if (!user || user.bot) return null; + + ensureOwnStatus(user); const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record; if (!status) return null; @@ -112,17 +154,10 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma {icons} @@ -130,10 +165,8 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma }; const badge: ProfileBadge = { - component: p => , + getBadges, position: BadgePosition.START, - shouldShow: userInfo => !!Object.keys(getStatus(userInfo.userId) ?? {}).length, - key: "indicator" }; const indicatorLocations = { diff --git a/src/plugins/platformIndicators/style.css b/src/plugins/platformIndicators/style.css new file mode 100644 index 000000000..38ea5ef4b --- /dev/null +++ b/src/plugins/platformIndicators/style.css @@ -0,0 +1,7 @@ +.vc-platform-indicator { + display: inline-flex; + justify-content: center; + align-items: center; + vertical-align: top; + position: relative; +} diff --git a/src/plugins/replaceGoogleSearch/index.tsx b/src/plugins/replaceGoogleSearch/index.tsx index 9882809f7..43b0762a1 100644 --- a/src/plugins/replaceGoogleSearch/index.tsx +++ b/src/plugins/replaceGoogleSearch/index.tsx @@ -13,13 +13,12 @@ import { Flex, Menu } from "@webpack/common"; const DefaultEngines = { Google: "https://www.google.com/search?q=", DuckDuckGo: "https://duckduckgo.com/", + Brave: "https://search.brave.com/search?q=", Bing: "https://www.bing.com/search?q=", Yahoo: "https://search.yahoo.com/search?p=", - GitHub: "https://github.com/search?q=", - Kagi: "https://kagi.com/search?q=", Yandex: "https://yandex.com/search/?text=", - AOL: "https://search.aol.com/aol/search?q=", - Baidu: "https://www.baidu.com/s?wd=", + GitHub: "https://github.com/search?q=", + Reddit: "https://www.reddit.com/search?q=", Wikipedia: "https://wikipedia.org/w/index.php?search=", } as const; @@ -55,7 +54,7 @@ function makeSearchItem(src: string) { key="search-text" id="vc-search-text" > - {Object.keys(Engines).map((engine, i) => { + {Object.keys(Engines).map(engine => { const key = "vc-search-content-" + engine; return (