Plugin
ImplicitRelationships
Shows your implicit relationships in the Friends tab.
1
/*2
* FiveCord — a Discord client mod3
* Copyright (c) 2025 FiveCord4
* SPDX-License-Identifier: GPL-3.0-or-later5
*/6
7
import { definePluginSettings } from "@api/Settings";8
import { Devs } from "@utils/constants";9
import { Logger } from "@utils/Logger";10
import definePlugin, { OptionType } from "@utils/types";11
import { Constants, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserAffinitiesStore, UserStore } from "@webpack/common";12
13
const settings = definePluginSettings(14
{15
sortByAffinity: {16
type: OptionType.BOOLEAN,17
default: true,18
description: "Whether to sort implicit relationships by their affinity to you.",19
restartNeeded: true20
},21
}22
);23
24
export default definePlugin({25
name: "ImplicitRelationships",26
description: "Shows your implicit relationships in the Friends tab.",27
tags: ["Friends", "Servers"],28
authors: [Devs.Dolfies],29
settings,30
31
patches: [32
// Counts header33
{34
find: "#{intl::FRIENDS_ALL_HEADER}",35
replacement: {36
match: /toString\(\)\}\);case (\i\.\i)\.PENDING/,37
replace: 039;toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED039;38
},39
},40
// No friends page41
{42
find: "FriendsEmptyState: Invalid empty state",43
replacement: {44
match: /case (\i\.\i)\.ONLINE:(?=return (\i)\.SECTION_ONLINE)/,45
replace: "case $1.ONLINE:case $1.IMPLICIT:"46
},47
},48
// Sections header49
{50
find: "#{intl::FRIENDS_SECTION_ONLINE}),className:",51
replacement: {52
match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.\i)(?=\},\{id:)/,53
replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}`54
}55
},56
// Sections content57
{58
find: 039;"FriendsStore"039;,59
replacement: {60
match: /(?<=case (\i\.\i)\.SUGGESTIONS:return \d+===(\i)\.type)/,61
replace: ";case $1.IMPLICIT:return $2.type===5"62
},63
},64
// Piggyback relationship fetch65
{66
find: 039;"FriendsStore039;,67
replacement: {68
match: /(\i\.\i)\.fetchRelationships\(\)/,69
// This relationship fetch is actually completely useless, but whatevs70
replace: "$1.fetchRelationships(),$self.fetchImplicitRelationships()"71
},72
},73
// Modify sort -- thanks megu for the patch (from sortFriendRequests)74
{75
find: "getRelationshipCounts(){",76
replacement: {77
predicate: () => settings.store.sortByAffinity,78
match: /\}\)\.sortBy\((.+?)\)\.value\(\)/,79
replace: "}).sortBy(row => $self.wrapSort(($1), row)).value()"80
}81
},82
83
// Add support for the nonce parameter to Discord's shitcode84
{85
find: ".REQUEST_GUILD_MEMBERS,",86
replacement: {87
match: /\.REQUEST_GUILD_MEMBERS,{/,88
replace: "$&nonce:arguments[1]?.nonce,"89
}90
},91
{92
find: "GUILD_MEMBERS_REQUEST:",93
replacement: {94
match: /presences:!!(\i)\.presences/,95
replace: "$&,nonce:$1.nonce"96
},97
},98
{99
find: ".not_found",100
replacement: {101
match: /notFound:(\i)\.not_found/,102
replace: "$&,nonce:$1.nonce"103
},104
}105
],106
107
wrapSort(comparator: Function, row: any) {108
return row.type === 5109
? (UserAffinitiesStore.getUserAffinity(row.user.id)?.communicationRank ?? 0)110
: comparator(row);111
},112
113
async fetchImplicitRelationships() {114
// Implicit relationships are defined as users that you:115
// 1. Have an affinity for116
// 2. Do not have a relationship with117
const userAffinities: Record<string, any>[] = UserAffinitiesStore.getUserAffinities();118
const relationships = RelationshipStore.getMutableRelationships();119
const nonFriendAffinities = userAffinities.filter(a => !RelationshipStore.getRelationshipType(a.otherUserId));120
nonFriendAffinities.forEach(a => {121
relationships.set(a.otherUserId, 5);122
});123
RelationshipStore.emitChange();124
125
const toRequest = nonFriendAffinities.filter(a => !UserStore.getUser(a.otherUserId));126
const allGuildIds = Object.keys(GuildStore.getGuilds());127
const sentNonce = SnowflakeUtils.fromTimestamp(Date.now());128
let count = allGuildIds.length * Math.ceil(toRequest.length / 100);129
130
// OP 8 Request Guild Members allows 100 user IDs at a time131
// Note: As we are using OP 8 here, implicit relationships who we do not share a guild132
// with will not be fetched; so, if they're not otherwise cached, they will not be shown133
// This should not be a big deal as these should be rare134
const callback = ({ chunks }) => {135
try {136
const chunkCount = chunks.filter(chunk => chunk.nonce === sentNonce).length;137
if (chunkCount === 0) return;138
139
count -= chunkCount;140
RelationshipStore.emitChange();141
if (count <= 0) {142
FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);143
}144
} catch (e) {145
new Logger("ImplicitRelationships").error("Error in GUILD_MEMBERS_CHUNK_BATCH handler", e);146
}147
};148
149
FluxDispatcher.subscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);150
for (let i = 0; i < toRequest.length; i += 100) {151
FluxDispatcher.dispatch({152
type: "GUILD_MEMBERS_REQUEST",153
guildIds: allGuildIds,154
userIds: toRequest.slice(i, i + 100),155
presences: true,156
nonce: sentNonce,157
});158
}159
},160
161
start() {162
Constants.FriendsSections.IMPLICIT = "IMPLICIT";163
}164
});165