diff --git a/README.md b/README.md index 68a542ee..9f64e444 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch ### Extra included plugins
-Extra included plugins (123 additional plugins) +Extra included plugins (124 additional plugins) - AllCallTimers by MaxHerbold and D3SOX - AltKrispSwitch by newwares @@ -59,6 +59,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - Grammar by Samwich - GrammarFix by unstream - HideMessage by Hanzy +- HideServers by bepvte - HolyNotes by Wolfie - HomeTyping by Samwich - HopOn by ImLvna diff --git a/src/api/ServerList.ts b/src/api/ServerList.ts index 75016e89..eedab468 100644 --- a/src/api/ServerList.ts +++ b/src/api/ServerList.ts @@ -23,13 +23,17 @@ const logger = new Logger("ServerListAPI"); export const enum ServerListRenderPosition { Above, In, + Below, } -const renderFunctionsAbove = new Set(); -const renderFunctionsIn = new Set(); +const renderFunctions = { + [ServerListRenderPosition.Above]: new Set(), + [ServerListRenderPosition.In]: new Set(), + [ServerListRenderPosition.Below]: new Set(), +}; function getRenderFunctions(position: ServerListRenderPosition) { - return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn; + return renderFunctions[position]; } export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) { diff --git a/src/equicordplugins/hideServers/HiddenServersStore.ts b/src/equicordplugins/hideServers/HiddenServersStore.ts new file mode 100644 index 00000000..bdad64fd --- /dev/null +++ b/src/equicordplugins/hideServers/HiddenServersStore.ts @@ -0,0 +1,58 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import * as DataStore from "@api/DataStore"; +import { findStoreLazy, proxyLazyWebpack } from "@webpack"; +import { Flux, FluxDispatcher, GuildStore } from "@webpack/common"; +import { Guild } from "discord-types/general"; + + +export const HiddenServersStore = proxyLazyWebpack(() => { + const { Store } = Flux; + + const SortedGuildStore = findStoreLazy("SortedGuildStore"); + const DB_KEY = "HideServers_servers"; + + class HiddenServersStore extends Store { + private _hiddenGuilds: Set = new Set(); + public get hiddenGuilds() { + return this._hiddenGuilds; + } + // id try to use .initialize() but i dont know how it works + public async load() { + const data = await DataStore.get(DB_KEY); + if (data) { + this._hiddenGuilds = data; + } + } + public unload() { + this._hiddenGuilds.clear(); + } + + public addHidden(guild: Guild) { + this._hiddenGuilds.add(guild.id); + DataStore.set(DB_KEY, this._hiddenGuilds); + this.emitChange(); + } + public removeHidden(id: string) { + this._hiddenGuilds.delete(id); + DataStore.set(DB_KEY, this._hiddenGuilds); + this.emitChange(); + } + public clearHidden() { + this._hiddenGuilds.clear(); + DataStore.del(DB_KEY); + this.emitChange(); + } + public hiddenGuildsDetail(): Guild[] { + const sortedGuildIds = SortedGuildStore.getFlattenedGuildIds() as string[]; + // otherwise the list is in order of increasing id number which is confusing + return sortedGuildIds.filter(id => this._hiddenGuilds.has(id)).map(id => GuildStore.getGuild(id)); + } + } + + return new HiddenServersStore(FluxDispatcher); +}); diff --git a/src/equicordplugins/hideServers/components/HiddenServersButton.tsx b/src/equicordplugins/hideServers/components/HiddenServersButton.tsx new file mode 100644 index 00000000..12b5770f --- /dev/null +++ b/src/equicordplugins/hideServers/components/HiddenServersButton.tsx @@ -0,0 +1,36 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./style.css"; + +import { classNameFactory } from "@api/Styles"; +import { Button, ButtonLooks, GuildStore, useStateFromStores } from "@webpack/common"; +import { HiddenServersStore } from "../HiddenServersStore"; +import { openHiddenServersModal } from "./HiddenServersMenu"; + +const cl = classNameFactory("vc-hideservers-"); + +function HiddenServersButton() { + const hiddenGuilds = useStateFromStores([HiddenServersStore], () => HiddenServersStore.hiddenGuilds, undefined, (old, newer) => old.size === newer.size); + // if youve left a server dont show it in the count + const actuallyHidden = Array.from(hiddenGuilds).filter(x => GuildStore.getGuild(x)).length; + return ( +
+ {actuallyHidden > 0 ? ( + + ) : null} +
+ ); +} + +export default () => { return ; }; diff --git a/src/equicordplugins/hideServers/components/HiddenServersMenu.tsx b/src/equicordplugins/hideServers/components/HiddenServersMenu.tsx new file mode 100644 index 00000000..e2b89aff --- /dev/null +++ b/src/equicordplugins/hideServers/components/HiddenServersMenu.tsx @@ -0,0 +1,105 @@ +/* + * 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 { classes } from "@utils/misc"; +import { + closeModal, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalProps, + ModalRoot, + ModalSize, + openModal, +} from "@utils/modal"; +import { findByPropsLazy } from "@webpack"; +import { Button, Forms, IconUtils, Text, useState, useStateFromStores } from "@webpack/common"; +import { Guild } from "discord-types/general"; + +import { HiddenServersStore } from "../HiddenServersStore"; + +const cl = classNameFactory("vc-hideservers-"); +const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); + +function HiddenServersModal({ + modalProps, + close, +}: { + modalProps: ModalProps; + close(): void; +}) { + const servers = useStateFromStores([HiddenServersStore], () => HiddenServersStore.hiddenGuildsDetail()); + return ( + + + + Hidden Servers + + + + + + + + + ); +} + +export function HiddenServersMenu({ servers }: { servers: Guild[]; }) { + return
+ {servers.length > 0 ? ( + servers.map(server => ( +
+
+ {server.icon + ? + :
+ {server.acronym} +
+ } +
+ + {server.name} + + +
+ )) + ) : ( + + No hidden servers + + )} +
; +}; + +export function openHiddenServersModal() { + const key = openModal(modalProps => { + return ( + closeModal(key)} + /> + ); + }); +} diff --git a/src/equicordplugins/hideServers/components/style.css b/src/equicordplugins/hideServers/components/style.css new file mode 100644 index 00000000..ee553841 --- /dev/null +++ b/src/equicordplugins/hideServers/components/style.css @@ -0,0 +1,47 @@ +.vc-hideservers-button-wrapper { + display: flex; + justify-content: center; + margin: 0 0 8px; +} + +.vc-hideservers-button { + max-width: 48px; + font-size: 60%; + background-color: var(--background-primary); + color: var(--header-secondary); +} + +.vc-hideservers-list { + flex: 1 1 auto; +} + +/* copied from blocked row */ +.vc-hideservers-row { + height: 62px; + display: flex; + align-items: center; + flex-direction: row; + margin-left: 20px; + margin-right: 20px; + border-top: 1px solid var(--background-modifier-accent); + border-bottom: 1px solid transparent; + justify-content: space-between; +} + +.vc-hideservers-guildicon { + display: flex; + align-items: center; + margin-right: 1em; +} + +.vc-hideservers-guildicon img { + border-radius: 50%; +} + +.vc-hideservers-name { + flex-grow: 1; +} + +.vc-hideservers-row-button { + margin-left: auto; +} diff --git a/src/equicordplugins/hideServers/index.tsx b/src/equicordplugins/hideServers/index.tsx new file mode 100644 index 00000000..75c3d162 --- /dev/null +++ b/src/equicordplugins/hideServers/index.tsx @@ -0,0 +1,141 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +// additional thanks to mwittrien/DevilBro and nexpid for their server hiding plugins, which served as inspiration + +import { + findGroupChildrenByChildId, + NavContextMenuPatchCallback, +} from "@api/ContextMenu"; +import { + addServerListElement, + removeServerListElement, + ServerListRenderPosition, +} from "@api/ServerList"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { Menu, React, useStateFromStores } from "@webpack/common"; +import { Guild } from "discord-types/general"; + +import hiddenServersButton from "./components/HiddenServersButton"; +import { HiddenServersStore } from "./HiddenServersStore"; +import settings from "./settings"; + +type guildsNode = { + type: "guild" | "folder"; + id: number | string; + children: guildsNode[]; +}; + +type qsResult = { + type: "GUILD" | string; + record?: { + id?: string; + guild_id?: string; + }; +}; + +const Patch: NavContextMenuPatchCallback = ( + children, + { guild }: { guild: Guild; } +) => { + const group = findGroupChildrenByChildId("privacy", children); + + group?.push( + HiddenServersStore.addHidden(guild)} + /> + ); +}; + +export function addIndicator() { + addServerListElement(ServerListRenderPosition.Below, hiddenServersButton); +} + +export function removeIndicator() { + removeServerListElement(ServerListRenderPosition.Below, hiddenServersButton); +} + +export default definePlugin({ + name: "HideServers", + description: "Allows you to hide servers from the guild list and quick switcher by right clicking them", + authors: [EquicordDevs.bep], + tags: ["guild", "server", "hide"], + + dependencies: ["ServerListAPI"], + contextMenus: { + "guild-context": Patch, + "guild-header-popout": Patch, + }, + patches: [ + { + find: '("guildsnav")', + replacement: [ + { + match: /(?<=Messages\.SERVERS,children:.{0,300}?)(\i)(\)?\.map\(\i\))/, + replace: "$self.useFilteredGuilds($1)$2", + }, + // despite my best efforts, the above doesnt trigger a rerender + { + match: /let{disableAppDownload.{0,10}isPlatformEmbedded/, + replace: "$self.useStore();$&", + } + ] + }, + { + find: "QUICKSWITCHER_PROTIP.format", + replacement: { + match: /(?<=renderResults\(\){)let{query/, + replace: "this.props.results = $self.flteredGuildResults(this.props.results);$&", + }, + }, + ], + settings, + useStore: () => { useStateFromStores([HiddenServersStore], () => HiddenServersStore.hiddenGuilds, undefined, (old, newer) => old.size === newer.size); }, + + async start() { + if (settings.store.showIndicator) { + addIndicator(); + } + await HiddenServersStore.load(); + }, + + async stop() { + removeIndicator(); + HiddenServersStore.unload(); + }, + + useFilteredGuilds(guilds: guildsNode[]): guildsNode[] { + const hiddenGuilds = useStateFromStores([HiddenServersStore], () => HiddenServersStore.hiddenGuilds, undefined, (old, newer) => old.size === newer.size); + return guilds.flatMap(guild => { + if (hiddenGuilds.has(guild.id.toString())) { + return []; + } + const newGuild = Object.assign({}, guild); + newGuild.children = guild.children.filter( + child => !hiddenGuilds.has(child.id.toString()) + ); + + return [newGuild]; + }); + }, + + filteredGuildResults(results: qsResult[]): qsResult[] { + // not used in a component so no useStateFromStore + const { hiddenGuilds } = HiddenServersStore; + return results.filter(result => { + if (result?.record?.guild_id && hiddenGuilds.has(result.record.guild_id)) { + return false; + } + if (result.type === "GUILD" && hiddenGuilds.has(result.record!.id!)) { + return false; + } + return true; + }); + }, +}); diff --git a/src/equicordplugins/hideServers/settings.tsx b/src/equicordplugins/hideServers/settings.tsx new file mode 100644 index 00000000..1f684824 --- /dev/null +++ b/src/equicordplugins/hideServers/settings.tsx @@ -0,0 +1,51 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { OptionType } from "@utils/types"; +import { Button, useStateFromStores } from "@webpack/common"; + +import { addIndicator, removeIndicator } from "."; +import { HiddenServersMenu } from "./components/HiddenServersMenu"; +import { HiddenServersStore } from "./HiddenServersStore"; + +export default definePluginSettings({ + showIndicator: { + type: OptionType.BOOLEAN, + description: "Show menu to unhide servers at the bottom of the list", + default: true, + onChange: val => { + if (val) { + addIndicator(); + } else { + removeIndicator(); + } + } + }, + guildsList: { + type: OptionType.COMPONENT, + description: "Remove hidden servers", + component: () => { + const detail = useStateFromStores([HiddenServersStore], () => HiddenServersStore.hiddenGuildsDetail()); + return ; + } + }, + resetHidden: { + type: OptionType.COMPONENT, + description: "Remove all hidden guilds from the list", + component: () => ( +
+ +
+ ), + }, +}); diff --git a/src/plugins/_api/serverList.ts b/src/plugins/_api/serverList.ts index 7904e78b..193f0588 100644 --- a/src/plugins/_api/serverList.ts +++ b/src/plugins/_api/serverList.ts @@ -33,10 +33,16 @@ export default definePlugin({ }, { find: "Messages.SERVERS,children", - replacement: { - match: /(?<=Messages\.SERVERS,children:)\i\.map\(\i\)/, - replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)" - } + replacement: [ + { + match: /(?<=Messages\.SERVERS,children:)\i\.map\(\i\)/, + replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)" + }, + { + match: /guildDiscoveryRef.{0,300}\{\}\)\]\}\)\]/, + replace: "$&.concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Below))" + } + ] } ] }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 4f9e8568..5df5b8ee 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -786,6 +786,10 @@ export const EquicordDevs = Object.freeze({ SpikeHD: { name: "SpikeHD", id: 221757857836564485n + }, + bep: { + name: "bep", + id: 0n, } } satisfies Record);