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