From 6f19368b707478dff7e166f73340e2b62cb73720 Mon Sep 17 00:00:00 2001
From: thororen1234 <78185467+thororen1234@users.noreply.github.com>
Date: Wed, 15 Jul 2026 22:31:22 -0400
Subject: [PATCH] SidebarChat Returns for nowTM
---
.github/workflows/reportBrokenPlugins.yml | 11 +-
src/equicordplugins/sidebarChat/index.tsx | 583 +++++++++++++++++++++
src/equicordplugins/sidebarChat/store.ts | 149 ++++++
src/equicordplugins/sidebarChat/styles.css | 19 +
4 files changed, 761 insertions(+), 1 deletion(-)
create mode 100644 src/equicordplugins/sidebarChat/index.tsx
create mode 100644 src/equicordplugins/sidebarChat/store.ts
create mode 100644 src/equicordplugins/sidebarChat/styles.css
diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml
index 3ff938350..13af9dcea 100644
--- a/.github/workflows/reportBrokenPlugins.yml
+++ b/.github/workflows/reportBrokenPlugins.yml
@@ -17,6 +17,14 @@ on:
visible for everyone, so DO NOT pass sensitive webhooks like
discord webhook. This is meant to be used by Equibot."
required: false
+ pr_repo:
+ type: string
+ description: "Fork repo to checkout when testing a PR"
+ required: false
+ pr_branch:
+ type: string
+ description: "Branch to checkout when testing a PR"
+ required: false
schedule:
# # Every day at midnight
- cron: 0 0 * * *
@@ -32,7 +40,8 @@ jobs:
steps:
- uses: actions/checkout@v7
with:
- ref: ${{ github.event_name == 'schedule' && 'dev' || github.ref }}
+ repository: ${{ inputs.pr_repo || github.repository }}
+ ref: ${{ inputs.pr_branch || (github.event_name == 'schedule' && 'dev' || github.ref) }}
- uses: pnpm/action-setup@v6
diff --git a/src/equicordplugins/sidebarChat/index.tsx b/src/equicordplugins/sidebarChat/index.tsx
new file mode 100644
index 000000000..69c5e51a4
--- /dev/null
+++ b/src/equicordplugins/sidebarChat/index.tsx
@@ -0,0 +1,583 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import "./styles.css";
+
+import { NavContextMenuPatchCallback } from "@api/ContextMenu";
+import { HeaderBarButton } from "@api/HeaderBar";
+import ErrorBoundary from "@components/ErrorBoundary";
+import { Devs, EquicordDevs } from "@utils/constants";
+import { classNameFactory } from "@utils/css";
+import { getCurrentChannel } from "@utils/discord";
+import definePlugin from "@utils/types";
+import { Channel, Guild, User } from "@vencord/discord-types";
+import { ChannelType } from "@vencord/discord-types/enums";
+import {
+ extractAndLoadChunksLazy,
+ findByPropsLazy,
+ findComponentByCodeLazy,
+ findCssClassesLazy,
+ findStoreLazy,
+} from "@webpack";
+import {
+ ChannelActionCreators,
+ ChannelRouter,
+ ChannelStore,
+ FluxDispatcher,
+ GuildStore,
+ Menu,
+ MessageActions,
+ MessageStore,
+ PermissionsBits,
+ PermissionStore,
+ PopoutActions,
+ PopoutWindowStore,
+ RelationshipStore,
+ SelectedChannelStore,
+ SelectedGuildStore,
+ useCallback,
+ useEffect,
+ useLayoutEffect,
+ UserStore,
+ useState,
+ useStateFromStores,
+} from "@webpack/common";
+
+import { getOpenPopoutWindowKeys, getPersistedPopoutChannelIds, getPopoutWindowKey, isPopoutWindowOpen, settings, SidebarStore, syncPersistedPopoutWindows } from "./store";
+
+const cl = classNameFactory("vc-sidebar-chat-");
+
+const HeaderBar = findComponentByCodeLazy("toolbarClassName:", "}),onDoubleClick:");
+const ForumView = findComponentByCodeLazy("sidebarState");
+
+const ArrowsLeftRightIcon = ({ color, ...rest }) => {
+ return (
+
+ );
+};
+
+const WindowLaunchIcon = findComponentByCodeLazy("1-1h6a1 1 0 1 0 0-2H5Z");
+const XSmallIcon = findComponentByCodeLazy("1.4L12 13.42l5.3 5.3Z");
+const Chat = findComponentByCodeLazy("filterAfterTimestamp:", "chatInputType");
+const Resize = findComponentByCodeLazy("sidebarType:", "RESIZE_HANDLE_WIDTH)");
+const ChannelHeader = findComponentByCodeLazy("`channel-${");
+const PopoutWindow = findComponentByCodeLazy("Missing guestWindow reference");
+const FullChannelView = findComponentByCodeLazy("showFollowButton:");
+const WanderingCubesLoading = findComponentByCodeLazy('="wanderingCubes"');
+
+const ChatInputTypes = findByPropsLazy("FORM", "NORMAL");
+const Sidebars = findByPropsLazy("ThreadSidebar", "MessageRequestSidebar");
+const ChatClasses = findCssClassesLazy("threadSidebarOpen", "loader");
+
+const ChannelSectionStore = findStoreLazy("ChannelSectionStore");
+
+const requireForumView = extractAndLoadChunksLazy(
+ ["Missing channel in Channel.renderHeaderToolbar"],
+ /Promise\.all\(\[((?:\i\.e\("\d+"\),?)+)\]\)\.then\(\i\.bind\(\i,(\d+)\)\)[^}]{0,100}?name:"ForumChannel"/
+);
+
+function getChannelTitle(channel: Channel | null | undefined) {
+ if (!channel) return "Chat";
+
+ if (channel.isPrivate()) {
+ const recipientId = channel.getRecipientId?.();
+ if (!channel.name && recipientId) {
+ const user = UserStore.getUser(recipientId);
+ if (user) {
+ return RelationshipStore.getNickname(recipientId) || user.globalName || user.username || "DM";
+ }
+ }
+
+ return channel.name || "DM";
+ }
+
+ return channel.name || "Chat";
+}
+
+function canOpenPopout(channel: Channel) {
+ if (channel.isPrivate()) return true;
+ return !channel.isCategory() && !channel.isDirectory() && !channel.isVocal();
+}
+
+function getPopoutMenuLabel(channelId: string) {
+ return isPopoutWindowOpen(channelId) ? "Close popout chat" : "Popout chat";
+}
+
+let restorePersistedPopoutsInterval: number | null = null;
+let restoringPersistedPopouts = false;
+
+function clearPersistedPopoutRestoreLoop() {
+ restoringPersistedPopouts = false;
+ if (restorePersistedPopoutsInterval !== null) {
+ window.clearInterval(restorePersistedPopoutsInterval);
+ restorePersistedPopoutsInterval = null;
+ }
+}
+
+async function waitForChannel(channelId: string, timeoutMs = 2500) {
+ const startedAt = Date.now();
+ while (Date.now() - startedAt <= timeoutMs) {
+ const channel = ChannelStore.getChannel(channelId);
+ if (channel) return channel;
+
+ await new Promise(resolve => setTimeout(resolve, 80));
+ }
+
+ return null;
+}
+
+async function waitForDmChannel(userId: string, timeoutMs = 2500) {
+ const startedAt = Date.now();
+ while (Date.now() - startedAt <= timeoutMs) {
+ const channelId = ChannelStore.getDMFromUserId?.(userId);
+ if (channelId) return channelId;
+
+ await new Promise(resolve => setTimeout(resolve, 80));
+ }
+
+ return null;
+}
+
+async function openPopoutFromUserMenu(userId: string) {
+ try {
+ const channelId = await Promise.resolve(ChannelActionCreators.getOrEnsurePrivateChannel(userId));
+ if (!channelId) return;
+
+ const channel = await waitForChannel(channelId);
+ if (channel) openPopout(channel.id);
+ return;
+ } catch {
+ const fallbackChannelId = await waitForDmChannel(userId);
+ if (!fallbackChannelId) return;
+
+ const channel = await waitForChannel(fallbackChannelId);
+ if (channel) openPopout(channel.id);
+ }
+}
+
+function closePopout(channelId: string, syncPersistence = true) {
+ const windowKey = getPopoutWindowKey(channelId);
+ PopoutActions.close(windowKey);
+ if (syncPersistence && !restoringPersistedPopouts) {
+ syncPersistedPopoutWindows();
+ }
+}
+
+function openPopout(channelId: string, syncPersistence = true) {
+ const channel = ChannelStore.getChannel(channelId);
+ if (!channel || !canOpenPopout(channel)) return;
+
+ const windowKey = getPopoutWindowKey(channelId);
+
+ if (isPopoutWindowOpen(channelId)) {
+ closePopout(channelId, syncPersistence);
+ return;
+ }
+
+ const title = getChannelTitle(channel);
+
+ PopoutActions.open(
+ windowKey,
+ () =>