Plugin
MutualGroupDMs
Shows mutual group dms in profiles
1
import "./style.css";2
3
import { BaseText } from "@components/BaseText";4
import ErrorBoundary from "@components/ErrorBoundary";5
import { Devs } from "@utils/constants";6
import { isNonNullish } from "@utils/guards";7
import { Logger } from "@utils/Logger";8
import definePlugin from "@utils/types";9
import { Channel, User } from "@vencord/discord-types";10
import { findByPropsLazy, findCssClassesLazy } from "@webpack";11
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, Text, useMemo, UserStore } from "@webpack/common";12
import { ComponentType, JSX } from "react";13
14
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");15
const UserUtils = findByPropsLazy("getGlobalName");16
17
const ProfileListClasses = findCssClassesLazy("empty", "textContainer", "connectionIcon");18
const TabBarClasses = findCssClassesLazy("tabPanelScroller", "tabBarPanel");19
const MutualsListClasses = findCssClassesLazy("row", "icon", "name", "details");20
21
let ExpandableList: ComponentType<any> = () => null;22
23
function getGroupDMName(channel: Channel) {24
return channel.name ||25
channel.recipients26
.map(UserStore.getUser)27
.filter(isNonNullish)28
.map(c => RelationshipStore.getNickname(c.id) || UserUtils.getName(c))29
.join(", ");30
}31
32
const getMutualGroupDms = (userId: string) =>33
ChannelStore.getSortedPrivateChannels()34
.filter(c => c.isGroupDM() && c.recipients.includes(userId));35
36
const isBotOrSelf = (user: User) => user.bot || user.id === UserStore.getCurrentUser().id;37
38
function getMutualGDMCountText(user: User) {39
const count = getMutualGroupDms(user.id).length;40
return `${count === 0 ? "No" : count} Mutual Group${count !== 1 ? "s" : ""}`;41
}42
43
function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) {44
return mutualDms.map(c => (45
<Clickable46
key={c.id}47
className={MutualsListClasses.row}48
onClick={() => {49
onClose();50
SelectedChannelActionCreators.selectPrivateChannel(c.id);51
}}52
>53
<Avatar54
src={IconUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}55
size="SIZE_40"56
className={MutualsListClasses.icon}57
>58
</Avatar>59
<div className={MutualsListClasses.details}>60
<div className={MutualsListClasses.name}>{getGroupDMName(c)}</div>61
<Text variant="text-xs/medium">{c.recipients.length + 1} Members</Text>62
</div>63
</Clickable>64
));65
}66
67
const IS_PATCHED = Symbol("MutualGroupDMs.Patched");68
69
export default definePlugin({70
name: "MutualGroupDMs",71
description: "Shows mutual group dms in profiles",72
tags: ["Friends", "Appearance"],73
authors: [Devs.amia],74
75
patches: [76
// User Profile Modal77
{78
find: ".BOT_DATA_ACCESS?(",79
replacement: [80
{81
match: /\i\.useEffect.{0,100}(\i)\[0\]\.section/,82
replace: "$self.pushSection($1,arguments[0].user);$&"83
},84
{85
match: /\(0,\i\.jsx\)\(\i,\{items:\i,section:(\i)/,86
replace: "$1===039;MUTUAL_GDMS039;?$self.renderMutualGDMs(arguments[0]):$&"87
},88
// Discord adds spacing between each item which pushes our tab off screen.89
// set the gap to zero to ensure ours stays on screen90
{91
match: /className:\i\.\i(?=,type:"top")/,92
replace: 039;$& + " vc-mutual-gdms-modal-tab-bar"039;93
}94
]95
},96
// User Profile Modal v297
{98
find: ".WIDGETS?",99
replacement: [100
{101
match: /items:(\i),.+?(?=return\(0,\i\.jsxs?\)\("div)/,102
replace: "$&$self.pushSection($1,arguments[0].user);"103
},104
{105
match: /children:(?=.{0,100}?component:.+?section:(\i))/,106
replace: "$&$1===039;MUTUAL_GDMS039;?$self.renderMutualGDMs(arguments[0]):"107
},108
// Make the gap between each item smaller so our tab can fit.109
{110
match: /type:"top",/,111
replace: 039;$&className:"vc-mutual-gdms-modal-v2-tab-bar",039;112
},113
]114
},115
{116
find: 039;section:"MUTUAL_FRIENDS"039;,117
replacement: [118
{119
match: /\i\|\|\i(?=\?\(0,\i\.jsxs?\)\(\i\.\i\.Overlay,)/,120
replace: "$&||$self.getMutualGroupDms(arguments[0].user.id).length>0"121
},122
{123
match: /\.openUserProfileModal.+?\)}\)}\)(?<=,(\i)&&(\i)&&(\(0,\i\.jsxs?\)\(\i\.\i,{className:(\i)\.\i}\)).{0,50}?"MUTUAL_FRIENDS".+?)/,124
replace: (m, hasMutualGuilds, hasMutualFriends, Divider, classes) => "" +125
`${m},$self.renderDMPageList({user:arguments[0].user,hasDivider:${hasMutualGuilds}||${hasMutualFriends},Divider:${Divider},listStyle:${classes}.list})`126
},127
{128
match: /(?=function (\i)\(\i\){let{section:\i,header:\i[^}]+?onExpand:)/,129
replace: "$self.ExpandableList=$1;"130
}131
]132
}133
],134
135
set ExpandableList(value: any) {136
ExpandableList = value;137
},138
139
getMutualGroupDms(userId: string) {140
try {141
return getMutualGroupDms(userId);142
} catch (e) {143
new Logger("MutualGroupDMs").error("Failed to get mutual group dms:", e);144
}145
146
return [];147
},148
149
pushSection(sections: any[], user: User) {150
try {151
if (isBotOrSelf(user) || sections[IS_PATCHED]) return;152
153
sections[IS_PATCHED] = true;154
sections.push({155
text: getMutualGDMCountText(user),156
section: "MUTUAL_GDMS",157
});158
} catch (e) {159
new Logger("MutualGroupDMs").error("Failed to push mutual group dms section:", e);160
}161
},162
163
renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => {164
const mutualGDms = useMemo(() => getMutualGroupDms(user.id), [user.id]);165
const entries = renderClickableGDMs(mutualGDms, onClose);166
167
return (168
<ScrollerThin169
className={TabBarClasses.tabPanelScroller}170
fade={true}171
onClose={onClose}172
>173
{entries.length > 0174
? entries175
: (176
<div className={ProfileListClasses.empty}>177
<div className={ProfileListClasses.textContainer}>178
<BaseText tag="h3" size="md" weight="medium" style={{ color: "var(--text-strong)" }}>You don039;t have any group chats in common</BaseText>179
</div>180
</div>181
)182
}183
</ScrollerThin>184
);185
}),186
187
renderDMPageList: ErrorBoundary.wrap(({ user, hasDivider, Divider, listStyle }: { user: User, hasDivider: boolean, Divider: JSX.Element, listStyle: string; }) => {188
const mutualGDms = getMutualGroupDms(user.id);189
if (mutualGDms.length === 0) return null;190
191
return (192
<>193
{hasDivider && Divider}194
<ExpandableList195
listClassName={listStyle}196
header={"Mutual Groups"}197
isLoading={false}198
items={renderClickableGDMs(mutualGDms, () => { })}199
/>200
</>201
);202
}, { noop: true })203
});204