Plugin

ImplicitRelationships

Shows your implicit relationships in the Friends tab.

Friends Servers
index.ts
Download

Source

src/plugins/implicitRelationships/index.ts
1import { definePluginSettings } from "@api/Settings";
2import { Devs } from "@utils/constants";
3import { Logger } from "@utils/Logger";
4import definePlugin, { OptionType } from "@utils/types";
5import { Constants, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserAffinitiesStore, UserStore } from "@webpack/common";
6
7const 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: true
14 },
15 }
16);
17
18export 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 header
27 {
28 find: "#{intl::FRIENDS_ALL_HEADER}",
29 replacement: {
30 match: /toString\(\)\}\);case (\i\.\i)\.PENDING/,
31 replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED'
32 },
33 },
34 // No friends page
35 {
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 header
43 {
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 content
51 {
52 find: '"FriendsStore"',
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 fetch
59 {
60 find: &#039;"FriendsStore&#039;,
61 replacement: {
62 match: /(\i\.\i)\.fetchRelationships\(\)/,
63 // This relationship fetch is actually completely useless, but whatevs
64 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 shitcode
78 {
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 === 5
103 ? (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 for
110 // 2. Do not have a relationship with
111 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 time
125 // Note: As we are using OP 8 here, implicit relationships who we do not share a guild
126 // with will not be fetched; so, if they're not otherwise cached, they will not be shown
127 // This should not be a big deal as these should be rare
128 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