Plugin

VoiceChatDoubleClick

Join voice chats via double click instead of single click

Voice
index.ts
Download

Source

src/plugins/vcDoubleClick/index.ts
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import { Devs } from "@utils/constants";
8import definePlugin from "@utils/types";
9import { ChannelStore, SelectedChannelStore } from "@webpack/common";
10
11const timers = {} as Record<string, {
12 timeout?: NodeJS.Timeout;
13 i: number;
14}>;
15
16export default definePlugin({
17 name: "VoiceChatDoubleClick",
18 description: "Join voice chats via double click instead of single click",
19 tags: ["Voice"],
20 authors: [Devs.Ven, Devs.D3SOX],
21 patches: [
22 // Stage Channels & Voice Channels
23 // the find is for stage channels, but it also handles voice
24 // channels because they're both in the same concatenated module
25 // the find for voice channels was `.handleVoiceStatusClick`
26 {
27 find: ".handleClickChat",
28 // hack: these are not React onClick, it is a custom prop handled by Discord
29 // thus, replacing this with onDoubleClick won't work, and you also cannot check
30 // e.detail since instead of the event they pass the channel.
31 // do this timer workaround instead
32 replacement: [
33 {
34 match: /onClick:\(\)=>\{this.handleClick\(\)/g,
35 replace: "onClick:()=>{$self.schedule(()=>{this.handleClick()},this)",
36 },
37 ]
38 },
39 {
40 // channel mentions
41 find: &#039;className:"channelMention",children:[null!=&#039;,
42 replacement: {
43 match: /onClick:(\i)(?=,.{0,30}className:"channelMention".+?(\i)\.inContent)/,
44 replace: (_, onClick, props) => ""
45 + `onClick:(vcDoubleClickEvt)=>$self.shouldRunOnClick(vcDoubleClickEvt,${props})&&${onClick}()`,
46 }
47 }
48 ],
49
50 shouldRunOnClick(e: MouseEvent, { channelId }) {
51 const channel = ChannelStore.getChannel(channelId);
52 if (!channel || ![2, 13].includes(channel.type)) return true;
53 return e.detail >= 2;
54 },
55
56 schedule(cb: () => void, e: any) {
57 const id = e.props.channel.id as string;
58 if (SelectedChannelStore.getVoiceChannelId() === id) {
59 cb();
60 return;
61 }
62 // use a different counter for each channel
63 const data = (timers[id] ??= { timeout: void 0, i: 0 });
64 // clear any existing timer
65 clearTimeout(data.timeout);
66
67 // if we already have 2 or more clicks, run the callback immediately
68 if (++data.i >= 2) {
69 cb();
70 delete timers[id];
71 } else {
72 // else reset the counter in 500ms
73 data.timeout = setTimeout(() => {
74 delete timers[id];
75 }, 500);
76 }
77 }
78});
79