From 81a4ea94c9f359705423539c19792fe6d566b689 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 31 Oct 2025 15:39:03 -0400 Subject: [PATCH] Boo: Add Plugin --- src/equicordplugins/boo/Boo.tsx | 60 +++++++++++++++++++++++ src/equicordplugins/boo/IconGhost.tsx | 19 ++++++++ src/equicordplugins/boo/index.tsx | 68 +++++++++++++++++++++++++++ src/equicordplugins/boo/styles.css | 16 +++++++ src/utils/constants.ts | 4 ++ 5 files changed, 167 insertions(+) create mode 100644 src/equicordplugins/boo/Boo.tsx create mode 100644 src/equicordplugins/boo/IconGhost.tsx create mode 100644 src/equicordplugins/boo/index.tsx create mode 100644 src/equicordplugins/boo/styles.css diff --git a/src/equicordplugins/boo/Boo.tsx b/src/equicordplugins/boo/Boo.tsx new file mode 100644 index 000000000..daca2fd5a --- /dev/null +++ b/src/equicordplugins/boo/Boo.tsx @@ -0,0 +1,60 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Channel, Message } from "@vencord/discord-types"; +import { findByPropsLazy } from "@webpack"; +import { MessageStore, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; + +import { cl } from "."; +import { IconGhost } from "./IconGhost"; + +const countedChannels = new Set(); +export let booCount = 0; + +const ChannelWrapperStyles = findByPropsLazy("muted", "wrapper"); + +export function Boo({ channel }: { channel: Channel; }) { + const { id } = channel; + + const currentUserId = useStateFromStores([UserStore], () => UserStore.getCurrentUser()?.id); + const lastMessage: Message = useStateFromStores([MessageStore], () => + MessageStore.getMessages(id)?.last() + ); + + const [state, setState] = useState({ + isCurrentUser: null as boolean | null, + containsQuestionMark: false, + isDataProcessed: false, + }); + + useEffect(() => { + if (!lastMessage || !currentUserId) return; + + const lastIsCurrentUser = lastMessage.author.id === currentUserId; + const containsQuestionMark = !lastIsCurrentUser && lastMessage.content.includes("?"); + + setState({ + isCurrentUser: lastIsCurrentUser, + containsQuestionMark, + isDataProcessed: true, + }); + }, [lastMessage, currentUserId]); + + if (!state.isDataProcessed || !currentUserId || !lastMessage || state.isCurrentUser) return null; + + if (!countedChannels.has(id)) { + countedChannels.add(id); + booCount++; + } + + return ( +
+ {state.containsQuestionMark + ? + : } +
+ ); +} diff --git a/src/equicordplugins/boo/IconGhost.tsx b/src/equicordplugins/boo/IconGhost.tsx new file mode 100644 index 000000000..91da280ac --- /dev/null +++ b/src/equicordplugins/boo/IconGhost.tsx @@ -0,0 +1,19 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export function IconGhost({ fill }) { + return ( + + + + ); +} + diff --git a/src/equicordplugins/boo/index.tsx b/src/equicordplugins/boo/index.tsx new file mode 100644 index 000000000..5289c311c --- /dev/null +++ b/src/equicordplugins/boo/index.tsx @@ -0,0 +1,68 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList"; +import { classNameFactory } from "@api/Styles"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs, EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { Channel } from "@vencord/discord-types"; +import { Tooltip } from "@webpack/common"; + +import { Boo, booCount } from "./Boo"; +import { IconGhost } from "./IconGhost"; + +export const cl = classNameFactory("vc-boo-"); + +export default definePlugin({ + name: "Boo", + description: "A cute ghost will appear if you don't answer their DMs", + authors: [EquicordDevs.vei, Devs.sadan], + patches: [ + { + find: "interactiveSelected]", + replacement: { + match: /interactiveSelected.{0,50}children:\[/, + replace: "$&$self.renderBoo(arguments[0])," + } + } + ], + + renderBoo(props: { channel: Channel; }) { + return ( + + + + ); + }, + + renderIndicator() { + return +
+ + {({ onMouseEnter, onMouseLeave }) => ( +
+ {booCount} +
+ )} +
+
+
; + }, + + start() { + addServerListElement(ServerListRenderPosition.Above, this.renderIndicator); + }, + + stop() { + removeServerListElement(ServerListRenderPosition.Above, this.renderIndicator); + } +}); diff --git a/src/equicordplugins/boo/styles.css b/src/equicordplugins/boo/styles.css new file mode 100644 index 000000000..7d278a3cf --- /dev/null +++ b/src/equicordplugins/boo/styles.css @@ -0,0 +1,16 @@ +.vc-boo-icon { + height: 42px; +} + +#vc-boo-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + background-color: var(--background-primary); + height: 30px; + width: 48px; + margin-left: 8px; + color: var(--text-default); + fill: var(--text-default); +} \ No newline at end of file diff --git a/src/utils/constants.ts b/src/utils/constants.ts index d52c83bc3..ea0f9a17a 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1178,6 +1178,10 @@ export const EquicordDevs = Object.freeze({ name: "mja00", id: 108698087769260032n }, + vei: { + name: "Vei", + id: 239414094799699968n + }, } satisfies Record); // iife so #__PURE__ works correctly