Plugin
ExportContacts
Export a list of friends to your clipboard. Adds a new button to the menu bar for 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 "./styles.css";8
9
import ErrorBoundary from "@components/ErrorBoundary";10
import { Devs } from "@utils/constants";11
import definePlugin from "@utils/types";12
import { Clipboard, Toasts } from "@webpack/common";13
14
interface User {15
id: string;16
username: string;17
avatar: string;18
discriminator: string;19
publicFlags: number;20
avatarDecorationData?: any;21
globalName: string;22
}23
24
interface ContactsList {25
id: string;26
type: number;27
nickname?: any;28
user: User;29
since: string;30
}31
32
// for type parameter, it takes in a number that determines the type of the contact33
// 1 is friends added34
// 2 is blocked users35
// 3 is incoming friend requests36
// 4 is outgoing friend requests37
function getUsernames(contacts: ContactsList[], type: number): string[] {38
return contacts39
// only select contacts that are the specified type40
.filter(e => e.type === type)41
// return the username, and discriminator if necessary42
.map(e => e.user.discriminator === "0" ? e.user.username : e.user.username + "#" + e.user.discriminator);43
}44
45
export default definePlugin({46
name: "ExportContacts",47
description: "Export a list of friends to your clipboard. Adds a new button to the menu bar for the friends tab.",48
authors: [Devs.FiveCord],49
patches: [50
{51
find: "fetchRelationships(){",52
replacement: {53
match: /\.then\(e=>o\.default\.dispatch\({type:"LOAD_RELATIONSHIPS_SUCCESS",relationships:e\.body}\)/,54
replace: ".then(e=>{o.default.dispatch({type:\"LOAD_RELATIONSHIPS_SUCCESS\",relationships:e.body}); $self.getContacts(e.body)}"55
}56
},57
{58
find: "[role=\"tab\"][aria-disabled=\"false\"]",59
replacement: {60
match: /this\.props;return\(/,61
replace: "this.props;console.log(a.Children);return("62
}63
},64
{65
find: "[role=\"tab\"][aria-disabled=\"false\"]",66
replacement: {67
match: /(\w+)\.Children\.map\((\w+),\s*this\.renderChildren\)/,68
replace: "[...$1.Children.map($2,this.renderChildren),$self.addExportButton()]"69
}70
}71
],72
73
getContacts(contacts: ContactsList[]) {74
this.contactList = {75
friendsAdded: [...getUsernames(contacts, 1)],76
blockedUsers: [...getUsernames(contacts, 2)],77
incomingFriendRequests: [...getUsernames(contacts, 3)],78
outgoingFriendRequests: [...getUsernames(contacts, 4)]79
};80
},81
82
addExportButton() {83
return <ErrorBoundary noop key=".2">84
<button className="export-contacts-button" onClick={() => { this.copyContactToClipboard(); console.log("clicked"); }}>Export</button>85
</ErrorBoundary>;86
},87
88
copyContactToClipboard() {89
if (this.contactList) {90
Clipboard.copy(JSON.stringify(this.contactList));91
Toasts.show({92
message: "Contacts copied to clipboard successfully.",93
type: Toasts.Type.SUCCESS,94
id: Toasts.genId(),95
options: {96
duration: 3000,97
position: Toasts.Position.BOTTOM98
}99
});100
return;101
}102
// reason why you need to click the all tab is because the data is extracted during103
// the request itself when you fetch all your friends. this is done to avoid sending a104
// manual request to discord, which may raise suspicion and might even get you terminated.105
Toasts.show({106
message: "Contact list is undefined. Click on the \"All\" tab before exporting.",107
type: Toasts.Type.FAILURE,108
id: Toasts.genId(),109
options: {110
duration: 3000,111
position: Toasts.Position.BOTTOM112
}113
});114
}115
});116